Files
OpenSpace/src/scene/scenegraphnode.cpp
2021-05-06 17:04:56 +02:00

1114 lines
38 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2021 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/scene/scenegraphnode.h>
#include <modules/base/scale/staticscale.h>
#include <modules/base/rotation/staticrotation.h>
#include <modules/base/translation/statictranslation.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/renderable.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/timeframe.h>
#include <openspace/util/memorymanager.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/misc/assert.h>
#include <ghoul/misc/profiling.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <cmath>
#include <optional>
namespace {
constexpr const char* _loggerCat = "SceneGraphNode";
constexpr openspace::properties::Property::PropertyInfo ComputeScreenSpaceInfo =
{
"ComputeScreenSpaceData",
"Compute Screen Space Data",
"If this value is set to 'true', the screenspace-based properties are calculated "
"at regular intervals. If these values are set to 'false', they are not updated."
};
constexpr openspace::properties::Property::PropertyInfo ScreenSpacePositionInfo = {
"ScreenSpacePosition",
"ScreenSpacePosition",
"The x,y position in screen space. Can be used for placing GUI elements",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo ScreenVisibilityInfo = {
"ScreenVisibility",
"ScreenVisibility",
"Determines if the node is currently visible on screen",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo DistanceFromCamToNodeInfo = {
"DistanceFromCamToNode",
"DistanceFromCamToNode",
"The distance from the camera to the node surface",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo ScreenSizeRadiusInfo = {
"ScreenSizeRadius",
"ScreenSizeRadius",
"The screen size of the radius of the node",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo VisibilityDistanceInfo = {
"VisibilityDistance",
"VisibilityDistance",
"The distace in world coordinates between node and camera "
"at which the screenspace object will become visible",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo BoundingSphereInfo = {
"BoundingSphere",
"Bounding Sphere",
"The bounding sphere of the scene graph node meaning that everything that this "
"scene graph node renders must be contained within this sphere. This value is "
"only used as an override to the bounding sphere calculated by the Renderable, "
"if present. If this value is -1, the Renderable's computed bounding sphere is "
"used",
openspace::properties::Property::Visibility::Developer
};
constexpr openspace::properties::Property::PropertyInfo InteractionSphereInfo = {
"InteractionSphere",
"Interaction Sphere",
"The minimum radius that the camera is allowed to get close to this scene graph "
"node. This value is "
"only used as an override to the bounding sphere calculated by the Renderable, "
"if present. If this value is -1, the Renderable's computed interaction sphere "
"is used",
openspace::properties::Property::Visibility::Developer
};
constexpr openspace::properties::Property::PropertyInfo GuiPathInfo = {
"GuiPath",
"Gui Path",
"This is the path for the scene graph node in the gui "
"example: /Solar System/Planets/Earth",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo GuiNameInfo = {
"GuiName",
"Gui Name",
"This is the name for the scene graph node in the gui "
"example: Earth",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo GuiDescriptionInfo = {
"GuiDescription",
"Gui Description",
"This is the description for the scene graph node to be shown in the gui "
"example: Earth is a special place",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo GuiHiddenInfo = {
"GuiHidden",
"Gui Hidden",
"This represents if the scene graph node should be shown in the gui "
"example: false",
openspace::properties::Property::Visibility::Hidden
};
constexpr openspace::properties::Property::PropertyInfo ShowDebugSphereInfo = {
"ShowDebugSphere",
"Show Debug Sphere",
"If enabled the bounding sphere of this scene graph node is rendered as a debug "
"method",
openspace::properties::Property::Visibility::Developer
};
struct [[codegen::Dictionary(SceneGraphNode)]] Parameters {
// The identifier of this scenegraph node. This name must be unique among all
// scene graph nodes that are loaded in a specific scene. If a duplicate is
// detected the loading of the node will fail, as will all childing that depend on
// the node. The identifier must not contain any whitespaces or '.'
std::string identifier;
// This names the parent of the currently specified scenegraph node. The parent
// must already exist in the scene graph. If not specified, the node will be
// attached to the root of the scenegraph
std::optional<std::string> parent
[[codegen::annotation(
"If specified, this must be a name for another scenegraph node"
)]];
// The renderable that is to be created for this scenegraph node. A renderable is
// a component of a scenegraph node that will lead to some visual result on the
// screen. The specifics heavily depend on the 'Type' of the renderable. If no
// Renderable is specified, this scenegraph node is an internal node and can be
// used for either group children, or apply common transformations to a group of
// children
std::optional<ghoul::Dictionary> renderable [[codegen::reference("renderable")]];
// A hard-coded bounding sphere to be used for the cases where the Renderable is
// not able to provide a reasonable bounding sphere or the calculated bounding
// sphere needs to be overwritten for some reason
std::optional<double> boundingSphere;
// A hard-coded radius for limiting the interaction radius, meaning the minimal
// distance that the camera can approach this scene graph node
std::optional<double> interactionSphere;
struct Transform {
// This node describes a translation that is applied to the scenegraph node
// and all its children. Depending on the 'Type' of the translation, this can
// either be a static translation or a time-varying one
std::optional<ghoul::Dictionary> translation
[[codegen::reference("core_transform_translation")]];
// This nodes describes a rotation that is applied to the scenegraph node and
// all its children. Depending on the 'Type' of the rotation, this can either
// be a static rotation or a time-varying one
std::optional<ghoul::Dictionary> rotation
[[codegen::reference("core_transform_rotation")]];
// This node describes a scaling that is applied to the scenegraph node and
// all its children. Depending on the 'Type' of the scaling, this can either
// be a static scaling or a time-varying one
std::optional<ghoul::Dictionary> scale
[[codegen::reference("core_transform_scaling")]];
};
// This describes a set of transformations that are applied to this scenegraph
// node and all of its children. There are only three possible values
// corresponding to a 'Translation', a 'Rotation', and a 'Scale'
std::optional<Transform> transform;
// Specifies the time frame for when this node should be active
std::optional<ghoul::Dictionary> timeFrame
[[codegen::reference("core_time_frame")]];
// A tag or list of tags that can be used to reference to a group of scenegraph
// nodes.
std::optional<std::variant<std::string, std::vector<std::string>>> tag;
struct Gui {
// An optional user-facing name for this SceneGraphNode, which does not have
// to be unique, though it is recommended, and can contain any characters
std::optional<std::string> name;
// If this value is specified, this '/' separated URI specifies the location
// of this scenegraph node in a GUI representation, for instance
// '/SolarSystem/Earth/Moon'
std::optional<std::string> path;
// A user-facing description about this scene graph node
std::optional<std::string> description;
// If this value is specified, GUI applications are incouraged to ignore this
// scenegraph node. This is most useful to trim collective lists of nodes and
// not display, for example, barycenters
std::optional<bool> hidden;
};
// Additional information that is passed to GUI applications. These are all hints
// and do not have any impact on the actual function of the scenegraph node
std::optional<Gui> gui [[codegen::key("GUI")]];
};
#include "scenegraphnode_codegen.cpp"
} // namespace
namespace openspace {
#ifdef Debugging_Core_SceneGraphNode_Indices
int SceneGraphNode::nextIndex = 0;
#endif // Debugging_Core_SceneGraphNode_Indices
ghoul::mm_unique_ptr<SceneGraphNode> SceneGraphNode::createFromDictionary(
const ghoul::Dictionary& dictionary)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
SceneGraphNode* n = global::memoryManager->PersistentMemory.alloc<SceneGraphNode>();
ghoul::mm_unique_ptr<SceneGraphNode> result = ghoul::mm_unique_ptr<SceneGraphNode>(n);
#ifdef Debugging_Core_SceneGraphNode_Indices
result->index = nextIndex++;
#endif // Debugging_Core_SceneGraphNode_Indices
result->setIdentifier(p.identifier);
if (p.gui.has_value()) {
if (p.gui->name.has_value()) {
result->setGuiName(*p.gui->name);
result->_guiDisplayName = result->guiName();
result->addProperty(result->_guiDisplayName);
}
if (p.gui->description.has_value()) {
result->setDescription(*p.gui->description);
result->_guiDescription = result->description();
result->addProperty(result->_guiDescription);
}
if (p.gui->hidden.has_value()) {
result->_guiHidden = *p.gui->hidden;
result->addProperty(result->_guiHidden);
}
if (p.gui->path.has_value()) {
result->_guiPath = *p.gui->path;
result->addProperty(result->_guiPath);
}
}
result->_overrideBoundingSphere = p.boundingSphere;
result->_overrideInteractionSphere = p.interactionSphere;
if (p.transform.has_value()) {
if (p.transform->translation.has_value()) {
result->_transform.translation = Translation::createFromDictionary(
*p.transform->translation
);
// @TODO(abock, 2021-03-05) I don't think this is necessary anymore as we
// transitioned to throwing exceptions when the construction fails
if (result->_transform.translation == nullptr) {
LERROR(fmt::format(
"Failed to create ephemeris for SceneGraphNode '{}'",
result->identifier()
));
return nullptr;
}
LDEBUG(fmt::format(
"Successfully created ephemeris for '{}'", result->identifier()
));
result->addPropertySubOwner(result->_transform.translation.get());
}
if (p.transform->rotation.has_value()) {
result->_transform.rotation = Rotation::createFromDictionary(
*p.transform->rotation
);
// @TODO(abock, 2021-03-05) I don't think this is necessary anymore as we
// transitioned to throwing exceptions when the construction fails
if (result->_transform.rotation == nullptr) {
LERROR(fmt::format(
"Failed to create rotation for SceneGraphNode '{}'",
result->identifier()
));
return nullptr;
}
LDEBUG(fmt::format(
"Successfully created rotation for '{}'", result->identifier()
));
result->addPropertySubOwner(result->_transform.rotation.get());
}
if (p.transform->scale.has_value()) {
result->_transform.scale = Scale::createFromDictionary(*p.transform->scale);
// @TODO(abock, 2021-03-05) I don't think this is necessary anymore as we
// transitioned to throwing exceptions when the construction fails
if (result->_transform.scale == nullptr) {
LERROR(fmt::format(
"Failed to create scale for SceneGraphNode '{}'",
result->identifier()
));
return nullptr;
}
LDEBUG(fmt::format(
"Successfully created scale for '{}'", result->identifier()
));
result->addPropertySubOwner(result->_transform.scale.get());
}
}
if (p.timeFrame.has_value()) {
result->_timeFrame = TimeFrame::createFromDictionary(*p.timeFrame);
// @TODO(abock, 2021-03-05) I don't think this is necessary anymore as we
// transitioned to throwing exceptions when the construction fails
if (result->_timeFrame == nullptr) {
LERROR(fmt::format(
"Failed to create time frame for SceneGraphNode '{}'",
result->identifier()
));
return nullptr;
}
LDEBUG(fmt::format(
"Successfully created time frame for '{}'", result->identifier()
));
result->addPropertySubOwner(result->_timeFrame.get());
}
// We initialize the renderable last as it probably has the most dependencies
if (p.renderable.has_value()) {
result->_renderable = Renderable::createFromDictionary(*p.renderable);
ghoul_assert(result->_renderable, "Failed to create Renderable");
result->_renderable->_parent = result.get();
result->addPropertySubOwner(result->_renderable.get());
LDEBUG(fmt::format(
"Successfully created renderable for '{}'", result->identifier()
));
}
if (p.tag.has_value()) {
if (std::holds_alternative<std::string>(*p.tag)) {
result->addTag(std::get<std::string>(*p.tag));
}
else if (std::holds_alternative<std::vector<std::string>>(*p.tag)) {
for (const std::string& tag : std::get<std::vector<std::string>>(*p.tag)) {
if (!tag.empty()) {
result->addTag(tag);
}
}
}
else {
throw ghoul::MissingCaseException();
}
}
LDEBUG(fmt::format("Successfully created SceneGraphNode '{}'", result->identifier()));
result->_lastScreenSpaceUpdateTime = std::chrono::high_resolution_clock::now();
return result;
}
documentation::Documentation SceneGraphNode::Documentation() {
documentation::Documentation doc = codegen::doc<Parameters>();
doc.id = "core_scene_node";
return doc;
}
ghoul::opengl::ProgramObject* SceneGraphNode::_debugSphereProgram = nullptr;
SceneGraphNode::SceneGraphNode()
: properties::PropertyOwner({ "" })
, _guiHidden(GuiHiddenInfo)
, _guiPath(GuiPathInfo)
, _guiDisplayName(GuiNameInfo)
, _guiDescription(GuiDescriptionInfo)
, _transform {
ghoul::mm_unique_ptr<Translation>(
global::memoryManager->PersistentMemory.alloc<StaticTranslation>()
),
ghoul::mm_unique_ptr<Rotation>(
global::memoryManager->PersistentMemory.alloc<StaticRotation>()
),
ghoul::mm_unique_ptr<Scale>(
global::memoryManager->PersistentMemory.alloc<StaticScale>()
)
}
, _boundingSphere(BoundingSphereInfo, -1.0, -1.0, 1e12)
, _interactionSphere(InteractionSphereInfo, -1.0, -1.0, 1e12)
, _computeScreenSpaceValues(ComputeScreenSpaceInfo, false)
, _screenSpacePosition(ScreenSpacePositionInfo, glm::ivec2(-1, -1))
, _screenVisibility(ScreenVisibilityInfo, false)
, _distFromCamToNode(DistanceFromCamToNodeInfo, -1.0)
, _screenSizeRadius(ScreenSizeRadiusInfo, 0)
, _visibilityDistance(VisibilityDistanceInfo, 6e10f)
, _showDebugSphere(ShowDebugSphereInfo, false)
{
addProperty(_computeScreenSpaceValues);
addProperty(_screenSpacePosition);
addProperty(_screenVisibility);
addProperty(_distFromCamToNode);
addProperty(_screenSizeRadius);
addProperty(_visibilityDistance);
_boundingSphere.onChange([this]() {
if (_boundingSphere >= 0.0) {
_overrideBoundingSphere = _boundingSphere;
}
else {
_overrideBoundingSphere = std::nullopt;
}
});
addProperty(_boundingSphere);
_interactionSphere.onChange([this]() {
if (_interactionSphere >= 0.0) {
_overrideInteractionSphere = _interactionSphere;
}
else {
_overrideInteractionSphere = std::nullopt;
}
});
addProperty(_interactionSphere);
addProperty(_showDebugSphere);
}
SceneGraphNode::~SceneGraphNode() {} // NOLINT
void SceneGraphNode::initialize() {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
LDEBUG(fmt::format("Initializing: {}", identifier()));
if (_renderable) {
_renderable->initialize();
}
if (_transform.translation) {
_transform.translation->initialize();
}
if (_transform.rotation) {
_transform.rotation->initialize();
}
if (_transform.scale) {
_transform.scale->initialize();
}
_state = State::Initialized;
LDEBUG(fmt::format("Finished initializing: {}", identifier()));
}
void SceneGraphNode::initializeGL() {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
LDEBUG(fmt::format("Initializing GL: {}", identifier()));
if (_renderable) {
_renderable->initializeGL();
}
// The first one to get here will create program shared between all scene graph nodes
if (_debugSphereProgram == nullptr) {
std::unique_ptr<ghoul::opengl::ProgramObject> shader =
global::renderEngine->buildRenderProgram(
"DebugSphere",
absPath("${SHADERS}/core/xyzuvrgba_vs.glsl"),
absPath("${SHADERS}/core/xyzuvrgba_fs.glsl")
);
// Since we are only going to create a single of these shaders for the lifetime of
// the program, we are not bothering with freeing it as the overhead of detecting
// when the last scenegraph node will be destroyed would be a bit too much for the
// benefit that we would gain from it
_debugSphereProgram = shader.release();
_debugSphereProgram->setIgnoreUniformLocationError(
ghoul::opengl::ProgramObject::IgnoreError::Yes
);
}
_state = State::GLInitialized;
LDEBUG(fmt::format("Finished initializating GL: {}", identifier()));
}
void SceneGraphNode::deinitialize() {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
LDEBUG(fmt::format("Deinitializing: {}", identifier()));
setScene(nullptr);
if (_renderable) {
_renderable->deinitialize();
}
clearChildren();
_parent = nullptr;
LDEBUG(fmt::format("Finished deinitializing: {}", identifier()));
}
void SceneGraphNode::deinitializeGL() {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
LDEBUG(fmt::format("Deinitializing GL: {}", identifier()));
if (_renderable) {
_renderable->deinitializeGL();
}
LDEBUG(fmt::format("Finished deinitializing GL: {}", identifier()));
}
void SceneGraphNode::traversePreOrder(const std::function<void(SceneGraphNode*)>& fn) {
fn(this);
for (ghoul::mm_unique_ptr<SceneGraphNode>& child : _children) {
child->traversePreOrder(fn);
}
}
void SceneGraphNode::traversePostOrder(const std::function<void(SceneGraphNode*)>& fn) {
for (ghoul::mm_unique_ptr<SceneGraphNode>& child : _children) {
child->traversePostOrder(fn);
}
fn(this);
}
void SceneGraphNode::update(const UpdateData& data) {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
State s = _state;
if (s != State::Initialized && _state != State::GLInitialized) {
return;
}
if (!isTimeFrameActive(data.time)) {
return;
}
if (_transform.translation) {
_transform.translation->update(data);
}
if (_transform.rotation) {
_transform.rotation->update(data);
}
if (_transform.scale) {
_transform.scale->update(data);
}
UpdateData newUpdateData = data;
// Assumes _worldRotationCached and _worldScaleCached have been calculated for parent
_worldPositionCached = calculateWorldPosition();
_worldRotationCached = calculateWorldRotation();
_worldScaleCached = calculateWorldScale();
newUpdateData.modelTransform.translation = _worldPositionCached;
newUpdateData.modelTransform.rotation = _worldRotationCached;
newUpdateData.modelTransform.scale = _worldScaleCached;
glm::dmat4 translation = glm::translate(
glm::dmat4(1.0),
newUpdateData.modelTransform.translation
);
glm::dmat4 rotation = glm::dmat4(newUpdateData.modelTransform.rotation);
glm::dmat4 scaling = glm::scale(
glm::dmat4(1.0),
newUpdateData.modelTransform.scale
);
_modelTransformCached = translation * rotation * scaling;
if (_renderable && _renderable->isReady() &&
(_renderable->isEnabled() || _renderable->shouldUpdateIfDisabled()))
{
_renderable->update(newUpdateData);
}
}
void SceneGraphNode::render(const RenderData& data, RendererTasks& tasks) {
ZoneScoped
ZoneName(identifier().c_str(), identifier().size())
if (_state != State::GLInitialized) {
return;
}
const bool visible = _renderable && _renderable->isVisible() &&
_renderable->isReady() && _renderable->isEnabled() &&
_renderable->matchesRenderBinMask(data.renderBinMask);
if (!visible) {
return;
}
if (!isTimeFrameActive(data.time)) {
return;
}
{
TracyGpuZone("Render")
RenderData newData = {
data.camera,
data.time,
data.renderBinMask,
{ _worldPositionCached, _worldRotationCached, _worldScaleCached }
};
_renderable->render(newData, tasks);
if (_computeScreenSpaceValues) {
computeScreenSpaceData(newData);
}
}
if (_showDebugSphere) {
if (const double bs = boundingSphere(); bs > 0.0) {
renderDebugSphere(data.camera, bs, glm::vec4(0.5f, 0.15f, 0.5f, 0.75f));
}
if (const double is = interactionSphere(); is > 0.0) {
renderDebugSphere(data.camera, is, glm::vec4(0.15f, 0.35f, 0.85f, 0.75f));
}
}
}
void SceneGraphNode::renderDebugSphere(const Camera& camera, double size, glm::vec4 color)
{
glm::dvec3 scaleVec = _worldScaleCached * size;
glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), _worldPositionCached) *
glm::dmat4(_worldRotationCached) *
glm::scale(glm::dmat4(1.0), scaleVec);
glm::mat4 modelViewProjection = camera.projectionMatrix() *
glm::mat4(camera.combinedViewMatrix() * modelTransform);
_debugSphereProgram->activate();
_debugSphereProgram->setUniform("hasTexture", 0);
_debugSphereProgram->setUniform("proj", modelViewProjection);
_debugSphereProgram->setUniform("color", color);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(rendering::helper::vertexObjects.sphere.vao);
glDrawElements(
GL_TRIANGLES,
rendering::helper::vertexObjects.sphere.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
glLineWidth(2.0);
_debugSphereProgram->setUniform("color", glm::vec4(1.f, 1.f, 1.f, 1.f));
glDrawElements(
GL_LINES,
rendering::helper::vertexObjects.sphere.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
glBindVertexArray(0);
_debugSphereProgram->deactivate();
}
void SceneGraphNode::setParent(SceneGraphNode& parent) {
ghoul_assert(_parent != nullptr, "Node must be attached to a parent");
parent.attachChild(_parent->detachChild(*this));
}
void SceneGraphNode::attachChild(ghoul::mm_unique_ptr<SceneGraphNode> child) {
ghoul_assert(child != nullptr, "Child may not be null");
ghoul_assert(child->parent() == nullptr, "Child may not already have a parent");
// Create link between parent and child
child->_parent = this;
SceneGraphNode* childRaw = child.get();
_children.push_back(std::move(child));
// Set scene of child (and children recursively)
childRaw->setScene(_scene);
}
ghoul::mm_unique_ptr<SceneGraphNode> SceneGraphNode::detachChild(SceneGraphNode& child) {
ghoul_assert(
child._dependentNodes.empty(),
"Nodes cannot depend on a node being detached"
);
ghoul_assert(child._parent != nullptr, "Node must be attached to a parent");
const auto iter = std::find_if(
_children.begin(),
_children.end(),
[&child] (const ghoul::mm_unique_ptr<SceneGraphNode>& c) {
return &child == c.get();
}
);
if (iter == _children.end()) {
LERROR("Trying to detach a non-existing child");
}
traversePreOrder([](SceneGraphNode* node) {
node->clearDependencies();
});
// Unset scene of child (and children recursively)
if (_scene) {
child.setScene(nullptr);
}
// Remove link between parent and child
child._parent = nullptr;
ghoul::mm_unique_ptr<SceneGraphNode> c = std::move(*iter);
_children.erase(iter);
return c;
}
void SceneGraphNode::clearChildren() {
traversePreOrder([](SceneGraphNode* node) {
node->clearDependencies();
});
for (const ghoul::mm_unique_ptr<SceneGraphNode>& c : _children) {
if (_scene) {
c->setScene(nullptr);
}
c->_parent = nullptr;
}
_children.clear();
}
void SceneGraphNode::addDependency(SceneGraphNode& dependency) {
dependency._dependentNodes.push_back(this);
_dependencies.push_back(&dependency);
if (_scene) {
_scene->markNodeRegistryDirty();
}
}
void SceneGraphNode::removeDependency(SceneGraphNode& dependency) {
dependency._dependentNodes.erase(
std::remove(
dependency._dependentNodes.begin(),
dependency._dependentNodes.end(),
this
),
dependency._dependentNodes.end()
);
_dependencies.erase(
std::remove(
_dependencies.begin(),
_dependencies.end(),
&dependency
),
_dependencies.end()
);
if (_scene) {
_scene->markNodeRegistryDirty();
}
}
void SceneGraphNode::clearDependencies() {
for (SceneGraphNode* dependency : _dependencies) {
dependency->_dependentNodes.erase(
std::remove(
dependency->_dependentNodes.begin(),
dependency->_dependentNodes.end(),
this
),
dependency->_dependentNodes.end()
);
}
_dependencies.clear();
if (_scene) {
_scene->markNodeRegistryDirty();
}
}
void SceneGraphNode::setDependencies(const std::vector<SceneGraphNode*>& dependencies) {
clearDependencies();
_dependencies = dependencies;
for (SceneGraphNode* dependency : dependencies) {
dependency->_dependentNodes.push_back(this);
}
if (_scene) {
_scene->markNodeRegistryDirty();
}
}
void SceneGraphNode::computeScreenSpaceData(RenderData& newData) {
// Purposely slow the update rate of screen space position in order to reduce the
// effects of jittering in the position of information icon markers in web gui.
auto now = std::chrono::high_resolution_clock::now();
if ((now - _lastScreenSpaceUpdateTime) < std::chrono::milliseconds(100)) {
return;
}
_lastScreenSpaceUpdateTime = now;
// Calculate ndc
const Camera& cam = newData.camera;
const glm::dvec3& worldPos = _worldPositionCached;
const glm::dvec4 clipSpace = glm::dmat4(cam.projectionMatrix()) *
cam.combinedViewMatrix() * glm::vec4(worldPos, 1.0);
const glm::dvec2 worldPosNDC = glm::dvec2(clipSpace / clipSpace.w);
const bool visible = worldPosNDC.x >= -1.0 && worldPosNDC.x <= 1.0 &&
worldPosNDC.y >= -1.0 && worldPosNDC.y <= 1.0 && clipSpace.z > 0;
// If not on the screen, we want to reset it or don't update it
if (!visible) {
_screenVisibility = false;
return;
}
glm::ivec2 res = global::windowDelegate->currentSubwindowSize();
// Get the radius of node
double nodeRadius = boundingSphere();
// Distance from the camera to the node
double distFromCamToNode = glm::distance(cam.positionVec3(), worldPos) - nodeRadius;
// Fix to limit the update of properties
if (distFromCamToNode >= _visibilityDistance) {
_screenVisibility = false;
return;
}
_screenVisibility = true;
// Calculate the node radius to screensize pixels
const glm::dvec3 lookUp = normalize(cam.lookUpVectorWorldSpace());
const glm::dvec3 radiusPos = worldPos + (nodeRadius * lookUp);
const glm::dvec4 clipSpaceRadius = glm::dmat4(cam.projectionMatrix()) *
cam.combinedViewMatrix() * glm::vec4(radiusPos, 1.0);
const glm::dvec3 radiusNDC = clipSpaceRadius / clipSpaceRadius.w;
const glm::ivec2 centerScreenSpace = glm::ivec2(
(worldPosNDC.x + 1.0) * res.x / 2,
(worldPosNDC.y + 1.0) * res.y / 2
);
const glm::ivec2 radiusScreenSpace = glm::ivec2(
(radiusNDC.x + 1.0) * res.x / 2,
(radiusNDC.y + 1.0) * res.y / 2
);
const double screenSpaceRadius = length(
glm::vec2(centerScreenSpace) - glm::vec2(radiusScreenSpace)
);
constexpr const double RadiusThreshold = 2.0;
const double r = std::fabs(_screenSizeRadius - screenSpaceRadius);
if (r > RadiusThreshold) {
_screenSizeRadius = screenSpaceRadius;
}
constexpr const double ZoomThreshold = 0.1;
const double d = std::fabs(_distFromCamToNode - distFromCamToNode);
if (d > (ZoomThreshold * distFromCamToNode)) {
_distFromCamToNode = distFromCamToNode;
}
constexpr const double MoveThreshold = 1.0;
const glm::ivec2 ssp = _screenSpacePosition;
const glm::dvec2 c = glm::abs(ssp - centerScreenSpace);
if (c.x > MoveThreshold || c.y > MoveThreshold) {
_screenSpacePosition = centerScreenSpace;
}
}
SurfacePositionHandle SceneGraphNode::calculateSurfacePositionHandle(
const glm::dvec3& targetModelSpace) const
{
if (_renderable) {
return _renderable->calculateSurfacePositionHandle(targetModelSpace);
}
else {
return { glm::dvec3(0.0), glm::normalize(targetModelSpace), 0.0 };
}
}
const std::vector<SceneGraphNode*>& SceneGraphNode::dependencies() const {
return _dependencies;
}
const std::vector<SceneGraphNode*>& SceneGraphNode::dependentNodes() const {
return _dependentNodes;
}
glm::dvec3 SceneGraphNode::position() const {
return _transform.translation->position();
}
const glm::dmat3& SceneGraphNode::rotationMatrix() const {
return _transform.rotation->matrix();
}
glm::dvec3 SceneGraphNode::scale() const {
return _transform.scale->scaleValue();
}
glm::dvec3 SceneGraphNode::worldPosition() const {
return _worldPositionCached;
}
const glm::dmat3& SceneGraphNode::worldRotationMatrix() const {
return _worldRotationCached;
}
glm::dmat4 SceneGraphNode::modelTransform() const {
return _modelTransformCached;
}
glm::dvec3 SceneGraphNode::worldScale() const {
return _worldScaleCached;
}
std::string SceneGraphNode::guiPath() const {
return _guiPath;
}
bool SceneGraphNode::hasGuiHintHidden() const {
return _guiHidden;
}
glm::dvec3 SceneGraphNode::calculateWorldPosition() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
const glm::dvec3 wp = _parent->worldPosition();
const glm::dmat3 wrot = _parent->worldRotationMatrix();
const glm::dvec3 ws = _parent->worldScale();
const glm::dvec3 p = position();
return wp + wrot * (ws * p);
}
else {
return position();
}
}
bool SceneGraphNode::isTimeFrameActive(const Time& time) const {
for (SceneGraphNode* dep : _dependencies) {
if (!dep->isTimeFrameActive(time)) {
return false;
}
}
if (_parent && !_parent->isTimeFrameActive(time)) {
return false;
}
return !_timeFrame || _timeFrame->isActive(time);
}
glm::dmat3 SceneGraphNode::calculateWorldRotation() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
return _parent->worldRotationMatrix() * rotationMatrix();
}
else {
return rotationMatrix();
}
}
glm::dvec3 SceneGraphNode::calculateWorldScale() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
return _parent->worldScale() * scale();
}
else {
return scale();
}
}
SceneGraphNode* SceneGraphNode::parent() const {
return _parent;
}
Scene* SceneGraphNode::scene() {
return _scene;
}
void SceneGraphNode::setScene(Scene* scene) {
ZoneScoped
// Unregister from previous scene, bottom up
traversePostOrder([](SceneGraphNode* node) {
if (node->_scene) {
node->_scene->unregisterNode(node);
}
node->_scene = nullptr;
});
if (!scene) {
return;
}
// Register on new scene, top down
traversePreOrder([scene](SceneGraphNode* node) {
node->_scene = scene;
if (scene) {
scene->registerNode(node);
}
});
}
std::vector<SceneGraphNode*> SceneGraphNode::children() const {
std::vector<SceneGraphNode*> nodes;
for (const ghoul::mm_unique_ptr<SceneGraphNode>& child : _children) {
nodes.push_back(child.get());
}
return nodes;
}
double SceneGraphNode::boundingSphere() const {
if (_overrideBoundingSphere.has_value()) {
return glm::compMax(scale() * *_overrideBoundingSphere);
}
if (_renderable) {
return glm::compMax(scale() * _renderable->boundingSphere());
}
else {
return 0.0;
}
}
double SceneGraphNode::interactionSphere() const {
if (_overrideInteractionSphere.has_value()) {
return glm::compMax(scale() * *_overrideInteractionSphere);
}
if (_renderable) {
return glm::compMax(scale() * _renderable->interactionSphere());
}
else {
return 0.0;
}
}
const Renderable* SceneGraphNode::renderable() const {
return _renderable.get();
}
Renderable* SceneGraphNode::renderable() {
return _renderable.get();
}
SceneGraphNode* SceneGraphNode::childNode(const std::string& id) {
if (identifier() == id) {
return this;
}
else {
for (ghoul::mm_unique_ptr<SceneGraphNode>& it : _children) {
SceneGraphNode* tmp = it->childNode(id);
if (tmp) {
return tmp;
}
}
}
return nullptr;
}
} // namespace openspace