From 85abf5767eca308fe81fbee39a789ed0de6a10d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 16 Mar 2013 18:05:06 +0100 Subject: [PATCH] SceneGraph: new class DualComplexTransformation. Uses dual complex numbers for 2D transformation. --- src/SceneGraph/CMakeLists.txt | 2 + src/SceneGraph/DualComplexTransformation.cpp | 35 ++++ src/SceneGraph/DualComplexTransformation.h | 185 ++++++++++++++++++ src/SceneGraph/Object.h | 1 + src/SceneGraph/SceneGraph.h | 2 + src/SceneGraph/Test/CMakeLists.txt | 4 +- .../Test/DualComplexTransformationTest.cpp | 180 +++++++++++++++++ 7 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 src/SceneGraph/DualComplexTransformation.cpp create mode 100644 src/SceneGraph/DualComplexTransformation.h create mode 100644 src/SceneGraph/Test/DualComplexTransformationTest.cpp diff --git a/src/SceneGraph/CMakeLists.txt b/src/SceneGraph/CMakeLists.txt index 6ad4f53e4..7f69e96bd 100644 --- a/src/SceneGraph/CMakeLists.txt +++ b/src/SceneGraph/CMakeLists.txt @@ -26,6 +26,7 @@ set(MagnumSceneGraph_SRCS Animable.cpp Camera.cpp + DualComplexTransformation.cpp RigidMatrixTransformation2D.cpp RigidMatrixTransformation3D.cpp Object.cpp) @@ -54,6 +55,7 @@ set(MagnumSceneGraph_HEADERS Camera3D.h Camera3D.hpp Drawable.h + DualComplexTransformation.h RigidMatrixTransformation2D.h RigidMatrixTransformation3D.h FeatureGroup.h diff --git a/src/SceneGraph/DualComplexTransformation.cpp b/src/SceneGraph/DualComplexTransformation.cpp new file mode 100644 index 000000000..56a1f0af5 --- /dev/null +++ b/src/SceneGraph/DualComplexTransformation.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 "DualComplexTransformation.h" + +#include "Object.hpp" + +namespace Magnum { namespace SceneGraph { + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class MAGNUM_SCENEGRAPH_EXPORT Object>; +#endif + +}} diff --git a/src/SceneGraph/DualComplexTransformation.h b/src/SceneGraph/DualComplexTransformation.h new file mode 100644 index 000000000..8f96126cb --- /dev/null +++ b/src/SceneGraph/DualComplexTransformation.h @@ -0,0 +1,185 @@ +#ifndef Magnum_SceneGraph_DualComplexTransformation_h +#define Magnum_SceneGraph_DualComplexTransformation_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::DualComplexTransformation + */ + +#include "Math/DualComplex.h" +#include "AbstractTranslationRotation2D.h" +#include "Object.h" + +namespace Magnum { namespace SceneGraph { + +/** +@brief Two-dimensional transformation implemented using dual complex numbers + +This class allows only rigid transformation (i.e. only rotation and +translation). +@see @ref scenegraph, Math::DualComplex, DualQuaternionTransformation +*/ +#ifndef DOXYGEN_GENERATING_OUTPUT +template +#else +template +#endif +class DualComplexTransformation: public AbstractTranslationRotation2D { + public: + /** @brief Transformation type */ + typedef Math::DualComplex DataType; + + #ifndef DOXYGEN_GENERATING_OUTPUT + inline static Math::DualComplex fromMatrix(const Math::Matrix3& matrix) { + return Math::DualComplex::fromMatrix(matrix); + } + + inline constexpr static Math::Matrix3 toMatrix(const Math::DualComplex& transformation) { + return transformation.toMatrix(); + } + + inline static Math::DualComplex compose(const Math::DualComplex& parent, const Math::DualComplex& child) { + return parent*child; + } + + inline static Math::DualComplex inverted(const Math::DualComplex& transformation) { + return transformation.invertedNormalized(); + } + + inline Math::DualComplex 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 DualComplex::normalized() + */ + DualComplexTransformation* normalizeRotation() { + setTransformationInternal(_transformation.normalized()); + return this; + } + + /** + * @brief Set transformation + * @return Pointer to self (for method chaining) + * + * Expects that the dual complex number is normalized. + * @see DualComplex::isNormalized() + */ + DualComplexTransformation* setTransformation(const Math::DualComplex& transformation) { + CORRADE_ASSERT(transformation.isNormalized(), + "SceneGraph::DualComplexTransformation::setTransformation(): the dual complex number is not normalized", this); + setTransformationInternal(transformation); + return this; + } + + inline DualComplexTransformation* resetTransformation() override { + setTransformationInternal({}); + return this; + } + + /** + * @brief Transform object + * @param transformation Transformation + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Expects that the dual complex number is normalized. + * @see DualComplex::isNormalized() + */ + inline DualComplexTransformation* transform(const Math::DualComplex& transformation, TransformationType type = TransformationType::Global) { + CORRADE_ASSERT(transformation.isNormalized(), + "SceneGraph::DualComplexTransformation::transform(): the dual complex number is not normalized", this); + transformInternal(transformation, type); + return this; + } + + /** + * @copydoc AbstractTranslationRotationScaling2D::translate() + * Same as calling transform() with DualComplex::translation(). + */ + inline DualComplexTransformation* translate(const Math::Vector2& vector, TransformationType type = TransformationType::Global) override { + transformInternal(Math::DualComplex::translation(vector), type); + return this; + } + + /** + * @brief Rotate object + * @param angle Angle (counterclockwise) + * @param type Transformation type + * @return Pointer to self (for method chaining) + * + * Same as calling transform() with DualComplex::rotation(). + * @see normalizeRotation() + */ + inline DualComplexTransformation* rotate(Math::Rad angle, TransformationType type = TransformationType::Global) override { + transformInternal(Math::DualComplex::rotation(angle), type); + return this; + } + + /** + * @brief Move object in stacking order + * @param under Sibling object under which to move or `nullptr`, + * if you want to move it above all. + * @return Pointer to self (for method chaining) + */ + inline DualComplexTransformation* move(Object>* under) { + static_cast*>(this)->Corrade::Containers::template LinkedList>>::move(this, under); + return this; + } + + protected: + /* Allow construction only from Object */ + inline explicit DualComplexTransformation() = default; + + private: + /* No assertions fired, for internal use */ + inline void setTransformationInternal(const Math::DualComplex& 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::DualComplex& transformation, TransformationType type) { + setTransformation(type == TransformationType::Global ? + transformation*_transformation : _transformation*transformation); + } + + Math::DualComplex _transformation; +}; + +}} + +#endif diff --git a/src/SceneGraph/Object.h b/src/SceneGraph/Object.h index 5216fab3c..a712837b2 100644 --- a/src/SceneGraph/Object.h +++ b/src/SceneGraph/Object.h @@ -81,6 +81,7 @@ For other specializations (e.g. using Double type or special transformation 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 MatrixTransformation2D "Object>" - @ref MatrixTransformation3D "Object>" - @ref RigidMatrixTransformation2D "Object>" diff --git a/src/SceneGraph/SceneGraph.h b/src/SceneGraph/SceneGraph.h index 61563633f..c316c2aa5 100644 --- a/src/SceneGraph/SceneGraph.h +++ b/src/SceneGraph/SceneGraph.h @@ -98,6 +98,8 @@ template using Drawable2D = Drawable<2, T>; template using Drawable3D = Drawable<3, T>; #endif +template class DualComplexTransformation; + template class FeatureGroup; #ifndef CORRADE_GCC46_COMPATIBILITY template using FeatureGroup2D = FeatureGroup<2, Feature, T>; diff --git a/src/SceneGraph/Test/CMakeLists.txt b/src/SceneGraph/Test/CMakeLists.txt index 8839941a1..b6e8ba3e4 100644 --- a/src/SceneGraph/Test/CMakeLists.txt +++ b/src/SceneGraph/Test/CMakeLists.txt @@ -24,6 +24,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(SceneGraphMatrixTransformation2DTest MatrixTransformation2DTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphMatrixTransformation3DTest MatrixTransformation3DTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphObjectTest ObjectTest.cpp LIBRARIES MagnumSceneGraphTestLib) @@ -31,6 +32,7 @@ corrade_add_test(SceneGraphRigidMatrixTransfor___2DTest RigidMatrixTransformatio corrade_add_test(SceneGraphRigidMatrixTransfor___3DTest RigidMatrixTransformation3DTest.cpp LIBRARIES MagnumSceneGraph) corrade_add_test(SceneGraphSceneTest SceneTest.cpp LIBRARIES MagnumSceneGraph) -set_target_properties(SceneGraphRigidMatrixTransfor___2DTest +set_target_properties(SceneGraphDualComplexTransforma___Test + SceneGraphRigidMatrixTransfor___2DTest SceneGraphRigidMatrixTransfor___3DTest PROPERTIES COMPILE_FLAGS "-DCORRADE_GRACEFUL_ASSERT") diff --git a/src/SceneGraph/Test/DualComplexTransformationTest.cpp b/src/SceneGraph/Test/DualComplexTransformationTest.cpp new file mode 100644 index 000000000..d9c71d1c9 --- /dev/null +++ b/src/SceneGraph/Test/DualComplexTransformationTest.cpp @@ -0,0 +1,180 @@ +/* + 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/DualComplexTransformation.h" +#include "SceneGraph/Scene.h" + +namespace Magnum { namespace SceneGraph { namespace Test { + +typedef Object> Object2D; +typedef Scene> Scene2D; + +class DualComplexTransformationTest: public Corrade::TestSuite::Tester { + public: + explicit DualComplexTransformationTest(); + + void fromMatrix(); + void toMatrix(); + void compose(); + void inverted(); + + void setTransformation(); + void resetTransformation(); + void transform(); + void translate(); + void rotate(); + void normalizeRotation(); +}; + +DualComplexTransformationTest::DualComplexTransformationTest() { + addTests({&DualComplexTransformationTest::fromMatrix, + &DualComplexTransformationTest::toMatrix, + &DualComplexTransformationTest::compose, + &DualComplexTransformationTest::inverted, + + &DualComplexTransformationTest::setTransformation, + &DualComplexTransformationTest::resetTransformation, + &DualComplexTransformationTest::transform, + &DualComplexTransformationTest::translate, + &DualComplexTransformationTest::rotate, + &DualComplexTransformationTest::normalizeRotation}); +} + +void DualComplexTransformationTest::fromMatrix() { + Matrix3 m = Matrix3::rotation(Deg(17.0f))*Matrix3::translation({1.0f, -0.3f}); + DualComplex c = DualComplex::rotation(Deg(17.0f))*DualComplex::translation({1.0f, -0.3f}); + CORRADE_COMPARE(DualComplexTransformation<>::fromMatrix(m), c); +} + +void DualComplexTransformationTest::toMatrix() { + DualComplex c = DualComplex::rotation(Deg(17.0f))*DualComplex::translation({1.0f, -0.3f}); + Matrix3 m = Matrix3::rotation(Deg(17.0f))*Matrix3::translation({1.0f, -0.3f}); + CORRADE_COMPARE(DualComplexTransformation<>::toMatrix(c), m); +} + +void DualComplexTransformationTest::compose() { + DualComplex parent = DualComplex::rotation(Deg(17.0f)); + DualComplex child = DualComplex::translation({1.0f, -0.3f}); + CORRADE_COMPARE(DualComplexTransformation<>::compose(parent, child), parent*child); +} + +void DualComplexTransformationTest::inverted() { + DualComplex c = DualComplex::rotation(Deg(17.0f))*DualComplex::translation({1.0f, -0.3f}); + CORRADE_COMPARE(DualComplexTransformation<>::inverted(c)*c, DualComplex()); +} + +void DualComplexTransformationTest::setTransformation() { + Object2D o; + + /* Can't transform with non-rigid transformation */ + std::ostringstream out; + Error::setOutput(&out); + o.setTransformation(DualComplex({1.0f, 2.0f}, {})); + CORRADE_COMPARE(out.str(), "SceneGraph::DualComplexTransformation::setTransformation(): the dual complex number is not normalized\n"); + + /* Dirty after setting transformation */ + o.setClean(); + CORRADE_VERIFY(!o.isDirty()); + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + CORRADE_VERIFY(o.isDirty()); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::rotation(Deg(17.0f))); + + /* Scene cannot be transformed */ + Scene2D s; + s.setClean(); + s.setTransformation(DualComplex::rotation(Deg(17.0f))); + CORRADE_VERIFY(!s.isDirty()); + CORRADE_COMPARE(s.transformationMatrix(), Matrix3()); +} + +void DualComplexTransformationTest::resetTransformation() { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + CORRADE_VERIFY(o.transformationMatrix() != Matrix3()); + o.resetTransformation(); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3()); +} + +void DualComplexTransformationTest::transform() { + { + /* Can't transform with non-rigid transformation */ + Object2D o; + std::ostringstream out; + Error::setOutput(&out); + o.transform(DualComplex({1.0f, 2.0f}, {})); + CORRADE_COMPARE(out.str(), "SceneGraph::DualComplexTransformation::transform(): the dual complex number is not normalized\n"); + } { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + o.transform(DualComplex::translation({1.0f, -0.3f})); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::translation({1.0f, -0.3f})*Matrix3::rotation(Deg(17.0f))); + } { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + o.transform(DualComplex::translation({1.0f, -0.3f}), TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::rotation(Deg(17.0f))*Matrix3::translation({1.0f, -0.3f})); + } +} + +void DualComplexTransformationTest::translate() { + { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + o.translate({1.0f, -0.3f}); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::translation({1.0f, -0.3f})*Matrix3::rotation(Deg(17.0f))); + } { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + o.translate({1.0f, -0.3f}, TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::rotation(Deg(17.0f))*Matrix3::translation({1.0f, -0.3f})); + } +} + +void DualComplexTransformationTest::rotate() { + { + Object2D o; + o.setTransformation(DualComplex::translation({1.0f, -0.3f})); + o.rotate(Deg(17.0f)); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::rotation(Deg(17.0f))*Matrix3::translation({1.0f, -0.3f})); + } { + Object2D o; + o.setTransformation(DualComplex::translation({1.0f, -0.3f})); + o.rotate(Deg(17.0f), TransformationType::Local); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::translation({1.0f, -0.3f})*Matrix3::rotation(Deg(17.0f))); + } +} + +void DualComplexTransformationTest::normalizeRotation() { + Object2D o; + o.setTransformation(DualComplex::rotation(Deg(17.0f))); + o.normalizeRotation(); + CORRADE_COMPARE(o.transformationMatrix(), Matrix3::rotation(Deg(17.0f))); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::SceneGraph::Test::DualComplexTransformationTest)