123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- .. _doc_matrices_and_transforms:
- Matrices and transforms
- =======================
- Introduction
- ------------
- Before reading this tutorial, it is advised to read the previous one
- about :ref:`doc_vector_math` as this one is a direct continuation.
- This tutorial will be about *transformations* and will cover a little
- about matrices (but not in-depth).
- Transformations are most of the time applied as translation, rotation
- and scale so they will be considered as priority here.
- Oriented coordinate system (OCS)
- --------------------------------
- Imagine we have a spaceship somewhere in space. In Godot this is easy,
- just move the ship somewhere and rotate it:
- .. image:: img/tutomat1.png
- Ok, so in 2D this looks simple, a position and an angle for a rotation.
- But remember, we are grown ups here and don't use angles (plus, angles
- are not even that useful when working in 3D).
- We should realize that at some point, someone *designed* this
- spaceship. Be it for 2D in a drawing such as Paint.net, Gimp,
- Photoshop, etc. or in 3D through a 3D DCC tool such as Blender, Max,
- Maya, etc.
- When it was designed, it was not rotated. It was designed in its own
- *coordinate system*.
- .. image:: img/tutomat2.png
- This means that the tip of the ship has a coordinate, the fin has
- another, etc. Be it in pixels (2D) or vertices (3D).
- So, let's recall again that the ship was somewhere in space:
- .. image:: img/tutomat3.png
- How did it get there? What moved it and rotated it from the place it was
- designed to its current position? The answer is... a **transform**, the
- ship was *transformed* from their original position to the new one. This
- allows the ship to be displayed where it is.
- But transform is too generic of a term to describe this process. To solve this
- puzzle, we will superimpose the ship's original design position at their
- current position:
- .. image:: img/tutomat4.png
- So, we can see that the "design space" has been transformed too. How can
- we best represent this transformation? Let's use 3 vectors for this (in
- 2D), a unit vector pointing towards X positive, a unit vector pointing
- towards Y positive and a translation.
- .. image:: img/tutomat5.png
- Let's call the 3 vectors "X", "Y" and "Origin", and let's also
- superimpose them over the ship so it makes more sense:
- .. image:: img/tutomat6.png
- Ok, this is nicer, but it still does not make sense. What do X,Y and
- Origin have to do with how the ship got there?
- Well, let's take the point from top tip of the ship as reference:
- .. image:: img/tutomat7.png
- And let's apply the following operation to it (and to all the points in
- the ship too, but we'll track the top tip as our reference point):
- .. tabs::
- .. code-tab:: gdscript GDScript
- var new_pos = pos - origin
- .. code-tab:: csharp
- var newPosition = pos - origin;
- Doing this to the selected point will move it back to the center:
- .. image:: img/tutomat8.png
- This was expected, but then let's do something more interesting. Use the
- dot product of X and the point, and add it to the dot product of Y and
- the point:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var final_pos = Vector2(x.dot(new_pos), y.dot(new_pos))
- .. code-tab:: csharp
- var finalPosition = new Vector2(x.Dot(newPosition), y.Dot(newPosition));
- Then what we have is.. wait a minute, it's the ship in its design
- position!
- .. image:: img/tutomat9.png
- How did this black magic happen? The ship was lost in space, and now
- it's back home!
- It might seem strange, but it does have plenty of logic. Remember, as
- we have seen in the :ref:`doc_vector_math`, what
- happened is that the distance to X axis, and the distance to Y axis
- were computed. Calculating distance in a direction or plane was one of
- the uses for the dot product. This was enough to obtain back the
- design coordinates for every point in the ship.
- So, what we have been working with so far (with X, Y and Origin) is an
- *Oriented Coordinate System*. X an Y are the **Basis**, and *Origin*
- is the offset.
- Basis
- -----
- We know what the Origin is. It's where the 0,0 (origin) of the design
- coordinate system ended up after being transformed to a new position.
- This is why it's called *Origin*, But in practice, it's just an offset
- to the new position.
- The Basis is more interesting. The basis is the direction of X and Y in the OCS
- from the new, transformed location. It tells what has changed, in either 2D or
- 3D. The Origin (offset) and Basis (direction) communicate "Hey, the original X
- and Y axes of your design are *right here*, pointing towards *these
- directions*."
- So, let's change the representation of the basis. Instead of 2 vectors,
- let's use a *matrix*.
- .. image:: img/tutomat10.png
- The vectors are up there in the matrix, horizontally. The next problem
- now is that.. what is this matrix thing? Well, we'll assume you've never
- heard of a matrix.
- Transforms in Godot
- -------------------
- This tutorial will not explain matrix math (and their operations) in
- depth, only its practical use. There is plenty of material for that,
- which should be a lot simpler to understand after completing this
- tutorial. We'll just explain how to use transforms.
- Transform2D
- ~~~~~~~~~~~
- :ref:`class_Transform2D` is a 3x2 matrix. It has 3 Vector2 elements and
- it's used for 2D. The "X" axis is the element 0, "Y" axis is the element 1 and
- "Origin" is element 2. It's not divided in basis/origin for convenience, due to
- its simplicity.
- .. tabs::
- .. code-tab:: gdscript GDScript
- var m = Transform2D()
- var x = m[0] # 'X'
- var y = m[1] # 'Y'
- var o = m[2] # 'Origin'
- .. code-tab:: csharp
- var m = new Transform2D();
- Vector2 x = m[0]; // 'X'
- Vector2 y = m[1]; // 'Y'
- Vector2 o = m[2]; // 'Origin'
- Most operations will be explained with this datatype (Transform2D), but the
- same logic applies to 3D.
- Identity
- ~~~~~~~~
- An important transform is the "identity" matrix. This means:
- - 'X' Points right: Vector2(1,0)
- - 'Y' Points up (or down in pixels): Vector2(0,1)
- - 'Origin' is the origin Vector2(0,0)
- .. image:: img/tutomat11.png
- It's easy to guess that an *identity* matrix is just a matrix that
- aligns the transform to its parent coordinate system. It's an *OCS*
- that hasn't been translated, rotated or scaled.
- .. tabs::
- .. code-tab:: gdscript GDScript
- # The Transform2D constructor will default to Identity
- var m = Transform2D()
- print(m)
- # prints: ((1, 0), (0, 1), (0, 0))
- .. code-tab:: csharp
- // Due to technical limitations on structs in C# the default
- // constructor will contain zero values for all fields.
- var defaultTransform = new Transform2D();
- GD.Print(defaultTransform);
- // prints: ((0, 0), (0, 0), (0, 0))
- // Instead we can use the Identity property.
- var identityTransform = Transform2D.Identity;
- GD.Print(identityTransform);
- // prints: ((1, 0), (0, 1), (0, 0))
- Operations
- ----------
- Rotation
- ~~~~~~~~
- Rotating Transform2D is done by using the "rotated" function:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var m = Transform2D()
- m = m.rotated(PI/2) # rotate 90°
- .. code-tab:: csharp
- var m = Transform2D.Identity;
- m = m.Rotated(Mathf.Pi / 2); // rotate 90°
- .. image:: img/tutomat12.png
- Translation
- ~~~~~~~~~~~
- There are two ways to translate a Transform2D, the first one is moving
- the origin:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # Move 2 units to the right
- var m = Transform2D()
- m = m.rotated(PI/2) # rotate 90°
- m[2] += Vector2(2,0)
- .. code-tab:: csharp
- // Move 2 units to the right
- var m = Transform2D.Identity;
- m = m.Rotated(Mathf.Pi / 2); // rotate 90°
- m[2] += new Vector2(2, 0);
- .. image:: img/tutomat13.png
- This will always work in global coordinates.
- If instead, translation is desired in *local* coordinates of the
- matrix (towards where the *basis* is oriented), there is the
- :ref:`Transform2D.translated() <class_Transform2D_method_translated>`
- method:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # Move 2 units towards where the basis is oriented
- var m = Transform2D()
- m = m.rotated(PI/2) # rotate 90°
- m = m.translated( Vector2(2,0) )
- .. code-tab:: csharp
- // Move 2 units towards where the basis is oriented
- var m = Transform2D.Identity;
- m = m.Rotated(Mathf.Pi / 2); // rotate 90°
- m = m.Translated(new Vector2(2, 0));
- .. image:: img/tutomat14.png
- You could also transform the global coordinates to local coordinates manually:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var local_pos = m.xform_inv(point)
- .. code-tab:: csharp
- var localPosition = m.XformInv(point);
- But even better, there are helper functions for this as you can read in the next sections.
- Local to global coordinates and vice versa
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- There are helper methods for converting between local and global coordinates.
- There are :ref:`Node2D.to_local() <class_Node2D_method_to_local>` and :ref:`Node2D.to_global() <class_Node2D_method_to_global>` for 2D
- as well as :ref:`Spatial.to_local() <class_Spatial_method_to_local>` and :ref:`Spatial.to_global() <class_Spatial_method_to_global>` for 3D.
- Scale
- ~~~~~
- A matrix can be scaled too. Scaling will multiply the basis vectors by a
- vector (X vector by x component of the scale, Y vector by y component of
- the scale). It will leave the origin alone:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # Make the basis twice its size.
- var m = Transform2D()
- m = m.scaled( Vector2(2,2) )
- .. code-tab:: csharp
- // Make the basis twice its size.
- var m = Transform2D.Identity;
- m = m.Scaled(new Vector2(2, 2));
- .. image:: img/tutomat15.png
- These kind of operations in matrices are accumulative. It means every
- one starts relative to the previous one. For those who have been living
- on this planet long enough, a good reference of how transform works is
- this:
- .. image:: img/tutomat16.png
- A matrix is used similarly to a turtle. The turtle most likely had a
- matrix inside (and you are likely learning this many years *after*
- discovering Santa is not real).
- Transform
- ~~~~~~~~~
- Transform is the act of switching between coordinate systems. To convert
- a position (either 2D or 3D) from "designer" coordinate system to the
- OCS, the "xform" method is used.
- .. tabs::
- .. code-tab:: gdscript GDScript
- var new_pos = m.xform(pos)
- .. code-tab:: csharp
- var newPosition = m.Xform(position);
- And only for basis (no translation):
- .. tabs::
- .. code-tab:: gdscript GDScript
- var new_pos = m.basis_xform(pos)
- .. code-tab:: csharp
- var newPosition = m.BasisXform(position);
- Inverse transform
- ~~~~~~~~~~~~~~~~~
- To do the opposite operation (what we did up there with the rocket), the
- "xform_inv" method is used:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var new_pos = m.xform_inv(pos)
- .. code-tab:: csharp
- var newPosition = m.XformInv(position);
- Only for Basis:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var new_pos = m.basis_xform_inv(pos)
- .. code-tab:: csharp
- var newPosition = m.BasisXformInv(position);
- Orthonormal matrices
- ^^^^^^^^^^^^^^^^^^^^
- However, if the matrix has been scaled (vectors are not unit length),
- or the basis vectors are not orthogonal (90°), the inverse transform
- will not work.
- In other words, inverse transform is only valid in *orthonormal*
- matrices. For this, these cases an affine inverse must be computed.
- The transform, or inverse transform of an identity matrix will return
- the position unchanged:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # Does nothing, pos is unchanged
- pos = Transform2D().xform(pos)
- .. code-tab:: csharp
- // Does nothing, position is unchanged
- position = Transform2D.Identity.Xform(position);
- Affine inverse
- ~~~~~~~~~~~~~~
- The affine inverse is a matrix that does the inverse operation of
- another matrix, no matter if the matrix has scale or the axis vectors
- are not orthogonal. The affine inverse is calculated with the
- affine_inverse() method:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var mi = m.affine_inverse()
- pos = m.xform(pos)
- pos = mi.xform(pos)
- # pos is unchanged
- .. code-tab:: csharp
- var mi = m.AffineInverse();
- position = m.Xform(position);
- position = mi.Xform(position);
- // position is unchanged
- If the matrix is orthonormal, then:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # if m is orthonormal, then
- pos = mi.xform(pos)
- # is the same is
- pos = m.xform_inv(pos)
- .. code-tab:: csharp
- // if m is orthonormal, then
- position = mi.Xform(position);
- // is the same is
- position = m.XformInv(position);
- Matrix multiplication
- ~~~~~~~~~~~~~~~~~~~~~
- Matrices can be multiplied. Multiplication of two matrices "chains"
- (concatenates) their transforms.
- However, as per convention, multiplication takes place in reverse
- order.
- Example:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var m = more_transforms * some_transforms
- .. code-tab:: csharp
- var m = moreTransforms * someTransforms;
- To make it a little clearer, this:
- .. tabs::
- .. code-tab:: gdscript GDScript
- pos = transform1.xform(pos)
- pos = transform2.xform(pos)
- .. code-tab:: csharp
- position = transform1.Xform(position);
- position = transform2.Xform(position);
- Is the same as:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # note the inverse order
- pos = (transform2 * transform1).xform(pos)
- .. code-tab:: csharp
- // note the inverse order
- position = (transform2 * transform1).Xform(position);
- However, this is not the same:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # yields a different results
- pos = (transform1 * transform2).xform(pos)
- .. code-tab:: csharp
- // yields a different results
- position = (transform1 * transform2).Xform(position);
- Because in matrix math, A * B is not the same as B * A.
- Multiplication by inverse
- ~~~~~~~~~~~~~~~~~~~~~~~~~
- Multiplying a matrix by its inverse, results in identity:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # No matter what A is, B will be identity
- var B = A.affine_inverse() * A
- .. code-tab:: csharp
- // No matter what A is, B will be identity
- var B = A.AffineInverse() * A;
- Multiplication by identity
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
- Multiplying a matrix by identity, will result in the unchanged matrix:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # B will be equal to A
- B = A * Transform2D()
- .. code-tab:: csharp
- // B will be equal to A
- var B = A * Transform2D.Identity;
- Matrix tips
- -----------
- When using a transform hierarchy, remember that matrix multiplication is
- reversed! To obtain the global transform for a hierarchy, do:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var global_xform = parent_matrix * child_matrix
- .. code-tab:: csharp
- var globalTransform = parentMatrix * childMatrix;
- For 3 levels:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var global_xform = gradparent_matrix * parent_matrix * child_matrix
- .. code-tab:: csharp
- var globalTransform = grandparentMatrix * parentMatrix * childMatrix;
- To make a matrix relative to the parent, use the affine inverse (or
- regular inverse for orthonormal matrices).
- .. tabs::
- .. code-tab:: gdscript GDScript
- # transform B from a global matrix to one local to A
- var B_local_to_A = A.affine_inverse() * B
- .. code-tab:: csharp
- // transform B from a global matrix to one local to A
- var bLocalToA = A.AffineInverse() * B;
- Revert it just like the example above:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # transform back local B to global B
- B = A * B_local_to_A
- .. code-tab:: csharp
- // transform back local B to global B
- B = A * bLocalToA;
- OK, hopefully this should be enough! Let's complete the tutorial by
- moving to 3D matrices.
- Matrices & transforms in 3D
- ---------------------------
- As mentioned before, for 3D, we deal with 3 :ref:`Vector3 <class_Vector3>`
- vectors for the rotation matrix, and an extra one for the origin.
- Basis
- ~~~~~
- Godot has a special type for a 3x3 matrix, named :ref:`Basis <class_Basis>`.
- It can be used to represent a 3D rotation and scale. Sub vectors can be
- accessed as:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var m = Basis()
- var x = m[0] # Vector3
- var y = m[1] # Vector3
- var z = m[2] # Vector3
- .. code-tab:: csharp
- var m = new Basis();
- Vector3 x = m[0];
- Vector3 y = m[1];
- Vector3 z = m[2];
- Or, alternatively as:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var m = Basis()
- var x = m.x # Vector3
- var y = m.y # Vector3
- var z = m.z # Vector3
- .. code-tab:: csharp
- var m = new Basis();
- Vector3 x = m.x;
- Vector3 y = m.y;
- Vector3 z = m.z;
- The Identity Basis has the following values:
- .. image:: img/tutomat17.png
- And can be accessed like this:
- .. tabs::
- .. code-tab:: gdscript GDScript
- # The Basis constructor will default to Identity
- var m = Basis()
- print(m)
- # prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
- .. code-tab:: csharp
- // Due to technical limitations on structs in C# the default
- // constructor will contain zero values for all fields.
- var defaultBasis = new Basis();
- GD.Print(defaultBasis);
- // prints: ((0, 0, 0), (0, 0, 0), (0, 0, 0))
- // Instead we can use the Identity property.
- var identityBasis = Basis.Identity;
- GD.Print(identityBasis);;
- // prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
- Rotation in 3D
- ~~~~~~~~~~~~~~
- Rotation in 3D is more complex than in 2D (translation and scale are the
- same), because rotation is an implicit 2D operation. To rotate in 3D, an
- *axis*, must be picked. Rotation, then, happens around this axis.
- The axis for the rotation must be a *normal vector*. As in, a vector
- that can point to any direction, but length must be one (1.0).
- .. tabs::
- .. code-tab:: gdscript GDScript
- #rotate in Y axis
- var m3 = Basis()
- m3 = m3.rotated( Vector3(0,1,0), PI/2 )
- .. code-tab:: csharp
- // rotate in Y axis
- var m3 = Basis.Identity;
- m3 = m3.Rotated(new Vector3(0, 1, 0), Mathf.Pi / 2);
- Transform
- ~~~~~~~~~
- To add the final component to the mix, Godot provides the
- :ref:`Transform <class_Transform>` type. Transform has two members:
- - *basis* (of type :ref:`Basis <class_Basis>`)
- - *origin* (of type :ref:`Vector3 <class_Vector3>`)
- Any 3D transform can be represented with Transform, and the separation
- of basis and origin makes it easier to work translation and rotation
- separately.
- An example:
- .. tabs::
- .. code-tab:: gdscript GDScript
- var t = Transform()
- pos = t.xform(pos) # transform 3D position
- pos = t.basis.xform(pos) # (only rotate)
- pos = t.origin + pos # (only translate)
- .. code-tab:: csharp
- var t = new Transform(Basis.Identity, Vector3.Zero);
- position = t.Xform(position); // transform 3D position
- position = t.basis.Xform(position); // (only rotate)
- position = t.origin + position; // (only translate)
|