From ebd69d13333055de4a466c40c697234b97412287 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Thu, 1 Mar 2018 19:55:50 -0500 Subject: [PATCH 01/24] Cleanup FieldlineSequence classes --- data/assets/util/asset_helper.asset | 1 - modules/fieldlinessequence/CMakeLists.txt | 1 - .../fieldlinessequencemodule.cpp | 29 +- .../fieldlinessequencemodule.h | 4 + .../renderablefieldlinessequence.cpp | 1037 ++++++++++++++++- .../rendering/renderablefieldlinessequence.h | 24 +- .../renderablefieldlinessequencesetup.cpp | 845 -------------- modules/fieldlinessequence/util/commons.cpp | 8 +- modules/fieldlinessequence/util/commons.h | 16 +- .../util/fieldlinesstate.cpp | 161 +-- .../fieldlinessequence/util/fieldlinesstate.h | 45 +- .../util/kameleonfieldlinehelper.cpp | 149 +-- .../util/kameleonfieldlinehelper.h | 9 +- 13 files changed, 1210 insertions(+), 1119 deletions(-) delete mode 100644 modules/fieldlinessequence/rendering/renderablefieldlinessequencesetup.cpp diff --git a/data/assets/util/asset_helper.asset b/data/assets/util/asset_helper.asset index cb70c7b739..bc738cf2e9 100644 --- a/data/assets/util/asset_helper.asset +++ b/data/assets/util/asset_helper.asset @@ -93,7 +93,6 @@ local requestAll = function (asset, directory) return End=='' or string.sub(String,-string.len(End))==End end - -- directory = asset.localResource('') .. directory local files = openspace.walkDirectoryFiles(asset.localResource('') .. directory, true) for _, file in pairs(files) do if file:ends('.asset') then diff --git a/modules/fieldlinessequence/CMakeLists.txt b/modules/fieldlinessequence/CMakeLists.txt index 97d9bd6559..c17e1937a8 100644 --- a/modules/fieldlinessequence/CMakeLists.txt +++ b/modules/fieldlinessequence/CMakeLists.txt @@ -34,7 +34,6 @@ source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablefieldlinessequence.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablefieldlinessequencesetup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/util/fieldlinesstate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/util/commons.cpp ${CMAKE_CURRENT_SOURCE_DIR}/util/kameleonfieldlinehelper.cpp diff --git a/modules/fieldlinessequence/fieldlinessequencemodule.cpp b/modules/fieldlinessequence/fieldlinessequencemodule.cpp index f1094fd14d..a9b75a07b7 100644 --- a/modules/fieldlinessequence/fieldlinessequencemodule.cpp +++ b/modules/fieldlinessequence/fieldlinessequencemodule.cpp @@ -24,19 +24,40 @@ #include +#include #include #include #include - +#include #include +#include -#include +namespace { + constexpr const char* DefaultTransferfunctionSource = +R"( +width 5 +lower 0.0 +upper 1.0 +mappingkey 0.0 0 0 0 255 +mappingkey 0.25 255 0 0 255 +mappingkey 0.5 255 140 0 255 +mappingkey 0.75 255 255 0 255 +mappingkey 1.0 255 255 255 255 +)"; +} // namespace namespace openspace { +std::string FieldlinesSequenceModule::DefaultTransferFunctionFile = ""; + FieldlinesSequenceModule::FieldlinesSequenceModule() - : OpenSpaceModule("FieldlinesSequence") -{} + : OpenSpaceModule(Name) +{ + DefaultTransferFunctionFile = absPath("${TEMPORARY}/default_transfer_function.txt"); + + std::ofstream file(DefaultTransferFunctionFile); + file << DefaultTransferfunctionSource; +} void FieldlinesSequenceModule::internalInitialize(const ghoul::Dictionary&) { auto factory = FactoryManager::ref().factory(); diff --git a/modules/fieldlinessequence/fieldlinessequencemodule.h b/modules/fieldlinessequence/fieldlinessequencemodule.h index f967b5d2a3..a855a215eb 100644 --- a/modules/fieldlinessequence/fieldlinessequencemodule.h +++ b/modules/fieldlinessequence/fieldlinessequencemodule.h @@ -31,7 +31,11 @@ namespace openspace { class FieldlinesSequenceModule : public OpenSpaceModule { public: + constexpr static const char* Name = "FieldlinesSequence"; + FieldlinesSequenceModule(); + + static std::string DefaultTransferFunctionFile; private: void internalInitialize(const ghoul::Dictionary&) override; diff --git a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp index 0e65f536fd..c6f6bb2cfd 100644 --- a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp +++ b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.cpp @@ -24,25 +24,939 @@ #include +#include +#include #include #include +#include #include +#include +#include +#include #include - +#include #include #include +#include +#include namespace { - std::string _loggerCat = "RenderableFieldlinesSequence"; + constexpr const char* _loggerCat = "RenderableFieldlinesSequence"; const GLuint VaPosition = 0; // MUST CORRESPOND TO THE SHADER PROGRAM const GLuint VaColor = 1; // MUST CORRESPOND TO THE SHADER PROGRAM const GLuint VaMasking = 2; // MUST CORRESPOND TO THE SHADER PROGRAM + + // ----- KEYS POSSIBLE IN MODFILE. EXPECTED DATA TYPE OF VALUE IN [BRACKETS] ----- // + // ---------------------------- MANDATORY MODFILE KEYS ---------------------------- // + const char* KeyInputFileType = "InputFileType"; // [STRING] "cdf", "json" or "osfls" + const char* KeySourceFolder = "SourceFolder"; // [STRING] should be path to folder containing the input files + + // ---------------------- MANDATORY INPUT TYPE SPECIFIC KEYS ---------------------- // + const char* KeyCdfSeedPointFile = "SeedPointFile"; // [STRING] Path to a .txt file containing seed points + const char* KeyJsonSimulationModel = "SimulationModel"; // [STRING] Currently supports: "batsrus", "enlil" & "pfss" + + // ----------------------- OPTIONAL INPUT TYPE SPECIFIC KEYS ---------------------- // + const char* KeyCdfExtraVariables = "ExtraVariables"; // [STRING ARRAY] + const char* KeyCdfTracingVariable = "TracingVariable"; // [STRING] + const char* KeyJsonScalingFactor = "ScaleToMeters"; // [STRING] + const char* KeyOslfsLoadAtRuntime = "LoadAtRuntime"; // [BOOLEAN] If value False => Load in initializing step and store in RAM + + // ---------------------------- OPTIONAL MODFILE KEYS ---------------------------- // + const char* KeyColorTablePaths = "ColorTablePaths"; // [STRING ARRAY] Values should be paths to .txt files + const char* KeyColorTableRanges = "ColorTableRanges";// [VEC2 ARRAY] Values should be entered as {X, Y}, where X & Y are numbers + const char* KeyMaskingRanges = "MaskingRanges"; // [VEC2 ARRAY] Values should be entered as {X, Y}, where X & Y are numbers + const char* KeyOutputFolder = "OutputFolder"; // [STRING] Value should be path to folder where states are saved (JSON/CDF input => osfls output & oslfs input => JSON output) + + // ------------- POSSIBLE STRING VALUES FOR CORRESPONDING MODFILE KEY ------------- // + const char* ValueInputFileTypeCdf = "cdf"; + const char* ValueInputFileTypeJson = "json"; + const char* ValueInputFileTypeOsfls = "osfls"; + + // --------------------------------- Property Info -------------------------------- // + static const openspace::properties::Property::PropertyInfo ColorMethodInfo = { + "colorMethod", + "Color Method", + "Color lines uniformly or using color tables based on extra quantities like, for " + "examples, temperature or particle density." + }; + static const openspace::properties::Property::PropertyInfo ColorQuantityInfo = { + "colorQuantity", + "Quantity to Color By", + "Quantity used to color lines if the 'By Quantity' color method is selected." + }; + static const openspace::properties::Property::PropertyInfo ColorQuantityMinInfo = { + "colorQuantityMin", + "ColorTable Min Value", + "Value to map to the lowest end of the color table." + }; + static const openspace::properties::Property::PropertyInfo ColorQuantityMaxInfo = { + "colorQuantityMax", + "ColorTable Max Value", + "Value to map to the highest end of the color table." + }; + static const openspace::properties::Property::PropertyInfo ColorTablePathInfo = { + "colorTablePath", + "Path to Color Table", + "Color Table/Transfer Function to use for 'By Quantity' coloring." + }; + static const openspace::properties::Property::PropertyInfo ColorUniformInfo = { + "uniform", + "Uniform Line Color", + "The uniform color of lines shown when 'Color Method' is set to 'Uniform'." + }; + static const openspace::properties::Property::PropertyInfo ColorUseABlendingInfo = { + "aBlendingEnabled", + "Additive Blending", + "Activate/deactivate additive blending." + }; + static const openspace::properties::Property::PropertyInfo DomainEnabledInfo = { + "domainEnabled", + "Domain Limits", + "Enable/Disable domain limits" + }; + static const openspace::properties::Property::PropertyInfo DomainXInfo = { + "limitsX", + "X-limits", + "Valid range along the X-axis. [Min, Max]" + }; + static const openspace::properties::Property::PropertyInfo DomainYInfo = { + "limitsY", + "Y-limits", + "Valid range along the Y-axis. [Min, Max]" + }; + static const openspace::properties::Property::PropertyInfo DomainZInfo = { + "limitsZ", + "Z-limits", + "Valid range along the Z-axis. [Min, Max]" + }; + static const openspace::properties::Property::PropertyInfo DomainRInfo = { + "limitsR", + "Radial limits", + "Valid radial range. [Min, Max]" + }; + static const openspace::properties::Property::PropertyInfo FlowColorInfo = { + "color", + "Color", + "Color of particles." + }; + static const openspace::properties::Property::PropertyInfo FlowEnabledInfo = { + "flowEnabled", + "Flow Direction", + "Toggles the rendering of moving particles along the lines. Can, for example, " + "illustrate magnetic flow." + }; + static const openspace::properties::Property::PropertyInfo FlowReversedInfo = { + "reversed", + "Reversed Flow", + "Toggle to make the flow move in the opposite direction." + }; + static const openspace::properties::Property::PropertyInfo FlowParticleSizeInfo = { + "particleSize", + "Particle Size", + "Size of the particles." + }; + static const openspace::properties::Property::PropertyInfo FlowParticleSpacingInfo = { + "particleSpacing", + "Particle Spacing", + "Spacing inbetween particles." + }; + static const openspace::properties::Property::PropertyInfo FlowSpeedInfo = { + "speed", + "Speed", + "Speed of the flow." + }; + static const openspace::properties::Property::PropertyInfo MaskingEnabledInfo = { + "maskingEnabled", + "Masking", + "Enable/disable masking. Use masking to show lines where a given quantity is " + "within a given range, for example, if you only want to see where the " + "temperature is between 10 and 20 degrees. Also used for masking out line " + "topologies like solar wind & closed lines." + }; + static const openspace::properties::Property::PropertyInfo MaskingMinInfo = { + "maskingMinLimit", + "Lower Limit", + "Lower limit of the valid masking range" + }; + static const openspace::properties::Property::PropertyInfo MaskingMaxInfo = { + "maskingMaxLimit", + "Upper Limit", + "Upper limit of the valid masking range" + }; + static const openspace::properties::Property::PropertyInfo MaskingQuantityInfo = { + "maskingQuantity", + "Quantity used for Masking", + "Quantity used for masking." + }; + static const openspace::properties::Property::PropertyInfo OriginButtonInfo = { + "focusCameraOnParent", + "Focus Camera", + "Focus camera on parent." + }; + static const openspace::properties::Property::PropertyInfo TimeJumpButtonInfo = { + "timeJumpToStart", + "Jump to Start Of Sequence", + "Performs a time jump to the start of the sequence." + }; + + enum class SourceFileType : int { + Cdf = 0, + Json, + Osfls, + Invalid + }; + + float stringToFloat(const std::string input, const float backupValue = 0.f) { + float tmp; + try { + tmp = std::stof(input); + } + catch (const std::invalid_argument& ia) { + LWARNING("Invalid argument: " << ia.what() << ". '" << input << + "' is NOT a valid number!"); + return backupValue; + } + return tmp; + } } // namespace namespace openspace { +using namespace properties; -void RenderableFieldlinesSequence::deinitialize() { +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)) { + 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)) { + return; + } + break; + case SourceFileType::Json: + if (!loadJsonStatesIntoRAM(outputFolderPath)) { + return; + } + break; + case SourceFileType::Osfls: + extractOsflsInfoFromDictionary(); + if (_loadingStatesDynamically) { + if (!prepareForOsflsStreaming()) { + return; + } + } + else { + loadOsflsStatesIntoRAM(outputFolderPath); + } + break; + default: + return; + } + + // dictionary is no longer needed as everything is extracted + _dictionary.reset(); + + // 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.size() == 0) { + LERROR("Wasn't able to extract any valid states from provided source files!"); + return; + } + + computeSequenceEndTime(); + setModelDependentConstants(); + + setupProperties(); + + // Setup shader program + _shaderProgram = OsEng.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::KeyName, _name); + + // ------------------- EXTRACT MANDATORY VALUES FROM DICTIONARY ------------------- // + std::string inputFileTypeString; + if (!_dictionary->getValue(KeyInputFileType, inputFileTypeString)) { + LERROR(_name << ": The field " << std::string(KeyInputFileType) << " is missing!"); + return false; + } + 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( + _name << ": " << inputFileTypeString << " is not a recognized " << + KeyInputFileType + ); + sourceFileType = SourceFileType::Invalid; + return false; + } + } + + std::string sourceFolderPath; + if (!_dictionary->getValue(KeySourceFolder, sourceFolderPath)) { + LERROR(_name << ": The field " << std::string(KeySourceFolder) << " is missing!"); + 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](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( + _name << ": " << sourceFolderPath << " contains no ." << + inputFileTypeString << " files!" + ); + return false; + } + } + else { + LERROR(_name << ": FieldlinesSequence" << sourceFolderPath + << " is not a valid directory!"); + 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( + _name << ": The specified output path: '" << + outputFolderPath << "', does not exist!" + ); + 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! + } +} + +/** + * 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(_name << ": Must specify '" << KeyJsonSimulationModel << "'"); + return false; + } + + float scaleFactor; + if (_dictionary->getValue(KeyJsonScalingFactor, scaleFactor)) { + _scalingFactor = scaleFactor; + } + else { + LWARNING(_name << ": Does not provide scalingFactor! " << + "Assumes coordinates are already expressed in meters!"); + } + return true; +} + +bool RenderableFieldlinesSequence::loadJsonStatesIntoRAM(const std::string& outputFolder) { + fls::Model model; + if (!extractJsonInfoFromDictionary(model)) { + return false; + } + // Load states into RAM! + for (std::string filePath : _sourceFiles) { + FieldlinesState newState; + bool loadedSuccessfully = newState.loadStateFromJson(filePath, model, + _scalingFactor); + if (loadedSuccessfully) { + addStateToSequence(newState); + if (!outputFolder.empty()) { + newState.saveStateToOsfls(outputFolder); + } + } + } + return true; +} + +bool RenderableFieldlinesSequence::prepareForOsflsStreaming() { + extractTriggerTimesFromFileNames(); + FieldlinesState newState; + if (!newState.loadStateFromOsfls(_sourceFiles[0])) { + LERROR("The provided .osfls files seem to be corrupt!"); + 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) { + FieldlinesState newState; + if (newState.loadStateFromOsfls(filePath)) { + addStateToSequence(newState); + if (!outputFolder.empty()) { + ghoul::filesystem::File tmpFile(filePath); + newState.saveStateToJson(outputFolder + tmpFile.baseName()); + } + } + else { + LWARNING("Failed to load state from: " << filePath); + } + } +} + +void RenderableFieldlinesSequence::extractOsflsInfoFromDictionary() { + bool shouldLoadInRealtime = false; + if (_dictionary->getValue(KeyOslfsLoadAtRuntime, shouldLoadInRealtime)) { + _loadingStatesDynamically = shouldLoadInRealtime; + } + else { + LWARNING(_name << ": " << KeyOslfsLoadAtRuntime << + " isn't specified! States will be stored in RAM!"); + } +} + +void RenderableFieldlinesSequence::setupProperties() { + bool hasExtras = _states[0].nExtraQuantities() > 0; + + // -------------- Add non-grouped properties (enablers and buttons) -------------- // + addProperty(_pColorABlendEnabled); + addProperty(_pDomainEnabled); + addProperty(_pFlowEnabled); + if (hasExtras) { + addProperty(_pMaskingEnabled); + } + addProperty(_pFocusOnOriginBtn); + addProperty(_pJumpToStartBtn); + + // ----------------------------- Add Property Groups ----------------------------- // + addPropertySubOwner(_pColorGroup); + addPropertySubOwner(_pDomainGroup); + addPropertySubOwner(_pFlowGroup); + if (hasExtras) { + addPropertySubOwner(_pMaskingGroup); + } + + // ------------------------- 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 < 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); + } +} + +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] { + float f = stringToFloat( + _pColorQuantityMin, + _colorTableRanges[_pColorQuantity].x + ); + _pColorQuantityMin = std::to_string(f); + _colorTableRanges[_pColorQuantity].x = f; + }); + + _pColorQuantityMax.onChange([this] { + 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] { + float f = stringToFloat(_pMaskingMin, _maskingRanges[_pMaskingQuantity].x); + _pMaskingMin = std::to_string(f); + _maskingRanges[_pMaskingQuantity].x = f; + }); + + _pMaskingMax.onChange([this] { + float f = stringToFloat(_pMaskingMax, _maskingRanges[_pMaskingQuantity].y); + _pMaskingMax = std::to_string(f); + _maskingRanges[_pMaskingQuantity].y = f; + }); + } + + _pFocusOnOriginBtn.onChange([this] { + SceneGraphNode* node = OsEng.renderEngine().scene()->sceneGraphNode(_name); + if (!node) { + LWARNING("Could not find a node in scenegraph called '" << _name << "'"); + return; + } + OsEng.navigationHandler().setFocusNode(node->parent()); + OsEng.navigationHandler().resetCameraDirection(); + }); + + _pJumpToStartBtn.onChange([this] { + OsEng.timeManager().time().setTime(_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; + } +} + +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; + default: + break; + } + _pDomainX.setMinValue(glm::vec2(-limit)); + _pDomainX.setMaxValue(glm::vec2(limit)); + + _pDomainY.setMinValue(glm::vec2(-limit)); + _pDomainY.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)); + + _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') + const size_t filenameSize = 23; + // size(".osfls") + const size_t 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); + _startTimes.push_back(triggerTime); + } +} + +void RenderableFieldlinesSequence::addStateToSequence(FieldlinesState& state) { + _states.push_back(state); + _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; + } + + std::vector seedPoints; + if (!extractSeedPointsFromFile(seedFilePath, seedPoints)) { + return false; + } + + 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); + } + } + } + return true; +} + +/* +* 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(_name << ": The specified seed point file: '" << + seedFilePath << "', does not exist!"); + return false; + } + } + else { + LERROR(_name << ": Must specify '" << KeyCdfSeedPointFile << "'"); + return false; + } + + if (!_dictionary->getValue(KeyCdfTracingVariable, tracingVar)) { + tracingVar = "b"; // Magnetic field variable as default + LWARNING(_name << ": No '" << KeyCdfTracingVariable << "', using default: " + << 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("Could not open seed points file '" << path << "'"); + return false; + } + + LDEBUG("Reading seed points from file '" << path << "'"); + std::string line; + while (std::getline(seedFile, line)) { + glm::vec3 point; + std::stringstream ss(line); + ss >> point.x; + ss >> point.y; + ss >> point.z; + outVec.push_back(std::move(point)); + } + + if (outVec.size() == 0) { + LERROR("Found no seed points in: " << path); + return false; + } + + return true; +} + +void RenderableFieldlinesSequence::extractMagnitudeVarsFromStrings( + std::vector& extraVars, + std::vector& extraMagVars) +{ + + for (int i = 0; i < 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; @@ -62,13 +976,18 @@ void RenderableFieldlinesSequence::deinitialize() { } // Stall main thread until thread that's loading states is done! + bool printedWarning = false; while (_isLoadingStateFromDisk) { - LWARNING("TRYING TO DESTROY CLASS WHEN A THREAD USING IT IS STILL ACTIVE"); + 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 _isReady; + return _shaderProgram != nullptr; } void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks&) { @@ -91,13 +1010,13 @@ void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks& _shaderProgram->setUniform("usingDomain", _pDomainEnabled); _shaderProgram->setUniform("usingMasking", _pMaskingEnabled); - if (_pColorMethod == ColorMethod::ByQuantity) { - ghoul::opengl::TextureUnit textureUnit; - textureUnit.activate(); - _transferFunction->bind(); // Calls update internally - _shaderProgram->setUniform("colorTable", textureUnit); - _shaderProgram->setUniform("colorTableRange", - _colorTableRanges[_pColorQuantity]); + 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) { @@ -159,23 +1078,25 @@ void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks& } void RenderableFieldlinesSequence::update(const UpdateData& data) { - // This node shouldn't do anything if its been disabled from the gui! - if (!_enabled) { - return; - } - if (_shaderProgram->isDirty()) { _shaderProgram->rebuildFromFile(); } const double currentTime = data.time.j2000Seconds(); - // Check if current time in OpenSpace is within sequence interval - if (isWithinSequenceInterval(currentTime)) { - const int nextIdx = _activeTriggerTimeIndex + 1; - if (_activeTriggerTimeIndex < 0 // true => Previous frame was not within the sequence interval - || currentTime < _startTimes[_activeTriggerTimeIndex] // true => OpenSpace has stepped back to a time represented by another state - || (nextIdx < _nStates && currentTime >= _startTimes[nextIdx])) { // true => OpenSpace has stepped forward to a time represented by another state + bool isInInterval = (currentTime >= _startTimes[0]) && + (currentTime < _sequenceEndTime); + // Check if current time in OpenSpace is within sequence interval + if (isInInterval) { + const int 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) { @@ -194,13 +1115,13 @@ void RenderableFieldlinesSequence::update(const UpdateData& data) { if (_mustLoadNewStateFromDisk) { if (!_isLoadingStateFromDisk && !_newStateIsReady) { - _isLoadingStateFromDisk = true; - _mustLoadNewStateFromDisk = false; - const std::string filePath = _sourceFiles[_activeTriggerTimeIndex]; - std::thread readBinaryThread([this, filePath] { - this->readNewState(filePath); - }); - readBinaryThread.detach(); + _isLoadingStateFromDisk = true; + _mustLoadNewStateFromDisk = false; + std::string filePath = _sourceFiles[_activeTriggerTimeIndex]; + std::thread readBinaryThread([this, f = std::move(filePath)] { + readNewState(f); + }); + readBinaryThread.detach(); } } @@ -232,21 +1153,16 @@ void RenderableFieldlinesSequence::update(const UpdateData& data) { } } -inline bool RenderableFieldlinesSequence::isWithinSequenceInterval( - const double currentTime) const -{ - return (currentTime >= _startTimes[0]) && (currentTime < _sequenceEndTime); -} - // Assumes we already know that currentTime is within the sequence interval -void RenderableFieldlinesSequence::updateActiveTriggerTimeIndex(const double currentTime) -{ +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 { + if (iter != _startTimes.begin()) { + _activeTriggerTimeIndex = static_cast( + std::distance(_startTimes.begin(), iter) + ) - 1; + } + else { _activeTriggerTimeIndex = 0; } } else { @@ -273,11 +1189,14 @@ void RenderableFieldlinesSequence::updateVertexPositionBuffer() { glBindVertexArray(_vertexArrayObject); glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); - const std::vector& vertexPosVec = - _states[_activeStateIndex].vertexPositions(); + const std::vector& vertPos = _states[_activeStateIndex].vertexPositions(); - glBufferData(GL_ARRAY_BUFFER, vertexPosVec.size() * sizeof(glm::vec3), - &vertexPosVec.front(), GL_STATIC_DRAW); + 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); @@ -290,12 +1209,17 @@ void RenderableFieldlinesSequence::updateVertexColorBuffer() { glBindBuffer(GL_ARRAY_BUFFER, _vertexColorBuffer); bool isSuccessful; - const std::vector& quantityVec = - _states[_activeStateIndex].extraQuantity(_pColorQuantity, isSuccessful); + const std::vector& quantities = _states[_activeStateIndex].extraQuantity( + _pColorQuantity, isSuccessful + ); if (isSuccessful) { - glBufferData(GL_ARRAY_BUFFER, quantityVec.size() * sizeof(float), - &quantityVec.front(), GL_STATIC_DRAW); + glBufferData( + GL_ARRAY_BUFFER, + quantities.size() * sizeof(float), + quantities.data(), + GL_STATIC_DRAW + ); glEnableVertexAttribArray(VaColor); glVertexAttribPointer(VaColor, 1, GL_FLOAT, GL_FALSE, 0, 0); @@ -309,12 +1233,17 @@ void RenderableFieldlinesSequence::updateVertexMaskingBuffer() { glBindBuffer(GL_ARRAY_BUFFER, _vertexMaskingBuffer); bool isSuccessful; - const std::vector& quantityVec = - _states[_activeStateIndex].extraQuantity(_pMaskingQuantity, isSuccessful); + const std::vector& maskings = _states[_activeStateIndex].extraQuantity( + _pMaskingQuantity, isSuccessful + ); if (isSuccessful) { - glBufferData(GL_ARRAY_BUFFER, quantityVec.size() * sizeof(float), - &quantityVec.front(), GL_STATIC_DRAW); + glBufferData( + GL_ARRAY_BUFFER, + maskings.size() * sizeof(float), + maskings.data(), + GL_STATIC_DRAW + ); glEnableVertexAttribArray(VaMasking); glVertexAttribPointer(VaMasking, 1, GL_FLOAT, GL_FALSE, 0, 0); diff --git a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.h b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.h index f96c1aa218..54cca77b1b 100644 --- a/modules/fieldlinessequence/rendering/renderablefieldlinessequence.h +++ b/modules/fieldlinessequence/rendering/renderablefieldlinessequence.h @@ -27,21 +27,17 @@ #include +#include #include -#include #include #include +#include #include #include #include - -#include - #include -namespace { - enum class SourceFileType; -} +namespace { enum class SourceFileType; } namespace openspace { @@ -49,17 +45,18 @@ class RenderableFieldlinesSequence : public Renderable { public: RenderableFieldlinesSequence(const ghoul::Dictionary& dictionary); - void initialize() override; - void deinitialize() override; + void initializeGL() override; + void deinitializeGL() override; bool isReady() const override; void render(const RenderData& data, RendererTasks& rendererTask) override; void update(const UpdateData& data) override; + private: // ------------------------------------- ENUMS -------------------------------------// // Used to determine if lines should be colored UNIFORMLY or by an extraQuantity - enum ColorMethod : int { + enum class ColorMethod : int { Uniform = 0, ByQuantity }; @@ -71,8 +68,6 @@ private: // Used for 'runtime-states'. True when loading a new state from disk on another // thread. std::atomic _isLoadingStateFromDisk { false}; - // If initialization proved successful - bool _isReady = false; // False => states are stored in RAM (using 'in-RAM-states'), True => states are // loaded from disk during runtime (using 'runtime-states') bool _loadingStatesDynamically = false; @@ -123,7 +118,7 @@ private: std::unique_ptr _newState; std::unique_ptr _shaderProgram; // Transfer function used to color lines when _pColorMethod is set to BY_QUANTITY - std::shared_ptr _transferFunction; + std::unique_ptr _transferFunction; // ------------------------------------ VECTORS ----------------------------------- // // Paths to color tables. One for each 'extraQuantity' @@ -225,9 +220,8 @@ private: bool prepareForOsflsStreaming(); // ------------------------- FUNCTIONS USED DURING RUNTIME ------------------------ // - inline bool isWithinSequenceInterval(const double currentTime) const; void readNewState(const std::string& filePath); - void updateActiveTriggerTimeIndex(const double currentTime); + void updateActiveTriggerTimeIndex(double currentTime); void updateVertexPositionBuffer(); void updateVertexColorBuffer(); void updateVertexMaskingBuffer(); diff --git a/modules/fieldlinessequence/rendering/renderablefieldlinessequencesetup.cpp b/modules/fieldlinessequence/rendering/renderablefieldlinessequencesetup.cpp deleted file mode 100644 index f0a71a4481..0000000000 --- a/modules/fieldlinessequence/rendering/renderablefieldlinessequencesetup.cpp +++ /dev/null @@ -1,845 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2018 * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this * - * software and associated documentation files (the "Software"), to deal in the Software * - * without restriction, including without limitation the rights to use, copy, modify, * - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * - * permit persons to whom the Software is furnished to do so, subject to the following * - * conditions: * - * * - * The above copyright notice and this permission notice shall be included in all copies * - * or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - ****************************************************************************************/ - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -namespace { - std::string _loggerCat = "RenderableFieldlinesSequence"; - - // ----- KEYS POSSIBLE IN MODFILE. EXPECTED DATA TYPE OF VALUE IN [BRACKETS] ----- // - // ---------------------------- MANDATORY MODFILE KEYS ---------------------------- // - const char* KeyInputFileType = "InputFileType"; // [STRING] "cdf", "json" or "osfls" - const char* KeySourceFolder = "SourceFolder"; // [STRING] should be path to folder containing the input files - - // ---------------------- MANDATORY INPUT TYPE SPECIFIC KEYS ---------------------- // - const char* KeyCdfSeedPointFile = "SeedPointFile"; // [STRING] Path to a .txt file containing seed points - const char* KeyJsonSimulationModel = "SimulationModel"; // [STRING] Currently supports: "batsrus", "enlil" & "pfss" - - // ----------------------- OPTIONAL INPUT TYPE SPECIFIC KEYS ---------------------- // - const char* KeyCdfExtraVariables = "ExtraVariables"; // [STRING ARRAY] - const char* KeyCdfTracingVariable = "TracingVariable"; // [STRING] - const char* KeyJsonScalingFactor = "ScaleToMeters"; // [STRING] - const char* KeyOslfsLoadAtRuntime = "LoadAtRuntime"; // [BOOLEAN] If value False => Load in initializing step and store in RAM - - // ---------------------------- OPTIONAL MODFILE KEYS ---------------------------- // - const char* KeyColorTablePaths = "ColorTablePaths"; // [STRING ARRAY] Values should be paths to .txt files - const char* KeyColorTableRanges = "ColorTableRanges";// [VEC2 ARRAY] Values should be entered as {X, Y}, where X & Y are numbers - const char* KeyMaskingRanges = "MaskingRanges"; // [VEC2 ARRAY] Values should be entered as {X, Y}, where X & Y are numbers - const char* KeyOutputFolder = "OutputFolder"; // [STRING] Value should be path to folder where states are saved (JSON/CDF input => osfls output & oslfs input => JSON output) - - // ------------- POSSIBLE STRING VALUES FOR CORRESPONDING MODFILE KEY ------------- // - const char* ValueInputFileTypeCdf = "cdf"; - const char* ValueInputFileTypeJson = "json"; - const char* ValueInputFileTypeOsfls = "osfls"; - - // --------------------------------- Property Info -------------------------------- // - static const openspace::properties::Property::PropertyInfo ColorMethodInfo = { - "colorMethod", "Color Method", "Color lines uniformly or using color tables based on extra quantities like e.g. temperature or particle density." - }; - static const openspace::properties::Property::PropertyInfo ColorQuantityInfo = { - "colorQuantity", "Quantity to Color By", "Quantity used to color lines if the \"By Quantity\" color method is selected." - }; - static const openspace::properties::Property::PropertyInfo ColorQuantityMinInfo = { - "colorQuantityMin", "ColorTable Min Value", "Value to map to the lowest end of the color table." - }; - static const openspace::properties::Property::PropertyInfo ColorQuantityMaxInfo = { - "colorQuantityMax", "ColorTable Max Value", "Value to map to the highest end of the color table." - }; - static const openspace::properties::Property::PropertyInfo ColorTablePathInfo = { - "colorTablePath", "Path to Color Table", "Color Table/Transfer Function to use for \"By Quantity\" coloring." - }; - static const openspace::properties::Property::PropertyInfo ColorUniformInfo = { - "uniform", "Uniform Line Color", "The uniform color of lines shown when \"Color Method\" is set to \"Uniform\"." - }; - static const openspace::properties::Property::PropertyInfo ColorUseABlendingInfo = { - "aBlendingEnabled", "Additive Blending", "Activate/deactivate additive blending." - }; - static const openspace::properties::Property::PropertyInfo DomainEnabledInfo = { - "domainEnabled", "Domain Limits", "Enable/Disable domain limits" - }; - static const openspace::properties::Property::PropertyInfo DomainXInfo = { - "limitsX", "X-limits", "Valid range along the X-axis. [Min, Max]" - }; - static const openspace::properties::Property::PropertyInfo DomainYInfo = { - "limitsY", "Y-limits", "Valid range along the Y-axis. [Min, Max]" - }; - static const openspace::properties::Property::PropertyInfo DomainZInfo = { - "limitsZ", "Z-limits", "Valid range along the Z-axis. [Min, Max]" - }; - static const openspace::properties::Property::PropertyInfo DomainRInfo = { - "limitsR", "Radial limits", "Valid radial range. [Min, Max]" - }; - static const openspace::properties::Property::PropertyInfo FlowColorInfo = { - "color", "Color", "Color of particles." - }; - static const openspace::properties::Property::PropertyInfo FlowEnabledInfo = { - "flowEnabled", "Flow Direction", - "Toggles the rendering of moving particles along the lines. Can e.g. illustrate magnetic flow." - }; - static const openspace::properties::Property::PropertyInfo FlowReversedInfo = { - "reversed", "Reversed Flow", "Toggle to make the flow move in the opposite direction." - }; - static const openspace::properties::Property::PropertyInfo FlowParticleSizeInfo = { - "particleSize", "Particle Size", "Size of the particles." - }; - static const openspace::properties::Property::PropertyInfo FlowParticleSpacingInfo = { - "particleSpacing", "Particle Spacing", "Spacing inbetween particles." - }; - static const openspace::properties::Property::PropertyInfo FlowSpeedInfo = { - "speed", "Speed", "Speed of the flow." - }; - static const openspace::properties::Property::PropertyInfo MaskingEnabledInfo = { - "maskingEnabled", "Masking", - "Enable/disable masking. Use masking to show lines where a given quantity is within a given range, e.g. if you only want to see where the temperature is between 10 and 20 degrees. Also used for masking out line topologies like solar wind & closed lines." - }; - static const openspace::properties::Property::PropertyInfo MaskingMinInfo = { - "maskingMinLimit", "Lower Limit", "Lower limit of the valid masking range" - }; - static const openspace::properties::Property::PropertyInfo MaskingMaxInfo = { - "maskingMaxLimit", "Upper Limit", "Upper limit of the valid masking range" - }; - static const openspace::properties::Property::PropertyInfo MaskingQuantityInfo = { - "maskingQuantity", "Quantity used for Masking", "Quantity used for masking." - }; - static const openspace::properties::Property::PropertyInfo OriginButtonInfo = { - "focusCameraOnParent", "Focus Camera", "Focus camera on parent." - }; - static const openspace::properties::Property::PropertyInfo TimeJumpButtonInfo = { - "timeJumpToStart", "Jump to Start Of Sequence", "Performs a time jump to the start of the sequence." - }; - - enum class SourceFileType : int { - Cdf = 0, - Json, - Osfls, - Invalid - }; - - float stringToFloat(const std::string input, const float backupValue = 0.f) { - float tmp; - try { - tmp = std::stof(input); - } catch (const std::invalid_argument& ia) { - LWARNING("Invalid argument: " << ia.what() << ". '" << input << - "' is NOT a valid number!"); - return backupValue; - } - return tmp; - } -} // namespace - -namespace openspace { - -RenderableFieldlinesSequence::RenderableFieldlinesSequence(const ghoul::Dictionary& dictionary) - : Renderable(dictionary), - _pColorGroup({ "Color" }), - _pColorMethod(ColorMethodInfo, properties::OptionProperty::DisplayType::Radio), - _pColorQuantity(ColorQuantityInfo, properties::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, properties::OptionProperty::DisplayType::Dropdown), - _pFocusOnOriginBtn(OriginButtonInfo), - _pJumpToStartBtn(TimeJumpButtonInfo) { - - _dictionary = std::make_unique(dictionary); -} - -void RenderableFieldlinesSequence::initialize() { - LINFO("RenderableFieldlinesSequence::initialize()"); - - // EXTRACT MANDATORY INFORMATION FROM DICTIONARY - SourceFileType sourceFileType = SourceFileType::Invalid; - if (!extractMandatoryInfoFromDictionary(sourceFileType)) { - return; - } - - // Set a default color table, just in case the (optional) user defined paths are corrupt/not provided! - _colorTablePaths.push_back("${DATA}/scene/fieldlinessequence/colortables/kroyw.txt"); - _transferFunction = std::make_shared(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; - } - break; - case SourceFileType::Json: - if (!loadJsonStatesIntoRAM(outputFolderPath)) { - return; - } - break; - case SourceFileType::Osfls: - extractOsflsInfoFromDictionary(); - if (_loadingStatesDynamically) { - if (!prepareForOsflsStreaming()) { - return; - } - } else { - loadOsflsStatesIntoRAM(outputFolderPath); - } - break; - default: - return; - } - - // dictionary is no longer needed as everything is extracted - _dictionary.reset(); - - // 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.size() == 0) { - LERROR("Wasn't able to extract any valid states from provided source files!"); - return; - } - - computeSequenceEndTime(); - setModelDependentConstants(); - - setupProperties(); - - // Setup shader program - _shaderProgram = OsEng.renderEngine().buildRenderProgram( - "FieldlinesSequence", - absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_vs.glsl"), - absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_fs.glsl") - ); - - if (!_shaderProgram) { - LERROR("Shader program failed initialization!"); - sourceFileType = SourceFileType::Invalid; - } - - //------------------ 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); - - _isReady = true; -} - -/** - * 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::KeyName, _name); - - // ------------------- EXTRACT MANDATORY VALUES FROM DICTIONARY ------------------- // - std::string inputFileTypeString; - if (!_dictionary->getValue(KeyInputFileType, inputFileTypeString)) { - LERROR(_name << ": The field " << std::string(KeyInputFileType) << " is missing!"); - return false; - } 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(_name << ": " << inputFileTypeString << " is not a recognised " - << KeyInputFileType); - sourceFileType = SourceFileType::Invalid; - return false; - } - } - - std::string sourceFolderPath; - if (!_dictionary->getValue(KeySourceFolder, sourceFolderPath)) { - LERROR(_name << ": The field " << std::string(KeySourceFolder) << " is missing!"); - 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 (Non-recursively! Sorted!) - _sourceFiles = sourceFolder.readFiles(ghoul::Boolean::No, ghoul::Boolean::Yes); - - // Remove all files that don't have as extension - _sourceFiles.erase(std::remove_if(_sourceFiles.begin(), _sourceFiles.end(), - [inputFileTypeString](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(_name << ": " << sourceFolderPath << " contains no ." << inputFileTypeString - << " files!"); - return false; - } - } else { - LERROR(_name << ": FieldlinesSequence" << sourceFolderPath - << " is not a valid directory!"); - 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(_name << ": The specified output path: '" << outputFolderPath << "', does not exist!"); - 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! - } -} - -/** - * 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(_name << ": Must specify '" << KeyJsonSimulationModel << "'"); - return false; - } - - float scaleFactor; - if (_dictionary->getValue(KeyJsonScalingFactor, scaleFactor)) { - _scalingFactor = scaleFactor; - } else { - LWARNING(_name << ": Does not provide scalingFactor! " << - "Assumes coordinates are already expressed in meters!"); - } - return true; -} - -bool RenderableFieldlinesSequence::loadJsonStatesIntoRAM(const std::string& outputFolder) { - fls::Model model; - if (!extractJsonInfoFromDictionary(model)) { - return false; - } - // Load states into RAM! - for (std::string filePath : _sourceFiles) { - FieldlinesState newState; - bool loadedSuccessfully = newState.loadStateFromJson(filePath, model, - _scalingFactor); - if (loadedSuccessfully) { - addStateToSequence(newState); - if (!outputFolder.empty()) { - newState.saveStateToOsfls(outputFolder); - } - } - } - return true; -} - -bool RenderableFieldlinesSequence::prepareForOsflsStreaming() { - extractTriggerTimesFromFileNames(); - FieldlinesState newState; - if (!newState.loadStateFromOsfls(_sourceFiles[0])) { - LERROR("The provided .osfls files seem to be corrupt!"); - 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) { - FieldlinesState newState; - if (newState.loadStateFromOsfls(filePath)) { - addStateToSequence(newState); - if (!outputFolder.empty()) { - ghoul::filesystem::File tmpFile(filePath); - newState.saveStateToJson(outputFolder + tmpFile.baseName()); - } - } else { - LWARNING("Failed to load state from: " << filePath); - } - } -} - -void RenderableFieldlinesSequence::extractOsflsInfoFromDictionary() { - bool shouldLoadInRealtime = false; - if (_dictionary->getValue(KeyOslfsLoadAtRuntime, shouldLoadInRealtime)) { - _loadingStatesDynamically = shouldLoadInRealtime; - } else { - LWARNING(_name << ": " << KeyOslfsLoadAtRuntime << - " isn't specified! States will be stored in RAM!"); - } -} - -void RenderableFieldlinesSequence::setupProperties() { - bool hasExtras = _states[0].nExtraQuantities() > 0; - - // -------------- Add non-grouped properties (enablers and buttons) -------------- // - addProperty(_pColorABlendEnabled); - addProperty(_pDomainEnabled); - addProperty(_pFlowEnabled); - if (hasExtras) { addProperty(_pMaskingEnabled); } - addProperty(_pFocusOnOriginBtn); - addProperty(_pJumpToStartBtn); - - // ----------------------------- Add Property Groups ----------------------------- // - addPropertySubOwner(_pColorGroup); - addPropertySubOwner(_pDomainGroup); - addPropertySubOwner(_pFlowGroup); - if (hasExtras) { addPropertySubOwner(_pMaskingGroup); } - - // ------------------------- 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(ColorMethod::Uniform, "Uniform"); - _pColorMethod.addOption(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 < 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); - } -} - -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] { - float f = stringToFloat(_pColorQuantityMin, _colorTableRanges[_pColorQuantity].x); - _pColorQuantityMin = std::to_string(f); - _colorTableRanges[_pColorQuantity].x = f; - }); - - _pColorQuantityMax.onChange([this] { - 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] { - float f = stringToFloat(_pMaskingMin, _maskingRanges[_pMaskingQuantity].x); - _pMaskingMin = std::to_string(f); - _maskingRanges[_pMaskingQuantity].x = f; - }); - - _pMaskingMax.onChange([this] { - float f = stringToFloat(_pMaskingMax, _maskingRanges[_pMaskingQuantity].y); - _pMaskingMax = std::to_string(f); - _maskingRanges[_pMaskingQuantity].y = f; - }); - } - - _pFocusOnOriginBtn.onChange([this] { - SceneGraphNode* node = OsEng.renderEngine().scene()->sceneGraphNode(_name); - if (!node) { - LWARNING("Could not find a node in scenegraph called '" << _name << "'"); - return; - } - OsEng.navigationHandler().setFocusNode(node->parent()); - OsEng.navigationHandler().resetCameraDirection(); - }); - - _pJumpToStartBtn.onChange([this] { - OsEng.timeManager().time().setTime(_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; - } -} - -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; - default: - break; - } - _pDomainX.setMinValue(glm::vec2(-limit)); _pDomainX.setMaxValue(glm::vec2(limit)); - _pDomainY.setMinValue(glm::vec2(-limit)); _pDomainY.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)); - - _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() { - const size_t filenameSize = 23; // number of characters in filename (excluding '.osfls') - const size_t extSize = 6; // size(".osfls") - - 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); - _startTimes.push_back(triggerTime); - } -} - -void RenderableFieldlinesSequence::addStateToSequence(FieldlinesState& state) { - _states.push_back(state); - _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; - } - - std::vector seedPoints; - if (!extractSeedPointsFromFile(seedFilePath, seedPoints)) { - return false; - } - - std::vector extraMagVars; - extractMagnitudeVarsFromStrings(extraVars, extraMagVars); - - // Load states into RAM! - for (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); - } - } - } - return true; -} - -/* - * 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(_name << ": The specified seed point file: '" << seedFilePath - << "', does not exist!"); - return false; - } - } else { - LERROR(_name << ": Must specify '" << KeyCdfSeedPointFile << "'"); - return false; - } - - if (!_dictionary->getValue(KeyCdfTracingVariable, tracingVar)) { - tracingVar = "b"; // Magnetic field variable as default - LWARNING(_name << ": No '" << KeyCdfTracingVariable << "', using default: " - << 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("Could not open seed points file '" << path << "'"); - return false; - } - - LDEBUG("Reading seed points from file '" << path << "'"); - std::string line; - while (std::getline(seedFile, line)) { - glm::vec3 point; - std::stringstream ss(line); - ss >> point.x; - ss >> point.y; - ss >> point.z; - outVec.push_back(std::move(point)); - } - - if (outVec.size() == 0) { - LERROR("Found no seed points in: " << path); - return false; - } - - return true; -} - -void RenderableFieldlinesSequence::extractMagnitudeVarsFromStrings( - std::vector& extraVars, - std::vector& extraMagVars) { - - for (int i = 0; i < 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--; - } - } -} - -} // namespace openspace diff --git a/modules/fieldlinessequence/util/commons.cpp b/modules/fieldlinessequence/util/commons.cpp index 4b2f795356..48bce4d610 100644 --- a/modules/fieldlinessequence/util/commons.cpp +++ b/modules/fieldlinessequence/util/commons.cpp @@ -24,10 +24,9 @@ #include -namespace openspace { -namespace fls { +namespace openspace::fls { -Model stringToModel(const std::string s) { +Model stringToModel(const std::string& s) { if (s == "batsrus") { return Model::Batsrus; } else if (s == "enlil") { @@ -38,5 +37,4 @@ Model stringToModel(const std::string s) { return Model::Invalid; } -} // namespace fls -} // namespace openspace +} // namespace openspace::fls diff --git a/modules/fieldlinessequence/util/commons.h b/modules/fieldlinessequence/util/commons.h index 4ab83825eb..9fde3fe9cc 100644 --- a/modules/fieldlinessequence/util/commons.h +++ b/modules/fieldlinessequence/util/commons.h @@ -27,23 +27,21 @@ #include -namespace openspace { -namespace fls { // (F)ield(L)ines(S)equence +namespace openspace::fls { // (F)ield(L)ines(S)equence -enum Model : int { +enum class Model : int { Batsrus = 0, Enlil, Pfss, Invalid }; -Model stringToModel(const std::string s); +Model stringToModel(const std::string& s); -const float AuToMeter = 149597870700.f; // Astronomical Units -const float ReToMeter = 6371000.f; // Earth radius -const float RsToMeter = 695700000.f; // Sun radius +constexpr const float AuToMeter = 149597870700.f; // Astronomical Units +constexpr const float ReToMeter = 6371000.f; // Earth radius +constexpr const float RsToMeter = 695700000.f; // Sun radius -} // namespace fls -} // namespace openspace +} // namespace openspace::fls #endif // __OPENSPACE_MODULE_FIELDLINESSEQUENCE___COMMONS___H__ diff --git a/modules/fieldlinessequence/util/fieldlinesstate.cpp b/modules/fieldlinessequence/util/fieldlinesstate.cpp index e089a719ea..1526c87f0c 100644 --- a/modules/fieldlinessequence/util/fieldlinesstate.cpp +++ b/modules/fieldlinessequence/util/fieldlinesstate.cpp @@ -24,12 +24,9 @@ #include -#include - #include - #include - +#include #include namespace { @@ -41,11 +38,11 @@ namespace { namespace openspace { /** -* Converts all glm::vec3 in _vertexPositions from spherical (radius, latitude, longitude) -* coordinates into cartesian coordinates. The longitude and latitude coordinates are -* expected to be in degrees. scale is an optional scaling factor. -*/ -void FieldlinesState::convertLatLonToCartesian(const float scale /* = 1.f */) { + * Converts all glm::vec3 in _vertexPositions from spherical (radius, latitude, longitude) + * coordinates into cartesian coordinates. The longitude and latitude coordinates are + * expected to be in degrees. scale is an optional scaling factor. + */ +void FieldlinesState::convertLatLonToCartesian(float scale /* = 1.f */) { for (glm::vec3& p : _vertexPositions) { const float r = p.x * scale; @@ -57,7 +54,7 @@ void FieldlinesState::convertLatLonToCartesian(const float scale /* = 1.f */) { } } -void FieldlinesState::scalePositions(const float scale) { +void FieldlinesState::scalePositions(float scale) { for (glm::vec3& p : _vertexPositions) { p *= scale; } @@ -66,21 +63,20 @@ void FieldlinesState::scalePositions(const float scale) { bool FieldlinesState::loadStateFromOsfls(const std::string& pathToOsflsFile) { std::ifstream ifs(pathToOsflsFile, std::ifstream::binary); if (!ifs.is_open()) { - LERRORC("FieldlinesState", "Couldn't open file: " + pathToOsflsFile); + LERROR("Couldn't open file: " + pathToOsflsFile); return false; } int binFileVersion; - ifs.read( reinterpret_cast(&binFileVersion), sizeof(int)); + ifs.read(reinterpret_cast(&binFileVersion), sizeof(int)); switch (binFileVersion) { - case 0 : { - // No need to put everything in this scope now, as only version 0 exists! - } + case 0: + // No need to put everything in this scope now, as only version 0 exists! break; - default : - LERRORC("FieldlinesState","VERSION OF BINARY FILE WAS NOT RECOGNISED!"); - return false; + default: + LERROR("VERSION OF BINARY FILE WAS NOT RECOGNIZED!"); + return false; } // Define tmp variables to store meta data in @@ -90,35 +86,32 @@ bool FieldlinesState::loadStateFromOsfls(const std::string& pathToOsflsFile) { size_t byteSizeAllNames; // Read single value variables - ifs.read( reinterpret_cast(&_triggerTime), sizeof(double)); - ifs.read( reinterpret_cast(&_model), sizeof(int)); - ifs.read( reinterpret_cast(&_isMorphable), sizeof(bool)); - ifs.read( reinterpret_cast(&nLines), sizeof(size_t)); - ifs.read( reinterpret_cast(&nPoints), sizeof(size_t)); - ifs.read( reinterpret_cast(&nExtras), sizeof(size_t)); - ifs.read( reinterpret_cast(&byteSizeAllNames), sizeof(size_t)); + ifs.read(reinterpret_cast(&_triggerTime), sizeof(double)); + ifs.read(reinterpret_cast(&_model), sizeof(int32_t)); + ifs.read(reinterpret_cast(&_isMorphable), sizeof(bool)); + ifs.read(reinterpret_cast(&nLines), sizeof(uint64_t)); + ifs.read(reinterpret_cast(&nPoints), sizeof(uint64_t)); + ifs.read(reinterpret_cast(&nExtras), sizeof(uint64_t)); + ifs.read(reinterpret_cast(&byteSizeAllNames), sizeof(uint64_t)); - // RESERVE/RESIZE vectors - // TODO: Do this without initializing values? Resize is slower than just using - // reserve, due to initialization of all values _lineStart.resize(nLines); _lineCount.resize(nLines); _vertexPositions.resize(nPoints); _extraQuantities.resize(nExtras); - _extraQuantityNames.reserve(nExtras); + _extraQuantityNames.resize(nExtras); // Read vertex position data - ifs.read( reinterpret_cast(_lineStart.data()), sizeof(GLint) * nLines); - ifs.read( reinterpret_cast(_lineCount.data()), sizeof(GLsizei) * nLines); + ifs.read(reinterpret_cast(_lineStart.data()), sizeof(int32_t) * nLines); + ifs.read(reinterpret_cast(_lineCount.data()), sizeof(uint32_t) * nLines); ifs.read( reinterpret_cast(_vertexPositions.data()), - sizeof(glm::vec3) * nPoints + 3 * sizeof(float) * nPoints ); // Read all extra quantities for (std::vector& vec : _extraQuantities) { vec.resize(nPoints); - ifs.read( reinterpret_cast(vec.data()), sizeof(float) * nPoints); + ifs.read(reinterpret_cast(vec.data()), sizeof(float) * nPoints); } // Read all extra quantities' names. Stored as multiple c-strings @@ -134,15 +127,16 @@ bool FieldlinesState::loadStateFromOsfls(const std::string& pathToOsflsFile) { endOfVarName -= offset; std::string varName = allNamesInOne.substr(offset, endOfVarName); offset += varName.size() + 1; - _extraQuantityNames.push_back(varName); + _extraQuantityNames[i] = varName; } return true; } bool FieldlinesState::loadStateFromJson(const std::string& pathToJsonFile, - const fls::Model Model, - const float coordToMeters = 1.f) { + fls::Model Model, + float coordToMeters = 1.f) +{ // --------------------- ENSURE FILE IS VALID, THEN PARSE IT --------------------- // std::ifstream ifs(pathToJsonFile); @@ -158,15 +152,16 @@ bool FieldlinesState::loadStateFromJson(const std::string& pathToJsonFile, _model = Model; - const std::string sData = "data"; - const std::string sTrace = "trace"; + const char* sData = "data"; + const char* sTrace = "trace"; // ----- EXTRACT THE EXTRA QUANTITY NAMES & TRIGGER TIME (same for all lines) ----- // { - const json jTmp = *jFile.begin(); // First field line in the file - _triggerTime = Time::convertTime(jTmp["time"]); + const char* sTime = "time"; + const json& jTmp = *(jFile.begin()); // First field line in the file + _triggerTime = Time::convertTime(jTmp[sTime]); - const std::string sColumns = "columns"; + const char* sColumns = "columns"; auto variableNameVec = jTmp[sTrace][sColumns]; const size_t nVariables = variableNameVec.size(); const size_t nPosComponents = 3; // x,y,z @@ -179,7 +174,7 @@ bool FieldlinesState::loadStateFromJson(const std::string& pathToJsonFile, return false; } - for (size_t i = nPosComponents ; i < nVariables ; i++) { + for (size_t i = nPosComponents ; i < nVariables ; ++i) { _extraQuantityNames.push_back(variableNameVec[i]); } } @@ -192,17 +187,23 @@ bool FieldlinesState::loadStateFromJson(const std::string& pathToJsonFile, for (json::iterator lineIter = jFile.begin(); lineIter != jFile.end(); ++lineIter) { // The 'data' field in the 'trace' variable contains all vertex positions and the // extra quantities. Each element is an array related to one vertex point. - const std::vector> jData = (*lineIter)[sTrace][sData]; + const std::vector>& jData = (*lineIter)[sTrace][sData]; const size_t nPoints = jData.size(); for (size_t j = 0; j < nPoints; ++j) { const std::vector& variables = jData[j]; // Expects the x, y and z variables to be stored first! - const size_t xIdx = 0, yIdx = 1, zIdx = 2; - _vertexPositions.push_back(coordToMeters * glm::vec3(variables[xIdx], - variables[yIdx], - variables[zIdx])); + const size_t xIdx = 0; + const size_t yIdx = 1; + const size_t zIdx = 2; + _vertexPositions.push_back( + coordToMeters * glm::vec3( + variables[xIdx], + variables[yIdx], + variables[zIdx] + ) + ); // Add the extra quantites. Stored in the same array as the x,y,z variables. // Hence index of the first extra quantity = 3 @@ -249,7 +250,7 @@ void FieldlinesState::saveStateToOsfls(const std::string& absPath) { pathSafeTimeString.replace(13, 1, "-"); pathSafeTimeString.replace(16, 1, "-"); pathSafeTimeString.replace(19, 1, "-"); - const std::string fileName = pathSafeTimeString + ".osfls"; + std::string fileName = pathSafeTimeString + ".osfls"; std::ofstream ofs(absPath + fileName, std::ofstream::binary | std::ofstream::trunc); if (!ofs.is_open()) { @@ -259,7 +260,7 @@ void FieldlinesState::saveStateToOsfls(const std::string& absPath) { // --------- Add each string of _extraQuantityNames into one long string --------- // std::string allExtraQuantityNamesInOne = ""; - for (std::string str : _extraQuantityNames) { + for (const std::string& str : _extraQuantityNames) { allExtraQuantityNamesInOne += str + '\0'; // Add null char '\0' for easier reading } @@ -270,30 +271,30 @@ void FieldlinesState::saveStateToOsfls(const std::string& absPath) { //----------------------------- WRITE EVERYTHING TO FILE ----------------------------- // VERSION OF BINARY FIELDLINES STATE FILE - IN CASE STRUCTURE CHANGES IN THE FUTURE - ofs.write( (char*)(&CurrentVersion), sizeof( int ) ); + ofs.write(reinterpret_cast(&CurrentVersion), sizeof(int)); //-------------------- WRITE META DATA FOR STATE -------------------------------- - ofs.write(reinterpret_cast(&_triggerTime), sizeof( _triggerTime )); - ofs.write(reinterpret_cast(&_model), sizeof( int )); - ofs.write(reinterpret_cast(&_isMorphable), sizeof( bool )); + ofs.write(reinterpret_cast(&_triggerTime), sizeof(_triggerTime)); + ofs.write(reinterpret_cast(&_model), sizeof(int32_t)); + ofs.write(reinterpret_cast(&_isMorphable), sizeof(bool)); - ofs.write(reinterpret_cast(&nLines), sizeof( size_t )); - ofs.write(reinterpret_cast(&nPoints), sizeof( size_t )); - ofs.write(reinterpret_cast(&nExtras), sizeof( size_t )); - ofs.write(reinterpret_cast(&nStringBytes), sizeof( size_t )); + ofs.write(reinterpret_cast(&nLines), sizeof(uint64_t)); + ofs.write(reinterpret_cast(&nPoints), sizeof(uint64_t)); + ofs.write(reinterpret_cast(&nExtras), sizeof(uint64_t)); + ofs.write(reinterpret_cast(&nStringBytes), sizeof(uint64_t)); //---------------------- WRITE ALL ARRAYS OF DATA -------------------------------- - ofs.write(reinterpret_cast(_lineStart.data()), sizeof(GLint) * nLines); - ofs.write(reinterpret_cast(_lineCount.data()), sizeof(GLsizei) * nLines); + ofs.write(reinterpret_cast(_lineStart.data()), sizeof(int32_t) * nLines); + ofs.write(reinterpret_cast(_lineCount.data()), sizeof(uint32_t) * nLines); ofs.write( reinterpret_cast(_vertexPositions.data()), - sizeof(glm::vec3) * nPoints + 3 * sizeof(float) * nPoints ); // Write the data for each vector in _extraQuantities for (std::vector& vec : _extraQuantities) { - ofs.write( reinterpret_cast(vec.data()), sizeof(float) * nPoints); + ofs.write(reinterpret_cast(vec.data()), sizeof(float) * nPoints); } - ofs.write( allExtraQuantityNamesInOne.c_str(), nStringBytes); + ofs.write(allExtraQuantityNamesInOne.c_str(), nStringBytes); } // TODO: This should probably be rewritten, but this is the way the files were structured @@ -322,7 +323,7 @@ void FieldlinesState::saveStateToOsfls(const std::string& absPath) { // } void FieldlinesState::saveStateToJson(const std::string& absPath) { // Create the file - const std::string ext = ".json"; + const char* ext = ".json"; std::ofstream ofs(absPath + ext, std::ofstream::trunc); if (!ofs.is_open()) { LERROR("Failed to save state to json file at location: " << absPath << ext); @@ -330,8 +331,8 @@ void FieldlinesState::saveStateToJson(const std::string& absPath) { } LINFO("Saving fieldline state to: " << absPath << ext ); - json jColumns = {"x", "y", "z"}; - for (std::string s : _extraQuantityNames) { + json jColumns = { "x", "y", "z" }; + for (const std::string& s : _extraQuantityNames) { jColumns.push_back(s); } @@ -343,23 +344,23 @@ void FieldlinesState::saveStateToJson(const std::string& absPath) { const size_t nExtras = _extraQuantities.size(); size_t pointIndex = 0; - for (size_t lineIndex = 0; lineIndex < nLines; lineIndex++) { + for (size_t lineIndex = 0; lineIndex < nLines; ++lineIndex) { json jData = json::array(); - for (size_t i = 0; i < _lineCount[lineIndex]; i++, pointIndex++) { + for (size_t i = 0; i < _lineCount[lineIndex]; i++, ++pointIndex) { const glm::vec3 pos = _vertexPositions[pointIndex]; json jDataElement = {pos.x, pos.y, pos.z}; - for (size_t extraIndex = 0; extraIndex < nExtras; extraIndex++) { + for (size_t extraIndex = 0; extraIndex < nExtras; ++extraIndex) { jDataElement.push_back(_extraQuantities[extraIndex][pointIndex]); } jData.push_back(jDataElement); } jFile[std::to_string(lineIndex)] = { - {"time", timeStr}, - {"trace", { - {"columns", jColumns}, - {"data", jData} + { "time", timeStr }, + { "trace", { + { "columns", jColumns }, + { "data", jData } }} }; } @@ -373,16 +374,17 @@ void FieldlinesState::saveStateToJson(const std::string& absPath) { // Returns one of the extra quantity vectors, _extraQuantities[index]. // If index is out of scope an empty vector is returned and the referenced bool is false. -std::vector FieldlinesState::extraQuantity(const size_t index, - bool& isSuccessful) const { +std::vector FieldlinesState::extraQuantity(size_t index, bool& isSuccessful) const +{ if (index < _extraQuantities.size()) { isSuccessful = true; return _extraQuantities[index]; } - LERROR("Provided Index was out of scope!"); - isSuccessful = false; - // return empty vector which goes out of scope hence unusable! - return std::vector(); + else { + isSuccessful = false; + LERROR("Provided Index was out of scope!"); + return {}; + } } // Moves the points in @param line over to _vertexPositions and updates @@ -402,9 +404,8 @@ void FieldlinesState::addLine(std::vector& line) { line.clear(); } -void FieldlinesState::setExtraQuantityNames(std::vector& names) { +void FieldlinesState::setExtraQuantityNames(std::vector names) { _extraQuantityNames = std::move(names); - names.clear(); _extraQuantities.resize(_extraQuantityNames.size()); } diff --git a/modules/fieldlinessequence/util/fieldlinesstate.h b/modules/fieldlinessequence/util/fieldlinesstate.h index e2b2fabf6d..94a0404d0b 100644 --- a/modules/fieldlinessequence/util/fieldlinesstate.h +++ b/modules/fieldlinessequence/util/fieldlinesstate.h @@ -27,48 +27,41 @@ #include -#include #include - +#include #include #include -#ifdef OPENSPACE_MODULE_KAMELEON_ENABLED -namespace ccmc { - class Kameleon; -} -#endif // OPENSPACE_MODULE_KAMELEON_ENABLED - namespace openspace { class FieldlinesState { public: - void convertLatLonToCartesian(const float scale = 1.f); - void scalePositions(const float scale); + void convertLatLonToCartesian(float scale = 1.f); + void scalePositions(float scale); bool loadStateFromOsfls(const std::string& pathToOsflsFile); void saveStateToOsfls(const std::string& pathToOsflsFile); - bool loadStateFromJson(const std::string& pathToJsonFile, - const fls::Model model, const float coordToMeters); + bool loadStateFromJson(const std::string& pathToJsonFile, fls::Model model, + float coordToMeters); void saveStateToJson(const std::string& pathToJsonFile); - // ----------------------------------- GETTERS ----------------------------------- // - const std::vector>& extraQuantities() const; - const std::vector& extraQuantityNames() const; - const std::vector& lineCount() const; - const std::vector& lineStart() const; - fls::Model model() const; - size_t nExtraQuantities() const; - double triggerTime() const; - const std::vector& vertexPositions() const; + const std::vector>& extraQuantities() const; + const std::vector& extraQuantityNames() const; + const std::vector& lineCount() const; + const std::vector& lineStart() const; - // Special getter. Returns extraQuantities[INDEX]. - std::vector extraQuantity(const size_t INDEX, bool& isSuccesful) const; + fls::Model model() const; + size_t nExtraQuantities() const; + double triggerTime() const; + const std::vector& vertexPositions() const; - void setModel(const fls::Model m) { _model = m; } - void setTriggerTime(const double t) { _triggerTime = t; } - void setExtraQuantityNames(std::vector& names); + // Special getter. Returns extraQuantities[index]. + std::vector extraQuantity(size_t index, bool& isSuccesful) const; + + void setModel(fls::Model m) { _model = m; } + void setTriggerTime(double t) { _triggerTime = t; } + void setExtraQuantityNames(std::vector names); void addLine(std::vector& line); void appendToExtra(size_t idx, float val) { _extraQuantities[idx].push_back(val); } diff --git a/modules/fieldlinessequence/util/kameleonfieldlinehelper.cpp b/modules/fieldlinessequence/util/kameleonfieldlinehelper.cpp index c08326e293..f54673cf6f 100644 --- a/modules/fieldlinessequence/util/kameleonfieldlinehelper.cpp +++ b/modules/fieldlinessequence/util/kameleonfieldlinehelper.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #ifdef OPENSPACE_MODULE_KAMELEON_ENABLED @@ -45,68 +47,63 @@ #endif // OPENSPACE_MODULE_KAMELEON_ENABLED -#include - -#include namespace { - std::string _loggerCat = "FieldlinesSequence[ Kameleon ]"; + constexpr const char* _loggerCat = "FieldlinesSequence[ Kameleon ]"; - const std::string TAsPOverRho = "T = p/rho"; - const std::string JParallelB = "Current: mag(J||B)"; + constexpr const char* TAsPOverRho = "T = p/rho"; + constexpr const char* JParallelB = "Current: mag(J||B)"; // [nPa]/[amu/cm^3] * ToKelvin => Temperature in Kelvin - const float ToKelvin = 72429735.6984f; -} + constexpr const float ToKelvin = 72429735.6984f; +} // namespace -namespace openspace { -namespace fls { +namespace openspace::fls { // -------------------- DECLARE FUNCTIONS USED (ONLY) IN THIS FILE -------------------- // #ifdef OPENSPACE_MODULE_KAMELEON_ENABLED bool addLinesToState(ccmc::Kameleon* kameleon, const std::vector& seeds, - const std::string tracingVar, FieldlinesState& state); + const std::string tracingVar, FieldlinesState& state); void addExtraQuantities(ccmc::Kameleon* kameleon, - std::vector& extraScalarVars, - std::vector& extraMagVars, - FieldlinesState& state); + std::vector& extraScalarVars, std::vector& extraMagVars, + FieldlinesState& state); void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, - std::vector& extraScalarVars, - std::vector& extraMagVars, - FieldlinesState& state); + std::vector& extraScalarVars, std::vector& extraMagVars, + FieldlinesState& state); #endif // OPENSPACE_MODULE_KAMELEON_ENABLED -// ----------------------------------------------------------------------------------- // +// ------------------------------------------------------------------------------------ // /** Traces field lines from the provided cdf file using kameleon and stores the data in * the provided FieldlinesState. - * Returns `false` if it fails to create a valid state. Requires the kameleon module to be - * activated! - * @param state, FieldlineState which should hold the extracted data - * @param cdfPath, std::string of the absolute path to a .cdf file - * @param seedPoints, vector of seed points from which to trace field lines - * @param tracingVar, which quantity to trace lines from. Typically "b" for magnetic field + * Returns `false` if it fails to create a valid state. Requires the kameleon module to + * be activated! + * \param state, FieldlineState which should hold the extracted data + * \param cdfPath, std::string of the absolute path to a .cdf file + * \param seedPoints, vector of seed points from which to trace field lines + * \param tracingVar, which quantity to trace lines from. Typically "b" for magnetic field * lines and "u" for velocity flow lines - * @param extraVars, extra scalar quantities to be stored in the FieldlinesState; e.g. "T" + * \param extraVars, extra scalar quantities to be stored in the FieldlinesState; e.g. "T" * for temperature, "rho" for density or "P" for pressure - * @param extraMagVars, variables which should be used for extracting magnitudes, must be + * \param extraMagVars, variables which should be used for extracting magnitudes, must be * a multiple of 3; e.g. "ux", "uy" & "uz" to get the magnitude of the velocity * vector at each line vertex */ -bool convertCdfToFieldlinesState(FieldlinesState& state, const std::string cdfPath, +bool convertCdfToFieldlinesState(FieldlinesState& state, const std::string& cdfPath, const std::vector& seedPoints, - const std::string tracingVar, + std::string tracingVar, std::vector& extraVars, - std::vector& extraMagVars) { + std::vector& extraMagVars) +{ #ifndef OPENSPACE_MODULE_KAMELEON_ENABLED - LERROR("CDF inputs provided but Kameleon module is deactivated!"); return false; #else // OPENSPACE_MODULE_KAMELEON_ENABLED // Create Kameleon object and open CDF file! - std::unique_ptr kameleon = - kameleonHelper::createKameleonObject(cdfPath); + std::unique_ptr kameleon = kameleonHelper::createKameleonObject( + cdfPath + ); state.setModel(fls::stringToModel(kameleon->getModelName())); state.setTriggerTime(kameleonHelper::getTime(kameleon.get())); @@ -117,10 +114,10 @@ bool convertCdfToFieldlinesState(FieldlinesState& state, const std::string cdfPa // the extraQuantites, as the iterpolator needs the unaltered positions addExtraQuantities(kameleon.get(), extraVars, extraMagVars, state); switch (state.model()) { - case fls::Batsrus: + case fls::Model::Batsrus: state.scalePositions(fls::ReToMeter); break; - case fls::Enlil : + case fls::Model::Enlil: state.convertLatLonToCartesian(fls::AuToMeter); break; default: @@ -147,28 +144,28 @@ bool addLinesToState(ccmc::Kameleon* kameleon, const std::vector& see float innerBoundaryLimit; switch (state.model()) { - case fls::Model::Batsrus : + case fls::Model::Batsrus: innerBoundaryLimit = 2.5f; // TODO specify in Lua? break; - case fls::Model::Enlil : + case fls::Model::Enlil: innerBoundaryLimit = 0.11f; // TODO specify in Lua? break; default: - LERROR("OpenSpace's fieldlines sequence currently only supports CDFs from the" - << " BATSRUS and ENLIL models!"); + LERROR("OpenSpace's fieldlines sequence currently only supports CDFs from " + << "the BATSRUS and ENLIL models!"); return false; } // ---------------------------- LOAD TRACING VARIABLE ---------------------------- // if (!kameleon->loadVariable(tracingVar)) { - LERROR("FAILED TO LOAD TRACING VARIABLE: " + tracingVar); + LERROR("Failed to load tracing variable: " + tracingVar); return false; } - bool isSuccesful = false; - LINFO("TRACING FIELD LINES!"); + bool success = false; + LINFO("Tracing field lines!"); // LOOP THROUGH THE SEED POINTS, TRACE LINES, CONVERT POINTS TO glm::vec3 AND STORE // - for (glm::vec3 seed : seedPoints) { + for (const glm::vec3& seed : seedPoints) { //--------------------------------------------------------------------------// // We have to create a new tracer (or actually a new interpolator) for each // // new line, otherwise some issues occur // @@ -177,9 +174,12 @@ bool addLinesToState(ccmc::Kameleon* kameleon, const std::vector& see std::make_unique(kameleon->model); ccmc::Tracer tracer(kameleon, interpolator.get()); tracer.setInnerBoundary(innerBoundaryLimit); // TODO specify in Lua? - ccmc::Fieldline ccmcFieldline = tracer.bidirectionalTrace(tracingVar, seed.x, - seed.y, - seed.z); + ccmc::Fieldline ccmcFieldline = tracer.bidirectionalTrace( + tracingVar, + seed.x, + seed.y, + seed.z + ); const std::vector& positions = ccmcFieldline.getPositions(); const size_t nLinePoints = positions.size(); @@ -190,17 +190,17 @@ bool addLinesToState(ccmc::Kameleon* kameleon, const std::vector& see vertices.emplace_back(p.component1, p.component2, p.component3); } state.addLine(vertices); - isSuccesful = (nLinePoints > 0) ? true : isSuccesful; + success = (nLinePoints > 0) ? true : success; } - return isSuccesful; + return success; } #endif // OPENSPACE_MODULE_KAMELEON_ENABLED /** * Loops through state's _vertexPositions and extracts corresponding 'extraQuantities' * from the kameleon object using a ccmc::interpolator. - * Note that the positions MUST be unaltered (NOT scaled NOR converted to different + * Note that the positions MUST be unaltered (NOT scaled NOR converted to a different * coordinate system)! * * @param kameleon raw pointer to an already opened Kameleon object @@ -215,7 +215,9 @@ bool addLinesToState(ccmc::Kameleon* kameleon, const std::vector& see #ifdef OPENSPACE_MODULE_KAMELEON_ENABLED void addExtraQuantities(ccmc::Kameleon* kameleon, std::vector& extraScalarVars, - std::vector& extraMagVars, FieldlinesState& state) { + std::vector& extraMagVars, + FieldlinesState& state) +{ prepareStateAndKameleonForExtras(kameleon, extraScalarVars, extraMagVars, state); @@ -286,27 +288,28 @@ void addExtraQuantities(ccmc::Kameleon* kameleon, void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, std::vector& extraScalarVars, std::vector& extraMagVars, - FieldlinesState& state) { + FieldlinesState& state) +{ std::vector extraQuantityNames; fls::Model model = fls::stringToModel(kameleon->getModelName()); // Load the existing SCALAR variables into kameleon. // Remove non-existing variables from vector - for (int i = 0; i < extraScalarVars.size(); i++) { + for (int i = 0; i < extraScalarVars.size(); ++i) { std::string& str = extraScalarVars[i]; - bool isSuccesful = kameleon->doesVariableExist(str) && - kameleon->loadVariable(str); - if (!isSuccesful && - (model == fls::Model::Batsrus && (str == TAsPOverRho || str == "T" ))) { + bool success = kameleon->doesVariableExist(str) && kameleon->loadVariable(str); + if (!success && + (model == fls::Model::Batsrus && (str == TAsPOverRho || str == "T" ))) + { LDEBUG("BATSRUS doesn't contain variable T for temperature. Trying to " << "calculate it using the ideal gas law: T = pressure/density"); const std::string p = "p", r = "rho"; - isSuccesful = kameleon->doesVariableExist(p) && kameleon->loadVariable(p) - && kameleon->doesVariableExist(r) && kameleon->loadVariable(r); + success = kameleon->doesVariableExist(p) && kameleon->loadVariable(p) && + kameleon->doesVariableExist(r) && kameleon->loadVariable(r); str = TAsPOverRho; } - if (!isSuccesful) { - LWARNING("FAILED TO LOAD EXTRA VARIABLE: '" << str << "'. Ignoring it!"); + if (!success) { + LWARNING("Failed to load extra variable: '" << str << "'. Ignoring it!"); extraScalarVars.erase(extraScalarVars.begin() + i); --i; } else { @@ -317,23 +320,24 @@ void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, // Load the existing magnitude variables (should be provided in multiple of 3) // into kameleon. Remove non-existing variables from vector if (extraMagVars.size() % 3 == 0) { - for (int i = 0; i < static_cast(extraMagVars.size()); i += 3) { - std::string s1 = extraMagVars[i]; - std::string s2 = extraMagVars[i+1]; - std::string s3 = extraMagVars[i+2]; - bool isSuccesful = kameleon->doesVariableExist(s1) && + for (size_t i = 0; i < extraMagVars.size(); i += 3) { + const std::string& s1 = extraMagVars[i]; + const std::string& s2 = extraMagVars[i+1]; + const std::string& s3 = extraMagVars[i+2]; + bool success = kameleon->doesVariableExist(s1) && kameleon->doesVariableExist(s2) && kameleon->doesVariableExist(s3) && kameleon->loadVariable(s1) && kameleon->loadVariable(s2) && kameleon->loadVariable(s3); std::string name = "Magnitude of (" + s1 + ", "+ s2 + ", "+ s3 + ")"; - if (isSuccesful && model == fls::Model::Batsrus && s1 == "jx" && s2 == "jy" - && s3 == "jz") { + if (success && model == fls::Model::Batsrus && + s1 == "jx" && s2 == "jy" && s3 == "jz") + { // CCMC isn't really interested in the magnitude of current, but by the // magnitude of the part of the current's vector that is parallel to the // magnetic field => ensure that the magnetic variables are loaded - isSuccesful = kameleon->doesVariableExist("bx") && + success = kameleon->doesVariableExist("bx") && kameleon->doesVariableExist("by") && kameleon->doesVariableExist("bz") && kameleon->loadVariable("bx") && @@ -341,8 +345,8 @@ void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, kameleon->loadVariable("bz"); name = JParallelB; } - if (!isSuccesful) { - LWARNING("FAILED TO LOAD AT LEAST ONE OF THE MAGNITUDE VARIABLES: " + if (!success) { + LWARNING("Failed to load at least one of the magnitude variables: " << s1 << ", " << s2 << " & " << s3 << ". Removing ability to store corresponding magnitude!"); extraMagVars.erase( @@ -351,7 +355,7 @@ void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, ); i -= 3; } else { - extraQuantityNames.push_back(name); + extraQuantityNames.push_back(std::move(name)); } } } else { @@ -361,9 +365,8 @@ void prepareStateAndKameleonForExtras(ccmc::Kameleon* kameleon, << "Expects multiple of 3 but " << extraMagVars.size() << " are provided"); } - state.setExtraQuantityNames(extraQuantityNames); + state.setExtraQuantityNames(std::move(extraQuantityNames)); } #endif // OPENSPACE_MODULE_KAMELEON_ENABLED -} // namespace fls -} // namespace openspace +} // namespace openspace::fls diff --git a/modules/fieldlinessequence/util/kameleonfieldlinehelper.h b/modules/fieldlinessequence/util/kameleonfieldlinehelper.h index d769f28aab..eb0bfaed14 100644 --- a/modules/fieldlinessequence/util/kameleonfieldlinehelper.h +++ b/modules/fieldlinessequence/util/kameleonfieldlinehelper.h @@ -26,7 +26,6 @@ #define __OPENSPACE_MODULE_FIELDLINESSEQUENCE___KAMELEONFIELDLINEHELPER___H__ #include - #include #include @@ -36,11 +35,9 @@ class FieldlinesState; namespace fls { -bool convertCdfToFieldlinesState(FieldlinesState& state, const std::string cdfPath, - const std::vector& seedPoints, - const std::string tracingVar, - std::vector& extraVars, - std::vector& extraMagVars); +bool convertCdfToFieldlinesState(FieldlinesState& state, const std::string& cdfPath, + const std::vector& seedPoints, std::string tracingVar, + std::vector& extraVars, std::vector& extraMagVars); } // namespace fls } // namespace openspace From 467555447105e6c273e4d1547601108a0afabdd7 Mon Sep 17 00:00:00 2001 From: Emil Axelsson Date: Tue, 6 Mar 2018 16:59:06 +0100 Subject: [PATCH 02/24] One Property Tree (#500) * Organize properties in one single property tree * Update scenes to work with one property tree. Fix documentation issues. --- data/assets/default.scene | 31 +++--- data/assets/newhorizons.scene | 56 +++++------ data/assets/osirisrex.scene | 5 +- data/assets/rosetta.scene | 18 ++-- .../missions/osirisrex/osirisrex.asset | 6 +- .../missions/osirisrex/scheduled_scripts.lua | 14 --- .../missions/osirisrex/script_schedule.asset | 18 ++++ data/assets/util/default_keybindings.asset | 43 ++++----- data/assets/util/property_helper.asset | 21 +++++ data/assets/util/renderable_helper.asset | 15 +++ data/assets/util/scene_helper.asset | 8 -- .../assets/util/script_scheduler_helper.asset | 24 +++++ data/web/common/base.js | 4 + data/web/common/style.css | 42 ++++++--- data/web/documentation/documentation.hbs | 2 +- data/web/documentation/script.js | 13 +++ data/web/factories/factory.hbs | 2 +- data/web/factories/script.js | 13 +++ data/web/kameleondocumentation/script.js | 13 +++ data/web/keybindings/keybinding.hbs | 4 +- data/web/keybindings/script.js | 13 +++ data/web/luascripting/script.js | 13 +++ data/web/luascripting/scripting.hbs | 2 +- data/web/properties/main.hbs | 4 +- data/web/properties/property.hbs | 5 +- data/web/properties/propertyowner.hbs | 2 +- data/web/properties/script.js | 13 +++ data/web/scenelicense/script.js | 13 +++ .../openspace/engine/configurationmanager.h | 4 +- include/openspace/engine/moduleengine.h | 5 +- include/openspace/engine/openspaceengine.h | 7 +- include/openspace/engine/settingsengine.h | 54 ----------- include/openspace/properties/propertyowner.h | 6 +- include/openspace/scene/scene.h | 11 +-- modules/imgui/imguimodule.cpp | 21 +++-- modules/imgui/include/gui.h | 5 +- modules/imgui/src/gui.cpp | 51 ++++++---- openspace.cfg | 1 + scripts/common.lua | 54 ----------- scripts/core_scripts.lua | 2 +- src/CMakeLists.txt | 2 - src/documentation/documentationengine.cpp | 13 +-- src/documentation/documentationgenerator.cpp | 33 +++++-- src/engine/configurationmanager.cpp | 1 + src/engine/moduleengine.cpp | 5 + src/engine/openspaceengine.cpp | 59 ++++++------ src/engine/settingsengine.cpp | 94 ------------------- src/interaction/keybindingmanager.cpp | 13 +-- src/interaction/touchbar.mm | 24 ++--- src/properties/propertyowner.cpp | 80 +++++++++++++++- src/query/query.cpp | 65 +------------ src/scene/scene.cpp | 91 +----------------- src/scripting/scriptengine.cpp | 12 +-- 53 files changed, 527 insertions(+), 603 deletions(-) delete mode 100644 data/assets/scene/solarsystem/missions/osirisrex/scheduled_scripts.lua create mode 100644 data/assets/scene/solarsystem/missions/osirisrex/script_schedule.asset create mode 100644 data/assets/util/property_helper.asset create mode 100644 data/assets/util/renderable_helper.asset create mode 100644 data/assets/util/script_scheduler_helper.asset create mode 100644 data/web/common/base.js delete mode 100644 include/openspace/engine/settingsengine.h delete mode 100644 scripts/common.lua delete mode 100644 src/engine/settingsengine.cpp diff --git a/data/assets/default.scene b/data/assets/default.scene index 364d38fcab..af3d3ae439 100644 --- a/data/assets/default.scene +++ b/data/assets/default.scene @@ -1,5 +1,6 @@ local assetHelper = asset.require('util/asset_helper') local sceneHelper = asset.require('util/scene_helper') +local propertyHelper = asset.require('util/property_helper') -- Specifying which other assets should be loaded in this scene asset.require('spice/base') @@ -18,31 +19,31 @@ asset.request('customization/globebrowsing') local Keybindings = { { Key = "s", - Command = sceneHelper.property.invert('Earth.RenderableGlobe.Layers.NightLayers.Earth at Night 2012.Enabled') .. - sceneHelper.property.invert('Earth.RenderableGlobe.PerformShading') .. - sceneHelper.property.invert('Earth.RenderableGlobe.Atmosphere') .. - sceneHelper.property.invert('Earth.RenderableGlobe.Layers.WaterMasks.MODIS_Water_Mask.Enabled'), + Command = propertyHelper.invert('Scene.Earth.RenderableGlobe.Layers.NightLayers.Earth at Night 2012.Enabled') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.PerformShading') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.Atmosphere') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.Layers.WaterMasks.MODIS_Water_Mask.Enabled'), Documentation = "Toggle night texture, shading, atmosphere, and water for Earth.", Local = false }, { Key = "b", - Command = sceneHelper.property.invert('MilkyWay.renderable.Enabled') .. - sceneHelper.property.invert('Stars.renderable.Enabled'), + Command = propertyHelper.invert('Scene.MilkyWay.renderable.Enabled') .. + propertyHelper.invert('Scene.Stars.renderable.Enabled'), Documentation = "Toggle background (Stars and Milkyway).", Local = false }, { Key = "g", - Command = sceneHelper.property.invert('MilkyWay.renderable.Enabled') .. - sceneHelper.property.invert('Stars.renderable.Enabled') .. - sceneHelper.property.invert('Earth.RenderableGlobe.Layers.NightLayers.Earth at Night 2012.Enabled') .. - sceneHelper.property.invert('Earth.RenderableGlobe.PerformShading') .. - sceneHelper.property.invert('Mars.RenderableGlobe.PerformShading') .. - sceneHelper.property.invert('Earth.RenderableGlobe.Atmosphere') .. - sceneHelper.property.invert('Earth.RenderableGlobe.Layers.WaterMasks.MODIS_Water_Mask.Enabled') .. - sceneHelper.property.invert('Moon.RenderableGlobe.Enabled') .. - sceneHelper.property.invert('Sun.renderable.Enabled'), + Command = propertyHelper.invert('Scene.MilkyWay.renderable.Enabled') .. + propertyHelper.invert('Scene.Stars.renderable.Enabled') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.Layers.NightLayers.Earth at Night 2012.Enabled') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.PerformShading') .. + propertyHelper.invert('Scene.Mars.RenderableGlobe.PerformShading') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.Atmosphere') .. + propertyHelper.invert('Scene.Earth.RenderableGlobe.Layers.WaterMasks.MODIS_Water_Mask.Enabled') .. + propertyHelper.invert('Scene.Moon.RenderableGlobe.Enabled') .. + propertyHelper.invert('Scene.Sun.renderable.Enabled'), Documentation = "Toogles background and shading mode on the Earth and Mars alongside visibility of the Moon and the Sun", Local = false }, diff --git a/data/assets/newhorizons.scene b/data/assets/newhorizons.scene index fb2e606c93..3b109a3a34 100644 --- a/data/assets/newhorizons.scene +++ b/data/assets/newhorizons.scene @@ -1,5 +1,7 @@ local assetHelper = asset.require('util/asset_helper') local sceneHelper = asset.require('util/scene_helper') +local propertyHelper = asset.require('util/property_helper') +local renderableHelper = asset.require('util/renderable_helper') -- Specifying which other assets should be loaded in this scene asset.require('spice/base') @@ -34,94 +36,94 @@ local Keybindings = { }, { Key = "F8", - Command = "openspace.setPropertyValue('Pluto.renderable.ProjectionComponent.ClearAllProjections', true);" .. - "openspace.setPropertyValue('Charon.renderable.ProjectionComponent.ClearAllProjections', true);", + Command = "openspace.setPropertyValue('Scene.Pluto.renderable.ProjectionComponent.ClearAllProjections', true);" .. + "openspace.setPropertyValue('Scene.Charon.renderable.ProjectionComponent.ClearAllProjections', true);", Documentation = "Removes all image projections from Pluto and Charon.", Local = false }, { Key = "F9", Command = "openspace.time.setTime('2015-07-14T09:00:00.00');" .. - "openspace.setPropertyValue('Pluto.renderable.ClearAllProjections', true);" .. - "openspace.setPropertyValue('Charon.renderable.ClearAllProjections', true);", + "openspace.setPropertyValue('Scene.Pluto.renderable.ClearAllProjections', true);" .. + "openspace.setPropertyValue('Scene.Charon.renderable.ClearAllProjections', true);", Documentation = "Jumps to the 14th of July 2015 at 0900 UTC and clears all projections.", Local = false }, { Key = "KP_8", - Command = sceneHelper.property.increment('Pluto.renderable.HeightExaggeration', 5000000), + Command = propertyHelper.increment('Scene.Pluto.renderable.HeightExaggeration', 5000000), Documentation = "Increases the height map exaggeration on Pluto.", Local = false }, { Key = "KP_2", - Command = sceneHelper.property.decrement('Pluto.renderable.HeightExaggeration', 5000000), + Command = propertyHelper.decrement('Scene.Pluto.renderable.HeightExaggeration', 5000000), Documentation = "Decreases the height map exaggeration on Pluto.", Local = false }, { Key = "KP_9", - Command = sceneHelper.property.increment('Charon.renderable.HeightExaggeration', 5000000), + Command = propertyHelper.increment('Scene.Charon.renderable.HeightExaggeration', 5000000), Documentation = "Increases the height map exaggeration on Charon.", Local = false }, { Key = "KP_3", - Command = sceneHelper.property.decrement('Charon.renderable.HeightExaggeration', 5000000), + Command = propertyHelper.decrement('Scene.Charon.renderable.HeightExaggeration', 5000000), Documentation = "Decreases the height map exaggeration on Charon.", Local = false }, { Key = "q", - Command = sceneHelper.property.invert('SunMarker.renderable.Enabled'), + Command = propertyHelper.invert('Scene.SunMarker.renderable.Enabled'), Documentation = "Toggles the visibility of the text marking the location of the Sun.", Local = false }, { Key = "e", - Command = sceneHelper.property.invert('EarthMarker.renderable.Enabled'), + Command = propertyHelper.invert('Scene.EarthMarker.renderable.Enabled'), Documentation = "Toggles the visibility of the text marking the location of the Earth.", Local = false }, { Key = "o", - Command = sceneHelper.property.invert('PlutoTrail.renderable.Enabled'), + Command = propertyHelper.invert('Scene.PlutoTrail.renderable.Enabled'), Documentation = "Toggles the visibility of the trail behind Pluto.", Local = false }, { Key = "j", - Command = sceneHelper.renderable.toggle('PlutoText') .. sceneHelper.renderable.toggle('CharonText') .. - sceneHelper.renderable.toggle('HydraText') .. sceneHelper.renderable.toggle('NixText') .. - sceneHelper.renderable.toggle('KerberosText') .. sceneHelper.renderable.toggle('StyxText'), + Command = renderableHelper.toggle('Scene.PlutoText') .. renderableHelper.toggle('Scene.CharonText') .. + renderableHelper.toggle('Scene.HydraText') .. renderableHelper.toggle('Scene.NixText') .. + renderableHelper.toggle('Scene.KerberosText') .. renderableHelper.toggle('Scene.StyxText'), Documentation = "Toggles the visibility of the text labels of Pluto, Charon, Hydra, Nix, Kerberos, and Styx.", Local = false }, { Key = "l", - Command = sceneHelper.property.invert('Labels.renderable.Enabled'), + Command = propertyHelper.invert('Scene.Labels.renderable.Enabled'), Documentation = "Toggles the visibility of the labels for the New Horizons instruments.", Local = false }, { Key = "m", - Command = sceneHelper.property.invert('NH_LORRI.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_LEISA.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_PAN1.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_PAN2.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_RED.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_BLUE.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_FT.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_METHANE.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_RALPH_MVIC_NIR.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_ALICE_AIRGLOW.renderable.SolidDraw') .. - sceneHelper.property.invert('NH_ALICE_SOC.renderable.SolidDraw'), + Command = propertyHelper.invert('Scene.NH_LORRI.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_LEISA.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_PAN1.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_PAN2.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_RED.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_BLUE.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_FT.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_METHANE.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_RALPH_MVIC_NIR.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_ALICE_AIRGLOW.renderable.SolidDraw') .. + propertyHelper.invert('Scene.NH_ALICE_SOC.renderable.SolidDraw'), Documentation = "Draws the instrument field of views in a solid color or as lines.", Local = false }, { Key = "t", - Command = sceneHelper.renderable.toggle('PlutoShadow') .. sceneHelper.renderable.toggle('CharonShadow'), + Command = renderableHelper.toggle('Scene.PlutoShadow') .. renderableHelper.toggle('Scene.CharonShadow'), Documentation = "Toggles the visibility of the shadow visualization of Pluto and Charon.", Local = false } diff --git a/data/assets/osirisrex.scene b/data/assets/osirisrex.scene index 556981a006..98a30d82b0 100644 --- a/data/assets/osirisrex.scene +++ b/data/assets/osirisrex.scene @@ -1,5 +1,6 @@ local assetHelper = asset.require('util/asset_helper') local sceneHelper = asset.require('util/scene_helper') +local propertyHelper = asset.require('util/property_helper') -- Specifying which other assets should be loaded in this scene asset.require('spice/base') @@ -64,13 +65,13 @@ local Keybindings = { }, { Key = "q", - Command = sceneHelper.property.invert('SunMarker.renderable.Enabled'), + Command = propertyHelper.invert('Scene.SunMarker.renderable.Enabled'), Documentation = "Toggles the visibility of the text marking the location of the Sun.", Local = false }, { Key = "e", - Command = sceneHelper.property.invert('EarthMarker.renderable.Enabled'), + Command = propertyHelper.invert('Scene.EarthMarker.renderable.Enabled'), Documentation = "Toggles the visibility of the text marking the location of the Earth.", Local = false } diff --git a/data/assets/rosetta.scene b/data/assets/rosetta.scene index 93f37fa291..b4fe3750e1 100644 --- a/data/assets/rosetta.scene +++ b/data/assets/rosetta.scene @@ -1,5 +1,7 @@ local assetHelper = asset.require('util/asset_helper') local sceneHelper = asset.require('util/scene_helper') +local propertyHelper = asset.require('util/property_helper') +local renderableHelper = asset.require('util/renderable_helper') -- Specifying which other assets should be loaded in this scene asset.require('spice/base') @@ -41,38 +43,38 @@ local Keybindings = { }, { Key = "F8", - Command = "openspace.setPropertyValue('67P.renderable.ProjectionComponent.clearAllProjections', true)", + Command = "openspace.setPropertyValue('Scene.67P.renderable.ProjectionComponent.clearAllProjections', true)", Documentation = "Removes all image projections from 67P.", Local = false }, { Key = "q", - Command = sceneHelper.property.invert('SunMarker.renderable.Enabled'), + Command = propertyHelper.invert('Scene.SunMarker.renderable.Enabled'), Documentation = "Toggles the visibility of the text marking the location of the Sun.", Local = false }, { Key = "e", - Command = helper.renderable.toggle('JupiterTrail') .. helper.renderable.toggle('SaturnTrail') .. - helper.renderable.toggle('UranusTrail') .. helper.renderable.toggle('NeptuneTrail'), + Command = renderableHelper.toggle('Scene.JupiterTrail') .. renderableHelper.toggle('Scene.SaturnTrail') .. + renderableHelper.toggle('Scene.UranusTrail') .. renderableHelper.toggle('Scene.NeptuneTrail'), Documentation = "Toggles the visibility of all trails further from the Sun than 67P.", Local = false }, { Key = "i", - Command = sceneHelper.renderable.toggle('ImagePlaneRosetta'), + Command = renderableHelper.toggle('Scene.ImagePlaneRosetta'), Documentation = "Toggles the visibility of the free floating image plane.", Local = false }, { Key = "f", - Command = sceneHelper.renderable.toggle('PhilaeTrail'), + Command = renderableHelper.toggle('Scene.PhilaeTrail'), Documentation = "Toggles the visibility of Philae's trail.", Local = false }, { Key = "p", - Command = sceneHelper.property.invert('67P.renderable.ProjectionComponent.performProjection'), + Command = propertyHelper.invert('Scene.67P.renderable.ProjectionComponent.performProjection'), Documentation = "Enables or disables the image projection on 67P.", Local = false } @@ -129,7 +131,7 @@ asset.onInitialize(function () Rotation = { -0.106166, 0.981574, -0.084545, 0.134513 }, }) - openspace.setPropertyValue('67P.renderable.PerformShading', false); + openspace.setPropertyValue('Scene.67P.renderable.PerformShading', false); end) asset.onDeinitialize(function () diff --git a/data/assets/scene/solarsystem/missions/osirisrex/osirisrex.asset b/data/assets/scene/solarsystem/missions/osirisrex/osirisrex.asset index 6d5bff9427..1444106adc 100644 --- a/data/assets/scene/solarsystem/missions/osirisrex/osirisrex.asset +++ b/data/assets/scene/solarsystem/missions/osirisrex/osirisrex.asset @@ -2,19 +2,15 @@ asset.request('./bennu') asset.request('./model') asset.request('./trail') - +asset.require('./script_schedule') local mission = asset.localResource('osirisrex.mission') local missionName -local scriptSchedule = asset.localResource("scheduled_scripts.lua") - asset.onInitialize(function() missionName = openspace.loadMission(mission) - openspace.scriptScheduler.loadFile(scriptSchedule) end) asset.onDeinitialize(function() openspace.unloadMission(missionName) - -- openspace.scriptScheduler.unloadFile end) diff --git a/data/assets/scene/solarsystem/missions/osirisrex/scheduled_scripts.lua b/data/assets/scene/solarsystem/missions/osirisrex/scheduled_scripts.lua deleted file mode 100644 index 39698b5532..0000000000 --- a/data/assets/scene/solarsystem/missions/osirisrex/scheduled_scripts.lua +++ /dev/null @@ -1,14 +0,0 @@ -dofile(openspace.absPath("${SCRIPTS}/common.lua")) - -return { - helper.scheduledScript.setEnabled("2016 SEP 08 23:05:00", "OsirisRexTrailSolarSystem", false), - helper.scheduledScript.setEnabled("2016 SEP 08 23:05:00", "OsirisRexTrailBennu", false), - - helper.scheduledScript.reversible.setEnabled("2016 SEP 08 23:05:01", "OsirisRexTrailEarth", true), - helper.scheduledScript.reversible.setEnabled("2016 SEP 09 00:00:00", "OsirisRexTrailSolarSystem", true), - helper.scheduledScript.reversible.setEnabled("2016 SEP 09 02:00:00", "OsirisRexTrailEarth", false), - helper.scheduledScript.reversible.setEnabled("2018 OCT 11 00:00:00", "OsirisRexTrailBennu", true), - helper.scheduledScript.reversible.setEnabled("2018 OCT 15 00:00:00", "OsirisRexTrailSolarSystem", false), - helper.scheduledScript.reversible.setEnabled("2019 AUG 01 00:00:00", "OsirisRexTrailSolarSystem", true), - helper.scheduledScript.reversible.setEnabled("2019 AUG 01 00:00:00", "OsirisRexTrailBennu", false), -} diff --git a/data/assets/scene/solarsystem/missions/osirisrex/script_schedule.asset b/data/assets/scene/solarsystem/missions/osirisrex/script_schedule.asset new file mode 100644 index 0000000000..147d918cfe --- /dev/null +++ b/data/assets/scene/solarsystem/missions/osirisrex/script_schedule.asset @@ -0,0 +1,18 @@ +local scriptSchedulerHelper = asset.require('util/script_scheduler_helper') + +asset.onInitialize(function () + scriptSchedulerHelper.scheduleRenderableEnabled("2016 SEP 08 23:05:00", "OsirisRexTrailSolarSystem", false) + scriptSchedulerHelper.scheduleRenderableEnabled("2016 SEP 08 23:05:00", "OsirisRexTrailBennu", false) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2016 SEP 08 23:05:01", "OsirisRexTrailEarth", true) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2016 SEP 09 00:00:00", "OsirisRexTrailSolarSystem", true) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2016 SEP 09 02:00:00", "OsirisRexTrailEarth", false) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2018 OCT 11 00:00:00", "OsirisRexTrailBennu", true) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2018 OCT 15 00:00:00", "OsirisRexTrailSolarSystem", false) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2019 AUG 01 00:00:00", "OsirisRexTrailSolarSystem", true) + scriptSchedulerHelper.scheduleRenderableEnabledReversable("2019 AUG 01 00:00:00", "OsirisRexTrailBennu", false) +end) + + +asset.onDeinitialize(function () + openspace.scriptScheduler.clear() +end) \ No newline at end of file diff --git a/data/assets/util/default_keybindings.asset b/data/assets/util/default_keybindings.asset index 01fcea5ee6..5a8e0b800b 100644 --- a/data/assets/util/default_keybindings.asset +++ b/data/assets/util/default_keybindings.asset @@ -1,23 +1,24 @@ -local helper = asset.require('./scene_helper') +local sceneHelper = asset.require('./scene_helper') +local propertyHelper = asset.require('./property_helper') local Keybindings = { { Key = "F2", Command = -[[local b = openspace.getPropertyValue('Global Properties.ImGUI.Main.Properties.Enabled'); -local c = openspace.getPropertyValue('Global Properties.ImGUI.Main.IsHidden'); -openspace.setPropertyValue('Global Properties.ImGUI.*.Enabled', false); +[[local b = openspace.getPropertyValue('Modules.ImGUI.Main.Scene Properties.Enabled'); +local c = openspace.getPropertyValue('Modules.ImGUI.Main.IsHidden'); +openspace.setPropertyValue('Modules.ImGUI.*.Enabled', false); if b and c then -- This can happen if the main properties window is enabled, the main gui is enabled -- and then closed again. So the main properties window is enabled, but also all -- windows are hidden - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.IsHidden', false); - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.Properties.Enabled', true); - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.Space/Time.Enabled', true); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.IsHidden', false); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.Scene Properties.Enabled', true); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.Space/Time.Enabled', true); else - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.Properties.Enabled', not b); - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.Space/Time.Enabled', not b); - openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.IsHidden', b); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.Scene Properties.Enabled', not b); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.Space/Time.Enabled', not b); + openspace.setPropertyValueSingle('Modules.ImGUI.Main.IsHidden', b); end]], Documentation = "Shows or hides the properties window", Local = true @@ -25,15 +26,15 @@ end]], { Key = "F3", Command = -[[local b = openspace.getPropertyValue('Global Properties.ImGUI.Main.Enabled'); -openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.Enabled', not b); -openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.IsHidden', b);]], +[[local b = openspace.getPropertyValue('Modules.ImGUI.Main.Enabled'); +openspace.setPropertyValueSingle('Modules.ImGUI.Main.Enabled', not b); +openspace.setPropertyValueSingle('Modules.ImGUI.Main.IsHidden', b);]], Documentation = "Shows or hides the entire user interface", Local = true }, { Key = "F4", - Command = helper.property.invert("RenderEngine.PerformanceMeasurements"), + Command = propertyHelper.invert("RenderEngine.PerformanceMeasurements"), Documentation = "Toogles performance measurements that shows rendering time informations.", Local = true }, @@ -57,19 +58,19 @@ openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.IsHidden', b);]], }, { Key = "f", - Command = helper.property.invert('NavigationHandler.OrbitalNavigator.Friction.RotationalFriction'), + Command = propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.RotationalFriction'), Documentation = "Toggles the rotational friction of the camera. If it is disabled, the camera rotates around the focus object indefinitely.", Local = false }, { Key = "Shift+f", - Command = helper.property.invert('NavigationHandler.OrbitalNavigator.Friction.ZoomFriction'), + Command = propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.ZoomFriction'), Documentation = "Toggles the zoom friction of the camera. If it is disabled, the camera rises up from or closes in towards the focus object indefinitely.", Local = false }, { Key = "Ctrl+f", - Command = helper.property.invert('NavigationHandler.OrbitalNavigator.Friction.RollFriction'), + Command = propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.RollFriction'), Documentation = "Toggles the roll friction of the camera. If it is disabled, the camera rolls around its own axis indefinitely.", Local = false }, @@ -84,9 +85,9 @@ openspace.setPropertyValueSingle('Global Properties.ImGUI.Main.IsHidden', b);]], local DeltaTimeKeys asset.onInitialize(function() - Keys = helper.bindKeys(Keybindings) + Keys = sceneHelper.bindKeys(Keybindings) - DeltaTimeKeys = helper.setDeltaTimeKeys({ + DeltaTimeKeys = sceneHelper.setDeltaTimeKeys({ -- 1 2 3 4 5 6 7 8 9 0 -------------------------------------------------------------------------------------------------------------------------- -- 1s 2s 5s 10s 30s 1m 2m 5m 10m 30m @@ -102,8 +103,8 @@ asset.onInitialize(function() end) asset.onDeinitialize(function () - helper.unbindKeys(Keybindings) - helper.unbindKeys(DeltaTimeKeys) + sceneHelper.unbindKeys(Keybindings) + sceneHelper.unbindKeys(DeltaTimeKeys) end) asset.export("DefaultKeybindings", Keybindings) diff --git a/data/assets/util/property_helper.asset b/data/assets/util/property_helper.asset new file mode 100644 index 0000000000..8d3576d130 --- /dev/null +++ b/data/assets/util/property_helper.asset @@ -0,0 +1,21 @@ +-- Function that returns the string that inverts the fully qualified boolean property 'property' +local invert = function(prop) + local escaped_property = "'" .. prop .. "'" + return "openspace.setPropertyValue(" .. escaped_property .. ", not openspace.getPropertyValue(" .. escaped_property .. "));" +end + +-- Function that returns the string that increments the 'property' by the 'value' +local increment = function(prop, value) + local v = value or 1 + local escaped_property = "'" .. prop .. "'" + return "openspace.setPropertyValue(" .. escaped_property .. ", openspace.getPropertyValue(" .. escaped_property .. ") + " .. v .. ");" +end + +-- Function that returns the string that decrements the 'property' by the 'value' +local decrement = function(prop, value) + return increment(prop, -value) +end + +asset.export('invert', invert) +asset.export('increment', increment) +asset.export('decrement', decrement) diff --git a/data/assets/util/renderable_helper.asset b/data/assets/util/renderable_helper.asset new file mode 100644 index 0000000000..300e66a574 --- /dev/null +++ b/data/assets/util/renderable_helper.asset @@ -0,0 +1,15 @@ +local propertyHelper = asset.require('./property_helper') + +-- Function that returns the string that enables/disables the renderable 'renderable' +local toggle = function(renderable) + return propertyHelper.invert(renderable .. ".renderable.Enabled") +end + +-- Function that returns the string that sets the enabled property of to +local setEnabled = function(renderable, enabled) + return "openspace.setPropertyValue('" .. renderable .. ".renderable.Enabled', " .. (enabled and "true" or "false") .. ");"; +end + + +asset.export('toggle', toggle) +asset.export('setEnabled', setEnabled) \ No newline at end of file diff --git a/data/assets/util/scene_helper.asset b/data/assets/util/scene_helper.asset index fa84befc03..45139ec6c6 100644 --- a/data/assets/util/scene_helper.asset +++ b/data/assets/util/scene_helper.asset @@ -52,11 +52,3 @@ local setDeltaTimeKeys = function(t) return result end asset.export("setDeltaTimeKeys", setDeltaTimeKeys) - ------------------------------------------------------------------ - -dofile(openspace.absPath("${SCRIPTS}/common.lua")) - -asset.export("property", helper.property) -asset.export("renderable", helper.renderable) -asset.export("scheduledScript", helper.scheduledScript) diff --git a/data/assets/util/script_scheduler_helper.asset b/data/assets/util/script_scheduler_helper.asset new file mode 100644 index 0000000000..4a3727b9b3 --- /dev/null +++ b/data/assets/util/script_scheduler_helper.asset @@ -0,0 +1,24 @@ +local renderableHelper = asset.require('./renderable_helper') + +-- Function that schedules scripts setting the enabled property +-- of to at time