* Box Simd

* Add 256 bit version of GetAABB

* Add AABB bechmarks

* No real diff between 128 & 256, so removing 256

| Method     | Mean      | Error     | StdDev    | Ratio |
|----------- |----------:|----------:|----------:|------:|
| GetAABB    | 5.8107 ns | 0.0154 ns | 0.0137 ns |  1.00 |
| GetAABB128 | 0.4927 ns | 0.0003 ns | 0.0002 ns |  0.08 |
| GetAABB256 | 0.4332 ns | 0.0006 ns | 0.0006 ns |  0.07 |

* Add Box2Rotated.Transform Benchmark

* Results

20% faster and much smaller code. Also I don't think it inlined RotateVec

* Add Matrix3x2Helper.TransformBox() benchmark

new:

| Method    | Mean     | Error     | StdDev    | Code Size |
|---------- |---------:|----------:|----------:|----------:|
| Transform | 2.463 ns | 0.0766 ns | 0.0679 ns |     216 B |

old:
| Method    | Mean     | Error     | StdDev    | Median   | Code Size |
|---------- |---------:|----------:|----------:|---------:|----------:|
| Transform | 9.469 ns | 0.2140 ns | 0.5408 ns | 9.206 ns |     621 B |

* Fix polygon constructor

* SlimPolygonBenchmark

* use new SimdHelper for other methods

* Fix bugs

* Use new methods

* Simd SlimPolygon.ComputeAABB

* Move simd transform to physics

* Cleanup

* Remove uneccesary Unsafe.SkipInit

* These tests all work on master

* Add Transform.MulSimd test

* Add SlimPolygon constructor tests

* Add ComputeAABB test

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2025-11-10 20:30:08 +13:00
committed by GitHub
parent fe19ff9bd5
commit 3f19d25018
21 changed files with 638 additions and 193 deletions

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using System.Runtime.Intrinsics;
using NUnit.Framework.Constraints;
using Robust.Shared.Maths;
@@ -59,6 +60,17 @@ namespace Robust.UnitTesting
return new ConstraintResult(this, actual, m3x2Expected.EqualsApprox(m3x2Actual));
}
if (Expected is Vector128<float> vecExpected && actual is Vector128<float> vecActual)
{
if (Tolerance != null)
{
return new ConstraintResult(this,
actual,
MathHelper.CloseToPercent(vecActual, vecExpected, (float)Tolerance.Value));
}
return new ConstraintResult(this, actual, MathHelper.CloseToPercent(vecActual, vecExpected));
}
return new ConstraintResult(this, actual, false);
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using NUnit.Framework;
using Robust.Client.Graphics.Clyde;
using Robust.Shared.Maths;
namespace Robust.UnitTesting.Client.Sprite;
[TestFixture]
[TestOf(typeof(Clyde))]
public sealed class TransformCenteredBoxTest
{
private static IEnumerable<(Box2 box, float angle, Vector2 offset, Vector2 scale)> _args =
[
(Box2.UnitCentered, 0f, new Vector2(0f,0f), new Vector2(1f,-1f)),
(Box2.UnitCentered, MathF.PI / 7, new Vector2(0f, 0f), new Vector2(1f, -1f)),
(Box2.UnitCentered, MathF.PI / 7, new Vector2(-1f, 2.3f), new Vector2(1f, -1f)),
(Box2.UnitCentered, MathF.PI / 7, new Vector2(-1f, 2.3f), new Vector2(1.25f, -0.32f)),
(new Box2(1,2,3,4), MathF.PI / 7, new Vector2(-1f, 2.3f), new Vector2(1.25f, -0.32f)),
];
[Test]
public void TestTransformCenteredBox([ValueSource(nameof(_args))]
(Box2 box, float angle, Vector2 offset, Vector2 scale) args)
{
var result = Clyde.TransformCenteredBox(args.box, args.angle, args.offset, args.scale);
var expected = Matrix3x2.CreateRotation(args.angle).TransformBox(args.box).Translated(args.offset);
expected = new(
expected.Left * args.scale.X,
expected.Top * args.scale.Y,
expected.Right * args.scale.X,
expected.Bottom * args.scale.Y);
Assert.That(result, Is.Approximately(expected));
}
}

View File

@@ -45,7 +45,7 @@ namespace Robust.UnitTesting.Shared.Maths
private static readonly float Cos45Deg = MathF.Cos(MathF.PI / 4);
private static readonly float Sqrt2 = MathF.Sqrt(2);
private static IEnumerable<(Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected)> CalcBoundingBoxData =>
public static IEnumerable<(Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected)> CalcBoundingBoxData =>
new (Box2, Vector2, Angle, Box2)[]
{
(new Box2(0, 0, 1, 1), new Vector2(0, 0), 0, new Box2(0, 0, 1, 1)),
@@ -54,10 +54,11 @@ namespace Robust.UnitTesting.Shared.Maths
(new Box2(0, 0, 1, 1), new Vector2(1, 1), Math.PI, new Box2(1, 1, 2, 2)),
(new Box2(1, 1, 2, 2), new Vector2(1, 1), Math.PI/4, new Box2(1 - Cos45Deg, 1, 1 + Cos45Deg, 1 + Sqrt2)),
(new Box2(-1, 1, 1, 2), new Vector2(0, 0), -Math.PI/2, new Box2(1, -1, 2, 1)),
(Box2.UnitCentered, new Vector2(1, Sqrt2), Angle.FromDegrees(30), new Box2(0.158069f, -0.993544f, 1.52409f,0.372481f)),
};
private static TestCaseData[] MatrixBox2Cases = new[]
{
private static TestCaseData[] MatrixBox2Cases =
[
new TestCaseData(Matrix3x2.Identity,
Box2Rotated.UnitCentered,
Box2Rotated.UnitCentered.CalcBoundingBox()),
@@ -67,7 +68,15 @@ namespace Robust.UnitTesting.Shared.Maths
new TestCaseData(Matrix3x2.CreateTranslation(Vector2.One),
Box2Rotated.UnitCentered,
new Box2Rotated(new Vector2(0.5f, 0.5f), new Vector2(1.5f, 1.5f)).CalcBoundingBox()),
};
new TestCaseData(Matrix3x2.CreateTranslation(new Vector2(-1, -Sqrt2))
* Matrix3Helpers.CreateTransform(new Vector2(1, Sqrt2), Angle.FromDegrees(30)),
new Box2Rotated(Box2.UnitCentered),
new Box2(0.158069f, -0.993544f, 1.52409f, 0.372481f)),
new TestCaseData(Matrix3x2.CreateTranslation(new Vector2(-1, -Sqrt2))
* Matrix3Helpers.CreateTransform(new Vector2(1, Sqrt2), Angle.FromDegrees(30)),
new Box2Rotated(Box2.UnitCentered, -Angle.FromDegrees(30), new Vector2(1, Sqrt2)),
Box2.UnitCentered)
];
/// <summary>
/// Tests that transforming a Box2Rotated into a Box2 works.
@@ -75,7 +84,7 @@ namespace Robust.UnitTesting.Shared.Maths
[Test, TestCaseSource(nameof(MatrixBox2Cases))]
public void TestBox2Matrices(Matrix3x2 matrix, Box2Rotated bounds, Box2 result)
{
Assert.That(matrix.TransformBox(bounds), Is.EqualTo(result));
Assert.That(matrix.TransformBox(bounds), Is.Approximately(result));
}
[Test]

View File

@@ -1,59 +0,0 @@
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Shapes;
namespace Robust.UnitTesting.Shared.Physics;
[TestFixture]
public sealed class Polygon_Test
{
/// <summary>
/// Check that Slim and normal Polygon are equals
/// </summary>
[Test]
public void TestSlim()
{
var slim = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
var poly = new Polygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(slim.Equals(poly));
}
[Test]
public void TestAABB()
{
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape.ComputeAABB(Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One)));
}
[Test]
public void TestBox2()
{
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape._vertices.AsSpan.ToArray(), Is.EqualTo(new Vector2[]
{
new Vector2(0.5f, 0.5f),
new Vector2(1.5f, 0.5f),
new Vector2(1.5f, 1.5f),
new Vector2(0.5f, 1.5f),
}));
}
[Test]
public void TestBox2Rotated()
{
var shape = new SlimPolygon(new Box2Rotated(Box2.UnitCentered, Angle.FromDegrees(90)));
Assert.That(shape._vertices.AsSpan.ToArray(), Is.EqualTo(new Vector2[]
{
new Vector2(0.5f, -0.5f),
new Vector2(0.5f, 0.5f),
new Vector2(-0.5f, 0.5f),
new Vector2(-0.5f, -0.5f),
}));
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Shapes;
using Robust.UnitTesting.Shared.Maths;
namespace Robust.UnitTesting.Shared.Physics.Shapes;
[TestFixture]
[TestOf(typeof(SlimPolygon))]
public sealed class SlimPolygonTest
{
/// <summary>
/// Check that Slim and normal Polygon are equals
/// </summary>
[Test]
public void TestSlim()
{
var slim = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
var poly = new Polygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(slim.Equals(poly));
}
[Test]
public void TestAABB()
{
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape.ComputeAABB(Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One)));
}
[Test]
public void TestBox2()
{
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape._vertices.AsSpan.ToArray(), Is.EqualTo(new Vector2[]
{
new Vector2(0.5f, 0.5f),
new Vector2(1.5f, 0.5f),
new Vector2(1.5f, 1.5f),
new Vector2(0.5f, 1.5f),
}));
}
public static IEnumerable<(Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected)> CalcBoundingBoxData
=> Box2Rotated_Test.CalcBoundingBoxData;
[Test]
public void TestBox2Rotated([ValueSource(nameof(CalcBoundingBoxData))] (Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected) dat)
{
var box = new Box2Rotated(dat.baseBox, dat.rotation, dat.origin);
var shape = new SlimPolygon(box);
Assert.That(shape._vertices._00, Is.Approximately(box.BottomLeft, 0.0001f));
Assert.That(shape._vertices._01, Is.Approximately(box.BottomRight, 0.0001f));
Assert.That(shape._vertices._02, Is.Approximately(box.TopRight, 0.0001f));
Assert.That(shape._vertices._03, Is.Approximately(box.TopLeft, 0.0001f));
}
[Test]
public void TestBox2RotatedBounds([ValueSource(nameof(CalcBoundingBoxData))](Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected) dat)
{
var box = new Box2Rotated(dat.baseBox, dat.rotation, dat.origin);
var shape = new SlimPolygon(box);
var aabb = shape.ComputeAABB(Transform.Empty, 0);
Assert.That(aabb, Is.Approximately(dat.expected));
}
[Test]
public void TestTransformConstructor([ValueSource(nameof(CalcBoundingBoxData))] (Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected) dat)
{
var box = new Box2Rotated(dat.baseBox, dat.rotation, dat.origin);
var shape = new SlimPolygon(box.Box, box.Transform, out var bounds);
Assert.That(shape._vertices._00, Is.Approximately(box.BottomLeft, 0.0001f));
Assert.That(shape._vertices._01, Is.Approximately(box.BottomRight, 0.0001f));
Assert.That(shape._vertices._02, Is.Approximately(box.TopRight, 0.0001f));
Assert.That(shape._vertices._03, Is.Approximately(box.TopLeft, 0.0001f));
Assert.That(box.CalcBoundingBox(), Is.Approximately(bounds, 0.0001f));
}
[Test]
public void TestTransformRotatedConstructor([ValueSource(nameof(CalcBoundingBoxData))](Box2 baseBox, Vector2 origin, Angle rotation, Box2 expected) dat)
{
var box = new Box2Rotated(dat.baseBox, dat.rotation, dat.origin);
Matrix3x2.Invert(box.Transform, out var inverse);
var shape = new SlimPolygon(box, inverse, out var bounds);
Assert.That(shape._vertices._00, Is.Approximately(dat.baseBox.BottomLeft, 0.0001f));
Assert.That(shape._vertices._01, Is.Approximately(dat.baseBox.BottomRight, 0.0001f));
Assert.That(shape._vertices._02, Is.Approximately(dat.baseBox.TopRight, 0.0001f));
Assert.That(shape._vertices._03, Is.Approximately(dat.baseBox.TopLeft, 0.0001f));
Assert.That(dat.baseBox, Is.Approximately(bounds, 0.0001f));
}
[Test]
public void TestComputeAABB()
{
var box = new Box2Rotated(Box2.UnitCentered, Angle.FromDegrees(45), Vector2.One);
var shape = new SlimPolygon(box);
Assert.That(shape._vertices._00, Is.Approximately(box.BottomLeft, 0.0001f));
Assert.That(shape._vertices._01, Is.Approximately(box.BottomRight, 0.0001f));
Assert.That(shape._vertices._02, Is.Approximately(box.TopRight, 0.0001f));
Assert.That(shape._vertices._03, Is.Approximately(box.TopLeft, 0.0001f));
// AABB of a 45 degree rotated unit box will be enlarged by a factor of sqrt(2)
var transform = Transform.Empty;
var expected = Box2.UnitCentered.Translated(new Vector2(1, 1 - MathF.Sqrt(2))).Scale(Vector2.One * MathF.Sqrt(2));
var aabb = shape.ComputeAABB(transform, 0);
Assert.That(aabb, Is.Approximately(expected, 0.0001f));
Assert.That(aabb.Size, Is.Approximately(Vector2.One * MathF.Sqrt(2), 0.0001f));
// But if we pass a 45 degree rotation into ComputeAABB, the box will not be enlarged.
transform = new Transform(Vector2.Zero, Angle.FromDegrees(45));
expected = Box2.UnitCentered.Translated(new Vector2(1, MathF.Sqrt(2) - 1));
aabb = shape.ComputeAABB(transform, 0);
Assert.That(aabb, Is.Approximately(expected, 0.0001f));
Assert.That(aabb.Size, Is.Approximately(Vector2.One, 0.0001f));
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Numerics;
using System.Runtime.Intrinsics;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.UnitTesting.Shared.Physics;
[TestFixture]
[TestOf(typeof(Transform))]
public sealed class TransformTest
{
private static (Vector2 V, Transform T, Vector2 Exp)[] _vecMulData =
[
(Vector2.Zero, new Transform(Vector2.Zero, 0), Exp: Vector2.Zero),
(Vector2.Zero, new Transform(Vector2.One, 0), Exp: Vector2.One),
(Vector2.Zero, new Transform(Vector2.One, Angle.FromDegrees(45)), Vector2.One),
(Vector2.One, new Transform(Vector2.Zero, Angle.FromDegrees(45)), new(0, MathF.Sqrt(2))),
(Vector2.One, new Transform(Vector2.One, Angle.FromDegrees(45)), new(1, 1+MathF.Sqrt(2))),
(new Vector2(2f, 1f), new Transform(Vector2.One, Angle.FromDegrees(45)), new(1.707107f, 3.12132f)),
];
[Test]
public void TestVectorMul([ValueSource(nameof(_vecMulData))] (Vector2 V, Transform T, Vector2 Exp) dat)
{
var result = Transform.Mul(dat.T, dat.V);
Assert.That(result, Is.Approximately(dat.Exp, 0.001f));
}
[Test]
public void TestVectorMulSimd([ValueSource(nameof(_vecMulData))] (Vector2 V, Transform T, Vector2 Exp) dat)
{
var x = Vector128.Create(dat.V.X);
var y = Vector128.Create(dat.V.Y);
Transform.MulSimd(dat.T, x, y, out var xOut, out var yOut);
Assert.That(xOut, Is.Approximately(Vector128.Create(dat.Exp[0]), 0.0001f));
Assert.That(yOut, Is.Approximately(Vector128.Create(dat.Exp[1]), 0.0001f));
}
}