From b349eaf753514bccd670834cd95cb16fd6b04d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 16 Mar 2013 18:06:59 +0100 Subject: [PATCH] SceneGraph: new class DualQuaternionTransformation. Uses dual quaternions for 3D transformation. --- src/SceneGraph/CMakeLists.txt | 2 + .../DualQuaternionTransformation.cpp | 35 ++++ src/SceneGraph/DualQuaternionTransformation.h | 178 ++++++++++++++++ src/SceneGraph/Object.h | 1 + src/SceneGraph/SceneGraph.h | 1 + src/SceneGraph/Test/CMakeLists.txt | 2 + .../Test/DualQuaternionTransformationTest.cpp | 197 ++++++++++++++++++ 7 files changed, 416 insertions(+) create mode 100644 src/SceneGraph/DualQuaternionTransformation.cpp create mode 100644 src/SceneGraph/DualQuaternionTransformation.h create mode 100644 src/SceneGraph/Test/DualQuaternionTransformationTest.cpp diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index 7f69e96bd..41bec81e3 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -27,6 +27,7 @@ set(MagnumSceneGraph_SRCS Animable.cpp Camera.cpp DualComplexTransformation.cpp + DualQuaternionTransformation.cpp RigidMatrixTransformation2D.cpp RigidMatrixTransformation3D.cpp Object.cpp) @@ -56,6 +57,7 @@ set(MagnumSceneGraph_HEADERS Camera3D.hpp Drawable.h DualComplexTransformation.h + DualQuaternionTransformation.h RigidMatrixTransformation2D.h RigidMatrixTransformation3D.h FeatureGroup.h diff --git a/src/SceneGraph/DualQuaternionTransformation.cpp b/src/SceneGraph/DualQuaternionTransformation.cpp new file mode 100644 index 000000000..bedf48480 --- /dev/null +++ b/src/SceneGraph/DualQuaternionTransformation.cpp @@ -0,0 +1,35 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + 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 "DualQuaternionTransformation.h" + +#include "Object.hpp" + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class MAGNUM_SCENEGRAPH_EXPORT Object>; +#endif + +}} diff --git a/src/SceneGraph/DualQuaternionTransformation.h b/src/SceneGraph/DualQuaternionTransformation.h new file mode 100644 index 000000000..4245749c6 --- /dev/null +++ b/src/SceneGraph/DualQuaternionTransformation.h @@ -0,0 +1,178 @@ +#ifndef Magnum_SceneGraph_DualQuaternionTransformation_h +#define Magnum_SceneGraph_DualQuaternionTransformation_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + 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. +*/ + +/** @file + * @brief Class Magnum::SceneGraph::DualQuaternionTransformation + */ + +#include "Math/DualQuaternion.h" +#include "AbstractTranslationRotation3D.h" +#include "Object.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Three-dimensional transformation implemented using dual quaternions + +This class allows only rigid transformation (i.e. only rotation and +translation). +@see @ref scenegraph, Math::DualQuaternion, DualComplexTransformation +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +template +#else +template +#endif +class DualQuaternionTransformation: public AbstractTranslationRotation3D { + public: + /** @brief Underlying transformation type */ + typedef Math::DualQuaternion DataType; + + #ifndef DOXYGEN_GENERATING_OUTPUT + inline static Math::DualQuaternion fromMatrix(const Math::Matrix4& matrix) { + CORRADE_ASSERT(matrix.isRigidTransformation(), + "SceneGraph::DualQuaternionTransformation::fromMatrix(): the matrix doesn't represent rigid transformation", {}); + return Math::DualQuaternion::fromMatrix(matrix); + } + + inline constexpr static Math::Matrix4 toMatrix(const Math::DualQuaternion& transformation) { + return transformation.toMatrix(); + } + + inline static Math::DualQuaternion compose(const Math::DualQuaternion& parent, const Math::DualQuaternion& child) { + return parent*child; + } + + inline static Math::DualQuaternion inverted(const Math::DualQuaternion& transformation) { + return transformation.invertedNormalized(); + } + + inline Math::DualQuaternion transformation() const { + return _transformation; + } + #endif + + /** + * @brief Normalize rotation part + * @return Pointer to self (for method chaining) + * + * Normalizes the rotation part to prevent rounding errors when rotating + * the object subsequently. + * @see DualQuaternion::normalized() + */ + DualQuaternionTransformation* normalizeRotation() { + setTransformation(_transformation.normalized()); + return this; + } + + /** + * @brief Set transformation + * @return Pointer to self (for method chaining) + * + * Expects that the dual quaternion is normalized. + * @see DualQuaternion::isNormalized() + */ + DualQuaternionTransformation* setTransformation(const Math::DualQuaternion& transformation) { + CORRADE_ASSERT(transformation.isNormalized(), + "SceneGraph::DualQuaternionTransformation::setTransformation(): the dual quaternion is not normalized", this); + setTransformationInternal(transformation); + return this; + } + + inline DualQuaternionTransformation* resetTransformation() override { + setTransformation({}); + return this; + } + + /** + * @brief Multiply transformation + * @param transformation Transformation + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Expects that the dual quaternion is normalized. + * @see DualQuaternion::isNormalized() + */ + inline DualQuaternionTransformation* transform(const Math::DualQuaternion& transformation, TransformationType type = TransformationType::Global) { + CORRADE_ASSERT(transformation.isNormalized(), + "SceneGraph::DualQuaternionTransformation::transform(): the dual quaternion is not normalized", this); + transformInternal(transformation, type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling3D::translate() + * Same as calling transform() with DualQuaternion::translation(). + */ + inline DualQuaternionTransformation* translate(const Math::Vector3& vector, TransformationType type = TransformationType::Global) override { + transformInternal(Math::DualQuaternion::translation(vector), type); + return this; + } + + /** + * @brief Rotate object + * @param angle Angle (counterclockwise) + * @param normalizedAxis Normalized rotation axis + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Same as calling transform() with DualQuaternion::rotation(). + * @see Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis(), + * normalizeRotation() + */ + inline DualQuaternionTransformation* rotate(Math::Rad angle, const Math::Vector3& normalizedAxis, TransformationType type = TransformationType::Global) override { + transformInternal(Math::DualQuaternion::rotation(angle, normalizedAxis), type); + return this; + } + + protected: + /* Allow construction only from Object */ + inline explicit DualQuaternionTransformation() = default; + + private: + /* No assertions fired, for internal use */ + inline void setTransformationInternal(const Math::DualQuaternion& transformation) { + /* Setting transformation is forbidden for the scene */ + /** @todo Assert for this? */ + /** @todo Do this in some common code so we don't need to include Object? */ + if(!static_cast>*>(this)->isScene()) { + _transformation = transformation; + static_cast>*>(this)->setDirty(); + } + } + + /* No assertions fired, for internal use */ + inline void transformInternal(const Math::DualQuaternion& transformation, TransformationType type) { + setTransformation(type == TransformationType::Global ? + transformation*_transformation : _transformation*transformation); + } + + Math::DualQuaternion _transformation; +}; + +}} + +#endif diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index a712837b2..07bfc61b1 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -82,6 +82,7 @@ class) you have to use Object.hpp implementation file to avoid linker errors. See @ref compilation-speedup-hpp for more information. - @ref DualComplexTransformation "Object>" + - @ref DualQuaternionTransformation "Object>" - @ref MatrixTransformation2D "Object>" - @ref MatrixTransformation3D "Object>" - @ref RigidMatrixTransformation2D "Object>" diff --git a/src/SceneGraph/SceneGraph.h b/src/SceneGraph/SceneGraph.h index c316c2aa5..8e3ccb63e 100644 --- a/src/SceneGraph/SceneGraph.h +++ b/src/SceneGraph/SceneGraph.h @@ -99,6 +99,7 @@ template using Drawable3D = Drawable<3, T>; #endif template class DualComplexTransformation; +template class DualQuaternionTransformation; template class FeatureGroup; #ifndef CORRADE_GCC46_COMPATIBILITY diff --git a/src/SceneGraph/Test/CMakeLists.txt b/src/SceneGraph/Test/CMakeLists.txt index b6e8ba3e4..83101f39e 100644 --- a/src/SceneGraph/Test/CMakeLists.txt +++ b/src/SceneGraph/Test/CMakeLists.txt @@ -25,6 +25,7 @@ corrade_add_test(SceneGraphAnimableTest AnimableTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphCameraTest CameraTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphDualComplexTransforma___Test DualComplexTransformationTest.cpp LIBRARIES MagnumSceneGraph) +corrade_add_test(SceneGraphDualQuaternionTransfo___Test DualQuaternionTransformationTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphMatrixTransformation2DTest MatrixTransformation2DTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphMatrixTransformation3DTest MatrixTransformation3DTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphObjectTest ObjectTest.cpp LIBRARIES MagnumSceneGraphTestLib) @@ -33,6 +34,7 @@ corrade_add_test(SceneGraphRigidMatrixTransfor___3DTest RigidMatrixTransformatio corrade_add_test(SceneGraphSceneTest SceneTest.cpp LIBRARIES MagnumSceneGraph) set_target_properties(SceneGraphDualComplexTransforma___Test + SceneGraphDualQuaternionTransfo___Test SceneGraphRigidMatrixTransfor___2DTest SceneGraphRigidMatrixTransfor___3DTest PROPERTIES COMPILE_FLAGS "-DCORRADE_GRACEFUL_ASSERT") diff --git a/src/SceneGraph/Test/DualQuaternionTransformationTest.cpp b/src/SceneGraph/Test/DualQuaternionTransformationTest.cpp new file mode 100644 index 000000000..6de60f080 --- /dev/null +++ b/src/SceneGraph/Test/DualQuaternionTransformationTest.cpp @@ -0,0 +1,197 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš + + 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 +#include + +#include "SceneGraph/DualQuaternionTransformation.h" +#include "SceneGraph/Scene.h" + +namespace Magnum { namespace SceneGraph { namespace Test { + +typedef Object> Object3D; +typedef Scene> Scene3D; + +class DualQuaternionTransformationTest: public Corrade::TestSuite::Tester { + public: + explicit DualQuaternionTransformationTest(); + + void fromMatrix(); + void toMatrix(); + void compose(); + void inverted(); + + void setTransformation(); + void resetTransformation(); + void transform(); + void translate(); + void rotate(); + void normalizeRotation(); +}; + +DualQuaternionTransformationTest::DualQuaternionTransformationTest() { + addTests({&DualQuaternionTransformationTest::fromMatrix, + &DualQuaternionTransformationTest::toMatrix, + &DualQuaternionTransformationTest::compose, + &DualQuaternionTransformationTest::inverted, + + &DualQuaternionTransformationTest::setTransformation, + &DualQuaternionTransformationTest::resetTransformation, + &DualQuaternionTransformationTest::transform, + &DualQuaternionTransformationTest::translate, + &DualQuaternionTransformationTest::rotate, + &DualQuaternionTransformationTest::normalizeRotation}); +} + +void DualQuaternionTransformationTest::fromMatrix() { + Matrix4 m = Matrix4::rotationX(Deg(17.0f))*Matrix4::translation({1.0f, -0.3f, 2.3f}); + DualQuaternion q = DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())*DualQuaternion::translation({1.0f, -0.3f, 2.3f}); + CORRADE_COMPARE(DualQuaternionTransformation<>::fromMatrix(m), q); +} + +void DualQuaternionTransformationTest::toMatrix() { + DualQuaternion q = DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())*DualQuaternion::translation({1.0f, -0.3f, 2.3f}); + Matrix4 m = Matrix4::rotationX(Deg(17.0f))*Matrix4::translation({1.0f, -0.3f, 2.3f}); + CORRADE_COMPARE(DualQuaternionTransformation<>::toMatrix(q), m); +} + +void DualQuaternionTransformationTest::compose() { + DualQuaternion parent = DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis()); + DualQuaternion child = DualQuaternion::translation({1.0f, -0.3f, 2.3f}); + CORRADE_COMPARE(DualQuaternionTransformation<>::compose(parent, child), parent*child); +} + +void DualQuaternionTransformationTest::inverted() { + DualQuaternion q = DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())*DualQuaternion::translation({1.0f, -0.3f, 2.3f}); + CORRADE_COMPARE(DualQuaternionTransformation<>::inverted(q)*q, DualQuaternion()); +} + +void DualQuaternionTransformationTest::setTransformation() { + Object3D o; + + /* Can't transform with non-rigid transformation */ + std::ostringstream out; + Error::setOutput(&out); + o.setTransformation(DualQuaternion({{1.0f, 2.0f, 3.0f}, 4.0f}, {})); + CORRADE_COMPARE(out.str(), "SceneGraph::DualQuaternionTransformation::setTransformation(): the dual quaternion is not normalized\n"); + + /* Dirty after setting transformation */ + o.setClean(); + CORRADE_VERIFY(!o.isDirty()); + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + CORRADE_VERIFY(o.isDirty()); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::rotationX(Deg(17.0f))); + + /* Scene cannot be transformed */ + Scene3D s; + s.setClean(); + CORRADE_VERIFY(!s.isDirty()); + s.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + CORRADE_VERIFY(!s.isDirty()); + CORRADE_COMPARE(s.transformationMatrix(), Matrix4()); +} + +void DualQuaternionTransformationTest::resetTransformation() { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + CORRADE_VERIFY(o.transformationMatrix() != Matrix4()); + o.resetTransformation(); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4()); +} + +void DualQuaternionTransformationTest::transform() { + { + /* Can't transform with non-rigid transformation */ + Object3D o; + std::ostringstream out; + Error::setOutput(&out); + o.transform(DualQuaternion({{1.0f, 2.0f, 3.0f}, 4.0f}, {})); + CORRADE_COMPARE(out.str(), "SceneGraph::DualQuaternionTransformation::transform(): the dual quaternion is not normalized\n"); + } { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + o.transform(DualQuaternion::translation({1.0f, -0.3f, 2.3f})); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::translation({1.0f, -0.3f, 2.3f})*Matrix4::rotationX(Deg(17.0f))); + } { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + o.transform(DualQuaternion::translation({1.0f, -0.3f, 2.3f}), TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::rotationX(Deg(17.0f))*Matrix4::translation({1.0f, -0.3f, 2.3f})); + } +} + +void DualQuaternionTransformationTest::translate() { + { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + o.translate({1.0f, -0.3f, 2.3f}); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::translation({1.0f, -0.3f, 2.3f})*Matrix4::rotationX(Deg(17.0f))); + } { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + o.translate({1.0f, -0.3f, 2.3f}, TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::rotationX(Deg(17.0f))*Matrix4::translation({1.0f, -0.3f, 2.3f})); + } +} + +void DualQuaternionTransformationTest::rotate() { + { + Object3D o; + o.transform(DualQuaternion::translation({1.0f, -0.3f, 2.3f})); + o.rotateX(Deg(17.0f)) + ->rotateY(Deg(25.0f)) + ->rotateZ(Deg(-23.0f)) + ->rotate(Deg(96.0f), Vector3(1.0f/Constants::sqrt3())); + CORRADE_COMPARE(o.transformationMatrix(), + Matrix4::rotation(Deg(96.0f), Vector3(1.0f/Constants::sqrt3()))* + Matrix4::rotationZ(Deg(-23.0f))* + Matrix4::rotationY(Deg(25.0f))* + Matrix4::rotationX(Deg(17.0f))* + Matrix4::translation({1.0f, -0.3f, 2.3f})); + } { + Object3D o; + o.transform(DualQuaternion::translation({1.0f, -0.3f, 2.3f})); + o.rotateX(Deg(17.0f), TransformationType::Local) + ->rotateY(Deg(25.0f), TransformationType::Local) + ->rotateZ(Deg(-23.0f), TransformationType::Local) + ->rotate(Deg(96.0f), Vector3(1.0f/Constants::sqrt3()), TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), + Matrix4::translation({1.0f, -0.3f, 2.3f})* + Matrix4::rotationX(Deg(17.0f))* + Matrix4::rotationY(Deg(25.0f))* + Matrix4::rotationZ(Deg(-23.0f))* + Matrix4::rotation(Deg(96.0f), Vector3(1.0f/Constants::sqrt3()))); + } +} + +void DualQuaternionTransformationTest::normalizeRotation() { + Object3D o; + o.setTransformation(DualQuaternion::rotation(Deg(17.0f), Vector3::xAxis())); + o.normalizeRotation(); + CORRADE_COMPARE(o.transformationMatrix(), Matrix4::rotationX(Deg(17.0f))); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::DualQuaternionTransformationTest)