|
@@ -95,7 +95,8 @@ A transform has a :ref:`class_Basis` (transform.basis sub-property), which consi
|
|
|
|
|
|
A default basis (unmodified) is akin to:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
var basis = Basis()
|
|
|
# Contains the following default values:
|
|
@@ -103,6 +104,23 @@ A default basis (unmodified) is akin to:
|
|
|
basis.y = Vector3(0, 1, 0) # Vector pointing along the Y axis
|
|
|
basis.z = Vector3(0, 0, 1) # Vector pointing along the Z axis
|
|
|
|
|
|
+ .. 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.x); // prints: (1, 0, 0)
|
|
|
+ GD.Print(identityBasis.y); // prints: (0, 1, 0)
|
|
|
+ GD.Print(identityBasis.z); // prints: (0, 0, 1)
|
|
|
+
|
|
|
+ // The Identity basis is equivalent to:
|
|
|
+ var basis = new Basis(Vector3.Right, Vector3.Up, Vector3.Back);
|
|
|
+ GD.Print(basis); // prints: ((1, 0, 0), (0, 1, 0), (0, 0, 1))
|
|
|
+
|
|
|
This is also an analog to a 3x3 identity matrix.
|
|
|
|
|
|
Following the OpenGL convention, ``X`` is the *Right* axis, ``Y`` is the *Up* axis and ``Z`` is the *Forward* axis.
|
|
@@ -129,34 +147,53 @@ Of course, transforms are not as straightforward to manipulate as angles and hav
|
|
|
|
|
|
It is possible to rotate a transform, either by multiplying its basis by another (this is called accumulation), or by using the rotation methods.
|
|
|
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
-.. code-block:: python
|
|
|
-
|
|
|
- # Rotate the transform in X axis
|
|
|
+ # Rotate the transform about the X axis
|
|
|
transform.basis = Basis(Vector3(1, 0, 0), PI) * transform.basis
|
|
|
# shortened
|
|
|
transform.basis = transform.basis.rotated(Vector3(1, 0, 0), PI)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // rotate the transform about the X axis
|
|
|
+ transform.basis = new Basis(Vector3.Right, Mathf.Pi) * transform.basis;
|
|
|
+ // shortened
|
|
|
+ transform.basis = transform.basis.Rotated(Vector3.Right, Mathf.Pi);
|
|
|
+
|
|
|
A method in Spatial simplifies this:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# Rotate the transform in X axis
|
|
|
rotate(Vector3(1, 0, 0), PI)
|
|
|
# shortened
|
|
|
rotate_x(PI)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Rotate the transform about the X axis
|
|
|
+ Rotate(Vector3.Right, Mathf.Pi);
|
|
|
+ // shortened
|
|
|
+ RotateX(Mathf.Pi);
|
|
|
+
|
|
|
This rotates the node relative to the parent node.
|
|
|
|
|
|
To rotate relative to object space (the node's own transform) use the following:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
- # Rotate locally, notice multiplication order is inverted
|
|
|
- transform = transform * Basis(Vector3(1, 0, 0), PI)
|
|
|
- # shortened
|
|
|
+ # Rotate locally
|
|
|
rotate_object_local(Vector3(1, 0, 0), PI)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Rotate locally
|
|
|
+ RotateObjectLocal(Vector3.Right, Mathf.Pi);
|
|
|
+
|
|
|
Precision Errors
|
|
|
================
|
|
|
|
|
@@ -166,19 +203,29 @@ If a transform is rotated every frame, it will eventually start deforming over t
|
|
|
|
|
|
There are two different ways to handle this. The first is to *orthonormalize* the transform after some time (maybe once per frame if you modify it every frame):
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
transform = transform.orthonormalized()
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ transform = transform.Orthonormalized();
|
|
|
+
|
|
|
This will make all axes have ``1.0`` length again and be ``90`` degrees from each other. However, any scale applied to the transform will be lost.
|
|
|
|
|
|
It is recommended you don't scale nodes that are going to be manipulated. Scale their children nodes instead (such as MeshInstance). If you absolutely must scale the node, then re-apply it at the end:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
transform = transform.orthonormalized()
|
|
|
transform = transform.scaled(scale)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ transform = transform.Orthonormalized();
|
|
|
+ transform = transform.Scaled(scale);
|
|
|
|
|
|
Obtaining Information
|
|
|
=====================
|
|
@@ -187,31 +234,57 @@ You might be thinking at this point: **"Ok, but how do I get angles from a trans
|
|
|
|
|
|
Imagine you need to shoot a bullet in the direction your player is facing. Just use the forward axis (commonly ``Z`` or ``-Z``).
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
bullet.transform = transform
|
|
|
bullet.speed = transform.basis.z * BULLET_SPEED
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ bullet.Transform = transform;
|
|
|
+ bullet.LinearVelocity = transform.basis.z * BulletSpeed;
|
|
|
+
|
|
|
Is the enemy looking at the player? Use the dot product for this (see the :ref:`doc_vector_math` tutorial for an explanation of the dot product):
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# Get the direction vector from player to enemy
|
|
|
var direction = enemy.transform.origin - player.transform.origin
|
|
|
if direction.dot(enemy.transform.basis.z) > 0:
|
|
|
- enemy.im_watching_you(player)
|
|
|
+ enemy.im_watching_you(player)
|
|
|
+
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Get the direction vector from player to enemy
|
|
|
+ Vector3 direction = enemy.Transform.origin - player.Transform.origin;
|
|
|
+ if (direction.Dot(enemy.Transform.basis.z) > 0)
|
|
|
+ {
|
|
|
+ enemy.ImWatchingYou(player);
|
|
|
+ }
|
|
|
|
|
|
Strafe left:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# Remember that +X is right
|
|
|
if Input.is_action_pressed("strafe_left"):
|
|
|
- translate_object_local(-transform.basis.x)
|
|
|
+ translate_object_local(-transform.basis.x)
|
|
|
+
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Remember that +X is right
|
|
|
+ if (Input.IsActionPressed("strafe_left"))
|
|
|
+ {
|
|
|
+ TranslateObjectLocal(-Transform.basis.x);
|
|
|
+ }
|
|
|
|
|
|
Jump:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# Keep in mind Y is up-axis
|
|
|
if Input.is_action_just_pressed("jump"):
|
|
@@ -219,6 +292,15 @@ Jump:
|
|
|
|
|
|
velocity = move_and_slide(velocity)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Keep in mind Y is up-axis
|
|
|
+ if (Input.IsActionJustPressed("jump"))
|
|
|
+ {
|
|
|
+ velocity.y = JumpSpeed;
|
|
|
+ }
|
|
|
+ velocity = MoveAndSlide(velocity);
|
|
|
+
|
|
|
All common behaviors and logic can be done with just vectors.
|
|
|
|
|
|
Setting Information
|
|
@@ -230,22 +312,47 @@ For such cases, keep the angles and rotations *outside* the transform and set th
|
|
|
|
|
|
Example of looking around, FPS style:
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# accumulators
|
|
|
var rot_x = 0
|
|
|
var rot_y = 0
|
|
|
|
|
|
func _input(event):
|
|
|
-
|
|
|
- if event is InputEventMouseMotion and ev.button_mask & 1:
|
|
|
+ if event is InputEventMouseMotion and event.button_mask & 1:
|
|
|
# modify accumulated mouse rotation
|
|
|
- rot_x += ev.relative.x * LOOKAROUND_SPEED
|
|
|
- rot_y += ev.relative.y * LOOKAROUND_SPEED
|
|
|
+ rot_x += event.relative.x * LOOKAROUND_SPEED
|
|
|
+ rot_y += event.relative.y * LOOKAROUND_SPEED
|
|
|
transform.basis = Basis() # reset rotation
|
|
|
rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y
|
|
|
rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // accumulators
|
|
|
+ private float _rotationX = 0f;
|
|
|
+ private float _rotationY = 0f;
|
|
|
+
|
|
|
+ public override void _Input(InputEvent @event)
|
|
|
+ {
|
|
|
+ var mouseMotion = @event as InputEventMouseMotion;
|
|
|
+ if (mouseMotion != null)
|
|
|
+ {
|
|
|
+ // modify accumulated mouse rotation
|
|
|
+ _rotationX += mouseMotion.Relative.x * LookAroundSpeed;
|
|
|
+ _rotationY += mouseMotion.Relative.y * LookAroundSpeed;
|
|
|
+
|
|
|
+ // reset rotation
|
|
|
+ Transform transform = Transform;
|
|
|
+ transform.basis = Basis.Identity;
|
|
|
+ Transform = transform;
|
|
|
+
|
|
|
+ RotateObjectLocal(Vector3.Up, _rotationX); // first rotate about Y
|
|
|
+ RotateObjectLocal(Vector3.Right, _rotationY); // then rotate about X
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
As you can see, in such cases it's even simpler to keep the rotation outside, then use the transform as the *final* orientation.
|
|
|
|
|
|
Interpolating with Quaternions
|
|
@@ -255,7 +362,8 @@ Interpolating between two transforms can efficiently be done with quaternions. M
|
|
|
|
|
|
Converting a rotation to quaternion is straightforward.
|
|
|
|
|
|
-.. code-block:: python
|
|
|
+.. tabs::
|
|
|
+ .. code-tab:: gdscript GDScript
|
|
|
|
|
|
# Convert basis to quaternion, keep in mind scale is lost
|
|
|
var a = Quat(transform.basis)
|
|
@@ -265,6 +373,16 @@ Converting a rotation to quaternion is straightforward.
|
|
|
# Apply back
|
|
|
transform.basis = Basis(c)
|
|
|
|
|
|
+ .. code-tab:: csharp
|
|
|
+
|
|
|
+ // Convert basis to quaternion, keep in mind scale is lost
|
|
|
+ var a = transform.basis.Quat();
|
|
|
+ var b = transform.basis.Quat();
|
|
|
+ // Interpolate using spherical-linear interpolation (SLERP).
|
|
|
+ var c = a.Slerp(b, 0.5f); // find halfway point between a and b
|
|
|
+ // Apply back
|
|
|
+ transform.basis = new Basis(c);
|
|
|
+
|
|
|
The :ref:`class_Quat` type reference has more information on the datatype (it can also do transform accumulation, transform points, etc. though this is used less often). If you interpolate or apply operations to quaternions many times, keep in mind they need to be eventually normalized or they also may suffer from numerical precision errors.
|
|
|
|
|
|
Quaternions are very useful when doing camera/path/etc. interpolations, as the result will be always correct and smooth.
|