Browse Source

Math: documented this whole transformation thingy.

pull/278/head
Vladimír Vondruš 13 years ago
parent
commit
8913e38432
  1. 1
      doc/features.dox
  2. 20
      doc/matrix-vector.dox
  3. 290
      doc/transformations.dox
  4. 2
      src/Math/Complex.h
  5. 3
      src/Math/DualComplex.h
  6. 3
      src/Math/DualQuaternion.h
  7. 3
      src/Math/Matrix3.h
  8. 3
      src/Math/Matrix4.h
  9. 2
      src/Math/Quaternion.h

1
doc/features.dox

@ -3,6 +3,7 @@ namespace Magnum {
@brief Fundamental principles and design goals @brief Fundamental principles and design goals
- @subpage matrix-vector - @copybrief matrix-vector - @subpage matrix-vector - @copybrief matrix-vector
- @subpage transformations - @copybrief transformations
- @subpage scenegraph - @copybrief scenegraph - @subpage scenegraph - @copybrief scenegraph
- @subpage collision-detection - @copybrief collision-detection - @subpage collision-detection - @copybrief collision-detection
- @subpage debug-tools - @copybrief debug-tools - @subpage debug-tools - @copybrief debug-tools

20
doc/matrix-vector.dox

@ -65,15 +65,6 @@ Matrix3<int> diag(Matrix3<int>::Identity, 2); // diagonal set to 2, zeros elsewh
Vector3<int> fill(10); // {10, 10, 10} Vector3<int> fill(10); // {10, 10, 10}
@endcode @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 It is possible to create matrices from other matrices and vectors with the
same row count; vectors from vector and scalar: same row count; vectors from vector and scalar:
@code @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 } Vector<6, int> w10xyz = swizzle<'w', '1', '0', 'x', 'y', 'z'>(original); // { 4, 1, 0, -1, 2, 3 }
@endcode @endcode
Matrix3 and Matrix4 have functions for accessing properties of given 2D/3D
transformation:
@code
Matrix4<float> a = Matrix4<float>::translation(Vector3::yAxis(4.0f));
Vector3<float> translation = a.translation();
Matrix3<float> b = Matrix3<float>::rotation(15.0_degf));
Matrix<2, float> rotationScaling = b.rotationScaling();
Vector2<float> up = b.up();
@endcode
@section matrix-vector-column-major Matrices are column-major and vectors are columns @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 OpenGL matrices are column-major, thus it is reasonable to have matrices in

290
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
*/
}}

2
src/Math/Complex.h

@ -32,7 +32,7 @@ namespace Magnum { namespace Math {
@brief %Complex number @brief %Complex number
@tparam T Data type @tparam T Data type
Represents 2D rotation. Represents 2D rotation. See @ref transformations for brief introduction.
@see Magnum::Complex, Matrix3 @see Magnum::Complex, Matrix3
*/ */
template<class T> class Complex { template<class T> class Complex {

3
src/Math/DualComplex.h

@ -29,7 +29,8 @@ namespace Magnum { namespace Math {
@brief %Dual complex number @brief %Dual complex number
@tparam T Underlying data type @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 @see Magnum::DualComplex, Dual, Complex, Matrix3
@todo Can this be done similarly as in dual quaternions? It sort of works, but @todo Can this be done similarly as in dual quaternions? It sort of works, but
the math beneath is weird. the math beneath is weird.

3
src/Math/DualQuaternion.h

@ -29,7 +29,8 @@ namespace Magnum { namespace Math {
@brief %Dual quaternion @brief %Dual quaternion
@tparam T Underlying data type @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 @see Magnum::DualQuaternion, Dual, Quaternion, Matrix4
*/ */
template<class T> class DualQuaternion: public Dual<Quaternion<T>> { template<class T> class DualQuaternion: public Dual<Quaternion<T>> {

3
src/Math/Matrix3.h

@ -28,7 +28,8 @@ namespace Magnum { namespace Math {
@brief 3x3 matrix @brief 3x3 matrix
@tparam T Underlying data type @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, @see Magnum::Matrix3, Magnum::Matrix3d, DualComplex,
SceneGraph::MatrixTransformation2D SceneGraph::MatrixTransformation2D
@configurationvalueref{Magnum::Math::Matrix3} @configurationvalueref{Magnum::Math::Matrix3}

3
src/Math/Matrix4.h

@ -33,7 +33,8 @@ namespace Magnum { namespace Math {
@brief 4x4 matrix @brief 4x4 matrix
@tparam T Underlying data type @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, @see Magnum::Matrix4, Magnum::Matrix4d, DualQuaternion,
SceneGraph::MatrixTransformation3D SceneGraph::MatrixTransformation3D
@configurationvalueref{Magnum::Math::Matrix4} @configurationvalueref{Magnum::Math::Matrix4}

2
src/Math/Quaternion.h

@ -33,7 +33,7 @@ namespace Magnum { namespace Math {
@brief %Quaternion @brief %Quaternion
@tparam T Underlying data type @tparam T Underlying data type
Represents 3D rotation. Represents 3D rotation. See @ref transformations for brief introduction.
@see Magnum::Quaternion, DualQuaternion, Matrix4 @see Magnum::Quaternion, DualQuaternion, Matrix4
*/ */
template<class T> class Quaternion { template<class T> class Quaternion {

Loading…
Cancel
Save