diff --git a/data/assets/examples/volume/generated/.gitignore b/data/assets/examples/volume/generated/.gitignore new file mode 100644 index 0000000000..5eabe86a70 --- /dev/null +++ b/data/assets/examples/volume/generated/.gitignore @@ -0,0 +1,3 @@ +cartesian/ +cartesiansequence/ +spherical/ \ No newline at end of file diff --git a/data/assets/examples/volume/generated/cartesian.asset b/data/assets/examples/volume/generated/cartesian.asset new file mode 100644 index 0000000000..6561a2a76a --- /dev/null +++ b/data/assets/examples/volume/generated/cartesian.asset @@ -0,0 +1,38 @@ +-- This asset requires OpenSpace to be built with the OPENSPACE_MODULE_VOLUME enabled + +-- Before using this example, +-- the volume data itself needs to be generated, +-- using the task 'data/tasks/volume/generate_cartesian.task' + +local assetHelper = asset.require('util/asset_helper') +local transforms = asset.require("scene/solarsystem/sun/transforms") + +local sunRadius = 695508000 + +local volume = { + Identifier = "GeneratedVolume", + Parent = transforms.SolarSystemBarycenter.Identifier, + Renderable = { + Type = "RenderableTimeVaryingVolume", + SourceDirectory = asset.localResource("cartesian"), + TransferFunction = asset.localResource("../transferfunction.txt"), + StepSize = 0.01, + MinValue = 0, + MaxValue = 1, + GridType = "Cartesian", + SecondsBefore = 50*365*24*60*60, -- 50 years before + SecondsAfter = 50*365*24*60*60 -- 50 years after + }, + GUI = { + Path = "/Examples" + }, + Transform = { + Scale = { + Type = "StaticScale", + Scale = 1000 * sunRadius + } + } +} + +local objects = { volume } +assetHelper.registerSceneGraphNodes(asset, objects) \ No newline at end of file diff --git a/data/assets/examples/volume/generated/cartesiansequence.asset b/data/assets/examples/volume/generated/cartesiansequence.asset new file mode 100644 index 0000000000..4ec617eb64 --- /dev/null +++ b/data/assets/examples/volume/generated/cartesiansequence.asset @@ -0,0 +1,38 @@ +-- This asset requires OpenSpace to be built with the OPENSPACE_MODULE_VOLUME enabled + +-- Before using this example, +-- the volume data itself needs to be generated, +-- using the task 'data/tasks/volume/generate_cartesian_sequence.task' + +local assetHelper = asset.require('util/asset_helper') +local transforms = asset.require("scene/solarsystem/sun/transforms") + +local sunRadius = 695508000 + +local volume = { + Identifier = "GeneratedVolume", + Parent = transforms.SolarSystemBarycenter.Identifier, + Renderable = { + Type = "RenderableTimeVaryingVolume", + SourceDirectory = asset.localResource("cartesiansequence"), + TransferFunction = asset.localResource("../transferfunction.txt"), + StepSize = 0.01, + MinValue = 0, + MaxValue = 1, + GridType = "Cartesian", + SecondsBefore = 50*365*24*60*60, -- 50 years before + SecondsAfter = 50*365*24*60*60 -- 50 years after + }, + GUI = { + Path = "/Examples" + }, + Transform = { + Scale = { + Type = "StaticScale", + Scale = 1000 * sunRadius + } + } +} + +local objects = { volume } +assetHelper.registerSceneGraphNodes(asset, objects) \ No newline at end of file diff --git a/data/assets/examples/volume/generated/spherical.asset b/data/assets/examples/volume/generated/spherical.asset new file mode 100644 index 0000000000..20c7564393 --- /dev/null +++ b/data/assets/examples/volume/generated/spherical.asset @@ -0,0 +1,38 @@ +-- This asset requires OpenSpace to be built with the OPENSPACE_MODULE_VOLUME enabled + +-- Before using this example, +-- the volume data itself needs to be generated, +-- using the task 'data/tasks/volume/generate_spherical.task' + +local assetHelper = asset.require('util/asset_helper') +local transforms = asset.require("scene/solarsystem/sun/transforms") + +local astronomicalUnit = 149597870700 + +local volume = { + Identifier = "GeneratedVolume", + Parent = transforms.SolarSystemBarycenter.Identifier, + Renderable = { + Type = "RenderableTimeVaryingVolume", + SourceDirectory = asset.localResource("spherical"), + TransferFunction = asset.localResource("../transferfunction.txt"), + StepSize = 0.01, + MinValue = 0, + MaxValue = 1, + GridType = "Spherical", + SecondsBefore = 50*365*24*60*60, -- 50 years before + SecondsAfter = 50*365*24*60*60 -- 50 years after + }, + GUI = { + Path = "/Examples" + }, + Transform = { + Scale = { + Type = "StaticScale", + Scale = astronomicalUnit + } + } +} + +local objects = { volume } +assetHelper.registerSceneGraphNodes(asset, objects) \ No newline at end of file diff --git a/data/assets/examples/toyvolume.asset b/data/assets/examples/volume/toyvolume.asset similarity index 100% rename from data/assets/examples/toyvolume.asset rename to data/assets/examples/volume/toyvolume.asset diff --git a/data/assets/examples/volume/transferfunction.txt b/data/assets/examples/volume/transferfunction.txt new file mode 100644 index 0000000000..a93353df7a --- /dev/null +++ b/data/assets/examples/volume/transferfunction.txt @@ -0,0 +1,5 @@ +width 1024 +lower 0.0 +upper 1.0 +mappingkey 0.0 250 250 250 0 +mappingkey 1.0 200 200 200 255 \ No newline at end of file diff --git a/data/tasks/volume/generate_cartesian.task b/data/tasks/volume/generate_cartesian.task new file mode 100644 index 0000000000..65c338404d --- /dev/null +++ b/data/tasks/volume/generate_cartesian.task @@ -0,0 +1,18 @@ +local fn = + "return function (x, y, z) " .. + " if math.sqrt(x^2 + y^2 + z^2) < 0.4 then " .. + " return 0.8 " .. + " end " .. + " return 0.0 " .. + "end" + +return {{ + Type = "GenerateRawVolumeTask", + Dimensions = {32, 32, 32}, + LowerDomainBound = {-0.5, -0.5, -0.5}, + UpperDomainBound = {0.5, 0.5, 0.5}, + ValueFunction = fn, + Time = "2018-05-04T00:00:00", + RawVolumeOutput = "${DATA}/assets/examples/volume/generated/cartesian/cartesian.rawvolume", + DictionaryOutput = "${DATA}/assets/examples/volume/generated/cartesian/cartesian.dictionary" +}} \ No newline at end of file diff --git a/data/tasks/volume/generate_cartesian_sequence.task b/data/tasks/volume/generate_cartesian_sequence.task new file mode 100644 index 0000000000..08aa2edd53 --- /dev/null +++ b/data/tasks/volume/generate_cartesian_sequence.task @@ -0,0 +1,27 @@ +local length = 60 +local tasks = {} + +for i=1,length do + local radius = 0.5 * (1 - i/length) + local step = string.format("%02d", i-1) + local fn = + "return function (x, y, z) " .. "\n" .. + " local v = math.sqrt(x^2 + y^2 + z^2) - " .. radius .. "\n" .. + " v = (-v - 0.1) / 0.2 " .. "\n" .. + " v = math.min(math.max(v, 0), 1) " .. "\n" .. + " v = 3*v^2 - 2*v^3 " .. "\n" .. + " return v" .. "\n" .. + "end" + tasks[#tasks+1] = { + Type = "GenerateRawVolumeTask", + Dimensions = {32, 32, 32}, + LowerDomainBound = {-0.5, -0.5, -0.5}, + UpperDomainBound = {0.5, 0.5, 0.5}, + ValueFunction = fn, + Time = "2018-05-04T00:00:" .. step, + RawVolumeOutput = "${DATA}/assets/examples/volume/generated/cartesiansequence/" .. step .. ".rawvolume", + DictionaryOutput = "${DATA}/assets/examples/volume/generated/cartesiansequence/" .. step .. ".dictionary" + } +end + +return tasks \ No newline at end of file diff --git a/data/tasks/volume/generate_spherical.task b/data/tasks/volume/generate_spherical.task new file mode 100644 index 0000000000..72c17dd3a2 --- /dev/null +++ b/data/tasks/volume/generate_spherical.task @@ -0,0 +1,15 @@ +local fn = + "return function (r, phi, theta) " .. + " return 1 - math.floor(r*10)/10 " .. + "end" + +return {{ + Type = "GenerateRawVolumeTask", + Dimensions = {32, 32, 32}, + LowerDomainBound = {0, 0, 0}, + UpperDomainBound = {1, math.pi, 2 * math.pi}, + ValueFunction = fn, + Time = "2018-05-04T00:00:00", + RawVolumeOutput = "${DATA}/assets/examples/volume/generated/spherical/spherical.rawvolume", + DictionaryOutput = "${DATA}/assets/examples/volume/generated/spherical/spherical.dictionary" +}} \ No newline at end of file diff --git a/modules/kameleonvolume/rendering/renderablekameleonvolume.cpp b/modules/kameleonvolume/rendering/renderablekameleonvolume.cpp index 90367eaa51..a20eb6572b 100644 --- a/modules/kameleonvolume/rendering/renderablekameleonvolume.cpp +++ b/modules/kameleonvolume/rendering/renderablekameleonvolume.cpp @@ -153,7 +153,7 @@ RenderableKameleonVolume::RenderableKameleonVolume(const ghoul::Dictionary& dict , _transferFunctionPath(TransferFunctionInfo) , _cache(CacheInfo) , _raycaster(nullptr) - , _transferFunctionHandler(nullptr) + , _transferFunction(nullptr) { glm::vec3 dimensions; @@ -172,8 +172,8 @@ RenderableKameleonVolume::RenderableKameleonVolume(const ghoul::Dictionary& dict std::string transferFunctionPath; if (dictionary.getValue(KeyTransferFunction, transferFunctionPath)) { _transferFunctionPath = transferFunctionPath; - _transferFunctionHandler = std::make_shared( - _transferFunctionPath + _transferFunction = std::make_shared( + _transferFunctionPath, [](const openspace::TransferFunction&) {} ); } @@ -263,11 +263,10 @@ void RenderableKameleonVolume::initializeGL() { load(); _volumeTexture->uploadTexture(); - _transferFunctionHandler->initialize(); _raycaster = std::make_unique( _volumeTexture, - _transferFunctionHandler, + _transferFunction, _clipPlanes ); @@ -318,7 +317,6 @@ void RenderableKameleonVolume::initializeGL() { addProperty(_gridType); addProperty(_cache); addPropertySubOwner(_clipPlanes.get()); - addPropertySubOwner(_transferFunctionHandler.get()); } void RenderableKameleonVolume::updateRaycasterModelTransform() { diff --git a/modules/kameleonvolume/rendering/renderablekameleonvolume.h b/modules/kameleonvolume/rendering/renderablekameleonvolume.h index e95a83b6fb..afecadf5f4 100644 --- a/modules/kameleonvolume/rendering/renderablekameleonvolume.h +++ b/modules/kameleonvolume/rendering/renderablekameleonvolume.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -93,7 +93,7 @@ private: std::unique_ptr _raycaster; std::shared_ptr _volumeTexture; - std::shared_ptr _transferFunctionHandler; + std::shared_ptr _transferFunction; }; } // namespace kameleonvolume diff --git a/modules/toyvolume/rendering/toyvolumeraycaster.cpp b/modules/toyvolume/rendering/toyvolumeraycaster.cpp index 691b295ab4..f3c6182d88 100644 --- a/modules/toyvolume/rendering/toyvolumeraycaster.cpp +++ b/modules/toyvolume/rendering/toyvolumeraycaster.cpp @@ -24,18 +24,21 @@ #include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include + +#include + namespace { - const char* GlslRaycastPath = "${MODULES}/toyvolume/shaders/raycast.glsl"; - const char* GlslBoundsVsPath = "${MODULES}/toyvolume/shaders/boundsvs.glsl"; - const char* GlslBoundsFsPath = "${MODULES}/toyvolume/shaders/boundsfs.glsl"; + const char* GlslRaycastPath = "${MODULE_TOYVOLUME}/shaders/raycast.glsl"; + const char* GlslBoundsVsPath = "${MODULE_TOYVOLUME}/shaders/boundsvs.glsl"; + const char* GlslBoundsFsPath = "${MODULE_TOYVOLUME}/shaders/boundsfs.glsl"; } // namespace namespace openspace { @@ -125,15 +128,15 @@ bool ToyVolumeRaycaster::cameraIsInside(const RenderData& data, } std::string ToyVolumeRaycaster::getBoundsVsPath() const { - return GlslBoundsVsPath; + return absPath(GlslBoundsVsPath); } std::string ToyVolumeRaycaster::getBoundsFsPath() const { - return GlslBoundsFsPath; + return absPath(GlslBoundsFsPath); } std::string ToyVolumeRaycaster::getRaycastPath() const { - return GlslRaycastPath; + return absPath(GlslRaycastPath); } std::string ToyVolumeRaycaster::getHelperPath() const { diff --git a/modules/volume/CMakeLists.txt b/modules/volume/CMakeLists.txt index b124a2c40d..d036f0af87 100644 --- a/modules/volume/CMakeLists.txt +++ b/modules/volume/CMakeLists.txt @@ -27,11 +27,9 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake) set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/envelope.h ${CMAKE_CURRENT_SOURCE_DIR}/rawvolume.h - ${CMAKE_CURRENT_SOURCE_DIR}/rawvolume.inl + ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumemetadata.h ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumereader.h - ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumereader.inl ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumewriter.h - ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumewriter.inl ${CMAKE_CURRENT_SOURCE_DIR}/textureslicevolumereader.h ${CMAKE_CURRENT_SOURCE_DIR}/textureslicevolumereader.inl ${CMAKE_CURRENT_SOURCE_DIR}/transferfunction.h @@ -47,12 +45,14 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rendering/basicvolumeraycaster.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/volumeclipplane.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/volumeclipplanes.h + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/generaterawvolumetask.h ) source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/envelope.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rawvolume.inl + ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumemetadata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumereader.inl ${CMAKE_CURRENT_SOURCE_DIR}/rawvolumewriter.inl ${CMAKE_CURRENT_SOURCE_DIR}/textureslicevolumereader.inl @@ -66,7 +66,9 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rendering/basicvolumeraycaster.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/volumeclipplane.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/volumeclipplanes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/generaterawvolumetask.cpp ) + source_group("Source Files" FILES ${SOURCE_FILES}) create_new_module( diff --git a/modules/volume/rawvolumemetadata.cpp b/modules/volume/rawvolumemetadata.cpp new file mode 100644 index 0000000000..b77e36cbe1 --- /dev/null +++ b/modules/volume/rawvolumemetadata.cpp @@ -0,0 +1,182 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include + +#include + +namespace { + const constexpr char* KeyDimensions = "Dimensions"; + const constexpr char* KeyLowerDomainBound = "LowerDomainBound"; + const constexpr char* KeyUpperDomainBound = "UpperDomainBound"; + + const constexpr char* KeyMinValue = "MinValue"; + const constexpr char* KeyMaxValue = "MaxValue"; + + const constexpr char* KeyTime = "Time"; + const constexpr char* KeyDomainUnit = "DomainUnit"; + const constexpr char* KeyValueUnit = "ValueUnit"; + + const constexpr char* KeyGridType = "GridType"; +} + +namespace openspace::volume { + +RawVolumeMetadata RawVolumeMetadata::CreateFromDictionary(const ghoul::Dictionary& dictionary) { + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "RawVolumeMetadata" + ); + + RawVolumeMetadata metadata; + metadata.dimensions = dictionary.value(KeyDimensions); + + metadata.hasDomainBounds = dictionary.hasValue(KeyLowerDomainBound) && + dictionary.hasValue(KeyUpperDomainBound); + + if (metadata.hasDomainBounds) { + metadata.lowerDomainBound = dictionary.value(KeyLowerDomainBound); + metadata.upperDomainBound = dictionary.value(KeyUpperDomainBound); + } + metadata.hasDomainUnit = dictionary.hasValue(KeyDomainUnit); + if (metadata.hasDomainUnit) { + metadata.domainUnit = dictionary.value(KeyDomainUnit); + } + + metadata.hasValueRange = dictionary.hasValue(KeyMinValue) && + dictionary.hasValue(KeyMaxValue); + + if (metadata.hasValueRange) { + metadata.minValue = dictionary.value(KeyMinValue); + metadata.maxValue = dictionary.value(KeyMaxValue); + } + metadata.hasValueUnit = dictionary.hasValue(KeyValueUnit); + if (metadata.hasValueUnit) { + metadata.valueUnit = dictionary.value(KeyValueUnit); + } + + metadata.hasTime = dictionary.hasValue(KeyTime); + if (metadata.hasTime) { + std::string timeString = dictionary.value(KeyTime); + metadata.time = Time::convertTime(timeString); + } + + return metadata; +} + +ghoul::Dictionary RawVolumeMetadata::dictionary() { + ghoul::Dictionary dict; + dict.setValue(KeyDimensions, dimensions); + dict.setValue(KeyGridType, gridTypeToString(gridType)); + + if (hasDomainUnit) { + dict.setValue(KeyDomainUnit, domainUnit); + } + if (hasDomainBounds) { + dict.setValue(KeyLowerDomainBound, lowerDomainBound); + dict.setValue(KeyUpperDomainBound, upperDomainBound); + } + + if (hasValueRange) { + dict.setValue(KeyMinValue, static_cast(minValue)); + dict.setValue(KeyMaxValue, static_cast(maxValue)); + } + if (hasDomainUnit) { + dict.setValue(KeyValueUnit, valueUnit); + } + + if (hasTime) { + std::string timeString = Time(time).ISO8601(); + // Do not include time offset in time string + if (timeString.back() == 'Z') { + timeString.pop_back(); + } + dict.setValue(KeyTime, timeString); + } + return dict; +} + +documentation::Documentation RawVolumeMetadata::Documentation() { + using namespace documentation; + return { + "RawVolumeMetadata", + "volume_rawvolumemetadata", + { + { + KeyDimensions, + new Vector3Verifier, + Optional::No, + "Specifies the number of grid cells in each dimension", + }, + { + KeyDomainUnit, + new StringVerifier, + Optional::Yes, + "Specifies the unit used to specity the domain", + }, + { + KeyLowerDomainBound, + new Vector3Verifier, + Optional::Yes, + "Specifies the lower domain bounds in the model coordinate system", + }, + { + KeyUpperDomainBound, + new Vector3Verifier, + Optional::Yes, + "Specifies the upper domain bounds in the model coordinate system", + }, + { + KeyTime, + new StringVerifier, + Optional::Yes, + "Specifies the time on the format YYYY-MM-DDTHH:MM:SS.000Z", + }, + { + KeyValueUnit, + new StringVerifier, + Optional::Yes, + "Specifies the unit used to specity the value", + }, + { + KeyMinValue, + new DoubleVerifier, + Optional::Yes, + "Specifies the minimum value stored in the volume" + }, + { + KeyMaxValue, + new DoubleVerifier, + Optional::Yes, + "Specifies the maximum value stored in the volume" + } + } + }; +} + +} // namespace openspace::volume diff --git a/modules/volume/rawvolumemetadata.h b/modules/volume/rawvolumemetadata.h new file mode 100644 index 0000000000..25ccc3d9d9 --- /dev/null +++ b/modules/volume/rawvolumemetadata.h @@ -0,0 +1,62 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_VOLUME___RAWVOLUMEMETADATA___H__ +#define __OPENSPACE_MODULE_VOLUME___RAWVOLUMEMETADATA___H__ + +#include +#include + +#include + +namespace openspace::volume { + +struct RawVolumeMetadata { + static RawVolumeMetadata CreateFromDictionary(const ghoul::Dictionary& dictionary); + static documentation::Documentation Documentation(); + + ghoul::Dictionary dictionary(); + + glm::uvec3 dimensions; + VolumeGridType gridType; + + bool hasTime; + double time; + + bool hasValueRange; + float minValue; + float maxValue; + bool hasValueUnit; + std::string valueUnit; + + bool hasDomainBounds; + glm::vec3 lowerDomainBound; + glm::vec3 upperDomainBound; + bool hasDomainUnit; + std::string domainUnit; +}; + +} // namespace openspace::volume + +#endif // __OPENSPACE_MODULE_VOLUME___RAWVOLUMEMETADATA___H__ diff --git a/modules/volume/rawvolumereader.inl b/modules/volume/rawvolumereader.inl index e11d44818b..48623ab1ae 100644 --- a/modules/volume/rawvolumereader.inl +++ b/modules/volume/rawvolumereader.inl @@ -86,6 +86,11 @@ std::unique_ptr> RawVolumeReader::read() { std::make_unique>(dims); std::ifstream file(_path, std::ios::binary); + + if (file.fail()) { + throw ghoul::FileNotFoundError("Volume file not found"); + } + char *buffer = reinterpret_cast(volume->data()); size_t length = static_cast(dims.x) * static_cast(dims.y) * @@ -93,6 +98,11 @@ std::unique_ptr> RawVolumeReader::read() { sizeof(VoxelType); file.read(buffer, length); + + if (file.fail()) { + throw ghoul::RuntimeError("Error reading volume file"); + } + return volume; } diff --git a/modules/volume/rawvolumewriter.inl b/modules/volume/rawvolumewriter.inl index 4080d9ba8b..a1b6f78b63 100644 --- a/modules/volume/rawvolumewriter.inl +++ b/modules/volume/rawvolumewriter.inl @@ -97,6 +97,11 @@ void RawVolumeWriter::write(const RawVolume& volume) { size_t length = volume.nCells() * sizeof(VoxelType); std::ofstream file(_path, std::ios::binary); + + if (!file.good()) { + throw ghoul::RuntimeError("Could not create file '" + _path + "'"); + } + file.write(buffer, length); file.close(); } diff --git a/modules/volume/rendering/basicvolumeraycaster.cpp b/modules/volume/rendering/basicvolumeraycaster.cpp index fa1598dd5c..82a5e0f29a 100644 --- a/modules/volume/rendering/basicvolumeraycaster.cpp +++ b/modules/volume/rendering/basicvolumeraycaster.cpp @@ -45,10 +45,10 @@ namespace openspace::volume { BasicVolumeRaycaster::BasicVolumeRaycaster( std::shared_ptr volumeTexture, - std::shared_ptr transferFunctionHandler, + std::shared_ptr transferFunction, std::shared_ptr clipPlanes) : _volumeTexture(volumeTexture) - , _transferFunctionHandler(transferFunctionHandler) + , _transferFunction(transferFunction) , _clipPlanes(clipPlanes) , _boundingBox(glm::vec3(1.0)) , _opacity(20.0) @@ -112,7 +112,7 @@ void BasicVolumeRaycaster::preRaycast( const RaycastData& data, ghoul::opengl::ProgramObject& program) { - if (!_volumeTexture || !_transferFunctionHandler) { + if (!_volumeTexture || !_transferFunction) { return; } @@ -121,9 +121,10 @@ void BasicVolumeRaycaster::preRaycast( std::string id = std::to_string(data.id); + _transferFunction->update(); _tfUnit = std::make_unique(); _tfUnit->activate(); - _transferFunctionHandler->getTexture().bind(); + _transferFunction->getTexture().bind(); program.setUniform("transferFunction_" + id, _tfUnit->unitNumber()); _textureUnit = std::make_unique(); @@ -182,10 +183,10 @@ std::string BasicVolumeRaycaster::getHelperPath() const { } -void BasicVolumeRaycaster::setTransferFunctionHandler( - std::shared_ptr transferFunctionHandler) +void BasicVolumeRaycaster::setTransferFunction( + std::shared_ptr transferFunction) { - _transferFunctionHandler = transferFunctionHandler; + _transferFunction = transferFunction; } void BasicVolumeRaycaster::setVolumeTexture( diff --git a/modules/volume/rendering/basicvolumeraycaster.h b/modules/volume/rendering/basicvolumeraycaster.h index 9c691ed68d..e2f71178d0 100644 --- a/modules/volume/rendering/basicvolumeraycaster.h +++ b/modules/volume/rendering/basicvolumeraycaster.h @@ -33,8 +33,8 @@ #include #include +#include #include -#include #include #include @@ -56,7 +56,7 @@ class BasicVolumeRaycaster : public VolumeRaycaster { public: BasicVolumeRaycaster( std::shared_ptr texture, - std::shared_ptr transferFunctionHandler, + std::shared_ptr transferFunction, std::shared_ptr clipPlanes); virtual ~BasicVolumeRaycaster(); void initialize(); @@ -80,8 +80,8 @@ public: void setVolumeTexture(std::shared_ptr texture); std::shared_ptr volumeTexture() const; - void setTransferFunctionHandler( - std::shared_ptr transferFunctionHandler); + void setTransferFunction( + std::shared_ptr transferFunction); void setStepSize(float stepSize); float opacity() const; @@ -99,7 +99,7 @@ private: std::shared_ptr _clipPlanes; std::shared_ptr _volumeTexture; - std::shared_ptr _transferFunctionHandler; + std::shared_ptr _transferFunction; BoxGeometry _boundingBox; VolumeGridType _gridType; glm::mat4 _modelTransform; diff --git a/modules/volume/rendering/renderabletimevaryingvolume.cpp b/modules/volume/rendering/renderabletimevaryingvolume.cpp index 09f331c3d3..06c245dd68 100644 --- a/modules/volume/rendering/renderabletimevaryingvolume.cpp +++ b/modules/volume/rendering/renderabletimevaryingvolume.cpp @@ -47,20 +47,15 @@ namespace { } // namespace namespace { - const char* KeyDimensions = "Dimensions"; const char* KeyStepSize = "StepSize"; + const char* KeyGridType = "GridType"; const char* KeyTransferFunction = "TransferFunction"; const char* KeySourceDirectory = "SourceDirectory"; - const char* KeyLowerDomainBound = "LowerDomainBound"; - const char* KeyUpperDomainBound = "UpperDomainBound"; + const char* KeyClipPlanes = "ClipPlanes"; const char* KeySecondsBefore = "SecondsBefore"; const char* KeySecondsAfter = "SecondsAfter"; - const char* KeyGridType = "GridType"; - const char* KeyMinValue = "MinValue"; - const char* KeyMaxValue = "MaxValue"; - const char* KeyTime = "Time"; - const char* KeyUnit = "VisUnit"; + const float SecondsInOneDay = 60 * 60 * 24; static const openspace::properties::Property::PropertyInfo StepSizeInfo = { @@ -134,18 +129,6 @@ namespace { "Radius upper bound", "" // @TODO Missing documentation }; - - static const openspace::properties::Property::PropertyInfo lowerValueBoundInfo = { - "lowerValueBound", - "Lower value bound", - "" // @TODO Missing documentation - }; - - static const openspace::properties::Property::PropertyInfo upperValueBoundInfo = { - "upperValueBound", - "Upper value bound", - "" // @TODO Missing documentation - }; } // namespace namespace openspace { @@ -168,8 +151,6 @@ RenderableTimeVaryingVolume::RenderableTimeVaryingVolume( , _jumpToTimestep(JumpToTimestepInfo, 0, 0, 256) , _currentTimestep(CurrentTimeStepInfo, 0, 0, 256) , _raycaster(nullptr) - , _transferFunctionHandler(nullptr) - { documentation::testSpecificationAndThrow( Documentation(), @@ -179,8 +160,10 @@ RenderableTimeVaryingVolume::RenderableTimeVaryingVolume( _sourceDirectory = absPath(dictionary.value(KeySourceDirectory)); _transferFunctionPath = absPath(dictionary.value(KeyTransferFunction)); - _transferFunctionHandler = std::make_shared(_transferFunctionPath); - + _transferFunction = std::make_shared( + _transferFunctionPath, + [](const openspace::TransferFunction&) {} + ); _gridType.addOptions({ { static_cast(volume::VolumeGridType::Cartesian), "Cartesian grid" }, @@ -233,9 +216,6 @@ void RenderableTimeVaryingVolume::initializeGL() { if (extension == "dictionary") { loadTimestepMetadata(path); } - if (extension == "tf") { - _transferFunctionHandler->setFilepath(path); - } } @@ -245,11 +225,11 @@ void RenderableTimeVaryingVolume::initializeGL() { std::string path = FileSys.pathByAppendingComponent( _sourceDirectory, t.baseName ) + ".rawvolume"; - RawVolumeReader reader(path, t.dimensions); + RawVolumeReader reader(path, t.metadata.dimensions); t.rawVolume = reader.read(); - float min = t.minValue; - float diff = t.maxValue - t.minValue; + float min = t.metadata.minValue; + float diff = t.metadata.maxValue - t.metadata.minValue; float *data = t.rawVolume->data(); for (size_t i = 0; i < t.rawVolume->nCells(); ++i) { data[i] = glm::clamp((data[i] - min) / diff, 0.0f, 1.0f); @@ -263,7 +243,7 @@ void RenderableTimeVaryingVolume::initializeGL() { // TODO: handle normalization properly for different timesteps + transfer function t.texture = std::make_shared( - t.dimensions, + t.metadata.dimensions, ghoul::opengl::Texture::Format::Red, GL_RED, GL_FLOAT, @@ -278,10 +258,10 @@ void RenderableTimeVaryingVolume::initializeGL() { t.texture->uploadTexture(); } - _transferFunctionHandler->initialize(); + //_transferFunction->initialize(); _clipPlanes->initialize(); - _raycaster = std::make_unique(nullptr, _transferFunctionHandler, _clipPlanes); + _raycaster = std::make_unique(nullptr, _transferFunction, _clipPlanes); _raycaster->initialize(); OsEng.renderEngine().raycasterManager().attachRaycaster(*_raycaster.get()); @@ -312,7 +292,6 @@ void RenderableTimeVaryingVolume::initializeGL() { addProperty(_transferFunctionPath); addProperty(_sourceDirectory); addPropertySubOwner(_clipPlanes.get()); - addPropertySubOwner(_transferFunctionHandler.get()); addProperty(_triggerTimeJump); addProperty(_jumpToTimestep); @@ -320,6 +299,7 @@ void RenderableTimeVaryingVolume::initializeGL() { addProperty(_opacity); addProperty(_rNormalization); addProperty(_rUpperBound); + addProperty(_gridType); _raycaster->setGridType( (_gridType.value() == 1) ? @@ -335,35 +315,30 @@ void RenderableTimeVaryingVolume::initializeGL() { }); _transferFunctionPath.onChange([this] { - _transferFunctionHandler = - std::make_shared(_transferFunctionPath); - _raycaster->setTransferFunctionHandler(_transferFunctionHandler); + _transferFunction = + std::make_shared(_transferFunctionPath); + _raycaster->setTransferFunction(_transferFunction); }); } void RenderableTimeVaryingVolume::loadTimestepMetadata(const std::string& path) { - ghoul::Dictionary dictionary = ghoul::lua::loadDictionaryFromFile(path); - documentation::testSpecificationAndThrow( - TimestepDocumentation(), - dictionary, - "TimeVaryingVolumeTimestep" - ); + RawVolumeMetadata metadata; + + try { + ghoul::Dictionary dictionary = ghoul::lua::loadDictionaryFromFile(path); + metadata = RawVolumeMetadata::CreateFromDictionary(dictionary); + } catch (...) { + return; + } Timestep t; + t.metadata = metadata; t.baseName = ghoul::filesystem::File(path).baseName(); - t.dimensions = dictionary.value(KeyDimensions); - t.lowerDomainBound = dictionary.value(KeyLowerDomainBound); - t.upperDomainBound = dictionary.value(KeyUpperDomainBound); - t.minValue = dictionary.value(KeyMinValue); - t.maxValue = dictionary.value(KeyMaxValue); - t.unit = dictionary.value(KeyUnit); - std::string timeString = dictionary.value(KeyTime); - t.time = Time::convertTime(timeString); t.inRam = false; t.onGpu = false; - _volumeTimesteps[t.time] = std::move(t); + _volumeTimesteps[t.metadata.time] = std::move(t); } RenderableTimeVaryingVolume::Timestep* RenderableTimeVaryingVolume::currentTimestep() { @@ -377,14 +352,16 @@ RenderableTimeVaryingVolume::Timestep* RenderableTimeVaryingVolume::currentTimes if (currentTimestepIt == _volumeTimesteps.end()) { // No such timestep was found: show last timestep if it is within the time margin. Timestep* lastTimestep = &(_volumeTimesteps.rbegin()->second); - double threshold = lastTimestep->time + static_cast(_secondsAfter); + double threshold = lastTimestep->metadata.time + + static_cast(_secondsAfter); return currentTime < threshold ? lastTimestep : nullptr; } if (currentTimestepIt == _volumeTimesteps.begin()) { // No such timestep was found: show first timestep if it is within the time margin Timestep* firstTimestep = &(_volumeTimesteps.begin()->second); - double threshold = firstTimestep->time - static_cast(_secondsBefore); + double threshold = firstTimestep->metadata.time - + static_cast(_secondsBefore); return currentTime >= threshold ? firstTimestep : nullptr; } @@ -430,35 +407,44 @@ void RenderableTimeVaryingVolume::jumpToTimestep(int target) { if (!t) { return; } - OsEng.timeManager().setTimeNextFrame(t->time); + OsEng.timeManager().setTimeNextFrame(t->metadata.time); } void RenderableTimeVaryingVolume::update(const UpdateData&) { if (_raycaster) { Timestep* t = currentTimestep(); _currentTimestep = timestepIndex(t); + + // Set scale and translation matrices: + // The original data cube is a unit cube centered in 0 + // ie with lower bound from (-0.5, -0.5, -0.5) and upper bound (0.5, 0.5, 0.5) if (t && t->texture) { if (_raycaster->gridType() == volume::VolumeGridType::Cartesian) { - glm::dvec3 scale = t->upperDomainBound - t->lowerDomainBound; + glm::dvec3 scale = t->metadata.upperDomainBound - + t->metadata.lowerDomainBound; glm::dvec3 translation = - (t->lowerDomainBound + t->upperDomainBound) * 0.5f; + (t->metadata.lowerDomainBound + t->metadata.upperDomainBound) * 0.5f; glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), translation); glm::dmat4 scaleMatrix = glm::scale(glm::dmat4(1.0), scale); modelTransform = modelTransform * scaleMatrix; _raycaster->setModelTransform(glm::mat4(modelTransform)); } else { + // The diameter is two times the maximum radius. + // No translation: the sphere is always centered in (0, 0, 0) _raycaster->setModelTransform( glm::scale( glm::dmat4(1.0), - glm::dvec3(t->upperDomainBound[0]) + glm::dvec3(2.0 * t->metadata.upperDomainBound[0]) ) ); } _raycaster->setVolumeTexture(t->texture); - _transferFunctionHandler->setUnit(t->unit); - _transferFunctionHandler->setMinAndMaxValue(t->minValue, t->maxValue); - _transferFunctionHandler->setHistogramProperty(t->histogram); + //_transferFunctionHandler->setUnit(t->metadata.valueUnit); + //_transferFunctionHandler->setMinAndMaxValue( + // t->metadata.minValue, t->metadata.maxValue); + + //_transferFunctionHandler->setHistogramProperty(t->histogram); } else { _raycaster->setVolumeTexture(nullptr); } @@ -504,12 +490,6 @@ documentation::Documentation RenderableTimeVaryingVolume::Documentation() { Optional::No, "Specifies the transfer function file path" }, - { - KeyGridType, - new StringInListVerifier({"Cartesian", "Spherical"}), - Optional::Yes, - "Specifies the grid type" - }, { KeySecondsBefore, new DoubleVerifier, @@ -529,51 +509,5 @@ documentation::Documentation RenderableTimeVaryingVolume::Documentation() { } -documentation::Documentation RenderableTimeVaryingVolume::TimestepDocumentation() { - using namespace documentation; - return { - "TimevaryingVolumeTimestep", - "volume_timevaryingvolumetimestep", - { - { - KeyLowerDomainBound, - new Vector3Verifier, - Optional::No, - "Specifies the lower domain bounds in the model coordinate system", - }, - { - KeyUpperDomainBound, - new Vector3Verifier, - Optional::No, - "Specifies the upper domain bounds in the model coordinate system", - }, - { - KeyDimensions, - new Vector3Verifier, - Optional::No, - "Specifies the number of grid cells in each dimension", - }, - { - KeyTime, - new StringVerifier, - Optional::No, - "Specifies the time on the format YYYY-MM-DDTHH:MM:SS.000Z", - }, - { - KeyMinValue, - new DoubleVerifier, - Optional::No, - "Specifies the minimum value stored in the volume" - }, - { - KeyMaxValue, - new DoubleVerifier, - Optional::No, - "Specifies the maximum value stored in the volume" - } - } - }; -} - } // namespace volume } // namespace openspace diff --git a/modules/volume/rendering/renderabletimevaryingvolume.h b/modules/volume/rendering/renderabletimevaryingvolume.h index e3169645d2..b195676c97 100644 --- a/modules/volume/rendering/renderabletimevaryingvolume.h +++ b/modules/volume/rendering/renderabletimevaryingvolume.h @@ -28,16 +28,16 @@ #include #include +#include #include #include -#include - #include #include #include #include #include +#include namespace openspace { @@ -57,21 +57,14 @@ public: void update(const UpdateData& data) override; static documentation::Documentation Documentation(); - static documentation::Documentation TimestepDocumentation(); private: struct Timestep { std::string baseName; - double time; - float minValue; - float maxValue; - glm::uvec3 dimensions; - glm::vec3 lowerDomainBound; - glm::vec3 upperDomainBound; - std::string unit; bool inRam; bool onGpu; - std::unique_ptr> rawVolume; + RawVolumeMetadata metadata; + std::shared_ptr> rawVolume; std::shared_ptr texture; std::shared_ptr histogram; }; @@ -102,7 +95,7 @@ private: std::map _volumeTimesteps; std::unique_ptr _raycaster; - std::shared_ptr _transferFunctionHandler; + std::shared_ptr _transferFunction; }; diff --git a/modules/volume/shaders/helper.glsl b/modules/volume/shaders/helper.glsl index fe7ec2b022..e8e8f5404f 100644 --- a/modules/volume/shaders/helper.glsl +++ b/modules/volume/shaders/helper.glsl @@ -38,5 +38,6 @@ vec3 volume_cartesianToSpherical(vec3 zeroToOneCoords) { theta = acos(cartesian.z / r) / VOLUME_PI; phi = (VOLUME_PI + atan(cartesian.y, cartesian.x)) / (2.0 * VOLUME_PI ); } - return vec3(r * VOLUME_SQRT1_3, theta, phi); + + return vec3(r, theta, phi); } diff --git a/modules/volume/shaders/raycast.glsl b/modules/volume/shaders/raycast.glsl index 49be9581ec..e9416c52d0 100644 --- a/modules/volume/shaders/raycast.glsl +++ b/modules/volume/shaders/raycast.glsl @@ -49,12 +49,14 @@ void sample#{id}(vec3 samplePos, vec3 dir, inout vec3 accumulatedColor, vec3 transformedPos = samplePos; if (gridType_#{id} == 1) { transformedPos = volume_cartesianToSpherical(samplePos); + if (abs(transformedPos.r) > 1.0) { + return; + } } float clipAlpha = 1.0; vec3 centerToPos = transformedPos - vec3(0.5); - for (int i = 0; i < nClips_#{id} && i < 8; i++) { vec3 clipNormal = clipNormals_#{id}[i]; float clipBegin = clipOffsets_#{id}[i].x; @@ -72,6 +74,7 @@ void sample#{id}(vec3 samplePos, vec3 dir, inout vec3 accumulatedColor, } vec4 color = texture(transferFunction_#{id}, val); + vec3 backColor = color.rgb; vec3 backAlpha = color.aaa; diff --git a/modules/volume/tasks/generaterawvolumetask.cpp b/modules/volume/tasks/generaterawvolumetask.cpp new file mode 100644 index 0000000000..7941fb8ee9 --- /dev/null +++ b/modules/volume/tasks/generaterawvolumetask.cpp @@ -0,0 +1,241 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + constexpr const char* KeyRawVolumeOutput = "RawVolumeOutput"; + constexpr const char* KeyDictionaryOutput = "DictionaryOutput"; + constexpr const char* KeyDimensions = "Dimensions"; + constexpr const char* KeyTime = "Time"; + constexpr const char* KeyValueFunction = "ValueFunction"; + constexpr const char* KeyLowerDomainBound = "LowerDomainBound"; + constexpr const char* KeyUpperDomainBound = "UpperDomainBound"; + + constexpr const char* KeyMinValue = "MinValue"; + constexpr const char* KeyMaxValue = "MaxValue"; +} // namespace + +namespace openspace { +namespace volume { + +GenerateRawVolumeTask::GenerateRawVolumeTask(const ghoul::Dictionary& dictionary) +{ + openspace::documentation::testSpecificationAndThrow( + documentation(), + dictionary, + "GenerateRawVolumeTask" + ); + + _rawVolumeOutputPath = absPath(dictionary.value(KeyRawVolumeOutput)); + _dictionaryOutputPath = absPath(dictionary.value(KeyDictionaryOutput)); + _dimensions = glm::uvec3(dictionary.value(KeyDimensions)); + _time = dictionary.value(KeyTime); + _valueFunctionLua = dictionary.value(KeyValueFunction); + _lowerDomainBound = dictionary.value(KeyLowerDomainBound); + _upperDomainBound = dictionary.value(KeyUpperDomainBound); +} + +std::string GenerateRawVolumeTask::description() { + return "Generate a raw volume with dimenstions: (" + + std::to_string(_dimensions.x) + ", " + + std::to_string(_dimensions.y) + ", " + + std::to_string(_dimensions.z) + "). " + + "For each cell, set the value by evaluating the lua function: " + + "`" + _valueFunctionLua + "`, with three arguments (x, y, z) ranging from " + + "(" + std::to_string(_lowerDomainBound.x) + ", " + + std::to_string(_lowerDomainBound.y) + ", " + + std::to_string(_lowerDomainBound.z) + ") to (" + + std::to_string(_upperDomainBound.x) + ", " + + std::to_string(_upperDomainBound.y) + ", " + + std::to_string(_upperDomainBound.z) + ")." + + "Write raw volume data into " + _rawVolumeOutputPath + + " and dictionary with metadata to " + _dictionaryOutputPath; +} + +void GenerateRawVolumeTask::perform(const Task::ProgressCallback& progressCallback) { + // Spice kernel is required for time conversions. + // Todo: Make this dependency less hard coded. + SpiceManager::KernelHandle kernel = + SpiceManager::ref().loadKernel(absPath("${DATA}/assets/spice/naif0012.tls")); + + defer { + SpiceManager::ref().unloadKernel(kernel); + }; + + volume::RawVolume rawVolume(_dimensions); + progressCallback(0.1f); + + ghoul::lua::LuaState state; + ghoul::lua::runScript(state, _valueFunctionLua); + + ghoul::lua::verifyStackSize(state, 1); + + int functionReference = luaL_ref(state, LUA_REGISTRYINDEX); + + ghoul::lua::verifyStackSize(state, 0); + + glm::vec3 domainSize = _upperDomainBound - _lowerDomainBound; + + + float minVal = std::numeric_limits::max(); + float maxVal = std::numeric_limits::min(); + + rawVolume.forEachVoxel([&](glm::uvec3 cell, float) { + glm::vec3 coord = _lowerDomainBound + + glm::vec3(cell) / glm::vec3(_dimensions) * domainSize; + + ghoul::lua::verifyStackSize(state, 0); + lua_rawgeti(state, LUA_REGISTRYINDEX, functionReference); + + lua_pushnumber(state, coord.x); + lua_pushnumber(state, coord.y); + lua_pushnumber(state, coord.z); + + ghoul::lua::verifyStackSize(state, 4); + + if (lua_pcall(state, 3, 1, 0) != LUA_OK) { + return; + } + + float value = luaL_checknumber(state, 1); + lua_pop(state, 1); + rawVolume.set(cell, value); + + minVal = std::min(minVal, value); + maxVal = std::max(maxVal, value); + }); + + luaL_unref(state, LUA_REGISTRYINDEX, functionReference); + + ghoul::filesystem::File file(_rawVolumeOutputPath); + const std::string directory = file.directoryName(); + if (!FileSys.directoryExists(directory)) { + FileSys.createDirectory(directory, ghoul::filesystem::FileSystem::Recursive::Yes); + } + + volume::RawVolumeWriter writer(_rawVolumeOutputPath); + writer.write(rawVolume); + + progressCallback(0.9f); + + RawVolumeMetadata metadata; + metadata.time = Time::convertTime(_time); + metadata.dimensions = _dimensions; + metadata.hasDomainUnit = true; + metadata.domainUnit = "m"; + metadata.hasValueUnit = true; + metadata.valueUnit = "K"; + metadata.gridType = VolumeGridType::Cartesian; + metadata.hasDomainBounds = true; + metadata.lowerDomainBound = _lowerDomainBound; + metadata.upperDomainBound = _upperDomainBound; + metadata.hasValueRange = true; + metadata.minValue = minVal; + metadata.maxValue = maxVal; + + ghoul::Dictionary outputDictionary = metadata.dictionary(); + ghoul::DictionaryLuaFormatter formatter; + std::string metadataString = formatter.format(outputDictionary); + + std::fstream f(_dictionaryOutputPath, std::ios::out); + f << "return " << metadataString; + f.close(); + + progressCallback(1.0f); +} + +documentation::Documentation GenerateRawVolumeTask::documentation() { + using namespace documentation; + return { + "GenerateRawVolumeTask", + "generate_raw_volume_task", + { + { + "Type", + new StringEqualVerifier("GenerateRawVolumeTask"), + Optional::No, + "The type of this task", + }, + { + KeyValueFunction, + new StringAnnotationVerifier("A lua expression that returns a function " + "taking three numbers as arguments (x, y, z) and returning a number."), + Optional::No, + "The lua function used to compute the cell values", + }, + { + KeyRawVolumeOutput, + new StringAnnotationVerifier("A valid filepath"), + Optional::No, + "The raw volume file to export data to", + }, + { + KeyDictionaryOutput, + new StringAnnotationVerifier("A valid filepath"), + Optional::No, + "The lua dictionary file to export metadata to", + }, + { + KeyDimensions, + new DoubleVector3Verifier, + Optional::No, + "A vector representing the number of cells in each dimension", + }, + { + KeyLowerDomainBound, + new DoubleVector3Verifier, + Optional::No, + "A vector representing the lower bound of the domain" + }, + { + KeyUpperDomainBound, + new DoubleVector3Verifier, + Optional::No, + "A vector representing the upper bound of the domain" + } + } + }; +} + +} // namespace volume +} // namespace openspace diff --git a/modules/volume/tasks/generaterawvolumetask.h b/modules/volume/tasks/generaterawvolumetask.h new file mode 100644 index 0000000000..fe118ec93a --- /dev/null +++ b/modules/volume/tasks/generaterawvolumetask.h @@ -0,0 +1,59 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_KAMELEONVOLUME___GENERATERAWVOLUMETASK___H__ +#define __OPENSPACE_MODULE_KAMELEONVOLUME___GENERATERAWVOLUMETASK___H__ + +#include + +#include + +#include + +namespace openspace { +namespace volume { + +class GenerateRawVolumeTask : public Task { +public: + GenerateRawVolumeTask(const ghoul::Dictionary& dictionary); + std::string description() override; + void perform(const Task::ProgressCallback& progressCallback) override; + static documentation::Documentation documentation(); + +private: + std::string _rawVolumeOutputPath; + std::string _dictionaryOutputPath; + std::string _time; + + glm::uvec3 _dimensions; + glm::vec3 _lowerDomainBound; + glm::vec3 _upperDomainBound; + + std::string _valueFunctionLua; +}; + +} // namespace volume +} // namespace openspace + +#endif // __OPENSPACE_MODULE_KAMELEONVOLUME___GENERATERAWVOLUMETASK___H__ diff --git a/modules/volume/volumegridtype.cpp b/modules/volume/volumegridtype.cpp index 87fe622a12..b540dcb9c6 100644 --- a/modules/volume/volumegridtype.cpp +++ b/modules/volume/volumegridtype.cpp @@ -38,6 +38,14 @@ VolumeGridType parseGridType(const std::string& gridType) { throw InvalidGridTypeError(gridType); } +std::string gridTypeToString(VolumeGridType gridType) { + switch (gridType) { + case VolumeGridType::Cartesian: return "Cartesian"; + case VolumeGridType::Spherical: return "Spherical"; + } + return "Unknown"; +} + InvalidGridTypeError::InvalidGridTypeError(std::string gt) : RuntimeError("Invalid grid type: '" + gt + "'") , gridType(std::move(gt)) diff --git a/modules/volume/volumegridtype.h b/modules/volume/volumegridtype.h index 8997d699db..66698c7abf 100644 --- a/modules/volume/volumegridtype.h +++ b/modules/volume/volumegridtype.h @@ -40,6 +40,7 @@ struct InvalidGridTypeError : public ghoul::RuntimeError { }; VolumeGridType parseGridType(const std::string& gridType); +std::string gridTypeToString(VolumeGridType); } // namespace openspace::volume diff --git a/modules/volume/volumemodule.cpp b/modules/volume/volumemodule.cpp index a6af7ba468..af2b42485f 100644 --- a/modules/volume/volumemodule.cpp +++ b/modules/volume/volumemodule.cpp @@ -24,8 +24,10 @@ #include #include +#include #include +#include #include #include @@ -34,12 +36,17 @@ namespace openspace { using namespace volume; -VolumeModule::VolumeModule() : OpenSpaceModule(Name) {} +VolumeModule::VolumeModule() : OpenSpaceModule(Name) {} void VolumeModule::internalInitialize(const ghoul::Dictionary&) { - auto factory = FactoryManager::ref().factory(); - ghoul_assert(factory, "No renderable factory existed"); - factory->registerClass("RenderableTimeVaryingVolume"); + auto rFactory = FactoryManager::ref().factory(); + ghoul_assert(rFactory, "No renderable factory existed"); + rFactory->registerClass("RenderableTimeVaryingVolume"); + + auto tFactory = FactoryManager::ref().factory(); + ghoul_assert(tFactory, "No task factory existed"); + tFactory->registerClass("GenerateRawVolumeTask"); + } } // namespace openspace diff --git a/src/rendering/transferfunction.cpp b/src/rendering/transferfunction.cpp index 7525713ce7..01a41ea188 100644 --- a/src/rendering/transferfunction.cpp +++ b/src/rendering/transferfunction.cpp @@ -93,7 +93,7 @@ ghoul::opengl::Texture& TransferFunction::getTexture() { } void TransferFunction::update() { - /* if (_needsUpdate) { + if (_needsUpdate) { if (hasExtension(_filepath, "txt")) { setTextureFromTxt(); } else { @@ -104,7 +104,7 @@ void TransferFunction::update() { if (_tfChangedCallback) { _tfChangedCallback(*this); } - }*/ + } } void TransferFunction::setCallback(TfChangedCallback callback) {