mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-05-04 09:59:44 -05:00
Feature/speck loader (#1585)
* Implement a shared speckfile loader * Apply new speck loader to RenderableBillboardsCloud, RenderablePlanesCloud, RenderablePoints
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
|
||||
|
||||
set(HEADER_FILES
|
||||
speckloader.h
|
||||
rendering/planetgeometry.h
|
||||
rendering/renderableconstellationbounds.h
|
||||
rendering/renderablehabitablezone.h
|
||||
@@ -43,6 +44,7 @@ set(HEADER_FILES
|
||||
source_group("Header Files" FILES ${HEADER_FILES})
|
||||
|
||||
set(SOURCE_FILES
|
||||
speckloader.cpp
|
||||
rendering/planetgeometry.cpp
|
||||
rendering/renderableconstellationbounds.cpp
|
||||
rendering/renderablehabitablezone.cpp
|
||||
|
||||
@@ -52,21 +52,21 @@ namespace {
|
||||
constexpr const char* _loggerCat = "RenderableStars";
|
||||
|
||||
constexpr const std::array<const char*, 17> UniformNames = {
|
||||
"modelMatrix", "cameraUp", "cameraViewProjectionMatrix",
|
||||
"colorOption", "magnitudeExponent", "eyePosition", "psfParamConf",
|
||||
"lumCent", "radiusCent", "brightnessCent", "colorTexture",
|
||||
"alphaValue", "psfTexture", "otherDataTexture", "otherDataRange",
|
||||
"filterOutOfRange", "fixedColor"
|
||||
"modelMatrix", "cameraUp", "cameraViewProjectionMatrix", "colorOption",
|
||||
"magnitudeExponent", "eyePosition", "psfParamConf", "lumCent", "radiusCent",
|
||||
"brightnessCent", "colorTexture", "alphaValue", "psfTexture", "otherDataTexture",
|
||||
"otherDataRange", "filterOutOfRange", "fixedColor"
|
||||
};
|
||||
|
||||
constexpr int8_t CurrentCacheVersion = 3;
|
||||
|
||||
constexpr const int RenderOptionPointSpreadFunction = 0;
|
||||
constexpr const int RenderOptionTexture = 1;
|
||||
|
||||
constexpr const int PsfMethodSpencer = 0;
|
||||
constexpr const int PsfMethodMoffat = 1;
|
||||
|
||||
constexpr const int PsfTextureSize = 64;
|
||||
constexpr const int ConvolvedfTextureSize = 257;
|
||||
|
||||
constexpr double PARSEC = 0.308567756E17;
|
||||
|
||||
struct ColorVBOLayout {
|
||||
@@ -159,12 +159,6 @@ namespace {
|
||||
"or filtered away"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo EnableTestGridInfo = {
|
||||
"EnableTestGrid",
|
||||
"Enable Test Grid",
|
||||
"Set it to true for rendering the test grid."
|
||||
};
|
||||
|
||||
// Old Method
|
||||
constexpr openspace::properties::Property::PropertyInfo PsfTextureInfo = {
|
||||
"Texture",
|
||||
@@ -173,11 +167,11 @@ namespace {
|
||||
"stars."
|
||||
};
|
||||
|
||||
/*constexpr openspace::properties::Property::PropertyInfo ShapeTextureInfo = {
|
||||
"ShapeTexture",
|
||||
"Shape Texture to be convolved",
|
||||
"The path to the texture that should be used as the base shape for the stars."
|
||||
};*/
|
||||
//constexpr openspace::properties::Property::PropertyInfo ShapeTextureInfo = {
|
||||
// "ShapeTexture",
|
||||
// "Shape Texture to be convolved",
|
||||
// "The path to the texture that should be used as the base shape for the stars."
|
||||
//};
|
||||
|
||||
// PSF
|
||||
constexpr openspace::properties::Property::PropertyInfo MagnitudeExponentInfo = {
|
||||
@@ -344,17 +338,28 @@ namespace {
|
||||
// [[codegen::verbatim(MagnitudeExponentInfo.description)]]
|
||||
std::optional<float> magnitudeExponent;
|
||||
|
||||
// [[codegen::verbatim(EnableTestGridInfo.description)]]
|
||||
std::optional<bool> enableTestGrid;
|
||||
enum class RenderMethod {
|
||||
PSF,
|
||||
TextureBased [[codegen::key("Texture Based")]]
|
||||
};
|
||||
|
||||
// [[codegen::verbatim(RenderMethodOptionInfo.description)]]
|
||||
std::string renderMethod;
|
||||
RenderMethod renderMethod;
|
||||
|
||||
// [[codegen::verbatim(PsfTextureInfo.description)]]
|
||||
std::filesystem::path texture;
|
||||
|
||||
enum class SizeComposition {
|
||||
AppBrightness [[codegen::key("App Brightness")]],
|
||||
LumSize [[codegen::key("Lum and Size")]],
|
||||
LumSizeAppBrightness [[codegen::key("Lum, Size and App Brightness")]],
|
||||
AbsMagnitude [[codegen::key("Abs Magnitude")]],
|
||||
AppMagnitude [[codegen::key("App Magnitude")]],
|
||||
DistanceModulus [[codegen::key("Distance Modulus")]]
|
||||
};
|
||||
|
||||
// [[codegen::verbatim(SizeCompositionOptionInfo.description)]]
|
||||
std::optional<std::string> sizeComposition;
|
||||
std::optional<SizeComposition> sizeComposition;
|
||||
|
||||
// [[codegen::verbatim(FadeInDistancesInfo.description)]]
|
||||
std::optional<glm::dvec2> fadeInDistances;
|
||||
@@ -487,13 +492,6 @@ RenderableStars::RenderableStars(const ghoul::Dictionary& dictionary)
|
||||
_colorTextureFile->setCallback([this]() { _colorTextureIsDirty = true; });
|
||||
addProperty(_colorTexturePath);
|
||||
|
||||
/*_shapeTexturePath.onChange([&] { _shapeTextureIsDirty = true; });
|
||||
_shapeTextureFile->setCallback([&](const File&) {
|
||||
_shapeTextureIsDirty = true;
|
||||
});
|
||||
addProperty(_shapeTexturePath);*/
|
||||
|
||||
_enableTestGrid = p.enableTestGrid.value_or(_enableTestGrid);
|
||||
_queuedOtherData = p.otherData.value_or(_queuedOtherData);
|
||||
|
||||
_otherDataOption.onChange([&]() { _dataIsDirty = true; });
|
||||
@@ -517,10 +515,10 @@ RenderableStars::RenderableStars(const ghoul::Dictionary& dictionary)
|
||||
_renderingMethodOption.addOption(RenderOptionTexture, "Textured Based");
|
||||
addProperty(_renderingMethodOption);
|
||||
|
||||
if (p.renderMethod == "PSF") {
|
||||
if (p.renderMethod == Parameters::RenderMethod::PSF) {
|
||||
_renderingMethodOption = RenderOptionPointSpreadFunction;
|
||||
}
|
||||
else if (p.renderMethod == "Texture Based") {
|
||||
else {
|
||||
_renderingMethodOption = RenderOptionTexture;
|
||||
}
|
||||
|
||||
@@ -551,23 +549,25 @@ RenderableStars::RenderableStars(const ghoul::Dictionary& dictionary)
|
||||
|
||||
|
||||
if (p.sizeComposition.has_value()) {
|
||||
if (*p.sizeComposition == "App Brightness") {
|
||||
_psfMultiplyOption = 0;
|
||||
}
|
||||
else if (*p.sizeComposition == "Lum and Size") {
|
||||
_psfMultiplyOption = 1;
|
||||
}
|
||||
else if (*p.sizeComposition == "Lum, Size and App Brightness") {
|
||||
_psfMultiplyOption = 2;
|
||||
}
|
||||
else if (*p.sizeComposition == "Abs Magnitude") {
|
||||
_psfMultiplyOption = 3;
|
||||
}
|
||||
else if (*p.sizeComposition == "App Maginitude") {
|
||||
_psfMultiplyOption = 4;
|
||||
}
|
||||
else if (*p.sizeComposition == "Distance Modulus") {
|
||||
_psfMultiplyOption = 5;
|
||||
switch (*p.sizeComposition) {
|
||||
case Parameters::SizeComposition::AppBrightness:
|
||||
_psfMultiplyOption = 0;
|
||||
break;
|
||||
case Parameters::SizeComposition::LumSize:
|
||||
_psfMultiplyOption = 1;
|
||||
break;
|
||||
case Parameters::SizeComposition::LumSizeAppBrightness:
|
||||
_psfMultiplyOption = 2;
|
||||
break;
|
||||
case Parameters::SizeComposition::AbsMagnitude:
|
||||
_psfMultiplyOption = 3;
|
||||
break;
|
||||
case Parameters::SizeComposition::AppMagnitude:
|
||||
_psfMultiplyOption = 4;
|
||||
break;
|
||||
case Parameters::SizeComposition::DistanceModulus:
|
||||
_psfMultiplyOption = 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -632,13 +632,16 @@ void RenderableStars::initializeGL() {
|
||||
|
||||
loadData();
|
||||
|
||||
// We need to wait until after loading the data until we can see if the requested
|
||||
// data value actually exists or not. Once we determine the index, we no longer
|
||||
// need the value and can clear it
|
||||
if (!_queuedOtherData.empty()) {
|
||||
auto it = std::find(_dataNames.begin(), _dataNames.end(), _queuedOtherData);
|
||||
if (it == _dataNames.end()) {
|
||||
int idx = _dataset.index(_queuedOtherData);
|
||||
if (idx == -1) {
|
||||
LERROR(fmt::format("Could not find other data column {}", _queuedOtherData));
|
||||
}
|
||||
else {
|
||||
_otherDataOption = static_cast<int>(std::distance(_dataNames.begin(), it));
|
||||
_otherDataOption = idx;
|
||||
_queuedOtherData.clear();
|
||||
}
|
||||
}
|
||||
@@ -651,7 +654,7 @@ void RenderableStars::initializeGL() {
|
||||
glBindVertexArray(_psfVao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _psfVbo);
|
||||
|
||||
const GLfloat vertexData[] = {
|
||||
constexpr const std::array<GLfloat, 24> VertexData = {
|
||||
//x y s t
|
||||
-1.f, -1.f, 0.f, 0.f,
|
||||
1.f, 1.f, 1.f, 1.f,
|
||||
@@ -661,15 +664,8 @@ void RenderableStars::initializeGL() {
|
||||
1.f, 1.f, 1.f, 1.f
|
||||
};
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(
|
||||
0,
|
||||
4,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
sizeof(GLfloat) * 4,
|
||||
nullptr
|
||||
);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData), VertexData.data(), GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr);
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
@@ -685,8 +681,8 @@ void RenderableStars::initializeGL() {
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
_psfTextureSize,
|
||||
_psfTextureSize,
|
||||
PsfTextureSize,
|
||||
PsfTextureSize,
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_BYTE,
|
||||
@@ -708,8 +704,8 @@ void RenderableStars::initializeGL() {
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
_convolvedfTextureSize,
|
||||
_convolvedfTextureSize,
|
||||
ConvolvedfTextureSize,
|
||||
ConvolvedfTextureSize,
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_BYTE,
|
||||
@@ -747,8 +743,7 @@ void RenderableStars::loadPSFTexture() {
|
||||
|
||||
if (_pointSpreadFunctionTexture) {
|
||||
LDEBUG(fmt::format(
|
||||
"Loaded texture from '{}'",
|
||||
absPath(_pointSpreadFunctionTexturePath)
|
||||
"Loaded texture from '{}'", absPath(_pointSpreadFunctionTexturePath)
|
||||
));
|
||||
_pointSpreadFunctionTexture->uploadTexture();
|
||||
}
|
||||
@@ -760,21 +755,13 @@ void RenderableStars::loadPSFTexture() {
|
||||
_pointSpreadFunctionTexturePath.value()
|
||||
);
|
||||
_pointSpreadFunctionFile->setCallback(
|
||||
[this]() { _pointSpreadFunctionTextureIsDirty = true; }
|
||||
[&]() { _pointSpreadFunctionTextureIsDirty = true; }
|
||||
);
|
||||
}
|
||||
_pointSpreadFunctionTextureIsDirty = false;
|
||||
|
||||
}
|
||||
|
||||
void RenderableStars::renderPSFToTexture() {
|
||||
// Saves current FBO first
|
||||
// GLint defaultFBO;
|
||||
// defaultFBO = global::renderEngine->openglStateCache().defaultFramebuffer();
|
||||
|
||||
// GLint m_viewport[4];
|
||||
// global::renderEngine.openglStateCache().viewPort(m_viewport);
|
||||
|
||||
// Creates the FBO for the calculations
|
||||
GLuint psfFBO;
|
||||
glGenFramebuffers(1, &psfFBO);
|
||||
@@ -783,9 +770,7 @@ void RenderableStars::renderPSFToTexture() {
|
||||
glDrawBuffers(1, drawBuffers);
|
||||
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _psfTexture, 0);
|
||||
|
||||
glViewport(0, 0, _psfTextureSize, _psfTextureSize);
|
||||
|
||||
glViewport(0, 0, PsfTextureSize, PsfTextureSize);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> program =
|
||||
@@ -796,8 +781,8 @@ void RenderableStars::renderPSFToTexture() {
|
||||
);
|
||||
|
||||
program->activate();
|
||||
constexpr const float black[] = { 0.f, 0.f, 0.f, 0.f };
|
||||
glClearBufferfv(GL_COLOR, 0, black);
|
||||
constexpr const std::array<float, 4> Black = { 0.f, 0.f, 0.f, 0.f };
|
||||
glClearBufferfv(GL_COLOR, 0, Black.data());
|
||||
|
||||
program->setUniform("psfMethod", _psfMethodOption.value());
|
||||
program->setUniform("p0Param", _p0Param);
|
||||
@@ -871,7 +856,7 @@ void RenderableStars::renderPSFToTexture() {
|
||||
// m_viewport[2],
|
||||
// m_viewport[3]
|
||||
//);
|
||||
//glDeleteFramebuffers(1, &psfFBO);
|
||||
glDeleteFramebuffers(1, &psfFBO);
|
||||
//glDeleteFramebuffers(1, &convolveFBO);
|
||||
|
||||
// Restores OpenGL blending state
|
||||
@@ -879,7 +864,7 @@ void RenderableStars::renderPSFToTexture() {
|
||||
}
|
||||
|
||||
void RenderableStars::render(const RenderData& data, RendererTasks&) {
|
||||
if (_fullData.empty()) {
|
||||
if (_dataset.entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -979,7 +964,7 @@ void RenderableStars::render(const RenderData& data, RendererTasks&) {
|
||||
|
||||
|
||||
glBindVertexArray(_vao);
|
||||
const GLsizei nStars = static_cast<GLsizei>(_fullData.size() / _nValuesPerStar);
|
||||
const GLsizei nStars = static_cast<GLsizei>(_dataset.entries.size());
|
||||
glDrawArrays(GL_POINTS, 0, nStars);
|
||||
|
||||
glBindVertexArray(0);
|
||||
@@ -997,7 +982,7 @@ void RenderableStars::update(const UpdateData&) {
|
||||
_dataIsDirty = true;
|
||||
}
|
||||
|
||||
if (_fullData.empty()) {
|
||||
if (_dataset.entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1005,9 +990,7 @@ void RenderableStars::update(const UpdateData&) {
|
||||
const int value = _colorOption;
|
||||
LDEBUG("Regenerating data");
|
||||
|
||||
createDataSlice(ColorOption(value));
|
||||
|
||||
int size = static_cast<int>(_slicedData.size());
|
||||
std::vector<float> slice = createDataSlice(ColorOption(value));
|
||||
|
||||
if (_vao == 0) {
|
||||
glGenVertexArrays(1, &_vao);
|
||||
@@ -1019,8 +1002,8 @@ void RenderableStars::update(const UpdateData&) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
size * sizeof(GLfloat),
|
||||
_slicedData.data(),
|
||||
slice.size() * sizeof(GLfloat),
|
||||
slice.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
|
||||
@@ -1030,8 +1013,8 @@ void RenderableStars::update(const UpdateData&) {
|
||||
"in_bvLumAbsMagAppMag"
|
||||
);
|
||||
|
||||
const size_t nStars = _fullData.size() / _nValuesPerStar;
|
||||
const size_t nValues = _slicedData.size() / nStars;
|
||||
const size_t nStars = _dataset.entries.size();
|
||||
const size_t nValues = slice.size() / nStars;
|
||||
|
||||
GLsizei stride = static_cast<GLsizei>(sizeof(GLfloat) * nValues);
|
||||
|
||||
@@ -1204,276 +1187,39 @@ void RenderableStars::update(const UpdateData&) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void RenderableStars::loadShapeTexture() {
|
||||
if (_shapeTextureIsDirty) {
|
||||
LDEBUG("Reloading Shape Texture");
|
||||
_shapeTexture = nullptr;
|
||||
if (_shapeTexturePath.value() != "") {
|
||||
_shapeTexture = ghoul::io::TextureReader::ref().loadTexture(
|
||||
absPath(_shapeTexturePath)
|
||||
);
|
||||
if (_shapeTexture) {
|
||||
LDEBUG(fmt::format(
|
||||
"Loaded texture from '{}'",
|
||||
absPath(_shapeTexturePath)
|
||||
));
|
||||
_shapeTexture->uploadTexture();
|
||||
}
|
||||
|
||||
_shapeTextureFile = std::make_unique<ghoul::filesystem::File>(
|
||||
_shapeTexturePath
|
||||
);
|
||||
_shapeTextureFile->setCallback(
|
||||
[&](const ghoul::filesystem::File&) { _shapeTextureIsDirty = true; }
|
||||
);
|
||||
}
|
||||
_shapeTextureIsDirty = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void RenderableStars::loadData() {
|
||||
std::filesystem::path file = absPath(_speckFile);
|
||||
if (!std::filesystem::is_regular_file(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_nValuesPerStar = 0;
|
||||
_slicedData.clear();
|
||||
_fullData.clear();
|
||||
_dataNames.clear();
|
||||
|
||||
std::string cachedFile = FileSys.cacheManager()->cachedFilename(file);
|
||||
bool hasCachedFile = std::filesystem::is_regular_file(cachedFile);
|
||||
if (hasCachedFile) {
|
||||
LINFO(fmt::format("Cached file '{}' used for Speck file {}",
|
||||
cachedFile, file
|
||||
));
|
||||
|
||||
bool success = loadCachedFile(cachedFile);
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
FileSys.cacheManager()->removeCacheFile(file);
|
||||
// Intentional fall-through to the 'else' computation to generate the cache
|
||||
// file for the next run
|
||||
}
|
||||
}
|
||||
else {
|
||||
LINFO(fmt::format("Cache for Speck file {} not found", file));
|
||||
}
|
||||
LINFO(fmt::format("Loading Speck file {}", file));
|
||||
|
||||
readSpeckFile();
|
||||
|
||||
LINFO("Saving cache");
|
||||
saveCachedFile(cachedFile);
|
||||
}
|
||||
|
||||
void RenderableStars::readSpeckFile() {
|
||||
std::filesystem::path filename = _speckFile.value();
|
||||
std::ifstream file(filename);
|
||||
if (!file.good()) {
|
||||
LERROR(fmt::format("Failed to open Speck file {}", filename));
|
||||
_dataset = speck::data::loadFileWithCache(file);
|
||||
if (_dataset.entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The beginning of the speck file has a header that either contains comments
|
||||
// (signaled by a preceding '#') or information about the structure of the file
|
||||
// (signaled by the keywords 'datavar', 'texturevar', and 'texture')
|
||||
std::string line;
|
||||
while (true) {
|
||||
std::streampos position = file.tellg();
|
||||
std::getline(file, line, '\n');
|
||||
|
||||
if (line[0] == '#' || line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.substr(0, 7) != "datavar" &&
|
||||
line.substr(0, 10) != "texturevar" &&
|
||||
line.substr(0, 7) != "texture")
|
||||
{
|
||||
// we read a line that doesn't belong to the header, so we have to jump back
|
||||
// before the beginning of the current line
|
||||
if (_enableTestGrid) {
|
||||
file.seekg(position - std::streamoff(8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.substr(0, 7) == "datavar") {
|
||||
// datavar lines are structured as follows:
|
||||
// datavar # description
|
||||
// where # is the index of the data variable; so if we repeatedly overwrite
|
||||
// the 'nValues' variable with the latest index, we will end up with the total
|
||||
// number of values (+3 since X Y Z are not counted in the Speck file index)
|
||||
std::stringstream str(line);
|
||||
|
||||
std::string dummy;
|
||||
str >> dummy;
|
||||
str >> _nValuesPerStar;
|
||||
|
||||
std::string name;
|
||||
str >> name;
|
||||
_dataNames.push_back(name);
|
||||
|
||||
// +3 because the position x, y, z
|
||||
if (name == "lum") {
|
||||
_lumArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
else if (name == "absmag") {
|
||||
_absMagArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
else if (name == "appmag") {
|
||||
_appMagArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
else if (name == "colorb_v") {
|
||||
_bvColorArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
else if (name == "vx") {
|
||||
_velocityArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
else if (name == "speed") {
|
||||
_speedArrayPos = _nValuesPerStar + 3;
|
||||
}
|
||||
_nValuesPerStar += 1; // We want the number, but the index is 0 based
|
||||
}
|
||||
std::vector<std::string> variableNames;
|
||||
variableNames.reserve(_dataset.variables.size());
|
||||
for (const speck::Dataset::Variable& v : _dataset.variables) {
|
||||
variableNames.push_back(v.name);
|
||||
}
|
||||
_otherDataOption.addOptions(variableNames);
|
||||
|
||||
_nValuesPerStar += 3; // X Y Z are not counted in the Speck file indices
|
||||
_otherDataOption.addOptions(_dataNames);
|
||||
|
||||
float minLumValue = std::numeric_limits<float>::max();
|
||||
float maxLumValue = std::numeric_limits<float>::min();
|
||||
|
||||
do {
|
||||
std::vector<float> values(_nValuesPerStar);
|
||||
std::stringstream str(line);
|
||||
|
||||
for (int i = 0; i < _nValuesPerStar; ++i) {
|
||||
str >> values[i];
|
||||
}
|
||||
|
||||
bool nullArray = true;
|
||||
for (float v : values) {
|
||||
if (v != 0.0) {
|
||||
nullArray = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
minLumValue = values[_lumArrayPos] < minLumValue ?
|
||||
values[_lumArrayPos] : minLumValue;
|
||||
maxLumValue = values[_lumArrayPos] > maxLumValue ?
|
||||
values[_lumArrayPos] : maxLumValue;
|
||||
if (!nullArray) {
|
||||
_fullData.insert(_fullData.end(), values.begin(), values.end());
|
||||
}
|
||||
|
||||
std::getline(file, line, '\n');
|
||||
|
||||
} while (!file.eof());
|
||||
|
||||
// Normalize Luminosity:
|
||||
for (size_t i = 0; i < _fullData.size(); i += _nValuesPerStar) {
|
||||
_fullData[i + _lumArrayPos] =
|
||||
(_fullData[i + _lumArrayPos] - minLumValue) / (maxLumValue - minLumValue);
|
||||
bool success = _dataset.normalizeVariable("lum");
|
||||
if (!success) {
|
||||
throw ghoul::RuntimeError("Could not find required variable 'luminosity'");
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableStars::loadCachedFile(const std::string& file) {
|
||||
std::ifstream fileStream(file, std::ifstream::binary);
|
||||
if (fileStream.good()) {
|
||||
int8_t version = 0;
|
||||
fileStream.read(reinterpret_cast<char*>(&version), sizeof(int8_t));
|
||||
if (version != CurrentCacheVersion) {
|
||||
LINFO("The format of the cached file has changed: deleting old cache");
|
||||
fileStream.close();
|
||||
if (std::filesystem::is_regular_file(file)) {
|
||||
std::filesystem::remove(file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t nValues = 0;
|
||||
fileStream.read(reinterpret_cast<char*>(&nValues), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_nValuesPerStar), sizeof(int32_t));
|
||||
|
||||
fileStream.read(reinterpret_cast<char*>(&_lumArrayPos), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_absMagArrayPos), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_appMagArrayPos), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_bvColorArrayPos), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_velocityArrayPos), sizeof(int32_t));
|
||||
fileStream.read(reinterpret_cast<char*>(&_speedArrayPos), sizeof(int32_t));
|
||||
|
||||
for (int i = 0; i < _nValuesPerStar - 3; ++i) {
|
||||
uint16_t len;
|
||||
fileStream.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
std::vector<char> buffer(len);
|
||||
fileStream.read(buffer.data(), len);
|
||||
std::string value(buffer.begin(), buffer.end());
|
||||
_dataNames.push_back(value);
|
||||
}
|
||||
_otherDataOption.addOptions(_dataNames);
|
||||
|
||||
_fullData.resize(nValues);
|
||||
fileStream.read(reinterpret_cast<char*>(
|
||||
_fullData.data()),
|
||||
nValues * sizeof(_fullData[0])
|
||||
);
|
||||
|
||||
bool success = fileStream.good();
|
||||
return success;
|
||||
}
|
||||
else {
|
||||
LERROR(fmt::format("Error opening file '{}' for loading cache file", file));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableStars::saveCachedFile(const std::string& file) const {
|
||||
std::ofstream fileStream(file, std::ofstream::binary);
|
||||
|
||||
if (!fileStream.good()) {
|
||||
LERROR(fmt::format("Error opening file '{}' for save cache file", file));
|
||||
return;
|
||||
}
|
||||
|
||||
fileStream.write(
|
||||
reinterpret_cast<const char*>(&CurrentCacheVersion),
|
||||
sizeof(int8_t)
|
||||
);
|
||||
|
||||
int32_t nValues = static_cast<int32_t>(_fullData.size());
|
||||
if (nValues == 0) {
|
||||
throw ghoul::RuntimeError("Error writing cache: No values were loaded");
|
||||
}
|
||||
fileStream.write(reinterpret_cast<const char*>(&nValues), sizeof(int32_t));
|
||||
|
||||
int32_t nValuesPerStar = static_cast<int32_t>(_nValuesPerStar);
|
||||
fileStream.write(reinterpret_cast<const char*>(&nValuesPerStar), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_lumArrayPos), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_absMagArrayPos), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_appMagArrayPos), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_bvColorArrayPos), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_velocityArrayPos), sizeof(int32_t));
|
||||
fileStream.write(reinterpret_cast<const char*>(&_speedArrayPos), sizeof(int32_t));
|
||||
|
||||
// -3 as we don't want to save the xyz values that are in the beginning of the file
|
||||
for (int i = 0; i < _nValuesPerStar - 3; ++i) {
|
||||
uint16_t len = static_cast<uint16_t>(_dataNames[i].size());
|
||||
fileStream.write(reinterpret_cast<const char*>(&len), sizeof(uint16_t));
|
||||
fileStream.write(_dataNames[i].c_str(), len);
|
||||
}
|
||||
|
||||
size_t nBytes = nValues * sizeof(_fullData[0]);
|
||||
fileStream.write(reinterpret_cast<const char*>(_fullData.data()), nBytes);
|
||||
}
|
||||
|
||||
void RenderableStars::createDataSlice(ColorOption option) {
|
||||
_slicedData.clear();
|
||||
std::vector<float> RenderableStars::createDataSlice(ColorOption option) {
|
||||
const int bvIdx = std::max(_dataset.index("colorb_v"), 0);
|
||||
const int lumIdx = std::max(_dataset.index("lum"), 0);
|
||||
const int absMagIdx = std::max(_dataset.index("absmag"), 0);
|
||||
const int appMagIdx = std::max(_dataset.index("appmag"), 0);
|
||||
const int vxIdx = std::max(_dataset.index("vx"), 0);
|
||||
const int vyIdx = std::max(_dataset.index("vy"), 0);
|
||||
const int vzIdx = std::max(_dataset.index("vz"), 0);
|
||||
const int speedIdx = std::max(_dataset.index("speed"), 0);
|
||||
|
||||
_otherDataRange = glm::vec2(
|
||||
std::numeric_limits<float>::max(),
|
||||
@@ -1482,18 +1228,12 @@ void RenderableStars::createDataSlice(ColorOption option) {
|
||||
|
||||
double maxRadius = 0.0;
|
||||
|
||||
for (size_t i = 0; i < _fullData.size(); i += _nValuesPerStar) {
|
||||
glm::dvec3 position = glm::dvec3(
|
||||
_fullData[i + 0],
|
||||
_fullData[i + 1],
|
||||
_fullData[i + 2]
|
||||
);
|
||||
position *= openspace::distanceconstants::Parsec;
|
||||
|
||||
const double r = glm::length(position);
|
||||
if (r > maxRadius) {
|
||||
maxRadius = r;
|
||||
}
|
||||
std::vector<float> result;
|
||||
// 7 for the default Color option of 3 positions + bv + lum + abs + app magnitude
|
||||
result.reserve(_dataset.entries.size() * 7);
|
||||
for (const speck::Dataset::Entry& e : _dataset.entries) {
|
||||
glm::dvec3 position = glm::dvec3(e.position) * distanceconstants::Parsec;
|
||||
maxRadius = std::max(maxRadius, glm::length(position));
|
||||
|
||||
switch (option) {
|
||||
case ColorOption::Color:
|
||||
@@ -1510,23 +1250,12 @@ void RenderableStars::createDataSlice(ColorOption option) {
|
||||
static_cast<float>(position[2])
|
||||
}};
|
||||
|
||||
if (_enableTestGrid) {
|
||||
float sunColor = 0.650f;
|
||||
layout.value.value = sunColor;// _fullData[i + 3];
|
||||
}
|
||||
else {
|
||||
layout.value.value = _fullData[i + _bvColorArrayPos];
|
||||
}
|
||||
|
||||
layout.value.luminance = _fullData[i + _lumArrayPos];
|
||||
layout.value.absoluteMagnitude = _fullData[i + _absMagArrayPos];
|
||||
layout.value.apparentMagnitude = _fullData[i + _appMagArrayPos];
|
||||
|
||||
_slicedData.insert(
|
||||
_slicedData.end(),
|
||||
layout.data.begin(),
|
||||
layout.data.end());
|
||||
layout.value.value = e.data[bvIdx];
|
||||
layout.value.luminance = e.data[lumIdx];
|
||||
layout.value.absoluteMagnitude = e.data[absMagIdx];
|
||||
layout.value.apparentMagnitude = e.data[appMagIdx];
|
||||
|
||||
result.insert(result.end(), layout.data.begin(), layout.data.end());
|
||||
break;
|
||||
}
|
||||
case ColorOption::Velocity:
|
||||
@@ -1542,20 +1271,16 @@ void RenderableStars::createDataSlice(ColorOption option) {
|
||||
static_cast<float>(position[2])
|
||||
}};
|
||||
|
||||
layout.value.value = _fullData[i + _bvColorArrayPos];
|
||||
layout.value.luminance = _fullData[i + _lumArrayPos];
|
||||
layout.value.absoluteMagnitude = _fullData[i + _absMagArrayPos];
|
||||
layout.value.apparentMagnitude = _fullData[i + _appMagArrayPos];
|
||||
layout.value.value = e.data[bvIdx];
|
||||
layout.value.luminance = e.data[lumIdx];
|
||||
layout.value.absoluteMagnitude = e.data[absMagIdx];
|
||||
layout.value.apparentMagnitude = e.data[appMagIdx];
|
||||
|
||||
layout.value.vx = _fullData[i + _velocityArrayPos];
|
||||
layout.value.vy = _fullData[i + _velocityArrayPos + 1];
|
||||
layout.value.vz = _fullData[i + _velocityArrayPos + 2];
|
||||
layout.value.vx = e.data[vxIdx];
|
||||
layout.value.vy = e.data[vyIdx];
|
||||
layout.value.vz = e.data[vzIdx];
|
||||
|
||||
_slicedData.insert(
|
||||
_slicedData.end(),
|
||||
layout.data.begin(),
|
||||
layout.data.end()
|
||||
);
|
||||
result.insert(result.end(), layout.data.begin(), layout.data.end());
|
||||
break;
|
||||
}
|
||||
case ColorOption::Speed:
|
||||
@@ -1571,18 +1296,13 @@ void RenderableStars::createDataSlice(ColorOption option) {
|
||||
static_cast<float>(position[2])
|
||||
}};
|
||||
|
||||
layout.value.value = _fullData[i + _bvColorArrayPos];
|
||||
layout.value.luminance = _fullData[i + _lumArrayPos];
|
||||
layout.value.absoluteMagnitude = _fullData[i + _absMagArrayPos];
|
||||
layout.value.apparentMagnitude = _fullData[i + _appMagArrayPos];
|
||||
layout.value.value = e.data[bvIdx];
|
||||
layout.value.luminance = e.data[lumIdx];
|
||||
layout.value.absoluteMagnitude = e.data[absMagIdx];
|
||||
layout.value.apparentMagnitude = e.data[appMagIdx];
|
||||
layout.value.speed = e.data[speedIdx];
|
||||
|
||||
layout.value.speed = _fullData[i + _speedArrayPos];
|
||||
|
||||
_slicedData.insert(
|
||||
_slicedData.end(),
|
||||
layout.data.begin(),
|
||||
layout.data.end()
|
||||
);
|
||||
result.insert(result.end(), layout.data.begin(), layout.data.end());
|
||||
break;
|
||||
}
|
||||
case ColorOption::OtherData:
|
||||
@@ -1600,37 +1320,32 @@ void RenderableStars::createDataSlice(ColorOption option) {
|
||||
|
||||
int index = _otherDataOption.value();
|
||||
// plus 3 because of the position
|
||||
layout.value.value = _fullData[i + index + 3];
|
||||
layout.value.value = e.data[index];
|
||||
|
||||
if (_staticFilterValue.has_value() &&
|
||||
layout.value.value == _staticFilterValue)
|
||||
if (_staticFilterValue.has_value() && e.data[index] == _staticFilterValue)
|
||||
{
|
||||
layout.value.value = _staticFilterReplacementValue;
|
||||
}
|
||||
|
||||
glm::vec2 range = _otherDataRange.value();
|
||||
glm::vec2 range = _otherDataRange;
|
||||
range.x = std::min(range.x, layout.value.value);
|
||||
range.y = std::max(range.y, layout.value.value);
|
||||
_otherDataRange = range;
|
||||
_otherDataRange.setMinValue(glm::vec2(range.x));
|
||||
_otherDataRange.setMaxValue(glm::vec2(range.y));
|
||||
|
||||
layout.value.luminance = _fullData[i + _lumArrayPos];
|
||||
layout.value.absoluteMagnitude = _fullData[i + _absMagArrayPos];
|
||||
layout.value.apparentMagnitude = _fullData[i + _appMagArrayPos];
|
||||
|
||||
_slicedData.insert(
|
||||
_slicedData.end(),
|
||||
layout.data.begin(),
|
||||
layout.data.end()
|
||||
);
|
||||
layout.value.luminance = e.data[lumIdx];
|
||||
layout.value.absoluteMagnitude = e.data[absMagIdx];
|
||||
layout.value.apparentMagnitude = e.data[appMagIdx];
|
||||
|
||||
result.insert(result.end(), layout.data.begin(), layout.data.end());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBoundingSphere(maxRadius);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/optionproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
@@ -74,15 +75,8 @@ private:
|
||||
FixedColor = 4
|
||||
};
|
||||
|
||||
static const int _psfTextureSize = 64;
|
||||
static const int _convolvedfTextureSize = 257;
|
||||
|
||||
void createDataSlice(ColorOption option);
|
||||
|
||||
void loadData();
|
||||
void readSpeckFile();
|
||||
bool loadCachedFile(const std::string& file);
|
||||
void saveCachedFile(const std::string& file) const;
|
||||
std::vector<float> createDataSlice(ColorOption option);
|
||||
|
||||
properties::StringProperty _speckFile;
|
||||
|
||||
@@ -90,10 +84,6 @@ private:
|
||||
std::unique_ptr<ghoul::opengl::Texture> _colorTexture;
|
||||
std::unique_ptr<ghoul::filesystem::File> _colorTextureFile;
|
||||
|
||||
//properties::StringProperty _shapeTexturePath;
|
||||
//std::unique_ptr<ghoul::opengl::Texture> _shapeTexture;
|
||||
//std::unique_ptr<ghoul::filesystem::File> _shapeTextureFile;
|
||||
|
||||
properties::OptionProperty _colorOption;
|
||||
properties::OptionProperty _otherDataOption;
|
||||
properties::StringProperty _otherDataColorMapPath;
|
||||
@@ -128,11 +118,10 @@ private:
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _program;
|
||||
UniformCache(
|
||||
modelMatrix, cameraUp, cameraViewProjectionMatrix,
|
||||
colorOption, magnitudeExponent, eyePosition, psfParamConf,
|
||||
lumCent, radiusCent, brightnessCent, colorTexture,
|
||||
alphaValue, psfTexture, otherDataTexture, otherDataRange,
|
||||
filterOutOfRange, fixedColor
|
||||
modelMatrix, cameraUp, cameraViewProjectionMatrix, colorOption, magnitudeExponent,
|
||||
eyePosition, psfParamConf, lumCent, radiusCent, brightnessCent, colorTexture,
|
||||
alphaValue, psfTexture, otherDataTexture, otherDataRange, filterOutOfRange,
|
||||
fixedColor
|
||||
) _uniformCache;
|
||||
|
||||
bool _speckFileIsDirty = true;
|
||||
@@ -142,26 +131,13 @@ private:
|
||||
bool _dataIsDirty = true;
|
||||
bool _otherDataColorMapIsDirty = true;
|
||||
|
||||
// Test Grid Enabled
|
||||
bool _enableTestGrid = false;
|
||||
speck::Dataset _dataset;
|
||||
|
||||
std::vector<float> _slicedData;
|
||||
std::vector<float> _fullData;
|
||||
|
||||
int _nValuesPerStar = 0;
|
||||
std::string _queuedOtherData;
|
||||
std::vector<std::string> _dataNames;
|
||||
|
||||
std::optional<float> _staticFilterValue;
|
||||
float _staticFilterReplacementValue = 0.f;
|
||||
|
||||
std::size_t _lumArrayPos = 0;
|
||||
std::size_t _absMagArrayPos = 0;
|
||||
std::size_t _appMagArrayPos = 0;
|
||||
std::size_t _bvColorArrayPos = 0;
|
||||
std::size_t _velocityArrayPos = 0;
|
||||
std::size_t _speedArrayPos = 0;
|
||||
|
||||
GLuint _vao = 0;
|
||||
GLuint _vbo = 0;
|
||||
GLuint _psfVao = 0;
|
||||
|
||||
@@ -0,0 +1,905 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2021 *
|
||||
* *
|
||||
* 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/space/speckloader.h>
|
||||
|
||||
#include <ghoul/fmt.h>
|
||||
#include <ghoul/filesystem/cachemanager.h>
|
||||
#include <ghoul/filesystem/file.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
constexpr const int8_t DataCacheFileVersion = 10;
|
||||
constexpr const int8_t LabelCacheFileVersion = 10;
|
||||
constexpr const int8_t ColorCacheFileVersion = 10;
|
||||
|
||||
constexpr bool startsWith(std::string_view lhs, std::string_view rhs) noexcept {
|
||||
return (rhs.size() <= lhs.size()) && (lhs.substr(0, rhs.size()) == rhs);
|
||||
}
|
||||
|
||||
void strip(std::string& line) noexcept {
|
||||
// 1. Remove all spaces from the beginning
|
||||
// 2. Remove #
|
||||
// 3. Remove all spaces from the new beginning
|
||||
// 4. Remove all spaces from the end
|
||||
|
||||
while (!line.empty() && line[0] == ' ') {
|
||||
line = line.substr(1);
|
||||
}
|
||||
|
||||
if (!line.empty() && line[0] == '#') {
|
||||
line = line.substr(1);
|
||||
}
|
||||
|
||||
while (!line.empty() && line[0] == ' ') {
|
||||
line = line.substr(1);
|
||||
}
|
||||
|
||||
while (!line.empty() && line.back() == ' ') {
|
||||
line = line.substr(0, line.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
void checkSize(U value, std::string_view message) {
|
||||
if (value > std::numeric_limits<U>::max()) {
|
||||
throw ghoul::RuntimeError(fmt::format("Error saving file: {}", message));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using LoadCacheFunc = std::function<std::optional<T>(std::filesystem::path)>;
|
||||
|
||||
template <typename T>
|
||||
using SaveCacheFunc = std::function<void(const T&, std::filesystem::path)>;
|
||||
|
||||
template <typename T>
|
||||
using LoadSpeckFunc = std::function<T(
|
||||
std::filesystem::path, openspace::speck::SkipAllZeroLines)>;
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
T internalLoadFileWithCache(std::filesystem::path speckPath,
|
||||
openspace::speck::SkipAllZeroLines skipAllZeroLines,
|
||||
LoadSpeckFunc<T> loadSpeckFunction,
|
||||
LoadCacheFunc<T> loadCacheFunction,
|
||||
SaveCacheFunc<T> saveCacheFunction)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same_v<T, openspace::speck::Dataset> ||
|
||||
std::is_same_v<T, openspace::speck::Labelset> ||
|
||||
std::is_same_v<T, openspace::speck::ColorMap>
|
||||
);
|
||||
|
||||
std::string cachePath = FileSys.cacheManager()->cachedFilename(speckPath);
|
||||
|
||||
if (std::filesystem::exists(cachePath)) {
|
||||
LINFOC(
|
||||
"SpeckLoader",
|
||||
fmt::format(
|
||||
"Cached file '{}' used for file {}", cachePath, speckPath
|
||||
)
|
||||
);
|
||||
|
||||
std::optional<T> dataset = loadCacheFunction(cachePath);
|
||||
if (dataset.has_value()) {
|
||||
// We could load the cache file and we are now done with this
|
||||
return *dataset;
|
||||
}
|
||||
else {
|
||||
FileSys.cacheManager()->removeCacheFile(cachePath);
|
||||
}
|
||||
}
|
||||
LINFOC("SpeckLoader", fmt::format("Loading file {}", speckPath));
|
||||
T dataset = loadSpeckFunction(speckPath, skipAllZeroLines);
|
||||
|
||||
if (!dataset.entries.empty()) {
|
||||
LINFOC("SpeckLoader", "Saving cache");
|
||||
saveCacheFunction(dataset, cachePath);
|
||||
}
|
||||
return dataset;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace::speck {
|
||||
|
||||
namespace data {
|
||||
|
||||
Dataset loadFile(std::filesystem::path path, SkipAllZeroLines skipAllZeroLines) {
|
||||
ghoul_assert(std::filesystem::exists(path), "File must exist");
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.good()) {
|
||||
throw ghoul::RuntimeError(fmt::format("Failed to open speck file {}", path));
|
||||
}
|
||||
|
||||
Dataset res;
|
||||
|
||||
int nDataValues = 0;
|
||||
|
||||
std::string line;
|
||||
// First phase: Loading the header information
|
||||
while (std::getline(file, line)) {
|
||||
// Ignore empty line or commented-out lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
strip(line);
|
||||
|
||||
// If the first character is a digit, we have left the preamble and are in the
|
||||
// data section of the file
|
||||
if (std::isdigit(line[0]) || line[0] == '-') {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (startsWith(line, "datavar")) {
|
||||
// each datavar line is following the form:
|
||||
// datavar <idx> <description>
|
||||
// with <idx> being the index of the data variable
|
||||
|
||||
std::stringstream str(line);
|
||||
std::string dummy;
|
||||
Dataset::Variable v;
|
||||
str >> dummy >> v.index >> v.name;
|
||||
|
||||
nDataValues += 1;
|
||||
res.variables.push_back(v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (startsWith(line, "texturevar")) {
|
||||
// each texturevar line is following the form:
|
||||
// texturevar <idx>
|
||||
// where <idx> is the data value index where the texture index is stored
|
||||
if (res.textureDataIndex != -1) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading speck file {}: Texturevar defined twice", path
|
||||
));
|
||||
}
|
||||
|
||||
std::stringstream str(line);
|
||||
std::string dummy;
|
||||
str >> dummy >> res.textureDataIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (startsWith(line, "polyorivar")) {
|
||||
// each polyorivar line is following the form:
|
||||
// texturevar <idx>
|
||||
// where <idx> is the data value index where the orientation index storage
|
||||
// starts. There are 6 values stored in total, xyz + uvw
|
||||
|
||||
if (res.orientationDataIndex != -1) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading speck file {}: Orientation index defined twice", path
|
||||
));
|
||||
}
|
||||
|
||||
std::stringstream str(line);
|
||||
std::string dummy;
|
||||
str >> dummy >> res.orientationDataIndex;
|
||||
|
||||
// Ok.. this is kind of weird. Speck unfortunately doesn't tell us in the
|
||||
// specification how many values a datavar has. Usually this is 1 value per
|
||||
// datavar, unless it is a polygon orientation thing. Now, the datavar name
|
||||
// for these can be anything (have seen 'orientation' and 'ori' before, so we
|
||||
// can't really check by name for these or we will miss some if they are
|
||||
// mispelled or whatever. So we have to go the roundabout way of adding the
|
||||
// 5 remaining values (the 6th nDataValue was already added in the
|
||||
// corresponding 'datavar' section) here
|
||||
nDataValues += 5;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (startsWith(line, "texture")) {
|
||||
// each texture line is following one of two forms:
|
||||
// 1: texture -M 1 halo.sgi
|
||||
// 2: texture 1 M1.sgi
|
||||
// The parameter in #1 is currently being ignored
|
||||
|
||||
std::stringstream str(line);
|
||||
|
||||
std::string dummy;
|
||||
str >> dummy;
|
||||
|
||||
if (line.find('-') != std::string::npos) {
|
||||
str >> dummy;
|
||||
}
|
||||
|
||||
Dataset::Texture texture;
|
||||
str >> texture.index >> texture.file;
|
||||
|
||||
for (const Dataset::Texture& t : res.textures) {
|
||||
if (t.index == texture.index) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading speck file {}: Texture index '{}' defined twice",
|
||||
path, texture.index
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
res.textures.push_back(texture);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(
|
||||
res.variables.begin(), res.variables.end(),
|
||||
[](const Dataset::Variable& lhs, const Dataset::Variable& rhs) {
|
||||
return lhs.index < rhs.index;
|
||||
}
|
||||
);
|
||||
|
||||
std::sort(
|
||||
res.textures.begin(), res.textures.end(),
|
||||
[](const Dataset::Texture& lhs, const Dataset::Texture& rhs) {
|
||||
return lhs.index < rhs.index;
|
||||
}
|
||||
);
|
||||
|
||||
// For the first line, we already loaded it and rejected it above, so if we do another
|
||||
// std::getline, we'd miss the first data value line
|
||||
bool isFirst = true;
|
||||
while (isFirst || std::getline(file, line)) {
|
||||
isFirst = false;
|
||||
|
||||
// Ignore empty line or commented-out lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
strip(line);
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the first character is a digit, we have left the preamble and are in the
|
||||
// data section of the file
|
||||
if (!std::isdigit(line[0]) && line[0] != '-') {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading speck file {}: Header information and datasegment "
|
||||
"intermixed", path
|
||||
));
|
||||
}
|
||||
|
||||
bool allZero = true;
|
||||
|
||||
std::stringstream str(line);
|
||||
Dataset::Entry entry;
|
||||
str >> entry.position.x >> entry.position.y >> entry.position.z;
|
||||
allZero &= (entry.position == glm::vec3(0.0));
|
||||
|
||||
entry.data.resize(nDataValues);
|
||||
for (int i = 0; i < nDataValues; i += 1) {
|
||||
str >> entry.data[i];
|
||||
allZero &= (entry.data[i] == 0.0);
|
||||
}
|
||||
|
||||
if (skipAllZeroLines && allZero) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string rest;
|
||||
std::getline(str, rest);
|
||||
if (!rest.empty()) {
|
||||
|
||||
strip(rest);
|
||||
entry.comment = rest;
|
||||
}
|
||||
|
||||
res.entries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
if (!res.entries.empty()) {
|
||||
size_t nValues = res.entries[0].data.size();
|
||||
ghoul_assert(nDataValues == nValues, "nDataValues calculation went wrong");
|
||||
for (const Dataset::Entry& e : res.entries) {
|
||||
ghoul_assert(
|
||||
e.data.size() == nDataValues,
|
||||
"Row had different number of data values"
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<Dataset> loadCachedFile(std::filesystem::path path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.good()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Dataset result;
|
||||
|
||||
int8_t fileVersion;
|
||||
file.read(reinterpret_cast<char*>(&fileVersion), sizeof(int8_t));
|
||||
if (fileVersion != DataCacheFileVersion) {
|
||||
// Incompatible version and we won't be able to read the file
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
//
|
||||
// Read variables
|
||||
uint16_t nVariables;
|
||||
file.read(reinterpret_cast<char*>(&nVariables), sizeof(uint16_t));
|
||||
result.variables.resize(nVariables);
|
||||
for (int i = 0; i < nVariables; i += 1) {
|
||||
Dataset::Variable var;
|
||||
|
||||
int16_t idx;
|
||||
file.read(reinterpret_cast<char*>(&idx), sizeof(int16_t));
|
||||
var.index = idx;
|
||||
|
||||
uint16_t len;
|
||||
file.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
var.name.resize(len);
|
||||
file.read(var.name.data(), len);
|
||||
|
||||
result.variables[i] = std::move(var);
|
||||
}
|
||||
|
||||
//
|
||||
// Read textures
|
||||
uint16_t nTextures;
|
||||
file.read(reinterpret_cast<char*>(&nTextures), sizeof(uint16_t));
|
||||
result.textures.resize(nTextures);
|
||||
for (int i = 0; i < nTextures; i += 1) {
|
||||
Dataset::Texture tex;
|
||||
|
||||
int16_t idx;
|
||||
file.read(reinterpret_cast<char*>(&idx), sizeof(int16_t));
|
||||
tex.index = idx;
|
||||
|
||||
uint16_t len;
|
||||
file.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
tex.file.resize(len);
|
||||
file.read(tex.file.data(), len);
|
||||
|
||||
result.textures[i] = std::move(tex);
|
||||
}
|
||||
|
||||
//
|
||||
// Read indices
|
||||
int16_t texDataIdx;
|
||||
file.read(reinterpret_cast<char*>(&texDataIdx), sizeof(int16_t));
|
||||
result.textureDataIndex = texDataIdx;
|
||||
|
||||
int16_t oriDataIdx;
|
||||
file.read(reinterpret_cast<char*>(&oriDataIdx), sizeof(int16_t));
|
||||
result.orientationDataIndex = oriDataIdx;
|
||||
|
||||
//
|
||||
// Read entries
|
||||
uint64_t nEntries;
|
||||
file.read(reinterpret_cast<char*>(&nEntries), sizeof(uint64_t));
|
||||
result.entries.reserve(nEntries);
|
||||
for (int i = 0; i < nEntries; i += 1) {
|
||||
Dataset::Entry e;
|
||||
file.read(reinterpret_cast<char*>(&e.position.x), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&e.position.y), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&e.position.z), sizeof(float));
|
||||
|
||||
uint16_t nValues;
|
||||
file.read(reinterpret_cast<char*>(&nValues), sizeof(uint16_t));
|
||||
e.data.resize(nValues);
|
||||
file.read(reinterpret_cast<char*>(e.data.data()), nValues * sizeof(float));
|
||||
|
||||
uint16_t len;
|
||||
file.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
if (len > 0) {
|
||||
std::string comment;
|
||||
comment.resize(len);
|
||||
file.read(comment.data(), len);
|
||||
e.comment = std::move(comment);
|
||||
}
|
||||
|
||||
result.entries.push_back(std::move(e));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void saveCachedFile(const Dataset& dataset, std::filesystem::path path) {
|
||||
std::ofstream file(path, std::ofstream::binary);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&DataCacheFileVersion), sizeof(int8_t));
|
||||
|
||||
//
|
||||
// Store variables
|
||||
checkSize<uint16_t>(dataset.variables.size(), "Too many variables");
|
||||
uint16_t nVariables = static_cast<uint16_t>(dataset.variables.size());
|
||||
file.write(reinterpret_cast<const char*>(&nVariables), sizeof(uint16_t));
|
||||
for (const Dataset::Variable& var : dataset.variables) {
|
||||
checkSize<int16_t>(var.index, "Variable index too large");
|
||||
int16_t idx = static_cast<int16_t>(var.index);
|
||||
file.write(reinterpret_cast<const char*>(&idx), sizeof(int16_t));
|
||||
|
||||
checkSize<uint16_t>(var.name.size(), "Variable name too long");
|
||||
uint16_t len = static_cast<uint16_t>(var.name.size());
|
||||
file.write(reinterpret_cast<const char*>(&len), sizeof(uint16_t));
|
||||
file.write(var.name.data(), len);
|
||||
}
|
||||
|
||||
//
|
||||
// Store textures
|
||||
checkSize<uint16_t>(dataset.textures.size(), "Too many textures");
|
||||
uint16_t nTextures = static_cast<uint16_t>(dataset.textures.size());
|
||||
file.write(reinterpret_cast<const char*>(&nTextures), sizeof(uint16_t));
|
||||
for (const Dataset::Texture& tex : dataset.textures) {
|
||||
checkSize<int16_t>(tex.index, "Texture index too large");
|
||||
int16_t idx = static_cast<int16_t>(tex.index);
|
||||
file.write(reinterpret_cast<const char*>(&idx), sizeof(int16_t));
|
||||
|
||||
|
||||
checkSize<uint16_t>(tex.file.size(), "Texture file too long");
|
||||
uint16_t len = static_cast<uint16_t>(tex.file.size());
|
||||
file.write(reinterpret_cast<const char*>(&len), sizeof(uint16_t));
|
||||
file.write(tex.file.data(), len);
|
||||
}
|
||||
|
||||
//
|
||||
// Store indices
|
||||
checkSize<int16_t>(dataset.textureDataIndex, "Texture index too large");
|
||||
int16_t texIdx = static_cast<int16_t>(dataset.textureDataIndex);
|
||||
file.write(reinterpret_cast<const char*>(&texIdx), sizeof(int16_t));
|
||||
|
||||
checkSize<int16_t>(dataset.orientationDataIndex, "Orientation index too large");
|
||||
int16_t orientationIdx = static_cast<int16_t>(dataset.orientationDataIndex);
|
||||
file.write(reinterpret_cast<const char*>(&orientationIdx), sizeof(int16_t));
|
||||
|
||||
//
|
||||
// Store entries
|
||||
checkSize<uint64_t>(dataset.entries.size(), "Too many entries");
|
||||
uint64_t nEntries = static_cast<uint64_t>(dataset.entries.size());
|
||||
file.write(reinterpret_cast<const char*>(&nEntries), sizeof(uint64_t));
|
||||
for (const Dataset::Entry& e : dataset.entries) {
|
||||
file.write(reinterpret_cast<const char*>(&e.position.x), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&e.position.y), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&e.position.z), sizeof(float));
|
||||
|
||||
checkSize<uint16_t>(e.data.size(), "Too many data variables");
|
||||
uint16_t nValues = static_cast<uint16_t>(e.data.size());
|
||||
file.write(reinterpret_cast<const char*>(&nValues), sizeof(uint16_t));
|
||||
file.write(
|
||||
reinterpret_cast<const char*>(e.data.data()),
|
||||
e.data.size() * sizeof(float)
|
||||
);
|
||||
|
||||
if (e.comment.has_value()) {
|
||||
checkSize<uint16_t>(e.comment->size(), "Comment too long");
|
||||
}
|
||||
uint16_t commentLen = e.comment.has_value() ?
|
||||
static_cast<uint16_t>(e.comment->size()) :
|
||||
0;
|
||||
file.write(reinterpret_cast<const char*>(&commentLen), sizeof(uint16_t));
|
||||
if (e.comment.has_value()) {
|
||||
file.write(e.comment->data(), e.comment->size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dataset loadFileWithCache(std::filesystem::path speckPath,
|
||||
SkipAllZeroLines skipAllZeroLines)
|
||||
{
|
||||
return internalLoadFileWithCache<Dataset>(
|
||||
speckPath,
|
||||
skipAllZeroLines,
|
||||
&loadFile,
|
||||
&loadCachedFile,
|
||||
&saveCachedFile
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace data
|
||||
|
||||
namespace label {
|
||||
|
||||
Labelset loadFile(std::filesystem::path path, SkipAllZeroLines) {
|
||||
ghoul_assert(std::filesystem::exists(path), "File must exist");
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.good()) {
|
||||
throw ghoul::RuntimeError(fmt::format("Failed to open speck file '{}'", path));
|
||||
}
|
||||
|
||||
Labelset res;
|
||||
|
||||
std::string line;
|
||||
// First phase: Loading the header information
|
||||
while (std::getline(file, line)) {
|
||||
// Ignore empty line or commented-out lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
strip(line);
|
||||
|
||||
// If the first character is a digit, we have left the preamble and are in the
|
||||
// data section of the file
|
||||
if (std::isdigit(line[0]) || line[0] == '-') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (startsWith(line, "textcolor")) {
|
||||
// each textcolor line is following the form:
|
||||
// textcolor <idx>
|
||||
// with <idx> being the index of the color into some configuration file (not
|
||||
// really sure how these configuration files work, but they don't seem to be
|
||||
// included in the speck file)
|
||||
if (res.textColorIndex != -1) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading label file '{}': Textcolor defined twice", path
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
std::stringstream str(line);
|
||||
std::string dummy;
|
||||
str >> dummy >> res.textColorIndex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For the first line, we already loaded it and rejected it above, so if we do another
|
||||
// std::getline, we'd miss the first data value line
|
||||
bool isFirst = true;
|
||||
while (isFirst || std::getline(file, line)) {
|
||||
isFirst = false;
|
||||
|
||||
// Ignore empty line or commented-out lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
strip(line);
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the first character is a digit, we have left the preamble and are in the
|
||||
// data section of the file
|
||||
if (!std::isdigit(line[0]) && line[0] != '-') {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading label file '{}': Header information and datasegment "
|
||||
"intermixed", path
|
||||
));
|
||||
}
|
||||
|
||||
// Each line looks like this:
|
||||
// <x> <y> <z> text <label> # potential comment
|
||||
// so we want to get the position, remove the 'text' text and the potential
|
||||
// comment at the end
|
||||
std::stringstream str(line);
|
||||
Labelset::Entry entry;
|
||||
str >> entry.position.x >> entry.position.y >> entry.position.z;
|
||||
|
||||
std::string rest;
|
||||
std::getline(str, rest);
|
||||
strip(rest);
|
||||
|
||||
if (!startsWith(rest, "text")) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading label file '{}': File contains some value between "
|
||||
"positions and text label, which is unsupported", path
|
||||
));
|
||||
}
|
||||
|
||||
// Remove the 'text' text
|
||||
rest = rest.substr(std::string_view("text ").size());
|
||||
|
||||
// Remove the trailing comment
|
||||
for (size_t i = 0; i < rest.size(); i += 1) {
|
||||
if (rest[i] == '#') {
|
||||
rest = rest.substr(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
strip(rest);
|
||||
|
||||
entry.text = rest;
|
||||
if (!rest.empty()) {
|
||||
res.entries.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<Labelset> loadCachedFile(std::filesystem::path path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.good()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int8_t fileVersion;
|
||||
file.read(reinterpret_cast<char*>(&fileVersion), sizeof(int8_t));
|
||||
if (fileVersion != LabelCacheFileVersion) {
|
||||
// Incompatible version and we won't be able to read the file
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Labelset result;
|
||||
|
||||
int16_t textColorIdx;
|
||||
file.read(reinterpret_cast<char*>(&textColorIdx), sizeof(int16_t));
|
||||
result.textColorIndex = textColorIdx;
|
||||
|
||||
uint32_t nEntries;
|
||||
file.read(reinterpret_cast<char*>(&nEntries), sizeof(uint32_t));
|
||||
result.entries.reserve(nEntries);
|
||||
for (unsigned int i = 0; i < nEntries; i += 1) {
|
||||
Labelset::Entry e;
|
||||
file.read(reinterpret_cast<char*>(&e.position.x), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&e.position.y), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&e.position.z), sizeof(float));
|
||||
|
||||
uint16_t len;
|
||||
file.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
e.text.resize(len);
|
||||
file.read(e.text.data(), len);
|
||||
|
||||
result.entries.push_back(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void saveCachedFile(const Labelset& labelset, std::filesystem::path path) {
|
||||
std::ofstream file(path, std::ofstream::binary);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&LabelCacheFileVersion), sizeof(int8_t));
|
||||
|
||||
//
|
||||
// Storage text color
|
||||
checkSize<int16_t>(labelset.textColorIndex, "Too high text color");
|
||||
int16_t textColorIdx = static_cast<int16_t>(labelset.textColorIndex);
|
||||
file.write(reinterpret_cast<const char*>(&textColorIdx), sizeof(int16_t));
|
||||
|
||||
//
|
||||
// Storage text lines
|
||||
checkSize<uint32_t>(labelset.entries.size(), "Too many entries");
|
||||
uint32_t nEntries = static_cast<uint32_t>(labelset.entries.size());
|
||||
file.write(reinterpret_cast<const char*>(&nEntries), sizeof(uint32_t));
|
||||
for (const Labelset::Entry& e : labelset.entries) {
|
||||
file.write(reinterpret_cast<const char*>(&e.position.x), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&e.position.y), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&e.position.z), sizeof(float));
|
||||
|
||||
checkSize<uint16_t>(e.text.size(), "Text too long");
|
||||
uint16_t len = static_cast<uint16_t>(e.text.size());
|
||||
file.write(reinterpret_cast<const char*>(&len), sizeof(uint16_t));
|
||||
file.write(e.text.data(), len);
|
||||
}
|
||||
}
|
||||
|
||||
Labelset loadFileWithCache(std::filesystem::path speckPath,
|
||||
SkipAllZeroLines skipAllZeroLines)
|
||||
{
|
||||
return internalLoadFileWithCache<Labelset>(
|
||||
speckPath,
|
||||
skipAllZeroLines,
|
||||
&loadFile,
|
||||
&loadCachedFile,
|
||||
&saveCachedFile
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace label
|
||||
|
||||
namespace color {
|
||||
|
||||
ColorMap loadFile(std::filesystem::path path, SkipAllZeroLines) {
|
||||
ghoul_assert(std::filesystem::exists(path), "File must exist");
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.good()) {
|
||||
throw ghoul::RuntimeError(fmt::format("Failed to open speck file '{}'", path));
|
||||
}
|
||||
|
||||
ColorMap res;
|
||||
int nColorLines = -1;
|
||||
|
||||
// The beginning of the speck file has a header that either contains comments
|
||||
// (signaled by a preceding '#') or information about the structure of the file
|
||||
// (signaled by the keywords 'datavar', 'texturevar', and 'texture')
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
// Ignore empty line or commented-out lines
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
strip(line);
|
||||
|
||||
std::stringstream str(line);
|
||||
if (nColorLines == -1) {
|
||||
// This is the first time we get this far, it will have to be the first number
|
||||
// meaning that it is the number of color values
|
||||
|
||||
str >> nColorLines;
|
||||
res.entries.reserve(nColorLines);
|
||||
}
|
||||
else {
|
||||
// We have already read the number of color lines, so we are in the process of
|
||||
// reading the individual value lines
|
||||
|
||||
glm::vec4 color;
|
||||
str >> color.x >> color.y >> color.z >> color.w;
|
||||
res.entries.push_back(std::move(color));
|
||||
}
|
||||
}
|
||||
|
||||
if (nColorLines != res.entries.size()) {
|
||||
LWARNINGC("SpeckLoader", fmt::format(
|
||||
"While loading color map, the expected number of color values '{}' was "
|
||||
"different from the actual number of color values '{}'",
|
||||
nColorLines, res.entries.size()
|
||||
));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<ColorMap> loadCachedFile(std::filesystem::path path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file.good()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int8_t fileVersion;
|
||||
file.read(reinterpret_cast<char*>(&fileVersion), sizeof(int8_t));
|
||||
if (fileVersion != ColorCacheFileVersion) {
|
||||
// Incompatible version and we won't be able to read the file
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ColorMap result;
|
||||
|
||||
uint32_t nColors;
|
||||
file.read(reinterpret_cast<char*>(&nColors), sizeof(uint32_t));
|
||||
result.entries.reserve(nColors);
|
||||
for (unsigned int i = 0; i < nColors; i += 1) {
|
||||
glm::vec4 color;
|
||||
file.read(reinterpret_cast<char*>(&color.x), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&color.y), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&color.z), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&color.w), sizeof(float));
|
||||
result.entries.push_back(color);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void saveCachedFile(const ColorMap& colorMap, std::filesystem::path path) {
|
||||
std::ofstream file(path, std::ofstream::binary);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&ColorCacheFileVersion), sizeof(int8_t));
|
||||
|
||||
uint32_t nColors = static_cast<uint32_t>(colorMap.entries.size());
|
||||
file.write(reinterpret_cast<const char*>(&nColors), sizeof(uint32_t));
|
||||
for (const glm::vec4& color : colorMap.entries) {
|
||||
file.write(reinterpret_cast<const char*>(&color.x), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&color.y), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&color.z), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&color.w), sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
ColorMap loadFileWithCache(std::filesystem::path path, SkipAllZeroLines skipAllZeroLines)
|
||||
{
|
||||
return internalLoadFileWithCache<ColorMap>(
|
||||
path,
|
||||
skipAllZeroLines,
|
||||
&loadFile,
|
||||
&loadCachedFile,
|
||||
&saveCachedFile
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace color
|
||||
|
||||
int Dataset::index(std::string_view variableName) const {
|
||||
for (const Dataset::Variable& v : variables) {
|
||||
if (v.name == variableName) {
|
||||
return v.index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Dataset::normalizeVariable(std::string_view variableName) {
|
||||
std::optional<int> idx;
|
||||
for (const Dataset::Variable& var : variables) {
|
||||
if (var.name == variableName) {
|
||||
idx = var.index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idx.has_value()) {
|
||||
// We didn't find the variable that was specified
|
||||
return false;
|
||||
}
|
||||
|
||||
float minValue = std::numeric_limits<float>::max();
|
||||
float maxValue = -std::numeric_limits<float>::max();
|
||||
for (Dataset::Entry& e : entries) {
|
||||
minValue = std::min(minValue, e.data[*idx]);
|
||||
maxValue = std::max(maxValue, e.data[*idx]);
|
||||
}
|
||||
|
||||
for (Dataset::Entry& e : entries) {
|
||||
e.data[*idx] = (e.data[*idx] - minValue) / (maxValue - minValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace openspace::speck
|
||||
@@ -0,0 +1,122 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2021 *
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___SPECKLOADER___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___SPECKLOADER___H__
|
||||
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/misc/boolean.h>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openspace::speck {
|
||||
|
||||
BooleanType(SkipAllZeroLines);
|
||||
|
||||
struct Dataset {
|
||||
struct Variable {
|
||||
int index;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<Variable> variables;
|
||||
|
||||
struct Texture {
|
||||
int index;
|
||||
std::string file;
|
||||
};
|
||||
std::vector<Texture> textures;
|
||||
|
||||
int textureDataIndex = -1;
|
||||
int orientationDataIndex = -1;
|
||||
|
||||
struct Entry {
|
||||
glm::vec3 position;
|
||||
std::vector<float> data;
|
||||
std::optional<std::string> comment;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
|
||||
int index(std::string_view variableName) const;
|
||||
bool normalizeVariable(std::string_view variableName);
|
||||
};
|
||||
|
||||
struct Labelset {
|
||||
int textColorIndex = -1;
|
||||
|
||||
struct Entry {
|
||||
glm::vec3 position;
|
||||
std::string text;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
|
||||
struct ColorMap {
|
||||
std::vector<glm::vec4> entries;
|
||||
};
|
||||
|
||||
namespace data {
|
||||
|
||||
Dataset loadFile(std::filesystem::path path,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
std::optional<Dataset> loadCachedFile(std::filesystem::path path);
|
||||
void saveCachedFile(const Dataset& dataset, std::filesystem::path path);
|
||||
|
||||
Dataset loadFileWithCache(std::filesystem::path speckPath,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
} // namespace data
|
||||
|
||||
namespace label {
|
||||
|
||||
Labelset loadFile(std::filesystem::path path,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
std::optional<Labelset> loadCachedFile(std::filesystem::path path);
|
||||
void saveCachedFile(const Labelset& labelset, std::filesystem::path path);
|
||||
|
||||
Labelset loadFileWithCache(std::filesystem::path speckPath,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
} // namespace label
|
||||
|
||||
namespace color {
|
||||
|
||||
ColorMap loadFile(std::filesystem::path path,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
std::optional<ColorMap> loadCachedFile(std::filesystem::path path);
|
||||
void saveCachedFile(const ColorMap& colorMap, std::filesystem::path path);
|
||||
|
||||
ColorMap loadFileWithCache(std::filesystem::path path,
|
||||
SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes);
|
||||
|
||||
} // namespace color
|
||||
|
||||
|
||||
} // namespace openspace::speck
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___SPECKLOADER___H__
|
||||
Reference in New Issue
Block a user