/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2017 * * * * 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 namespace { const char* _loggerCat = "RenderableBillboardsCloud"; const char* KeyFile = "File"; const char* keyColor = "Color"; const char* keyUnit = "Unit"; const char* MeterUnit = "m"; const char* KilometerUnit = "Km"; const char* ParsecUnit = "pc"; const char* KiloparsecUnit = "Kpc"; const char* MegaparsecUnit = "Mpc"; const char* GigaparsecUnit = "Gpc"; const char* GigalightyearUnit = "Gly"; const int8_t CurrentCacheVersion = 1; const double PARSEC = 0.308567756E17; static const openspace::properties::Property::PropertyInfo SpriteTextureInfo = { "Texture", "Point Sprite Texture", "The path to the texture that should be used as the point sprite." }; static const openspace::properties::Property::PropertyInfo TransparencyInfo = { "Transparency", "Transparency", "This value is a multiplicative factor that is applied to the transparency of " "all points." }; static const openspace::properties::Property::PropertyInfo ScaleFactorInfo = { "ScaleFactor", "Scale Factor", "This value is used as a multiplicative factor that is applied to the apparent " "size of each point." }; static const openspace::properties::Property::PropertyInfo ColorInfo = { "Color", "Color", "This value is used to define the color of the astronomical object." }; static const openspace::properties::Property::PropertyInfo ColorMapInfo = { "ColorMap", "Color Map File", "The path to the color map file of the astronomical object." }; static const openspace::properties::Property::PropertyInfo PolygonSidesInfo = { "PolygonSides", "Polygon Sides", "The number of sides for the polygon used to represent the astronomical object." }; static const openspace::properties::Property::PropertyInfo TextColorInfo = { "TextColor", "Text Color", "The text color for the astronomical object." }; static const openspace::properties::Property::PropertyInfo TextSizeInfo = { "TextSize", "Text Size", "The text size for the astronomical object labels." }; static const openspace::properties::Property::PropertyInfo LabelFileInfo = { "LabelFile", "Label File", "The path to the label file that contains information about the astronomical " "objects being rendered." }; static const openspace::properties::Property::PropertyInfo LabelMinSizeInfo = { "TextMinSize", "Text Min Size", "The minimal size (in pixels) of the text for the labels for the astronomical " "objects being rendered." }; static const openspace::properties::Property::PropertyInfo DrawElementsInfo = { "DrawElements", "Draw Elements", "Enables/Disables the drawing of the astronomical objects." }; static const openspace::properties::Property::PropertyInfo DrawLabelInfo = { "DrawLabels", "Draw Labels", "Determines whether labels should be drawn or hidden." }; static const openspace::properties::Property::PropertyInfo ColorOptionInfo = { "ColorOption", "Color Option", "This value determines which paramenter is used default color of the " "astronomical objects." }; static const openspace::properties::Property::PropertyInfo ColorRangeInfo = { "ColorRange", "Color Range", "This value determines the color ranges for the color parameter of the " "astronomical objects." }; static const openspace::properties::Property::PropertyInfo RenderOptionInfo = { "RenderOptionInfo", "Render Option", "Debug option for rendering of billboards and texts." }; } // namespace namespace openspace { documentation::Documentation RenderableBillboardsCloud::Documentation() { using namespace documentation; return { "RenderableBillboardsCloud", "digitaluniverse_RenderableBillboardsCloud", { { "Type", new StringEqualVerifier("RenderableBillboardsCloud"), Optional::No }, { KeyFile, new StringVerifier, Optional::Yes, "The path to the SPECK file that contains information about the astronomical " "object being rendered." }, { keyColor, new Vector3Verifier, Optional::No, "Astronomical Object Color (r,g,b)." }, { SpriteTextureInfo.identifier, new StringVerifier, Optional::Yes, SpriteTextureInfo.description }, { TransparencyInfo.identifier, new DoubleVerifier, Optional::No, TransparencyInfo.description }, { ScaleFactorInfo.identifier, new DoubleVerifier, Optional::Yes, ScaleFactorInfo.description }, { ColorMapInfo.identifier, new StringVerifier, Optional::Yes, ColorMapInfo.description }, { PolygonSidesInfo.identifier, new IntVerifier, Optional::Yes, PolygonSidesInfo.description }, { DrawLabelInfo.identifier, new BoolVerifier, Optional::Yes, DrawLabelInfo.description }, { TextColorInfo.identifier, new DoubleVector4Verifier, Optional::Yes, TextColorInfo.description }, { TextSizeInfo.identifier, new DoubleVerifier, Optional::Yes, TextSizeInfo.description }, { LabelFileInfo.identifier, new StringVerifier, Optional::Yes, LabelFileInfo.description }, { LabelMinSizeInfo.identifier, new DoubleVerifier, Optional::Yes, LabelMinSizeInfo.description }, { ColorOptionInfo.identifier, new StringListVerifier, Optional::Yes, ColorOptionInfo.description }, { ColorRangeInfo.identifier, new Vector2ListVerifier, Optional::Yes, ColorRangeInfo.description } } }; } RenderableBillboardsCloud::RenderableBillboardsCloud(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _hasSpeckFile(false) , _dataIsDirty(true) , _textColorIsDirty(true) , _hasSpriteTexture(false) , _spriteTextureIsDirty(true) , _hasColorMapFile(false) , _hasPolygon(false) , _hasLabel(false) , _labelDataIsDirty(true) , _polygonSides(0) , _pTexture(0) , _alphaValue(TransparencyInfo, 1.f, 0.f, 1.f) , _scaleFactor(ScaleFactorInfo, 1.f, 0.f, 600.f) , _pointColor(ColorInfo, glm::vec3(1.f, 0.4f, 0.2f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.0f, 1.0f, 1.0f)) , _spriteTexturePath(SpriteTextureInfo) , _textColor( TextColorInfo, glm::vec4(1.0f, 1.0, 1.0f, 1.f), glm::vec4(0.f), glm::vec4(1.f) ) , _textSize(TextSizeInfo, 8.0, 0.5, 24.0) , _textMinSize(LabelMinSizeInfo, 8.0, 0.5, 24.0) , _drawElements(DrawElementsInfo, true) , _drawLabels(DrawLabelInfo, false) , _colorOption(ColorOptionInfo, properties::OptionProperty::DisplayType::Dropdown) , _renderOption(RenderOptionInfo, properties::OptionProperty::DisplayType::Dropdown) , _polygonTexture(nullptr) , _spriteTexture(nullptr) , _program(nullptr) , _fontRenderer(nullptr) , _font(nullptr) , _speckFile("") , _colorMapFile("") , _labelFile("") , _colorOptionString("") , _unit(Parsec) , _nValuesPerAstronomicalObject(0) , _vao(0) , _vbo(0) , _polygonVao(0) , _polygonVbo(0) { using File = ghoul::filesystem::File; documentation::testSpecificationAndThrow( Documentation(), dictionary, "RenderableBillboardsCloud" ); if (dictionary.hasKey(KeyFile)) { _speckFile = absPath(dictionary.value(KeyFile)); _hasSpeckFile = true; _drawElements.onChange([&]() { _hasSpeckFile = _hasSpeckFile == true? false : true; }); addProperty(_drawElements); } // DEBUG: _renderOption.addOption(0, "Camera View Direction"); _renderOption.addOption(1, "Camera Position Normal"); _renderOption.addOption(2, "Screen center Position Normal"); addProperty(_renderOption); if (dictionary.hasKey(keyUnit)) { std::string unit = dictionary.value(keyUnit); if (unit == MeterUnit) { _unit = Meter; } else if (unit == KilometerUnit) { _unit = Kilometer; } else if (unit == ParsecUnit) { _unit = Parsec; } else if (unit == KiloparsecUnit) { _unit = Kiloparsec; } else if (unit == MegaparsecUnit) { _unit = Megaparsec; } else if (unit == GigaparsecUnit) { _unit = Gigaparsec; } else if (unit == GigalightyearUnit) { _unit = GigalightYears; } else { LWARNING("No unit given for RenderableBillboardsCloud. Using meters as units."); _unit = Meter; } } if (dictionary.hasKey(SpriteTextureInfo.identifier)) { _spriteTexturePath = absPath(dictionary.value( SpriteTextureInfo.identifier )); _spriteTextureFile = std::make_unique(_spriteTexturePath); _spriteTexturePath.onChange([&] { _spriteTextureIsDirty = true; }); _spriteTextureFile->setCallback( [&](const File&) { _spriteTextureIsDirty = true; } ); addProperty(_spriteTexturePath); _hasSpriteTexture = true; } if (dictionary.hasKey(ColorMapInfo.identifier)) { _colorMapFile = absPath(dictionary.value( ColorMapInfo.identifier )); _hasColorMapFile = true; if (dictionary.hasKey(ColorOptionInfo.identifier)) { ghoul::Dictionary colorOptionDataDic = dictionary.value( ColorOptionInfo.identifier ); for (int i = 0; i < static_cast(colorOptionDataDic.size()); ++i) { std::string colorMapInUseName( colorOptionDataDic.value(std::to_string(i + 1))); _colorOption.addOption(i, colorMapInUseName); _optionConversionMap.insert({i, colorMapInUseName}); _colorOptionString = colorMapInUseName; } } _colorOption.onChange( [&] { _dataIsDirty = true; _colorOptionString = _optionConversionMap[_colorOption.value()]; }); addProperty(_colorOption); if (dictionary.hasKey(ColorRangeInfo.identifier)) { ghoul::Dictionary rangeDataDic = dictionary.value( ColorRangeInfo.identifier ); for (int i = 0; i < static_cast(rangeDataDic.size()); ++i) { _colorRangeData.push_back( rangeDataDic.value(std::to_string(i+1))); } } } else if (dictionary.hasKey(keyColor)) { _pointColor = dictionary.value(keyColor); addProperty(_pointColor); } if (dictionary.hasKey(TransparencyInfo.identifier)) { _alphaValue = static_cast( dictionary.value(TransparencyInfo.identifier) ); } addProperty(_alphaValue); if (dictionary.hasKey(ScaleFactorInfo.identifier)) { _scaleFactor = static_cast( dictionary.value(ScaleFactorInfo.identifier) ); } addProperty(_scaleFactor); if (dictionary.hasKey(PolygonSidesInfo.identifier)) { _polygonSides = static_cast( dictionary.value(PolygonSidesInfo.identifier) ); _hasPolygon = true; } if (dictionary.hasKey(LabelFileInfo.identifier)) { if (dictionary.hasKey(DrawLabelInfo.identifier)) { _drawLabels = dictionary.value(DrawLabelInfo.identifier); } addProperty(_drawLabels); _labelFile = absPath(dictionary.value( LabelFileInfo.identifier )); _hasLabel = true; if (dictionary.hasKey(TextColorInfo.identifier)) { _textColor = dictionary.value(TextColorInfo.identifier); _hasLabel = true; } _textColor.setViewOption(properties::Property::ViewOptions::Color); addProperty(_textColor); _textColor.onChange([&]() { _textColorIsDirty = true; }); if (dictionary.hasKey(TextSizeInfo.identifier)) { _textSize = dictionary.value(TextSizeInfo.identifier); } addProperty(_textSize); if (dictionary.hasKey(LabelMinSizeInfo.identifier)) { _textMinSize = static_cast(dictionary.value(LabelMinSizeInfo.identifier)); } addProperty(_textMinSize); } } bool RenderableBillboardsCloud::isReady() const { return ((_program != nullptr) && (!_fullData.empty())) || (!_labelData.empty()); } void RenderableBillboardsCloud::initialize() { RenderEngine& renderEngine = OsEng.renderEngine(); _program = renderEngine.buildRenderProgram("RenderableBillboardsCloud", "${MODULE_DIGITALUNIVERSE}/shaders/billboard2_vs.glsl", "${MODULE_DIGITALUNIVERSE}/shaders/billboard2_fs.glsl", "${MODULE_DIGITALUNIVERSE}/shaders/billboard2_gs.glsl"); bool success = loadData(); if (!success) { throw ghoul::RuntimeError("Error loading data"); } if (!_colorOptionString.empty()) { // Following DU behavior here. The last colormap variable // entry is the one selected by default. _colorOption = static_cast(_colorRangeData.size() - 1); } if (_hasPolygon) { createPolygonTexture(); } if (_hasLabel) { if (_fontRenderer == nullptr) _fontRenderer = std::unique_ptr( ghoul::fontrendering::FontRenderer::createProjectionSubjectText()); if (_font == nullptr) { size_t _fontSize = 30; _font = OsEng.fontManager().font("Mono", static_cast(_fontSize), ghoul::fontrendering::FontManager::Outline::Yes, ghoul::fontrendering::FontManager::LoadGlyphs::No ); } } } void RenderableBillboardsCloud::deinitialize() { glDeleteBuffers(1, &_vbo); _vbo = 0; glDeleteVertexArrays(1, &_vao); _vao = 0; RenderEngine& renderEngine = OsEng.renderEngine(); if (_program) { renderEngine.removeRenderProgram(_program); _program = nullptr; } if (_hasSpriteTexture) { _spriteTexture = nullptr; } if (_hasPolygon) { _polygonTexture = nullptr; glDeleteTextures(1, &_pTexture); } } void RenderableBillboardsCloud::renderBillboards(const RenderData& data, const glm::dmat4& modelViewMatrix, const glm::dmat4& projectionMatrix, const glm::vec3& orthoRight, const glm::vec3& orthoUp) { glDepthMask(false); // Saving current OpenGL state GLboolean blendEnabled = glIsEnabled(GL_BLEND); GLenum blendEquationRGB; GLenum blendEquationAlpha; GLenum blendDestAlpha; GLenum blendDestRGB; GLenum blendSrcAlpha; GLenum blendSrcRGB; 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); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); _program->activate(); using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; _program->setIgnoreUniformLocationError(IgnoreError::Yes); _program->setUniform("screenSize", glm::vec2(OsEng.renderEngine().renderingResolution())); _program->setUniform("projection", projectionMatrix); _program->setUniform("modelViewTransform", modelViewMatrix); _program->setUniform("modelViewProjectionTransform", glm::dmat4(projectionMatrix) * modelViewMatrix); _program->setUniform("cameraPosition", data.camera.positionVec3()); _program->setUniform("cameraLookUp", data.camera.lookUpVectorWorldSpace()); _program->setUniform("renderOption", _renderOption.value()); glm::dvec4 centerScreenWorld = glm::inverse(data.camera.combinedViewMatrix()) * glm::dvec4(0.0, 0.0, 0.0, 1.0); _program->setUniform("centerScreenInWorldPosition", centerScreenWorld); _program->setUniform("minBillboardSize", 1.f); // in pixels _program->setUniform("color", _pointColor); _program->setUniform("sides", 4); _program->setUniform("alphaValue", _alphaValue); _program->setUniform("scaleFactor", _scaleFactor); _program->setUniform("up", orthoUp); _program->setUniform("right", orthoRight); ghoul::opengl::TextureUnit spriteTextureUnit; if (_hasSpriteTexture) { spriteTextureUnit.activate(); _spriteTexture->bind(); _program->setUniform("spriteTexture", spriteTextureUnit); } ghoul::opengl::TextureUnit polygonTextureUnit; if (_hasPolygon) { polygonTextureUnit.activate(); glBindTexture(GL_TEXTURE_2D, _pTexture); _program->setUniform("polygonTexture", polygonTextureUnit); _program->setUniform("hasPolygon", _hasPolygon); } if (_hasColorMapFile) { _program->setUniform("hasColorMap", true); } else { _program->setUniform("hasColorMap", false); } glBindVertexArray(_vao); const GLsizei nAstronomicalObjects = static_cast(_fullData.size() / _nValuesPerAstronomicalObject); glDrawArrays(GL_POINTS, 0, nAstronomicalObjects); glBindVertexArray(0); using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; _program->setIgnoreUniformLocationError(IgnoreError::No); _program->deactivate(); // Restores blending state glBlendEquationSeparate(blendEquationRGB, blendEquationAlpha); glBlendFuncSeparate(blendSrcRGB, blendDestRGB, blendSrcAlpha, blendDestAlpha); if (!blendEnabled) { glDisable(GL_BLEND); } glDepthMask(true); } void RenderableBillboardsCloud::renderLabels(const RenderData& data, const glm::dmat4& modelViewProjectionMatrix, const glm::vec3& orthoRight, const glm::vec3& orthoUp) { RenderEngine& renderEngine = OsEng.renderEngine(); _fontRenderer->setFramebufferSize(renderEngine.renderingResolution()); float scale = 0.0; switch (_unit) { case Meter: scale = 1.0; break; case Kilometer: scale = 1e3; break; case Parsec: scale = PARSEC; break; case Kiloparsec: scale = 1e3 * PARSEC; break; case Megaparsec: scale = 1e6 * PARSEC; break; case Gigaparsec: scale = 1e9 * PARSEC; break; case GigalightYears: scale = 306391534.73091 * PARSEC; break; } for (const std::pair& pair : _labelData) { //glm::vec3 scaledPos(_transformationMatrix * glm::dvec4(pair.first, 1.0)); glm::vec3 scaledPos(pair.first); scaledPos *= scale; _fontRenderer->render( *_font, scaledPos, _textColor, pow(10.0, _textSize.value()), _textMinSize, modelViewProjectionMatrix, orthoRight, orthoUp, data.camera.positionVec3(), data.camera.lookUpVectorWorldSpace(), _renderOption.value(), "%s", pair.second.c_str()); } } void RenderableBillboardsCloud::render(const RenderData& data, RendererTasks&) { glm::dmat4 modelMatrix = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation glm::dmat4(data.modelTransform.rotation) * // Spice rotation glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); glm::dmat4 modelViewMatrix = data.camera.combinedViewMatrix() * modelMatrix; // glm::mat4 viewMatrix = data.camera.viewMatrix(); glm::mat4 projectionMatrix = data.camera.projectionMatrix(); glm::dmat4 modelViewProjectionMatrix = glm::dmat4(projectionMatrix) * modelViewMatrix; glm::vec3 lookup = data.camera.lookUpVectorWorldSpace(); glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace(); glm::vec3 right = glm::cross(viewDirection, lookup); glm::vec3 up = glm::cross(right, viewDirection); glm::dmat4 worldToModelTransform = glm::inverse(modelMatrix); glm::vec3 orthoRight = glm::normalize(glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))); glm::vec3 orthoUp = glm::normalize(glm::vec3(worldToModelTransform * glm::vec4(up, 0.0))); if (_hasSpeckFile) { renderBillboards(data, modelViewMatrix, projectionMatrix, orthoRight, orthoUp); } if (_drawLabels && _hasLabel) { renderLabels(data, modelViewProjectionMatrix, orthoRight, orthoUp); } } void RenderableBillboardsCloud::update(const UpdateData&) { if (_dataIsDirty && _hasSpeckFile) { LDEBUG("Regenerating data"); createDataSlice(); 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(float), &_slicedData[0], GL_STATIC_DRAW ); GLint positionAttrib = _program->attributeLocation("in_position"); if (_hasColorMapFile) { /*const size_t nAstronomicalObjects = _fullData.size() / _nValuesPerAstronomicalObject; const size_t nValues = _slicedData.size() / nAstronomicalObjects; GLsizei stride = static_cast(sizeof(float) * nValues);*/ glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer( positionAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(float)*8, nullptr ); GLint colorMapAttrib = _program->attributeLocation("in_colormap"); glEnableVertexAttribArray(colorMapAttrib); glVertexAttribPointer( colorMapAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(float) * 8, reinterpret_cast(sizeof(float)*4) ); } else { glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer( positionAttrib, 4, GL_FLOAT, GL_FALSE, 0, nullptr ); } glBindVertexArray(0); _dataIsDirty = false; } if (_hasSpriteTexture && _spriteTextureIsDirty) { LDEBUG("Reloading Sprite Texture"); _spriteTexture = nullptr; if (_spriteTexturePath.value() != "") { _spriteTexture = ghoul::io::TextureReader::ref().loadTexture(absPath(_spriteTexturePath)); if (_spriteTexture) { LDEBUG("Loaded texture from '" << absPath(_spriteTexturePath) << "'"); _spriteTexture->uploadTexture(); } _spriteTexture->setFilter(ghoul::opengl::Texture::FilterMode::AnisotropicMipMap); _spriteTextureFile = std::make_unique( _spriteTexturePath); _spriteTextureFile->setCallback( [&](const ghoul::filesystem::File&) { _spriteTextureIsDirty = true; } ); } _spriteTextureIsDirty = false; } if (_hasLabel && _labelDataIsDirty) { _labelDataIsDirty = false; } } bool RenderableBillboardsCloud::loadData() { bool success = false; if (_hasSpeckFile) { std::string _file = _speckFile; // I disabled the cache as it didn't work on Mac --- abock // 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 << "'"); // success = loadCachedFile(cachedFile); // if (!success) { // 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 << "'"); success = readSpeckFile(); if (!success) { return false; } // LINFO("Saving cache"); // success &= saveCachedFile(cachedFile); // } } if (_hasColorMapFile) { if (!_hasSpeckFile) success = true; success &= readColorMapFile(); } std::string labelFile = _labelFile; if (!labelFile.empty()) { // I disabled the cache as it didn't work on Mac --- abock // std::string cachedFile = FileSys.cacheManager()->cachedFilename( // labelFile, // ghoul::filesystem::CacheManager::Persistent::Yes // ); if (!_hasSpeckFile && !_hasColorMapFile) success = true; //bool hasCachedFile = FileSys.fileExists(cachedFile); //if (hasCachedFile) { // LINFO("Cached file '" << cachedFile << "' used for Label file '" << labelFile << "'"); // // success &= loadCachedFile(cachedFile); // if (!success) { // FileSys.cacheManager()->removeCacheFile(labelFile); // // Intentional fall-through to the 'else' computation to generate the cache // // file for the next run // } //} //else // { // LINFO("Cache for Label file '" << labelFile << "' not found"); LINFO("Loading Label file '" << labelFile << "'"); success &= readLabelFile(); if (!success) { return false; } // } } return success; } bool RenderableBillboardsCloud::readSpeckFile() { std::string _file = _speckFile; std::ifstream file(_file); if (!file.good()) { LERROR("Failed to open Speck file '" << _file << "'"); return false; } _nValuesPerAstronomicalObject = 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::streampos position = file.tellg(); std::getline(file, line); // Guard against wrong line endings (copying files from Windows to Mac) causes // lines to have a final \r if (!line.empty() && line.back() == '\r') { line = line.substr(0, line.length() -1); } if (line.empty() || line[0] == '#') { continue; } if (line.substr(0, 7) != "datavar" && line.substr(0, 10) != "texturevar" && line.substr(0, 7) != "texture" && line.substr(0, 10) != "polyorivar" && line.substr(0, 10) != "maxcomment") { // 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; // command str >> _nValuesPerAstronomicalObject; // variable index dummy.clear(); str >> dummy; // variable name _variableDataPositionMap.insert({ dummy, _nValuesPerAstronomicalObject }); _nValuesPerAstronomicalObject += 1; // We want the number, but the index is 0 based } } _nValuesPerAstronomicalObject += 3; // X Y Z are not counted in the Speck file indices do { std::vector values(_nValuesPerAstronomicalObject); std::getline(file, line); // Guard against wrong line endings (copying files from Windows to Mac) causes // lines to have a final \r if (!line.empty() && line.back() == '\r') { line = line.substr(0, line.length() -1); } if (line.empty()) { continue; } std::stringstream str(line); for (int i = 0; i < _nValuesPerAstronomicalObject; ++i) { str >> values[i]; } _fullData.insert(_fullData.end(), values.begin(), values.end()); } while (!file.eof()); return true; } bool RenderableBillboardsCloud::readColorMapFile() { std::string _file = _colorMapFile; std::ifstream file(_file); if (!file.good()) { LERROR("Failed to open Color Map file '" << _file << "'"); return false; } int numberOfColors = 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::streampos position = file.tellg(); std::getline(file, line); if (line[0] == '#' || line.empty()) { continue; } // Initial number of colors std::locale loc; if (std::isdigit(line[0], loc)) { std::string::size_type sz; numberOfColors = static_cast(std::stoi(line, &sz)); break; } else if (file.eof()) { return false; } } for (int i = 0; i < numberOfColors; ++i) { std::getline(file, line); std::stringstream str(line); glm::vec4 color; for (auto j = 0; j < 4; ++j) { str >> color[j]; } _colorMapData.push_back(color); } return true; } bool RenderableBillboardsCloud::readLabelFile() { std::string _file = _labelFile; std::ifstream file(_file); if (!file.good()) { LERROR("Failed to open Label file '" << _file << "'"); return false; } // 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::streampos position = file.tellg(); std::getline(file, line); // Guard against wrong line endings (copying files from Windows to Mac) causes // lines to have a final \r if (!line.empty() && line.back() == '\r') { line = line.substr(0, line.length() -1); } if (line.empty() || line[0] == '#') { continue; } if (line.substr(0, 9) != "textcolor") { // 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); continue; } if (line.substr(0, 9) == "textcolor") { // textcolor lines are structured as follows: // textcolor # description // where # is color text defined in configuration file std::stringstream str(line); // TODO: handle cases of labels with different colors break; } } do { std::vector values(_nValuesPerAstronomicalObject); std::getline(file, line); // Guard against wrong line endings (copying files from Windows to Mac) causes // lines to have a final \r if (!line.empty() && line.back() == '\r') { line = line.substr(0, line.length() -1); } if (line.empty()) { continue; } std::stringstream str(line); glm::vec3 position; for (auto j = 0; j < 3; ++j) { str >> position[j]; } std::string dummy; str >> dummy; // text keyword std::string label; str >> label; dummy.clear(); while (str >> dummy) { label += " " + dummy; dummy.clear(); } _labelData.push_back(std::make_pair(position, label)); } while (!file.eof()); return true; } bool RenderableBillboardsCloud::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(&_nValuesPerAstronomicalObject), sizeof(int32_t)); _fullData.resize(nValues); fileStream.read(reinterpret_cast(&_fullData[0]), nValues * sizeof(_fullData[0])); if (_hasColorMapFile) { int32_t nItems = 0; fileStream.read(reinterpret_cast(&nItems), sizeof(int32_t)); for (int i = 0; i < nItems; ++i) { int32_t keySize = 0; fileStream.read(reinterpret_cast(&keySize), sizeof(int32_t)); std::string key; for (int c = 0; c < keySize; ++c) { char t[2]; t[1] = '\0'; fileStream.read(reinterpret_cast(&t), sizeof(int32_t)); key.append(t); } int32_t value = 0; fileStream.read(reinterpret_cast(&value), sizeof(int32_t)); _variableDataPositionMap.insert({ key, value }); } } bool success = fileStream.good(); return success; } else { LERROR("Error opening file '" << file << "' for loading cache file"); return false; } } bool RenderableBillboardsCloud::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 nValuesPerAstronomicalObject = static_cast(_nValuesPerAstronomicalObject); fileStream.write(reinterpret_cast(&nValuesPerAstronomicalObject), sizeof(int32_t)); size_t nBytes = nValues * sizeof(_fullData[0]); fileStream.write(reinterpret_cast(&_fullData[0]), nBytes); if (_hasColorMapFile) { int32_t nItems = static_cast(_variableDataPositionMap.size()); fileStream.write(reinterpret_cast(&nItems), sizeof(int32_t)); for (auto pair : _variableDataPositionMap) { int32_t keySize = static_cast(pair.first.size()); fileStream.write(reinterpret_cast(&keySize), sizeof(int32_t)); for (int c = 0; c < static_cast(pair.first.size()); ++c) { int32_t keyChar = static_cast(pair.first[c]); fileStream.write(reinterpret_cast(&keyChar), sizeof(int32_t)); } int32_t value = static_cast(pair.second); fileStream.write(reinterpret_cast(&value), sizeof(int32_t)); } } bool success = fileStream.good(); return success; } else { LERROR("Error opening file '" << file << "' for save cache file"); return false; } } void RenderableBillboardsCloud::createDataSlice() { _slicedData.clear(); if (_hasColorMapFile) { _slicedData.reserve(8 * (_fullData.size() / _nValuesPerAstronomicalObject)); } else { _slicedData.reserve(4 * (_fullData.size()/_nValuesPerAstronomicalObject)); } // Generate the color bins for the colomap int colorMapInUse = 0; std::vector colorBins; if (_hasColorMapFile) { colorMapInUse = _variableDataPositionMap[_colorOptionString]; glm::vec2 currentColorRange = _colorRangeData[_colorOption.value()]; float colorMapBinSize = (currentColorRange.y - currentColorRange.x) / static_cast(_colorMapData.size()); float bin = colorMapBinSize; for (int i = 0; i < static_cast(_colorMapData.size()); ++i) { colorBins.push_back(bin); bin += colorMapBinSize; } } for (size_t i = 0; i < _fullData.size(); i += _nValuesPerAstronomicalObject) { glm::dvec4 transformedPos = glm::dvec4(_fullData[i + 0], _fullData[i + 1], _fullData[i + 2], 1.0); glm::vec4 position(glm::vec3(transformedPos), static_cast(_unit)); if (_hasColorMapFile) { for (int j = 0; j < 4; ++j) { _slicedData.push_back(position[j]); } // Finds from which bin to get the color. // Note: the first color in the colormap file // is the outliers color. glm::vec4 itemColor; float variableColor = _fullData[i + 3 + colorMapInUse]; int c = static_cast(colorBins.size() - 1); // Float vs int comparison? while (variableColor < colorBins[c]) { --c; if (c == 0) break; } int colorIndex = 0; if (c != static_cast(colorBins.size() - 1)) { colorIndex = c + 1; } for (auto j = 0; j < 4; ++j) { _slicedData.push_back(_colorMapData[colorIndex][j]); } } else { for (auto j = 0; j < 4; ++j) { _slicedData.push_back(position[j]); } } } } void RenderableBillboardsCloud::createPolygonTexture() { LDEBUG("Creating Polygon Texture"); glGenTextures(1, &_pTexture); glBindTexture(GL_TEXTURE_2D, _pTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Stopped using a buffer object for GL_PIXEL_UNPACK_BUFFER glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_BYTE, nullptr); renderToTexture(std::bind(&openspace::RenderableBillboardsCloud::loadPolygonGeometryForRendering, this), std::bind(&openspace::RenderableBillboardsCloud::renderPolygonGeometry, this, std::placeholders::_1), _pTexture, 256, 256); } void RenderableBillboardsCloud::renderToTexture( std::function geometryLoadingFunction, std::function renderFunction, GLuint textureToRenderTo, GLuint textureWidth, GLuint textureHeight) { LDEBUG("Rendering to Texture"); // Saves initial Application's OpenGL State GLint defaultFBO; GLint viewport[4]; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); glGetIntegerv(GL_VIEWPORT, viewport); GLuint textureFBO; glGenFramebuffers(1, &textureFBO); glBindFramebuffer(GL_FRAMEBUFFER, textureFBO); GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 }; glDrawBuffers(1, drawBuffers); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureToRenderTo, 0); glViewport(0, 0, textureWidth, textureHeight); geometryLoadingFunction(); renderFunction(_polygonVao); // Restores Applications' OpenGL State glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); if (_polygonVbo) { glDeleteBuffers(1, &_polygonVbo); } if (_polygonVao) { glDeleteVertexArrays(1, &_polygonVao); } glDeleteFramebuffers(1, &textureFBO); } void RenderableBillboardsCloud::loadPolygonGeometryForRendering() { glGenVertexArrays(1, &_polygonVao); glGenBuffers(1, &_polygonVbo); glBindVertexArray(_polygonVao); glBindBuffer(GL_ARRAY_BUFFER, _polygonVbo); const GLfloat vertex_data[] = { // x y z w 0.0f, 0.0f, 0.0f, 1.0f, }; glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, reinterpret_cast(0)); glEnableVertexAttribArray(0); glBindVertexArray(0); } void RenderableBillboardsCloud::renderPolygonGeometry(GLuint vao) { std::unique_ptr program = ghoul::opengl::ProgramObject::Build("RenderableBillboardsCloud_Polygon", "${MODULE_DIGITALUNIVERSE}/shaders/billboardpolygon_vs.glsl", "${MODULE_DIGITALUNIVERSE}/shaders/billboardpolygon_fs.glsl", "${MODULE_DIGITALUNIVERSE}/shaders/billboardpolygon_gs.glsl"); program->activate(); static const float black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; glClearBufferfv(GL_COLOR, 0, black); program->setUniform("sides", _polygonSides); program->setUniform("polygonColor", _pointColor); glBindVertexArray(vao); glDrawArrays(GL_POINTS, 0, 1); glBindVertexArray(0); program->deactivate(); } } // namespace openspace