Update SceneGraphNode to have a full transformation defined in terms of translation, scale and rotation.

This commit is contained in:
Kalle Bladin
2016-08-17 00:19:25 -04:00
parent c55872d5f4
commit 542b9e11e6
11 changed files with 296 additions and 157 deletions

View File

@@ -147,7 +147,7 @@ function preInitialization()
-- openspace.time.setTime("2015-07-12T22:19:20.00")
-- openspace.time.setTime("2015-07-13T20:59:00.00")
-- openspace.time.setTime("2015-07-14T02:41:55.00")
openspace.time.setTime("2016-09-08T00:00:00.00")
openspace.time.setTime("2018-12-20T22:47:00.00")
openspace.time.setDeltaTime(0)
end

View File

@@ -75,12 +75,22 @@ return {
},
},
Ephemeris = {
Type = "Spice",
Body = BENNU_BODY,
-- Reference = "ECLIPJ2000",
Reference = "GALACTIC",
Observer = "SUN",
Transform = {
Translation = {
Type = "SpiceEphemeris",
Body = BENNU_BODY,
Reference = "GALACTIC",
Observer = "SUN",
},
Rotation = {
Type = "SpiceRotation",
SourceFrame = "IAU_BENNU",
DestinationFrame = "GALACTIC",
},
Scale = {
Type = "StaticScale",
Scale = 1,
},
},
GuiName = "/Solar/Bennu"

View File

@@ -6,7 +6,7 @@ return {
------------------------
{
Name = "OsirisRex",
Parent = "Bennu2",
Parent = "SolarSystemBarycenter",
Renderable = {
Type = "RenderableModel",
Body = "OSIRIS-REX",
@@ -25,18 +25,23 @@ return {
Ghosting = false,
},
},
Ephemeris = {
Type = "Spice",
Body = "OSIRIS-REX",
-- Reference = "ECLIPJ2000",
Reference = "GALACTIC",
Observer = BENNU_BODY,
Transform = {
Translation = {
Type = "SpiceEphemeris",
Body = "OSIRIS-REX",
Reference = "GALACTIC",
Observer = "SUN",
},
Rotation = {
Type = "SpiceRotation",
SourceFrame = "ORX_SPACECRAFT",
DestinationFrame = "GALACTIC",
},
Scale = {
Type = "StaticScale",
Scale = 1,
},
},
Rotation = {
Source = "ORX_SPACECRAFT",
Destination = "GALACTIC"
},
GuiName = "/Solar/OsirisRex"
},
@@ -61,55 +66,67 @@ return {
Ghosting = false,
},
},
Ephemeris = {
Type = "Static",
Transform = {
Translation = {
Type = "StaticEphemeris",
Position = {1, 0, 0},
},
Rotation = {
Type = "SpiceRotation",
SourceFrame = "ORX_OCAMS_POLYCAM",
DestinationFrame = "ORX_SPACECRAFT",
},
Scale = {
Type = "StaticScale",
Scale = 1,
},
},
Rotation = {
Source = "ORX_OCAMS_POLYCAM",
Destination = "ORX_SPACECRAFT"
GuiName = "/Solar/ORX_OCAMS_POLYCAM"
},
{
Name = "ORX_REXIS",
Parent = "OsirisRex",
Renderable = {
Type = "RenderableModel",
Body = "OSIRIS-REX",
Geometry = {
Type = "MultiModelGeometry",
GeometryFile = "models/Osiris.obj",
Magnification = 0,
},
Textures = {
Type = "simple",
Color = "textures/osirisTex.png",
},
Shading = {
PerformShading = true,
Fadeable = false,
Ghosting = false,
},
},
Transform = {
Translation = {1,0,0}, -- Translation relative to parent
--Rotation = {0,0,0}, -- Euler angles relative to parent (not implemented)
--Scale = {1,1,1}, -- Scale relative to parent (not implemented)
Translation = {
Type = "StaticEphemeris",
Position = {-1, 0, 0},
},
Rotation = {
Type = "SpiceRotation",
SourceFrame = "ORX_REXIS",
DestinationFrame = "ORX_SPACECRAFT",
},
Scale = {
Type = "StaticScale",
Scale = 1,
},
},
GuiName = "/Solar/ORX_OCAMS_POLYCAM"
GuiName = "/Solar/ORX_REXIS"
},
--[[
{
Name = "ORX_OCAMS_POLYCAM FOV",
Parent = "ORX_OCAMS_POLYCAM",
Renderable = {
Type = "RenderableFov",
Body = "OSIRIS-REX",
Frame = "GALACTIC",
RGB = { 0.8, 0.7, 0.7 },
Textures = {
Type = "simple",
Color = "textures/glare_blue.png",
-- need to add different texture
},
Instrument = {
Name = "ORX_OCAMS_POLYCAM",
Method = "ELLIPSOID",
Aberration = "NONE",
},
PotentialTargets = {
"Bennu2",
}
},
GuiName = "/Solar/ORX_OCAMS_POLYCAM"
},
]]
{
Name = "POLYCAM",
Name = "POLYCAM FOV",
Parent = "ORX_OCAMS_POLYCAM",
Renderable = {
Type = "RenderableFov",
@@ -130,10 +147,35 @@ return {
BENNU_BODY -- Bennu
}
},
GuiName = "/Solar/POLYCAM"
GuiName = "/Solar/POLYCAM FOV"
},
{
Name = "REXIS FOV",
Parent = "ORX_REXIS",
Renderable = {
Type = "RenderableFov",
Body = "OSIRIS-REX",
Frame = "ORX_REXIS",
RGB = { 0.8, 0.7, 0.7 },
Textures = {
Type = "simple",
Color = "textures/glare_blue.png",
-- need to add different texture
},
Instrument = {
Name = "ORX_REXIS",
Method = "ELLIPSOID",
Aberration = "NONE",
},
PotentialTargets = {
BENNU_BODY -- Bennu
}
},
GuiName = "/Solar/REXIS FOV"
},
-- Latest image taken by POLYCAM
--[[
-- Latest image taken by POLYCAM
{
Name = "ImagePlaneOsirisRex",
Parent = "OsirisRex",
@@ -151,6 +193,24 @@ return {
Position = {0, 0, 0, 1}
},
},
-- POLYCAM FoV square
{
Name = "FovImagePlane",
Parent = "OsirisRex",
Renderable = {
Type = "RenderablePlaneProjection",
Frame = "IAU_BENNU",
DefaultTarget = BENNU_BODY,
Spacecraft = "OSIRIS-REX",
Instrument = "ORX_OCAMS_POLYCAM",
Moving = true,
Texture = "textures/squarefov.png",
},
Ephemeris = {
Type = "Static",
Position = {0, 0, 0, 1}
},
},
]]
{

View File

@@ -28,6 +28,8 @@
// open space includes
#include <openspace/rendering/renderable.h>
#include <openspace/scene/ephemeris.h>
#include <openspace/scene/rotation.h>
#include <openspace/scene/scale.h>
#include <openspace/properties/propertyowner.h>
#include <openspace/scene/scene.h>
@@ -76,10 +78,12 @@ public:
//bool abandonChild(SceneGraphNode* child);
glm::dvec3 position() const;
glm::dvec3 worldPosition() const;
const glm::dmat3& rotationMatrix() const;
double scale() const;
glm::dvec3 worldPosition() const;
const glm::dmat3& worldRotationMatrix() const;
double worldScale() const;
SceneGraphNode* parent() const;
const std::vector<SceneGraphNode*>& children() const;
@@ -96,6 +100,7 @@ public:
Renderable* renderable();
// @TODO Remove once the scalegraph is in effect ---abock
void setEphemeris(Ephemeris* eph) {
delete _ephemeris;
_ephemeris = eph;
@@ -106,14 +111,19 @@ private:
glm::dvec3 calculateWorldPosition() const;
glm::dmat3 calculateWorldRotation() const;
double calculateWorldScale() const;
// Transformation defined by ephemeris, rotation and scale
Ephemeris* _ephemeris;
Rotation* _rotation;
Scale* _scale;
glm::dvec3 _worldPositionCached;
glm::dmat3 _worldRotationCached;
double _worldScaleCached;
std::vector<SceneGraphNode*> _children;
SceneGraphNode* _parent;
Ephemeris* _ephemeris;
std::string _rotationSourceFrame;
std::string _rotationDestinationFrame;
glm::dmat3 _rotationMatrix;
PerformanceRecord _performanceRecord;
@@ -123,9 +133,6 @@ private:
bool _boundingSphereVisible;
PowerScaledScalar _boundingSphere;
glm::dvec3 _translation; // Relative translation added on the ephemeris position
glm::dvec3 _worldPositionCached;
glm::dmat3 _worldRotationCached;
};
} // namespace openspace

View File

@@ -52,6 +52,7 @@ struct RenderData {
bool doPerformanceMeasurement;
glm::dvec3 positionVec3;
glm::dmat3 rotation;
double scale;
};
struct RaycasterTask {

View File

@@ -43,6 +43,9 @@ set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/rendering/screenspaceimage.h
${CMAKE_CURRENT_SOURCE_DIR}/ephemeris/spiceephemeris.h
${CMAKE_CURRENT_SOURCE_DIR}/ephemeris/staticephemeris.h
${CMAKE_CURRENT_SOURCE_DIR}/rotation/spicerotation.h
${CMAKE_CURRENT_SOURCE_DIR}/rotation/staticrotation.h
${CMAKE_CURRENT_SOURCE_DIR}/scale/staticscale.h
)
source_group("Header Files" FILES ${HEADER_FILES})
@@ -65,6 +68,9 @@ set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/rendering/screenspaceimage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ephemeris/spiceephemeris.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ephemeris/staticephemeris.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rotation/spicerotation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rotation/staticrotation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/scale/staticscale.cpp
)
source_group("Source Files" FILES ${SOURCE_FILES})

View File

@@ -50,6 +50,11 @@
#include <modules/base/ephemeris/staticephemeris.h>
#include <modules/base/ephemeris/spiceephemeris.h>
#include <modules/base/rotation/staticrotation.h>
#include <modules/base/rotation/spicerotation.h>
#include <modules/base/scale/staticscale.h>
#include <ghoul/filesystem/filesystem>
namespace openspace {
@@ -61,7 +66,10 @@ BaseModule::BaseModule()
void BaseModule::internalInitialize() {
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<planetgeometry::PlanetGeometry>>());
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<modelgeometry::ModelGeometry>>());
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<ScreenSpaceRenderable>>());
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<ScreenSpaceRenderable>>());
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<Rotation>>());
FactoryManager::ref().addFactory(std::make_unique<ghoul::TemplateFactory<Scale>>());
auto fScreenSpaceRenderable = FactoryManager::ref().factory<ScreenSpaceRenderable>();
ghoul_assert(fScreenSpaceRenderable, "ScreenSpaceRenderable factory was not created");
@@ -85,8 +93,20 @@ void BaseModule::internalInitialize() {
auto fEphemeris = FactoryManager::ref().factory<Ephemeris>();
ghoul_assert(fEphemeris, "Ephemeris factory was not created");
fEphemeris->registerClass<StaticEphemeris>("Static");
fEphemeris->registerClass<SpiceEphemeris>("Spice");
fEphemeris->registerClass<StaticEphemeris>("StaticEphemeris");
fEphemeris->registerClass<SpiceEphemeris>("SpiceEphemeris");
auto fRotation = FactoryManager::ref().factory<Rotation>();
ghoul_assert(fRotation, "Rotation factory was not created");
fRotation->registerClass<StaticRotation>("StaticRotation");
fRotation->registerClass<SpiceRotation>("SpiceRotation");
auto fScale = FactoryManager::ref().factory<Scale>();
ghoul_assert(fScale, "Scale factory was not created");
fScale->registerClass <StaticScale> ("StaticScale");
auto fPlanetGeometry = FactoryManager::ref().factory<planetgeometry::PlanetGeometry>();
ghoul_assert(fPlanetGeometry, "Planet geometry factory was not created");

View File

@@ -31,13 +31,11 @@ namespace {
namespace openspace {
StaticEphemeris::StaticEphemeris(const ghoul::Dictionary& dictionary)
: _position(0.f, 0.f, 0.f)
: _position(0.0, 0.0, 0.0)
{
const bool hasPosition = dictionary.hasKeyAndValue<glm::vec4>(KeyPosition);
const bool hasPosition = dictionary.hasKeyAndValue<glm::vec3>(KeyPosition);
if (hasPosition) {
glm::dvec4 tmp;
dictionary.getValue(KeyPosition, tmp);
_position = tmp.xyz() * glm::pow(10.0, tmp.w);
dictionary.getValue(KeyPosition, _position);
}
}

View File

@@ -211,11 +211,10 @@ void RenderableModel::render(const RenderData& data) {
glm::dmat4 modelTransform =
glm::translate(glm::dmat4(1.0), data.positionVec3) * // Translation
glm::dmat4(data.rotation) * // Spice rotation
glm::dmat4(glm::scale(glm::dmat4(1.0), glm::dvec3(data.scale)));
debugModelRotation; // debug model rotation controlled from GUI
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
glm::vec3 directionToSun = glm::normalize(_sunPos - data.positionVec3);
glm::vec3 directionToSunViewSpace = glm::mat3(data.camera.combinedViewMatrix()) * directionToSun;

View File

@@ -74,6 +74,8 @@ set(OPENSPACE_SOURCE
${OPENSPACE_BASE_DIR}/src/rendering/screenspacerenderable.cpp
${OPENSPACE_BASE_DIR}/src/rendering/transferfunction.cpp
${OPENSPACE_BASE_DIR}/src/scene/ephemeris.cpp
${OPENSPACE_BASE_DIR}/src/scene/rotation.cpp
${OPENSPACE_BASE_DIR}/src/scene/scale.cpp
${OPENSPACE_BASE_DIR}/src/scene/scene.cpp
${OPENSPACE_BASE_DIR}/src/scene/scene_lua.inl
${OPENSPACE_BASE_DIR}/src/scene/scenegraph.cpp
@@ -159,6 +161,8 @@ set(OPENSPACE_HEADER
${OPENSPACE_BASE_DIR}/include/openspace/rendering/volumeraycaster.h
${OPENSPACE_BASE_DIR}/include/openspace/rendering/transferfunction.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/ephemeris.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/rotation.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/scale.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/scene.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/scenegraph.h
${OPENSPACE_BASE_DIR}/include/openspace/scene/scenegraphnode.h

View File

@@ -37,6 +37,9 @@
#include <ghoul/opengl/shaderobject.h>
#include <modules/base/ephemeris/staticephemeris.h>
#include <modules/base/rotation/staticrotation.h>
#include <modules/base/scale/staticscale.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/factorymanager.h>
@@ -46,10 +49,10 @@
namespace {
const std::string _loggerCat = "SceneGraphNode";
const std::string KeyRenderable = "Renderable";
const std::string KeyEphemeris = "Ephemeris";
const std::string keyRotation = "Rotation";
const std::string keyTranslation = "Transform.Translation";
const std::string keyTransformTranslation = "Transform.Translation";
const std::string keyTransformRotation = "Transform.Rotation";
const std::string keyTransformScale = "Transform.Scale";
}
namespace openspace {
@@ -90,49 +93,46 @@ SceneGraphNode* SceneGraphNode::createFromDictionary(const ghoul::Dictionary& di
LDEBUG("Successfully created renderable for '" << result->name() << "'");
}
if (dictionary.hasKey(KeyEphemeris)) {
ghoul::Dictionary ephemerisDictionary;
dictionary.getValue(KeyEphemeris, ephemerisDictionary);
delete result->_ephemeris;
result->_ephemeris = Ephemeris::createFromDictionary(ephemerisDictionary);
if (dictionary.hasKey(keyTransformTranslation)) {
ghoul::Dictionary translationDictionary;
dictionary.getValue(keyTransformTranslation, translationDictionary);
result->_ephemeris =
(Ephemeris::createFromDictionary(translationDictionary));
if (result->_ephemeris == nullptr) {
LERROR("Failed to create ephemeris for SceneGraphNode '"
<< result->name() << "'");
<< result->name() << "'");
delete result;
return nullptr;
}
//result->addPropertySubOwner(result->_ephemeris);
LDEBUG("Successfully created ephemeris for '" << result->name() << "'");
}
if (dictionary.hasKey(keyRotation)) {
bool rotationSuccess = true;
if (dictionary.hasKey(keyTransformRotation)) {
ghoul::Dictionary rotationDictionary;
rotationSuccess &= dictionary.getValue("Rotation.Source", result->_rotationSourceFrame);
rotationSuccess &= dictionary.getValue("Rotation.Destination", result->_rotationDestinationFrame);
if (!rotationSuccess) {
dictionary.getValue(keyTransformRotation, rotationDictionary);
result->_rotation =
(Rotation::createFromDictionary(rotationDictionary));
if (result->_rotation == nullptr) {
LERROR("Failed to create rotation for SceneGraphNode '"
<< result->name() << "'");
delete result;
return nullptr;
}
else
LDEBUG("Successfully created rotation for SceneGraphNode '" << result->name() << "'");
LDEBUG("Successfully created rotation for '" << result->name() << "'");
}
else {
LERROR("Rotation specification not found for SceneGraphNode '"
<< result->name() << "'. Using static rotation instead");
result->_rotationSourceFrame = "GALACTIC";
result->_rotationDestinationFrame = "GALACTIC";
}
if (dictionary.hasKey(keyTranslation)) {
dictionary.getValue(keyTranslation, result->_translation);
}
else {
result->_translation = glm::dvec3(0, 0, 0);
if (dictionary.hasKey(keyTransformScale)) {
ghoul::Dictionary scaleDictionary;
dictionary.getValue(keyTransformScale, scaleDictionary);
result->_scale =
(Scale::createFromDictionary(scaleDictionary));
if (result->_scale == nullptr) {
LERROR("Failed to create scale for SceneGraphNode '"
<< result->name() << "'");
delete result;
return nullptr;
}
LDEBUG("Successfully created scale for '" << result->name() << "'");
}
//std::string parentName;
@@ -157,9 +157,9 @@ SceneGraphNode* SceneGraphNode::createFromDictionary(const ghoul::Dictionary& di
SceneGraphNode::SceneGraphNode()
: _parent(nullptr)
, _ephemeris(new StaticEphemeris)
, _rotationMatrix(glm::dmat3(1.0))
, _translation(glm::dvec3(0.0))
, _ephemeris(new StaticEphemeris())
, _rotation(new StaticRotation())
, _scale(new StaticScale())
, _performanceRecord({0, 0, 0})
, _renderable(nullptr)
, _renderableVisible(false)
@@ -177,6 +177,10 @@ bool SceneGraphNode::initialize() {
if (_ephemeris)
_ephemeris->initialize();
if (_rotation)
_rotation->initialize();
if (_scale)
_scale->initialize();
return true;
}
@@ -190,8 +194,8 @@ bool SceneGraphNode::deinitialize() {
_renderable = nullptr;
}
delete _ephemeris;
_ephemeris = nullptr;
//delete _ephemeris;
//_ephemeris = nullptr;
// for (SceneGraphNode* child : _children) {
// child->deinitialize();
@@ -225,6 +229,36 @@ void SceneGraphNode::update(const UpdateData& data) {
_ephemeris->update(newUpdateData);
}
if (_rotation) {
if (data.doPerformanceMeasurement) {
glFinish();
auto start = std::chrono::high_resolution_clock::now();
_rotation->update(newUpdateData);
glFinish();
auto end = std::chrono::high_resolution_clock::now();
_performanceRecord.updateTimeEphemeris = (end - start).count();
}
else
_rotation->update(newUpdateData);
}
if (_scale) {
if (data.doPerformanceMeasurement) {
glFinish();
auto start = std::chrono::high_resolution_clock::now();
_scale->update(newUpdateData);
glFinish();
auto end = std::chrono::high_resolution_clock::now();
_performanceRecord.updateTimeEphemeris = (end - start).count();
}
else
_scale->update(newUpdateData);
}
if (_renderable && _renderable->isReady()) {
if (data.doPerformanceMeasurement) {
glFinish();
@@ -240,35 +274,12 @@ void SceneGraphNode::update(const UpdateData& data) {
_renderable->update(newUpdateData);
}
// TODO : Need to be checking for real if the frame is fixed since fixed frames
// do not have CK coverage.
bool sourceFrameIsFixed = _rotationSourceFrame == "GALACTIC";
bool destinationFrameIsFixed = _rotationDestinationFrame == "GALACTIC";
bool sourceHasCoverage =
!_rotationSourceFrame.empty() &&
SpiceManager::ref().hasFrameId(_rotationSourceFrame) &&
(SpiceManager::ref().hasCkCoverage(_rotationSourceFrame, data.time) ||
sourceFrameIsFixed);
bool destinationHasCoverage =
!_rotationDestinationFrame.empty() &&
SpiceManager::ref().hasFrameId(_rotationDestinationFrame) &&
(SpiceManager::ref().hasCkCoverage(_rotationDestinationFrame, data.time) ||
destinationFrameIsFixed);
if (sourceHasCoverage && destinationHasCoverage) {
_rotationMatrix = SpiceManager::ref().positionTransformMatrix(
_rotationSourceFrame,
_rotationDestinationFrame,
data.time);
}
else {
_rotationMatrix = glm::dmat3(1.0);
}
_worldRotationCached = calculateWorldRotation();
_worldScaleCached = calculateWorldScale();
// Assumes _worldRotationCached and _worldScaleCached have been calculated for parent
_worldPositionCached = calculateWorldPosition();
newUpdateData.position = worldPosition();
}
@@ -320,7 +331,8 @@ void SceneGraphNode::render(const RenderData& data, RendererTasks& tasks) {
thisPositionPSC,
data.doPerformanceMeasurement,
_worldPositionCached,
_worldRotationCached};
_worldRotationCached,
_worldScaleCached};
_performanceRecord.renderTime = 0;
if (_renderableVisible && _renderable->isVisible() && _renderable->isReady() && _renderable->isEnabled()) {
@@ -388,8 +400,17 @@ void SceneGraphNode::addChild(SceneGraphNode* child) {
glm::dvec3 SceneGraphNode::position() const
{
glm::dvec3 translationRotated = _parent->rotationMatrix() * _translation;
return _ephemeris->position() + translationRotated;
return _ephemeris->position();
}
const glm::dmat3& SceneGraphNode::rotationMatrix() const
{
return _rotation->matrix();
}
double SceneGraphNode::scale() const
{
return _scale->scaleValue();
}
glm::dvec3 SceneGraphNode::worldPosition() const
@@ -397,34 +418,47 @@ glm::dvec3 SceneGraphNode::worldPosition() const
return _worldPositionCached;
}
const glm::dmat3& SceneGraphNode::rotationMatrix() const
{
return _rotationMatrix;
}
const glm::dmat3& SceneGraphNode::worldRotationMatrix() const
{
return _worldRotationCached;
}
double SceneGraphNode::worldScale() const
{
return _worldScaleCached;
}
glm::dvec3 SceneGraphNode::calculateWorldPosition() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
glm::dvec3 translationRotated = _parent->rotationMatrix() * _translation;
return _ephemeris->position() + translationRotated + _parent->calculateWorldPosition();
return
_parent->calculateWorldPosition() +
_parent->worldRotationMatrix() *
_parent->worldScale() *
position();
}
else {
return _ephemeris->position();
return position();
}
}
glm::dmat3 SceneGraphNode::calculateWorldRotation() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
return _parent->calculateWorldRotation() * _rotationMatrix;
return rotationMatrix() * _parent->calculateWorldRotation();
}
else {
return _rotationMatrix;
return rotationMatrix();
}
}
double SceneGraphNode::calculateWorldScale() const {
// recursive up the hierarchy if there are parents available
if (_parent) {
return _parent->calculateWorldScale() * scale();
}
else {
return scale();
}
}