/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 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. */ namespace Magnum { /** @page transformations 2D and 3D transformations @brief Introduction to essential operations on vectors and points. @tableofcontents @m_footernavigation @m_keywords{Transformations} Transformations are essential operations involved in scene management --- object relations, hierarchies, animations etc. They extend basic vectors and matrices in @ref Math namespace, see its documentation for more information about usage with CMake. 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. @ref Matrix3 for 2D and @ref 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 @ref Complex in 2D and @ref Quaternion in 3D, or @ref DualComplex and @ref 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 @ref Matrix3, @ref Matrix4, @ref Complex, @ref Quaternion, @ref DualComplex and @ref 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 @ref Matrix3::rotation(), @ref Complex::rotation() or @ref DualComplex::rotation(), for example: @snippet MagnumMath.cpp transformations-rotation2D 3D rotation is represented by angle and (three-dimensional) axis. The rotation can be created by calling @ref Matrix4::rotation(), @ref Quaternion::rotation() or @ref DualQuaternion::rotation(). The axis must be always of unit length to avoid redundant normalization. Shortcuts @ref Vector3::xAxis(), @ref Vector3::yAxis() and @ref Vector3::zAxis() are provided for convenience. Matrix representation has also @ref Matrix4::rotationX(), @ref Matrix4::rotationY() and @ref Matrix4::rotationZ() which are faster than using the generic function for rotation around primary axes. Examples: @snippet MagnumMath.cpp transformations-rotation3D 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 @ref Matrix3::translation() or @ref DualComplex::translation(). You can use @ref Vector2::xAxis() or @ref Vector2::yAxis() to translate only along given axis. Examples: @snippet MagnumMath.cpp transformations-translation2D 3D translation is defined by three-dimensional vector and can be created with @ref Matrix4::translation() or @ref DualQuaternion::translation(). You can use @ref Vector3::xAxis() and friends also here. Examples: @snippet MagnumMath.cpp transformations-translation3D @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 @ref Matrix3::scaling() or @ref Matrix4::scaling(). You can use @ref Vector3::xScale(), @ref Vector3::yScale(), @ref 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: @snippet MagnumMath.cpp transformations-scaling 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 @ref Matrix3::reflection() or @ref Matrix4::reflection(). You can use @ref Vector3::xAxis() and friends also here. Examples: @snippet MagnumMath.cpp transformations-reflection Scaling and reflection is also done relative to origin, you can use method mentioned above to scale or reflect around arbitrary point. Scaling 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 @ref Matrix3::projection() and it is defined by area which will be projected into unit rectangle. In 3D there is orthographic projection, created with @ref Matrix4::orthographicProjection() and defined by volume to project into unit cube, and perspective projection. Perspective projection is created with @ref 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: @snippet MagnumMath.cpp transformations-projection @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: @snippet MagnumMath.cpp transformations-composing Inverse transformation can be computed using @ref Matrix3::inverted(), @ref Matrix4::inverted(), @ref Complex::inverted(), @ref Quaternion::inverted(), @ref DualComplex::inverted() or @ref DualQuaternion::inverted(). Matrix inversion is quite costly, so if your transformation involves only translation and rotation, you can use faster alternatives @ref Matrix3::invertedRigid() and @ref Matrix4::invertedRigid(). If you are sure that the (dual) complex number or (dual) quaternion is of unit length, you can use @ref Complex::invertedNormalized(), @ref Quaternion::invertedNormalized(), @ref DualComplex::invertedNormalized() or @ref 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 @ref Matrix3::transformVector() and @ref Complex::transformVector(), in 3D using @ref Matrix4::transformVector() and @ref Quaternion::transformVector(). For transformation with normalized quaternion you can use faster alternative @ref Quaternion::transformVectorNormalized(). Example: @snippet MagnumMath.cpp transformations-transform2D Point transformation involves also translation, in 2D is done with @ref Matrix3::transformPoint() and @ref DualComplex::transformPoint(), in 3D with @ref Matrix4::transformPoint() and @ref DualQuaternion::transformPoint(). Also here you can use faster alternative @ref DualQuaternion::transformPointNormalized(): @snippet MagnumMath.cpp transformations-transform3D @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: @snippet MagnumMath.cpp transformations-properties Extracting scaling and rotation from arbitrary transformation matrices is harder and can be done using @ref Math::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: @snippet MagnumMath.cpp transformations-recreate Complex numbers and quaternions are far better in this regard and they allow you to extract rotation angle using @ref Complex::angle() or @ref Quaternion::angle() or rotation axis in 3D using @ref Quaternion::axis(). Their dual versions allow to extract both rotation and translation part using @ref DualComplex::rotation() const, @ref DualQuaternion::rotation() const, @ref DualComplex::translation() const and @ref DualQuaternion::translation() const. @snippet MagnumMath.cpp transformations-properties-complex-quat You can convert Complex and Quaternion to rotation matrix using @ref Complex::toMatrix() and @ref Quaternion::toMatrix() or their dual version to rotation and translation matrix using @ref DualComplex::toMatrix() and @ref DualQuaternion::toMatrix(): @snippet MagnumMath.cpp transformations-properties-complex-quat-to-matrix Conversion the other way around is possible only from rotation matrices using @ref Complex::fromMatrix() or @ref Quaternion::fromMatrix() and from rotation and translation matrices using @ref DualComplex::fromMatrix() and @ref DualQuaternion::fromMatrix(): @snippet MagnumMath.cpp transformations-properties-complex-quat-from-matrix @section transformations-interpolation Transformation interpolation Magnum provides various tools for interpolation, from basic constant/linear interpolation of scalars and vectors to spline-based interpolation of quaternions or dual quaternions. The table below is a complete list of all builtin interpolation methods: @m_class{m-fullwidth} Interpolation | Value type | Result type | Interpolator ------------------- | ----------------- | ------------- | ------------ Constant | any `V` | `V` | @ref Math::select() Constant | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::select(const CubicHermite&, const CubicHermite&, U) "Math::select()" Linear | @cpp bool @ce | @cpp bool @ce | @ref Math::select() Linear | @ref Math::BitVector | @ref Math::BitVector | @ref Math::select() Linear | any scalar `V` | `V` | @ref Math::lerp() Linear | any vector `V` | `V` | @ref Math::lerp() Linear | @ref Math::Complex | @ref Math::Complex | @ref Math::lerp(const Complex&, const Complex&, T) "Math::lerp()" Linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::lerp(const Quaternion&, const Quaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::lerpShortestPath()" Linear | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::lerp(const CubicHermite&, const CubicHermite&, U) "Math::lerp()" Linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::lerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::lerp()" Linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::lerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerp()", \n @ref Math::lerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::lerpShortestPath()" Spherical linear | @ref Math::Complex | @ref Math::Complex | @ref Math::slerp(const Complex&, const Complex&, T) "Math::slerp()" Spherical linear | @ref Math::Quaternion | @ref Math::Quaternion | @ref Math::slerp(const Quaternion&, const Quaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const Quaternion&, const Quaternion&, T) "Math::slerpShortestPath()" Spherical linear | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::slerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::slerp()" Spherical linear | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::slerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::slerp()", \n @ref Math::slerpShortestPath(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::slerpShortestPath()" Screw linear | @ref Math::DualQuaternion | @ref Math::DualQuaternion | @ref Math::sclerp(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerp()", \n @ref Math::sclerpShortestPath(const DualQuaternion&, const DualQuaternion&, T) "Math::sclerpShortestPath()" Spline | @ref Math::CubicHermite "Math::CubicHermite" | `T` | @ref Math::splerp(const CubicHermite&, const CubicHermite&, U) "Math::splerp()" Spline | @ref Math::CubicHermiteComplex | @ref Math::Complex | @ref Math::splerp(const CubicHermiteComplex&, const CubicHermiteComplex&, T) "Math::splerp()" Spline | @ref Math::CubicHermiteQuaternion | @ref Math::Quaternion | @ref Math::splerp(const CubicHermiteQuaternion&, const CubicHermiteQuaternion&, T) "Math::splerp()" The @ref Math::CubicHermite class is a generic implementation of cubic Hermite splines to which other curve types such as Bézier are convertible. See its documentation for more information. @attention Direct interpolation of transformation matrices or Euler angles is not supported due to their well known performance / correctness issues and limitations such as gimbal lock --- you have to convert them to one of the supported representations such as @ref Complex numbers or @ref Quaternion and interpolate these instead. Interpolation is most commonly used in animations --- see @ref animation for more information. @section transformations-normalization Normalizing transformations When doing multiplicative transformations, e.g. adding rotating to a 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 @ref Math::Algorithms::gramSchmidtOrthogonalize() (or @ref Math::Algorithms::gramSchmidtOrthonormalize(), if you don't have any scaling). You can also use @ref Math::Algorithms::svd() to more precisely (but way more slowly) account for the drift. Example: @snippet MagnumMath.cpp transformations-normalization-matrix For quaternions and complex number this problem can be solved far more easily using @ref Complex::normalized(), @ref Quaternion::normalized(), @ref DualComplex::normalized() and @ref DualQuaternion::normalized(). Transformation quaternions and complex numbers are always of unit length, thus normalizing them reduces the drift. @snippet MagnumMath.cpp transformations-normalization-quat @section transformations-modelview Model/view terminology and Magnum The model / view matrix terminology, originating in the design of classical fixed-function GPU pipelines, is not used in Magnum. Instead, the matrix/vector APIs provide basic building blocks for creating transformations and concatenating them. No particular convention is enforced and you're free to create helper functions, if you need them. The @ref SceneGraph API provides a hierarchical transformation hierarchy and a correct camera-relative transformation is calculated automatically in the background, avoiding the need for manually handling of model / view transformations. In particular, camera position is always specified as relative to scene root and it gets inverted when calculating the final per-object transformation. See @ref scenegraph for detailed description. There's also a @ref Math::Matrix4::lookAt() function, but compared to the classical @cpp gluLookAt() @ce function it's an inverse, as that makes more sense together with the @ref SceneGraph API. */ }