From 8913e38432ffa0ab1d18f65d1396f8105ddcaf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 25 Feb 2013 21:24:30 +0100 Subject: [PATCH] Math: documented this whole transformation thingy. --- doc/features.dox | 1 + doc/matrix-vector.dox | 20 --- doc/transformations.dox | 290 ++++++++++++++++++++++++++++++++++++++ src/Math/Complex.h | 2 +- src/Math/DualComplex.h | 3 +- src/Math/DualQuaternion.h | 3 +- src/Math/Matrix3.h | 3 +- src/Math/Matrix4.h | 3 +- src/Math/Quaternion.h | 2 +- 9 files changed, 301 insertions(+), 26 deletions(-) create mode 100644 doc/transformations.dox diff --git a/doc/features.dox b/doc/features.dox index 3a0084de2..0a084a3bc 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -3,6 +3,7 @@ namespace Magnum { @brief Fundamental principles and design goals - @subpage matrix-vector - @copybrief matrix-vector +- @subpage transformations - @copybrief transformations - @subpage scenegraph - @copybrief scenegraph - @subpage collision-detection - @copybrief collision-detection - @subpage debug-tools - @copybrief debug-tools diff --git a/doc/matrix-vector.dox b/doc/matrix-vector.dox index c12adb0b6..a5addb366 100644 --- a/doc/matrix-vector.dox +++ b/doc/matrix-vector.dox @@ -65,15 +65,6 @@ Matrix3 diag(Matrix3::Identity, 2); // diagonal set to 2, zeros elsewh Vector3 fill(10); // {10, 10, 10} @endcode -Vectors are commonly used to specify various axes and scaling coefficients in -transformations, you can use convenience functions instead of typing out all -other elements: -@code -Matrix4::rotation(5.0_degf, Vector3::xAxis()); // {1.0f, 0.0f, 0.0f} -Matrix3::translation(Vector2::yAxis(2.0f)); // {0.0f, 2.0f} -Matrix4::scaling(Vector3::zScale(-10.0f)); // {1.0f, 1.0f, -10.0f} -@endcode - It is possible to create matrices from other matrices and vectors with the same row count; vectors from vector and scalar: @code @@ -133,17 +124,6 @@ Vector<4, int> bgra = swizzle<'b', 'g', 'r', 'a'>(original); // { 3, 2, -1, 4 } Vector<6, int> w10xyz = swizzle<'w', '1', '0', 'x', 'y', 'z'>(original); // { 4, 1, 0, -1, 2, 3 } @endcode -Matrix3 and Matrix4 have functions for accessing properties of given 2D/3D -transformation: -@code -Matrix4 a = Matrix4::translation(Vector3::yAxis(4.0f)); -Vector3 translation = a.translation(); - -Matrix3 b = Matrix3::rotation(15.0_degf)); -Matrix<2, float> rotationScaling = b.rotationScaling(); -Vector2 up = b.up(); -@endcode - @section matrix-vector-column-major Matrices are column-major and vectors are columns OpenGL matrices are column-major, thus it is reasonable to have matrices in diff --git a/doc/transformations.dox b/doc/transformations.dox new file mode 100644 index 000000000..43bba0457 --- /dev/null +++ b/doc/transformations.dox @@ -0,0 +1,290 @@ +namespace Magnum { namespace Math { +/** @page transformations 2D and 3D transformations + +@brief Introduction to essential operations on vectors and points. + +@tableofcontents + +Transformations are essential operations involved in scene management -- object +relations, hierarchies, animations etc. %Magnum provides classes for +transformations in both 2D and 3D. Each class is suited for different purposes, +but their usage is nearly the same to make your life simpler. This page will +explain the basic operation and differences between various representations. + +@section transformations-representation Representing transformations + +The first and most straightforward way to represent transformations is to use +homogeneous transformation matrix, i.e. Matrix3 for 2D and Matrix4 for 3D. The +matrices are able to represent all possible types of transformations -- rotation, +translation, scaling, reflection etc. and also projective transformation, thus +they are used at the very core of graphics pipeline and are supported natively +in OpenGL. + +On the other hand, matrices need 9 or 16 floats to represent the transformation, +which has implications on both memory usage and performance (relatively slow +matrix multiplication). It is also relatively hard to extract transformation +properties (such as rotation angle/axis) from them, interpolate between them or +compute inverse transformation. They suffer badly from so-called floating-point +drift -- e.g. after a few combined rotations the transformation won't be pure +rotation anymore, but will involve also a bit of scaling, shearing and whatnot. + +However, you can trade some transformation features for improved performance and +better behavior -- for just a rotation you can use Complex in 2D and Quaternion +in 3D, or DualComplex and DualQuaternion if you want also translation. It is not +possible to represent scaling, reflection or other transformations with them, +but they occupy only 2 or 4 floats (4 or 8 floats in dual versions), can be +easily inverted and interpolated and have many other awesome properties. However, +they are not magic so they also suffer slightly from floating-point drift, but +not too much and the drift can be accounted for more easily than with matrices. + +@section transformations-types Transformation types + +Transformation matrices and (dual) complex numbers or quaternions have completely +different internals, but they share the same API to achieve the same things, +greatly simplifying their usage. In many cases it is even possible to hot-swap +the transformation class type without changing any function calls. + +@subsection transformations-default Default (identity) transformation + +Default-constructed Matrix3, Matrix4, Complex, Quaternion, DualComplex and +DualQuaternion represent identity transformation, so you don't need to worry +about them in initialization. + +@subsection transformations-rotation Rotation + +2D rotation is represented solely by its angle in counterclockwise direction and +rotation transformation can be created by calling Matrix3::rotation(), +Complex::rotation() or DualComplex::rotation(), for example: +@code +auto a = Matrix3::rotation(23.0_degf); +auto b = Complex::rotation(Rad(Constants::pi()/2)); +auto c = DualComplex::rotation(-1.57_radf); +@endcode + +3D rotation is represented by angle and (three-dimensional) axis. The rotation +can be created by calling Matrix4::rotation(), Quaternion::rotation() or +DualQuaternion::rotation(). The axis must be always of unit length to avoid +redundant normalization. Shortcuts Vector3::xAxis(), Vector3::yAxis() and +Vector3::zAxis() are provided for convenience. %Matrix representation has also +Matrix4::rotationX(), Matrix4::rotationY() and Matrix4::rotationZ() which are +faster than using the generic function for rotation around primary axes. +Examples: +@code +auto a = Quaternion::rotation(60.0_degf, Vector3::xAxis()); +auto b = DualQuaternion::rotation(-1.0_degf, Vector3(1.0f, 0.5f, 3.0f).normalized()); +auto c = Matrix4::rotationZ(angle); +@endcode + +Rotations are always around origin. Rotation about arbitrary point can be done +by applying translation to have the point at origin, performing the rotation and +then translating back. Read below for more information. +@todo DualQuaternion and rotation around arbitrary axis + +@subsection transformations-translation Translation + +2D translation is defined by two-dimensional vector and can be created with +Matrix3::translation() or DualComplex::translation(). You can use Vector2::xAxis() +or Vector2::yAxis() to translate only along given axis. Examples: +@code +auto a = Matrix3::translation(Vector2::xAxis(-5.0f)); +auto b = DualComplex::translation({-1.0f, 0.5f}); +@endcode + +3D translation is defined by three-dimensional vector and can be created with +Matrix4::translation() or DualQuaternion::translation(). You can use +Vector3::xAxis() and friends also here. Examples: +@code +auto a = Matrix4::translation(vector); +auto b = DualQuaternion::translation(Vector3::zAxis(1.3f)); +@endcode + +@subsection transformations-scaling Scaling and reflection + +Scaling is defined by two- or three-dimensional vector and is represented by +matrices. You can create it with Matrix3::scaling() or Matrix4::scaling(). You +can use Vector3::xScale(), Vector3::yScale(), Vector3::zScale() or their 2D +counterparts to scale along one axis and leave the rest unchanged or call +explicit one-parameter vector constructor to scale uniformly on all axes. +Examples: +@code +auto a = Matrix3::scaling(Vector2::xScale(2.0f)); +auto b = Matrix4::scaling({2.0f, -2.0f, 1.5f}); +auto c = Matrix4::scaling(Vector3(10.0f)); +@endcode + +Reflections are defined by normal along which to reflect (i.e., two- or +three-dimensional vector of unit length) and they are also represented by +matrices. Reflection is created with Matrix3::reflection() or Matrix4::reflection(). +You can use Vector3::xAxis() and friends also here. Examples: +@code +auto a = Matrix3::reflection(Vector2::yAxis()); +auto b = Matrix4::reflection(axis.normalized()); +@endcode + +Scaling and reflection is also done relative to origin, you can use method +mentioned above to scale or reflect around arbitrary point. + +Sscaling and reflection can be (to some extent) also represented by complex +numbers and quaternions, but it has some bad properties and would make some +operations more expensive, so it's not implemented. + +@subsection transformations-projective Projective transformations + +Projective transformations eploit the full potential of transformation matrices. +In 2D there is only one projection type, which can be created with Matrix3::projection() +and it is defined by area which will be projected into unit rectangle. In 3D +there is orthographic projection, created with Matrix4::orthographicProjection() +and defined by volume to project into unit cube, and perspective projection. +Perspective projection is created with Matrix4::perspectiveProjection() and is +defined either by field-of-view, aspect ratio and distance to near and far plane +of view frustum or by size of near plane, its distance and distance to far +plane. Some examples: +@code +auto a = Matrix3::projection({4.0f, 3.0f}); +auto b = Matrix4::orthographicProjection({4.0f, 3.0f, 100.0f}); +auto c = Matrix4::perspectiveProjection(35.0_degf, 1.333f, 0.001f, 100.0f); +@endcode + +@section transformations-composing Composing and inverting transformations + +Transformations (of the same representation) can be composed simply by +multiplying them, it works the same for matrices, complex numbers, quaternions +and their dual counterparts. Order of multiplication matters -- the +transformation on the right-hand side of multiplication is applied first, the +transformation on the left-hand side is applied second. For example, rotation +followed by translation is done like this: +@code +auto a = DualComplex::translation(Vector2::yAxis(2.0f))* + DualComplex::rotation(25.0_degf); +auto b = Matrix4::translation(Vector3::yAxis(5.0f))* + Matrix4::rotationY(25.0_degf); +@endcode + +Inverse transformation can be computed using Matrix3::inverted(), Matrix4::inverted(), +Complex::inverted(), Quaternion::inverted(), DualComplex::inverted() or +DualQuaternion::inverted(). %Matrix inversion is quite costly, so if your +transformation involves only translation and rotation, you can use faster +alternatives Matrix3::invertedEuclidean() and Matrix4::invertedEuclidean(). If +you are sure that the (dual) complex number or (dual) quaternion is of unit +length, you can use Complex::invertedNormalized(), Quaternion::invertedNormalized(), +DualComplex::invertedNormalized() or DualQuaternion::invertedNormalized() which +is a little bit faster, because it doesn't need to renormalize the result. + +@section transformations-transforming Transforming vectors and points + +Transformations can be used directly for transforming vectors and points. %Vector +transformation does not involve translation, in 2D can be done using +Matrix3::transformVector() and Complex::transformVector(), in 3D using +Matrix4::transformVector() and Quaternion::transformVector(). For transformation +with normalized quaternion you can use faster alternative Quaternion::transformVectorNormalized(). +Example: +@code +auto transformation = Matrix3::rotation(-30.0_degf)*Matrix3::scaling(Vector2(3.0f)); +Vector2 transformed = transformation.transformVector({1.5f, -7.9f}); +@endcode + +Point transformation involves also translation, in 2D is done with +Matrix3::transformPoint() and DualComplex::transformPoint(), in 3D with +Matrix4::transformPoint() and DualQuaternion::transformPoint(). Also here you +can use faster alternative Quaternion::transformPointNormalized(): +@code +auto transformation = DualQuaternion::rotation(-30.0_degf, Vector3::xAxis())* + DualQuaternion::translation(Vector3::yAxis(3.0f)); +Vector3 transformed = transformation.transformPointNormalized({1.5f, 3.0f, -7.9f}); +@endcode + +@section transformations-properties Transformation properties and conversion + +It is possible to extract some transformation properties from transformation +matrices, particularly translation vector, rotation/scaling part of the matrix +(or pure rotation if the matrix has uniform scaling) and also base vectors: +@code +Matrix4 a; +auto rotationScaling = transformation.rotationScaling(); +Vector3 up = transformation.up(); +Vector3 right = transformation.right(); + +Matrix3 b; +auto rotation = b.rotation(); +Float xTranslation = b.translation().x(); +@endcode +Extracting scaling and rotation from arbitrary transformation matrices is harder +and can be done using Algorithms::svd(). Extracting rotation angle (and axis in +3D) from rotation part is possible using by converting it to complex number or +quaternion, see below. + +You can also recreate transformation matrix from rotation and translation parts: +@code +Matrix3 c = Matrix3::from(rotation, {1.0f, 3.0f}); +@endcode + +%Complex numbers and quaternions are far better in this regard and they allow +you to extract rotation angle using Complex::rotationAngle(), Quaternion::rotationAngle(), +DualComplex::rotationAngle() or DualQuaternion::rotationAngle(), and rotation +axis in 3D using Quaternion::rotationAxis() or DualQuaternion::rotationAxis(). +It is also possible to extract translation from their dual versions using +DualComplex::translation() const and DualQuaternion::translation() const. +@code +DualComplex a; +Rad rotation = a.rotationAngle(); +Vector2 translation = a.translation(); + +Quaternion b; +Vector3 rotationAxis = b.rotationAxis(); +@endcode + +You can convert Complex and Quaternion to rotation matrix using Complex::toMatrix() +and Quaternion::toMatrix() or their dual version to rotation and translation +matrix using DualComplex::toMatrix() and DualQuaternion::toMatrix(): +@code +Quaternion a; +auto rotation = Matrix4::from(a.toMatrix(), {}); + +DualComplex b; +Matrix3 transformation = b.toMatrix(); +@endcode + +Conversion the other way around is possible only from rotation matrices using +Complex::fromMatrix() or Quaternion::fromMatrix() and from rotation and +translation matrices using DualComplex::fromMatrix() and +DualQuaternion::fromMatrix(): +@code +Matrix3 rotation; +auto a = Complex::fromMatrix(rotation.rotationScaling()); + +Matrix4 transformation; +auto b = DualQuaternion::fromMatrix(transformation); +@endcode + +@section transformations-interpolation Transformation interpolation + +@todoc Write this when interpolation is done also for (dual) complex numbers and + dual quaternions + +@section transformations-normalization Normalizing transformations + +When doing multiplicative transformations, e.g. adding rotating to an +transformation many times during an animation, the resulting transformation will +accumulate rounding errors and behave strangely. For transformation matrices +this can't always be fixed, because they can represent any transformation (and +thus no algorithm can't tell if the transformation is in expected form or not). +If you restrict yourselves (e.g. only uniform scaling and no skew), the matrix +can be reorthogonalized using Algorithms::gramSchmidtOrthogonalize() (or +Algorithms::gramSchmidtOrthonormalize(), if you don't have any scaling). You can +also use Algorithms::svd() to more precisely (but way more slowly) account for +the drift. Example: +@code +Matrix4 transformation; +Math::Algorithms::gramSchmidtOrthonormalizeInPlace(transformation); +@endcode + +For quaternions and complex number this problem can be solved far more easily +using Complex::normalized(), Quaternion::normalized(), DualComplex::normalized() +and DualQuaternion::normalized(). Transformation quaternions and complex numbers +are always of unit length, thus normalizing them reduces the drift. +@code +DualQuaternion transformation; +transformation = transformation.normalized(); +@endcode +*/ +}} diff --git a/src/Math/Complex.h b/src/Math/Complex.h index 8bd7486ac..5a363b08b 100644 --- a/src/Math/Complex.h +++ b/src/Math/Complex.h @@ -32,7 +32,7 @@ namespace Magnum { namespace Math { @brief %Complex number @tparam T Data type -Represents 2D rotation. +Represents 2D rotation. See @ref transformations for brief introduction. @see Magnum::Complex, Matrix3 */ template class Complex { diff --git a/src/Math/DualComplex.h b/src/Math/DualComplex.h index a17198d8d..d455d72e6 100644 --- a/src/Math/DualComplex.h +++ b/src/Math/DualComplex.h @@ -29,7 +29,8 @@ namespace Magnum { namespace Math { @brief %Dual complex number @tparam T Underlying data type -Represents 2D rotation and translation. +Represents 2D rotation and translation. See @ref transformations for brief +introduction. @see Magnum::DualComplex, Dual, Complex, Matrix3 @todo Can this be done similarly as in dual quaternions? It sort of works, but the math beneath is weird. diff --git a/src/Math/DualQuaternion.h b/src/Math/DualQuaternion.h index 6a305e0b5..7bc3d70d7 100644 --- a/src/Math/DualQuaternion.h +++ b/src/Math/DualQuaternion.h @@ -29,7 +29,8 @@ namespace Magnum { namespace Math { @brief %Dual quaternion @tparam T Underlying data type -Represents 3D rotation and translation. +Represents 3D rotation and translation. See @ref transformations for brief +introduction. @see Magnum::DualQuaternion, Dual, Quaternion, Matrix4 */ template class DualQuaternion: public Dual> { diff --git a/src/Math/Matrix3.h b/src/Math/Matrix3.h index 4bd73a8ec..81d930e7b 100644 --- a/src/Math/Matrix3.h +++ b/src/Math/Matrix3.h @@ -28,7 +28,8 @@ namespace Magnum { namespace Math { @brief 3x3 matrix @tparam T Underlying data type -Represents 2D transformation. See @ref matrix-vector for brief introduction. +Represents 2D transformation. See @ref matrix-vector and @ref transformations +for brief introduction. @see Magnum::Matrix3, Magnum::Matrix3d, DualComplex, SceneGraph::MatrixTransformation2D @configurationvalueref{Magnum::Math::Matrix3} diff --git a/src/Math/Matrix4.h b/src/Math/Matrix4.h index 8333b2271..8ba2e4f7a 100644 --- a/src/Math/Matrix4.h +++ b/src/Math/Matrix4.h @@ -33,7 +33,8 @@ namespace Magnum { namespace Math { @brief 4x4 matrix @tparam T Underlying data type -Represents 3D transformation. See @ref matrix-vector for brief introduction. +Represents 3D transformation. See @ref matrix-vector and @ref transformations +for brief introduction. @see Magnum::Matrix4, Magnum::Matrix4d, DualQuaternion, SceneGraph::MatrixTransformation3D @configurationvalueref{Magnum::Math::Matrix4} diff --git a/src/Math/Quaternion.h b/src/Math/Quaternion.h index e92397145..f50904fdc 100644 --- a/src/Math/Quaternion.h +++ b/src/Math/Quaternion.h @@ -33,7 +33,7 @@ namespace Magnum { namespace Math { @brief %Quaternion @tparam T Underlying data type -Represents 3D rotation. +Represents 3D rotation. See @ref transformations for brief introduction. @see Magnum::Quaternion, DualQuaternion, Matrix4 */ template class Quaternion {