mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2025-12-31 16:30:07 -06:00
* a new profile for the 2012 solar storm event, with looping hotkeys * added interaction spheres, profile cleanup + better discription * better actions and moved to assets instead of in profile * Set unique names for the different fieldline data synchronizations * single dataset in sequence now rendered before its associated timestamp * fix read access violation + added redundancy * load at start up instead of on runtime Co-authored-by: Alexander Bock <mail@alexanderbock.eu>
1242 lines
45 KiB
C++
1242 lines
45 KiB
C++
/*****************************************************************************************
|
|
* *
|
|
* OpenSpace *
|
|
* *
|
|
* Copyright (c) 2014-2022 *
|
|
* *
|
|
* 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 <modules/fieldlinessequence/rendering/renderablefieldlinessequence.h>
|
|
|
|
#include <modules/fieldlinessequence/fieldlinessequencemodule.h>
|
|
#include <modules/fieldlinessequence/util/kameleonfieldlinehelper.h>
|
|
#include <openspace/engine/globals.h>
|
|
#include <openspace/engine/windowdelegate.h>
|
|
#include <openspace/navigation/navigationhandler.h>
|
|
#include <openspace/navigation/orbitalnavigator.h>
|
|
#include <openspace/rendering/renderengine.h>
|
|
#include <openspace/scene/scene.h>
|
|
#include <openspace/util/timemanager.h>
|
|
#include <openspace/util/updatestructures.h>
|
|
#include <ghoul/filesystem/filesystem.h>
|
|
#include <ghoul/logging/logmanager.h>
|
|
#include <ghoul/opengl/openglstatecache.h>
|
|
#include <ghoul/opengl/programobject.h>
|
|
#include <ghoul/opengl/textureunit.h>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
constexpr const char* _loggerCat = "RenderableFieldlinesSequence";
|
|
|
|
constexpr 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."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo ColorQuantityInfo = {
|
|
"ColorQuantity",
|
|
"Quantity to Color By",
|
|
"Quantity used to color lines if the 'By Quantity' color method is selected."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo ColorMinMaxInfo = {
|
|
"ColorQuantityMinMax",
|
|
"ColorTable Min Value",
|
|
"Value to map to the lowest and highest end of the color table."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo ColorTablePathInfo = {
|
|
"ColorTablePath",
|
|
"Path to Color Table",
|
|
"Color Table/Transfer Function to use for 'By Quantity' coloring."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo ColorUniformInfo = {
|
|
"Color",
|
|
"Uniform Line Color",
|
|
"The uniform color of lines shown when 'Color Method' is set to 'Uniform'."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo ColorUseABlendingInfo = {
|
|
"ABlendingEnabled",
|
|
"Additive Blending",
|
|
"Activate/deactivate additive blending."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo DomainEnabledInfo = {
|
|
"DomainEnabled",
|
|
"Domain Limits",
|
|
"Enable/Disable domain limits"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo DomainXInfo = {
|
|
"LimitsX",
|
|
"X-limits",
|
|
"Valid range along the X-axis. [Min, Max]"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo DomainYInfo = {
|
|
"LimitsY",
|
|
"Y-limits",
|
|
"Valid range along the Y-axis. [Min, Max]"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo DomainZInfo = {
|
|
"LimitsZ",
|
|
"Z-limits",
|
|
"Valid range along the Z-axis. [Min, Max]"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo DomainRInfo = {
|
|
"LimitsR",
|
|
"Radial limits",
|
|
"Valid radial range. [Min, Max]"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowColorInfo = {
|
|
"FlowColor",
|
|
"Flow Color",
|
|
"Color of particles flow direction indication."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowEnabledInfo = {
|
|
"FlowEnabled",
|
|
"Flow Direction",
|
|
"Toggles the rendering of moving particles along the lines. Can, for example, "
|
|
"illustrate magnetic flow."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowReversedInfo = {
|
|
"Reversed",
|
|
"Reversed Flow",
|
|
"Toggle to make the flow move in the opposite direction."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowParticleSizeInfo = {
|
|
"ParticleSize",
|
|
"Particle Size",
|
|
"Size of the particles."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowParticleSpacingInfo = {
|
|
"ParticleSpacing",
|
|
"Particle Spacing",
|
|
"Spacing inbetween particles."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo FlowSpeedInfo = {
|
|
"Speed",
|
|
"Speed",
|
|
"Speed of the flow."
|
|
};
|
|
constexpr 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."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo MaskingMinMaxInfo = {
|
|
"MaskingMinLimit",
|
|
"Lower Limit",
|
|
"Lower and upper limit of the valid masking range"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo MaskingQuantityInfo = {
|
|
"MaskingQuantity",
|
|
"Quantity used for Masking",
|
|
"Quantity used for masking."
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
|
|
"LineWidth",
|
|
"Line Width",
|
|
"This value specifies the line width of the fieldlines"
|
|
};
|
|
constexpr openspace::properties::Property::PropertyInfo TimeJumpButtonInfo = {
|
|
"TimeJumpToStart",
|
|
"Jump to Start Of Sequence",
|
|
"Performs a time jump to the start of the sequence."
|
|
};
|
|
|
|
struct [[codegen::Dictionary(RenderableFieldlinesSequence)]] Parameters {
|
|
enum class SourceFileType {
|
|
Cdf,
|
|
Json,
|
|
Osfls
|
|
};
|
|
// Input file type. Should be cdf, json or osfls
|
|
SourceFileType inputFileType;
|
|
|
|
// Path to folder containing the input files
|
|
std::filesystem::path sourceFolder [[codegen::directory()]];
|
|
|
|
// Path to a .txt file containing seed points. Mandatory if CDF as input.
|
|
// Files need time stamp in file name like so: yyyymmdd_hhmmss.txt
|
|
std::optional<std::filesystem::path> seedPointDirectory [[codegen::directory()]];
|
|
|
|
// Currently supports: batsrus, enlil & pfss
|
|
std::optional<std::string> simulationModel;
|
|
|
|
// Extra variables such as rho, p or t
|
|
std::optional<std::vector<std::string>> extraVariables;
|
|
|
|
// Which variable in CDF file to trace. b is default for fieldline
|
|
std::optional<std::string> tracingVariable;
|
|
|
|
// Convert the models distance unit, ex. AU for Enlil, to meters.
|
|
// Can be used during runtime to scale domain limits.
|
|
// 1.f is default, assuming meters as input.
|
|
std::optional<float> scaleToMeters;
|
|
|
|
// Set to true if you are streaming data during runtime
|
|
std::optional<bool> loadAtRuntime;
|
|
|
|
// [[codegen::verbatim(ColorUniformInfo.description)]]
|
|
std::optional<glm::vec4> color [[codegen::color()]];
|
|
|
|
// A list of paths to transferfunction .txt files containing color tables
|
|
// used for colorizing the fieldlines according to different parameters
|
|
std::optional<std::vector<std::string>> colorTablePaths;
|
|
|
|
// [[codegen::verbatim(ColorMethodInfo.description)]]
|
|
std::optional<std::string> colorMethod;
|
|
|
|
// [[codegen::verbatim(ColorQuantityInfo.description)]]
|
|
std::optional<int> colorQuantity;
|
|
|
|
// List of ranges for which their corresponding parameters values will be
|
|
// colorized by. Should be entered as {min value, max value} per range
|
|
std::optional<std::vector<glm::vec2>> colorTableRanges;
|
|
|
|
// Enables flow, showing the direction, but not accurate speed, that particles
|
|
// would be traveling
|
|
std::optional<bool> flowEnabled;
|
|
|
|
// [[codegen::verbatim(FlowColorInfo.description)]]
|
|
std::optional<glm::vec4> flowColor [[codegen::color()]];
|
|
|
|
// [[codegen::verbatim(FlowReversedInfo.description)]]
|
|
std::optional<bool> reversedFlow;
|
|
|
|
// [[codegen::verbatim(FlowParticleSizeInfo.description)]]
|
|
std::optional<int> particleSize;
|
|
|
|
// [[codegen::verbatim(FlowParticleSpacingInfo.description)]]
|
|
std::optional<int> particleSpacing;
|
|
|
|
// [[codegen::verbatim(FlowSpeedInfo.description)]]
|
|
std::optional<int> flowSpeed;
|
|
|
|
// [[codegen::verbatim(MaskingEnabledInfo.description)]]
|
|
std::optional<bool> maskingEnabled;
|
|
|
|
// [[codegen::verbatim(MaskingQuantityInfo.description)]]
|
|
std::optional<int> maskingQuantity;
|
|
|
|
// List of ranges for which their corresponding parameters values will be
|
|
// masked by. Should be entered as {min value, max value} per range
|
|
std::optional<std::vector<glm::vec2>> maskingRanges;
|
|
|
|
// Value should be path to folder where states are saved. Specifying this
|
|
// makes it use file type converter
|
|
// (JSON/CDF input => osfls output & oslfs input => JSON output)
|
|
std::optional<std::string> outputFolder;
|
|
|
|
// [[codegen::verbatim(LineWidthInfo.description)]]
|
|
std::optional<float> lineWidth;
|
|
|
|
// If data sets parameter start_time differ from start of run,
|
|
// elapsed_time_in_seconds might be in relation to start of run.
|
|
// ManuelTimeOffset will be added to trigger time.
|
|
std::optional<double> manualTimeOffset;
|
|
};
|
|
#include "renderablefieldlinessequence_codegen.cpp"
|
|
} // namespace
|
|
|
|
namespace openspace {
|
|
fls::Model stringToModel(std::string str);
|
|
std::unordered_map<std::string, std::vector<glm::vec3>>
|
|
extractSeedPointsFromFiles(std::filesystem::path);
|
|
std::vector<std::string>
|
|
extractMagnitudeVarsFromStrings(std::vector<std::string> extrVars);
|
|
|
|
documentation::Documentation RenderableFieldlinesSequence::Documentation() {
|
|
return codegen::doc<Parameters>("fieldlinessequence_renderablefieldlinessequence");
|
|
}
|
|
|
|
RenderableFieldlinesSequence::RenderableFieldlinesSequence(
|
|
const ghoul::Dictionary& dictionary)
|
|
: Renderable(dictionary)
|
|
, _colorGroup({ "Color" })
|
|
, _colorMethod(ColorMethodInfo, properties::OptionProperty::DisplayType::Radio)
|
|
, _colorQuantity(ColorQuantityInfo, properties::OptionProperty::DisplayType::Dropdown)
|
|
, _colorQuantityMinMax(
|
|
ColorMinMaxInfo,
|
|
glm::vec2(-0.f, 100.f),
|
|
glm::vec2(-5000.f),
|
|
glm::vec2(5000.f)
|
|
)
|
|
, _colorTablePath(ColorTablePathInfo)
|
|
, _colorUniform(
|
|
ColorUniformInfo,
|
|
glm::vec4(0.3f, 0.57f, 0.75f, 0.5f),
|
|
glm::vec4(0.f),
|
|
glm::vec4(1.f)
|
|
)
|
|
, _colorABlendEnabled(ColorUseABlendingInfo, true)
|
|
, _domainEnabled(DomainEnabledInfo, true)
|
|
, _domainGroup({ "Domain" })
|
|
, _domainX(DomainXInfo)
|
|
, _domainY(DomainYInfo)
|
|
, _domainZ(DomainZInfo)
|
|
, _domainR(DomainRInfo)
|
|
, _flowColor(
|
|
FlowColorInfo,
|
|
glm::vec4(0.96f, 0.88f, 0.8f, 0.5f),
|
|
glm::vec4(0.f),
|
|
glm::vec4(1.f)
|
|
)
|
|
, _flowEnabled(FlowEnabledInfo, false)
|
|
, _flowGroup({ "Flow" })
|
|
, _flowParticleSize(FlowParticleSizeInfo, 5, 0, 500)
|
|
, _flowParticleSpacing(FlowParticleSpacingInfo, 60, 0, 500)
|
|
, _flowReversed(FlowReversedInfo, false)
|
|
, _flowSpeed(FlowSpeedInfo, 20, 0, 1000)
|
|
, _maskingEnabled(MaskingEnabledInfo, false)
|
|
, _maskingGroup({ "Masking" })
|
|
, _maskingMinMax(
|
|
MaskingMinMaxInfo,
|
|
glm::vec2(0.f, 100.f),
|
|
glm::vec2(-5000.f),
|
|
glm::vec2(5000.f)
|
|
)
|
|
, _maskingQuantity(
|
|
MaskingQuantityInfo,
|
|
properties::OptionProperty::DisplayType::Dropdown
|
|
)
|
|
, _lineWidth(LineWidthInfo, 1.f, 1.f, 20.f)
|
|
, _jumpToStartBtn(TimeJumpButtonInfo)
|
|
{
|
|
const Parameters p = codegen::bake<Parameters>(dictionary);
|
|
|
|
// Extracts the general information (from the asset file) that
|
|
// is mandatory for the class to function;
|
|
std::string fileTypeString;
|
|
switch (p.inputFileType) {
|
|
case Parameters::SourceFileType::Cdf:
|
|
_inputFileType = SourceFileType::Cdf;
|
|
fileTypeString = "cdf";
|
|
if (p.tracingVariable.has_value()) {
|
|
_tracingVariable = *p.tracingVariable;
|
|
}
|
|
else {
|
|
_tracingVariable = "b"; // Magnetic field variable as default
|
|
LWARNING(fmt::format(
|
|
"No tracing variable, using default '{}'", _tracingVariable
|
|
));
|
|
}
|
|
break;
|
|
case Parameters::SourceFileType::Json:
|
|
_inputFileType = SourceFileType::Json;
|
|
fileTypeString = "json";
|
|
break;
|
|
case Parameters::SourceFileType::Osfls:
|
|
_inputFileType = SourceFileType::Osfls;
|
|
fileTypeString = "osfls";
|
|
break;
|
|
}
|
|
|
|
// Ensure that the source folder exists and then extract
|
|
// the files with the same extension as fileTypeString
|
|
std::filesystem::path sourcePath = p.sourceFolder;
|
|
if (!std::filesystem::is_directory(sourcePath)) {
|
|
LERROR(fmt::format(
|
|
"FieldlinesSequence {} is not a valid directory",
|
|
sourcePath.string()
|
|
));
|
|
}
|
|
|
|
// Extract all file paths from the provided folder
|
|
namespace fs = std::filesystem;
|
|
for (const fs::directory_entry& e : fs::directory_iterator(sourcePath)) {
|
|
if (e.is_regular_file()) {
|
|
std::string eStr = e.path().string();
|
|
_sourceFiles.push_back(eStr);
|
|
}
|
|
}
|
|
std::sort(_sourceFiles.begin(), _sourceFiles.end());
|
|
|
|
// Remove all files that don't have fileTypeString as file extension
|
|
_sourceFiles.erase(
|
|
std::remove_if(
|
|
_sourceFiles.begin(),
|
|
_sourceFiles.end(),
|
|
[&fileTypeString](const std::string& str) {
|
|
const size_t extLength = fileTypeString.length();
|
|
std::string sub = str.substr(str.length() - extLength, extLength);
|
|
std::transform(
|
|
sub.begin(),
|
|
sub.end(),
|
|
sub.begin(),
|
|
[](char c) { return static_cast<char>(::tolower(c)); }
|
|
);
|
|
return sub != fileTypeString;
|
|
}
|
|
),
|
|
_sourceFiles.end()
|
|
);
|
|
|
|
// Ensure that there are available and valid source files left
|
|
if (_sourceFiles.empty()) {
|
|
LERROR(fmt::format(
|
|
"{} contains no {} files", sourcePath.string(), fileTypeString
|
|
));
|
|
}
|
|
_extraVars = p.extraVariables.value_or(_extraVars);
|
|
_flowEnabled = p.flowEnabled.value_or(_flowEnabled);
|
|
_flowColor = p.flowColor.value_or(_flowColor);
|
|
_flowReversed = p.reversedFlow.value_or(_flowReversed);
|
|
_flowParticleSize = p.particleSize.value_or(_flowParticleSize);
|
|
_flowParticleSpacing = p.particleSpacing.value_or(_flowParticleSpacing);
|
|
_flowSpeed = p.flowSpeed.value_or(_flowSpeed);
|
|
_lineWidth = p.lineWidth.value_or(_lineWidth);
|
|
_manualTimeOffset = p.manualTimeOffset.value_or(_manualTimeOffset);
|
|
_modelStr = p.simulationModel.value_or(_modelStr);
|
|
_seedPointDirectory = p.seedPointDirectory.value_or(_seedPointDirectory);
|
|
_maskingEnabled = p.maskingEnabled.value_or(_maskingEnabled);
|
|
_maskingQuantityTemp = p.maskingQuantity.value_or(_maskingQuantityTemp);
|
|
if (p.colorTablePaths.has_value()) {
|
|
_colorTablePaths = p.colorTablePaths.value();
|
|
}
|
|
else {
|
|
// Set a default color table, just in case the (optional) user defined paths are
|
|
// corrupt or not provided
|
|
_colorTablePaths.push_back(FieldlinesSequenceModule::DefaultTransferFunctionFile);
|
|
}
|
|
|
|
_colorUniform = p.color.value_or(_colorUniform);
|
|
|
|
_colorMethod.addOption(static_cast<int>(ColorMethod::Uniform), "Uniform");
|
|
_colorMethod.addOption(static_cast<int>(ColorMethod::ByQuantity), "By Quantity");
|
|
if (p.colorMethod.has_value()) {
|
|
if (p.colorMethod.value() == "Uniform") {
|
|
_colorMethod = static_cast<int>(ColorMethod::Uniform);
|
|
}
|
|
else {
|
|
_colorMethod = static_cast<int>(ColorMethod::ByQuantity);
|
|
}
|
|
}
|
|
else {
|
|
_colorMethod = static_cast<int>(ColorMethod::Uniform);
|
|
}
|
|
|
|
if (p.colorQuantity.has_value()) {
|
|
_colorMethod = static_cast<int>(ColorMethod::ByQuantity);
|
|
_colorQuantityTemp = *p.colorQuantity;
|
|
}
|
|
|
|
if (p.colorTableRanges.has_value()) {
|
|
_colorTableRanges = *p.colorTableRanges;
|
|
}
|
|
else {
|
|
_colorTableRanges.push_back(glm::vec2(0.f, 1.f));
|
|
}
|
|
|
|
_loadingStatesDynamically = p.loadAtRuntime.value_or(_loadingStatesDynamically);
|
|
if (_loadingStatesDynamically && _inputFileType != SourceFileType::Osfls) {
|
|
LWARNING("Load at run time is only supported for osfls file type");
|
|
_loadingStatesDynamically = false;
|
|
}
|
|
|
|
if (p.maskingRanges.has_value()) {
|
|
_maskingRanges = *p.maskingRanges;
|
|
}
|
|
else {
|
|
_maskingRanges.push_back(glm::vec2(-100000.f, 100000.f)); // some default values
|
|
}
|
|
|
|
_outputFolderPath = p.outputFolder.value_or(_outputFolderPath);
|
|
if (!_outputFolderPath.empty() && !std::filesystem::is_directory(_outputFolderPath)) {
|
|
_outputFolderPath.clear();
|
|
LERROR(fmt::format(
|
|
"The specified output path: '{}', does not exist", _outputFolderPath
|
|
));
|
|
}
|
|
|
|
_scalingFactor = p.scaleToMeters.value_or(_scalingFactor);
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::initialize() {
|
|
_transferFunction = std::make_unique<TransferFunction>(
|
|
absPath(_colorTablePaths[0]).string()
|
|
);
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::initializeGL() {
|
|
// Setup shader program
|
|
_shaderProgram = global::renderEngine->buildRenderProgram(
|
|
"FieldlinesSequence",
|
|
absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_vs.glsl"),
|
|
absPath("${MODULE_FIELDLINESSEQUENCE}/shaders/fieldlinessequence_fs.glsl")
|
|
);
|
|
|
|
// Extract source file type specific information from dictionary
|
|
// & get states from source
|
|
switch (_inputFileType) {
|
|
case SourceFileType::Cdf:
|
|
if (!getStatesFromCdfFiles()) {
|
|
return;
|
|
}
|
|
break;
|
|
case SourceFileType::Json:
|
|
if (!loadJsonStatesIntoRAM()) {
|
|
return;
|
|
}
|
|
break;
|
|
case SourceFileType::Osfls:
|
|
if (_loadingStatesDynamically) {
|
|
if (!prepareForOsflsStreaming()) {
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
loadOsflsStatesIntoRAM();
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// No need to store source paths in memory if they are already in RAM
|
|
if (!_loadingStatesDynamically) {
|
|
_sourceFiles.clear();
|
|
}
|
|
|
|
// At this point there should be at least one state loaded into memory
|
|
if (_states.empty()) {
|
|
LERROR("Wasn't able to extract any valid states from provided source files");
|
|
return;
|
|
}
|
|
|
|
computeSequenceEndTime();
|
|
setModelDependentConstants();
|
|
setupProperties();
|
|
|
|
glGenVertexArrays(1, &_vertexArrayObject);
|
|
glGenBuffers(1, &_vertexPositionBuffer);
|
|
glGenBuffers(1, &_vertexColorBuffer);
|
|
glGenBuffers(1, &_vertexMaskingBuffer);
|
|
|
|
// Needed for additive blending
|
|
setRenderBin(Renderable::RenderBin::Overlay);
|
|
}
|
|
|
|
// Returns fls::Model::Invalid if it fails to extract mandatory information
|
|
fls::Model stringToModel(std::string str) {
|
|
std::transform(
|
|
str.begin(),
|
|
str.end(),
|
|
str.begin(),
|
|
[](char c) { return static_cast<char>(::tolower(c)); }
|
|
);
|
|
return fls::stringToModel(str);
|
|
}
|
|
|
|
bool RenderableFieldlinesSequence::loadJsonStatesIntoRAM() {
|
|
fls::Model model = stringToModel(_modelStr);
|
|
if (model == fls::Model::Invalid) {
|
|
return false;
|
|
}
|
|
for (const std::string& filePath : _sourceFiles) {
|
|
FieldlinesState newState;
|
|
const bool loadedSuccessfully = newState.loadStateFromJson(
|
|
filePath,
|
|
model,
|
|
_scalingFactor
|
|
);
|
|
if (loadedSuccessfully) {
|
|
addStateToSequence(newState);
|
|
if (!_outputFolderPath.empty()) {
|
|
newState.saveStateToOsfls(_outputFolderPath);
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
if (_nStates == 1) {
|
|
// loading dynamicaly is not nessesary if only having one set in the sequence
|
|
_loadingStatesDynamically = false;
|
|
}
|
|
_activeStateIndex = 0;
|
|
return true;
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::loadOsflsStatesIntoRAM() {
|
|
for (const std::string& filePath : _sourceFiles) {
|
|
FieldlinesState newState;
|
|
if (newState.loadStateFromOsfls(filePath)) {
|
|
addStateToSequence(newState);
|
|
if (!_outputFolderPath.empty()) {
|
|
newState.saveStateToJson(
|
|
_outputFolderPath + std::filesystem::path(filePath).stem().string()
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
LWARNING(fmt::format("Failed to load state from: {}", filePath));
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::setupProperties() {
|
|
bool hasExtras = (_states[0].nExtraQuantities() > 0);
|
|
|
|
// Add non-grouped properties (enablers and buttons)
|
|
addProperty(_colorABlendEnabled);
|
|
addProperty(_domainEnabled);
|
|
addProperty(_flowEnabled);
|
|
if (hasExtras) {
|
|
addProperty(_maskingEnabled);
|
|
}
|
|
addProperty(_lineWidth);
|
|
addProperty(_jumpToStartBtn);
|
|
|
|
// Add Property Groups
|
|
addPropertySubOwner(_colorGroup);
|
|
addPropertySubOwner(_domainGroup);
|
|
addPropertySubOwner(_flowGroup);
|
|
if (hasExtras) {
|
|
addPropertySubOwner(_maskingGroup);
|
|
}
|
|
|
|
// Add Properties to the groups
|
|
_colorUniform.setViewOption(properties::Property::ViewOptions::Color);
|
|
_colorGroup.addProperty(_colorUniform);
|
|
_domainGroup.addProperty(_domainX);
|
|
_domainGroup.addProperty(_domainY);
|
|
_domainGroup.addProperty(_domainZ);
|
|
_domainGroup.addProperty(_domainR);
|
|
_flowGroup.addProperty(_flowReversed);
|
|
_flowColor.setViewOption(properties::Property::ViewOptions::Color);
|
|
_flowGroup.addProperty(_flowColor);
|
|
_flowGroup.addProperty(_flowParticleSize);
|
|
_flowGroup.addProperty(_flowParticleSpacing);
|
|
_flowGroup.addProperty(_flowSpeed);
|
|
if (hasExtras) {
|
|
_colorGroup.addProperty(_colorMethod);
|
|
_colorGroup.addProperty(_colorQuantity);
|
|
_colorQuantityMinMax.setViewOption(
|
|
properties::Property::ViewOptions::MinMaxRange
|
|
);
|
|
_colorGroup.addProperty(_colorQuantityMinMax);
|
|
_colorGroup.addProperty(_colorTablePath);
|
|
_maskingGroup.addProperty(_maskingQuantity);
|
|
_maskingMinMax.setViewOption(properties::Property::ViewOptions::MinMaxRange);
|
|
_maskingGroup.addProperty(_maskingMinMax);
|
|
|
|
// 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<std::string>& extraNamesVec = _states[0].extraQuantityNames();
|
|
for (int i = 0; i < static_cast<int>(nExtraQuantities); ++i) {
|
|
_colorQuantity.addOption(i, extraNamesVec[i]);
|
|
_maskingQuantity.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());
|
|
_colorTablePath = _colorTablePaths[0];
|
|
_colorTableRanges.resize(nExtraQuantities, _colorTableRanges.back());
|
|
_maskingRanges.resize(nExtraQuantities, _maskingRanges.back());
|
|
}
|
|
|
|
definePropertyCallbackFunctions();
|
|
|
|
if (hasExtras) {
|
|
// Set defaults
|
|
_colorQuantity = _colorQuantityTemp;
|
|
_colorQuantityMinMax = _colorTableRanges[_colorQuantity];
|
|
|
|
_maskingQuantity = _maskingQuantityTemp;
|
|
_maskingMinMax = _maskingRanges[_colorQuantity];
|
|
}
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::definePropertyCallbackFunctions() {
|
|
// Add Property Callback Functions
|
|
bool hasExtras = (_states[0].nExtraQuantities() > 0);
|
|
if (hasExtras) {
|
|
_colorQuantity.onChange([this]() {
|
|
_shouldUpdateColorBuffer = true;
|
|
_colorQuantityMinMax = _colorTableRanges[_colorQuantity];
|
|
_colorTablePath = _colorTablePaths[_colorQuantity];
|
|
});
|
|
|
|
_colorTablePath.onChange([this]() {
|
|
_transferFunction->setPath(_colorTablePath);
|
|
});
|
|
|
|
_colorQuantityMinMax.onChange([this]() {
|
|
_colorTableRanges[_colorQuantity] = _colorQuantityMinMax;
|
|
});
|
|
|
|
_maskingQuantity.onChange([this]() {
|
|
_shouldUpdateMaskingBuffer = true;
|
|
_maskingMinMax = _maskingRanges[_maskingQuantity];
|
|
});
|
|
|
|
_maskingMinMax.onChange([this]() {
|
|
_maskingRanges[_maskingQuantity] = _maskingMinMax;
|
|
});
|
|
}
|
|
|
|
_jumpToStartBtn.onChange([this]() {
|
|
global::timeManager->setTimeNextFrame(Time(_startTimes[0]));
|
|
});
|
|
}
|
|
|
|
// Calculate expected end time.
|
|
void RenderableFieldlinesSequence::computeSequenceEndTime() {
|
|
if (_nStates > 1) {
|
|
const double lastTriggerTime = _startTimes[_nStates - 1];
|
|
const double sequenceDuration = lastTriggerTime - _startTimes[0];
|
|
const double averageStateDuration = sequenceDuration /
|
|
(static_cast<double>(_nStates) - 1.0);
|
|
_sequenceEndTime = lastTriggerTime + averageStateDuration;
|
|
}
|
|
}
|
|
|
|
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.f; // Should include a long magnetotail
|
|
break;
|
|
case fls::Model::Enlil:
|
|
_flowReversed = true;
|
|
_scalingFactor = fls::AuToMeter;
|
|
limit = 50.f; // Should include Plutos furthest distance from the Sun
|
|
break;
|
|
case fls::Model::Pfss:
|
|
_scalingFactor = fls::RsToMeter;
|
|
limit = 100.f; // Just a default value far away from the solar surface
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
_domainX.setMinValue(glm::vec2(-limit));
|
|
_domainX.setMaxValue(glm::vec2(limit));
|
|
|
|
_domainY.setMinValue(glm::vec2(-limit));
|
|
_domainY.setMaxValue(glm::vec2(limit));
|
|
|
|
_domainZ.setMinValue(glm::vec2(-limit));
|
|
_domainZ.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
|
|
_domainR.setMinValue(glm::vec2(0.f));
|
|
_domainR.setMaxValue(glm::vec2(limit * 1.75f));
|
|
|
|
_domainX = glm::vec2(-limit, limit);
|
|
_domainY = glm::vec2(-limit, limit);
|
|
_domainZ = glm::vec2(-limit, limit);
|
|
_domainR = glm::vec2(0.f, limit * 1.5f);
|
|
}
|
|
|
|
// Extract J2000 time from file names
|
|
// Requires files to be named as such: 'YYYY-MM-DDTHH-MM-SS-XXX.osfls'
|
|
void RenderableFieldlinesSequence::extractTriggerTimesFromFileNames() {
|
|
// number of characters in filename (excluding '.osfls')
|
|
constexpr const int FilenameSize = 23;
|
|
// size(".osfls")
|
|
constexpr const int ExtSize = 6;
|
|
|
|
for (const std::string& filePath : _sourceFiles) {
|
|
const size_t strLength = filePath.size();
|
|
// Extract the filename from the path (without extension)
|
|
std::string timeString = filePath.substr(
|
|
strLength - FilenameSize - ExtSize,
|
|
FilenameSize - 1
|
|
);
|
|
// Ensure the separators are correct
|
|
timeString.replace(4, 1, "-");
|
|
timeString.replace(7, 1, "-");
|
|
timeString.replace(13, 1, ":");
|
|
timeString.replace(16, 1, ":");
|
|
timeString.replace(19, 1, ".");
|
|
const double triggerTime = Time::convertTime(timeString);
|
|
_startTimes.push_back(triggerTime);
|
|
}
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::addStateToSequence(FieldlinesState& state) {
|
|
_states.push_back(state);
|
|
_startTimes.push_back(state.triggerTime());
|
|
++_nStates;
|
|
}
|
|
|
|
bool RenderableFieldlinesSequence::getStatesFromCdfFiles() {
|
|
std::vector<std::string> extraMagVars = extractMagnitudeVarsFromStrings(_extraVars);
|
|
|
|
std::unordered_map<std::string, std::vector<glm::vec3>> seedsPerFiles =
|
|
extractSeedPointsFromFiles(_seedPointDirectory);
|
|
if (seedsPerFiles.empty()) {
|
|
LERROR("No seed files found");
|
|
return false;
|
|
}
|
|
|
|
for (const std::string& cdfPath : _sourceFiles) {
|
|
FieldlinesState newState;
|
|
bool isSuccessful = fls::convertCdfToFieldlinesState(
|
|
newState,
|
|
cdfPath,
|
|
seedsPerFiles,
|
|
_manualTimeOffset,
|
|
_tracingVariable,
|
|
_extraVars,
|
|
extraMagVars
|
|
);
|
|
|
|
if (isSuccessful) {
|
|
addStateToSequence(newState);
|
|
if (!_outputFolderPath.empty()) {
|
|
newState.saveStateToOsfls(_outputFolderPath);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unordered_map<std::string, std::vector<glm::vec3>>
|
|
extractSeedPointsFromFiles(std::filesystem::path filePath)
|
|
{
|
|
std::unordered_map<std::string, std::vector<glm::vec3>> outMap;
|
|
|
|
if (!std::filesystem::is_directory(filePath)) {
|
|
LERROR(fmt::format(
|
|
"The specified seed point directory: '{}' does not exist", filePath
|
|
));
|
|
return outMap;
|
|
}
|
|
|
|
namespace fs = std::filesystem;
|
|
for (const fs::directory_entry& spFile : fs::directory_iterator(filePath)) {
|
|
std::string seedFilePath = spFile.path().string();
|
|
if (!spFile.is_regular_file() ||
|
|
seedFilePath.substr(seedFilePath.find_last_of('.') + 1) != "txt")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::ifstream seedFile(seedFilePath);
|
|
if (!seedFile.good()) {
|
|
LERROR(fmt::format("Could not open seed points file '{}'", seedFilePath));
|
|
outMap.clear();
|
|
return {};
|
|
}
|
|
|
|
LDEBUG(fmt::format("Reading seed points from file '{}'", seedFilePath));
|
|
std::string line;
|
|
std::vector<glm::vec3> outVec;
|
|
while (std::getline(seedFile, line)) {
|
|
std::stringstream ss(line);
|
|
glm::vec3 point;
|
|
ss >> point.x;
|
|
ss >> point.y;
|
|
ss >> point.z;
|
|
outVec.push_back(std::move(point));
|
|
}
|
|
|
|
if (outVec.empty()) {
|
|
LERROR(fmt::format("Found no seed points in: {}", seedFilePath));
|
|
outMap.clear();
|
|
return {};
|
|
}
|
|
|
|
size_t lastIndex = seedFilePath.find_last_of('.');
|
|
std::string name = seedFilePath.substr(0, lastIndex); // remove file extention
|
|
size_t dateAndTimeSeperator = name.find_last_of('_');
|
|
std::string time = name.substr(dateAndTimeSeperator + 1, name.length());
|
|
std::string date = name.substr(dateAndTimeSeperator - 8, 8); // 8 for yyyymmdd
|
|
std::string dateAndTime = date + time;
|
|
|
|
// add outVec as value and time stamp as int as key
|
|
outMap[dateAndTime] = outVec;
|
|
}
|
|
return outMap;
|
|
}
|
|
|
|
std::vector<std::string>
|
|
extractMagnitudeVarsFromStrings(std::vector<std::string> extrVars)
|
|
{
|
|
std::vector<std::string> extraMagVars;
|
|
for (int i = 0; i < static_cast<int>(extrVars.size()); i++) {
|
|
const std::string& str = extrVars[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());
|
|
}
|
|
extrVars.erase(extrVars.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
return extraMagVars;
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::deinitializeGL() {
|
|
glDeleteVertexArrays(1, &_vertexArrayObject);
|
|
_vertexArrayObject = 0;
|
|
|
|
glDeleteBuffers(1, &_vertexPositionBuffer);
|
|
_vertexPositionBuffer = 0;
|
|
|
|
glDeleteBuffers(1, &_vertexColorBuffer);
|
|
_vertexColorBuffer = 0;
|
|
|
|
glDeleteBuffers(1, &_vertexMaskingBuffer);
|
|
_vertexMaskingBuffer = 0;
|
|
|
|
if (_shaderProgram) {
|
|
global::renderEngine->removeRenderProgram(_shaderProgram.get());
|
|
_shaderProgram = nullptr;
|
|
}
|
|
|
|
// Stall main thread until thread that's loading states is done
|
|
bool printedWarning = false;
|
|
while (_isLoadingStateFromDisk) {
|
|
if (!printedWarning) {
|
|
LWARNING("Trying to destroy class when an active thread is still using it");
|
|
printedWarning = true;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
}
|
|
|
|
bool RenderableFieldlinesSequence::isReady() const {
|
|
return _shaderProgram != nullptr;
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::render(const RenderData& data, RendererTasks&) {
|
|
if (_activeTriggerTimeIndex == -1) {
|
|
return;
|
|
}
|
|
_shaderProgram->activate();
|
|
|
|
// Calculate Model View MatrixProjection
|
|
const glm::dmat4 rotMat = glm::dmat4(data.modelTransform.rotation);
|
|
const glm::dmat4 modelMat =
|
|
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) *
|
|
rotMat *
|
|
glm::dmat4(glm::scale(glm::dmat4(1), glm::dvec3(data.modelTransform.scale)));
|
|
const glm::dmat4 modelViewMat = data.camera.combinedViewMatrix() * modelMat;
|
|
|
|
_shaderProgram->setUniform("modelViewProjection",
|
|
data.camera.sgctInternal.projectionMatrix() * glm::mat4(modelViewMat));
|
|
|
|
_shaderProgram->setUniform("colorMethod", _colorMethod);
|
|
_shaderProgram->setUniform("lineColor", _colorUniform);
|
|
_shaderProgram->setUniform("usingDomain", _domainEnabled);
|
|
_shaderProgram->setUniform("usingMasking", _maskingEnabled);
|
|
|
|
if (_colorMethod == static_cast<int>(ColorMethod::ByQuantity)) {
|
|
ghoul::opengl::TextureUnit textureUnit;
|
|
textureUnit.activate();
|
|
_transferFunction->bind(); // Calls update internally
|
|
_shaderProgram->setUniform("colorTable", textureUnit);
|
|
_shaderProgram->setUniform("colorTableRange", _colorTableRanges[_colorQuantity]);
|
|
}
|
|
|
|
if (_maskingEnabled) {
|
|
_shaderProgram->setUniform("maskingRange", _maskingRanges[_maskingQuantity]);
|
|
}
|
|
|
|
_shaderProgram->setUniform("domainLimR", _domainR.value() * _scalingFactor);
|
|
_shaderProgram->setUniform("domainLimX", _domainX.value() * _scalingFactor);
|
|
_shaderProgram->setUniform("domainLimY", _domainY.value() * _scalingFactor);
|
|
_shaderProgram->setUniform("domainLimZ", _domainZ.value() * _scalingFactor);
|
|
|
|
// Flow/Particles
|
|
_shaderProgram->setUniform("flowColor", _flowColor);
|
|
_shaderProgram->setUniform("usingParticles", _flowEnabled);
|
|
_shaderProgram->setUniform("particleSize", _flowParticleSize);
|
|
_shaderProgram->setUniform("particleSpacing", _flowParticleSpacing);
|
|
_shaderProgram->setUniform("particleSpeed", _flowSpeed);
|
|
_shaderProgram->setUniform(
|
|
"time",
|
|
global::windowDelegate->applicationTime() * (_flowReversed ? -1 : 1)
|
|
);
|
|
|
|
bool additiveBlending = false;
|
|
if (_colorABlendEnabled) {
|
|
additiveBlending = true;
|
|
glDepthMask(false);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
}
|
|
|
|
glBindVertexArray(_vertexArrayObject);
|
|
#ifndef __APPLE__
|
|
glLineWidth(_lineWidth);
|
|
#else
|
|
glLineWidth(1.f);
|
|
#endif
|
|
|
|
glMultiDrawArrays(
|
|
GL_LINE_STRIP,
|
|
_states[_activeStateIndex].lineStart().data(),
|
|
_states[_activeStateIndex].lineCount().data(),
|
|
static_cast<GLsizei>(_states[_activeStateIndex].lineStart().size())
|
|
);
|
|
|
|
glBindVertexArray(0);
|
|
_shaderProgram->deactivate();
|
|
|
|
if (additiveBlending) {
|
|
// Restores OpenGL Rendering State
|
|
global::renderEngine->openglStateCache().resetBlendState();
|
|
global::renderEngine->openglStateCache().resetDepthState();
|
|
}
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::update(const UpdateData& data) {
|
|
if (_shaderProgram->isDirty()) {
|
|
_shaderProgram->rebuildFromFile();
|
|
}
|
|
// True if new 'runtime-state' must be loaded from disk.
|
|
// False => the previous frame's state should still be shown
|
|
bool mustLoadNewStateFromDisk = false;
|
|
// True if new 'in-RAM-state' must be loaded.
|
|
// False => the previous frame's state should still be shown
|
|
bool needUpdate = false;
|
|
const double currentTime = data.time.j2000Seconds();
|
|
const bool isInInterval = (currentTime >= _startTimes[0]) &&
|
|
(currentTime < _sequenceEndTime);
|
|
|
|
// Check if current time in OpenSpace is within sequence interval
|
|
if (isInInterval) {
|
|
const size_t nextIdx = _activeTriggerTimeIndex + 1;
|
|
if (
|
|
// true => Previous frame was not within the sequence interval
|
|
_activeTriggerTimeIndex < 0 ||
|
|
// true => We stepped back to a time represented by another state
|
|
currentTime < _startTimes[_activeTriggerTimeIndex] ||
|
|
// true => We stepped forward to a time represented by another state
|
|
(nextIdx < _nStates && currentTime >= _startTimes[nextIdx]))
|
|
{
|
|
updateActiveTriggerTimeIndex(currentTime);
|
|
|
|
if (_loadingStatesDynamically) {
|
|
mustLoadNewStateFromDisk = true;
|
|
}
|
|
else {
|
|
needUpdate = true;
|
|
_activeStateIndex = _activeTriggerTimeIndex;
|
|
}
|
|
} // else {we're still in same state as previous frame (no changes needed)}
|
|
}
|
|
// if only one state
|
|
else if (_nStates == 1) {
|
|
_activeTriggerTimeIndex = 0;
|
|
_activeStateIndex = 0;
|
|
if (!_hasBeenUpdated) {
|
|
updateVertexPositionBuffer();
|
|
}
|
|
_hasBeenUpdated = true;
|
|
}
|
|
else {
|
|
// Not in interval => set everything to false
|
|
_activeTriggerTimeIndex = -1;
|
|
mustLoadNewStateFromDisk = false;
|
|
needUpdate = false;
|
|
}
|
|
|
|
if (mustLoadNewStateFromDisk) {
|
|
if (!_isLoadingStateFromDisk && !_newStateIsReady) {
|
|
_isLoadingStateFromDisk = true;
|
|
mustLoadNewStateFromDisk = false;
|
|
std::string filePath = _sourceFiles[_activeTriggerTimeIndex];
|
|
std::thread readBinaryThread([this, f = std::move(filePath)]() {
|
|
readNewState(f);
|
|
});
|
|
readBinaryThread.detach();
|
|
}
|
|
}
|
|
|
|
if (needUpdate || _newStateIsReady) {
|
|
if (_loadingStatesDynamically) {
|
|
_states[0] = std::move(*_newState);
|
|
}
|
|
|
|
updateVertexPositionBuffer();
|
|
|
|
if (_states[_activeStateIndex].nExtraQuantities() > 0) {
|
|
_shouldUpdateColorBuffer = true;
|
|
_shouldUpdateMaskingBuffer = true;
|
|
}
|
|
|
|
// Everything is set and ready for rendering
|
|
needUpdate = false;
|
|
_newStateIsReady = false;
|
|
}
|
|
|
|
if (_colorMethod == 1) { //By quantity
|
|
if (_shouldUpdateColorBuffer) {
|
|
updateVertexColorBuffer();
|
|
_shouldUpdateColorBuffer = false;
|
|
}
|
|
|
|
if (_shouldUpdateMaskingBuffer) {
|
|
updateVertexMaskingBuffer();
|
|
_shouldUpdateMaskingBuffer = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assumes we already know that currentTime is within the sequence interval
|
|
void RenderableFieldlinesSequence::updateActiveTriggerTimeIndex(double currentTime) {
|
|
auto iter = std::upper_bound(_startTimes.begin(), _startTimes.end(), currentTime);
|
|
if (iter != _startTimes.end()) {
|
|
if (iter != _startTimes.begin()) {
|
|
_activeTriggerTimeIndex = static_cast<int>(
|
|
std::distance(_startTimes.begin(), iter)
|
|
) - 1;
|
|
}
|
|
else {
|
|
_activeTriggerTimeIndex = 0;
|
|
}
|
|
}
|
|
else {
|
|
_activeTriggerTimeIndex = static_cast<int>(_nStates) - 1;
|
|
}
|
|
if (_nStates == 1) {
|
|
_activeTriggerTimeIndex = 0;
|
|
}
|
|
}
|
|
|
|
// Reading state from disk. Must be thread safe
|
|
void RenderableFieldlinesSequence::readNewState(const std::string& filePath) {
|
|
_newState = std::make_unique<FieldlinesState>();
|
|
if (_newState->loadStateFromOsfls(filePath)) {
|
|
_newStateIsReady = true;
|
|
}
|
|
_isLoadingStateFromDisk = false;
|
|
}
|
|
|
|
// Unbind buffers and arrays
|
|
void unbindGL() {
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::updateVertexPositionBuffer() {
|
|
if (_activeStateIndex == -1) { return; }
|
|
glBindVertexArray(_vertexArrayObject);
|
|
glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer);
|
|
|
|
const std::vector<glm::vec3>& vertPos = _states[_activeStateIndex].vertexPositions();
|
|
|
|
glBufferData(
|
|
GL_ARRAY_BUFFER,
|
|
vertPos.size() * sizeof(glm::vec3),
|
|
vertPos.data(),
|
|
GL_STATIC_DRAW
|
|
);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
|
|
|
unbindGL();
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::updateVertexColorBuffer() {
|
|
if (_activeStateIndex == -1) { return; }
|
|
glBindVertexArray(_vertexArrayObject);
|
|
glBindBuffer(GL_ARRAY_BUFFER, _vertexColorBuffer);
|
|
|
|
bool isSuccessful;
|
|
const std::vector<float>& quantities = _states[_activeStateIndex].extraQuantity(
|
|
_colorQuantity,
|
|
isSuccessful
|
|
);
|
|
|
|
if (isSuccessful) {
|
|
glBufferData(
|
|
GL_ARRAY_BUFFER,
|
|
quantities.size() * sizeof(float),
|
|
quantities.data(),
|
|
GL_STATIC_DRAW
|
|
);
|
|
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0);
|
|
|
|
unbindGL();
|
|
}
|
|
}
|
|
|
|
void RenderableFieldlinesSequence::updateVertexMaskingBuffer() {
|
|
if (_activeStateIndex == -1) { return; }
|
|
glBindVertexArray(_vertexArrayObject);
|
|
glBindBuffer(GL_ARRAY_BUFFER, _vertexMaskingBuffer);
|
|
|
|
bool isSuccessful;
|
|
const std::vector<float>& maskings = _states[_activeStateIndex].extraQuantity(
|
|
_maskingQuantity,
|
|
isSuccessful
|
|
);
|
|
|
|
if (isSuccessful) {
|
|
glBufferData(
|
|
GL_ARRAY_BUFFER,
|
|
maskings.size() * sizeof(float),
|
|
maskings.data(),
|
|
GL_STATIC_DRAW
|
|
);
|
|
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 0, 0);
|
|
|
|
unbindGL();
|
|
}
|
|
}
|
|
|
|
} // namespace openspace
|