From 4e53b17f8dc2c70eb8f8935d3140f3df155a6538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 7 Nov 2012 01:45:14 +0100 Subject: [PATCH] SceneGraph rework, part 5: bulk transformation computing. --- src/SceneGraph/AbstractObject.h | 11 +++ src/SceneGraph/Object.h | 22 ++++- src/SceneGraph/Object.hpp | 124 +++++++++++++++++++++++++++++ src/SceneGraph/Test/ObjectTest.cpp | 69 ++++++++++++++++ src/SceneGraph/Test/ObjectTest.h | 1 + 5 files changed, 225 insertions(+), 2 deletions(-) diff --git a/src/SceneGraph/AbstractObject.h b/src/SceneGraph/AbstractObject.h index 662a56c07..286e56f2b 100644 --- a/src/SceneGraph/AbstractObject.h +++ b/src/SceneGraph/AbstractObject.h @@ -101,6 +101,17 @@ template class AbstractObject */ virtual typename DimensionTraits::MatrixType absoluteTransformationMatrix() const = 0; + /** + * @brief Transformation matrices of given set of objects relative to this object + * + * All transformations are premultiplied with @p initialTransformationMatrix, + * if specified. + * @warning This function cannot check if all objects are of the same + * Object type, use typesafe Object::transformations() when + * possible. + */ + virtual std::vector::MatrixType> transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix = typename DimensionTraits::MatrixType()) const = 0; + /*@}*/ /** diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index 587f2f07d..94ac66e63 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -33,7 +33,9 @@ template class Scene; #ifndef DOXYGEN_GENERATING_OUTPUT namespace Implementation { enum class ObjectFlag: std::uint8_t { - Dirty = 1 << 0 + Dirty = 1 << 0, + Visited = 1 << 1, + Joint = 1 << 2 }; typedef Corrade::Containers::EnumSet ObjectFlags; @@ -80,7 +82,7 @@ template class Object: public AbstractObject* parent = nullptr): flags(Flag::Dirty) { + inline Object(Object* parent = nullptr): counter(0xFFFFu), flags(Flag::Dirty) { setParent(parent); } @@ -186,6 +188,17 @@ template class Object: public AbstractObject transformations(std::vector*> objects, const typename Transformation::DataType& initialTransformation = typename Transformation::DataType()) const; + /*@}*/ inline bool isDirty() const override { return !!(flags & Flag::Dirty); } @@ -196,8 +209,13 @@ template class Object: public AbstractObject* sceneObject() override; const Object* sceneObject() const override; + std::vector::MatrixType> transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix = typename DimensionTraits::MatrixType()) const override; + + typename Transformation::DataType computeJointTransformation(const std::vector*>& jointObjects, std::vector& jointTransformations, const std::size_t joint, const typename Transformation::DataType& initialTransformation) const; + typedef Implementation::ObjectFlag Flag; typedef Implementation::ObjectFlags Flags; + std::uint16_t counter; Flags flags; }; diff --git a/src/SceneGraph/Object.hpp b/src/SceneGraph/Object.hpp index 092cc9c70..dec18a843 100644 --- a/src/SceneGraph/Object.hpp +++ b/src/SceneGraph/Object.hpp @@ -162,6 +162,130 @@ template void Object::setClean() { } } +template std::vector::MatrixType> Object::transformationMatrices(const std::vector*>& objects, const typename DimensionTraits::MatrixType& initialTransformationMatrix) const { + std::vector*> castObjects(objects.size()); + for(std::size_t i = 0; i != objects.size(); ++i) + /** @todo Ensure this doesn't crash, somehow */ + castObjects[i] = static_cast*>(objects[i]); + + std::vector transformations = this->transformations(std::move(castObjects), Transformation::fromMatrix(initialTransformationMatrix)); + std::vector::MatrixType> transformationMatrices(transformations.size()); + for(std::size_t i = 0; i != objects.size(); ++i) + transformationMatrices[i] = Transformation::toMatrix(transformations[i]); + + return transformationMatrices; +} + +template std::vector Object::transformations(std::vector*> objects, const typename Transformation::DataType& initialTransformation) const { + /* Remember object count for later */ + std::size_t objectCount = objects.size(); + + /* Create initial list of joints from original objects */ + std::vector*> jointObjects(objects.size()); + for(std::size_t i = 0; i != jointObjects.size(); ++i) { + jointObjects[i] = static_cast*>(objects[i]); + CORRADE_INTERNAL_ASSERT(jointObjects[i]->counter == 0xFFFFu); + jointObjects[i]->counter = i; + jointObjects[i]->flags |= Flag::Joint; + } + + /* Scene object */ + const Scene* scene = this->scene(); + + /* Nearest common ancestor not yet implemented - assert this is done on scene */ + CORRADE_ASSERT(scene == this, "SceneGraph::Object::transformationMatrices(): currently implemented only for Scene", {}); + + /* Mark all objects up the hierarchy as visited */ + auto it = objects.begin(); + while(!objects.empty()) { + /* Mark the object as visited */ + CORRADE_INTERNAL_ASSERT(!((*it)->flags & Flag::Visited)); + (*it)->flags |= Flag::Visited; + + Object* parent = (*it)->parent(); + + /* If this is root object, remove from list */ + if(!parent) { + CORRADE_ASSERT(*it == scene, "SceneGraph::Object::transformations(): the objects are not part of the same tree", {}); + it = objects.erase(it); + + /* Parent is an joint or already visited - remove current from list */ + } else if(parent->flags & (Flag::Visited|Flag::Joint)) { + it = objects.erase(it); + + /* If not already marked as joint, mark it as such and add it to + list of joint objects */ + if(!(parent->flags & Flag::Joint)) { + CORRADE_INTERNAL_ASSERT(parent->counter == 0xFFFFu); + parent->counter = jointObjects.size(); + parent->flags |= Flag::Joint; + jointObjects.push_back(parent); + } + + /* Else go up the hierarchy */ + } else *it = parent; + + /* Cycle if reached end */ + if(it == objects.end()) it = objects.begin(); + } + + CORRADE_ASSERT(objects.size() < 0xFFFFu, "SceneGraph::Object::transformations(): too large scene", {}); + + /* Array of absolute transformations in joints */ + std::vector jointTransformations(jointObjects.size()); + + /* Compute transformations for all joints */ + for(std::size_t i = 0; i != jointTransformations.size(); ++i) + computeJointTransformation(jointObjects, jointTransformations, i, initialTransformation); + + /* All visited marks are now cleaned, clean joint marks and counters */ + for(auto i: jointObjects) { + CORRADE_INTERNAL_ASSERT(i->flags & Flag::Joint); + i->flags &= ~Flag::Joint; + i->counter = 0xFFFFu; + } + + /* Shrink the array to contain only transformations of requested objects and return */ + jointTransformations.resize(objectCount); + return jointTransformations; +} + +template typename Transformation::DataType Object::computeJointTransformation(const std::vector*>& jointObjects, std::vector& jointTransformations, const std::size_t joint, const typename Transformation::DataType& initialTransformation) const { + Object* o = jointObjects[joint]; + + /* Transformation already computed ("unvisited" by this function before), done */ + if(!(o->flags & Flag::Visited)) return jointTransformations[joint]; + + /* Initialize transformation */ + jointTransformations[joint] = o->transformation(); + + /* Go up until next joint or root */ + for(;;) { + /* Clean visited mark */ + CORRADE_INTERNAL_ASSERT(o->flags & Flag::Visited); + o->flags &= ~Flag::Visited; + + Object* parent = o->parent(); + + /* Root object, compose transformation with initial, done */ + if(!parent) { + CORRADE_INTERNAL_ASSERT(o->isScene()); + return (jointTransformations[joint] = + Transformation::compose(initialTransformation, jointTransformations[joint])); + + /* Joint object, compose transformation with the joint, done */ + } else if(parent->flags & Flag::Joint) { + return (jointTransformations[joint] = + Transformation::compose(computeJointTransformation(jointObjects, jointTransformations, parent->counter, initialTransformation), jointTransformations[joint])); + + /* Else compose transformation with parent, go up the hierarchy */ + } else { + jointTransformations[joint] = Transformation::compose(parent->transformation(), jointTransformations[joint]); + o = parent; + } + } +} + }} #endif diff --git a/src/SceneGraph/Test/ObjectTest.cpp b/src/SceneGraph/Test/ObjectTest.cpp index 80f8a2236..bba87ffe8 100644 --- a/src/SceneGraph/Test/ObjectTest.cpp +++ b/src/SceneGraph/Test/ObjectTest.cpp @@ -34,6 +34,7 @@ ObjectTest::ObjectTest() { addTests(&ObjectTest::parenting, &ObjectTest::scene, &ObjectTest::absoluteTransformation, + &ObjectTest::transformations, &ObjectTest::caching); } @@ -99,6 +100,74 @@ void ObjectTest::absoluteTransformation() { CORRADE_COMPARE(o3.absoluteTransformation(), Matrix4::translation({1.0f, 2.0f, 3.0f})); } +void ObjectTest::transformations() { + Scene3D s; + + Matrix4 initial = Matrix4::rotationX(deg(90.0f)).inverted(); + + /* Scene alone */ + CORRADE_COMPARE(s.transformations({&s}, initial), vector{initial}); + + /* One object */ + Object3D first(&s); + first.rotateZ(deg(30.0f)); + Object3D second(&first); + second.scale(Vector3(0.5f)); + CORRADE_COMPARE(s.transformations({&second}, initial), vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)) + }); + + /* One object and scene */ + CORRADE_COMPARE(s.transformations({&second, &s}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial + })); + + /* Two objects with foreign joint */ + Object3D third(&first); + third.translate(Vector3::xAxis(5.0f)); + CORRADE_COMPARE(s.transformations({&second, &third}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::translation(Vector3::xAxis(5.0f)), + })); + + /* Three objects with joint as one of them */ + CORRADE_COMPARE(s.transformations({&second, &third, &first}, initial), (vector{ + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::scaling(Vector3(0.5f)), + initial*Matrix4::rotationZ(deg(30.0f))*Matrix4::translation(Vector3::xAxis(5.0f)), + initial*Matrix4::rotationZ(deg(30.0f)), + })); + + { + CORRADE_EXPECT_FAIL("Transformations not relative to scene are not yet implemented."); + + /* Transformation relative to another object */ + CORRADE_COMPARE(second.transformations({&third}), vector{ + Matrix4::scaling(Vector3(0.5f)).inverted()*Matrix4::translation(Vector3::xAxis(5.0f)) + }); + + /* Transformation relative to another object, not part of any scene (but should work) */ + Object3D orphanParent1; + orphanParent1.rotate(deg(31.0f), Vector3(1.0f).normalized()); + Object3D orphanParent(&orphanParent1); + Object3D orphan1(&orphanParent); + orphan1.scale(Vector3::xScale(3.0f)); + Object3D orphan2(&orphanParent); + orphan2.translate(Vector3::zAxis(5.0f)); + CORRADE_COMPARE(orphan1.transformations({&orphan2}), vector{ + Matrix4::scaling(Vector3::xScale(3.0f)).inverted()*Matrix4::translation(Vector3::zAxis(5.0f)) + }); + } + + ostringstream o; + Error::setOutput(&o); + + /* Transformation of objects not part of the same scene */ + Object3D orphan; + CORRADE_COMPARE(s.transformations({&orphan}), vector()); + CORRADE_COMPARE(o.str(), "SceneGraph::Object::transformations(): the objects are not part of the same tree\n"); +} + void ObjectTest::caching() { Scene3D scene; diff --git a/src/SceneGraph/Test/ObjectTest.h b/src/SceneGraph/Test/ObjectTest.h index b0294ca89..f0e44b5c7 100644 --- a/src/SceneGraph/Test/ObjectTest.h +++ b/src/SceneGraph/Test/ObjectTest.h @@ -26,6 +26,7 @@ class ObjectTest: public Corrade::TestSuite::Tester { void parenting(); void scene(); void absoluteTransformation(); + void transformations(); void caching(); };