diff --git a/modules/softwareintegration/CMakeLists.txt b/modules/softwareintegration/CMakeLists.txt index 065f3971bc..782b9ced23 100644 --- a/modules/softwareintegration/CMakeLists.txt +++ b/modules/softwareintegration/CMakeLists.txt @@ -25,38 +25,47 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake) set(HEADER_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/galaxymodule.h - ${CMAKE_CURRENT_SOURCE_DIR}/rendering/galaxyraycaster.h - ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablegalaxy.h - ${CMAKE_CURRENT_SOURCE_DIR}/tasks/milkywayconversiontask.h - ${CMAKE_CURRENT_SOURCE_DIR}/tasks/milkywaypointsconversiontask.h + ${CMAKE_CURRENT_SOURCE_DIR}/gaiamodule.h + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablegaiastars.h + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/octreemanager.h + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/octreeculler.h + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readfilejob.h + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readfitstask.h + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readspecktask.h + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/constructoctreetask.h + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/gaiaoptions.h ) source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/galaxymodule.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rendering/galaxyraycaster.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablegalaxy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tasks/milkywayconversiontask.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tasks/milkywaypointsconversiontask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gaiamodule.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablegaiastars.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/octreemanager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/octreeculler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readfilejob.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readfitstask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/readspecktask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tasks/constructoctreetask.cpp ) source_group("Source Files" FILES ${SOURCE_FILES}) set(SHADER_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/galaxyraycast.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/billboard_vs.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/billboard_fs.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/billboard_ge.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/points_fs.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/points_vs.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/raycasterbounds_fs.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/shaders/raycasterbounds_vs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_vbo_vs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_ssbo_vs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_billboard_nofbo_fs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_billboard_fs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_billboard_ge.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_point_fs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_point_ge.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_tonemapping_vs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_tonemapping_point_fs.glsl + ${CMAKE_CURRENT_SOURCE_DIR}/shaders/gaia_tonemapping_billboard_fs.glsl ) source_group("Shader Files" FILES ${SHADER_FILES}) create_new_module( - "Galaxy" - galaxy + "Gaia" + gaia STATIC ${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES} ) diff --git a/modules/softwareintegration/include.cmake b/modules/softwareintegration/include.cmake index 586a4f7717..98bffaa6bf 100644 --- a/modules/softwareintegration/include.cmake +++ b/modules/softwareintegration/include.cmake @@ -1,6 +1,6 @@ set(DEFAULT_MODULE ON) -set (OPENSPACE_DEPENDENCIES - volume - space -) \ No newline at end of file +set(OPENSPACE_DEPENDENCIES + fitsfilereader + globebrowsing +) diff --git a/modules/softwareintegration/shaders/raycasterbounds_vs.glsl b/modules/softwareintegration/rendering/gaiaoptions.h similarity index 79% rename from modules/softwareintegration/shaders/raycasterbounds_vs.glsl rename to modules/softwareintegration/rendering/gaiaoptions.h index 5e1ddaed80..b1740b7cb5 100644 --- a/modules/softwareintegration/shaders/raycasterbounds_vs.glsl +++ b/modules/softwareintegration/rendering/gaiaoptions.h @@ -22,22 +22,33 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#version __CONTEXT__ +#ifndef __OPENSPACE_MODULE_GAIA___GAIAOPTIONS___H__ +#define __OPENSPACE_MODULE_GAIA___GAIAOPTIONS___H__ -layout(location = 0) in vec4 vertPosition; +namespace openspace::gaia { -out vec3 modelPosition; -out vec4 viewPosition; +enum RenderOption { + Static = 0, + Color = 1, + Motion = 2 +}; -uniform mat4 projectionTransform; -uniform mat4 modelViewTransform; +enum FileReaderOption { + Fits = 0, + Speck = 1, + BinaryRaw = 2, + BinaryOctree = 3, + StreamOctree = 4 +}; +enum ShaderOption { + Point_SSBO = 0, + Point_VBO = 1, + Billboard_SSBO = 2, + Billboard_VBO = 3, + Billboard_SSBO_noFBO = 4 +}; -void main() { - modelPosition = vertPosition.xyz; - viewPosition = modelViewTransform*vertPosition; +} // namespace openspace::gaiamission - // project the position to view space - gl_Position = projectionTransform * viewPosition; - gl_Position.z = 0.0; -} +#endif // __OPENSPACE_MODULE_GAIA___GAIAOPTIONS___H__ diff --git a/modules/softwareintegration/rendering/galaxyraycaster.cpp b/modules/softwareintegration/rendering/galaxyraycaster.cpp deleted file mode 100644 index 076662d73c..0000000000 --- a/modules/softwareintegration/rendering/galaxyraycaster.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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 - -namespace { - constexpr const char* GlslRaycastPath = - "${MODULES}/galaxy/shaders/galaxyraycast.glsl"; - constexpr const char* GlslBoundsVsPath = - "${MODULES}/galaxy/shaders/raycasterbounds_vs.glsl"; - constexpr const char* GlslBoundsFsPath = - "${MODULES}/galaxy/shaders/raycasterbounds_fs.glsl"; -} // namespace - -namespace openspace { - -GalaxyRaycaster::GalaxyRaycaster(ghoul::opengl::Texture& texture) - : _boundingBox(glm::vec3(1.0)) - , _texture(texture) - , _textureUnit(nullptr) -{} - -void GalaxyRaycaster::initialize() { - _boundingBox.initialize(); -} - -void GalaxyRaycaster::renderEntryPoints(const RenderData& data, - ghoul::opengl::ProgramObject& program) -{ - program.setUniform("modelViewTransform", glm::mat4(modelViewTransform(data))); - program.setUniform("projectionTransform", data.camera.projectionMatrix()); - - // Cull back face - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - // Render bounding geometry - _boundingBox.render(); -} - -void GalaxyRaycaster::renderExitPoints(const RenderData& data, - ghoul::opengl::ProgramObject& program) -{ - // Uniforms - program.setUniform("modelViewTransform", glm::mat4(modelViewTransform(data))); - program.setUniform("projectionTransform", data.camera.projectionMatrix()); - - // Cull front face - glEnable(GL_CULL_FACE); - glCullFace(GL_FRONT); - - // Render bounding geometry - _boundingBox.render(); - - // Restore defaults - glCullFace(GL_BACK); -} - -glm::dmat4 GalaxyRaycaster::modelViewTransform(const RenderData& data) { - glm::dmat4 modelTransform = - glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * - glm::dmat4(data.modelTransform.rotation) * - glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)) * - glm::dmat4(_modelTransform); - - return data.camera.combinedViewMatrix() * modelTransform; -} - -void GalaxyRaycaster::preRaycast(const RaycastData& data, - ghoul::opengl::ProgramObject& program) -{ - const std::string colorUniformName = "color" + std::to_string(data.id); - const std::string stepSizeUniformName = "maxStepSize" + std::to_string(data.id); - const std::string galaxyTextureUniformName = "galaxyTexture" + - std::to_string(data.id); - const std::string volumeAspectUniformName = "aspect" + std::to_string(data.id); - const std::string opacityCoefficientUniformName = "opacityCoefficient" + - std::to_string(data.id); - - const std::string absorptionMultiplyUniformName = "absorptionMultiply" + - std::to_string(data.id); - - const std::string emissionMultiplyUniformName = "emissionMultiply" + - std::to_string(data.id); - - program.setUniform(volumeAspectUniformName, _aspect); - program.setUniform(stepSizeUniformName, _stepSize); - program.setUniform(opacityCoefficientUniformName, _opacityCoefficient); - program.setUniform(absorptionMultiplyUniformName, _absorptionMultiply); - program.setUniform(emissionMultiplyUniformName, _emissionMultiply); - - _textureUnit = std::make_unique(); - _textureUnit->activate(); - _texture.bind(); - program.setUniform(galaxyTextureUniformName, *_textureUnit); -} - -void GalaxyRaycaster::postRaycast(const RaycastData&, ghoul::opengl::ProgramObject&) { - _textureUnit = nullptr; // release texture unit. -} - -bool GalaxyRaycaster::isCameraInside(const RenderData& data, glm::vec3& localPosition) { - glm::vec4 modelPos = glm::inverse(modelViewTransform(data)) * - glm::vec4(0.f, 0.f, 0.f, 1.f); - - localPosition = (glm::vec3(modelPos) + glm::vec3(0.5)); - - return (localPosition.x > 0 && localPosition.x < 1 && - localPosition.y > 0 && localPosition.y < 1 && - localPosition.z > 0 && localPosition.z < 1); -} - -std::string GalaxyRaycaster::boundsVertexShaderPath() const { - return GlslBoundsVsPath; -} - -std::string GalaxyRaycaster::boundsFragmentShaderPath() const { - return GlslBoundsFsPath; -} - -std::string GalaxyRaycaster::raycasterPath() const { - return GlslRaycastPath; -} - -std::string GalaxyRaycaster::helperPath() const { - return ""; // no helper file -} - -void GalaxyRaycaster::setAspect(const glm::vec3& aspect) { - _aspect = aspect / std::max(std::max(aspect.x, aspect.y), aspect.z); -} - -void GalaxyRaycaster::setModelTransform(glm::mat4 transform) { - _modelTransform = transform; -} - -void GalaxyRaycaster::setOpacityCoefficient(float opacityCoefficient) { - _opacityCoefficient = opacityCoefficient; -} - -void GalaxyRaycaster::setAbsorptionMultiplier(float absorptionMultiply) { - _absorptionMultiply = absorptionMultiply; -} - -void GalaxyRaycaster::setEmissionMultiplier(float emissionMultiply) { - _emissionMultiply = emissionMultiply; -} - -void GalaxyRaycaster::setTime(double time) { - _time = time; -} - -void GalaxyRaycaster::setStepSize(float stepSize) { - _stepSize = stepSize; -} - -} // namespace openspace diff --git a/modules/softwareintegration/rendering/galaxyraycaster.h b/modules/softwareintegration/rendering/galaxyraycaster.h deleted file mode 100644 index cb8cf6de12..0000000000 --- a/modules/softwareintegration/rendering/galaxyraycaster.h +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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_GALAXY___GALAXYRAYCASTER___H__ -#define __OPENSPACE_MODULE_GALAXY___GALAXYRAYCASTER___H__ - -#include - -#include -#include -#include -#include - -namespace ghoul::opengl { - class Texture; - class TextureUnit; - class ProgramObject; -} // namespace ghoul::opengl - -namespace openspace { - -struct RenderData; -struct RaycastData; - -class GalaxyRaycaster : public VolumeRaycaster { -public: - GalaxyRaycaster(ghoul::opengl::Texture& texture); - - virtual ~GalaxyRaycaster() = default; - void initialize(); - - void renderEntryPoints(const RenderData& data, - ghoul::opengl::ProgramObject& program) override; - void renderExitPoints(const RenderData& data, - ghoul::opengl::ProgramObject& program) override; - void preRaycast(const RaycastData& data, - ghoul::opengl::ProgramObject& program) override; - void postRaycast(const RaycastData& data, - ghoul::opengl::ProgramObject& program) override; - bool isCameraInside(const RenderData& data, - glm::vec3& localPosition) override; - - std::string boundsVertexShaderPath() const override; - std::string boundsFragmentShaderPath() const override; - std::string raycasterPath() const override; - std::string helperPath() const override; - - void setAspect(const glm::vec3& aspect); - void setModelTransform(glm::mat4 transform); - void setTime(double time); - void setStepSize(float stepSize); - void setOpacityCoefficient(float opacityCoefficient); - void setAbsorptionMultiplier(float absorptionMultiply); - void setEmissionMultiplier(float emissionMultiply); - -private: - glm::dmat4 modelViewTransform(const RenderData& data); - - BoxGeometry _boundingBox; - float _stepSize = 0.f; - glm::mat4 _modelTransform = glm::mat4(1.f); - glm::vec3 _aspect = glm::vec3(0.f); - double _time = 0.0; - float _opacityCoefficient = 0.f; - float _absorptionMultiply = 0.f; - float _emissionMultiply = 0.f; - ghoul::opengl::Texture& _texture; - std::unique_ptr _textureUnit; - -}; // GalaxyRaycaster - -} // namespace openspace - -#endif // __OPENSPACE_MODULE_GALAXY___GALAXYRAYCASTER___H__ diff --git a/modules/softwareintegration/tasks/milkywaypointsconversiontask.cpp b/modules/softwareintegration/rendering/octreeculler.cpp similarity index 53% rename from modules/softwareintegration/tasks/milkywaypointsconversiontask.cpp rename to modules/softwareintegration/rendering/octreeculler.cpp index a79de9153e..0ffef8d717 100644 --- a/modules/softwareintegration/tasks/milkywaypointsconversiontask.cpp +++ b/modules/softwareintegration/rendering/octreeculler.cpp @@ -22,76 +22,61 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include -#include - -#include -#include -#include +#include +#include namespace openspace { -/*MilkywayPointsConversionTask::MilkywayPointsConversionTask( - const std::string& inFilename, - const std::string& outFilename) - : _inFilename(inFilename) - , _outFilename(outFilename) {}*/ - -MilkywayPointsConversionTask::MilkywayPointsConversionTask(const ghoul::Dictionary&) {} - -std::string MilkywayPointsConversionTask::description() { - return std::string(); -} - -void MilkywayPointsConversionTask::perform(const Task::ProgressCallback& progressCallback) -{ - std::ifstream in(_inFilename, std::ios::in); - std::ofstream out(_outFilename, std::ios::out | std::ios::binary); - - std::string format; - int64_t nPoints; - in >> format >> nPoints; - - size_t nFloats = nPoints * 7; - - std::vector pointData(nFloats); - - float x; - float y; - float z; - float r; - float g; - float b; - float a; - - for (int64_t i = 0; i < nPoints; ++i) { - in >> x >> y >> z >> r >> g >> b >> a; - if (in.good()) { - pointData[i * 7 + 0] = x; - pointData[i * 7 + 1] = y; - pointData[i * 7 + 2] = z; - pointData[i * 7 + 3] = r; - pointData[i * 7 + 4] = g; - pointData[i * 7 + 5] = b; - pointData[i * 7 + 6] = a; - progressCallback(static_cast(i + 1) / nPoints); - } - else { - std::cout << "Failed to convert point data."; - return; - } +namespace { + bool intersects(const globebrowsing::AABB3& bb, const globebrowsing::AABB3& o) { + return (bb.min.x <= o.max.x) && (o.min.x <= bb.max.x) + && (bb.min.y <= o.max.y) && (o.min.y <= bb.max.y) + && (bb.min.z <= o.max.z) && (o.min.z <= bb.max.z); } - out.write(reinterpret_cast(&nPoints), sizeof(int64_t)); - out.write(reinterpret_cast(pointData.data()), nFloats * sizeof(float)); + void expand(globebrowsing::AABB3& bb, const glm::vec3& p) { + bb.min = glm::min(bb.min, p); + bb.max = glm::max(bb.max, p); + } +} // namespace - in.close(); - out.close(); +OctreeCuller::OctreeCuller(globebrowsing::AABB3 viewFrustum) + : _viewFrustum(std::move(viewFrustum)) +{} + +bool OctreeCuller::isVisible(const std::vector& corners, + const glm::dmat4& mvp) +{ + createNodeBounds(corners, mvp); + return intersects(_viewFrustum, _nodeBounds); } -documentation::Documentation MilkywayPointsConversionTask::documentation() { - return documentation::Documentation(); +glm::vec2 OctreeCuller::getNodeSizeInPixels(const std::vector& corners, + const glm::dmat4& mvp, + const glm::vec2& screenSize) +{ + + createNodeBounds(corners, mvp); + + // Screen space is mapped to [-1, 1] so divide by 2 and multiply with screen size. + glm::vec3 size = (_nodeBounds.max - _nodeBounds.min) / 2.f; + size = glm::abs(size); + return glm::vec2(size.x * screenSize.x, size.y * screenSize.y); +} + +void OctreeCuller::createNodeBounds(const std::vector& corners, + const glm::dmat4& mvp) +{ + // Create a bounding box in clipping space from node boundaries. + _nodeBounds = globebrowsing::AABB3(); + + for (size_t i = 0; i < 8; ++i) { + glm::dvec4 cornerClippingSpace = mvp * corners[i]; + glm::dvec4 ndc = (1.f / glm::abs(cornerClippingSpace.w)) * cornerClippingSpace; + expand(_nodeBounds, glm::dvec3(ndc)); + } } } // namespace openspace diff --git a/modules/softwareintegration/tasks/milkywaypointsconversiontask.h b/modules/softwareintegration/rendering/octreeculler.h similarity index 60% rename from modules/softwareintegration/tasks/milkywaypointsconversiontask.h rename to modules/softwareintegration/rendering/octreeculler.h index e2a1be4a4f..c8a2e2362f 100644 --- a/modules/softwareintegration/tasks/milkywaypointsconversiontask.h +++ b/modules/softwareintegration/rendering/octreeculler.h @@ -22,38 +22,55 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GALAXY___MILKYWAYPOINTSCONVERSIONTASK_H__ -#define __OPENSPACE_MODULE_GALAXY___MILKYWAYPOINTSCONVERSIONTASK_H__ +#ifndef __OPENSPACE_MODULE_GAIA___OCTREECULLER___H__ +#define __OPENSPACE_MODULE_GAIA___OCTREECULLER___H__ -#include +#include +#include -#include +// TODO: Move /geometry/* to libOpenSpace so as not to depend on globebrowsing. namespace openspace { -namespace documentation { struct Documentation; } - /** - * Converts ascii based point data - * int64_t n - * (float x, float y, float z, float r, float g, float b) * n - * to a binary (floating point) representation with the same layout. + * Culls all octree nodes that are completely outside the view frustum. + * + * The frustum culling uses a 2D axis aligned bounding box for the OctreeNode in + * screen space. */ -class MilkywayPointsConversionTask : public Task { + +class OctreeCuller { public: - MilkywayPointsConversionTask(const ghoul::Dictionary& dictionary); - virtual ~MilkywayPointsConversionTask() = default; - std::string description() override; - void perform(const Task::ProgressCallback& progressCallback) override; + /** + * \param viewFrustum is the view space in normalized device coordinates space. + * Hence it is an axis aligned bounding box and not a real frustum. + */ + OctreeCuller(globebrowsing::AABB3 viewFrustum); - static documentation::Documentation documentation(); + ~OctreeCuller() = default; + + /** + * \return true if any part of the node is visible in the current view. + */ + bool isVisible(const std::vector& corners, const glm::dmat4& mvp); + + /** + * \return the size [in pixels] of the node in clipping space. + */ + glm::vec2 getNodeSizeInPixels(const std::vector& corners, + const glm::dmat4& mvp, const glm::vec2& screenSize); private: - std::string _inFilename; - std::string _outFilename; + /** + * Creates an axis-aligned bounding box containing all \p corners in clipping space. + */ + void createNodeBounds(const std::vector& corners, const glm::dmat4& mvp); + + const globebrowsing::AABB3 _viewFrustum; + globebrowsing::AABB3 _nodeBounds; }; } // namespace openspace -#endif // __OPENSPACE_MODULE_GALAXY___MILKYWAYPOINTSCONVERSIONTASK_H__ +#endif // __OPENSPACE_MODULE_GAIA___OCTREECULLER___H__ diff --git a/modules/softwareintegration/rendering/octreemanager.cpp b/modules/softwareintegration/rendering/octreemanager.cpp new file mode 100644 index 0000000000..5b56c62816 --- /dev/null +++ b/modules/softwareintegration/rendering/octreemanager.cpp @@ -0,0 +1,1399 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +namespace { + constexpr const char* _loggerCat = "OctreeManager"; +} // namespace + +namespace openspace { + +void OctreeManager::initOctree(long long cpuRamBudget, int maxDist, int maxStarsPerNode) { + if (_root) { + LDEBUG("Clear existing Octree"); + clearAllData(); + } + + LDEBUG("Initializing new Octree"); + _root = std::make_shared(); + _root->octreePositionIndex = 8; + + // Initialize the culler. The NDC.z of the comparing corners are always -1 or 1. + globebrowsing::AABB3 box; + box.min = glm::vec3(-1.f, -1.f, 0.f); + box.max = glm::vec3(1.f, 1.f, 100.f); + _culler = std::make_unique(box); + _removedKeysInPrevCall = std::set(); + _leastRecentlyFetchedNodes = std::queue(); + + // Reset default values when rebuilding the Octree during runtime. + _numInnerNodes = 0; + _numLeafNodes = 0; + _totalDepth = 0; + _valuesPerStar = POS_SIZE + COL_SIZE + VEL_SIZE; + _maxCpuRamBudget = cpuRamBudget; + _cpuRamBudget = cpuRamBudget; + _parentNodeOfCamera = 8; + + if (maxDist > 0) { + MAX_DIST = static_cast(maxDist); + } + if (maxStarsPerNode > 0) { + MAX_STARS_PER_NODE = static_cast(maxStarsPerNode); + } + + for (size_t i = 0; i < 8; ++i) { + _numLeafNodes++; + _root->Children[i] = std::make_shared(); + _root->Children[i]->posData = std::vector(); + _root->Children[i]->colData = std::vector(); + _root->Children[i]->velData = std::vector(); + _root->Children[i]->magOrder = std::vector>(); + _root->Children[i]->isLeaf = true; + _root->Children[i]->isLoaded = false; + _root->Children[i]->hasLoadedDescendant = false; + _root->Children[i]->bufferIndex = DEFAULT_INDEX; + _root->Children[i]->octreePositionIndex = 80 + i; + _root->Children[i]->numStars = 0; + _root->Children[i]->halfDimension = MAX_DIST / 2.f; + _root->Children[i]->originX = (i % 2 == 0) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + _root->Children[i]->originY = (i % 4 < 2) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + _root->Children[i]->originZ = (i < 4) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + } +} + +void OctreeManager::initBufferIndexStack(long long maxNodes, bool useVBO, + bool datasetFitInMemory) +{ + // Clear stack if we've used it before. + _biggestChunkIndexInUse = 0; + _freeSpotsInBuffer = std::stack(); + _rebuildBuffer = true; + _useVBO = useVBO; + _datasetFitInMemory = datasetFitInMemory; + + // Build stack back-to-front. + for (long long idx = maxNodes - 1; idx >= 0; --idx) { + _freeSpotsInBuffer.push(static_cast(idx)); + } + _maxStackSize = _freeSpotsInBuffer.size(); + LINFO("StackSize: " + std::to_string(maxNodes)); +} + +void OctreeManager::insert(const std::vector& starValues) { + size_t index = getChildIndex(starValues[0], starValues[1], starValues[2]); + + insertInNode(*_root->Children[index], starValues); +} + +void OctreeManager::sliceLodData(size_t branchIndex) { + if (branchIndex != 8) { + sliceNodeLodCache(*_root->Children[branchIndex]); + } + else { + for (int i = 0; i < 7; ++i) { + sliceNodeLodCache(*_root->Children[i]); + } + } +} + +void OctreeManager::printStarsPerNode() const { + auto accumulatedString = std::string(); + + for (int i = 0; i < 8; ++i) { + std::string prefix = "{" + std::to_string(i); + accumulatedString += printStarsPerNode(*_root->Children[i], prefix); + } + LINFO(fmt::format("Number of stars per node: \n{}", accumulatedString)); + LINFO(fmt::format("Number of leaf nodes: {}", std::to_string(_numLeafNodes))); + LINFO(fmt::format("Number of inner nodes: {}", std::to_string(_numInnerNodes))); + LINFO(fmt::format("Depth of tree: {}", std::to_string(_totalDepth))); +} + +void OctreeManager::fetchSurroundingNodes(const glm::dvec3& cameraPos, + size_t chunkSizeInBytes, + const glm::ivec2& additionalNodes) +{ + + // If entire dataset fits in RAM then load the entire dataset asynchronously now. + // Nodes will be rendered when they've been made available. + if (_datasetFitInMemory) { + // Only traverse Octree once! + if (_parentNodeOfCamera == 8) { + // Fetch first layer of children + fetchChildrenNodes(*_root, 0); + + for (int i = 0; i < 8; ++i) { + // Check so branch doesn't have a single layer. + if (_root->Children[i]->isLeaf) { + continue; + } + + // Use multithreading to load files and detach thread from main execution + // so it can execute independently. Thread will be destroyed when + // finished! + std::thread([this, n = _root->Children[i]]() { + fetchChildrenNodes(*n, -1); + }).detach(); + } + _parentNodeOfCamera = 0; + } + return; + } + + // Get leaf node in which the camera resides. + glm::vec3 fCameraPos = static_cast( + cameraPos / (1000.0 * distanceconstants::Parsec) + ); + size_t idx = getChildIndex(fCameraPos.x, fCameraPos.y, fCameraPos.z); + std::shared_ptr node = _root->Children[idx]; + + while (!node->isLeaf) { + idx = getChildIndex( + fCameraPos.x, + fCameraPos.y, + fCameraPos.z, + node->originX, + node->originY, + node->originZ + ); + node = node->Children[idx]; + } + unsigned long long leafId = node->octreePositionIndex; + unsigned long long firstParentId = leafId / 10; + + // Return early if camera resides in the same first parent as before! + // Otherwise camera has moved and may need to load more nodes! + if (_parentNodeOfCamera == firstParentId) { + return; + } + _parentNodeOfCamera = firstParentId; + + // Each parent level may be root, make sure to propagate it in that case! + unsigned long long secondParentId = (firstParentId == 8) ? 8 : leafId / 100; + unsigned long long thirdParentId = (secondParentId == 8) ? 8 : leafId / 1000; + unsigned long long fourthParentId = (thirdParentId == 8) ? 8 : leafId / 10000; + unsigned long long fifthParentId = (fourthParentId == 8) ? 8 : leafId / 100000; + + // Get the number of levels to fetch from user input. + int additionalLevelsToFetch = additionalNodes.y; + + // Get more descendants when closer to root. + if (_parentNodeOfCamera < 80000) { + additionalLevelsToFetch++; + } + + // Get the 3^3 closest parents and load all their (eventual) children. + for (int x = -1; x <= 1; x += 1) { + for (int y = -2; y <= 2; y += 2) { + for (int z = -4; z <= 4; z += 4) { + // Fetch all stars the 216 closest leaf nodes. + findAndFetchNeighborNode( + firstParentId, + x, + y, + z, + additionalLevelsToFetch + ); + // Fetch LOD stars from 208 parents one and two layer(s) up. + if (x != 0 || y != 0 || z != 0) { + if (additionalNodes.x > 0) { + findAndFetchNeighborNode( + secondParentId, + x, + y, + z, + additionalLevelsToFetch + ); + } + if (additionalNodes.x > 1) { + findAndFetchNeighborNode( + thirdParentId, + x, + y, + z, + additionalLevelsToFetch + ); + } + if (additionalNodes.x > 2) { + findAndFetchNeighborNode( + fourthParentId, + x, + y, + z, + additionalLevelsToFetch + ); + } + if (additionalNodes.x > 3) { + findAndFetchNeighborNode( + fifthParentId, + x, + y, + z, + additionalLevelsToFetch + ); + } + } + } + } + } + + // Check if we should remove any nodes from RAM. + long long tenthOfRamBudget = _maxCpuRamBudget / 10; + if (_cpuRamBudget < tenthOfRamBudget) { + long long bytesToTenthOfRam = tenthOfRamBudget - _cpuRamBudget; + size_t nNodesToRemove = static_cast(bytesToTenthOfRam / chunkSizeInBytes); + std::vector nodesToRemove; + while (nNodesToRemove > 0) { + // Dequeue nodes that were least recently fetched by findAndFetchNeighborNode. + nodesToRemove.push_back(_leastRecentlyFetchedNodes.front()); + _leastRecentlyFetchedNodes.pop(); + nNodesToRemove--; + } + // Use asynchronous removal. + if (!nodesToRemove.empty()) { + std::thread(&OctreeManager::removeNodesFromRam, this, nodesToRemove).detach(); + } + } +} + +void OctreeManager::findAndFetchNeighborNode(unsigned long long firstParentId, int x, + int y, int z, int additionalLevelsToFetch) +{ + unsigned long long parentId = firstParentId; + auto indexStack = std::stack(); + + // Fetch first layer children if we're already at root. + if (parentId == 8) { + fetchChildrenNodes(*_root, 0); + return; + } + + //----------------- Change first index -------------------// + int nodeIndex = parentId % 10; + + int dx = (nodeIndex % 2 == 0) ? 1 : -1; + int dy = (nodeIndex % 4 < 2) ? 2 : -2; + int dz = (nodeIndex < 4) ? 4 : -4; + + // Determine if we need to switch any side (find a higher common parent). + bool needToSwitchX = (x == dx); + bool needToSwitchY = (y == dy); + bool needToSwitchZ = (z == dz); + + if (!needToSwitchX) { + x = -x; + } + if (!needToSwitchY) { + y = -y; + } + if (!needToSwitchZ) { + z = -z; + } + + // Update node index and store it at the back of our stack. + nodeIndex += x + y + z; + indexStack.push(nodeIndex); + parentId /= 10; + + //--------- Change all indices until we find a common parent --------------// + while (parentId != 8 && (needToSwitchX || needToSwitchY || needToSwitchZ) ) { + nodeIndex = parentId % 10; + + dx = (nodeIndex % 2 == 0) ? 1 : -1; + dy = (nodeIndex % 4 < 2) ? 2 : -2; + dz = (nodeIndex < 4) ? 4 : -4; + + if (needToSwitchX) { + if (x != dx) { + needToSwitchX = false; + } + nodeIndex += dx; + } + if (needToSwitchY) { + if (y != dy) { + needToSwitchY = false; + } + nodeIndex += dy; + } + if (needToSwitchZ) { + if (z != dz) { + needToSwitchZ = false; + } + nodeIndex += dz; + } + + indexStack.push(nodeIndex); + parentId /= 10; + } + + // Take care of edge cases. If we got to the root but still need to switch to a + // common parent then no neighbor exists in that direction. + if (needToSwitchX || needToSwitchY || needToSwitchZ) { + return; + } + + // Continue to root if we didn't reach it. + while (parentId != 8) { + nodeIndex = parentId % 10; + indexStack.push(nodeIndex); + parentId /= 10; + } + + // Traverse to that parent node (as long as such a child exists!). + std::shared_ptr node = _root; + while (!indexStack.empty() && !node->Children[indexStack.top()]->isLeaf) { + node = node->Children[indexStack.top()]; + node->hasLoadedDescendant = true; + indexStack.pop(); + } + + // Fetch all children nodes from found parent. Use multithreading to load files + // asynchronously! Detach thread from main execution so it can execute independently. + // Thread will then be destroyed when it has finished! + std::thread([this, node, additionalLevelsToFetch]() { + fetchChildrenNodes(*node, additionalLevelsToFetch); + }).detach(); +} + +std::map> OctreeManager::traverseData(const glm::dmat4& mvp, + const glm::vec2& screenSize, + int& deltaStars, + gaia::RenderOption option, + float lodPixelThreshold) +{ + auto renderData = std::map>(); + bool innerRebuild = false; + _minTotalPixelsLod = lodPixelThreshold; + + // Reclaim indices from previous render call. + for (auto removedKey = _removedKeysInPrevCall.rbegin(); + removedKey != _removedKeysInPrevCall.rend(); ++removedKey) { + + // Uses a reverse loop to try to decrease the biggest chunk. + if (*removedKey == _biggestChunkIndexInUse - 1) { + _biggestChunkIndexInUse = *removedKey; + LDEBUG(fmt::format( + "Decreased size to: {} Free Spots in VBO: {}", + _biggestChunkIndexInUse, _freeSpotsInBuffer.size() + )); + } + _freeSpotsInBuffer.push(*removedKey); + } + // Clear cache of removed keys before next render call. + if (!_removedKeysInPrevCall.empty()) { + _removedKeysInPrevCall.clear(); + } + + // Rebuild VBO from scratch if we're not using most of it but have a high max index. + if ((_biggestChunkIndexInUse > _maxStackSize * 4 / 5) && + (_freeSpotsInBuffer.size() > _maxStackSize * 5 / 6)) + { + LDEBUG(fmt::format( + "Rebuilding VBO. Biggest Chunk: {} 4/5: {} FreeSpotsInVBO: {} 5/6: {}", + _biggestChunkIndexInUse, _maxStackSize * 4 / 5, _freeSpotsInBuffer.size(), + _maxStackSize * 5 / 6 + )); + initBufferIndexStack(_maxStackSize, _useVBO, _datasetFitInMemory); + innerRebuild = true; + } + + // Check if entire tree is too small to see, and if so remove it. + std::vector corners(8); + float fMaxDist = static_cast(MAX_DIST); + for (int i = 0; i < 8; ++i) { + float x = (i % 2 == 0) ? fMaxDist : -fMaxDist; + float y = (i % 4 < 2) ? fMaxDist : -fMaxDist; + float z = (i < 4) ? fMaxDist : -fMaxDist; + glm::dvec3 pos = glm::dvec3(x, y, z) * 1000.0 * distanceconstants::Parsec; + corners[i] = glm::dvec4(pos, 1.0); + } + if (!_culler->isVisible(corners, mvp)) { + return renderData; + } + glm::vec2 nodeSize = _culler->getNodeSizeInPixels(corners, mvp, screenSize); + float totalPixels = nodeSize.x * nodeSize.y; + if (totalPixels < _minTotalPixelsLod * 2) { + // Remove LOD from first layer of children. + for (int i = 0; i < 8; ++i) { + std::map> tmpData = removeNodeFromCache( + *_root->Children[i], + deltaStars + ); + renderData.insert(tmpData.begin(), tmpData.end()); + } + return renderData; + } + + for (size_t i = 0; i < 8; ++i) { + if (i < _traversedBranchesInRenderCall) { + continue; + } + + std::map> tmpData = checkNodeIntersection( + *_root->Children[i], + mvp, + screenSize, + deltaStars, + option + ); + + // Avoid freezing when switching render mode for large datasets by only fetching + // one branch at a time when rebuilding buffer. + if (_rebuildBuffer) { + //renderData = std::move(tmpData); + _traversedBranchesInRenderCall++; + //break; + } + + // Observe that if there exists identical keys in renderData then those values in + // tmpData will be ignored! Thus we store the removed keys until next render call! + renderData.insert(tmpData.begin(), tmpData.end()); + } + + if (_rebuildBuffer) { + if (_useVBO) { + // We need to overwrite bigger indices that had data before! No need for SSBO. + std::map> idxToRemove; + for (int idx : _removedKeysInPrevCall) { + idxToRemove[idx] = std::vector(); + } + + // This will only insert indices that doesn't already exist in map + // (i.e. > biggestIdx). + renderData.insert(idxToRemove.begin(), idxToRemove.end()); + } + if (innerRebuild) { + deltaStars = 0; + } + + // Clear potential removed keys for both VBO and SSBO! + _removedKeysInPrevCall.clear(); + + LDEBUG(fmt::format( + "After rebuilding branch {} - Biggest chunk: {} Free spots in buffer: {}", + _traversedBranchesInRenderCall, _biggestChunkIndexInUse, + _freeSpotsInBuffer.size() + )); + + // End rebuild when all branches has been fetched. + if (_traversedBranchesInRenderCall == 8) { + _rebuildBuffer = false; + _traversedBranchesInRenderCall = 0; + } + } + return renderData; +} + +std::vector OctreeManager::getAllData(gaia::RenderOption option) { + std::vector fullData; + + for (size_t i = 0; i < 8; ++i) { + std::vector tmpData = getNodeData(*_root->Children[i], option); + fullData.insert(fullData.end(), tmpData.begin(), tmpData.end()); + } + return fullData; +} + +void OctreeManager::clearAllData(int branchIndex) { + // Don't clear everything if not needed. + if (branchIndex != -1) { + clearNodeData(*_root->Children[branchIndex]); + } + else { + for (size_t i = 0; i < 8; ++i) { + clearNodeData(*_root->Children[i]); + } + } +} + +void OctreeManager::writeToFile(std::ofstream& outFileStream, bool writeData) { + outFileStream.write(reinterpret_cast(&_valuesPerStar), sizeof(int32_t)); + outFileStream.write( + reinterpret_cast(&MAX_STARS_PER_NODE), + sizeof(int32_t) + ); + outFileStream.write(reinterpret_cast(&MAX_DIST), sizeof(int32_t)); + + // Use pre-traversal (Morton code / Z-order). + for (size_t i = 0; i < 8; ++i) { + writeNodeToFile(outFileStream, *_root->Children[i], writeData); + } +} + +void OctreeManager::writeNodeToFile(std::ofstream& outFileStream, const OctreeNode& node, + bool writeData) +{ + // Write node structure. + bool isLeaf = node.isLeaf; + int32_t numStars = static_cast(node.numStars); + outFileStream.write(reinterpret_cast(&isLeaf), sizeof(bool)); + outFileStream.write(reinterpret_cast(&numStars), sizeof(int32_t)); + + // Write node data if specified + if (writeData) { + std::vector nodeData = node.posData; + nodeData.insert(nodeData.end(), node.colData.begin(), node.colData.end()); + nodeData.insert(nodeData.end(), node.velData.begin(), node.velData.end()); + int32_t nDataSize = static_cast(nodeData.size()); + size_t nBytes = nDataSize * sizeof(nodeData[0]); + + outFileStream.write(reinterpret_cast(&nDataSize), sizeof(int32_t)); + if (nDataSize > 0) { + outFileStream.write(reinterpret_cast(nodeData.data()), nBytes); + } + } + + // Write children to file (in Morton order) if we're in an inner node. + if (!node.isLeaf) { + for (size_t i = 0; i < 8; ++i) { + writeNodeToFile(outFileStream, *node.Children[i], writeData); + } + } +} + +int OctreeManager::readFromFile(std::ifstream& inFileStream, bool readData, + const std::string& folderPath) +{ + int nStarsRead = 0; + int oldMaxdist = static_cast(MAX_DIST); + + // If we're not reading data then we need to stream from files later on. + _streamOctree = !readData; + if (_streamOctree) { + _streamFolderPath = folderPath; + } + + _valuesPerStar = 0; + inFileStream.read(reinterpret_cast(&_valuesPerStar), sizeof(int32_t)); + inFileStream.read(reinterpret_cast(&MAX_STARS_PER_NODE), sizeof(int32_t)); + inFileStream.read(reinterpret_cast(&MAX_DIST), sizeof(int32_t)); + + LDEBUG(fmt::format( + "Max stars per node in read Octree: {} - Radius of root layer: {}", + MAX_STARS_PER_NODE, MAX_DIST + )); + + // Octree Manager root halfDistance must be updated before any nodes are created! + if (MAX_DIST != oldMaxdist) { + for (size_t i = 0; i < 8; ++i) { + _root->Children[i]->halfDimension = MAX_DIST / 2.f; + _root->Children[i]->originX = (i % 2 == 0) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + _root->Children[i]->originY = (i % 4 < 2) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + _root->Children[i]->originZ = (i < 4) ? + _root->Children[i]->halfDimension : + -_root->Children[i]->halfDimension; + } + } + + if (_valuesPerStar != (POS_SIZE + COL_SIZE + VEL_SIZE)) { + LERROR("Read file doesn't have the same structure of render parameters!"); + } + + // Use the same technique to construct octree from file. + for (size_t i = 0; i < 8; ++i) { + nStarsRead += readNodeFromFile(inFileStream, *_root->Children[i], readData); + } + return nStarsRead; +} + +int OctreeManager::readNodeFromFile(std::ifstream& inFileStream, OctreeNode& node, + bool readData) +{ + // Read node structure. + bool isLeaf; + int32_t numStars = 0; + + inFileStream.read(reinterpret_cast(&isLeaf), sizeof(bool)); + inFileStream.read(reinterpret_cast(&numStars), sizeof(int32_t)); + + node.isLeaf = isLeaf; + node.numStars = numStars; + + // Read node data if specified. + if (readData) { + int32_t nDataSize = 0; + inFileStream.read(reinterpret_cast(&nDataSize), sizeof(int32_t)); + + if (nDataSize > 0) { + std::vector fetchedData(nDataSize, 0.0f); + size_t nBytes = nDataSize * sizeof(fetchedData[0]); + + inFileStream.read(reinterpret_cast(&fetchedData[0]), nBytes); + + int starsInNode = static_cast(nDataSize / _valuesPerStar); + auto posEnd = fetchedData.begin() + (starsInNode * POS_SIZE); + auto colEnd = posEnd + (starsInNode * COL_SIZE); + auto velEnd = colEnd + (starsInNode * VEL_SIZE); + node.posData = std::vector(fetchedData.begin(), posEnd); + node.colData = std::vector(posEnd, colEnd); + node.velData = std::vector(colEnd, velEnd); + } + } + + // Create children if we're in an inner node and read from the corresponding nodes. + if (!node.isLeaf) { + numStars = 0; + createNodeChildren(node); + for (size_t i = 0; i < 8; ++i) { + numStars += readNodeFromFile(inFileStream, *node.Children[i], readData); + } + } + + // Return the number of stars in the entire branch, no need to check children. + return numStars; +} + +void OctreeManager::writeToMultipleFiles(const std::string& outFolderPath, + size_t branchIndex) +{ + // Write entire branch to disc, with one file per node. + std::string outFilePrefix = outFolderPath + std::to_string(branchIndex); + // More threads doesn't make it much faster, disk speed still the limiter. + writeNodeToMultipleFiles(outFilePrefix, *_root->Children[branchIndex], false); + + // Clear all data in branch. + LINFO(fmt::format("Clear all data from branch {} in octree", branchIndex)); + clearNodeData(*_root->Children[branchIndex]); +} + +void OctreeManager::writeNodeToMultipleFiles(const std::string& outFilePrefix, + OctreeNode& node, bool threadWrites) +{ + // Prepare node data, save nothing else. + std::vector nodeData = node.posData; + nodeData.insert(nodeData.end(), node.colData.begin(), node.colData.end()); + nodeData.insert(nodeData.end(), node.velData.begin(), node.velData.end()); + int32_t nDataSize = static_cast(nodeData.size()); + size_t nBytes = nDataSize * sizeof(nodeData[0]); + + // Only open output stream if we have any values to write. + if (nDataSize > 0) { + // Use Morton code to name file (placement in Octree). + std::string outPath = outFilePrefix + BINARY_SUFFIX; + std::ofstream outFileStream(outPath, std::ofstream::binary); + if (outFileStream.good()) { + // LINFO("Write " + std::to_string(nDataSize) + " values to " + outPath); + outFileStream.write( + reinterpret_cast(&nDataSize), + sizeof(int32_t) + ); + outFileStream.write(reinterpret_cast(nodeData.data()), nBytes); + + outFileStream.close(); + } + else { + LERROR(fmt::format("Error opening file: {} as output data file.", outPath)); + } + } + + // Recursively write children to file (in Morton order) if we're in an inner node. + if (!node.isLeaf) { + std::vector writeThreads(8); + for (size_t i = 0; i < 8; ++i) { + std::string newOutFilePrefix = outFilePrefix + std::to_string(i); + if (threadWrites) { + // Divide writing to new threads to speed up the process. + std::thread t( + [this, newOutFilePrefix, n = node.Children[i]]() { + writeNodeToMultipleFiles(newOutFilePrefix, *n, false); + } + ); + writeThreads[i] = std::move(t); + } + else { + writeNodeToMultipleFiles(newOutFilePrefix, *node.Children[i], false); + } + } + if (threadWrites) { + // Make sure all threads are done. + for (int thread = 0; thread < 8; ++thread) { + writeThreads[thread].join(); + } + } + } +} + +void OctreeManager::fetchChildrenNodes(OctreeNode& parentNode, + int additionalLevelsToFetch) +{ + // Lock node to make sure nobody else are trying to load the same children. + std::lock_guard lock(parentNode.loadingLock); + + for (int i = 0; i < 8; ++i) { + // Fetch node data if we're streaming and it doesn't exist in RAM yet. + // (As long as there is any RAM budget left and node actually has any data!) + if (!parentNode.Children[i]->isLoaded && + (parentNode.Children[i]->numStars > 0) && + _cpuRamBudget > static_cast(parentNode.Children[i]->numStars + * (POS_SIZE + COL_SIZE + VEL_SIZE) * 4)) + { + fetchNodeDataFromFile(*parentNode.Children[i]); + } + + // Fetch all Children's Children if recursive is set to true! + if (additionalLevelsToFetch != 0 && !parentNode.Children[i]->isLeaf) { + fetchChildrenNodes(*parentNode.Children[i], --additionalLevelsToFetch); + } + } +} + +void OctreeManager::fetchNodeDataFromFile(OctreeNode& node) { + // Remove root ID ("8") from index before loading file. + std::string posId = std::to_string(node.octreePositionIndex); + posId.erase(posId.begin()); + + std::string inFilePath = _streamFolderPath + posId + BINARY_SUFFIX; + std::ifstream inFileStream(inFilePath, std::ifstream::binary); + // LINFO("Fetch node data file: " + inFilePath); + + if (inFileStream.good()) { + // Read node data. + int32_t nDataSize = 0; + + // Octree knows if we have any data in this node = it exists. + // Otherwise don't call this function! + inFileStream.read(reinterpret_cast(&nDataSize), sizeof(int32_t)); + + std::vector readData(nDataSize, 0.f); + int nBytes = nDataSize * sizeof(readData[0]); + if (nDataSize > 0) { + inFileStream.read(reinterpret_cast(&readData[0]), nBytes); + } + + int starsInNode = static_cast(nDataSize / _valuesPerStar); + auto posEnd = readData.begin() + (starsInNode * POS_SIZE); + auto colEnd = posEnd + (starsInNode * COL_SIZE); + auto velEnd = colEnd + (starsInNode * VEL_SIZE); + node.posData = std::vector(readData.begin(), posEnd); + node.colData = std::vector(posEnd, colEnd); + node.velData = std::vector(colEnd, velEnd); + + // Keep track of nodes that are loaded and update CPU RAM budget. + node.isLoaded = true; + if (!_datasetFitInMemory) { + std::lock_guard g(_leastRecentlyFetchedNodesMutex); + _leastRecentlyFetchedNodes.push(node.octreePositionIndex); + } + _cpuRamBudget -= nBytes; + } + else { + LERROR("Error opening node data file: " + inFilePath); + } +} + +void OctreeManager::removeNodesFromRam( + const std::vector& nodesToRemove) +{ + // LINFO("Removed " + std::to_string(nodesToRemove.size()) + " nodes from RAM."); + + for (unsigned long long nodePosIndex : nodesToRemove) { + std::stack indexStack; + while (nodePosIndex != 8) { + int nodeIndex = nodePosIndex % 10; + indexStack.push(nodeIndex); + nodePosIndex /= 10; + } + + // Traverse to node and remove it. + std::shared_ptr node = _root; + std::vector> ancestors; + while (!indexStack.empty()) { + ancestors.push_back(node); + node = node->Children[indexStack.top()]; + indexStack.pop(); + } + removeNode(*node); + + propagateUnloadedNodes(ancestors); + } +} + +void OctreeManager::removeNode(OctreeNode& node) { + // Lock node to make sure nobody else is trying to access it while removing. + std::lock_guard lock(node.loadingLock); + + int nBytes = static_cast( + node.numStars * _valuesPerStar * sizeof(node.posData[0]) + ); + // Keep track of which nodes that are loaded and update CPU RAM budget. + node.isLoaded = false; + _cpuRamBudget += nBytes; + + // Clear data + node.posData.clear(); + node.posData.shrink_to_fit(); + node.colData.clear(); + node.colData.shrink_to_fit(); + node.velData.clear(); + node.velData.shrink_to_fit(); +} + +void OctreeManager::propagateUnloadedNodes( + std::vector> ancestorNodes) +{ + std::shared_ptr parentNode = ancestorNodes.back(); + while (parentNode->octreePositionIndex != 8) { + // Check if any children of inner node is still loaded, or has loaded descendants. + if (parentNode->Children[0]->isLoaded || parentNode->Children[1]->isLoaded || + parentNode->Children[2]->isLoaded || parentNode->Children[3]->isLoaded || + parentNode->Children[4]->isLoaded || parentNode->Children[5]->isLoaded || + parentNode->Children[6]->isLoaded || parentNode->Children[7]->isLoaded || + parentNode->Children[0]->hasLoadedDescendant || + parentNode->Children[1]->hasLoadedDescendant || + parentNode->Children[2]->hasLoadedDescendant || + parentNode->Children[3]->hasLoadedDescendant || + parentNode->Children[4]->hasLoadedDescendant || + parentNode->Children[5]->hasLoadedDescendant || + parentNode->Children[6]->hasLoadedDescendant || + parentNode->Children[7]->hasLoadedDescendant) + { + return; + } + // Else all children has been unloaded and we can update parent flag. + parentNode->hasLoadedDescendant = false; + // LINFO("Removed ancestor: " + std::to_string(parentNode->octreePositionIndex)); + + // Propagate change upwards. + ancestorNodes.pop_back(); + parentNode = ancestorNodes.back(); + } +} + +size_t OctreeManager::numLeafNodes() const { + return _numLeafNodes; +} + +size_t OctreeManager::numInnerNodes() const { + return _numInnerNodes; +} + +size_t OctreeManager::totalNodes() const { + return _numLeafNodes + _numInnerNodes; +} + +size_t OctreeManager::totalDepth() const { + return _totalDepth; +} + +size_t OctreeManager::maxDist() const { + return MAX_DIST; +} + +size_t OctreeManager::maxStarsPerNode() const { + return MAX_STARS_PER_NODE; +} + +size_t OctreeManager::biggestChunkIndexInUse() const { + return _biggestChunkIndexInUse; +} + +size_t OctreeManager::numFreeSpotsInBuffer() const { + return _freeSpotsInBuffer.size(); +} + +long long OctreeManager::cpuRamBudget() const { + return _cpuRamBudget; +} + +bool OctreeManager::isRebuildOngoing() const { + return _rebuildBuffer; +} + +size_t OctreeManager::getChildIndex(float posX, float posY, float posZ, float origX, + float origY, float origZ) +{ + size_t index = 0; + if (posX < origX) { + index += 1; + } + if (posY < origY) { + index += 2; + } + if (posZ < origZ) { + index += 4; + } + return index; +} + +bool OctreeManager::insertInNode(OctreeNode& node, const std::vector& starValues, + int depth) +{ + if (node.isLeaf && node.numStars < MAX_STARS_PER_NODE) { + // Node is a leaf and it's not yet full -> insert star. + storeStarData(node, starValues); + + if (depth > _totalDepth) { + _totalDepth = depth; + } + return true; + } + else if (node.isLeaf) { + // Too many stars in leaf node, subdivide into 8 new nodes. + // Create children and clean up parent. + createNodeChildren(node); + + // Distribute stars from parent node into children. + for (size_t n = 0; n < MAX_STARS_PER_NODE; ++n) { + // Position data. + auto posBegin = node.posData.begin() + n * POS_SIZE; + auto posEnd = posBegin + POS_SIZE; + std::vector tmpValues(posBegin, posEnd); + // Color data. + auto colBegin = node.colData.begin() + n * COL_SIZE; + auto colEnd = colBegin + COL_SIZE; + tmpValues.insert(tmpValues.end(), colBegin, colEnd); + // Velocity data. + auto velBegin = node.velData.begin() + n * VEL_SIZE; + auto velEnd = velBegin + VEL_SIZE; + tmpValues.insert(tmpValues.end(), velBegin, velEnd); + + // Find out which child that will inherit the data and store it. + size_t index = getChildIndex( + tmpValues[0], + tmpValues[1], + tmpValues[2], + node.originX, + node.originY, + node.originZ + ); + insertInNode(*node.Children[index], tmpValues, depth); + } + + // Sort magnitudes in inner node. + // (The last value will be used as comparison for what to store in LOD cache.) + std::sort(node.magOrder.begin(), node.magOrder.end()); + } + + // Node is an inner node, keep recursion going. + // This will also take care of the new star when a subdivision has taken place. + size_t index = getChildIndex( + starValues[0], + starValues[1], + starValues[2], + node.originX, + node.originY, + node.originZ + ); + + // Determine if new star should be kept in our LOD cache. + // Keeps track of the brightest nodes in children. + if (starValues[POS_SIZE] < node.magOrder[MAX_STARS_PER_NODE - 1].first) { + storeStarData(node, starValues); + } + + return insertInNode(*node.Children[index], starValues, ++depth); +} + +void OctreeManager::sliceNodeLodCache(OctreeNode& node) { + // Slice stored LOD data in inner nodes. + if (!node.isLeaf) { + // Sort by magnitude. Inverse relation (i.e. a lower magnitude means a brighter + // star!) + std::sort(node.magOrder.begin(), node.magOrder.end()); + node.magOrder.resize(MAX_STARS_PER_NODE); + + std::vector tmpPos; + std::vector tmpCol; + std::vector tmpVel; + // Ordered map contain the MAX_STARS_PER_NODE brightest stars in all children! + for (auto const &[absMag, placement] : node.magOrder) { + auto posBegin = node.posData.begin() + placement * POS_SIZE; + auto colBegin = node.colData.begin() + placement * COL_SIZE; + auto velBegin = node.velData.begin() + placement * VEL_SIZE; + tmpPos.insert(tmpPos.end(), posBegin, posBegin + POS_SIZE); + tmpCol.insert(tmpCol.end(), colBegin, colBegin + COL_SIZE); + tmpVel.insert(tmpVel.end(), velBegin, velBegin + VEL_SIZE); + } + node.posData = std::move(tmpPos); + node.colData = std::move(tmpCol); + node.velData = std::move(tmpVel); + node.numStars = node.magOrder.size(); // = MAX_STARS_PER_NODE + + for (int i = 0; i < 8; ++i) { + sliceNodeLodCache(*node.Children[i]); + } + } +} + +void OctreeManager::storeStarData(OctreeNode& node, const std::vector& starValues) +{ + // Insert star data at the back of vectors and store a vector with pairs consisting of + // star magnitude and insert index for later sorting and slicing of LOD cache. + float mag = starValues[POS_SIZE]; + node.magOrder.insert(node.magOrder.end(), std::make_pair(mag, node.numStars)); + node.numStars++; + + // If LOD is growing too large then sort it and resize to [chunk size] to avoid too + // much RAM usage and increase threshold for adding new stars. + if (node.magOrder.size() > MAX_STARS_PER_NODE * 2) { + std::sort(node.magOrder.begin(), node.magOrder.end()); + node.magOrder.resize(MAX_STARS_PER_NODE); + } + + auto posEnd = starValues.begin() + POS_SIZE; + auto colEnd = posEnd + COL_SIZE; + node.posData.insert(node.posData.end(), starValues.begin(), posEnd); + node.colData.insert(node.colData.end(), posEnd, colEnd); + node.velData.insert(node.velData.end(), colEnd, starValues.end()); +} + +std::string OctreeManager::printStarsPerNode(const OctreeNode& node, + const std::string& prefix) const +{ + + // Print both inner and leaf nodes. + auto str = prefix + "} : " + std::to_string(node.numStars); + + if (node.isLeaf) { + return str + " - [Leaf] \n"; + } + else { + str += fmt::format("LOD: {} - [Parent]\n", node.posData.size() / POS_SIZE); + for (int i = 0; i < 8; ++i) { + auto pref = prefix + "->" + std::to_string(i); + str += printStarsPerNode(*node.Children[i], pref); + } + return str; + } +} + +std::map> OctreeManager::checkNodeIntersection(OctreeNode& node, + const glm::dmat4& mvp, + const glm::vec2& screenSize, + int& deltaStars, + gaia::RenderOption option) +{ + std::map> fetchedData; + //int depth = static_cast(log2( MAX_DIST / node->halfDimension )); + + // Calculate the corners of the node. + std::vector corners(8); + for (int i = 0; i < 8; ++i) { + const float x = (i % 2 == 0) ? + node.originX + node.halfDimension : + node.originX - node.halfDimension; + const float y = (i % 4 < 2) ? + node.originY + node.halfDimension : + node.originY - node.halfDimension; + const float z = (i < 4) ? + node.originZ + node.halfDimension : + node.originZ - node.halfDimension; + glm::dvec3 pos = glm::dvec3(x, y, z) * 1000.0 * distanceconstants::Parsec; + corners[i] = glm::dvec4(pos, 1.0); + } + + // Check if node is visible from camera. If not then return early. + if (!(_culler->isVisible(corners, mvp))) { + // Check if this node or any of its children existed in cache previously. + // If so, then remove them from cache and add those indices to stack. + fetchedData = removeNodeFromCache(node, deltaStars); + return fetchedData; + } + + // Remove node if it has been unloaded while still in view. + // (While streaming big datasets.) + if (node.bufferIndex != DEFAULT_INDEX && !node.isLoaded && _streamOctree && + !_datasetFitInMemory) + { + fetchedData = removeNodeFromCache(node, deltaStars); + return fetchedData; + } + + // Take care of inner nodes. + if (!(node.isLeaf)) { + glm::vec2 nodeSize = _culler->getNodeSizeInPixels(corners, mvp, screenSize); + float totalPixels = nodeSize.x * nodeSize.y; + + // Check if we should return any LOD cache data. If we're streaming a big dataset + // from files and inner node is visible and loaded, then it should be rendered + // (as long as it doesn't have loaded children because then we should traverse to + // lowest loaded level and render it instead)! + if ((totalPixels < _minTotalPixelsLod) || (_streamOctree && + !_datasetFitInMemory && node.isLoaded && !node.hasLoadedDescendant)) + { + // Get correct insert index from stack if node didn't exist already. Otherwise + // we will overwrite the old data. Key merging is not a problem here. + if ((node.bufferIndex == DEFAULT_INDEX) || _rebuildBuffer) { + // Return empty if we couldn't claim a buffer stream index. + if (!updateBufferIndex(node)) { + return fetchedData; + } + + // We're in an inner node, remove indices from potential children in cache + for (int i = 0; i < 8; ++i) { + std::map> tmpData = removeNodeFromCache( + *node.Children[i], + deltaStars + ); + fetchedData.insert(tmpData.begin(), tmpData.end()); + } + + // Insert data and adjust stars added in this frame. + fetchedData[node.bufferIndex] = constructInsertData( + node, + option, + deltaStars + ); + } + return fetchedData; + } + } + // Return node data if node is a leaf. + else { + // If node already is in cache then skip it, otherwise store it. + if ((node.bufferIndex == DEFAULT_INDEX) || _rebuildBuffer) { + // Return empty if we couldn't claim a buffer stream index. + if (!updateBufferIndex(node)) { + return fetchedData; + } + + // Insert data and adjust stars added in this frame. + fetchedData[node.bufferIndex] = constructInsertData( + node, + option, + deltaStars + ); + } + return fetchedData; + } + + // We're in a big, visible inner node -> remove it from cache if it existed. + // But not its children -> set recursive check to false. + fetchedData = removeNodeFromCache(node, deltaStars, false); + + // Recursively check if children should be rendered. + for (size_t i = 0; i < 8; ++i) { + // Observe that if there exists identical keys in fetchedData then those values in + // tmpData will be ignored! Thus we store the removed keys until next render call! + std::map> tmpData = checkNodeIntersection( + *node.Children[i], + mvp, + screenSize, + deltaStars, + option + ); + fetchedData.insert(tmpData.begin(), tmpData.end()); + } + return fetchedData; +} + +std::map> OctreeManager::removeNodeFromCache(OctreeNode& node, + int& deltaStars, + bool recursive) +{ + std::map> keysToRemove; + + // If we're in rebuilding mode then there is no need to remove any nodes. + //if (_rebuildBuffer) return keysToRemove; + + // Check if this node was rendered == had a specified index. + if (node.bufferIndex != DEFAULT_INDEX) { + + // Reclaim that index. We need to wait until next render call to use it again! + _removedKeysInPrevCall.insert(node.bufferIndex); + + // Insert dummy node at offset index that should be removed from render. + keysToRemove[node.bufferIndex] = std::vector(); + + // Reset index and adjust stars removed this frame. + node.bufferIndex = DEFAULT_INDEX; + deltaStars -= static_cast(node.numStars); + } + + // Check children recursively if we're in an inner node. + if (!(node.isLeaf) && recursive) { + for (int i = 0; i < 8; ++i) { + std::map> tmpData = removeNodeFromCache( + *node.Children[i], + deltaStars + ); + keysToRemove.insert(tmpData.begin(), tmpData.end()); + } + } + return keysToRemove; +} + +std::vector OctreeManager::getNodeData(const OctreeNode& node, + gaia::RenderOption option) +{ + // Return node data if node is a leaf. + if (node.isLeaf) { + int dStars = 0; + return constructInsertData(node, option, dStars); + } + + // If we're not in a leaf, get data from all children recursively. + auto nodeData = std::vector(); + for (size_t i = 0; i < 8; ++i) { + std::vector tmpData = getNodeData(*node.Children[i], option); + nodeData.insert(nodeData.end(), tmpData.begin(), tmpData.end()); + } + return nodeData; +} + +void OctreeManager::clearNodeData(OctreeNode& node) { + // Clear data and its allocated memory. + node.posData.clear(); + node.posData.shrink_to_fit(); + node.colData.clear(); + node.colData.shrink_to_fit(); + node.velData.clear(); + node.velData.shrink_to_fit(); + + // Clear magnitudes as well! + //std::vector>().swap(node->magOrder); + node.magOrder.clear(); + + if (!node.isLeaf) { + // Remove data from all children recursively. + for (size_t i = 0; i < 8; ++i) { + clearNodeData(*node.Children[i]); + } + } +} + +void OctreeManager::createNodeChildren(OctreeNode& node) { + for (size_t i = 0; i < 8; ++i) { + _numLeafNodes++; + node.Children[i] = std::make_shared(); + node.Children[i]->isLeaf = true; + node.Children[i]->isLoaded = false; + node.Children[i]->hasLoadedDescendant = false; + node.Children[i]->bufferIndex = DEFAULT_INDEX; + node.Children[i]->octreePositionIndex = (node.octreePositionIndex * 10) + i; + node.Children[i]->numStars = 0; + node.Children[i]->posData = std::vector(); + node.Children[i]->colData = std::vector(); + node.Children[i]->velData = std::vector(); + node.Children[i]->magOrder = std::vector>(); + node.Children[i]->halfDimension = node.halfDimension / 2.f; + + // Calculate new origin. + node.Children[i]->originX = node.originX; + node.Children[i]->originX += (i % 2 == 0) ? + node.Children[i]->halfDimension : + -node.Children[i]->halfDimension; + node.Children[i]->originY = node.originY; + node.Children[i]->originY += (i % 4 < 2) ? + node.Children[i]->halfDimension : + -node.Children[i]->halfDimension; + node.Children[i]->originZ = node.originZ; + node.Children[i]->originZ += (i < 4) ? + node.Children[i]->halfDimension : + -node.Children[i]->halfDimension; + } + + // Clean up parent. + node.isLeaf = false; + _numLeafNodes--; + _numInnerNodes++; +} + +bool OctreeManager::updateBufferIndex(OctreeNode& node) { + if (node.bufferIndex != DEFAULT_INDEX) { + // If we're rebuilding Buffer Index Cache then store indices to overwrite later. + _removedKeysInPrevCall.insert(node.bufferIndex); + } + + // Make sure node isn't loading/unloading as we're checking isLoaded flag. + std::lock_guard lock(node.loadingLock); + + // Return false if there are no more spots in our buffer, or if we're streaming and + // node isn't loaded yet, or if node doesn't have any stars. + if (_freeSpotsInBuffer.empty() || (_streamOctree && !node.isLoaded) || + node.numStars == 0) + { + return false; + } + + // Get correct insert index from stack. + node.bufferIndex = _freeSpotsInBuffer.top(); + _freeSpotsInBuffer.pop(); + + // Keep track of how many chunks are in use (ceiling). + if (_freeSpotsInBuffer.empty()) { + _biggestChunkIndexInUse++; + } + else if (_freeSpotsInBuffer.top() > _biggestChunkIndexInUse) { + _biggestChunkIndexInUse = _freeSpotsInBuffer.top(); + } + return true; +} + +std::vector OctreeManager::constructInsertData(const OctreeNode& node, + gaia::RenderOption option, + int& deltaStars) +{ + // Return early if node doesn't contain any stars! + if (node.numStars == 0) { + return std::vector(); + } + + // Fill chunk by appending zeroes to data so we overwrite possible earlier values. + // And more importantly so our attribute pointers knows where to read! + auto insertData = std::vector(node.posData.begin(), node.posData.end()); + if (_useVBO) { + insertData.resize(POS_SIZE * MAX_STARS_PER_NODE, 0.f); + } + if (option != gaia::RenderOption::Static) { + insertData.insert(insertData.end(), node.colData.begin(), node.colData.end()); + if (_useVBO) { + insertData.resize((POS_SIZE + COL_SIZE) * MAX_STARS_PER_NODE, 0.f); + } + if (option == gaia::RenderOption::Motion) { + insertData.insert(insertData.end(), node.velData.begin(), node.velData.end()); + if (_useVBO) { + insertData.resize( + (POS_SIZE + COL_SIZE + VEL_SIZE) * MAX_STARS_PER_NODE, 0.f + ); + } + } + } + + // Update deltaStars. + deltaStars += static_cast(node.numStars); + return insertData; +} + +} // namespace openspace diff --git a/modules/softwareintegration/rendering/octreemanager.h b/modules/softwareintegration/rendering/octreemanager.h new file mode 100644 index 0000000000..031e9e3630 --- /dev/null +++ b/modules/softwareintegration/rendering/octreemanager.h @@ -0,0 +1,387 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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_GAIA___OCTREEMANAGER___H__ +#define __OPENSPACE_MODULE_GAIA___OCTREEMANAGER___H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openspace { + +class OctreeCuller; + +class OctreeManager { +public: + struct OctreeNode { + std::shared_ptr Children[8]; + std::vector posData; + std::vector colData; + std::vector velData; + std::vector> magOrder; + float originX; + float originY; + float originZ; + float halfDimension; + size_t numStars; + bool isLeaf; + bool isLoaded; + bool hasLoadedDescendant; + std::mutex loadingLock; + int bufferIndex; + unsigned long long octreePositionIndex; + }; + + OctreeManager() = default; + ~OctreeManager() = default; + + /** + * Initializes a one layer Octree with root and 8 children that covers all stars. + * + * \param maxDist together with \param maxstarsPerNode (if defined) determines the + * depth of the tree as well as how many nodes will be created. + */ + void initOctree(long long cpuRamBudget = 0, int maxDist = 0, int maxStarsPerNode = 0); + + /** + * Initializes a stack of size \param maxNodes that keeps track of all free spot in + * buffer stream. Can be used to trigger a rebuild of buffer(s). + * + * \param useVBO defines if VBO or SSBO is used as buffer(s) + * \param datasetFitInMemory defines if streaming of nodes during runtime is used + */ + void initBufferIndexStack(long long maxNodes, bool useVBO, bool datasetFitInMemory); + + /** + * Inserts star values in correct position in Octree. Makes use of a recursive + * traversal strategy. Internally calls insertInNode() + */ + void insert(const std::vector& starValues); + + /** + * Slices LOD data so only the MAX_STARS_PER_NODE brightest stars are stored in inner + * nodes. If \p branchIndex is defined then only that branch will be sliced. + * Calls sliceNodeLodCache() internally. + */ + void sliceLodData(size_t branchIndex = 8); + + /** + * Prints the whole tree structure, including number of stars per node, number of + * nodes, tree depth and if node is a leaf. + * Calls printStarsPerNode(node, prefix) internally. + */ + void printStarsPerNode() const; + + /** + * Used while streaming nodes from files. Checks if any nodes need to be loaded or + * unloaded. If entire dataset fits in RAM then the whole dataset will be loaded + * asynchronously. Otherwise only nodes close to the camera will be fetched. + * When RAM stars to fill up least-recently used nodes will start to unload. + * Calls findAndFetchNeighborNode() and + * removeNodesFromRam() internally. + */ + void fetchSurroundingNodes(const glm::dvec3& cameraPos, size_t chunkSizeInBytes, + const glm::ivec2& additionalNodes); + + /** + * Builds render data structure by traversing the Octree and checking for intersection + * with view frustum. Every vector in map contains data for one node. + * The corresponding integer key is the index where chunk should be inserted into + * streaming buffer. Calls checkNodeIntersection() for every branch. + * \pdeltaStars keeps track of how many stars that were added/removed this render + * call. + */ + std::map> traverseData(const glm::dmat4& mvp, + const glm::vec2& screenSize, int& deltaStars, gaia::RenderOption option, + float lodPixelThreshold); + + /** + * Builds full render data structure by traversing all leaves in the Octree. + */ + std::vector getAllData(gaia::RenderOption option); + + /** + * Removes all data from Octree, or only from a specific branch if specified. + * \param branchIndex defined which branch to clear if defined. + */ + void clearAllData(int branchIndex = -1); + + /** + * Write entire Octree structure to a binary file. \param writeData defines if data + * should be included or if only structure should be written to the file. + * Calls writeNodeToFile() which recursively writes all nodes. + */ + void writeToFile(std::ofstream& outFileStream, bool writeData); + + /** + * Read a constructed Octree from a file. \returns the total number of (distinct) + * stars read. + * + * \param readData defines if full data or only structure should be read. + * Calls readNodeFromFile() which recursively reads all nodes. + */ + int readFromFile(std::ifstream& inFileStream, bool readData, + const std::string& folderPath = std::string()); + + /** + * Write specified part of Octree to multiple files, including all data. + * \param branchIndex defines which branch to write. + * Clears specified branch after writing is done. + * Calls writeNodeToMultipleFiles() for the specified branch. + */ + void writeToMultipleFiles(const std::string& outFolderPath, size_t branchIndex); + + /** + * Getters. + */ + size_t numLeafNodes() const; + size_t numInnerNodes() const; + size_t totalNodes() const; + size_t totalDepth() const; + size_t maxDist() const; + size_t maxStarsPerNode() const; + size_t biggestChunkIndexInUse() const; + size_t numFreeSpotsInBuffer() const; + bool isRebuildOngoing() const; + + /** + * \returns current CPU RAM budget in bytes. + */ + long long cpuRamBudget() const; + +private: + const size_t POS_SIZE = 3; + const size_t COL_SIZE = 2; + const size_t VEL_SIZE = 3; + + // MAX_DIST [kPc] - Determines the depth of Octree together with MAX_STARS_PER_NODE. + // A smaller distance is better (i.e. a smaller total depth) and a smaller MAX_STARS + // is also better (i.e. finer borders and fewer nodes/less data needs to be uploaded + // to the GPU), but MAX_STARS still needs to be big enough to be able to swallow all + // stars that falls outside of top border nodes, otherwise it causes a stack overflow + // when building Octree. However, fewer total nodes (i.e. bigger Stars/Node) reduces + // traversing time which is preferable, especially with big datasets + // DR1_TGAS [2M] - A MAX_DIST of 5 kPc works fine with down to 1 kSPN. + // DR1_full [1.2B] - A MAX_DIST of 10 kPc works fine with most SPN. + // DR2_rv [7.2M] - A MAX_DIST of 15 kPc works fine with down to 10 kSPN. + // DR2_subset [42.9M] - A MAX_DIST of 100 kPc works fine with 20 kSPN. + // DR2_full [1.7B] - A MAX_DIST of 250 kPc works fine with 150 kSPN. + size_t MAX_DIST = 2; // [kPc] + size_t MAX_STARS_PER_NODE = 2000; + + const int DEFAULT_INDEX = -1; + const std::string BINARY_SUFFIX = ".bin"; + + /** + * \returns the correct index of child node. Maps [1,1,1] to 0 and [-1,-1,-1] to 7. + */ + size_t getChildIndex(float posX, float posY, float posZ, float origX = 0.f, + float origY = 0.f, float origZ = 0.f); + + /** + * Private help function for insert(). Inserts star into node if leaf and + * numStars < MAX_STARS_PER_NODE. If a leaf goes above the threshold it is subdivided + * into 8 new nodes. + * If node is an inner node, then star is stores in LOD cache if it is among the + * brightest stars in all children. + */ + bool insertInNode(OctreeNode& node, const std::vector& starValues, + int depth = 1); + + /** + * Slices LOD cache data in node to the MAX_STARS_PER_NODE brightest stars. This needs + * to be called after the last star has been inserted into Octree but before it is + * saved to file(s). Slices all descendants recursively. + */ + void sliceNodeLodCache(OctreeNode& node); + + /** + * Private help function for insertInNode(). Stores star data in node and + * keeps track of the brightest stars all children. + */ + void storeStarData(OctreeNode& node, const std::vector& starValues); + + /** + * Private help function for printStarsPerNode(). \returns an accumulated + * string containing all descendant nodes. + */ + std::string printStarsPerNode(const OctreeNode& node, + const std::string& prefix) const; + + /** + * Private help function for traverseData(). Recursively checks which + * nodes intersect with the view frustum (interpreted as an AABB) and decides if data + * should be optimized away or not. Keeps track of which nodes that are visible and + * loaded (if streaming). \param deltaStars keeps track of how many stars that were + * added/removed this render call. + */ + std::map> checkNodeIntersection(OctreeNode& node, + const glm::dmat4& mvp, const glm::vec2& screenSize, int& deltaStars, + gaia::RenderOption option); + + /** + * Checks if specified node existed in cache, and removes it if that's the case. + * If node is an inner node then all children will be checked recursively as well as + * long as \param recursive is not set to false. \param deltaStars keeps track of how + * many stars that were removed. + */ + std::map> removeNodeFromCache(OctreeNode& node, + int& deltaStars, bool recursive = true); + + /** + * Get data in node and its descendants regardless if they are visible or not. + */ + std::vector getNodeData(const OctreeNode& node, gaia::RenderOption option); + + /** + * Clear data from node and its descendants and shrink vectors to deallocate memory. + */ + void clearNodeData(OctreeNode& node); + + /** + * Contruct default children nodes for specified node. + */ + void createNodeChildren(OctreeNode& node); + + /** + * Checks if node should be inserted into stream or not. \returns true if it should, + * (i.e. it doesn't already exists, there is room for it in the buffer and node data + * is loaded if streaming). \returns false otherwise. + */ + bool updateBufferIndex(OctreeNode& node); + + /** + * Node should be inserted into stream. This function \returns the data to be + * inserted. If VBOs are used then the chunks will be appended by zeros, otherwise + * only the star data corresponding to RenderOption \param option will be inserted. + * + * \param deltaStars keeps track of how many stars that were added. + */ + std::vector constructInsertData(const OctreeNode& node, + gaia::RenderOption option, int& deltaStars); + + /** + * Write a node to outFileStream. \param writeData defines if data should be included + * or if only structure should be written. + */ + void writeNodeToFile(std::ofstream& outFileStream, const OctreeNode& node, + bool writeData); + + /** + * Read a node from file and its potential children. \param readData defines if full + * data or only structure should be read. + * \returns accumulated sum of all read stars in node and its descendants. + */ + int readNodeFromFile(std::ifstream& inFileStream, OctreeNode& node, bool readData); + + /** + * Write node data to a file. \param outFilePrefix specifies the accumulated path + * and name of the file. If \param threadWrites is set to true then one new thread + * will be created for each child to write its descendents. + */ + void writeNodeToMultipleFiles(const std::string& outFilePrefix, OctreeNode& node, + bool threadWrites); + + /** + * Finds the neighboring node on the same level (or a higher level if there is no + * corresponding level) in the specified direction. Also fetches data from found node + * if it's not already loaded. \param additionalLevelsToFetch determines if any + * descendants of the found node should be fetched as well (if they exists). + */ + void findAndFetchNeighborNode(unsigned long long firstParentId, int x, int y, int z, + int additionalLevelsToFetch); + + /** + * Fetches data from all children of \param parentNode, as long as it's not already + * fetched, it exists and it can fit in RAM. + * \param additionalLevelsToFetch determines how many levels of descendants to fetch. + * If it is set to 0 no additional level will be fetched. + * If it is set to a negative value then all descendants will be fetched recursively. + * Calls fetchNodeDataFromFile() for every child that passes the tests. + */ + void fetchChildrenNodes(OctreeNode& parentNode, int additionalLevelsToFetch); + + /** + * Fetches data for specified node from file. + * OBS! Only call if node file exists (i.e. node has any data, node->numStars > 0) + * and is not already loaded. + */ + void fetchNodeDataFromFile(OctreeNode& node); + + /** + * Loops though all nodes in \param nodesToRemove and clears them from RAM. + * Also checks if any ancestor should change the hasLoadedDescendant flag + * by calling propagateUnloadedNodes() with all ancestors. + */ + void removeNodesFromRam(const std::vector& nodesToRemove); + + /** + * Removes data in specified node from main memory and updates RAM budget and flags + * accordingly. + */ + void removeNode(OctreeNode& node); + + /** + * Loops through \param ancestorNodes backwards and checks if parent node has any + * loaded descendants left. If not, then flag hasLoadedDescendant will be + * set to false for that parent node and next parent in line will be checked. + */ + void propagateUnloadedNodes(std::vector> ancestorNodes); + + std::shared_ptr _root; + std::unique_ptr _culler; + std::stack _freeSpotsInBuffer; + std::set _removedKeysInPrevCall; + std::queue _leastRecentlyFetchedNodes; + std::mutex _leastRecentlyFetchedNodesMutex; + + size_t _totalDepth = 0; + size_t _numLeafNodes = 0; + size_t _numInnerNodes = 0; + size_t _biggestChunkIndexInUse = 0; + size_t _valuesPerStar = 0; + float _minTotalPixelsLod = 0.f; + + size_t _maxStackSize = 0; + bool _rebuildBuffer = false; + bool _useVBO = false; + bool _streamOctree = false; + bool _datasetFitInMemory = false; + long long _cpuRamBudget = 0; + long long _maxCpuRamBudget = 0; + unsigned long long _parentNodeOfCamera = 8; + std::string _streamFolderPath; + size_t _traversedBranchesInRenderCall = 0; + +}; // class OctreeManager + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_GAIA___OCTREEMANAGER___H__ diff --git a/modules/softwareintegration/rendering/renderablegaiastars.cpp b/modules/softwareintegration/rendering/renderablegaiastars.cpp new file mode 100644 index 0000000000..a67a1d17c2 --- /dev/null +++ b/modules/softwareintegration/rendering/renderablegaiastars.cpp @@ -0,0 +1,2522 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* _loggerCat = "RenderableGaiaStars"; + + constexpr size_t PositionSize = 3; + constexpr size_t ColorSize = 2; + constexpr size_t VelocitySize = 3; + + constexpr openspace::properties::Property::PropertyInfo FilePathInfo = { + "File", + "File Path", + "The path to the file with data for the stars to be rendered." + }; + + constexpr openspace::properties::Property::PropertyInfo FileReaderOptionInfo = { + "FileReaderOption", + "File Reader Option", + "This value tells the renderable what format the input data file has. " + "'Fits' will read a FITS file, construct an Octree from it and render full " + "data. 'Speck' will read a SPECK file, construct an Octree from it and render " + "full data. 'BinaryRaw' will read a preprocessed binary file with ordered star " + "data, construct an Octree and render it. 'BinaryOctree' will read a constructed " + "Octree from binary file and render full data. 'StreamOctree' will read an index " + "file with full Octree structure and then stream nodes during runtime. (This " + "option is suited for bigger datasets.)" + }; + + constexpr openspace::properties::Property::PropertyInfo RenderOptionInfo = { + "RenderOption", + "Render Option", + "This value determines which predefined columns to use in rendering. If " + "'Static' only the position of the stars is used. 'Color' uses position + color " + "parameters and 'Motion' uses pos, color as well as velocity for the stars." + }; + + constexpr openspace::properties::Property::PropertyInfo ShaderOptionInfo = { + "ShaderOption", + "Shader Option", + "This value determines which shaders to use while rendering. If 'Point_*' is " + "chosen then gl_Points will be rendered and then spread out with a bloom " + "filter. If 'Billboard_*' is chosen then the geometry shaders will generate " + "screen-faced billboards for all stars. For '*_SSBO' the data will be stored in " + "Shader Storage Buffer Objects while '*_VBO' uses Vertex Buffer Objects for the " + "streaming. OBS! SSBO won't work on APPLE!" + }; + + constexpr openspace::properties::Property::PropertyInfo PsfTextureInfo = { + "Texture", + "Point Spread Function Texture", + "The path to the texture that should be used as a point spread function for the " + "stars." + }; + + constexpr openspace::properties::Property::PropertyInfo LuminosityMultiplierInfo = { + "LuminosityMultiplier", + "Luminosity Multiplier", + "Factor by which to multiply the luminosity with. [Works in Color and Motion " + "modes]" + }; + + constexpr openspace::properties::Property::PropertyInfo MagnitudeBoostInfo = { + "MagnitudeBoost", + "Magnitude Boost", + "Sets what percent of the star magnitude that will be used as boost to star " + "size. [Works only with billboards in Color and Motion modes]" + }; + + constexpr openspace::properties::Property::PropertyInfo CutOffThresholdInfo = { + "CutOffThreshold", + "Cut Off Threshold", + "Set threshold for when to cut off star rendering. " + "Stars closer than this threshold are given full opacity. " + "Farther away, stars dim proportionally to the 4-logarithm of their distance." + }; + + constexpr openspace::properties::Property::PropertyInfo SharpnessInfo = { + "Sharpness", + "Sharpness", + "Adjust star sharpness. [Works only with billboards]" + }; + + constexpr openspace::properties::Property::PropertyInfo BillboardSizeInfo = { + "BillboardSize", + "Billboard Size", + "Set the billboard size of all stars. [Works only with billboards]" + }; + + constexpr openspace::properties::Property::PropertyInfo CloseUpBoostDistInfo = { + "CloseUpBoostDist", + "Close-Up Boost Distance [pc]", + "Set the distance where stars starts to increase in size. Unit is Parsec." + "[Works only with billboards]" + }; + + constexpr openspace::properties::Property::PropertyInfo TmPointFilterSizeInfo = { + "FilterSize", + "Filter Size [px]", + "Set the filter size in pixels used in tonemapping for point splatting rendering." + "[Works only with points]" + }; + + constexpr openspace::properties::Property::PropertyInfo TmPointSigmaInfo = { + "Sigma", + "Normal Distribution Sigma", + "Set the normal distribution sigma used in tonemapping for point splatting " + "rendering. [Works only with points]" + }; + + constexpr openspace::properties::Property::PropertyInfo AdditionalNodesInfo = { + "AdditionalNodes", + "Additional Nodes", + "Determines how many additional nodes around the camera that will be fetched " + "from disk. The first value determines how many additional layers of parents " + "that will be fetched. The second value determines how many layers of descendant " + "that will be fetched from the found parents." + }; + + constexpr openspace::properties::Property::PropertyInfo TmPointPxThresholdInfo = { + "PixelWeightThreshold", + "Pixel Weight Threshold", + "Set the threshold for how big the elliptic weight of a pixel has to be to " + "contribute to the final elliptic shape. A smaller value gives a more visually " + "pleasing result while a bigger value will speed up the rendering on skewed " + "frustums (aka Domes)." + }; + + constexpr openspace::properties::Property::PropertyInfo ColorTextureInfo = { + "ColorMap", + "Color Texture", + "The path to the texture that is used to convert from the magnitude of the star " + "to its color. The texture is used as a one dimensional lookup function." + }; + + constexpr openspace::properties::Property::PropertyInfo FirstRowInfo = { + "FirstRow", + "First Row to Read", + "Defines the first row that will be read from the specified FITS file." + "No need to define if data already has been processed." + "[Works only with FileReaderOption::Fits]" + }; + + constexpr openspace::properties::Property::PropertyInfo LastRowInfo = { + "LastRow", + "Last Row to Read", + "Defines the last row that will be read from the specified FITS file." + "Has to be equal to or greater than FirstRow. No need to define if " + "data already has been processed." + "[Works only with FileReaderOption::Fits]" + }; + + constexpr openspace::properties::Property::PropertyInfo ColumnNamesInfo = { + "ColumnNames", + "Column Names", + "A list of strings with the names of all the columns that are to be " + "read from the specified FITS file. No need to define if data already " + "has been processed." + "[Works only with FileReaderOption::Fits]" + }; + + constexpr openspace::properties::Property::PropertyInfo NumRenderedStarsInfo = { + "NumRenderedStars", + "Rendered Stars", + "The number of rendered stars in the current frame." + }; + + constexpr openspace::properties::Property::PropertyInfo CpuRamBudgetInfo = { + "CpuRamBudget", + "CPU RAM Budget", + "Current remaining budget (bytes) on the CPU RAM for loading more node data " + "files." + }; + + constexpr openspace::properties::Property::PropertyInfo GpuStreamBudgetInfo = { + "GpuStreamBudget", + "GPU Stream Budget", + "Current remaining memory budget [in number of chunks] on the GPU for streaming " + "additional stars." + }; + + constexpr openspace::properties::Property::PropertyInfo LodPixelThresholdInfo = { + "LodPixelThreshold", + "LOD Pixel Threshold", + "The number of total pixels a nodes AABB can have in clipping space before its " + "parent is fetched as LOD cache." + }; + + constexpr openspace::properties::Property::PropertyInfo MaxGpuMemoryPercentInfo = { + "MaxGpuMemoryPercent", + "Max GPU Memory", + "Sets the max percent of existing GPU memory budget that the streaming will use." + }; + + constexpr openspace::properties::Property::PropertyInfo MaxCpuMemoryPercentInfo = { + "MaxCpuMemoryPercent", + "Max CPU Memory", + "Sets the max percent of existing CPU memory budget that the streaming of files " + "will use." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterPosXInfo = { + "FilterPosX", + "PosX Threshold", + "If defined then only stars with Position X values between [min, max] " + "will be rendered (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). Measured in kiloParsec." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterPosYInfo = { + "FilterPosY", + "PosY Threshold", + "If defined then only stars with Position Y values between [min, max] " + "will be rendered (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). Measured in kiloParsec." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterPosZInfo = { + "FilterPosZ", + "PosZ Threshold", + "If defined then only stars with Position Z values between [min, max] " + "will be rendered (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). Measured in kiloParsec." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterGMagInfo = { + "FilterGMag", + "GMag Threshold", + "If defined then only stars with G mean magnitude values between [min, max] " + "will be rendered (if min is set to 20.0 it is read as -Inf, " + "if max is set to 20.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterBpRpInfo = { + "FilterBpRp", + "Bp-Rp Threshold", + "If defined then only stars with Bp-Rp color values between [min, max] " + "will be rendered (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }; + + constexpr openspace::properties::Property::PropertyInfo FilterDistInfo = { + "FilterDist", + "Dist Threshold", + "If defined then only stars with Distances values between [min, max] " + "will be rendered (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). Measured in kParsec." + }; + + constexpr openspace::properties::Property::PropertyInfo ReportGlErrorsInfo = { + "ReportGlErrors", + "Report GL Errors", + "If set to true, any OpenGL errors will be reported if encountered" + }; +} // namespace + +namespace openspace { + +documentation::Documentation RenderableGaiaStars::Documentation() { + using namespace documentation; + return { + "RenderableGaiaStars", + "gaiamission_renderablegaiastars", + { + { + "Type", + new StringEqualVerifier("RenderableGaiaStars"), + Optional::No + }, + { + FilePathInfo.identifier, + new StringVerifier, + Optional::No, + FilePathInfo.description + }, + { + FileReaderOptionInfo.identifier, + new StringInListVerifier({ + "Fits", "Speck", "BinaryRaw", "BinaryOctree", "StreamOctree" + }), + Optional::No, + FileReaderOptionInfo.description + }, + { + RenderOptionInfo.identifier, + new StringInListVerifier({ + "Static", "Color", "Motion" + }), + Optional::Yes, + RenderOptionInfo.description + }, + { + ShaderOptionInfo.identifier, + new StringInListVerifier({ + "Point_SSBO", "Point_VBO", "Billboard_SSBO", "Billboard_VBO", + "Billboard_SSBO_noFBO" + }), + Optional::Yes, + ShaderOptionInfo.description + }, + { + PsfTextureInfo.identifier, + new StringVerifier, + Optional::No, + PsfTextureInfo.description + }, + { + ColorTextureInfo.identifier, + new StringVerifier, + Optional::No, + ColorTextureInfo.description + }, + { + LuminosityMultiplierInfo.identifier, + new DoubleVerifier, + Optional::Yes, + LuminosityMultiplierInfo.description + }, + { + MagnitudeBoostInfo.identifier, + new DoubleVerifier, + Optional::Yes, + MagnitudeBoostInfo.description + }, + { + CutOffThresholdInfo.identifier, + new DoubleVerifier, + Optional::Yes, + CutOffThresholdInfo.description + }, + { + SharpnessInfo.identifier, + new DoubleVerifier, + Optional::Yes, + SharpnessInfo.description + }, + { + BillboardSizeInfo.identifier, + new DoubleVerifier, + Optional::Yes, + BillboardSizeInfo.description + }, + { + CloseUpBoostDistInfo.identifier, + new DoubleVerifier, + Optional::Yes, + CloseUpBoostDistInfo.description + }, + { + TmPointFilterSizeInfo.identifier, + new IntVerifier, + Optional::Yes, + TmPointFilterSizeInfo.description + }, + { + TmPointSigmaInfo.identifier, + new DoubleVerifier, + Optional::Yes, + TmPointSigmaInfo.description + }, + { + AdditionalNodesInfo.identifier, + new Vector2Verifier, + Optional::Yes, + AdditionalNodesInfo.description + }, + { + TmPointPxThresholdInfo.identifier, + new DoubleVerifier, + Optional::Yes, + TmPointPxThresholdInfo.description + }, + { + FirstRowInfo.identifier, + new IntVerifier, + Optional::Yes, + FirstRowInfo.description + }, + { + LastRowInfo.identifier, + new IntVerifier, + Optional::Yes, + LastRowInfo.description + }, + { + ColumnNamesInfo.identifier, + new StringListVerifier, + Optional::Yes, + ColumnNamesInfo.description + }, + { + LodPixelThresholdInfo.identifier, + new DoubleVerifier, + Optional::Yes, + LodPixelThresholdInfo.description + }, + { + MaxGpuMemoryPercentInfo.identifier, + new DoubleVerifier, + Optional::Yes, + MaxGpuMemoryPercentInfo.description + }, + { + MaxCpuMemoryPercentInfo.identifier, + new DoubleVerifier, + Optional::Yes, + MaxCpuMemoryPercentInfo.description + }, + { + FilterPosXInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterPosXInfo.description + }, + { + FilterPosYInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterPosYInfo.description + }, + { + FilterPosZInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterPosZInfo.description + }, + { + FilterGMagInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterGMagInfo.description + }, + { + FilterBpRpInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterBpRpInfo.description + }, + { + FilterDistInfo.identifier, + new Vector2Verifier, + Optional::Yes, + FilterDistInfo.description + }, + { + ReportGlErrorsInfo.identifier, + new BoolVerifier, + Optional::Yes, + ReportGlErrorsInfo.description + } + } + }; +} + +RenderableGaiaStars::RenderableGaiaStars(const ghoul::Dictionary& dictionary) + : Renderable(dictionary) + , _filePath(FilePathInfo) + , _fileReaderOption( + FileReaderOptionInfo, + properties::OptionProperty::DisplayType::Dropdown + ) + , _renderOption(RenderOptionInfo, properties::OptionProperty::DisplayType::Dropdown) + , _shaderOption(ShaderOptionInfo, properties::OptionProperty::DisplayType::Dropdown) + , _pointSpreadFunctionTexturePath(PsfTextureInfo) + , _colorTexturePath(ColorTextureInfo) + , _luminosityMultiplier(LuminosityMultiplierInfo, 35.f, 1.f, 1000.f) + , _magnitudeBoost(MagnitudeBoostInfo, 25.f, 0.f, 100.f) + , _cutOffThreshold(CutOffThresholdInfo, 38.f, 0.f, 50.f) + , _sharpness(SharpnessInfo, 1.45f, 0.f, 5.f) + , _billboardSize(BillboardSizeInfo, 10.f, 1.f, 100.f) + , _closeUpBoostDist(CloseUpBoostDistInfo, 300.f, 1.f, 1000.f) + , _tmPointFilterSize(TmPointFilterSizeInfo, 7, 1, 19) + , _tmPointSigma(TmPointSigmaInfo, 0.7f, 0.1f, 3.f) + , _additionalNodes(AdditionalNodesInfo, glm::ivec2(1), glm::ivec2(0), glm::ivec2(4)) + , _tmPointPixelWeightThreshold(TmPointPxThresholdInfo, 0.001f, 0.000001f, 0.01f) + , _lodPixelThreshold(LodPixelThresholdInfo, 250.f, 0.f, 5000.f) + , _maxGpuMemoryPercent(MaxGpuMemoryPercentInfo, 0.45f, 0.f, 1.f) + , _maxCpuMemoryPercent(MaxCpuMemoryPercentInfo, 0.5f, 0.f, 1.f) + , _posXThreshold(FilterPosXInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) + , _posYThreshold(FilterPosYInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) + , _posZThreshold(FilterPosZInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) + , _gMagThreshold(FilterGMagInfo, glm::vec2(20.f), glm::vec2(-10.f), glm::vec2(30.f)) + , _bpRpThreshold(FilterBpRpInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(30.f)) + , _distThreshold(FilterDistInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(100.f)) + , _firstRow(FirstRowInfo, 0, 0, 2539913) // DR1-max: 2539913 + , _lastRow(LastRowInfo, 0, 0, 2539913) + , _columnNamesList(ColumnNamesInfo) + , _nRenderedStars(NumRenderedStarsInfo, 0, 0, 2000000000) // 2 Billion stars + , _cpuRamBudgetProperty(CpuRamBudgetInfo, 0.f, 0.f, 1.f) + , _gpuStreamBudgetProperty(GpuStreamBudgetInfo, 0.f, 0.f, 1.f) + , _reportGlErrors(ReportGlErrorsInfo, false) + , _accumulatedIndices(1, 0) +{ + using File = ghoul::filesystem::File; + + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "RenderableGaiaStars" + ); + + _filePath = absPath(dictionary.value(FilePathInfo.identifier)); + _dataFile = std::make_unique(_filePath); + _dataFile->setCallback([&](const File&) { _dataIsDirty = true; }); + + _filePath.onChange([&]() { _dataIsDirty = true; }); + addProperty(_filePath); + + _fileReaderOption.addOptions({ + { gaia::FileReaderOption::Fits, "Fits" }, + { gaia::FileReaderOption::Speck, "Speck" }, + { gaia::FileReaderOption::BinaryRaw, "BinaryRaw" }, + { gaia::FileReaderOption::BinaryOctree, "BinaryOctree" }, + { gaia::FileReaderOption::StreamOctree, "StreamOctree" } + }); + if (dictionary.hasKey(FileReaderOptionInfo.identifier)) { + const std::string fileReaderOption = dictionary.value( + FileReaderOptionInfo.identifier + ); + if (fileReaderOption == "Fits") { + _fileReaderOption = gaia::FileReaderOption::Fits; + } + else if (fileReaderOption == "Speck") { + _fileReaderOption = gaia::FileReaderOption::Speck; + } + else if (fileReaderOption == "BinaryRaw") { + _fileReaderOption = gaia::FileReaderOption::BinaryRaw; + } + else if (fileReaderOption == "BinaryOctree") { + _fileReaderOption = gaia::FileReaderOption::BinaryOctree; + } + else { + _fileReaderOption = gaia::FileReaderOption::StreamOctree; + } + } + + _renderOption.addOptions({ + { gaia::RenderOption::Static, "Static" }, + { gaia::RenderOption::Color, "Color" }, + { gaia::RenderOption::Motion, "Motion" } + }); + if (dictionary.hasKey(RenderOptionInfo.identifier)) { + const std::string renderOption = dictionary.value( + RenderOptionInfo.identifier + ); + if (renderOption == "Static") { + _renderOption = gaia::RenderOption::Static; + } + else if (renderOption == "Color") { + _renderOption = gaia::RenderOption::Color; + } + else { + _renderOption = gaia::RenderOption::Motion; + } + } + _renderOption.onChange([&]() { _buffersAreDirty = true; }); + addProperty(_renderOption); + +#ifndef __APPLE__ + _shaderOption.addOptions({ + { gaia::ShaderOption::Point_SSBO, "Point_SSBO" }, + { gaia::ShaderOption::Point_VBO, "Point_VBO" }, + { gaia::ShaderOption::Billboard_SSBO, "Billboard_SSBO" }, + { gaia::ShaderOption::Billboard_VBO, "Billboard_VBO" }, + { gaia::ShaderOption::Billboard_SSBO_noFBO, "Billboard_SSBO_noFBO" } + }); +#else // __APPLE__ + _shaderOption.addOptions({ + { gaia::ShaderOption::Point_VBO, "Point_VBO" }, + { gaia::ShaderOption::Billboard_VBO, "Billboard_VBO" }, + }); +#endif // __APPLE__ + + if (dictionary.hasKey(ShaderOptionInfo.identifier)) { + // Default shader option: + _shaderOption = gaia::ShaderOption::Billboard_VBO; + + const std::string shaderOption = dictionary.value( + ShaderOptionInfo.identifier + ); + +#ifndef __APPLE__ + if (shaderOption == "Point_SSBO") { + _shaderOption = gaia::ShaderOption::Point_SSBO; + } + else if (shaderOption == "Billboard_SSBO") { + _shaderOption = gaia::ShaderOption::Billboard_SSBO; + } + else if (shaderOption == "Billboard_SSBO_noFBO") { + _shaderOption = gaia::ShaderOption::Billboard_SSBO_noFBO; + } +#endif // __APPLE__ + + if (shaderOption == "Point_VBO") { + _shaderOption = gaia::ShaderOption::Point_VBO; + } + else if (shaderOption == "Billboard_VBO") { + _shaderOption = gaia::ShaderOption::Billboard_VBO; + } + } + _shaderOption.onChange([&]() { + _buffersAreDirty = true; + _shadersAreDirty = true; + }); + addProperty(_shaderOption); + + _pointSpreadFunctionTexturePath = absPath(dictionary.value( + PsfTextureInfo.identifier + )); + _pointSpreadFunctionFile = std::make_unique(_pointSpreadFunctionTexturePath); + + _pointSpreadFunctionTexturePath.onChange( + [&](){ _pointSpreadFunctionTextureIsDirty = true; } + ); + _pointSpreadFunctionFile->setCallback( + [&](const File&) { _pointSpreadFunctionTextureIsDirty = true; } + ); + + _colorTexturePath = absPath(dictionary.value( + ColorTextureInfo.identifier + )); + _colorTextureFile = std::make_unique(_colorTexturePath); + _colorTexturePath.onChange([&]() { _colorTextureIsDirty = true; }); + _colorTextureFile->setCallback([&](const File&) { _colorTextureIsDirty = true; }); + + if (dictionary.hasKey(LuminosityMultiplierInfo.identifier)) { + _luminosityMultiplier = static_cast( + dictionary.value(LuminosityMultiplierInfo.identifier) + ); + } + + if (dictionary.hasKey(MagnitudeBoostInfo.identifier)) { + _magnitudeBoost = static_cast( + dictionary.value(MagnitudeBoostInfo.identifier) + ); + } + + if (dictionary.hasKey(CutOffThresholdInfo.identifier)) { + _cutOffThreshold = static_cast( + dictionary.value(CutOffThresholdInfo.identifier) + ); + } + + if (dictionary.hasKey(SharpnessInfo.identifier)) { + _sharpness = static_cast( + dictionary.value(SharpnessInfo.identifier) + ); + } + + if (dictionary.hasKey(BillboardSizeInfo.identifier)) { + _billboardSize = static_cast( + dictionary.value(BillboardSizeInfo.identifier) + ); + } + + if (dictionary.hasKey(CloseUpBoostDistInfo.identifier)) { + _closeUpBoostDist = static_cast( + dictionary.value(CloseUpBoostDistInfo.identifier) + ); + } + + if (dictionary.hasKey(TmPointFilterSizeInfo.identifier)) { + _tmPointFilterSize = static_cast( + dictionary.value(TmPointFilterSizeInfo.identifier) + ); + } + + if (dictionary.hasKey(TmPointSigmaInfo.identifier)) { + _tmPointSigma = static_cast( + dictionary.value(TmPointSigmaInfo.identifier) + ); + } + if (dictionary.hasKey(TmPointPxThresholdInfo.identifier)) { + _tmPointPixelWeightThreshold = static_cast( + dictionary.value(TmPointPxThresholdInfo.identifier) + ); + } + + if (dictionary.hasKey(AdditionalNodesInfo.identifier)) { + _additionalNodes = static_cast( + dictionary.value(AdditionalNodesInfo.identifier) + ); + } + + if (dictionary.hasKey(LodPixelThresholdInfo.identifier)) { + _lodPixelThreshold = static_cast( + dictionary.value(LodPixelThresholdInfo.identifier) + ); + } + + if (dictionary.hasKey(MaxGpuMemoryPercentInfo.identifier)) { + _maxGpuMemoryPercent = static_cast( + dictionary.value(MaxGpuMemoryPercentInfo.identifier) + ); + } + _maxGpuMemoryPercent.onChange([&]() { + if (_ssboData != 0) { + glDeleteBuffers(1, &_ssboData); + glGenBuffers(1, &_ssboData); + LDEBUG(fmt::format( + "Re-generating Data Shader Storage Buffer Object id '{}'", _ssboData + )); + } + + // Find out our new budget. Use dedicated video memory instead of current + // available to always be consistant with previous call(s). + GLint nDedicatedVidMemoryInKB = 0; + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nDedicatedVidMemoryInKB); + float dedicatedVidMem = static_cast( + static_cast(nDedicatedVidMemoryInKB) * 1024 + ); + + // TODO: Need to fix what happens if we can't query! For now use 2 GB by default. + _gpuMemoryBudgetInBytes = dedicatedVidMem > 0 ? + static_cast(dedicatedVidMem * _maxGpuMemoryPercent) : + 2147483648; + _buffersAreDirty = true; + _maxStreamingBudgetInBytes = 0; + }); + + if (dictionary.hasKey(MaxCpuMemoryPercentInfo.identifier)) { + _maxCpuMemoryPercent = static_cast( + dictionary.value(MaxCpuMemoryPercentInfo.identifier) + ); + } + + if (dictionary.hasKey(FilterPosXInfo.identifier)) { + _posXThreshold = dictionary.value(FilterPosXInfo.identifier); + } + addProperty(_posXThreshold); + + if (dictionary.hasKey(FilterPosYInfo.identifier)) { + _posXThreshold = dictionary.value(FilterPosYInfo.identifier); + } + addProperty(_posYThreshold); + + if (dictionary.hasKey(FilterPosZInfo.identifier)) { + _posZThreshold = dictionary.value(FilterPosZInfo.identifier); + } + addProperty(_posZThreshold); + + if (dictionary.hasKey(FilterGMagInfo.identifier)) { + _gMagThreshold = dictionary.value(FilterGMagInfo.identifier); + } + addProperty(_gMagThreshold); + + if (dictionary.hasKey(FilterBpRpInfo.identifier)) { + _bpRpThreshold = dictionary.value(FilterBpRpInfo.identifier); + } + addProperty(_bpRpThreshold); + + if (dictionary.hasKey(FilterDistInfo.identifier)) { + _distThreshold = dictionary.value(FilterDistInfo.identifier); + } + addProperty(_distThreshold); + + // Only add properties correlated to fits files if we're reading from a fits file. + if (_fileReaderOption == gaia::FileReaderOption::Fits) { + if (dictionary.hasKey(FirstRowInfo.identifier)) { + _firstRow = static_cast( + dictionary.value(FirstRowInfo.identifier) + ); + } + _firstRow.onChange([&]() { _dataIsDirty = true; }); + addProperty(_firstRow); + + if (dictionary.hasKey(LastRowInfo.identifier)) { + _lastRow = static_cast(dictionary.value(LastRowInfo.identifier)); + } + _lastRow.onChange([&]() { _dataIsDirty = true; }); + addProperty(_lastRow); + + if (dictionary.hasKey(ColumnNamesInfo.identifier)) { + ghoul::Dictionary tmpDict = dictionary.value( + ColumnNamesInfo.identifier + ); + + // Ugly fix for ASCII sorting when there are more columns read than 10. + std::set intKeys; + for (const std::string& key : tmpDict.keys()) { + intKeys.insert(std::stoi(key)); + } + + for (int key : intKeys) { + _columnNames.push_back(tmpDict.value(std::to_string(key))); + } + + // Copy values to the StringListproperty to be shown in the Property list. + _columnNamesList = _columnNames; + // OBS - This is not used atm! + } + + if (_firstRow > _lastRow) { + throw ghoul::RuntimeError("User defined FirstRow is bigger than LastRow."); + } + } + + if (dictionary.hasKey(ReportGlErrorsInfo.identifier)) { + _reportGlErrors = dictionary.value(ReportGlErrorsInfo.identifier); + } + addProperty(_reportGlErrors); + + // Add a read-only property for the number of rendered stars per frame. + _nRenderedStars.setReadOnly(true); + addProperty(_nRenderedStars); + + // Add CPU RAM Budget Property and GPU Stream Budget Property to menu. + _cpuRamBudgetProperty.setReadOnly(true); + addProperty(_cpuRamBudgetProperty); + _gpuStreamBudgetProperty.setReadOnly(true); + addProperty(_gpuStreamBudgetProperty); +} + +bool RenderableGaiaStars::isReady() const { + return _program && _programTM; +} + +void RenderableGaiaStars::initializeGL() { + //using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; + //_program->setIgnoreUniformLocationError(IgnoreError::Yes); + + // Add common properties to menu. + addProperty(_colorTexturePath); + addProperty(_luminosityMultiplier); + addProperty(_cutOffThreshold); + addProperty(_lodPixelThreshold); + addProperty(_maxGpuMemoryPercent); + + // Construct shader program depending on user-defined shader option. + const int option = _shaderOption; + switch (option) { + case gaia::ShaderOption::Point_SSBO: { + _program = ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") + ); + _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); + _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); + _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); + + _programTM = global::renderEngine.buildRenderProgram( + "ToneMapping", + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") + ); + _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); + _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); + _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); + _uniformCacheTM.projection = _programTM->uniformLocation("projection"); + _uniformCacheTM.pixelWeightThreshold = + _programTM->uniformLocation("pixelWeightThreshold"); + + addProperty(_tmPointFilterSize); + addProperty(_tmPointSigma); + addProperty(_tmPointPixelWeightThreshold); + break; + } + case gaia::ShaderOption::Point_VBO: { + _program = ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") + ); + + _programTM = global::renderEngine.buildRenderProgram("ToneMapping", + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") + ); + _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); + _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); + _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); + _uniformCacheTM.projection = _programTM->uniformLocation("projection"); + _uniformCacheTM.pixelWeightThreshold = + _programTM->uniformLocation("pixelWeightThreshold"); + + addProperty(_tmPointFilterSize); + addProperty(_tmPointSigma); + addProperty(_tmPointPixelWeightThreshold); + break; + } + case gaia::ShaderOption::Billboard_SSBO: { + _program = ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); + _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); + _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); + _uniformCache.sharpness = _program->uniformLocation("sharpness"); + _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); + _uniformCache.closeUpBoostDist = _program->uniformLocation( + "closeUpBoostDist" + ); + _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); + + _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); + _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); + _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); + + _programTM = global::renderEngine.buildRenderProgram( + "ToneMapping", + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl") + ); + + addProperty(_magnitudeBoost); + addProperty(_sharpness); + addProperty(_billboardSize); + addProperty(_closeUpBoostDist); + //addProperty(_pointSpreadFunctionTexturePath); + break; + } + case gaia::ShaderOption::Billboard_SSBO_noFBO: { + _program = global::renderEngine.buildRenderProgram("GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_nofbo_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); + _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); + _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); + _uniformCache.sharpness = _program->uniformLocation("sharpness"); + _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); + _uniformCache.closeUpBoostDist = _program->uniformLocation( + "closeUpBoostDist" + ); + _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); + + _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); + _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); + _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); + + addProperty(_magnitudeBoost); + addProperty(_sharpness); + addProperty(_billboardSize); + addProperty(_closeUpBoostDist); + break; + } + case gaia::ShaderOption::Billboard_VBO: { + _program = ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); + _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); + _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); + _uniformCache.sharpness = _program->uniformLocation("sharpness"); + _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); + _uniformCache.closeUpBoostDist = _program->uniformLocation( + "closeUpBoostDist" + ); + _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); + + _programTM = global::renderEngine.buildRenderProgram("ToneMapping", + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl") + ); + + addProperty(_magnitudeBoost); + addProperty(_sharpness); + addProperty(_billboardSize); + addProperty(_closeUpBoostDist); + //addProperty(_pointSpreadFunctionTexturePath); + break; + } + } + + // Common uniforms for all shaders: + _uniformCache.model = _program->uniformLocation("model"); + _uniformCache.view = _program->uniformLocation("view"); + _uniformCache.projection = _program->uniformLocation("projection"); + _uniformCache.time = _program->uniformLocation("time"); + _uniformCache.renderOption = _program->uniformLocation("renderOption"); + _uniformCache.viewScaling = _program->uniformLocation("viewScaling"); + _uniformCache.cutOffThreshold = _program->uniformLocation("cutOffThreshold"); + _uniformCache.luminosityMultiplier = _program->uniformLocation( + "luminosityMultiplier" + ); + _uniformCache.colorTexture = _program->uniformLocation("colorTexture"); + + _uniformFilterCache.posXThreshold = _program->uniformLocation("posXThreshold"); + _uniformFilterCache.posYThreshold = _program->uniformLocation("posYThreshold"); + _uniformFilterCache.posZThreshold = _program->uniformLocation("posZThreshold"); + _uniformFilterCache.gMagThreshold = _program->uniformLocation("gMagThreshold"); + _uniformFilterCache.bpRpThreshold = _program->uniformLocation("bpRpThreshold"); + _uniformFilterCache.distThreshold = _program->uniformLocation("distThreshold"); + + _uniformCacheTM.renderedTexture = _programTM->uniformLocation("renderedTexture"); + + + // Find out how much GPU memory this computer has (Nvidia cards). + GLint nDedicatedVidMemoryInKB = 0; + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nDedicatedVidMemoryInKB); + GLint nTotalMemoryInKB = 0; + glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &nTotalMemoryInKB); + GLint nCurrentAvailMemoryInKB = 0; + glGetIntegerv( + GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, + &nCurrentAvailMemoryInKB + ); + + LDEBUG(fmt::format( + "nDedicatedVidMemoryKB: {} - nTotalMemoryKB: {} - nCurrentAvailMemoryKB: {}", + nDedicatedVidMemoryInKB, nTotalMemoryInKB, nCurrentAvailMemoryInKB + )); + + // Set ceiling for video memory to use in streaming. + float dedicatedVidMem = static_cast( + static_cast(nDedicatedVidMemoryInKB) * 1024 + ); + // TODO: Need to fix what happens if we can't query! For now use 2 GB by default. + _gpuMemoryBudgetInBytes = dedicatedVidMem > 0 ? + static_cast(dedicatedVidMem * _maxGpuMemoryPercent) : + 2147483648; + + // Set ceiling for how much of the installed CPU RAM to use for streaming + long long installedRam = static_cast(CpuCap.installedMainMemory()) * + 1024 * 1024; + // TODO: What to do if we can't query? As for now we use 4 GB by default. + _cpuRamBudgetInBytes = installedRam > 0 ? + static_cast(static_cast(installedRam) * _maxCpuMemoryPercent) : + 4294967296; + _cpuRamBudgetProperty.setMaxValue(static_cast(_cpuRamBudgetInBytes)); + + LDEBUG(fmt::format( + "GPU Memory Budget (bytes): {} - CPU RAM Budget (bytes): {}", + _gpuMemoryBudgetInBytes, _cpuRamBudgetInBytes + )); +} + +void RenderableGaiaStars::deinitializeGL() { + if (_vboPos != 0) { + glDeleteBuffers(1, &_vboPos); + _vboPos = 0; + } + if (_vboCol != 0) { + glDeleteBuffers(1, &_vboCol); + _vboCol = 0; + } + if (_vboVel != 0) { + glDeleteBuffers(1, &_vboVel); + _vboVel = 0; + } + if (_ssboIdx != 0) { + glDeleteBuffers(1, &_ssboIdx); + _ssboIdx = 0; + glDeleteBuffers(1, &_ssboData); + _ssboData = 0; + } + if (_vao != 0) { + glDeleteVertexArrays(1, &_vao); + _vao = 0; + } + if (_vaoEmpty != 0) { + glDeleteVertexArrays(1, &_vaoEmpty); + _vaoEmpty = 0; + } + + glDeleteBuffers(1, &_vboQuad); + _vboQuad = 0; + glDeleteVertexArrays(1, &_vaoQuad); + _vaoQuad = 0; + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + + _dataFile = nullptr; + _pointSpreadFunctionTexture = nullptr; + _colorTexture = nullptr; + _fboTexture = nullptr; + + if (_program) { + global::renderEngine.removeRenderProgram(_program.get()); + _program = nullptr; + } + if (_programTM) { + global::renderEngine.removeRenderProgram(_programTM.get()); + _programTM = nullptr; + } +} + +void RenderableGaiaStars::render(const RenderData& data, RendererTasks&) { + checkGlErrors("Before render"); + + // Save current FBO. + GLint defaultFbo; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFbo); + + glm::dmat4 model = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * + glm::dmat4(data.modelTransform.rotation) * + glm::dmat4(glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale))); + + float viewScaling = data.camera.scaling(); + glm::dmat4 view = data.camera.combinedViewMatrix(); + glm::dmat4 projection = data.camera.projectionMatrix(); + + glm::dmat4 modelViewProjMat = projection * view * model; + glm::vec2 screenSize = glm::vec2(global::renderEngine.renderingResolution()); + + // Wait until camera has stabilized before we traverse the Octree/stream from files. + const double rotationDiff = abs(length(_previousCameraRotation) - + length(data.camera.rotationQuaternion())); + if (_firstDrawCalls && rotationDiff > 1e-10) { + _previousCameraRotation = data.camera.rotationQuaternion(); + return; + } + else { + _firstDrawCalls = false; + } + + // Update which nodes that are stored in memory as the camera moves around + // (if streaming) + if (_fileReaderOption == gaia::FileReaderOption::StreamOctree) { + glm::dvec3 cameraPos = data.camera.positionVec3(); + size_t chunkSizeBytes = _chunkSize * sizeof(GLfloat); + _octreeManager.fetchSurroundingNodes(cameraPos, chunkSizeBytes, _additionalNodes); + + // Update CPU Budget property. + _cpuRamBudgetProperty = static_cast(_octreeManager.cpuRamBudget()); + } + + // Traverse Octree and build a map with new nodes to render, uses mvp matrix to decide + const int renderOption = _renderOption; + int deltaStars = 0; + std::map> updateData = _octreeManager.traverseData( + modelViewProjMat, + screenSize, + deltaStars, + gaia::RenderOption(renderOption), + _lodPixelThreshold + ); + + // Update number of rendered stars. + _nStarsToRender += deltaStars; + _nRenderedStars = _nStarsToRender; + + // Update GPU Stream Budget property. + _gpuStreamBudgetProperty = static_cast(_octreeManager.numFreeSpotsInBuffer()); + + int nChunksToRender = static_cast(_octreeManager.biggestChunkIndexInUse()); + int maxStarsPerNode = static_cast(_octreeManager.maxStarsPerNode()); + int valuesPerStar = static_cast(_nRenderValuesPerStar); + + // Switch rendering technique depending on user-defined shader option. + const int shaderOption = _shaderOption; + if (shaderOption == gaia::ShaderOption::Billboard_SSBO || + shaderOption == gaia::ShaderOption::Point_SSBO || + shaderOption == gaia::ShaderOption::Billboard_SSBO_noFBO) + { +#ifndef __APPLE__ + //------------------------ RENDER WITH SSBO --------------------------- + // Update SSBO Index array with accumulated stars in all chunks. + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); + int lastValue = _accumulatedIndices.back(); + _accumulatedIndices.resize(nChunksToRender + 1, lastValue); + + // Update vector with accumulated indices. + for (const auto& [offset, subData] : updateData) { + int newValue = static_cast(subData.size() / _nRenderValuesPerStar) + + _accumulatedIndices[offset]; + int changeInValue = newValue - _accumulatedIndices[offset + 1]; + _accumulatedIndices[offset + 1] = newValue; + // Propagate change. + for (int i = offset + 1; i < nChunksToRender; ++i) { + _accumulatedIndices[i + 1] += changeInValue; + } + } + + // Fix number of stars rendered if it doesn't correspond to our buffers. + if (_accumulatedIndices.back() != _nStarsToRender) { + _nStarsToRender = _accumulatedIndices.back(); + _nRenderedStars = _nStarsToRender; + } + + size_t indexBufferSize = _accumulatedIndices.size() * sizeof(GLint); + + // Update SSBO Index (stars per chunk). + glBufferData( + GL_SHADER_STORAGE_BUFFER, + indexBufferSize, + _accumulatedIndices.data(), + GL_STREAM_DRAW + ); + + // Use orphaning strategy for data SSBO. + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); + + glBufferData( + GL_SHADER_STORAGE_BUFFER, + _maxStreamingBudgetInBytes, + nullptr, + GL_STREAM_DRAW + ); + + // Update SSBO with one insert per chunk/node. + // The key in map holds the offset index. + for (const auto &[offset, subData] : updateData) { + // We don't need to fill chunk with zeros for SSBOs! + // Just check if we have any values to update. + if (!subData.empty()) { + glBufferSubData( + GL_SHADER_STORAGE_BUFFER, + offset * _chunkSize * sizeof(GLfloat), + subData.size() * sizeof(GLfloat), + subData.data() + ); + } + } + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#endif // !__APPLE__ + } + else { + //---------------------- RENDER WITH VBO ----------------------------- + // Update VBOs with new nodes. + // This will overwrite old data that's not visible anymore as well. + glBindVertexArray(_vao); + + // Always update Position VBO. + glBindBuffer(GL_ARRAY_BUFFER, _vboPos); + float posMemoryShare = static_cast(PositionSize) / _nRenderValuesPerStar; + size_t posChunkSize = maxStarsPerNode * PositionSize; + long long posStreamingBudget = static_cast( + _maxStreamingBudgetInBytes * posMemoryShare + ); + + // Use buffer orphaning to update a subset of total data. + glBufferData( + GL_ARRAY_BUFFER, + posStreamingBudget, + nullptr, + GL_STREAM_DRAW + ); + + // Update buffer with one insert per chunk/node. + //The key in map holds the offset index. + for (const auto& [offset, subData] : updateData) { + // Fill chunk by appending zeroes so we overwrite possible earlier values. + // Only required when removing nodes because chunks are filled up in octree + // fetch on add. + std::vector vectorData(subData.begin(), subData.end()); + vectorData.resize(posChunkSize, 0.f); + glBufferSubData( + GL_ARRAY_BUFFER, + offset * posChunkSize * sizeof(GLfloat), + posChunkSize * sizeof(GLfloat), + vectorData.data() + ); + } + + // Update Color VBO if render option is 'Color' or 'Motion'. + if (renderOption != gaia::RenderOption::Static) { + glBindBuffer(GL_ARRAY_BUFFER, _vboCol); + float colMemoryShare = static_cast(ColorSize) / _nRenderValuesPerStar; + size_t colChunkSize = maxStarsPerNode * ColorSize; + long long colStreamingBudget = static_cast( + _maxStreamingBudgetInBytes * colMemoryShare + ); + + // Use buffer orphaning to update a subset of total data. + glBufferData( + GL_ARRAY_BUFFER, + colStreamingBudget, + nullptr, + GL_STREAM_DRAW + ); + + // Update buffer with one insert per chunk/node. + //The key in map holds the offset index. + for (const auto& [offset, subData] : updateData) { + // Fill chunk by appending zeroes so we overwrite possible earlier values. + std::vector vectorData(subData.begin(), subData.end()); + vectorData.resize(posChunkSize + colChunkSize, 0.f); + glBufferSubData( + GL_ARRAY_BUFFER, + offset * colChunkSize * sizeof(GLfloat), + colChunkSize * sizeof(GLfloat), + vectorData.data() + posChunkSize + ); + } + + // Update Velocity VBO if specified. + if (renderOption == gaia::RenderOption::Motion) { + glBindBuffer(GL_ARRAY_BUFFER, _vboVel); + float velMemoryShare = static_cast(VelocitySize) / + _nRenderValuesPerStar; + size_t velChunkSize = maxStarsPerNode * VelocitySize; + long long velStreamingBudget = static_cast( + _maxStreamingBudgetInBytes * velMemoryShare + ); + + // Use buffer orphaning to update a subset of total data. + glBufferData( + GL_ARRAY_BUFFER, + velStreamingBudget, + nullptr, + GL_STREAM_DRAW + ); + + // Update buffer with one insert per chunk/node. + //The key in map holds the offset index. + for (const auto& [offset, subData] : updateData) { + // Fill chunk by appending zeroes. + std::vector vectorData(subData.begin(), subData.end()); + vectorData.resize(_chunkSize, 0.f); + glBufferSubData( + GL_ARRAY_BUFFER, + offset * velChunkSize * sizeof(GLfloat), + velChunkSize * sizeof(GLfloat), + vectorData.data() + posChunkSize + colChunkSize + ); + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + checkGlErrors("After buffer updates"); + + // Activate shader program and send uniforms. + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDepthMask(false); + _program->activate(); + + _program->setUniform(_uniformCache.model, model); + _program->setUniform(_uniformCache.view, view); + _program->setUniform(_uniformCache.projection, projection); + _program->setUniform( + _uniformCache.time, + static_cast(data.time.j2000Seconds()) + ); + _program->setUniform(_uniformCache.renderOption, _renderOption); + _program->setUniform(_uniformCache.viewScaling, viewScaling); + _program->setUniform(_uniformCache.cutOffThreshold, _cutOffThreshold); + _program->setUniform(_uniformCache.luminosityMultiplier, _luminosityMultiplier); + + // Send filterValues. + _program->setUniform(_uniformFilterCache.posXThreshold, _posXThreshold); + _program->setUniform(_uniformFilterCache.posYThreshold, _posYThreshold); + _program->setUniform(_uniformFilterCache.posZThreshold, _posZThreshold); + _program->setUniform(_uniformFilterCache.gMagThreshold, _gMagThreshold); + _program->setUniform(_uniformFilterCache.bpRpThreshold, _bpRpThreshold); + _program->setUniform(_uniformFilterCache.distThreshold, _distThreshold); + + ghoul::opengl::TextureUnit colorUnit; + if (_colorTexture) { + colorUnit.activate(); + _colorTexture->bind(); + _program->setUniform(_uniformCache.colorTexture, colorUnit); + } + + // Specify how many stars we will render. Will be overwritten if rendering billboards + GLsizei nShaderCalls = _nStarsToRender; + + ghoul::opengl::TextureUnit psfUnit; + switch (shaderOption) { + case gaia::ShaderOption::Point_SSBO: { + _program->setUniform(_uniformCache.maxStarsPerNode, maxStarsPerNode); + _program->setUniform(_uniformCache.valuesPerStar, valuesPerStar); + _program->setUniform(_uniformCache.nChunksToRender, nChunksToRender); + break; + } + case gaia::ShaderOption::Point_VBO: { + // Specify how many potential stars we have to render. + nShaderCalls = maxStarsPerNode * nChunksToRender; + break; + } + case gaia::ShaderOption::Billboard_SSBO: + case gaia::ShaderOption::Billboard_SSBO_noFBO: { + _program->setUniform( + _uniformCache.cameraPos, + data.camera.positionVec3() + ); + _program->setUniform( + _uniformCache.cameraLookUp, + data.camera.lookUpVectorWorldSpace() + ); + _program->setUniform(_uniformCache.maxStarsPerNode, maxStarsPerNode); + _program->setUniform(_uniformCache.valuesPerStar, valuesPerStar); + _program->setUniform(_uniformCache.nChunksToRender, nChunksToRender); + + _program->setUniform(_uniformCache.closeUpBoostDist, + _closeUpBoostDist * static_cast(distanceconstants::Parsec) + ); + _program->setUniform(_uniformCache.billboardSize, _billboardSize); + _program->setUniform(_uniformCache.magnitudeBoost, _magnitudeBoost); + _program->setUniform(_uniformCache.sharpness, _sharpness); + + psfUnit.activate(); + _pointSpreadFunctionTexture->bind(); + _program->setUniform(_uniformCache.psfTexture, psfUnit); + break; + } + case gaia::ShaderOption::Billboard_VBO: { + _program->setUniform( + _uniformCache.cameraPos, + data.camera.positionVec3() + ); + _program->setUniform( + _uniformCache.cameraLookUp, + data.camera.lookUpVectorWorldSpace() + ); + _program->setUniform(_uniformCache.closeUpBoostDist, + _closeUpBoostDist * static_cast(distanceconstants::Parsec) + ); + _program->setUniform(_uniformCache.billboardSize, _billboardSize); + _program->setUniform(_uniformCache.magnitudeBoost, _magnitudeBoost); + _program->setUniform(_uniformCache.sharpness, _sharpness); + + psfUnit.activate(); + _pointSpreadFunctionTexture->bind(); + _program->setUniform(_uniformCache.psfTexture, psfUnit); + + // Specify how many potential stars we have to render. + nShaderCalls = maxStarsPerNode * nChunksToRender; + break; + } + } + + if (shaderOption != gaia::ShaderOption::Billboard_SSBO_noFBO) { + // Render to FBO. + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + //glEnable(GL_PROGRAM_POINT_SIZE); + // A non-zero named vao MUST ALWAYS be bound! + if (_useVBO) { + glBindVertexArray(_vao); + } + else { + glBindVertexArray(_vaoEmpty); + } + + glDrawArrays(GL_POINTS, 0, nShaderCalls); + glBindVertexArray(0); + //glDisable(GL_PROGRAM_POINT_SIZE); + _program->deactivate(); + + if (shaderOption != gaia::ShaderOption::Billboard_SSBO_noFBO) { + // Use ToneMapping shaders and render to default FBO again! + _programTM->activate(); + + glBindFramebuffer(GL_FRAMEBUFFER, defaultFbo); + + ghoul::opengl::TextureUnit fboTexUnit; + if (_fboTexture) { + fboTexUnit.activate(); + _fboTexture->bind(); + _programTM->setUniform(_uniformCacheTM.renderedTexture, fboTexUnit); + } + + if (shaderOption == gaia::ShaderOption::Point_SSBO || + shaderOption == gaia::ShaderOption::Point_VBO) + { + _programTM->setUniform(_uniformCacheTM.screenSize, screenSize); + _programTM->setUniform(_uniformCacheTM.filterSize, _tmPointFilterSize); + _programTM->setUniform(_uniformCacheTM.sigma, _tmPointSigma); + _programTM->setUniform(_uniformCacheTM.projection, projection); + _programTM->setUniform( + _uniformCacheTM.pixelWeightThreshold, + _tmPointPixelWeightThreshold + ); + } + + glBindVertexArray(_vaoQuad); + glDrawArrays(GL_TRIANGLES, 0, 6); // 2 triangles + glBindVertexArray(0); + + _programTM->deactivate(); + } + + glDepthMask(true); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + checkGlErrors("After render"); +} + +void RenderableGaiaStars::checkGlErrors(const std::string& identifier) const { + if (_reportGlErrors) { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + switch (error) { + case GL_INVALID_ENUM: + LINFO(identifier + " - GL_INVALID_ENUM"); + break; + case GL_INVALID_VALUE: + LINFO(identifier + " - GL_INVALID_VALUE"); + break; + case GL_INVALID_OPERATION: + LINFO(identifier + " - GL_INVALID_OPERATION"); + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + LINFO(identifier + " - GL_INVALID_FRAMEBUFFER_OPERATION"); + break; + case GL_OUT_OF_MEMORY: + LINFO(identifier + " - GL_OUT_OF_MEMORY"); + break; + default: + LINFO(identifier + " - Unknown error"); + break; + } + } + } +} + +void RenderableGaiaStars::update(const UpdateData&) { + const int shaderOption = _shaderOption; + const int renderOption = _renderOption; + + // Don't update anything if we are in the middle of a rebuild. + if (_octreeManager.isRebuildOngoing()) { + return; + } + + if (_dataIsDirty) { + LDEBUG("Regenerating data"); + // Reload data file. This may reconstruct the Octree as well. + bool success = readDataFile(); + if (!success) { + throw ghoul::RuntimeError("Error loading Gaia Star data"); + } + _dataIsDirty = false; + // Make sure we regenerate buffers if data has reloaded! + _buffersAreDirty = true; + } + + if (_program->isDirty() || _shadersAreDirty) { + switch (shaderOption) { + case gaia::ShaderOption::Point_SSBO: { +#ifndef __APPLE__ + std::unique_ptr program = + ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") + ); + if (_program) { + global::renderEngine.removeRenderProgram(_program.get()); + } + _program = std::move(program); + + _uniformCache.maxStarsPerNode = _program->uniformLocation( + "maxStarsPerNode" + ); + _uniformCache.valuesPerStar = _program->uniformLocation( + "valuesPerStar" + ); + _uniformCache.nChunksToRender = _program->uniformLocation( + "nChunksToRender" + ); + + // If rebuild was triggered by switching ShaderOption then ssboBinding may + // not have been initialized yet. Binding will happen in later in + // buffersAredirty. + if (!_shadersAreDirty) { + _program->setSsboBinding( + "ssbo_idx_data", + _ssboIdxBinding->bindingNumber() + ); + _program->setSsboBinding( + "ssbo_comb_data", + _ssboDataBinding->bindingNumber() + ); + } + if (hasProperty(&_magnitudeBoost)) { + removeProperty(_magnitudeBoost); + } + if (hasProperty(&_sharpness)) { + removeProperty(_sharpness); + } + if (hasProperty(&_billboardSize)) { + removeProperty(_billboardSize); + } + if (hasProperty(&_closeUpBoostDist)) { + removeProperty(_closeUpBoostDist); + } + if (hasProperty(&_pointSpreadFunctionTexturePath)) { + removeProperty(_pointSpreadFunctionTexturePath); + } + if (!hasProperty(&_tmPointFilterSize)) { + addProperty(_tmPointFilterSize); + } + if (!hasProperty(&_tmPointSigma)) { + addProperty(_tmPointSigma); + } + if (!hasProperty(&_tmPointPixelWeightThreshold)) { + addProperty(_tmPointPixelWeightThreshold); + } + #endif // !__APPLE__ + break; + } + case gaia::ShaderOption::Point_VBO: { + std::unique_ptr program = + ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") + ); + if (_program) { + global::renderEngine.removeRenderProgram(_program.get()); + } + _program = std::move(program); + + if (hasProperty(&_magnitudeBoost)) { + removeProperty(_magnitudeBoost); + } + if (hasProperty(&_sharpness)) { + removeProperty(_sharpness); + } + if (hasProperty(&_billboardSize)) { + removeProperty(_billboardSize); + } + if (hasProperty(&_closeUpBoostDist)) { + removeProperty(_closeUpBoostDist); + } + if (hasProperty(&_pointSpreadFunctionTexturePath)) { + removeProperty(_pointSpreadFunctionTexturePath); + } + if (!hasProperty(&_tmPointFilterSize)) { + addProperty(_tmPointFilterSize); + } + if (!hasProperty(&_tmPointSigma)) { + addProperty(_tmPointSigma); + } + if (!hasProperty(&_tmPointPixelWeightThreshold)) { + addProperty(_tmPointPixelWeightThreshold); + } + break; + } + case gaia::ShaderOption::Billboard_SSBO: + case gaia::ShaderOption::Billboard_SSBO_noFBO: { + #ifndef __APPLE__ + std::unique_ptr program; + if (shaderOption == gaia::ShaderOption::Billboard_SSBO) { + program = ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + } + else { + program = global::renderEngine.buildRenderProgram("GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_nofbo_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + } + + if (_program) { + global::renderEngine.removeRenderProgram(_program.get()); + } + _program = std::move(program); + + _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); + _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); + _uniformCache.magnitudeBoost = _program->uniformLocation( + "magnitudeBoost" + ); + _uniformCache.sharpness = _program->uniformLocation("sharpness"); + _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); + _uniformCache.closeUpBoostDist = _program->uniformLocation( + "closeUpBoostDist" + ); + _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); + + _uniformCache.maxStarsPerNode = _program->uniformLocation( + "maxStarsPerNode" + ); + _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); + _uniformCache.nChunksToRender = _program->uniformLocation( + "nChunksToRender" + ); + + // If rebuild was triggered by switching ShaderOption then ssboBinding + // may not have been initialized yet. Binding will happen in later in + // buffersAredirty. + if (!_shadersAreDirty) { + _program->setSsboBinding( + "ssbo_idx_data", + _ssboIdxBinding->bindingNumber() + ); + _program->setSsboBinding( + "ssbo_comb_data", + _ssboDataBinding->bindingNumber() + ); + } + + if (!hasProperty(&_magnitudeBoost)) { + addProperty(_magnitudeBoost); + } + if (!hasProperty(&_sharpness)) { + addProperty(_sharpness); + } + if (!hasProperty(&_billboardSize)) { + addProperty(_billboardSize); + } + if (!hasProperty(&_closeUpBoostDist)) { + addProperty(_closeUpBoostDist); + } + if (!hasProperty(&_pointSpreadFunctionTexturePath)) { + addProperty(_pointSpreadFunctionTexturePath); + } + if (hasProperty(&_tmPointFilterSize)) { + removeProperty(_tmPointFilterSize); + } + if (hasProperty(&_tmPointSigma)) { + removeProperty(_tmPointSigma); + } + if (hasProperty(&_tmPointPixelWeightThreshold)) { + removeProperty(_tmPointPixelWeightThreshold); + } + #endif // !__APPLE__ + break; + } + case gaia::ShaderOption::Billboard_VBO: { + std::unique_ptr program = + ghoul::opengl::ProgramObject::Build( + "GaiaStar", + absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") + ); + if (_program) { + global::renderEngine.removeRenderProgram(_program.get()); + } + _program = std::move(program); + + _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); + _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); + _uniformCache.magnitudeBoost = _program->uniformLocation( + "magnitudeBoost" + ); + _uniformCache.sharpness = _program->uniformLocation("sharpness"); + _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); + _uniformCache.closeUpBoostDist = _program->uniformLocation( + "closeUpBoostDist" + ); + _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); + + if (!hasProperty(&_magnitudeBoost)) { + addProperty(_magnitudeBoost); + } + if (!hasProperty(&_sharpness)) { + addProperty(_sharpness); + } + if (!hasProperty(&_billboardSize)) { + addProperty(_billboardSize); + } + if (!hasProperty(&_closeUpBoostDist)) { + addProperty(_closeUpBoostDist); + } + if (!hasProperty(&_pointSpreadFunctionTexturePath)) { + addProperty(_pointSpreadFunctionTexturePath); + } + if (hasProperty(&_tmPointFilterSize)) { + removeProperty(_tmPointFilterSize); + } + if (hasProperty(&_tmPointSigma)) { + removeProperty(_tmPointSigma); + } + if (hasProperty(&_tmPointPixelWeightThreshold)) { + removeProperty(_tmPointPixelWeightThreshold); + } + break; + } + } + + // Common uniforms for all shaders: + _uniformCache.model = _program->uniformLocation("model"); + _uniformCache.view = _program->uniformLocation("view"); + _uniformCache.projection = _program->uniformLocation("projection"); + _uniformCache.time = _program->uniformLocation("time"); + _uniformCache.renderOption = _program->uniformLocation("renderOption"); + _uniformCache.viewScaling = _program->uniformLocation("viewScaling"); + _uniformCache.cutOffThreshold = _program->uniformLocation("cutOffThreshold"); + _uniformCache.luminosityMultiplier = _program->uniformLocation( + "luminosityMultiplier" + ); + _uniformCache.colorTexture = _program->uniformLocation("colorTexture"); + // Filter uniforms: + _uniformFilterCache.posXThreshold = _program->uniformLocation("posXThreshold"); + _uniformFilterCache.posYThreshold = _program->uniformLocation("posYThreshold"); + _uniformFilterCache.posZThreshold = _program->uniformLocation("posZThreshold"); + _uniformFilterCache.gMagThreshold = _program->uniformLocation("gMagThreshold"); + _uniformFilterCache.bpRpThreshold = _program->uniformLocation("bpRpThreshold"); + _uniformFilterCache.distThreshold = _program->uniformLocation("distThreshold"); + } + + if (_programTM->isDirty() || _shadersAreDirty) { + switch (shaderOption) { + case gaia::ShaderOption::Point_SSBO: + case gaia::ShaderOption::Point_VBO: { + std::unique_ptr programTM = + global::renderEngine.buildRenderProgram( + "ToneMapping", + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), + absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") + ); + if (_programTM) { + global::renderEngine.removeRenderProgram(_programTM.get()); + } + _programTM = std::move(programTM); + + _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); + _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); + _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); + _uniformCacheTM.projection = _programTM->uniformLocation("projection"); + _uniformCacheTM.pixelWeightThreshold = _programTM->uniformLocation( + "pixelWeightThreshold" + ); + break; + } + case gaia::ShaderOption::Billboard_SSBO: + case gaia::ShaderOption::Billboard_VBO: { + std::string vs = absPath( + "${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl" + ); + std::string fs = absPath( + "${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl" + ); + std::unique_ptr programTM = + global::renderEngine.buildRenderProgram("ToneMapping", vs, fs); + if (_programTM) { + global::renderEngine.removeRenderProgram(_programTM.get()); + } + _programTM = std::move(programTM); + break; + } + } + // Common uniforms: + _uniformCacheTM.renderedTexture = _programTM->uniformLocation("renderedTexture"); + + _shadersAreDirty = false; + } + + if (_buffersAreDirty) { + LDEBUG("Regenerating buffers"); + + // Set values per star slice depending on render option. + if (renderOption == gaia::RenderOption::Static) { + _nRenderValuesPerStar = PositionSize; + } + else if (renderOption == gaia::RenderOption::Color) { + _nRenderValuesPerStar = PositionSize + ColorSize; + } + else { // (renderOption == gaia::RenderOption::Motion) + _nRenderValuesPerStar = PositionSize + ColorSize + VelocitySize; + } + + // Calculate memory budgets. + _chunkSize = _octreeManager.maxStarsPerNode() * _nRenderValuesPerStar; + long long totalChunkSizeInBytes = _octreeManager.totalNodes() * + _chunkSize * sizeof(GLfloat); + _maxStreamingBudgetInBytes = std::min( + totalChunkSizeInBytes, + _gpuMemoryBudgetInBytes + ); + long long maxNodesInStream = _maxStreamingBudgetInBytes / + (_chunkSize * sizeof(GLfloat)); + + _gpuStreamBudgetProperty.setMaxValue(static_cast(maxNodesInStream)); + bool datasetFitInMemory = + static_cast(_totalDatasetSizeInBytes) < (_cpuRamBudgetInBytes * 0.9f); + + if (!datasetFitInMemory && !hasProperty(&_additionalNodes)) { + addProperty(_additionalNodes); + } + else if (hasProperty(&_additionalNodes)) { + removeProperty(_additionalNodes); + } + + LDEBUG(fmt::format( + "Chunk size: {} - Max streaming budget (bytes): {} - Max nodes in stream: {}", + _chunkSize, _maxStreamingBudgetInBytes, maxNodesInStream + )); + + // ------------------ RENDER WITH SSBO ----------------------- + if (shaderOption == gaia::ShaderOption::Billboard_SSBO || + shaderOption == gaia::ShaderOption::Point_SSBO || + shaderOption == gaia::ShaderOption::Billboard_SSBO_noFBO) + { +#ifndef __APPLE__ + _useVBO = false; + + // Trigger a rebuild of buffer data from octree. + // With SSBO we won't fill the chunks. + _octreeManager.initBufferIndexStack( + maxNodesInStream, + _useVBO, + datasetFitInMemory + ); + _nStarsToRender = 0; + + // Generate SSBO Buffers and bind them. + if (_vaoEmpty == 0) { + glGenVertexArrays(1, &_vaoEmpty); + LDEBUG(fmt::format("Generating Empty Vertex Array id '{}'", _vaoEmpty)); + } + if (_ssboIdx == 0) { + glGenBuffers(1, &_ssboIdx); + LDEBUG(fmt::format( + "Generating Index Shader Storage Buffer Object id '{}'", _ssboIdx + )); + } + if (_ssboData == 0) { + glGenBuffers(1, &_ssboData); + LDEBUG(fmt::format( + "Generating Data Shader Storage Buffer Object id '{}'", _ssboData + )); + } + + // Bind SSBO blocks to our shader positions. + // Number of stars per chunk (a.k.a. Index). + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); + + _ssboIdxBinding = std::make_unique + >(); + glBindBufferBase( + GL_SHADER_STORAGE_BUFFER, + _ssboIdxBinding->bindingNumber(), + _ssboIdx + ); + _program->setSsboBinding("ssbo_idx_data", _ssboIdxBinding->bindingNumber()); + + // Combined SSBO with all data. + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); + + _ssboDataBinding = std::make_unique + >(); + glBindBufferBase( + GL_SHADER_STORAGE_BUFFER, + _ssboDataBinding->bindingNumber(), + _ssboData + ); + _program->setSsboBinding("ssbo_comb_data", _ssboDataBinding->bindingNumber()); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + // Deallocate VBO Buffers if any existed. + if (_vboPos != 0) { + glBindBuffer(GL_ARRAY_BUFFER, _vboPos); + glBufferData( + GL_ARRAY_BUFFER, + 0, + nullptr, + GL_STREAM_DRAW + ); + } + if (_vboCol != 0) { + glBindBuffer(GL_ARRAY_BUFFER, _vboCol); + glBufferData( + GL_ARRAY_BUFFER, + 0, + nullptr, + GL_STREAM_DRAW + ); + } + if (_vboVel != 0) { + glBindBuffer(GL_ARRAY_BUFFER, _vboVel); + glBufferData( + GL_ARRAY_BUFFER, + 0, + nullptr, + GL_STREAM_DRAW + ); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif // !__APPLE__ + } + else { // ------------------ RENDER WITH VBO ----------------------- + _useVBO = true; + + // Trigger a rebuild of buffer data from octree. + // With VBO we will fill the chunks. + _octreeManager.initBufferIndexStack( + maxNodesInStream, + _useVBO, + datasetFitInMemory + ); + _nStarsToRender = 0; + + // Generate VAO and VBOs + if (_vao == 0) { + glGenVertexArrays(1, &_vao); + LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vao)); + } + if (_vboPos == 0) { + glGenBuffers(1, &_vboPos); + LDEBUG(fmt::format( + "Generating Position Vertex Buffer Object id '{}'", _vboPos + )); + } + if (_vboCol == 0) { + glGenBuffers(1, &_vboCol); + LDEBUG(fmt::format( + "Generating Color Vertex Buffer Object id '{}'", _vboCol + )); + } + if (_vboVel == 0) { + glGenBuffers(1, &_vboVel); + LDEBUG(fmt::format( + "Generating Velocity Vertex Buffer Object id '{}'", _vboVel + )); + } + + // Bind our different VBOs to our vertex array layout. + glBindVertexArray(_vao); + + switch (renderOption) { + case gaia::RenderOption::Static: { + glBindBuffer(GL_ARRAY_BUFFER, _vboPos); + GLint positionAttrib = _program->attributeLocation("in_position"); + glEnableVertexAttribArray(positionAttrib); + + glVertexAttribPointer( + positionAttrib, + PositionSize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + + break; + } + case gaia::RenderOption::Color: { + glBindBuffer(GL_ARRAY_BUFFER, _vboPos); + GLint positionAttrib = _program->attributeLocation("in_position"); + glEnableVertexAttribArray(positionAttrib); + + glVertexAttribPointer( + positionAttrib, + PositionSize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + + glBindBuffer(GL_ARRAY_BUFFER, _vboCol); + GLint brightnessAttrib = _program->attributeLocation("in_brightness"); + glEnableVertexAttribArray(brightnessAttrib); + + glVertexAttribPointer( + brightnessAttrib, + ColorSize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + break; + } + case gaia::RenderOption::Motion: { + glBindBuffer(GL_ARRAY_BUFFER, _vboPos); + GLint positionAttrib = _program->attributeLocation("in_position"); + glEnableVertexAttribArray(positionAttrib); + + glVertexAttribPointer( + positionAttrib, + PositionSize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + + glBindBuffer(GL_ARRAY_BUFFER, _vboCol); + GLint brightnessAttrib = _program->attributeLocation("in_brightness"); + glEnableVertexAttribArray(brightnessAttrib); + + glVertexAttribPointer( + brightnessAttrib, + ColorSize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + + glBindBuffer(GL_ARRAY_BUFFER, _vboVel); + GLint velocityAttrib = _program->attributeLocation("in_velocity"); + glEnableVertexAttribArray(velocityAttrib); + + glVertexAttribPointer( + velocityAttrib, + VelocitySize, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + break; + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + +#ifndef __APPLE__ + // Deallocate SSBO buffers if they existed. + if (_ssboIdx != 0) { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); + glBufferData( + GL_SHADER_STORAGE_BUFFER, + 0, + nullptr, + GL_STREAM_DRAW + ); + } + if (_ssboData != 0) { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); + glBufferData( + GL_SHADER_STORAGE_BUFFER, + 0, + nullptr, + GL_STREAM_DRAW + ); + } + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#endif //!__APPLE__ + } + + // Generate VAO and VBO for Quad. + if (_vaoQuad == 0) { + glGenVertexArrays(1, &_vaoQuad); + LDEBUG(fmt::format("Generating Quad Vertex Array id '{}'", _vaoQuad)); + } + if (_vboQuad == 0) { + glGenBuffers(1, &_vboQuad); + LDEBUG(fmt::format("Generating Quad Vertex Buffer Object id '{}'", _vboQuad)); + } + + // Bind VBO and VAO for Quad rendering. + glBindVertexArray(_vaoQuad); + glBindBuffer(GL_ARRAY_BUFFER, _vboQuad); + + // Quad for fullscreen. + static const GLfloat vbo_quad_data[] = { + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + }; + + glBufferData( + GL_ARRAY_BUFFER, + sizeof(vbo_quad_data), + vbo_quad_data, + GL_STATIC_DRAW + ); + + GLint tmPositionAttrib = _programTM->attributeLocation("in_position"); + glEnableVertexAttribArray(tmPositionAttrib); + glVertexAttribPointer( + tmPositionAttrib, + 3, + GL_FLOAT, + GL_FALSE, + 0, + nullptr + ); + + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Generate Framebuffer Object and Texture. + if (_fbo == 0) { + glGenFramebuffers(1, &_fbo); + LDEBUG(fmt::format("Generating Framebuffer Object id '{}'", _fbo)); + } + if (!_fboTexture) { + // Generate a new texture and attach it to our FBO. + glm::vec2 screenSize = glm::vec2(global::renderEngine.renderingResolution()); + _fboTexture = std::make_unique( + glm::uvec3(screenSize, 1), + ghoul::opengl::Texture::Format::RGBA, + GL_RGBA32F, + GL_FLOAT + ); + _fboTexture->uploadTexture(); + LDEBUG("Generating Framebuffer Texture!"); + } + // Bind render texture to FBO. + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + glBindTexture(GL_TEXTURE_2D, *_fboTexture); + glFramebufferTexture( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + *_fboTexture, + 0 + ); + GLenum textureBuffers[1] = { GL_COLOR_ATTACHMENT0 }; + glDrawBuffers(1, textureBuffers); + + // Check that our framebuffer is ok. + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LERROR("Error when generating GaiaStar Framebuffer."); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + _buffersAreDirty = false; + } + + if (_pointSpreadFunctionTextureIsDirty) { + LDEBUG("Reloading Point Spread Function texture"); + _pointSpreadFunctionTexture = nullptr; + if (!_pointSpreadFunctionTexturePath.value().empty()) { + _pointSpreadFunctionTexture = ghoul::io::TextureReader::ref().loadTexture( + absPath(_pointSpreadFunctionTexturePath) + ); + + if (_pointSpreadFunctionTexture) { + LDEBUG(fmt::format( + "Loaded texture from '{}'", absPath(_pointSpreadFunctionTexturePath) + )); + _pointSpreadFunctionTexture->uploadTexture(); + } + _pointSpreadFunctionTexture->setFilter( + ghoul::opengl::Texture::FilterMode::AnisotropicMipMap + ); + + _pointSpreadFunctionFile = std::make_unique( + _pointSpreadFunctionTexturePath + ); + _pointSpreadFunctionFile->setCallback( + [&](const ghoul::filesystem::File&) { + _pointSpreadFunctionTextureIsDirty = true; + } + ); + } + _pointSpreadFunctionTextureIsDirty = false; + } + + if (_colorTextureIsDirty) { + LDEBUG("Reloading Color Texture"); + _colorTexture = nullptr; + if (!_colorTexturePath.value().empty()) { + _colorTexture = ghoul::io::TextureReader::ref().loadTexture( + absPath(_colorTexturePath) + ); + if (_colorTexture) { + LDEBUG(fmt::format( + "Loaded texture from '{}'", absPath(_colorTexturePath) + )); + _colorTexture->uploadTexture(); + } + + _colorTextureFile = std::make_unique( + _colorTexturePath + ); + _colorTextureFile->setCallback( + [&](const ghoul::filesystem::File&) { _colorTextureIsDirty = true; } + ); + } + _colorTextureIsDirty = false; + } + + if (global::windowDelegate.windowHasResized()) { + // Update FBO texture resolution if we haven't already. + glm::vec2 screenSize = glm::vec2(global::renderEngine.renderingResolution()); + const bool hasChanged = glm::any( + glm::notEqual(_fboTexture->dimensions(), glm::uvec3(screenSize, 1)) + ); + + if (hasChanged) { + _fboTexture = std::make_unique( + glm::uvec3(screenSize, 1), + ghoul::opengl::Texture::Format::RGBA, + GL_RGBA32F, + GL_FLOAT + ); + _fboTexture->uploadTexture(); + LDEBUG("Re-Generating Gaia Framebuffer Texture!"); + + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + glBindTexture(GL_TEXTURE_2D, *_fboTexture); + glFramebufferTexture( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + *_fboTexture, + 0 + ); + GLenum textureBuffers[1] = { GL_COLOR_ATTACHMENT0 }; + glDrawBuffers(1, textureBuffers); + + // Check that our framebuffer is ok. + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LERROR("Error when re-generating GaiaStar Framebuffer."); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } +} + +bool RenderableGaiaStars::readDataFile() { + const int fileReaderOption = _fileReaderOption; + int nReadStars = 0; + + _octreeManager.initOctree(_cpuRamBudgetInBytes); + + LINFO("Loading data file: " + _filePath.value()); + + switch (fileReaderOption) { + case gaia::FileReaderOption::Fits: + // Read raw fits file and construct Octree. + nReadStars = readFitsFile(_filePath); + break; + case gaia::FileReaderOption::Speck: + // Read raw speck file and construct Octree. + nReadStars = readSpeckFile(_filePath); + break; + case gaia::FileReaderOption::BinaryRaw: + // Stars are stored in an ordered binary file. + nReadStars = readBinaryRawFile(_filePath); + break; + case gaia::FileReaderOption::BinaryOctree: + // Octree already constructed and stored as a binary file. + nReadStars = readBinaryOctreeFile(_filePath); + break; + case gaia::FileReaderOption::StreamOctree: + // Read Octree structure from file, without data. + nReadStars = readBinaryOctreeStructureFile(_filePath); + break; + default: + LERROR("Wrong FileReaderOption - no data file loaded!"); + break; + } + + //_octreeManager->printStarsPerNode(); + _nRenderedStars.setMaxValue(nReadStars); + LINFO("Dataset contains a total of " + std::to_string(nReadStars) + " stars."); + _totalDatasetSizeInBytes = nReadStars * (PositionSize + ColorSize + VelocitySize) * 4; + + return nReadStars > 0; +} + +int RenderableGaiaStars::readFitsFile(const std::string& filePath) { + int nReadValuesPerStar = 0; + + FitsFileReader fitsFileReader(false); + std::vector fullData = fitsFileReader.readFitsFile( + filePath, + nReadValuesPerStar, + _firstRow, + _lastRow, + _columnNames + ); + + // Insert stars into octree. + for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { + auto first = fullData.begin() + i; + auto last = fullData.begin() + i + nReadValuesPerStar; + std::vector starValues(first, last); + + _octreeManager.insert(starValues); + } + _octreeManager.sliceLodData(); + return static_cast(fullData.size() / nReadValuesPerStar); +} + +int RenderableGaiaStars::readSpeckFile(const std::string& filePath) { + int nReadValuesPerStar = 0; + + FitsFileReader fileReader(false); + std::vector fullData = fileReader.readSpeckFile(filePath, nReadValuesPerStar); + + // Insert stars into octree. + for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { + auto first = fullData.begin() + i; + auto last = fullData.begin() + i + nReadValuesPerStar; + std::vector starValues(first, last); + + _octreeManager.insert(starValues); + } + _octreeManager.sliceLodData(); + return static_cast(fullData.size() / nReadValuesPerStar); +} + +int RenderableGaiaStars::readBinaryRawFile(const std::string& filePath) { + std::vector fullData; + int nReadStars = 0; + + std::ifstream fileStream(filePath, std::ifstream::binary); + if (fileStream.good()) { + int32_t nValues = 0; + int32_t nReadValuesPerStar = 0; + int renderValues = 8; + fileStream.read(reinterpret_cast(&nValues), sizeof(int32_t)); + fileStream.read(reinterpret_cast(&nReadValuesPerStar), sizeof(int32_t)); + + fullData.resize(nValues); + fileStream.read( + reinterpret_cast(fullData.data()), + nValues * sizeof(fullData[0]) + ); + + // Insert stars into octree. + for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { + auto first = fullData.begin() + i; + auto last = fullData.begin() + i + renderValues; + std::vector starValues(first, last); + + _octreeManager.insert(starValues); + } + _octreeManager.sliceLodData(); + + nReadStars = nValues / nReadValuesPerStar; + fileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file '{}' for loading raw binary file!", filePath + )); + return nReadStars; + } + return nReadStars; +} + +int RenderableGaiaStars::readBinaryOctreeFile(const std::string& filePath) { + int nReadStars = 0; + + std::ifstream fileStream(filePath, std::ifstream::binary); + if (fileStream.good()) { + nReadStars = _octreeManager.readFromFile(fileStream, true); + + fileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file '{}' for loading binary Octree file!", filePath + )); + return nReadStars; + } + return nReadStars; +} + +int RenderableGaiaStars::readBinaryOctreeStructureFile(const std::string& folderPath) { + int nReadStars = 0; + std::string indexFile = folderPath + "index.bin"; + + std::ifstream fileStream(indexFile, std::ifstream::binary); + if (fileStream.good()) { + nReadStars = _octreeManager.readFromFile(fileStream, false, folderPath); + + fileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file '{}' for loading binary Octree file!", indexFile + )); + return nReadStars; + } + return nReadStars; +} + +} // namespace openspace diff --git a/modules/softwareintegration/rendering/renderablegaiastars.h b/modules/softwareintegration/rendering/renderablegaiastars.h new file mode 100644 index 0000000000..dfde5abc04 --- /dev/null +++ b/modules/softwareintegration/rendering/renderablegaiastars.h @@ -0,0 +1,216 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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_GAIA___RENDERABLEGAIASTARS___H__ +#define __OPENSPACE_MODULE_GAIA___RENDERABLEGAIASTARS___H__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ghoul::filesystem { class File; } +namespace ghoul::opengl { + class ProgramObject; + class Texture; +} // namespace ghoul::opengl + +namespace openspace { + +namespace documentation { struct Documentation; } + +class RenderableGaiaStars : public Renderable { +public: + explicit RenderableGaiaStars(const ghoul::Dictionary& dictionary); + virtual ~RenderableGaiaStars() = default; + + void initializeGL() override; + void deinitializeGL() override; + + bool isReady() const override; + + void render(const RenderData& data, RendererTasks& rendererTask) override; + void update(const UpdateData& data) override; + + static documentation::Documentation Documentation(); + +private: + /** + * Reads data file in format defined by FileReaderOption. + * + * \return true if data was successfully read. + */ + bool readDataFile(); + + /** + * Reads a FITS file by using FitsFileReader.readFitsFile() and constructs an octree. + * + * \return the number of stars read. + */ + int readFitsFile(const std::string& filePath); + + /** + * Read a SPECK file by using FitsFileReader.readSpeckFile() and constructs an octree. + * + * \return the number of stars read. + */ + int readSpeckFile(const std::string& filePath); + + /** + * Reads a preprocessed binary file and constructs an octree. + * + * \return the number of stars read. + */ + int readBinaryRawFile(const std::string& filePath); + + /** + * Reads a pre-constructed octree, with all data, from a binary file. + * + * \return the number of stars read. + */ + int readBinaryOctreeFile(const std::string& filePath); + + /** + * Reads the structure of a pre-constructed octree from a binary file, without any + * data. + * + * \return the number of stars read. + */ + int readBinaryOctreeStructureFile(const std::string& folderPath); + + /** + * Checks for any OpenGL errors and reports these to the log if _reportGlErrors is + * set to true. + */ + void checkGlErrors(const std::string& identifier) const; + + properties::StringProperty _filePath; + std::unique_ptr _dataFile; + bool _dataIsDirty = true; + bool _buffersAreDirty = true; + bool _shadersAreDirty = false; + + properties::StringProperty _pointSpreadFunctionTexturePath; + std::unique_ptr _pointSpreadFunctionTexture; + std::unique_ptr _pointSpreadFunctionFile; + bool _pointSpreadFunctionTextureIsDirty = true; + + properties::StringProperty _colorTexturePath; + std::unique_ptr _colorTexture; + std::unique_ptr _colorTextureFile; + bool _colorTextureIsDirty = true; + + properties::FloatProperty _luminosityMultiplier; + properties::FloatProperty _magnitudeBoost; + properties::FloatProperty _cutOffThreshold; + properties::FloatProperty _sharpness; + properties::FloatProperty _billboardSize; + properties::FloatProperty _closeUpBoostDist; + properties::IntProperty _tmPointFilterSize; + properties::FloatProperty _tmPointSigma; + properties::IVec2Property _additionalNodes; + properties::FloatProperty _tmPointPixelWeightThreshold; + properties::FloatProperty _lodPixelThreshold; + + properties::Vec2Property _posXThreshold; + properties::Vec2Property _posYThreshold; + properties::Vec2Property _posZThreshold; + properties::Vec2Property _gMagThreshold; + properties::Vec2Property _bpRpThreshold; + properties::Vec2Property _distThreshold; + + properties::IntProperty _firstRow; + properties::IntProperty _lastRow; + properties::StringListProperty _columnNamesList; + std::vector _columnNames; + properties::OptionProperty _fileReaderOption; + properties::OptionProperty _renderOption; + properties::OptionProperty _shaderOption; + properties::IntProperty _nRenderedStars; + // LongLongProperty doesn't show up in menu, use FloatProperty instead. + properties::FloatProperty _cpuRamBudgetProperty; + properties::FloatProperty _gpuStreamBudgetProperty; + properties::FloatProperty _maxGpuMemoryPercent; + properties::FloatProperty _maxCpuMemoryPercent; + + properties::BoolProperty _reportGlErrors; + + std::unique_ptr _program; + UniformCache(model, view, cameraPos, cameraLookUp, viewScaling, projection, + renderOption, luminosityMultiplier, magnitudeBoost, cutOffThreshold, + sharpness, billboardSize, closeUpBoostDist, screenSize, psfTexture, + time, colorTexture, nChunksToRender, valuesPerStar, maxStarsPerNode) + _uniformCache; + + UniformCache(posXThreshold, posYThreshold, posZThreshold, gMagThreshold, + bpRpThreshold, distThreshold) _uniformFilterCache; + + std::unique_ptr _programTM; + UniformCache(renderedTexture, screenSize, filterSize, sigma, pixelWeightThreshold, + projection) _uniformCacheTM; + std::unique_ptr _fboTexture; + + OctreeManager _octreeManager; + std::unique_ptr> _ssboIdxBinding; + std::unique_ptr> _ssboDataBinding; + + std::vector _accumulatedIndices; + size_t _nRenderValuesPerStar = 0; + int _nStarsToRender = 0; + bool _firstDrawCalls = true; + glm::dquat _previousCameraRotation = glm::dquat(1.0, 0.0, 0.0, 0.0); + bool _useVBO = false; + long long _cpuRamBudgetInBytes = 0; + long long _totalDatasetSizeInBytes = 0; + long long _gpuMemoryBudgetInBytes = 0; + long long _maxStreamingBudgetInBytes = 0; + size_t _chunkSize = 0; + + GLuint _vao = 0; + GLuint _vaoEmpty = 0; + GLuint _vboPos = 0; + GLuint _vboCol = 0; + GLuint _vboVel = 0; + GLuint _ssboIdx = 0; + GLuint _ssboData = 0; + GLuint _vaoQuad = 0; + GLuint _vboQuad = 0; + GLuint _fbo = 0; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_GAIA___RENDERABLEGAIASTARS___H__ diff --git a/modules/softwareintegration/rendering/renderablegalaxy.cpp b/modules/softwareintegration/rendering/renderablegalaxy.cpp deleted file mode 100644 index ba5a801649..0000000000 --- a/modules/softwareintegration/rendering/renderablegalaxy.cpp +++ /dev/null @@ -1,892 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - constexpr int8_t CurrentCacheVersion = 1; - - constexpr const char* GlslRaycastPath = - "${MODULE_GALAXY}/shaders/galaxyraycast.glsl"; - constexpr const char* GlslBoundsVsPath = - "${MODULE_GALAXY}/shaders/raycasterbounds_vs.glsl"; - constexpr const char* GlslBoundsFsPath = - "${MODULE_GALAXY}/shaders/raycasterbounds_fs.glsl"; - constexpr const char* _loggerCat = "Renderable Galaxy"; - - constexpr const std::array UniformNamesPoints = { - "modelMatrix", "cameraViewProjectionMatrix", "eyePosition", - "opacityCoefficient" - }; - - constexpr const std::array UniformNamesBillboards = { - "modelMatrix", "cameraViewProjectionMatrix", - "cameraUp", "eyePosition", "psfTexture" - }; - - constexpr openspace::properties::Property::PropertyInfo VolumeRenderingEnabledInfo = { - "VolumeRenderingEnabled", - "Volume Rendering", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo StarRenderingEnabledInfo = { - "StarRenderingEnabled", - "Star Rendering", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo StepSizeInfo = { - "StepSize", - "Step Size", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo AbsorptionMultiplyInfo = { - "AbsorptionMultiply", - "Absorption Multiplier", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo EmissionMultiplyInfo = { - "EmissionMultiply", - "Emission Multiplier", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo TranslationInfo = { - "Translation", - "Translation", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo RotationInfo = { - "Rotation", - "Euler rotation", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo StarRenderingMethodInfo = { - "StarRenderingMethod", - "Star Rendering Method", - "This value determines which rendering method is used for visualization of the " - "stars." - }; - - constexpr openspace::properties::Property::PropertyInfo EnabledPointsRatioInfo = { - "EnabledPointsRatio", - "Enabled points", - "" // @TODO Missing documentation - }; - - constexpr openspace::properties::Property::PropertyInfo DownscaleVolumeRenderingInfo = - { - "Downscale", - "Downscale Factor Volume Rendering", - "This value set the downscaling factor" - " when rendering the current volume." - }; - - constexpr openspace::properties::Property::PropertyInfo NumberOfRayCastingStepsInfo = - { - "Steps", - "Number of RayCasting Steps", - "This value set the number of integration steps during the raycasting procedure." - }; - - void saveCachedFile(const std::string& file, const std::vector& positions, - const std::vector& colors, int64_t nPoints, - float pointsRatio) - { - std::ofstream fileStream(file, std::ofstream::binary); - - if (!fileStream.good()) { - LERROR(fmt::format("Error opening file '{}' for save cache file", file)); - return; - } - - fileStream.write( - reinterpret_cast(&CurrentCacheVersion), - sizeof(int8_t) - ); - fileStream.write(reinterpret_cast(&nPoints), sizeof(int64_t)); - fileStream.write(reinterpret_cast(&pointsRatio), sizeof(float)); - uint64_t nPositions = static_cast(positions.size()); - fileStream.write(reinterpret_cast(&nPositions), sizeof(uint64_t)); - fileStream.write( - reinterpret_cast(positions.data()), - positions.size() * sizeof(glm::vec3) - ); - uint64_t nColors = static_cast(colors.size()); - fileStream.write(reinterpret_cast(&nColors), sizeof(uint64_t)); - fileStream.write( - reinterpret_cast(colors.data()), - colors.size() * sizeof(glm::vec3) - ); - } - -} // namespace - -namespace openspace { - -RenderableGalaxy::RenderableGalaxy(const ghoul::Dictionary& dictionary) - : Renderable(dictionary) - , _volumeRenderingEnabled(VolumeRenderingEnabledInfo, true) - , _starRenderingEnabled(StarRenderingEnabledInfo, true) - , _stepSize(StepSizeInfo, 0.01f, 0.0005f, 0.05f, 0.001f) - , _absorptionMultiply(AbsorptionMultiplyInfo, 40.f, 0.0f, 200.0f) - , _emissionMultiply(EmissionMultiplyInfo, 200.f, 0.0f, 1000.0f) - , _starRenderingMethod( - StarRenderingMethodInfo, - properties::OptionProperty::DisplayType::Dropdown - ) - , _enabledPointsRatio(EnabledPointsRatioInfo, 0.5f, 0.01f, 1.0f) - , _translation(TranslationInfo, glm::vec3(0.f), glm::vec3(0.f), glm::vec3(1.f)) - , _rotation( - RotationInfo, - glm::vec3(0.f), - glm::vec3(0.f), - glm::vec3(glm::two_pi()) - ) - , _downScaleVolumeRendering(DownscaleVolumeRenderingInfo, 1.f, 0.1f, 1.f) - , _numberOfRayCastingSteps(NumberOfRayCastingStepsInfo, 1000.f, 1.f, 1000.f) -{ - dictionary.getValue("VolumeRenderingEnabled", _volumeRenderingEnabled); - dictionary.getValue("StarRenderingEnabled", _starRenderingEnabled); - dictionary.getValue("StepSize", _stepSize); - dictionary.getValue("AbsorptionMultiply", _absorptionMultiply); - dictionary.getValue("EmissionMultiply", _emissionMultiply); - dictionary.getValue("StarRenderingMethod", _starRenderingMethod); - dictionary.getValue("EnabledPointsRatio", _enabledPointsRatio); - dictionary.getValue("Translation", _translation); - dictionary.getValue("Rotation", _rotation); - - if (dictionary.hasKeyAndValue(VolumeRenderingEnabledInfo.identifier)) { - _volumeRenderingEnabled = dictionary.value( - VolumeRenderingEnabledInfo.identifier - ); - } - - if (dictionary.hasKeyAndValue(StarRenderingEnabledInfo.identifier)) { - _starRenderingEnabled = static_cast(StarRenderingEnabledInfo.identifier); - } - - if (dictionary.hasKeyAndValue(StepSizeInfo.identifier)) { - _stepSize = static_cast(dictionary.value(StepSizeInfo.identifier)); - } - - if (dictionary.hasKeyAndValue(AbsorptionMultiplyInfo.identifier)) { - _absorptionMultiply = static_cast( - dictionary.value(AbsorptionMultiplyInfo.identifier) - ); - } - - if (dictionary.hasKeyAndValue(EmissionMultiplyInfo.identifier)) { - _emissionMultiply = static_cast( - dictionary.value(EmissionMultiplyInfo.identifier) - ); - } - - _starRenderingMethod.addOptions({ - { 0, "Points" }, - { 1, "Billboards" } - }); - if (dictionary.hasKey(StarRenderingMethodInfo.identifier)) { - const std::string starRenderingMethod = dictionary.value( - StarRenderingMethodInfo.identifier - ); - if (starRenderingMethod == "Points") { - _starRenderingMethod = 0; - } - else if (starRenderingMethod == "Billboards") { - _starRenderingMethod = 1; - } - } - - if (dictionary.hasKeyAndValue(TranslationInfo.identifier)) { - _translation = dictionary.value(TranslationInfo.identifier); - } - - if (dictionary.hasKeyAndValue(RotationInfo.identifier)) { - _rotation = dictionary.value(RotationInfo.identifier); - } - - if (!dictionary.hasKeyAndValue("Volume")) { - LERROR("No volume dictionary specified."); - } - - ghoul::Dictionary volumeDictionary = dictionary.value("Volume"); - - std::string volumeFilename; - if (volumeDictionary.getValue("Filename", volumeFilename)) { - _volumeFilename = absPath(volumeFilename); - } - else { - LERROR("No volume filename specified."); - } - glm::vec3 volumeDimensions = glm::vec3(0.f); - if (volumeDictionary.getValue("Dimensions", volumeDimensions)) { - _volumeDimensions = static_cast(volumeDimensions); - } - else { - LERROR("No volume dimensions specified."); - } - glm::vec3 volumeSize = glm::vec3(0.f); - if (volumeDictionary.getValue("Size", volumeSize)) { - _volumeSize = volumeSize; - } - else { - LERROR("No volume dimensions specified."); - } - - if (volumeDictionary.hasKey(NumberOfRayCastingStepsInfo.identifier)) { - _numberOfRayCastingSteps = static_cast( - volumeDictionary.value(NumberOfRayCastingStepsInfo.identifier) - ); - } - else { - LINFO("Number of raycasting steps not specified. Using default value."); - } - - _downScaleVolumeRendering.setVisibility(properties::Property::Visibility::Developer); - if (volumeDictionary.hasKey(DownscaleVolumeRenderingInfo.identifier)) { - _downScaleVolumeRendering = - volumeDictionary.value(DownscaleVolumeRenderingInfo.identifier); - } - - if (!dictionary.hasKeyAndValue("Points")) { - LERROR("No points dictionary specified."); - } - - ghoul::Dictionary pointsDictionary = dictionary.value("Points"); - std::string pointsFilename; - if (pointsDictionary.getValue("Filename", pointsFilename)) { - _pointsFilename = absPath(pointsFilename); - } - else { - LERROR("No points filename specified."); - } - - if (pointsDictionary.hasKeyAndValue(EnabledPointsRatioInfo.identifier)) { - _enabledPointsRatio = static_cast( - pointsDictionary.value(EnabledPointsRatioInfo.identifier) - ); - } - - std::string pointSpreadFunctionTexturePath; - if (pointsDictionary.getValue("Texture", pointSpreadFunctionTexturePath)) { - _pointSpreadFunctionTexturePath = absPath(pointSpreadFunctionTexturePath); - _pointSpreadFunctionFile = std::make_unique( - _pointSpreadFunctionTexturePath - ); - } - else { - LERROR("No points filename specified."); - } -} - -void RenderableGalaxy::initializeGL() { - // Aspect is currently hardcoded to cubic voxels. - _aspect = static_cast(_volumeDimensions); - _aspect /= std::max(std::max(_aspect.x, _aspect.y), _aspect.z); - - // The volume - volume::RawVolumeReader> reader( - _volumeFilename, - _volumeDimensions - ); - _volume = reader.read(); - - _texture = std::make_unique( - _volumeDimensions, - ghoul::opengl::Texture::Format::RGBA, - GL_RGBA, - GL_UNSIGNED_BYTE, - ghoul::opengl::Texture::FilterMode::Linear, - ghoul::opengl::Texture::WrappingMode::ClampToEdge - ); - - _texture->setPixelData( - reinterpret_cast(_volume->data()), - ghoul::opengl::Texture::TakeOwnership::No - ); - - _texture->setDimensions(_volume->dimensions()); - _texture->uploadTexture(); - - _raycaster = std::make_unique(*_texture); - _raycaster->initialize(); - - global::raycasterManager.attachRaycaster(*_raycaster); - - auto onChange = [&](bool enabled) { - if (enabled) { - global::raycasterManager.attachRaycaster(*_raycaster); - } - else { - global::raycasterManager.detachRaycaster(*_raycaster); - } - }; - - onEnabledChange(onChange); - - addProperty(_volumeRenderingEnabled); - addProperty(_starRenderingEnabled); - addProperty(_stepSize); - addProperty(_absorptionMultiply); - addProperty(_emissionMultiply); - addProperty(_starRenderingMethod); - addProperty(_enabledPointsRatio); - addProperty(_translation); - addProperty(_rotation); - addProperty(_downScaleVolumeRendering); - addProperty(_numberOfRayCastingSteps); - - // initialize points. - if (_pointsFilename.empty()) { - return; - } - - _pointsProgram = global::renderEngine.buildRenderProgram( - "Galaxy points", - absPath("${MODULE_GALAXY}/shaders/points_vs.glsl"), - absPath("${MODULE_GALAXY}/shaders/points_fs.glsl") - ); - _billboardsProgram = global::renderEngine.buildRenderProgram( - "Galaxy billboard", - absPath("${MODULE_GALAXY}/shaders/billboard_vs.glsl"), - absPath("${MODULE_GALAXY}/shaders/billboard_fs.glsl"), - absPath("${MODULE_GALAXY}/shaders/billboard_ge.glsl") - ); - - if (!_pointSpreadFunctionTexturePath.empty()) { - _pointSpreadFunctionTexture = ghoul::io::TextureReader::ref().loadTexture( - absPath(_pointSpreadFunctionTexturePath) - ); - - if (_pointSpreadFunctionTexture) { - LDEBUG(fmt::format( - "Loaded texture from '{}'", - absPath(_pointSpreadFunctionTexturePath) - )); - _pointSpreadFunctionTexture->uploadTexture(); - } - _pointSpreadFunctionTexture->setFilter( - ghoul::opengl::Texture::FilterMode::AnisotropicMipMap - ); - - _pointSpreadFunctionFile = std::make_unique( - _pointSpreadFunctionTexturePath - ); - } - - ghoul::opengl::updateUniformLocations( - *_pointsProgram, - _uniformCachePoints, - UniformNamesPoints - ); - ghoul::opengl::updateUniformLocations( - *_billboardsProgram, - _uniformCacheBillboards, - UniformNamesBillboards - ); - - _pointsProgram->setIgnoreUniformLocationError( - ghoul::opengl::ProgramObject::IgnoreError::Yes - ); - - GLint positionAttrib = _pointsProgram->attributeLocation("in_position"); - GLint colorAttrib = _pointsProgram->attributeLocation("in_color"); - - - std::vector pointPositions; - std::vector pointColors; - - std::string cachedPointsFile = FileSys.cacheManager()->cachedFilename( - _pointsFilename, - ghoul::filesystem::CacheManager::Persistent::Yes - ); - const bool hasCachedFile = FileSys.fileExists(cachedPointsFile); - if (hasCachedFile) { - LINFO(fmt::format("Cached file '{}' used for galaxy point file '{}'", - cachedPointsFile, _pointsFilename - )); - - Result res = loadCachedFile(cachedPointsFile); - if (res.success) { - pointPositions = std::move(res.positions); - pointColors = std::move(res.color); - } - else { - FileSys.cacheManager()->removeCacheFile(_pointsFilename); - Result resPoint = loadPointFile(_pointsFilename); - pointPositions = std::move(resPoint.positions); - pointColors = std::move(resPoint.color); - saveCachedFile( - cachedPointsFile, - pointPositions, - pointColors, - _nPoints, - _enabledPointsRatio - ); - } - } - else { - Result res = loadPointFile(_pointsFilename); - ghoul_assert(res.success, "Point file loading failed"); - pointPositions = std::move(res.positions); - pointColors = std::move(res.color); - saveCachedFile( - cachedPointsFile, - pointPositions, - pointColors, - _nPoints, - _enabledPointsRatio - ); - } - - glGenVertexArrays(1, &_pointsVao); - glGenBuffers(1, &_positionVbo); - glGenBuffers(1, &_colorVbo); - - glBindVertexArray(_pointsVao); - glBindBuffer(GL_ARRAY_BUFFER, _positionVbo); - glBufferData(GL_ARRAY_BUFFER, - pointPositions.size() * sizeof(glm::vec3), - pointPositions.data(), - GL_STATIC_DRAW - ); - - glBindBuffer(GL_ARRAY_BUFFER, _colorVbo); - glBufferData(GL_ARRAY_BUFFER, - pointColors.size() * sizeof(glm::vec3), - pointColors.data(), - GL_STATIC_DRAW - ); - - glBindBuffer(GL_ARRAY_BUFFER, _positionVbo); - glEnableVertexAttribArray(positionAttrib); - glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, _colorVbo); - glEnableVertexAttribArray(colorAttrib); - glVertexAttribPointer(colorAttrib, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} - -void RenderableGalaxy::deinitializeGL() { - if (_raycaster) { - global::raycasterManager.detachRaycaster(*_raycaster); - _raycaster = nullptr; - } - - glDeleteVertexArrays(1, &_pointsVao); - glDeleteBuffers(1, &_positionVbo); - glDeleteBuffers(1, &_colorVbo); -} - -bool RenderableGalaxy::isReady() const { - return true; -} - -void RenderableGalaxy::update(const UpdateData& data) { - if (!_raycaster) { - return; - } - //glm::mat4 transform = glm::translate(, static_cast(_translation)); - const glm::vec3 eulerRotation = static_cast(_rotation); - glm::mat4 transform = glm::rotate( - glm::mat4(1.f), - eulerRotation.x, - glm::vec3(1.f, 0.f, 0.f) - ); - transform = glm::rotate(transform, eulerRotation.y, glm::vec3(0.f, 1.f, 0.f)); - transform = glm::rotate(transform, eulerRotation.z, glm::vec3(0.f, 0.f, 1.f)); - - glm::mat4 volumeTransform = glm::scale(transform, _volumeSize); - _pointTransform = transform; - //_pointTransform = glm::scale(transform, _pointScaling); - - const glm::vec4 translation = glm::vec4(_translation.value()*_volumeSize, 0.f); - - // Todo: handle floating point overflow, to actually support translation. - - volumeTransform[3] += translation; - _pointTransform[3] += translation; - - _raycaster->setDownscaleRender(_downScaleVolumeRendering); - _raycaster->setMaxSteps(static_cast(_numberOfRayCastingSteps)); - _raycaster->setStepSize(_stepSize); - _raycaster->setAspect(_aspect); - _raycaster->setModelTransform(volumeTransform); - _raycaster->setAbsorptionMultiplier(_absorptionMultiply); - _raycaster->setEmissionMultiplier(_emissionMultiply); - _raycaster->setTime(data.time.j2000Seconds()); -} - -void RenderableGalaxy::render(const RenderData& data, RendererTasks& tasks) { - // Render the volume - if (_raycaster && _volumeRenderingEnabled) { - RaycasterTask task { _raycaster.get(), data }; - - const glm::vec3 position = data.camera.positionVec3(); - const float length = safeLength(position); - const glm::vec3 galaxySize = _volumeSize; - - const float maxDim = std::max(std::max(galaxySize.x, galaxySize.y), galaxySize.z); - - const float lowerRampStart = maxDim * 0.01f; - const float lowerRampEnd = maxDim * 0.1f; - - const float upperRampStart = maxDim * 2.f; - const float upperRampEnd = maxDim * 10.f; - - float opacityCoefficient = 1.f; - if (length < lowerRampStart) { - opacityCoefficient = 0.f; // camera really close - } - else if (length < lowerRampEnd) { - opacityCoefficient = (length - lowerRampStart) / - (lowerRampEnd - lowerRampStart); - } - else if (length < upperRampStart) { - opacityCoefficient = 1.f; // sweet spot (max) - } - else if (length < upperRampEnd) { - opacityCoefficient = 1.f - (length - upperRampStart) / - (upperRampEnd - upperRampStart); //fade out - } - else { - opacityCoefficient = 0; - } - - _opacityCoefficient = opacityCoefficient; - ghoul_assert( - _opacityCoefficient >= 0.f && _opacityCoefficient <= 1.f, - "Opacity coefficient was not between 0 and 1" - ); - if (opacityCoefficient > 0) { - _raycaster->setOpacityCoefficient(_opacityCoefficient); - tasks.raycasterTasks.push_back(task); - } - } - - // Render the stars - if (_starRenderingEnabled && _opacityCoefficient > 0.f) { - if (_starRenderingMethod == 1) { - renderBillboards(data); - } - else { - renderPoints(data); - } - } -} - -void RenderableGalaxy::renderPoints(const RenderData& data) { - if (!_pointsProgram) { - return; - } - // Saving current OpenGL state - GLenum blendEquationRGB; - GLenum blendEquationAlpha; - GLenum blendDestAlpha; - GLenum blendDestRGB; - GLenum blendSrcAlpha; - GLenum blendSrcRGB; - GLboolean depthMask; - - glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha); - glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDestAlpha); - glGetIntegerv(GL_BLEND_DST_RGB, &blendDestRGB); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha); - glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB); - - glGetBooleanv(GL_DEPTH_WRITEMASK, &depthMask); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glDepthMask(false); - glDisable(GL_DEPTH_TEST); - - _pointsProgram->activate(); - - glm::dmat4 rotMatrix = glm::rotate( - glm::dmat4(1.0), - glm::pi(), - glm::dvec3(1.0, 0.0, 0.0)) * - glm::rotate(glm::dmat4(1.0), 3.1248, glm::dvec3(0.0, 1.0, 0.0)) * - glm::rotate(glm::dmat4(1.0), 4.45741, glm::dvec3(0.0, 0.0, 1.0) - ); - - glm::dmat4 modelMatrix = - glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * - glm::dmat4(data.modelTransform.rotation) * rotMatrix * - glm::dmat4(glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale))); - - glm::dmat4 projectionMatrix = glm::dmat4(data.camera.projectionMatrix()); - - glm::dmat4 cameraViewProjectionMatrix = projectionMatrix * - data.camera.combinedViewMatrix(); - - _pointsProgram->setUniform(_uniformCachePoints.modelMatrix, modelMatrix); - _pointsProgram->setUniform( - _uniformCachePoints.cameraViewProjectionMatrix, - cameraViewProjectionMatrix - ); - - glm::dvec3 eyePosition = glm::dvec3( - glm::inverse(data.camera.combinedViewMatrix()) * - glm::dvec4(0.0, 0.0, 0.0, 1.0) - ); - _pointsProgram->setUniform(_uniformCachePoints.eyePosition, eyePosition); - _pointsProgram->setUniform( - _uniformCachePoints.opacityCoefficient, - _opacityCoefficient - ); - - glBindVertexArray(_pointsVao); - glDrawArrays(GL_POINTS, 0, static_cast(_nPoints * _enabledPointsRatio)); - - glBindVertexArray(0); - - _pointsProgram->deactivate(); - - glEnable(GL_DEPTH_TEST); - glDepthMask(true); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Restores OpenGL blending state - glBlendEquationSeparate(blendEquationRGB, blendEquationAlpha); - glBlendFuncSeparate(blendSrcRGB, blendDestRGB, blendSrcAlpha, blendDestAlpha); - glDepthMask(depthMask); -} - -void RenderableGalaxy::renderBillboards(const RenderData& data) { - if (!_billboardsProgram) { - return; - } - - // Saving current OpenGL state - GLenum blendEquationRGB; - GLenum blendEquationAlpha; - GLenum blendDestAlpha; - GLenum blendDestRGB; - GLenum blendSrcAlpha; - GLenum blendSrcRGB; - GLboolean depthMask; - - glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha); - glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDestAlpha); - glGetIntegerv(GL_BLEND_DST_RGB, &blendDestRGB); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha); - glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB); - - glGetBooleanv(GL_DEPTH_WRITEMASK, &depthMask); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glDepthMask(false); - glDisable(GL_DEPTH_TEST); - - _billboardsProgram->activate(); - - glm::dmat4 rotMatrix = glm::rotate( - glm::dmat4(1.0), - glm::pi(), - glm::dvec3(1.0, 0.0, 0.0)) * - glm::rotate(glm::dmat4(1.0), 3.1248, glm::dvec3(0.0, 1.0, 0.0)) * - glm::rotate(glm::dmat4(1.0), 4.45741, glm::dvec3(0.0, 0.0, 1.0) - ); - - glm::dmat4 modelMatrix = - glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * - glm::dmat4(data.modelTransform.rotation) * rotMatrix * - glm::dmat4(glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale))); - - glm::dmat4 projectionMatrix = glm::dmat4(data.camera.projectionMatrix()); - - glm::dmat4 cameraViewProjectionMatrix = projectionMatrix * - data.camera.combinedViewMatrix(); - - _billboardsProgram->setUniform(_uniformCacheBillboards.modelMatrix, modelMatrix); - _billboardsProgram->setUniform( - _uniformCacheBillboards.cameraViewProjectionMatrix, - cameraViewProjectionMatrix - ); - - glm::dvec3 eyePosition = glm::dvec3( - glm::inverse(data.camera.combinedViewMatrix()) * - glm::dvec4(0.0, 0.0, 0.0, 1.0) - ); - _billboardsProgram->setUniform(_uniformCacheBillboards.eyePosition, eyePosition); - - glm::dvec3 cameraUp = data.camera.lookUpVectorWorldSpace(); - _billboardsProgram->setUniform(_uniformCacheBillboards.cameraUp, cameraUp); - - ghoul::opengl::TextureUnit psfUnit; - psfUnit.activate(); - _pointSpreadFunctionTexture->bind(); - _billboardsProgram->setUniform(_uniformCacheBillboards.psfTexture, psfUnit); - - glBindVertexArray(_pointsVao); - glDrawArrays(GL_POINTS, 0, static_cast(_nPoints * _enabledPointsRatio)); - - glBindVertexArray(0); - - _billboardsProgram->deactivate(); - - glEnable(GL_DEPTH_TEST); - glDepthMask(true); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Restores OpenGL blending state - glBlendEquationSeparate(blendEquationRGB, blendEquationAlpha); - glBlendFuncSeparate(blendSrcRGB, blendDestRGB, blendSrcAlpha, blendDestAlpha); - glDepthMask(depthMask); -} - -float RenderableGalaxy::safeLength(const glm::vec3& vector) const { - const float maxComponent = std::max( - std::max(std::abs(vector.x), std::abs(vector.y)), std::abs(vector.z) - ); - return glm::length(vector / maxComponent) * maxComponent; -} - -RenderableGalaxy::Result RenderableGalaxy::loadPointFile(const std::string&) { - std::vector pointPositions; - std::vector pointColors; - int64_t nPoints; - - std::ifstream pointFile(_pointsFilename, std::ios::in); - - // Read header for OFF (Object File Format) - std::string line; - std::getline(pointFile, line); - - // Read point count - std::getline(pointFile, line); - std::istringstream iss(line); - iss >> nPoints; - - // Prepare point reading - _nPoints = static_cast(nPoints); - - // Read points - float x, y, z, r, g, b, a; - for (size_t i = 0; - i < static_cast(_nPoints * _enabledPointsRatio.maxValue()) + 1; - ++i) - { - std::getline(pointFile, line); - std::istringstream issp(line); - issp >> x >> y >> z >> r >> g >> b >> a; - - // Convert kiloparsec to meters - glm::vec3 position = glm::vec3(x, y, z); - position *= (distanceconstants::Parsec * 100); - - pointPositions.emplace_back(position); - pointColors.emplace_back(r, g, b); - } - - Result res; - res.success = true; - res.positions = std::move(pointPositions); - res.color = std::move(pointColors); - return res; -} - -RenderableGalaxy::Result RenderableGalaxy::loadCachedFile(const std::string& file) { - std::ifstream fileStream(file, std::ifstream::binary); - if (!fileStream.good()) { - LERROR(fmt::format("Error opening file '{}' for loading cache file", file)); - return { false, {}, {} }; - } - - int8_t cacheVersion; - fileStream.read(reinterpret_cast(&cacheVersion), sizeof(int8_t)); - if (cacheVersion != CurrentCacheVersion) { - LINFO(fmt::format("Removing cache file '{}' as the version changed")); - return { false, {}, {} }; - } - - int64_t nPoints; - fileStream.read(reinterpret_cast(&nPoints), sizeof(int64_t)); - _nPoints = static_cast(nPoints); - - float enabledPointsRatio; - fileStream.read(reinterpret_cast(&enabledPointsRatio), sizeof(float)); - _enabledPointsRatio = enabledPointsRatio; - - uint64_t nPositions; - fileStream.read(reinterpret_cast(&nPositions), sizeof(uint64_t)); - std::vector positions; - positions.resize(nPositions); - fileStream.read( - reinterpret_cast(positions.data()), - nPositions * sizeof(glm::vec3) - ); - - uint64_t nColors; - fileStream.read(reinterpret_cast(&nColors), sizeof(uint64_t)); - std::vector colors; - colors.resize(nColors); - fileStream.read( - reinterpret_cast(colors.data()), - nColors * sizeof(glm::vec3) - ); - - Result result; - result.success = true; - result.positions = std::move(positions); - result.color = std::move(colors); - return result; -} - -} // namespace openspace diff --git a/modules/softwareintegration/rendering/renderablegalaxy.h b/modules/softwareintegration/rendering/renderablegalaxy.h deleted file mode 100644 index d48ac6d2ab..0000000000 --- a/modules/softwareintegration/rendering/renderablegalaxy.h +++ /dev/null @@ -1,117 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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_GALAXY___RENDERABLEGALAXY___H__ -#define __OPENSPACE_MODULE_GALAXY___RENDERABLEGALAXY___H__ - -#include - -#include -#include -#include -#include -#include - -namespace ghoul::opengl { class ProgramObject; } - -namespace openspace { - -namespace volume { template class RawVolume; } - -class GalaxyRaycaster; -struct RenderData; - -class RenderableGalaxy : public Renderable { -public: - explicit RenderableGalaxy(const ghoul::Dictionary& dictionary); - virtual ~RenderableGalaxy() = default; - - void initializeGL() override; - void deinitializeGL() override; - bool isReady() const override; - void render(const RenderData& data, RendererTasks& tasks) override; - void update(const UpdateData& data) override; - -private: - void renderPoints(const RenderData& data); - void renderBillboards(const RenderData& data); - float safeLength(const glm::vec3& vector) const; - - struct Result { - bool success; - std::vector positions; - std::vector color; - }; - Result loadPointFile(const std::string& file); - Result loadCachedFile(const std::string& file); - - glm::vec3 _volumeSize = glm::vec3(0.f); - glm::vec3 _pointScaling = glm::vec3(0.f); - properties::BoolProperty _volumeRenderingEnabled; - properties::BoolProperty _starRenderingEnabled; - properties::FloatProperty _stepSize; - properties::FloatProperty _absorptionMultiply; - properties::FloatProperty _emissionMultiply; - properties::OptionProperty _starRenderingMethod; - properties::FloatProperty _enabledPointsRatio; - properties::Vec3Property _translation; - properties::Vec3Property _rotation; - properties::FloatProperty _downScaleVolumeRendering; - properties::FloatProperty _numberOfRayCastingSteps; - - std::unique_ptr _pointSpreadFunctionTexture; - std::unique_ptr _pointSpreadFunctionFile; - - std::string _volumeFilename; - glm::ivec3 _volumeDimensions = glm::ivec3(0); - std::string _pointsFilename; - std::string _pointSpreadFunctionTexturePath; - - std::unique_ptr _raycaster; - std::unique_ptr>> _volume; - std::unique_ptr _texture; - glm::mat4 _pointTransform = glm::mat4(1.f); - glm::vec3 _aspect = glm::vec3(0.f); - float _opacityCoefficient = 0.f; - - std::unique_ptr _pointsProgram; - std::unique_ptr _billboardsProgram; - UniformCache( - modelMatrix, cameraViewProjectionMatrix, eyePosition, - opacityCoefficient - ) _uniformCachePoints; - UniformCache( - modelMatrix, cameraViewProjectionMatrix, - cameraUp, eyePosition, psfTexture - ) _uniformCacheBillboards; - std::vector _pointsData; - size_t _nPoints = 0; - GLuint _pointsVao = 0; - GLuint _positionVbo = 0; - GLuint _colorVbo = 0; -}; - -} // namespace openspace - -#endif // __OPENSPACE_MODULE_GALAXY___RENDERABLEGALAXY___H__ diff --git a/modules/softwareintegration/scripts/filtering.lua b/modules/softwareintegration/scripts/filtering.lua new file mode 100644 index 0000000000..70cc99ef6c --- /dev/null +++ b/modules/softwareintegration/scripts/filtering.lua @@ -0,0 +1,102 @@ +openspace.gaia.documentation = { + { + Name = "addClippingBox", + Arguments = "string, vec3, vec3", + Documentation = "Creates a clipping box for the Gaia renderable in the first argument" + }, + { + Name = "removeClippingBox", + Arguments = "", + Documentation = "" + }, + { + Name = "addClippingSphere", + Arguments = "string, float", + Documentation = "Creates a clipping sphere for the Gaia renderable in the first argument" + }, + { + Name = "removeClippingBox", + Arguments = "", + Documentation = "" + } +} + +openspace.gaia.addClippingBox = function (name, size, position) + local grid_identifier = "Filtering_Box" + local kilo_parsec_in_meter = 30856775814913700000 + + if openspace.hasSceneGraphNode(grid_identifier) then + openspace.removeSceneGraphNode(grid_identifier) + end + + local grid = { + Identifier = grid_identifier, + Transform = { + Translation = { + Type = "StaticTranslation", + Position = { position[1] * kilo_parsec_in_meter, position[2] * kilo_parsec_in_meter, position[3] * kilo_parsec_in_meter } + } + }, + Renderable = { + Type = "RenderableBoxGrid", + GridColor = { 0.6, 0.5, 0.7, 1.0 }, + LineWidth = 2.0, + Size = { size[1] * kilo_parsec_in_meter, size[2] * kilo_parsec_in_meter, size[3] * kilo_parsec_in_meter} + }, + GUI = { + Name = "Filtering Grid", + Path = "/Other/Grids" + } + } + + openspace.addSceneGraphNode(grid) + + openspace.setPropertyValue('Scene.' .. name .. '.renderable.FilterPosX', { (position[1] - size[1] / 2) * kilo_parsec_in_meter, (position[1] + size[1] / 2) * kilo_parsec_in_meter }) + openspace.setPropertyValue('Scene.' .. name .. '.renderable.FilterPosY', { (position[2] - size[2] / 2) * kilo_parsec_in_meter, (position[2] + size[2] / 2) * kilo_parsec_in_meter }) + openspace.setPropertyValue('Scene.' .. name .. '.renderable.FilterPosZ', { (position[3] - size[3] / 2) * kilo_parsec_in_meter, (position[3] + size[3] / 2) * kilo_parsec_in_meter }) +end + +openspace.gaia.removeClippingBox = function() + local grid_identifier = "Filtering_Box" + + if openspace.hasSceneGraphNode(grid_identifier) then + openspace.removeSceneGraphNode(grid_identifier) + end +end + + +openspace.gaia.addClippingSphere = function (name, radius) + local grid_identifier = "Filtering_Sphere" + local kilo_parsec_in_meter = 30856775814913700000 + + + if openspace.hasSceneGraphNode(grid_identifier) then + openspace.removeSceneGraphNode(grid_identifier) + end + + local grid = { + Identifier = grid_identifier, + Renderable = { + Type = "RenderableSphericalGrid", + GridColor = { 0.6, 0.5, 0.7, 1.0 }, + LineWidth = 1.0, + Radius = radius * kilo_parsec_in_meter + }, + GUI = { + Name = "Filtering Sphere", + Path = "/Other/Grids" + } + } + + openspace.addSceneGraphNode(grid) + + openspace.setPropertyValue('Scene.' .. name .. '.renderable.FilterDist', radius * kilo_parsec_in_meter) +end + +openspace.gaia.removeClippingSphere = function() + local grid_identifier = "Filtering_Sphere" + + if openspace.hasSceneGraphNode(grid_identifier) then + openspace.removeSceneGraphNode(grid_identifier) + end +end \ No newline at end of file diff --git a/modules/softwareintegration/shaders/billboard_ge.glsl b/modules/softwareintegration/shaders/billboard_ge.glsl deleted file mode 100644 index f6d1514312..0000000000 --- a/modules/softwareintegration/shaders/billboard_ge.glsl +++ /dev/null @@ -1,101 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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. * - ****************************************************************************************/ - -#version __CONTEXT__ - -#include "floatoperations.glsl" -#include "PowerScaling/powerScalingMath.hglsl" - -uniform dvec3 eyePosition; -uniform dvec3 cameraUp; -uniform dmat4 cameraViewProjectionMatrix; -uniform dmat4 modelMatrix; - -layout(points) in; -layout(triangle_strip, max_vertices = 4) out; - -in vec4 vs_gPosition[]; -in vec3 vs_color[]; - -out vec4 vs_position; -out vec2 psfCoords; -flat out vec3 ge_color; -flat out float ge_screenSpaceDepth; - -const double PARSEC = 3.08567756E16; - -void main() { - vs_position = gl_in[0].gl_Position; // in object space - ge_color = vs_color[0]; - - double scaleMultiply = 8.0; - - dvec4 dpos = dvec4(vs_position); - dpos.xyz *= scaleMultiply; - dpos = modelMatrix * dpos; - dpos /= PARSEC; - //It lies about 8 kpc from the center on what is known as the Orion Arm of the Milky Way - dpos.x += 8000; - - scaleMultiply *= 4.0; - dvec3 scaledRight = dvec3(0.0); - dvec3 scaledUp = dvec3(0.0); - vec4 bottomLeftVertex, bottomRightVertex, topLeftVertex, topRightVertex; - - dvec3 normal = normalize(eyePosition - dpos.xyz); - dvec3 newRight = normalize(cross(cameraUp, normal)); - dvec3 newUp = cross(normal, newRight); - scaledRight = scaleMultiply * newRight; - scaledUp = scaleMultiply * newUp; - - bottomLeftVertex = z_normalization(vec4(cameraViewProjectionMatrix * - dvec4(dpos.xyz - scaledRight - scaledUp, dpos.w))); - - //dvec4 dposCamera = cameraViewProjectionMatrix * dpos; - //ge_screenSpaceDepth = float(length(eyePosition - dposCamera.xyz)/PARSEC); - ge_screenSpaceDepth = bottomLeftVertex.w; - - topRightVertex = z_normalization(vec4(cameraViewProjectionMatrix * - dvec4(dpos.xyz + scaledUp + scaledRight, dpos.w))); - - bottomRightVertex = z_normalization(vec4(cameraViewProjectionMatrix * - dvec4(dpos.xyz + scaledRight - scaledUp, dpos.w))); - topLeftVertex = z_normalization(vec4(cameraViewProjectionMatrix * - dvec4(dpos.xyz + scaledUp - scaledRight, dpos.w))); - - // Build primitive - gl_Position = topLeftVertex; - psfCoords = vec2(-1.0, 1.0); - EmitVertex(); - gl_Position = bottomLeftVertex; - psfCoords = vec2(-1.0, -1.0); - EmitVertex(); - gl_Position = topRightVertex; - psfCoords = vec2(1.0, 1.0); - EmitVertex(); - gl_Position = bottomRightVertex; - psfCoords = vec2(1.0, -1.0); - EmitVertex(); - EndPrimitive(); -} diff --git a/modules/softwareintegration/shaders/gaia_billboard_fs.glsl b/modules/softwareintegration/shaders/gaia_billboard_fs.glsl new file mode 100644 index 0000000000..d9b9ebbbd5 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_billboard_fs.glsl @@ -0,0 +1,114 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "floatoperations.glsl" + +layout (location = 0) out vec4 outColor; + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float ONE_PARSEC = 3.08567758e16; // 1 Parsec +const float FLT_MAX = 3.402823466e38; // Max float constant in GLSL +const float LUM_LOWER_CAP = 0.01; + +in vec2 ge_brightness; +in vec4 ge_gPosition; +in vec2 texCoord; +in float ge_starDistFromSun; +in float ge_cameraDistFromSun; +in float ge_observedDist; + +uniform sampler2D psfTexture; +uniform sampler1D colorTexture; +uniform float luminosityMultiplier; +uniform float sharpness; +uniform int renderOption; + +vec3 color2rgb(float color) { + // BV is [-0.4, 2.0] + float st = (color + 0.4) / (2.0 + 0.4); + + // Bp-Rp[-2.0, 6.5], Bp-G[-2.1, 5.0], G-Rp[-1.0, 3.0] + //float st = (color + 1.0) / (5.0 + 1.0); + + return texture(colorTexture, st).rgb; +} + +void main() { + + // Assume all stars has equal luminosity as the Sun when no magnitude is loaded. + float luminosity = 0.05; + vec3 color = vec3(luminosity); + float ratioMultiplier = 0.05; + + vec4 textureColor = texture(psfTexture, texCoord); + textureColor.a = pow(textureColor.a, sharpness); + if (textureColor.a < 0.001) { + discard; + } + + // Calculate the color and luminosity if we have the magnitude and B-V color. + if ( renderOption != RENDEROPTION_STATIC ) { + color = color2rgb(ge_brightness.y); + ratioMultiplier = 0.5; + + // Absolute magnitude is brightness a star would have at 10 pc away. + float absoluteMagnitude = ge_brightness.x; + + // From formula: MagSun - MagStar = 2.5*log(LumStar / LumSun), it gives that: + // LumStar = 10^(1.89 - 0.4*Magstar) , if LumSun = 1 and MagSun = 4.72 + luminosity = pow(10.0, 1.89 - 0.4 * absoluteMagnitude); + + // If luminosity is really really small then set it to a static low number. + if (luminosity < LUM_LOWER_CAP) { + luminosity = LUM_LOWER_CAP; + } + } + + // Luminosity decrease by {squared} distance [measured in Pc]. + float observedDistance = ge_observedDist / ONE_PARSEC; + luminosity /= pow(observedDistance, 2.0); + + // Multiply our color with the luminosity as well as a user-controlled property. + color *= luminosity * pow(luminosityMultiplier, 3.0); + + // Decrease contributing brightness for stars in central cluster. + if ( ge_cameraDistFromSun > ge_starDistFromSun ) { + float ratio = ge_starDistFromSun / ge_cameraDistFromSun; + //color *= ratio * ratioMultiplier; + } + + // Use truncating tonemapping here so we don't overexposure individual stars. + //color = 1.0 - 1.0 * exp(-5.0 * color.rgb); + float maxVal = max(max(color.r, color.g), color.b); + if (maxVal > 1.0) { + color /= maxVal; + } + + outColor = vec4(color, textureColor.a); +} diff --git a/modules/softwareintegration/shaders/gaia_billboard_ge.glsl b/modules/softwareintegration/shaders/gaia_billboard_ge.glsl new file mode 100644 index 0000000000..3296ae1fa2 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_billboard_ge.glsl @@ -0,0 +1,132 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "floatoperations.glsl" + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float EPS = 1e-5; + +layout(points) in; +in vec2 vs_brightness[]; +in vec4 vs_gPosition[]; +in float vs_starDistFromSun[]; +in float vs_cameraDistFromSun[]; + +layout(triangle_strip, max_vertices = 4) out; +out vec2 ge_brightness; +out vec4 ge_gPosition; +out vec2 texCoord; +out float ge_starDistFromSun; +out float ge_cameraDistFromSun; +out float ge_observedDist; + +uniform dmat4 view; +uniform dmat4 projection; + +uniform dvec3 cameraPos; +uniform dvec3 cameraLookUp; +uniform float viewScaling; +uniform float cutOffThreshold; +uniform float closeUpBoostDist; +uniform float billboardSize; +uniform int renderOption; +uniform float magnitudeBoost; + +const vec2 corners[4] = vec2[4]( + vec2(0.0, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 1.0), + vec2(1.0, 0.0) +); + + +void main() { + + ge_brightness = vs_brightness[0]; + ge_starDistFromSun = vs_starDistFromSun[0]; + ge_cameraDistFromSun = vs_cameraDistFromSun[0]; + + vec4 viewPosition = vec4(view * vs_gPosition[0]); + + // Make closer stars look a bit bigger. + ge_observedDist = safeLength(viewPosition / viewScaling); + float closeUpBoost = closeUpBoostDist / ge_observedDist; + float initStarSize = billboardSize; + + // Use magnitude for size boost as well. + if ( renderOption != RENDEROPTION_STATIC ) { + // DR1 magnitudes are [4, 20], but could be [-15, 20] according to this chart: + // https://qph.fs.quoracdn.net/main-qimg-317a18e3b228efc7d7f67a1632a55961 + // Negative magnitude => Giants + // Big positive magnitude => Dwarfs + float absoluteMagnitude = vs_brightness[0].x; + float normalizedMagnitude = (absoluteMagnitude - 20) / -1; // (-15 - 20); + + // TODO: A linear scale is prabably not the best! + initStarSize += normalizedMagnitude * (magnitudeBoost / 50); + } + + vec4 position = gl_in[0].gl_Position; + vec2 starSize = vec2(initStarSize + closeUpBoost) * position.w / 1000.0; + + float distThreshold = cutOffThreshold - log(ge_observedDist) / log(4.0); + + // Discard geometry if star has no position (but wasn't a nullArray). + // Or if observed distance is above threshold set by cutOffThreshold. + // By discarding in gs instead of fs we save computations for when nothing is visible. + if (length(position) < EPS || distThreshold <= 0) { + return; + } + + vec4 centerWorldPos = vs_gPosition[0]; + + dvec3 cameraNormal = normalize(cameraPos - dvec3(centerWorldPos.xyz)); + dvec3 newRight = normalize(cross(cameraLookUp, cameraNormal)); + dvec3 newUp = cross(cameraNormal, newRight); + vec4 wCameraRight = vec4(newRight, 0.0); + vec4 wCameraUp = vec4(newUp, 0.0); + + float multiplier = float(length(cameraPos)); + starSize *= float(multiplier/1E1); + + for (int i = 0; i < 4; i++) { + // Always turn the billboard towards the camera (needed for warped screen). + vec4 cornerPoint = centerWorldPos + + wCameraRight * starSize.x * (corners[i].x - 0.5) + + wCameraUp * starSize.y * (corners[i].y - 0.5); + gl_Position = vec4(projection * view * cornerPoint); + gl_Position.z = 0.0; + texCoord = corners[i]; + ge_gPosition = viewPosition; + + EmitVertex(); + } + + EndPrimitive(); +} diff --git a/modules/softwareintegration/shaders/gaia_billboard_nofbo_fs.glsl b/modules/softwareintegration/shaders/gaia_billboard_nofbo_fs.glsl new file mode 100644 index 0000000000..2e67386300 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_billboard_nofbo_fs.glsl @@ -0,0 +1,123 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 "fragment.glsl" +#include "floatoperations.glsl" + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float ONE_PARSEC = 3.08567758e16; // 1 Parsec +const float DEFAULT_DEPTH = 3.08567758e19; // 1000 Pc +const float LUM_LOWER_CAP = 0.01; + +in vec2 ge_brightness; +in vec4 ge_gPosition; +in vec2 texCoord; +in float ge_starDistFromSun; +in float ge_cameraDistFromSun; +in float ge_observedDist; + +uniform sampler2D psfTexture; +uniform sampler1D colorTexture; +uniform float luminosityMultiplier; +uniform float sharpness; +uniform int renderOption; + +vec3 color2rgb(float color) { + // BV is [-0.4, 2.0] + float st = (color + 0.4) / (2.0 + 0.4); + + // Bp-Rp[-2.0, 6.5], Bp-G[-2.1, 5.0], G-Rp[-1.0, 3.0] + //float st = (color + 1.0) / (5.0 + 1.0); + + return texture(colorTexture, st).rgb; +} + +Fragment getFragment() { + + // Assume all stars has equal luminosity as the Sun when no magnitude is loaded. + float luminosity = 1.0; + vec3 color = vec3(luminosity); + float ratioMultiplier = 0.03; + + vec4 textureColor = texture(psfTexture, texCoord); + textureColor.a = pow(textureColor.a, sharpness); + if (textureColor.a < 0.001) { + discard; + } + + // Calculate the color and luminosity if we have the magnitude and B-V color. + if ( renderOption != RENDEROPTION_STATIC ) { + color = color2rgb(ge_brightness.y); + ratioMultiplier = 0.5; + + // Absolute magnitude is brightness a star would have at 10 pc away. + float absoluteMagnitude = ge_brightness.x; + + // From formula: MagSun - MagStar = 2.5*log(LumStar / LumSun), it gives that: + // LumStar = 10^(1.89 - 0.4*Magstar) , if LumSun = 1 and MagSun = 4.72 + luminosity = pow(10.0, 1.89 - 0.4 * absoluteMagnitude); + + // If luminosity is really really small then set it to a static low number. + if (luminosity < LUM_LOWER_CAP) { + luminosity = LUM_LOWER_CAP; + } + } + + // Luminosity decrease by {squared} distance [measured in Pc]. + float observedDistance = ge_observedDist / ONE_PARSEC; + luminosity /= pow(observedDistance, 2.0); + + // Multiply our color with the luminosity as well as a user-controlled property. + color *= luminosity * pow(luminosityMultiplier, 3.0); + + // Decrease contributing brightness for stars in central cluster. + if ( ge_cameraDistFromSun > ge_starDistFromSun ) { + float ratio = ge_starDistFromSun / ge_cameraDistFromSun; + //color *= ratio * ratioMultiplier; + } + + // Use truncating tonemapping here so we don't overexposure individual stars. + //color = 1.0 - 1.0 * exp(-5.0 * color.rgb); + float maxVal = max(max(color.r, color.g), color.b); + if (maxVal > 1.0) { + color /= maxVal; + } + + if (length(color) < 0.01) { + discard; + } + + Fragment frag; + frag.color = vec4(color, textureColor.a);; + // Place stars at back to begin with. + frag.depth = DEFAULT_DEPTH; + frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0); + frag.blend = BLEND_MODE_NORMAL; + + return frag; +} + diff --git a/modules/softwareintegration/shaders/gaia_point_fs.glsl b/modules/softwareintegration/shaders/gaia_point_fs.glsl new file mode 100644 index 0000000000..22fa3fafb9 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_point_fs.glsl @@ -0,0 +1,98 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "floatoperations.glsl" + +layout (location = 0) out vec4 outColor; + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float ONE_PARSEC = 3.08567758e16; // 1 Parsec +const float LUM_LOWER_CAP = 0.01; + +in vec2 ge_brightness; +in vec4 ge_gPosition; +in float ge_starDistFromSun; +in float ge_cameraDistFromSun; +in float ge_observedDist; + +uniform sampler1D colorTexture; +uniform float luminosityMultiplier; +uniform int renderOption; +uniform float viewScaling; + +vec3 color2rgb(float color) { + // BV is [-0.4, 2.0] + float st = (color + 0.4) / (2.0 + 0.4); + + // Bp-Rp[-2.0, 6.5], Bp-G[-2.1, 5.0], G-Rp[-1.0, 3.0] + //float st = (color + 1.0) / (5.0 + 1.0); + + return texture(colorTexture, st).rgb; +} + +void main() { + + // Assume all stars has equal luminosity as the Sun when no magnitude is loaded. + float luminosity = 0.05; + vec3 color = vec3(luminosity); + float ratioMultiplier = 1.0; + + // Calculate the color and luminosity if we have the magnitude and B-V color. + if ( renderOption != RENDEROPTION_STATIC ) { + color = color2rgb(ge_brightness.y); + ratioMultiplier = 0.01; + + // Absolute magnitude is brightness a star would have at 10 pc away. + float absoluteMagnitude = ge_brightness.x; + + // From formula: MagSun - MagStar = 2.5*log(LumStar / LumSun), it gives that: + // LumStar = 10^(1.89 - 0.4*Magstar) , if LumSun = 1 and MagSun = 4.72 + luminosity = pow(10.0, 1.89 - 0.4 * absoluteMagnitude); + + // If luminosity is really really small then set it to a static low number. + if (luminosity < LUM_LOWER_CAP) { + luminosity = LUM_LOWER_CAP; + } + } + + // Luminosity decrease by {squared} distance [measured in Pc]. + float observedDistance = ge_observedDist / ONE_PARSEC; + luminosity /= pow(observedDistance, 2.0); + + // Multiply our color with the luminosity as well as a user-controlled property. + color *= luminosity * pow(luminosityMultiplier, 3.0); + + // Decrease contributing brightness for stars in central cluster. + if ( ge_cameraDistFromSun > ge_starDistFromSun ) { + float ratio = ge_starDistFromSun / ge_cameraDistFromSun; + //color *= ratio * ratioMultiplier; + } + + outColor = vec4(color, 1.0f); +} diff --git a/modules/softwareintegration/shaders/points_vs.glsl b/modules/softwareintegration/shaders/gaia_point_ge.glsl similarity index 62% rename from modules/softwareintegration/shaders/points_vs.glsl rename to modules/softwareintegration/shaders/gaia_point_ge.glsl index c8be748996..b36e18f5bb 100644 --- a/modules/softwareintegration/shaders/points_vs.glsl +++ b/modules/softwareintegration/shaders/gaia_point_ge.glsl @@ -24,35 +24,53 @@ #version __CONTEXT__ -#include "PowerScaling/powerScaling_vs.hglsl" +#include "floatoperations.glsl" -layout(location = 0) in vec3 in_position; -layout(location = 1) in vec3 in_color; +const float EPS = 1e-5; -out vec4 vs_position; -out vec3 vs_color; -out float vs_screenSpaceDepth; -out float vs_starBrightness; +layout(points) in; +in vec2 vs_brightness[]; +in vec4 vs_gPosition[]; +in float vs_starDistFromSun[]; +in float vs_cameraDistFromSun[]; -uniform dmat4 cameraViewProjectionMatrix; -uniform dmat4 modelMatrix; -uniform dvec3 eyePosition; +layout(points, max_vertices = 1) out; +out vec2 ge_brightness; +out vec4 ge_gPosition; +out float ge_starDistFromSun; +out float ge_cameraDistFromSun; +out float ge_observedDist; -const double PARSEC = 3.08567756E16; +uniform dmat4 view; +uniform float viewScaling; +uniform float cutOffThreshold; void main() { - vs_position = vec4(in_position, 1.0); - dvec4 dpos = dvec4(vs_position); - double distanceToStar = length((dpos.xyz - eyePosition)); - vs_starBrightness = clamp(float(8000*PARSEC/distanceToStar), 0.0, 1.0); + ge_brightness = vs_brightness[0]; + ge_starDistFromSun = vs_starDistFromSun[0]; + ge_cameraDistFromSun = vs_cameraDistFromSun[0]; - dpos.xyz *= 8.0; - dpos = modelMatrix * dpos; - dpos /= PARSEC; + vec4 viewPosition = vec4(view * vs_gPosition[0]); - vec4 positionScreenSpace = z_normalization(vec4(cameraViewProjectionMatrix * dpos)); - vs_color = in_color; - vs_screenSpaceDepth = positionScreenSpace.w; - gl_Position = positionScreenSpace; + ge_observedDist = safeLength(viewPosition / viewScaling); + float distThreshold = cutOffThreshold - log(ge_observedDist) / log(4.0); + + vec4 position = gl_in[0].gl_Position; + + // Discard geometry if star has no position (but wasn't a nullArray). + // Or if observed distance is above threshold set by cutOffThreshold. + // By discarding in gs instead of fs we save computations for when nothing is visible. + if (length(position) < EPS || distThreshold <= 0) { + return; + } + + //gl_PointSize = 1.0; + gl_Position = position; + gl_Position.z = 0.0; + ge_gPosition = viewPosition; + + EmitVertex(); + + EndPrimitive(); } diff --git a/modules/softwareintegration/shaders/gaia_ssbo_vs.glsl b/modules/softwareintegration/shaders/gaia_ssbo_vs.glsl new file mode 100644 index 0000000000..ecd6f1b064 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_ssbo_vs.glsl @@ -0,0 +1,187 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "floatoperations.glsl" + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float EPS = 1e-5; +const float Parsec = 3.0856776e16; + +layout (std430) buffer ssbo_idx_data { + int starsPerChunk[]; +}; + +layout (std430) buffer ssbo_comb_data { + float allData[]; +}; + +in int gl_VertexID; + +out vec2 vs_brightness; +out vec4 vs_gPosition; +out float vs_starDistFromSun; +out float vs_cameraDistFromSun; + +uniform dmat4 model; +uniform dmat4 view; +uniform dmat4 projection; +uniform float time; +uniform int renderOption; + +uniform int maxStarsPerNode; +uniform int valuesPerStar; +uniform int nChunksToRender; + +uniform vec2 posXThreshold; +uniform vec2 posYThreshold; +uniform vec2 posZThreshold; +uniform vec2 gMagThreshold; +uniform vec2 bpRpThreshold; +uniform vec2 distThreshold; + +// Use binary search to find the chunk containing our star ID. +int findChunkId(int left, int right, int id) { + + while ( left <= right ) { + int middle = (left + right) / 2; + int firstStarInChunk = starsPerChunk[middle]; + if (left == right || (firstStarInChunk <= id && id < starsPerChunk[middle+1])) { + return middle; + } + else if (id < firstStarInChunk) { + // Go smaller + right = middle - 1; + } + else { + // Go bigger + left = middle + 1; + } + } + return -1; +} + +void main() { + // Fetch our data. + int chunkId = findChunkId(0, nChunksToRender - 1, gl_VertexID); + // Fail safe - this should never happen! + if (chunkId == -1) { + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + int placeInChunk = gl_VertexID - starsPerChunk[chunkId]; + int firstStarInChunk = valuesPerStar * maxStarsPerNode * chunkId; // Chunk offset + int nStarsInChunk = starsPerChunk[chunkId + 1] - starsPerChunk[chunkId]; // Stars in current chunk. + // Remove possible duplicates. + if (nStarsInChunk <= 0) { + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + int startOfPos = firstStarInChunk + placeInChunk * 3; + vec3 in_position = vec3(allData[startOfPos], allData[startOfPos + 1], allData[startOfPos + 2]); + vec2 in_brightness = vec2(0.0); + vec3 in_velocity = vec3(0.0); + + // Check if we should filter this star by position. + if ( (abs(posXThreshold.x) > EPS && in_position.x < posXThreshold.x) || + (abs(posXThreshold.y) > EPS && in_position.x > posXThreshold.y) || + (abs(posYThreshold.x) > EPS && in_position.y < posYThreshold.x) || + (abs(posYThreshold.y) > EPS && in_position.y > posYThreshold.y) || + (abs(posZThreshold.x) > EPS && in_position.z < posZThreshold.x) || + (abs(posZThreshold.y) > EPS && in_position.z > posZThreshold.y) || + (abs(distThreshold.x - distThreshold.y) < EPS + && abs(length(in_position) - distThreshold.y) < EPS) ) { + // Discard star in geometry shader. + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + + if ( renderOption != RENDEROPTION_STATIC ) { + int startOfCol = firstStarInChunk + nStarsInChunk * 3 + placeInChunk * 2; + in_brightness = vec2(allData[startOfCol], allData[startOfCol + 1]); + + // Check if we should filter this star by magnitude or color. + if ( (abs(gMagThreshold.x - gMagThreshold.y) < EPS && abs(gMagThreshold.x - in_brightness.x) < EPS) || + (abs(gMagThreshold.x - 20.0f) > EPS && in_brightness.x < gMagThreshold.x) || + (abs(gMagThreshold.y - 20.0f) > EPS && in_brightness.x > gMagThreshold.y) || + (abs(bpRpThreshold.x - bpRpThreshold.y) < EPS && abs(bpRpThreshold.x - in_brightness.y) < EPS) || + (abs(bpRpThreshold.x) > EPS && in_brightness.y < bpRpThreshold.x) || + (abs(bpRpThreshold.y) > EPS && in_brightness.y > bpRpThreshold.y) ) { + // Discard star in geometry shader. + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + if ( renderOption == RENDEROPTION_MOTION ) { + int startOfVel = firstStarInChunk + nStarsInChunk * 5 + placeInChunk * 3; + in_velocity = vec3(allData[startOfVel], allData[startOfVel + 1], allData[startOfVel + 2]); + } + } + vs_brightness = in_brightness; + + // Convert kiloParsec to meter. + vec4 objectPosition = vec4(in_position * 1000 * Parsec, 1.0); + + // Add velocity [m/s] if we've read any. + objectPosition.xyz += time * in_velocity; + + // Thres moving stars by their new position. + float distPosition = length(objectPosition.xyz / (1000.0 * Parsec) ); + if ( (abs(distThreshold.x - distThreshold.y) > EPS && + ((abs(distThreshold.x) > EPS && distPosition < distThreshold.x) || + (abs(distThreshold.y) > EPS && distPosition > distThreshold.y))) ) { + // Discard star in geometry shader. + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + // Apply camera transforms. + dvec4 viewPosition = view * model * objectPosition; + vec4 sunPosition = vec4(view * model * dvec4(0.0f, 0.0f, 0.0f, 1.0f)); + + vs_starDistFromSun = safeLength(objectPosition); + vs_cameraDistFromSun = safeLength(sunPosition); + + // Remove stars without position, happens when VBO chunk is stuffed with zeros. + // Has to be done in Geometry shader because Vertices cannot be discarded here. + if ( length(in_position) > EPS ){ + vs_gPosition = vec4(model * objectPosition); + gl_Position = vec4(projection * viewPosition); + } + else { + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + } +} diff --git a/modules/softwareintegration/shaders/billboard_fs.glsl b/modules/softwareintegration/shaders/gaia_tonemapping_billboard_fs.glsl similarity index 80% rename from modules/softwareintegration/shaders/billboard_fs.glsl rename to modules/softwareintegration/shaders/gaia_tonemapping_billboard_fs.glsl index 8d6bb8086d..3cf794162f 100644 --- a/modules/softwareintegration/shaders/billboard_fs.glsl +++ b/modules/softwareintegration/shaders/gaia_tonemapping_billboard_fs.glsl @@ -23,28 +23,31 @@ ****************************************************************************************/ #include "fragment.glsl" -#include "floatoperations.glsl" -uniform sampler2D psfTexture; +in vec2 uv; + +uniform sampler2D renderedTexture; + +const float DEFAULT_DEPTH = 3.08567758e19; // 1000 Pc -in vec4 vs_position; -in vec2 psfCoords; -flat in vec3 ge_color; -flat in float ge_screenSpaceDepth; Fragment getFragment() { + + vec4 color = vec4(0.0); + + // BILLBOARDS + // Sample color. Tonemapping done in first shader pass. + vec4 textureColor = texture( renderedTexture, uv ); + + // Use the following to check for any intensity at all. + //color = (length(intensity.rgb) > 0.001) ? vec4(1.0) : vec4(0.0); + Fragment frag; - - vec4 textureColor = texture(psfTexture, 0.5*psfCoords + 0.5); - vec4 fullColor = vec4(ge_color*textureColor.a, textureColor.a); - if (fullColor.a == 0) { - discard; - } - frag.color = fullColor; - - frag.depth = ge_screenSpaceDepth; - frag.gPosition = vs_position; + frag.color = textureColor; + // Place stars at back to begin with. + frag.depth = DEFAULT_DEPTH; frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0); + frag.blend = BLEND_MODE_NORMAL; return frag; } diff --git a/modules/softwareintegration/shaders/gaia_tonemapping_point_fs.glsl b/modules/softwareintegration/shaders/gaia_tonemapping_point_fs.glsl new file mode 100644 index 0000000000..f2da517b0b --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_tonemapping_point_fs.glsl @@ -0,0 +1,179 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 "fragment.glsl" + +in vec2 uv; + +uniform sampler2D renderedTexture; +uniform dmat4 projection; +uniform vec2 screenSize; +uniform int filterSize; +uniform float sigma; +uniform float pixelWeightThreshold; + +const float M_PI = 3.141592653589793238462; +const float DEFAULT_DEPTH = 3.08567758e19; // 1000 Pc + +Fragment getFragment() { + + vec4 color = vec4(0.0); + + // GL_POINTS + + // Use frustum params to be able to compensate for a skewed frustum (in a dome). + float near = float(projection[3][2] / (projection[2][2] - 1.0)); + float left = float(near * (projection[2][0] - 1.0) / projection[0][0]); + float right = float(near * (projection[2][0] + 1.0) / projection[0][0]); + float top = float(near * (projection[2][1] + 1.0) / projection[1][1]); + float bottom = float(near * (projection[2][1] - 1.0) / projection[1][1]); + + float xFactor = float(projection[0][0]); + float yFactor = float(projection[1][1]); + + float planeAspect = yFactor / xFactor; // Equals: (right - left) / (top - bottom) + float screenAspect = screenSize.x / screenSize.y; + float fullAspect = planeAspect / screenAspect; + + // Find screenPos in skewed frustum. uv is [0, 1] + vec2 screenPos = uv * vec2(right - left, top - bottom) + vec2(left, bottom); + + // Find our elliptic scale factors by trigonometric approximation. + float beta = atan(length(screenPos) / near); + vec2 sigmaScaleFactor = vec2( 1.0 / cos(beta), 1.0 / pow(cos(beta), 2.0)); + + float defaultScreen = 1200.0; + float scaling = screenSize.y / defaultScreen * yFactor; + + // Scale filter size depending on screen pos. + vec2 filterScaleFactor = vec2( + pow(screenPos.x / near, 2.0) * fullAspect, + pow(screenPos.y / near, 2.0) + ); + + // Use to ignore scaling. + //filterScaleFactor = vec2(0.0); + //scaling = 1.0; + //sigmaScaleFactor = vec2(1.0); + + // Use the following to find the origo in a skewed frustum. + //Fragment origoFrag; + //vec2 screenOrigo = vec2(-left, -bottom) / vec2(right - left, top - bottom); + //if (abs(screenOrigo.x - uv.x) > 0.0005 && abs(screenOrigo.y - uv.y) > 0.0005) { + // origoFrag.color = vec4(0.0); + //} + //else { + // origoFrag.color = vec4(1.0); + //} + //return origoFrag; + + // Uncomment to compare to original filterSize (assumes origo in center of screen). + //screenPos = (uv - 0.5) * 2.0; // [-1, 1] + //filterScaleFactor = vec2( + // pow(screenPos.x, 2.0), + // pow(screenPos.y, 2.0) + //); + + // Make use of the following flag this to toggle betweeen circular and elliptic distribution. + bool useCircleDist = false; + + // Apply scaling on bloom filter. + vec2 newFilterSize = vec2(filterSize) * (1.0 + length(filterScaleFactor)) * scaling; + + // Calculate params for a rotated Elliptic Gaussian distribution. + float sigmaMajor; + float sigmaMinor; + float a; + float b; + float c; + if (!useCircleDist) { + float alpha = atan(screenPos.y, screenPos.x); + // Apply scaling on sigma. + sigmaMajor = sigma * sigmaScaleFactor.y * scaling; + sigmaMinor = sigma * sigmaScaleFactor.x * scaling; + + a = pow(cos(alpha), 2.0) / (2 * pow(sigmaMajor, 2.0)) + + pow(sin(alpha), 2.0) / (2 * pow(sigmaMinor, 2.0)) ; + b = sin(2 * alpha) / (4 * pow(sigmaMajor, 2.0)) + - sin(2 * alpha) / (4 * pow(sigmaMinor, 2.0)) ; + c = pow(sin(alpha), 2.0) / (2 * pow(sigmaMajor, 2.0)) + + pow(cos(alpha), 2.0) / (2 * pow(sigmaMinor, 2.0)) ; + } + + // Get a [newFilterSize x newFilterSize] filter around our pixel. UV is [0, 1] + vec3 intensity = vec3(0.0); + vec2 pixelSize = 1.0 / screenSize; + ivec2 halfFilterSize = ivec2((newFilterSize - 1.0) / 2.0); + for (int y = -halfFilterSize.y; y <= halfFilterSize.y; y += 1) { + for (int x = -halfFilterSize.x; x <= halfFilterSize.x; x += 1) { + vec2 sPoint = uv + (pixelSize * ivec2(x, y)); + + // Calculate the contribution of this pixel (elliptic gaussian distribution). + float pixelWeight = exp(-( + a * pow(x * fullAspect, 2.0) + + 2 * b * x * y * fullAspect + + c * pow(y, 2.0) + )); + + // Only sample inside FBO texture and if the pixel will contribute to final color. + if (all(greaterThan(sPoint, vec2(0.0))) && all(lessThan(sPoint, vec2(1.0))) + && pixelWeight > pixelWeightThreshold) { + vec4 sIntensity = texture( renderedTexture, sPoint ); + + // Use normal distribution function for halo/bloom effect. + if (useCircleDist) { + float circleDist = sqrt(pow(x / (1 + length(filterScaleFactor)), 2.0) + + pow(y / (1 + length(filterScaleFactor)), 2.0)); + intensity += sIntensity.rgb * (1.0 / (sigma * sqrt(2.0 * M_PI))) * + exp(-(pow(circleDist, 2.0) / (2.0 * pow(sigma, 2.0)))) / filterSize; + } + else { + // Divide contribution by area of ellipse. + intensity += sIntensity.rgb * pixelWeight * fullAspect; + } + } + } + } + // Tonemap intensity to color! + //intensity = 1.0 - 1.0 * exp(-25.0 * intensity); + intensity = pow(intensity, vec3(0.8)); + + if (length(intensity) < 0.01) { + discard; + } + + color = vec4(intensity, 1.0f); + + // Use the following to check for any intensity at all. + //color = (length(intensity.rgb) > 0.001) ? vec4(1.0) : vec4(0.0); + + Fragment frag; + frag.color = color; + // Place stars at back to begin with. + frag.depth = DEFAULT_DEPTH; + frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0); + frag.blend = BLEND_MODE_NORMAL; + + return frag; +} \ No newline at end of file diff --git a/modules/softwareintegration/shaders/billboard_vs.glsl b/modules/softwareintegration/shaders/gaia_tonemapping_vs.glsl similarity index 92% rename from modules/softwareintegration/shaders/billboard_vs.glsl rename to modules/softwareintegration/shaders/gaia_tonemapping_vs.glsl index ee5fff53fb..2f9122836d 100644 --- a/modules/softwareintegration/shaders/billboard_vs.glsl +++ b/modules/softwareintegration/shaders/gaia_tonemapping_vs.glsl @@ -24,13 +24,11 @@ #version __CONTEXT__ -layout(location = 0) in vec3 in_position; -layout(location = 1) in vec3 in_color; +in vec3 in_position; -out vec3 vs_color; +out vec2 uv; void main() { - vs_color = in_color; - - gl_Position = vec4(in_position, 1.0); + uv = (in_position.xy + 1.0) / 2.0; + gl_Position = vec4(in_position, 1.0); } diff --git a/modules/softwareintegration/shaders/gaia_vbo_vs.glsl b/modules/softwareintegration/shaders/gaia_vbo_vs.glsl new file mode 100644 index 0000000000..f5a635c3d2 --- /dev/null +++ b/modules/softwareintegration/shaders/gaia_vbo_vs.glsl @@ -0,0 +1,120 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "floatoperations.glsl" + +// Keep in sync with gaiaoptions.h:RenderOption enum +const int RENDEROPTION_STATIC = 0; +const int RENDEROPTION_COLOR = 1; +const int RENDEROPTION_MOTION = 2; +const float EPS = 1e-5; +const float Parsec = 3.0856776e16; + +in vec3 in_position; +in vec2 in_brightness; +in vec3 in_velocity; + +out vec2 vs_brightness; +out vec4 vs_gPosition; +out float vs_starDistFromSun; +out float vs_cameraDistFromSun; + +uniform dmat4 model; +uniform dmat4 view; +uniform dmat4 projection; +uniform float time; +uniform int renderOption; + +uniform vec2 posXThreshold; +uniform vec2 posYThreshold; +uniform vec2 posZThreshold; +uniform vec2 gMagThreshold; +uniform vec2 bpRpThreshold; +uniform vec2 distThreshold; + +void main() { + vs_brightness = in_brightness; + + // Check if we should filter this star by position. Thres depending on original values. + if ( (abs(posXThreshold.x) > EPS && in_position.x < posXThreshold.x) || + (abs(posXThreshold.y) > EPS && in_position.x > posXThreshold.y) || + (abs(posYThreshold.x) > EPS && in_position.y < posYThreshold.x) || + (abs(posYThreshold.y) > EPS && in_position.y > posYThreshold.y) || + (abs(posZThreshold.x) > EPS && in_position.z < posZThreshold.x) || + (abs(posZThreshold.y) > EPS && in_position.z > posZThreshold.y) || + (abs(distThreshold.x - distThreshold.y) < EPS + && abs(length(in_position) - distThreshold.y) < EPS) || + ( renderOption != RENDEROPTION_STATIC && ( + (abs(gMagThreshold.x - gMagThreshold.y) < EPS && abs(gMagThreshold.x - in_brightness.x) < EPS) || + (abs(gMagThreshold.x - 20.0f) > EPS && in_brightness.x < gMagThreshold.x) || + (abs(gMagThreshold.y - 20.0f) > EPS && in_brightness.x > gMagThreshold.y) || + (abs(bpRpThreshold.x - bpRpThreshold.y) < EPS && abs(bpRpThreshold.x - in_brightness.y) < EPS) || + (abs(bpRpThreshold.x) > EPS && in_brightness.y < bpRpThreshold.x) || + (abs(bpRpThreshold.y) > EPS && in_brightness.y > bpRpThreshold.y))) ) { + // Discard star in geometry shader. + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + // Convert kiloParsec to meter. + vec4 objectPosition = vec4(in_position * 1000 * Parsec, 1.0); + + // Add velocity if we've read any. + if ( renderOption == RENDEROPTION_MOTION ) { + // Velocity is already in [m/s]. + objectPosition.xyz += time * in_velocity; + } + + // Thres moving stars by their new position. + float distPosition = length(objectPosition.xyz / (1000.0 * Parsec) ); + if ( (abs(distThreshold.x - distThreshold.y) > EPS && + ((abs(distThreshold.x) > EPS && distPosition< distThreshold.x) || + (abs(distThreshold.y) > EPS && distPosition > distThreshold.y))) ) { + // Discard star in geometry shader. + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + return; + } + + // Apply camera transforms. + dvec4 viewPosition = view * model * objectPosition; + vec4 sunPosition = vec4(view * model * vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + vs_starDistFromSun = safeLength(objectPosition); + vs_cameraDistFromSun = safeLength(sunPosition); + + // Remove stars without position, happens when VBO chunk is stuffed with zeros. + // Has to be done in Geometry shader because Vertices cannot be discarded here. + if ( length(in_position) > EPS ){ + vs_gPosition = vec4(model * objectPosition); + gl_Position = vec4(projection * viewPosition); + } + else { + vs_gPosition = vec4(0.0); + gl_Position = vec4(0.0); + } +} diff --git a/modules/softwareintegration/shaders/galaxyraycast.glsl b/modules/softwareintegration/shaders/galaxyraycast.glsl deleted file mode 100644 index 5bc7bcb4c6..0000000000 --- a/modules/softwareintegration/shaders/galaxyraycast.glsl +++ /dev/null @@ -1,77 +0,0 @@ -/***************************************************************************************** -* * -* OpenSpace * -* * -* Copyright (c) 2014-2020 * -* * -* 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. * -****************************************************************************************/ - -uniform float maxStepSize#{id} = 0.1; -uniform vec3 aspect#{id} = vec3(1.0); -uniform float opacityCoefficient#{id} = 1.0; -uniform float absorptionMultiply#{id} = 50.0; -uniform float emissionMultiply#{id} = 1500.0; -uniform sampler3D galaxyTexture#{id}; - -void sample#{id}( - vec3 samplePos, - vec3 dir, - inout vec3 accumulatedColor, - inout vec3 accumulatedAlpha, - inout float stepSize - ) { - vec3 aspect = aspect#{id}; - stepSize = maxStepSize#{id} / length(dir / aspect); - - //Early ray termination on black parts of the data - vec3 normalizedPos = samplePos * 2.f - 1.f; - if (normalizedPos.x * normalizedPos.x + normalizedPos.y * normalizedPos.y > 0.7) { - return; - } - - vec4 sampledColor = texture(galaxyTexture#{id}, samplePos.xyz); - - // Source textures currently are square-rooted to avoid dithering in the shadows. - // So square them back - sampledColor = sampledColor*sampledColor; - - // Fudge for the dust "spreading" - sampledColor.a = clamp(sampledColor.a, 0.f, 1.f); - sampledColor.a = pow(sampledColor.a, 0.7f); - - // Absorption probability - float scaledDensity = sampledColor.a * stepSize * absorptionMultiply#{id}; - vec3 alphaTint = vec3(0.3f, 0.54f, 0.85f); - vec3 absorption = alphaTint * scaledDensity; - - // Extinction - vec3 extinction = exp(-absorption); - accumulatedColor.rgb *= extinction; - - // Emission - accumulatedColor.rgb += - sampledColor.rgb * stepSize * emissionMultiply#{id} * opacityCoefficient#{id}; - - vec3 oneMinusFrontAlpha = vec3(1.f) - accumulatedAlpha; - accumulatedAlpha += oneMinusFrontAlpha * sampledColor.rgb * opacityCoefficient#{id}; - } - - float stepSize#{id}(vec3 samplePos, vec3 dir) { - return maxStepSize#{id} * length(dir * 1.f / aspect#{id}); - } diff --git a/modules/softwareintegration/shaders/points_fs.glsl b/modules/softwareintegration/shaders/points_fs.glsl deleted file mode 100644 index 702046e45a..0000000000 --- a/modules/softwareintegration/shaders/points_fs.glsl +++ /dev/null @@ -1,48 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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 "fragment.glsl" -#include "floatoperations.glsl" - -in vec4 vs_position; -in vec3 vs_color; -in float vs_screenSpaceDepth; -in float vs_starBrightness; - -uniform float opacityCoefficient; - -Fragment getFragment() { - Fragment frag; - - float multipliedOpacityCoefficient = opacityCoefficient*opacityCoefficient; - vec3 extinction = exp(vec3(0.6, 0.2, 0.3) - vs_color); - vec4 fullColor = vec4(vs_color*extinction*vs_starBrightness*multipliedOpacityCoefficient, opacityCoefficient); - frag.color = fullColor; - - frag.depth = vs_screenSpaceDepth; - frag.gPosition = vs_position; - frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0); - - return frag; -} diff --git a/modules/softwareintegration/shaders/raycasterbounds_fs.glsl b/modules/softwareintegration/shaders/raycasterbounds_fs.glsl deleted file mode 100644 index 2ff99f2c5a..0000000000 --- a/modules/softwareintegration/shaders/raycasterbounds_fs.glsl +++ /dev/null @@ -1,46 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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 "floatoperations.glsl" -#include "fragment.glsl" - -in vec3 modelPosition; -in vec4 viewPosition; - -Fragment getFragment() { - Fragment frag; - //Early ray termination on black parts of the data - /*vec3 normalizedPos = (modelPosition*2.0)-1.0; - if (abs(modelPosition.x) > 0.9 || abs(modelPosition.y) > 0.9) { - frag.color = vec4(0.0, 0.0, 0.0, 1.0); - } - else {*/ - vec3 pos = modelPosition + 0.5; - //vec3 posClamp = clamp(pos, vec3(0.0), vec3(1.0)); - frag.color = vec4(pos, 1.0); - //} - - frag.depth = safeLength(viewPosition); - return frag; -} diff --git a/modules/softwareintegration/softwareintegrationmodule.cpp b/modules/softwareintegration/softwareintegrationmodule.cpp index 504c807102..7174ca6705 100644 --- a/modules/softwareintegration/softwareintegrationmodule.cpp +++ b/modules/softwareintegration/softwareintegrationmodule.cpp @@ -22,29 +22,51 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include -#include -#include -#include +#include +#include +#include +#include +#include #include +#include #include +#include #include -#include namespace openspace { -GalaxyModule::GalaxyModule() : OpenSpaceModule(Name) {} +GaiaModule::GaiaModule() : OpenSpaceModule(Name) {} -void GalaxyModule::internalInitialize(const ghoul::Dictionary&) { +void GaiaModule::internalInitialize(const ghoul::Dictionary&) { auto fRenderable = FactoryManager::ref().factory(); ghoul_assert(fRenderable, "No renderable factory existed"); - fRenderable->registerClass("RenderableGalaxy"); + fRenderable->registerClass("RenderableGaiaStars"); auto fTask = FactoryManager::ref().factory(); ghoul_assert(fRenderable, "No task factory existed"); - fTask->registerClass("MilkywayConversionTask"); - fTask->registerClass("MilkywayPointsConversionTask"); + fTask->registerClass("ReadFitsTask"); + fTask->registerClass("ReadSpeckTask"); + fTask->registerClass("ConstructOctreeTask"); +} + +std::vector GaiaModule::documentations() const { + return { + RenderableGaiaStars::Documentation(), + ReadFitsTask::Documentation(), + ReadSpeckTask::Documentation(), + ConstructOctreeTask::Documentation(), + }; +} + +scripting::LuaLibrary GaiaModule::luaLibrary() const { + scripting::LuaLibrary res; + res.name = "gaia"; + res.scripts = { + absPath("${MODULE_GAIA}/scripts/filtering.lua") + }; + return res; } } // namespace openspace diff --git a/modules/softwareintegration/softwareintegrationmodule.h b/modules/softwareintegration/softwareintegrationmodule.h index bc72bcf948..657ac52f04 100644 --- a/modules/softwareintegration/softwareintegrationmodule.h +++ b/modules/softwareintegration/softwareintegrationmodule.h @@ -22,18 +22,24 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GALAXY___GALAXYMODULE___H__ -#define __OPENSPACE_MODULE_GALAXY___GALAXYMODULE___H__ +#ifndef __OPENSPACE_MODULE_GAIA___GAIAMODULE___H__ +#define __OPENSPACE_MODULE_GAIA___GAIAMODULE___H__ #include +#include + namespace openspace { -class GalaxyModule : public OpenSpaceModule { +class GaiaModule : public OpenSpaceModule { public: - constexpr static const char* Name = "Galaxy"; + constexpr static const char* Name = "Gaia"; - GalaxyModule(); + GaiaModule(); + virtual ~GaiaModule() = default; + + std::vector documentations() const override; + scripting::LuaLibrary luaLibrary() const override; private: void internalInitialize(const ghoul::Dictionary&) override; @@ -41,4 +47,4 @@ private: } // namespace openspace -#endif // __OPENSPACE_MODULE_GALAXY___GALAXYMODULE___H__ +#endif // __OPENSPACE_MODULE_GAIA___GAIAMODULE___H__ diff --git a/modules/softwareintegration/tasks/constructoctreetask.cpp b/modules/softwareintegration/tasks/constructoctreetask.cpp new file mode 100644 index 0000000000..725d86963c --- /dev/null +++ b/modules/softwareintegration/tasks/constructoctreetask.cpp @@ -0,0 +1,816 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +namespace { + constexpr const char* KeyInFileOrFolderPath = "InFileOrFolderPath"; + constexpr const char* KeyOutFileOrFolderPath = "OutFileOrFolderPath"; + constexpr const char* KeyMaxDist = "MaxDist"; + constexpr const char* KeyMaxStarsPerNode = "MaxStarsPerNode"; + constexpr const char* KeySingleFileInput = "SingleFileInput"; + + constexpr const char* KeyFilterPosX = "FilterPosX"; + constexpr const char* KeyFilterPosY = "FilterPosY"; + constexpr const char* KeyFilterPosZ = "FilterPosZ"; + constexpr const char* KeyFilterGMag = "FilterGMag"; + constexpr const char* KeyFilterBpRp = "FilterBpRp"; + constexpr const char* KeyFilterVelX = "FilterVelX"; + constexpr const char* KeyFilterVelY = "FilterVelY"; + constexpr const char* KeyFilterVelZ = "FilterVelZ"; + constexpr const char* KeyFilterBpMag = "FilterBpMag"; + constexpr const char* KeyFilterRpMag = "FilterRpMag"; + constexpr const char* KeyFilterBpG = "FilterBpG"; + constexpr const char* KeyFilterGRp = "FilterGRp"; + constexpr const char* KeyFilterRa = "FilterRa"; + constexpr const char* KeyFilterRaError = "FilterRaError"; + constexpr const char* KeyFilterDec = "FilterDec"; + constexpr const char* KeyFilterDecError = "FilterDecError"; + constexpr const char* KeyFilterParallax = "FilterParallax"; + constexpr const char* KeyFilterParallaxError = "FilterParallaxError"; + constexpr const char* KeyFilterPmra = "FilterPmra"; + constexpr const char* KeyFilterPmraError = "FilterPmraError"; + constexpr const char* KeyFilterPmdec = "FilterPmdec"; + constexpr const char* KeyFilterPmdecError = "FilterPmdecError"; + constexpr const char* KeyFilterRv = "FilterRv"; + constexpr const char* KeyFilterRvError = "FilterRvError"; + + constexpr const char* _loggerCat = "ConstructOctreeTask"; +} // namespace + +namespace openspace { + +ConstructOctreeTask::ConstructOctreeTask(const ghoul::Dictionary& dictionary) { + openspace::documentation::testSpecificationAndThrow( + documentation(), + dictionary, + "ConstructOctreeTask" + ); + + _inFileOrFolderPath = absPath(dictionary.value(KeyInFileOrFolderPath)); + _outFileOrFolderPath = absPath(dictionary.value(KeyOutFileOrFolderPath)); + + if (dictionary.hasKey(KeyMaxDist)) { + _maxDist = static_cast(dictionary.value(KeyMaxDist)); + } + + if (dictionary.hasKey(KeyMaxStarsPerNode)) { + _maxStarsPerNode = static_cast(dictionary.value(KeyMaxStarsPerNode)); + } + + if (dictionary.hasKey(KeySingleFileInput)) { + _singleFileInput = dictionary.value(KeySingleFileInput); + } + + _octreeManager = std::make_shared(); + _indexOctreeManager = std::make_shared(); + + // Check for filter params. + if (dictionary.hasKey(KeyFilterPosX)) { + _posX = dictionary.value(KeyFilterPosX); + _filterPosX = true; + } + if (dictionary.hasKey(KeyFilterPosY)) { + _posY = dictionary.value(KeyFilterPosY); + _filterPosY = true; + } + if (dictionary.hasKey(KeyFilterPosZ)) { + _posZ = dictionary.value(KeyFilterPosZ); + _filterPosZ = true; + } + if (dictionary.hasKey(KeyFilterGMag)) { + _gMag = dictionary.value(KeyFilterGMag); + _filterGMag = true; + } + if (dictionary.hasKey(KeyFilterBpRp)) { + _bpRp = dictionary.value(KeyFilterBpRp); + _filterBpRp = true; + } + if (dictionary.hasKey(KeyFilterVelX)) { + _velX = dictionary.value(KeyFilterVelX); + _filterVelX = true; + } + if (dictionary.hasKey(KeyFilterVelY)) { + _velY = dictionary.value(KeyFilterVelY); + _filterVelY = true; + } + if (dictionary.hasKey(KeyFilterVelZ)) { + _velZ = dictionary.value(KeyFilterVelZ); + _filterVelZ = true; + } + if (dictionary.hasKey(KeyFilterBpMag)) { + _bpMag = dictionary.value(KeyFilterBpMag); + _filterBpMag = true; + } + if (dictionary.hasKey(KeyFilterRpMag)) { + _rpMag = dictionary.value(KeyFilterRpMag); + _filterRpMag = true; + } + if (dictionary.hasKey(KeyFilterBpG)) { + _bpG = dictionary.value(KeyFilterBpG); + _filterBpG = true; + } + if (dictionary.hasKey(KeyFilterGRp)) { + _gRp = dictionary.value(KeyFilterGRp); + _filterGRp = true; + } + if (dictionary.hasKey(KeyFilterRa)) { + _ra = dictionary.value(KeyFilterRa); + _filterRa = true; + } + if (dictionary.hasKey(KeyFilterRaError)) { + _raError = dictionary.value(KeyFilterRaError); + _filterRaError = true; + } + if (dictionary.hasKey(KeyFilterDec)) { + _dec = dictionary.value(KeyFilterDec); + _filterDec = true; + } + if (dictionary.hasKey(KeyFilterDecError)) { + _decError = dictionary.value(KeyFilterDecError); + _filterDecError = true; + } + if (dictionary.hasKey(KeyFilterParallax)) { + _parallax = dictionary.value(KeyFilterParallax); + _filterParallax = true; + } + if (dictionary.hasKey(KeyFilterParallaxError)) { + _parallaxError = dictionary.value(KeyFilterParallaxError); + _filterParallaxError = true; + } + if (dictionary.hasKey(KeyFilterPmra)) { + _pmra = dictionary.value(KeyFilterPmra); + _filterPmra = true; + } + if (dictionary.hasKey(KeyFilterPmraError)) { + _pmraError = dictionary.value(KeyFilterPmraError); + _filterPmraError = true; + } + if (dictionary.hasKey(KeyFilterPmdec)) { + _pmdec = dictionary.value(KeyFilterPmdec); + _filterPmdec = true; + } + if (dictionary.hasKey(KeyFilterPmdecError)) { + _pmdecError = dictionary.value(KeyFilterPmdecError); + _filterPmdecError = true; + } + if (dictionary.hasKey(KeyFilterRv)) { + _rv = dictionary.value(KeyFilterRv); + _filterRv = true; + } + if (dictionary.hasKey(KeyFilterRvError)) { + _rvError = dictionary.value(KeyFilterRvError); + _filterRvError = true; + } +} + +std::string ConstructOctreeTask::description() { + return "Read bin file (or files in folder): " + _inFileOrFolderPath + "\n " + "and write octree data file (or files) into: " + _outFileOrFolderPath + "\n"; +} + +void ConstructOctreeTask::perform(const Task::ProgressCallback& onProgress) { + onProgress(0.0f); + + if (_singleFileInput) { + constructOctreeFromSingleFile(onProgress); + } + else { + constructOctreeFromFolder(onProgress); + } + + onProgress(1.0f); +} + +void ConstructOctreeTask::constructOctreeFromSingleFile( + const Task::ProgressCallback& progressCallback) +{ + std::vector fullData; + int32_t nValues = 0; + int32_t nValuesPerStar = 0; + size_t nFilteredStars = 0; + int nTotalStars = 0; + + _octreeManager->initOctree(0, _maxDist, _maxStarsPerNode); + + LINFO("Reading data file: " + _inFileOrFolderPath); + + LINFO(fmt::format( + "MAX DIST: {} - MAX STARS PER NODE: {}", + _octreeManager->maxDist(), _octreeManager->maxStarsPerNode() + )); + + // Use to generate a synthetic dataset + /*for (float z = -1.0; z < 1.0; z += 0.05) { + for (float y = -1.0; y < 1.0; y += 0.05) { + for (float x = -1.0; x < 1.0; x += 0.05) { + float r = static_cast (rand()) / static_cast (RAND_MAX); + std::vector renderValues(8); + renderValues[0] = x; // + x * r; + renderValues[2] = z; // + y * r; + renderValues[1] = y; // + z * r; + renderValues[3] = 5.0; // + 10 * r; + renderValues[4] = 2.0; // + 10 * r; + renderValues[5] = r; + renderValues[6] = r; + renderValues[7] = r; + _octreeManager->insert(renderValues); + nTotalStars++; + nValues+=8; + } + } + } + + for (float phi = -180.0; phi < 180.0; phi += 10.0) { + for (float theta = -90.0; theta <= 90.0; theta += 10.0) { + float r = 1.0; + std::vector renderValues(8); + renderValues[0] = r * sin(glm::radians(theta)) * cos(glm::radians(phi)); + renderValues[2] = r * sin(glm::radians(theta)) * sin(glm::radians(phi)); + renderValues[1] = r * cos(glm::radians(theta)); + renderValues[3] = 5.0; + renderValues[4] = 2.0; + renderValues[5] = r; + renderValues[6] = r; + renderValues[7] = r; + _octreeManager->insert(renderValues); + nTotalStars++; + nValues += 8; + } + }*/ + + std::ifstream inFileStream(_inFileOrFolderPath, std::ifstream::binary); + if (inFileStream.good()) { + inFileStream.read(reinterpret_cast(&nValues), sizeof(int32_t)); + inFileStream.read(reinterpret_cast(&nValuesPerStar), sizeof(int32_t)); + + fullData.resize(nValues); + inFileStream.read( + reinterpret_cast(fullData.data()), + nValues * sizeof(fullData[0]) + ); + nTotalStars = nValues / nValuesPerStar; + + progressCallback(0.3f); + LINFO("Constructing Octree."); + + // Insert star into octree. We assume the data already is in correct order. + for (size_t i = 0; i < fullData.size(); i += nValuesPerStar) { + auto first = fullData.begin() + i; + auto last = fullData.begin() + i + nValuesPerStar; + std::vector filterValues(first, last); + std::vector renderValues(first, first + RENDER_VALUES); + + // Filter data by parameters. + if (checkAllFilters(filterValues)) { + nFilteredStars++; + continue; + } + + // If all filters passed then insert render values into Octree. + _octreeManager->insert(renderValues); + } + inFileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file '{}' for loading preprocessed file!", + _inFileOrFolderPath + )); + } + LINFO(fmt::format("{} of {} read stars were filtered", nFilteredStars, nTotalStars)); + + // Slice LOD data before writing to files. + _octreeManager->sliceLodData(); + + LINFO("Writing octree to: " + _outFileOrFolderPath); + std::ofstream outFileStream(_outFileOrFolderPath, std::ofstream::binary); + if (outFileStream.good()) { + if (nValues == 0) { + LERROR("Error writing file - No values were read from file."); + } + _octreeManager->writeToFile(outFileStream, true); + + outFileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file: {} as output data file.", _outFileOrFolderPath + )); + } +} + +void ConstructOctreeTask::constructOctreeFromFolder( + const Task::ProgressCallback& progressCallback) +{ + int32_t nStars = 0; + int32_t nValuesPerStar = 0; + size_t nFilteredStars = 0; + //float maxRadius = 0.0; + //int starsOutside10 = 0; + //int starsOutside25 = 0; + //int starsOutside50 = 0; + //int starsOutside75 = 0; + //int starsOutside100 = 0; + //int starsOutside200 = 0; + //int starsOutside300 = 0; + //int starsOutside400 = 0; + //int starsOutside500 = 0; + //int starsOutside750 = 0; + //int starsOutside1000 = 0; + //int starsOutside1500 = 0; + //int starsOutside2000 = 0; + //int starsOutside5000 = 0; + + ghoul::filesystem::Directory currentDir(_inFileOrFolderPath); + std::vector allInputFiles = currentDir.readFiles(); + std::vector filterValues; + auto writeThreads = std::vector(8); + + _indexOctreeManager->initOctree(0, _maxDist, _maxStarsPerNode); + + float processOneFile = 1.f / allInputFiles.size(); + + LINFO(fmt::format( + "MAX DIST: {} - MAX STARS PER NODE: {}", + _indexOctreeManager->maxDist(), _indexOctreeManager->maxStarsPerNode() + )); + + for (size_t idx = 0; idx < allInputFiles.size(); ++idx) { + std::string inFilePath = allInputFiles[idx]; + int nStarsInfile = 0; + + LINFO("Reading data file: " + inFilePath); + + std::ifstream inFileStream(inFilePath, std::ifstream::binary); + if (inFileStream.good()) { + inFileStream.read(reinterpret_cast(&nValuesPerStar), sizeof(int32_t)); + filterValues.resize(nValuesPerStar, 0.f); + + while (inFileStream.read( + reinterpret_cast(filterValues.data()), + nValuesPerStar * sizeof(filterValues[0]) + )) + { + // Filter data by parameters. + if (checkAllFilters(filterValues)) { + nFilteredStars++; + continue; + } + // Generate a 50/12,5 dataset (gMag <=13/>13). + //if ((filterStar(glm::vec2(20.0), filterValues[3], 20.f)) || + // (filterStar(glm::vec2(0.0), filterValues[16])) || + // (filterValues[3] > 13.0 && filterValues[17] > 0.125) || + // (filterValues[3] <= 13.0 && filterValues[17] > 0.5)) { + // nFilteredStars++; + // continue; + //} + + // If all filters passed then insert render values into Octree. + std::vector renderValues( + filterValues.begin(), + filterValues.begin() + RENDER_VALUES + ); + + _indexOctreeManager->insert(renderValues); + nStarsInfile++; + + //float maxVal = fmax(fmax(fabs(renderValues[0]), fabs(renderValues[1])), + // fabs(renderValues[2])); + //if (maxVal > maxRadius) maxRadius = maxVal; + //// Calculate how many stars are outside of different thresholds. + //if (maxVal > 10) starsOutside10++; + //if (maxVal > 25) starsOutside25++; + //if (maxVal > 50) starsOutside50++; + //if (maxVal > 75) starsOutside75++; + //if (maxVal > 100) starsOutside100++; + //if (maxVal > 200) starsOutside200++; + //if (maxVal > 300) starsOutside300++; + //if (maxVal > 400) starsOutside400++; + //if (maxVal > 500) starsOutside500++; + //if (maxVal > 750) starsOutside750++; + //if (maxVal > 1000) starsOutside1000++; + //if (maxVal > 1500) starsOutside1500++; + //if (maxVal > 2000) starsOutside2000++; + //if (maxVal > 5000) starsOutside5000++; + } + inFileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file '{}' for loading preprocessed file!", inFilePath + )); + } + + // Slice LOD data. + LINFO("Slicing LOD data!"); + _indexOctreeManager->sliceLodData(idx); + + progressCallback((idx + 1) * processOneFile); + nStars += nStarsInfile; + + LINFO(fmt::format("Writing {} stars to octree files!", nStarsInfile)); + LINFO(fmt::format( + "Number leaf nodes: {}\n Number inner nodes: {}\n Total depth of tree: {}", + _indexOctreeManager->numLeafNodes(), + _indexOctreeManager->numInnerNodes(), + _indexOctreeManager->totalDepth() + )); + + // Write to 8 separate files in a separate thread. Data will be cleared after it + // has been written. Store joinable thread for later sync. + std::thread t( + &OctreeManager::writeToMultipleFiles, + _indexOctreeManager, + _outFileOrFolderPath, + idx + ); + writeThreads[idx] = std::move(t); + } + + LINFO(fmt::format( + "A total of {} stars were read from files and distributed into {} total nodes", + nStars, _indexOctreeManager->totalNodes() + )); + LINFO(std::to_string(nFilteredStars) + " stars were filtered"); + + //LINFO("Max radius of dataset is: " + std::to_string(maxRadius) + + // "\n Number of stars outside of:" + + // " - 10kPc is " + std::to_string(starsOutside10) + "\n" + + // " - 25kPc is " + std::to_string(starsOutside25) + "\n" + + // " - 50kPc is " + std::to_string(starsOutside50) + "\n" + + // " - 75kPc is " + std::to_string(starsOutside75) + "\n" + + // " - 100kPc is " + std::to_string(starsOutside100) + "\n" + + // " - 200kPc is " + std::to_string(starsOutside200) + "\n" + + // " - 300kPc is " + std::to_string(starsOutside300) + "\n" + + // " - 400kPc is " + std::to_string(starsOutside400) + "\n" + + // " - 500kPc is " + std::to_string(starsOutside500) + "\n" + + // " - 750kPc is " + std::to_string(starsOutside750) + "\n" + + // " - 1000kPc is " + std::to_string(starsOutside1000) + "\n" + + // " - 1500kPc is " + std::to_string(starsOutside1500) + "\n" + + // " - 2000kPc is " + std::to_string(starsOutside2000) + "\n" + + // " - 5000kPc is " + std::to_string(starsOutside5000)); + + // Write index file of Octree structure. + std::string indexFileOutPath = _outFileOrFolderPath + "index.bin"; + std::ofstream outFileStream(indexFileOutPath, std::ofstream::binary); + if (outFileStream.good()) { + LINFO("Writing index file!"); + _indexOctreeManager->writeToFile(outFileStream, false); + + outFileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file: {} as index output file.", indexFileOutPath + )); + } + + // Make sure all threads are done. + for (int i = 0; i < 8; ++i) { + writeThreads[i].join(); + } +} + +bool ConstructOctreeTask::checkAllFilters(const std::vector& filterValues) { + // Return true if star is caught in any filter. + return (_filterPosX && filterStar(_posX, filterValues[0])) || + (_filterPosY && filterStar(_posY, filterValues[1])) || + (_filterPosZ && filterStar(_posZ, filterValues[2])) || + (_filterGMag && filterStar(_gMag, filterValues[3], 20.f)) || + (_filterBpRp && filterStar(_bpRp, filterValues[4])) || + (_filterVelX && filterStar(_velX, filterValues[5])) || + (_filterVelY && filterStar(_velY, filterValues[6])) || + (_filterVelZ && filterStar(_velZ, filterValues[7])) || + (_filterBpMag && filterStar(_bpMag, filterValues[8], 20.f)) || + (_filterRpMag && filterStar(_rpMag, filterValues[9], 20.f)) || + (_filterBpG && filterStar(_bpG, filterValues[10])) || + (_filterGRp && filterStar(_gRp, filterValues[11])) || + (_filterRa && filterStar(_ra, filterValues[12])) || + (_filterRaError && filterStar(_raError, filterValues[13])) || + (_filterDec && filterStar(_dec, filterValues[14])) || + (_filterDecError && filterStar(_decError, filterValues[15])) || + (_filterParallax && filterStar(_parallax, filterValues[16])) || + (_filterParallaxError && filterStar(_parallaxError, filterValues[17])) || + (_filterPmra && filterStar(_pmra, filterValues[18])) || + (_filterPmraError && filterStar(_pmraError, filterValues[19])) || + (_filterPmdec && filterStar(_pmdec, filterValues[20])) || + (_filterPmdecError && filterStar(_pmdecError, filterValues[21])) || + (_filterRv && filterStar(_rv, filterValues[22])) || + (_filterRvError && filterStar(_rvError, filterValues[23])); +} + +bool ConstructOctreeTask::filterStar(const glm::vec2& range, float filterValue, + float normValue) +{ + // Return true if star should be filtered away, i.e. if min = max = filterValue or + // if filterValue < min (when min != 0.0) or filterValue > max (when max != 0.0). + return (fabs(range.x - range.y) < FLT_EPSILON && + fabs(range.x - filterValue) < FLT_EPSILON) || + (fabs(range.x - normValue) > FLT_EPSILON && filterValue < range.x) || + (fabs(range.y - normValue) > FLT_EPSILON && filterValue > range.y); +} + +documentation::Documentation ConstructOctreeTask::Documentation() { + using namespace documentation; + return { + "ConstructOctreeTask", + "gaiamission_constructoctreefrombin", + { + { + "Type", + new StringEqualVerifier("ConstructOctreeTask"), + Optional::No + }, + { + KeyInFileOrFolderPath, + new StringVerifier, + Optional::No, + "If SingleFileInput is set to true then this specifies the path to a " + "single BIN file containing a full dataset. Otherwise this specifies the " + "path to a folder with multiple BIN files containing subsets of sorted " + "star data.", + }, + { + KeyOutFileOrFolderPath, + new StringVerifier, + Optional::No, + "If SingleFileInput is set to true then this specifies the output file " + "name (including full path). Otherwise this specifies the path to the " + "folder which to save all files.", + }, + { + KeyMaxDist, + new IntVerifier, + Optional::Yes, + "If set it determines what MAX_DIST to use when creating Octree." + }, + { + KeyMaxStarsPerNode, + new IntVerifier, + Optional::Yes, + "If set it determines what MAX_STAR_PER_NODE to use when creating Octree." + }, + { + KeySingleFileInput, + new BoolVerifier, + Optional::Yes, + "If true then task will read from a single file and output a single " + "binary file with the full Octree. If false then task will read all " + "files in specified folder and output multiple files for the Octree." + }, + { + KeyFilterPosX, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Position X values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterPosY, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Position Y values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterPosZ, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Position Z values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterGMag, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with G mean magnitude values between " + "[min, max] will be inserted into Octree (if min is set to 20.0 it is " + "read as -Inf, if max is set to 20.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away. Default " + "GMag = 20.0 if no value existed." + }, + { + KeyFilterBpRp, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Bp-Rp color values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterVelX, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Velocity X values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterVelY, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Velocity Y values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterVelZ, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Velocity Z values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterBpMag, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Bp mean magnitude values between " + "[min, max] will be inserted into Octree (if min is set to 20.0 it is " + "read as -Inf, if max is set to 20.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away. Default " + "BpMag = 20.0 if no value existed." + }, + { + KeyFilterRpMag, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Rp mean magnitude values between " + "[min, max] will be inserted into Octree (if min is set to 20.0 it is " + "read as -Inf, if max is set to 20.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away. Default RpMag = " + "20.0 if no value existed." + }, + { + KeyFilterBpG, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Bp-G color values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterGRp, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with G-Rp color values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterRa, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with RA values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterRaError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with RA Error values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterDec, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with DEC values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterDecError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with DEC Error values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterParallax, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Parallax values between [min, max] " + "will be inserted into Octree (if min is set to 0.0 it is read as -Inf, " + "if max is set to 0.0 it is read as +Inf). If min = max then all values " + "equal min|max will be filtered away." + }, + { + KeyFilterParallaxError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Parallax Error values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterPmra, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Proper Motion RA values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterPmraError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Proper Motion RA Error values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterPmdec, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Proper Motion DEC values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterPmdecError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Proper Motion DEC Error values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterRv, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Radial Velocity values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + { + KeyFilterRvError, + new Vector2Verifier, + Optional::Yes, + "If defined then only stars with Radial Velocity Error values between " + "[min, max] will be inserted into Octree (if min is set to 0.0 it is " + "read as -Inf, if max is set to 0.0 it is read as +Inf). If min = max " + "then all values equal min|max will be filtered away." + }, + } + }; +} + +} // namespace openspace diff --git a/modules/softwareintegration/tasks/constructoctreetask.h b/modules/softwareintegration/tasks/constructoctreetask.h new file mode 100644 index 0000000000..824521e340 --- /dev/null +++ b/modules/softwareintegration/tasks/constructoctreetask.h @@ -0,0 +1,143 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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_GAIA___CONSTRUCTOCTREETASK___H__ +#define __OPENSPACE_MODULE_GAIA___CONSTRUCTOCTREETASK___H__ + +#include + +#include +#include + +namespace openspace { + +namespace documentation { struct Documentation; } + +class ConstructOctreeTask : public Task { +public: + ConstructOctreeTask(const ghoul::Dictionary& dictionary); + virtual ~ConstructOctreeTask() = default; + + std::string description() override; + void perform(const Task::ProgressCallback& onProgress) override; + static documentation::Documentation Documentation(); + +private: + const int RENDER_VALUES = 8; + + /** + * Reads a single binary file with preprocessed star data and insert the render values + * into an octree structure (if star data passed all defined filters). + * Stores the entire octree in one binary file. + */ + void constructOctreeFromSingleFile(const Task::ProgressCallback& progressCallback); + + /** + * Reads binary star data from 8 preprocessed files (one per branch) in specified + * folder, prepared by ReadFitsTask, and inserts star render data into an octree + * (if star data passed all defined filters). + * Stores octree structure in a binary index file and stores all render data + * separate files, one file per node in the octree. + */ + void constructOctreeFromFolder(const Task::ProgressCallback& progressCallback); + + /** + * Checks all defined filter ranges and \returns true if any of the corresponding + * filterValues are outside of the defined range. + * \returns false if value should be inserted into Octree. + * \param filterValues are all read filter values in binary file. + */ + bool checkAllFilters(const std::vector& filterValues); + + /** + * \returns true if star should be filtered away and false if all filters passed. + * \param range contains ]min, max[ and \param filterValue corresponding value in + * star. Star is filtered either if min = max = filterValue or if filterValue < min + * (when min != 0.0) or filterValue > max (when max != 0.0). + */ + bool filterStar(const glm::vec2& range, float filterValue, float normValue = 0.f); + + std::string _inFileOrFolderPath; + std::string _outFileOrFolderPath; + int _maxDist = 0; + int _maxStarsPerNode = 0; + bool _singleFileInput = false; + + std::shared_ptr _octreeManager; + std::shared_ptr _indexOctreeManager; + + // Filter params + glm::vec2 _posX = glm::vec2(0.f); + bool _filterPosX = false; + glm::vec2 _posY = glm::vec2(0.f); + bool _filterPosY = false; + glm::vec2 _posZ = glm::vec2(0.f); + bool _filterPosZ = false; + glm::vec2 _gMag = glm::vec2(0.f); + bool _filterGMag = false; + glm::vec2 _bpRp = glm::vec2(0.f); + bool _filterBpRp = false; + glm::vec2 _velX = glm::vec2(0.f); + bool _filterVelX = false; + glm::vec2 _velY = glm::vec2(0.f); + bool _filterVelY = false; + glm::vec2 _velZ = glm::vec2(0.f); + bool _filterVelZ = false; + glm::vec2 _bpMag = glm::vec2(0.f); + bool _filterBpMag = false; + glm::vec2 _rpMag = glm::vec2(0.f); + bool _filterRpMag = false; + glm::vec2 _bpG = glm::vec2(0.f); + bool _filterBpG = false; + glm::vec2 _gRp = glm::vec2(0.f); + bool _filterGRp = false; + glm::vec2 _ra = glm::vec2(0.f); + bool _filterRa = false; + glm::vec2 _raError = glm::vec2(0.f); + bool _filterRaError = false; + glm::vec2 _dec = glm::vec2(0.f); + bool _filterDec = false; + glm::vec2 _decError = glm::vec2(0.f); + bool _filterDecError = false; + glm::vec2 _parallax = glm::vec2(0.f); + bool _filterParallax = false; + glm::vec2 _parallaxError = glm::vec2(0.f); + bool _filterParallaxError = false; + glm::vec2 _pmra = glm::vec2(0.f); + bool _filterPmra = false; + glm::vec2 _pmraError = glm::vec2(0.f); + bool _filterPmraError = false; + glm::vec2 _pmdec = glm::vec2(0.f); + bool _filterPmdec = false; + glm::vec2 _pmdecError = glm::vec2(0.f); + bool _filterPmdecError = false; + glm::vec2 _rv = glm::vec2(0.f); + bool _filterRv = false; + glm::vec2 _rvError = glm::vec2(0.f); + bool _filterRvError = false; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_GAIA___CONSTRUCTOCTREETASK___H__ diff --git a/modules/softwareintegration/tasks/milkywayconversiontask.cpp b/modules/softwareintegration/tasks/milkywayconversiontask.cpp deleted file mode 100644 index ec985ad917..0000000000 --- a/modules/softwareintegration/tasks/milkywayconversiontask.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2020 * - * * - * 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 - -namespace { - constexpr const char* KeyInFilenamePrefix = "InFilenamePrefix"; - constexpr const char* KeyInFilenameSuffix = "InFilenameSuffix"; - constexpr const char* KeyInFirstIndex = "InFirstIndex"; - constexpr const char* KeyInNSlices = "InNSlices"; - constexpr const char* KeyOutFilename = "OutFilename"; - constexpr const char* KeyOutDimensions = "OutDimensions"; -} // namespace - -namespace openspace { - -MilkywayConversionTask::MilkywayConversionTask(const ghoul::Dictionary& dictionary) - : _inFirstIndex(0) - , _inNSlices(0) -{ - dictionary.getValue(KeyInFilenamePrefix, _inFilenamePrefix); - dictionary.getValue(KeyInFilenameSuffix, _inFilenameSuffix); - dictionary.getValue(KeyInFirstIndex, _inFirstIndex); - dictionary.getValue(KeyInNSlices, _inNSlices); - dictionary.getValue(KeyOutFilename, _outFilename); - dictionary.getValue(KeyOutDimensions, _outDimensions); -} - -std::string MilkywayConversionTask::description() { - return std::string(); -} - -void MilkywayConversionTask::perform(const Task::ProgressCallback& onProgress) { - using namespace openspace::volume; - - std::vector filenames; - for (size_t i = 0; i < _inNSlices; i++) { - filenames.push_back( - _inFilenamePrefix + std::to_string(i + _inFirstIndex) + _inFilenameSuffix - ); - } - - TextureSliceVolumeReader> sliceReader(filenames, _inNSlices, 10); - sliceReader.initialize(); - - RawVolumeWriter> rawWriter(_outFilename); - rawWriter.setDimensions(_outDimensions); - - const glm::vec3 resolutionRatio = static_cast(sliceReader.dimensions()) / - static_cast(rawWriter.dimensions()); - - VolumeSampler>> sampler( - &sliceReader, - resolutionRatio - ); - std::function(glm::ivec3)> sampleFunction = - [&](glm::ivec3 outCoord) { - const glm::vec3 inCoord = ((glm::vec3(outCoord) + glm::vec3(0.5f)) * - resolutionRatio) - glm::vec3(0.5f); - const glm::tvec4 value = sampler.sample(inCoord); - return value; - }; - - rawWriter.write(sampleFunction, onProgress); -} - -documentation::Documentation MilkywayConversionTask::documentation() { - return documentation::Documentation(); -} - -} // namespace openspace diff --git a/modules/softwareintegration/tasks/readfilejob.cpp b/modules/softwareintegration/tasks/readfilejob.cpp new file mode 100644 index 0000000000..ac15d85d87 --- /dev/null +++ b/modules/softwareintegration/tasks/readfilejob.cpp @@ -0,0 +1,264 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +namespace { + constexpr const char* _loggerCat = "ReadFileJob"; +} + +namespace openspace::gaia { + +ReadFileJob::ReadFileJob(std::string filePath, std::vector allColumns, + int firstRow, int lastRow, size_t nDefaultCols, + int nValuesPerStar, std::shared_ptr fitsReader) + : _inFilePath(std::move(filePath)) + , _allColumns(std::move(allColumns)) + , _firstRow(firstRow) + , _lastRow(lastRow) + , _nDefaultCols(nDefaultCols) + , _nValuesPerStar(nValuesPerStar) + , _fitsFileReader(std::move(fitsReader)) + , _octants(8) +{} + +void ReadFileJob::execute() { + // Read columns from FITS file. If rows aren't specified then full table will be read. + std::shared_ptr> table = _fitsFileReader->readTable( + _inFilePath, + _allColumns, + _firstRow, + _lastRow + ); + + if (!table) { + throw ghoul::RuntimeError( + fmt::format("Failed to open Fits file '{}'", _inFilePath + )); + } + + int nStars = table->readRows - _firstRow + 1; + + int nNullArr = 0; + size_t nColumnsRead = _allColumns.size(); + if (nColumnsRead != _nDefaultCols) { + LINFO("Additional columns will be read! Consider add column in code for " + "significant speedup!"); + } + + // Copy columns to local variables. + std::unordered_map>& tableContent = table->contents; + + // Default columns parameters. + //std::vector l_longitude = std::move(tableContent[_allColumns[0]]); + //std::vector b_latitude = std::move(tableContent[_allColumns[1]]); + std::vector ra = std::move(tableContent[_allColumns[0]]); + std::vector ra_err = std::move(tableContent[_allColumns[1]]); + std::vector dec = std::move(tableContent[_allColumns[2]]); + std::vector dec_err = std::move(tableContent[_allColumns[3]]); + std::vector parallax = std::move(tableContent[_allColumns[4]]); + std::vector parallax_err = std::move(tableContent[_allColumns[5]]); + std::vector pmra = std::move(tableContent[_allColumns[6]]); + std::vector pmra_err = std::move(tableContent[_allColumns[7]]); + std::vector pmdec = std::move(tableContent[_allColumns[8]]); + std::vector pmdec_err = std::move(tableContent[_allColumns[9]]); + std::vector meanMagG = std::move(tableContent[_allColumns[10]]); + std::vector meanMagBp = std::move(tableContent[_allColumns[11]]); + std::vector meanMagRp = std::move(tableContent[_allColumns[12]]); + std::vector bp_rp = std::move(tableContent[_allColumns[13]]); + std::vector bp_g = std::move(tableContent[_allColumns[14]]); + std::vector g_rp = std::move(tableContent[_allColumns[15]]); + std::vector radial_vel = std::move(tableContent[_allColumns[16]]); + std::vector radial_vel_err = std::move(tableContent[_allColumns[17]]); + + + // Construct data array. OBS: ORDERING IS IMPORTANT! This is where slicing happens. + for (int i = 0; i < nStars; ++i) { + std::vector values(_nValuesPerStar); + size_t idx = 0; + + // Default order for rendering: + // Position [X, Y, Z] + // Mean G-band Magnitude + // -- Mean Bp-band Magnitude + // -- Mean Rp-band Magnitude + // Bp-Rp Color + // -- Bp-G Color + // -- G-Rp Color + // Velocity [X, Y, Z] + + // Return early if star doesn't have a measured position. + if (std::isnan(ra[i]) || std::isnan(dec[i])) { + nNullArr++; + continue; + } + + // Store positions. Set to a default distance if parallax doesn't exist. + float radiusInKiloParsec = 9.0; + if (!std::isnan(parallax[i])) { + // Parallax is in milliArcseconds -> distance in kiloParsecs + // https://gea.esac.esa.int/archive/documentation/GDR2/Gaia_archive/ + // chap_datamodel/sec_dm_main_tables/ssec_dm_gaia_source.html + //LINFO("Parallax: " + std::to_string(parallax[i])); + radiusInKiloParsec = 1.f / parallax[i]; + } + /*// Convert to Galactic Coordinates from Galactic Lon & Lat. + // https://gea.esac.esa.int/archive/documentation/GDR2/Data_processing/ + // chap_cu3ast/sec_cu3ast_intro/ssec_cu3ast_intro_tansforms.html#SSS1 + values[idx++] = radiusInKiloParsec * cos(glm::radians(b_latitude[i])) * + cos(glm::radians(l_longitude[i])); // Pos X + values[idx++] = radiusInKiloParsec * cos(glm::radians(b_latitude[i])) * + sin(glm::radians(l_longitude[i])); // Pos Y + values[idx++] = radiusInKiloParsec * sin(glm::radians(b_latitude[i])); // Pos Z + */ + + // Convert ICRS Equatorial Ra and Dec to Galactic latitude and longitude. + glm::mat3 aPrimG = glm::mat3( + // Col 0 + glm::vec3(-0.0548755604162154, 0.4941094278755837, -0.8676661490190047), + // Col 1 + glm::vec3(-0.8734370902348850, -0.4448296299600112, -0.1980763734312015), + // Col 2 + glm::vec3(-0.4838350155487132, 0.7469822444972189, 0.4559837761750669) + ); + glm::vec3 rICRS = glm::vec3( + cos(glm::radians(ra[i])) * cos(glm::radians(dec[i])), + sin(glm::radians(ra[i])) * cos(glm::radians(dec[i])), + sin(glm::radians(dec[i])) + ); + glm::vec3 rGal = aPrimG * rICRS; + values[idx++] = radiusInKiloParsec * rGal.x; // Pos X + values[idx++] = radiusInKiloParsec * rGal.y; // Pos Y + values[idx++] = radiusInKiloParsec * rGal.z; // Pos Z + + /*if (abs(rGal.x - values[0]) > 1e-5 || abs(rGal.y - values[1]) > 1e-5 || + abs(rGal.z - values[2]) > 1e-5) { + LINFO("rGal: " + std::to_string(rGal) + + " - LB: [" + std::to_string(values[0]) + ", " + std::to_string(values[1]) + + ", " + std::to_string(values[2]) + "]"); + }*/ + + // Store magnitude render value. (Set default to high mag = low brightness) + values[idx++] = std::isnan(meanMagG[i]) ? 20.f : meanMagG[i]; // Mean G-band Mag + + // Store color render value. (Default value is bluish stars) + values[idx++] = std::isnan(bp_rp[i]) ? 0.f : bp_rp[i]; // Bp-Rp Color + + + // Store velocity. + if (std::isnan(pmra[i])) { + pmra[i] = 0.f; + } + if (std::isnan(pmdec[i])) { + pmdec[i] = 0.f; + } + + // Convert Proper Motion from ICRS [Ra,Dec] to Galactic Tanget Vector [l,b]. + glm::vec3 uICRS = glm::vec3( + -sin(glm::radians(ra[i])) * pmra[i] - + cos(glm::radians(ra[i])) * sin(glm::radians(dec[i])) * pmdec[i], + cos(glm::radians(ra[i])) * pmra[i] - + sin(glm::radians(ra[i])) * sin(glm::radians(dec[i])) * pmdec[i], + cos(glm::radians(dec[i])) * pmdec[i] + ); + glm::vec3 pmVecGal = aPrimG * uICRS; + + // Convert to Tangential vector [m/s] from Proper Motion vector [mas/yr] + float tanVelX = 1000.f * 4.74f * radiusInKiloParsec * pmVecGal.x; + float tanVelY = 1000.f * 4.74f * radiusInKiloParsec * pmVecGal.y; + float tanVelZ = 1000.f * 4.74f * radiusInKiloParsec * pmVecGal.z; + + // Calculate True Space Velocity [m/s] if we have the radial velocity + if (!std::isnan(radial_vel[i])) { + // Calculate Radial Velocity in the direction of the star. + // radial_vel is given in [km/s] -> convert to [m/s]. + float radVelX = 1000.f * radial_vel[i] * rGal.x; + float radVelY = 1000.f * radial_vel[i] * rGal.y; + float radVelZ = 1000.f * radial_vel[i] * rGal.z; + + // Use Pythagoras theorem for the final Space Velocity [m/s]. + values[idx++] = sqrt(pow(radVelX, 2) + pow(tanVelX, 2)); // Vel X [U] + values[idx++] = sqrt(pow(radVelY, 2) + pow(tanVelY, 2)); // Vel Y [V] + values[idx++] = sqrt(pow(radVelZ, 2) + pow(tanVelZ, 2)); // Vel Z [W] + } + // Otherwise use the vector [m/s] we got from proper motion. + else { + radial_vel[i] = 0.f; + values[idx++] = tanVelX; // Vel X [U] + values[idx++] = tanVelY; // Vel Y [V] + values[idx++] = tanVelZ; // Vel Z [W] + } + + // Store additional parameters to filter by. + values[idx++] = std::isnan(meanMagBp[i]) ? 20.f : meanMagBp[i]; + values[idx++] = std::isnan(meanMagRp[i]) ? 20.f : meanMagRp[i]; + values[idx++] = std::isnan(bp_g[i]) ? 0.f : bp_g[i]; + values[idx++] = std::isnan(g_rp[i]) ? 0.f : g_rp[i]; + values[idx++] = ra[i]; + values[idx++] = std::isnan(ra_err[i]) ? 0.f : ra_err[i]; + values[idx++] = dec[i]; + values[idx++] = std::isnan(dec_err[i]) ? 0.f : dec_err[i]; + values[idx++] = std::isnan(parallax[i]) ? 0.f : parallax[i]; + values[idx++] = std::isnan(parallax_err[i]) ? 0.f : parallax_err[i]; + values[idx++] = pmra[i]; + values[idx++] = std::isnan(pmra_err[i]) ? 0.f : pmra_err[i]; + values[idx++] = pmdec[i]; + values[idx++] = std::isnan(pmdec_err[i]) ? 0.f : pmdec_err[i]; + values[idx++] = radial_vel[i]; + values[idx++] = std::isnan(radial_vel_err[i]) ? 0.f : radial_vel_err[i]; + + // Read extra columns, if any. This will slow down the sorting tremendously! + for (size_t col = _nDefaultCols; col < nColumnsRead; ++col) { + std::vector vecData = std::move(tableContent[_allColumns[col]]); + values[idx++] = std::isnan(vecData[col]) ? 0.f : vecData[col]; + } + + size_t index = 0; + if (values[0] < 0.0) { + index += 1; + } + if (values[1] < 0.0) { + index += 2; + } + if (values[2] < 0.0) { + index += 4; + } + + _octants[index].insert(_octants[index].end(), values.begin(), values.end()); + } + + /*LINFO(std::to_string(nNullArr) + " out of " + + std::to_string(nStars) + " read stars were nullArrays.");*/ +} + +std::vector> ReadFileJob::product() { + return _octants; +} + +} // namespace openspace::gaiamission diff --git a/modules/softwareintegration/tasks/readfilejob.h b/modules/softwareintegration/tasks/readfilejob.h new file mode 100644 index 0000000000..13f3cfc38b --- /dev/null +++ b/modules/softwareintegration/tasks/readfilejob.h @@ -0,0 +1,71 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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_GAIA___READFILEJOB___H__ +#define __OPENSPACE_MODULE_GAIA___READFILEJOB___H__ + +#include + +#include + +namespace openspace::gaia { + +struct ReadFileJob : public Job>> { + /** + * Constructs a Job that will read a single FITS file in a concurrent thread and + * divide the star data into 8 octants depending on position. + * \param allColumns define which columns that will be read, it should correspond + * to the pre-defined order in the job. If additional columns are defined they will + * be read but slow down the process. + * Proper conversions of positions and velocities will take place and all values + * will be checked for NaNs. + * If \param firstRow is < 1 then reading will begin at first row in table. + * If \param lastRow < firstRow then entire table will be read. + * \param nValuesPerStar defines how many values that will be stored per star. + */ + ReadFileJob(std::string filePath, std::vector allColumns, int firstRow, + int lastRow, size_t nDefaultCols, int nValuesPerStar, + std::shared_ptr fitsReader); + + ~ReadFileJob() = default; + + void execute() override; + + std::vector> product() override; + +private: + std::string _inFilePath; + int _firstRow; + int _lastRow; + size_t _nDefaultCols; + int _nValuesPerStar; + std::vector _allColumns; + + std::shared_ptr _fitsFileReader; + std::vector> _octants; +}; + +} // namespace openspace::gaiamission + +#endif // __OPENSPACE_MODULE_GAIA___READFILEJOB___H__ diff --git a/modules/softwareintegration/tasks/readfitstask.cpp b/modules/softwareintegration/tasks/readfitstask.cpp new file mode 100644 index 0000000000..5cbe1cf384 --- /dev/null +++ b/modules/softwareintegration/tasks/readfitstask.cpp @@ -0,0 +1,392 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +namespace { + constexpr const char* KeyInFileOrFolderPath = "InFileOrFolderPath"; + constexpr const char* KeyOutFileOrFolderPath = "OutFileOrFolderPath"; + constexpr const char* KeySingleFileProcess = "SingleFileProcess"; + constexpr const char* KeyThreadsToUse = "ThreadsToUse"; + constexpr const char* KeyFirstRow = "FirstRow"; + constexpr const char* KeyLastRow = "LastRow"; + constexpr const char* KeyFilterColumnNames = "FilterColumnNames"; + + constexpr const char* _loggerCat = "ReadFitsTask"; +} // namespace + +namespace openspace { + +ReadFitsTask::ReadFitsTask(const ghoul::Dictionary& dictionary) { + openspace::documentation::testSpecificationAndThrow( + documentation(), + dictionary, + "ReadFitsTask" + ); + + _inFileOrFolderPath = absPath(dictionary.value(KeyInFileOrFolderPath)); + _outFileOrFolderPath = absPath(dictionary.value(KeyOutFileOrFolderPath)); + + if (dictionary.hasKey(KeySingleFileProcess)) { + _singleFileProcess = dictionary.value(KeySingleFileProcess); + } + + if (dictionary.hasKey(KeyThreadsToUse)) { + _threadsToUse = static_cast(dictionary.value(KeyThreadsToUse)); + if (_threadsToUse < 1) { + LINFO(fmt::format( + "User defined ThreadsToUse was: {}. Will be set to 1", _threadsToUse + )); + _threadsToUse = 1; + } + } + + if (dictionary.hasKey(KeyFirstRow)) { + _firstRow = static_cast(dictionary.value(KeyFirstRow)); + } + + if (dictionary.hasKey(KeyLastRow)) { + _lastRow = static_cast(dictionary.value(KeyLastRow)); + } + + + if (dictionary.hasKey(KeyFilterColumnNames)) { + ghoul::Dictionary d = dictionary.value(KeyFilterColumnNames); + + // Ugly fix for ASCII sorting when there are more columns read than 10. + std::set intKeys; + for (const std::string& key : d.keys()) { + intKeys.insert(std::stoi(key)); + } + + for (int key : intKeys) { + _filterColumnNames.push_back(d.value(std::to_string(key))); + } + } +} + +std::string ReadFitsTask::description() { + return fmt::format( + "Read the specified fits file (or all fits files in specified folder): {}\n and " + "write raw star data into: {}\nAll columns required for default rendering and " + "filtering parameters will always be read but user can define additional filter " + "columns to read.", _inFileOrFolderPath, _outFileOrFolderPath + ); +} + +void ReadFitsTask::perform(const Task::ProgressCallback& onProgress) { + onProgress(0.f); + + if (_singleFileProcess) { + readSingleFitsFile(onProgress); + } + else { + readAllFitsFilesFromFolder(onProgress); + } + + onProgress(1.f); +} + +void ReadFitsTask::readSingleFitsFile(const Task::ProgressCallback& progressCallback) { + int32_t nValuesPerStar = 0; + + FitsFileReader fileReader(false); + std::vector fullData = fileReader.readFitsFile( + _inFileOrFolderPath, + nValuesPerStar, + _firstRow, + _lastRow, + _filterColumnNames + ); + + progressCallback(0.8f); + + std::ofstream outFileStream(_outFileOrFolderPath, std::ofstream::binary); + if (outFileStream.good()) { + int32_t nValues = static_cast(fullData.size()); + LINFO(fmt::format("Writing {} values to file {}", nValues, _outFileOrFolderPath)); + LINFO("Number of values per star: " + std::to_string(nValuesPerStar)); + + if (nValues == 0) { + LERROR("Error writing file - No values were read from file."); + } + outFileStream.write( + reinterpret_cast(&nValues), + sizeof(int32_t) + ); + outFileStream.write( + reinterpret_cast(&nValuesPerStar), + sizeof(int32_t) + ); + + size_t nBytes = nValues * sizeof(fullData[0]); + outFileStream.write(reinterpret_cast(fullData.data()), nBytes); + + outFileStream.close(); + } + else { + LERROR(fmt::format( + "Error opening file: {} as output data file.", _outFileOrFolderPath + )); + } +} + +void ReadFitsTask::readAllFitsFilesFromFolder(const Task::ProgressCallback&) { + std::vector> octants(8); + std::vector isFirstWrite(8, true); + size_t finishedJobs = 0; + int totalStars = 0; + + _firstRow = std::max(_firstRow, 1); + + // Create Threadpool and JobManager. + LINFO("Threads in pool: " + std::to_string(_threadsToUse)); + ThreadPool threadPool(_threadsToUse); + ConcurrentJobManager>> jobManager(threadPool); + + // Get all files in specified folder. + ghoul::filesystem::Directory currentDir(_inFileOrFolderPath); + std::vector allInputFiles = currentDir.readFiles(); + size_t nInputFiles = allInputFiles.size(); + LINFO("Files to read: " + std::to_string(nInputFiles)); + + // Define what columns to read. + _allColumnNames.clear(); + // Read in the order of table in file. + std::vector defaultColumnNames = { + "ra", + "ra_error", + "dec", + "dec_error", + "parallax", + "parallax_error", + "pmra", + "pmra_error", + "pmdec", + "pmdec_error", + "phot_g_mean_mag", + "phot_bp_mean_mag", + "phot_rp_mean_mag", + "bp_rp", + "bp_g", + "g_rp", + "radial_velocity", + "radial_velocity_error", + }; + _allColumnNames.insert( + _allColumnNames.end(), + defaultColumnNames.begin(), + defaultColumnNames.end() + ); + // Append additional filter parameters to default rendering parameters. + _allColumnNames.insert( + _allColumnNames.end(), + _filterColumnNames.begin(), + _filterColumnNames.end() + ); + + std::string allNames = "Columns to read: \n"; + for (const std::string& colName : _allColumnNames) { + allNames += colName + "\n"; + } + LINFO(allNames); + + // Declare how many values to save for each star. + int32_t nValuesPerStar = 24; + size_t nDefaultColumns = defaultColumnNames.size(); + auto fitsFileReader = std::make_shared(false); + + // Divide all files into ReadFilejobs and then delegate them onto several threads! + while (!allInputFiles.empty()) { + std::string fileToRead = allInputFiles.back(); + allInputFiles.erase(allInputFiles.end() - 1); + + // Add reading of file to jobmanager, which will distribute it to our threadpool. + auto readFileJob = std::make_shared( + fileToRead, + _allColumnNames, + _firstRow, + _lastRow, + nDefaultColumns, + nValuesPerStar, + fitsFileReader + ); + jobManager.enqueueJob(readFileJob); + } + + LINFO("All files added to queue!"); + + // Check for finished jobs. + while (finishedJobs < nInputFiles) { + if (jobManager.numFinishedJobs() > 0) { + std::vector> newOctant = + jobManager.popFinishedJob()->product(); + + finishedJobs++; + + for (int i = 0; i < 8; ++i) { + // Add read values to global octant and check if it's time to write! + octants[i].insert( + octants[i].end(), + newOctant[i].begin(), + newOctant[i].end() + ); + if ((octants[i].size() > MAX_SIZE_BEFORE_WRITE) || + (finishedJobs == nInputFiles)) + { + // Write to file! + totalStars += writeOctantToFile( + octants[i], + i, + isFirstWrite, + nValuesPerStar + ); + + octants[i].clear(); + octants[i].shrink_to_fit(); + } + } + } + } + LINFO(fmt::format("A total of {} stars were written to binary files.", totalStars)); +} + +int ReadFitsTask::writeOctantToFile(const std::vector& octantData, int index, + std::vector& isFirstWrite, int nValuesPerStar) +{ + std::string outPath = fmt::format("{}octant_{}.bin", _outFileOrFolderPath, index); + std::ofstream fileStream(outPath, std::ofstream::binary | std::ofstream::app); + if (fileStream.good()) { + int32_t nValues = static_cast(octantData.size()); + LINFO("Write " + std::to_string(nValues) + " values to " + outPath); + + if (nValues == 0) { + LERROR("Error writing file - No values were read from file."); + } + // If this is the first write then write number of values per star! + if (isFirstWrite[index]) { + LINFO("First write for Octant_" + std::to_string(index)); + fileStream.write( + reinterpret_cast(&nValuesPerStar), + sizeof(int32_t) + ); + isFirstWrite[index] = false; + } + + size_t nBytes = nValues * sizeof(octantData[0]); + fileStream.write(reinterpret_cast(octantData.data()), nBytes); + + fileStream.close(); + + // Return number of stars written. + return nValues / nValuesPerStar; + } + else { + LERROR(fmt::format("Error opening file: {} as output data file.", outPath)); + return 0; + } +} + +documentation::Documentation ReadFitsTask::Documentation() { + using namespace documentation; + return { + "ReadFitsFile", + "gaiamission_fitsfiletorawdata", + { + { + "Type", + new StringEqualVerifier("ReadFitsTask"), + Optional::No + }, + { + KeyInFileOrFolderPath, + new StringVerifier, + Optional::No, + "If SingleFileProcess is set to true then this specifies the path to a " + "single FITS file that will be read. Otherwise it specifies the path to " + "a folder with multiple FITS files that are to be read.", + }, + { + KeyOutFileOrFolderPath, + new StringVerifier, + Optional::No, + "If SingleFileProcess is set to true then this specifies the name " + "(including entire path) to the output file. Otherwise it specifies the " + "path to the output folder which to export binary star data to.", + }, + { + KeySingleFileProcess, + new BoolVerifier, + Optional::Yes, + "If true then task will read from a single FITS file and output a single " + "binary file. If false then task will read all files in specified folder " + "and output multiple files sorted by location." + }, + { + KeyThreadsToUse, + new IntVerifier, + Optional::Yes, + "Defines how many threads to use when reading from multiple files." + }, + { + KeyFirstRow, + new IntVerifier, + Optional::Yes, + "Defines the first row that will be read from the specified FITS " + "file(s). If not defined then reading will start at first row.", + }, + { + KeyLastRow, + new IntVerifier, + Optional::Yes, + "Defines the last row that will be read from the specified FITS file(s). " + "If not defined (or less than FirstRow) then full file(s) will be read.", + }, + { + KeyFilterColumnNames, + new StringListVerifier, + Optional::Yes, + "A list of strings with the names of all the additional columns that are " + "to be read from the specified FITS file(s). These columns can be used " + "for filtering while constructing Octree later.", + }, + + } + }; +} + +} // namespace openspace diff --git a/modules/softwareintegration/tasks/readfitstask.h b/modules/softwareintegration/tasks/readfitstask.h new file mode 100644 index 0000000000..ba3981fa0b --- /dev/null +++ b/modules/softwareintegration/tasks/readfitstask.h @@ -0,0 +1,82 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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_GAIA___READFITSTASK___H__ +#define __OPENSPACE_MODULE_GAIA___READFITSTASK___H__ + +#include +#include +#include +#include + +namespace openspace { + +namespace documentation { struct Documentation; } + +class ReadFitsTask : public Task { +public: + ReadFitsTask(const ghoul::Dictionary& dictionary); + virtual ~ReadFitsTask() = default; + + std::string description() override; + void perform(const Task::ProgressCallback& onProgress) override; + static documentation::Documentation Documentation(); + +private: + const size_t MAX_SIZE_BEFORE_WRITE = 48000000; // ~183MB -> 2M stars with 24 values + //const size_t MAX_SIZE_BEFORE_WRITE = 9000000; // ~34MB -> 0,5 stars with 18 values + + /** + * Reads a single FITS file and stores ordered star data in one binary file. + */ + void readSingleFitsFile(const Task::ProgressCallback& progressCallback); + + /** + * Reads all FITS files in a folder with multiple threads and stores ordered star + * data into 8 binary files. + */ + void readAllFitsFilesFromFolder(const Task::ProgressCallback& progressCallback); + + /** + * Writes \param data to octant [\param index] file. + * \param isFirstWrite defines if this is the first write to specified octant, if so + * the file is created, otherwise the accumulated data is appended to the end of the + * file. + */ + int writeOctantToFile(const std::vector& data, int index, + std::vector& isFirstWrite, int nValuesPerStar); + + std::string _inFileOrFolderPath; + std::string _outFileOrFolderPath; + bool _singleFileProcess = false; + size_t _threadsToUse = 1; + int _firstRow = 0; + int _lastRow = 0; + std::vector _allColumnNames; + std::vector _filterColumnNames; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_GAIA___READFITSTASK___H__ diff --git a/modules/softwareintegration/tasks/readspecktask.cpp b/modules/softwareintegration/tasks/readspecktask.cpp new file mode 100644 index 0000000000..d30d39d058 --- /dev/null +++ b/modules/softwareintegration/tasks/readspecktask.cpp @@ -0,0 +1,122 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +namespace { + constexpr const char* KeyInFilePath = "InFilePath"; + constexpr const char* KeyOutFilePath = "OutFilePath"; + + constexpr const char* _loggerCat = "ReadSpeckTask"; +} // namespace + +namespace openspace { + +ReadSpeckTask::ReadSpeckTask(const ghoul::Dictionary& dictionary) { + openspace::documentation::testSpecificationAndThrow( + documentation(), + dictionary, + "ReadSpeckTask" + ); + + _inFilePath = absPath(dictionary.value(KeyInFilePath)); + _outFilePath = absPath(dictionary.value(KeyOutFilePath)); +} + +std::string ReadSpeckTask::description() { + return fmt::format( + "Read speck file {} and write raw star data into {}", _inFilePath, _outFilePath + ); +} + +void ReadSpeckTask::perform(const Task::ProgressCallback& onProgress) { + onProgress(0.f); + + int32_t nRenderValues = 0; + + FitsFileReader fileReader(false); + std::vector fullData = fileReader.readSpeckFile(_inFilePath, nRenderValues); + + onProgress(0.9f); + + std::ofstream fileStream(_outFilePath, std::ofstream::binary); + if (fileStream.good()) { + int32_t nValues = static_cast(fullData.size()); + LINFO("nValues: " + std::to_string(nValues)); + + if (nValues == 0) { + LERROR("Error writing file - No values were read from file."); + } + fileStream.write(reinterpret_cast(&nValues), sizeof(int32_t)); + fileStream.write(reinterpret_cast(&nRenderValues), sizeof(int32_t)); + + size_t nBytes = nValues * sizeof(fullData[0]); + fileStream.write(reinterpret_cast(fullData.data()), nBytes); + + fileStream.close(); + } + else { + LERROR(fmt::format("Error opening file: {} as output data file.", _outFilePath)); + } + + onProgress(1.f); +} + +documentation::Documentation ReadSpeckTask::Documentation() { + using namespace documentation; + return { + "ReadSpeckTask", + "gaiamission_speckfiletorawdata", + { + { + "Type", + new StringEqualVerifier("ReadSpeckTask"), + Optional::No + }, + { + KeyInFilePath, + new StringVerifier, + Optional::No, + "The path to the SPECK file that are to be read.", + }, + { + KeyOutFilePath, + new StringVerifier, + Optional::No, + "The path to the file to export raw VBO data to.", + }, + } + }; +} + +} // namespace openspace diff --git a/modules/softwareintegration/tasks/milkywayconversiontask.h b/modules/softwareintegration/tasks/readspecktask.h similarity index 75% rename from modules/softwareintegration/tasks/milkywayconversiontask.h rename to modules/softwareintegration/tasks/readspecktask.h index 47ff0707aa..e9c6517dfb 100644 --- a/modules/softwareintegration/tasks/milkywayconversiontask.h +++ b/modules/softwareintegration/tasks/readspecktask.h @@ -22,40 +22,31 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GALAXY___MILKYWAYCONVERSIONTASK___H__ -#define __OPENSPACE_MODULE_GALAXY___MILKYWAYCONVERSIONTASK___H__ +#ifndef __OPENSPACE_MODULE_GAIA___READSPECKTASK___H__ +#define __OPENSPACE_MODULE_GAIA___READSPECKTASK___H__ #include -#include #include namespace openspace { namespace documentation { struct Documentation; } -/** - * Converts a set of exr image slices to a raw volume - * with floating point RGBA data (32 bit per channel). - */ -class MilkywayConversionTask : public Task { +class ReadSpeckTask : public Task { public: - MilkywayConversionTask(const ghoul::Dictionary& dictionary); - virtual ~MilkywayConversionTask() = default; + ReadSpeckTask(const ghoul::Dictionary& dictionary); + virtual ~ReadSpeckTask() = default; + std::string description() override; void perform(const Task::ProgressCallback& onProgress) override; - - static documentation::Documentation documentation(); + static documentation::Documentation Documentation(); private: - std::string _inFilenamePrefix; - std::string _inFilenameSuffix; - size_t _inFirstIndex = 0; - size_t _inNSlices = 0; - std::string _outFilename; - glm::ivec3 _outDimensions = glm::ivec3(0); + std::string _inFilePath; + std::string _outFilePath; }; } // namespace openspace -#endif // __OPENSPACE_MODULE_GALAXY___MILKYWAYCONVERSIONTASK___H__ +#endif // __OPENSPACE_MODULE_GAIA___READSPECKTASK___H__