diff --git a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp index 58e3a86a99..cbd3b4bba7 100644 --- a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp +++ b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp @@ -241,1167 +241,1177 @@ namespace { } // namespace namespace openspace { - using namespace properties; +using namespace properties; - RenderableFieldlinesSequence::RenderableFieldlinesSequence( - const ghoul::Dictionary& dictionary) - : Renderable(dictionary) - , _pColorGroup({ "Color" }) - , _pColorMethod(ColorMethodInfo, OptionProperty::DisplayType::Radio) - , _pColorQuantity(ColorQuantityInfo, OptionProperty::DisplayType::Dropdown) - , _pColorQuantityMin(ColorQuantityMinInfo) - , _pColorQuantityMax(ColorQuantityMaxInfo) - , _pColorTablePath(ColorTablePathInfo) - , _pColorUniform( - ColorUniformInfo, - glm::vec4(0.75f, 0.5f, 0.0f, 0.5f), - glm::vec4(0.f), - glm::vec4(1.f) - ) - , _pColorABlendEnabled(ColorUseABlendingInfo, true) - , _pDomainEnabled(DomainEnabledInfo, true) - , _pDomainGroup({ "Domain" }) - , _pDomainX(DomainXInfo) - , _pDomainY(DomainYInfo) - , _pDomainZ(DomainZInfo) - , _pDomainR(DomainRInfo) - , _pFlowColor( - FlowColorInfo, - glm::vec4(0.8f, 0.7f, 0.0f, 0.6f), - glm::vec4(0.f), - glm::vec4(1.f) - ) - , _pFlowEnabled(FlowEnabledInfo, true) - , _pFlowGroup({ "Flow" }) - , _pFlowParticleSize(FlowParticleSizeInfo, 5, 0, 500) - , _pFlowParticleSpacing(FlowParticleSpacingInfo, 60, 0, 500) - , _pFlowReversed(FlowReversedInfo, false) - , _pFlowSpeed(FlowSpeedInfo, 20, 0, 1000) - , _pMaskingEnabled(MaskingEnabledInfo, false) - , _pMaskingGroup({ "Masking" }) - , _pMaskingMin(MaskingMinInfo) - , _pMaskingMax(MaskingMaxInfo) - , _pMaskingQuantity(MaskingQuantityInfo, OptionProperty::DisplayType::Dropdown) - , _pFocusOnOriginBtn(OriginButtonInfo) - , _pJumpToStartBtn(TimeJumpButtonInfo) - { - _dictionary = std::make_unique(dictionary); +RenderableFieldlinesSequence::RenderableFieldlinesSequence( + const ghoul::Dictionary& dictionary) + : Renderable(dictionary) + , _pColorGroup({ "Color" }) + , _pColorMethod(ColorMethodInfo, OptionProperty::DisplayType::Radio) + , _pColorQuantity(ColorQuantityInfo, OptionProperty::DisplayType::Dropdown) + , _pColorQuantityMin(ColorQuantityMinInfo) + , _pColorQuantityMax(ColorQuantityMaxInfo) + , _pColorTablePath(ColorTablePathInfo) + , _pColorUniform( + ColorUniformInfo, + glm::vec4(0.75f, 0.5f, 0.0f, 0.5f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _pColorABlendEnabled(ColorUseABlendingInfo, true) + , _pDomainEnabled(DomainEnabledInfo, true) + , _pDomainGroup({ "Domain" }) + , _pDomainX(DomainXInfo) + , _pDomainY(DomainYInfo) + , _pDomainZ(DomainZInfo) + , _pDomainR(DomainRInfo) + , _pFlowColor( + FlowColorInfo, + glm::vec4(0.8f, 0.7f, 0.0f, 0.6f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _pFlowEnabled(FlowEnabledInfo, true) + , _pFlowGroup({ "Flow" }) + , _pFlowParticleSize(FlowParticleSizeInfo, 5, 0, 500) + , _pFlowParticleSpacing(FlowParticleSpacingInfo, 60, 0, 500) + , _pFlowReversed(FlowReversedInfo, false) + , _pFlowSpeed(FlowSpeedInfo, 20, 0, 1000) + , _pMaskingEnabled(MaskingEnabledInfo, false) + , _pMaskingGroup({ "Masking" }) + , _pMaskingMin(MaskingMinInfo) + , _pMaskingMax(MaskingMaxInfo) + , _pMaskingQuantity(MaskingQuantityInfo, OptionProperty::DisplayType::Dropdown) + , _pFocusOnOriginBtn(OriginButtonInfo) + , _pJumpToStartBtn(TimeJumpButtonInfo) +{ + _dictionary = std::make_unique(dictionary); +} + +void RenderableFieldlinesSequence::initializeGL() { + // EXTRACT MANDATORY INFORMATION FROM DICTIONARY + SourceFileType sourceFileType = SourceFileType::Invalid; + if (!extractMandatoryInfoFromDictionary(sourceFileType)) { + //wait for a fieldline + return; } - void RenderableFieldlinesSequence::initializeGL() { - // EXTRACT MANDATORY INFORMATION FROM DICTIONARY - SourceFileType sourceFileType = SourceFileType::Invalid; - if (!extractMandatoryInfoFromDictionary(sourceFileType)) { - //wait for a fieldline + // Set a default color table, just in case the (optional) user defined paths are + // corrupt or not provided! + _colorTablePaths.push_back(FieldlinesSequenceModule::DefaultTransferFunctionFile); + _transferFunction = std::make_unique(absPath(_colorTablePaths[0])); + + // EXTRACT OPTIONAL INFORMATION FROM DICTIONARY + std::string outputFolderPath; + extractOptionalInfoFromDictionary(outputFolderPath); + + // EXTRACT SOURCE FILE TYPE SPECIFIC INFOMRATION FROM DICTIONARY & GET STATES FROM + // SOURCE + switch (sourceFileType) { + case SourceFileType::Cdf: + if (!getStatesFromCdfFiles(outputFolderPath)) { return; } - - // Set a default color table, just in case the (optional) user defined paths are - // corrupt or not provided! - _colorTablePaths.push_back(FieldlinesSequenceModule::DefaultTransferFunctionFile); - _transferFunction = std::make_unique(absPath(_colorTablePaths[0])); - - // EXTRACT OPTIONAL INFORMATION FROM DICTIONARY - std::string outputFolderPath; - extractOptionalInfoFromDictionary(outputFolderPath); - - // EXTRACT SOURCE FILE TYPE SPECIFIC INFOMRATION FROM DICTIONARY & GET STATES FROM - // SOURCE - switch (sourceFileType) { - case SourceFileType::Cdf: - if (!getStatesFromCdfFiles(outputFolderPath)) { + break; + case SourceFileType::Json: + if (!loadJsonStatesIntoRAM(outputFolderPath)) { + return; + } + break; + case SourceFileType::Osfls: + extractOsflsInfoFromDictionary(); + if (_loadingStatesDynamically) { + if (!prepareForOsflsStreaming()) { return; } - break; - case SourceFileType::Json: - if (!loadJsonStatesIntoRAM(outputFolderPath)) { - return; + if (_dynamicWebContent) { + initializeWebManager(); } - break; - case SourceFileType::Osfls: - extractOsflsInfoFromDictionary(); - if (_loadingStatesDynamically) { - if (!prepareForOsflsStreaming()) { - return; - } - if (_dynamicWebContent) { - initializeWebManager(); - } - } - else { - loadOsflsStatesIntoRAM(outputFolderPath); - } - break; - default: - return; } - - - - // No need to store source paths in memory if they are already in RAM! - if (!_loadingStatesDynamically) { - _sourceFiles.clear(); + else { + loadOsflsStatesIntoRAM(outputFolderPath); } + break; + default: + return; + } - // At this point there should be at least one state loaded into memory! - if (_states.empty()) { - LERROR("Wasn't able to extract any valid states from provided source files!"); - return; + + + // No need to store source paths in memory if they are already in RAM! + if (!_loadingStatesDynamically) { + _sourceFiles.clear(); + } + + // At this point there should be at least one state loaded into memory! + if (_states.empty()) { + LERROR("Wasn't able to extract any valid states from provided source files!"); + return; + } + + computeSequenceEndTime(); + setModelDependentConstants(); + + setupProperties(); + + extractPropertyInfoFromDictionary(); + + // dictionary is no longer needed as everything is extracted + _dictionary.reset(); + + // Setup shader program + _shaderProgram = global::renderEngine.buildRenderProgram( + "FieldlinesSequence", + absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_vs.glsl"), + absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_fs.glsl") + ); + + //------------------ Initialize OpenGL VBOs and VAOs-------------------------------// + glGenVertexArrays(1, &_vertexArrayObject); + glGenBuffers(1, &_vertexPositionBuffer); + glGenBuffers(1, &_vertexColorBuffer); + glGenBuffers(1, &_vertexMaskingBuffer); + + // Needed for additive blending + setRenderBin(Renderable::RenderBin::Overlay); + +} + +/** + * Extracts the general information (from the lua modfile) that is mandatory for the class + * to function; such as the file type and the location of the source files. + * Returns false if it fails to extract mandatory information! + */ +bool RenderableFieldlinesSequence::extractMandatoryInfoFromDictionary( + SourceFileType& sourceFileType) +{ + + _dictionary->getValue(SceneGraphNode::KeyIdentifier, _identifier); + + // ------------------- EXTRACT MANDATORY VALUES FROM DICTIONARY ------------------- // + std::string inputFileTypeString; + if (!_dictionary->getValue(KeyInputFileType, inputFileTypeString)) { + LERROR(fmt::format("{}: The field {} is missing", _identifier, KeyInputFileType)); + } + else { + std::transform( + inputFileTypeString.begin(), + inputFileTypeString.end(), + inputFileTypeString.begin(), + [](char c) { return static_cast(tolower(c)); } + ); + // Verify that the input type is correct + if (inputFileTypeString == ValueInputFileTypeCdf) { + sourceFileType = SourceFileType::Cdf; } + else if (inputFileTypeString == ValueInputFileTypeJson) { + sourceFileType = SourceFileType::Json; + } + else if (inputFileTypeString == ValueInputFileTypeOsfls) { + sourceFileType = SourceFileType::Osfls; + } + else { + LERROR(fmt::format( + "{}: {} is not a recognized {}", + _identifier, inputFileTypeString, KeyInputFileType + )); + sourceFileType = SourceFileType::Invalid; + return false; + } + } - computeSequenceEndTime(); - setModelDependentConstants(); + std::string sourceFolderPath; - setupProperties(); + // Try to get dynamicwebcontent setting, if not try to get sourcefolder setting + if (!_dictionary->getValue(KeyDynamicWebContent, _dynWebContentUrl)) { + if (!_dictionary->getValue(KeySourceFolder, sourceFolderPath)) { + LERROR(fmt::format( + "{}: The field {} or {} is missing.", + _identifier, KeySourceFolder, KeyDynamicWebContent + )); + return false; + } + } + else { + if (!_dynWebContentUrl.empty()) { + _dynamicWebContent = true; + LINFO("Initializing sync-directory and downloading a startset"); + sourceFolderPath = _webFieldlinesManager.initializeSyncDirectory(_identifier); + _webFieldlinesManager.preDownload(_dynWebContentUrl); + } + else { + LERROR(fmt::format( + "{}: The field {} is missing a url", _identifier, KeyDynamicWebContent + )); + return false; + } + } - extractPropertyInfoFromDictionary(); - - // dictionary is no longer needed as everything is extracted - _dictionary.reset(); - - // Setup shader program - _shaderProgram = global::renderEngine.buildRenderProgram( - "FieldlinesSequence", - absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_vs.glsl"), - absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_fs.glsl") + // Ensure that the source folder exists and then extract + // the files with the same extension as + ghoul::filesystem::Directory sourceFolder(sourceFolderPath); + if (FileSys.directoryExists(sourceFolder)) { + // Extract all file paths from the provided folder + _sourceFiles = sourceFolder.readFiles( + ghoul::filesystem::Directory::Recursive::No, + ghoul::filesystem::Directory::Sort::Yes ); - //------------------ Initialize OpenGL VBOs and VAOs-------------------------------// - glGenVertexArrays(1, &_vertexArrayObject); - glGenBuffers(1, &_vertexPositionBuffer); - glGenBuffers(1, &_vertexColorBuffer); - glGenBuffers(1, &_vertexMaskingBuffer); - - // Needed for additive blending - setRenderBin(Renderable::RenderBin::Overlay); - - } - - /** - * Extracts the general information (from the lua modfile) that is mandatory for the class - * to function; such as the file type and the location of the source files. - * Returns false if it fails to extract mandatory information! - */ - bool RenderableFieldlinesSequence::extractMandatoryInfoFromDictionary( - SourceFileType& sourceFileType) - { - - _dictionary->getValue(SceneGraphNode::KeyIdentifier, _identifier); - - // ------------------- EXTRACT MANDATORY VALUES FROM DICTIONARY ------------------- // - std::string inputFileTypeString; - if (!_dictionary->getValue(KeyInputFileType, inputFileTypeString)) { - LERROR(fmt::format("{}: The field {} is missing", _identifier, KeyInputFileType)); - } - else { + // Remove all files that don't have as extension + _sourceFiles.erase( + std::remove_if( + _sourceFiles.begin(), + _sourceFiles.end(), + [inputFileTypeString](const std::string& str) { + const size_t extLength = inputFileTypeString.length(); + std::string sub = str.substr(str.length() - extLength, extLength); std::transform( - inputFileTypeString.begin(), - inputFileTypeString.end(), - inputFileTypeString.begin(), - [](char c) { return static_cast(tolower(c)); } - ); - // Verify that the input type is correct - if (inputFileTypeString == ValueInputFileTypeCdf) { - sourceFileType = SourceFileType::Cdf; - } - else if (inputFileTypeString == ValueInputFileTypeJson) { - sourceFileType = SourceFileType::Json; - } - else if (inputFileTypeString == ValueInputFileTypeOsfls) { - sourceFileType = SourceFileType::Osfls; - } - else { - LERROR(fmt::format( - "{}: {} is not a recognized {}", - _identifier, inputFileTypeString, KeyInputFileType - )); - sourceFileType = SourceFileType::Invalid; - return false; - } - } - - std::string sourceFolderPath; - - // Try to get dynamicwebcontent setting, if not try to get sourcefolder setting - if (!_dictionary->getValue(KeyDynamicWebContent, _dynWebContentUrl)) { - if (!_dictionary->getValue(KeySourceFolder, sourceFolderPath)) { - LERROR(fmt::format("{}: The field {} or {} is missing.", _identifier, KeySourceFolder, KeyDynamicWebContent)); - return false; - } - } - else { - if (!_dynWebContentUrl.empty()) { - _dynamicWebContent = true; - LINFO("Initializing sync-directory and downloading a startset"); - sourceFolderPath = _webFieldlinesManager.initializeSyncDirectory(_identifier); - _webFieldlinesManager.preDownload(_dynWebContentUrl); - } - else { - LERROR(fmt::format("{}: The field {} is missing a url", _identifier, KeyDynamicWebContent)); - return false; - } - } - - // Ensure that the source folder exists and then extract - // the files with the same extension as - ghoul::filesystem::Directory sourceFolder(sourceFolderPath); - if (FileSys.directoryExists(sourceFolder)) { - // Extract all file paths from the provided folder - _sourceFiles = sourceFolder.readFiles( - ghoul::filesystem::Directory::Recursive::No, - ghoul::filesystem::Directory::Sort::Yes - ); - - // Remove all files that don't have as extension - _sourceFiles.erase( - std::remove_if( - _sourceFiles.begin(), - _sourceFiles.end(), - [inputFileTypeString](const std::string& str) { - const size_t extLength = inputFileTypeString.length(); - std::string sub = str.substr(str.length() - extLength, extLength); - std::transform( - sub.begin(), - sub.end(), - sub.begin(), - [](char c) { return static_cast(::tolower(c)); } - ); - return sub != inputFileTypeString; - }), - _sourceFiles.end() - ); - - // Ensure that there are available and valid source files left - if (_sourceFiles.empty()) { - LERROR(fmt::format( - "{}: {} contains no {} files", - _identifier, sourceFolderPath, inputFileTypeString - )); - return false; - } - } - else { - LERROR(fmt::format( - "{}: FieldlinesSequence {} is not a valid directory", - _identifier, - sourceFolderPath - )); - return false; - } - - return true; - } - - void RenderableFieldlinesSequence::extractOptionalInfoFromDictionary( - std::string& outputFolderPath) - { - - // ------------------- EXTRACT OPTIONAL VALUES FROM DICTIONARY ------------------- // - if (_dictionary->getValue(KeyOutputFolder, outputFolderPath)) { - ghoul::filesystem::Directory outputFolder(outputFolderPath); - if (FileSys.directoryExists(outputFolder)) { - outputFolderPath = absPath(outputFolderPath); - } - else { - LERROR(fmt::format( - "{}: The specified output path: '{}', does not exist", - _identifier, outputFolderPath - )); - outputFolderPath = ""; - } - } - - ghoul::Dictionary colorTablesPathsDictionary; - if (_dictionary->getValue(KeyColorTablePaths, colorTablesPathsDictionary)) { - const size_t nProvidedPaths = colorTablesPathsDictionary.size(); - if (nProvidedPaths > 0) { - // Clear the default! It is already specified in the transferFunction - _colorTablePaths.clear(); - for (size_t i = 1; i <= nProvidedPaths; ++i) { - _colorTablePaths.push_back( - colorTablesPathsDictionary.value(std::to_string(i))); - } - } - } - - ghoul::Dictionary colorTablesRangesDictionary; - if (_dictionary->getValue(KeyColorTableRanges, colorTablesRangesDictionary)) { - const size_t nProvidedRanges = colorTablesRangesDictionary.size(); - for (size_t i = 1; i <= nProvidedRanges; ++i) { - _colorTableRanges.push_back( - colorTablesRangesDictionary.value(std::to_string(i))); - } - } - else { - _colorTableRanges.push_back(glm::vec2(0, 1)); - } - - ghoul::Dictionary maskingRangesDictionary; - if (_dictionary->getValue(KeyMaskingRanges, maskingRangesDictionary)) { - const size_t nProvidedRanges = maskingRangesDictionary.size(); - for (size_t i = 1; i <= nProvidedRanges; ++i) { - _maskingRanges.push_back( - maskingRangesDictionary.value(std::to_string(i))); - } - } - else { - _maskingRanges.push_back(glm::vec2(-100000, 100000)); // Just some default values! - } - - } - // The reason this exists is because some values are read from the properties in the GUI. - // This function may change the default values of those properties, if the user has specified them - // in the asset file - void RenderableFieldlinesSequence::extractPropertyInfoFromDictionary() { - // Specified weather to use uniform coloring or by specified quantity. - std::string coloringMethodDictionary; - if (_dictionary->getValue(KeyColoringMethod, coloringMethodDictionary)) { - if (coloringMethodDictionary == "Quantity") - _pColorMethod = static_cast(ColorMethod::ByQuantity); - } - } - - /** - * Returns false if it fails to extract mandatory information! - */ - bool RenderableFieldlinesSequence::extractJsonInfoFromDictionary(fls::Model& model) { - std::string modelStr; - if (_dictionary->getValue(KeyJsonSimulationModel, modelStr)) { - std::transform( - modelStr.begin(), - modelStr.end(), - modelStr.begin(), + sub.begin(), + sub.end(), + sub.begin(), [](char c) { return static_cast(::tolower(c)); } ); - model = fls::stringToModel(modelStr); + return sub != inputFileTypeString; + }), + _sourceFiles.end() + ); + + // Ensure that there are available and valid source files left + if (_sourceFiles.empty()) { + LERROR(fmt::format( + "{}: {} contains no {} files", + _identifier, sourceFolderPath, inputFileTypeString + )); + return false; + } + } + else { + LERROR(fmt::format( + "{}: FieldlinesSequence {} is not a valid directory", + _identifier, + sourceFolderPath + )); + return false; + } + + return true; +} + +void RenderableFieldlinesSequence::extractOptionalInfoFromDictionary( + std::string& outputFolderPath) +{ + + // ------------------- EXTRACT OPTIONAL VALUES FROM DICTIONARY ------------------- // + if (_dictionary->getValue(KeyOutputFolder, outputFolderPath)) { + ghoul::filesystem::Directory outputFolder(outputFolderPath); + if (FileSys.directoryExists(outputFolder)) { + outputFolderPath = absPath(outputFolderPath); } else { LERROR(fmt::format( - "{}: Must specify '{}'", _identifier, KeyJsonSimulationModel + "{}: The specified output path: '{}', does not exist", + _identifier, outputFolderPath )); - return false; + outputFolderPath = ""; } - - float scaleFactor; - if (_dictionary->getValue(KeyJsonScalingFactor, scaleFactor)) { - _scalingFactor = scaleFactor; - } - else { - LWARNING(fmt::format( - "{}: Does not provide scalingFactor. Assumes coordinates are in meters", - _identifier - )); - } - return true; } - bool RenderableFieldlinesSequence::loadJsonStatesIntoRAM(const std::string& outputFolder) - { - fls::Model model; - if (!extractJsonInfoFromDictionary(model)) { - return false; - } - // Load states into RAM! - for (const std::string& filePath : _sourceFiles) { - FieldlinesState newState; - const bool loadedSuccessfully = newState.loadStateFromJson( - filePath, - model, - _scalingFactor - ); - if (loadedSuccessfully) { - addStateToSequence(newState); - if (!outputFolder.empty()) { - newState.saveStateToOsfls(outputFolder); - } + ghoul::Dictionary colorTablesPathsDictionary; + if (_dictionary->getValue(KeyColorTablePaths, colorTablesPathsDictionary)) { + const size_t nProvidedPaths = colorTablesPathsDictionary.size(); + if (nProvidedPaths > 0) { + // Clear the default! It is already specified in the transferFunction + _colorTablePaths.clear(); + for (size_t i = 1; i <= nProvidedPaths; ++i) { + _colorTablePaths.push_back( + colorTablesPathsDictionary.value(std::to_string(i))); } } - return true; } - // streaming from means streaming from disk - bool RenderableFieldlinesSequence::prepareForOsflsStreaming() { - extractTriggerTimesFromFileNames(); + ghoul::Dictionary colorTablesRangesDictionary; + if (_dictionary->getValue(KeyColorTableRanges, colorTablesRangesDictionary)) { + const size_t nProvidedRanges = colorTablesRangesDictionary.size(); + for (size_t i = 1; i <= nProvidedRanges; ++i) { + _colorTableRanges.push_back( + colorTablesRangesDictionary.value(std::to_string(i))); + } + } + else { + _colorTableRanges.push_back(glm::vec2(0, 1)); + } + + ghoul::Dictionary maskingRangesDictionary; + if (_dictionary->getValue(KeyMaskingRanges, maskingRangesDictionary)) { + const size_t nProvidedRanges = maskingRangesDictionary.size(); + for (size_t i = 1; i <= nProvidedRanges; ++i) { + _maskingRanges.push_back( + maskingRangesDictionary.value(std::to_string(i))); + } + } + else { + _maskingRanges.push_back(glm::vec2(-100000, 100000)); // Just some default values! + } + +} +// The reason this exists is because some values are read from the properties in the GUI. +// This function may change the default values of those properties, +// if the user has specified them in the asset file +void RenderableFieldlinesSequence::extractPropertyInfoFromDictionary() { + // Specified weather to use uniform coloring or by specified quantity. + std::string coloringMethodDictionary; + if (_dictionary->getValue(KeyColoringMethod, coloringMethodDictionary)) { + if (coloringMethodDictionary == "Quantity") + _pColorMethod = static_cast(ColorMethod::ByQuantity); + } +} + +/** + * Returns false if it fails to extract mandatory information! + */ +bool RenderableFieldlinesSequence::extractJsonInfoFromDictionary(fls::Model& model) { + std::string modelStr; + if (_dictionary->getValue(KeyJsonSimulationModel, modelStr)) { + std::transform( + modelStr.begin(), + modelStr.end(), + modelStr.begin(), + [](char c) { return static_cast(::tolower(c)); } + ); + model = fls::stringToModel(modelStr); + } + else { + LERROR(fmt::format( + "{}: Must specify '{}'", _identifier, KeyJsonSimulationModel + )); + return false; + } + + float scaleFactor; + if (_dictionary->getValue(KeyJsonScalingFactor, scaleFactor)) { + _scalingFactor = scaleFactor; + } + else { + LWARNING(fmt::format( + "{}: Does not provide scalingFactor. Assumes coordinates are in meters", + _identifier + )); + } + return true; +} + +bool RenderableFieldlinesSequence::loadJsonStatesIntoRAM(const std::string& outputFolder) +{ + fls::Model model; + if (!extractJsonInfoFromDictionary(model)) { + return false; + } + // Load states into RAM! + for (const std::string& filePath : _sourceFiles) { FieldlinesState newState; - if (!newState.loadStateFromOsfls(_sourceFiles[0])) { - LERROR("The provided .osfls files seem to be corrupt: " + _sourceFiles[0]); - return false; - } - _states.push_back(newState); - _nStates = _startTimes.size(); - _activeStateIndex = 0; - return true; - } - - void RenderableFieldlinesSequence::loadOsflsStatesIntoRAM(const std::string& outputFolder) - { - // Load states from .osfls files into RAM! - for (const std::string& filePath : _sourceFiles) { - //LERROR("Loading file into RAM: " + filePath); - FieldlinesState newState; - if (newState.loadStateFromOsfls(filePath)) { - addStateToSequence(newState); - if (!outputFolder.empty()) { - ghoul::filesystem::File tmpFile(filePath); - newState.saveStateToJson(outputFolder + tmpFile.baseName()); - } - } - else { - LWARNING(fmt::format("Failed to load state from: {}", filePath)); + const bool loadedSuccessfully = newState.loadStateFromJson( + filePath, + model, + _scalingFactor + ); + if (loadedSuccessfully) { + addStateToSequence(newState); + if (!outputFolder.empty()) { + newState.saveStateToOsfls(outputFolder); } } } + return true; +} - void RenderableFieldlinesSequence::extractOsflsInfoFromDictionary() { - bool shouldLoadInRealtime = false; - if (_dictionary->getValue(KeyOslfsLoadAtRuntime, shouldLoadInRealtime)) { - _loadingStatesDynamically = shouldLoadInRealtime; +// Streaming from means streaming from disk +bool RenderableFieldlinesSequence::prepareForOsflsStreaming() { + extractTriggerTimesFromFileNames(); + FieldlinesState newState; + if (!newState.loadStateFromOsfls(_sourceFiles[0])) { + LERROR("The provided .osfls files seem to be corrupt: " + _sourceFiles[0]); + return false; + } + _states.push_back(newState); + _nStates = _startTimes.size(); + _activeStateIndex = 0; + return true; +} + +void RenderableFieldlinesSequence::loadOsflsStatesIntoRAM(const std::string& outputFolder) +{ + // Load states from .osfls files into RAM! + for (const std::string& filePath : _sourceFiles) { + //LERROR("Loading file into RAM: " + filePath); + FieldlinesState newState; + if (newState.loadStateFromOsfls(filePath)) { + addStateToSequence(newState); + if (!outputFolder.empty()) { + ghoul::filesystem::File tmpFile(filePath); + newState.saveStateToJson(outputFolder + tmpFile.baseName()); + } } else { - LWARNING(fmt::format( - "{}: {} is not specified. States will be stored in RAM", - _identifier, KeyOslfsLoadAtRuntime - )); + LWARNING(fmt::format("Failed to load state from: {}", filePath)); } } +} - void RenderableFieldlinesSequence::setupProperties() { - bool hasExtras = (_states[0].nExtraQuantities() > 0); +void RenderableFieldlinesSequence::extractOsflsInfoFromDictionary() { + bool shouldLoadInRealtime = false; + if (_dictionary->getValue(KeyOslfsLoadAtRuntime, shouldLoadInRealtime)) { + _loadingStatesDynamically = shouldLoadInRealtime; + } + else { + LWARNING(fmt::format( + "{}: {} is not specified. States will be stored in RAM", + _identifier, KeyOslfsLoadAtRuntime + )); + } +} - // -------------- Add non-grouped properties (enablers and buttons) -------------- // - addProperty(_pColorABlendEnabled); - addProperty(_pDomainEnabled); - addProperty(_pFlowEnabled); - if (hasExtras) { - addProperty(_pMaskingEnabled); - } - addProperty(_pFocusOnOriginBtn); - addProperty(_pJumpToStartBtn); +void RenderableFieldlinesSequence::setupProperties() { + bool hasExtras = (_states[0].nExtraQuantities() > 0); - // ----------------------------- Add Property Groups ----------------------------- // - addPropertySubOwner(_pColorGroup); - addPropertySubOwner(_pDomainGroup); - addPropertySubOwner(_pFlowGroup); - if (hasExtras) { - addPropertySubOwner(_pMaskingGroup); - } + // -------------- Add non-grouped properties (enablers and buttons) -------------- // + addProperty(_pColorABlendEnabled); + addProperty(_pDomainEnabled); + addProperty(_pFlowEnabled); + if (hasExtras) { + addProperty(_pMaskingEnabled); + } + addProperty(_pFocusOnOriginBtn); + addProperty(_pJumpToStartBtn); - // ------------------------- Add Properties to the groups ------------------------- // - _pColorGroup.addProperty(_pColorUniform); - _pDomainGroup.addProperty(_pDomainX); - _pDomainGroup.addProperty(_pDomainY); - _pDomainGroup.addProperty(_pDomainZ); - _pDomainGroup.addProperty(_pDomainR); - _pFlowGroup.addProperty(_pFlowReversed); - _pFlowGroup.addProperty(_pFlowColor); - _pFlowGroup.addProperty(_pFlowParticleSize); - _pFlowGroup.addProperty(_pFlowParticleSpacing); - _pFlowGroup.addProperty(_pFlowSpeed); - if (hasExtras) { - _pColorGroup.addProperty(_pColorMethod); - _pColorGroup.addProperty(_pColorQuantity); - _pColorGroup.addProperty(_pColorQuantityMin); - _pColorGroup.addProperty(_pColorQuantityMax); - _pColorGroup.addProperty(_pColorTablePath); - _pMaskingGroup.addProperty(_pMaskingMin); - _pMaskingGroup.addProperty(_pMaskingMax); - _pMaskingGroup.addProperty(_pMaskingQuantity); - - // --------------------- Add Options to OptionProperties --------------------- // - _pColorMethod.addOption(static_cast(ColorMethod::Uniform), "Uniform"); - _pColorMethod.addOption(static_cast(ColorMethod::ByQuantity), "By Quantity"); - // Add option for each extra quantity. Assumes there are just as many names to - // extra quantities as there are extra quantities. Also assume that all states in - // the given sequence have the same extra quantities! */ - const size_t nExtraQuantities = _states[0].nExtraQuantities(); - const std::vector& extraNamesVec = _states[0].extraQuantityNames(); - for (int i = 0; i < static_cast(nExtraQuantities); ++i) { - _pColorQuantity.addOption(i, extraNamesVec[i]); - _pMaskingQuantity.addOption(i, extraNamesVec[i]); - } - // Each quantity should have its own color table and color table range - // no more, no less - _colorTablePaths.resize(nExtraQuantities, _colorTablePaths.back()); - _colorTableRanges.resize(nExtraQuantities, _colorTableRanges.back()); - _maskingRanges.resize(nExtraQuantities, _maskingRanges.back()); - } - - definePropertyCallbackFunctions(); - - if (hasExtras) { - // Set defaults - _pColorQuantity = 0; - _pColorQuantityMin = std::to_string(_colorTableRanges[0].x); - _pColorQuantityMax = std::to_string(_colorTableRanges[0].y); - _pColorTablePath = _colorTablePaths[0]; - - _pMaskingQuantity = 0; - _pMaskingMin = std::to_string(_maskingRanges[0].x); - _pMaskingMax = std::to_string(_maskingRanges[0].y); - } + // ----------------------------- Add Property Groups ----------------------------- // + addPropertySubOwner(_pColorGroup); + addPropertySubOwner(_pDomainGroup); + addPropertySubOwner(_pFlowGroup); + if (hasExtras) { + addPropertySubOwner(_pMaskingGroup); } - void RenderableFieldlinesSequence::definePropertyCallbackFunctions() { - // Add Property Callback Functions - bool hasExtras = (_states[0].nExtraQuantities() > 0); - if (hasExtras) { - _pColorQuantity.onChange([this] { - _shouldUpdateColorBuffer = true; - _pColorQuantityMin = std::to_string(_colorTableRanges[_pColorQuantity].x); - _pColorQuantityMax = std::to_string(_colorTableRanges[_pColorQuantity].y); - _pColorTablePath = _colorTablePaths[_pColorQuantity]; - }); + // ------------------------- Add Properties to the groups ------------------------- // + _pColorGroup.addProperty(_pColorUniform); + _pDomainGroup.addProperty(_pDomainX); + _pDomainGroup.addProperty(_pDomainY); + _pDomainGroup.addProperty(_pDomainZ); + _pDomainGroup.addProperty(_pDomainR); + _pFlowGroup.addProperty(_pFlowReversed); + _pFlowGroup.addProperty(_pFlowColor); + _pFlowGroup.addProperty(_pFlowParticleSize); + _pFlowGroup.addProperty(_pFlowParticleSpacing); + _pFlowGroup.addProperty(_pFlowSpeed); + if (hasExtras) { + _pColorGroup.addProperty(_pColorMethod); + _pColorGroup.addProperty(_pColorQuantity); + _pColorGroup.addProperty(_pColorQuantityMin); + _pColorGroup.addProperty(_pColorQuantityMax); + _pColorGroup.addProperty(_pColorTablePath); + _pMaskingGroup.addProperty(_pMaskingMin); + _pMaskingGroup.addProperty(_pMaskingMax); + _pMaskingGroup.addProperty(_pMaskingQuantity); - _pColorTablePath.onChange([this] { - _transferFunction->setPath(_pColorTablePath); - _colorTablePaths[_pColorQuantity] = _pColorTablePath; - }); - - _pColorQuantityMin.onChange([this] { - const float f = stringToFloat( - _pColorQuantityMin, - _colorTableRanges[_pColorQuantity].x - ); - _pColorQuantityMin = std::to_string(f); - _colorTableRanges[_pColorQuantity].x = f; - }); - - _pColorQuantityMax.onChange([this] { - const float f = stringToFloat( - _pColorQuantityMax, - _colorTableRanges[_pColorQuantity].y - ); - _pColorQuantityMax = std::to_string(f); - _colorTableRanges[_pColorQuantity].y = f; - }); - - _pMaskingQuantity.onChange([this] { - _shouldUpdateMaskingBuffer = true; - _pMaskingMin = std::to_string(_maskingRanges[_pMaskingQuantity].x); - _pMaskingMax = std::to_string(_maskingRanges[_pMaskingQuantity].y); - }); - - _pMaskingMin.onChange([this] { - const float f = stringToFloat( - _pMaskingMin, - _maskingRanges[_pMaskingQuantity].x - ); - _pMaskingMin = std::to_string(f); - _maskingRanges[_pMaskingQuantity].x = f; - }); - - _pMaskingMax.onChange([this] { - const float f = stringToFloat( - _pMaskingMax, - _maskingRanges[_pMaskingQuantity].y - ); - _pMaskingMax = std::to_string(f); - _maskingRanges[_pMaskingQuantity].y = f; - }); + // --------------------- Add Options to OptionProperties --------------------- // + _pColorMethod.addOption(static_cast(ColorMethod::Uniform), "Uniform"); + _pColorMethod.addOption(static_cast(ColorMethod::ByQuantity), "By Quantity"); + // Add option for each extra quantity. Assumes there are just as many names to + // extra quantities as there are extra quantities. Also assume that all states in + // the given sequence have the same extra quantities! */ + const size_t nExtraQuantities = _states[0].nExtraQuantities(); + const std::vector& extraNamesVec = _states[0].extraQuantityNames(); + for (int i = 0; i < static_cast(nExtraQuantities); ++i) { + _pColorQuantity.addOption(i, extraNamesVec[i]); + _pMaskingQuantity.addOption(i, extraNamesVec[i]); } + // Each quantity should have its own color table and color table range + // no more, no less + _colorTablePaths.resize(nExtraQuantities, _colorTablePaths.back()); + _colorTableRanges.resize(nExtraQuantities, _colorTableRanges.back()); + _maskingRanges.resize(nExtraQuantities, _maskingRanges.back()); + } - _pFocusOnOriginBtn.onChange([this] { - SceneGraphNode* node = global::renderEngine.scene()->sceneGraphNode(_identifier); - if (!node) { - LWARNING(fmt::format( - "Could not find a node in scenegraph called '{}'", _identifier - )); - return; - } - global::navigationHandler.orbitalNavigator().setFocusNode( - node->parent()->identifier() + definePropertyCallbackFunctions(); + + if (hasExtras) { + // Set defaults + _pColorQuantity = 0; + _pColorQuantityMin = std::to_string(_colorTableRanges[0].x); + _pColorQuantityMax = std::to_string(_colorTableRanges[0].y); + _pColorTablePath = _colorTablePaths[0]; + + _pMaskingQuantity = 0; + _pMaskingMin = std::to_string(_maskingRanges[0].x); + _pMaskingMax = std::to_string(_maskingRanges[0].y); + } +} + +void RenderableFieldlinesSequence::definePropertyCallbackFunctions() { + // Add Property Callback Functions + bool hasExtras = (_states[0].nExtraQuantities() > 0); + if (hasExtras) { + _pColorQuantity.onChange([this] { + _shouldUpdateColorBuffer = true; + _pColorQuantityMin = std::to_string(_colorTableRanges[_pColorQuantity].x); + _pColorQuantityMax = std::to_string(_colorTableRanges[_pColorQuantity].y); + _pColorTablePath = _colorTablePaths[_pColorQuantity]; + }); + + _pColorTablePath.onChange([this] { + _transferFunction->setPath(_pColorTablePath); + _colorTablePaths[_pColorQuantity] = _pColorTablePath; + }); + + _pColorQuantityMin.onChange([this] { + const float f = stringToFloat( + _pColorQuantityMin, + _colorTableRanges[_pColorQuantity].x ); - global::navigationHandler.orbitalNavigator().startRetargetAnchor(); + _pColorQuantityMin = std::to_string(f); + _colorTableRanges[_pColorQuantity].x = f; }); - _pJumpToStartBtn.onChange([this] { - global::timeManager.setTimeNextFrame(openspace::Time(_startTimes[0])); + _pColorQuantityMax.onChange([this] { + const float f = stringToFloat( + _pColorQuantityMax, + _colorTableRanges[_pColorQuantity].y + ); + _pColorQuantityMax = std::to_string(f); + _colorTableRanges[_pColorQuantity].y = f; + }); + + _pMaskingQuantity.onChange([this] { + _shouldUpdateMaskingBuffer = true; + _pMaskingMin = std::to_string(_maskingRanges[_pMaskingQuantity].x); + _pMaskingMax = std::to_string(_maskingRanges[_pMaskingQuantity].y); + }); + + _pMaskingMin.onChange([this] { + const float f = stringToFloat( + _pMaskingMin, + _maskingRanges[_pMaskingQuantity].x + ); + _pMaskingMin = std::to_string(f); + _maskingRanges[_pMaskingQuantity].x = f; + }); + + _pMaskingMax.onChange([this] { + const float f = stringToFloat( + _pMaskingMax, + _maskingRanges[_pMaskingQuantity].y + ); + _pMaskingMax = std::to_string(f); + _maskingRanges[_pMaskingQuantity].y = f; }); } + _pFocusOnOriginBtn.onChange([this] { + SceneGraphNode* node = global::renderEngine.scene()->sceneGraphNode(_identifier); + if (!node) { + LWARNING(fmt::format( + "Could not find a node in scenegraph called '{}'", _identifier + )); + return; + } + global::navigationHandler.orbitalNavigator().setFocusNode( + node->parent()->identifier() + ); + global::navigationHandler.orbitalNavigator().startRetargetAnchor(); + }); + + _pJumpToStartBtn.onChange([this] { + global::timeManager.setTimeNextFrame(openspace::Time(_startTimes[0])); + }); +} + // Calculate expected end time. void RenderableFieldlinesSequence::computeSequenceEndTime() { - if (_nStates > 1) { - const double lastTriggerTime = _startTimes[_nStates - 1]; - const double sequenceDuration = lastTriggerTime - _startTimes[0]; - const double averageStateDuration = sequenceDuration / - (static_cast(_nStates) - 1.0); - _sequenceEndTime = lastTriggerTime + averageStateDuration; - } - else { - // If there's just one state it should never disappear! - _sequenceEndTime = DBL_MAX; - } +if (_nStates > 1) { + const double lastTriggerTime = _startTimes[_nStates - 1]; + const double sequenceDuration = lastTriggerTime - _startTimes[0]; + const double averageStateDuration = sequenceDuration / + (static_cast(_nStates) - 1.0); + _sequenceEndTime = lastTriggerTime + averageStateDuration; +} +else { + // If there's just one state it should never disappear! + _sequenceEndTime = DBL_MAX; +} } - void RenderableFieldlinesSequence::setModelDependentConstants() { - const fls::Model simulationModel = _states[0].model(); - float limit = 100.f; // Just used as a default value. - switch (simulationModel) { - case fls::Model::Batsrus: - _scalingFactor = fls::ReToMeter; - limit = 300; // Should include a long magnetotail - break; - case fls::Model::Enlil: - _pFlowReversed = true; - _scalingFactor = fls::AuToMeter; - limit = 50; // Should include Plutos furthest distance from the Sun - break; - case fls::Model::Pfss: - _scalingFactor = fls::RsToMeter; - limit = 100; // Just a default value far away from the solar surface - break; - case fls::Model::Wsa: - // should something go here? - break; - default: - break; - } - _pDomainX.setMinValue(glm::vec2(-limit)); - _pDomainX.setMaxValue(glm::vec2(limit)); +void RenderableFieldlinesSequence::setModelDependentConstants() { + const fls::Model simulationModel = _states[0].model(); + float limit = 100.f; // Just used as a default value. + switch (simulationModel) { + case fls::Model::Batsrus: + _scalingFactor = fls::ReToMeter; + limit = 300; // Should include a long magnetotail + break; + case fls::Model::Enlil: + _pFlowReversed = true; + _scalingFactor = fls::AuToMeter; + limit = 50; // Should include Plutos furthest distance from the Sun + break; + case fls::Model::Pfss: + _scalingFactor = fls::RsToMeter; + limit = 100; // Just a default value far away from the solar surface + break; + case fls::Model::Wsa: + // should something go here? + break; + default: + break; + } + _pDomainX.setMinValue(glm::vec2(-limit)); + _pDomainX.setMaxValue(glm::vec2(limit)); - _pDomainY.setMinValue(glm::vec2(-limit)); - _pDomainY.setMaxValue(glm::vec2(limit)); + _pDomainY.setMinValue(glm::vec2(-limit)); + _pDomainY.setMaxValue(glm::vec2(limit)); - _pDomainZ.setMinValue(glm::vec2(-limit)); - _pDomainZ.setMaxValue(glm::vec2(limit)); + _pDomainZ.setMinValue(glm::vec2(-limit)); + _pDomainZ.setMaxValue(glm::vec2(limit)); - // Radial should range from 0 out to a corner of the cartesian box: - // sqrt(3) = 1.732..., 1.75 is a nice and round number - _pDomainR.setMinValue(glm::vec2(0)); - _pDomainR.setMaxValue(glm::vec2(limit * 1.75f)); + // Radial should range from 0 out to a corner of the cartesian box: + // sqrt(3) = 1.732..., 1.75 is a nice and round number + _pDomainR.setMinValue(glm::vec2(0)); + _pDomainR.setMaxValue(glm::vec2(limit * 1.75f)); - _pDomainX = glm::vec2(-limit, limit); - _pDomainY = glm::vec2(-limit, limit); - _pDomainZ = glm::vec2(-limit, limit); - _pDomainR = glm::vec2(0, limit * 1.5f); + _pDomainX = glm::vec2(-limit, limit); + _pDomainY = glm::vec2(-limit, limit); + _pDomainZ = glm::vec2(-limit, limit); + _pDomainR = glm::vec2(0, limit * 1.5f); +} + +// Extract J2000 time from file names +// Requires files to be named as such: 'YYYY-MM-DDTHH-MM-SS-XXX.osfls' +void RenderableFieldlinesSequence::extractTriggerTimesFromFileNames() { + // number of characters in filename (excluding '.osfls') + constexpr const int FilenameSize = 23; + // size(".osfls") + constexpr const int ExtSize = 6; + + for (const std::string& filePath : _sourceFiles) { + const size_t strLength = filePath.size(); + // Extract the filename from the path (without extension) + std::string timeString = filePath.substr( + strLength - FilenameSize - ExtSize, + FilenameSize - 1 + ); + // Ensure the separators are correct + timeString.replace(4, 1, "-"); + timeString.replace(7, 1, "-"); + timeString.replace(13, 1, ":"); + timeString.replace(16, 1, ":"); + timeString.replace(19, 1, "."); + const double triggerTime = Time::convertTime(timeString); + //LERROR("Adding starttime " + this->_identifier + " : " + std::to_string(triggerTime)); + _startTimes.push_back(triggerTime); } - // Extract J2000 time from file names - // Requires files to be named as such: 'YYYY-MM-DDTHH-MM-SS-XXX.osfls' - void RenderableFieldlinesSequence::extractTriggerTimesFromFileNames() { - // number of characters in filename (excluding '.osfls') - constexpr const int FilenameSize = 23; - // size(".osfls") - constexpr const int ExtSize = 6; +} - for (const std::string& filePath : _sourceFiles) { - const size_t strLength = filePath.size(); - // Extract the filename from the path (without extension) - std::string timeString = filePath.substr( - strLength - FilenameSize - ExtSize, - FilenameSize - 1 - ); - // Ensure the separators are correct - timeString.replace(4, 1, "-"); - timeString.replace(7, 1, "-"); - timeString.replace(13, 1, ":"); - timeString.replace(16, 1, ":"); - timeString.replace(19, 1, "."); - const double triggerTime = Time::convertTime(timeString); - //LERROR("Adding starttime " + this->_identifier + " : " + std::to_string(triggerTime)); - _startTimes.push_back(triggerTime); - } +void RenderableFieldlinesSequence::addStateToSequence(FieldlinesState& state) { + _states.push_back(state); + //LERROR("Adding state to list of states : " + std::to_string(state.triggerTime())); + _startTimes.push_back(state.triggerTime()); + _nStates++; +} + +bool RenderableFieldlinesSequence::getStatesFromCdfFiles(const std::string& outputFolder) +{ + std::string seedFilePath; + std::string tracingVar; + std::vector extraVars; + if (!extractCdfInfoFromDictionary(seedFilePath, tracingVar, extraVars)) { + return false; } - void RenderableFieldlinesSequence::addStateToSequence(FieldlinesState& state) { - _states.push_back(state); - //LERROR("Adding state to list of states : " + std::to_string(state.triggerTime())); - _startTimes.push_back(state.triggerTime()); - - _nStates++; + std::vector seedPoints; + if (!extractSeedPointsFromFile(seedFilePath, seedPoints)) { + return false; } - bool RenderableFieldlinesSequence::getStatesFromCdfFiles(const std::string& outputFolder) - { - std::string seedFilePath; - std::string tracingVar; - std::vector extraVars; - if (!extractCdfInfoFromDictionary(seedFilePath, tracingVar, extraVars)) { - return false; - } + std::vector extraMagVars; + extractMagnitudeVarsFromStrings(extraVars, extraMagVars); - std::vector seedPoints; - if (!extractSeedPointsFromFile(seedFilePath, seedPoints)) { - return false; - } + // Load states into RAM! + for (const std::string& cdfPath : _sourceFiles) { + FieldlinesState newState; + bool isSuccessful = fls::convertCdfToFieldlinesState( + newState, + cdfPath, + seedPoints, + tracingVar, + extraVars, + extraMagVars + ); - std::vector extraMagVars; - extractMagnitudeVarsFromStrings(extraVars, extraMagVars); - - // Load states into RAM! - for (const std::string& cdfPath : _sourceFiles) { - FieldlinesState newState; - bool isSuccessful = fls::convertCdfToFieldlinesState( - newState, - cdfPath, - seedPoints, - tracingVar, - extraVars, - extraMagVars - ); - - if (isSuccessful) { - addStateToSequence(newState); - if (!outputFolder.empty()) { - newState.saveStateToOsfls(outputFolder); - } + if (isSuccessful) { + addStateToSequence(newState); + if (!outputFolder.empty()) { + newState.saveStateToOsfls(outputFolder); } } - return true; } + return true; +} - /* - * Returns false if it fails to extract mandatory information! - */ - bool RenderableFieldlinesSequence::extractCdfInfoFromDictionary(std::string& seedFilePath, - std::string& tracingVar, - std::vector& extraVars) - { +/* +* Returns false if it fails to extract mandatory information! +*/ +bool RenderableFieldlinesSequence::extractCdfInfoFromDictionary(std::string& seedFilePath, + std::string& tracingVar, + std::vector& extraVars) +{ - if (_dictionary->getValue(KeyCdfSeedPointFile, seedFilePath)) { - ghoul::filesystem::File seedPointFile(seedFilePath); - if (FileSys.fileExists(seedPointFile)) { - seedFilePath = absPath(seedFilePath); - } - else { - LERROR(fmt::format( - "{}: The specified seed poitn file: '{}' does not exist", - _identifier, seedFilePath - )); - return false; - } + if (_dictionary->getValue(KeyCdfSeedPointFile, seedFilePath)) { + ghoul::filesystem::File seedPointFile(seedFilePath); + if (FileSys.fileExists(seedPointFile)) { + seedFilePath = absPath(seedFilePath); } else { - LERROR(fmt::format("{}: Must specify '{}'", _identifier, KeyCdfSeedPointFile)); - return false; - } - - if (!_dictionary->getValue(KeyCdfTracingVariable, tracingVar)) { - tracingVar = "b"; // Magnetic field variable as default - LWARNING(fmt::format( - "{}: No '{}', using default '{}'", - _identifier, KeyCdfTracingVariable, tracingVar + LERROR(fmt::format( + "{}: The specified seed poitn file: '{}' does not exist", + _identifier, seedFilePath )); + return false; } + } + else { + LERROR(fmt::format("{}: Must specify '{}'", _identifier, KeyCdfSeedPointFile)); + return false; + } - ghoul::Dictionary extraQuantityNamesDictionary; - if (_dictionary->getValue(KeyCdfExtraVariables, extraQuantityNamesDictionary)) { - const size_t nProvidedExtras = extraQuantityNamesDictionary.size(); - for (size_t i = 1; i <= nProvidedExtras; ++i) { - extraVars.push_back( - extraQuantityNamesDictionary.value(std::to_string(i)) + if (!_dictionary->getValue(KeyCdfTracingVariable, tracingVar)) { + tracingVar = "b"; // Magnetic field variable as default + LWARNING(fmt::format( + "{}: No '{}', using default '{}'", + _identifier, KeyCdfTracingVariable, tracingVar + )); + } + + ghoul::Dictionary extraQuantityNamesDictionary; + if (_dictionary->getValue(KeyCdfExtraVariables, extraQuantityNamesDictionary)) { + const size_t nProvidedExtras = extraQuantityNamesDictionary.size(); + for (size_t i = 1; i <= nProvidedExtras; ++i) { + extraVars.push_back( + extraQuantityNamesDictionary.value(std::to_string(i)) + ); + } + } + + return true; +} + +bool RenderableFieldlinesSequence::extractSeedPointsFromFile(const std::string& path, + std::vector& outVec) +{ + + std::ifstream seedFile(FileSys.relativePath(path)); + if (!seedFile.good()) { + LERROR(fmt::format("Could not open seed points file '{}'", path)); + return false; + } + + LDEBUG(fmt::format("Reading seed points from file '{}'", path)); + std::string line; + while (std::getline(seedFile, line)) { + std::stringstream ss(line); + glm::vec3 point; + ss >> point.x; + ss >> point.y; + ss >> point.z; + outVec.push_back(std::move(point)); + } + + if (outVec.size() == 0) { + LERROR(fmt::format("Found no seed points in: {}", path)); + return false; + } + + return true; +} + +void RenderableFieldlinesSequence::extractMagnitudeVarsFromStrings( + std::vector& extraVars, + std::vector& extraMagVars) +{ + + for (int i = 0; i < static_cast(extraVars.size()); i++) { + const std::string& str = extraVars[i]; + // Check if string is in the format specified for magnitude variables + if (str.substr(0, 2) == "|(" && str.substr(str.size() - 2, 2) == ")|") { + std::istringstream ss(str.substr(2, str.size() - 4)); + std::string magVar; + size_t counter = 0; + while (std::getline(ss, magVar, ',')) { + magVar.erase( + std::remove_if( + magVar.begin(), + magVar.end(), + ::isspace + ), + magVar.end() ); - } - } - - return true; - } - - bool RenderableFieldlinesSequence::extractSeedPointsFromFile(const std::string& path, - std::vector& outVec) - { - - std::ifstream seedFile(FileSys.relativePath(path)); - if (!seedFile.good()) { - LERROR(fmt::format("Could not open seed points file '{}'", path)); - return false; - } - - LDEBUG(fmt::format("Reading seed points from file '{}'", path)); - std::string line; - while (std::getline(seedFile, line)) { - std::stringstream ss(line); - glm::vec3 point; - ss >> point.x; - ss >> point.y; - ss >> point.z; - outVec.push_back(std::move(point)); - } - - if (outVec.size() == 0) { - LERROR(fmt::format("Found no seed points in: {}", path)); - return false; - } - - return true; - } - - void RenderableFieldlinesSequence::extractMagnitudeVarsFromStrings( - std::vector& extraVars, - std::vector& extraMagVars) - { - - for (int i = 0; i < static_cast(extraVars.size()); i++) { - const std::string& str = extraVars[i]; - // Check if string is in the format specified for magnitude variables - if (str.substr(0, 2) == "|(" && str.substr(str.size() - 2, 2) == ")|") { - std::istringstream ss(str.substr(2, str.size() - 4)); - std::string magVar; - size_t counter = 0; - while (std::getline(ss, magVar, ',')) { - magVar.erase( - std::remove_if( - magVar.begin(), - magVar.end(), - ::isspace - ), - magVar.end() - ); - extraMagVars.push_back(magVar); - counter++; - if (counter == 3) { - break; - } - } - if (counter != 3 && counter > 0) { - extraMagVars.erase(extraMagVars.end() - counter, extraMagVars.end()); - } - extraVars.erase(extraVars.begin() + i); - i--; - } - } - } - - void RenderableFieldlinesSequence::deinitializeGL() { - glDeleteVertexArrays(1, &_vertexArrayObject); - _vertexArrayObject = 0; - - glDeleteBuffers(1, &_vertexPositionBuffer); - _vertexPositionBuffer = 0; - - glDeleteBuffers(1, &_vertexColorBuffer); - _vertexColorBuffer = 0; - - glDeleteBuffers(1, &_vertexMaskingBuffer); - _vertexMaskingBuffer = 0; - - if (_shaderProgram) { - global::renderEngine.removeRenderProgram(_shaderProgram.get()); - _shaderProgram = nullptr; - } - - // Stall main thread until thread that's loading states is done! - bool printedWarning = false; - while (_isLoadingStateFromDisk) { - if (!printedWarning) { - LWARNING("Trying to destroy class when an active thread is still using it"); - printedWarning = true; - } - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - } - - bool RenderableFieldlinesSequence::isReady() const { - return _shaderProgram != nullptr; - } - - void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks&) { - if (_activeTriggerTimeIndex != -1) { - _shaderProgram->activate(); - - // Calculate Model View MatrixProjection - const glm::dmat4 rotMat = glm::dmat4(data.modelTransform.rotation); - const glm::dmat4 modelMat = - glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * - rotMat * - glm::dmat4(glm::scale(glm::dmat4(1), glm::dvec3(data.modelTransform.scale))); - const glm::dmat4 modelViewMat = data.camera.combinedViewMatrix() * modelMat; - - _shaderProgram->setUniform("modelViewProjection", - data.camera.sgctInternal.projectionMatrix() * glm::mat4(modelViewMat)); - - _shaderProgram->setUniform("colorMethod", _pColorMethod); - _shaderProgram->setUniform("lineColor", _pColorUniform); - _shaderProgram->setUniform("usingDomain", _pDomainEnabled); - _shaderProgram->setUniform("usingMasking", _pMaskingEnabled); - - if (_pColorMethod == static_cast(ColorMethod::ByQuantity)) { - ghoul::opengl::TextureUnit textureUnit; - textureUnit.activate(); - _transferFunction->bind(); // Calls update internally - _shaderProgram->setUniform("colorTable", textureUnit); - _shaderProgram->setUniform("colorTableRange", - _colorTableRanges[_pColorQuantity]); - } - - if (_pMaskingEnabled) { - _shaderProgram->setUniform("maskingRange", _maskingRanges[_pMaskingQuantity]); - } - - _shaderProgram->setUniform("domainLimR", _pDomainR.value() * _scalingFactor); - _shaderProgram->setUniform("domainLimX", _pDomainX.value() * _scalingFactor); - _shaderProgram->setUniform("domainLimY", _pDomainY.value() * _scalingFactor); - _shaderProgram->setUniform("domainLimZ", _pDomainZ.value() * _scalingFactor); - - // Flow/Particles - _shaderProgram->setUniform("flowColor", _pFlowColor); - _shaderProgram->setUniform("usingParticles", _pFlowEnabled); - _shaderProgram->setUniform("particleSize", _pFlowParticleSize); - _shaderProgram->setUniform("particleSpacing", _pFlowParticleSpacing); - _shaderProgram->setUniform("particleSpeed", _pFlowSpeed); - _shaderProgram->setUniform( - "time", - global::windowDelegate.applicationTime() * (_pFlowReversed ? -1 : 1) - ); - - bool additiveBlending = false; - if (_pColorABlendEnabled) { - const auto renderer = global::renderEngine.rendererImplementation(); - const bool usingFBufferRenderer = renderer == - RenderEngine::RendererImplementation::Framebuffer; - - const bool usingABufferRenderer = renderer == - RenderEngine::RendererImplementation::ABuffer; - - if (usingABufferRenderer) { - _shaderProgram->setUniform("usingAdditiveBlending", _pColorABlendEnabled); - } - - additiveBlending = usingFBufferRenderer; - if (additiveBlending) { - glDepthMask(false); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); + extraMagVars.push_back(magVar); + counter++; + if (counter == 3) { + break; } } + if (counter != 3 && counter > 0) { + extraMagVars.erase(extraMagVars.end() - counter, extraMagVars.end()); + } + extraVars.erase(extraVars.begin() + i); + i--; + } + } +} - glBindVertexArray(_vertexArrayObject); - glMultiDrawArrays( - GL_LINE_STRIP, //_drawingOutputType, - _states[_activeStateIndex].lineStart().data(), - _states[_activeStateIndex].lineCount().data(), - static_cast(_states[_activeStateIndex].lineStart().size()) - ); +void RenderableFieldlinesSequence::deinitializeGL() { + glDeleteVertexArrays(1, &_vertexArrayObject); + _vertexArrayObject = 0; - glBindVertexArray(0); - _shaderProgram->deactivate(); + glDeleteBuffers(1, &_vertexPositionBuffer); + _vertexPositionBuffer = 0; + glDeleteBuffers(1, &_vertexColorBuffer); + _vertexColorBuffer = 0; + + glDeleteBuffers(1, &_vertexMaskingBuffer); + _vertexMaskingBuffer = 0; + + if (_shaderProgram) { + global::renderEngine.removeRenderProgram(_shaderProgram.get()); + _shaderProgram = nullptr; + } + + // Stall main thread until thread that's loading states is done! + bool printedWarning = false; + while (_isLoadingStateFromDisk) { + if (!printedWarning) { + LWARNING("Trying to destroy class when an active thread is still using it"); + printedWarning = true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } +} + +bool RenderableFieldlinesSequence::isReady() const { + return _shaderProgram != nullptr; +} + +void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks&) { + if (_activeTriggerTimeIndex != -1) { + _shaderProgram->activate(); + + // Calculate Model View MatrixProjection + const glm::dmat4 rotMat = glm::dmat4(data.modelTransform.rotation); + const glm::dmat4 modelMat = + glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * + rotMat * + glm::dmat4(glm::scale(glm::dmat4(1), glm::dvec3(data.modelTransform.scale))); + const glm::dmat4 modelViewMat = data.camera.combinedViewMatrix() * modelMat; + + _shaderProgram->setUniform("modelViewProjection", + data.camera.sgctInternal.projectionMatrix() * glm::mat4(modelViewMat)); + + _shaderProgram->setUniform("colorMethod", _pColorMethod); + _shaderProgram->setUniform("lineColor", _pColorUniform); + _shaderProgram->setUniform("usingDomain", _pDomainEnabled); + _shaderProgram->setUniform("usingMasking", _pMaskingEnabled); + + if (_pColorMethod == static_cast(ColorMethod::ByQuantity)) { + ghoul::opengl::TextureUnit textureUnit; + textureUnit.activate(); + _transferFunction->bind(); // Calls update internally + _shaderProgram->setUniform("colorTable", textureUnit); + _shaderProgram->setUniform("colorTableRange", + _colorTableRanges[_pColorQuantity]); + } + + if (_pMaskingEnabled) { + _shaderProgram->setUniform("maskingRange", _maskingRanges[_pMaskingQuantity]); + } + + _shaderProgram->setUniform("domainLimR", _pDomainR.value() * _scalingFactor); + _shaderProgram->setUniform("domainLimX", _pDomainX.value() * _scalingFactor); + _shaderProgram->setUniform("domainLimY", _pDomainY.value() * _scalingFactor); + _shaderProgram->setUniform("domainLimZ", _pDomainZ.value() * _scalingFactor); + + // Flow/Particles + _shaderProgram->setUniform("flowColor", _pFlowColor); + _shaderProgram->setUniform("usingParticles", _pFlowEnabled); + _shaderProgram->setUniform("particleSize", _pFlowParticleSize); + _shaderProgram->setUniform("particleSpacing", _pFlowParticleSpacing); + _shaderProgram->setUniform("particleSpeed", _pFlowSpeed); + _shaderProgram->setUniform( + "time", + global::windowDelegate.applicationTime() * (_pFlowReversed ? -1 : 1) + ); + + bool additiveBlending = false; + if (_pColorABlendEnabled) { + const auto renderer = global::renderEngine.rendererImplementation(); + const bool usingFBufferRenderer = renderer == + RenderEngine::RendererImplementation::Framebuffer; + + const bool usingABufferRenderer = renderer == + RenderEngine::RendererImplementation::ABuffer; + + if (usingABufferRenderer) { + _shaderProgram->setUniform("usingAdditiveBlending", _pColorABlendEnabled); + } + + additiveBlending = usingFBufferRenderer; if (additiveBlending) { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDepthMask(true); + glDepthMask(false); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); } } + + glBindVertexArray(_vertexArrayObject); + glMultiDrawArrays( + GL_LINE_STRIP, //_drawingOutputType, + _states[_activeStateIndex].lineStart().data(), + _states[_activeStateIndex].lineCount().data(), + static_cast(_states[_activeStateIndex].lineStart().size()) + ); + + glBindVertexArray(0); + _shaderProgram->deactivate(); + + if (additiveBlending) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(true); + } + } +} + +void RenderableFieldlinesSequence::initializeWebManager() { + _webFieldlinesManager.initializeWebFieldlinesManager( + _identifier,_dynWebContentUrl, _nStates, _sourceFiles, _startTimes + ); +} + +void RenderableFieldlinesSequence::update(const UpdateData& data) { + if (_shaderProgram->isDirty()) { + _shaderProgram->rebuildFromFile(); } - void RenderableFieldlinesSequence::initializeWebManager() { - _webFieldlinesManager.initializeWebFieldlinesManager(_identifier,_dynWebContentUrl, _nStates, _sourceFiles, _startTimes); + const double currentTime = data.time.j2000Seconds(); + + + if (_dynamicWebContent) { + + if (!_webFieldlinesManager.hasUpdated && + _webFieldlinesManager.checkIfWindowIsReadyToLoad()) + { + _startTimes.clear(); + extractTriggerTimesFromFileNames(); + + // _startTimes are not sorted right now, have to do something about that -> + std::sort(_startTimes.begin(), _startTimes.end()); + _nStates = _startTimes.size(); + + _webFieldlinesManager.hasUpdated = true; + _webFieldlinesManager.notifyUpdate = true; + _webFieldlinesManager.resetWorker(); + } + _webFieldlinesManager.update(); + // we could also send time as a variable as we already have it } - void RenderableFieldlinesSequence::update(const UpdateData& data) { - if (_shaderProgram->isDirty()) { - _shaderProgram->rebuildFromFile(); - } + const bool isInInterval = (currentTime >= _startTimes[0]) && + (currentTime < _sequenceEndTime); - const double currentTime = data.time.j2000Seconds(); + // Check if current time in OpenSpace is within sequence interval + if (isInInterval) { + const size_t nextIdx = _activeTriggerTimeIndex + 1; - - if (_dynamicWebContent) { - - if (!_webFieldlinesManager.hasUpdated && _webFieldlinesManager.checkIfWindowIsReadyToLoad()) { - _startTimes.clear(); - extractTriggerTimesFromFileNames(); + if ( + // true => Previous frame was not within the sequence interval + _activeTriggerTimeIndex < 0 || + // true => We stepped back to a time represented by another state + currentTime < _startTimes[_activeTriggerTimeIndex] || + // true => We stepped forward to a time represented by another state + (nextIdx < _nStates && currentTime >= _startTimes[nextIdx])) + { - // _startTimes are not sorted right now, have to do something about that -> - std::sort(_startTimes.begin(), _startTimes.end()); - _nStates = _startTimes.size(); - - _webFieldlinesManager.hasUpdated = true; - _webFieldlinesManager.notifyUpdate = true; - _webFieldlinesManager.resetWorker(); - } - _webFieldlinesManager.update(); // we could also send time as a variable as we already have it - } - - const bool isInInterval = (currentTime >= _startTimes[0]) && - (currentTime < _sequenceEndTime); - - // Check if current time in OpenSpace is within sequence interval - if (isInInterval) { - const size_t nextIdx = _activeTriggerTimeIndex + 1; - - if ( - // true => Previous frame was not within the sequence interval - _activeTriggerTimeIndex < 0 || - // true => We stepped back to a time represented by another state - currentTime < _startTimes[_activeTriggerTimeIndex] || - // true => We stepped forward to a time represented by another state - (nextIdx < _nStates && currentTime >= _startTimes[nextIdx])) - { - - updateActiveTriggerTimeIndex(currentTime); - - if (_loadingStatesDynamically) { - _mustLoadNewStateFromDisk = true; - } - else { - _needsUpdate = true; - _activeStateIndex = _activeTriggerTimeIndex; - } - } - - - // else {we're still in same state as previous frame (no changes needed)} - } - else { - // Not in interval => set everything to false - _activeTriggerTimeIndex = -1; - _mustLoadNewStateFromDisk = false; - _needsUpdate = false; - } - - if (_dynamicWebContent && _webFieldlinesManager.notifyUpdate) { updateActiveTriggerTimeIndex(currentTime); - computeSequenceEndTime(); - _webFieldlinesManager.notifyUpdate = false; - } - - if (_mustLoadNewStateFromDisk) { - if (!_isLoadingStateFromDisk && !_newStateIsReady) { - _isLoadingStateFromDisk = true; - _mustLoadNewStateFromDisk = false; - std::string filePath = _sourceFiles[_activeTriggerTimeIndex]; - std::thread readBinaryThread([this, f = std::move(filePath)]{ - readNewState(f); - }); - readBinaryThread.detach(); - } - } - - if (_needsUpdate || _newStateIsReady) { if (_loadingStatesDynamically) { - _states[0] = std::move(*_newState); - - } - - updateVertexPositionBuffer(); - - if (_states[_activeStateIndex].nExtraQuantities() > 0) { - _shouldUpdateColorBuffer = true; - _shouldUpdateMaskingBuffer = true; - } - - // Everything is set and ready for rendering! - _needsUpdate = false; - _newStateIsReady = false; - } - - if (_shouldUpdateColorBuffer) { - updateVertexColorBuffer(); - _shouldUpdateColorBuffer = false; - } - - if (_shouldUpdateMaskingBuffer) { - updateVertexMaskingBuffer(); - _shouldUpdateMaskingBuffer = false; - } - } - - // Assumes we already know that currentTime is within the sequence interval - void RenderableFieldlinesSequence::updateActiveTriggerTimeIndex(double currentTime) { - auto iter = std::upper_bound(_startTimes.begin(), _startTimes.end(), currentTime); - if (iter != _startTimes.end()) { - if (iter != _startTimes.begin()) { - _activeTriggerTimeIndex = static_cast( - std::distance(_startTimes.begin(), iter) - ) - 1; + _mustLoadNewStateFromDisk = true; } else { - _activeTriggerTimeIndex = 0; + _needsUpdate = true; + _activeStateIndex = _activeTriggerTimeIndex; } + } + + + // else {we're still in same state as previous frame (no changes needed)} + } + else { + // Not in interval => set everything to false + _activeTriggerTimeIndex = -1; + _mustLoadNewStateFromDisk = false; + _needsUpdate = false; + } + + if (_dynamicWebContent && _webFieldlinesManager.notifyUpdate) { + updateActiveTriggerTimeIndex(currentTime); + computeSequenceEndTime(); + _webFieldlinesManager.notifyUpdate = false; + } + + if (_mustLoadNewStateFromDisk) { + if (!_isLoadingStateFromDisk && !_newStateIsReady) { + _isLoadingStateFromDisk = true; + _mustLoadNewStateFromDisk = false; + std::string filePath = _sourceFiles[_activeTriggerTimeIndex]; + std::thread readBinaryThread([this, f = std::move(filePath)]{ + readNewState(f); + }); + readBinaryThread.detach(); + } + } + + if (_needsUpdate || _newStateIsReady) { + + if (_loadingStatesDynamically) { + _states[0] = std::move(*_newState); + + } + + updateVertexPositionBuffer(); + + if (_states[_activeStateIndex].nExtraQuantities() > 0) { + _shouldUpdateColorBuffer = true; + _shouldUpdateMaskingBuffer = true; + } + + // Everything is set and ready for rendering! + _needsUpdate = false; + _newStateIsReady = false; + } + + if (_shouldUpdateColorBuffer) { + updateVertexColorBuffer(); + _shouldUpdateColorBuffer = false; + } + + if (_shouldUpdateMaskingBuffer) { + updateVertexMaskingBuffer(); + _shouldUpdateMaskingBuffer = false; + } +} + +// Assumes we already know that currentTime is within the sequence interval +void RenderableFieldlinesSequence::updateActiveTriggerTimeIndex(double currentTime) { + auto iter = std::upper_bound(_startTimes.begin(), _startTimes.end(), currentTime); + if (iter != _startTimes.end()) { + if (iter != _startTimes.begin()) { + _activeTriggerTimeIndex = static_cast( + std::distance(_startTimes.begin(), iter) + ) - 1; } else { - _activeTriggerTimeIndex = static_cast(_nStates) - 1; + _activeTriggerTimeIndex = 0; } } - - // Reading state from disk. Must be thread safe! - void RenderableFieldlinesSequence::readNewState(const std::string& filePath) { - _newState = std::make_unique(); - if (_newState->loadStateFromOsfls(filePath)) { - _newStateIsReady = true; - } - _isLoadingStateFromDisk = false; + else { + _activeTriggerTimeIndex = static_cast(_nStates) - 1; } +} - // Unbind buffers and arrays - inline void unbindGL() { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); +// Reading state from disk. Must be thread safe! +void RenderableFieldlinesSequence::readNewState(const std::string& filePath) { + _newState = std::make_unique(); + if (_newState->loadStateFromOsfls(filePath)) { + _newStateIsReady = true; } + _isLoadingStateFromDisk = false; +} - void RenderableFieldlinesSequence::updateVertexPositionBuffer() { - glBindVertexArray(_vertexArrayObject); - glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); +// Unbind buffers and arrays +inline void unbindGL() { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} - const std::vector& vertPos = _states[_activeStateIndex].vertexPositions(); +void RenderableFieldlinesSequence::updateVertexPositionBuffer() { + glBindVertexArray(_vertexArrayObject); + glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); + const std::vector& vertPos = _states[_activeStateIndex].vertexPositions(); + + glBufferData( + GL_ARRAY_BUFFER, + vertPos.size() * sizeof(glm::vec3), + vertPos.data(), + GL_STATIC_DRAW + ); + + glEnableVertexAttribArray(VaPosition); + glVertexAttribPointer(VaPosition, 3, GL_FLOAT, GL_FALSE, 0, 0); + + unbindGL(); +} + +void RenderableFieldlinesSequence::updateVertexColorBuffer() { + glBindVertexArray(_vertexArrayObject); + glBindBuffer(GL_ARRAY_BUFFER, _vertexColorBuffer); + + bool isSuccessful; + const std::vector& quantities = _states[_activeStateIndex].extraQuantity( + _pColorQuantity, + isSuccessful + ); + + if (isSuccessful) { glBufferData( GL_ARRAY_BUFFER, - vertPos.size() * sizeof(glm::vec3), - vertPos.data(), + quantities.size() * sizeof(float), + quantities.data(), GL_STATIC_DRAW ); - glEnableVertexAttribArray(VaPosition); - glVertexAttribPointer(VaPosition, 3, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(VaColor); + glVertexAttribPointer(VaColor, 1, GL_FLOAT, GL_FALSE, 0, 0); unbindGL(); } +} - void RenderableFieldlinesSequence::updateVertexColorBuffer() { - glBindVertexArray(_vertexArrayObject); - glBindBuffer(GL_ARRAY_BUFFER, _vertexColorBuffer); +void RenderableFieldlinesSequence::updateVertexMaskingBuffer() { + glBindVertexArray(_vertexArrayObject); + glBindBuffer(GL_ARRAY_BUFFER, _vertexMaskingBuffer); - bool isSuccessful; - const std::vector& quantities = _states[_activeStateIndex].extraQuantity( - _pColorQuantity, - isSuccessful + bool isSuccessful; + const std::vector& maskings = _states[_activeStateIndex].extraQuantity( + _pMaskingQuantity, + isSuccessful + ); + + if (isSuccessful) { + glBufferData( + GL_ARRAY_BUFFER, + maskings.size() * sizeof(float), + maskings.data(), + GL_STATIC_DRAW ); - if (isSuccessful) { - glBufferData( - GL_ARRAY_BUFFER, - quantities.size() * sizeof(float), - quantities.data(), - GL_STATIC_DRAW - ); + glEnableVertexAttribArray(VaMasking); + glVertexAttribPointer(VaMasking, 1, GL_FLOAT, GL_FALSE, 0, 0); - glEnableVertexAttribArray(VaColor); - glVertexAttribPointer(VaColor, 1, GL_FLOAT, GL_FALSE, 0, 0); - - unbindGL(); - } - } - - void RenderableFieldlinesSequence::updateVertexMaskingBuffer() { - glBindVertexArray(_vertexArrayObject); - glBindBuffer(GL_ARRAY_BUFFER, _vertexMaskingBuffer); - - bool isSuccessful; - const std::vector& maskings = _states[_activeStateIndex].extraQuantity( - _pMaskingQuantity, - isSuccessful - ); - - if (isSuccessful) { - glBufferData( - GL_ARRAY_BUFFER, - maskings.size() * sizeof(float), - maskings.data(), - GL_STATIC_DRAW - ); - - glEnableVertexAttribArray(VaMasking); - glVertexAttribPointer(VaMasking, 1, GL_FLOAT, GL_FALSE, 0, 0); - - unbindGL(); - } + unbindGL(); } +} } // namespace openspace diff --git a/modules/fieldlinessequence/util/webfieldlinesmanager.cpp b/modules/fieldlinessequence/util/webfieldlinesmanager.cpp index ed80acb2b5..920ce059b1 100644 --- a/modules/fieldlinessequence/util/webfieldlinesmanager.cpp +++ b/modules/fieldlinessequence/util/webfieldlinesmanager.cpp @@ -34,169 +34,185 @@ namespace { - constexpr const char* _loggerCat = "FieldlinesSequence[ Web FLs Manager ]"; + constexpr const char* _loggerCat = "WebFieldlinesManager"; } // namepace namespace openspace{ - // --------------------------- PUBLIC FUNCTIONS --------------------------- // - void WebFieldlinesManager::initializeWebFieldlinesManager(std::string identifier, std::string url, size_t& _nStates, std::vector& _sourceFiles, - std::vector& _startTimes) +// --------------------------- PUBLIC FUNCTIONS --------------------------- // +void WebFieldlinesManager::initializeWebFieldlinesManager(std::string identifier, + std::string url, + size_t& _nStates, + std::vector& _sourceFiles, + std::vector& _startTimes) +{ + // Initialize the sliding window + _webFieldlinesWindow = + WebFieldlinesWindow(_syncDir, url, _sourceFiles, _startTimes, _nStates); + + LTRACE("WebFieldlinesManager initialized " + identifier); +} + +bool WebFieldlinesManager::isConnected() { + return _connected; +} + +// Make sure that the sync directory exists +// Also creates a new directory in the web_fieldlines directory corresponding to the +// field line identifier +std::string WebFieldlinesManager::initializeSyncDirectory(std::string identifier) { + std::string path = + absPath("${BASE}/sync/http/web_fieldlines") + FileSys.PathSeparator; + + if (!FileSys.directoryExists(path)) { + FileSys.createDirectory(path); + } + + path = absPath(path + identifier); + if(!FileSys.directoryExists(path)) { + FileSys.createDirectory(path); + } + + _syncDir = path; + return _syncDir; +} + +// Temporary function - this should be moved to the worker. It's to download +// the start latest line +// this could be used to test the connection too +void WebFieldlinesManager::preDownload(std::string dUrl){ + + int ID = std::stoi(dUrl.substr(dUrl.size() - 4)); + std::string type = ""; + switch (ID) { - // Initialize the sliding window - _webFieldlinesWindow = WebFieldlinesWindow(_syncDir, url, _sourceFiles, _startTimes, _nStates); + case 1176: + type = "trace_sun_earth"; + break; + case 1177: + type = "trace_scs_outtoin"; + break; + case 1178: + type = "trace_pfss_intoout"; + break; + case 1179: + type = "trace_pfss_outtoin"; + break; + case 1180: + type = "WSA_OUT"; + break; - LTRACE("WebFieldlinesManager initialized " + identifier); + // Parker solar probe endpoints + case 1192: + type = "trace_sub_psp"; + break; + case 1193: + type = "trace_scs_outtoin"; + break; + case 1194: + type = "trace_pfss_intoout"; + break; + case 1195: + type = "trace_pfss_outtoin"; + break; + case 1196: + type = "ADAPT_WSA_OUT"; + break; } - bool WebFieldlinesManager::isConnected() { - return _connected; - } - - // Make sure that the sync directory exists - // Also creates a new directory in the web_fieldlines directory corresponding to the field line identifier - std::string WebFieldlinesManager::initializeSyncDirectory(std::string identifier) { - std::string path = absPath("${BASE}/sync/http/web_fieldlines") + FileSys.PathSeparator; - - if (!FileSys.directoryExists(path)) { - FileSys.createDirectory(path); - } - path = absPath(path + identifier); - if(!FileSys.directoryExists(path)) { - FileSys.createDirectory(path); - } - - _syncDir = path; - return _syncDir; - } - - // Temporary function - this should be moved to the worker. It's to download - // the start latest line - // this could be used to test the connection too - void WebFieldlinesManager::preDownload(std::string dUrl){ - - int ID = std::stoi(dUrl.substr(dUrl.size() - 4)); - std::string type = ""; - switch (ID) - { - case 1176: - type = "trace_sun_earth"; - break; - case 1177: - type = "trace_scs_outtoin"; - break; - case 1178: - type = "trace_pfss_intoout"; - break; - case 1179: - type = "trace_pfss_outtoin"; - break; - case 1180: - type = "WSA_OUT"; - break; - - // Parker solar probe endpoints - case 1192: - type = "trace_sub_psp"; - break; - case 1193: - type = "trace_scs_outtoin"; - break; - case 1194: - type = "trace_pfss_intoout"; - break; - case 1195: - type = "trace_pfss_outtoin"; - break; - case 1196: - type = "ADAPT_WSA_OUT"; - break; - } - - // TODO(Axel): Change this endpoint to be dynamic for each dataset - std::string url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/WSA4.5/WSA4.5_fieldlines/" + type + "/2017/09/2017-09-28T00-23-22.000.osfls"; - std::string destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "2017-09-28T00-23-22.000.osfls"); // what the downloaded filename is to be + // TODO(Axel): Change this endpoint to be dynamic for each dataset + std::string url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/WSA4.5/WSA4.5_fieldlines/" + type + "/2017/09/2017-09-28T00-23-22.000.osfls"; + std::string destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "2017-09-28T00-23-22.000.osfls"); // what the downloaded filename is to be - /* For experimentation with suntexturemanager & parker solor probe, hopefully this hardcoding can go away */ - if (ID == 1180) { - url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/WSA4.5/" + type + "/2017/09/wsa_201709280023R000_gong.fits"; - destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "wsa_201709280023R000_gong.fits"); // what the downloaded filename is to be - } - // For parker solar probe - if (ID > 1190) { - url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/ADAPT_WSA4.5/ADAPT_WSA4.5_fieldlines/" + type + "/2018/11/2018-11-01T00-00-00.000.osfls"; - destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "2018-11-01T00-00-00.000.osfls"); // what the downloaded filename is to be - } - - if (ID == 1196) { - url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/ADAPT_WSA4.5/" + type + "/2018/11/wsa_201811010000R007_agong.fits"; - destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "wsa_201811010000R007_agong.fits"); // what the downloaded filename is to be - } - /* End of experiment */ - - AsyncHttpFileDownload ashd = AsyncHttpFileDownload(url, destinationpath, HttpFileDownload::Overwrite::Yes); - HttpRequest::RequestOptions opt = {}; - opt.requestTimeoutSeconds = 0; - ashd.start(opt); - ashd.wait(); + /* For experimentation with suntexturemanager & parker solor probe, hopefully this hardcoding can go away */ + if (ID == 1180) { + url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/WSA4.5/" + type + "/2017/09/wsa_201709280023R000_gong.fits"; + destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "wsa_201709280023R000_gong.fits"); // what the downloaded filename is to be } + // For parker solar probe + if (ID > 1190) { + url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/ADAPT_WSA4.5/ADAPT_WSA4.5_fieldlines/" + type + "/2018/11/2018-11-01T00-00-00.000.osfls"; + destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "2018-11-01T00-00-00.000.osfls"); // what the downloaded filename is to be + } + + if (ID == 1196) { + url = "https://iswa.ccmc.gsfc.nasa.gov/iswa_data_tree/model/solar/ADAPT_WSA4.5/" + type + "/2018/11/wsa_201811010000R007_agong.fits"; + destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + "wsa_201811010000R007_agong.fits"); // what the downloaded filename is to be + } + /* End of experiment */ + + AsyncHttpFileDownload ashd = AsyncHttpFileDownload(url, destinationpath, HttpFileDownload::Overwrite::Yes); + HttpRequest::RequestOptions opt = {}; + opt.requestTimeoutSeconds = 0; + ashd.start(opt); + ashd.wait(); +} - void WebFieldlinesManager::update(){ - const double openspaceTime = global::timeManager.time().j2000Seconds(); - const auto deltaTime = global::timeManager.deltaTime(); - const int speedThreshhold = 7200; // More than 2hrs a second would generally be unfeasable for a regular internet connection to operate at +void WebFieldlinesManager::update(){ + const double openspaceTime = global::timeManager.time().j2000Seconds(); + const auto deltaTime = global::timeManager.deltaTime(); + + // More than 2hrs a second would generally be unfeasable + // for a regular internet connection to operate at + const int speedThreshhold = 7200; - // Hold your horses, we don't want to do anything while deltatime is too high - if (abs(deltaTime) < speedThreshhold){ - - - // First it checks the time against the "bigger window" aka the long list of - // timesteps we know are available online. If it's outside that we're gonna need a new one - if (_webFieldlinesWindow.timeIsInTriggerTimesWebList(openspaceTime) && !_webFieldlinesWindow.expectedWindowIsOutOfBounds(openspaceTime) || _webFieldlinesWindow.checkWorkerEdgeMode()){ - - // Check if in window - if (_webFieldlinesWindow.edgeWindowReady() || _webFieldlinesWindow.timeIsInWindow(openspaceTime)) { - - // Check if in the edge of the window, so we can start downloading a new one - if (!_webFieldlinesWindow.edgeWindowReady() && _webFieldlinesWindow.timeIsInWindowMargin(openspaceTime, deltaTime)) { - // get new window - _webFieldlinesWindow.newWindow(openspaceTime); - hasUpdated = false; - } - else { - // If it's in the middle of the window, we can just sit back and relax and let the worker work - _webFieldlinesWindow.executeDownloadWorker(); - } - } - else { + // Hold your horses, we don't want to do anything while deltatime is too high + if (abs(deltaTime) < speedThreshhold) { + // First it checks the time against the "bigger window" aka the long list of + // timesteps we know are available online. + // If it's outside that we're gonna need a new one + if (_webFieldlinesWindow.timeIsInTriggerTimesWebList(openspaceTime) && + !_webFieldlinesWindow.expectedWindowIsOutOfBounds(openspaceTime) || + _webFieldlinesWindow.checkWorkerEdgeMode()) + { + // Check if in window + if (_webFieldlinesWindow.edgeWindowReady() || + _webFieldlinesWindow.timeIsInWindow(openspaceTime)) + { + // Check if in the edge of the window, + // so we can start downloading a new one + if (!_webFieldlinesWindow.edgeWindowReady() && + _webFieldlinesWindow.timeIsInWindowMargin(openspaceTime, deltaTime)) + { // get new window _webFieldlinesWindow.newWindow(openspaceTime); hasUpdated = false; } + else { + // If it's in the middle of the window, + // we can just sit back and relax and let the worker work + _webFieldlinesWindow.executeDownloadWorker(); + } } else { - _webFieldlinesWindow.getNewTriggerTimesWebList(openspaceTime); + // get new window + _webFieldlinesWindow.newWindow(openspaceTime); + hasUpdated = false; } - } + } + else { + _webFieldlinesWindow.getNewTriggerTimesWebList(openspaceTime); + } } +} - bool WebFieldlinesManager::checkIfWindowIsReadyToLoad() - { - return _webFieldlinesWindow.workerWindowIsReady(); - } +bool WebFieldlinesManager::checkIfWindowIsReadyToLoad() +{ + return _webFieldlinesWindow.workerWindowIsReady(); +} - void WebFieldlinesManager::resetWorker() { - _webFieldlinesWindow.rfsHasUpdated(); - } +void WebFieldlinesManager::resetWorker() { + _webFieldlinesWindow.rfsHasUpdated(); +} - // --------------------------- PRIVATE FUNCTIONS --------------------------- // +// --------------------------- PRIVATE FUNCTIONS --------------------------- // - std::string WebFieldlinesManager::getDirectory(){ - return _syncDir; - } +std::string WebFieldlinesManager::getDirectory(){ + return _syncDir; +} } // namespace openspace diff --git a/modules/fieldlinessequence/util/webfieldlineswindow.cpp b/modules/fieldlinessequence/util/webfieldlineswindow.cpp index 3735e65e26..0210c1b8da 100644 --- a/modules/fieldlinessequence/util/webfieldlineswindow.cpp +++ b/modules/fieldlinessequence/util/webfieldlineswindow.cpp @@ -34,160 +34,176 @@ namespace { - constexpr const char* _loggerCat = "FieldlinesSequence[ Web FLs Window ]"; + constexpr const char* _loggerCat = "WebFieldlinesWindow"; } // namespace namespace openspace{ - // --------------------------- CONSTRUCTORS ---------------------------------------// - WebFieldlinesWindow::WebFieldlinesWindow(std::string syncDir, std::string serverUrl, - std::vector& _sourceFiles, - std::vector& _startTimes, size_t& _nStates) - { - _window.backWidth = 3; - _window.forwardWidth = 3; + +WebFieldlinesWindow::WebFieldlinesWindow(std::string syncDir, std::string serverUrl, + std::vector& _sourceFiles, + std::vector& _startTimes, size_t& _nStates) +{ + _window.backWidth = 3; + _window.forwardWidth = 3; - _window.nTriggerTimes = static_cast(_nStates); + _window.nTriggerTimes = static_cast(_nStates); - for(int i = 0; i < _window.nTriggerTimes ; i++){ - _window.triggerTimes.push_back(std::make_pair(_startTimes[i], _sourceFiles[i])); - } - - rfs_nStates = &_nStates; - rfs_sourceFiles = &_sourceFiles; - rfs_startTimes = &_startTimes; - - _nAvailableWeb = 0; // haven't downloaded that list yet - - _worker = WebFieldlinesWorker(syncDir, serverUrl); - + for(int i = 0; i < _window.nTriggerTimes ; i++){ + _window.triggerTimes.push_back( + std::make_pair(_startTimes[i], _sourceFiles[i]) + ); } + + rfs_nStates = &_nStates; + rfs_sourceFiles = &_sourceFiles; + rfs_startTimes = &_startTimes; + + _nAvailableWeb = 0; // haven't downloaded that list yet + + _worker = WebFieldlinesWorker(syncDir, serverUrl); + +} + +// Returns true if time is inside the current window +bool WebFieldlinesWindow::timeIsInWindow(double time) { + return time >= windowStart() && time <= windowEnd(); +} - // -------------------------- PUBLIC FUNCTIONS -----------------------------------// - - // Returns true if time is inside the current window - bool WebFieldlinesWindow::timeIsInWindow(double time){ - if(time >= windowStart() && time <= windowEnd()) +// Returns true if time is at edge of the current window, +// and will probably need to update window +bool WebFieldlinesWindow::timeIsInWindowMargin(double time, double direction) { + const int threshold = 2; // base this on speed later + + if (direction > 0){ // If time is moving forward + if (time >= _window.triggerTimes[_window.nTriggerTimes - threshold].first) { + if (time > windowEnd()) { + return false; + } return true; + } else return false; } - - // Returns true if time is at edge of the current window, - // and will probably need to update window - bool WebFieldlinesWindow::timeIsInWindowMargin(double time, double direction){ - int threshold = 2; // base this on speed later - - if (direction > 0){ // If time is moving forward - if(time >= _window.triggerTimes[_window.nTriggerTimes - threshold].first){ - if(time > windowEnd()){ - return false; - } - return true; + else{ // If time is moving backwards + if (time <= _window.triggerTimes[threshold].first){ + if (time < windowStart()) { + return false; } - else return false; - } - else{ // If time is moving backwards - if(time <= _window.triggerTimes[threshold].first){ - if(time < windowStart()){ - return false; - } - return true; - } - else return false; - } - } - - /* Release the worker for execution, - Pick up a timestep to request for download, - Check if that timestep is already on disk, - repeated until a proper timestep to download is found, and start download */ - void WebFieldlinesWindow::executeDownloadWorker(){ - _worker.downloadWindow(_window.triggerTimes); - _worker.updateRFSSourceFiles(*rfs_sourceFiles); - _edgeWindowReady = false; - } - - void WebFieldlinesWindow::newWindow(double time){ - - auto it = std::find_if(_triggerTimesWeb.rbegin(), _triggerTimesWeb.rend(), [time](auto element) { - return time > element.first; - }); - - const int index = static_cast(std::distance(it, _triggerTimesWeb.rend())) - 1; - _window.triggerTimes.clear(); - _window.nTriggerTimes = 0; - - // This should be safe, because in the manager, it is checked wether the current position is within - // the boundaries with respect to back & forward width - for(int i = std::max(index - _window.backWidth,0); i <= std::min(index + _window.forwardWidth, static_cast(_triggerTimesWeb.size() -1)); i++){ - _window.triggerTimes.push_back(std::make_pair(_triggerTimesWeb[i].first, _triggerTimesWeb[i].second)); - _window.nTriggerTimes++; - } - - if (_worker.edgeMode()) - _edgeWindowReady = true; - - _worker.newWindowToDownload(); - } - - bool WebFieldlinesWindow::timeIsInTriggerTimesWebList(double time){ - // There are no files to compare with, just going to be false. - if(_nAvailableWeb == 0) return false; - - // Most cases, we are currently in the middle of a bunch of datasets, if we are not, lets get some new ones. - if(time >= (_triggerTimesWeb.front().first) && time <= (_triggerTimesWeb.back().first)) return true; - else - return false; + } + else return false; } +} + +/* + Release the worker for execution, + Pick up a timestep to request for download, + Check if that timestep is already on disk, + repeated until a proper timestep to download is found, and start download +*/ +void WebFieldlinesWindow::executeDownloadWorker() { + _worker.downloadWindow(_window.triggerTimes); + _worker.updateRFSSourceFiles(*rfs_sourceFiles); + _edgeWindowReady = false; +} - void WebFieldlinesWindow::getNewTriggerTimesWebList(double time){ - _worker.getRangeOfAvailableTriggerTimes(time, time, _triggerTimesWeb); - _nAvailableWeb = static_cast(_triggerTimesWeb.size()); +void WebFieldlinesWindow::newWindow(double time) { + + auto it = std::find_if( + _triggerTimesWeb.rbegin(), + _triggerTimesWeb.rend(), + [time](auto element) { + return time > element.first; + } + ); + + const int index = static_cast(std::distance(it, _triggerTimesWeb.rend())) - 1; + _window.triggerTimes.clear(); + _window.nTriggerTimes = 0; + + // This should be safe, because in the manager, + // it is checked wether the current position is within + // the boundaries with respect to back & forward width + for(int i = std::max(index - _window.backWidth,0); + i <= std::min(index + _window.forwardWidth, + static_cast(_triggerTimesWeb.size() -1)); + i++) { + _window.triggerTimes.push_back( + std::make_pair(_triggerTimesWeb[i].first, _triggerTimesWeb[i].second) + ); + _window.nTriggerTimes++; } - - bool WebFieldlinesWindow::workerWindowIsReady() - { - return _worker.windowIsComplete(); + if (_worker.edgeMode()) { + _edgeWindowReady = true; } - bool WebFieldlinesWindow::expectedWindowIsOutOfBounds(double time) { - auto resultForwards = std::find_if(_triggerTimesWeb.rbegin(), _triggerTimesWeb.rbegin() + _window.forwardWidth, [time](auto pair) { + _worker.newWindowToDownload(); +} + +bool WebFieldlinesWindow::timeIsInTriggerTimesWebList(double time) { + // There are no files to compare with, just going to be false. + if (_nAvailableWeb == 0) return false; + + // Most cases, we are currently in the middle of a bunch of datasets, + // if we are not, lets get some new ones. + return ( + time >= (_triggerTimesWeb.front().first) && + time <= (_triggerTimesWeb.back().first) + ); +} + +void WebFieldlinesWindow::getNewTriggerTimesWebList(double time) { + _worker.getRangeOfAvailableTriggerTimes(time, time, _triggerTimesWeb); + _nAvailableWeb = static_cast(_triggerTimesWeb.size()); +} + + +bool WebFieldlinesWindow::workerWindowIsReady() { + return _worker.windowIsComplete(); +} + +bool WebFieldlinesWindow::expectedWindowIsOutOfBounds(double time) { + auto resultForwards = std::find_if( + _triggerTimesWeb.rbegin(), + _triggerTimesWeb.rbegin() + _window.forwardWidth, + [time](auto pair) { return time > pair.first; - }); + } + ); - auto resultBackwards = std::find_if(_triggerTimesWeb.begin(), _triggerTimesWeb.begin() + _window.backWidth, [time](auto pair) { + auto resultBackwards = std::find_if( + _triggerTimesWeb.begin(), + _triggerTimesWeb.begin() + _window.backWidth, + [time](auto pair) { return time < pair.first; - }); + } + ); - return resultForwards != _triggerTimesWeb.rbegin() + _window.forwardWidth || resultBackwards != _triggerTimesWeb.begin() + _window.backWidth; - } + return resultForwards != _triggerTimesWeb.rbegin() + _window.forwardWidth || + resultBackwards != _triggerTimesWeb.begin() + _window.backWidth; +} - void WebFieldlinesWindow::rfsHasUpdated() { - _worker.flagUpdated(); - } +void WebFieldlinesWindow::rfsHasUpdated() { + _worker.flagUpdated(); +} - bool WebFieldlinesWindow::checkWorkerEdgeMode(){ - return _worker.edgeMode(); - } +bool WebFieldlinesWindow::checkWorkerEdgeMode(){ + return _worker.edgeMode(); +} - bool WebFieldlinesWindow::edgeWindowReady(){ - return _edgeWindowReady; - } - - // -------------------------- PRIVATE FUNCTIONS -----------------------------------// - // Returns first trigger of window - double WebFieldlinesWindow::windowStart(){ - return _window.triggerTimes.front().first; - } - // Returns last trigger of window - double WebFieldlinesWindow::windowEnd(){ - return _window.triggerTimes.back().first; - } +bool WebFieldlinesWindow::edgeWindowReady(){ + return _edgeWindowReady; +} +// Returns first trigger of window +double WebFieldlinesWindow::windowStart(){ + return _window.triggerTimes.front().first; +} - - +// Returns last trigger of window +double WebFieldlinesWindow::windowEnd(){ + return _window.triggerTimes.back().first; +} } // namespace openspace diff --git a/modules/fieldlinessequence/util/webfieldlinesworker.cpp b/modules/fieldlinessequence/util/webfieldlinesworker.cpp index 9fc2d238f1..8aa2f086a2 100644 --- a/modules/fieldlinessequence/util/webfieldlinesworker.cpp +++ b/modules/fieldlinessequence/util/webfieldlinesworker.cpp @@ -39,156 +39,218 @@ namespace { } // namespace namespace openspace{ - // CONSTRUCTOR - WebFieldlinesWorker::WebFieldlinesWorker(std::string syncDir, std::string serverUrl) - : _syncDir(syncDir), _serverUrl(serverUrl) { - // Maybe to be used - _endpointSingleDownload = _serverUrl; // should be set by argument to be more general [DEPRICATED FOR NOW, DO WE NEED THIS?] - } - // Destructor, deleting all files that were downloaded during the run. - WebFieldlinesWorker::~WebFieldlinesWorker() { - // Cancel any potential download - if (_downloading && _downloading->hasStarted()) - _downloading->wait(); - // Remove all files - std::vector temp = ghoul::filesystem::Directory(_syncDir).readFiles(); - // Sneaky check, just want to make sure it is only deleting fieldlines for now - if (temp.back().substr(temp.back().size() - 5) == "osfls" && temp.front().substr(temp.front().size() - 5) == "osfls") { - std::for_each(temp.begin(), temp.end(), [&](auto it) { - FileSys.deleteFile(it); - }); - } +WebFieldlinesWorker::WebFieldlinesWorker(std::string syncDir, std::string serverUrl) +: _syncDir(syncDir), _serverUrl(serverUrl) { + // Maybe to be used + _endpointSingleDownload = _serverUrl; + // should be set by argument to be more general + // [DEPRICATED FOR NOW, DO WE NEED THIS?] +} + +WebFieldlinesWorker::~WebFieldlinesWorker() { + // Deleting all files that were downloaded during the run. + // Cancel any potential download + if (_downloading && _downloading->hasStarted()) + _downloading->wait(); + // Remove all files + std::vector temp = + ghoul::filesystem::Directory(_syncDir).readFiles(); + // Sneaky check, just want to make sure it is only deleting fieldlines for now + if (temp.back().substr(temp.back().size() - 5) == "osfls" && + temp.front().substr(temp.front().size() - 5) == "osfls") + { + std::for_each(temp.begin(), temp.end(), [&](auto it) { + FileSys.deleteFile(it); + }); } +} - // PUBLIC FUNCTIONS - void WebFieldlinesWorker::getRangeOfAvailableTriggerTimes(double startTime, double endTime, std::vector> &_triggerTimesWeb){ +// PUBLIC FUNCTIONS +void WebFieldlinesWorker::getRangeOfAvailableTriggerTimes(double startTime, + double endTime, + std::vector> &_triggerTimesWeb) +{ + // We don't want to keep sending request, if we just get empty responses. + if (!_noMoreRequests) { + auto time = global::timeManager.time().ISO8601(); + Time maxTime; + Time minTime; - // We don't want to keep sending request, if we just get empty responses. - if (!_noMoreRequests) { - auto time = global::timeManager.time().ISO8601(); - Time maxTime; - Time minTime; - const int timeSpann = 2*86400; // The timespann we would like to request (in seconds) [ 1 day = 86400, 1 week = 604800 ] - const std::string dataID = "dataID"; - const std::string files = "files"; - std::string stringResult; - std::vector urlList; - std::vector timeList; + // The timespan we would like to request (in seconds) + // [ 1 day = 86400, 1 week = 604800 ] + const int timeSpan = 2*86400; - maxTime.setTime(time); - minTime.setTime(time); - maxTime.advanceTime(timeSpann); - minTime.advanceTime(-timeSpann); + const std::string dataID = "dataID"; + const std::string files = "files"; + std::string stringResult; + std::vector urlList; + std::vector timeList; - std::string url = _serverUrl + "&time.min=" + minTime.ISO8601() + "&time.max=" + maxTime.ISO8601(); - SyncHttpMemoryDownload mmryDld = SyncHttpMemoryDownload(url); - HttpRequest::RequestOptions opt = {}; - opt.requestTimeoutSeconds = 0; - mmryDld.download(opt); + maxTime.setTime(time); + minTime.setTime(time); + maxTime.advanceTime(timeSpan); + minTime.advanceTime(-timeSpan); - std::transform(mmryDld.downloadedData().begin(), mmryDld.downloadedData().end(), std::back_inserter(stringResult), - [](char c) { + std::string url = _serverUrl + + "&time.min=" + minTime.ISO8601() + + "&time.max=" + maxTime.ISO8601(); + + SyncHttpMemoryDownload mmryDld = SyncHttpMemoryDownload(url); + HttpRequest::RequestOptions opt = {}; + opt.requestTimeoutSeconds = 0; + mmryDld.download(opt); + + // TODO emiax: std::copy or similar should be possible here + std::transform( + mmryDld.downloadedData().begin(), + mmryDld.downloadedData().end(), + std::back_inserter(stringResult), + [](char c) { return c; - }); - - auto res = json::parse(stringResult); - auto temp = std::move(_triggerTimesWeb); - _triggerTimesWeb.clear(); // Clear old big window - - for (auto& elem : res[files]) { - timeList.push_back(elem["timestamp"]); - urlList.push_back(elem["url"]); } + ); - // Just want to make sure there is no error in the parsing, so taking the smallest dimension, but should be the same - for (int i = 0; i < std::min(timeList.size(), urlList.size()); i++) { - _triggerTimesWeb.push_back(std::make_pair(triggerTimeString2Double(timeList[i]), urlList[i])); - } + auto res = json::parse(stringResult); + auto temp = std::move(_triggerTimesWeb); + _triggerTimesWeb.clear(); // Clear old big window - std::sort(_triggerTimesWeb.begin(), _triggerTimesWeb.end()); // If by any chance it would not sort in properly - - if (_triggerTimesWeb.size() == 0 || std::equal(temp.begin(),temp.end(),_triggerTimesWeb.begin(), _triggerTimesWeb.end())) // We got an empty response or the same response twice, stahp it - _strikes++; - - if (_strikes % 2 == 0){ // We have got 2 strikes, no more requests for you, Mr.Sir. - _bigWindowHasData = (_triggerTimesWeb.size() > 0); - _noMoreRequests = true; - acceptableToStartRequestingAgain = std::make_pair(minTime.j2000Seconds(), maxTime.j2000Seconds()); - } + for (auto& elem : res[files]) { + timeList.push_back(elem["timestamp"]); + urlList.push_back(elem["url"]); } + + // Just want to make sure there is no error in the parsing, + // so taking the smallest dimension, but should be the same + for (int i = 0; i < std::min(timeList.size(), urlList.size()); i++) { + _triggerTimesWeb.push_back( + std::make_pair(triggerTimeString2Double(timeList[i]), urlList[i]) + ); + } + + std::sort(_triggerTimesWeb.begin(), _triggerTimesWeb.end()); + // If by any chance it would not sort in properly + + if (_triggerTimesWeb.size() == 0 || + std::equal(temp.begin(), + temp.end(), + _triggerTimesWeb.begin(), + _triggerTimesWeb.end() + ) + ) { // We got an empty response or the same response twice, stahp it + _strikes++; + } + + // We have got 2 strikes, no more requests for you, Mr.Sir. + if (_strikes % 2 == 0) { + _bigWindowHasData = (_triggerTimesWeb.size() > 0); + _noMoreRequests = true; + acceptableToStartRequestingAgain = + std::make_pair(minTime.j2000Seconds(), maxTime.j2000Seconds()); + } + } +} + +// Download all files in the current window +// This function starts usually in the middle of the window and proceeds to download all +// future timesteps, then steps backwards from the startingpoint +// TODO(Axel): Different behaviour depending on direction the user is moving in, +// might be wanted? +void WebFieldlinesWorker::downloadWindow( + std::vector> triggerTimes) +{ + // Helper variables + int startingPoint = triggerTimes.size() / 2; + bool downloaded = false; + bool oneUpdate = false; + bool fastDownload = global::timeManager.deltaTime() > 1800.0; + + if (fastDownload) { + startingPoint = triggerTimes.size() - 1; } - // Download all files in the current window - // This function starts usually in the middle of the window and proceeds to download all future timesteps, - // then steps backwards from the startingpoint - //TODO(Axel): Different behaviour depending on direction the user is moving in, might be wanted? - void WebFieldlinesWorker::downloadWindow(std::vector> triggerTimes) { - - // Helper variables - int startingPoint = triggerTimes.size() / 2; - bool downloaded = false; - bool oneUpdate = false; - bool fastDownload = global::timeManager.deltaTime() > 1800.0; + // Is there a download thread to be joined and added to the list? + if (_downloading && _downloading->hasSucceeded() && _newWindow) { + _downloading->wait(); + addToDownloadedList(_latestDownload); + _readyToDownload = true; + // This is to trigger one update of the fieldline timestamp that the user is + // currently on, while the rest of them will be downloaded in the background, + // and updated once ready + if (_latestDownload.second == triggerTimes[startingPoint].second) + oneUpdate = true; + } - if (fastDownload) startingPoint = triggerTimes.size() - 1; + if (_readyToDownload) { + // Forwards + std::vector>::iterator forwardIt = + triggerTimes.begin(); - // Is there a download thread to be joined and added to the list? - if (_downloading && _downloading->hasSucceeded() && _newWindow) { - _downloading->wait(); - addToDownloadedList(_latestDownload); - _readyToDownload = true; - // This is to trigger one update of the fieldline timestamp that the user is currently on, - // while the rest of them will be downloaded in the background, and updated once ready - if (_latestDownload.second == triggerTimes[startingPoint].second) - oneUpdate = true; - } + std::advance(forwardIt, startingPoint); + std::for_each(forwardIt, triggerTimes.end(), [this, &downloaded](auto it) { + if (!downloaded && !fileIsOnDisk(it.first)) { + downloadOsfls(it); + downloaded = true; + _doneUpdating = false; + } + }); - if (_readyToDownload) { - // Forwards - std::vector>::iterator forwardIt = triggerTimes.begin(); - std::advance(forwardIt, startingPoint); - std::for_each(forwardIt, triggerTimes.end(), [this, &downloaded](auto it) { - if (!downloaded && !fileIsOnDisk(it.first)) { - downloadOsfls(it); - downloaded = true; - _doneUpdating = false; - } - }); - - // Backwards - if (!downloaded) { - std::for_each(triggerTimes.rbegin(), triggerTimes.rend(), [this, &downloaded](auto it) { + // Backwards + if (!downloaded) { + std::for_each( + triggerTimes.rbegin(), + triggerTimes.rend(), + [this, &downloaded](auto it) { if (!downloaded && !fileIsOnDisk(it.first)) { downloadOsfls(it); downloaded = true; _doneUpdating = false; } - }); - } - } - - if ((!downloaded && !_doneUpdating && _newWindow && _readyToDownload && _downloadedSomething) || oneUpdate && _downloadedSomething) { - // If reach this point, we now know that we have downloaded all the sets - _readyToUpdateSourceFiles = true; - if(!oneUpdate) - _newWindow = false; - oneUpdate = false; + } + ); } } - // Updates the list of available sourcefiles, owned by renderablefieldlinessequence. - void WebFieldlinesWorker::updateRFSSourceFiles(std::vector& _sourceFiles) { - if (_readyToUpdateSourceFiles) { - std::vector toInsert; - std::transform(_downloadedTriggerTimes.begin(), _downloadedTriggerTimes.end(), std::back_inserter(toInsert), [this](auto const& pair) { return _syncDir + FileSys.PathSeparator + pair.second; }); - auto sourcePtr = _sourceFiles.begin(); - std::for_each(toInsert.begin(), toInsert.end(), [&sourcePtr, &_sourceFiles](auto insertableElement) { + if ((!downloaded && + !_doneUpdating && + _newWindow && + _readyToDownload && + _downloadedSomething) || + oneUpdate && _downloadedSomething) + { + // If reach this point, we now know that we have downloaded all the sets + _readyToUpdateSourceFiles = true; + if (!oneUpdate) { + _newWindow = false; + } + oneUpdate = false; + } +} +// Updates the list of available sourcefiles, owned by renderablefieldlinessequence. +void WebFieldlinesWorker::updateRFSSourceFiles(std::vector& _sourceFiles) { + if (_readyToUpdateSourceFiles) { + std::vector toInsert; + std::transform( + _downloadedTriggerTimes.begin(), + _downloadedTriggerTimes.end(), + std::back_inserter(toInsert), + [this] (auto const& pair) { + return _syncDir + FileSys.PathSeparator + pair.second; + } + ); + + auto sourcePtr = _sourceFiles.begin(); + std::for_each( + toInsert.begin(), + toInsert.end(), + [&sourcePtr, &_sourceFiles] (auto insertableElement) { for (sourcePtr; sourcePtr != _sourceFiles.end(); sourcePtr++) { - if (sourcePtr != (--_sourceFiles.end())) { - if (insertableElement > *sourcePtr && insertableElement < *sourcePtr++) { + if (insertableElement > *sourcePtr && + insertableElement < *sourcePtr++) + { _sourceFiles.insert(sourcePtr++, insertableElement); break; } @@ -204,122 +266,158 @@ namespace openspace{ if (sourcePtr == _sourceFiles.end()) { sourcePtr = _sourceFiles.insert(sourcePtr, insertableElement); } - }); + } + ); - // Set flags to let anyone interested know that this has been done. - _readyToUpdateSourceFiles = false; - _doneUpdating = true; - } + // Set flags to let anyone interested know that this has been done. + _readyToUpdateSourceFiles = false; + _doneUpdating = true; + } - } +} - bool WebFieldlinesWorker::windowIsComplete(){ - return _doneUpdating; - } +bool WebFieldlinesWorker::windowIsComplete() { + return _doneUpdating; +} - void WebFieldlinesWorker::flagUpdated() { - _doneUpdating = false; - } +void WebFieldlinesWorker::flagUpdated() { + _doneUpdating = false; +} - void WebFieldlinesWorker::newWindowToDownload(){ - _newWindow = true; - _downloadedSomething = false; - } +void WebFieldlinesWorker::newWindowToDownload() { + _newWindow = true; + _downloadedSomething = false; +} - bool WebFieldlinesWorker::edgeMode(){ - if (global::timeManager.time().j2000Seconds() < acceptableToStartRequestingAgain.first || global::timeManager.time().j2000Seconds() > acceptableToStartRequestingAgain.second) { - _noMoreRequests = false; - _bigWindowHasData = false; - } - return _noMoreRequests && _bigWindowHasData; +bool WebFieldlinesWorker::edgeMode() { + const double currentTime = global::timeManager.time().j2000Seconds(); + + if (currentTime < acceptableToStartRequestingAgain.first || + currentTime > acceptableToStartRequestingAgain.second) + { + _noMoreRequests = false; + _bigWindowHasData = false; } + return _noMoreRequests && _bigWindowHasData; +} - std::string WebFieldlinesWorker::downloadOsfls(std::pair downloadKey){ - _downloadedSomething = true; - _latestDownload = downloadKey; - // YYYY-MM-DDTHH-MM-SS.sss.osfls - Might change - const int fileNameLength = 29; - const std::string fileName = downloadKey.second.substr(downloadKey.second.size()-fileNameLength); - std::string url = downloadKey.second; - std::string destinationpath = absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + fileName); // what the downloaded filename is to be - _downloading = std::make_unique(url, destinationpath, HttpFileDownload::Overwrite::Yes); - HttpRequest::RequestOptions opt = {}; - opt.requestTimeoutSeconds = 0; - _downloading->start(opt); - _readyToDownload = false; - return destinationpath; - } +std::string WebFieldlinesWorker::downloadOsfls(std::pair downloadKey) +{ + _downloadedSomething = true; + _latestDownload = downloadKey; - // This function searches for triggerTime in _downloadedTriggerTimes, - // to see weather a file has already been downloaded or not - bool WebFieldlinesWorker::fileIsOnDisk(double triggerTime){ - if(std::find_if(_downloadedTriggerTimes.begin(), _downloadedTriggerTimes.end(), - [&triggerTime](std::pair const &element){ + // YYYY-MM-DDTHH-MM-SS.sss.osfls - Might change + const int fileNameLength = 29; + const std::string fileName = + downloadKey.second.substr(downloadKey.second.size() - fileNameLength); + + std::string url = downloadKey.second; + std::string destinationPath = + absPath(_syncDir + ghoul::filesystem::FileSystem::PathSeparator + fileName); + // what the downloaded filename is to be + + _downloading = std::make_unique( + url, destinationPath, HttpFileDownload::Overwrite::Yes + ); + + HttpRequest::RequestOptions opt = {}; + opt.requestTimeoutSeconds = 0; + + _downloading->start(opt); + _readyToDownload = false; + + return destinationPath; +} + +// This function searches for triggerTime in _downloadedTriggerTimes, +// to see weather a file has already been downloaded or not +bool WebFieldlinesWorker::fileIsOnDisk(double triggerTime) { + return std::find_if( + _downloadedTriggerTimes.begin(), + _downloadedTriggerTimes.end(), + [&triggerTime] (std::pair const &element) { return element.first == triggerTime; - }) != _downloadedTriggerTimes.end()) - return true; - else - return false; - } + }) != _downloadedTriggerTimes.end(); +} - void WebFieldlinesWorker::checkForExistingData(std::vector>& _triggerTimesWeb, - std::vector>& _triggerTimesOnDisk){ - int indexWeb = 0; - int indexDisk = 0; - - while(true){ - if(compareTimetriggersEqual(std::get<0>(_triggerTimesWeb[indexWeb]), _triggerTimesOnDisk[indexDisk].first)){ - std::get<1>(_triggerTimesWeb[indexWeb]) = _triggerTimesOnDisk[indexDisk].second; - } +void WebFieldlinesWorker::checkForExistingData( + std::vector>& _triggerTimesWeb, + std::vector>& _triggerTimesOnDisk) +{ + int indexWeb = 0; + int indexDisk = 0; + + // TODO emiax: This looks like an infinite loop. + // Need a way to break out from this when destroying the worker..? + while (true) { + if (compareTimetriggersEqual( + std::get<0>(_triggerTimesWeb[indexWeb]), + _triggerTimesOnDisk[indexDisk].first + )) { + std::get<1>(_triggerTimesWeb[indexWeb]) = + _triggerTimesOnDisk[indexDisk].second; } } +} - // PRIVATE FUNCTIONS - void WebFieldlinesWorker::parseTriggerTimesList(std::string s, std::vector> &_triggerTimesWeb){ - // Turn into stringstream to parse the comma-delimited string into vector - std::stringstream ss(s); - char c; - std::string sub; - while(ss >> c) - { - if (c == '[' || c == ']' || c == '"' ) continue; - else if (c == ','){ - _triggerTimesWeb.push_back(std::make_tuple(triggerTimeString2Double(sub), sub, -1)); - sub.clear(); - } - else sub += c; +void WebFieldlinesWorker::parseTriggerTimesList(std::string s, + std::vector> &_triggerTimesWeb) +{ + // Turn into stringstream to parse the comma-delimited string into vector + std::stringstream ss(s); + char c; + std::string sub; + while(ss >> c) { + if (c == '[' || c == ']' || c == '"') { + continue; } - _triggerTimesWeb.push_back(std::make_tuple(triggerTimeString2Double(sub), sub, -1)); - } - - double WebFieldlinesWorker::triggerTimeString2Double(std::string s){ - s.replace(13, 1, ":"); - s.replace(16, 1, ":"); - Time time = Time(); - time.setTime(s); - return (time.j2000Seconds() /*- 69.185013294*/); // openspace timeconverter gives an error. but we're gonna be consistent with the error - } - - void WebFieldlinesWorker::triggerTimeDouble2String(double i, std::string& s){ - double temp = i /*+ 69.185013294*/; - Time time = Time(); - time.setTime(temp); - s = time.ISO8601(); - s.replace(13, 1, "-"); - s.replace(16, 1, "-"); - } - - // Inserts the pair in sorted order - void WebFieldlinesWorker::addToDownloadedList(std::pair pair){ - const std::string fileName = pair.second.substr(pair.second.size() - 29); - _downloadedTriggerTimes.insert(std::upper_bound(_downloadedTriggerTimes.begin(),_downloadedTriggerTimes.end(),pair),std::make_pair(pair.first, fileName)); + if (c == ',') { + _triggerTimesWeb.push_back( + std::make_tuple(triggerTimeString2Double(sub), sub, -1) + ); + sub.clear(); + } + else { + sub += c; + } } - bool WebFieldlinesWorker::compareTimetriggersEqual(double first, double second){ - double eps = 100.0; - if(first > second - eps && first < second + eps) return true; - return false; - } + _triggerTimesWeb.push_back(std::make_tuple(triggerTimeString2Double(sub), sub, -1)); +} + +double WebFieldlinesWorker::triggerTimeString2Double(std::string s){ + s.replace(13, 1, ":"); + s.replace(16, 1, ":"); + Time time = Time(); + time.setTime(s); + return time.j2000Seconds() /*- 69.185013294*/; + // openspace timeconverter gives an error. + // but we're gonna be consistent with the error +} + +void WebFieldlinesWorker::triggerTimeDouble2String(double i, std::string& s) { + double temp = i /*+ 69.185013294*/; + Time time = Time(); + time.setTime(temp); + s = time.ISO8601(); + s.replace(13, 1, "-"); + s.replace(16, 1, "-"); +} + +// Inserts the pair in sorted order +void WebFieldlinesWorker::addToDownloadedList(std::pair pair) { + const std::string fileName = pair.second.substr(pair.second.size() - 29); + _downloadedTriggerTimes.insert( + std::upper_bound( + _downloadedTriggerTimes.begin(), _downloadedTriggerTimes.end(), pair + ), std::make_pair(pair.first, fileName) + ); +} + +bool WebFieldlinesWorker::compareTimetriggersEqual(double first, double second) { + const double eps = 100.0; + return first > second - eps && first < second + eps; +} } // namespace openspace