Skip to content

Commit 01f7cb0

Browse files
authored
feat: updates and bugfixes (#30)
* feat: refactor Fixed4x4 to support affine and full transformations - added TransformPoint and InverseTransformPoint methods - renamed SRT & TRS methods * feat: added ToAngularVelocity conversion method to FixedQuanternion
1 parent 570a89a commit 01f7cb0

File tree

8 files changed

+331
-77
lines changed

8 files changed

+331
-77
lines changed

src/FixedMathSharp/Extensions/Fixed4x4.Extensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Numerics;
2-
using System.Runtime.CompilerServices;
1+
using System.Runtime.CompilerServices;
32

43
namespace FixedMathSharp
54
{
@@ -41,6 +40,18 @@ public static Fixed4x4 SetGlobalScale(this ref Fixed4x4 matrix, Vector3d globalS
4140
return matrix = Fixed4x4.SetGlobalScale(matrix, globalScale);
4241
}
4342

43+
/// <inheritdoc cref="Fixed4x4.TransformPoint(Fixed4x4, Vector3d)" />
44+
public static Vector3d TransformPoint(this Fixed4x4 matrix, Vector3d point)
45+
{
46+
return Fixed4x4.TransformPoint(matrix, point);
47+
}
48+
49+
/// <inheritdoc cref="Fixed4x4.InverseTransformPoint(Fixed4x4, Vector3d)" />
50+
public static Vector3d InverseTransformPoint(this Fixed4x4 matrix, Vector3d point)
51+
{
52+
return Fixed4x4.InverseTransformPoint(matrix, point);
53+
}
54+
4455
#endregion
4556
}
4657
}

src/FixedMathSharp/Extensions/FixedQuaternion.Extensions.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
using FixedMathSharp;
2-
using System.Runtime.CompilerServices;
1+
using System.Runtime.CompilerServices;
32

43
namespace FixedMathSharp
54
{
65
public static partial class FixedQuaternionExtensions
76
{
7+
/// <inheritdoc cref="FixedQuaternion.ToAngularVelocity" />
8+
public static Vector3d ToAngularVelocity(
9+
this FixedQuaternion currentRotation,
10+
FixedQuaternion previousRotation,
11+
Fixed64 deltaTime)
12+
{
13+
return FixedQuaternion.ToAngularVelocity(currentRotation, previousRotation, deltaTime);
14+
}
15+
816
#region Equality
917

1018
/// <summary>

src/FixedMathSharp/Numerics/Fixed4x4.cs

Lines changed: 180 additions & 62 deletions
Large diffs are not rendered by default.

src/FixedMathSharp/Numerics/FixedCurve.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Linq;
33

4-
54
#if NET8_0_OR_GREATER
65
using System.Text.Json.Serialization;
76
#endif

src/FixedMathSharp/Numerics/FixedQuaternion.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,63 @@ public static FixedQuaternion FromEulerAngles(Fixed64 pitch, Fixed64 yaw, Fixed6
385385
return GetNormalized(new FixedQuaternion(x, y, z, w));
386386
}
387387

388+
/// <summary>
389+
/// Computes the logarithm of a quaternion, which represents the rotational displacement.
390+
/// This is useful for interpolation and angular velocity calculations.
391+
/// </summary>
392+
/// <param name="q">The quaternion to compute the logarithm of.</param>
393+
/// <returns>A Vector3d representing the logarithm of the quaternion (axis-angle representation).</returns>
394+
/// <remarks>
395+
/// The logarithm of a unit quaternion is given by:
396+
/// log(q) = (θ * v̂), where:
397+
/// - θ = 2 * acos(w) is the rotation angle.
398+
/// - v̂ = (x, y, z) / ||(x, y, z)|| is the unit vector representing the axis of rotation.
399+
/// If the quaternion is close to identity, the function returns a zero vector to avoid numerical instability.
400+
/// </remarks>
401+
public static Vector3d QuaternionLog(FixedQuaternion q)
402+
{
403+
// Ensure the quaternion is normalized
404+
q = q.Normal;
405+
406+
// Extract vector part
407+
Vector3d v = new Vector3d(q.x, q.y, q.z);
408+
Fixed64 vLength = v.Magnitude;
409+
410+
// If rotation is very small, avoid division by zero
411+
if (vLength < Fixed64.FromRaw(0x00001000L)) // Small epsilon
412+
return Vector3d.Zero;
413+
414+
// Compute angle (theta = 2 * acos(w))
415+
Fixed64 theta = Fixed64.Two * FixedMath.Acos(q.w);
416+
417+
// Convert to angular velocity
418+
return (v / vLength) * theta;
419+
}
420+
421+
/// <summary>
422+
/// Computes the angular velocity required to move from `previousRotation` to `currentRotation` over a given time step.
423+
/// </summary>
424+
/// <param name="currentRotation">The current orientation as a quaternion.</param>
425+
/// <param name="previousRotation">The previous orientation as a quaternion.</param>
426+
/// <param name="deltaTime">The time step over which the rotation occurs.</param>
427+
/// <returns>A Vector3d representing the angular velocity (in radians per second).</returns>
428+
/// <remarks>
429+
/// This function calculates the change in rotation over `deltaTime` and converts it into angular velocity.
430+
/// - First, it computes the relative rotation: `rotationDelta = currentRotation * previousRotation.Inverse()`.
431+
/// - Then, it applies `QuaternionLog(rotationDelta)` to extract the axis-angle representation.
432+
/// - Finally, it divides by `deltaTime` to compute the angular velocity.
433+
/// </remarks>
434+
public static Vector3d ToAngularVelocity(
435+
FixedQuaternion currentRotation,
436+
FixedQuaternion previousRotation,
437+
Fixed64 deltaTime)
438+
{
439+
FixedQuaternion rotationDelta = currentRotation * previousRotation.Inverse();
440+
Vector3d angularDisplacement = QuaternionLog(rotationDelta);
441+
442+
return angularDisplacement / deltaTime; // Convert to angular velocity
443+
}
444+
388445
/// <summary>
389446
/// Performs a simple linear interpolation between the components of the input quaternions
390447
/// </summary>

src/FixedMathSharp/Numerics/Vector3d.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -997,12 +997,26 @@ public static Vector3d InverseRotate(Vector3d source, Vector3d position, FixedQu
997997
}
998998

999999
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1000-
public static Vector3d operator *(Fixed4x4 matrix, Vector3d vector)
1000+
public static Vector3d operator *(Fixed4x4 matrix, Vector3d point)
10011001
{
1002+
if(matrix.IsAffine)
1003+
{
1004+
return new Vector3d(
1005+
matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m03 + matrix.m30,
1006+
matrix.m10 * point.x + matrix.m11 * point.y + matrix.m12 * point.z + matrix.m13 + matrix.m31,
1007+
matrix.m20 * point.x + matrix.m21 * point.y + matrix.m22 * point.z + matrix.m23 + matrix.m32
1008+
);
1009+
}
1010+
1011+
// Full 4×4 transformation
1012+
Fixed64 w = matrix.m03 * point.x + matrix.m13 * point.y + matrix.m23 * point.z + matrix.m33;
1013+
if (w == Fixed64.Zero) w = Fixed64.One; // Prevent divide-by-zero
1014+
10021015
return new Vector3d(
1003-
matrix.m00 * vector.x + matrix.m01 * vector.y + matrix.m02 * vector.z + matrix.m03,
1004-
matrix.m10 * vector.x + matrix.m11 * vector.y + matrix.m12 * vector.z + matrix.m13,
1005-
matrix.m20 * vector.x + matrix.m21 * vector.y + matrix.m22 * vector.z + matrix.m23);
1016+
(matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m03 + matrix.m30) / w,
1017+
(matrix.m10 * point.x + matrix.m11 * point.y + matrix.m12 * point.z + matrix.m13 + matrix.m31) / w,
1018+
(matrix.m20 * point.x + matrix.m21 * point.y + matrix.m22 * point.z + matrix.m23 + matrix.m32) / w
1019+
);
10061020
}
10071021

10081022
[MethodImpl(MethodImplOptions.AggressiveInlining)]

tests/FixedMathSharp.Tests/Fixed4x4.Tests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public void FixedMatrix4x4_TRS_CreatesCorrectTransformationMatrix()
214214
var rotation = FixedQuaternion.FromEulerAnglesInDegrees((Fixed64)30, (Fixed64)45, (Fixed64)60);
215215
var scale = new Vector3d(2, 3, 4);
216216

217-
var trsMatrix = Fixed4x4.SRT(translation, rotation, scale);
217+
var trsMatrix = Fixed4x4.ScaleRotateTranslate(translation, rotation, scale);
218218

219219
// Instead of direct equality, compare the decomposed components
220220
Assert.True(Fixed4x4.Decompose(trsMatrix, out var decomposedScale, out var decomposedRotation, out var decomposedTranslation));
@@ -279,7 +279,7 @@ public void TransformPoint_WorldToLocal_ReturnsCorrectResult()
279279
var rotation = FixedQuaternion.FromEulerAnglesInDegrees(-(Fixed64)20, (Fixed64)35, (Fixed64)50);
280280
var scale = new Vector3d(1, 2, 1.5);
281281

282-
var transformMatrix = Fixed4x4.SRT(translation, rotation, scale);
282+
var transformMatrix = Fixed4x4.ScaleRotateTranslate(translation, rotation, scale);
283283

284284
var worldPoint = new Vector3d(10, 15, -2);
285285
var localPoint = Fixed4x4.InverseTransformPoint(transformMatrix, worldPoint);
@@ -296,7 +296,7 @@ public void InverseTransformPoint_LocalToWorld_ReturnsCorrectResult()
296296
var rotation = FixedQuaternion.FromEulerAnglesInDegrees((Fixed64)45, -(Fixed64)30, (Fixed64)90);
297297
var scale = new Vector3d(1.2, 0.8, 1.5);
298298

299-
var transformMatrix = Fixed4x4.SRT(translation, rotation, scale);
299+
var transformMatrix = Fixed4x4.ScaleRotateTranslate(translation, rotation, scale);
300300

301301
var localPoint = new Vector3d(2, 3, -1);
302302
var worldPoint = Fixed4x4.TransformPoint(transformMatrix, localPoint);
@@ -313,7 +313,7 @@ public void TransformPoint_InverseTransformPoint_RoundTripConsistency()
313313
var rotation = FixedQuaternion.FromEulerAnglesInDegrees(-(Fixed64)45, (Fixed64)30, (Fixed64)90);
314314
var scale = new Vector3d(1.5, 2.5, 3.0);
315315

316-
var transformMatrix = Fixed4x4.SRT(translation, rotation, scale);
316+
var transformMatrix = Fixed4x4.ScaleRotateTranslate(translation, rotation, scale);
317317

318318
var originalPoint = new Vector3d(3, 5, 7);
319319
var transformedPoint = Fixed4x4.TransformPoint(transformMatrix, originalPoint);
@@ -330,7 +330,7 @@ public void Fixed4x4_Serialization_RoundTripMaintainsData()
330330
var rotation = FixedQuaternion.FromEulerAnglesInDegrees(Fixed64.Zero, FixedMath.PiOver2, Fixed64.Zero);
331331
var scale = new Vector3d(1, 1, 1);
332332

333-
var original4x4 = Fixed4x4.SRT(translation, rotation, scale);
333+
var original4x4 = Fixed4x4.ScaleRotateTranslate(translation, rotation, scale);
334334

335335
// Serialize the Fixed4x4 object
336336
#if NET48_OR_GREATER

tests/FixedMathSharp.Tests/FixedQuanternion.Tests.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#if NET8_0_OR_GREATER
77
using System.Text.Json;
88
using System.Text.Json.Serialization;
9-
109
#endif
1110

1211
using Xunit;
@@ -197,6 +196,33 @@ public void FixedQuaternion_ToMatrix_WorksCorrectly()
197196
Assert.True(result == expected, $"ToMatrix returned {result}, expected Identity matrix.");
198197
}
199198

199+
[Fact]
200+
public void FixedQuaternion_ToAngularVelocity_WorksCorrectly()
201+
{
202+
var prevRotation = FixedQuaternion.Identity;
203+
var currentRotation = FixedQuaternion.FromAxisAngle(Vector3d.Up, FixedMath.PiOver4); // Rotated 45 degrees around Y-axis
204+
var deltaTime = new Fixed64(2); // Assume 2 seconds elapsed
205+
206+
var angularVelocity = FixedQuaternion.ToAngularVelocity(currentRotation, prevRotation, deltaTime);
207+
208+
var expected = new Vector3d(Fixed64.Zero, FixedMath.PiOver4 / deltaTime, Fixed64.Zero); // Expect ω = θ / dt
209+
Assert.True(angularVelocity.FuzzyEqual(expected, new Fixed64(0.0001)),
210+
$"ToAngularVelocity returned {angularVelocity}, expected {expected}");
211+
}
212+
213+
[Fact]
214+
public void FixedQuaternion_ToAngularVelocity_ZeroForNoRotation()
215+
{
216+
var prevRotation = FixedQuaternion.Identity;
217+
var currentRotation = FixedQuaternion.Identity;
218+
var deltaTime = Fixed64.One;
219+
220+
var angularVelocity = FixedQuaternion.ToAngularVelocity(currentRotation, prevRotation, deltaTime);
221+
222+
Assert.True(angularVelocity.FuzzyEqual(Vector3d.Zero),
223+
$"ToAngularVelocity should return zero for no rotation, but got {angularVelocity}");
224+
}
225+
200226
[Fact]
201227
public void FixedQuanternion_Serialization_RoundTripMaintainsData()
202228
{
@@ -336,6 +362,27 @@ public void FixedQuaternion_LookRotation_WorksCorrectly()
336362
Assert.True(result.FuzzyEqual(expected), $"Look rotation returned {result}, expected {expected}.");
337363
}
338364

365+
[Fact]
366+
public void FixedQuaternion_QuaternionLog_WorksCorrectly()
367+
{
368+
var quaternion = FixedQuaternion.FromAxisAngle(Vector3d.Up, FixedMath.PiOver4); // 45-degree rotation around Y-axis
369+
var logResult = FixedQuaternion.QuaternionLog(quaternion);
370+
371+
var expected = new Vector3d(Fixed64.Zero, FixedMath.PiOver4, Fixed64.Zero); // Expect log(q) = θ * axis
372+
Assert.True(logResult.FuzzyEqual(expected, new Fixed64(0.0001)),
373+
$"QuaternionLog returned {logResult}, expected {expected}");
374+
}
375+
376+
[Fact]
377+
public void FixedQuaternion_QuaternionLog_ReturnsZeroForIdentity()
378+
{
379+
var identity = FixedQuaternion.Identity;
380+
var logResult = FixedQuaternion.QuaternionLog(identity);
381+
382+
Assert.True(logResult.FuzzyEqual(Vector3d.Zero),
383+
$"QuaternionLog of Identity should be (0,0,0), but got {logResult}");
384+
}
385+
339386
#endregion
340387

341388
#region Test: Operators

0 commit comments

Comments
 (0)