diff --git a/tutorials/math/img/empty_grid.png b/tutorials/math/img/empty_grid.png new file mode 100644 index 000000000..b8bd3b955 Binary files /dev/null and b/tutorials/math/img/empty_grid.png differ diff --git a/tutorials/math/img/matrices_and_transforms/3d-identity.png b/tutorials/math/img/matrices_and_transforms/3d-identity.png new file mode 100644 index 000000000..b212ae5d3 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/3d-identity.png differ diff --git a/tutorials/math/img/matrices_and_transforms/3d-identity.xcf b/tutorials/math/img/matrices_and_transforms/3d-identity.xcf new file mode 100644 index 000000000..245436083 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/3d-identity.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/apply.png b/tutorials/math/img/matrices_and_transforms/apply.png new file mode 100644 index 000000000..926fe2bfc Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/apply.png differ diff --git a/tutorials/math/img/matrices_and_transforms/apply.xcf b/tutorials/math/img/matrices_and_transforms/apply.xcf new file mode 100644 index 000000000..9289754a2 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/apply.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-godot.png b/tutorials/math/img/matrices_and_transforms/identity-godot.png new file mode 100644 index 000000000..4486aa31e Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-godot.png differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-godot.xcf b/tutorials/math/img/matrices_and_transforms/identity-godot.xcf new file mode 100644 index 000000000..b7ed84499 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-godot.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-grid.png b/tutorials/math/img/matrices_and_transforms/identity-grid.png new file mode 100644 index 000000000..dc5084c55 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-grid.png differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-grid.xcf b/tutorials/math/img/matrices_and_transforms/identity-grid.xcf new file mode 100644 index 000000000..3a08166c1 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-grid.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-origin.png b/tutorials/math/img/matrices_and_transforms/identity-origin.png new file mode 100644 index 000000000..b9effbbeb Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-origin.png differ diff --git a/tutorials/math/img/matrices_and_transforms/identity-origin.xcf b/tutorials/math/img/matrices_and_transforms/identity-origin.xcf new file mode 100644 index 000000000..1fab2d6f3 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity-origin.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/identity.png b/tutorials/math/img/matrices_and_transforms/identity.png new file mode 100644 index 000000000..b302a1362 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity.png differ diff --git a/tutorials/math/img/matrices_and_transforms/identity.xcf b/tutorials/math/img/matrices_and_transforms/identity.xcf new file mode 100644 index 000000000..b1befbe1a Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/identity.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/putting-all-together.png b/tutorials/math/img/matrices_and_transforms/putting-all-together.png new file mode 100644 index 000000000..4bf440ddb Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/putting-all-together.png differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate1.png b/tutorials/math/img/matrices_and_transforms/rotate1.png new file mode 100644 index 000000000..4c90bb2ed Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate1.png differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate1.xcf b/tutorials/math/img/matrices_and_transforms/rotate1.xcf new file mode 100644 index 000000000..db01af3b0 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate1.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate2.png b/tutorials/math/img/matrices_and_transforms/rotate2.png new file mode 100644 index 000000000..f475a8cad Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate2.png differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate2.xcf b/tutorials/math/img/matrices_and_transforms/rotate2.xcf new file mode 100644 index 000000000..ad28a574d Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate2.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate3.png b/tutorials/math/img/matrices_and_transforms/rotate3.png new file mode 100644 index 000000000..022a10f58 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate3.png differ diff --git a/tutorials/math/img/matrices_and_transforms/rotate3.xcf b/tutorials/math/img/matrices_and_transforms/rotate3.xcf new file mode 100644 index 000000000..3a2ef312e Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/rotate3.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/scale.png b/tutorials/math/img/matrices_and_transforms/scale.png new file mode 100644 index 000000000..f78d8b2c6 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/scale.png differ diff --git a/tutorials/math/img/matrices_and_transforms/scale.xcf b/tutorials/math/img/matrices_and_transforms/scale.xcf new file mode 100644 index 000000000..6c5babafa Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/scale.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/shear.png b/tutorials/math/img/matrices_and_transforms/shear.png new file mode 100644 index 000000000..14480de49 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/shear.png differ diff --git a/tutorials/math/img/matrices_and_transforms/shear.xcf b/tutorials/math/img/matrices_and_transforms/shear.xcf new file mode 100644 index 000000000..007e3206b Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/shear.xcf differ diff --git a/tutorials/math/img/matrices_and_transforms/translate.png b/tutorials/math/img/matrices_and_transforms/translate.png new file mode 100644 index 000000000..34af38a4a Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/translate.png differ diff --git a/tutorials/math/img/matrices_and_transforms/translate.xcf b/tutorials/math/img/matrices_and_transforms/translate.xcf new file mode 100644 index 000000000..8c34aa827 Binary files /dev/null and b/tutorials/math/img/matrices_and_transforms/translate.xcf differ diff --git a/tutorials/math/img/tutomat1.png b/tutorials/math/img/tutomat1.png deleted file mode 100644 index 2cde12f7c..000000000 Binary files a/tutorials/math/img/tutomat1.png and /dev/null differ diff --git a/tutorials/math/img/tutomat10.png b/tutorials/math/img/tutomat10.png deleted file mode 100644 index 7683c10a0..000000000 Binary files a/tutorials/math/img/tutomat10.png and /dev/null differ diff --git a/tutorials/math/img/tutomat11.png b/tutorials/math/img/tutomat11.png deleted file mode 100644 index e8c583896..000000000 Binary files a/tutorials/math/img/tutomat11.png and /dev/null differ diff --git a/tutorials/math/img/tutomat12.png b/tutorials/math/img/tutomat12.png deleted file mode 100644 index 329596276..000000000 Binary files a/tutorials/math/img/tutomat12.png and /dev/null differ diff --git a/tutorials/math/img/tutomat13.png b/tutorials/math/img/tutomat13.png deleted file mode 100644 index 8fb70721b..000000000 Binary files a/tutorials/math/img/tutomat13.png and /dev/null differ diff --git a/tutorials/math/img/tutomat14.png b/tutorials/math/img/tutomat14.png deleted file mode 100644 index 860e284db..000000000 Binary files a/tutorials/math/img/tutomat14.png and /dev/null differ diff --git a/tutorials/math/img/tutomat15.png b/tutorials/math/img/tutomat15.png deleted file mode 100644 index 4b6728df6..000000000 Binary files a/tutorials/math/img/tutomat15.png and /dev/null differ diff --git a/tutorials/math/img/tutomat16.png b/tutorials/math/img/tutomat16.png deleted file mode 100644 index 967baca5b..000000000 Binary files a/tutorials/math/img/tutomat16.png and /dev/null differ diff --git a/tutorials/math/img/tutomat17.png b/tutorials/math/img/tutomat17.png deleted file mode 100644 index b9645a540..000000000 Binary files a/tutorials/math/img/tutomat17.png and /dev/null differ diff --git a/tutorials/math/img/tutomat2.png b/tutorials/math/img/tutomat2.png deleted file mode 100644 index e74a88439..000000000 Binary files a/tutorials/math/img/tutomat2.png and /dev/null differ diff --git a/tutorials/math/img/tutomat3.png b/tutorials/math/img/tutomat3.png deleted file mode 100644 index 7cb2bba12..000000000 Binary files a/tutorials/math/img/tutomat3.png and /dev/null differ diff --git a/tutorials/math/img/tutomat4.png b/tutorials/math/img/tutomat4.png deleted file mode 100644 index a5a22c32b..000000000 Binary files a/tutorials/math/img/tutomat4.png and /dev/null differ diff --git a/tutorials/math/img/tutomat5.png b/tutorials/math/img/tutomat5.png deleted file mode 100644 index 30c267abd..000000000 Binary files a/tutorials/math/img/tutomat5.png and /dev/null differ diff --git a/tutorials/math/img/tutomat6.png b/tutorials/math/img/tutomat6.png deleted file mode 100644 index ac9b0c1a7..000000000 Binary files a/tutorials/math/img/tutomat6.png and /dev/null differ diff --git a/tutorials/math/img/tutomat7.png b/tutorials/math/img/tutomat7.png deleted file mode 100644 index 6dc1a4c75..000000000 Binary files a/tutorials/math/img/tutomat7.png and /dev/null differ diff --git a/tutorials/math/img/tutomat8.png b/tutorials/math/img/tutomat8.png deleted file mode 100644 index 8b52d0902..000000000 Binary files a/tutorials/math/img/tutomat8.png and /dev/null differ diff --git a/tutorials/math/img/tutomat9.png b/tutorials/math/img/tutomat9.png deleted file mode 100644 index 13574b283..000000000 Binary files a/tutorials/math/img/tutomat9.png and /dev/null differ diff --git a/tutorials/math/matrices_and_transforms.rst b/tutorials/math/matrices_and_transforms.rst index dc2a10727..06b5837a5 100644 --- a/tutorials/math/matrices_and_transforms.rst +++ b/tutorials/math/matrices_and_transforms.rst @@ -6,725 +6,617 @@ 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. +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): +To do this in code, we can simply multiply each of the vectors: .. tabs:: .. code-tab:: gdscript GDScript - - var new_pos = pos - origin + 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 + Transform2D t = new Transform2D(); + // Scale + t.x *= 2; + t.y *= 2; + Transform = t; // Change the node's transform to what we just calculated. - var newPosition = pos - origin; +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. -Doing this to the selected point will move it back to the center: +To calculate the object's scale from an existing transformation +matrix, you can use `length()` on each of the column vectors. -.. image:: img/tutomat8.png +.. note:: In actual projects, you can use the `scaled()` + method to perform scaling. -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: +Rotating the transformation matrix +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We'll start the same way as earlier, with the Godot logo underneath +the identity matrix: + +.. image:: img/matrices_and_transforms/identity-godot.png + +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. + +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. + +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: + +.. image:: img/matrices_and_transforms/rotate1.png + +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. + +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. + +.. image:: img/matrices_and_transforms/rotate2.png + +.. 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. + +.. 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". + +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: + +.. image:: img/matrices_and_transforms/rotate3.png + +Here's how that would be done in code (place the script on a Node2D): .. tabs:: .. code-tab:: gdscript GDScript - - var final_pos = Vector2(x.dot(new_pos), y.dot(new_pos)) + 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 + 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. - var finalPosition = new Vector2(x.Dot(newPosition), y.Dot(newPosition)); +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. -Then what we have is.. wait a minute, it's the ship in its design -position! +.. note:: In actual projects, you can use the `rotated()` + method to perform rotations. -.. image:: img/tutomat9.png +Basis of the transformation matrix +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -How did this black magic happen? The ship was lost in space, and now -it's back home! +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. -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. +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. -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. +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). -Basis ------ +Translating the transformation matrix +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -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. +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. -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*." +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: -So, let's change the representation of the basis. Instead of 2 vectors, -let's use a *matrix*. +.. image:: img/matrices_and_transforms/identity-origin.png -.. image:: img/tutomat10.png +If we want the object to move to a position of (1, 2), we simply need +to set its `origin` vector to (1, 2): -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. +.. image:: img/matrices_and_transforms/translate.png -Transforms in Godot -------------------- +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`. -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. +.. note:: Godot's 2D uses coordinates based on pixels, so in actual + projects you will want to translate by hundreds of units. -Transform2D -~~~~~~~~~~~ +Putting it all together +~~~~~~~~~~~~~~~~~~~~~~~ -: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. +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. + +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! + +.. image:: img/matrices_and_transforms/putting-all-together.png .. tabs:: .. code-tab:: gdscript GDScript - - var m = Transform2D() - var x = m[0] # 'X' - var y = m[1] # 'Y' - var o = m[2] # 'Origin' + 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 + 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. - var m = new Transform2D(); - Vector2 x = m[0]; // 'X' - Vector2 y = m[1]; // 'Y' - Vector2 o = m[2]; // 'Origin' +Shearing the transformation matrix (advanced) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Most operations will be explained with this datatype (Transform2D), but the -same logic applies to 3D. +.. 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. -Identity -~~~~~~~~ +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*. -An important transform is the "identity" matrix. This means: +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. -- 'X' Points right: Vector2(1,0) -- 'Y' Points up (or down in pixels): Vector2(0,1) -- 'Origin' is the origin Vector2(0,0) +To show you visually how it will look, let's overlay a grid onto the Godot +logo: -.. image:: img/tutomat11.png +.. image:: img/matrices_and_transforms/identity-grid.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. +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:: .. code-tab:: gdscript GDScript - - # The Transform2D constructor will default to Identity - var m = Transform2D() - print(m) - # prints: ((1, 0), (0, 1), (0, 0)) - + 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 + 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. - // 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)) +.. 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. - // Instead we can use the Identity property. - var identityTransform = Transform2D.Identity; - GD.Print(identityTransform); - // prints: ((1, 0), (0, 1), (0, 0)) +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). -Operations ----------- +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. -Rotation -~~~~~~~~ +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. -Rotating Transform2D is done by using the "rotated" function: +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. + +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. + +.. 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. + +If you would like additional explanation, you should check out +3Blue1Brown's excellent video about linear transformations: +https://www.youtube.com/watch?v=kYB8IZa5AuE + +Practical applications of transforms +------------------------------------ + +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. + +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. + +Converting positions between transforms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +We can find what a vector relative to the player would be defined in +world space as using the "xform" method: .. tabs:: .. code-tab:: gdscript GDScript - - var m = Transform2D() - m = m.rotated(PI/2) # rotate 90° + # World space vector 100 units below the player. + print(transform.xform(Vector2(0, 100))) .. code-tab:: csharp + // World space vector 100 units below the player. + GD.Print(Transform.Xform(new Vector2(0, 100))); - 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: +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:: .. 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) + # Where is (0, 100) relative to the player? + print(transform.xform_inv(Vector2(0, 100))) .. code-tab:: csharp + // Where is (0, 100) relative to the player? + GD.Print(Transform.XformInv(new Vector2(0, 100))); - // Move 2 units to the right - var m = Transform2D.Identity; - m = m.Rotated(Mathf.Pi / 2); // rotate 90° - m[2] += new Vector2(2, 0); +.. 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. -.. image:: img/tutomat13.png +Moving an object relative to itself +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This will always work in global coordinates. +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. -If instead, translation is desired in *local* coordinates of the -matrix (towards where the *basis* is oriented), there is the -:ref:`Transform2D.translated() ` -method: +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. + +This code moves an object 100 units to its own right: .. 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) ) + transform.origin += transform.x * 100 .. code-tab:: csharp + Transform2D t = Transform; + t.origin += t.x * 100; + Transform = t; - // 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)); +For moving in 3D, you would need to replace "x" with "basis.x". -.. image:: img/tutomat14.png +.. 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. -You could also transform the global coordinates to local coordinates manually: +Applying transforms onto transforms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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. + +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. + +.. image:: img/matrices_and_transforms/apply.png + +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. + +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. + +To calculate a child transform's world space transform manually, this is +the code we would use: .. tabs:: .. 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) - var local_pos = m.xform_inv(point) + # 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 + + # Change the node's transform to what we just calculated. + transform = Transform2D(basis_x, basis_y, origin) .. 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 localPosition = m.XformInv(point); + // 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; -But even better, there are helper functions for this as you can read in the next sections. + // Change the node's transform to what we just calculated. + Transform = new Transform2D(basisX, basisY, origin); -Local to global coordinates and vice versa -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are helper methods for converting between local and global coordinates. - -There are :ref:`Node2D.to_local() ` and :ref:`Node2D.to_global() ` for 2D -as well as :ref:`Spatial.to_local() ` and :ref:`Spatial.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: +In actual projects, we can find the world transform of the child by +applying one transform onto another using the `*` operator: .. tabs:: .. 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)) - # Make the basis twice its size. - var m = Transform2D() - m = m.scaled( Vector2(2,2) ) + # Change the node's transform to what would be the child's world transform. + transform = parent * child .. 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); - // Make the basis twice its size. - var m = Transform2D.Identity; - m = m.Scaled(new Vector2(2, 2)); + // Change the node's transform to what would be the child's world transform. + Transform = parent * child; -.. image:: img/tutomat15.png +.. note:: When multiplying matrices, order matters! Don't mix them up. -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: +Lastly, applying the identity transform will always do nothing. -.. image:: img/tutomat16.png +If you would like additional explanation, you should check out +3Blue1Brown's excellent video about matrix composition: +https://www.youtube.com/watch?v=XkY2DOUCWMU -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). +Inverting a transformation matrix +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Transform -~~~~~~~~~ +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. -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. +Multiplying an inverse transform by the normal transform undoes all +transformations: .. tabs:: .. code-tab:: gdscript GDScript - - var new_pos = m.xform(pos) + var ti = transform.affine_inverse() + var t = ti * transform + # The transform is the identity transform. .. code-tab:: csharp + Transform2D ti = Transform.AffineInverse(); + Transform2D t = ti * Transform; + // The transform is the identity transform. - var newPosition = m.Xform(position); - -And only for basis (no translation): +Transforming a position by a transform and its inverse results in the +same position (same for "xform_inv"): .. tabs:: .. code-tab:: gdscript GDScript - - var new_pos = m.basis_xform(pos) + var ti = transform.affine_inverse() + position = transform.xform(pos) + position = ti.xform(pos) + # The position is the same as before. .. code-tab:: csharp + Transform2D ti = Transform.AffineInverse(); + Position = Transform.Xform(Position); + Position = ti.Xform(Position); + // The position is the same as before. - 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 +How does it all work in 3D? --------------------------- -As mentioned before, for 3D, we deal with 3 :ref:`Vector3 ` -vectors for the rotation matrix, and an extra one for the origin. +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. -Basis -~~~~~ +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. -Godot has a special type for a 3x3 matrix, named :ref:`Basis `. -It can be used to represent a 3D rotation and scale. Sub vectors can be -accessed as: +.. 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 - var m = Basis() - var x = m[0] # Vector3 - var y = m[1] # Vector3 - var z = m[2] # Vector3 +.. 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. - var m = new Basis(); - Vector3 x = m[0]; - Vector3 y = m[1]; - Vector3 z = m[2]; +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 -Or, alternatively as: +Representing rotation in 3D (advanced) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. tabs:: - .. code-tab:: gdscript GDScript +The biggest difference between 2D and 3D transformation matrices is +how you represent rotation by itself without the basis vectors. - var m = Basis() - var x = m.x # Vector3 - var y = m.y # Vector3 - var z = m.z # Vector3 +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. - .. code-tab:: csharp +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. - var m = new Basis(); - Vector3 x = m.x; - Vector3 y = m.y; - Vector3 z = m.z; +However, if you really must know how it works, here are some great +resources, which you can follow in order: -The Identity Basis has the following values: +https://www.youtube.com/watch?v=mvmuCPvRoWQ -.. image:: img/tutomat17.png +https://www.youtube.com/watch?v=d4EgbgTm0Bg -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 ` type. Transform has two members: - -- *basis* (of type :ref:`Basis `) -- *origin* (of type :ref:`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) +https://eater.net/quaternions