From bf817ddfc3629efdd4aed66a11903623e16d4e39 Mon Sep 17 00:00:00 2001 From: Kelly thomas Date: Mon, 23 Apr 2018 01:32:49 +0800 Subject: [PATCH] c sharp samples for using 3D transforms --- tutorials/3d/using_transforms.rst | 164 +++++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 23 deletions(-) diff --git a/tutorials/3d/using_transforms.rst b/tutorials/3d/using_transforms.rst index a6587e06a..d76fd142a 100644 --- a/tutorials/3d/using_transforms.rst +++ b/tutorials/3d/using_transforms.rst @@ -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.