/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2016 * * * * 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 namespace { static const std::string _loggerCat = "RenderableStars"; const char* KeyFile = "File"; const char* KeyTexture = "Texture"; const char* KeyColorMap = "ColorMap"; const int8_t CurrentCacheVersion = 1; struct ColorVBOLayout { std::array position; // (x,y,z,e) float bvColor; // B-V color value float luminance; float absoluteMagnitude; }; struct VelocityVBOLayout { std::array position; // (x,y,z,e) float bvColor; // B-V color value float luminance; float absoluteMagnitude; float vx; // v_x float vy; // v_y float vz; // v_z }; struct SpeedVBOLayout { std::array position; // (x,y,z,e) float bvColor; // B-V color value float luminance; float absoluteMagnitude; float speed; }; } namespace openspace { openspace::Documentation RenderableStars::Documentation() { using namespace documentation; return { "RenderableStars", "base_renderablestars", { { "Type", new StringEqualVerifier("RenderableStars"), "", Optional::No }, { KeyFile, new StringVerifier, "The path to the SPECK file that contains information about the stars " "being rendered.", Optional::No }, { KeyTexture, new StringVerifier, "The path to the texture that should be used as a point spread function " "for the stars. The path is relative to the location of the .mod file " "and can contain file system token.", Optional::No }, { KeyColorMap, new StringVerifier, "The path to the texture that is used to convert from the B-V value of " "the star to its color. The texture is used as a one dimensional lookup " "function.", Optional::No } } }; } RenderableStars::RenderableStars(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _pointSpreadFunctionTexturePath("psfTexture", "Point Spread Function Texture") , _pointSpreadFunctionTexture(nullptr) , _pointSpreadFunctionTextureIsDirty(true) , _colorTexturePath("colorTexture", "ColorBV Texture") , _colorTexture(nullptr) , _colorTextureIsDirty(true) , _colorOption( "colorOption", "Color Option", properties::OptionProperty::DisplayType::Dropdown ) , _dataIsDirty(true) , _alphaValue("alphaValue", "Transparency", 1.f, 0.f, 1.f) , _scaleFactor("scaleFactor", "Scale Factor", 1.f, 0.f, 10.f) , _minBillboardSize("minBillboardSize", "Min Billboard Size", 1.f, 1.f, 100.f) , _program(nullptr) , _speckFile("") , _nValuesPerStar(0) , _vao(0) , _vbo(0) { using File = ghoul::filesystem::File; documentation::testSpecificationAndThrow( Documentation(), dictionary, "RenderableStars" ); _pointSpreadFunctionTexturePath = absPath(dictionary.value(KeyTexture)); _pointSpreadFunctionFile = std::make_unique(_pointSpreadFunctionTexturePath); _colorTexturePath = absPath(dictionary.value(KeyColorMap)); _colorTextureFile = std::make_unique(_colorTexturePath); _speckFile = absPath(dictionary.value(KeyFile)); //_colorOption.addOptions({ // { ColorOption::Color, "Color" }, // { ColorOption::Velocity, "Velocity" }, // { ColorOption::Speed, "Speed" } //}); _colorOption.addOption(ColorOption::Color, "Color"); _colorOption.addOption(ColorOption::Velocity, "Velocity"); _colorOption.addOption(ColorOption::Speed, "Speed"); addProperty(_colorOption); _colorOption.onChange([&]{ _dataIsDirty = true;}); addProperty(_pointSpreadFunctionTexturePath); _pointSpreadFunctionTexturePath.onChange( [&]{ _pointSpreadFunctionTextureIsDirty = true; } ); _pointSpreadFunctionFile->setCallback( [&](const File&) { _pointSpreadFunctionTextureIsDirty = true; } ); addProperty(_colorTexturePath); _colorTexturePath.onChange([&]{ _colorTextureIsDirty = true; }); _colorTextureFile->setCallback( [&](const File&) { _colorTextureIsDirty = true; } ); addProperty(_alphaValue); addProperty(_scaleFactor); addProperty(_minBillboardSize); } RenderableStars::~RenderableStars() {} bool RenderableStars::isReady() const { return (_program != nullptr) && (!_fullData.empty()); } bool RenderableStars::initialize() { bool completeSuccess = true; RenderEngine& renderEngine = OsEng.renderEngine(); _program = renderEngine.buildRenderProgram("Star", "${MODULE_BASE}/shaders/star_vs.glsl", "${MODULE_BASE}/shaders/star_fs.glsl", "${MODULE_BASE}/shaders/star_ge.glsl"); if (!_program) { return false; } completeSuccess &= loadData(); completeSuccess &= (_pointSpreadFunctionTexture != nullptr); return completeSuccess; } bool RenderableStars::deinitialize() { glDeleteBuffers(1, &_vbo); _vbo = 0; glDeleteVertexArrays(1, &_vao); _vao = 0; _pointSpreadFunctionTexture = nullptr; _colorTexture = nullptr; RenderEngine& renderEngine = OsEng.renderEngine(); if (_program) { renderEngine.removeRenderProgram(_program); _program = nullptr; } return true; } void RenderableStars::render(const RenderData& data) { glDepthMask(false); _program->activate(); // @Check overwriting the scaling from the camera; error as parsec->meter conversion // is done twice? ---abock glm::vec2 scaling = glm::vec2(1, -19); glm::mat4 modelMatrix = glm::mat4(1.0); glm::mat4 viewMatrix = data.camera.viewMatrix(); glm::mat4 projectionMatrix = data.camera.projectionMatrix(); using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; _program->setIgnoreUniformLocationError(IgnoreError::Yes); //_program->setUniform("ViewProjection", data.camera.viewProjectionMatrix()); //_program->setUniform("ModelTransform", glm::mat4(1.f)); _program->setUniform("model", modelMatrix); _program->setUniform("view", viewMatrix); _program->setUniform("projection", projectionMatrix); _program->setUniform("colorOption", _colorOption); _program->setUniform("alphaValue", _alphaValue); _program->setUniform("scaleFactor", _scaleFactor); _program->setUniform("minBillboardSize", _minBillboardSize); _program->setUniform("screenSize", glm::vec2(OsEng.renderEngine().renderingResolution())); setPscUniforms(*_program.get(), data.camera, data.position); _program->setUniform("scaling", scaling); ghoul::opengl::TextureUnit psfUnit; psfUnit.activate(); _pointSpreadFunctionTexture->bind(); _program->setUniform("psfTexture", psfUnit); ghoul::opengl::TextureUnit colorUnit; colorUnit.activate(); _colorTexture->bind(); _program->setUniform("colorTexture", colorUnit); glBindVertexArray(_vao); const GLsizei nStars = static_cast(_fullData.size() / _nValuesPerStar); glDrawArrays(GL_POINTS, 0, nStars); glBindVertexArray(0); using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; _program->setIgnoreUniformLocationError(IgnoreError::No); _program->deactivate(); glDepthMask(true); } void RenderableStars::update(const UpdateData& data) { if (_dataIsDirty) { const int value = _colorOption; LDEBUG("Regenerating data"); createDataSlice(ColorOption(value)); int size = static_cast(_slicedData.size()); if (_vao == 0) { glGenVertexArrays(1, &_vao); LDEBUG("Generating Vertex Array id '" << _vao << "'"); } if (_vbo == 0) { glGenBuffers(1, &_vbo); LDEBUG("Generating Vertex Buffer Object id '" << _vbo << "'"); } glBindVertexArray(_vao); glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBufferData( GL_ARRAY_BUFFER, size * sizeof(GLfloat), &_slicedData[0], GL_STATIC_DRAW ); GLint positionAttrib = _program->attributeLocation("in_position"); GLint brightnessDataAttrib = _program->attributeLocation("in_brightness"); const size_t nStars = _fullData.size() / _nValuesPerStar; const size_t nValues = _slicedData.size() / nStars; GLsizei stride = static_cast(sizeof(GLfloat) * nValues); glEnableVertexAttribArray(positionAttrib); glEnableVertexAttribArray(brightnessDataAttrib); const int colorOption = _colorOption; switch (colorOption) { case ColorOption::Color: glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(ColorVBOLayout, position))); glVertexAttribPointer(brightnessDataAttrib, 3, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(ColorVBOLayout, bvColor))); break; case ColorOption::Velocity: { glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(VelocityVBOLayout, position))); glVertexAttribPointer(brightnessDataAttrib, 3, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(VelocityVBOLayout, bvColor))); GLint velocityAttrib = _program->attributeLocation("in_velocity"); glEnableVertexAttribArray(velocityAttrib); glVertexAttribPointer(velocityAttrib, 3, GL_FLOAT, GL_TRUE, stride, reinterpret_cast(offsetof(VelocityVBOLayout, vx))); break; } case ColorOption::Speed: { glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(SpeedVBOLayout, position))); glVertexAttribPointer(brightnessDataAttrib, 3, GL_FLOAT, GL_FALSE, stride, reinterpret_cast(offsetof(SpeedVBOLayout, bvColor))); GLint speedAttrib = _program->attributeLocation("in_speed"); glEnableVertexAttribArray(speedAttrib); glVertexAttribPointer(speedAttrib, 1, GL_FLOAT, GL_TRUE, stride, reinterpret_cast(offsetof(SpeedVBOLayout, speed))); } } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); _dataIsDirty = false; } if (_pointSpreadFunctionTextureIsDirty) { LDEBUG("Reloading Point Spread Function texture"); _pointSpreadFunctionTexture = nullptr; if (_pointSpreadFunctionTexturePath.value() != "") { _pointSpreadFunctionTexture = std::move( ghoul::io::TextureReader::ref().loadTexture( absPath(_pointSpreadFunctionTexturePath) ) ); if (_pointSpreadFunctionTexture) { LDEBUG("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() != "") { _colorTexture = std::move(ghoul::io::TextureReader::ref().loadTexture(absPath(_colorTexturePath))); if (_colorTexture) { LDEBUG("Loaded texture from '" << absPath(_colorTexturePath) << "'"); _colorTexture->uploadTexture(); } _colorTextureFile = std::make_unique( _colorTexturePath ); _colorTextureFile->setCallback( [&](const ghoul::filesystem::File&) { _colorTextureIsDirty = true; } ); } _colorTextureIsDirty = false; } } bool RenderableStars::loadData() { std::string _file = _speckFile; std::string cachedFile = FileSys.cacheManager()->cachedFilename( _file, ghoul::filesystem::CacheManager::Persistent::Yes ); bool hasCachedFile = FileSys.fileExists(cachedFile); if (hasCachedFile) { LINFO("Cached file '" << cachedFile << "' used for Speck file '" << _file << "'"); bool success = loadCachedFile(cachedFile); if (success) { return true; } else { FileSys.cacheManager()->removeCacheFile(_file); // Intentional fall-through to the 'else' computation to generate the cache // file for the next run } } else { LINFO("Cache for Speck file '" << _file << "' not found"); } LINFO("Loading Speck file '" << _file << "'"); bool success = readSpeckFile(); if (!success) { return false; } LINFO("Saving cache"); success = saveCachedFile(cachedFile); return success; } bool RenderableStars::readSpeckFile() { std::string _file = _speckFile; std::ifstream file(_file); if (!file.good()) { LERROR("Failed to open Speck file '" << _file << "'"); return false; } _nValuesPerStar = 0; // The beginning of the speck file has a header that either contains comments // (signaled by a preceding '#') or information about the structure of the file // (signaled by the keywords 'datavar', 'texturevar', and 'texture') std::string line = ""; while (true) { std::ifstream::streampos position = file.tellg(); std::getline(file, line); if (line[0] == '#' || line.empty()) { continue; } if (line.substr(0, 7) != "datavar" && line.substr(0, 10) != "texturevar" && line.substr(0, 7) != "texture") { // we read a line that doesn't belong to the header, so we have to jump back // before the beginning of the current line file.seekg(position); break; } if (line.substr(0, 7) == "datavar") { // datavar lines are structured as follows: // datavar # description // where # is the index of the data variable; so if we repeatedly overwrite // the 'nValues' variable with the latest index, we will end up with the total // number of values (+3 since X Y Z are not counted in the Speck file index) std::stringstream str(line); std::string dummy; str >> dummy; str >> _nValuesPerStar; _nValuesPerStar += 1; // We want the number, but the index is 0 based } } _nValuesPerStar += 3; // X Y Z are not counted in the Speck file indices do { std::vector values(_nValuesPerStar); std::getline(file, line); std::stringstream str(line); for (int i = 0; i < _nValuesPerStar; ++i) { str >> values[i]; } _fullData.insert(_fullData.end(), values.begin(), values.end()); } while (!file.eof()); return true; } bool RenderableStars::loadCachedFile(const std::string& file) { std::ifstream fileStream(file, std::ifstream::binary); if (fileStream.good()) { int8_t version = 0; fileStream.read(reinterpret_cast(&version), sizeof(int8_t)); if (version != CurrentCacheVersion) { LINFO("The format of the cached file has changed, deleting old cache"); fileStream.close(); FileSys.deleteFile(file); return false; } int32_t nValues = 0; fileStream.read(reinterpret_cast(&nValues), sizeof(int32_t)); fileStream.read(reinterpret_cast(&_nValuesPerStar), sizeof(int32_t)); _fullData.resize(nValues); fileStream.read(reinterpret_cast(&_fullData[0]), nValues * sizeof(_fullData[0])); bool success = fileStream.good(); return success; } else { LERROR("Error opening file '" << file << "' for loading cache file"); return false; } } bool RenderableStars::saveCachedFile(const std::string& file) const { std::ofstream fileStream(file, std::ofstream::binary); if (fileStream.good()) { fileStream.write(reinterpret_cast(&CurrentCacheVersion), sizeof(int8_t)); int32_t nValues = static_cast(_fullData.size()); if (nValues == 0) { LERROR("Error writing cache: No values were loaded"); return false; } fileStream.write(reinterpret_cast(&nValues), sizeof(int32_t)); int32_t nValuesPerStar = static_cast(_nValuesPerStar); fileStream.write(reinterpret_cast(&nValuesPerStar), sizeof(int32_t)); size_t nBytes = nValues * sizeof(_fullData[0]); fileStream.write(reinterpret_cast(&_fullData[0]), nBytes); bool success = fileStream.good(); return success; } else { LERROR("Error opening file '" << file << "' for save cache file"); return false; } } void RenderableStars::createDataSlice(ColorOption option) { _slicedData.clear(); // This is only temporary until the scalegraph is in place ---abock float minDistance = std::numeric_limits::max(); float maxDistance = -std::numeric_limits::max(); for (size_t i = 0; i < _fullData.size(); i+=_nValuesPerStar) { float distLy = _fullData[i + 6]; //if (distLy < 20.f) { minDistance = std::min(minDistance, distLy); maxDistance = std::max(maxDistance, distLy); //} } for (size_t i = 0; i < _fullData.size(); i+=_nValuesPerStar) { glm::vec3 p = glm::vec3(_fullData[i + 0], _fullData[i + 1], _fullData[i + 2]); // This is only temporary until the scalegraph is in place. It places all stars // on a sphere with a small variation in the distance to account for blending // issues ---abock //if (p != glm::vec3(0.f)) // p = glm::normalize(p); //float distLy = _fullData[i + 6]; //float normalizedDist = (distLy - minDistance) / (maxDistance - minDistance); //float distance = 18.f - normalizedDist / 1.f ; //psc position = psc(glm::vec4(p, distance)); // Convert parsecs -> meter psc position = psc(glm::vec4(p * 0.308567756f, 17)); //position[1] *= parsecsToMetersFactor[0]; //position[2] *= parsecsToMetersFactor[0]; //position[3] += parsecsToMetersFactor[1]; switch (option) { case ColorOption::Color: { union { ColorVBOLayout value; std::array data; } layout; layout.value.position = { { position[0], position[1], position[2], position[3] } }; #ifdef USING_STELLAR_TEST_GRID layout.value.bvColor = _fullData[i + 3]; layout.value.luminance = _fullData[i + 3]; layout.value.absoluteMagnitude = _fullData[i + 3]; #else layout.value.bvColor = _fullData[i + 3]; layout.value.luminance = _fullData[i + 4]; layout.value.absoluteMagnitude = _fullData[i + 5]; #endif _slicedData.insert(_slicedData.end(), layout.data.begin(), layout.data.end()); break; } case ColorOption::Velocity: { union { VelocityVBOLayout value; std::array data; } layout; layout.value.position = { { position[0], position[1], position[2], position[3] } }; layout.value.bvColor = _fullData[i + 3]; layout.value.luminance = _fullData[i + 4]; layout.value.absoluteMagnitude = _fullData[i + 5]; layout.value.vx = _fullData[i + 12]; layout.value.vy = _fullData[i + 13]; layout.value.vz = _fullData[i + 14]; _slicedData.insert(_slicedData.end(), layout.data.begin(), layout.data.end()); break; } case ColorOption::Speed: { union { SpeedVBOLayout value; std::array data; } layout; layout.value.position = { { position[0], position[1], position[2], position[3] } }; layout.value.bvColor = _fullData[i + 3]; layout.value.luminance = _fullData[i + 4]; layout.value.absoluteMagnitude = _fullData[i + 5]; layout.value.speed = _fullData[i + 15]; _slicedData.insert(_slicedData.end(), layout.data.begin(), layout.data.end()); break; } } } } } // namespace openspace