/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2025 * * * * 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 #include #include #include #include #include #include #include #include #include namespace { constexpr int8_t CurrentCacheVersion = 1; constexpr std::string_view _loggerCat = "RenderableGalaxy"; enum StarRenderingMethod { Points, Billboards }; constexpr openspace::properties::Property::PropertyInfo VolumeRenderingEnabledInfo = { "VolumeRenderingEnabled", "Volume rendering", "Decides whether the volume rendering component of the galaxy rendering should " "be enabled or not. If disabled, the volume rendering is skipped.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo StarRenderingEnabledInfo = { "StarRenderingEnabled", "Star rendering", "Decides whether the point-based star rendering component of the galaxy " "rendering should be enabled or not. If disabled, the point-based star rendering " "is skipped.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo StepSizeInfo = { "StepSize", "Step size", "Determines the distance between steps taken in the volume rendering. The lower " "the number is, the better the rendering looks, but also takes more " "computational resources to render.", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo AbsorptionMultiplyInfo = { "AbsorptionMultiply", "Absorption multiplier", "A unit-less scale factor for the probability of dust absorbing a light " "particle. The amount of absorption determines the spectrum of the light that is " "emitted from the galaxy.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo EmissionMultiplyInfo = { "EmissionMultiply", "Emission multiplier", "A unit-less scale factor for the amount of light being emitted by dust in the " "galaxy.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo RotationInfo = { "Rotation", "Euler rotation", "The internal rotation of the volume rendering in Euler angles.", openspace::properties::Property::Visibility::Developer }; constexpr openspace::properties::Property::PropertyInfo StarRenderingMethodInfo = { "StarRenderingMethod", "Star rendering method", "The rendering method used for visualizing the stars.", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo EnabledPointsRatioInfo = { "EnabledPointsRatio", "Enabled points", "The ratio of point-like stars that are rendered to produce the overall galaxy " "image. At a value of 0, no stars are rendered, at a value of 1 all points " "contained in the dataset are rendered. The specific value chosen is a " "compromise between image fidelity and rendering performance.", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo DownscaleVolumeRenderingInfo = { "Downscale", "Downscale factor volume rendering", "The downscaling factor used when rendering the volume.", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo NumberOfRayCastingStepsInfo = { "Steps", "Number of raycasting steps", "The number of integration steps used during the raycasting procedure.", openspace::properties::Property::Visibility::AdvancedUser }; void saveCachedFile(const std::filesystem::path& 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(std::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 = positions.size(); fileStream.write(reinterpret_cast(&nPositions), sizeof(uint64_t)); fileStream.write( reinterpret_cast(positions.data()), positions.size() * sizeof(glm::vec3) ); uint64_t nColors = colors.size(); fileStream.write(reinterpret_cast(&nColors), sizeof(uint64_t)); fileStream.write( reinterpret_cast(colors.data()), colors.size() * sizeof(glm::vec3) ); } float safeLength(const glm::vec3& vector) { 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; } struct [[codegen::Dictionary(RenderableGalaxy)]] Parameters { // [[codegen::verbatim(VolumeRenderingEnabledInfo.description)]] std::optional volumeRenderingEnabled; // [[codegen::verbatim(StarRenderingEnabledInfo.description)]] std::optional starRenderingEnabled; // [[codegen::verbatim(StepSizeInfo.description)]] std::optional stepSizeInfo; // [[codegen::verbatim(AbsorptionMultiplyInfo.description)]] std::optional absorptionMultiply; // [[codegen::verbatim(EmissionMultiplyInfo.description)]] std::optional emissionMultiply; // If specified, the default raycasting shader is overwritten and the shader at // this location is used instead. std::optional raycastingShader; enum class [[codegen::map(StarRenderingMethod)]] StarRenderingMethod { Points, Billboards }; // [[codegen::verbatim(StarRenderingMethodInfo.description)]] std::optional starRenderingMethod; // [[codegen::verbatim(RotationInfo.description)]] std::optional rotation; struct Volume { std::filesystem::path filename; glm::ivec3 dimensions; glm::vec3 size; // [[codegen::verbatim(NumberOfRayCastingStepsInfo.description)]] std::optional steps; // [[codegen::verbatim(DownscaleVolumeRenderingInfo.description)]] std::optional downscale; }; Volume volume; struct Points { std::filesystem::path filename; std::filesystem::path texture; // [[codegen::verbatim(EnabledPointsRatioInfo.description)]] std::optional enabledPointsRatio; }; Points points; }; #include "renderablegalaxy_codegen.cpp" } // namespace namespace openspace { documentation::Documentation RenderableGalaxy::Documentation() { return codegen::doc("galaxy_renderablegalaxy"); } RenderableGalaxy::RenderableGalaxy(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _volumeRenderingEnabled(VolumeRenderingEnabledInfo, true) , _starRenderingEnabled(StarRenderingEnabledInfo, true) , _stepSize(StepSizeInfo, 0.01f, 0.001f, 0.05f, 0.001f) , _absorptionMultiply(AbsorptionMultiplyInfo, 40.f, 0.f, 200.f) , _emissionMultiply(EmissionMultiplyInfo, 200.f, 0.f, 1000.f) , _starRenderingMethod(StarRenderingMethodInfo) , _enabledPointsRatio(EnabledPointsRatioInfo, 0.5f, 0.01f, 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) { const Parameters p = codegen::bake(dictionary); _volumeRenderingEnabled = p.volumeRenderingEnabled.value_or(_volumeRenderingEnabled); _starRenderingEnabled = p.starRenderingEnabled.value_or(_starRenderingEnabled); _volumeRenderingEnabled = p.volumeRenderingEnabled.value_or(_volumeRenderingEnabled); _stepSize = p.stepSizeInfo.value_or(_stepSize); _absorptionMultiply = p.absorptionMultiply.value_or(_absorptionMultiply); _emissionMultiply = p.emissionMultiply.value_or(_emissionMultiply); _raycastingShader = p.raycastingShader.value_or(_raycastingShader); _starRenderingMethod.addOptions({ { StarRenderingMethod::Points, "Points" }, { StarRenderingMethod::Billboards, "Billboards" } }); if (p.starRenderingMethod.has_value()) { _starRenderingMethod = codegen::map(*p.starRenderingMethod); } _rotation = p.rotation.value_or(_rotation); _volumeFilename = p.volume.filename; _volumeDimensions = p.volume.dimensions; _volumeSize = p.volume.size; _numberOfRayCastingSteps = p.volume.steps.value_or(_numberOfRayCastingSteps); _downScaleVolumeRendering = p.volume.downscale.value_or(_downScaleVolumeRendering); _pointsFilename = p.points.filename; _enabledPointsRatio = p.points.enabledPointsRatio.value_or(_enabledPointsRatio); _pointSpreadFunctionTexturePath = p.points.texture; _pointSpreadFunctionFile = std::make_unique( _pointSpreadFunctionTexturePath ); auto onChange = [this](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(_rotation); _downScaleVolumeRendering.setVisibility(properties::Property::Visibility::Developer); addProperty(_downScaleVolumeRendering); addProperty(_numberOfRayCastingSteps); // Use max component instead of length, to avoid problems with taking square // of huge value setBoundingSphere(glm::compMax(0.5f * _volumeSize)); } void RenderableGalaxy::initialize() { ZoneScoped; // Aspect is currently hardcoded to cubic voxels const glm::vec3 d = _volumeDimensions; _aspect = d / glm::compMax(d); // The volume volume::RawVolumeReader> reader( _volumeFilename, _volumeDimensions ); _volume = reader.read(); std::filesystem::path cachedPointsFile = FileSys.cacheManager()->cachedFilename( _pointsFilename ); const bool hasCachedFile = std::filesystem::is_regular_file(cachedPointsFile); if (hasCachedFile) { LINFO(std::format("Cached file '{}' used for galaxy point file '{}'", cachedPointsFile, _pointsFilename )); Result res = loadCachedFile(cachedPointsFile); if (res.success) { _pointPositionsCache = std::move(res.positions); _pointColorsCache = std::move(res.color); } else { FileSys.cacheManager()->removeCacheFile(_pointsFilename); Result resPoint = loadPointFile(); _pointPositionsCache = std::move(resPoint.positions); _pointColorsCache = std::move(resPoint.color); saveCachedFile( cachedPointsFile, _pointPositionsCache, _pointColorsCache, _nPoints, _enabledPointsRatio ); } } else { Result res = loadPointFile(); ghoul_assert(res.success, "Point file loading failed"); _pointPositionsCache = std::move(res.positions); _pointColorsCache = std::move(res.color); saveCachedFile( cachedPointsFile, _pointPositionsCache, _pointColorsCache, _nPoints, _enabledPointsRatio ); } } void RenderableGalaxy::initializeGL() { ZoneScoped; _texture = std::make_unique( _volumeDimensions, GL_TEXTURE_3D, ghoul::opengl::Texture::Format::RGBA, GL_RGBA, GL_UNSIGNED_BYTE, ghoul::opengl::Texture::FilterMode::Linear, ghoul::opengl::Texture::WrappingMode::ClampToEdge, ghoul::opengl::Texture::AllocateData::No, ghoul::opengl::Texture::TakeOwnership::No ); _texture->setPixelData( reinterpret_cast(_volume->data()), ghoul::opengl::Texture::TakeOwnership::No ); _texture->setDimensions(_volume->dimensions()); _texture->uploadTexture(); if (_raycastingShader.empty()) { _raycaster = std::make_unique(*_texture); } else { _raycaster = std::make_unique(*_texture, _raycastingShader); } _raycaster->initialize(); // We no longer need the data _volume = nullptr; global::raycasterManager->attachRaycaster(*_raycaster); // 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), 2 ); if (_pointSpreadFunctionTexture) { LDEBUG(std::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); ghoul::opengl::updateUniformLocations(*_billboardsProgram, _uniformCacheBillboards); glGenVertexArrays(1, &_pointsVao); glGenBuffers(1, &_positionVbo); glGenBuffers(1, &_colorVbo); glBindVertexArray(_pointsVao); glBindBuffer(GL_ARRAY_BUFFER, _positionVbo); glBufferData( GL_ARRAY_BUFFER, _pointPositionsCache.size() * sizeof(glm::vec3), _pointPositionsCache.data(), GL_STATIC_DRAW ); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); _pointPositionsCache.clear(); glBindBuffer(GL_ARRAY_BUFFER, _colorVbo); glBufferData( GL_ARRAY_BUFFER, _pointColorsCache.size() * sizeof(glm::vec3), _pointColorsCache.data(), GL_STATIC_DRAW ); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr); _pointColorsCache.clear(); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } void RenderableGalaxy::deinitializeGL() { if (_raycaster) { global::raycasterManager->detachRaycaster(*_raycaster); _raycaster = nullptr; } global::renderEngine->removeRenderProgram(_pointsProgram.get()); _pointsProgram = nullptr; global::renderEngine->removeRenderProgram(_billboardsProgram.get()); _billboardsProgram = 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; } 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)); const glm::mat4 volumeTransform = glm::scale(transform, _volumeSize); _pointTransform = transform; _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) { const 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 = glm::compMax(galaxySize); 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 * opacity(); 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); } } if (!_starRenderingEnabled || _opacityCoefficient <= 0.f) { return; } if (_starRenderingMethod == 1) { if (_billboardsProgram) { renderBillboards(data); } } else { if (_pointsProgram) { renderPoints(data); } } } void RenderableGalaxy::renderPoints(const RenderData& data) { glBlendFunc(GL_SRC_ALPHA, GL_ONE); glDepthMask(false); glDisable(GL_DEPTH_TEST); _pointsProgram->activate(); const 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) ); const AlternativeTransform altTransform = { .rotation = glm::dmat4(data.modelTransform.rotation) * rotMatrix }; const glm::dmat4 modelTransform = calcModelTransform(data, altTransform); const glm::dmat4 cameraViewProjectionMatrix = glm::dmat4(data.camera.projectionMatrix()) * data.camera.combinedViewMatrix(); _pointsProgram->setUniform(_uniformCachePoints.modelMatrix, modelTransform); _pointsProgram->setUniform( _uniformCachePoints.viewProjectionMatrix, cameraViewProjectionMatrix ); const 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(); // Restores OpenGL Rendering State global::renderEngine->openglStateCache().resetBlendState(); global::renderEngine->openglStateCache().resetDepthState(); } void RenderableGalaxy::renderBillboards(const RenderData& data) { // Change OpenGL Blending and Depth states glBlendFunc(GL_SRC_ALPHA, GL_ONE); glDepthMask(false); glDisable(GL_DEPTH_TEST); _billboardsProgram->activate(); const 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) ); const AlternativeTransform altTransform = { .rotation = glm::dmat4(data.modelTransform.rotation) * rotMatrix }; const glm::dmat4 modelTransform = calcModelTransform(data, altTransform); const glm::dmat4 cameraViewProjectionMatrix = glm::dmat4(data.camera.projectionMatrix()) * data.camera.combinedViewMatrix(); _billboardsProgram->setUniform(_uniformCacheBillboards.modelMatrix, modelTransform); _billboardsProgram->setUniform( _uniformCacheBillboards.viewProjectionMatrix, cameraViewProjectionMatrix ); const 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); const 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(); // Restores OpenGL Rendering State global::renderEngine->openglStateCache().resetBlendState(); global::renderEngine->openglStateCache().resetDepthState(); } RenderableGalaxy::Result RenderableGalaxy::loadPointFile() { std::ifstream pointFile = std::ifstream(_pointsFilename, std::ios::in); // Read header for OFF (Object File Format) std::string line; ghoul::getline(pointFile, line); // Read point count ghoul::getline(pointFile, line); std::istringstream iss(line); int64_t nPoints = 0; iss >> nPoints; // Prepare point reading _nPoints = static_cast(nPoints); std::vector pointPositions; std::vector pointColors; // Read points for (size_t i = 0; i < static_cast(_nPoints * _enabledPointsRatio.maxValue()) + 1; i++) { float x = 0.f; float y = 0.f; float z = 0.f; float r = 0.f; float g = 0.f; float b = 0.f; float a = 0.f; ghoul::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::filesystem::path& file) { ZoneScoped; std::ifstream fileStream = std::ifstream(file, std::ifstream::binary); if (!fileStream.good()) { LERROR(std::format("Error opening file '{}' for loading cache file", file)); return { false, {}, {} }; } int8_t cacheVersion = 0; fileStream.read(reinterpret_cast(&cacheVersion), sizeof(int8_t)); if (cacheVersion != CurrentCacheVersion) { LINFO(std::format("Removing cache file '{}' as the version changed", file)); return { false, {}, {} }; } int64_t nPoints = 0; fileStream.read(reinterpret_cast(&nPoints), sizeof(int64_t)); _nPoints = static_cast(nPoints); float enabledPointsRatio = false; fileStream.read(reinterpret_cast(&enabledPointsRatio), sizeof(float)); _enabledPointsRatio = enabledPointsRatio; uint64_t nPositions = 0; 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 = 0; 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