mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-06 03:29:44 -06:00
Merge branch 'master' into issue/2093
# Conflicts: # modules/skybrowser/src/targetbrowserpair.cpp
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <ghoul/misc/profiling.h>
|
||||
#include <openspace/properties/property.h>
|
||||
#include <openspace/rendering/deferredcastermanager.h>
|
||||
#include <math.h>
|
||||
|
||||
@@ -44,7 +44,7 @@ set(HEADER_FILES
|
||||
rendering/grids/renderablesphericalgrid.h
|
||||
rendering/renderablecartesianaxes.h
|
||||
rendering/renderabledisc.h
|
||||
rendering/renderablelabels.h
|
||||
rendering/renderablelabel.h
|
||||
rendering/renderablemodel.h
|
||||
rendering/renderablenodeline.h
|
||||
rendering/renderableplane.h
|
||||
@@ -98,7 +98,7 @@ set(SOURCE_FILES
|
||||
rendering/grids/renderablesphericalgrid.cpp
|
||||
rendering/renderablecartesianaxes.cpp
|
||||
rendering/renderabledisc.cpp
|
||||
rendering/renderablelabels.cpp
|
||||
rendering/renderablelabel.cpp
|
||||
rendering/renderablemodel.cpp
|
||||
rendering/renderablenodeline.cpp
|
||||
rendering/renderableplane.cpp
|
||||
@@ -165,3 +165,14 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
target_precompile_headers(${base_module} PRIVATE
|
||||
<openspace/documentation/documentation.h>
|
||||
<openspace/documentation/verifier.h>
|
||||
<openspace/properties/numericalproperty.h>
|
||||
<openspace/rendering/renderable.h>
|
||||
<ghoul/opengl/programobject.h>
|
||||
<ghoul/opengl/shaderobject.h>
|
||||
<ghoul/opengl/uniformcache.h>
|
||||
<future>
|
||||
<map>
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
#include <modules/base/rendering/grids/renderablesphericalgrid.h>
|
||||
#include <modules/base/rendering/renderablecartesianaxes.h>
|
||||
#include <modules/base/rendering/renderabledisc.h>
|
||||
#include <modules/base/rendering/renderablelabels.h>
|
||||
#include <modules/base/rendering/renderablelabel.h>
|
||||
#include <modules/base/rendering/renderablemodel.h>
|
||||
#include <modules/base/rendering/renderablenodeline.h>
|
||||
#include <modules/base/rendering/renderablesphere.h>
|
||||
@@ -129,7 +129,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
|
||||
fRenderable->registerClass<RenderableCartesianAxes>("RenderableCartesianAxes");
|
||||
fRenderable->registerClass<RenderableDisc>("RenderableDisc");
|
||||
fRenderable->registerClass<RenderableGrid>("RenderableGrid");
|
||||
fRenderable->registerClass<RenderableLabels>("RenderableLabels");
|
||||
fRenderable->registerClass<RenderableLabel>("RenderableLabel");
|
||||
fRenderable->registerClass<RenderableModel>("RenderableModel");
|
||||
fRenderable->registerClass<RenderableNodeLine>("RenderableNodeLine");
|
||||
fRenderable->registerClass<RenderablePlaneImageLocal>("RenderablePlaneImageLocal");
|
||||
@@ -212,7 +212,7 @@ std::vector<documentation::Documentation> BaseModule::documentations() const {
|
||||
RenderableCartesianAxes::Documentation(),
|
||||
RenderableDisc::Documentation(),
|
||||
RenderableGrid::Documentation(),
|
||||
RenderableLabels::Documentation(),
|
||||
RenderableLabel::Documentation(),
|
||||
RenderableModel::Documentation(),
|
||||
RenderableNodeLine::Documentation(),
|
||||
RenderablePlane::Documentation(),
|
||||
|
||||
@@ -54,6 +54,19 @@ namespace {
|
||||
"This value species the size of each dimensions of the box"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
|
||||
"DrawLabels",
|
||||
"Draw Labels",
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the grid"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableBoxGrid)]] Parameters {
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
@@ -63,6 +76,13 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(SizeInfo.description)]]
|
||||
std::optional<glm::vec3> size;
|
||||
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
};
|
||||
#include "renderableboxgrid_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -78,6 +98,7 @@ RenderableBoxGrid::RenderableBoxGrid(const ghoul::Dictionary& dictionary)
|
||||
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
|
||||
, _size(SizeInfo, glm::vec3(1.f), glm::vec3(1.f), glm::vec3(100.f))
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -94,10 +115,26 @@ RenderableBoxGrid::RenderableBoxGrid(const ghoul::Dictionary& dictionary)
|
||||
_size = p.size.value_or(_size);
|
||||
_size.onChange([&]() { _gridIsDirty = true; });
|
||||
addProperty(_size);
|
||||
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableBoxGrid::isReady() const {
|
||||
return _gridProgram != nullptr;
|
||||
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
|
||||
}
|
||||
|
||||
void RenderableBoxGrid::initialize() {
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableBoxGrid::initializeGL() {
|
||||
@@ -146,12 +183,12 @@ void RenderableBoxGrid::render(const RenderData& data, RendererTasks&){
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
|
||||
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
|
||||
|
||||
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
|
||||
_gridProgram->setUniform(
|
||||
"MVPTransform",
|
||||
glm::dmat4(data.camera.projectionMatrix()) * modelViewTransform
|
||||
);
|
||||
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
|
||||
_gridProgram->setUniform("opacity", opacity());
|
||||
_gridProgram->setUniform("gridColor", _color);
|
||||
|
||||
@@ -176,6 +213,31 @@ void RenderableBoxGrid::render(const RenderData& data, RendererTasks&){
|
||||
global::renderEngine->openglStateCache().resetBlendState();
|
||||
global::renderEngine->openglStateCache().resetLineState();
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
|
||||
// Draw labels
|
||||
if (_drawLabels && _hasLabels) {
|
||||
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
|
||||
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
|
||||
glm::vec3 right = glm::cross(viewDirection, lookup);
|
||||
const glm::vec3 up = glm::cross(right, viewDirection);
|
||||
|
||||
const glm::dmat4 worldToModelTransform = glm::inverse(modelTransform);
|
||||
glm::vec3 orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
|
||||
if (orthoRight == glm::vec3(0.0)) {
|
||||
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
|
||||
right = glm::cross(viewDirection, otherVector);
|
||||
orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
}
|
||||
const glm::vec3 orthoUp = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
|
||||
);
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableBoxGrid::update(const UpdateData&) {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <ghoul/opengl/ghoul_gl.h>
|
||||
@@ -43,6 +44,7 @@ class RenderableBoxGrid : public Renderable {
|
||||
public:
|
||||
RenderableBoxGrid(const ghoul::Dictionary& dictionary);
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -71,6 +73,11 @@ protected:
|
||||
|
||||
GLenum _mode = GL_LINE_STRIP;
|
||||
std::vector<Vertex> _varray;
|
||||
|
||||
// Labels
|
||||
bool _hasLabels = false;
|
||||
properties::BoolProperty _drawLabels;
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
};
|
||||
|
||||
}// namespace openspace
|
||||
|
||||
@@ -42,6 +42,12 @@ namespace {
|
||||
"This value determines the color of the grid lines that are rendered"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo HighlightColorInfo = {
|
||||
"HighlightColor",
|
||||
"Highlight Color",
|
||||
"This value determines the color of the highlighted lines in the grid"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
|
||||
"Segments",
|
||||
"Number of Segments",
|
||||
@@ -49,30 +55,73 @@ namespace {
|
||||
"grid in each direction"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo HighlightRateInfo = {
|
||||
"HighlightRate",
|
||||
"Highlight Rate",
|
||||
"The rate that the columns and rows are highlighted, counted with respect to the "
|
||||
"center of the grid. If the number of segments in the grid is odd, the "
|
||||
"highlighting might be offset from the center."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
|
||||
"LineWidth",
|
||||
"Line Width",
|
||||
"This value specifies the line width of the grid"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo HighlightLineWidthInfo = {
|
||||
"HighlightLineWidth",
|
||||
"Highlight Line Width",
|
||||
"This value specifies the line width of the highlighted lines in the grid"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
|
||||
"Size",
|
||||
"Grid Size",
|
||||
"This value species the size of each dimensions of the grid"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
|
||||
"DrawLabels",
|
||||
"Draw Labels",
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the grid"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableGrid)]] Parameters {
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(HighlightColorInfo.description)]]
|
||||
std::optional<glm::vec3> highlightColor [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(SegmentsInfo.description)]]
|
||||
std::optional<glm::ivec2> segments;
|
||||
|
||||
// [[codegen::verbatim(HighlightRateInfo.description)]]
|
||||
std::optional<glm::ivec2> highlightRate;
|
||||
|
||||
// [[codegen::verbatim(LineWidthInfo.description)]]
|
||||
std::optional<float> lineWidth;
|
||||
|
||||
// [[codegen::verbatim(HighlightLineWidthInfo.description)]]
|
||||
std::optional<float> highlightLineWidth;
|
||||
|
||||
// [[codegen::verbatim(SizeInfo.description)]]
|
||||
std::optional<glm::vec2> size;
|
||||
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
};
|
||||
#include "renderablegrid_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -86,9 +135,13 @@ documentation::Documentation RenderableGrid::Documentation() {
|
||||
RenderableGrid::RenderableGrid(const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _highlightColor(HighlightColorInfo, glm::vec3(0.8f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _segments(SegmentsInfo, glm::uvec2(10), glm::uvec2(1), glm::uvec2(200))
|
||||
, _highlightRate(HighlightRateInfo, glm::uvec2(0), glm::uvec2(0), glm::uvec2(200))
|
||||
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
|
||||
, _highlightLineWidth(HighlightLineWidthInfo, 0.5f, 1.f, 20.f)
|
||||
, _size(SizeInfo, glm::vec2(1.f), glm::vec2(1.f), glm::vec2(1e11f))
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -99,21 +152,50 @@ RenderableGrid::RenderableGrid(const ghoul::Dictionary& dictionary)
|
||||
_color.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_color);
|
||||
|
||||
// If no highlight color is specified then use the base color
|
||||
_highlightColor = p.highlightColor.value_or(_color);
|
||||
_highlightColor.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_highlightColor);
|
||||
|
||||
_segments = p.segments.value_or(_segments);
|
||||
_segments.onChange([&]() { _gridIsDirty = true; });
|
||||
addProperty(_segments);
|
||||
|
||||
_highlightRate = p.highlightRate.value_or(_highlightRate);
|
||||
_highlightRate.onChange([&]() { _gridIsDirty = true; });
|
||||
addProperty(_highlightRate);
|
||||
|
||||
_lineWidth = p.lineWidth.value_or(_lineWidth);
|
||||
addProperty(_lineWidth);
|
||||
|
||||
// If no highlight line width is specified then use the base line width
|
||||
_highlightLineWidth = p.highlightLineWidth.value_or(_lineWidth);
|
||||
addProperty(_highlightLineWidth);
|
||||
|
||||
_size.setExponent(10.f);
|
||||
_size = p.size.value_or(_size);
|
||||
_size.onChange([&]() { _gridIsDirty = true; });
|
||||
addProperty(_size);
|
||||
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableGrid::isReady() const {
|
||||
return _gridProgram != nullptr;
|
||||
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
|
||||
}
|
||||
|
||||
void RenderableGrid::initialize() {
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableGrid::initializeGL() {
|
||||
@@ -130,9 +212,14 @@ void RenderableGrid::initializeGL() {
|
||||
|
||||
glGenVertexArrays(1, &_vaoID);
|
||||
glGenBuffers(1, &_vBufferID);
|
||||
glGenVertexArrays(1, &_highlightVaoID);
|
||||
glGenBuffers(1, &_highlightVBufferID);
|
||||
|
||||
glBindVertexArray(_vaoID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vBufferID);
|
||||
glBindVertexArray(_highlightVaoID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _highlightVBufferID);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
@@ -140,9 +227,13 @@ void RenderableGrid::initializeGL() {
|
||||
void RenderableGrid::deinitializeGL() {
|
||||
glDeleteVertexArrays(1, &_vaoID);
|
||||
_vaoID = 0;
|
||||
glDeleteVertexArrays(1, &_highlightVaoID);
|
||||
_highlightVaoID = 0;
|
||||
|
||||
glDeleteBuffers(1, &_vBufferID);
|
||||
_vBufferID = 0;
|
||||
glDeleteBuffers(1, &_highlightVBufferID);
|
||||
_highlightVBufferID = 0;
|
||||
|
||||
BaseModule::ProgramObjectManager.release(
|
||||
"GridProgram",
|
||||
@@ -156,18 +247,36 @@ void RenderableGrid::deinitializeGL() {
|
||||
void RenderableGrid::render(const RenderData& data, RendererTasks&){
|
||||
_gridProgram->activate();
|
||||
|
||||
glm::dmat4 modelTransform =
|
||||
const glm::dmat4 modelMatrix =
|
||||
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
|
||||
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
|
||||
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelMatrix;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
|
||||
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
|
||||
|
||||
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
|
||||
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
|
||||
glm::vec3 right = glm::cross(viewDirection, lookup);
|
||||
const glm::vec3 up = glm::cross(right, viewDirection);
|
||||
|
||||
const glm::dmat4 worldToModelTransform = glm::inverse(modelMatrix);
|
||||
glm::vec3 orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
|
||||
if (orthoRight == glm::vec3(0.0)) {
|
||||
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
|
||||
right = glm::cross(viewDirection, otherVector);
|
||||
orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
}
|
||||
|
||||
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
|
||||
_gridProgram->setUniform(
|
||||
"MVPTransform",
|
||||
glm::dmat4(data.camera.projectionMatrix()) * modelViewTransform
|
||||
);
|
||||
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
|
||||
_gridProgram->setUniform("opacity", opacity());
|
||||
_gridProgram->setUniform("gridColor", _color);
|
||||
|
||||
@@ -180,18 +289,37 @@ void RenderableGrid::render(const RenderData& data, RendererTasks&){
|
||||
glEnablei(GL_BLEND, 0);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glDepthMask(false);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
// Render minor grid
|
||||
glBindVertexArray(_vaoID);
|
||||
glDrawArrays(_mode, 0, static_cast<GLsizei>(_varray.size()));
|
||||
glBindVertexArray(0);
|
||||
|
||||
_gridProgram->deactivate();
|
||||
// Render major grid
|
||||
#ifndef __APPLE__
|
||||
glLineWidth(_highlightLineWidth);
|
||||
#else
|
||||
glLineWidth(1.f);
|
||||
#endif
|
||||
_gridProgram->setUniform("gridColor", _highlightColor);
|
||||
|
||||
glBindVertexArray(_highlightVaoID);
|
||||
glDrawArrays(_mode, 0, static_cast<GLsizei>(_highlightArray.size()));
|
||||
|
||||
// Restore GL State
|
||||
glBindVertexArray(0);
|
||||
_gridProgram->deactivate();
|
||||
global::renderEngine->openglStateCache().resetBlendState();
|
||||
global::renderEngine->openglStateCache().resetLineState();
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
|
||||
// Draw labels
|
||||
if (_drawLabels && _hasLabels) {
|
||||
const glm::vec3 orthoUp = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
|
||||
);
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableGrid::update(const UpdateData&) {
|
||||
@@ -199,50 +327,111 @@ void RenderableGrid::update(const UpdateData&) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::vec2 halfSize = _size.value() / 2.f;
|
||||
const glm::dvec2 halfSize = static_cast<glm::dvec2>(_size.value()) / 2.0;
|
||||
const glm::uvec2 nSegments = _segments.value();
|
||||
const glm::vec2 step = _size.value() / static_cast<glm::vec2>(nSegments);
|
||||
const glm::dvec2 step =
|
||||
static_cast<glm::dvec2>(_size.value()) / static_cast<glm::dvec2>(nSegments);
|
||||
|
||||
const int nLines = (2 * nSegments.x * nSegments.y) + nSegments.x + nSegments.y;
|
||||
const int nVertices = 2 * nLines;
|
||||
_varray.resize(nVertices);
|
||||
_varray.clear();
|
||||
_varray.reserve(nVertices);
|
||||
_highlightArray.clear();
|
||||
_highlightArray.reserve(nVertices);
|
||||
// OBS! Could be optimized further by removing duplicate vertices
|
||||
|
||||
int nr = 0;
|
||||
// If the number of segments are uneven the center won't be completly centered
|
||||
const glm::uvec2 center = glm::uvec2(nSegments.x / 2.f, nSegments.y / 2.f);
|
||||
for (unsigned int i = 0; i < nSegments.x; ++i) {
|
||||
for (unsigned int j = 0; j < nSegments.y; ++j) {
|
||||
const float y0 = -halfSize.y + j * step.y;
|
||||
const float y1 = y0 + step.y;
|
||||
const double y0 = -halfSize.y + j * step.y;
|
||||
const double y1 = y0 + step.y;
|
||||
|
||||
const float x0 = -halfSize.x + i * step.x;
|
||||
const float x1 = x0 + step.x;
|
||||
const double x0 = -halfSize.x + i * step.x;
|
||||
const double x1 = x0 + step.x;
|
||||
|
||||
_varray[nr++] = { x0, y0, 0.f };
|
||||
_varray[nr++] = { x0, y1, 0.f };
|
||||
// Line in y direction
|
||||
bool shouldHighlight = false;
|
||||
if (_highlightRate.value().x != 0) {
|
||||
int dist = abs(static_cast<int>(i) - static_cast<int>(center.x));
|
||||
int rest = dist % _highlightRate.value().x;
|
||||
shouldHighlight = rest == 0;
|
||||
}
|
||||
|
||||
_varray[nr++] = { x0, y0, 0.f };
|
||||
_varray[nr++] = { x1, y0, 0.f };
|
||||
if (shouldHighlight) {
|
||||
_highlightArray.push_back({ x0, y0, 0.0 });
|
||||
_highlightArray.push_back({ x0, y1, 0.0 });
|
||||
}
|
||||
else {
|
||||
_varray.push_back({ x0, y0, 0.0 });
|
||||
_varray.push_back({ x0, y1, 0.0 });
|
||||
}
|
||||
|
||||
// Line in x direction
|
||||
shouldHighlight = false;
|
||||
if (_highlightRate.value().y != 0) {
|
||||
int dist = abs(static_cast<int>(j) - static_cast<int>(center.y));
|
||||
int rest = dist % _highlightRate.value().y;
|
||||
shouldHighlight = abs(rest) == 0;
|
||||
}
|
||||
|
||||
if (shouldHighlight) {
|
||||
_highlightArray.push_back({ x0, y0, 0.0 });
|
||||
_highlightArray.push_back({ x1, y0, 0.0 });
|
||||
}
|
||||
else {
|
||||
_varray.push_back({ x0, y0, 0.0 });
|
||||
_varray.push_back({ x1, y0, 0.0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last x row
|
||||
for (unsigned int i = 0; i < nSegments.x; ++i) {
|
||||
const float x0 = -halfSize.x + i * step.x;
|
||||
const float x1 = x0 + step.x;
|
||||
_varray[nr++] = { x0, halfSize.y, 0.f };
|
||||
_varray[nr++] = { x1, halfSize.y, 0.f };
|
||||
const double x0 = -halfSize.x + i * step.x;
|
||||
const double x1 = x0 + step.x;
|
||||
|
||||
bool shouldHighlight = false;
|
||||
if (_highlightRate.value().y != 0) {
|
||||
int dist = abs(static_cast<int>(nSegments.y) - static_cast<int>(center.y));
|
||||
int rest = dist % _highlightRate.value().y;
|
||||
shouldHighlight = abs(rest) == 0;
|
||||
}
|
||||
|
||||
if (shouldHighlight) {
|
||||
_highlightArray.push_back({ x0, halfSize.y, 0.0 });
|
||||
_highlightArray.push_back({ x1, halfSize.y, 0.0 });
|
||||
}
|
||||
else {
|
||||
_varray.push_back({ x0, halfSize.y, 0.0 });
|
||||
_varray.push_back({ x1, halfSize.y, 0.0 });
|
||||
}
|
||||
}
|
||||
|
||||
// last y col
|
||||
for (unsigned int i = 0; i < nSegments.y; ++i) {
|
||||
const float y0 = -halfSize.y + i * step.y;
|
||||
const float y1 = y0 + step.y;
|
||||
_varray[nr++] = { halfSize.x, y0, 0.f };
|
||||
_varray[nr++] = { halfSize.x, y1, 0.f };
|
||||
for (unsigned int j = 0; j < nSegments.y; ++j) {
|
||||
const double y0 = -halfSize.y + j * step.y;
|
||||
const double y1 = y0 + step.y;
|
||||
|
||||
bool shouldHighlight = false;
|
||||
if (_highlightRate.value().x != 0) {
|
||||
int dist = abs(static_cast<int>(nSegments.x) - static_cast<int>(center.x));
|
||||
int rest = dist % _highlightRate.value().x;
|
||||
shouldHighlight = abs(rest) == 0;
|
||||
}
|
||||
if (shouldHighlight) {
|
||||
_highlightArray.push_back({ halfSize.x, y0, 0.0 });
|
||||
_highlightArray.push_back({ halfSize.x, y1, 0.0 });
|
||||
}
|
||||
else {
|
||||
_varray.push_back({ halfSize.x, y0, 0.0 });
|
||||
_varray.push_back({ halfSize.x, y1, 0.0 });
|
||||
}
|
||||
}
|
||||
|
||||
setBoundingSphere(glm::length(glm::dvec2(halfSize)));
|
||||
|
||||
// Minor grid
|
||||
glBindVertexArray(_vaoID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vBufferID);
|
||||
glBufferData(
|
||||
@@ -251,8 +440,19 @@ void RenderableGrid::update(const UpdateData&) {
|
||||
_varray.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, sizeof(Vertex), nullptr);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
|
||||
// Major grid
|
||||
glBindVertexArray(_highlightVaoID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _highlightVBufferID);
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
_highlightArray.size() * sizeof(Vertex),
|
||||
_highlightArray.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
glVertexAttribPointer(0, 3, GL_DOUBLE, GL_FALSE, sizeof(Vertex), nullptr);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/vector/ivec2property.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
@@ -43,6 +44,7 @@ class RenderableGrid : public Renderable {
|
||||
public:
|
||||
RenderableGrid(const ghoul::Dictionary& dictionary);
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -55,25 +57,36 @@ public:
|
||||
|
||||
protected:
|
||||
struct Vertex {
|
||||
float location[3];
|
||||
double location[3];
|
||||
};
|
||||
|
||||
ghoul::opengl::ProgramObject* _gridProgram = nullptr;
|
||||
|
||||
properties::Vec3Property _color;
|
||||
properties::Vec3Property _highlightColor;
|
||||
// @TODO (abock, 2021-01-28) This was a UVec2Property before, but it wasn't supported
|
||||
// be the codegen. As soon as it does, this should be changed back
|
||||
properties::IVec2Property _segments;
|
||||
properties::IVec2Property _highlightRate;
|
||||
properties::FloatProperty _lineWidth;
|
||||
properties::FloatProperty _highlightLineWidth;
|
||||
properties::Vec2Property _size;
|
||||
|
||||
bool _gridIsDirty = true;
|
||||
|
||||
GLuint _vaoID = 0;
|
||||
GLuint _vBufferID = 0;
|
||||
GLuint _highlightVaoID = 0;
|
||||
GLuint _highlightVBufferID = 0;
|
||||
|
||||
GLenum _mode = GL_LINES;
|
||||
std::vector<Vertex> _varray;
|
||||
std::vector<Vertex> _highlightArray;
|
||||
|
||||
// Labels
|
||||
bool _hasLabels = false;
|
||||
properties::BoolProperty _drawLabels;
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
};
|
||||
|
||||
}// namespace openspace
|
||||
|
||||
@@ -71,6 +71,19 @@ namespace {
|
||||
"ring"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
|
||||
"DrawLabels",
|
||||
"Draw Labels",
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the grid"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableRadialGrid)]] Parameters {
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
@@ -86,6 +99,13 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(RadiiInfo.description)]]
|
||||
std::optional<glm::vec2> radii;
|
||||
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
};
|
||||
#include "renderableradialgrid_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -103,6 +123,7 @@ RenderableRadialGrid::RenderableRadialGrid(const ghoul::Dictionary& dictionary)
|
||||
, _circleSegments(CircleSegmentsInfo, 36, 4, 200)
|
||||
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
|
||||
, _radii(RadiiInfo, glm::vec2(0.f, 1.f), glm::vec2(0.f), glm::vec2(20.f))
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -134,10 +155,26 @@ RenderableRadialGrid::RenderableRadialGrid(const ghoul::Dictionary& dictionary)
|
||||
_radii.onChange([&]() { _gridIsDirty = true; });
|
||||
|
||||
addProperty(_radii);
|
||||
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableRadialGrid::isReady() const {
|
||||
return _gridProgram != nullptr;
|
||||
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
|
||||
}
|
||||
|
||||
void RenderableRadialGrid::initialize() {
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableRadialGrid::initializeGL() {
|
||||
@@ -171,14 +208,14 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
|
||||
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() *
|
||||
modelTransform;
|
||||
const glm::dmat4 modelViewTransform =
|
||||
data.camera.combinedViewMatrix() * modelTransform;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
|
||||
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
|
||||
|
||||
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
|
||||
_gridProgram->setUniform(
|
||||
"MVPTransform",
|
||||
glm::dmat4(data.camera.projectionMatrix()) * modelViewTransform
|
||||
);
|
||||
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
|
||||
_gridProgram->setUniform("opacity", opacity());
|
||||
_gridProgram->setUniform("gridColor", _color);
|
||||
|
||||
@@ -205,6 +242,31 @@ void RenderableRadialGrid::render(const RenderData& data, RendererTasks&) {
|
||||
global::renderEngine->openglStateCache().resetBlendState();
|
||||
global::renderEngine->openglStateCache().resetLineState();
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
|
||||
// Draw labels
|
||||
if (_drawLabels && _hasLabels) {
|
||||
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
|
||||
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
|
||||
glm::vec3 right = glm::cross(viewDirection, lookup);
|
||||
const glm::vec3 up = glm::cross(right, viewDirection);
|
||||
|
||||
const glm::dmat4 worldToModelTransform = glm::inverse(modelTransform);
|
||||
glm::vec3 orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
|
||||
if (orthoRight == glm::vec3(0.0)) {
|
||||
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
|
||||
right = glm::cross(viewDirection, otherVector);
|
||||
orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
}
|
||||
const glm::vec3 orthoUp = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
|
||||
);
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableRadialGrid::update(const UpdateData&) {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/scalar/intproperty.h>
|
||||
#include <openspace/properties/vector/ivec2property.h>
|
||||
@@ -46,6 +47,7 @@ public:
|
||||
RenderableRadialGrid(const ghoul::Dictionary& dictionary);
|
||||
~RenderableRadialGrid() override = default;
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -85,6 +87,11 @@ protected:
|
||||
|
||||
std::vector<GeometryData> _circles;
|
||||
GeometryData _lines{GL_LINES};
|
||||
|
||||
// Labels
|
||||
bool _hasLabels = false;
|
||||
properties::BoolProperty _drawLabels;
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
};
|
||||
|
||||
}// namespace openspace
|
||||
|
||||
@@ -55,6 +55,19 @@ namespace {
|
||||
"This value specifies the line width of the spherical grid"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
|
||||
"DrawLabels",
|
||||
"Draw Labels",
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the grid"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableSphericalGrid)]] Parameters {
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
@@ -64,6 +77,13 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(LineWidthInfo.description)]]
|
||||
std::optional<float> lineWidth;
|
||||
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
};
|
||||
#include "renderablesphericalgrid_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -80,6 +100,7 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio
|
||||
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _segments(SegmentsInfo, 36, 4, 200)
|
||||
, _lineWidth(LineWidthInfo, 0.5f, 1.f, 20.f)
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -104,12 +125,26 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio
|
||||
|
||||
// Radius is always 1
|
||||
setBoundingSphere(1.0);
|
||||
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableSphericalGrid::isReady() const {
|
||||
bool ready = true;
|
||||
ready &= (_gridProgram != nullptr);
|
||||
return ready;
|
||||
return _hasLabels ? _gridProgram && _labels->isReady() : _gridProgram != nullptr;
|
||||
}
|
||||
|
||||
void RenderableSphericalGrid::initialize() {
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSphericalGrid::initializeGL() {
|
||||
@@ -162,14 +197,14 @@ void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){
|
||||
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() *
|
||||
modelTransform;
|
||||
const glm::dmat4 modelViewTransform =
|
||||
data.camera.combinedViewMatrix() * modelTransform;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
|
||||
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewTransform;
|
||||
|
||||
_gridProgram->setUniform("modelViewTransform", modelViewTransform);
|
||||
_gridProgram->setUniform(
|
||||
"MVPTransform",
|
||||
glm::dmat4(data.camera.projectionMatrix()) * modelViewTransform
|
||||
);
|
||||
_gridProgram->setUniform("MVPTransform", modelViewProjectionMatrix);
|
||||
_gridProgram->setUniform("opacity", opacity());
|
||||
_gridProgram->setUniform("gridColor", _color);
|
||||
|
||||
@@ -195,6 +230,31 @@ void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){
|
||||
global::renderEngine->openglStateCache().resetBlendState();
|
||||
global::renderEngine->openglStateCache().resetLineState();
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
|
||||
// Draw labels
|
||||
if (_drawLabels && _hasLabels) {
|
||||
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
|
||||
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
|
||||
glm::vec3 right = glm::cross(viewDirection, lookup);
|
||||
const glm::vec3 up = glm::cross(right, viewDirection);
|
||||
|
||||
const glm::dmat4 worldToModelTransform = glm::inverse(modelTransform);
|
||||
glm::vec3 orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
|
||||
if (orthoRight == glm::vec3(0.0)) {
|
||||
glm::vec3 otherVector = glm::vec3(lookup.y, lookup.x, lookup.z);
|
||||
right = glm::cross(viewDirection, otherVector);
|
||||
orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.0))
|
||||
);
|
||||
}
|
||||
const glm::vec3 orthoUp = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.0))
|
||||
);
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSphericalGrid::update(const UpdateData&) {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/scalar/intproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
@@ -43,6 +44,7 @@ public:
|
||||
RenderableSphericalGrid(const ghoul::Dictionary& dictionary);
|
||||
~RenderableSphericalGrid() override = default;
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -75,6 +77,11 @@ protected:
|
||||
unsigned int _vsize = 0;
|
||||
std::vector<Vertex> _varray;
|
||||
std::vector<int> _iarray;
|
||||
|
||||
// Labels
|
||||
bool _hasLabels = false;
|
||||
properties::BoolProperty _drawLabels;
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
};
|
||||
|
||||
}// namespace openspace
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <modules/base/rendering/renderablelabels.h>
|
||||
#include <modules/base/rendering/renderablelabel.h>
|
||||
|
||||
#include <modules/base/basemodule.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
@@ -166,7 +166,7 @@ namespace {
|
||||
"Distance unit for fade-in/-out distance calculations. Defaults to \"au\""
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableLabels)]] Parameters {
|
||||
struct [[codegen::Dictionary(RenderableLabel)]] Parameters {
|
||||
enum class [[codegen::map(BlendMode)]] BlendMode {
|
||||
Normal,
|
||||
Additive
|
||||
@@ -228,16 +228,16 @@ namespace {
|
||||
// [[codegen::verbatim(FadeWidthsInfo.description)]]
|
||||
std::optional<glm::vec2> fadeWidths;
|
||||
};
|
||||
#include "renderablelabels_codegen.cpp"
|
||||
#include "renderablelabel_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation RenderableLabels::Documentation() {
|
||||
documentation::Documentation RenderableLabel::Documentation() {
|
||||
return codegen::doc<Parameters>("base_renderable_labels");
|
||||
}
|
||||
|
||||
RenderableLabels::RenderableLabels(const ghoul::Dictionary& dictionary)
|
||||
RenderableLabel::RenderableLabel(const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
, _blendMode(BlendModeInfo, properties::OptionProperty::DisplayType::Dropdown)
|
||||
, _color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
@@ -357,17 +357,17 @@ RenderableLabels::RenderableLabels(const ghoul::Dictionary& dictionary)
|
||||
addProperty(_fadeWidths);
|
||||
}
|
||||
|
||||
bool RenderableLabels::isReady() const {
|
||||
bool RenderableLabel::isReady() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderableLabels::initialize() {
|
||||
void RenderableLabel::initialize() {
|
||||
ZoneScoped
|
||||
|
||||
setRenderBin(Renderable::RenderBin::PreDeferredTransparent);
|
||||
}
|
||||
|
||||
void RenderableLabels::initializeGL() {
|
||||
void RenderableLabel::initializeGL() {
|
||||
if (_font == nullptr) {
|
||||
_font = global::fontManager->font(
|
||||
"Mono",
|
||||
@@ -378,9 +378,9 @@ void RenderableLabels::initializeGL() {
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableLabels::deinitializeGL() {}
|
||||
void RenderableLabel::deinitializeGL() {}
|
||||
|
||||
void RenderableLabels::render(const RenderData& data, RendererTasks&) {
|
||||
void RenderableLabel::render(const RenderData& data, RendererTasks&) {
|
||||
glDepthMask(true);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
|
||||
@@ -421,11 +421,11 @@ void RenderableLabels::render(const RenderData& data, RendererTasks&) {
|
||||
}
|
||||
|
||||
|
||||
void RenderableLabels::setLabelText(const std::string & newText) {
|
||||
void RenderableLabel::setLabelText(const std::string & newText) {
|
||||
_text = newText;
|
||||
}
|
||||
|
||||
void RenderableLabels::renderLabels(const RenderData& data,
|
||||
void RenderableLabel::renderLabels(const RenderData& data,
|
||||
const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::dvec3& orthoRight,
|
||||
const glm::dvec3& orthoUp, float fadeInVariable)
|
||||
@@ -463,7 +463,7 @@ void RenderableLabels::renderLabels(const RenderData& data,
|
||||
);
|
||||
}
|
||||
|
||||
float RenderableLabels::computeFadeFactor(float distanceNodeToCamera) const {
|
||||
float RenderableLabel::computeFadeFactor(float distanceNodeToCamera) const {
|
||||
float distanceUnit = unit(_fadeUnitOption);
|
||||
|
||||
float x = distanceNodeToCamera;
|
||||
@@ -487,7 +487,7 @@ float RenderableLabels::computeFadeFactor(float distanceNodeToCamera) const {
|
||||
}
|
||||
}
|
||||
|
||||
float RenderableLabels::unit(int unit) const {
|
||||
float RenderableLabel::unit(int unit) const {
|
||||
switch (static_cast<Unit>(unit)) {
|
||||
case Meter: return 1.f;
|
||||
case Kilometer: return 1e3f;
|
||||
@@ -505,7 +505,7 @@ float RenderableLabels::unit(int unit) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view RenderableLabels::toString(int unit) const {
|
||||
std::string_view RenderableLabel::toString(int unit) const {
|
||||
switch (static_cast<Unit>(unit)) {
|
||||
case Meter: return MeterUnit;
|
||||
case Kilometer: return KilometerUnit;
|
||||
@@ -22,8 +22,8 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_BASE___RENDERABLELABELS___H__
|
||||
#define __OPENSPACE_MODULE_BASE___RENDERABLELABELS___H__
|
||||
#ifndef __OPENSPACE_MODULE_BASE___RENDERABLELABEL___H__
|
||||
#define __OPENSPACE_MODULE_BASE___RENDERABLELABEL___H__
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
@@ -54,9 +54,9 @@ namespace documentation { struct Documentation; }
|
||||
|
||||
struct LinePoint;
|
||||
|
||||
class RenderableLabels : public Renderable {
|
||||
class RenderableLabel : public Renderable {
|
||||
public:
|
||||
RenderableLabels(const ghoul::Dictionary& dictionary);
|
||||
RenderableLabel(const ghoul::Dictionary& dictionary);
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
@@ -111,4 +111,4 @@ private:
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_BASE___RENDERABLELABELS___H__
|
||||
#endif // __OPENSPACE_MODULE_BASE___RENDERABLELABEL___H__
|
||||
@@ -37,11 +37,11 @@ void main() {
|
||||
dvec4 objPosDouble = dvec4(in_position, 1.0);
|
||||
dvec4 positionViewSpace = modelViewTransform * objPosDouble;
|
||||
dvec4 positionClipSpace = MVPTransform * objPosDouble;
|
||||
|
||||
|
||||
positionClipSpace.z = 0.0;
|
||||
|
||||
|
||||
vs_depthClipSpace = float(positionClipSpace.w);
|
||||
vs_positionViewSpace = vec4(positionViewSpace);
|
||||
|
||||
|
||||
gl_Position = vec4(positionClipSpace);
|
||||
}
|
||||
|
||||
@@ -48,4 +48,18 @@ create_new_module(
|
||||
${OPENSPACE_HEADER_FILES} ${OPENSPACE_SOURCE_FILES}
|
||||
)
|
||||
|
||||
target_precompile_headers(${cefwebgui_module} PRIVATE
|
||||
[["include/capi/cef_base_capi.h"]]
|
||||
[["include/cef_render_handler.h"]]
|
||||
<string>
|
||||
<sstream>
|
||||
<istream>
|
||||
<ostream>
|
||||
)
|
||||
if (WIN32)
|
||||
target_precompile_headers(${cefwebgui_module} PRIVATE
|
||||
<Windows.h>
|
||||
)
|
||||
endif ()
|
||||
|
||||
set_modules_dependency_on_cef_libraries(${cefwebgui_module})
|
||||
|
||||
@@ -32,10 +32,10 @@ GUIKeyboardHandler::GUIKeyboardHandler() {
|
||||
_keyConsumed = false;
|
||||
|
||||
global::callback::keyboard->emplace_back(
|
||||
[&](Key, KeyModifier, KeyAction) -> bool {
|
||||
[&](Key, KeyModifier, KeyAction, IsGuiWindow isGuiWindow) -> bool {
|
||||
const bool previous = _keyConsumed;
|
||||
_keyConsumed = false;
|
||||
return previous;
|
||||
return isGuiWindow ? previous : false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/font/fontmanager.h>
|
||||
#include <ghoul/font/fontrenderer.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/io/texture/texturereader.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
@@ -102,32 +100,6 @@ namespace {
|
||||
"The path to the color map file of the astronomical object"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextColorInfo = {
|
||||
"TextColor",
|
||||
"Text Color",
|
||||
"The text color for the astronomical object"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextOpacityInfo = {
|
||||
"TextOpacity",
|
||||
"Text Opacity",
|
||||
"Determines the transparency of the text label, where 1 is completely opaque "
|
||||
"and 0 fully transparent"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextSizeInfo = {
|
||||
"TextSize",
|
||||
"Text Size",
|
||||
"The text size for the astronomical object labels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LabelMinMaxSizeInfo = {
|
||||
"TextMinMaxSize",
|
||||
"Text Min/Max Size",
|
||||
"The minimal and maximal size (in pixels) of the text for the labels for the "
|
||||
"astronomical objects being rendered"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawElementsInfo = {
|
||||
"DrawElements",
|
||||
"Draw Elements",
|
||||
@@ -140,6 +112,13 @@ namespace {
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the astronomical objects"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorOptionInfo = {
|
||||
"ColorOption",
|
||||
"Color Option",
|
||||
@@ -163,7 +142,8 @@ namespace {
|
||||
constexpr openspace::properties::Property::PropertyInfo RenderOptionInfo = {
|
||||
"RenderOption",
|
||||
"Render Option",
|
||||
"Debug option for rendering of billboards and texts"
|
||||
"Option wether the billboards should face the camera or not. Used for "
|
||||
"non-linear display envierments such as fisheye."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FadeInDistancesInfo = {
|
||||
@@ -274,21 +254,9 @@ namespace {
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(TextColorInfo.description)]]
|
||||
std::optional<glm::vec3> textColor [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(TextOpacityInfo.description)]]
|
||||
std::optional<float> textOpacity;
|
||||
|
||||
// [[codegen::verbatim(TextSizeInfo.description)]]
|
||||
std::optional<float> textSize;
|
||||
|
||||
// The path to the label file that contains information about the astronomical
|
||||
// objects being rendered
|
||||
std::optional<std::string> labelFile;
|
||||
|
||||
// [[codegen::verbatim(LabelMinMaxSizeInfo.description)]]
|
||||
std::optional<glm::ivec2> textMinMaxSize;
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
|
||||
// [[codegen::verbatim(ColorOptionInfo.description)]]
|
||||
std::optional<std::vector<std::string>> colorOption;
|
||||
@@ -339,15 +307,6 @@ RenderableBillboardsCloud::RenderableBillboardsCloud(const ghoul::Dictionary& di
|
||||
, _useColorMap(UseColorMapInfo, true)
|
||||
, _pointColor(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _spriteTexturePath(SpriteTextureInfo)
|
||||
, _textColor(TextColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _textOpacity(TextOpacityInfo, 1.f, 0.f, 1.f)
|
||||
, _textSize(TextSizeInfo, 8.f, 0.5f, 24.f)
|
||||
, _textMinMaxSize(
|
||||
LabelMinMaxSizeInfo,
|
||||
glm::ivec2(8, 20),
|
||||
glm::ivec2(0),
|
||||
glm::ivec2(100)
|
||||
)
|
||||
, _drawElements(DrawElementsInfo, true)
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
, _pixelSizeControl(PixelSizeControlInfo, false)
|
||||
@@ -481,28 +440,13 @@ RenderableBillboardsCloud::RenderableBillboardsCloud(const ghoul::Dictionary& di
|
||||
_polygonSides = p.polygonSides.value_or(_polygonSides);
|
||||
_hasPolygon = p.polygonSides.has_value();
|
||||
|
||||
if (p.labelFile.has_value()) {
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labelFile = absPath(*p.labelFile).string();
|
||||
_hasLabel = true;
|
||||
|
||||
_textColor = p.textColor.value_or(_textColor);
|
||||
_hasLabel = p.textColor.has_value();
|
||||
_textColor.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_textColor);
|
||||
_textColor.onChange([&]() { _textColorIsDirty = true; });
|
||||
|
||||
_textOpacity = p.textOpacity.value_or(_textOpacity);
|
||||
addProperty(_textOpacity);
|
||||
|
||||
_textSize = p.textSize.value_or(_textSize);
|
||||
addProperty(_textSize);
|
||||
|
||||
_textMinMaxSize = p.textMinMaxSize.value_or(_textMinMaxSize);
|
||||
_textMinMaxSize.setViewOption(properties::Property::ViewOptions::MinMaxRange);
|
||||
addProperty(_textMinMaxSize);
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
|
||||
_transformationMatrix = p.transformationMatrix.value_or(_transformationMatrix);
|
||||
@@ -557,7 +501,13 @@ RenderableBillboardsCloud::RenderableBillboardsCloud(const ghoul::Dictionary& di
|
||||
}
|
||||
|
||||
bool RenderableBillboardsCloud::isReady() const {
|
||||
return (_program && (!_dataset.entries.empty())) || (!_labelset.entries.empty());
|
||||
bool isReady = _program && !_dataset.entries.empty();
|
||||
|
||||
// If we have labels, they also need to be loaded
|
||||
if (_hasLabels) {
|
||||
isReady = isReady || _labels->isReady();
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
void RenderableBillboardsCloud::initialize() {
|
||||
@@ -571,11 +521,9 @@ void RenderableBillboardsCloud::initialize() {
|
||||
_colorMap = speck::color::loadFileWithCache(_colorMapFile);
|
||||
}
|
||||
|
||||
if (!_labelFile.empty()) {
|
||||
_labelset = speck::label::loadFileWithCache(_labelFile);
|
||||
for (speck::Labelset::Entry& e : _labelset.entries) {
|
||||
e.position = glm::vec3(_transformationMatrix * glm::dvec4(e.position, 1.0));
|
||||
}
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
|
||||
if (!_colorOptionString.empty() && (_colorRangeData.size() > 1)) {
|
||||
@@ -619,18 +567,6 @@ void RenderableBillboardsCloud::initializeGL() {
|
||||
if (_hasPolygon) {
|
||||
createPolygonTexture();
|
||||
}
|
||||
|
||||
if (_hasLabel) {
|
||||
if (!_font) {
|
||||
size_t _fontSize = 50;
|
||||
_font = global::fontManager->font(
|
||||
"Mono",
|
||||
static_cast<float>(_fontSize),
|
||||
ghoul::fontrendering::FontManager::Outline::Yes,
|
||||
ghoul::fontrendering::FontManager::LoadGlyphs::No
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableBillboardsCloud::deinitializeGL() {
|
||||
@@ -739,40 +675,6 @@ void RenderableBillboardsCloud::renderBillboards(const RenderData& data,
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
}
|
||||
|
||||
void RenderableBillboardsCloud::renderLabels(const RenderData& data,
|
||||
const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::dvec3& orthoRight,
|
||||
const glm::dvec3& orthoUp,
|
||||
float fadeInVariable)
|
||||
{
|
||||
glm::vec4 textColor = glm::vec4(glm::vec3(_textColor), _textOpacity * fadeInVariable);
|
||||
|
||||
ghoul::fontrendering::FontRenderer::ProjectedLabelsInformation labelInfo;
|
||||
labelInfo.orthoRight = orthoRight;
|
||||
labelInfo.orthoUp = orthoUp;
|
||||
labelInfo.minSize = _textMinMaxSize.value().x;
|
||||
labelInfo.maxSize = _textMinMaxSize.value().y;
|
||||
labelInfo.cameraPos = data.camera.positionVec3();
|
||||
labelInfo.cameraLookUp = data.camera.lookUpVectorWorldSpace();
|
||||
labelInfo.renderType = _renderOption;
|
||||
labelInfo.mvpMatrix = modelViewProjectionMatrix;
|
||||
labelInfo.scale = pow(10.f, _textSize);
|
||||
labelInfo.enableDepth = true;
|
||||
labelInfo.enableFalseDepth = false;
|
||||
|
||||
for (const speck::Labelset::Entry& e : _labelset.entries) {
|
||||
glm::vec3 scaledPos(e.position);
|
||||
scaledPos *= toMeter(_unit);
|
||||
ghoul::fontrendering::FontRenderer::defaultProjectionRenderer().render(
|
||||
*_font,
|
||||
scaledPos,
|
||||
e.text,
|
||||
textColor,
|
||||
labelInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableBillboardsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
float fadeInVar = 1.f;
|
||||
if (!_disableFadeInDistance) {
|
||||
@@ -806,7 +708,7 @@ void RenderableBillboardsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
glm::cross(cameraUpDirectionWorld, cameraViewDirectionWorld)
|
||||
);
|
||||
if (orthoRight == glm::dvec3(0.0)) {
|
||||
glm::dvec3 otherVector(
|
||||
glm::dvec3 otherVector = glm::vec3(
|
||||
cameraUpDirectionWorld.y,
|
||||
cameraUpDirectionWorld.x,
|
||||
cameraUpDirectionWorld.z
|
||||
@@ -819,8 +721,8 @@ void RenderableBillboardsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
renderBillboards(data, modelMatrix, orthoRight, orthoUp, fadeInVar);
|
||||
}
|
||||
|
||||
if (_drawLabels && _hasLabel) {
|
||||
renderLabels(data, modelViewProjectionMatrix, orthoRight, orthoUp, fadeInVar);
|
||||
if (_drawLabels && _hasLabels) {
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp, fadeInVar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/optionproperty.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/triggerproperty.h>
|
||||
@@ -43,7 +43,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ghoul::filesystem { class File; }
|
||||
namespace ghoul::fontrendering { class Font; }
|
||||
namespace ghoul::opengl {
|
||||
class ProgramObject;
|
||||
class Texture;
|
||||
@@ -79,19 +78,16 @@ private:
|
||||
void renderPolygonGeometry(GLuint vao);
|
||||
void renderBillboards(const RenderData& data, const glm::dmat4& modelMatrix,
|
||||
const glm::dvec3& orthoRight, const glm::dvec3& orthoUp, float fadeInVariable);
|
||||
void renderLabels(const RenderData& data, const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::dvec3& orthoRight, const glm::dvec3& orthoUp, float fadeInVariable);
|
||||
|
||||
bool _hasSpeckFile = false;
|
||||
bool _dataIsDirty = true;
|
||||
bool _textColorIsDirty = true;
|
||||
bool _hasSpriteTexture = false;
|
||||
bool _spriteTextureIsDirty = true;
|
||||
bool _hasColorMapFile = false;
|
||||
bool _isColorMapExact = false;
|
||||
bool _hasDatavarSize = false;
|
||||
bool _hasPolygon = false;
|
||||
bool _hasLabel = false;
|
||||
bool _hasLabels = false;
|
||||
|
||||
int _polygonSides = 0;
|
||||
|
||||
@@ -101,10 +97,6 @@ private:
|
||||
properties::BoolProperty _useColorMap;
|
||||
properties::Vec3Property _pointColor;
|
||||
properties::StringProperty _spriteTexturePath;
|
||||
properties::Vec3Property _textColor;
|
||||
properties::FloatProperty _textOpacity;
|
||||
properties::FloatProperty _textSize;
|
||||
properties::IVec2Property _textMinMaxSize;
|
||||
properties::BoolProperty _drawElements;
|
||||
properties::BoolProperty _drawLabels;
|
||||
properties::BoolProperty _pixelSizeControl;
|
||||
@@ -133,20 +125,19 @@ private:
|
||||
hasDvarScaling
|
||||
) _uniformCache;
|
||||
|
||||
std::shared_ptr<ghoul::fontrendering::Font> _font;
|
||||
|
||||
std::string _speckFile;
|
||||
std::string _colorMapFile;
|
||||
std::string _labelFile;
|
||||
std::string _colorOptionString;
|
||||
std::string _datavarSizeOptionString;
|
||||
|
||||
DistanceUnit _unit = DistanceUnit::Parsec;
|
||||
|
||||
speck::Dataset _dataset;
|
||||
speck::Labelset _labelset;
|
||||
speck::ColorMap _colorMap;
|
||||
|
||||
// Everything related to the labels is handled by LabelsComponent
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
|
||||
std::vector<glm::vec2> _colorRangeData;
|
||||
std::unordered_map<int, std::string> _optionConversionMap;
|
||||
std::unordered_map<int, std::string> _optionConversionSizeMap;
|
||||
|
||||
@@ -255,6 +255,13 @@ bool RenderableDUMeshes::isReady() const {
|
||||
(!_renderingMeshesMap.empty() || (!_labelset.entries.empty()));
|
||||
}
|
||||
|
||||
void RenderableDUMeshes::initialize() {
|
||||
bool success = loadData();
|
||||
if (!success) {
|
||||
throw ghoul::RuntimeError("Error loading data");
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableDUMeshes::initializeGL() {
|
||||
_program = DigitalUniverseModule::ProgramObjectManager.request(
|
||||
"RenderableDUMeshes",
|
||||
@@ -269,11 +276,6 @@ void RenderableDUMeshes::initializeGL() {
|
||||
|
||||
ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames);
|
||||
|
||||
bool success = loadData();
|
||||
if (!success) {
|
||||
throw ghoul::RuntimeError("Error loading data");
|
||||
}
|
||||
|
||||
createMeshes();
|
||||
|
||||
if (_hasLabel) {
|
||||
@@ -536,8 +538,7 @@ bool RenderableDUMeshes::readSpeckFile() {
|
||||
|
||||
std::getline(file, line);
|
||||
std::stringstream dim(line);
|
||||
dim >> mesh.numU; // numU
|
||||
dim >> mesh.numV; // numV
|
||||
dim >> mesh.numU >> mesh.numV;
|
||||
|
||||
// We can now read the vertices data:
|
||||
for (int l = 0; l < mesh.numU * mesh.numV; ++l) {
|
||||
@@ -602,7 +603,6 @@ bool RenderableDUMeshes::readSpeckFile() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBoundingSphere(maxRadius);
|
||||
|
||||
return true;
|
||||
@@ -636,7 +636,7 @@ void RenderableDUMeshes::createMeshes() {
|
||||
// in_position
|
||||
glEnableVertexAttribArray(0);
|
||||
// (2022-03-23, emmbr) This code was actually never used. We only read three
|
||||
// values per line and di not handle any texture cooridnates, even if there
|
||||
// values per line and did not handle any texture cooridnates, even if there
|
||||
// would have been some in the file
|
||||
//// U and V may not be given by the user
|
||||
//if (p.second.vertices.size() / (p.second.numU * p.second.numV) > 3) {
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
explicit RenderableDUMeshes(const ghoul::Dictionary& dictionary);
|
||||
~RenderableDUMeshes() override = default;
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -84,7 +85,7 @@ private:
|
||||
// "If you wish to draw a line between points, then numU will be 1 while
|
||||
// numV will equal the number of points to connect.
|
||||
// If you want a square, 4000×4000 grid with lines every 200 units,
|
||||
// then numU numU will both equal 21
|
||||
// then numU numV will both equal 21
|
||||
int numU;
|
||||
int numV;
|
||||
MeshType style;
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/font/fontmanager.h>
|
||||
#include <ghoul/font/fontrenderer.h>
|
||||
#include <ghoul/io/texture/texturereader.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/profiling.h>
|
||||
@@ -67,44 +65,11 @@ namespace {
|
||||
"size of each point"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextColorInfo = {
|
||||
"TextColor",
|
||||
"Text Color",
|
||||
"The text color for the astronomical object"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextOpacityInfo = {
|
||||
"TextOpacity",
|
||||
"Text Opacity",
|
||||
"Determines the transparency of the text label, where 1 is completely opaque "
|
||||
"and 0 fully transparent"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextSizeInfo = {
|
||||
"TextSize",
|
||||
"Text Size",
|
||||
"The text size for the astronomical object labels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LabelFileInfo = {
|
||||
"LabelFile",
|
||||
"Label File",
|
||||
"The path to the label file that contains information about the astronomical "
|
||||
"objects being rendered"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LabelMinSizeInfo = {
|
||||
"TextMinSize",
|
||||
"Text Min Size",
|
||||
"The minimal size (in pixels) of the text for the labels for the astronomical "
|
||||
"objects being rendered"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LabelMaxSizeInfo = {
|
||||
"TextMaxSize",
|
||||
"Text Max Size",
|
||||
"The maximum size (in pixels) of the text for the labels for the astronomical "
|
||||
"objects being rendered"
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the astronomical objects"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawElementsInfo = {
|
||||
@@ -176,23 +141,9 @@ namespace {
|
||||
// [[codegen::verbatim(ScaleFactorInfo.description)]]
|
||||
std::optional<float> scaleFactor;
|
||||
|
||||
// [[codegen::verbatim(TextColorInfo.description)]]
|
||||
std::optional<glm::vec3> textColor [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(TextOpacityInfo.description)]]
|
||||
std::optional<float> textOpacity;
|
||||
|
||||
// [[codegen::verbatim(TextSizeInfo.description)]]
|
||||
std::optional<float> textSize;
|
||||
|
||||
// [[codegen::verbatim(LabelFileInfo.description)]]
|
||||
std::optional<std::string> labelFile;
|
||||
|
||||
// [[codegen::verbatim(LabelMinSizeInfo.description)]]
|
||||
std::optional<int> textMinSize;
|
||||
|
||||
// [[codegen::verbatim(LabelMaxSizeInfo.description)]]
|
||||
std::optional<int> textMaxSize;
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
|
||||
// [[codegen::verbatim(TransformationMatrixInfo.description)]]
|
||||
std::optional<glm::dmat4x4> transformationMatrix;
|
||||
@@ -246,9 +197,6 @@ documentation::Documentation RenderablePlanesCloud::Documentation() {
|
||||
RenderablePlanesCloud::RenderablePlanesCloud(const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
, _scaleFactor(ScaleFactorInfo, 1.f, 0.f, 300000.f)
|
||||
, _textColor(TextColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _textOpacity(TextOpacityInfo, 1.f, 0.f, 1.f)
|
||||
, _textSize(TextSizeInfo, 8.0, 0.5, 24.0)
|
||||
, _drawElements(DrawElementsInfo, true)
|
||||
, _blendMode(BlendModeInfo, properties::OptionProperty::DisplayType::Dropdown)
|
||||
, _fadeInDistances(
|
||||
@@ -290,23 +238,10 @@ RenderablePlanesCloud::RenderablePlanesCloud(const ghoul::Dictionary& dictionary
|
||||
addProperty(_scaleFactor);
|
||||
_scaleFactor.onChange([&]() { _dataIsDirty = true; });
|
||||
|
||||
if (p.labelFile.has_value()) {
|
||||
_labelFile = absPath(*p.labelFile);
|
||||
_hasLabel = true;
|
||||
|
||||
_textColor = p.textColor.value_or(_textColor);
|
||||
_textColor.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_textColor);
|
||||
_textColor.onChange([&]() { _textColorIsDirty = true; });
|
||||
|
||||
_textOpacity = p.textOpacity.value_or(_textOpacity);
|
||||
addProperty(_textOpacity);
|
||||
|
||||
_textSize = p.textSize.value_or(_textSize);
|
||||
addProperty(_textSize);
|
||||
|
||||
_textMinSize = p.textMinSize.value_or(_textMinSize);
|
||||
_textMaxSize = p.textMaxSize.value_or(_textMaxSize);
|
||||
if (p.labels.has_value()) {
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
|
||||
_transformationMatrix = p.transformationMatrix.value_or(_transformationMatrix);
|
||||
@@ -360,7 +295,13 @@ RenderablePlanesCloud::RenderablePlanesCloud(const ghoul::Dictionary& dictionary
|
||||
}
|
||||
|
||||
bool RenderablePlanesCloud::isReady() const {
|
||||
return (_program && (!_dataset.entries.empty())) || (!_labelset.entries.empty());
|
||||
bool isReady = _program && !_dataset.entries.empty();
|
||||
|
||||
// If we have labels, they also need to be loaded
|
||||
if (_hasLabels) {
|
||||
isReady = isReady || _labels->isReady();
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
void RenderablePlanesCloud::initialize() {
|
||||
@@ -373,12 +314,9 @@ void RenderablePlanesCloud::initialize() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!_labelFile.empty()) {
|
||||
LINFO(fmt::format("Loading Label file {}", _labelFile));
|
||||
_labelset = speck::label::loadFileWithCache(_labelFile);
|
||||
for (speck::Labelset::Entry& e : _labelset.entries) {
|
||||
e.position = glm::vec3(_transformationMatrix * glm::dvec4(e.position, 1.0));
|
||||
}
|
||||
if (_hasLabels) {
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,18 +338,6 @@ void RenderablePlanesCloud::initializeGL() {
|
||||
|
||||
createPlanes();
|
||||
loadTextures();
|
||||
|
||||
if (_hasLabel) {
|
||||
if (!_font) {
|
||||
constexpr int FontSize = 30;
|
||||
_font = global::fontManager->font(
|
||||
"Mono",
|
||||
static_cast<float>(FontSize),
|
||||
ghoul::fontrendering::FontManager::Outline::Yes,
|
||||
ghoul::fontrendering::FontManager::LoadGlyphs::No
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePlanesCloud::deleteDataGPUAndCPU() {
|
||||
@@ -488,39 +414,6 @@ void RenderablePlanesCloud::renderPlanes(const RenderData&,
|
||||
global::renderEngine->openglStateCache().resetPolygonAndClippingState();
|
||||
}
|
||||
|
||||
void RenderablePlanesCloud::renderLabels(const RenderData& data,
|
||||
const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::dvec3& orthoRight,
|
||||
const glm::dvec3& orthoUp, float fadeInVariable)
|
||||
{
|
||||
double scale = toMeter(_unit);
|
||||
glm::vec4 textColor = glm::vec4(glm::vec3(_textColor), _textOpacity * fadeInVariable);
|
||||
|
||||
ghoul::fontrendering::FontRenderer::ProjectedLabelsInformation labelInfo;
|
||||
labelInfo.orthoRight = orthoRight;
|
||||
labelInfo.orthoUp = orthoUp;
|
||||
labelInfo.minSize = _textMinSize;
|
||||
labelInfo.maxSize = _textMaxSize;
|
||||
labelInfo.cameraPos = data.camera.positionVec3();
|
||||
labelInfo.cameraLookUp = data.camera.lookUpVectorWorldSpace();
|
||||
labelInfo.renderType = _renderOption;
|
||||
labelInfo.mvpMatrix = modelViewProjectionMatrix;
|
||||
labelInfo.scale = pow(10.f, _textSize);
|
||||
labelInfo.enableDepth = true;
|
||||
labelInfo.enableFalseDepth = false;
|
||||
|
||||
for (const speck::Labelset::Entry& e : _labelset.entries) {
|
||||
glm::dvec3 scaledPos = glm::dvec3(e.position) * scale;
|
||||
ghoul::fontrendering::FontRenderer::defaultProjectionRenderer().render(
|
||||
*_font,
|
||||
scaledPos,
|
||||
e.text,
|
||||
textColor,
|
||||
labelInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePlanesCloud::render(const RenderData& data, RendererTasks&) {
|
||||
const double scale = toMeter(_unit);
|
||||
|
||||
@@ -547,24 +440,25 @@ void RenderablePlanesCloud::render(const RenderData& data, RendererTasks&) {
|
||||
|
||||
const glm::dmat4 modelViewMatrix = data.camera.combinedViewMatrix() * modelMatrix;
|
||||
const glm::mat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
const glm::dmat4 mvpMatrix = glm::dmat4(projectionMatrix) * modelViewMatrix;
|
||||
|
||||
const glm::dmat4 invMVPParts = glm::inverse(modelMatrix) *
|
||||
glm::inverse(data.camera.combinedViewMatrix()) *
|
||||
glm::inverse(glm::dmat4(projectionMatrix));
|
||||
const glm::dvec3 orthoRight = glm::normalize(
|
||||
glm::dvec3(invMVPParts * glm::dvec4(1.0, 0.0, 0.0, 0.0))
|
||||
);
|
||||
const glm::dvec3 orthoUp = glm::normalize(
|
||||
glm::dvec3(invMVPParts * glm::dvec4(0.0, 1.0, 0.0, 0.0))
|
||||
);
|
||||
|
||||
if (_hasSpeckFile) {
|
||||
renderPlanes(data, modelViewMatrix, projectionMatrix, fadeInVariable);
|
||||
}
|
||||
|
||||
if (_hasLabel) {
|
||||
renderLabels(data, mvpMatrix, orthoRight, orthoUp, fadeInVariable);
|
||||
if (_hasLabels) {
|
||||
const glm::dmat4 mvpMatrix = glm::dmat4(projectionMatrix) * modelViewMatrix;
|
||||
|
||||
const glm::dmat4 invMVPParts = glm::inverse(modelMatrix) *
|
||||
glm::inverse(data.camera.combinedViewMatrix()) *
|
||||
glm::inverse(glm::dmat4(projectionMatrix));
|
||||
const glm::dvec3 orthoRight = glm::normalize(
|
||||
glm::dvec3(invMVPParts * glm::dvec4(1.0, 0.0, 0.0, 0.0))
|
||||
);
|
||||
const glm::dvec3 orthoUp = glm::normalize(
|
||||
glm::dvec3(invMVPParts * glm::dvec4(0.0, 1.0, 0.0, 0.0))
|
||||
);
|
||||
|
||||
_labels->render(data, mvpMatrix, orthoRight, orthoUp, fadeInVariable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,10 +647,6 @@ void RenderablePlanesCloud::createPlanes() {
|
||||
setBoundingSphere(maxRadius * _scaleFactor);
|
||||
_fadeInDistances.setMaxValue(glm::vec2(10.f * maxSize));
|
||||
}
|
||||
|
||||
if (_hasLabel && _labelDataIsDirty) {
|
||||
_labelDataIsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/optionproperty.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
@@ -42,7 +42,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ghoul::filesystem { class File; }
|
||||
namespace ghoul::fontrendering { class Font; }
|
||||
namespace ghoul::opengl {
|
||||
class ProgramObject;
|
||||
class Texture;
|
||||
@@ -83,25 +82,15 @@ private:
|
||||
void createPlanes();
|
||||
void renderPlanes(const RenderData& data, const glm::dmat4& modelViewMatrix,
|
||||
const glm::dmat4& projectionMatrix, float fadeInVariable);
|
||||
void renderLabels(const RenderData& data,
|
||||
const glm::dmat4& modelViewProjectionMatrix, const glm::dvec3& orthoRight,
|
||||
const glm::dvec3& orthoUp, float fadeInVariable);
|
||||
|
||||
void loadTextures();
|
||||
|
||||
bool _hasSpeckFile = false;
|
||||
bool _dataIsDirty = true;
|
||||
bool _textColorIsDirty = true;
|
||||
bool _hasLabel = false;
|
||||
bool _labelDataIsDirty = true;
|
||||
|
||||
int _textMinSize = 0;
|
||||
int _textMaxSize = 200;
|
||||
bool _hasLabels = false;
|
||||
|
||||
properties::FloatProperty _scaleFactor;
|
||||
properties::Vec3Property _textColor;
|
||||
properties::FloatProperty _textOpacity;
|
||||
properties::FloatProperty _textSize;
|
||||
properties::BoolProperty _drawElements;
|
||||
properties::OptionProperty _blendMode;
|
||||
properties::Vec2Property _fadeInDistances;
|
||||
@@ -113,20 +102,20 @@ private:
|
||||
UniformCache(
|
||||
modelViewProjectionTransform, alphaValue, fadeInValue, galaxyTexture
|
||||
) _uniformCache;
|
||||
std::shared_ptr<ghoul::fontrendering::Font> _font = nullptr;
|
||||
std::unordered_map<int, std::unique_ptr<ghoul::opengl::Texture>> _textureMap;
|
||||
std::unordered_map<int, std::string> _textureFileMap;
|
||||
std::unordered_map<int, PlaneAggregate> _planesMap;
|
||||
|
||||
std::filesystem::path _speckFile;
|
||||
std::filesystem::path _labelFile;
|
||||
std::filesystem::path _texturesPath;
|
||||
std::string _luminosityVar;
|
||||
|
||||
DistanceUnit _unit = DistanceUnit::Parsec;
|
||||
|
||||
speck::Dataset _dataset;
|
||||
speck::Labelset _labelset;
|
||||
|
||||
// Everything related to the labels is handled by LabelsComponent
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
|
||||
float _sluminosity = 1.f;
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view _loggerCat = "ExoplanetsModule";
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
constexpr std::array<const char*, 6> UniformNames = {
|
||||
constexpr std::array<const char*, 7> UniformNames = {
|
||||
"modelViewProjectionTransform", "offset", "opacity",
|
||||
"discTexture", "eccentricity", "semiMajorAxis"
|
||||
"discTexture", "eccentricity", "semiMajorAxis", "multiplyColor"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextureInfo = {
|
||||
@@ -74,6 +74,13 @@ namespace {
|
||||
"from the semi-major axis and 1 is a whole semi-major axis's worth of deviation"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo MultiplyColorInfo = {
|
||||
"MultiplyColor",
|
||||
"Multiply Color",
|
||||
"If set, the disc's texture is multiplied with this color. "
|
||||
"Useful for applying a color grayscale images"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableOrbitDisc)]] Parameters {
|
||||
// [[codegen::verbatim(TextureInfo.description)]]
|
||||
std::filesystem::path texture;
|
||||
@@ -86,6 +93,9 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(OffsetInfo.description)]]
|
||||
std::optional<glm::vec2> offset;
|
||||
|
||||
// [[codegen::verbatim(MultiplyColorInfo.description)]]
|
||||
std::optional<glm::vec3> multiplyColor [[codegen::color()]];
|
||||
};
|
||||
#include "renderableorbitdisc_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -102,6 +112,7 @@ RenderableOrbitDisc::RenderableOrbitDisc(const ghoul::Dictionary& dictionary)
|
||||
, _size(SizeInfo, 1.f, 0.f, 3.0e12f)
|
||||
, _eccentricity(EccentricityInfo, 0.f, 0.f, 1.f)
|
||||
, _offset(OffsetInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(1.f))
|
||||
, _multiplyColor(MultiplyColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -119,6 +130,10 @@ RenderableOrbitDisc::RenderableOrbitDisc(const ghoul::Dictionary& dictionary)
|
||||
_texturePath.onChange([&]() { _texture->loadFromFile(_texturePath.value()); });
|
||||
addProperty(_texturePath);
|
||||
|
||||
_multiplyColor = p.multiplyColor.value_or(_multiplyColor);
|
||||
_multiplyColor.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_multiplyColor);
|
||||
|
||||
_eccentricity = p.eccentricity;
|
||||
_eccentricity.onChange([&]() { _planeIsDirty = true; });
|
||||
addProperty(_eccentricity);
|
||||
@@ -179,6 +194,7 @@ void RenderableOrbitDisc::render(const RenderData& data, RendererTasks&) {
|
||||
_shader->setUniform(_uniformCache.opacity, opacity());
|
||||
_shader->setUniform(_uniformCache.eccentricity, _eccentricity);
|
||||
_shader->setUniform(_uniformCache.semiMajorAxis, _size);
|
||||
_shader->setUniform(_uniformCache.multiplyColor, _multiplyColor);
|
||||
|
||||
ghoul::opengl::TextureUnit unit;
|
||||
unit.activate();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
#include <openspace/rendering/texturecomponent.h>
|
||||
#include <openspace/util/planegeometry.h>
|
||||
@@ -64,10 +65,11 @@ private:
|
||||
properties::FloatProperty _size;
|
||||
properties::FloatProperty _eccentricity;
|
||||
properties::Vec2Property _offset;
|
||||
properties::Vec3Property _multiplyColor;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _shader = nullptr;
|
||||
UniformCache(modelViewProjection, offset, opacity, texture,
|
||||
eccentricity, semiMajorAxis) _uniformCache;
|
||||
eccentricity, semiMajorAxis, multiplyColor) _uniformCache;
|
||||
|
||||
std::unique_ptr<PlaneGeometry> _plane;
|
||||
std::unique_ptr<TextureComponent> _texture;
|
||||
|
||||
@@ -32,6 +32,7 @@ uniform vec2 offset; // relative to semi major axis
|
||||
uniform float opacity;
|
||||
uniform float eccentricity;
|
||||
uniform float semiMajorAxis;
|
||||
uniform vec3 multiplyColor = vec3(1.0);
|
||||
|
||||
const float Epsilon = 0.0000001;
|
||||
|
||||
@@ -122,6 +123,7 @@ Fragment getFragment() {
|
||||
|
||||
vec4 diffuse = texture(discTexture, textureCoord);
|
||||
diffuse.a *= opacity;
|
||||
diffuse.rgb *= multiplyColor;
|
||||
|
||||
Fragment frag;
|
||||
frag.color = diffuse;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#include <ccmc/Kameleon.h>
|
||||
#include <ccmc/KameleonInterpolator.h>
|
||||
#include <ccmc/Tracer.h>
|
||||
#include <modules/kameleon/include/kameleonhelper.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
@@ -64,3 +64,8 @@ disable_external_warnings(CCfits)
|
||||
|
||||
target_include_directories(openspace-module-fitsfilereader SYSTEM PRIVATE ${INCLUDES_FOR_TARGET})
|
||||
target_link_libraries(openspace-module-fitsfilereader PRIVATE cfitsio CCfits)
|
||||
|
||||
target_precompile_headers(CCfits PRIVATE
|
||||
<istream>
|
||||
<ostream>
|
||||
)
|
||||
|
||||
Submodule modules/fitsfilereader/ext/CCfits updated: 27f655a80f...3c16ca3f1e
Submodule modules/fitsfilereader/ext/cfitsio updated: e8fc91cc4b...8d98f1495b
@@ -139,13 +139,21 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
target_precompile_headers(${globebrowsing_module} PRIVATE
|
||||
<modules/globebrowsing/src/tileprovider/tileprovider.h>
|
||||
<modules/globebrowsing/globebrowsingmodule.h>
|
||||
<future>
|
||||
<map>
|
||||
)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gdal_data DESTINATION modules/globebrowsing)
|
||||
|
||||
if (WIN32)
|
||||
target_include_directories(openspace-module-globebrowsing SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/include)
|
||||
target_link_libraries(openspace-module-globebrowsing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/lib/gdal_i.lib)
|
||||
register_external_libraries("${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/lib/gdal241.dll")
|
||||
add_library(gdal SHARED IMPORTED)
|
||||
target_include_directories(gdal SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/include)
|
||||
set_target_properties(gdal PROPERTIES IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/lib/gdal_i.lib)
|
||||
set_target_properties(gdal PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ext/gdal/lib/gdal241.dll)
|
||||
target_link_libraries(openspace-module-globebrowsing PRIVATE gdal)
|
||||
else (WIN32)
|
||||
find_package(GDAL REQUIRED)
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ uniform vec3 lightDirectionCameraSpace;
|
||||
|
||||
#if PERFORM_SHADING
|
||||
uniform float orenNayarRoughness;
|
||||
uniform float ambientIntensity;
|
||||
#endif // PERFORM_SHADING
|
||||
|
||||
#if SHADOW_MAPPING_ENABLED
|
||||
@@ -205,7 +206,8 @@ Fragment getFragment() {
|
||||
normal,
|
||||
lightDirectionCameraSpace,
|
||||
normalize(positionCameraSpace),
|
||||
orenNayarRoughness
|
||||
orenNayarRoughness,
|
||||
ambientIntensity
|
||||
);
|
||||
#endif // PERFORM_SHADING
|
||||
|
||||
|
||||
@@ -374,9 +374,9 @@ vec4 calculateNight(vec4 currentColor, vec2 uv, vec3 levelWeights,
|
||||
|
||||
vec4 calculateShadedColor(vec4 currentColor, vec3 ellipsoidNormalCameraSpace,
|
||||
vec3 lightDirectionCameraSpace, vec3 viewDirectionCameraSpace,
|
||||
float roughness)
|
||||
float roughness, float ambientIntensity)
|
||||
{
|
||||
vec3 shadedColor = currentColor.rgb * 0.05;
|
||||
vec3 shadedColor = currentColor.rgb * ambientIntensity;
|
||||
|
||||
vec3 n = normalize(ellipsoidNormalCameraSpace);
|
||||
|
||||
|
||||
@@ -222,6 +222,12 @@ namespace {
|
||||
"The roughness factor that is used for the Oren-Nayar lighting mode"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo AmbientIntensityInfo = {
|
||||
"AmbientIntensity",
|
||||
"Ambient Intensity",
|
||||
"The intensity factor for the ambient light used for light shading"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo NActiveLayersInfo = {
|
||||
"NActiveLayers",
|
||||
"Number of active layers",
|
||||
@@ -524,6 +530,7 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
FloatProperty(TargetLodScaleFactorInfo, 15.f, 1.f, 50.f),
|
||||
FloatProperty(CurrentLodScaleFactorInfo, 15.f, 1.f, 50.f),
|
||||
FloatProperty(OrenNayarRoughnessInfo, 0.f, 0.f, 1.f),
|
||||
FloatProperty(AmbientIntensityInfo, 0.05f, 0.f, 1.f),
|
||||
IntProperty(NActiveLayersInfo, 0, 0, OpenGLCap.maxTextureUnits() / 3)
|
||||
})
|
||||
, _debugPropertyOwner({ "Debug" })
|
||||
@@ -607,6 +614,7 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
addProperty(_generalProperties.targetLodScaleFactor);
|
||||
addProperty(_generalProperties.currentLodScaleFactor);
|
||||
addProperty(_generalProperties.orenNayarRoughness);
|
||||
addProperty(_generalProperties.ambientIntensity);
|
||||
_generalProperties.nActiveLayers.setReadOnly(true);
|
||||
addProperty(_generalProperties.nActiveLayers);
|
||||
|
||||
@@ -964,6 +972,10 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&,
|
||||
const float onr = _generalProperties.orenNayarRoughness;
|
||||
_localRenderer.program->setUniform("orenNayarRoughness", onr);
|
||||
_globalRenderer.program->setUniform("orenNayarRoughness", onr);
|
||||
|
||||
const float amb = _generalProperties.ambientIntensity;
|
||||
_localRenderer.program->setUniform("ambientIntensity", amb);
|
||||
_globalRenderer.program->setUniform("ambientIntensity", amb);
|
||||
}
|
||||
|
||||
_localRenderer.program->setUniform("opacity", opacity());
|
||||
@@ -2271,7 +2283,7 @@ int RenderableGlobe::desiredLevelByAvailableTileData(const Chunk& chunk) const {
|
||||
ZoneScoped
|
||||
|
||||
const int currLevel = chunk.tileIndex.level;
|
||||
|
||||
|
||||
for (const layers::Group& gi : layers::Groups) {
|
||||
const std::vector<Layer*>& lyrs = _layerManager.layerGroup(gi.id).activeLayers();
|
||||
for (Layer* layer : lyrs) {
|
||||
|
||||
@@ -141,6 +141,7 @@ private:
|
||||
properties::FloatProperty targetLodScaleFactor;
|
||||
properties::FloatProperty currentLodScaleFactor;
|
||||
properties::FloatProperty orenNayarRoughness;
|
||||
properties::FloatProperty ambientIntensity;
|
||||
properties::IntProperty nActiveLayers;
|
||||
} _generalProperties;
|
||||
|
||||
|
||||
@@ -512,7 +512,7 @@ void ShadowComponent::saveDepthBuffer() {
|
||||
<< std::endl;
|
||||
ppmFile << "255" << std::endl;
|
||||
|
||||
std::cout << "\n\nSaving depth texture to file depthBufferShadowMapping.ppm\n\n";
|
||||
LDEBUG("Saving depth texture to file depthBufferShadowMapping.ppm");
|
||||
int k = 0;
|
||||
for (int i = 0; i < _shadowDepthTextureWidth; i++) {
|
||||
for (int j = 0; j < _shadowDepthTextureHeight; j++, k++) {
|
||||
@@ -523,8 +523,7 @@ void ShadowComponent::saveDepthBuffer() {
|
||||
}
|
||||
|
||||
ppmFile.close();
|
||||
|
||||
std::cout << "Texture saved to file depthBufferShadowMapping.ppm\n\n";
|
||||
LDEBUG("Texture saved to file depthBufferShadowMapping.ppm");
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
@@ -552,7 +551,7 @@ void ShadowComponent::saveDepthBuffer() {
|
||||
<< std::endl;
|
||||
ppmFile << "255" << std::endl;
|
||||
|
||||
std::cout << "\n\nSaving texture position to positionBufferShadowMapping.ppm\n\n";
|
||||
LDEBUG("Saving texture position to positionBufferShadowMapping.ppm");
|
||||
|
||||
float biggestValue = 0.f;
|
||||
|
||||
@@ -580,7 +579,7 @@ void ShadowComponent::saveDepthBuffer() {
|
||||
|
||||
ppmFile.close();
|
||||
|
||||
LINFO("Texture saved to file positionBufferShadowMapping.ppm");
|
||||
LDEBUG("Texture saved to file positionBufferShadowMapping.ppm");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,31 +153,50 @@ ImGUIModule::ImGUIModule()
|
||||
});
|
||||
|
||||
global::callback::keyboard->emplace_back(
|
||||
[&](Key key, KeyModifier mod, KeyAction action) -> bool {
|
||||
[&](Key key, KeyModifier mod, KeyAction action,
|
||||
IsGuiWindow isGuiWindow) -> bool
|
||||
{
|
||||
ZoneScopedN("ImGUI")
|
||||
|
||||
return _isEnabled ? keyCallback(key, mod, action) : false;
|
||||
if (!isGuiWindow || !_isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return keyCallback(key, mod, action);
|
||||
}
|
||||
);
|
||||
|
||||
global::callback::character->emplace_back(
|
||||
[&](unsigned int codepoint, KeyModifier modifier) -> bool {
|
||||
[&](unsigned int codepoint, KeyModifier modifier,
|
||||
IsGuiWindow isGuiWindow) -> bool
|
||||
{
|
||||
ZoneScopedN("ImGUI")
|
||||
|
||||
return _isEnabled ? charCallback(codepoint, modifier) : false;
|
||||
if (!isGuiWindow || !_isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return charCallback(codepoint, modifier);
|
||||
}
|
||||
);
|
||||
|
||||
global::callback::mousePosition->emplace_back(
|
||||
[&](double x, double y) {
|
||||
[&](double x, double y, IsGuiWindow isGuiWindow) {
|
||||
if (!isGuiWindow) {
|
||||
return; // do nothing
|
||||
}
|
||||
_mousePosition = glm::vec2(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
);
|
||||
|
||||
global::callback::mouseButton->emplace_back(
|
||||
[&](MouseButton button, MouseAction action, KeyModifier) -> bool {
|
||||
[&](MouseButton button, MouseAction action, KeyModifier,
|
||||
IsGuiWindow isGuiWindow) -> bool
|
||||
{
|
||||
ZoneScopedN("ImGUI")
|
||||
|
||||
if (!isGuiWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MouseAction::Press) {
|
||||
_mouseButtons |= (1 << static_cast<int>(button));
|
||||
}
|
||||
@@ -190,10 +209,13 @@ ImGUIModule::ImGUIModule()
|
||||
);
|
||||
|
||||
global::callback::mouseScrollWheel->emplace_back(
|
||||
[&](double, double posY) -> bool {
|
||||
[&](double, double posY, IsGuiWindow isGuiWindow) -> bool {
|
||||
ZoneScopedN("ImGUI")
|
||||
|
||||
return _isEnabled ? mouseWheelCallback(posY) : false;
|
||||
if (!isGuiWindow || !_isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return mouseWheelCallback(posY);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -225,7 +247,7 @@ void ImGUIModule::internalInitialize(const ghoul::Dictionary&) {
|
||||
const std::vector<SceneGraphNode*>& nodes = scene ?
|
||||
scene->allSceneGraphNodes() :
|
||||
std::vector<SceneGraphNode*>();
|
||||
|
||||
|
||||
return std::vector<properties::PropertyOwner*>(nodes.begin(), nodes.end());
|
||||
}
|
||||
);
|
||||
@@ -412,7 +434,7 @@ void ImGUIModule::internalInitializeGL() {
|
||||
sizeof(ImDrawVert),
|
||||
nullptr
|
||||
);
|
||||
|
||||
|
||||
glEnableVertexAttribArray(uvAttrib);
|
||||
glVertexAttribPointer(
|
||||
uvAttrib,
|
||||
@@ -422,7 +444,7 @@ void ImGUIModule::internalInitializeGL() {
|
||||
sizeof(ImDrawVert),
|
||||
reinterpret_cast<GLvoid*>(offsetof(ImDrawVert, uv))
|
||||
);
|
||||
|
||||
|
||||
glEnableVertexAttribArray(colorAttrib);
|
||||
glVertexAttribPointer(
|
||||
colorAttrib,
|
||||
|
||||
@@ -80,3 +80,7 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
target_precompile_headers(${iswa_module} PRIVATE
|
||||
<openspace/json.h>
|
||||
<future>
|
||||
)
|
||||
|
||||
@@ -93,6 +93,7 @@ protected:
|
||||
* \return \c true if update was successful
|
||||
*/
|
||||
virtual bool updateTexture() = 0;
|
||||
|
||||
/**
|
||||
* Is called before updateTexture. For IswaCygnets getting data from a HTTP request,
|
||||
* this function should get the dataFile from the future object.
|
||||
@@ -100,6 +101,7 @@ protected:
|
||||
* \return \c true if update was successful
|
||||
*/
|
||||
virtual bool updateTextureResource() = 0;
|
||||
|
||||
/**
|
||||
* Should send a HTTP request to get the resource it needs to create a texture. For
|
||||
* Texture cygnets, this should be an image. For DataCygnets, this should be the data
|
||||
|
||||
@@ -64,27 +64,25 @@
|
||||
|
||||
#endif // OPENSPACE_MODULE_KAMELEON_ENABLED
|
||||
|
||||
constexpr std::string_view monthNumber(std::string_view month) {
|
||||
if (month == "JAN") return "01";
|
||||
else if (month == "FEB") return "02";
|
||||
else if (month == "MAR") return "03";
|
||||
else if (month == "APR") return "04";
|
||||
else if (month == "MAY") return "05";
|
||||
else if (month == "JUN") return "06";
|
||||
else if (month == "JUL") return "07";
|
||||
else if (month == "AUG") return "08";
|
||||
else if (month == "SEP") return "09";
|
||||
else if (month == "OCT") return "10";
|
||||
else if (month == "NOV") return "11";
|
||||
else if (month == "DEC") return "12";
|
||||
else return "";
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
using json = nlohmann::json;
|
||||
constexpr std::string_view _loggerCat = "IswaManager";
|
||||
|
||||
constexpr std::string_view monthNumber(std::string_view month) {
|
||||
if (month == "JAN") return "01";
|
||||
else if (month == "FEB") return "02";
|
||||
else if (month == "MAR") return "03";
|
||||
else if (month == "APR") return "04";
|
||||
else if (month == "MAY") return "05";
|
||||
else if (month == "JUN") return "06";
|
||||
else if (month == "JUL") return "07";
|
||||
else if (month == "AUG") return "08";
|
||||
else if (month == "SEP") return "09";
|
||||
else if (month == "OCT") return "10";
|
||||
else if (month == "NOV") return "11";
|
||||
else if (month == "DEC") return "12";
|
||||
else return "";
|
||||
}
|
||||
|
||||
void createScreenSpace(int id) {
|
||||
std::string idStr = std::to_string(id);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
@@ -92,7 +90,6 @@ namespace {
|
||||
openspace::scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
@@ -178,7 +175,7 @@ void IswaManager::addIswaCygnet(int id, const std::string& type, std::string gro
|
||||
metaFuture.json = res;
|
||||
|
||||
//convert to json
|
||||
json j = json::parse(res);
|
||||
nlohmann::json j = nlohmann::json::parse(res);
|
||||
|
||||
// Check what kind of geometry here
|
||||
if (j["Coordinate Type"].is_null()) {
|
||||
@@ -357,7 +354,7 @@ std::string IswaManager::jsonPlaneToLuaTable(MetadataFuture& data) {
|
||||
if (data.json.empty()) {
|
||||
return "";
|
||||
}
|
||||
json j = json::parse(data.json);
|
||||
nlohmann::json j = nlohmann::json::parse(data.json);
|
||||
|
||||
std::string parent = j["Central Body"];
|
||||
std::string frame = j["Coordinates"];
|
||||
@@ -473,7 +470,7 @@ std::string IswaManager::jsonSphereToLuaTable(MetadataFuture& data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
json j = json::parse(data.json);
|
||||
nlohmann::json j = nlohmann::json::parse(data.json);
|
||||
j = j["metadata"];
|
||||
std::string parent = j["central_body"];
|
||||
parent[0] = static_cast<char>(toupper(static_cast<int>(parent[0])));
|
||||
@@ -667,16 +664,16 @@ void IswaManager::createFieldline(std::string name, std::string cdfPath,
|
||||
|
||||
void IswaManager::fillCygnetInfo(std::string jsonString) {
|
||||
if (jsonString != "") {
|
||||
json j = json::parse(jsonString);
|
||||
nlohmann::json j = nlohmann::json::parse(jsonString);
|
||||
|
||||
std::set<std::string> lists = {"listOfPriorityCygnets", "listOfOKCygnets"
|
||||
// ,"listOfStaleCygnets", "listOfInactiveCygnets",
|
||||
};
|
||||
|
||||
for (const std::string& list : lists) {
|
||||
json jsonList = j[list];
|
||||
nlohmann::json jsonList = j[list];
|
||||
for (size_t i = 0; i < jsonList.size(); ++i) {
|
||||
json jCygnet = jsonList.at(i);
|
||||
nlohmann::json jCygnet = jsonList.at(i);
|
||||
|
||||
std::string name = jCygnet["cygnetDisplayTitle"];
|
||||
std::replace(name.begin(), name.end(),'.', ',');
|
||||
@@ -706,9 +703,9 @@ void IswaManager::addCdfFiles(std::string cdfpath) {
|
||||
std::ifstream jsonFile(cdfFile);
|
||||
|
||||
if (jsonFile.is_open()) {
|
||||
json cdfGroups = json::parse(jsonFile);
|
||||
nlohmann::json cdfGroups = nlohmann::json::parse(jsonFile);
|
||||
for(size_t i = 0; i < cdfGroups.size(); ++i) {
|
||||
json cdfGroup = cdfGroups.at(i);
|
||||
nlohmann::json cdfGroup = cdfGroups.at(i);
|
||||
|
||||
std::string groupName = cdfGroup["group"];
|
||||
std::string fieldlineSeedsIndexFile = cdfGroup["fieldlinefile"];
|
||||
@@ -720,9 +717,9 @@ void IswaManager::addCdfFiles(std::string cdfpath) {
|
||||
|
||||
_cdfInformation[groupName] = std::vector<CdfInfo>();
|
||||
|
||||
json cdfs = cdfGroup["cdfs"];
|
||||
nlohmann::json cdfs = cdfGroup["cdfs"];
|
||||
for (size_t j = 0; j < cdfs.size(); j++) {
|
||||
json cdf = cdfs.at(j);
|
||||
nlohmann::json cdf = cdfs.at(j);
|
||||
|
||||
std::string name = cdf["name"];
|
||||
std::string path = cdf["path"];
|
||||
|
||||
@@ -71,6 +71,20 @@ if (TARGET cdf)
|
||||
set_folder_location(cdf "External")
|
||||
endif ()
|
||||
|
||||
target_precompile_headers(cdf PRIVATE
|
||||
[["stdio.h"]]
|
||||
[["stdlib.h"]]
|
||||
[["string.h"]]
|
||||
)
|
||||
|
||||
target_precompile_headers(ccmc PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:iostream>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:map>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:unordered_map>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:vector>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:boost/unordered_map.hpp>"
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_compile_options(ccmc PRIVATE /MP)
|
||||
if (TARGET cdf)
|
||||
|
||||
Submodule modules/kameleon/ext/kameleon updated: e6978911c5...08b0e5dbfc
@@ -35,6 +35,7 @@
|
||||
#endif // _MSC_VER
|
||||
|
||||
#include <ccmc/Kameleon.h>
|
||||
#include <ccmc/FileReader.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (pop)
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#endif // WIN32
|
||||
|
||||
#include <ccmc/Kameleon.h>
|
||||
#include <ccmc/Constants.h>
|
||||
#include <ccmc/FileReader.h>
|
||||
#include <ccmc/Model.h>
|
||||
#include <ccmc/Interpolator.h>
|
||||
#include <ccmc/BATSRUS.h>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <ccmc/Kameleon.h>
|
||||
#include <ccmc/Model.h>
|
||||
#include <ccmc/FileReader.h>
|
||||
#include <ccmc/BATSRUS.h>
|
||||
#include <ccmc/ENLIL.h>
|
||||
#include <ccmc/CCMCTime.h>
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include <openspace/util/histogram.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
|
||||
@@ -82,3 +82,10 @@ create_new_module(
|
||||
server_module
|
||||
${HEADER_FILES} ${SOURCE_FILES}
|
||||
)
|
||||
|
||||
target_precompile_headers(${server_module} PRIVATE
|
||||
<modules/server/include/connection.h>
|
||||
<modules/server/include/topics/topic.h>
|
||||
<openspace/json.h>
|
||||
<ghoul/misc/templatefactory.h>
|
||||
)
|
||||
|
||||
@@ -101,7 +101,7 @@ void SkyBrowserTopic::sendBrowserData() {
|
||||
|
||||
// Pass data for all the browsers and the corresponding targets
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
ghoul::Dictionary targets;
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
|
||||
@@ -63,3 +63,7 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
target_precompile_headers(${skybrowser_module} PRIVATE
|
||||
[["include/cef_accessibility_handler.h"]]
|
||||
[["include/cef_render_handler.h"]]
|
||||
)
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include <modules/webbrowser/include/webrenderhandler.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
#include <openspace/properties/triggerproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (push)
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
explicit Browser(const ghoul::Dictionary& dictionary);
|
||||
virtual ~Browser();
|
||||
|
||||
bool initializeGL();
|
||||
void initializeGL();
|
||||
void deinitializeGL();
|
||||
bool isReady() const;
|
||||
|
||||
@@ -72,13 +72,14 @@ public:
|
||||
void update();
|
||||
|
||||
void updateBrowserSize();
|
||||
void reload();
|
||||
|
||||
glm::vec2 browserPixelDimensions() const;
|
||||
float browserRatio() const;
|
||||
void setCallbackDimensions(const std::function<void(const glm::dvec2&)>& function);
|
||||
|
||||
protected:
|
||||
properties::Vec2Property _browserPixeldimensions;
|
||||
properties::Vec2Property _browserDimensions;
|
||||
properties::StringProperty _url;
|
||||
properties::TriggerProperty _reload;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
|
||||
namespace openspace::documentation { struct Documentation; }
|
||||
|
||||
@@ -50,6 +49,7 @@ public:
|
||||
void setRatio(float ratio);
|
||||
void setColor(glm::ivec3 color);
|
||||
void setVerticalFov(double fov);
|
||||
void setBorderRadius(double radius);
|
||||
|
||||
// Display
|
||||
void highlight(const glm::ivec3& addition);
|
||||
@@ -62,9 +62,9 @@ private:
|
||||
properties::FloatProperty _crossHairSize;
|
||||
properties::FloatProperty _showRectangleThreshold;
|
||||
properties::FloatProperty _lineWidth;
|
||||
properties::DoubleProperty _verticalFov;
|
||||
|
||||
double _verticalFov = 10.0;
|
||||
|
||||
double _borderRadius = 0.0;
|
||||
glm::ivec3 _borderColor = glm::ivec3(230);
|
||||
float _ratio = 1.f;
|
||||
};
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include <modules/skybrowser/include/wwtcommunicator.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec2property.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
glm::dvec2 fineTuneVector(const glm::dvec2& drag);
|
||||
bool isInitialized() const;
|
||||
|
||||
void setVerticalFovWithScroll(float scroll);
|
||||
double setVerticalFovWithScroll(float scroll);
|
||||
void setOpacity(float opacity);
|
||||
void setRatio(float ratio);
|
||||
void setIdInBrowser() const;
|
||||
@@ -78,6 +78,7 @@ private:
|
||||
bool _isSyncedWithWwt = false;
|
||||
bool _textureDimensionsIsDirty = false;
|
||||
bool _ratioIsDirty = false;
|
||||
bool _radiusIsDirty = false;
|
||||
bool _isInitialized = false;
|
||||
|
||||
float _ratio = 1.f;
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___TARGETBROWSERPAIR___H__
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <deque>
|
||||
|
||||
namespace ghoul { class Dictionary; }
|
||||
|
||||
@@ -42,7 +40,6 @@ class ScreenSpaceRenderable;
|
||||
class TargetBrowserPair {
|
||||
public:
|
||||
TargetBrowserPair(SceneGraphNode* target, ScreenSpaceSkyBrowser* browser);
|
||||
TargetBrowserPair& operator=(TargetBrowserPair other);
|
||||
|
||||
// Target & Browser
|
||||
void initialize();
|
||||
@@ -60,9 +57,7 @@ public:
|
||||
|
||||
// Browser
|
||||
void sendIdToBrowser() const;
|
||||
void updateBrowserSize();
|
||||
std::vector<std::pair<std::string, glm::dvec3>> displayCopies() const;
|
||||
bool isImageCollectionLoaded();
|
||||
|
||||
// Target
|
||||
void centerTargetOnScreen();
|
||||
@@ -73,10 +68,10 @@ public:
|
||||
bool isEnabled() const;
|
||||
|
||||
void setEnabled(bool enable);
|
||||
void setOpacity(float opacity);
|
||||
void setVerticalFov(double vfov);
|
||||
void setEquatorialAim(const glm::dvec2& aim);
|
||||
void setBorderColor(const glm::ivec3& color);
|
||||
void setBorderRadius(double radius);
|
||||
void setBrowserRatio(float ratio);
|
||||
void setVerticalFovWithScroll(float scroll);
|
||||
void setImageCollectionIsLoaded(bool isLoaded);
|
||||
@@ -89,9 +84,7 @@ public:
|
||||
std::string browserId() const;
|
||||
std::string targetRenderableId() const;
|
||||
std::string targetNodeId() const;
|
||||
float browserRatio() const;
|
||||
|
||||
SceneGraphNode* targetNode() const;
|
||||
ScreenSpaceSkyBrowser* browser() const;
|
||||
std::vector<int> selectedImages() const;
|
||||
|
||||
@@ -106,12 +99,7 @@ public:
|
||||
void setImageOpacity(int i, float opacity);
|
||||
void hideChromeInterface();
|
||||
|
||||
friend bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs);
|
||||
friend bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs);
|
||||
|
||||
private:
|
||||
void aimTargetGalactic(glm::dvec3 direction);
|
||||
|
||||
// Target and browser
|
||||
RenderableSkyTarget* _targetRenderable = nullptr;
|
||||
ScreenSpaceSkyBrowser* _browser = nullptr;
|
||||
|
||||
@@ -33,20 +33,7 @@ namespace openspace::skybrowser {
|
||||
|
||||
// Constants
|
||||
constexpr double ScreenSpaceZ = -2.1;
|
||||
constexpr glm::dvec3 NorthPole = { 0.0, 0.0, 1.0 };
|
||||
constexpr double CelestialSphereRadius = 4 * distanceconstants::Parsec;
|
||||
|
||||
// Conversion matrix - J2000 equatorial <-> galactic
|
||||
// https://arxiv.org/abs/1010.3773v1
|
||||
const glm::dmat3 conversionMatrix = glm::dmat3(
|
||||
-0.054875539390, 0.494109453633, -0.867666135681, // col 0
|
||||
-0.873437104725, -0.444829594298, -0.198076389622, // col 1
|
||||
-0.483834991775, 0.746982248696, 0.455983794523 // col 2
|
||||
);
|
||||
|
||||
// Galactic coordinates are projected onto the celestial sphere
|
||||
// Equatorial coordinates are unit length
|
||||
// Conversion spherical <-> Cartesian
|
||||
constexpr double CelestialSphereRadius = 4.0 * distanceconstants::Parsec;
|
||||
|
||||
/**
|
||||
* Converts from Cartesian coordinates to spherical coordinates with unit length.
|
||||
@@ -205,9 +192,8 @@ public:
|
||||
Animation(T start, T goal, double time)
|
||||
: _goal(std::move(goal))
|
||||
, _start(std::move(start))
|
||||
{
|
||||
_animationTime = std::chrono::milliseconds(static_cast<int>(time * 1000));
|
||||
}
|
||||
, _animationTime(std::chrono::milliseconds(static_cast<int>(time * 1000)))
|
||||
{}
|
||||
|
||||
void start() {
|
||||
_isStarted = true;
|
||||
@@ -219,12 +205,12 @@ public:
|
||||
}
|
||||
|
||||
bool isAnimating() const {
|
||||
bool timeLeft = timeSpent().count() < _animationTime.count() ? true : false;
|
||||
bool timeLeft = timeSpent().count() < _animationTime.count();
|
||||
return timeLeft && _isStarted;
|
||||
}
|
||||
|
||||
T getNewValue();
|
||||
glm::dmat4 getRotationMatrix();
|
||||
T newValue() const;
|
||||
glm::dmat4 rotationMatrix();
|
||||
|
||||
private:
|
||||
std::chrono::duration<double, std::milli> timeSpent() const {
|
||||
|
||||
@@ -26,11 +26,8 @@
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___WWTCOMMUNICATOR___H__
|
||||
|
||||
#include <modules/skybrowser/include/browser.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/properties/vector/dvec2property.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/ivec3property.h>
|
||||
#include <deque>
|
||||
|
||||
namespace openspace {
|
||||
@@ -38,8 +35,7 @@ namespace openspace {
|
||||
class WwtCommunicator : public Browser {
|
||||
public:
|
||||
explicit WwtCommunicator(const ghoul::Dictionary& dictionary);
|
||||
WwtCommunicator(const WwtCommunicator&) = default;
|
||||
~WwtCommunicator() override;
|
||||
~WwtCommunicator() override = default;
|
||||
|
||||
void update();
|
||||
|
||||
@@ -60,11 +56,13 @@ public:
|
||||
glm::dvec2 fieldsOfView() const;
|
||||
std::vector<int> selectedImages() const;
|
||||
std::vector<double> opacities() const;
|
||||
double borderRadius() const;
|
||||
|
||||
void setImageCollectionIsLoaded(bool isLoaded);
|
||||
void setVerticalFov(double vfov);
|
||||
void setEquatorialAim(glm::dvec2 equatorial);
|
||||
void setBorderColor(glm::ivec3 color);
|
||||
void setBorderRadius(double radius);
|
||||
void setTargetRoll(double roll);
|
||||
|
||||
void updateBorderColor() const;
|
||||
@@ -73,8 +71,10 @@ public:
|
||||
protected:
|
||||
void setIdInBrowser(const std::string& id) const;
|
||||
std::deque<std::pair<int, double>>::iterator findSelectedImage(int i);
|
||||
|
||||
double _verticalFov = 10.0f;
|
||||
|
||||
properties::DoubleProperty _verticalFov;
|
||||
|
||||
double _borderRadius = 0.0;
|
||||
glm::ivec3 _borderColor = glm::ivec3(70);
|
||||
glm::dvec2 _equatorialAim = glm::dvec2(0.0);
|
||||
double _targetRoll = 0.0;
|
||||
@@ -82,23 +82,10 @@ protected:
|
||||
std::deque<std::pair<int, double>> _selectedImages;
|
||||
|
||||
private:
|
||||
void setWebpageBorderColor(glm::ivec3 color) const;
|
||||
void sendMessageToWwt(const ghoul::Dictionary& msg) const;
|
||||
|
||||
// WorldWide Telescope messages
|
||||
ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov,
|
||||
double roll, bool shouldMoveInstantly = true) const;
|
||||
ghoul::Dictionary loadCollectionMessage(const std::string& url) const;
|
||||
ghoul::Dictionary setForegroundMessage(const std::string& name) const;
|
||||
ghoul::Dictionary addImageMessage(const std::string& id,
|
||||
const std::string& url) const;
|
||||
ghoul::Dictionary removeImageMessage(const std::string& id) const;
|
||||
ghoul::Dictionary setImageOpacityMessage(const std::string& id, double opacity) const;
|
||||
ghoul::Dictionary setLayerOrderMessage(const std::string& id, int version);
|
||||
|
||||
bool _borderColorIsDirty = false;
|
||||
bool _equatorialAimIsDirty = false;
|
||||
int messageCounter = 0;
|
||||
|
||||
// Time variables
|
||||
// For capping the message passing to WWT
|
||||
|
||||
@@ -25,51 +25,22 @@
|
||||
#ifndef __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__
|
||||
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsuggest-override"
|
||||
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
|
||||
#endif
|
||||
|
||||
#include <modules/skybrowser/ext/tinyxml2/tinyxml2.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace openspace::documentation { struct Documentation; }
|
||||
|
||||
namespace openspace::wwt {
|
||||
const std::string Thumbnail = "Thumbnail";
|
||||
const std::string Name = "Name";
|
||||
const std::string ImageSet = "ImageSet";
|
||||
const std::string Dec = "Dec";
|
||||
const std::string RA = "RA";
|
||||
const std::string Undefined = "";
|
||||
const std::string Folder = "Folder";
|
||||
const std::string Place = "Place";
|
||||
const std::string ThumbnailUrl = "ThumbnailUrl";
|
||||
const std::string Url = "Url";
|
||||
const std::string Credits = "Credits";
|
||||
const std::string CreditsUrl = "CreditsUrl";
|
||||
const std::string ZoomLevel = "ZoomLevel";
|
||||
const std::string DataSetType = "DataSetType";
|
||||
const std::string Sky = "Sky";
|
||||
} // namespace openspace::wwt
|
||||
namespace tinyxml2 { class XMLElement; }
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
struct ImageData {
|
||||
std::string name = wwt::Undefined;
|
||||
std::string thumbnailUrl = wwt::Undefined;
|
||||
std::string imageUrl = wwt::Undefined;
|
||||
std::string credits = wwt::Undefined;
|
||||
std::string creditsUrl = wwt::Undefined;
|
||||
std::string collection = wwt::Undefined;
|
||||
std::string name;
|
||||
std::string thumbnailUrl;
|
||||
std::string imageUrl;
|
||||
std::string credits;
|
||||
std::string creditsUrl;
|
||||
std::string collection;
|
||||
bool hasCelestialCoords = false;
|
||||
float fov = 0.f;
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
@@ -78,20 +49,15 @@ struct ImageData {
|
||||
|
||||
class WwtDataHandler {
|
||||
public:
|
||||
WwtDataHandler() = default;
|
||||
~WwtDataHandler();
|
||||
|
||||
void loadImages(const std::string& root, const std::filesystem::path& directory);
|
||||
int nLoadedImages() const;
|
||||
const ImageData& getImage(int i) const;
|
||||
const ImageData& image(int i) const;
|
||||
|
||||
private:
|
||||
void saveImageFromNode(tinyxml2::XMLElement* node, std::string collection);
|
||||
void saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection);
|
||||
void saveImagesFromXml(const tinyxml2::XMLElement* root, std::string collection);
|
||||
|
||||
// Images
|
||||
std::vector<ImageData> _images;
|
||||
std::vector<tinyxml2::XMLDocument*> _xmls;
|
||||
};
|
||||
} // namespace openspace
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ uniform float lineWidth;
|
||||
uniform float ratio;
|
||||
uniform vec4 lineColor;
|
||||
uniform float fov;
|
||||
uniform float borderRadius;
|
||||
|
||||
uniform bool additiveBlending;
|
||||
uniform float opacity;
|
||||
@@ -45,7 +46,6 @@ uniform vec3 multiplyColor;
|
||||
// This compensates for the optical illusion that vertical lines appear thinner
|
||||
const float VerticalThickness = 1.1;
|
||||
|
||||
|
||||
float createLine(float lineCenter, float lineWidth, float coord) {
|
||||
// Calculate edges of line
|
||||
float startEdge = lineCenter - (lineWidth * 0.5);
|
||||
@@ -66,6 +66,12 @@ float createCrosshair(in float linewidth, in float ratio, in vec2 coord) {
|
||||
return crosshairHorizontal + crosshairVertical;
|
||||
}
|
||||
|
||||
// Creates a rounded rectangle where radius is [0, 1] from completely square
|
||||
// to completely round
|
||||
float roundedRectangle(vec2 coord, vec2 size, float radius) {
|
||||
vec2 q = abs(coord) - size + vec2(radius);
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
|
||||
}
|
||||
|
||||
Fragment getFragment() {
|
||||
float rectangle = 0.0;
|
||||
@@ -78,13 +84,34 @@ Fragment getFragment() {
|
||||
crosshair *= crossHairBox;
|
||||
|
||||
if (showRectangle) {
|
||||
float lineWidthX = lineWidth * 2 * VerticalThickness;
|
||||
float lineWidthY = lineWidth * 2;
|
||||
float height = ((fov * 0.5)/maxWwtFov)-lineWidthX;
|
||||
float lineWidthX = lineWidth * 2 * VerticalThickness;
|
||||
float height = ((fov * 0.5) / maxWwtFov) - lineWidth;
|
||||
float width = (height * ratio) - lineWidthY;
|
||||
float outerEdge = createFilledRectangle(width, height, vs_st);
|
||||
float innerEdge = createFilledRectangle(width-lineWidthY, height-lineWidthX, vs_st);
|
||||
rectangle = outerEdge - innerEdge;
|
||||
vec2 size = vec2(width, height);
|
||||
|
||||
// The radius of the corners (in pixels) clockwise starting in the top left
|
||||
float radius = clamp(borderRadius * 0.5 * min(height, width), 0.0, 0.5 * min(height, width));
|
||||
|
||||
// Calculate distance to edge
|
||||
float distance = roundedRectangle(vs_st.xy - vec2(0.5), size * 0.5, radius);
|
||||
|
||||
// How soft the edges should be (in pixels)
|
||||
// Higher values could be used to simulate a drop shadow
|
||||
float edgeSoftness = 2.0;
|
||||
// Smooth the result (free antialiasing)
|
||||
float smoothedAlpha = 1.0 - smoothstep(0.0, edgeSoftness, distance);
|
||||
|
||||
// Border
|
||||
float borderThickness = lineWidth * 0.5;
|
||||
float borderSoftness = 0.0;
|
||||
float borderAlpha = 1.0-smoothstep(borderThickness - borderSoftness, borderThickness, abs(distance));
|
||||
|
||||
// Colors
|
||||
float borderColor = 1.0;
|
||||
float bgColor = 0.0;
|
||||
|
||||
rectangle = mix(bgColor, mix(bgColor, borderColor, borderAlpha), smoothedAlpha);
|
||||
}
|
||||
|
||||
float result = clamp(crosshair + rectangle, 0.0, 1.0);
|
||||
|
||||
@@ -92,6 +92,12 @@ namespace {
|
||||
"inversed"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SynchronizeAimInfo = {
|
||||
"SynchronizeAim",
|
||||
"Synchronize Aim",
|
||||
"If checked, the target and the browser will have synchronized aim."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SpaceCraftTimeInfo = {
|
||||
"SpaceCraftAnimationTime",
|
||||
"Space Craft Animation Time",
|
||||
@@ -127,6 +133,9 @@ namespace {
|
||||
// [[codegen::verbatim(InverseZoomInfo.description)]]
|
||||
std::optional<bool> inverseZoomDirection;
|
||||
|
||||
// [[codegen::verbatim(SynchronizeAimInfo.description)]]
|
||||
std::optional<bool> synchronizeAim;
|
||||
|
||||
// [[codegen::verbatim(SpaceCraftTimeInfo.description)]]
|
||||
std::optional<double> spaceCraftAnimationTime;
|
||||
|
||||
@@ -149,9 +158,12 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
, _browserAnimationSpeed(BrowserSpeedInfo, 5.0, 0.0, 10.0)
|
||||
, _hideTargetsBrowsersWithGui(HideWithGuiInfo, false)
|
||||
, _inverseZoomDirection(InverseZoomInfo, false)
|
||||
, _synchronizeAim(SynchronizeAimInfo, true)
|
||||
, _spaceCraftAnimationTime(SpaceCraftTimeInfo, 2.0, 0.0, 10.0)
|
||||
, _wwtImageCollectionUrl(ImageCollectionInfo,
|
||||
"https://data.openspaceproject.com/wwt/1/imagecollection.wtml")
|
||||
, _wwtImageCollectionUrl(
|
||||
ImageCollectionInfo,
|
||||
"https://data.openspaceproject.com/wwt/1/imagecollection.wtml"
|
||||
)
|
||||
{
|
||||
addProperty(_enabled);
|
||||
addProperty(_showTitleInGuiBrowser);
|
||||
@@ -163,11 +175,13 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
addProperty(_inverseZoomDirection);
|
||||
addProperty(_spaceCraftAnimationTime);
|
||||
addProperty(_wwtImageCollectionUrl);
|
||||
addProperty(_synchronizeAim);
|
||||
_wwtImageCollectionUrl.setReadOnly(true);
|
||||
|
||||
// Set callback functions
|
||||
global::callback::mouseButton->emplace(global::callback::mouseButton->begin(),
|
||||
[&](MouseButton, MouseAction action, KeyModifier) -> bool {
|
||||
global::callback::mouseButton->emplace(
|
||||
global::callback::mouseButton->begin(),
|
||||
[&](MouseButton button, MouseAction action, KeyModifier, IsGuiWindow) -> bool {
|
||||
if (action == MouseAction::Press) {
|
||||
_cameraRotation.stop();
|
||||
}
|
||||
@@ -188,7 +202,8 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
if (vizModeChanged) {
|
||||
constexpr float FadeDuration = 2.f;
|
||||
|
||||
if (camWasInSolarSystem) { // Camera moved out of the solar system => fade out
|
||||
if (camWasInSolarSystem) {
|
||||
// Camera moved out of the solar system => fade out
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->startFading(0.f, FadeDuration);
|
||||
}
|
||||
@@ -196,7 +211,8 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
// Also hide the hover circle
|
||||
disableHoverCircle();
|
||||
}
|
||||
else { // Camera moved into the solar system => fade in
|
||||
else {
|
||||
// Camera moved into the solar system => fade in
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->startFading(1.f, FadeDuration);
|
||||
}
|
||||
@@ -204,8 +220,10 @@ SkyBrowserModule::SkyBrowserModule()
|
||||
}
|
||||
|
||||
if (_isCameraInSolarSystem) {
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->synchronizeAim();
|
||||
if (_synchronizeAim) {
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : _targetsBrowsers) {
|
||||
pair->synchronizeAim();
|
||||
}
|
||||
}
|
||||
incrementallyAnimateTargets();
|
||||
}
|
||||
@@ -225,6 +243,7 @@ void SkyBrowserModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
_browserAnimationSpeed = p.browserSpeed.value_or(_browserAnimationSpeed);
|
||||
_inverseZoomDirection = p.inverseZoomDirection.value_or(_inverseZoomDirection);
|
||||
_wwtImageCollectionUrl = p.wwtImageCollectionUrl.value_or(_wwtImageCollectionUrl);
|
||||
_synchronizeAim = p.synchronizeAim.value_or(_synchronizeAim);
|
||||
_hideTargetsBrowsersWithGui = p.hideTargetsBrowsersGui.value_or(
|
||||
_hideTargetsBrowsersWithGui
|
||||
);
|
||||
@@ -245,10 +264,6 @@ void SkyBrowserModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
|
||||
// Register ScreenSpaceSkyTarget
|
||||
fRenderable->registerClass<RenderableSkyTarget>("RenderableSkyTarget");
|
||||
|
||||
// Create data handler dynamically to avoid the linking error that
|
||||
// came up when including the include file in the module header file
|
||||
_dataHandler = std::make_unique<WwtDataHandler>();
|
||||
}
|
||||
|
||||
void SkyBrowserModule::addTargetBrowserPair(const std::string& targetId,
|
||||
@@ -280,8 +295,6 @@ void SkyBrowserModule::removeTargetBrowserPair(const std::string& id) {
|
||||
_targetsBrowsers.begin(),
|
||||
_targetsBrowsers.end(),
|
||||
[&](const std::unique_ptr<TargetBrowserPair>& pair) {
|
||||
// should this be?
|
||||
// found == pair.get()
|
||||
return found == pair.get();
|
||||
}
|
||||
);
|
||||
@@ -297,6 +310,7 @@ void SkyBrowserModule::lookAtTarget(const std::string& id) {
|
||||
}
|
||||
|
||||
void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) {
|
||||
ghoul_assert(circle, "No circle specified");
|
||||
_hoverCircle = circle;
|
||||
|
||||
// Always disable it per default. It should only be visible on interaction
|
||||
@@ -304,46 +318,48 @@ void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) {
|
||||
}
|
||||
|
||||
void SkyBrowserModule::moveHoverCircle(int i, bool useScript) {
|
||||
const ImageData& image = _dataHandler->getImage(i);
|
||||
const ImageData& image = _dataHandler.image(i);
|
||||
|
||||
// Only move and show circle if the image has coordinates
|
||||
if (_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem) {
|
||||
const std::string id = _hoverCircle->identifier();
|
||||
if (!(_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the circle
|
||||
if (useScript) {
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);",
|
||||
id
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
else {
|
||||
Renderable* renderable = _hoverCircle->renderable();
|
||||
if (renderable) {
|
||||
renderable->property("Fade")->set(1.f);
|
||||
}
|
||||
}
|
||||
const std::string id = _hoverCircle->identifier();
|
||||
|
||||
// Set the exact target position
|
||||
// Move it slightly outside of the celestial sphere so it doesn't overlap with
|
||||
// the target
|
||||
glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian);
|
||||
pos *= skybrowser::CelestialSphereRadius * 1.1;
|
||||
|
||||
// Note that the position can only be set through the script engine
|
||||
// Show the circle
|
||||
if (useScript) {
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(pos)
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);",
|
||||
id
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
else {
|
||||
Renderable* renderable = _hoverCircle->renderable();
|
||||
if (renderable) {
|
||||
renderable->setFade(1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the exact target position
|
||||
// Move it slightly outside of the celestial sphere so it doesn't overlap with
|
||||
// the target
|
||||
glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian);
|
||||
pos *= skybrowser::CelestialSphereRadius * 1.1;
|
||||
|
||||
// Note that the position can only be set through the script engine
|
||||
const std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(pos)
|
||||
);
|
||||
global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void SkyBrowserModule::disableHoverCircle(bool useScript) {
|
||||
@@ -367,18 +383,18 @@ void SkyBrowserModule::disableHoverCircle(bool useScript) {
|
||||
void SkyBrowserModule::loadImages(const std::string& root,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
_dataHandler->loadImages(root, directory);
|
||||
_dataHandler.loadImages(root, directory);
|
||||
}
|
||||
|
||||
int SkyBrowserModule::nLoadedImages() const {
|
||||
return _dataHandler->nLoadedImages();
|
||||
return _dataHandler.nLoadedImages();
|
||||
}
|
||||
|
||||
const std::unique_ptr<WwtDataHandler>& SkyBrowserModule::getWwtDataHandler() const {
|
||||
const WwtDataHandler& SkyBrowserModule::wwtDataHandler() const {
|
||||
return _dataHandler;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& SkyBrowserModule::getPairs() {
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& SkyBrowserModule::pairs() {
|
||||
return _targetsBrowsers;
|
||||
}
|
||||
|
||||
@@ -386,7 +402,7 @@ int SkyBrowserModule::nPairs() const {
|
||||
return static_cast<int>(_targetsBrowsers.size());
|
||||
}
|
||||
|
||||
TargetBrowserPair* SkyBrowserModule::pair(const std::string& id) const {
|
||||
TargetBrowserPair* SkyBrowserModule::pair(std::string_view id) const {
|
||||
auto it = std::find_if(
|
||||
_targetsBrowsers.begin(),
|
||||
_targetsBrowsers.end(),
|
||||
@@ -415,7 +431,7 @@ void SkyBrowserModule::startRotatingCamera(glm::dvec3 endAnimation) {
|
||||
|
||||
void SkyBrowserModule::incrementallyRotateCamera() {
|
||||
if (_cameraRotation.isAnimating()) {
|
||||
glm::dmat4 rotMat = _cameraRotation.getRotationMatrix();
|
||||
glm::dmat4 rotMat = _cameraRotation.rotationMatrix();
|
||||
global::navigationHandler->camera()->rotate(glm::quat_cast(rotMat));
|
||||
}
|
||||
}
|
||||
@@ -444,9 +460,9 @@ std::string SkyBrowserModule::wwtImageCollectionUrl() const {
|
||||
return _wwtImageCollectionUrl;
|
||||
}
|
||||
|
||||
void SkyBrowserModule::setSelectedBrowser(const std::string& id) {
|
||||
TargetBrowserPair* found = pair(id);
|
||||
if (found) {
|
||||
void SkyBrowserModule::setSelectedBrowser(std::string_view id) {
|
||||
TargetBrowserPair* p = pair(id);
|
||||
if (p) {
|
||||
_selectedBrowser = id;
|
||||
}
|
||||
}
|
||||
@@ -522,7 +538,9 @@ scripting::LuaLibrary SkyBrowserModule::luaLibrary() const {
|
||||
codegen::lua::ShowAllTargetsAndBrowsers,
|
||||
codegen::lua::PointSpaceCraft,
|
||||
codegen::lua::GetWwtImageCollectionUrl,
|
||||
codegen::lua::StopAnimations
|
||||
codegen::lua::StopAnimations,
|
||||
codegen::lua::SetBorderRadius,
|
||||
codegen::lua::ReloadDisplayCopyOnNode
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,24 +25,21 @@
|
||||
#ifndef __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__
|
||||
#define __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
|
||||
#include <openspace/util/openspacemodule.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/util/distanceconstants.h>
|
||||
#include <openspace/util/mouse.h>
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <modules/skybrowser/include/wwtdatahandler.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/doubleproperty.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class ScreenSpaceImageLocal;
|
||||
class WwtDataHandler;
|
||||
class TargetBrowserPair;
|
||||
class SceneGraphNode;
|
||||
struct ImageData;
|
||||
class SceneGraphNode;
|
||||
class ScreenSpaceImageLocal;
|
||||
class TargetBrowserPair;
|
||||
|
||||
class SkyBrowserModule : public OpenSpaceModule {
|
||||
public:
|
||||
@@ -50,25 +47,24 @@ public:
|
||||
|
||||
SkyBrowserModule();
|
||||
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& getPairs();
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>>& pairs();
|
||||
int nPairs() const;
|
||||
TargetBrowserPair* pair(const std::string& id) const;
|
||||
const std::unique_ptr<WwtDataHandler>& getWwtDataHandler() const;
|
||||
TargetBrowserPair* pair(std::string_view id) const;
|
||||
const WwtDataHandler& wwtDataHandler() const;
|
||||
std::string selectedBrowserId() const;
|
||||
std::string selectedTargetId() const;
|
||||
int uniqueIdentifierCounter() const;
|
||||
|
||||
void setSelectedBrowser(const std::string& id);
|
||||
void setSelectedBrowser(std::string_view id);
|
||||
void setHoverCircle(SceneGraphNode* circle);
|
||||
|
||||
// Rotation, animation, placement
|
||||
void lookAtTarget(const std::string& id);
|
||||
void startRotatingCamera(glm::dvec3 endAnimation); // Pass in galactic coordinate
|
||||
void incrementallyRotateCamera();
|
||||
void incrementallyAnimateTargets();
|
||||
double targetAnimationSpeed() const;
|
||||
double browserAnimationSpeed() const;
|
||||
double spaceCraftAnimationTime() const;
|
||||
|
||||
std::string wwtImageCollectionUrl() const;
|
||||
|
||||
bool isCameraInSolarSystem() const;
|
||||
@@ -94,6 +90,9 @@ protected:
|
||||
void internalInitialize(const ghoul::Dictionary& dict) override;
|
||||
|
||||
private:
|
||||
void incrementallyRotateCamera();
|
||||
void incrementallyAnimateTargets();
|
||||
|
||||
properties::BoolProperty _enabled;
|
||||
properties::BoolProperty _showTitleInGuiBrowser;
|
||||
properties::BoolProperty _allowCameraRotation;
|
||||
@@ -102,13 +101,14 @@ private:
|
||||
properties::DoubleProperty _browserAnimationSpeed;
|
||||
properties::BoolProperty _hideTargetsBrowsersWithGui;
|
||||
properties::BoolProperty _inverseZoomDirection;
|
||||
properties::BoolProperty _synchronizeAim;
|
||||
properties::DoubleProperty _spaceCraftAnimationTime;
|
||||
properties::StringProperty _wwtImageCollectionUrl;
|
||||
|
||||
// The browsers and targets
|
||||
std::vector<std::unique_ptr<TargetBrowserPair>> _targetsBrowsers;
|
||||
SceneGraphNode* _hoverCircle = nullptr;
|
||||
std::string _selectedBrowser = ""; // Currently selected browser
|
||||
std::string _selectedBrowser; // Currently selected browser
|
||||
int _uniqueIdentifierCounter = 0;
|
||||
|
||||
// Flags
|
||||
@@ -119,7 +119,7 @@ private:
|
||||
skybrowser::Animation(glm::dvec3(0.0), glm::dvec3(0.0), 0.0);
|
||||
|
||||
// Data handler for the image collections
|
||||
std::unique_ptr<WwtDataHandler> _dataHandler;
|
||||
WwtDataHandler _dataHandler;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -39,6 +39,37 @@
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "SkyBrowserModule";
|
||||
|
||||
|
||||
/**
|
||||
* Reloads the sky browser display copy for the node index that is sent in.
|
||||
* .If no ID is sent in, it will reload all display copies on that node.
|
||||
*/
|
||||
[[codegen::luawrap]] void reloadDisplayCopyOnNode(int nodeIndex, std::string id = "all") {
|
||||
using namespace openspace;
|
||||
|
||||
if (global::windowDelegate->currentNode() != nodeIndex)
|
||||
return;
|
||||
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
if (id != "all") {
|
||||
TargetBrowserPair* pair = module->pair(id);
|
||||
if (pair) {
|
||||
pair->browser()->setIsInitialized(false);
|
||||
pair->browser()->setImageCollectionIsLoaded(false);
|
||||
pair->browser()->reload();
|
||||
}
|
||||
}
|
||||
else {
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
pair->browser()->setIsInitialized(false);
|
||||
pair->browser()->setImageCollectionIsLoaded(false);
|
||||
pair->browser()->reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes an index to an image and selects that image in the currently
|
||||
* selected sky browser.
|
||||
@@ -52,7 +83,7 @@ namespace {
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
TargetBrowserPair* selected = module->pair(module->selectedBrowserId());
|
||||
if (selected) {
|
||||
const ImageData& image = module->getWwtDataHandler()->getImage(imageIndex);
|
||||
const ImageData& image = module->wwtDataHandler().image(imageIndex);
|
||||
// Load image into browser
|
||||
std::string str = image.name;
|
||||
// Check if character is ASCII - if it isn't, remove
|
||||
@@ -147,7 +178,7 @@ namespace {
|
||||
TargetBrowserPair* pair = module->pair(identifier);
|
||||
if (pair) {
|
||||
pair->hideChromeInterface();
|
||||
pair->loadImageCollection(module->wwtImageCollectionUrl());
|
||||
pair->browser()->loadImageCollection(module->wwtImageCollectionUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +193,7 @@ namespace {
|
||||
// Set all border colors to the border color in the master node
|
||||
if (global::windowDelegate->isMaster()) {
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
glm::ivec3 color = pair->borderColor();
|
||||
@@ -194,7 +225,7 @@ namespace {
|
||||
// This is called when the sky_browser website is connected to OpenSpace
|
||||
// Send out identifiers to the browsers
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
pair->sendIdToBrowser();
|
||||
}
|
||||
@@ -257,9 +288,9 @@ namespace {
|
||||
// Send image list to GUI
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
std::string url = module->wwtImageCollectionUrl();
|
||||
// If no data has been loaded yet, download the data from the web!
|
||||
// If no data has been loaded yet, download the data from the web
|
||||
if (module->nLoadedImages() == 0) {
|
||||
std::filesystem::path directory = absPath("${MODULE_SKYBROWSER}/wwtimagedata/");
|
||||
std::filesystem::path directory = absPath("${SYNC}/wwtimagedata/");
|
||||
module->loadImages(url, directory);
|
||||
}
|
||||
|
||||
@@ -267,7 +298,7 @@ namespace {
|
||||
ghoul::Dictionary list;
|
||||
|
||||
for (int i = 0; i < module->nLoadedImages(); i++) {
|
||||
const ImageData& img = module->getWwtDataHandler()->getImage(i);
|
||||
const ImageData& img = module->wwtDataHandler().image(i);
|
||||
|
||||
// Push ("Key", value)
|
||||
ghoul::Dictionary image;
|
||||
@@ -327,7 +358,7 @@ namespace {
|
||||
|
||||
// Pass data for all the browsers and the corresponding targets
|
||||
if (module->isCameraInSolarSystem()) {
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
std::string id = pair->browserId();
|
||||
@@ -419,6 +450,10 @@ namespace {
|
||||
[[codegen::luawrap]] void createTargetBrowserPair() {
|
||||
using namespace openspace;
|
||||
|
||||
if (!global::windowDelegate->isMaster()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
|
||||
int uniqueIdentifier = module->uniqueIdentifierCounter();
|
||||
@@ -480,23 +515,23 @@ namespace {
|
||||
|
||||
global::scriptEngine->queueScript(
|
||||
"openspace.addScreenSpaceRenderable(" + browser + ");",
|
||||
scripting::ScriptEngine::RemoteScripting::No
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
global::scriptEngine->queueScript(
|
||||
"openspace.addSceneGraphNode(" + target + ");",
|
||||
scripting::ScriptEngine::RemoteScripting::No
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
global::scriptEngine->queueScript(
|
||||
"openspace.skybrowser.addPairToSkyBrowserModule('" + idTarget + "','"
|
||||
+ idBrowser + "');",
|
||||
scripting::ScriptEngine::RemoteScripting::No
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
global::scriptEngine->queueScript(
|
||||
"openspace.skybrowser.setSelectedBrowser('" + idBrowser + "');",
|
||||
scripting::ScriptEngine::RemoteScripting::No
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
@@ -561,7 +596,7 @@ namespace {
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
TargetBrowserPair* pair = module->pair(identifier);
|
||||
if (pair) {
|
||||
pair->removeSelectedImage(imageIndex);
|
||||
pair->browser()->removeSelectedImage(imageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +663,21 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an identifier to a sky browser and a radius value between 0 and 1, where 0 is
|
||||
* rectangular and 1 is circular
|
||||
*/
|
||||
[[codegen::luawrap]] void setBorderRadius(std::string identifier, double radius) {
|
||||
using namespace openspace;
|
||||
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
TargetBrowserPair* pair = module->pair(identifier);
|
||||
// Make sure the webpage has loaded properly before executing javascript on it
|
||||
if (pair && pair->browser()->isInitialized()) {
|
||||
pair->setBorderRadius(std::clamp(radius, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the screen space size of the sky browser to the numbers specified by the input
|
||||
* [x, y].
|
||||
@@ -722,7 +772,7 @@ namespace {
|
||||
std::for_each(
|
||||
images.rbegin(), images.rend(),
|
||||
[&](int index) {
|
||||
const ImageData& image = module->getWwtDataHandler()->getImage(index);
|
||||
const ImageData& image = module->wwtDataHandler().image(index);
|
||||
// Index of image is used as layer ID as it is unique in the image data set
|
||||
pair->browser()->addImageLayerToWwt(image.imageUrl, index);
|
||||
}
|
||||
@@ -737,7 +787,7 @@ namespace {
|
||||
[[codegen::luawrap]] void showAllTargetsAndBrowsers(bool show) {
|
||||
using namespace openspace;
|
||||
SkyBrowserModule* module = global::moduleEngine->module<SkyBrowserModule>();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->getPairs();
|
||||
const std::vector<std::unique_ptr<TargetBrowserPair>>& pairs = module->pairs();
|
||||
for (const std::unique_ptr<TargetBrowserPair>& pair : pairs) {
|
||||
pair->setEnabled(show);
|
||||
}
|
||||
|
||||
@@ -55,9 +55,6 @@ namespace {
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(Browser)]] Parameters {
|
||||
// [[codegen::verbatim(DimensionsInfo.description)]]
|
||||
std::optional<glm::vec2> dimensions;
|
||||
|
||||
// [[codegen::verbatim(UrlInfo.description)]]
|
||||
std::optional<std::string> url;
|
||||
|
||||
@@ -66,7 +63,6 @@ namespace {
|
||||
};
|
||||
|
||||
#include "browser_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
@@ -80,29 +76,21 @@ void Browser::RenderHandler::setTexture(GLuint t) {
|
||||
}
|
||||
|
||||
Browser::Browser(const ghoul::Dictionary& dictionary)
|
||||
: _browserPixeldimensions(
|
||||
: _browserDimensions(
|
||||
DimensionsInfo,
|
||||
glm::vec2(500.f),
|
||||
global::windowDelegate->currentSubwindowSize(),
|
||||
glm::vec2(10.f),
|
||||
glm::vec2(3000.f)
|
||||
)
|
||||
, _url(UrlInfo)
|
||||
, _reload(ReloadInfo)
|
||||
{
|
||||
if (dictionary.hasValue<std::string>(UrlInfo.identifier)) {
|
||||
_url = dictionary.value<std::string>(UrlInfo.identifier);
|
||||
}
|
||||
|
||||
// Handle target dimension property
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
_url = p.url.value_or(_url);
|
||||
_browserPixeldimensions = p.dimensions.value_or(_browserPixeldimensions);
|
||||
|
||||
glm::vec2 windowDimensions = global::windowDelegate->currentSubwindowSize();
|
||||
_browserPixeldimensions = windowDimensions;
|
||||
|
||||
_url.onChange([this]() { _isUrlDirty = true; });
|
||||
_browserPixeldimensions.onChange([this]() { _isDimensionsDirty = true; });
|
||||
|
||||
_browserDimensions.onChange([this]() { _isDimensionsDirty = true; });
|
||||
_reload.onChange([this]() { _shouldReload = true; });
|
||||
|
||||
// Create browser and render handler
|
||||
@@ -121,9 +109,9 @@ Browser::Browser(const ghoul::Dictionary& dictionary)
|
||||
|
||||
Browser::~Browser() {}
|
||||
|
||||
bool Browser::initializeGL() {
|
||||
void Browser::initializeGL() {
|
||||
_texture = std::make_unique<ghoul::opengl::Texture>(
|
||||
glm::uvec3(glm::ivec2(_browserPixeldimensions.value()), 1),
|
||||
glm::uvec3(glm::ivec2(_browserDimensions.value()), 1),
|
||||
GL_TEXTURE_2D
|
||||
);
|
||||
|
||||
@@ -131,7 +119,6 @@ bool Browser::initializeGL() {
|
||||
|
||||
_browserInstance->initialize();
|
||||
_browserInstance->loadUrl(_url);
|
||||
return isReady();
|
||||
}
|
||||
|
||||
void Browser::deinitializeGL() {
|
||||
@@ -164,11 +151,11 @@ void Browser::update() {
|
||||
_browserInstance->loadUrl(_url);
|
||||
_isUrlDirty = false;
|
||||
}
|
||||
|
||||
if (_isDimensionsDirty) {
|
||||
if (_browserPixeldimensions.value().x > 0 &&
|
||||
_browserPixeldimensions.value().y > 0)
|
||||
{
|
||||
_browserInstance->reshape(_browserPixeldimensions.value());
|
||||
glm::vec2 dim = _browserDimensions;
|
||||
if (dim.x > 0 && dim.y > 0) {
|
||||
_browserInstance->reshape(dim);
|
||||
_isDimensionsDirty = false;
|
||||
}
|
||||
}
|
||||
@@ -184,12 +171,16 @@ bool Browser::isReady() const {
|
||||
}
|
||||
|
||||
glm::vec2 Browser::browserPixelDimensions() const {
|
||||
return _browserPixeldimensions;
|
||||
return _browserDimensions;
|
||||
}
|
||||
|
||||
// Updates the browser size to match the size of the texture
|
||||
void Browser::updateBrowserSize() {
|
||||
_browserPixeldimensions = _texture->dimensions();
|
||||
_browserDimensions = _texture->dimensions();
|
||||
}
|
||||
|
||||
void Browser::reload() {
|
||||
_reload.set(true);
|
||||
}
|
||||
|
||||
float Browser::browserRatio() const {
|
||||
@@ -198,23 +189,19 @@ float Browser::browserRatio() const {
|
||||
}
|
||||
|
||||
void Browser::setCallbackDimensions(const std::function<void(const glm::dvec2&)>& func) {
|
||||
_browserPixeldimensions.onChange([&]() {
|
||||
func(_browserPixeldimensions.value());
|
||||
_browserDimensions.onChange([&]() {
|
||||
func(_browserDimensions.value());
|
||||
});
|
||||
}
|
||||
|
||||
void Browser::executeJavascript(const std::string& script) const {
|
||||
// Make sure that the browser has a main frame
|
||||
const bool browserExists = _browserInstance && _browserInstance->getBrowser();
|
||||
const bool frameIsLoaded = browserExists &&
|
||||
_browserInstance->getBrowser()->GetMainFrame();
|
||||
bool browserExists = _browserInstance && _browserInstance->getBrowser();
|
||||
bool frameIsLoaded = browserExists && _browserInstance->getBrowser()->GetMainFrame();
|
||||
|
||||
if (frameIsLoaded) {
|
||||
_browserInstance->getBrowser()->GetMainFrame()->ExecuteJavaScript(
|
||||
script,
|
||||
_browserInstance->getBrowser()->GetMainFrame()->GetURL(),
|
||||
0
|
||||
);
|
||||
CefRefPtr<CefFrame> frame = _browserInstance->getBrowser()->GetMainFrame();
|
||||
frame->ExecuteJavaScript(script, frame->GetURL(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,12 @@ namespace {
|
||||
"The thickness of the line of the target. The larger number, the thicker line"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo VerticalFovInfo = {
|
||||
"VerticalFov",
|
||||
"Vertical Field Of View",
|
||||
"The vertical field of view of the target."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableSkyTarget)]] Parameters {
|
||||
// [[codegen::verbatim(crossHairSizeInfo.description)]]
|
||||
std::optional<float> crossHairSize;
|
||||
@@ -77,6 +83,9 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(LineWidthInfo.description)]]
|
||||
std::optional<float> lineWidth;
|
||||
|
||||
// [[codegen::verbatim(VerticalFovInfo.description)]]
|
||||
std::optional<double> verticalFov;
|
||||
};
|
||||
|
||||
#include "renderableskytarget_codegen.cpp"
|
||||
@@ -93,6 +102,7 @@ RenderableSkyTarget::RenderableSkyTarget(const ghoul::Dictionary& dictionary)
|
||||
, _crossHairSize(crossHairSizeInfo, 2.f, 1.f, 10.f)
|
||||
, _showRectangleThreshold(RectangleThresholdInfo, 5.f, 0.1f, 70.f)
|
||||
, _lineWidth(LineWidthInfo, 13.f, 1.f, 100.f)
|
||||
, _verticalFov(VerticalFovInfo, 10.0, 0.00000000001, 70.0)
|
||||
, _borderColor(220, 220, 220)
|
||||
{
|
||||
// Handle target dimension property
|
||||
@@ -104,7 +114,12 @@ RenderableSkyTarget::RenderableSkyTarget(const ghoul::Dictionary& dictionary)
|
||||
_showRectangleThreshold = p.rectangleThreshold.value_or(_showRectangleThreshold);
|
||||
addProperty(_showRectangleThreshold);
|
||||
|
||||
_lineWidth = p.lineWidth.value_or(_lineWidth);
|
||||
addProperty(_lineWidth);
|
||||
|
||||
_verticalFov= p.verticalFov.value_or(_verticalFov);
|
||||
_verticalFov.setReadOnly(true);
|
||||
addProperty(_verticalFov);
|
||||
}
|
||||
|
||||
void RenderableSkyTarget::bindTexture() {}
|
||||
@@ -151,6 +166,7 @@ void RenderableSkyTarget::render(const RenderData& data, RendererTasks&) {
|
||||
_shader->setUniform("ratio", _ratio);
|
||||
_shader->setUniform("lineColor", color);
|
||||
_shader->setUniform("fov", static_cast<float>(_verticalFov));
|
||||
_shader->setUniform("borderRadius", static_cast<float>(_borderRadius));
|
||||
|
||||
glm::dvec3 objectPositionWorld = glm::dvec3(
|
||||
glm::translate(
|
||||
@@ -231,4 +247,8 @@ void RenderableSkyTarget::setVerticalFov(double fov) {
|
||||
_verticalFov = fov;
|
||||
}
|
||||
|
||||
void RenderableSkyTarget::setBorderRadius(double radius) {
|
||||
_borderRadius = radius;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -116,9 +116,10 @@ ScreenSpaceSkyBrowser::ScreenSpaceSkyBrowser(const ghoul::Dictionary& dictionary
|
||||
|
||||
addProperty(_isHidden);
|
||||
addProperty(_url);
|
||||
addProperty(_browserPixeldimensions);
|
||||
addProperty(_browserDimensions);
|
||||
addProperty(_reload);
|
||||
addProperty(_textureQuality);
|
||||
addProperty(_verticalFov);
|
||||
|
||||
_textureQuality.onChange([this]() { _textureDimensionsIsDirty = true; });
|
||||
|
||||
@@ -198,9 +199,10 @@ void ScreenSpaceSkyBrowser::updateTextureResolution() {
|
||||
float newResX = newResY * _ratio;
|
||||
glm::vec2 newSize = glm::vec2(newResX , newResY) * _textureQuality.value();
|
||||
|
||||
_browserPixeldimensions = glm::ivec2(newSize);
|
||||
_browserDimensions = glm::ivec2(newSize);
|
||||
_texture->setDimensions(glm::ivec3(newSize, 1));
|
||||
_objectSize = glm::ivec3(_texture->dimensions());
|
||||
_radiusIsDirty = true;
|
||||
}
|
||||
|
||||
void ScreenSpaceSkyBrowser::addDisplayCopy(const glm::vec3& raePosition, int nCopies) {
|
||||
@@ -236,7 +238,9 @@ void ScreenSpaceSkyBrowser::addDisplayCopy(const glm::vec3& raePosition, int nCo
|
||||
void ScreenSpaceSkyBrowser::removeDisplayCopy() {
|
||||
if (!_displayCopies.empty()) {
|
||||
removeProperty(_displayCopies.back().get());
|
||||
removeProperty(_showDisplayCopies.back().get());
|
||||
_displayCopies.pop_back();
|
||||
_showDisplayCopies.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,16 +326,22 @@ void ScreenSpaceSkyBrowser::update() {
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
WwtCommunicator::update();
|
||||
if (_radiusIsDirty && _isInitialized) {
|
||||
setBorderRadius(_borderRadius);
|
||||
_radiusIsDirty = false;
|
||||
}
|
||||
|
||||
ScreenSpaceRenderable::update();
|
||||
WwtCommunicator::update();
|
||||
}
|
||||
|
||||
void ScreenSpaceSkyBrowser::setVerticalFovWithScroll(float scroll) {
|
||||
double ScreenSpaceSkyBrowser::setVerticalFovWithScroll(float scroll) {
|
||||
// Make scroll more sensitive the smaller the FOV
|
||||
double x = _verticalFov;
|
||||
double zoomFactor = atan(x / 50.0) + exp(x / 40.0) - 0.99999999999999999999999999999;
|
||||
double zoom = scroll > 0.0 ? zoomFactor : -zoomFactor;
|
||||
_verticalFov = std::clamp(_verticalFov + zoom, 0.0, 70.0);
|
||||
return _verticalFov;
|
||||
}
|
||||
|
||||
void ScreenSpaceSkyBrowser::bindTexture() {
|
||||
|
||||
@@ -40,6 +40,22 @@
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
void aimTargetGalactic(std::string id, glm::dvec3 direction) {
|
||||
glm::dvec3 positionCelestial = glm::normalize(direction) *
|
||||
openspace::skybrowser::CelestialSphereRadius;
|
||||
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(positionCelestial)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
openspace::scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode,
|
||||
@@ -53,31 +69,10 @@ TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode,
|
||||
_targetRenderable = dynamic_cast<RenderableSkyTarget*>(_targetNode->renderable());
|
||||
}
|
||||
|
||||
TargetBrowserPair& TargetBrowserPair::operator=(TargetBrowserPair other) {
|
||||
std::swap(_targetNode, other._targetNode);
|
||||
std::swap(_browser, other._browser);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setImageOrder(int i, int order) {
|
||||
_browser->setImageOrder(i, order);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::aimTargetGalactic(glm::dvec3 direction) {
|
||||
std::string id = _targetNode->identifier();
|
||||
glm::dvec3 positionCelestial = glm::normalize(direction) *
|
||||
skybrowser::CelestialSphereRadius;
|
||||
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});",
|
||||
id, ghoul::to_string(positionCelestial)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::startFinetuningTarget() {
|
||||
_startTargetPosition = _targetNode->worldPosition();
|
||||
}
|
||||
@@ -90,9 +85,13 @@ void TargetBrowserPair::fineTuneTarget(const glm::vec2& translation)
|
||||
glm::dvec2 startRaDec = skybrowser::cartesianToSpherical(
|
||||
skybrowser::galacticToEquatorial(glm::normalize(_startTargetPosition))
|
||||
);
|
||||
|
||||
glm::dvec2 newRaDec = startRaDec + glm::dvec2(translation);
|
||||
glm::dvec3 newCartesian = skybrowser::sphericalToCartesian(newRaDec);
|
||||
aimTargetGalactic(skybrowser::equatorialToGalactic(newCartesian));
|
||||
aimTargetGalactic(
|
||||
_targetNode->identifier(),
|
||||
skybrowser::equatorialToGalactic(newCartesian)
|
||||
);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::synchronizeAim() {
|
||||
@@ -108,11 +107,6 @@ void TargetBrowserPair::setEnabled(bool enable) {
|
||||
_targetRenderable->property("Enabled")->set(enable);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setOpacity(float opacity) {
|
||||
_browser->property("Opacity")->set(opacity);
|
||||
_targetRenderable->property("Opacity")->set(opacity);
|
||||
}
|
||||
|
||||
bool TargetBrowserPair::isEnabled() const {
|
||||
return _targetRenderable->isEnabled() || _browser->isEnabled();
|
||||
}
|
||||
@@ -157,10 +151,6 @@ std::string TargetBrowserPair::targetNodeId() const {
|
||||
return _targetNode->identifier();
|
||||
}
|
||||
|
||||
float TargetBrowserPair::browserRatio() const {
|
||||
return _browser->browserRatio();
|
||||
}
|
||||
|
||||
double TargetBrowserPair::verticalFov() const {
|
||||
return _browser->verticalFov();
|
||||
}
|
||||
@@ -182,12 +172,13 @@ ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const {
|
||||
res.setValue("roll", targetRoll());
|
||||
res.setValue("color", borderColor());
|
||||
res.setValue("cartesianDirection", cartesian);
|
||||
res.setValue("ratio", static_cast<double>(browserRatio()));
|
||||
res.setValue("ratio", static_cast<double>(_browser->browserRatio()));
|
||||
res.setValue("isFacingCamera", isFacingCamera());
|
||||
res.setValue("isUsingRae", isUsingRadiusAzimuthElevation());
|
||||
res.setValue("selectedImages", selectedImages());
|
||||
res.setValue("scale", static_cast<double>(_browser->scale()));
|
||||
res.setValue("opacities", _browser->opacities());
|
||||
res.setValue("borderRadius", _browser->borderRadius());
|
||||
|
||||
std::vector<std::pair<std::string, glm::dvec3>> copies = displayCopies();
|
||||
std::vector<std::pair<std::string, bool>> showCopies = _browser->showDisplayCopies();
|
||||
@@ -240,19 +231,10 @@ void TargetBrowserPair::hideChromeInterface() {
|
||||
void TargetBrowserPair::sendIdToBrowser() const {
|
||||
_browser->setIdInBrowser();
|
||||
}
|
||||
|
||||
void TargetBrowserPair::updateBrowserSize() {
|
||||
_browser->updateBrowserSize();
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, glm::dvec3>> TargetBrowserPair::displayCopies() const {
|
||||
return _browser->displayCopies();
|
||||
}
|
||||
|
||||
bool TargetBrowserPair::isImageCollectionLoaded() {
|
||||
return _browser->isImageCollectionLoaded();
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setVerticalFov(double vfov) {
|
||||
_browser->setVerticalFov(vfov);
|
||||
_targetRenderable->setVerticalFov(vfov);
|
||||
@@ -260,6 +242,7 @@ void TargetBrowserPair::setVerticalFov(double vfov) {
|
||||
|
||||
void TargetBrowserPair::setEquatorialAim(const glm::dvec2& aim) {
|
||||
aimTargetGalactic(
|
||||
_targetNode->identifier(),
|
||||
skybrowser::equatorialToGalactic(skybrowser::sphericalToCartesian(aim))
|
||||
);
|
||||
_browser->setEquatorialAim(aim);
|
||||
@@ -270,13 +253,19 @@ void TargetBrowserPair::setBorderColor(const glm::ivec3& color) {
|
||||
_browser->setBorderColor(color);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setBorderRadius(double radius) {
|
||||
_browser->setBorderRadius(radius);
|
||||
_targetRenderable->setBorderRadius(radius);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setBrowserRatio(float ratio) {
|
||||
_browser->setRatio(ratio);
|
||||
_targetRenderable->setRatio(ratio);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setVerticalFovWithScroll(float scroll) {
|
||||
_browser->setVerticalFovWithScroll(scroll);
|
||||
double fov = _browser->setVerticalFovWithScroll(scroll);
|
||||
_targetRenderable->setVerticalFov(fov);
|
||||
}
|
||||
|
||||
void TargetBrowserPair::setImageCollectionIsLoaded(bool isLoaded) {
|
||||
@@ -286,16 +275,16 @@ void TargetBrowserPair::setImageCollectionIsLoaded(bool isLoaded) {
|
||||
void TargetBrowserPair::incrementallyAnimateToCoordinate() {
|
||||
// Animate the target before the field of view starts to animate
|
||||
if (_targetAnimation.isAnimating()) {
|
||||
aimTargetGalactic(_targetAnimation.getNewValue());
|
||||
aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue());
|
||||
}
|
||||
else if (!_targetAnimation.isAnimating() && _targetIsAnimating) {
|
||||
// Set the finished position
|
||||
aimTargetGalactic(_targetAnimation.getNewValue());
|
||||
aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue());
|
||||
_fovAnimation.start();
|
||||
_targetIsAnimating = false;
|
||||
}
|
||||
if (_fovAnimation.isAnimating()) {
|
||||
_browser->setVerticalFov(_fovAnimation.getNewValue());
|
||||
_browser->setVerticalFov(_fovAnimation.newValue());
|
||||
_targetRenderable->setVerticalFov(_browser->verticalFov());
|
||||
}
|
||||
}
|
||||
@@ -369,20 +358,8 @@ bool TargetBrowserPair::isUsingRadiusAzimuthElevation() const {
|
||||
return _browser->isUsingRaeCoords();
|
||||
}
|
||||
|
||||
SceneGraphNode* TargetBrowserPair::targetNode() const {
|
||||
return _targetNode;
|
||||
}
|
||||
|
||||
ScreenSpaceSkyBrowser* TargetBrowserPair::browser() const {
|
||||
return _browser;
|
||||
}
|
||||
|
||||
bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) {
|
||||
return lhs._targetNode == rhs._targetNode && lhs._browser == rhs._browser;
|
||||
}
|
||||
|
||||
bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -32,6 +32,20 @@
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
// Galactic coordinates are projected onto the celestial sphere
|
||||
// Equatorial coordinates are unit length
|
||||
// Conversion spherical <-> Cartesian
|
||||
|
||||
// Conversion matrix - J2000 equatorial <-> galactic
|
||||
// https://arxiv.org/abs/1010.3773v1
|
||||
constexpr glm::dmat3 ConversionMatrix = glm::dmat3(
|
||||
-0.054875539390, 0.494109453633, -0.867666135681, // col 0
|
||||
-0.873437104725, -0.444829594298, -0.198076389622, // col 1
|
||||
-0.483834991775, 0.746982248696, 0.455983794523 // col 2
|
||||
);
|
||||
} // namespace
|
||||
|
||||
namespace openspace::skybrowser {
|
||||
|
||||
// Converts from spherical coordinates in the unit of degrees to cartesian coordianates
|
||||
@@ -53,26 +67,26 @@ glm::dvec2 cartesianToSpherical(const glm::dvec3& coord) {
|
||||
double ra = atan2(coord.y, coord.x);
|
||||
double dec = atan2(coord.z, glm::sqrt((coord.x * coord.x) + (coord.y * coord.y)));
|
||||
|
||||
ra = ra > 0 ? ra : ra + glm::two_pi<double>();
|
||||
ra = ra > 0.0 ? ra : ra + glm::two_pi<double>();
|
||||
|
||||
glm::dvec2 celestialCoords = glm::dvec2(ra, dec);
|
||||
return glm::degrees(celestialCoords);
|
||||
}
|
||||
|
||||
glm::dvec3 galacticToEquatorial(const glm::dvec3& coords) {
|
||||
return glm::transpose(conversionMatrix) * glm::normalize(coords);
|
||||
return glm::transpose(ConversionMatrix) * glm::normalize(coords);
|
||||
}
|
||||
|
||||
glm::dvec3 equatorialToGalactic(const glm::dvec3& coords) {
|
||||
// On the unit sphere
|
||||
glm::dvec3 rGalactic = conversionMatrix * glm::normalize(coords);
|
||||
glm::dvec3 rGalactic = ConversionMatrix * glm::normalize(coords);
|
||||
return rGalactic;
|
||||
}
|
||||
|
||||
glm::dvec3 localCameraToScreenSpace3d(const glm::dvec3& coords) {
|
||||
// Ensure that if the coord is behind the camera,
|
||||
// the converted coordinate will be there too
|
||||
double zCoord = coords.z > 0 ? -ScreenSpaceZ : ScreenSpaceZ;
|
||||
double zCoord = coords.z > 0.0 ? -ScreenSpaceZ : ScreenSpaceZ;
|
||||
|
||||
// Calculate screen space coords x and y
|
||||
double tanX = coords.x / coords.z;
|
||||
@@ -91,15 +105,15 @@ glm::dvec3 localCameraToGalactic(const glm::dvec3& coords) {
|
||||
// Subtract camera position to get the view direction
|
||||
glm::dvec3 galactic = glm::dvec3(camMat * coordsVec4) - camPos;
|
||||
|
||||
return glm::normalize(galactic) * skybrowser::CelestialSphereRadius;
|
||||
return glm::normalize(galactic) * CelestialSphereRadius;
|
||||
}
|
||||
|
||||
glm::dvec3 localCameraToEquatorial(const glm::dvec3& coords) {
|
||||
// Calculate the galactic coordinate of the target direction
|
||||
// projected onto the celestial sphere
|
||||
glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3();
|
||||
glm::dvec3 galactic = camPos + skybrowser::localCameraToGalactic(coords);
|
||||
return skybrowser::galacticToEquatorial(galactic);
|
||||
glm::dvec3 galactic = camPos + localCameraToGalactic(coords);
|
||||
return galacticToEquatorial(galactic);
|
||||
}
|
||||
|
||||
glm::dvec3 equatorialToLocalCamera(const glm::dvec3& coords) {
|
||||
@@ -117,8 +131,10 @@ glm::dvec3 galacticToLocalCamera(const glm::dvec3& coords) {
|
||||
}
|
||||
|
||||
double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) {
|
||||
glm::dvec3 upJ2000 = skybrowser::galacticToEquatorial(up);
|
||||
glm::dvec3 forwardJ2000 = skybrowser::galacticToEquatorial(forward);
|
||||
constexpr glm::dvec3 NorthPole = glm::dvec3(0.0, 0.0, 1.0);
|
||||
|
||||
glm::dvec3 upJ2000 = galacticToEquatorial(up);
|
||||
glm::dvec3 forwardJ2000 = galacticToEquatorial(forward);
|
||||
|
||||
glm::dvec3 crossUpNorth = glm::cross(upJ2000, NorthPole);
|
||||
double dotNorthUp = glm::dot(NorthPole, upJ2000);
|
||||
@@ -129,14 +145,15 @@ double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) {
|
||||
|
||||
glm::dvec3 cameraDirectionEquatorial() {
|
||||
// Get the view direction of the screen in cartesian J2000 coordinates
|
||||
return galacticToEquatorial(cameraDirectionGalactic());
|
||||
glm::dvec3 camDirGalactic = cameraDirectionGalactic();
|
||||
return galacticToEquatorial(camDirGalactic);
|
||||
}
|
||||
|
||||
glm::dvec3 cameraDirectionGalactic() {
|
||||
// Get the view direction of the screen in galactic coordinates
|
||||
glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3();
|
||||
glm::dvec3 view = global::navigationHandler->camera()->viewDirectionWorldSpace();
|
||||
glm::dvec3 galCoord = camPos + (skybrowser::CelestialSphereRadius * view);
|
||||
glm::dvec3 galCoord = camPos + CelestialSphereRadius * view;
|
||||
|
||||
return galCoord;
|
||||
}
|
||||
@@ -150,11 +167,10 @@ bool isCoordinateInView(const glm::dvec3& equatorial) {
|
||||
// Check if image coordinate is within current FOV
|
||||
glm::dvec3 localCamera = equatorialToLocalCamera(equatorial);
|
||||
glm::dvec3 coordsScreen = localCameraToScreenSpace3d(localCamera);
|
||||
double r = static_cast<float>(windowRatio());
|
||||
|
||||
bool isCoordInView = abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f &&
|
||||
coordsScreen.z < 0;
|
||||
double r = windowRatio();
|
||||
|
||||
bool isCoordInView =
|
||||
abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f && coordsScreen.z < 0.f;
|
||||
return isCoordInView;
|
||||
}
|
||||
|
||||
@@ -200,31 +216,18 @@ glm::dmat4 incrementalAnimationMatrix(const glm::dvec3& start, const glm::dvec3&
|
||||
}
|
||||
|
||||
double sizeFromFov(double fov, glm::dvec3 worldPosition) {
|
||||
|
||||
// Calculate the size with trigonometry
|
||||
// /|
|
||||
// /_| Adjacent is the horizontal line, opposite the vertical
|
||||
// \ | Calculate for half the triangle first, then multiply with 2
|
||||
// \|
|
||||
double adjacent = glm::length(worldPosition);
|
||||
double opposite = 2 * adjacent * glm::tan(glm::radians(fov * 0.5));
|
||||
double opposite = 2.0 * adjacent * glm::tan(glm::radians(fov * 0.5));
|
||||
return opposite;
|
||||
}
|
||||
|
||||
template <>
|
||||
float Animation<float>::getNewValue() {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
else {
|
||||
float percentage = static_cast<float>(percentageSpent());
|
||||
float diff = static_cast<float>((_goal - _start) * ghoul::exponentialEaseOut(percentage));
|
||||
return _start + diff;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
double Animation<double>::getNewValue() {
|
||||
double Animation<double>::newValue() const {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
@@ -236,7 +239,7 @@ double Animation<double>::getNewValue() {
|
||||
}
|
||||
|
||||
template <>
|
||||
glm::dmat4 Animation<glm::dvec3>::getRotationMatrix() {
|
||||
glm::dmat4 Animation<glm::dvec3>::rotationMatrix() {
|
||||
if (!isAnimating()) {
|
||||
return glm::dmat4(1.0);
|
||||
}
|
||||
@@ -254,7 +257,7 @@ glm::dmat4 Animation<glm::dvec3>::getRotationMatrix() {
|
||||
}
|
||||
|
||||
template <>
|
||||
glm::dvec3 Animation<glm::dvec3>::getNewValue() {
|
||||
glm::dvec3 Animation<glm::dvec3>::newValue() const {
|
||||
if (!isAnimating()) {
|
||||
return _goal;
|
||||
}
|
||||
|
||||
@@ -32,15 +32,137 @@
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "WwtCommunicator";
|
||||
|
||||
// WWT messages
|
||||
ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov,
|
||||
double roll)
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "center_on_coordinates"s);
|
||||
msg.setValue("ra", celestCoords.x);
|
||||
msg.setValue("dec", celestCoords.y);
|
||||
msg.setValue("fov", fov);
|
||||
msg.setValue("roll", roll);
|
||||
msg.setValue("instant", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary loadCollectionMessage(const std::string& url) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "load_image_collection"s);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("loadChildFolders", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setForegroundMessage(const std::string& name) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "set_foreground_by_name"s);
|
||||
msg.setValue("name", name);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary addImageMessage(const std::string& id, const std::string& url) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_create"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("mode", "preloaded"s);
|
||||
msg.setValue("goto", false);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary removeImageMessage(const std::string& imageId) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_remove"s);
|
||||
msg.setValue("id", imageId);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setImageOpacityMessage(const std::string& imageId, double opacity) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_set"s);
|
||||
msg.setValue("id", imageId);
|
||||
msg.setValue("setting", "opacity"s);
|
||||
msg.setValue("value", opacity);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary setLayerOrderMessage(const std::string& id, int order) {
|
||||
static int MessageCounter = 0;
|
||||
|
||||
// The lower the layer order, the more towards the back the image is placed
|
||||
// 0 is the background
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_order"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("order", order);
|
||||
msg.setValue("version", MessageCounter);
|
||||
|
||||
MessageCounter++;
|
||||
return msg;
|
||||
}
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo VerticalFovInfo = {
|
||||
"VerticalFov",
|
||||
"Vertical Field Of View",
|
||||
"The vertical field of view of the target."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(WwtCommunicator)]] Parameters {
|
||||
// [[codegen::verbatim(VerticalFovInfo.description)]]
|
||||
std::optional<double> verticalFov;
|
||||
};
|
||||
#include "wwtcommunicator_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
WwtCommunicator::WwtCommunicator(const ghoul::Dictionary& dictionary)
|
||||
: Browser(dictionary)
|
||||
{}
|
||||
, _verticalFov(VerticalFovInfo, 10.0, 0.00000000001, 70.0)
|
||||
{
|
||||
// Handle target dimension property
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
_verticalFov = p.verticalFov.value_or(_verticalFov);
|
||||
_verticalFov.setReadOnly(true);
|
||||
}
|
||||
|
||||
WwtCommunicator::~WwtCommunicator() {}
|
||||
void WwtCommunicator::update() {
|
||||
// Cap how messages are passed
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime;
|
||||
|
||||
if (timeSinceLastUpdate > TimeUpdateInterval) {
|
||||
if (_equatorialAimIsDirty) {
|
||||
updateAim();
|
||||
_equatorialAimIsDirty = false;
|
||||
}
|
||||
if (_borderColorIsDirty) {
|
||||
updateBorderColor();
|
||||
_borderColorIsDirty = false;
|
||||
}
|
||||
_lastUpdateTime = std::chrono::system_clock::now();
|
||||
}
|
||||
if (_shouldReload) {
|
||||
_isImageCollectionLoaded = false;
|
||||
}
|
||||
Browser::update();
|
||||
}
|
||||
|
||||
void WwtCommunicator::selectImage(const std::string& url, int i) {
|
||||
// Ensure there are no duplicates
|
||||
@@ -66,7 +188,6 @@ void WwtCommunicator::addImageLayerToWwt(const std::string& url, int i) {
|
||||
void WwtCommunicator::removeSelectedImage(int i) {
|
||||
// Remove from selected list
|
||||
auto it = findSelectedImage(i);
|
||||
|
||||
if (it != _selectedImages.end()) {
|
||||
_selectedImages.erase(it);
|
||||
sendMessageToWwt(removeImageMessage(std::to_string(i)));
|
||||
@@ -74,26 +195,38 @@ void WwtCommunicator::removeSelectedImage(int i) {
|
||||
}
|
||||
|
||||
void WwtCommunicator::sendMessageToWwt(const ghoul::Dictionary& msg) const {
|
||||
std::string script = "sendMessageToWWT(" + ghoul::formatJson(msg) + ");";
|
||||
executeJavascript(script);
|
||||
std::string m = ghoul::formatJson(msg);
|
||||
executeJavascript(fmt::format("sendMessageToWWT({});", m));
|
||||
}
|
||||
|
||||
std::vector<int> WwtCommunicator::selectedImages() const {
|
||||
std::vector<int> selectedImagesVector;
|
||||
for (const std::pair<int, double>& image : _selectedImages) {
|
||||
selectedImagesVector.push_back(image.first);
|
||||
}
|
||||
selectedImagesVector.resize(_selectedImages.size());
|
||||
std::transform(
|
||||
_selectedImages.cbegin(),
|
||||
_selectedImages.cend(),
|
||||
selectedImagesVector.begin(),
|
||||
[](const std::pair<int, double>& image) { return image.first; }
|
||||
);
|
||||
return selectedImagesVector;
|
||||
}
|
||||
|
||||
std::vector<double> WwtCommunicator::opacities() const {
|
||||
std::vector<double> opacities;
|
||||
for (const std::pair<int, double>& image : _selectedImages) {
|
||||
opacities.push_back(image.second);
|
||||
}
|
||||
opacities.resize(_selectedImages.size());
|
||||
std::transform(
|
||||
_selectedImages.cbegin(),
|
||||
_selectedImages.cend(),
|
||||
opacities.begin(),
|
||||
[](const std::pair<int, double>& image) { return image.second; }
|
||||
);
|
||||
return opacities;
|
||||
}
|
||||
|
||||
double WwtCommunicator::borderRadius() const {
|
||||
return _borderRadius;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setTargetRoll(double roll) {
|
||||
_targetRoll = roll;
|
||||
}
|
||||
@@ -103,12 +236,6 @@ void WwtCommunicator::setVerticalFov(double vfov) {
|
||||
_equatorialAimIsDirty = true;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setWebpageBorderColor(glm::ivec3 color) const {
|
||||
std::string stringColor = fmt::format("{},{},{}", color.x, color.y, color.z);
|
||||
std::string scr = "document.body.style.backgroundColor = 'rgb(" + stringColor + ")';";
|
||||
executeJavascript(scr);
|
||||
}
|
||||
|
||||
void WwtCommunicator::setEquatorialAim(glm::dvec2 equatorial) {
|
||||
_equatorialAim = std::move(equatorial);
|
||||
_equatorialAimIsDirty = true;
|
||||
@@ -119,8 +246,18 @@ void WwtCommunicator::setBorderColor(glm::ivec3 color) {
|
||||
_borderColorIsDirty = true;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setBorderRadius(double radius) {
|
||||
_borderRadius = radius;
|
||||
std::string scr = fmt::format("setBorderRadius({});", radius);
|
||||
executeJavascript(scr);
|
||||
}
|
||||
|
||||
void WwtCommunicator::updateBorderColor() const {
|
||||
setWebpageBorderColor(_borderColor);
|
||||
std::string script = fmt::format(
|
||||
"setBackgroundColor('rgb({},{},{})');",
|
||||
_borderColor.x, _borderColor.y, _borderColor.z
|
||||
);
|
||||
executeJavascript(script);
|
||||
}
|
||||
|
||||
void WwtCommunicator::updateAim() const {
|
||||
@@ -130,8 +267,9 @@ void WwtCommunicator::updateAim() const {
|
||||
}
|
||||
|
||||
glm::dvec2 WwtCommunicator::fieldsOfView() const {
|
||||
glm::dvec2 browserFov = glm::dvec2(verticalFov() * browserRatio(), verticalFov());
|
||||
return browserFov;
|
||||
const double vFov = verticalFov();
|
||||
const double hFov = vFov * browserRatio();
|
||||
return glm::dvec2(hFov, vFov);
|
||||
}
|
||||
|
||||
bool WwtCommunicator::isImageCollectionLoaded() const {
|
||||
@@ -139,10 +277,11 @@ bool WwtCommunicator::isImageCollectionLoaded() const {
|
||||
}
|
||||
|
||||
std::deque<std::pair<int, double>>::iterator WwtCommunicator::findSelectedImage(int i) {
|
||||
auto it = std::find_if(_selectedImages.begin(), _selectedImages.end(),
|
||||
[i](std::pair<int, double>& pair) {
|
||||
return (pair.first == i);
|
||||
});
|
||||
auto it = std::find_if(
|
||||
_selectedImages.begin(),
|
||||
_selectedImages.end(),
|
||||
[i](const std::pair<int, double>& pair) { return pair.first == i; }
|
||||
);
|
||||
return it;
|
||||
}
|
||||
|
||||
@@ -186,35 +325,13 @@ void WwtCommunicator::hideChromeInterface() const {
|
||||
executeJavascript(script);
|
||||
}
|
||||
|
||||
void WwtCommunicator::update() {
|
||||
// Cap how messages are passed
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime;
|
||||
|
||||
if (timeSinceLastUpdate > TimeUpdateInterval) {
|
||||
if (_equatorialAimIsDirty) {
|
||||
updateAim();
|
||||
_equatorialAimIsDirty = false;
|
||||
}
|
||||
if (_borderColorIsDirty) {
|
||||
updateBorderColor();
|
||||
_borderColorIsDirty = false;
|
||||
}
|
||||
_lastUpdateTime = std::chrono::system_clock::now();
|
||||
}
|
||||
if (_shouldReload) {
|
||||
_isImageCollectionLoaded = false;
|
||||
}
|
||||
Browser::update();
|
||||
}
|
||||
|
||||
void WwtCommunicator::setImageCollectionIsLoaded(bool isLoaded) {
|
||||
_isImageCollectionLoaded = isLoaded;
|
||||
}
|
||||
|
||||
void WwtCommunicator::setIdInBrowser(const std::string& id) const {
|
||||
// Send ID to it's browser
|
||||
executeJavascript("setId('" + id + "')");
|
||||
// Send ID to its browser
|
||||
executeJavascript(fmt::format("setId('{}')", id));
|
||||
}
|
||||
|
||||
glm::ivec3 WwtCommunicator::borderColor() const {
|
||||
@@ -225,92 +342,4 @@ double WwtCommunicator::verticalFov() const {
|
||||
return _verticalFov;
|
||||
}
|
||||
|
||||
// WWT messages
|
||||
ghoul::Dictionary WwtCommunicator::moveCameraMessage(const glm::dvec2& celestCoords,
|
||||
double fov, double roll,
|
||||
bool shouldMoveInstantly) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "center_on_coordinates"s);
|
||||
msg.setValue("ra", celestCoords.x);
|
||||
msg.setValue("dec", celestCoords.y);
|
||||
msg.setValue("fov", fov);
|
||||
msg.setValue("roll", roll);
|
||||
msg.setValue("instant", shouldMoveInstantly);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::loadCollectionMessage(const std::string& url) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "load_image_collection"s);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("loadChildFolders", true);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setForegroundMessage(const std::string& name) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "set_foreground_by_name"s);
|
||||
msg.setValue("name", name);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::addImageMessage(const std::string& id,
|
||||
const std::string& url) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_create"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("url", url);
|
||||
msg.setValue("mode", "preloaded"s);
|
||||
msg.setValue("goto", false);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::removeImageMessage(const std::string& imageId) const {
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_remove"s);
|
||||
msg.setValue("id", imageId);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setImageOpacityMessage(const std::string& imageId,
|
||||
double opacity) const
|
||||
{
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_set"s);
|
||||
msg.setValue("id", imageId);
|
||||
msg.setValue("setting", "opacity"s);
|
||||
msg.setValue("value", opacity);
|
||||
return msg;
|
||||
}
|
||||
|
||||
ghoul::Dictionary WwtCommunicator::setLayerOrderMessage(const std::string& id, int order)
|
||||
{
|
||||
// The lower the layer order, the more towards the back the image is placed
|
||||
// 0 is the background
|
||||
using namespace std::string_literals;
|
||||
|
||||
ghoul::Dictionary msg;
|
||||
msg.setValue("event", "image_layer_order"s);
|
||||
msg.setValue("id", id);
|
||||
msg.setValue("order", order);
|
||||
msg.setValue("version", messageCounter);
|
||||
|
||||
messageCounter++;
|
||||
return msg;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,239 +25,316 @@
|
||||
#include <modules/skybrowser/include/wwtdatahandler.h>
|
||||
|
||||
#include <modules/skybrowser/include/utility.h>
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/util/httprequest.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string_view>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsuggest-override"
|
||||
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
|
||||
#endif
|
||||
|
||||
#include <modules/skybrowser/ext/tinyxml2/tinyxml2.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "WwtDataHandler";
|
||||
|
||||
constexpr std::string_view Thumbnail = "Thumbnail";
|
||||
constexpr std::string_view Name = "Name";
|
||||
constexpr std::string_view ImageSet = "ImageSet";
|
||||
constexpr std::string_view Dec = "Dec";
|
||||
constexpr std::string_view RA = "RA";
|
||||
constexpr std::string_view Undefined = "";
|
||||
constexpr std::string_view Folder = "Folder";
|
||||
constexpr std::string_view Place = "Place";
|
||||
constexpr std::string_view ThumbnailUrl = "ThumbnailUrl";
|
||||
constexpr std::string_view Url = "Url";
|
||||
constexpr std::string_view Credits = "Credits";
|
||||
constexpr std::string_view CreditsUrl = "CreditsUrl";
|
||||
constexpr std::string_view ZoomLevel = "ZoomLevel";
|
||||
constexpr std::string_view DataSetType = "DataSetType";
|
||||
constexpr std::string_view Sky = "Sky";
|
||||
|
||||
bool hasAttribute(const tinyxml2::XMLElement* element, std::string_view name) {
|
||||
std::string n = std::string(name);
|
||||
return element->FindAttribute(n.c_str());
|
||||
}
|
||||
|
||||
std::string attribute(const tinyxml2::XMLElement* element, std::string_view name) {
|
||||
if (hasAttribute(element, name)) {
|
||||
std::string n = std::string(name);
|
||||
return element->FindAttribute(n.c_str())->Value();
|
||||
}
|
||||
return std::string(Undefined);
|
||||
}
|
||||
|
||||
// Parsing and downloading of wtml files
|
||||
bool downloadFile(const std::string& url, const std::filesystem::path& destination) {
|
||||
using namespace openspace;
|
||||
|
||||
HttpFileDownload wtmlRoot(url, destination, HttpFileDownload::Overwrite::Yes);
|
||||
wtmlRoot.start(std::chrono::milliseconds(10000));
|
||||
return wtmlRoot.wait();
|
||||
}
|
||||
|
||||
bool directoryExists(const std::filesystem::path& path) {
|
||||
return std::filesystem::exists(path) && std::filesystem::is_directory(path);
|
||||
}
|
||||
|
||||
const tinyxml2::XMLElement* directChildNode(const tinyxml2::XMLElement* node,
|
||||
std::string_view name)
|
||||
{
|
||||
while (node && node->Name() != name) {
|
||||
node = node->FirstChildElement();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
const tinyxml2::XMLElement* childNode(const tinyxml2::XMLElement* node,
|
||||
std::string_view name)
|
||||
{
|
||||
const tinyxml2::XMLElement* child = node->FirstChildElement();
|
||||
|
||||
// Traverse the children and look at all their first child to find ImageSet
|
||||
while (child) {
|
||||
const tinyxml2::XMLElement* imageSet = directChildNode(child, name);
|
||||
if (imageSet) {
|
||||
return imageSet;
|
||||
}
|
||||
child = child->NextSiblingElement();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string childNodeContentFromImageSet(const tinyxml2::XMLElement* imageSet,
|
||||
std::string_view elementName)
|
||||
{
|
||||
// Find the thumbnail image url
|
||||
// The thumbnail is the last node so traverse backwards for speed
|
||||
std::string n = std::string(elementName);
|
||||
const tinyxml2::XMLElement* child = imageSet->FirstChildElement(n.c_str());
|
||||
return child && child->GetText() ? child->GetText() : std::string(Undefined);
|
||||
}
|
||||
|
||||
std::string urlFromPlace(const tinyxml2::XMLElement* place) {
|
||||
// If the place has a thumbnail url, return it
|
||||
if (hasAttribute(place, Thumbnail)) {
|
||||
return attribute(place, Thumbnail);
|
||||
}
|
||||
|
||||
// If the place doesn't have a thumbnail url data attribute,
|
||||
// Load the image set it stores instead
|
||||
const tinyxml2::XMLElement* imageSet = childNode(place, ImageSet);
|
||||
|
||||
// If there is an imageSet, collect thumbnail url, if it doesn't contain an
|
||||
// ImageSet, it doesn't have an url
|
||||
return imageSet ?
|
||||
childNodeContentFromImageSet(imageSet, ThumbnailUrl) :
|
||||
std::string(Undefined);
|
||||
}
|
||||
|
||||
bool downloadWtmlFiles(const std::filesystem::path& directory, const std::string& url,
|
||||
const std::string& fileName)
|
||||
{
|
||||
using namespace openspace;
|
||||
// Download file from url
|
||||
std::filesystem::path file = directory.string() + fileName + ".aspx";
|
||||
const bool success = downloadFile(url, file);
|
||||
if (!success) {
|
||||
LINFO(fmt::format(
|
||||
"Could not download file '{}' to directory {}", url, directory
|
||||
));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse file to XML
|
||||
auto document = std::make_unique<tinyxml2::XMLDocument>();
|
||||
document->LoadFile(file.string().c_str());
|
||||
|
||||
// Search XML file for folders with urls
|
||||
const tinyxml2::XMLElement* root = document->RootElement();
|
||||
const tinyxml2::XMLElement* element = root->FirstChildElement(Folder.data());
|
||||
const bool folderExists = element != nullptr;
|
||||
const bool folderContainNoUrls = folderExists && !hasAttribute(element, Url);
|
||||
|
||||
// If the file contains no folders, or there are folders but without urls,
|
||||
// stop recursion
|
||||
if (!folderExists || folderContainNoUrls) {
|
||||
LINFO(fmt::format("Saving {}", url));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterate through all the folders in the XML file
|
||||
while (element && std::string(element->Value()) == Folder) {
|
||||
// If folder contains urls, download and parse those urls
|
||||
if (hasAttribute(element, Url) && hasAttribute(element, Name)) {
|
||||
std::string urlAttr = attribute(element, Url);
|
||||
std::string fileNameAttr = attribute(element, Name);
|
||||
downloadWtmlFiles(directory, urlAttr, fileNameAttr);
|
||||
}
|
||||
element = element->NextSiblingElement();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<openspace::ImageData> loadImageFromNode(
|
||||
const tinyxml2::XMLElement* node,
|
||||
std::string collection)
|
||||
{
|
||||
using namespace openspace;
|
||||
|
||||
// Collect the image set of the node. The structure is different depending on if
|
||||
// it is a Place or an ImageSet
|
||||
std::string thumbnailUrl = std::string(Undefined);
|
||||
const tinyxml2::XMLElement* imageSet = nullptr;
|
||||
std::string type = node->Name();
|
||||
|
||||
if (type == ImageSet) {
|
||||
thumbnailUrl = childNodeContentFromImageSet(node, ThumbnailUrl);
|
||||
imageSet = node;
|
||||
}
|
||||
else if (type == Place) {
|
||||
thumbnailUrl = urlFromPlace(node);
|
||||
imageSet = childNode(node, ImageSet);
|
||||
}
|
||||
|
||||
// Only collect the images that have a thumbnail image, that are sky images and
|
||||
// that have an image
|
||||
const bool hasThumbnailUrl = thumbnailUrl != Undefined;
|
||||
const bool isSkyImage = attribute(node, DataSetType) == Sky;
|
||||
const bool hasImageUrl = imageSet ? hasAttribute(imageSet, Url) : false;
|
||||
|
||||
if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Collect name, image url and credits
|
||||
std::string name = attribute(node, Name);
|
||||
if (std::islower(name[0])) {
|
||||
// convert string to upper case
|
||||
name[0] = static_cast<char>(std::toupper(name[0]));
|
||||
}
|
||||
|
||||
std::string imageUrl = attribute(imageSet, Url);
|
||||
std::string credits = childNodeContentFromImageSet(imageSet, Credits);
|
||||
std::string creditsUrl = childNodeContentFromImageSet(imageSet, CreditsUrl);
|
||||
|
||||
// Collect equatorial coordinates. All-sky surveys do not have these coordinates
|
||||
bool hasCelestialCoords = hasAttribute(node, RA) && hasAttribute(node, Dec);
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
glm::dvec3 equatorialCartesian = glm::dvec3(0.0);
|
||||
|
||||
if (hasCelestialCoords) {
|
||||
// The RA from WWT is in the unit hours:
|
||||
// to convert to degrees, multiply with 360 (deg) /24 (h) = 15
|
||||
double ra = 15.0 * std::stod(attribute(node, RA));
|
||||
double dec = std::stod(attribute(node, Dec));
|
||||
equatorialSpherical = glm::dvec2(ra, dec);
|
||||
equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical);
|
||||
}
|
||||
|
||||
// Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6
|
||||
float fov = 0.f;
|
||||
if (hasAttribute(node, ZoomLevel)) {
|
||||
fov = std::stof(attribute(node, ZoomLevel)) / 6.f;
|
||||
}
|
||||
|
||||
return ImageData{
|
||||
name,
|
||||
thumbnailUrl,
|
||||
imageUrl,
|
||||
credits,
|
||||
creditsUrl,
|
||||
collection,
|
||||
hasCelestialCoords,
|
||||
fov,
|
||||
equatorialSpherical,
|
||||
equatorialCartesian
|
||||
};
|
||||
}
|
||||
} //namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
bool hasAttribute(const tinyxml2::XMLElement* element, const std::string_view& name) {
|
||||
return element->FindAttribute(std::string(name).c_str());
|
||||
}
|
||||
|
||||
std::string attribute(const tinyxml2::XMLElement* element, const std::string& name) {
|
||||
if (hasAttribute(element, name)) {
|
||||
return element->FindAttribute(name.c_str())->Value();
|
||||
}
|
||||
return wwt::Undefined;
|
||||
}
|
||||
|
||||
// Parsing and downloading of wtml files
|
||||
bool downloadFile(const std::string& url, const std::filesystem::path& fileDestination) {
|
||||
// Get the web page and save to file
|
||||
HttpFileDownload wtmlRoot(
|
||||
url,
|
||||
fileDestination,
|
||||
HttpFileDownload::Overwrite::Yes
|
||||
);
|
||||
wtmlRoot.start(std::chrono::milliseconds(10000));
|
||||
return wtmlRoot.wait();
|
||||
}
|
||||
|
||||
bool directoryExists(const std::filesystem::path& path) {
|
||||
return std::filesystem::exists(path) && std::filesystem::is_directory(path);
|
||||
}
|
||||
|
||||
std::string createSearchableString(std::string str) {
|
||||
// Remove white spaces and all special characters
|
||||
str.erase(
|
||||
std::remove_if(
|
||||
str.begin(), str.end(),
|
||||
[](char c) {
|
||||
const bool isNumberOrLetter = std::isdigit(c) || std::isalpha(c);
|
||||
return !isNumberOrLetter;
|
||||
}
|
||||
),
|
||||
str.end()
|
||||
);
|
||||
// Make the word lower case
|
||||
std::transform(
|
||||
str.begin(), str.end(),
|
||||
str.begin(),
|
||||
[](char c) { return static_cast<char>(std::tolower(c)); }
|
||||
);
|
||||
return str;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* getDirectChildNode(tinyxml2::XMLElement* node,
|
||||
const std::string& name)
|
||||
{
|
||||
while (node && node->Name() != name) {
|
||||
node = node->FirstChildElement();
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement* getChildNode(tinyxml2::XMLElement* node,
|
||||
const std::string& name)
|
||||
{
|
||||
tinyxml2::XMLElement* child = node->FirstChildElement();
|
||||
|
||||
// Traverse the children and look at all their first child to find ImageSet
|
||||
while (child) {
|
||||
tinyxml2::XMLElement* imageSet = getDirectChildNode(child, name);
|
||||
// Found
|
||||
if (imageSet) {
|
||||
return imageSet;
|
||||
}
|
||||
child = child->NextSiblingElement();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string getChildNodeContentFromImageSet(tinyxml2::XMLElement* imageSet,
|
||||
const std::string& elementName)
|
||||
{
|
||||
// Find the thumbnail image url
|
||||
// The thumbnail is the last node so traverse backwards for speed
|
||||
tinyxml2::XMLElement* imageSetChild =
|
||||
imageSet->FirstChildElement(elementName.c_str());
|
||||
|
||||
if (imageSetChild && imageSetChild->GetText()) {
|
||||
return imageSetChild->GetText();
|
||||
}
|
||||
else {
|
||||
return wwt::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getUrlFromPlace(tinyxml2::XMLElement* place) {
|
||||
// If the place has a thumbnail url, return it
|
||||
if (hasAttribute(place, wwt::Thumbnail)) {
|
||||
return attribute(place, wwt::Thumbnail);
|
||||
}
|
||||
|
||||
// If the place doesn't have a thumbnail url data attribute,
|
||||
// Load the image set it stores instead
|
||||
tinyxml2::XMLElement* imageSet = getChildNode(place, wwt::ImageSet);
|
||||
|
||||
// If there is an imageSet, collect thumbnail url
|
||||
if (imageSet) {
|
||||
return getChildNodeContentFromImageSet(imageSet, wwt::ThumbnailUrl);
|
||||
}
|
||||
else {
|
||||
// If it doesn't contain an ImageSet, it doesn't have an url
|
||||
return wwt::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
void parseWtmlsFromDisc(std::vector<tinyxml2::XMLDocument*>& xmls,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
|
||||
std::string path = entry.path().string();
|
||||
tinyxml2::XMLError successCode = document->LoadFile(path.c_str());
|
||||
|
||||
if (successCode == tinyxml2::XMLError::XML_SUCCESS) {
|
||||
xmls.push_back(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool downloadAndParseWtmlFilesFromUrl(std::vector<tinyxml2::XMLDocument*>& xmls,
|
||||
const std::filesystem::path& directory,
|
||||
const std::string& url, const std::string& fileName)
|
||||
{
|
||||
// Look for WWT image data folder, create folder if it doesn't exist
|
||||
if (!directoryExists(directory)) {
|
||||
std::string newDir = directory.string();
|
||||
// Remove the '/' at the end
|
||||
newDir.pop_back();
|
||||
LINFO("Creating directory" + newDir);
|
||||
std::filesystem::create_directory(newDir);
|
||||
}
|
||||
|
||||
// Download file from url
|
||||
std::filesystem::path file = directory.string() + fileName + ".aspx";
|
||||
if (!downloadFile(url, file)) {
|
||||
LINFO(
|
||||
fmt::format("Couldn't download file '{}' to directory '{}'", url, directory)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse file to XML
|
||||
using namespace tinyxml2;
|
||||
tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument();
|
||||
doc->LoadFile(file.string().c_str());
|
||||
|
||||
// Search XML file for folders with urls
|
||||
XMLElement* root = doc->RootElement();
|
||||
XMLElement* element = root->FirstChildElement(wwt::Folder.c_str());
|
||||
const bool folderExists = element;
|
||||
const bool folderContainNoUrls = folderExists && !hasAttribute(element, wwt::Url);
|
||||
|
||||
// If the file contains no folders, or there are folders but without urls,
|
||||
// stop recursion
|
||||
if (!folderExists || folderContainNoUrls) {
|
||||
xmls.push_back(doc);
|
||||
LINFO("Saving " + url);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Iterate through all the folders in the XML file
|
||||
while (element && std::string(element->Value()) == wwt::Folder) {
|
||||
// If folder contains urls, download and parse those urls
|
||||
if (hasAttribute(element, wwt::Url) && hasAttribute(element, wwt::Name)) {
|
||||
std::string urlAttr = attribute(element, wwt::Url);
|
||||
std::string fileNameAttr = attribute(element, wwt::Name);
|
||||
downloadAndParseWtmlFilesFromUrl(xmls, directory, urlAttr, fileNameAttr);
|
||||
}
|
||||
element = element->NextSiblingElement();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WwtDataHandler::~WwtDataHandler() {
|
||||
// Call destructor of all allocated xmls
|
||||
_xmls.clear();
|
||||
}
|
||||
|
||||
void WwtDataHandler::loadImages(const std::string& root,
|
||||
const std::filesystem::path& directory)
|
||||
{
|
||||
// Collect the wtml files, either by reading from disc or from a url
|
||||
if (directoryExists(directory) && !std::filesystem::is_empty(directory)) {
|
||||
parseWtmlsFromDisc(_xmls, directory);
|
||||
LINFO("Loading images from directory");
|
||||
}
|
||||
else {
|
||||
downloadAndParseWtmlFilesFromUrl(_xmls, directory, root, "root");
|
||||
LINFO("Loading images from url");
|
||||
// Steps to download new images
|
||||
// 1. Create the target directory if it doesn't already exist
|
||||
// 2. If the 'root' has an associated hash file, download and compare it with the
|
||||
// local file. If the hash has changed, nuke the folder
|
||||
// 3. If the folder is empty, download files
|
||||
|
||||
// 1.
|
||||
if (!directoryExists(directory)) {
|
||||
LINFO(fmt::format("Creating directory {}", directory));
|
||||
std::filesystem::create_directory(directory);
|
||||
}
|
||||
|
||||
// Traverse through the collected wtml documents and collect the images
|
||||
for (tinyxml2::XMLDocument* doc : _xmls) {
|
||||
tinyxml2::XMLElement* rootNode = doc->FirstChildElement();
|
||||
std::string collectionName = attribute(rootNode, wwt::Name);
|
||||
saveImagesFromXml(rootNode, collectionName);
|
||||
// Get the hash from the remote. If no such hash exists, the remoteHash will be empty
|
||||
std::string remoteHash;
|
||||
{
|
||||
std::string remoteHashFile = root.substr(0, root.find_last_of('/')) + "/hash.md5";
|
||||
bool success = downloadFile(remoteHashFile, directory / "hash.tmp");
|
||||
// The hash download might fail if the provided 'root' does not have a hash
|
||||
// in which case we assume that the underlying data has not changed
|
||||
if (success) {
|
||||
std::ifstream(directory / "hash.tmp") >> remoteHash;
|
||||
std::filesystem::remove(directory / "hash.tmp");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the local hash. If no such hash exists, the localHash will be empty
|
||||
std::string localHash;
|
||||
std::filesystem::path localHashFile = directory / "hash.md5";
|
||||
if (std::filesystem::exists(localHashFile)) {
|
||||
std::ifstream(localHashFile) >> localHash;
|
||||
}
|
||||
|
||||
// Check if the hash has changed. This will be ignored if either the local of remote
|
||||
// hash does not exist
|
||||
if (!localHash.empty() && !remoteHash.empty() && localHash != remoteHash) {
|
||||
LINFO(fmt::format(
|
||||
"Local hash '{}' differs from remote hash '{}'. Cleaning directory",
|
||||
localHash, remoteHash
|
||||
));
|
||||
|
||||
std::filesystem::remove_all(directory);
|
||||
std::filesystem::create_directory(directory);
|
||||
}
|
||||
|
||||
// If there is no directory (either because it is the first start, or the previous
|
||||
// contents were deleted because of a change in hash) we have to download the files
|
||||
if (std::filesystem::is_empty(directory)) {
|
||||
LINFO("Loading images from url");
|
||||
downloadWtmlFiles(directory, root, "root");
|
||||
std::ofstream(localHashFile) << remoteHash;
|
||||
}
|
||||
|
||||
// Finally, we can load the files that are now on disk
|
||||
LINFO("Loading images from directory");
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
tinyxml2::XMLDocument document;
|
||||
std::string path = entry.path().string();
|
||||
tinyxml2::XMLError successCode = document.LoadFile(path.c_str());
|
||||
|
||||
if (successCode == tinyxml2::XMLError::XML_SUCCESS) {
|
||||
tinyxml2::XMLElement* rootNode = document.FirstChildElement();
|
||||
std::string collectionName = attribute(rootNode, Name);
|
||||
saveImagesFromXml(rootNode, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort images in alphabetical order
|
||||
std::sort(
|
||||
_images.begin(),
|
||||
_images.end(),
|
||||
[](ImageData& a, ImageData& b) {
|
||||
// If the first character in the names are lowercase, make it upper case
|
||||
if (std::islower(a.name[0])) {
|
||||
// convert string to upper case
|
||||
a.name[0] = static_cast<char>(::toupper(a.name[0]));
|
||||
}
|
||||
if (std::islower(b.name[0])) {
|
||||
b.name[0] = static_cast<char>(::toupper(b.name[0]));
|
||||
}
|
||||
return a.name < b.name;
|
||||
}
|
||||
[](ImageData& a, ImageData& b) { return a.name < b.name; }
|
||||
);
|
||||
|
||||
LINFO(fmt::format("Loaded {} WorldWide Telescope images", _images.size()));
|
||||
@@ -267,100 +344,33 @@ int WwtDataHandler::nLoadedImages() const {
|
||||
return static_cast<int>(_images.size());
|
||||
}
|
||||
|
||||
const ImageData& WwtDataHandler::getImage(int i) const {
|
||||
const ImageData& WwtDataHandler::image(int i) const {
|
||||
ghoul_assert(i < static_cast<int>(_images.size()), "Index outside of vector size");
|
||||
return _images[i];
|
||||
}
|
||||
|
||||
void WwtDataHandler::saveImageFromNode(tinyxml2::XMLElement* node, std::string collection)
|
||||
{
|
||||
// Collect the image set of the node. The structure is different depending on if
|
||||
// it is a Place or an ImageSet
|
||||
std::string thumbnailUrl = wwt::Undefined;
|
||||
tinyxml2::XMLElement* imageSet = nullptr;
|
||||
std::string type = std::string(node->Name());
|
||||
|
||||
if (type == wwt::ImageSet) {
|
||||
thumbnailUrl = getChildNodeContentFromImageSet(node, wwt::ThumbnailUrl);
|
||||
imageSet = node;
|
||||
}
|
||||
else if (type == wwt::Place) {
|
||||
thumbnailUrl = getUrlFromPlace(node);
|
||||
imageSet = getChildNode(node, wwt::ImageSet);
|
||||
}
|
||||
|
||||
// Only collect the images that have a thumbnail image, that are sky images and
|
||||
// that have an image
|
||||
const bool hasThumbnailUrl = thumbnailUrl != wwt::Undefined;
|
||||
const bool isSkyImage = attribute(node, wwt::DataSetType) == wwt::Sky;
|
||||
const bool hasImageUrl = imageSet ? hasAttribute(imageSet, wwt::Url) : false;
|
||||
|
||||
if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect name, image url and credits
|
||||
std::string name = attribute(node, wwt::Name);
|
||||
std::string imageUrl = attribute(imageSet, wwt::Url);
|
||||
std::string credits = getChildNodeContentFromImageSet(imageSet, wwt::Credits);
|
||||
std::string creditsUrl = getChildNodeContentFromImageSet(imageSet, wwt::CreditsUrl);
|
||||
|
||||
// Collect equatorial coordinates. All-sky surveys do not have this kind of
|
||||
// coordinate
|
||||
bool hasCelestialCoords = hasAttribute(node, wwt::RA) && hasAttribute(node, wwt::Dec);
|
||||
glm::dvec2 equatorialSpherical = glm::dvec2(0.0);
|
||||
glm::dvec3 equatorialCartesian = glm::vec3(0.0);
|
||||
|
||||
if (hasCelestialCoords) {
|
||||
// The RA from WWT is in the unit hours:
|
||||
// to convert to degrees, multiply with 360 (deg) /24 (h) = 15
|
||||
double ra = 15.0 * std::stod(attribute(node, wwt::RA));
|
||||
double dec = std::stod(attribute(node, wwt::Dec));
|
||||
equatorialSpherical = glm::dvec2(ra, dec);
|
||||
equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical);
|
||||
}
|
||||
|
||||
// Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6
|
||||
float fov = 0.f;
|
||||
if (hasAttribute(node, wwt::ZoomLevel)) {
|
||||
fov = std::stof(attribute(node, wwt::ZoomLevel)) / 6.0f;
|
||||
}
|
||||
|
||||
ImageData image = {
|
||||
name,
|
||||
thumbnailUrl,
|
||||
imageUrl,
|
||||
credits,
|
||||
creditsUrl,
|
||||
collection,
|
||||
hasCelestialCoords,
|
||||
fov,
|
||||
equatorialSpherical,
|
||||
equatorialCartesian
|
||||
};
|
||||
|
||||
_images.push_back(image);
|
||||
}
|
||||
|
||||
void WwtDataHandler::saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection)
|
||||
void WwtDataHandler::saveImagesFromXml(const tinyxml2::XMLElement* root,
|
||||
std::string collection)
|
||||
{
|
||||
// Get direct child of node called Place
|
||||
using namespace tinyxml2;
|
||||
XMLElement* node = root->FirstChildElement();
|
||||
const tinyxml2::XMLElement* node = root->FirstChildElement();
|
||||
|
||||
// Iterate through all siblings of node. If sibling is folder, open recursively.
|
||||
// If sibling is image, save it.
|
||||
while (node) {
|
||||
const std::string name = node->Name();
|
||||
// If node is an image or place, load it
|
||||
if (name == wwt::ImageSet || name == wwt::Place) {
|
||||
saveImageFromNode(node, collection);
|
||||
if (name == ImageSet || name == Place) {
|
||||
std::optional<ImageData> image = loadImageFromNode(node, collection);
|
||||
if (image.has_value()) {
|
||||
_images.push_back(std::move(*image));
|
||||
}
|
||||
|
||||
}
|
||||
// If node is another folder, open recursively
|
||||
else if (name == wwt::Folder) {
|
||||
std::string newCollectionName = collection + "/";
|
||||
newCollectionName += attribute(node, wwt::Name);
|
||||
|
||||
else if (name == Folder) {
|
||||
std::string nodeName = attribute(node, Name);
|
||||
std::string newCollectionName = fmt::format("{}/{}", collection, nodeName);
|
||||
saveImagesFromXml(node, newCollectionName);
|
||||
}
|
||||
node = node->NextSiblingElement();
|
||||
|
||||
@@ -26,19 +26,21 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
|
||||
|
||||
set(HEADER_FILES
|
||||
horizonsfile.h
|
||||
kepler.h
|
||||
labelscomponent.h
|
||||
speckloader.h
|
||||
rendering/renderableconstellationsbase.h
|
||||
rendering/renderableconstellationbounds.h
|
||||
rendering/renderableconstellationlines.h
|
||||
rendering/renderablefluxnodes.h
|
||||
rendering/renderablehabitablezone.h
|
||||
rendering/renderablerings.h
|
||||
rendering/renderableorbitalkepler.h
|
||||
rendering/renderablesatellites.h
|
||||
rendering/renderablesmallbody.h
|
||||
rendering/renderablestars.h
|
||||
rendering/renderabletravelspeed.h
|
||||
translation/gptranslation.h
|
||||
translation/keplertranslation.h
|
||||
translation/spicetranslation.h
|
||||
translation/tletranslation.h
|
||||
translation/horizonstranslation.h
|
||||
rotation/spicerotation.h
|
||||
)
|
||||
@@ -46,20 +48,22 @@ source_group("Header Files" FILES ${HEADER_FILES})
|
||||
|
||||
set(SOURCE_FILES
|
||||
horizonsfile.cpp
|
||||
kepler.cpp
|
||||
spacemodule_lua.inl
|
||||
labelscomponent.cpp
|
||||
speckloader.cpp
|
||||
rendering/renderableconstellationsbase.cpp
|
||||
rendering/renderableconstellationbounds.cpp
|
||||
rendering/renderableconstellationlines.cpp
|
||||
rendering/renderablefluxnodes.cpp
|
||||
rendering/renderablehabitablezone.cpp
|
||||
rendering/renderablerings.cpp
|
||||
rendering/renderableorbitalkepler.cpp
|
||||
rendering/renderablesatellites.cpp
|
||||
rendering/renderablesmallbody.cpp
|
||||
rendering/renderablestars.cpp
|
||||
rendering/renderabletravelspeed.cpp
|
||||
translation/gptranslation.cpp
|
||||
translation/keplertranslation.cpp
|
||||
translation/spicetranslation.cpp
|
||||
translation/tletranslation.cpp
|
||||
translation/horizonstranslation.cpp
|
||||
rotation/spicerotation.cpp
|
||||
)
|
||||
@@ -68,6 +72,8 @@ source_group("Source Files" FILES ${SOURCE_FILES})
|
||||
set(SHADER_FILES
|
||||
shaders/constellationbounds_fs.glsl
|
||||
shaders/constellationbounds_vs.glsl
|
||||
shaders/constellationlines_fs.glsl
|
||||
shaders/constellationlines_vs.glsl
|
||||
shaders/debrisViz_fs.glsl
|
||||
shaders/debrisViz_vs.glsl
|
||||
shaders/fluxnodes_fs.glsl
|
||||
@@ -90,3 +96,12 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
target_precompile_headers(${space_module} PRIVATE
|
||||
<openspace/documentation/documentation.h>
|
||||
<openspace/documentation/verifier.h>
|
||||
<openspace/properties/numericalproperty.h>
|
||||
<openspace/rendering/renderable.h>
|
||||
<ghoul/opengl/programobject.h>
|
||||
<ghoul/opengl/shaderobject.h>
|
||||
<ghoul/opengl/uniformcache.h>
|
||||
)
|
||||
|
||||
751
modules/space/kepler.cpp
Normal file
751
modules/space/kepler.cpp
Normal file
@@ -0,0 +1,751 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/kepler.h>
|
||||
|
||||
#include <ghoul/filesystem/cachemanager.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/misc.h>
|
||||
#include <scn/scn.h>
|
||||
#include <scn/tuple_return.h>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "Kepler";
|
||||
constexpr int8_t CurrentCacheVersion = 1;
|
||||
|
||||
// The list of leap years only goes until 2056 as we need to touch this file then
|
||||
// again anyway ;)
|
||||
constexpr const std::array<int, 36> LeapYears = {
|
||||
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
|
||||
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
|
||||
2044, 2048, 2052, 2056
|
||||
};
|
||||
constexpr const std::array<int, 12> DaysOfMonths = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||||
};
|
||||
|
||||
// Count the number of full days since the beginning of 2000 to the beginning of
|
||||
// the parameter 'year'
|
||||
int countDays(int year) {
|
||||
// Find the position of the current year in the vector, the difference
|
||||
// between its position and the position of 2000 (for J2000) gives the
|
||||
// number of leap years
|
||||
constexpr const int Epoch = 2000;
|
||||
constexpr const int DaysRegularYear = 365;
|
||||
constexpr const int DaysLeapYear = 366;
|
||||
|
||||
if (year == Epoch) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the position of the most recent leap year
|
||||
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
|
||||
|
||||
// Get the position of the epoch
|
||||
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
|
||||
|
||||
const int nYears = std::abs(year - Epoch);
|
||||
const int nRegularYears = nYears - nLeapYears;
|
||||
|
||||
// Get the total number of days as the sum of leap years + non leap years
|
||||
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the number of leap seconds that lie between the {year, dayOfYear}
|
||||
// time point and { 2000, 1 }
|
||||
int countLeapSeconds(int year, int dayOfYear) {
|
||||
// Find the position of the current year in the vector; its position in the vector
|
||||
// gives the number of leap seconds
|
||||
struct LeapSecond {
|
||||
int year;
|
||||
int dayOfYear;
|
||||
bool operator<(const LeapSecond& rhs) const {
|
||||
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr const LeapSecond LeapEpoch = { 2000, 1 };
|
||||
|
||||
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
|
||||
constexpr const std::array<LeapSecond, 28> LeapSeconds = {
|
||||
LeapSecond{ 1972, 1 },
|
||||
LeapSecond{ 1972, 183 },
|
||||
LeapSecond{ 1973, 1 },
|
||||
LeapSecond{ 1974, 1 },
|
||||
LeapSecond{ 1975, 1 },
|
||||
LeapSecond{ 1976, 1 },
|
||||
LeapSecond{ 1977, 1 },
|
||||
LeapSecond{ 1978, 1 },
|
||||
LeapSecond{ 1979, 1 },
|
||||
LeapSecond{ 1980, 1 },
|
||||
LeapSecond{ 1981, 182 },
|
||||
LeapSecond{ 1982, 182 },
|
||||
LeapSecond{ 1983, 182 },
|
||||
LeapSecond{ 1985, 182 },
|
||||
LeapSecond{ 1988, 1 },
|
||||
LeapSecond{ 1990, 1 },
|
||||
LeapSecond{ 1991, 1 },
|
||||
LeapSecond{ 1992, 183 },
|
||||
LeapSecond{ 1993, 182 },
|
||||
LeapSecond{ 1994, 182 },
|
||||
LeapSecond{ 1996, 1 },
|
||||
LeapSecond{ 1997, 182 },
|
||||
LeapSecond{ 1999, 1 },
|
||||
LeapSecond{ 2006, 1 },
|
||||
LeapSecond{ 2009, 1 },
|
||||
LeapSecond{ 2012, 183 },
|
||||
LeapSecond{ 2015, 182 },
|
||||
LeapSecond{ 2017, 1 }
|
||||
};
|
||||
// Get the position of the last leap second before the desired date
|
||||
LeapSecond date{ year, dayOfYear };
|
||||
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
|
||||
|
||||
// Get the position of the Epoch
|
||||
const auto y2000 = std::lower_bound(
|
||||
LeapSeconds.begin(),
|
||||
LeapSeconds.end(),
|
||||
LeapEpoch
|
||||
);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
|
||||
return nLeapSeconds;
|
||||
}
|
||||
|
||||
int daysIntoGivenYear(int year, int month, int dayOfMonth) {
|
||||
// month and dayCount are zero-based.
|
||||
month -= 1;
|
||||
int dayCount = dayOfMonth - 1;
|
||||
constexpr int February = 1;
|
||||
const bool isInLeapYear =
|
||||
std::find(LeapYears.begin(), LeapYears.end(), year) != LeapYears.end();
|
||||
|
||||
for (int m = 0; m < month; ++m) {
|
||||
dayCount += DaysOfMonths[m];
|
||||
if (m == February && isInLeapYear) {
|
||||
dayCount += 1;
|
||||
}
|
||||
}
|
||||
return dayCount;
|
||||
}
|
||||
|
||||
double calculateSemiMajorAxis(double meanMotion) {
|
||||
constexpr const double GravitationalConstant = 6.6740831e-11;
|
||||
constexpr const double MassEarth = 5.9721986e24;
|
||||
constexpr const double muEarth = GravitationalConstant * MassEarth;
|
||||
|
||||
// Use Kepler's 3rd law to calculate semimajor axis
|
||||
// a^3 / P^2 = mu / (2pi)^2
|
||||
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
|
||||
// with a = semimajor axis
|
||||
// P = period in seconds
|
||||
// mu = G*M_earth
|
||||
const double period =
|
||||
std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
|
||||
|
||||
constexpr const double pisq = glm::pi<double>() * glm::pi<double>();
|
||||
const double semiMajorAxis = pow(
|
||||
(muEarth * period * period) / (4 * pisq),
|
||||
1.0 / 3.0
|
||||
);
|
||||
|
||||
// We need the semi major axis in km instead of m
|
||||
return semiMajorAxis / 1000.0;
|
||||
}
|
||||
|
||||
|
||||
double epochFromSubstring(const std::string& epoch) {
|
||||
// The epochString is in the form:
|
||||
// YYDDD.DDDDDDDD
|
||||
// With YY being the last two years of the launch epoch, the first DDD the day of
|
||||
// the year and the remaning a fractional part of the day
|
||||
|
||||
// The main overview of this function:
|
||||
// 1. Reconstruct the full year from the YY part
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
// According to https://celestrak.com/columns/v04n03/
|
||||
// Apparently, US Space Command sees no need to change the two-line element set
|
||||
// format yet since no artificial earth satellites existed prior to 1957. By their
|
||||
// reasoning, two-digit years from 57-99 correspond to 1957-1999 and those from
|
||||
// 00-56 correspond to 2000-2056. We'll see each other again in 2057!
|
||||
|
||||
// 1,2. Get the full year and days
|
||||
auto [res, year, daysInYear] = scn::scan_tuple<int, double>(epoch, "{:2}{}");
|
||||
if (!res) {
|
||||
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
|
||||
}
|
||||
year += year > 57 ? 1900 : 2000;
|
||||
const int daysSince2000 = countDays(year);
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
//Need to subtract 1 from daysInYear since it is not a zero-based count
|
||||
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additional leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = countLeapSeconds(
|
||||
year,
|
||||
static_cast<int>(std::floor(daysInYear))
|
||||
);
|
||||
|
||||
// 5
|
||||
const double nSecondsEpochOffset =
|
||||
static_cast<double>(seconds(hours(12)).count());
|
||||
|
||||
// Combine all of the values
|
||||
return nSecondsSince2000 - nLeapSecondsOffset - nSecondsEpochOffset;
|
||||
}
|
||||
|
||||
double epochFromYMDdSubstring(const std::string& epoch) {
|
||||
// The epochString is in the form:
|
||||
// YYYYMMDD.ddddddd
|
||||
// With YYYY as the year, MM the month (1 - 12), DD the day of month (1-31),
|
||||
// and dddd the fraction of that day.
|
||||
|
||||
// The main overview of this function:
|
||||
// 1. Read the year value
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
// 1, 2
|
||||
auto [res, year, monthNum, dayOfMonthNum, fractionOfDay] =
|
||||
scn::scan_tuple<int, int, int, double>(epoch, "{:4}{:2}{:2}{}");
|
||||
if (!res) {
|
||||
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
|
||||
}
|
||||
const int daysSince2000 = countDays(year);
|
||||
int wholeDaysInto = daysIntoGivenYear(year, monthNum, dayOfMonthNum);
|
||||
double daysInYear = static_cast<double>(wholeDaysInto) + fractionOfDay;
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
//Need to subtract 1 from daysInYear since it is not a zero-based count
|
||||
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additional leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = -countLeapSeconds(
|
||||
year,
|
||||
static_cast<int>(std::floor(daysInYear))
|
||||
);
|
||||
|
||||
// 5
|
||||
const double offset = static_cast<double>(seconds(hours(12)).count());
|
||||
|
||||
// Combine all of the values
|
||||
return nSecondsSince2000 + nLeapSecondsOffset - offset;
|
||||
}
|
||||
|
||||
double epochFromOmmString(const std::string& epoch) {
|
||||
// The epochString is in the form:
|
||||
// YYYY-MM-DDThh:mm:ss[.d->d][Z]
|
||||
// or
|
||||
// YYYY-DDDThh:mm:ss[.d->d][Z]
|
||||
|
||||
// The main overview of this function:
|
||||
// 0. Determine which type it is
|
||||
// 1. Read the year value
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Add the hh:mm:ss component
|
||||
// 6. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
std::string e = epoch;
|
||||
if (e.back() == 'Z') {
|
||||
e.pop_back();
|
||||
}
|
||||
|
||||
struct {
|
||||
int year;
|
||||
int nDays;
|
||||
int hours;
|
||||
int minutes;
|
||||
double seconds;
|
||||
} date;
|
||||
|
||||
// 1, 2
|
||||
const size_t pos = epoch.find('T');
|
||||
if (pos == 10) {
|
||||
// We have the first form
|
||||
|
||||
int month;
|
||||
int days;
|
||||
auto res = scn::scan(
|
||||
epoch, "{:4}-{:2}-{:2}T{:2}:{:2}:{}",
|
||||
date.year, month, days, date.hours, date.minutes, date.seconds
|
||||
);
|
||||
if (!res) {
|
||||
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
|
||||
}
|
||||
date.nDays = daysIntoGivenYear(date.year, month, days);
|
||||
}
|
||||
else if (pos == 8) {
|
||||
// We have the second form
|
||||
|
||||
auto res = scn::scan(
|
||||
epoch, "{:4}-{:3}T{:2}:{:2}:{}",
|
||||
date.year, date.nDays, date.hours, date.minutes, date.seconds
|
||||
);
|
||||
if (!res) {
|
||||
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(fmt::format("Malformed epoch string '{}'", epoch));
|
||||
}
|
||||
|
||||
const int daysSince2000 = countDays(date.year);
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
const double nSecondsSince2000 = (daysSince2000 + date.nDays) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additional leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = -countLeapSeconds(
|
||||
date.year,
|
||||
static_cast<int>(std::floor(date.nDays))
|
||||
);
|
||||
|
||||
// 5
|
||||
const long long totalSeconds =
|
||||
std::chrono::seconds(std::chrono::hours(date.hours)).count() +
|
||||
std::chrono::seconds(std::chrono::minutes(date.minutes)).count();
|
||||
|
||||
// 6
|
||||
const long long offset = std::chrono::seconds(std::chrono::hours(12)).count();
|
||||
|
||||
// Combine all of the values
|
||||
return
|
||||
nSecondsSince2000 + totalSeconds + nLeapSecondsOffset - offset + date.seconds;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openspace::kepler {
|
||||
|
||||
std::vector<Parameters> readTleFile(std::filesystem::path file) {
|
||||
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
|
||||
|
||||
std::vector<Parameters> result;
|
||||
|
||||
std::ifstream f;
|
||||
f.open(file);
|
||||
|
||||
int lineNum = 1;
|
||||
|
||||
std::string header;
|
||||
while (std::getline(f, header)) {
|
||||
Parameters p;
|
||||
|
||||
// Header
|
||||
p.name = header;
|
||||
|
||||
// First line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 08-08 Classification (U = Unclassified)
|
||||
// 4 10-11 International Designator (Last two digits of launch year)
|
||||
// 5 12-14 International Designator (Launch number of the year)
|
||||
// 6 15-17 International Designator(piece of the launch) A
|
||||
// 7 19-20 Epoch Year(last two digits of year)
|
||||
// 8 21-32 Epoch(day of the year and fractional portion of the day)
|
||||
// 9 34-43 First Time Derivative of the Mean Motion divided by two
|
||||
// 10 45-52 Second Time Derivative of Mean Motion divided by six
|
||||
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
|
||||
// 12 63-63 The "Ephemeris type"
|
||||
// 13 65-68 Element set number.Incremented when a new TLE is generated
|
||||
// 14 69-69 Checksum (modulo 10)
|
||||
std::string firstLine;
|
||||
std::getline(f, firstLine);
|
||||
if (f.bad() || firstLine[0] != '1') {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Malformed TLE file '{}' at line {}", file, lineNum + 1
|
||||
));
|
||||
}
|
||||
// The id only contains the last two digits of the launch year, so we have to
|
||||
// patch it to the full year
|
||||
{
|
||||
std::string id = firstLine.substr(9, 6);
|
||||
std::string prefix = [y = id.substr(0, 2)](){
|
||||
int year = std::atoi(y.c_str());
|
||||
return year >= 57 ? "19" : "20";
|
||||
}();
|
||||
p.id = fmt::format("{}{}-{}", prefix, id.substr(0, 2), id.substr(3));
|
||||
}
|
||||
p.epoch = epochFromSubstring(firstLine.substr(18, 14)); // should be 13?
|
||||
|
||||
|
||||
// Second line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 09-16 Inclination (degrees)
|
||||
// 4 18-25 Right ascension of the ascending node (degrees)
|
||||
// 5 27-33 Eccentricity (decimal point assumed)
|
||||
// 6 35-42 Argument of perigee (degrees)
|
||||
// 7 44-51 Mean Anomaly (degrees)
|
||||
// 8 53-63 Mean Motion (revolutions per day)
|
||||
// 9 64-68 Revolution number at epoch (revolutions)
|
||||
// 10 69-69 Checksum (modulo 10)
|
||||
std::string secondLine;
|
||||
std::getline(f, secondLine);
|
||||
if (f.bad() || secondLine[0] != '2') {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Malformed TLE file '{}' at line {}", file, lineNum + 1
|
||||
));
|
||||
}
|
||||
|
||||
std::stringstream stream;
|
||||
stream.exceptions(std::ios::failbit);
|
||||
|
||||
// Get inclination
|
||||
stream.str(secondLine.substr(8, 8));
|
||||
stream >> p.inclination;
|
||||
stream.clear();
|
||||
|
||||
// Get Right ascension of the ascending node
|
||||
stream.str(secondLine.substr(17, 8));
|
||||
stream >> p.ascendingNode;
|
||||
stream.clear();
|
||||
|
||||
// Get Eccentricity
|
||||
stream.str("0." + secondLine.substr(26, 7));
|
||||
stream >> p.eccentricity;
|
||||
stream.clear();
|
||||
|
||||
// Get argument of periapsis
|
||||
stream.str(secondLine.substr(34, 8));
|
||||
stream >> p.argumentOfPeriapsis;
|
||||
stream.clear();
|
||||
|
||||
// Get mean anomaly
|
||||
stream.str(secondLine.substr(43, 8));
|
||||
stream >> p.meanAnomaly;
|
||||
stream.clear();
|
||||
|
||||
// Get mean motion
|
||||
stream.str(secondLine.substr(52, 11));
|
||||
float meanMotion;
|
||||
stream >> meanMotion;
|
||||
|
||||
p.semiMajorAxis = calculateSemiMajorAxis(meanMotion);
|
||||
p.period = std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
|
||||
|
||||
result.push_back(p);
|
||||
|
||||
lineNum = lineNum + 3;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Parameters> readOmmFile(std::filesystem::path file) {
|
||||
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
|
||||
|
||||
std::vector<Parameters> result;
|
||||
|
||||
std::ifstream f;
|
||||
f.open(file);
|
||||
|
||||
int lineNum = 1;
|
||||
std::optional<Parameters> current = std::nullopt;
|
||||
std::string line;
|
||||
while (std::getline(f, line)) {
|
||||
if (line.empty() || line == "\r") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tokenize the line
|
||||
std::vector<std::string> parts = ghoul::tokenizeString(line, '=');
|
||||
for (std::string& p : parts) {
|
||||
ghoul::trimWhitespace(p);
|
||||
}
|
||||
|
||||
if (parts.size() != 2) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Malformed line '{}' at {}", line, lineNum
|
||||
));
|
||||
}
|
||||
|
||||
if (parts[0] == "CCSDS_OMM_VERS") {
|
||||
if (parts[1] != "2.0") {
|
||||
LWARNINGC(
|
||||
"OMM",
|
||||
fmt::format(
|
||||
"Only version 2.0 is currently supported but found {}. "
|
||||
"Parsing might fail",
|
||||
parts[1]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// We start a new value so we need to store the last one...
|
||||
if (current.has_value()) {
|
||||
result.push_back(*current);
|
||||
}
|
||||
|
||||
// ... and start a new one
|
||||
current = Parameters();
|
||||
}
|
||||
|
||||
ghoul_assert(current.has_value(), "No current element");
|
||||
|
||||
if (parts[0] == "OBJECT_NAME") {
|
||||
current->name = parts[1];
|
||||
}
|
||||
else if (parts[0] == "OBJECT_ID") {
|
||||
current->id = parts[1];
|
||||
}
|
||||
else if (parts[0] == "EPOCH") {
|
||||
current->epoch = epochFromOmmString(parts[1]);
|
||||
}
|
||||
else if (parts[0] == "MEAN_MOTION") {
|
||||
float mm = std::stof(parts[1]);
|
||||
current->semiMajorAxis = calculateSemiMajorAxis(mm);
|
||||
current->period = std::chrono::seconds(std::chrono::hours(24)).count() / mm;
|
||||
}
|
||||
else if (parts[0] == "SEMI_MAJOR_AXIS") {
|
||||
|
||||
}
|
||||
else if (parts[0] == "ECCENTRICITY") {
|
||||
current->eccentricity = std::stof(parts[1]);
|
||||
}
|
||||
else if (parts[0] == "INCLINATION") {
|
||||
current->inclination = std::stof(parts[1]);
|
||||
}
|
||||
else if (parts[0] == "RA_OF_ASC_NODE") {
|
||||
current->ascendingNode = std::stof(parts[1]);
|
||||
}
|
||||
else if (parts[0] == "ARG_OF_PERICENTER") {
|
||||
current->argumentOfPeriapsis = std::stof(parts[1]);
|
||||
}
|
||||
else if (parts[0] == "MEAN_ANOMALY") {
|
||||
current->meanAnomaly = std::stof(parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.has_value()) {
|
||||
result.push_back(*current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Parameters> readSbdbFile(std::filesystem::path file) {
|
||||
constexpr int NDataFields = 9;
|
||||
constexpr std::string_view ExpectedHeader = "full_name,epoch_cal,e,a,i,om,w,ma,per";
|
||||
|
||||
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
|
||||
|
||||
std::ifstream f;
|
||||
f.open(file);
|
||||
|
||||
std::string line;
|
||||
std::getline(f, line);
|
||||
if (line != ExpectedHeader) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Expected JPL SBDB file to start with '{}' but found '{}' instead",
|
||||
ExpectedHeader, line
|
||||
));
|
||||
}
|
||||
|
||||
std::vector<Parameters> result;
|
||||
while (std::getline(f, line)) {
|
||||
constexpr double AuToKm = 1.496e8;
|
||||
|
||||
std::vector<std::string> parts = ghoul::tokenizeString(line, ',');
|
||||
if (parts.size() != NDataFields) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Malformed line {}, expected 8 data fields, got {}", line, parts.size()
|
||||
));
|
||||
}
|
||||
Parameters p;
|
||||
|
||||
ghoul::trimWhitespace(parts[0]);
|
||||
p.name = parts[0];
|
||||
|
||||
p.epoch = epochFromYMDdSubstring(parts[1]);
|
||||
p.eccentricity = std::stod(parts[2]);
|
||||
p.semiMajorAxis = std::stod(parts[3]) * AuToKm;
|
||||
|
||||
auto importAngleValue = [](const std::string& angle) {
|
||||
if (angle.empty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double output = std::stod(angle);
|
||||
output = std::fmod(output, 360.0);
|
||||
if (output < 0.0) {
|
||||
output += 360.0;
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
p.inclination = importAngleValue(parts[4]);
|
||||
p.ascendingNode = importAngleValue(parts[5]);
|
||||
p.argumentOfPeriapsis = importAngleValue(parts[6]);
|
||||
p.meanAnomaly = importAngleValue(parts[7]);
|
||||
p.period =
|
||||
std::stod(parts[8]) * std::chrono::seconds(std::chrono::hours(24)).count();
|
||||
|
||||
result.push_back(std::move(p));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void saveCache(const std::vector<Parameters>& params, std::filesystem::path file) {
|
||||
std::ofstream stream(file, std::ofstream::binary);
|
||||
|
||||
stream.write(reinterpret_cast<const char*>(&CurrentCacheVersion), sizeof(int8_t));
|
||||
|
||||
uint32_t size = static_cast<uint32_t>(params.size());
|
||||
stream.write(reinterpret_cast<const char*>(&size), sizeof(uint32_t));
|
||||
for (const Parameters& param : params) {
|
||||
uint32_t nameLength = static_cast<uint32_t>(param.name.size());
|
||||
stream.write(reinterpret_cast<const char*>(&nameLength), sizeof(uint32_t));
|
||||
stream.write(param.name.data(), nameLength * sizeof(char));
|
||||
|
||||
uint32_t idLength = static_cast<uint32_t>(param.id.size());
|
||||
stream.write(reinterpret_cast<const char*>(&idLength), sizeof(uint32_t));
|
||||
stream.write(param.id.data(), idLength * sizeof(char));
|
||||
|
||||
stream.write(reinterpret_cast<const char*>(¶m.inclination), sizeof(double));
|
||||
stream.write(reinterpret_cast<const char*>(¶m.semiMajorAxis), sizeof(double));
|
||||
stream.write(reinterpret_cast<const char*>(¶m.ascendingNode), sizeof(double));
|
||||
stream.write(reinterpret_cast<const char*>(¶m.eccentricity), sizeof(double));
|
||||
stream.write(
|
||||
reinterpret_cast<const char*>(¶m.argumentOfPeriapsis),
|
||||
sizeof(double)
|
||||
);
|
||||
stream.write(reinterpret_cast<const char*>(¶m.meanAnomaly), sizeof(double));
|
||||
stream.write(reinterpret_cast<const char*>(¶m.epoch), sizeof(double));
|
||||
stream.write(reinterpret_cast<const char*>(¶m.period), sizeof(double));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::vector<Parameters>> loadCache(std::filesystem::path file) {
|
||||
std::ifstream stream(file, std::ifstream::binary);
|
||||
|
||||
int8_t version = 0;
|
||||
stream.read(reinterpret_cast<char*>(&version), sizeof(int8_t));
|
||||
if (version != CurrentCacheVersion) {
|
||||
LINFO("The format of the cached file has changed");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
|
||||
std::vector<Parameters> res;
|
||||
res.reserve(size);
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
Parameters param;
|
||||
|
||||
uint32_t nameLength = 0;
|
||||
stream.read(reinterpret_cast<char*>(&nameLength), sizeof(uint32_t));
|
||||
param.name.resize(nameLength);
|
||||
stream.read(param.name.data(), nameLength * sizeof(char));
|
||||
|
||||
uint32_t idLength = 0;
|
||||
stream.read(reinterpret_cast<char*>(&idLength), sizeof(uint32_t));
|
||||
param.id.resize(idLength);
|
||||
stream.read(param.id.data(), idLength * sizeof(char));
|
||||
|
||||
stream.read(reinterpret_cast<char*>(¶m.inclination), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.semiMajorAxis), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.ascendingNode), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.eccentricity), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.argumentOfPeriapsis), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.meanAnomaly), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.epoch), sizeof(double));
|
||||
stream.read(reinterpret_cast<char*>(¶m.period), sizeof(double));
|
||||
|
||||
res.push_back(std::move(param));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Parameters> readFile(std::filesystem::path file, Format format) {
|
||||
std::filesystem::path cachedFile = FileSys.cacheManager()->cachedFilename(file);
|
||||
if (std::filesystem::is_regular_file(cachedFile)) {
|
||||
LINFO(fmt::format(
|
||||
"Cached file {} used for Kepler file {}", cachedFile, file
|
||||
));
|
||||
|
||||
std::optional<std::vector<Parameters>> res = loadCache(cachedFile);
|
||||
if (res.has_value()) {
|
||||
return *res;
|
||||
}
|
||||
|
||||
// If there is no value in the optional, the cached loading failed
|
||||
}
|
||||
|
||||
std::vector<Parameters> res;
|
||||
switch (format) {
|
||||
case Format::TLE:
|
||||
res = readTleFile(file);
|
||||
break;
|
||||
case Format::OMM:
|
||||
res = readOmmFile(file);
|
||||
break;
|
||||
case Format::SBDB:
|
||||
res = readSbdbFile(file);
|
||||
break;
|
||||
default:
|
||||
throw ghoul::MissingCaseException();
|
||||
}
|
||||
|
||||
LINFO(fmt::format("Saving cache {} for Kepler file {}", cachedFile, file));
|
||||
saveCache(res, cachedFile);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace openspace::kepler
|
||||
110
modules/space/kepler.h
Normal file
110
modules/space/kepler.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___KEPLER___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___KEPLER___H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openspace::kepler {
|
||||
|
||||
struct Parameters {
|
||||
// Some human-readable name for the object represented by this kepler parameter set
|
||||
std::string name;
|
||||
|
||||
// Some form of unique identifier for the object represented by this data
|
||||
std::string id;
|
||||
|
||||
double inclination = 0.0;
|
||||
double semiMajorAxis = 0.0;
|
||||
double ascendingNode = 0.0;
|
||||
double eccentricity = 0.0;
|
||||
double argumentOfPeriapsis = 0.0;
|
||||
double meanAnomaly = 0.0;
|
||||
double epoch = 0.0;
|
||||
double period = 0.0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads the object information from the provided \p file and returns them as individual
|
||||
* values.
|
||||
*
|
||||
* \param file The file to the TLE file. This file must be a valid file
|
||||
* \return Information about all of the contained objects in the \p file
|
||||
*
|
||||
* \pre \p file must be a file and must exist
|
||||
* \throw ghoul::RuntimeError If the provided \p file is not a valid TLE file
|
||||
*/
|
||||
std::vector<Parameters> readTleFile(std::filesystem::path file);
|
||||
|
||||
/**
|
||||
* Reads the object information from the provided \p file and returns them as individual
|
||||
* values.
|
||||
*
|
||||
* \param file The file to the OMM file. This file must be a valid file
|
||||
* \return Information about all of the contained objects in the \p file
|
||||
*
|
||||
* \pre \p file must be a file and must exist
|
||||
* \throw ghoul::RuntimeError If the provided \p file is not a valid OMM file
|
||||
*/
|
||||
std::vector<Parameters> readOmmFile(std::filesystem::path file);
|
||||
|
||||
/**
|
||||
* Reads the object information from a CSV file following JPL's Small Body Database
|
||||
* format, which provides the Epoch, eccentricity, semi-major axis (in AU), inclination,
|
||||
* ascending node, argument of periapsis, mean anomaly, and period in that order.
|
||||
*
|
||||
* \param file The CSV file containing the information about the objects
|
||||
* \return Information about all of the contained objects in the \p file
|
||||
*
|
||||
* \pre \p file must be a file and must exist
|
||||
* \throw ghoul::RuntimeError If the provided \p is not a valid JPL SBDB CSV format
|
||||
*/
|
||||
std::vector<Parameters> readSbdbFile(std::filesystem::path file);
|
||||
|
||||
/**
|
||||
* The different formats that the #readFile function is capable of loading
|
||||
*/
|
||||
enum class Format {
|
||||
TLE,
|
||||
OMM,
|
||||
SBDB
|
||||
};
|
||||
/**
|
||||
* Reads the object information from the provided file.
|
||||
*
|
||||
* \param file The file containing the information about the objects
|
||||
* \param format The format of the provided \p file
|
||||
* \return Information about all of the contained objects in the \p file
|
||||
*
|
||||
* \pre \p file must be a file and must exist
|
||||
* \throw ghoul::RuntimeError If the provided \p is not in the provided file
|
||||
*/
|
||||
std::vector<Parameters> readFile(std::filesystem::path file, Format format);
|
||||
|
||||
} // namespace openspace::kepler
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___KEPLER___H__
|
||||
258
modules/space/labelscomponent.cpp
Normal file
258
modules/space/labelscomponent.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/labelscomponent.h>
|
||||
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/font/fontmanager.h>
|
||||
#include <ghoul/font/fontrenderer.h>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "LabelsComponent";
|
||||
|
||||
constexpr int RenderOptionFaceCamera = 0;
|
||||
constexpr int RenderOptionPositionNormal = 1;
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FileInfo = {
|
||||
"File",
|
||||
"File",
|
||||
"The speck label file with tha data for the labels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo UnitInfo = {
|
||||
"Unit",
|
||||
"Unit",
|
||||
"Distance unit for the label data"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo OpacityInfo = {
|
||||
"Opacity",
|
||||
"Opacity",
|
||||
"Determines the transparency of the labels, where 1 is completely opaque "
|
||||
"and 0 fully transparent"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
|
||||
"Color",
|
||||
"Color",
|
||||
"The color of the labels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SizeInfo = {
|
||||
"Size",
|
||||
"Size",
|
||||
"The size of the labels in pixels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FontSizeInfo = {
|
||||
"FontSize",
|
||||
"Font Size",
|
||||
"Font size for the labels. This is different from the text size"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo MinMaxInfo = {
|
||||
"MinMaxSize",
|
||||
"Min/Max Size",
|
||||
"The minimum and maximum size (in pixels) of the labels"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo FaceCameraInfo = {
|
||||
"FaceCamera",
|
||||
"Face Camera",
|
||||
"If enabled, the labels will be rotated to face the camera. "
|
||||
"For non-linear display rendering (for example fisheye) this should be set to "
|
||||
"false."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(LabelsComponent)]] Parameters {
|
||||
// [[codegen::verbatim(FileInfo.description)]]
|
||||
std::filesystem::path file;
|
||||
|
||||
// [[codegen::verbatim(OpacityInfo.description)]]
|
||||
std::optional<float> opacity [[codegen::inrange(0.0, 1.0)]];
|
||||
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(SizeInfo.description)]]
|
||||
std::optional<float> size;
|
||||
|
||||
// [[codegen::verbatim(FontSizeInfo.description)]]
|
||||
std::optional<float> fontSize;
|
||||
|
||||
// [[codegen::verbatim(MinMaxInfo.description)]]
|
||||
std::optional<glm::ivec2> minMaxSize;
|
||||
|
||||
enum class [[codegen::map(openspace::DistanceUnit)]] Unit {
|
||||
Meter [[codegen::key("m")]],
|
||||
Kilometer [[codegen::key("Km")]],
|
||||
Parsec [[codegen::key("pc")]],
|
||||
Kiloparsec [[codegen::key("Kpc")]],
|
||||
Megaparsec [[codegen::key("Mpc")]],
|
||||
Gigaparsec [[codegen::key("Gpc")]],
|
||||
Gigalightyear [[codegen::key("Gly")]]
|
||||
};
|
||||
std::optional<Unit> unit;
|
||||
|
||||
// [[codegen::verbatim(FaceCameraInfo.description)]]
|
||||
std::optional<bool> faceCamera;
|
||||
};
|
||||
#include "labelscomponent_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation LabelsComponent::Documentation() {
|
||||
return codegen::doc<Parameters>("space_labelscomponent");
|
||||
}
|
||||
|
||||
LabelsComponent::LabelsComponent(const ghoul::Dictionary& dictionary)
|
||||
: properties::PropertyOwner({ "Labels" })
|
||||
, _opacity(OpacityInfo, 1.f, 0.f, 1.f)
|
||||
, _color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _size(SizeInfo, 8.f, 0.5f, 24.f)
|
||||
, _fontSize(FontSizeInfo, 50.f, 1.f, 300.f)
|
||||
, _minMaxSize(
|
||||
MinMaxInfo,
|
||||
glm::ivec2(8, 500),
|
||||
glm::ivec2(0),
|
||||
glm::ivec2(1000)
|
||||
)
|
||||
, _faceCamera(FaceCameraInfo, true)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
_labelFile = absPath(p.file);
|
||||
|
||||
if (p.unit.has_value()) {
|
||||
_unit = codegen::map<DistanceUnit>(*p.unit);
|
||||
}
|
||||
else {
|
||||
_unit = DistanceUnit::Meter;
|
||||
}
|
||||
|
||||
_opacity = p.opacity.value_or(_opacity);
|
||||
addProperty(_opacity);
|
||||
|
||||
_color = p.color.value_or(_color);
|
||||
_color.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_color);
|
||||
|
||||
_size = p.size.value_or(_size);
|
||||
addProperty(_size);
|
||||
|
||||
_fontSize = p.fontSize.value_or(_fontSize);
|
||||
_fontSize.onChange([this]() { initialize(); });
|
||||
addProperty(_fontSize);
|
||||
// @TODO (emmbr, 2021-05-31): Temporarily set as read only, to avoid errors from font
|
||||
// rendering (avoid filling font atlas)
|
||||
_fontSize.setReadOnly(true);
|
||||
|
||||
_minMaxSize = p.minMaxSize.value_or(_minMaxSize);
|
||||
_minMaxSize.setViewOption(properties::Property::ViewOptions::MinMaxRange);
|
||||
addProperty(_minMaxSize);
|
||||
|
||||
if (p.faceCamera.has_value()) {
|
||||
_faceCamera = *p.faceCamera;
|
||||
}
|
||||
else {
|
||||
// @TODO (abock. 2021-01-31) In the other DU classes, this is done with an enum, and
|
||||
// doing it based on the fisheye rendering seems a bit brittle?
|
||||
|
||||
// (malej 2022-SEP-14)
|
||||
// For non-linear display rendering (for example fisheye) _faceCamera should be false,
|
||||
// otherwise true.
|
||||
_faceCamera = !global::windowDelegate->isFisheyeRendering();
|
||||
}
|
||||
addProperty(_faceCamera);
|
||||
}
|
||||
|
||||
speck::Labelset& LabelsComponent::labelSet() {
|
||||
return _labelset;
|
||||
}
|
||||
|
||||
const speck::Labelset& LabelsComponent::labelSet() const {
|
||||
return _labelset;
|
||||
}
|
||||
|
||||
void LabelsComponent::initialize() {
|
||||
_font = global::fontManager->font(
|
||||
"Mono",
|
||||
_fontSize,
|
||||
ghoul::fontrendering::FontManager::Outline::Yes,
|
||||
ghoul::fontrendering::FontManager::LoadGlyphs::No
|
||||
);
|
||||
}
|
||||
|
||||
void LabelsComponent::loadLabels() {
|
||||
LINFO(fmt::format("Loading label file {}", _labelFile));
|
||||
_labelset = speck::label::loadFileWithCache(_labelFile);
|
||||
}
|
||||
|
||||
bool LabelsComponent::isReady() const {
|
||||
return !(_labelset.entries.empty());
|
||||
}
|
||||
|
||||
void LabelsComponent::render(const RenderData& data, const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::vec3& orthoRight, const glm::vec3& orthoUp,
|
||||
float fadeInVariable)
|
||||
{
|
||||
float scale = static_cast<float>(toMeter(_unit));
|
||||
|
||||
int renderOption = _faceCamera ? RenderOptionFaceCamera : RenderOptionPositionNormal;
|
||||
|
||||
ghoul::fontrendering::FontRenderer::ProjectedLabelsInformation labelInfo;
|
||||
labelInfo.orthoRight = orthoRight;
|
||||
labelInfo.orthoUp = orthoUp;
|
||||
labelInfo.minSize = _minMaxSize.value().x;
|
||||
labelInfo.maxSize = _minMaxSize.value().y;
|
||||
labelInfo.cameraPos = data.camera.positionVec3();
|
||||
labelInfo.cameraLookUp = data.camera.lookUpVectorWorldSpace();
|
||||
labelInfo.renderType = renderOption;
|
||||
labelInfo.mvpMatrix = modelViewProjectionMatrix;
|
||||
labelInfo.scale = pow(10.f, _size);
|
||||
labelInfo.enableDepth = true;
|
||||
labelInfo.enableFalseDepth = false;
|
||||
|
||||
glm::vec4 textColor = glm::vec4(glm::vec3(_color), _opacity * fadeInVariable);
|
||||
|
||||
for (const speck::Labelset::Entry& e : _labelset.entries) {
|
||||
glm::vec3 scaledPos(e.position);
|
||||
scaledPos *= scale;
|
||||
ghoul::fontrendering::FontRenderer::defaultProjectionRenderer().render(
|
||||
*_font,
|
||||
scaledPos,
|
||||
e.text,
|
||||
textColor,
|
||||
labelInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -1,4 +1,4 @@
|
||||
/****************************************************************************************
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
@@ -22,50 +22,63 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___LABELSCOMPONENT___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___LABELSCOMPONENT___H__
|
||||
|
||||
#include <modules/space/rendering/renderableorbitalkepler.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
#include <openspace/properties/propertyowner.h>
|
||||
|
||||
#include <modules/base/rendering/renderabletrail.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/scalar/uintproperty.h>
|
||||
#include <modules/space/speckloader.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/ivec2property.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/util/distanceconversion.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/misc/objectmanager.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace ghoul::fontrendering { class Font; }
|
||||
|
||||
namespace openspace {
|
||||
struct RenderData;
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
class RenderableSmallBody : public RenderableOrbitalKepler {
|
||||
class LabelsComponent : public properties::PropertyOwner {
|
||||
public:
|
||||
RenderableSmallBody(const ghoul::Dictionary& dictionary);
|
||||
explicit LabelsComponent(const ghoul::Dictionary& dictionary);
|
||||
~LabelsComponent() override = default;
|
||||
|
||||
speck::Labelset& labelSet();
|
||||
const speck::Labelset& labelSet() const;
|
||||
|
||||
void initialize();
|
||||
|
||||
void loadLabels();
|
||||
|
||||
bool isReady() const;
|
||||
|
||||
void render(const RenderData& data, const glm::dmat4& modelViewProjectionMatrix,
|
||||
const glm::vec3& orthoRight, const glm::vec3& orthoUp,
|
||||
float fadeInVariable = 1.f);
|
||||
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
private:
|
||||
void readOrbitalParamsFromThisLine(bool firstDataLine, int& fieldCount,
|
||||
unsigned int& csvLine, std::ifstream& file);
|
||||
virtual void readDataFile(const std::string& filename) override;
|
||||
void initializeFileReading();
|
||||
void skipSingleLineInFile(std::ifstream& file);
|
||||
std::filesystem::path _labelFile;
|
||||
DistanceUnit _unit = DistanceUnit::Parsec;
|
||||
speck::Labelset _labelset;
|
||||
|
||||
std::vector<std::string> _sbNames;
|
||||
std::function<void()> _updateContiguousModeSelect;
|
||||
std::function<void()> _updateRenderUpperLimitSelect;
|
||||
std::shared_ptr<ghoul::fontrendering::Font> _font = nullptr;
|
||||
|
||||
/// The index array that is potentially used in the draw call. If this is empty, no
|
||||
/// element draw call is used.
|
||||
std::vector<unsigned int> _indexBufferData;
|
||||
properties::BoolProperty _contiguousMode;
|
||||
properties::UIntProperty _upperLimit;
|
||||
properties::Property::OnChangeHandle _contiguousModeCallbackhandle;
|
||||
properties::Property::OnChangeHandle _upperLimitCallbackHandle;
|
||||
// Properties
|
||||
properties::FloatProperty _opacity;
|
||||
properties::Vec3Property _color;
|
||||
properties::FloatProperty _size;
|
||||
properties::FloatProperty _fontSize;
|
||||
properties::IVec2Property _minMaxSize;
|
||||
properties::BoolProperty _faceCamera;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___LABELSCOMPONENT___H__
|
||||
@@ -25,13 +25,11 @@
|
||||
#include <modules/space/rendering/renderableconstellationbounds.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/misc.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
@@ -50,14 +48,6 @@ namespace {
|
||||
"constellations"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ConstellationInfo = {
|
||||
"ConstellationFile",
|
||||
"Constellation File Path",
|
||||
"Specifies the file that contains the mapping between constellation "
|
||||
"abbreviations and full name of the constellation. If this value is empty, the "
|
||||
"abbreviations are used as the full names"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
|
||||
"Color",
|
||||
"Color of constellation lines",
|
||||
@@ -65,33 +55,12 @@ namespace {
|
||||
"full opacity"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
|
||||
"LineWidth",
|
||||
"Line Width",
|
||||
"The line width of the constellation bounds"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SelectionInfo = {
|
||||
"ConstellationSelection",
|
||||
"Constellation Selection",
|
||||
"The constellations that are selected are displayed on the celestial sphere"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableConstellationBounds)]] Parameters {
|
||||
// [[codegen::verbatim(VertexInfo.description)]]
|
||||
std::string file;
|
||||
|
||||
// [[codegen::verbatim(ConstellationInfo.description)]]
|
||||
std::optional<std::string> constellationFile;
|
||||
std::filesystem::path file;
|
||||
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color [[codegen::color()]];
|
||||
|
||||
// [[codegen::verbatim(LineWidthInfo.description)]]
|
||||
std::optional<float> lineWidth;
|
||||
|
||||
// [[codegen::verbatim(SelectionInfo.description)]]
|
||||
std::optional<std::vector<std::string>> constellationSelection;
|
||||
};
|
||||
#include "renderableconstellationbounds_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -103,53 +72,46 @@ documentation::Documentation RenderableConstellationBounds::Documentation() {
|
||||
}
|
||||
|
||||
RenderableConstellationBounds::RenderableConstellationBounds(
|
||||
const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
const ghoul::Dictionary& dictionary)
|
||||
: RenderableConstellationsBase(dictionary)
|
||||
, _vertexFilename(VertexInfo)
|
||||
, _constellationFilename(ConstellationInfo)
|
||||
, _color(ColorInfo, glm::vec3(1.f, 0.f, 0.f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _lineWidth(LineWidthInfo, 2.f, 1.f, 32.f)
|
||||
, _constellationSelection(SelectionInfo)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
_vertexFilename.onChange([&](){ loadVertexFile(); });
|
||||
// Avoid reading files here, instead do it in multithreaded initialize()
|
||||
_vertexFilename = absPath(p.file.string()).string();
|
||||
_vertexFilename.onChange([&](){ loadData(); });
|
||||
addProperty(_vertexFilename);
|
||||
_vertexFilename = p.file;
|
||||
|
||||
_constellationFilename.onChange([&](){ loadConstellationFile(); });
|
||||
_constellationFilename = p.constellationFile.value_or(_constellationFilename);
|
||||
addProperty(_constellationFilename);
|
||||
|
||||
_color.setViewOption(properties::Property::ViewOptions::Color);
|
||||
_color = p.color.value_or(_color);
|
||||
addProperty(_color);
|
||||
}
|
||||
|
||||
_lineWidth = p.lineWidth.value_or(_lineWidth);
|
||||
addProperty(_lineWidth);
|
||||
void RenderableConstellationBounds::initialize() {
|
||||
RenderableConstellationsBase::initialize();
|
||||
|
||||
fillSelectionProperty();
|
||||
_constellationSelection.onChange([this]() { selectionPropertyHasChanged(); });
|
||||
addProperty(_constellationSelection);
|
||||
loadData();
|
||||
|
||||
if (p.constellationSelection.has_value()) {
|
||||
const std::vector<std::string> options = _constellationSelection.options();
|
||||
if (!_assetSelection.empty()) {
|
||||
const std::vector<std::string> options = _selection.options();
|
||||
std::set<std::string> selectedConstellations;
|
||||
|
||||
std::set<std::string> selectedNames;
|
||||
for (const std::string& s : *p.constellationSelection) {
|
||||
for (const std::string& s : _assetSelection) {
|
||||
const auto it = std::find(options.begin(), options.end(), s);
|
||||
if (it == options.end()) {
|
||||
// The user has specified a constellation name that doesn't exist
|
||||
LWARNINGC(
|
||||
"RenderableConstellationBounds",
|
||||
"RenderableConstellationsBase",
|
||||
fmt::format("Option '{}' not found in list of constellations", s)
|
||||
);
|
||||
}
|
||||
else {
|
||||
selectedNames.insert(s);
|
||||
selectedConstellations.insert(s);
|
||||
}
|
||||
}
|
||||
_constellationSelection = selectedNames;
|
||||
_selection = selectedConstellations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,10 +154,16 @@ void RenderableConstellationBounds::deinitializeGL() {
|
||||
}
|
||||
|
||||
bool RenderableConstellationBounds::isReady() const {
|
||||
return (_vao != 0) && (_vbo != 0) && _program;
|
||||
bool isReady = _program && _vao != 0 && _vbo != 0;
|
||||
|
||||
// If we have labels, they also need to be loaded
|
||||
if (_hasLabels) {
|
||||
isReady = isReady && RenderableConstellationsBase::isReady();
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
void RenderableConstellationBounds::render(const RenderData& data, RendererTasks&) {
|
||||
void RenderableConstellationBounds::render(const RenderData& data, RendererTasks& tasks) {
|
||||
_program->activate();
|
||||
|
||||
_program->setUniform("campos", glm::vec4(data.camera.positionVec3(), 1.f));
|
||||
@@ -212,6 +180,7 @@ void RenderableConstellationBounds::render(const RenderData& data, RendererTasks
|
||||
_program->setUniform("ViewProjection", data.camera.viewProjectionMatrix());
|
||||
_program->setUniform("ModelTransform", glm::mat4(modelTransform));
|
||||
_program->setUniform("color", _color);
|
||||
_program->setUniform("opacity", opacity());
|
||||
|
||||
glLineWidth(_lineWidth);
|
||||
|
||||
@@ -223,6 +192,16 @@ void RenderableConstellationBounds::render(const RenderData& data, RendererTasks
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
_program->deactivate();
|
||||
|
||||
RenderableConstellationsBase::render(data, tasks);
|
||||
}
|
||||
|
||||
bool RenderableConstellationBounds::loadData() {
|
||||
bool success = loadVertexFile();
|
||||
if (!success) {
|
||||
throw ghoul::RuntimeError("Error loading data");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool RenderableConstellationBounds::loadVertexFile() {
|
||||
@@ -261,8 +240,8 @@ bool RenderableConstellationBounds::loadVertexFile() {
|
||||
float dec;
|
||||
s >> dec;
|
||||
|
||||
std::string constellationName;
|
||||
s >> constellationName;
|
||||
std::string abbreviation;
|
||||
s >> abbreviation;
|
||||
|
||||
if (!s.good()) {
|
||||
// If this evaluates to true, the stream was not completely filled, which
|
||||
@@ -277,7 +256,7 @@ bool RenderableConstellationBounds::loadVertexFile() {
|
||||
}
|
||||
|
||||
// Did we arrive at a new constellation?
|
||||
if (constellationName != currentBound.constellationAbbreviation) {
|
||||
if (abbreviation != currentBound.constellationAbbreviation) {
|
||||
// Store how many vertices we read during the active time of the constellation
|
||||
currentBound.nVertices = static_cast<GLsizei>(
|
||||
_vertexValues.size() - currentBound.startIndex
|
||||
@@ -286,8 +265,9 @@ bool RenderableConstellationBounds::loadVertexFile() {
|
||||
_constellationBounds.push_back(currentBound);
|
||||
currentBound = ConstellationBound();
|
||||
currentBound.isEnabled = true;
|
||||
currentBound.constellationAbbreviation = constellationName;
|
||||
currentBound.constellationFullName = constellationName;
|
||||
currentBound.constellationAbbreviation = abbreviation;
|
||||
std::string name = constellationFullName(abbreviation);
|
||||
currentBound.constellationFullName = name.empty() ? abbreviation : name;
|
||||
currentBound.startIndex = static_cast<GLsizei>(_vertexValues.size());
|
||||
}
|
||||
|
||||
@@ -326,64 +306,9 @@ bool RenderableConstellationBounds::loadVertexFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderableConstellationBounds::loadConstellationFile() {
|
||||
if (_constellationFilename.value().empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ifstream::goodbit);
|
||||
file.open(absPath(_constellationFilename));
|
||||
|
||||
std::string line;
|
||||
int index = 0;
|
||||
while (file.good()) {
|
||||
std::getline(file, line);
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string abbreviation;
|
||||
std::stringstream s(line);
|
||||
s >> abbreviation;
|
||||
|
||||
const auto it = std::find_if(
|
||||
_constellationBounds.begin(),
|
||||
_constellationBounds.end(),
|
||||
[abbreviation](const ConstellationBound& bound) {
|
||||
return bound.constellationAbbreviation == abbreviation;
|
||||
}
|
||||
);
|
||||
if (it == _constellationBounds.end()) {
|
||||
LERRORC(
|
||||
"RenderableConstellationBounds",
|
||||
fmt::format("Could not find constellation '{}' in list", abbreviation)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the constellations full name
|
||||
std::string fullName;
|
||||
std::getline(s, fullName);
|
||||
ghoul::trimWhitespace(fullName);
|
||||
it->constellationFullName = std::move(fullName);
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderableConstellationBounds::fillSelectionProperty() {
|
||||
for (int i = 0 ; i < static_cast<int>(_constellationBounds.size()); ++i) {
|
||||
const ConstellationBound& bound = _constellationBounds[i];
|
||||
_constellationSelection.addOption(bound.constellationFullName);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableConstellationBounds::selectionPropertyHasChanged() {
|
||||
// If no values are selected (the default), we want to show all constellations
|
||||
if (!_constellationSelection.hasSelected()) {
|
||||
if (!_selection.hasSelected()) {
|
||||
for (ConstellationBound& b : _constellationBounds) {
|
||||
b.isEnabled = true;
|
||||
}
|
||||
@@ -391,7 +316,7 @@ void RenderableConstellationBounds::selectionPropertyHasChanged() {
|
||||
else {
|
||||
// Enable all constellations that are selected
|
||||
for (ConstellationBound& b : _constellationBounds) {
|
||||
b.isEnabled = _constellationSelection.isSelected(b.constellationFullName);
|
||||
b.isEnabled = _selection.isSelected(b.constellationFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONBOUNDS___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONBOUNDS___H__
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <openspace/properties/selectionproperty.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <ghoul/opengl/ghoul_gl.h>
|
||||
#include <vector>
|
||||
#include <modules/space/rendering/renderableconstellationsbase.h>
|
||||
|
||||
namespace ghoul::opengl { class ProgramObject; }
|
||||
|
||||
@@ -48,10 +42,11 @@ namespace documentation { struct Documentation; }
|
||||
* <code>_distance</code> property. Currently, all constellation bounds are lines, which
|
||||
* leads to artifacts if the radius is very small.
|
||||
*/
|
||||
class RenderableConstellationBounds : public Renderable {
|
||||
class RenderableConstellationBounds : public RenderableConstellationsBase {
|
||||
public:
|
||||
RenderableConstellationBounds(const ghoul::Dictionary& dictionary);
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
@@ -79,41 +74,25 @@ private:
|
||||
* \return \c true if the loading succeeded, \c false otherwise
|
||||
*/
|
||||
bool loadVertexFile();
|
||||
|
||||
/**
|
||||
* Loads the file specified in _constellationFilename that contains the mapping
|
||||
* between abbreviations and full names of constellations.
|
||||
*
|
||||
* \return <code>true</code> if the loading succeeded, <code>false</code> otherwise
|
||||
*/
|
||||
bool loadConstellationFile();
|
||||
|
||||
/// Fills the <code>_constellationSelection</code> property with all constellations
|
||||
void fillSelectionProperty();
|
||||
bool loadData();
|
||||
|
||||
/**
|
||||
* Callback method that gets triggered when <code>_constellationSelection</code>
|
||||
* changes.
|
||||
*/
|
||||
void selectionPropertyHasChanged();
|
||||
void selectionPropertyHasChanged() override;
|
||||
|
||||
/// The filename containing the constellation bounds
|
||||
properties::StringProperty _vertexFilename;
|
||||
|
||||
/// The file containing constellation names
|
||||
properties::StringProperty _constellationFilename;
|
||||
|
||||
/// Determines the color of the constellation lines
|
||||
properties::Vec3Property _color;
|
||||
|
||||
// Linewidth for the constellation bounds
|
||||
properties::FloatProperty _lineWidth;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _program;
|
||||
|
||||
/// The list of all loaded constellation bounds
|
||||
std::vector<ConstellationBound> _constellationBounds;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _program;
|
||||
|
||||
struct Vertex {
|
||||
float x;
|
||||
float y;
|
||||
@@ -121,9 +100,6 @@ private:
|
||||
};
|
||||
std::vector<Vertex> _vertexValues; ///< A list of all vertices of all bounds
|
||||
|
||||
/// The property that stores all indices of constellations that should be drawn
|
||||
properties::SelectionProperty _constellationSelection;
|
||||
|
||||
GLuint _vao = 0;
|
||||
GLuint _vbo = 0;
|
||||
};
|
||||
|
||||
448
modules/space/rendering/renderableconstellationlines.cpp
Normal file
448
modules/space/rendering/renderableconstellationlines.cpp
Normal file
@@ -0,0 +1,448 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/rendering/renderableconstellationlines.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <ghoul/misc/misc.h>
|
||||
#include <ghoul/opengl/openglstatecache.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <scn/scn.h>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "RenderableConstellationLines";
|
||||
|
||||
constexpr std::array<const char*, 4> UniformNames = {
|
||||
"modelViewTransform", "projectionTransform", "opacity", "color"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SpeckInfo = {
|
||||
"File",
|
||||
"Constellation Data File Path",
|
||||
"The file that contains the data for the constellation lines"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawElementsInfo = {
|
||||
"DrawElements",
|
||||
"Draw Elements",
|
||||
"Enables/Disables the drawing of the constellations"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo UnitInfo = {
|
||||
"Unit",
|
||||
"Unit",
|
||||
"The distance unit used for the constellation lines data"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorsInfo = {
|
||||
"Colors",
|
||||
"Constellation Colors",
|
||||
"The defined colors for the constellations to be rendered. "
|
||||
"There can be several groups of constellaitons that can have distinct colors."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableConstellationLines)]] Parameters {
|
||||
// The path to the SPECK file that contains constellation lines data
|
||||
std::filesystem::path file;
|
||||
|
||||
enum class [[codegen::map(openspace::DistanceUnit)]] Unit {
|
||||
Meter [[codegen::key("m")]],
|
||||
Kilometer [[codegen::key("Km")]],
|
||||
Parsec [[codegen::key("pc")]],
|
||||
Kiloparsec [[codegen::key("Kpc")]],
|
||||
Megaparsec [[codegen::key("Mpc")]],
|
||||
Gigaparsec [[codegen::key("Gpc")]],
|
||||
Gigalightyear [[codegen::key("Gly")]]
|
||||
};
|
||||
// [[codegen::verbatim(UnitInfo.description)]]
|
||||
std::optional<Unit> unit;
|
||||
|
||||
// [[codegen::verbatim(ColorsInfo.description)]]
|
||||
std::optional<std::vector<glm::vec3>> colors;
|
||||
};
|
||||
#include "renderableconstellationlines_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation RenderableConstellationLines::Documentation() {
|
||||
return codegen::doc<Parameters>("space_renderable_constellationlines");
|
||||
}
|
||||
|
||||
RenderableConstellationLines::RenderableConstellationLines(
|
||||
const ghoul::Dictionary& dictionary)
|
||||
: RenderableConstellationsBase(dictionary)
|
||||
, _speckFile(SpeckInfo)
|
||||
, _drawElements(DrawElementsInfo, true)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
// Avoid reading files here, instead do it in multithreaded initialize()
|
||||
_speckFile = absPath(p.file.string()).string();
|
||||
_speckFile.onChange([&]() { loadData(); });
|
||||
addProperty(_speckFile);
|
||||
|
||||
addProperty(_drawElements);
|
||||
|
||||
if (p.unit.has_value()) {
|
||||
_constellationUnit = codegen::map<DistanceUnit>(*p.unit);
|
||||
}
|
||||
else {
|
||||
_constellationUnit = DistanceUnit::Meter;
|
||||
}
|
||||
|
||||
if (p.colors.has_value()) {
|
||||
std::vector<glm::vec3> ops = *p.colors;
|
||||
for (size_t i = 0; i < ops.size(); ++i) {
|
||||
_constellationColorMap.insert({ static_cast<int>(i) + 1, ops[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::selectionPropertyHasChanged() {
|
||||
using ConstellationKeyValuePair = std::pair<const int, ConstellationLine>;
|
||||
|
||||
// If no values are selected (the default), we want to show all constellations
|
||||
if (!_selection.hasSelected()) {
|
||||
for (ConstellationKeyValuePair& pair : _renderingConstellationsMap)
|
||||
{
|
||||
pair.second.isEnabled = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Enable all constellations that are selected
|
||||
for (ConstellationKeyValuePair& pair : _renderingConstellationsMap)
|
||||
{
|
||||
pair.second.isEnabled = _selection.isSelected(pair.second.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableConstellationLines::isReady() const {
|
||||
bool isReady = _program && !_renderingConstellationsMap.empty();
|
||||
|
||||
// If we have labels, they also need to be loaded
|
||||
if (_hasLabels) {
|
||||
return isReady && RenderableConstellationsBase::isReady();
|
||||
}
|
||||
return isReady;
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::initialize() {
|
||||
RenderableConstellationsBase::initialize();
|
||||
|
||||
loadData();
|
||||
|
||||
if (!_assetSelection.empty()) {
|
||||
const std::vector<std::string> options = _selection.options();
|
||||
std::set<std::string> selectedConstellations;
|
||||
|
||||
for (const std::string& s : _assetSelection) {
|
||||
const auto it = std::find(options.begin(), options.end(), s);
|
||||
if (it == options.end()) {
|
||||
// The user has specified a constellation name that doesn't exist
|
||||
LWARNINGC(
|
||||
"RenderableConstellationsBase",
|
||||
fmt::format("Option '{}' not found in list of constellations", s)
|
||||
);
|
||||
}
|
||||
else {
|
||||
selectedConstellations.insert(s);
|
||||
}
|
||||
}
|
||||
_selection = selectedConstellations;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::initializeGL() {
|
||||
_program = global::renderEngine->buildRenderProgram(
|
||||
"RenderableConstellationLines",
|
||||
absPath("${MODULE_SPACE}/shaders/constellationlines_vs.glsl"),
|
||||
absPath("${MODULE_SPACE}/shaders/constellationlines_fs.glsl")
|
||||
);
|
||||
|
||||
ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames);
|
||||
|
||||
createConstellations();
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::deinitializeGL() {
|
||||
using ConstellationKeyValuePair = std::pair<const int, ConstellationLine>;
|
||||
for (const ConstellationKeyValuePair& pair : _renderingConstellationsMap)
|
||||
{
|
||||
glDeleteVertexArrays(1, &pair.second.vaoArray);
|
||||
glDeleteBuffers(1, &pair.second.vboArray);
|
||||
}
|
||||
|
||||
if (_program) {
|
||||
global::renderEngine->removeRenderProgram(_program.get());
|
||||
_program = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::renderConstellations(const RenderData&,
|
||||
const glm::dmat4& modelViewMatrix,
|
||||
const glm::dmat4& projectionMatrix)
|
||||
{
|
||||
glEnablei(GL_BLEND, 0);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
glDepthMask(false);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
_program->activate();
|
||||
|
||||
_program->setUniform(_uniformCache.modelViewTransform, modelViewMatrix);
|
||||
_program->setUniform(_uniformCache.projectionTransform, projectionMatrix);
|
||||
_program->setUniform(_uniformCache.opacity, opacity());
|
||||
|
||||
using ConstellationKeyValuePair = std::pair<const int, ConstellationLine>;
|
||||
for (const ConstellationKeyValuePair& pair : _renderingConstellationsMap)
|
||||
{
|
||||
if (!pair.second.isEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_program->setUniform(
|
||||
_uniformCache.color,
|
||||
_constellationColorMap[pair.second.colorIndex]
|
||||
);
|
||||
|
||||
glBindVertexArray(pair.second.vaoArray);
|
||||
|
||||
glLineWidth(_lineWidth);
|
||||
glDrawArrays(GL_LINE_STRIP, 0, pair.second.numV);
|
||||
global::renderEngine->openglStateCache().resetLineState();
|
||||
}
|
||||
|
||||
glBindVertexArray(0);
|
||||
_program->deactivate();
|
||||
|
||||
// Restores GL State
|
||||
global::renderEngine->openglStateCache().resetDepthState();
|
||||
global::renderEngine->openglStateCache().resetBlendState();
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::render(const RenderData& data, RendererTasks& tasks) {
|
||||
const glm::dmat4 modelMatrix =
|
||||
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
|
||||
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
const glm::dmat4 modelViewMatrix = data.camera.combinedViewMatrix() * modelMatrix;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
|
||||
if (_drawElements) {
|
||||
renderConstellations(data, modelViewMatrix, projectionMatrix);
|
||||
}
|
||||
|
||||
RenderableConstellationsBase::render(data, tasks);
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::update(const UpdateData&) {
|
||||
if (_program->isDirty()) {
|
||||
_program->rebuildFromFile();
|
||||
ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableConstellationLines::loadData() {
|
||||
bool success = readSpeckFile();
|
||||
if (!success) {
|
||||
throw ghoul::RuntimeError("Error loading data");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool RenderableConstellationLines::readSpeckFile() {
|
||||
if (_speckFile.value().empty()) {
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path fileName = absPath(_speckFile);
|
||||
|
||||
LINFO(fmt::format("Loading Speck file {}", fileName));
|
||||
std::ifstream file(fileName);
|
||||
if (!file.good()) {
|
||||
LERROR(fmt::format("Failed to open Speck file {}", fileName));
|
||||
return false;
|
||||
}
|
||||
|
||||
const float scale = static_cast<float>(toMeter(_constellationUnit));
|
||||
double maxRadius = 0.0;
|
||||
|
||||
int lineIndex = 0;
|
||||
|
||||
// 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::getline(file, line);
|
||||
|
||||
if (file.eof()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Guard against wrong line endings (copying files from Windows to Mac) causes
|
||||
// lines to have a final \r
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t found = line.find("mesh");
|
||||
if (found == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// mesh lines are structured as follows:
|
||||
// mesh -c colorindex {
|
||||
// colorindex is the index of the color for the mesh
|
||||
std::stringstream str(line);
|
||||
|
||||
ConstellationLine constellationLine;
|
||||
constellationLine.lineIndex = lineIndex;
|
||||
|
||||
std::string dummy;
|
||||
str >> dummy; // mesh command
|
||||
dummy.clear();
|
||||
str >> dummy; // color index command
|
||||
do {
|
||||
if (dummy == "-c") {
|
||||
str >> constellationLine.colorIndex; // color index
|
||||
}
|
||||
else {
|
||||
std::string message = fmt::format("Unknown command '{}' found in "
|
||||
"constellation file '{}'", dummy, fileName);
|
||||
LWARNING(message);
|
||||
}
|
||||
dummy.clear();
|
||||
str >> dummy;
|
||||
} while (dummy != "{");
|
||||
|
||||
std::getline(file, line);
|
||||
|
||||
// Read the identifier
|
||||
std::stringstream id(line);
|
||||
std::string identifier;
|
||||
|
||||
id >> dummy; // id command
|
||||
dummy.clear();
|
||||
std::getline(id, identifier); // identifier
|
||||
ghoul::trimWhitespace(identifier);
|
||||
std::string name = constellationFullName(identifier);
|
||||
if (!name.empty()) {
|
||||
constellationLine.name = name;
|
||||
}
|
||||
|
||||
// Read the number of vertices
|
||||
std::getline(file, line);
|
||||
std::stringstream dim(line);
|
||||
dim >> constellationLine.numV;
|
||||
|
||||
// We can now read the vertices data:
|
||||
for (int l = 0; l < constellationLine.numV; ++l) {
|
||||
std::getline(file, line);
|
||||
if (line.substr(0, 1) == "}") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to read three values for the position
|
||||
glm::vec3 pos;
|
||||
bool success = true;
|
||||
auto reading = scn::scan(line, "{} {} {}", pos.x, pos.y, pos.z);
|
||||
if (reading) {
|
||||
pos *= scale;
|
||||
constellationLine.vertices.push_back(pos.x);
|
||||
constellationLine.vertices.push_back(pos.y);
|
||||
constellationLine.vertices.push_back(pos.z);
|
||||
}
|
||||
else {
|
||||
success = false;
|
||||
LERROR(fmt::format(
|
||||
"Failed reading position on line {} of mesh {} in file: '{}'. "
|
||||
"Stopped reading constellation data", l, lineIndex, fileName
|
||||
));
|
||||
}
|
||||
|
||||
// Check if new max radius
|
||||
const double r = glm::length(glm::dvec3(pos));
|
||||
maxRadius = std::max(maxRadius, r);
|
||||
}
|
||||
|
||||
std::getline(file, line);
|
||||
if (line.substr(0, 1) == "}") {
|
||||
_renderingConstellationsMap.insert({ lineIndex++, constellationLine });
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
setBoundingSphere(maxRadius);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderableConstellationLines::createConstellations() {
|
||||
LDEBUG("Creating constellations");
|
||||
|
||||
for (std::pair<const int, ConstellationLine>& p : _renderingConstellationsMap) {
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
p.second.vaoArray = vao;
|
||||
|
||||
GLuint vbo;
|
||||
glGenBuffers(1, &vbo);
|
||||
p.second.vboArray = vbo;
|
||||
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
p.second.vertices.size() * sizeof(GLfloat),
|
||||
p.second.vertices.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
// in_position
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
}
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
102
modules/space/rendering/renderableconstellationlines.h
Normal file
102
modules/space/rendering/renderableconstellationlines.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONLINES___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONLINES___H__
|
||||
|
||||
#include <modules/space/rendering/renderableconstellationsbase.h>
|
||||
|
||||
#include <ghoul/opengl/uniformcache.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ghoul::filesystem { class File; }
|
||||
namespace ghoul::fontrendering { class Font; }
|
||||
namespace ghoul::opengl {
|
||||
class ProgramObject;
|
||||
class Texture;
|
||||
} // namespace ghoul::opengl
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
class RenderableConstellationLines : public RenderableConstellationsBase {
|
||||
public:
|
||||
explicit RenderableConstellationLines(const ghoul::Dictionary& dictionary);
|
||||
~RenderableConstellationLines() override = default;
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
bool isReady() const override;
|
||||
|
||||
void render(const RenderData& data, RendererTasks& rendererTask) override;
|
||||
void update(const UpdateData& data) override;
|
||||
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
private:
|
||||
struct ConstellationLine {
|
||||
bool isEnabled = true;
|
||||
std::string name;
|
||||
int lineIndex;
|
||||
int colorIndex;
|
||||
int numV;
|
||||
GLuint vaoArray;
|
||||
GLuint vboArray;
|
||||
std::vector<GLfloat> vertices;
|
||||
};
|
||||
|
||||
void createConstellations();
|
||||
void renderConstellations(const RenderData& data, const glm::dmat4& modelViewMatrix,
|
||||
const glm::dmat4& projectionMatrix);
|
||||
|
||||
bool loadData();
|
||||
bool readSpeckFile();
|
||||
|
||||
/**
|
||||
* Callback method that gets triggered when <code>_constellationSelection</code>
|
||||
* changes
|
||||
*/
|
||||
void selectionPropertyHasChanged() override;
|
||||
|
||||
properties::BoolProperty _drawElements;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _program = nullptr;
|
||||
UniformCache(modelViewTransform, projectionTransform, opacity,
|
||||
color) _uniformCache;
|
||||
|
||||
properties::StringProperty _speckFile;
|
||||
|
||||
DistanceUnit _constellationUnit = DistanceUnit::Parsec;
|
||||
|
||||
std::vector<float> _fullData;
|
||||
|
||||
std::unordered_map<int, glm::vec3> _constellationColorMap;
|
||||
std::unordered_map<int, ConstellationLine> _renderingConstellationsMap;
|
||||
};
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONLINES___H__
|
||||
258
modules/space/rendering/renderableconstellationsbase.cpp
Normal file
258
modules/space/rendering/renderableconstellationsbase.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/rendering/renderableconstellationsbase.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/misc/misc.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
constexpr openspace::properties::Property::PropertyInfo NamesFileInfo = {
|
||||
"NamesFile",
|
||||
"Constellation Names File Path",
|
||||
"Specifies the file that contains the mapping between constellation "
|
||||
"abbreviations and full names of the constellations. If this value is empty, the "
|
||||
"abbreviations are used as the full names"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
|
||||
"LineWidth",
|
||||
"Line Width",
|
||||
"The line width of the constellation"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DrawLabelInfo = {
|
||||
"DrawLabels",
|
||||
"Draw Labels",
|
||||
"Determines whether labels should be drawn or hidden"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SelectionInfo = {
|
||||
"ConstellationSelection",
|
||||
"Constellation Selection",
|
||||
"The constellations that are selected are displayed on the celestial sphere"
|
||||
};
|
||||
|
||||
static const openspace::properties::PropertyOwner::PropertyOwnerInfo LabelsInfo =
|
||||
{
|
||||
"Labels",
|
||||
"Labels",
|
||||
"The labels for the constellations"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableConstellationsBase)]] Parameters {
|
||||
// [[codegen::verbatim(DrawLabelInfo.description)]]
|
||||
std::optional<bool> drawLabels;
|
||||
|
||||
// [[codegen::verbatim(NamesFileInfo.description)]]
|
||||
std::optional<std::filesystem::path> namesFile;
|
||||
|
||||
// [[codegen::verbatim(LineWidthInfo.description)]]
|
||||
std::optional<float> lineWidth;
|
||||
|
||||
// [[codegen::verbatim(SelectionInfo.description)]]
|
||||
std::optional<std::vector<std::string>> selection;
|
||||
|
||||
// [[codegen::verbatim(LabelsInfo.description)]]
|
||||
std::optional<ghoul::Dictionary> labels
|
||||
[[codegen::reference("space_labelscomponent")]];
|
||||
};
|
||||
#include "renderableconstellationsbase_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation RenderableConstellationsBase::Documentation() {
|
||||
return codegen::doc<Parameters>("space_renderable_constellationsbase");
|
||||
}
|
||||
|
||||
RenderableConstellationsBase::RenderableConstellationsBase(const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
, _drawLabels(DrawLabelInfo, false)
|
||||
, _lineWidth(LineWidthInfo, 2.f, 1.f, 16.f)
|
||||
, _namesFilename(NamesFileInfo)
|
||||
, _selection(SelectionInfo)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
addProperty(_opacity);
|
||||
registerUpdateRenderBinFromOpacity();
|
||||
|
||||
// Avoid reading files here, instead do it in multithreaded initialize()
|
||||
if (p.namesFile.has_value()) {
|
||||
_namesFilename = absPath(p.namesFile.value().string()).string();
|
||||
}
|
||||
_namesFilename.onChange([&]() { loadConstellationFile(); });
|
||||
addProperty(_namesFilename);
|
||||
|
||||
_lineWidth = p.lineWidth.value_or(_lineWidth);
|
||||
addProperty(_lineWidth);
|
||||
|
||||
if (p.labels.has_value()) {
|
||||
_drawLabels = p.drawLabels.value_or(_drawLabels);
|
||||
addProperty(_drawLabels);
|
||||
|
||||
_labels = std::make_unique<LabelsComponent>(*p.labels);
|
||||
_hasLabels = true;
|
||||
addPropertySubOwner(_labels.get());
|
||||
}
|
||||
|
||||
_selection.onChange([this]() { selectionPropertyHasChanged(); });
|
||||
addProperty(_selection);
|
||||
|
||||
_assetSelection = p.selection.value_or(_assetSelection);
|
||||
}
|
||||
|
||||
std::string RenderableConstellationsBase::constellationFullName(
|
||||
const std::string& identifier) const
|
||||
{
|
||||
if (_namesTranslation.empty() || identifier.empty()) {
|
||||
std::string message = "List of constellations or the given identifier was empty";
|
||||
LWARNINGC("RenderableConstellationsBase", message);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (_namesTranslation.contains(identifier)) {
|
||||
return _namesTranslation.at(identifier);
|
||||
}
|
||||
|
||||
std::string message = fmt::format(
|
||||
"Identifier '{}' could not be found in list of constellations", identifier
|
||||
);
|
||||
LERRORC("RenderableConstellationsBase", message);
|
||||
return "";
|
||||
}
|
||||
|
||||
void RenderableConstellationsBase::loadConstellationFile() {
|
||||
if (_namesFilename.value().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset
|
||||
_selection.clearOptions();
|
||||
_namesTranslation.clear();
|
||||
|
||||
// Load the constellation names file
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ifstream::goodbit);
|
||||
file.open(absPath(_namesFilename));
|
||||
|
||||
std::string line;
|
||||
while (file.good()) {
|
||||
std::getline(file, line);
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string abbreviation;
|
||||
std::stringstream s(line);
|
||||
s >> abbreviation;
|
||||
|
||||
std::string fullName;
|
||||
std::getline(s, fullName);
|
||||
ghoul::trimWhitespace(fullName);
|
||||
_namesTranslation[abbreviation] = fullName;
|
||||
}
|
||||
|
||||
fillSelectionProperty();
|
||||
}
|
||||
|
||||
void RenderableConstellationsBase::fillSelectionProperty() {
|
||||
for (const std::pair<std::string, std::string>& pair : _namesTranslation) {
|
||||
_selection.addOption(pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableConstellationsBase::initialize() {
|
||||
loadConstellationFile();
|
||||
|
||||
if (!_hasLabels) {
|
||||
return;
|
||||
}
|
||||
|
||||
_labels->initialize();
|
||||
_labels->loadLabels();
|
||||
|
||||
for (speck::Labelset::Entry& entry : _labels->labelSet().entries) {
|
||||
if (!entry.identifier.empty()) {
|
||||
std::string fullName = constellationFullName(entry.identifier);
|
||||
if (!fullName.empty()) {
|
||||
entry.text = fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableConstellationsBase::isReady() const {
|
||||
return _hasLabels ? _labels->isReady() : true;
|
||||
}
|
||||
|
||||
void RenderableConstellationsBase::render(const RenderData& data, RendererTasks&) {
|
||||
if (!_hasLabels || !_drawLabels) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glm::dmat4 modelMatrix =
|
||||
glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation
|
||||
glm::dmat4(data.modelTransform.rotation) * // Spice rotation
|
||||
glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale));
|
||||
|
||||
const glm::dmat4 modelViewMatrix = data.camera.combinedViewMatrix() * modelMatrix;
|
||||
const glm::dmat4 projectionMatrix = data.camera.projectionMatrix();
|
||||
const glm::dmat4 modelViewProjectionMatrix = projectionMatrix * modelViewMatrix;
|
||||
|
||||
const glm::vec3 lookup = data.camera.lookUpVectorWorldSpace();
|
||||
const glm::vec3 viewDirection = data.camera.viewDirectionWorldSpace();
|
||||
glm::vec3 right = glm::cross(viewDirection, lookup);
|
||||
const glm::vec3 up = glm::cross(right, viewDirection);
|
||||
|
||||
const glm::dmat4 worldToModelTransform = glm::inverse(modelMatrix);
|
||||
glm::vec3 orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.f))
|
||||
);
|
||||
|
||||
if (orthoRight == glm::vec3(0.f)) {
|
||||
glm::vec3 otherVector(lookup.y, lookup.x, lookup.z);
|
||||
right = glm::cross(viewDirection, otherVector);
|
||||
orthoRight = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::vec4(right, 0.f))
|
||||
);
|
||||
}
|
||||
|
||||
const glm::vec3 orthoUp = glm::normalize(
|
||||
glm::vec3(worldToModelTransform * glm::dvec4(up, 0.f))
|
||||
);
|
||||
_labels->render(data, modelViewProjectionMatrix, orthoRight, orthoUp);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
114
modules/space/rendering/renderableconstellationsbase.h
Normal file
114
modules/space/rendering/renderableconstellationsbase.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONSBASE___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONSBASE___H__
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <openspace/properties/optionproperty.h>
|
||||
#include <openspace/properties/selectionproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/properties/vector/ivec2property.h>
|
||||
#include <openspace/util/distanceconversion.h>
|
||||
#include <ghoul/misc/managedmemoryuniqueptr.h>
|
||||
#include <ghoul/opengl/ghoul_gl.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ghoul::opengl { class ProgramObject; }
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
/**
|
||||
* This is a base class for constellation lines and bounds
|
||||
*/
|
||||
class RenderableConstellationsBase : public Renderable {
|
||||
public:
|
||||
virtual ~RenderableConstellationsBase() override = default;
|
||||
|
||||
virtual void initialize() override;
|
||||
virtual void initializeGL() override = 0;
|
||||
virtual void deinitializeGL() override = 0;
|
||||
|
||||
virtual bool isReady() const override;
|
||||
|
||||
virtual void render(const RenderData& data, RendererTasks& rendererTask) override;
|
||||
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
protected:
|
||||
explicit RenderableConstellationsBase(const ghoul::Dictionary& dictionary);
|
||||
|
||||
/**
|
||||
* Callback method that gets triggered when <code>_constellationSelection</code>
|
||||
* changes
|
||||
*/
|
||||
virtual void selectionPropertyHasChanged() = 0;
|
||||
|
||||
/// Takes the given constellation <code>identifier</code> and returns the coresponding
|
||||
/// full name
|
||||
std::string constellationFullName(const std::string& identifier) const;
|
||||
|
||||
// Width for the rendered lines
|
||||
properties::FloatProperty _lineWidth;
|
||||
|
||||
// Property that stores all constellations chosen by the user to be drawn
|
||||
properties::SelectionProperty _selection;
|
||||
|
||||
// Temporary storage of which constellations should be rendered as stated in the
|
||||
// asset file
|
||||
std::vector<std::string> _assetSelection;
|
||||
|
||||
// Labels
|
||||
bool _hasLabels = false;
|
||||
properties::BoolProperty _drawLabels;
|
||||
|
||||
private:
|
||||
// Map over the constellations names and their abbreviations
|
||||
// key = abbreviation, value = full name
|
||||
std::map<std::string, std::string> _namesTranslation;
|
||||
|
||||
/**
|
||||
* Loads the file specified in <code>_constellationNamesFilename</code> that contains
|
||||
* the mapping between abbreviations and full names of constellations
|
||||
*/
|
||||
void loadConstellationFile();
|
||||
|
||||
/// Fills the <code>_constellationSelection</code> property with all constellations
|
||||
void fillSelectionProperty();
|
||||
|
||||
// The file containing constellation names and abbreviations
|
||||
properties::StringProperty _namesFilename;
|
||||
|
||||
// Everything related to the labels is handled by LabelsComponent
|
||||
std::unique_ptr<LabelsComponent> _labels;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLECONSTELLATIONSBASE___H__
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <modules/space/rendering/renderableorbitalkepler.h>
|
||||
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <modules/space/translation/tletranslation.h>
|
||||
#include <modules/space/spacemodule.h>
|
||||
#include <openspace/engine/openspaceengine.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
@@ -42,130 +41,10 @@
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <math.h>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr std::array<int, 36> LeapYears = {
|
||||
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
|
||||
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
|
||||
2044, 2048, 2052, 2056
|
||||
};
|
||||
constexpr std::array<int, 12> DaysOfMonths = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||||
};
|
||||
|
||||
// Find the position of the current year in the vector; its position in
|
||||
// the vector gives the number of leap seconds
|
||||
struct LeapSecond {
|
||||
int year;
|
||||
int dayOfYear;
|
||||
bool operator<(const LeapSecond& rhs) const {
|
||||
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr LeapSecond LeapEpoch = { 2000, 1 };
|
||||
|
||||
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
|
||||
constexpr std::array<LeapSecond, 28> LeapSeconds = {
|
||||
LeapSecond{ 1972, 1 },
|
||||
LeapSecond{ 1972, 183 },
|
||||
LeapSecond{ 1973, 1 },
|
||||
LeapSecond{ 1974, 1 },
|
||||
LeapSecond{ 1975, 1 },
|
||||
LeapSecond{ 1976, 1 },
|
||||
LeapSecond{ 1977, 1 },
|
||||
LeapSecond{ 1978, 1 },
|
||||
LeapSecond{ 1979, 1 },
|
||||
LeapSecond{ 1980, 1 },
|
||||
LeapSecond{ 1981, 182 },
|
||||
LeapSecond{ 1982, 182 },
|
||||
LeapSecond{ 1983, 182 },
|
||||
LeapSecond{ 1985, 182 },
|
||||
LeapSecond{ 1988, 1 },
|
||||
LeapSecond{ 1990, 1 },
|
||||
LeapSecond{ 1991, 1 },
|
||||
LeapSecond{ 1992, 183 },
|
||||
LeapSecond{ 1993, 182 },
|
||||
LeapSecond{ 1994, 182 },
|
||||
LeapSecond{ 1996, 1 },
|
||||
LeapSecond{ 1997, 182 },
|
||||
LeapSecond{ 1999, 1 },
|
||||
LeapSecond{ 2006, 1 },
|
||||
LeapSecond{ 2009, 1 },
|
||||
LeapSecond{ 2012, 183 },
|
||||
LeapSecond{ 2015, 182 },
|
||||
LeapSecond{ 2017, 1 }
|
||||
};
|
||||
|
||||
// Count the number of full days since the beginning of 2000 to the beginning of
|
||||
// the parameter 'year'
|
||||
int countDays(int year) {
|
||||
// Find the position of the current year in the vector, the difference
|
||||
// between its position and the position of 2000 (for J2000) gives the
|
||||
// number of leap years
|
||||
constexpr int Epoch = 2000;
|
||||
constexpr int DaysRegularYear = 365;
|
||||
constexpr int DaysLeapYear = 366;
|
||||
|
||||
if (year == Epoch) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the position of the most recent leap year
|
||||
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
|
||||
|
||||
// Get the position of the epoch
|
||||
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
|
||||
|
||||
const int nYears = std::abs(year - Epoch);
|
||||
const int nRegularYears = nYears - nLeapYears;
|
||||
|
||||
// Get the total number of days as the sum of leap years + non leap years
|
||||
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the number of leap seconds that lie between the {year, dayOfYear}
|
||||
// time point and { 2000, 1 }
|
||||
int countLeapSeconds(int year, int dayOfYear) {
|
||||
// Get the position of the last leap second before the desired date
|
||||
LeapSecond date{ year, dayOfYear };
|
||||
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
|
||||
|
||||
// Get the position of the Epoch
|
||||
const auto y2000 = std::lower_bound(
|
||||
LeapSeconds.begin(),
|
||||
LeapSeconds.end(),
|
||||
LeapEpoch
|
||||
);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
|
||||
return nLeapSeconds;
|
||||
}
|
||||
|
||||
int daysIntoGivenYear(int year, int month, int dayOfMonth) {
|
||||
//month and dayCount are zero-based.
|
||||
month -= 1;
|
||||
int dayCount = dayOfMonth - 1;
|
||||
const int February = 1;
|
||||
const bool isInLeapYear =
|
||||
std::find(LeapYears.begin(), LeapYears.end(), year)
|
||||
!= LeapYears.end();
|
||||
|
||||
for (int m = 0; m < month; ++m) {
|
||||
dayCount += DaysOfMonths[m];
|
||||
if (m == February && isInLeapYear) {
|
||||
dayCount += 1;
|
||||
}
|
||||
}
|
||||
return dayCount;
|
||||
}
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo PathInfo = {
|
||||
"Path",
|
||||
"Path",
|
||||
@@ -213,10 +92,30 @@ namespace {
|
||||
"Contiguous Size of Render Block",
|
||||
"Number of objects to render sequentially from StartRenderIdx"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ContiguousModeInfo = {
|
||||
"ContiguousMode",
|
||||
"Contiguous Mode",
|
||||
"If enabled, then the contiguous set of objects starting from StartRenderIdx "
|
||||
"of size RenderSize will be rendered. If disabled, then the number of objects "
|
||||
"defined by UpperLimit will rendered from an evenly dispersed sample of the "
|
||||
"full length of the data file."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableOrbitalKepler)]] Parameters {
|
||||
// [[codegen::verbatim(PathInfo.description)]]
|
||||
std::string path;
|
||||
std::filesystem::path path;
|
||||
|
||||
enum class [[codegen::map(openspace::kepler::Format)]] Format {
|
||||
// A NORAD-style Two-Line element
|
||||
TLE,
|
||||
// Orbit Mean-Elements Message in the KVN notation
|
||||
OMM,
|
||||
// JPL's Small Bodies Database
|
||||
SBDB
|
||||
};
|
||||
// The file format that is contained in the file
|
||||
Format format;
|
||||
|
||||
// [[codegen::verbatim(SegmentQualityInfo.description)]]
|
||||
int segmentQuality;
|
||||
@@ -235,6 +134,9 @@ namespace {
|
||||
|
||||
// [[codegen::verbatim(RenderSizeInfo.description)]]
|
||||
std::optional<int> renderSize;
|
||||
|
||||
// [[codegen::verbatim(ContiguousModeInfo.description)]]
|
||||
std::optional<bool> contiguousMode;
|
||||
};
|
||||
#include "renderableorbitalkepler_codegen.cpp"
|
||||
} // namespace
|
||||
@@ -245,140 +147,20 @@ documentation::Documentation RenderableOrbitalKepler::Documentation() {
|
||||
return codegen::doc<Parameters>("space_renderableorbitalkepler");
|
||||
}
|
||||
|
||||
double RenderableOrbitalKepler::calculateSemiMajorAxis(double meanMotion) const {
|
||||
constexpr double GravitationalConstant = 6.6740831e-11;
|
||||
constexpr double MassEarth = 5.9721986e24;
|
||||
constexpr double muEarth = GravitationalConstant * MassEarth;
|
||||
|
||||
// Use Kepler's 3rd law to calculate semimajor axis
|
||||
// a^3 / P^2 = mu / (2pi)^2
|
||||
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
|
||||
// with a = semimajor axis
|
||||
// P = period in seconds
|
||||
// mu = G*M_earth
|
||||
const double period =
|
||||
std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
|
||||
|
||||
constexpr double pisq = glm::pi<double>() * glm::pi<double>();
|
||||
const double semiMajorAxis = pow((muEarth * period*period) / (4 * pisq), 1.0 / 3.0);
|
||||
|
||||
// We need the semi major axis in km instead of m
|
||||
return semiMajorAxis / 1000.0;
|
||||
}
|
||||
|
||||
double RenderableOrbitalKepler::epochFromSubstring(const std::string& epochString) const {
|
||||
// The epochString is in the form:
|
||||
// YYDDD.DDDDDDDD
|
||||
// With YY being the last two years of the launch epoch, the first DDD the day
|
||||
// of the year and the remaning a fractional part of the day
|
||||
|
||||
// The main overview of this function:
|
||||
// 1. Reconstruct the full year from the YY part
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
// According to https://celestrak.com/columns/v04n03/
|
||||
// Apparently, US Space Command sees no need to change the two-line element
|
||||
// set format yet since no artificial earth satellites existed prior to 1957.
|
||||
// By their reasoning, two-digit years from 57-99 correspond to 1957-1999 and
|
||||
// those from 00-56 correspond to 2000-2056. We'll see each other again in 057!
|
||||
|
||||
// 1. Get the full year
|
||||
std::string yearPrefix =
|
||||
std::atoi(epochString.substr(0, 2).c_str()) > 57 ? "19" : "20";
|
||||
const int year = std::atoi((yearPrefix + epochString.substr(0, 2)).c_str());
|
||||
const int daysSince2000 = countDays(year);
|
||||
|
||||
// 2.
|
||||
double daysInYear = std::atof(epochString.substr(2).c_str());
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
//Need to subtract 1 from daysInYear since it is not a zero-based count
|
||||
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additional leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = -countLeapSeconds(
|
||||
year,
|
||||
static_cast<int>(std::floor(daysInYear))
|
||||
);
|
||||
|
||||
// 5
|
||||
const double nSecondsEpochOffset = static_cast<double>(seconds(hours(12)).count());
|
||||
|
||||
// Combine all of the values
|
||||
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
|
||||
return epoch;
|
||||
}
|
||||
|
||||
double RenderableOrbitalKepler::epochFromYMDdSubstring(const std::string& epochString) {
|
||||
// The epochString is in the form:
|
||||
// YYYYMMDD.ddddddd
|
||||
// With YYYY as the year, MM the month (1 - 12), DD the day of month (1-31),
|
||||
// and dddd the fraction of that day.
|
||||
|
||||
// The main overview of this function:
|
||||
// 1. Read the year value
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
// 1
|
||||
int year = std::atoi(epochString.substr(0, 4).c_str());
|
||||
const int daysSince2000 = countDays(year);
|
||||
|
||||
// 2.
|
||||
int monthNum = std::atoi(epochString.substr(4, 2).c_str());
|
||||
int dayOfMonthNum = std::atoi(epochString.substr(6, 2).c_str());
|
||||
int wholeDaysInto = daysIntoGivenYear(year, monthNum, dayOfMonthNum);
|
||||
double fractionOfDay = std::atof(epochString.substr(9, 7).c_str());
|
||||
double daysInYear = static_cast<double>(wholeDaysInto) + fractionOfDay;
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
//Need to subtract 1 from daysInYear since it is not a zero-based count
|
||||
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additional leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = -countLeapSeconds(
|
||||
year,
|
||||
static_cast<int>(std::floor(daysInYear))
|
||||
);
|
||||
|
||||
// 5
|
||||
const double nSecondsEpochOffset = static_cast<double>(seconds(hours(12)).count());
|
||||
|
||||
// Combine all of the values
|
||||
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
|
||||
return epoch;
|
||||
}
|
||||
|
||||
RenderableOrbitalKepler::RenderableOrbitalKepler(const ghoul::Dictionary& dict)
|
||||
: Renderable(dict)
|
||||
, _segmentQuality(SegmentQualityInfo, 2, 1, 10)
|
||||
, _startRenderIdx(StartRenderIdxInfo, 0, 0, 1)
|
||||
, _sizeRender(RenderSizeInfo, 1, 1, 2)
|
||||
, _path(PathInfo)
|
||||
, _contiguousMode(ContiguousModeInfo, false)
|
||||
{
|
||||
_reinitializeTrailBuffers = std::function<void()>([this] { initializeGL(); });
|
||||
|
||||
const Parameters p = codegen::bake<Parameters>(dict);
|
||||
|
||||
addProperty(_opacity);
|
||||
|
||||
_segmentQuality = static_cast<unsigned int>(p.segmentQuality);
|
||||
_segmentQuality.onChange(_reinitializeTrailBuffers);
|
||||
_segmentQuality.onChange([this]() { updateBuffers(); });
|
||||
addProperty(_segmentQuality);
|
||||
|
||||
_appearance.lineColor = p.color;
|
||||
@@ -386,13 +168,37 @@ RenderableOrbitalKepler::RenderableOrbitalKepler(const ghoul::Dictionary& dict)
|
||||
_appearance.lineWidth = p.lineWidth.value_or(2.f);
|
||||
addPropertySubOwner(_appearance);
|
||||
|
||||
_path = p.path;
|
||||
_path.onChange(_reinitializeTrailBuffers);
|
||||
_path = p.path.string();
|
||||
_path.onChange([this]() { updateBuffers(); });
|
||||
addProperty(_path);
|
||||
|
||||
_format = codegen::map<kepler::Format>(p.format);
|
||||
|
||||
_startRenderIdx = p.startRenderIdx.value_or(0);
|
||||
_startRenderIdx.onChange([this]() {
|
||||
if (_contiguousMode) {
|
||||
if ((_numObjects - _startRenderIdx) < _sizeRender) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
|
||||
}
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
}
|
||||
});
|
||||
addProperty(_startRenderIdx);
|
||||
|
||||
_sizeRender = p.renderSize.value_or(0u);
|
||||
_sizeRender.onChange([this]() {
|
||||
if (_contiguousMode) {
|
||||
if (_sizeRender > (_numObjects - _startRenderIdx)) {
|
||||
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
|
||||
}
|
||||
}
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
});
|
||||
addProperty(_sizeRender);
|
||||
|
||||
_contiguousMode = p.contiguousMode.value_or(false);
|
||||
_contiguousMode.onChange([this]() { _updateDataBuffersAtNextRender = true; });
|
||||
addProperty(_contiguousMode);
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::initializeGL() {
|
||||
@@ -420,14 +226,6 @@ void RenderableOrbitalKepler::initializeGL() {
|
||||
_uniformCache.opacity = _programObject->uniformLocation("opacity");
|
||||
|
||||
updateBuffers();
|
||||
|
||||
double maxSemiMajorAxis = 0.0;
|
||||
for (const KeplerParameters& kp : _data) {
|
||||
if (kp.semiMajorAxis > maxSemiMajorAxis) {
|
||||
maxSemiMajorAxis = kp.semiMajorAxis;
|
||||
}
|
||||
}
|
||||
setBoundingSphere(maxSemiMajorAxis * 1000);
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::deinitializeGL() {
|
||||
@@ -447,14 +245,16 @@ bool RenderableOrbitalKepler::isReady() const {
|
||||
return _programObject != nullptr;
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
|
||||
if (_data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::update(const UpdateData&) {
|
||||
if (_updateDataBuffersAtNextRender) {
|
||||
_updateDataBuffersAtNextRender = false;
|
||||
initializeGL();
|
||||
updateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
|
||||
if (_vertexBufferData.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_programObject->activate();
|
||||
@@ -472,9 +272,7 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
|
||||
);
|
||||
|
||||
// Because we want the property to work similar to the planet trails
|
||||
const float fade = static_cast<float>(
|
||||
pow(_appearance.lineFade.maxValue() - _appearance.lineFade, 2.0)
|
||||
);
|
||||
const float fade = pow(_appearance.lineFade.maxValue() - _appearance.lineFade, 2.f);
|
||||
|
||||
_programObject->setUniform(_uniformCache.projection, data.camera.projectionMatrix());
|
||||
_programObject->setUniform(_uniformCache.color, _appearance.lineColor);
|
||||
@@ -482,7 +280,7 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
|
||||
|
||||
glLineWidth(_appearance.lineWidth);
|
||||
|
||||
const size_t nrOrbits = _data.size();
|
||||
const size_t nrOrbits = _segmentSize.size();
|
||||
gl::GLint vertices = 0;
|
||||
|
||||
//glDepthMask(false);
|
||||
@@ -499,21 +297,83 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
|
||||
}
|
||||
|
||||
void RenderableOrbitalKepler::updateBuffers() {
|
||||
readDataFile(_path);
|
||||
std::vector<kepler::Parameters> parameters = kepler::readFile(
|
||||
_path.value(),
|
||||
_format
|
||||
);
|
||||
|
||||
_numObjects = parameters.size();
|
||||
|
||||
if (_startRenderIdx < 0 || _startRenderIdx >= _numObjects) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Start index {} out of range [0, {}]", _startRenderIdx, _numObjects
|
||||
));
|
||||
}
|
||||
|
||||
long long endElement = _startRenderIdx + _sizeRender - 1;
|
||||
endElement = (endElement >= _numObjects) ? _numObjects - 1 : endElement;
|
||||
if (endElement < 0 || endElement >= _numObjects) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"End index {} out of range [0, {}]", endElement, _numObjects
|
||||
));
|
||||
}
|
||||
|
||||
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
|
||||
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
|
||||
if (_sizeRender == 0u) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects);
|
||||
}
|
||||
|
||||
if (_contiguousMode) {
|
||||
if (_startRenderIdx >= parameters.size() ||
|
||||
(_startRenderIdx + _sizeRender) >= parameters.size())
|
||||
{
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Tried to load {} objects but only {} are available",
|
||||
_startRenderIdx + _sizeRender, parameters.size()
|
||||
));
|
||||
}
|
||||
|
||||
// Extract subset that starts at _startRenderIdx and contains _sizeRender obejcts
|
||||
parameters = std::vector<kepler::Parameters>(
|
||||
parameters.begin() + _startRenderIdx,
|
||||
parameters.begin() + _startRenderIdx + _sizeRender
|
||||
);
|
||||
}
|
||||
else {
|
||||
// First shuffle the whole array
|
||||
std::default_random_engine rng;
|
||||
std::shuffle(parameters.begin(), parameters.end(), rng);
|
||||
|
||||
// Then take the first _sizeRender values
|
||||
parameters = std::vector<kepler::Parameters>(
|
||||
parameters.begin(),
|
||||
parameters.begin() + _sizeRender
|
||||
);
|
||||
}
|
||||
|
||||
_segmentSize.clear();
|
||||
for (const kepler::Parameters& p : parameters) {
|
||||
const double scale = static_cast<double>(_segmentQuality) * 10.0;
|
||||
_segmentSize.push_back(
|
||||
static_cast<size_t>(scale + (scale / pow(1 - p.eccentricity, 1.2)))
|
||||
);
|
||||
}
|
||||
|
||||
size_t nVerticesTotal = 0;
|
||||
|
||||
int numOrbits = static_cast<int>(_data.size());
|
||||
int numOrbits = static_cast<int>(parameters.size());
|
||||
for (int i = 0; i < numOrbits; ++i) {
|
||||
nVerticesTotal += _segmentSize[i] + 1;
|
||||
}
|
||||
_vertexBufferData.resize(nVerticesTotal);
|
||||
|
||||
size_t vertexBufIdx = 0;
|
||||
KeplerTranslation keplerTranslator;
|
||||
for (int orbitIdx = 0; orbitIdx < numOrbits; ++orbitIdx) {
|
||||
const KeplerParameters& orbit = _data[orbitIdx];
|
||||
const kepler::Parameters& orbit = parameters[orbitIdx];
|
||||
|
||||
_keplerTranslator.setKeplerElements(
|
||||
keplerTranslator.setKeplerElements(
|
||||
orbit.eccentricity,
|
||||
orbit.semiMajorAxis,
|
||||
orbit.inclination,
|
||||
@@ -526,9 +386,9 @@ void RenderableOrbitalKepler::updateBuffers() {
|
||||
|
||||
for (size_t j = 0 ; j < (_segmentSize[orbitIdx] + 1); ++j) {
|
||||
double timeOffset = orbit.period *
|
||||
static_cast<double>(j)/ static_cast<double>(_segmentSize[orbitIdx]);
|
||||
static_cast<double>(j) / static_cast<double>(_segmentSize[orbitIdx]);
|
||||
|
||||
glm::dvec3 position = _keplerTranslator.position({
|
||||
glm::dvec3 position = keplerTranslator.position({
|
||||
{},
|
||||
Time(timeOffset + orbit.epoch),
|
||||
Time(0.0)
|
||||
@@ -556,7 +416,7 @@ void RenderableOrbitalKepler::updateBuffers() {
|
||||
);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(TrailVBOLayout), nullptr);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(TrailVBOLayout), nullptr);
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(
|
||||
@@ -569,6 +429,14 @@ void RenderableOrbitalKepler::updateBuffers() {
|
||||
);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
double maxSemiMajorAxis = 0.0;
|
||||
for (const kepler::Parameters& kp : parameters) {
|
||||
if (kp.semiMajorAxis > maxSemiMajorAxis) {
|
||||
maxSemiMajorAxis = kp.semiMajorAxis;
|
||||
}
|
||||
}
|
||||
setBoundingSphere(maxSemiMajorAxis * 1000);
|
||||
}
|
||||
|
||||
} // namespace opensapce
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/base/rendering/renderabletrail.h>
|
||||
#include <modules/space/kepler.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/scalar/uintproperty.h>
|
||||
@@ -47,65 +48,21 @@ public:
|
||||
void deinitializeGL() override;
|
||||
|
||||
bool isReady() const override;
|
||||
void update(const UpdateData& data) override;
|
||||
void render(const RenderData& data, RendererTasks& rendererTask) override;
|
||||
|
||||
/**
|
||||
* Reads the provided data file and calls the KeplerTranslation::setKeplerElments
|
||||
* method with the correct values. If \p filename is a valid data file but contains
|
||||
* disallowed values (see KeplerTranslation::setKeplerElements), a
|
||||
* KeplerTranslation::RangeError is thrown.
|
||||
*
|
||||
* \param filename The path to the file that contains the data file.
|
||||
*
|
||||
* \throw ghoul::RuntimeError if the data file does not exist or there is a
|
||||
* problem with its format.
|
||||
* \pre The \p filename must exist
|
||||
*/
|
||||
virtual void readDataFile(const std::string& filename) = 0;
|
||||
|
||||
protected:
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
double calculateSemiMajorAxis(double meanMotion) const;
|
||||
double epochFromSubstring(const std::string& epochString) const;
|
||||
double epochFromYMDdSubstring(const std::string& epochString);
|
||||
private:
|
||||
void updateBuffers();
|
||||
|
||||
std::function<void()> _reinitializeTrailBuffers;
|
||||
std::function<void()> _updateStartRenderIdxSelect;
|
||||
std::function<void()> _updateRenderSizeSelect;
|
||||
|
||||
struct KeplerParameters {
|
||||
double inclination = 0.0;
|
||||
double semiMajorAxis = 0.0;
|
||||
double ascendingNode = 0.0;
|
||||
double eccentricity = 0.0;
|
||||
double argumentOfPeriapsis = 0.0;
|
||||
double meanAnomaly = 0.0;
|
||||
double meanMotion = 0.0;
|
||||
double epoch = 0.0;
|
||||
double period = 0.0;
|
||||
};
|
||||
|
||||
bool _updateDataBuffersAtNextRender = false;
|
||||
std::streamoff _numObjects;
|
||||
bool _isFileReadinitialized = false;
|
||||
inline static constexpr double convertAuToKm = 1.496e8;
|
||||
inline static constexpr double convertDaysToSecs = 86400.0;
|
||||
std::vector<KeplerParameters> _data;
|
||||
std::streamoff _numObjects;
|
||||
std::vector<size_t> _segmentSize;
|
||||
properties::UIntProperty _segmentQuality;
|
||||
properties::UIntProperty _startRenderIdx;
|
||||
properties::UIntProperty _sizeRender;
|
||||
properties::Property::OnChangeHandle _startRenderIdxCallbackHandle;
|
||||
properties::Property::OnChangeHandle _sizeRenderCallbackHandle;
|
||||
|
||||
private:
|
||||
struct Vertex {
|
||||
glm::vec3 position = glm::vec3(0.f);
|
||||
glm::vec3 color = glm::vec3(0.f);
|
||||
glm::vec2 texcoord = glm::vec2(0.f);
|
||||
};
|
||||
|
||||
/// The layout of the VBOs
|
||||
struct TrailVBOLayout {
|
||||
@@ -117,20 +74,17 @@ private:
|
||||
double period = 0.0;
|
||||
};
|
||||
|
||||
KeplerTranslation _keplerTranslator;
|
||||
|
||||
/// The backend storage for the vertex buffer object containing all points for this
|
||||
/// trail.
|
||||
/// The backend storage for the vertex buffer object containing all points
|
||||
std::vector<TrailVBOLayout> _vertexBufferData;
|
||||
|
||||
GLuint _vertexArray;
|
||||
GLuint _vertexBuffer;
|
||||
|
||||
|
||||
ghoul::opengl::ProgramObject* _programObject;
|
||||
properties::StringProperty _path;
|
||||
properties::BoolProperty _contiguousMode;
|
||||
kepler::Format _format;
|
||||
RenderableTrail::Appearance _appearance;
|
||||
glm::vec3 _position = glm::vec3(0.f);
|
||||
|
||||
UniformCache(modelView, projection, lineFade, inGameTime, color, opacity,
|
||||
numberOfSegments) _uniformCache;
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/rendering/renderablesatellites.h>
|
||||
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <modules/space/translation/tletranslation.h>
|
||||
#include <modules/space/spacemodule.h>
|
||||
#include <openspace/engine/openspaceengine.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/util/time.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/filesystem/file.h>
|
||||
#include <ghoul/misc/csvreader.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "Satellites";
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
|
||||
"Segments",
|
||||
"Segments",
|
||||
"The number of segments to use for each orbit ellipse"
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderableSatellites)]] Parameters {
|
||||
// [[codegen::verbatim(SegmentsInfo.description)]]
|
||||
double segments;
|
||||
};
|
||||
#include "renderablesatellites_codegen.cpp"
|
||||
}
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation RenderableSatellites::Documentation() {
|
||||
return codegen::doc<Parameters>(
|
||||
"space_renderablesatellites",
|
||||
RenderableOrbitalKepler::Documentation()
|
||||
);
|
||||
}
|
||||
|
||||
RenderableSatellites::RenderableSatellites(const ghoul::Dictionary& dictionary)
|
||||
: RenderableOrbitalKepler(dictionary)
|
||||
{
|
||||
// Commented out right now as its not super clear how it works with inheritance. We'd
|
||||
// probably want a codegen::check function that only does the checking without
|
||||
// actually creating a Parameter objects
|
||||
// codegen::bake<Parameters>(dictionary);
|
||||
addProperty(_startRenderIdx);
|
||||
addProperty(_sizeRender);
|
||||
|
||||
_updateStartRenderIdxSelect = [this]() {
|
||||
if ((_numObjects - _startRenderIdx) < _sizeRender) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
|
||||
}
|
||||
updateBuffers();
|
||||
};
|
||||
_updateRenderSizeSelect = [this]() {
|
||||
if (_sizeRender > (_numObjects - _startRenderIdx)) {
|
||||
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
|
||||
}
|
||||
updateBuffers();
|
||||
};
|
||||
_startRenderIdxCallbackHandle = _startRenderIdx.onChange(_updateStartRenderIdxSelect);
|
||||
_sizeRenderCallbackHandle = _sizeRender.onChange(_updateRenderSizeSelect);
|
||||
}
|
||||
|
||||
void RenderableSatellites::readDataFile(const std::string& filename) {
|
||||
if (!std::filesystem::is_regular_file(filename)) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Satellite TLE file {} does not exist", filename
|
||||
));
|
||||
}
|
||||
_data.clear();
|
||||
_segmentSize.clear();
|
||||
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
file.open(filename);
|
||||
|
||||
std::streamoff numberOfLines = std::count(
|
||||
std::istreambuf_iterator<char>(file),
|
||||
std::istreambuf_iterator<char>(),
|
||||
'\n'
|
||||
);
|
||||
file.seekg(std::ios_base::beg); // reset iterator to beginning of file
|
||||
|
||||
_numObjects = numberOfLines / nLineEntriesPerSatellite;
|
||||
|
||||
if (!_isFileReadinitialized) {
|
||||
_isFileReadinitialized = true;
|
||||
initializeFileReading();
|
||||
}
|
||||
|
||||
std::string line = "-";
|
||||
std::string name;
|
||||
long long endElement = _startRenderIdx + _sizeRender - 1;
|
||||
endElement = (endElement >= _numObjects) ? _numObjects - 1 : endElement;
|
||||
//Burn lines if not starting at first element
|
||||
for (unsigned int k = 0; k < _startRenderIdx; ++k) {
|
||||
skipSingleEntryInFile(file);
|
||||
}
|
||||
for (std::streamoff i = _startRenderIdx; i <= endElement; i++) {
|
||||
//Read title line
|
||||
std::getline(file, name);
|
||||
KeplerParameters keplerElements;
|
||||
|
||||
std::getline(file, line);
|
||||
if (line[0] == '1') {
|
||||
// First line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 08-08 Classification (U = Unclassified)
|
||||
// 4 10-11 International Designator (Last two digits of launch year)
|
||||
// 5 12-14 International Designator (Launch number of the year)
|
||||
// 6 15-17 International Designator(piece of the launch) A
|
||||
name += " " + line.substr(2, 15);
|
||||
if (_startRenderIdx == i && _sizeRender == 1) {
|
||||
LINFO(fmt::format(
|
||||
"Set render block to start at object {}",
|
||||
name
|
||||
));
|
||||
}
|
||||
// 7 19-20 Epoch Year(last two digits of year)
|
||||
// 8 21-32 Epoch(day of the year and fractional portion of the day)
|
||||
// 9 34-43 First Time Derivative of the Mean Motion divided by two
|
||||
// 10 45-52 Second Time Derivative of Mean Motion divided by six
|
||||
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
|
||||
// 12 63-63 The "Ephemeris type"
|
||||
// 13 65-68 Element set number.Incremented when a new TLE is generated
|
||||
// 14 69-69 Checksum (modulo 10)
|
||||
keplerElements.epoch = epochFromSubstring(line.substr(18, 14));
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"File {} entry {} does not have '1' header", filename, i + 1
|
||||
));
|
||||
}
|
||||
|
||||
std::getline(file, line);
|
||||
if (line[0] == '2') {
|
||||
// Second line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 09-16 Inclination (degrees)
|
||||
// 4 18-25 Right ascension of the ascending node (degrees)
|
||||
// 5 27-33 Eccentricity (decimal point assumed)
|
||||
// 6 35-42 Argument of perigee (degrees)
|
||||
// 7 44-51 Mean Anomaly (degrees)
|
||||
// 8 53-63 Mean Motion (revolutions per day)
|
||||
// 9 64-68 Revolution number at epoch (revolutions)
|
||||
// 10 69-69 Checksum (modulo 10)
|
||||
|
||||
std::stringstream stream;
|
||||
stream.exceptions(std::ios::failbit);
|
||||
|
||||
// Get inclination
|
||||
stream.str(line.substr(8, 8));
|
||||
stream >> keplerElements.inclination;
|
||||
stream.clear();
|
||||
|
||||
// Get Right ascension of the ascending node
|
||||
stream.str(line.substr(17, 8));
|
||||
stream >> keplerElements.ascendingNode;
|
||||
stream.clear();
|
||||
|
||||
// Get Eccentricity
|
||||
stream.str("0." + line.substr(26, 7));
|
||||
stream >> keplerElements.eccentricity;
|
||||
stream.clear();
|
||||
|
||||
// Get argument of periapsis
|
||||
stream.str(line.substr(34, 8));
|
||||
stream >> keplerElements.argumentOfPeriapsis;
|
||||
stream.clear();
|
||||
|
||||
// Get mean anomaly
|
||||
stream.str(line.substr(43, 8));
|
||||
stream >> keplerElements.meanAnomaly;
|
||||
stream.clear();
|
||||
|
||||
// Get mean motion
|
||||
stream.str(line.substr(52, 11));
|
||||
stream >> keplerElements.meanMotion;
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"File {} entry {} does not have '2' header", filename, i + 1
|
||||
));
|
||||
}
|
||||
|
||||
// Calculate the semi major axis based on the mean motion using kepler's laws
|
||||
keplerElements.semiMajorAxis = calculateSemiMajorAxis(keplerElements.meanMotion);
|
||||
|
||||
using namespace std::chrono;
|
||||
double period = seconds(hours(24)).count() / keplerElements.meanMotion;
|
||||
keplerElements.period = period;
|
||||
|
||||
_data.push_back(keplerElements);
|
||||
_segmentSize.push_back(_segmentQuality * 16);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void RenderableSatellites::initializeFileReading() {
|
||||
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
|
||||
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
|
||||
if (_sizeRender == 0u) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSatellites::skipSingleEntryInFile(std::ifstream& file) {
|
||||
std::string line;
|
||||
for (unsigned int i = 0; i < nLineEntriesPerSatellite; i++) {
|
||||
std::getline(file, line);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* 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/space/rendering/renderablesmallbody.h>
|
||||
|
||||
#include <modules/space/rendering/renderablesatellites.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <modules/space/translation/tletranslation.h>
|
||||
#include <modules/space/spacemodule.h>
|
||||
#include <openspace/engine/openspaceengine.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/util/time.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/filesystem/file.h>
|
||||
#include <ghoul/misc/csvreader.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "SmallSolarSystemBody";
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ContiguousModeInfo = {
|
||||
"ContiguousMode",
|
||||
"Contiguous Mode",
|
||||
"If enabled, then the contiguous set of objects starting from StartRenderIdx "
|
||||
"of size RenderSize will be rendered. If disabled, then the number of objects "
|
||||
"defined by UpperLimit will rendered from an evenly dispersed sample of the "
|
||||
"full length of the data file"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo UpperLimitInfo = {
|
||||
"UpperLimit",
|
||||
"Upper Limit",
|
||||
"Upper limit on the number of objects for this renderable, regardless of "
|
||||
"how many objects are contained in the data file. Produces an evenly-distributed"
|
||||
"sample from the data file"
|
||||
};
|
||||
|
||||
double importAngleValue(const std::string& angle) {
|
||||
if (angle.empty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double output = std::stod(angle);
|
||||
output = std::fmod(output, 360.0);
|
||||
if (output < 0.0) {
|
||||
output += 360.0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string& formatObjectName(std::string& name) {
|
||||
const std::string trimChars = "\t\n\v\f\r\" ";
|
||||
name.erase(0, name.find_first_not_of(trimChars));
|
||||
name.erase(name.find_last_not_of(trimChars) + 1);
|
||||
return name;
|
||||
}
|
||||
|
||||
struct [[codegen::Dictionary(RenderableSmallBody)]] Parameters {
|
||||
// [[codegen::verbatim(ContiguousModeInfo.description)]]
|
||||
std::optional<bool> contiguousMode;
|
||||
|
||||
// [[codegen::verbatim(UpperLimitInfo.description)]]
|
||||
std::optional<int> upperLimit;
|
||||
};
|
||||
#include "renderablesmallbody_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation RenderableSmallBody::Documentation() {
|
||||
return codegen::doc<Parameters>(
|
||||
"space_renderablesmallbody",
|
||||
RenderableOrbitalKepler::Documentation()
|
||||
);
|
||||
}
|
||||
|
||||
RenderableSmallBody::RenderableSmallBody(const ghoul::Dictionary& dictionary)
|
||||
: RenderableOrbitalKepler(dictionary)
|
||||
, _contiguousMode(ContiguousModeInfo, false)
|
||||
, _upperLimit(UpperLimitInfo, 1000, 1, 1000000)
|
||||
{
|
||||
codegen::bake<Parameters>(dictionary);
|
||||
|
||||
addProperty(_startRenderIdx);
|
||||
addProperty(_sizeRender);
|
||||
addProperty(_contiguousMode);
|
||||
addProperty(_upperLimit);
|
||||
|
||||
if (dictionary.hasValue<double>(UpperLimitInfo.identifier)) {
|
||||
_upperLimit = static_cast<unsigned int>(
|
||||
dictionary.value<double>(UpperLimitInfo.identifier));
|
||||
}
|
||||
else {
|
||||
_upperLimit = 0u;
|
||||
}
|
||||
|
||||
if (dictionary.hasValue<bool>(ContiguousModeInfo.identifier)) {
|
||||
_contiguousMode = dictionary.value<bool>(ContiguousModeInfo.identifier);
|
||||
}
|
||||
else {
|
||||
_contiguousMode = false;
|
||||
}
|
||||
|
||||
_updateStartRenderIdxSelect = std::function<void()>([this] {
|
||||
if (_contiguousMode) {
|
||||
if ((_numObjects - _startRenderIdx) < _sizeRender) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
|
||||
}
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
}
|
||||
});
|
||||
_updateRenderSizeSelect = std::function<void()>([this] {
|
||||
if (_contiguousMode) {
|
||||
if (_sizeRender > (_numObjects - _startRenderIdx)) {
|
||||
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
|
||||
}
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
}
|
||||
});
|
||||
_updateRenderUpperLimitSelect = std::function<void()>([this] {
|
||||
if (!_contiguousMode) {
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
}
|
||||
});
|
||||
_updateContiguousModeSelect = std::function<void()>([this] {
|
||||
_updateDataBuffersAtNextRender = true;
|
||||
});
|
||||
|
||||
_startRenderIdxCallbackHandle = _startRenderIdx.onChange(_updateStartRenderIdxSelect);
|
||||
_sizeRenderCallbackHandle = _sizeRender.onChange(_updateRenderSizeSelect);
|
||||
_upperLimitCallbackHandle = _upperLimit.onChange(_updateRenderUpperLimitSelect);
|
||||
_contiguousModeCallbackhandle =
|
||||
_contiguousMode.onChange(_updateContiguousModeSelect);
|
||||
}
|
||||
|
||||
void RenderableSmallBody::readDataFile(const std::string& filename) {
|
||||
if (!std::filesystem::is_regular_file(filename)) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"JPL SBDB file {} does not exist", filename
|
||||
));
|
||||
}
|
||||
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
file.open(filename);
|
||||
|
||||
std::streamoff numberOfLines = std::count(
|
||||
std::istreambuf_iterator<char>(file),
|
||||
std::istreambuf_iterator<char>(),
|
||||
'\n'
|
||||
);
|
||||
file.seekg(std::ios_base::beg); // reset iterator to beginning of file
|
||||
_data.clear();
|
||||
_sbNames.clear();
|
||||
_segmentSize.clear();
|
||||
|
||||
std::string line;
|
||||
unsigned int csvLine = 0;
|
||||
int fieldCount = 0;
|
||||
const std::string expectedHeaderLine = "full_name,epoch_cal,e,a,i,om,w,ma,per";
|
||||
|
||||
try {
|
||||
std::getline(file, line); // get rid of first line (header)
|
||||
numberOfLines -= 1;
|
||||
if (_numObjects != numberOfLines) {
|
||||
_isFileReadinitialized = false;
|
||||
}
|
||||
_numObjects = numberOfLines;
|
||||
|
||||
float lineSkipFraction = 1.0;
|
||||
if (!_isFileReadinitialized) {
|
||||
_isFileReadinitialized = true;
|
||||
initializeFileReading();
|
||||
}
|
||||
|
||||
unsigned int startElement = 0;
|
||||
unsigned int endElement;
|
||||
if (_contiguousMode) {
|
||||
lineSkipFraction = 1.0;
|
||||
startElement = _startRenderIdx;
|
||||
endElement = _startRenderIdx + _sizeRender - 1;
|
||||
}
|
||||
else {
|
||||
lineSkipFraction = static_cast<float>(_upperLimit)
|
||||
/ static_cast<float>(_numObjects);
|
||||
endElement = static_cast<unsigned int>(_numObjects - 1);
|
||||
}
|
||||
|
||||
if (line.compare(expectedHeaderLine) != 0) {
|
||||
LERROR(fmt::format(
|
||||
"File {} does not have the appropriate JPL SBDB header at line 1",
|
||||
filename
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int sequentialLineErrors = 0;
|
||||
endElement =
|
||||
(endElement >= _numObjects) ?
|
||||
static_cast<unsigned int>(_numObjects - 1) :
|
||||
endElement;
|
||||
// Burn lines if not starting at first element
|
||||
for (unsigned int k = 0; k < startElement; ++k) {
|
||||
skipSingleLineInFile(file);
|
||||
}
|
||||
bool firstDataLine = true;
|
||||
int lastLineCount = -1;
|
||||
for (csvLine = startElement + 1;
|
||||
csvLine <= endElement + 1;
|
||||
csvLine++, sequentialLineErrors++)
|
||||
{
|
||||
float currLineFraction = static_cast<float>(csvLine - 1) * lineSkipFraction;
|
||||
int currLineCount = static_cast<int>(currLineFraction);
|
||||
if (currLineCount > lastLineCount) {
|
||||
try {
|
||||
readOrbitalParamsFromThisLine(firstDataLine, fieldCount, csvLine,
|
||||
file);
|
||||
sequentialLineErrors = 0;
|
||||
}
|
||||
catch (std::invalid_argument&) {
|
||||
constexpr std::string_view errMsg = "Unable to convert field {} to "
|
||||
"double value (invalid_argument exception). Ignoring line {}/{} "
|
||||
"of {}";
|
||||
LINFO(fmt::format(
|
||||
errMsg,
|
||||
fieldCount, csvLine + 1, numberOfLines, filename
|
||||
));
|
||||
}
|
||||
catch (std::out_of_range&) {
|
||||
constexpr std::string_view errMsg = "Unable to convert field {} to "
|
||||
"double value (out_of_range exception). Ignoring line {}/{} of "
|
||||
"{}";
|
||||
LINFO(fmt::format(
|
||||
errMsg,
|
||||
fieldCount, csvLine + 1, numberOfLines, filename
|
||||
));
|
||||
}
|
||||
catch (std::ios_base::failure&) {
|
||||
throw;
|
||||
}
|
||||
|
||||
if (sequentialLineErrors == 4) {
|
||||
_data.clear();
|
||||
_sbNames.clear();
|
||||
LERROR(fmt::format(
|
||||
"Abandoning data file {} (too many sequential line errors)",
|
||||
filename
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
skipSingleLineInFile(file);
|
||||
}
|
||||
lastLineCount = currLineCount;
|
||||
firstDataLine = false;
|
||||
}
|
||||
}
|
||||
catch (const std::ios_base::failure&) {
|
||||
LERROR(fmt::format(
|
||||
"File read exception (ios_base::failure) while trying to read field {} at "
|
||||
"line {}/{} of {}",
|
||||
fieldCount, csvLine + 1, numberOfLines, filename
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSmallBody::initializeFileReading() {
|
||||
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
|
||||
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
|
||||
if (_sizeRender == 0u) {
|
||||
_sizeRender = static_cast<unsigned int>(_numObjects);
|
||||
}
|
||||
|
||||
_upperLimit.setMaxValue(static_cast<unsigned int>(_numObjects));
|
||||
if (_upperLimit == 0u) {
|
||||
_upperLimit = static_cast<unsigned int>(_numObjects);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSmallBody::skipSingleLineInFile(std::ifstream& file) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
}
|
||||
|
||||
void RenderableSmallBody::readOrbitalParamsFromThisLine(bool firstDataLine,
|
||||
int& fieldCount,
|
||||
unsigned int& csvLine,
|
||||
std::ifstream& file)
|
||||
{
|
||||
const int numDataFields = 8;
|
||||
std::string name;
|
||||
std::string field;
|
||||
KeplerParameters keplerElements;
|
||||
|
||||
//If there was a read/conversion error in the previous line, then read the remainder
|
||||
// of that line and throw it out first before proceeding with the next line.
|
||||
if (fieldCount != (numDataFields + 1) && !firstDataLine) {
|
||||
std::getline(file, field);
|
||||
}
|
||||
fieldCount = 0;
|
||||
|
||||
// Object designator string
|
||||
std::getline(file, name, ',');
|
||||
if (_startRenderIdx > 0 && _startRenderIdx == (csvLine - 1) && _sizeRender == 1) {
|
||||
formatObjectName(name);
|
||||
LINFO(fmt::format("Set render block to start at object {}", name));
|
||||
}
|
||||
fieldCount++;
|
||||
|
||||
// Epoch
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read epoch from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.epoch = epochFromYMDdSubstring(field);
|
||||
fieldCount++;
|
||||
|
||||
// Eccentricity (unit-less)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read eccentricity from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.eccentricity = std::stod(field);
|
||||
fieldCount++;
|
||||
|
||||
// Semi-major axis (astronomical units - au)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read semi-major axis from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.semiMajorAxis = std::stod(field);
|
||||
keplerElements.semiMajorAxis *= convertAuToKm;
|
||||
fieldCount++;
|
||||
|
||||
// Inclination (degrees)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read inclination from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.inclination = importAngleValue(field);
|
||||
fieldCount++;
|
||||
|
||||
// Longitude of ascending node (degrees)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read ascending node from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.ascendingNode = importAngleValue(field);
|
||||
fieldCount++;
|
||||
|
||||
// Argument of Periapsis (degrees)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read arg of periapsis from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.argumentOfPeriapsis = importAngleValue(field);
|
||||
fieldCount++;
|
||||
|
||||
// Mean Anomaly (degrees)
|
||||
if (!std::getline(file, field, ',')) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read mean anomaly from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.meanAnomaly = importAngleValue(field);
|
||||
fieldCount++;
|
||||
|
||||
// Period (days)
|
||||
if (!std::getline(file, field)) {
|
||||
throw std::invalid_argument(
|
||||
"Unable to read period from line" + std::to_string(csvLine + 1)
|
||||
);
|
||||
}
|
||||
keplerElements.period = std::stod(field);
|
||||
keplerElements.period *= convertDaysToSecs;
|
||||
fieldCount++;
|
||||
|
||||
_data.push_back(keplerElements);
|
||||
_sbNames.push_back(name);
|
||||
const double scale = static_cast<double>(_segmentQuality) * 10.0;
|
||||
_segmentSize.push_back(
|
||||
static_cast<size_t>(scale + (scale / pow(1 - keplerElements.eccentricity, 1.2)))
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -28,13 +28,18 @@
|
||||
in vec4 vs_position;
|
||||
|
||||
uniform vec3 color;
|
||||
uniform float opacity;
|
||||
|
||||
|
||||
Fragment getFragment() {
|
||||
vec4 position = vs_position;
|
||||
|
||||
Fragment frag;
|
||||
frag.color = vec4(color, 1.0);
|
||||
if (opacity == 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec4 position = vs_position;
|
||||
|
||||
frag.color = vec4(color, opacity);
|
||||
frag.depth = pscDepth(position);
|
||||
|
||||
return frag;
|
||||
|
||||
47
modules/space/shaders/constellationlines_fs.glsl
Normal file
47
modules/space/shaders/constellationlines_fs.glsl
Normal file
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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 "fragment.glsl"
|
||||
|
||||
in float vs_screenSpaceDepth;
|
||||
in vec4 vs_positionViewSpace;
|
||||
|
||||
uniform vec3 color;
|
||||
uniform float opacity;
|
||||
|
||||
|
||||
Fragment getFragment() {
|
||||
Fragment frag;
|
||||
if (opacity == 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
frag.color = vec4(color, opacity);
|
||||
frag.depth = vs_screenSpaceDepth;
|
||||
|
||||
frag.gPosition = vs_positionViewSpace;
|
||||
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
return frag;
|
||||
}
|
||||
47
modules/space/shaders/constellationlines_vs.glsl
Normal file
47
modules/space/shaders/constellationlines_vs.glsl
Normal file
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#version __CONTEXT__
|
||||
|
||||
#include "PowerScaling/powerScaling_vs.hglsl"
|
||||
|
||||
in vec3 in_position;
|
||||
|
||||
out float vs_screenSpaceDepth;
|
||||
out vec4 vs_positionViewSpace;
|
||||
|
||||
uniform dmat4 modelViewTransform;
|
||||
uniform dmat4 projectionTransform;
|
||||
|
||||
|
||||
void main() {
|
||||
dvec4 positionViewSpace = modelViewTransform * dvec4(in_position, 1.0);
|
||||
vec4 positionClipSpace = vec4(projectionTransform * positionViewSpace);
|
||||
vec4 positionScreenSpace = vec4(z_normalization(positionClipSpace));
|
||||
|
||||
vs_screenSpaceDepth = positionScreenSpace.w;
|
||||
vs_positionViewSpace = vec4(positionViewSpace);
|
||||
|
||||
gl_Position = positionScreenSpace;
|
||||
}
|
||||
@@ -24,17 +24,18 @@
|
||||
|
||||
#include <modules/space/spacemodule.h>
|
||||
|
||||
#include <modules/space/labelscomponent.h>
|
||||
#include <modules/space/rendering/renderableconstellationbounds.h>
|
||||
#include <modules/space/rendering/renderableconstellationlines.h>
|
||||
#include <modules/space/rendering/renderablefluxnodes.h>
|
||||
#include <modules/space/rendering/renderablehabitablezone.h>
|
||||
#include <modules/space/rendering/renderableorbitalkepler.h>
|
||||
#include <modules/space/rendering/renderablerings.h>
|
||||
#include <modules/space/rendering/renderablesatellites.h>
|
||||
#include <modules/space/rendering/renderablesmallbody.h>
|
||||
#include <modules/space/rendering/renderablestars.h>
|
||||
#include <modules/space/rendering/renderabletravelspeed.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <modules/space/translation/spicetranslation.h>
|
||||
#include <modules/space/translation/tletranslation.h>
|
||||
#include <modules/space/translation/gptranslation.h>
|
||||
#include <modules/space/translation/horizonstranslation.h>
|
||||
#include <modules/space/rotation/spicerotation.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
@@ -80,11 +81,13 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
|
||||
fRenderable->registerClass<RenderableConstellationBounds>(
|
||||
"RenderableConstellationBounds"
|
||||
);
|
||||
fRenderable->registerClass<RenderableConstellationLines>(
|
||||
"RenderableConstellationLines"
|
||||
);
|
||||
fRenderable->registerClass<RenderableFluxNodes>("RenderableFluxNodes");
|
||||
fRenderable->registerClass<RenderableHabitableZone>("RenderableHabitableZone");
|
||||
fRenderable->registerClass<RenderableRings>("RenderableRings");
|
||||
fRenderable->registerClass<RenderableSatellites>("RenderableSatellites");
|
||||
fRenderable->registerClass<RenderableSmallBody>("RenderableSmallBody");
|
||||
fRenderable->registerClass<RenderableOrbitalKepler>("RenderableOrbitalKepler");
|
||||
fRenderable->registerClass<RenderableStars>("RenderableStars");
|
||||
fRenderable->registerClass<RenderableTravelSpeed>("RenderableTravelSpeed");
|
||||
|
||||
@@ -94,7 +97,7 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
|
||||
|
||||
fTranslation->registerClass<KeplerTranslation>("KeplerTranslation");
|
||||
fTranslation->registerClass<SpiceTranslation>("SpiceTranslation");
|
||||
fTranslation->registerClass<TLETranslation>("TLETranslation");
|
||||
fTranslation->registerClass<GPTranslation>("GPTranslation");
|
||||
fTranslation->registerClass<HorizonsTranslation>("HorizonsTranslation");
|
||||
|
||||
ghoul::TemplateFactory<Rotation>* fRotation =
|
||||
@@ -117,16 +120,17 @@ std::vector<documentation::Documentation> SpaceModule::documentations() const {
|
||||
HorizonsTranslation::Documentation(),
|
||||
KeplerTranslation::Documentation(),
|
||||
RenderableConstellationBounds::Documentation(),
|
||||
RenderableConstellationLines::Documentation(),
|
||||
RenderableFluxNodes::Documentation(),
|
||||
RenderableHabitableZone::Documentation(),
|
||||
RenderableRings::Documentation(),
|
||||
RenderableSatellites::Documentation(),
|
||||
RenderableSmallBody::Documentation(),
|
||||
RenderableOrbitalKepler::Documentation(),
|
||||
RenderableStars::Documentation(),
|
||||
RenderableTravelSpeed::Documentation(),
|
||||
SpiceRotation::Documentation(),
|
||||
SpiceTranslation::Documentation(),
|
||||
TLETranslation::Documentation()
|
||||
LabelsComponent::Documentation(),
|
||||
GPTranslation::Documentation()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,7 +139,8 @@ scripting::LuaLibrary SpaceModule::luaLibrary() const {
|
||||
"space",
|
||||
{
|
||||
codegen::lua::ConvertFromRaDec,
|
||||
codegen::lua::ConvertToRaDec
|
||||
codegen::lua::ConvertToRaDec,
|
||||
codegen::lua::ReadKeplerFile
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "kepler.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@@ -77,6 +79,43 @@ namespace {
|
||||
return { raDecPair.first, raDecPair.second, deg.z };
|
||||
}
|
||||
|
||||
[[codegen::luawrap]]
|
||||
std::vector<ghoul::Dictionary> readKeplerFile(std::filesystem::path p, std::string type)
|
||||
{
|
||||
openspace::kepler::Format f;
|
||||
if (type == "TLE") {
|
||||
f = openspace::kepler::Format::TLE;
|
||||
}
|
||||
else if (type == "OMM") {
|
||||
f = openspace::kepler::Format::OMM;
|
||||
}
|
||||
else if (type == "SBDB") {
|
||||
f = openspace::kepler::Format::SBDB;
|
||||
}
|
||||
else {
|
||||
throw ghoul::lua::LuaError(fmt::format("Unsupported format '{}'", type));
|
||||
}
|
||||
|
||||
std::vector<openspace::kepler::Parameters> params = openspace::kepler::readFile(p, f);
|
||||
std::vector<ghoul::Dictionary> res;
|
||||
res.reserve(params.size());
|
||||
for (const openspace::kepler::Parameters& param : params) {
|
||||
ghoul::Dictionary d;
|
||||
d.setValue("Name", param.name);
|
||||
d.setValue("ID", param.id);
|
||||
d.setValue("inclination", param.inclination);
|
||||
d.setValue("SemiMajorAxis", param.semiMajorAxis);
|
||||
d.setValue("AscendingNode", param.ascendingNode);
|
||||
d.setValue("Eccentricity", param.eccentricity);
|
||||
d.setValue("ArgumentOfPeriapsis", param.argumentOfPeriapsis);
|
||||
d.setValue("MeanAnomaly", param.meanAnomaly);
|
||||
d.setValue("Epoch", param.epoch);
|
||||
d.setValue("Period", param.period);
|
||||
res.push_back(d);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#include "spacemodule_lua_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
namespace {
|
||||
constexpr int8_t DataCacheFileVersion = 10;
|
||||
constexpr int8_t LabelCacheFileVersion = 10;
|
||||
constexpr int8_t LabelCacheFileVersion = 11;
|
||||
constexpr int8_t ColorCacheFileVersion = 10;
|
||||
|
||||
bool startsWith(std::string lhs, std::string_view rhs) noexcept {
|
||||
@@ -321,7 +321,7 @@ Dataset loadFile(std::filesystem::path path, SkipAllZeroLines skipAllZeroLines)
|
||||
|
||||
if (!str.good()) {
|
||||
// Need to subtract one of the line number here as we increase the current
|
||||
// line count in the beginning of the while loop we are currently in
|
||||
// line count in the beginning of the while loop we are currently in
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading position information out of data line {} in file {}. "
|
||||
"Value was not a number",
|
||||
@@ -346,7 +346,7 @@ Dataset loadFile(std::filesystem::path path, SkipAllZeroLines skipAllZeroLines)
|
||||
if (valueStream.fail()) {
|
||||
// Need to subtract one of the line number here as we increase the
|
||||
// current line count in the beginning of the while loop we are
|
||||
// currently in
|
||||
// currently in
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Error loading data value {} out of data line {} in file {}. "
|
||||
"Value was not a number",
|
||||
@@ -674,10 +674,20 @@ Labelset loadFile(std::filesystem::path path, SkipAllZeroLines) {
|
||||
std::getline(str, rest);
|
||||
strip(rest);
|
||||
|
||||
if (startsWith(rest, "id")) {
|
||||
// optional arument with identifier
|
||||
// Remove the 'id' text
|
||||
rest = rest.substr(std::string_view("id ").size());
|
||||
size_t index = rest.find("text");
|
||||
entry.identifier = rest.substr(0, index - 1);
|
||||
|
||||
// update the rest, remove the identifier
|
||||
rest = rest.substr(index);
|
||||
}
|
||||
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
|
||||
"Error loading label file {}: File contains an unsupported value "
|
||||
"between positions and text label", path
|
||||
));
|
||||
}
|
||||
|
||||
@@ -731,6 +741,13 @@ std::optional<Labelset> loadCachedFile(std::filesystem::path path) {
|
||||
file.read(reinterpret_cast<char*>(&e.position.y), sizeof(float));
|
||||
file.read(reinterpret_cast<char*>(&e.position.z), sizeof(float));
|
||||
|
||||
// Identifier
|
||||
uint8_t idLen;
|
||||
file.read(reinterpret_cast<char*>(&idLen), sizeof(uint8_t));
|
||||
e.identifier.resize(idLen);
|
||||
file.read(e.identifier.data(), idLen);
|
||||
|
||||
// Text
|
||||
uint16_t len;
|
||||
file.read(reinterpret_cast<char*>(&len), sizeof(uint16_t));
|
||||
e.text.resize(len);
|
||||
@@ -763,6 +780,13 @@ void saveCachedFile(const Labelset& labelset, std::filesystem::path path) {
|
||||
file.write(reinterpret_cast<const char*>(&e.position.y), sizeof(float));
|
||||
file.write(reinterpret_cast<const char*>(&e.position.z), sizeof(float));
|
||||
|
||||
// Identifier
|
||||
checkSize<uint8_t>(e.identifier.size(), "Identifier too long");
|
||||
uint8_t idLen = static_cast<uint8_t>(e.identifier.size());
|
||||
file.write(reinterpret_cast<const char*>(&idLen), sizeof(uint8_t));
|
||||
file.write(e.identifier.data(), idLen);
|
||||
|
||||
// Text
|
||||
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));
|
||||
|
||||
@@ -68,6 +68,7 @@ struct Labelset {
|
||||
|
||||
struct Entry {
|
||||
glm::vec3 position = glm::vec3(0.f);
|
||||
std::string identifier;
|
||||
std::string text;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <openspace/util/task.h>
|
||||
#include <openspace/util/time.h>
|
||||
|
||||
#include <modules/space/rendering/renderablesatellites.h>
|
||||
#include <modules/space/rendering/renderableorbitalkepler.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
|
||||
|
||||
@@ -50,8 +50,6 @@ public:
|
||||
|
||||
std::string _gridType;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
std::string _rawVolumeOutputPath;
|
||||
std::string _dictionaryOutputPath;
|
||||
|
||||
92
modules/space/translation/gptranslation.cpp
Normal file
92
modules/space/translation/gptranslation.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/translation/gptranslation.h>
|
||||
|
||||
#include <modules/space/kepler.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
struct [[codegen::Dictionary(GPTranslation)]] Parameters {
|
||||
// Specifies the filename of the general pertubation file
|
||||
std::filesystem::path file;
|
||||
|
||||
enum class [[codegen::map(openspace::kepler::Format)]] Format {
|
||||
// A NORAD-style Two-Line element
|
||||
TLE,
|
||||
// Orbit Mean-Elements Message in the KVN notation
|
||||
OMM,
|
||||
// JPL's Small Bodies Database
|
||||
SBDB
|
||||
};
|
||||
// The file format that is contained in the file
|
||||
Format format;
|
||||
|
||||
// Specifies the element within the file that should be used in case the file
|
||||
// provides multiple general pertubation elements. Defaults to 1.
|
||||
std::optional<int> element [[codegen::greater(0)]];
|
||||
};
|
||||
#include "gptranslation_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation GPTranslation::Documentation() {
|
||||
return codegen::doc<Parameters>("space_transform_gp");
|
||||
}
|
||||
|
||||
GPTranslation::GPTranslation(const ghoul::Dictionary& dictionary) {
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
if (!std::filesystem::is_regular_file(p.file)) {
|
||||
throw ghoul::RuntimeError("The provided TLE file must exist");
|
||||
}
|
||||
|
||||
int element = p.element.value_or(1);
|
||||
|
||||
std::vector<kepler::Parameters> parameters = kepler::readFile(
|
||||
p.file,
|
||||
codegen::map<kepler::Format>(p.format)
|
||||
);
|
||||
if (parameters.size() < element) {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"Requested element {} but only {} are available", element, parameters.size()
|
||||
));
|
||||
}
|
||||
|
||||
kepler::Parameters param = parameters[element - 1];
|
||||
setKeplerElements(
|
||||
param.eccentricity,
|
||||
param.semiMajorAxis,
|
||||
param.inclination,
|
||||
param.ascendingNode,
|
||||
param.argumentOfPeriapsis,
|
||||
param.meanAnomaly,
|
||||
param.period,
|
||||
param.epoch
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -22,37 +22,38 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
|
||||
|
||||
#include <modules/space/rendering/renderableorbitalkepler.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <modules/base/rendering/renderabletrail.h>
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/scalar/uintproperty.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <ghoul/misc/objectmanager.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace documentation { struct Documentation; }
|
||||
|
||||
class RenderableSatellites : public RenderableOrbitalKepler {
|
||||
/**
|
||||
* A specialization of the KeplerTranslation that utilizes general pertubation file
|
||||
* formats to extracts the Keplerian elements
|
||||
*/
|
||||
class GPTranslation : public KeplerTranslation {
|
||||
public:
|
||||
RenderableSatellites(const ghoul::Dictionary& dictionary);
|
||||
virtual void readDataFile(const std::string& filename) override;
|
||||
static documentation::Documentation Documentation();
|
||||
void initializeFileReading();
|
||||
/**
|
||||
* Constructor for the GPTranslation class. The \p dictionary must contain a key for
|
||||
* the file that contains the general pertubation information as well as the file
|
||||
* format that is to be used.
|
||||
*
|
||||
* \param The ghoul::Dictionary that contains the information for this TLETranslation
|
||||
*/
|
||||
explicit GPTranslation(const ghoul::Dictionary& dictionary);
|
||||
|
||||
private:
|
||||
void skipSingleEntryInFile(std::ifstream& file);
|
||||
const unsigned int nLineEntriesPerSatellite = 3;
|
||||
/**
|
||||
* Method returning the openspace::Documentation that describes the ghoul::Dictionary
|
||||
* that can be passed to the constructor.
|
||||
*
|
||||
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
|
||||
* be passed to the constructor
|
||||
*/
|
||||
static documentation::Documentation Documentation();
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
glm::dvec3 position(const UpdateData& data) const override;
|
||||
|
||||
/**
|
||||
* Method returning the openspace::Documentation that describes the ghoul::Dictinoary
|
||||
* Method returning the openspace::Documentation that describes the ghoul::Dictionary
|
||||
* that can be passed to the constructor.
|
||||
*
|
||||
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
|
||||
|
||||
@@ -1,364 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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/space/translation/tletranslation.h>
|
||||
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <ghoul/filesystem/file.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
// The list of leap years only goes until 2056 as we need to touch this file then
|
||||
// again anyway ;)
|
||||
const std::vector<int> LeapYears = {
|
||||
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
|
||||
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
|
||||
2044, 2048, 2052, 2056
|
||||
};
|
||||
|
||||
// Count the number of full days since the beginning of 2000 to the beginning of
|
||||
// the parameter 'year'
|
||||
int countDays(int year) {
|
||||
// Find the position of the current year in the vector, the difference
|
||||
// between its position and the position of 2000 (for J2000) gives the
|
||||
// number of leap years
|
||||
constexpr int Epoch = 2000;
|
||||
constexpr int DaysRegularYear = 365;
|
||||
constexpr int DaysLeapYear = 366;
|
||||
|
||||
if (year == Epoch) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the position of the most recent leap year
|
||||
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
|
||||
|
||||
// Get the position of the epoch
|
||||
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
|
||||
|
||||
const int nYears = std::abs(year - Epoch);
|
||||
const int nRegularYears = nYears - nLeapYears;
|
||||
|
||||
// Get the total number of days as the sum of leap years + non leap years
|
||||
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the number of leap seconds that lie between the {year, dayOfYear}
|
||||
// time point and { 2000, 1 }
|
||||
int countLeapSeconds(int year, int dayOfYear) {
|
||||
// Find the position of the current year in the vector; its position in
|
||||
// the vector gives the number of leap seconds
|
||||
struct LeapSecond {
|
||||
int year;
|
||||
int dayOfYear;
|
||||
bool operator<(const LeapSecond& rhs) const {
|
||||
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
|
||||
}
|
||||
};
|
||||
|
||||
const LeapSecond Epoch = { 2000, 1 };
|
||||
|
||||
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
|
||||
static const std::vector<LeapSecond> LeapSeconds = {
|
||||
{ 1972, 1 },
|
||||
{ 1972, 183 },
|
||||
{ 1973, 1 },
|
||||
{ 1974, 1 },
|
||||
{ 1975, 1 },
|
||||
{ 1976, 1 },
|
||||
{ 1977, 1 },
|
||||
{ 1978, 1 },
|
||||
{ 1979, 1 },
|
||||
{ 1980, 1 },
|
||||
{ 1981, 182 },
|
||||
{ 1982, 182 },
|
||||
{ 1983, 182 },
|
||||
{ 1985, 182 },
|
||||
{ 1988, 1 },
|
||||
{ 1990, 1 },
|
||||
{ 1991, 1 },
|
||||
{ 1992, 183 },
|
||||
{ 1993, 182 },
|
||||
{ 1994, 182 },
|
||||
{ 1996, 1 },
|
||||
{ 1997, 182 },
|
||||
{ 1999, 1 },
|
||||
{ 2006, 1 },
|
||||
{ 2009, 1 },
|
||||
{ 2012, 183 },
|
||||
{ 2015, 182 },
|
||||
{ 2017, 1 }
|
||||
};
|
||||
|
||||
// Get the position of the last leap second before the desired date
|
||||
LeapSecond date { year, dayOfYear };
|
||||
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
|
||||
|
||||
// Get the position of the Epoch
|
||||
const auto y2000 = std::lower_bound(
|
||||
LeapSeconds.begin(),
|
||||
LeapSeconds.end(),
|
||||
Epoch
|
||||
);
|
||||
|
||||
// The distance between the two iterators gives us the number of leap years
|
||||
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
|
||||
return nLeapSeconds;
|
||||
}
|
||||
|
||||
double epochFromSubstring(const std::string& epochString) {
|
||||
// The epochString is in the form:
|
||||
// YYDDD.DDDDDDDD
|
||||
// With YY being the last two years of the launch epoch, the first DDD the day
|
||||
// of the year and the remaning a fractional part of the day
|
||||
|
||||
// The main overview of this function:
|
||||
// 1. Reconstruct the full year from the YY part
|
||||
// 2. Calculate the number of days since the beginning of the year
|
||||
// 3. Convert the number of days to a number of seconds
|
||||
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
|
||||
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
|
||||
// midnight
|
||||
|
||||
// According to https://celestrak.com/columns/v04n03/
|
||||
// Apparently, US Space Command sees no need to change the two-line element
|
||||
// set format yet since no artificial earth satellites existed prior to 1957.
|
||||
// By their reasoning, two-digit years from 57-99 correspond to 1957-1999 and
|
||||
// those from 00-56 correspond to 2000-2056. We'll see each other again in 2057!
|
||||
|
||||
// 1. Get the full year
|
||||
std::string yearPrefix = [y = epochString.substr(0, 2)](){
|
||||
int year = std::atoi(y.c_str());
|
||||
return year >= 57 ? "19" : "20";
|
||||
}();
|
||||
const int year = std::atoi((yearPrefix + epochString.substr(0, 2)).c_str());
|
||||
const int daysSince2000 = countDays(year);
|
||||
|
||||
// 2.
|
||||
double daysInYear = std::atof(epochString.substr(2).c_str());
|
||||
|
||||
// 3
|
||||
using namespace std::chrono;
|
||||
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
|
||||
//Need to subtract 1 from daysInYear since it is not a zero-based count
|
||||
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
|
||||
|
||||
// 4
|
||||
// We need to remove additionbal leap seconds past 2000 and add them prior to
|
||||
// 2000 to sync up the time zones
|
||||
const double nLeapSecondsOffset = -countLeapSeconds(
|
||||
year,
|
||||
static_cast<int>(std::floor(daysInYear))
|
||||
);
|
||||
|
||||
// 5
|
||||
const double nSecondsEpochOffset = static_cast<double>(
|
||||
seconds(hours(12)).count()
|
||||
);
|
||||
|
||||
// Combine all of the values
|
||||
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
|
||||
return epoch;
|
||||
}
|
||||
|
||||
double calculateSemiMajorAxis(double meanMotion) {
|
||||
constexpr double GravitationalConstant = 6.6740831e-11;
|
||||
constexpr double MassEarth = 5.9721986e24;
|
||||
constexpr double muEarth = GravitationalConstant * MassEarth;
|
||||
|
||||
// Use Kepler's 3rd law to calculate semimajor axis
|
||||
// a^3 / P^2 = mu / (2pi)^2
|
||||
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
|
||||
// with a = semimajor axis
|
||||
// P = period in seconds
|
||||
// mu = G*M_earth
|
||||
double period = std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
|
||||
|
||||
constexpr double pisq = glm::pi<double>() * glm::pi<double>();
|
||||
double semiMajorAxis = pow((muEarth * period*period) / (4 * pisq), 1.0 / 3.0);
|
||||
|
||||
// We need the semi major axis in km instead of m
|
||||
return semiMajorAxis / 1000.0;
|
||||
}
|
||||
|
||||
struct [[codegen::Dictionary(TLETranslation)]] Parameters {
|
||||
// Specifies the filename of the Two-Line-Element file
|
||||
std::string file;
|
||||
|
||||
// Specifies the line number within the file where the group of 3 TLE lines begins
|
||||
// (1-based). Defaults to 1
|
||||
std::optional<int> lineNumber [[codegen::greater(0)]];
|
||||
};
|
||||
#include "tletranslation_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
documentation::Documentation TLETranslation::Documentation() {
|
||||
return codegen::doc<Parameters>("space_transform_tle");
|
||||
}
|
||||
|
||||
TLETranslation::TLETranslation(const ghoul::Dictionary& dictionary) {
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
|
||||
int lineNum = p.lineNumber.value_or(1);
|
||||
readTLEFile(p.file, lineNum);
|
||||
}
|
||||
|
||||
void TLETranslation::readTLEFile(const std::string& filename, int lineNum) {
|
||||
ghoul_assert(std::filesystem::is_regular_file(filename), "The filename must exist");
|
||||
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ofstream::failbit | std::ofstream::badbit);
|
||||
file.open(filename);
|
||||
|
||||
// All of the Kepler element information
|
||||
struct {
|
||||
double inclination = 0.0;
|
||||
double semiMajorAxis = 0.0;
|
||||
double ascendingNode = 0.0;
|
||||
double eccentricity = 0.0;
|
||||
double argumentOfPeriapsis = 0.0;
|
||||
double meanAnomaly = 0.0;
|
||||
double meanMotion = 0.0;
|
||||
double epoch = 0.0;
|
||||
} keplerElements;
|
||||
|
||||
std::string line;
|
||||
// Loop through and throw out lines until getting to the linNum of interest
|
||||
for (int i = 1; i < lineNum; ++i) {
|
||||
std::getline(file, line);
|
||||
}
|
||||
std::getline(file, line); // Throw out the TLE title line (1st)
|
||||
|
||||
std::getline(file, line); // Get line 1 of TLE format
|
||||
if (line[0] == '1') {
|
||||
// First line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 08-08 Classification (U = Unclassified)
|
||||
// 4 10-11 International Designator (Last two digits of launch year)
|
||||
// 5 12-14 International Designator (Launch number of the year)
|
||||
// 6 15-17 International Designator(piece of the launch) A
|
||||
// 7 19-20 Epoch Year(last two digits of year)
|
||||
// 8 21-32 Epoch(day of the year and fractional portion of the day)
|
||||
// 9 34-43 First Time Derivative of the Mean Motion divided by two
|
||||
// 10 45-52 Second Time Derivative of Mean Motion divided by six
|
||||
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
|
||||
// 12 63-63 The "Ephemeris type"
|
||||
// 13 65-68 Element set number.Incremented when a new TLE is generated
|
||||
// 14 69-69 Checksum (modulo 10)
|
||||
keplerElements.epoch = epochFromSubstring(line.substr(18, 14));
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"File {} @ line {} does not have '1' header", filename, lineNum + 1
|
||||
));
|
||||
}
|
||||
|
||||
std::getline(file, line); // Get line 2 of TLE format
|
||||
if (line[0] == '2') {
|
||||
// Second line
|
||||
// Field Columns Content
|
||||
// 1 01-01 Line number
|
||||
// 2 03-07 Satellite number
|
||||
// 3 09-16 Inclination (degrees)
|
||||
// 4 18-25 Right ascension of the ascending node (degrees)
|
||||
// 5 27-33 Eccentricity (decimal point assumed)
|
||||
// 6 35-42 Argument of perigee (degrees)
|
||||
// 7 44-51 Mean Anomaly (degrees)
|
||||
// 8 53-63 Mean Motion (revolutions per day)
|
||||
// 9 64-68 Revolution number at epoch (revolutions)
|
||||
// 10 69-69 Checksum (modulo 10)
|
||||
|
||||
std::stringstream stream;
|
||||
stream.exceptions(std::ios::failbit);
|
||||
|
||||
// Get inclination
|
||||
stream.str(line.substr(8, 8));
|
||||
stream >> keplerElements.inclination;
|
||||
stream.clear();
|
||||
|
||||
// Get Right ascension of the ascending node
|
||||
stream.str(line.substr(17, 8));
|
||||
stream >> keplerElements.ascendingNode;
|
||||
stream.clear();
|
||||
|
||||
// Get Eccentricity
|
||||
stream.str("0." + line.substr(26, 7));
|
||||
stream >> keplerElements.eccentricity;
|
||||
stream.clear();
|
||||
|
||||
// Get argument of periapsis
|
||||
stream.str(line.substr(34, 8));
|
||||
stream >> keplerElements.argumentOfPeriapsis;
|
||||
stream.clear();
|
||||
|
||||
// Get mean anomaly
|
||||
stream.str(line.substr(43, 8));
|
||||
stream >> keplerElements.meanAnomaly;
|
||||
stream.clear();
|
||||
|
||||
// Get mean motion
|
||||
stream.str(line.substr(52, 11));
|
||||
stream >> keplerElements.meanMotion;
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(fmt::format(
|
||||
"File {} @ line {} does not have '2' header", filename, lineNum + 2
|
||||
));
|
||||
}
|
||||
file.close();
|
||||
|
||||
// Calculate the semi major axis based on the mean motion using kepler's laws
|
||||
keplerElements.semiMajorAxis = calculateSemiMajorAxis(keplerElements.meanMotion);
|
||||
|
||||
// Converting the mean motion (revolutions per day) to period (seconds per revolution)
|
||||
using namespace std::chrono;
|
||||
double period = seconds(hours(24)).count() / keplerElements.meanMotion;
|
||||
|
||||
setKeplerElements(
|
||||
keplerElements.eccentricity,
|
||||
keplerElements.semiMajorAxis,
|
||||
keplerElements.inclination,
|
||||
keplerElements.ascendingNode,
|
||||
keplerElements.argumentOfPeriapsis,
|
||||
keplerElements.meanAnomaly,
|
||||
period,
|
||||
keplerElements.epoch
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -1,83 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__
|
||||
|
||||
#include <modules/space/translation/keplertranslation.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
/**
|
||||
* A specialization of the KeplerTranslation that extracts the Keplerian elements from a
|
||||
* two-line element as described by the US Space Command
|
||||
* https://celestrak.com/columns/v04n03
|
||||
* The ghoul::Dictionary passed to the constructor must contain the pointer to a file that
|
||||
* will be read.
|
||||
*/
|
||||
class TLETranslation : public KeplerTranslation {
|
||||
public:
|
||||
struct FileFormatError : public ghoul::RuntimeError {
|
||||
explicit FileFormatError(std::string offense);
|
||||
std::string offense;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the TLETranslation class. The \p dictionary must contain a key for
|
||||
* the file that contains the TLE information. The ghoul::Dictionary will be tested
|
||||
* against the openspace::Documentation returned by Documentation.
|
||||
* \param The ghoul::Dictionary that contains the information for this TLETranslation
|
||||
(*/
|
||||
TLETranslation(const ghoul::Dictionary& dictionary = ghoul::Dictionary());
|
||||
|
||||
/**
|
||||
* Method returning the openspace::Documentation that describes the ghoul::Dictinoary
|
||||
* that can be passed to the constructor.
|
||||
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
|
||||
* be passed to the constructor
|
||||
*/
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Reads the provided TLE file and calles the KeplerTranslation::setKeplerElments
|
||||
* method with the correct values. If \p filename is a valid TLE file but contains
|
||||
* disallowed values (see KeplerTranslation::setKeplerElements), a
|
||||
* KeplerTranslation::RangeError is thrown.
|
||||
*
|
||||
* \param filename The path to the file that contains the TLE file.
|
||||
* \param lineNum The line number in the file where the set of 3 TLE lines starts
|
||||
*
|
||||
* \throw std::system_error if the TLE file is malformed (does not contain at least
|
||||
* two lines that start with \c 1 and \c 2.
|
||||
* \throw KeplerTranslation::RangeError If the Keplerian elements are outside of
|
||||
* the valid range supported by Kepler::setKeplerElements
|
||||
* \pre The \p filename must exist
|
||||
*/
|
||||
void readTLEFile(const std::string& filename, int lineNum);
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__
|
||||
@@ -91,3 +91,6 @@ create_new_module(
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES} ${SHADER_FILES}
|
||||
)
|
||||
|
||||
target_precompile_headers(${spacecraftinstruments_module} PRIVATE
|
||||
)
|
||||
|
||||
@@ -50,8 +50,13 @@ create_new_module(
|
||||
${HEADER_FILES} ${SOURCE_FILES}
|
||||
)
|
||||
|
||||
target_include_directories(openspace-module-spout SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ext/spout)
|
||||
target_link_libraries(openspace-module-spout PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ext/spout/SpoutLibrary.lib)
|
||||
register_external_libraries("${CMAKE_CURRENT_SOURCE_DIR}/ext/spout/SpoutLibrary.dll")
|
||||
add_library(spout SHARED IMPORTED)
|
||||
target_include_directories(spout SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/ext/spout)
|
||||
set_target_properties(
|
||||
spout PROPERTIES
|
||||
IMPORTED_IMPLIB ${CMAKE_CURRENT_SOURCE_DIR}/ext/spout/SpoutLibrary.lib
|
||||
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ext/spout/SpoutLibrary.dll
|
||||
)
|
||||
target_link_libraries(openspace-module-spout PRIVATE spout)
|
||||
|
||||
target_compile_definitions(openspace-module-spout PUBLIC "OPENSPACE_HAS_SPOUT")
|
||||
|
||||
@@ -25,23 +25,30 @@
|
||||
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
|
||||
|
||||
set(HEADER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/state.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/transition.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/statemachine.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/state.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/transition.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/statemachine.h
|
||||
)
|
||||
source_group("Header Files" FILES ${HEADER_FILES})
|
||||
|
||||
set(SOURCE_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/statemachinemodule_lua.inl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/state.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/transition.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/statemachine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/statemachinemodule_lua.inl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/state.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/transition.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/statemachine.cpp
|
||||
)
|
||||
source_group("Source Files" FILES ${SOURCE_FILES})
|
||||
|
||||
create_new_module(
|
||||
"StateMachine"
|
||||
statemachine_module
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES}
|
||||
"StateMachine"
|
||||
statemachine_module
|
||||
STATIC
|
||||
${HEADER_FILES} ${SOURCE_FILES}
|
||||
)
|
||||
target_precompile_headers(${statemachine_module} PRIVATE
|
||||
<ghoul/glm.h>
|
||||
<ghoul/lua/ghoul_lua.h>
|
||||
<chrono>
|
||||
<filesystem>
|
||||
<string>
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <modules/statemachine/include/statemachine.h>
|
||||
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <ghoul/fmt.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
@@ -76,6 +76,23 @@ target_include_directories(libTUIO11 SYSTEM
|
||||
"${PROJECT_SOURCE_DIR}/libTUIO11/"
|
||||
"${PROJECT_SOURCE_DIR}/libTUIO11/oscpack"
|
||||
)
|
||||
target_precompile_headers(libTUIO11 PRIVATE
|
||||
<iostream>
|
||||
<istream>
|
||||
<ostream>
|
||||
<stdexcept>
|
||||
)
|
||||
# # [["tuiopoint.h"]]
|
||||
# # [["tuiocontainer.h"]]
|
||||
# # [["tuiotime.h"]]
|
||||
# # [["tuioobject.h"]]
|
||||
# # [["tuiodispatcher.h"]]
|
||||
# # [["tuiolistener.h"]]
|
||||
# # [["tuioclient.h"]]
|
||||
# # [["oscreceiver.h"]]
|
||||
# <stdexcept>
|
||||
# <math.h>
|
||||
# )
|
||||
|
||||
if (WIN32)
|
||||
# Tuio dependencies
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user