fix(math): correct select logic in quaternion and svd

Fixed conditional selection logic in quaternion and SVD math functions by swapping select argument order for correctness. Fixed LookRotationSafe and normalizesafe to return valid quaternions. Corrected SVD helper functions for proper value swapping and safe reciprocal. Added unit tests for matrix, reflection, projection, refraction, quaternion normalization, LookRotationSafe, and SVD operations. Incremented project version to 1.3.3. Minor formatting and using directive updates.
This commit is contained in:
2026-04-07 22:18:55 +09:00
parent 7ea7e8aa9e
commit b08662b77d
7 changed files with 191 additions and 15 deletions

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.Mathematics;
using System.Numerics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
@@ -288,4 +289,64 @@ public class TestMathFunctions
Assert.AreEqual(5f, result.y, 1e-6f); // condition is false, so select b.y
Assert.AreEqual(3f, result.z, 1e-6f); // condition is true, so select a.z
}
[TestMethod]
public void TestMatrixFunctions()
{
// Test determinant and inverse of a 2x2 matrix
var m = new float2x2(new float2(1f, 2f), new float2(3f, 4f));
var det = math.determinant(m);
Assert.AreEqual(-2f, det, 1e-6f);
var inv = math.inverse(m);
var expectedInv = new float2x2(new float2(-2f, 1f), new float2(1.5f, -0.5f));
Assert.AreEqual(expectedInv.c0.x, inv.c0.x, 1e-6f);
Assert.AreEqual(expectedInv.c0.y, inv.c0.y, 1e-6f);
Assert.AreEqual(expectedInv.c1.x, inv.c1.x, 1e-6f);
Assert.AreEqual(expectedInv.c1.y, inv.c1.y, 1e-6f);
}
[TestMethod]
public void TestRflections()
{
// Test reflect function
var incident = new float3(1f, -1f, 0f);
var normal = math.normalize(new float3(0f, 1f, 0f));
var reflected = math.reflect(incident, normal);
// The reflected vector should be (1, 1, 0)
Assert.AreEqual(1f, reflected.x, 1e-6f);
Assert.AreEqual(1f, reflected.y, 1e-6f);
Assert.AreEqual(0f, reflected.z, 1e-6f);
}
[TestMethod]
public void TestProjections()
{
// Test project function
var vector = new float3(1f, 2f, 3f);
var onNormal = math.normalize(new float3(0f, 1f, 0f));
var projected = math.project(vector, onNormal);
// The projection of (1,2,3) onto the Y-axis should be (0,2,0)
Assert.AreEqual(0f, projected.x, 1e-6f);
Assert.AreEqual(2f, projected.y, 1e-6f);
Assert.AreEqual(0f, projected.z, 1e-6f);
}
[TestMethod]
public void TestRefraction()
{
// Test refract function
var incident = math.normalize(new float3(1f, -1f, 0f));
var normal = math.normalize(new float3(0f, 1f, 0f));
var eta = 0.5f; // Refractive index ratio
var refracted = math.refract(incident, normal, eta);
// The refracted vector should be approximately (0.707, -0.707, 0)
Assert.AreEqual(0.3535534f, refracted.x, 1e-6f);
Assert.AreEqual(-0.9354144f, refracted.y, 1e-6f);
Assert.AreEqual(0f, refracted.z, 1e-6f);
}
}

View File

@@ -151,6 +151,18 @@ public class TestQuaternion
Assert.AreEqual(1f, math.length(normalized.value), 1e-6f);
}
[TestMethod]
public void TestQuaternionNormalizeSafe()
{
// Create non-unit quaternion
var q = new quaternion(1f, 2f, 3f, 4f);
var normalized = math.normalizesafe(q);
// Should have length 1
Assert.AreEqual(1f, math.length(normalized.value), 1e-6f);
}
[TestMethod]
public void TestMatrixFromQuaternion()
{
@@ -240,4 +252,27 @@ public class TestQuaternion
Assert.AreEqual(1f, rotatedUp.y, 1e-6f);
Assert.AreEqual(0f, rotatedUp.z, 1e-6f);
}
[TestMethod]
public void TestLookRotationSafe()
{
var forward = new float3(0f, 0f, -1f);
var up = new float3(0f, 1f, 0f);
var q = quaternion.LookRotationSafe(forward, up);
// Should be unit quaternion
Assert.AreEqual(1f, math.length(q.value), 1e-6f);
// Should rotate (0,0,1) to approximately (0,0,1)
var rotatedForward = math.mul(q, forward);
Assert.AreEqual(0f, rotatedForward.x, 1e-6f);
Assert.AreEqual(0f, rotatedForward.y, 1e-6f);
Assert.AreEqual(1f, rotatedForward.z, 1e-6f);
// Should rotate (0,1,0) to approximately (0,1,0)
var rotatedUp = math.mul(q, up);
Assert.AreEqual(0f, rotatedUp.x, 1e-6f);
Assert.AreEqual(1f, rotatedUp.y, 1e-6f);
Assert.AreEqual(0f, rotatedUp.z, 1e-6f);
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestSVD
{
private const float TOLERANCE = 1e-5f;
private static void AssertFloat3x3Equal(float3x3 expected, float3x3 actual, float tolerance = TOLERANCE)
{
Assert.AreEqual(expected.c0.x, actual.c0.x, tolerance, "c0.x mismatch");
Assert.AreEqual(expected.c0.y, actual.c0.y, tolerance, "c0.y mismatch");
Assert.AreEqual(expected.c0.z, actual.c0.z, tolerance, "c0.z mismatch");
Assert.AreEqual(expected.c1.x, actual.c1.x, tolerance, "c1.x mismatch");
Assert.AreEqual(expected.c1.y, actual.c1.y, tolerance, "c1.y mismatch");
Assert.AreEqual(expected.c1.z, actual.c1.z, tolerance, "c1.z mismatch");
Assert.AreEqual(expected.c2.x, actual.c2.x, tolerance, "c2.x mismatch");
Assert.AreEqual(expected.c2.y, actual.c2.y, tolerance, "c2.y mismatch");
Assert.AreEqual(expected.c2.z, actual.c2.z, tolerance, "c2.z mismatch");
}
private static float3x3 CreateDiagonal(float3 s)
{
return new float3x3(new float3(s.x, 0, 0), new float3(0, s.y, 0), new float3(0, 0, s.z));
}
[TestMethod]
public void TestSVDInverse_Identity()
{
var identity = float3x3.identity;
var inverse = svd.svdInverse(identity);
AssertFloat3x3Equal(identity, inverse);
}
[TestMethod]
public void TestSVDInverse_Diagonal()
{
var scale = new float3(2f, 0.5f, 4f);
var a = CreateDiagonal(scale);
var expected = CreateDiagonal(1f / scale);
var actual = svd.svdInverse(a);
AssertFloat3x3Equal(expected, actual);
}
[TestMethod]
public void TestSVDRotation_PureRotation()
{
var q = quaternion.AxisAngle(math.normalize(new float3(1f, 2f, 3f)), 1.2f);
var a = new float3x3(q);
var result = svd.svdRotation(a);
// SVD rotation should extract the rotation part.
// For a pure rotation matrix, result should be the same as input q (or -q)
var dot = math.dot(q.value, result.value);
Assert.IsTrue(math.abs(dot) > 0.999f, $"Rotation mismatch: dot product {dot}");
}
[TestMethod]
public void TestSVDInverse_RotationAndScale()
{
var q = quaternion.AxisAngle(math.normalize(new float3(0.5f, -0.2f, 0.8f)), 0.5f);
var r = new float3x3(q);
var scale = new float3(1.5f, 0.8f, 2.0f);
// A = R * S
var a = math.mulScale(r, scale);
var inverse = svd.svdInverse(a);
// Check inverse * A = Identity
var identityResult = math.mul(inverse, a);
AssertFloat3x3Equal(float3x3.identity, identityResult, 1e-4f);
}
}