|
@@ -6,725 +6,617 @@ Matrices and transforms
|
|
Introduction
|
|
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.
|
|
|
|
|
|
+Before reading this tutorial, we recommend that you thoroughly read
|
|
|
|
+and understand the :ref:`doc_vector_math` tutorial, as this tutorial
|
|
|
|
+requires a knowledge of vectors.
|
|
|
|
|
|
-This tutorial will be about *transformations* and will cover a little
|
|
|
|
-about matrices (but not in-depth).
|
|
|
|
|
|
+This tutorial is about *transformations* and how we represent them
|
|
|
|
+in Godot using matrices. It is not a full in-depth guide to matrices.
|
|
|
|
+Transformations are most of the time applied as translation, rotation,
|
|
|
|
+and scale, so we will focus on how to represent those with matrices.
|
|
|
|
|
|
-Transformations are most of the time applied as translation, rotation
|
|
|
|
-and scale so they will be considered as priority here.
|
|
|
|
|
|
+Most of this guide focuses on 2D, using :ref:`class_Transform2D` and
|
|
|
|
+:ref:`class_Vector2`, but the way things work in 3D is very similar.
|
|
|
|
|
|
-Oriented coordinate system (OCS)
|
|
|
|
---------------------------------
|
|
|
|
|
|
+.. note:: As mentioned in the previous tutorial, it is important to
|
|
|
|
+ remember that in Godot, the Y axis points *down* in 2D.
|
|
|
|
+ This is the opposite of how most schools teach linear
|
|
|
|
+ algebra, with the Y axis pointing up.
|
|
|
|
|
|
-Imagine we have a spaceship somewhere in space. In Godot this is easy,
|
|
|
|
-just move the ship somewhere and rotate it:
|
|
|
|
|
|
+.. note:: The convention is that the X axis is red, the Y axis is
|
|
|
|
+ green, and the Z axis is blue. This tutorial is color-coded
|
|
|
|
+ to match these conventions, but we will also represent
|
|
|
|
+ the origin vector with a blue color.
|
|
|
|
|
|
-.. image:: img/tutomat1.png
|
|
|
|
|
|
+Matrix components and the Identity matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-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).
|
|
|
|
|
|
+The identity matrix represents a transform with no translation,
|
|
|
|
+no rotation, and no scale. Let's start by looking at the identity
|
|
|
|
+matrix and how its components relate to how it visually appears.
|
|
|
|
|
|
-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.
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/identity.png
|
|
|
|
|
|
-When it was designed, it was not rotated. It was designed in its own
|
|
|
|
-*coordinate system*.
|
|
|
|
|
|
+Matrices have rows and columns, and a transformation matrix has
|
|
|
|
+specific conventions on what each does.
|
|
|
|
|
|
-.. image:: img/tutomat2.png
|
|
|
|
|
|
+In the image above, we can see that the red X vector is represented
|
|
|
|
+by the first column of the matrix, and the green Y vector is
|
|
|
|
+likewise represented by the second column. A change to the columns
|
|
|
|
+will change these vectors. We will see how they can be manipulated
|
|
|
|
+in the next few examples.
|
|
|
|
|
|
-This means that the tip of the ship has a coordinate, the fin has
|
|
|
|
-another, etc. Be it in pixels (2D) or vertices (3D).
|
|
|
|
|
|
+You should not worry about manipulating rows directly, as we usually
|
|
|
|
+work with columns. However, you can think of the rows of the matrix
|
|
|
|
+as showing which vectors contribute to moving in a given direction.
|
|
|
|
|
|
-So, let's recall again that the ship was somewhere in space:
|
|
|
|
|
|
+When we refer to a value such as `t.x.y`, that's the Y component of
|
|
|
|
+the X column vector. In other words, the bottom-left of the matrix.
|
|
|
|
+Similarly, `t.x.x` is top-left, `t.y.x` is top-right,and `t.y.y`
|
|
|
|
+is bottom-right, where `t` is the Transform2D.
|
|
|
|
|
|
-.. image:: img/tutomat3.png
|
|
|
|
|
|
+Scaling the transformation matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-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.
|
|
|
|
|
|
+Applying a scale is one of the easiest operations to understand.
|
|
|
|
+Let's start by placing the Godot logo underneath our vectors
|
|
|
|
+so that we can visually see the effects on an object:
|
|
|
|
|
|
-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/matrices_and_transforms/identity-godot.png
|
|
|
|
|
|
-.. image:: img/tutomat4.png
|
|
|
|
|
|
+Now, to scale the matrix, all we need to do is multiply each
|
|
|
|
+component by the scale we want. Let's scale it up by 2. 1 times 2
|
|
|
|
+becomes 2, and 0 times 2 becomes 0, so we end up with this:
|
|
|
|
|
|
-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/matrices_and_transforms/scale.png
|
|
|
|
|
|
-.. 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:
|
|
|
|
|
|
+To do this in code, we can simply multiply each of the vectors:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- var m = Transform2D()
|
|
|
|
- m = m.rotated(PI/2) # rotate 90°
|
|
|
|
|
|
+ var t = Transform2D()
|
|
|
|
+ # Scale
|
|
|
|
+ t.x *= 2
|
|
|
|
+ t.y *= 2
|
|
|
|
+ transform = t # Change the node's transform to what we just calculated.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ Transform2D t = new Transform2D();
|
|
|
|
+ // Scale
|
|
|
|
+ t.x *= 2;
|
|
|
|
+ t.y *= 2;
|
|
|
|
+ Transform = t; // Change the node's transform to what we just calculated.
|
|
|
|
|
|
- var m = Transform2D.Identity;
|
|
|
|
- m = m.Rotated(Mathf.Pi / 2); // rotate 90°
|
|
|
|
|
|
+If we wanted to return it to its original scale, we can multiply
|
|
|
|
+each component by 0.5. That's pretty much all there is to scaling
|
|
|
|
+a transformation matrix.
|
|
|
|
|
|
-.. image:: img/tutomat12.png
|
|
|
|
|
|
+To calculate the object's scale from an existing transformation
|
|
|
|
+matrix, you can use `length()` on each of the column vectors.
|
|
|
|
|
|
-Translation
|
|
|
|
-~~~~~~~~~~~
|
|
|
|
|
|
+.. note:: In actual projects, you can use the `scaled()`
|
|
|
|
+ method to perform scaling.
|
|
|
|
|
|
-There are two ways to translate a Transform2D, the first one is moving
|
|
|
|
-the origin:
|
|
|
|
|
|
+Rotating the transformation matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+We'll start the same way as earlier, with the Godot logo underneath
|
|
|
|
+the identity matrix:
|
|
|
|
|
|
- # Move 2 units to the right
|
|
|
|
- var m = Transform2D()
|
|
|
|
- m = m.rotated(PI/2) # rotate 90°
|
|
|
|
- m[2] += Vector2(2,0)
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/identity-godot.png
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+As an example, let's say we want to rotate our Godot logo clockwise
|
|
|
|
+by 90 degrees. Right now the X axis points right and the Y axis
|
|
|
|
+points down. If we rotate these in our head, we would logically
|
|
|
|
+see that the new X axis should point down and the new Y axis
|
|
|
|
+should point left.
|
|
|
|
|
|
- // Move 2 units to the right
|
|
|
|
- var m = Transform2D.Identity;
|
|
|
|
- m = m.Rotated(Mathf.Pi / 2); // rotate 90°
|
|
|
|
- m[2] += new Vector2(2, 0);
|
|
|
|
|
|
+You can imagine that you grab both the Godot logo and its vectors,
|
|
|
|
+and then spin it around the center. Wherever you finish spinning,
|
|
|
|
+the orientation of the vectors determines what the matrix is.
|
|
|
|
|
|
-.. image:: img/tutomat13.png
|
|
|
|
|
|
+We need to represent "down" and "left" in normal coordinates,
|
|
|
|
+so means we'll set X to (0, 1) and Y to (-1, 0). These are
|
|
|
|
+also the values of `Vector2.DOWN` and `Vector2.LEFT`.
|
|
|
|
+When we do this, we get the desired result of rotating the object:
|
|
|
|
|
|
-This will always work in global coordinates.
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/rotate1.png
|
|
|
|
|
|
-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:
|
|
|
|
|
|
+If you have trouble understanding the above, try this excercise:
|
|
|
|
+Cut a square of paper, draw X and Y vectors on top of it, place
|
|
|
|
+it on graph paper, then rotate it and note the endpoints.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+To perform rotation in code, we need to be able to calculate
|
|
|
|
+the values programatically. This image shows the formulas needed
|
|
|
|
+to calculate the transformation matrix from a rotation angle.
|
|
|
|
+Don't worry if this part seems complicated, I promise it's the
|
|
|
|
+hardest thing you need to know.
|
|
|
|
|
|
- # 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) )
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/rotate2.png
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+.. note:: Godot represents all rotations with radians, not degrees.
|
|
|
|
+ A full turn is `TAU` or `PI*2` radians, and a quarter
|
|
|
|
+ turn of 90 degrees is `TAU/4` or `PI/2` radians. Working
|
|
|
|
+ with `TAU` usually results in more readable code.
|
|
|
|
|
|
- // 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));
|
|
|
|
|
|
+.. note:: Fun fact: In addition to Y being *down* in Godot, rotation
|
|
|
|
+ is represented clockwise. This means that all the math and
|
|
|
|
+ trig functions behave the same as a Y-is-up CCW system,
|
|
|
|
+ since these differences "cancel out". You can think of
|
|
|
|
+ rotations in both systems being "from X to Y".
|
|
|
|
|
|
-.. image:: img/tutomat14.png
|
|
|
|
|
|
+In order to perform a rotation of 0.5 radians (about 28.65 degrees),
|
|
|
|
+we simply plug in a value of 0.5 to the formula above and evaluate
|
|
|
|
+to find what the actual values should be:
|
|
|
|
|
|
-You could also transform the global coordinates to local coordinates manually:
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/rotate3.png
|
|
|
|
|
|
-.. 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:
|
|
|
|
|
|
+Here's how that would be done in code (place the script on a Node2D):
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- # Make the basis twice its size.
|
|
|
|
- var m = Transform2D()
|
|
|
|
- m = m.scaled( Vector2(2,2) )
|
|
|
|
|
|
+ var t = Transform2D()
|
|
|
|
+ var rot = 0.5 # The rotation to apply.
|
|
|
|
+ t.x.x = cos(rot)
|
|
|
|
+ t.y.y = cos(rot)
|
|
|
|
+ t.x.y = sin(rot)
|
|
|
|
+ t.y.x = -sin(rot)
|
|
|
|
+ transform = t # Change the node's transform to what we just calculated.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ float rot = 0.5f; // The rotation to apply.
|
|
|
|
+ Transform2D t = new Transform2D();
|
|
|
|
+ t.x.x = t.y.y = Mathf.Cos(rot);
|
|
|
|
+ t.x.y = t.y.x = Mathf.Sin(rot);
|
|
|
|
+ t.y.x *= -1;
|
|
|
|
+ Transform = t; // Change the node's transform to what we just calculated.
|
|
|
|
|
|
- // Make the basis twice its size.
|
|
|
|
- var m = Transform2D.Identity;
|
|
|
|
- m = m.Scaled(new Vector2(2, 2));
|
|
|
|
|
|
+To calculate the object's rotation from an existing transformation
|
|
|
|
+matrix, you can use `atan2(t.x.y, t.x.x)`, where t is the Transform2D.
|
|
|
|
|
|
-.. image:: img/tutomat15.png
|
|
|
|
|
|
+.. note:: In actual projects, you can use the `rotated()`
|
|
|
|
+ method to perform rotations.
|
|
|
|
|
|
-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:
|
|
|
|
|
|
+Basis of the transformation matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-.. image:: img/tutomat16.png
|
|
|
|
|
|
+So far we have only been working with the `x` and `y`, vectors, which
|
|
|
|
+are in charge of representing rotation, scale, and/or shearing
|
|
|
|
+(advanced, covered at the end). The X and Y vectors are together
|
|
|
|
+called the *basis* of the transformation matrix. The terms "basis"
|
|
|
|
+and "basis vectors" are important to know.
|
|
|
|
|
|
-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).
|
|
|
|
|
|
+You might have noticed that :ref:`class_Transform2D` actually
|
|
|
|
+has three :ref:`class_Vector2` values: `x`, `y`, and `origin`.
|
|
|
|
+The `origin` value is not part of the basis, but it is part of the
|
|
|
|
+transform, and we need it to represent position. From now on we'll
|
|
|
|
+keep track of the origin vector in all examples. You can think of
|
|
|
|
+origin as another column, but it's often better to think of it as
|
|
|
|
+completely separate.
|
|
|
|
|
|
-Transform
|
|
|
|
-~~~~~~~~~
|
|
|
|
|
|
+Note that in 3D, Godot has a separate :ref:`class_Basis` structure
|
|
|
|
+for holding the three :ref:`class_Vector3` values of the basis,
|
|
|
|
+since the code can get complex and it makes sense to separate
|
|
|
|
+it from :ref:`class_Transform` (which is composed of one
|
|
|
|
+:ref:`class_Basis` and one extra :ref:`class_Vector3` for the origin).
|
|
|
|
|
|
-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.
|
|
|
|
|
|
+Translating the transformation matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+Changing the `origin` vector is called a *translating* the transformation
|
|
|
|
+matrix. Translating is basically a technical term for "moving" the
|
|
|
|
+object, but it explicitly does not involve any rotation.
|
|
|
|
|
|
- var new_pos = m.xform(pos)
|
|
|
|
|
|
+This should be fairly common sense, assuming that you read and
|
|
|
|
+understood the vector tutorial, but let's work through an example.
|
|
|
|
+Again, we'll start with the identity transform, but this time we'll
|
|
|
|
+also keep track of the origin vector:
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/identity-origin.png
|
|
|
|
|
|
- var newPosition = m.Xform(position);
|
|
|
|
|
|
+If we want the object to move to a position of (1, 2), we simply need
|
|
|
|
+to set its `origin` vector to (1, 2):
|
|
|
|
|
|
-And only for basis (no translation):
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/translate.png
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+There is also a `translated()` method, which performs a different
|
|
|
|
+operation to adding or changing `origin` directly. The `translated()`
|
|
|
|
+method will translate the object *relative to its own rotation*.
|
|
|
|
+For example, an object rotated 90 degrees clockwise will move to
|
|
|
|
+the right when `translated()` with `Vector2.UP`.
|
|
|
|
|
|
- var new_pos = m.basis_xform(pos)
|
|
|
|
|
|
+.. note:: Godot's 2D uses coordinates based on pixels, so in actual
|
|
|
|
+ projects you will want to translate by hundreds of units.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+Putting it all together
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
- var newPosition = m.BasisXform(position);
|
|
|
|
|
|
+We're going to apply everything we mentioned so far onto one transform.
|
|
|
|
+To follow along, get a simple project with a Sprite set to the Godot logo.
|
|
|
|
|
|
-Inverse transform
|
|
|
|
-~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
+Let's set the translation to (350, 150), rotate by -0.5 rad, and scale by 3.
|
|
|
|
+I've posted a screenshot, and the code to reproduce it, but I encourage
|
|
|
|
+you to try and reproduce the screenshot without looking at the code!
|
|
|
|
|
|
-To do the opposite operation (what we did up there with the rocket), the
|
|
|
|
-"xform_inv" method is used:
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/putting-all-together.png
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- var new_pos = m.xform_inv(pos)
|
|
|
|
|
|
+ var t = Transform2D()
|
|
|
|
+ # Translation
|
|
|
|
+ t.origin = Vector2(350, 150)
|
|
|
|
+ # Rotation
|
|
|
|
+ var rot = -0.5 # The rotation to apply.
|
|
|
|
+ t.x.x = cos(rot)
|
|
|
|
+ t.y.y = cos(rot)
|
|
|
|
+ t.x.y = sin(rot)
|
|
|
|
+ t.y.x = -sin(rot)
|
|
|
|
+ # Scale
|
|
|
|
+ t.x *= 3
|
|
|
|
+ t.y *= 3
|
|
|
|
+ transform = t # Change the node's transform to what we just calculated.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
-
|
|
|
|
- var newPosition = m.XformInv(position);
|
|
|
|
-
|
|
|
|
-Only for Basis:
|
|
|
|
|
|
+ Transform2D t = new Transform2D();
|
|
|
|
+ // Translation
|
|
|
|
+ t.origin = new Vector2(350, 150);
|
|
|
|
+ // Rotation
|
|
|
|
+ float rot = -0.5f; // The rotation to apply.
|
|
|
|
+ t.x.x = t.y.y = Mathf.Cos(rot);
|
|
|
|
+ t.x.y = t.y.x = Mathf.Sin(rot);
|
|
|
|
+ t.y.x *= -1;
|
|
|
|
+ // Scale
|
|
|
|
+ t.x *= 3;
|
|
|
|
+ t.y *= 3;
|
|
|
|
+ Transform = t; // Change the node's transform to what we just calculated.
|
|
|
|
+
|
|
|
|
+Shearing the transformation matrix (advanced)
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
+
|
|
|
|
+.. note:: If you are only looking for how to *use* transformation matrices,
|
|
|
|
+ feel free to skip this section of the tutorial. This section
|
|
|
|
+ explores an uncommonly used aspect of transformation matrices
|
|
|
|
+ for the purpose of building an understanding of them.
|
|
|
|
+
|
|
|
|
+You may have noticed that a transform has more degrees of freedom than
|
|
|
|
+the combination of the above actions. The basis of a 2D transformation
|
|
|
|
+matrix has four total numbers in two :ref:`class_Vector2` values, while
|
|
|
|
+a rotation value and a Vector2 for scale only has 3 numbers. The high-level
|
|
|
|
+concept for the missing degree of freedom is called *shearing*.
|
|
|
|
+
|
|
|
|
+Normally you will always have the basis vectors perpendicular to each
|
|
|
|
+other. However, shearing can be useful in some situations, and
|
|
|
|
+understanding shearing helps you understand how transforms work.
|
|
|
|
+
|
|
|
|
+To show you visually how it will look, let's overlay a grid onto the Godot
|
|
|
|
+logo:
|
|
|
|
+
|
|
|
|
+.. image:: img/matrices_and_transforms/identity-grid.png
|
|
|
|
+
|
|
|
|
+Each point on this grid is obtained by adding the basis vectors together.
|
|
|
|
+The bottom-right corner is X + Y, while the top-right corner is X - Y.
|
|
|
|
+If we change the basis vectors, the entire grid moves with it, as the
|
|
|
|
+grid is composed of the basis vectors. All lines on the grid that are
|
|
|
|
+currently parallel will remain parallel no matter what changes we make to
|
|
|
|
+the basis vectors.
|
|
|
|
+
|
|
|
|
+As an example, let's set Y to (1, 1):
|
|
|
|
+
|
|
|
|
+.. image:: img/matrices_and_transforms/shear.png
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- var new_pos = m.basis_xform_inv(pos)
|
|
|
|
|
|
+ var t = Transform2D()
|
|
|
|
+ # Shear by setting Y to (1, 1)
|
|
|
|
+ t.y = Vector2.ONE
|
|
|
|
+ transform = t # Change the node's transform to what we just calculated.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ Transform2D t = new Transform2D();
|
|
|
|
+ // Shear by setting Y to (1, 1)
|
|
|
|
+ t.y = Vector2.One;
|
|
|
|
+ Transform = t; // Change the node's transform to what we just calculated.
|
|
|
|
|
|
- var newPosition = m.BasisXformInv(position);
|
|
|
|
|
|
+.. note:: You can't set the raw values of a Transform2D in the editor,
|
|
|
|
+ so you *must* use code if you want to shear the object.
|
|
|
|
|
|
-Orthonormal matrices
|
|
|
|
-^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
+Due to the vectors no longer being perpendicular, the object has been
|
|
|
|
+sheared. The bottom-center of the grid, which is (0, 1) relative
|
|
|
|
+to itself, is now located at a world position of (1, 1).
|
|
|
|
|
|
-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.
|
|
|
|
|
|
+The intra-object coordinates are called UV coordinates in textures,
|
|
|
|
+so let's borrow that terminology for here. To find the world position
|
|
|
|
+from a relative position, the formula is U * X + V * Y, where U and V
|
|
|
|
+are numbers and X and Y are the basis vectors.
|
|
|
|
|
|
-In other words, inverse transform is only valid in *orthonormal*
|
|
|
|
-matrices. For this, these cases an affine inverse must be computed.
|
|
|
|
|
|
+The bottom-right corner of the grid, which is always at the UV position
|
|
|
|
+of (1, 1), is at the world position of (2, 1), which is calculated from
|
|
|
|
+X*1 + Y*1, which is (1, 0) + (1, 1), or (1 + 1, 0 + 1), or (2, 1).
|
|
|
|
+This matches up with our observation of where the bottom-right corner
|
|
|
|
+of the image is.
|
|
|
|
|
|
-The transform, or inverse transform of an identity matrix will return
|
|
|
|
-the position unchanged:
|
|
|
|
|
|
+Similarly, the top-right corner of the grid, which is always at the UV
|
|
|
|
+position of (1, -1), is at the world position of (0, -1), which is calculated
|
|
|
|
+from X*1 + Y*-1, which is (1, 0) - (1, 1), or (1 - 1, 0 - 1), or (0, -1).
|
|
|
|
+This matches up with our observation of where the top-right corner
|
|
|
|
+of the image is.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+Hopefully you now fully understand the how a transformation matrix affects
|
|
|
|
+the object, and the relationship between the basis vectors and how the
|
|
|
|
+object's "UV" or "intra-coordinates" have their world position changed.
|
|
|
|
|
|
- # Does nothing, pos is unchanged
|
|
|
|
- pos = Transform2D().xform(pos)
|
|
|
|
|
|
+.. note:: In Godot, all transform math is done relative to the parent node.
|
|
|
|
+ When we refer to "world position", that would be relative to the
|
|
|
|
+ node's parent instead, if the node had a parent.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
-
|
|
|
|
- // Does nothing, position is unchanged
|
|
|
|
- position = Transform2D.Identity.Xform(position);
|
|
|
|
-
|
|
|
|
-Affine inverse
|
|
|
|
-~~~~~~~~~~~~~~
|
|
|
|
|
|
+If you would like additional explanation, you should check out
|
|
|
|
+3Blue1Brown's excellent video about linear transformations:
|
|
|
|
+https://www.youtube.com/watch?v=kYB8IZa5AuE
|
|
|
|
|
|
-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:
|
|
|
|
|
|
+Practical applications of transforms
|
|
|
|
+------------------------------------
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+In actual projects, you will usually be working with transforms inside
|
|
|
|
+transforms by having multiple :ref:`class_Node2D` or :ref:`class_Spatial`
|
|
|
|
+nodes parented to each other.
|
|
|
|
|
|
- var mi = m.affine_inverse()
|
|
|
|
- pos = m.xform(pos)
|
|
|
|
- pos = mi.xform(pos)
|
|
|
|
- # pos is unchanged
|
|
|
|
|
|
+However, sometimes it's very useful to manually calculate the values we
|
|
|
|
+need. We will go over how you could use :ref:`class_Transform2D` or
|
|
|
|
+:ref:`class_Transform` to manually calculate transforms of nodes.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+Converting positions between transforms
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
- var mi = m.AffineInverse();
|
|
|
|
- position = m.Xform(position);
|
|
|
|
- position = mi.Xform(position);
|
|
|
|
- // position is unchanged
|
|
|
|
|
|
+There are many cases where you'd want to convert a position in and out of
|
|
|
|
+a transform. For example, if you have a position relative to the player
|
|
|
|
+and would like to find the world (parent-relative) position, or if you
|
|
|
|
+have a world position and want to know where it is relative to the player.
|
|
|
|
|
|
-If the matrix is orthonormal, then:
|
|
|
|
|
|
+We can find what a vector relative to the player would be defined in
|
|
|
|
+world space as using the "xform" method:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- # if m is orthonormal, then
|
|
|
|
- pos = mi.xform(pos)
|
|
|
|
- # is the same is
|
|
|
|
- pos = m.xform_inv(pos)
|
|
|
|
|
|
+ # World space vector 100 units below the player.
|
|
|
|
+ print(transform.xform(Vector2(0, 100)))
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ // World space vector 100 units below the player.
|
|
|
|
+ GD.Print(Transform.Xform(new Vector2(0, 100)));
|
|
|
|
|
|
- // 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:
|
|
|
|
|
|
+And we can use the "xform_inv" method to find a what world space position
|
|
|
|
+would be if it was instead defined relative to the player:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- var m = more_transforms * some_transforms
|
|
|
|
|
|
+ # Where is (0, 100) relative to the player?
|
|
|
|
+ print(transform.xform_inv(Vector2(0, 100)))
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ // Where is (0, 100) relative to the player?
|
|
|
|
+ GD.Print(Transform.XformInv(new Vector2(0, 100)));
|
|
|
|
|
|
- var m = moreTransforms * someTransforms;
|
|
|
|
-
|
|
|
|
-To make it a little clearer, this:
|
|
|
|
|
|
+.. note:: If you know in advance that the transform is positioned at
|
|
|
|
+ (0, 0), you can use the "basis_xform" or "basis_xform_inv"
|
|
|
|
+ methods instead, which skip dealing with translation.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+Moving an object relative to itself
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
- pos = transform1.xform(pos)
|
|
|
|
- pos = transform2.xform(pos)
|
|
|
|
|
|
+A common operation, especially in 3D games, is to move an object relative
|
|
|
|
+to itself. For example, in first-person shooter games, you would want the
|
|
|
|
+character to move forward (-Z axis) when you press the W key.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+Since the basis vectors are the orientation relative to the parent,
|
|
|
|
+and the origin vector is the position relative to the parent, we can simply
|
|
|
|
+add multiples of the basis vectors to move an object relative to itself.
|
|
|
|
|
|
- position = transform1.Xform(position);
|
|
|
|
- position = transform2.Xform(position);
|
|
|
|
-
|
|
|
|
-Is the same as:
|
|
|
|
|
|
+This code moves an object 100 units to its own right:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- # note the inverse order
|
|
|
|
- pos = (transform2 * transform1).xform(pos)
|
|
|
|
|
|
+ transform.origin += transform.x * 100
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ Transform2D t = Transform;
|
|
|
|
+ t.origin += t.x * 100;
|
|
|
|
+ Transform = t;
|
|
|
|
|
|
- // note the inverse order
|
|
|
|
- position = (transform2 * transform1).Xform(position);
|
|
|
|
|
|
+For moving in 3D, you would need to replace "x" with "basis.x".
|
|
|
|
|
|
-However, this is not the same:
|
|
|
|
|
|
+.. note:: In actual projects, you can use `translate_object_local` in 3D
|
|
|
|
+ or `move_local_x` and `move_local_y` in 2D to do this.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+Applying transforms onto transforms
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
- # yields a different results
|
|
|
|
- pos = (transform1 * transform2).xform(pos)
|
|
|
|
|
|
+One of the most important things to know about transforms is how you
|
|
|
|
+can use several of them together. A parent node's transform affects
|
|
|
|
+all of its children. Let's dissect an example.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+In this image, the child node has a "2" after the component names
|
|
|
|
+to distinguish them from the parent node. It might look a bit
|
|
|
|
+overwhelming with so many numbers, but remember that each number
|
|
|
|
+is displayed twice (next to the arrows and also in the matrices),
|
|
|
|
+and that almost half of the numbers are zero.
|
|
|
|
|
|
- // yields a different results
|
|
|
|
- position = (transform1 * transform2).Xform(position);
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/apply.png
|
|
|
|
|
|
-Because in matrix math, A * B is not the same as B * A.
|
|
|
|
|
|
+The only transformations going on here are that the parent node has
|
|
|
|
+been given a scale of (2, 1), the child has been given a scale of
|
|
|
|
+(0.5, 0.5), and both nodes have been given positions.
|
|
|
|
|
|
-Multiplication by inverse
|
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
+All child transformations are affected by the parent transformations.
|
|
|
|
+The child has a scale of (0.5, 0.5), so you would expect it to be
|
|
|
|
+a 1:1 ratio square, and it is, but only relative to the parent.
|
|
|
|
+The child's X vector ends up being (1, 0) in world space, because
|
|
|
|
+it is scaled by the parent's basis vectors.
|
|
|
|
+Similarly, the child node's `origin` vector is set to (1, 1), but this
|
|
|
|
+actually moves it (2, 1) in world space, due to the parent node's
|
|
|
|
+basis vectors.
|
|
|
|
|
|
-Multiplying a matrix by its inverse, results in identity:
|
|
|
|
|
|
+To calculate a child transform's world space transform manually, this is
|
|
|
|
+the code we would use:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
|
|
+ # Set up transforms just like in the image, except make positions be 100 times bigger.
|
|
|
|
+ var parent = Transform2D(2, 0, 0, 1, 100, 200)
|
|
|
|
+ var child = Transform2D(0.5, 0, 0, 0.5, 100, 100)
|
|
|
|
|
|
- # No matter what A is, B will be identity
|
|
|
|
- var B = A.affine_inverse() * A
|
|
|
|
|
|
+ # Calculate the child's world space transform
|
|
|
|
+ # origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
|
|
|
|
+ var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin
|
|
|
|
+ # basis_x = (2, 0) * 0.5 + (0, 1) * 0
|
|
|
|
+ var basis_x = parent.x * child.x.x + parent.y * child.x.y
|
|
|
|
+ # basis_y = (2, 0) * 0 + (0, 1) * 0.5
|
|
|
|
+ var basis_y = parent.x * child.y.x + parent.y * child.y.y
|
|
|
|
|
|
- .. 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()
|
|
|
|
|
|
+ # Change the node's transform to what we just calculated.
|
|
|
|
+ transform = Transform2D(basis_x, basis_y, origin)
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ // Set up transforms just like in the image, except make positions be 100 times bigger.
|
|
|
|
+ Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
|
|
|
|
+ Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
|
|
|
|
|
|
- // B will be equal to A
|
|
|
|
- var B = A * Transform2D.Identity;
|
|
|
|
|
|
+ // Calculate the child's world space transform
|
|
|
|
+ // origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
|
|
|
|
+ Vector2 origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin;
|
|
|
|
+ // basisX = (2, 0) * 0.5 + (0, 1) * 0 = (0.5, 0)
|
|
|
|
+ Vector2 basisX = parent.x * child.x.x + parent.y * child.x.y;
|
|
|
|
+ // basisY = (2, 0) * 0 + (0, 1) * 0.5 = (0.5, 0)
|
|
|
|
+ Vector2 basisY = parent.x * child.y.x + parent.y * child.y.y;
|
|
|
|
|
|
-Matrix tips
|
|
|
|
------------
|
|
|
|
|
|
+ // Change the node's transform to what we just calculated.
|
|
|
|
+ Transform = new Transform2D(basisX, basisY, origin);
|
|
|
|
|
|
-When using a transform hierarchy, remember that matrix multiplication is
|
|
|
|
-reversed! To obtain the global transform for a hierarchy, do:
|
|
|
|
|
|
+In actual projects, we can find the world transform of the child by
|
|
|
|
+applying one transform onto another using the `*` operator:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
|
|
+ # Set up transforms just like in the image, except make positions be 100 times bigger.
|
|
|
|
+ var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
|
|
|
|
+ var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))
|
|
|
|
|
|
- var global_xform = parent_matrix * child_matrix
|
|
|
|
|
|
+ # Change the node's transform to what would be the child's world transform.
|
|
|
|
+ transform = parent * child
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ // Set up transforms just like in the image, except make positions be 100 times bigger.
|
|
|
|
+ Transform2D parent = new Transform2D(2, 0, 0, 1, 100, 200);
|
|
|
|
+ Transform2D child = new Transform2D(0.5f, 0, 0, 0.5f, 100, 100);
|
|
|
|
|
|
- var globalTransform = parentMatrix * childMatrix;
|
|
|
|
|
|
+ // Change the node's transform to what would be the child's world transform.
|
|
|
|
+ Transform = parent * child;
|
|
|
|
|
|
-For 3 levels:
|
|
|
|
|
|
+.. note:: When multiplying matrices, order matters! Don't mix them up.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+Lastly, applying the identity transform will always do nothing.
|
|
|
|
|
|
- var global_xform = gradparent_matrix * parent_matrix * child_matrix
|
|
|
|
|
|
+If you would like additional explanation, you should check out
|
|
|
|
+3Blue1Brown's excellent video about matrix composition:
|
|
|
|
+https://www.youtube.com/watch?v=XkY2DOUCWMU
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+Inverting a transformation matrix
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
- var globalTransform = grandparentMatrix * parentMatrix * childMatrix;
|
|
|
|
|
|
+The "affine_inverse" function returns a transform that "undoes" the
|
|
|
|
+previous transform. This can be useful in some situations, but it's
|
|
|
|
+easier to just provide a few examples.
|
|
|
|
|
|
-To make a matrix relative to the parent, use the affine inverse (or
|
|
|
|
-regular inverse for orthonormal matrices).
|
|
|
|
|
|
+Multiplying an inverse transform by the normal transform undoes all
|
|
|
|
+transformations:
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- # transform B from a global matrix to one local to A
|
|
|
|
- var B_local_to_A = A.affine_inverse() * B
|
|
|
|
|
|
+ var ti = transform.affine_inverse()
|
|
|
|
+ var t = ti * transform
|
|
|
|
+ # The transform is the identity transform.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ Transform2D ti = Transform.AffineInverse();
|
|
|
|
+ Transform2D t = ti * Transform;
|
|
|
|
+ // The transform is the identity transform.
|
|
|
|
|
|
- // transform B from a global matrix to one local to A
|
|
|
|
- var bLocalToA = A.AffineInverse() * B;
|
|
|
|
-
|
|
|
|
-Revert it just like the example above:
|
|
|
|
|
|
+Transforming a position by a transform and its inverse results in the
|
|
|
|
+same position (same for "xform_inv"):
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
.. code-tab:: gdscript GDScript
|
|
.. code-tab:: gdscript GDScript
|
|
-
|
|
|
|
- # transform back local B to global B
|
|
|
|
- B = A * B_local_to_A
|
|
|
|
|
|
+ var ti = transform.affine_inverse()
|
|
|
|
+ position = transform.xform(pos)
|
|
|
|
+ position = ti.xform(pos)
|
|
|
|
+ # The position is the same as before.
|
|
|
|
|
|
.. code-tab:: csharp
|
|
.. code-tab:: csharp
|
|
|
|
+ Transform2D ti = Transform.AffineInverse();
|
|
|
|
+ Position = Transform.Xform(Position);
|
|
|
|
+ Position = ti.Xform(Position);
|
|
|
|
+ // The position is the same as before.
|
|
|
|
|
|
- // 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
|
|
|
|
|
|
+How does it all work 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:
|
|
|
|
|
|
+One of the great things about transformation matrices is that they
|
|
|
|
+work very similarly between 2D and 3D transformations.
|
|
|
|
+All of the code and formulas used above for 2D work the same in 3D,
|
|
|
|
+with 3 exceptions: the addition of a third axis, that each
|
|
|
|
+axis is of type :ref:`class_Vector3`, and also that Godot stores
|
|
|
|
+the :ref:`class_Basis` separately from the :ref:`class_Transform`,
|
|
|
|
+since the math can get complex and it makes sense to separate it.
|
|
|
|
|
|
-.. image:: img/tutomat17.png
|
|
|
|
|
|
+All of the concepts for how translation, rotation, scale, and shearing
|
|
|
|
+work in 3D are all the same compared to 2D. To scale, we take each
|
|
|
|
+component and multiply it; to rotate, we change where each basis vector
|
|
|
|
+is pointing; to translate, we manipulate the origin; and to shear, we
|
|
|
|
+change the basis vectors to be non-perpendicular.
|
|
|
|
|
|
-And can be accessed like this:
|
|
|
|
|
|
+.. image:: img/matrices_and_transforms/3d-identity.png
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
|
|
+If you would like, it's a good idea to play around with transforms
|
|
|
|
+to get an understanding of how they work. Godot allows you to edit
|
|
|
|
+3D transform matrices directly from the inspector. You can download
|
|
|
|
+this project which has colored lines and cubes to help visualize the
|
|
|
|
+:ref:`class_Basis` vectors and the origin in both 2D and 3D:
|
|
|
|
+https://github.com/godotengine/godot-demo-projects/tree/master/misc/matrix_transform
|
|
|
|
|
|
- # The Basis constructor will default to Identity
|
|
|
|
- var m = Basis()
|
|
|
|
- print(m)
|
|
|
|
- # prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
|
|
|
|
|
|
+.. note:: Spatial's "Matrix" section in Godot 3.2's inspector
|
|
|
|
+ displays the matrix as transposed, with the columns
|
|
|
|
+ horizontal and the rows vertical. This may be changed
|
|
|
|
+ to be less confusing in a future release of Godot.
|
|
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+.. note:: You cannot edit Node2D's transform matrix directly in Godot 3.2's
|
|
|
|
+ inspector. This may be changed in a future release of Godot.
|
|
|
|
|
|
- // 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))
|
|
|
|
|
|
+If you would like additional explanation, you should check out
|
|
|
|
+3Blue1Brown's excellent video about 3D linear transformations:
|
|
|
|
+https://www.youtube.com/watch?v=rHLEWRxRGiM
|
|
|
|
|
|
- // Instead we can use the Identity property.
|
|
|
|
- var identityBasis = Basis.Identity;
|
|
|
|
- GD.Print(identityBasis);;
|
|
|
|
- // prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
|
|
|
|
|
|
+Representing rotation in 3D (advanced)
|
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-Rotation in 3D
|
|
|
|
-~~~~~~~~~~~~~~
|
|
|
|
|
|
+The biggest difference between 2D and 3D transformation matrices is
|
|
|
|
+how you represent rotation by itself without the basis vectors.
|
|
|
|
|
|
-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.
|
|
|
|
|
|
+With 2D, we have an easy way (atan2) to switch between a transformation
|
|
|
|
+matrix and an angle. In 3D, we can't simply represent rotation as one
|
|
|
|
+number. There is something called Euler angles, which can represent
|
|
|
|
+rotations as a set of 3 numbers, however they are limited and not very
|
|
|
|
+useful, except for trivial cases.
|
|
|
|
|
|
-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).
|
|
|
|
|
|
+In 3D we do not typically use angles, we either use a transformation basis
|
|
|
|
+(used pretty much everywhere in Godot), or we use quaternions. Godot can
|
|
|
|
+represent quaternions using the :ref:`class_Quat` struct. My suggestion
|
|
|
|
+to you is to completely ignore how they work under-the-hood, because
|
|
|
|
+they are very complicated and unintuitive.
|
|
|
|
|
|
-.. tabs::
|
|
|
|
- .. code-tab:: gdscript GDScript
|
|
|
|
-
|
|
|
|
- #rotate in Y axis
|
|
|
|
- var m3 = Basis()
|
|
|
|
- m3 = m3.rotated( Vector3(0,1,0), PI/2 )
|
|
|
|
-
|
|
|
|
- .. code-tab:: csharp
|
|
|
|
|
|
+However, if you really must know how it works, here are some great
|
|
|
|
+resources, which you can follow in order:
|
|
|
|
|
|
- // rotate in Y axis
|
|
|
|
- var m3 = Basis.Identity;
|
|
|
|
- m3 = m3.Rotated(new Vector3(0, 1, 0), Mathf.Pi / 2);
|
|
|
|
|
|
+https://www.youtube.com/watch?v=mvmuCPvRoWQ
|
|
|
|
|
|
-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
|
|
|
|
|
|
+https://www.youtube.com/watch?v=d4EgbgTm0Bg
|
|
|
|
|
|
- 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)
|
|
|
|
|
|
+https://eater.net/quaternions
|