From 0089c3b7370359427d6898bcf2a134d4f8327000 Mon Sep 17 00:00:00 2001 From: Elie Thorne Date: Thu, 4 Sep 2025 18:23:56 +0200 Subject: [PATCH 1/4] Add section about angular velocity to using_transforms.rst How to calculate angular velocity doesn't seem to be anywhere in the online doc, and it is very easy to incorrectly assume that they are Euler angles, which happened to me and was reported by others. Since this is where quaternions and their importance are explained, it seems a good place to explain how to obtain correct angular velocity from quaternions. This is a subject I had considerable difficulties with, and could only solve it thanks to the help of others. Notably the code here is simplified from code helpfully shared by Norgorim (any error is however mine). Hence the need for an explanation in the documentation, and this appears to me as at least the necessary minimum. --- tutorials/3d/using_transforms.rst | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tutorials/3d/using_transforms.rst b/tutorials/3d/using_transforms.rst index df9eccd09d3..862fda6076f 100644 --- a/tutorials/3d/using_transforms.rst +++ b/tutorials/3d/using_transforms.rst @@ -400,6 +400,85 @@ suffer from numerical precision errors. Quaternions are useful when doing camera/path/etc. interpolations, as the result will always be correct and smooth. +Angular velocity and quaternions +================================ + +While angular velocity is stored in a :ref:`class_Vector3`, it is not in fact a vector, and using Euler angles for angular velocity will result in errors. The correct value can be obtained from a quaternion, by multiplying the axis vector with the angle. + +Example of making a :ref:`class_RigidBody3D` rotate over time so it ends up with the same orientation than another node two seconds later: + +.. tabs:: + .. code-tab:: gdscript GDScript + + func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: + var a = global_basis.get_rotation_quaternion() + var b = other_node.global_basis.get_rotation_quaternion() + # Interpolate the orientation for the next frame + var c = a.slerp(b, 0.5 * state.step) + var d = (c * a.inverse()).normalized() + state.angular_velocity = d.get_axis() * (d.get_angle() / state.step) + + .. code-tab:: csharp + + _IntegrateForces(PhysicsDirectBodyState3D state) + var a = new Quaternion(transform.Basis); + var b = new Quaternion(transform2.Basis); + // Interpolate using spherical-linear interpolation (SLERP). + var c = a.Slerp(b, 0.1f * state.Step); + var d = (c * a.Inverse()).Normalized(); + state.AngularVelocity = d.getAxis() * (d.getAngle() / state.Step) + +Note that the built-in functions for getting the angle and axis from the quaternion are not perfect, and will have issues for very small angles. If you need accurate small angles, you will have to use your own function for obtaining them. + +Example with accurate small angle results, using the algorithm taken from the Jolt source code: + +.. tabs:: + .. code-tab:: gdscript GDScript + + func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: + var a = global_basis.get_rotation_quaternion() + var b = other_node.global_basis.get_rotation_quaternion() + # Interpolate the orientation for the next frame + var c = a.slerp(b, 0.5 * state.step) + var d = (c * a.inverse()).normalized() + + if d.w > 0: + c = c * -1 + if d.w >= 1 + state.angular_velocity = Vector3.ZERO + else: + var angle = 2 * acos(in_quat.w) + var axis = Vector3(in_quat.x, in_quat.y, in_quat.z) + axis = axis.normalized() if axis.length() > 0 else Vector3.ZERO + state.angular_velocity = axis * (angle / state.step) + + .. code-tab:: csharp + + _IntegrateForces(PhysicsDirectBodyState3D state) + var a = new Quaternion(transform.Basis); + var b = new Quaternion(transform2.Basis); + // Interpolate using spherical-linear interpolation (SLERP). + var c = a.Slerp(b, 0.1f * state.Step); + var d = (c * a.Inverse()).Normalized(); + + if (d.W < 0) + { + c *= -1; + } + + if (d.W >= 1) + { + state.AngularVelocity = Vector3.Zero; + } + else + { + float angle = 2 * (float)Math.Acos(d.W); + Vector3 axis = new(d.X, d.Y, d.Z); + axis = axis.Length() > 0 ? axis.Normalized() : Vector3.Zero; + state.AngularVelocity = axis * (angle / delta); + } + + Transforms are your friend -------------------------- From 52d07dd0f0cddb30faede01772af44c2b2d4ae7d Mon Sep 17 00:00:00 2001 From: Elie Thorne Date: Thu, 4 Sep 2025 22:10:03 +0200 Subject: [PATCH 2/4] Apply suggestions from code review Added corrections from AThousandShips Fixed the comment mismatch between GDScript and C# noted in said corrections Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> --- tutorials/3d/using_transforms.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tutorials/3d/using_transforms.rst b/tutorials/3d/using_transforms.rst index 862fda6076f..079dfbca03c 100644 --- a/tutorials/3d/using_transforms.rst +++ b/tutorials/3d/using_transforms.rst @@ -413,7 +413,7 @@ Example of making a :ref:`class_RigidBody3D` rotate over time so it ends up with func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: var a = global_basis.get_rotation_quaternion() var b = other_node.global_basis.get_rotation_quaternion() - # Interpolate the orientation for the next frame + # Interpolate the orientation for the next frame (SLERP). var c = a.slerp(b, 0.5 * state.step) var d = (c * a.inverse()).normalized() state.angular_velocity = d.get_axis() * (d.get_angle() / state.step) @@ -421,12 +421,14 @@ Example of making a :ref:`class_RigidBody3D` rotate over time so it ends up with .. code-tab:: csharp _IntegrateForces(PhysicsDirectBodyState3D state) + { var a = new Quaternion(transform.Basis); var b = new Quaternion(transform2.Basis); - // Interpolate using spherical-linear interpolation (SLERP). + // Interpolate the orientation for the next frame (SLERP). var c = a.Slerp(b, 0.1f * state.Step); var d = (c * a.Inverse()).Normalized(); state.AngularVelocity = d.getAxis() * (d.getAngle() / state.Step) + } Note that the built-in functions for getting the angle and axis from the quaternion are not perfect, and will have issues for very small angles. If you need accurate small angles, you will have to use your own function for obtaining them. @@ -438,7 +440,7 @@ Example with accurate small angle results, using the algorithm taken from the Jo func _integrate_forces(state: PhysicsDirectBodyState3D) -> void: var a = global_basis.get_rotation_quaternion() var b = other_node.global_basis.get_rotation_quaternion() - # Interpolate the orientation for the next frame + # Interpolate the orientation for the next frame (SLERP). var c = a.slerp(b, 0.5 * state.step) var d = (c * a.inverse()).normalized() @@ -449,15 +451,16 @@ Example with accurate small angle results, using the algorithm taken from the Jo else: var angle = 2 * acos(in_quat.w) var axis = Vector3(in_quat.x, in_quat.y, in_quat.z) - axis = axis.normalized() if axis.length() > 0 else Vector3.ZERO + axis = axis.normalized() if axis.length_squared() > 0.0 else Vector3.ZERO state.angular_velocity = axis * (angle / state.step) .. code-tab:: csharp _IntegrateForces(PhysicsDirectBodyState3D state) + { var a = new Quaternion(transform.Basis); var b = new Quaternion(transform2.Basis); - // Interpolate using spherical-linear interpolation (SLERP). + // Interpolate the orientation for the next frame (SLERP). var c = a.Slerp(b, 0.1f * state.Step); var d = (c * a.Inverse()).Normalized(); @@ -474,9 +477,10 @@ Example with accurate small angle results, using the algorithm taken from the Jo { float angle = 2 * (float)Math.Acos(d.W); Vector3 axis = new(d.X, d.Y, d.Z); - axis = axis.Length() > 0 ? axis.Normalized() : Vector3.Zero; + axis = axis.LengthSquared() > 0.0 ? axis.Normalized() : Vector3.Zero; state.AngularVelocity = axis * (angle / delta); } + } Transforms are your friend From d198dbb67d3dc64afb9b3eb09b7100addc150aed Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Fri, 5 Sep 2025 00:33:25 +0200 Subject: [PATCH 3/4] Apply suggestions from code review --- tutorials/3d/using_transforms.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tutorials/3d/using_transforms.rst b/tutorials/3d/using_transforms.rst index 079dfbca03c..5f766a8964c 100644 --- a/tutorials/3d/using_transforms.rst +++ b/tutorials/3d/using_transforms.rst @@ -403,7 +403,8 @@ Quaternions are useful when doing camera/path/etc. interpolations, as the result Angular velocity and quaternions ================================ -While angular velocity is stored in a :ref:`class_Vector3`, it is not in fact a vector, and using Euler angles for angular velocity will result in errors. The correct value can be obtained from a quaternion, by multiplying the axis vector with the angle. +While angular velocity is stored in a :ref:`class_Vector3`, it is in fact *not* a vector. Using Euler angles for angular velocity will result in errors. +The correct value can be obtained from a quaternion by multiplying the axis vector with the angle. Example of making a :ref:`class_RigidBody3D` rotate over time so it ends up with the same orientation than another node two seconds later: From 0e37e9db1c2d8931b806fde6ae0c17d64d622bc3 Mon Sep 17 00:00:00 2001 From: Elie Thorne Date: Sun, 7 Sep 2025 00:32:59 +0200 Subject: [PATCH 4/4] Apply suggestions from code review More fixes of mistakes caught by reviewers Co-authored-by: Rageking8 --- tutorials/3d/using_transforms.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorials/3d/using_transforms.rst b/tutorials/3d/using_transforms.rst index 5f766a8964c..53a162bd309 100644 --- a/tutorials/3d/using_transforms.rst +++ b/tutorials/3d/using_transforms.rst @@ -428,7 +428,7 @@ Example of making a :ref:`class_RigidBody3D` rotate over time so it ends up with // Interpolate the orientation for the next frame (SLERP). var c = a.Slerp(b, 0.1f * state.Step); var d = (c * a.Inverse()).Normalized(); - state.AngularVelocity = d.getAxis() * (d.getAngle() / state.Step) + state.AngularVelocity = d.GetAxis() * (d.GetAngle() / state.Step); } Note that the built-in functions for getting the angle and axis from the quaternion are not perfect, and will have issues for very small angles. If you need accurate small angles, you will have to use your own function for obtaining them. @@ -445,9 +445,9 @@ Example with accurate small angle results, using the algorithm taken from the Jo var c = a.slerp(b, 0.5 * state.step) var d = (c * a.inverse()).normalized() - if d.w > 0: + if d.w < 0: c = c * -1 - if d.w >= 1 + if d.w >= 1: state.angular_velocity = Vector3.ZERO else: var angle = 2 * acos(in_quat.w)