/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2018 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "DataCygnet"; constexpr const int MaxTextures = 6; constexpr openspace::properties::Property::PropertyInfo DataOptionsInfo = { "DataOptions", "Data Options", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo UseLogInfo = { "UseLog", "Use Logarithm", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo UseHistogramInfo = { "UseHistogram", "Auto Contrast", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo AutoFilterInfo = { "AutoFilter", "Auto Filter", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo NormalizeValuesInfo = { "NormValues", "Normalize Values", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo BackgroundInfo = { "BackgroundValues", "Background Values", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo TransferFunctionsFile = { "Transferfunctions", "Transfer Functions", "" // @TODO Missing documentation }; } // namespace namespace openspace { DataCygnet::DataCygnet(const ghoul::Dictionary& dictionary) : IswaCygnet(dictionary) , _dataOptions(DataOptionsInfo) , _transferFunctionsFile(TransferFunctionsFile, "${SCENE}/iswa/tfs/default.tf") , _backgroundValues(BackgroundInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(1.f)) , _normValues(NormalizeValuesInfo, glm::vec2(1.f), glm::vec2(0.f), glm::vec2(5.f)) , _useLog(UseLogInfo, false) , _useHistogram(UseHistogramInfo, false) , _autoFilter(AutoFilterInfo, true) { addProperty(_dataOptions); addProperty(_useLog); addProperty(_useHistogram); addProperty(_autoFilter); addProperty(_normValues); addProperty(_backgroundValues); addProperty(_transferFunctionsFile); registerProperties(); } DataCygnet::~DataCygnet() {} bool DataCygnet::updateTexture() { const std::vector& data = textureData(); if (data.empty()) { return false; } bool texturesReady = false; const std::vector& selectedOptions = _dataOptions.value(); for (int option : selectedOptions) { float* values = data[option]; if (!values) { continue; } if (!_textures[option]) { using namespace ghoul::opengl; std::unique_ptr texture = std::make_unique( values, _textureDimensions, ghoul::opengl::Texture::Format::Red, GL_RED, GL_FLOAT, ghoul::opengl::Texture::FilterMode::Linear, ghoul::opengl::Texture::WrappingMode::ClampToEdge ); if (texture) { texture->uploadTexture(); texture->setFilter(Texture::FilterMode::LinearMipMap); _textures[option] = std::move(texture); } } else { _textures[option]->setPixelData(values); _textures[option]->uploadTexture(); } texturesReady = true; } return texturesReady; } bool DataCygnet::downloadTextureResource(double timestamp) { if (_futureObject.valid()) { return false; } std::future future = IswaManager::ref().fetchDataCygnet( _data.id, timestamp ); if (future.valid()) { _futureObject = std::move(future); return true; } return false; } bool DataCygnet::updateTextureResource() { DownloadManager::MemoryFile dataFile = _futureObject.get(); if (dataFile.corrupted) { return false; } _dataBuffer = std::string(dataFile.buffer, dataFile.size); delete[] dataFile.buffer; return true; } bool DataCygnet::readyToRender() const { return (!_textures.empty() && !_transferFunctions.empty()); } /** * Set both transfer function textures and data textures in same function so that they * bind to the right texture units. If separate in to two functions a list of * ghoul::TextureUnit needs to be passed as an argument to both. */ void DataCygnet::setTextureUniforms() { const std::vector& selectedOptions = _dataOptions.value(); int activeTextures = std::min(static_cast(selectedOptions.size()), MaxTextures); int activeTransferfunctions = std::min( static_cast(_transferFunctions.size()), MaxTextures ); // Set Textures ghoul::opengl::TextureUnit txUnits[MaxTextures]; int j = 0; for (int option : selectedOptions) { if (_textures[option]) { txUnits[j].activate(); _textures[option]->bind(); _shader->setUniform("textures[" + std::to_string(j) + "]", txUnits[j]); j++; if (j >= MaxTextures) { break; } } } if (activeTextures > 0 && selectedOptions.back() >= static_cast(_transferFunctions.size())) { activeTransferfunctions = 1; } // This array + txUnits will use up 12 Texture Units, which is alot ghoul::opengl::TextureUnit tfUnits[MaxTextures]; j = 0; if (activeTransferfunctions == 1) { tfUnits[0].activate(); _transferFunctions[0].bind(); _shader->setUniform("transferFunctions[0]", tfUnits[0]); } else { for (int option : selectedOptions) { if (static_cast(_transferFunctions.size()) >= option) { tfUnits[j].activate(); _transferFunctions[option].bind(); _shader->setUniform( "transferFunctions[" + std::to_string(j) + "]", tfUnits[j] ); j++; if (j >= MaxTextures) { break; } } } } _shader->setUniform("numTransferFunctions", activeTransferfunctions); _shader->setUniform("numTextures", activeTextures); } void DataCygnet::readTransferFunctions(std::string tfPath) { std::ifstream tfFile(absPath(std::move(tfPath))); std::vector tfs; if (tfFile.is_open()) { std::string line; while (getline(tfFile, line)) { tfs.emplace_back(absPath(line)); } tfFile.close(); } if (!tfs.empty()) { _transferFunctions = std::move(tfs); } } void DataCygnet::fillOptions(const std::string& source) { std::vector options = _dataProcessor->readMetadata( source, _textureDimensions ); for (int i = 0; i < static_cast(options.size()); i++) { _dataOptions.addOption({ i, options[i] }); _textures.push_back(nullptr); } if (_group) { IswaDataGroup* g = dynamic_cast(_group); g->registerOptions(_dataOptions.options()); _dataOptions.setValue(g->dataOptionsValue()); } else { _dataOptions.setValue(std::vector(1, 0)); } } void DataCygnet::setPropertyCallbacks() { _normValues.onChange([this]() { _dataProcessor->normValues(_normValues); updateTexture(); }); _useLog.onChange([this]() { _dataProcessor->useLog(_useLog); updateTexture(); }); _useHistogram.onChange([this]() { _dataProcessor->useHistogram(_useHistogram); updateTexture(); if (_autoFilter) { _backgroundValues = _dataProcessor->filterValues(); } }); _dataOptions.onChange([this]() { if (_dataOptions.value().size() > MaxTextures) { LWARNING("Too many options chosen, max is " + std::to_string(MaxTextures)); } updateTexture(); }); _transferFunctionsFile.onChange([this]() { readTransferFunctions(_transferFunctionsFile); }); } void DataCygnet::subscribeToGroup() { ghoul::Event& groupEvent = _group->groupEvent(); groupEvent.subscribe( identifier(), "dataOptionsChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event dataOptionsChanged"); if (dict.hasKeyAndValue>("dataOptions")) { _dataOptions = dict.value>("dataOptions"); } } ); groupEvent.subscribe( identifier(), "normValuesChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event normValuesChanged"); if (dict.hasKeyAndValue("normValues")) { _normValues = dict.value("normValues"); } } ); groupEvent.subscribe( identifier(), "backgroundValuesChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event backgroundValuesChanged"); if (dict.hasKeyAndValue("backgroundValues")) { _backgroundValues = dict.value("backgroundValues"); } } ); groupEvent.subscribe( identifier(), "transferFunctionsChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event transferFunctionsChanged"); _transferFunctionsFile = dict.value("transferFunctions"); } ); groupEvent.subscribe( identifier(), "useLogChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event useLogChanged"); _useLog = dict.value("useLog"); } ); groupEvent.subscribe( identifier(), "useHistogramChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event useHistogramChanged"); _useHistogram = dict.value("useHistogram"); } ); groupEvent.subscribe( identifier(), "autoFilterChanged", [&](const ghoul::Dictionary& dict) { LDEBUG(identifier() + " Event autoFilterChanged"); _autoFilter = dict.value("autoFilter"); } ); groupEvent.subscribe( identifier(), "updateGroup", [&](const ghoul::Dictionary&) { LDEBUG(identifier() + " Event updateGroup"); if (_autoFilter) { _backgroundValues = _dataProcessor->filterValues(); } updateTexture(); } ); } } //namespace openspace