2026-05-08 01:49:44 +01:00
|
|
|
using CultMath;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace CultMath.Tests;
|
|
|
|
|
|
|
|
|
|
public sealed class MathTests
|
|
|
|
|
{
|
|
|
|
|
[Fact]
|
|
|
|
|
public void FracMatchesShaderStylePositiveAndNegativeValues()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(0.25f, math.frac(1.25f));
|
|
|
|
|
Assert.Equal(0.75f, math.frac(-1.25f));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void VectorOperationsAreComponentWise()
|
|
|
|
|
{
|
|
|
|
|
var value = new float3(1.0f, 2.0f, 3.0f) * new float3(4.0f, 5.0f, 6.0f);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new float3(4.0f, 10.0f, 18.0f), value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 20:33:04 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void VectorConstructorsAcceptLowerDimensionVectors()
|
|
|
|
|
{
|
2026-07-12 19:33:59 +01:00
|
|
|
Assert.Equal(new int2(1, 2), math.int2(1, 2));
|
2026-06-10 21:22:43 +01:00
|
|
|
Assert.Equal(new float2(1.0f, 2.0f), math.float2(1.0f, 2.0f));
|
|
|
|
|
Assert.Equal(new float3(1.0f, 1.0f, 1.0f), math.float3(1.0f));
|
|
|
|
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(new float3(1.0f, 2.0f, 3.0f), 4.0f));
|
2026-05-25 20:33:04 +01:00
|
|
|
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), new float3(new float2(1.0f, 2.0f), 3.0f));
|
|
|
|
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float2(1.0f, 2.0f), 3.0f, 4.0f));
|
|
|
|
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f));
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 19:33:59 +01:00
|
|
|
[Fact]
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
public void VectorsExposeCommonSwizzles()
|
2026-07-12 19:33:59 +01:00
|
|
|
{
|
|
|
|
|
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).yzx);
|
|
|
|
|
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).zyx);
|
|
|
|
|
Assert.Equal(new float2(3.0f, 2.0f), new float3(1.0f, 2.0f, 3.0f).zy);
|
|
|
|
|
Assert.Equal(new float3(1.0f, 2.0f, 4.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).xyw);
|
|
|
|
|
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).yzx);
|
|
|
|
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(1.0f, new float3(2.0f, 3.0f, 4.0f)));
|
|
|
|
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(1.0f, new float3(2.0f, 3.0f, 4.0f)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void VectorsSupportUnityStyleFieldAndSwizzleMutation()
|
|
|
|
|
{
|
|
|
|
|
var position = new float3(1.0f, 2.0f, 3.0f);
|
|
|
|
|
position.y = 5.0f;
|
|
|
|
|
position.xz = new float2(8.0f, 13.0f);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new float3(8.0f, 5.0f, 13.0f), position);
|
|
|
|
|
|
|
|
|
|
var bounds = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
|
|
|
|
bounds.xy = new float2(-1.0f, -2.0f);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new float4(-1.0f, -2.0f, 3.0f, 4.0f), bounds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void DoubleVectorsSupportDampedMath()
|
|
|
|
|
{
|
|
|
|
|
var value = new double3(1.0, 2.0, 3.0);
|
|
|
|
|
value.xy = new double2(5.0, 8.0);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new double3(5.0, 8.0, 3.0), value);
|
|
|
|
|
Assert.Equal(new double2(Math.Exp(-2.0), Math.Exp(-4.0)), math.exp(new double2(-2.0, -4.0)));
|
|
|
|
|
Assert.Equal(new double3(2.0, 4.0, 6.0), math.lerp(new double3(0.0, 0.0, 0.0), new double3(4.0, 8.0, 12.0), new double3(0.5, 0.5, 0.5)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Int2SupportsGridArithmeticAndFloatPromotion()
|
|
|
|
|
{
|
|
|
|
|
var cell = new int2(2, 3);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new int2(3, 5), cell + new int2(1, 2));
|
|
|
|
|
Assert.Equal(new int2(4, 6), cell * new int2(2, 2));
|
|
|
|
|
Assert.Equal(3, cell[1]);
|
|
|
|
|
|
|
|
|
|
float2 promoted = cell;
|
|
|
|
|
Assert.Equal(new float2(2.0f, 3.0f), promoted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Bool2SupportsHullGridStyleStorage()
|
|
|
|
|
{
|
|
|
|
|
bool2 conductivity = true;
|
|
|
|
|
|
|
|
|
|
conductivity.y = false;
|
|
|
|
|
|
|
|
|
|
Assert.True(conductivity.x);
|
|
|
|
|
Assert.False(conductivity[1]);
|
|
|
|
|
Assert.Equal(new bool2(false, true), math.bool2(false, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void QuaternionLookRotationProducesUnitOrientation()
|
|
|
|
|
{
|
|
|
|
|
var rotation = quaternion.LookRotation(new float3(0.0f, 0.0f, 1.0f), new float3(0.0f, 1.0f, 0.0f));
|
|
|
|
|
|
|
|
|
|
Assert.Equal(quaternion.identity, rotation);
|
|
|
|
|
Assert.Equal(1.0f, math.length((float4)quaternion.LookRotation(new float3(1.0f, 0.0f, 0.0f), new float3(0.0f, 1.0f, 0.0f))), precision: 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-08-26 17:35:51 +02:00
|
|
|
public void RandomUsesStableXorShift32Sequence()
|
|
|
|
|
{
|
|
|
|
|
var a = new Random(1234);
|
|
|
|
|
var b = new Random(1234);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(332584831u, a.NextUInt());
|
|
|
|
|
Assert.Equal(1855942593u, a.NextUInt());
|
|
|
|
|
Assert.Equal(4018585650u, a.NextUInt());
|
|
|
|
|
Assert.Equal(3348358578u, a.NextUInt());
|
|
|
|
|
Assert.Equal(332584831u, b.NextUInt());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void RandomProvidesDeterministicPortableSampling()
|
2026-07-12 19:33:59 +01:00
|
|
|
{
|
|
|
|
|
var a = new Random(1234);
|
|
|
|
|
var b = new Random(1234);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(a.NextUInt(), b.NextUInt());
|
|
|
|
|
Assert.Equal(a.NextFloat(), b.NextFloat());
|
|
|
|
|
|
|
|
|
|
var bounded = a.NextFloat(-2.0f, 4.0f);
|
|
|
|
|
Assert.True(bounded >= -2.0f && bounded < 4.0f);
|
|
|
|
|
|
|
|
|
|
var direction = a.NextFloat2Direction();
|
|
|
|
|
Assert.Equal(1.0f, math.length(direction), precision: 5);
|
|
|
|
|
|
|
|
|
|
var point = a.NextFloat3(new float3(-1.0f, -2.0f, -3.0f), new float3(1.0f, 2.0f, 3.0f));
|
|
|
|
|
Assert.True(point.x >= -1.0f && point.x < 1.0f);
|
|
|
|
|
Assert.True(point.y >= -2.0f && point.y < 2.0f);
|
|
|
|
|
Assert.True(point.z >= -3.0f && point.z < 3.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 01:49:44 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void NormalizeProducesUnitLength()
|
|
|
|
|
{
|
|
|
|
|
var value = math.normalize(new float3(3.0f, 4.0f, 0.0f));
|
|
|
|
|
|
|
|
|
|
Assert.Equal(1.0f, math.length(value), precision: 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void SmoothstepUsesHermiteRamp()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(0.0f, math.smoothstep(0.0f, 1.0f, -1.0f));
|
|
|
|
|
Assert.Equal(0.5f, math.smoothstep(0.0f, 1.0f, 0.5f), precision: 5);
|
|
|
|
|
Assert.Equal(1.0f, math.smoothstep(0.0f, 1.0f, 2.0f));
|
2026-06-10 21:22:43 +01:00
|
|
|
Assert.Equal(0.5f, math.smootherstep(0.5f), precision: 5);
|
2026-05-08 01:49:44 +01:00
|
|
|
}
|
2026-05-25 20:33:04 +01:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void TrigIntrinsicsUseRadians()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(1.0f, math.sin(math.HALF_PI), precision: 5);
|
|
|
|
|
Assert.Equal(-1.0f, math.cos(math.PI), precision: 5);
|
|
|
|
|
Assert.Equal(1.0f, math.tan(math.PI * 0.25f), precision: 5);
|
|
|
|
|
Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5);
|
2026-07-12 19:33:59 +01:00
|
|
|
Assert.Equal(2, math.min(2, 5));
|
|
|
|
|
Assert.Equal(5, math.max(2, 5));
|
|
|
|
|
Assert.True(math.isinf(float.PositiveInfinity));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void SignIsComponentWiseForSimulationVectors()
|
|
|
|
|
{
|
Generate every HLSL swizzle and mixed constructor; fix intrinsic types
tools/generate-swizzles.ps1 writes the checked-in src/CultMath/Swizzles.g.cs
as partial structs plus a partial math: for float2/3/4, int2/3/4 and bool2/3/4
every swizzle of length 2-4 including repeats, in xyzw and rgba naming, with
setters only on non-repeating masks, plus single-component r/g/b/a. double2/3
get lengths 2-3 (there is no double4). Every HLSL mixed constructor
(float4(float2, float2), float4(float, float2, float), copy forms, ...) exists
as a struct constructor and, for float/int/bool, a math function. The
hand-written swizzles and mixed constructors it supersedes are deleted.
Matrices store private rows and return ref float2/float3 from the row
indexer ([UnscopedRef], polyfilled for netstandard2.1), so m[1][2] = s
compiles as in HLSL; _mRC are get/set properties over the rows. Equality and
hashing stay value-based.
Intrinsics follow HLSL: sign returns int/intN and yields 0 for NaN; step is
x >= y ? 1 : 0 so NaN yields 0; any/all accept float and int vectors;
min/max/abs on int vectors return int vectors, and abs(int) returns int.
Strips the UTF-8 BOMs this branch added to math.cs, CultMath.csproj and
HlslSemanticsTests.cs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:57:02 +02:00
|
|
|
Assert.Equal(-1, math.sign(-12.0f));
|
|
|
|
|
Assert.Equal(0, math.sign(0.0f));
|
|
|
|
|
Assert.Equal(1, math.sign(12.0f));
|
|
|
|
|
Assert.Equal(new int3(-1, 0, 1), math.sign(new float3(-2.0f, 0.0f, 4.0f)));
|
2026-07-12 19:33:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void RotateTurnsFloat2CounterClockwiseInRadians()
|
|
|
|
|
{
|
|
|
|
|
var rotated = math.rotate(new float2(1.0f, 0.0f), math.HALF_PI);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(0.0f, rotated.x, precision: 5);
|
|
|
|
|
Assert.Equal(1.0f, rotated.y, precision: 5);
|
|
|
|
|
|
|
|
|
|
var rotatedDegrees = math.rotate_degrees(new float2(0.0f, 1.0f), 90.0f);
|
|
|
|
|
Assert.Equal(-1.0f, rotatedDegrees.x, precision: 5);
|
|
|
|
|
Assert.Equal(0.0f, rotatedDegrees.y, precision: 5);
|
2026-05-25 20:33:04 +01:00
|
|
|
}
|
2026-06-03 02:58:56 +01:00
|
|
|
|
2026-06-10 21:22:43 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void AetheriaPrimitivesRemainSmallAndDeterministic()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(6.0f, math.csum(new float3(1.0f, 2.0f, 3.0f)));
|
2026-07-12 19:33:59 +01:00
|
|
|
Assert.Equal(0.25f, math.unlerp(10.0f, 30.0f, 15.0f));
|
|
|
|
|
Assert.Equal(new float2(0.25f, 0.75f), math.unlerp(new float2(10.0f, 10.0f), new float2(30.0f, 30.0f), new float2(15.0f, 25.0f)));
|
2026-06-10 21:22:43 +01:00
|
|
|
Assert.Equal(MathF.Exp(-2.0f), math.decay(1.0f, 2.0f, 1.0f), precision: 5);
|
|
|
|
|
Assert.Equal(1.0f - MathF.Exp(-2.0f), math.damp(0.0f, 1.0f, 2.0f, 1.0f), precision: 5);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(15.0f, math.catmullrom(0.0f, 10.0f, 20.0f, 30.0f, 0.5f), precision: 5);
|
|
|
|
|
Assert.Equal(0.25f, math.quadratic_bezier(0.0f, 0.0f, 1.0f, 0.5f), precision: 5);
|
|
|
|
|
Assert.Equal(0.5f, math.cubic_bezier(0.0f, 0.0f, 1.0f, 1.0f, 0.5f), precision: 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void SegmentDistanceReportsClosestPoint()
|
|
|
|
|
{
|
|
|
|
|
var distance = math.distance_to_segment(new float2(2.0f, 3.0f), new float2(0.0f, 0.0f), new float2(4.0f, 0.0f), out var closest);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(3.0f, distance, precision: 5);
|
|
|
|
|
Assert.Equal(new float2(2.0f, 0.0f), closest);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-07 20:02:24 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void RectNormalizesAndDerivesFromMinMax()
|
|
|
|
|
{
|
|
|
|
|
var bounds = new rect(8.0f, 6.0f, 2.0f, -2.0f);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(new float2(2.0f, -2.0f), bounds.min);
|
|
|
|
|
Assert.Equal(new float2(8.0f, 6.0f), bounds.max);
|
|
|
|
|
Assert.Equal(new float2(6.0f, 8.0f), bounds.size);
|
|
|
|
|
Assert.Equal(new float2(5.0f, 2.0f), bounds.center);
|
|
|
|
|
Assert.Equal(48.0f, bounds.area);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void RectContainmentIncludesBoundary()
|
|
|
|
|
{
|
|
|
|
|
var bounds = new rect(-2.0f, -1.0f, 4.0f, 3.0f);
|
|
|
|
|
|
|
|
|
|
Assert.True(bounds.Contains(new float2(-2.0f, -1.0f)));
|
|
|
|
|
Assert.True(bounds.Contains(new float2(4.0f, 3.0f)));
|
|
|
|
|
Assert.True(bounds.Contains(new rect(-1.0f, 0.0f, 1.0f, 2.0f)));
|
|
|
|
|
Assert.False(bounds.Contains(new float2(4.001f, 3.0f)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void RectIntersectionTreatsTouchingEdgesAsContact()
|
|
|
|
|
{
|
|
|
|
|
var left = new rect(0.0f, 0.0f, 4.0f, 4.0f);
|
|
|
|
|
var touching = new rect(4.0f, 1.0f, 8.0f, 3.0f);
|
|
|
|
|
var separate = new rect(4.001f, 1.0f, 8.0f, 3.0f);
|
|
|
|
|
|
|
|
|
|
Assert.True(left.Intersects(touching));
|
|
|
|
|
Assert.Equal(0.0f, left.Intersection(touching).area);
|
|
|
|
|
Assert.False(left.Intersects(separate));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 21:22:43 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void FirstOrderInterceptSolvesSimpleClosingTarget()
|
|
|
|
|
{
|
|
|
|
|
var time = math.first_order_intercept_time(
|
|
|
|
|
2.0f,
|
|
|
|
|
new float3(10.0f, 0.0f, 0.0f),
|
|
|
|
|
new float3(-1.0f, 0.0f, 0.0f));
|
|
|
|
|
|
|
|
|
|
Assert.Equal(10.0f / 3.0f, time, precision: 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void ValueNoiseIsDeterministicAndSmoothAtCellCorners()
|
|
|
|
|
{
|
|
|
|
|
var position = new float2(12.25f, -4.5f);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(math.value_noise(position), math.value_noise(position));
|
|
|
|
|
Assert.Equal(math.hash(new float2(3.0f, 5.0f)), math.value_noise(new float2(3.0f, 5.0f)), precision: 5);
|
|
|
|
|
|
|
|
|
|
var bicubic = math.value_noise_bicubic(position);
|
|
|
|
|
Assert.True(bicubic > -0.25f && bicubic < 1.25f);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-17 07:45:28 +01:00
|
|
|
[Theory]
|
|
|
|
|
[InlineData(0.0f, 0.0f, 0.0f)]
|
|
|
|
|
[InlineData(0.25f, -0.5f, -0.425866783f)]
|
|
|
|
|
[InlineData(12.25f, -4.5f, 0.258312f)]
|
|
|
|
|
[InlineData(536.5106f, 536.5106f, -0.6901103f)]
|
|
|
|
|
public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected)
|
|
|
|
|
{
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
Assert.Equal(expected, math.snoise(new float2(x, y)), precision: 6);
|
2026-07-17 07:45:28 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-03 02:58:56 +01:00
|
|
|
[Fact]
|
|
|
|
|
public void VoronoiBatchProducesToneMappedColors()
|
|
|
|
|
{
|
|
|
|
|
var xs = new[] { 0.0f, 24.0f, 96.0f };
|
|
|
|
|
var ys = new[] { 0.0f, 36.0f, 144.0f };
|
|
|
|
|
var tones = new[] { CultMathTone.Background, CultMathTone.Header, CultMathTone.Body };
|
|
|
|
|
var spans = new[] { 1920.0f, 8.0f, 8.0f };
|
|
|
|
|
var colors = new Color32[xs.Length];
|
|
|
|
|
|
|
|
|
|
Voronoi.SampleTones(xs, ys, tones, spans, 1080.0f, 12, colors);
|
|
|
|
|
|
|
|
|
|
Assert.NotEqual(new Color32(), colors[0]);
|
|
|
|
|
Assert.True(colors[1].r > colors[1].g);
|
|
|
|
|
Assert.True(colors[2].r > 120 && colors[2].g > 120 && colors[2].b > 120);
|
|
|
|
|
}
|
2026-06-17 02:53:02 +01:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void BatchRadialFalloffAccelerationMatchesScalarContract()
|
|
|
|
|
{
|
|
|
|
|
var xs = new[] { 0.0f, 5.0f, 10.0f };
|
|
|
|
|
var ys = new[] { 0.0f, 0.0f, 0.0f };
|
|
|
|
|
var ax = new float[xs.Length];
|
|
|
|
|
var ay = new float[xs.Length];
|
|
|
|
|
|
|
|
|
|
BatchMath.AddRadialFalloffAcceleration2D(xs, ys, 10.0f, 0.0f, 10.0f, 20.0f, ax, ay);
|
|
|
|
|
|
|
|
|
|
Assert.True(BatchMath.LaneCount >= 4);
|
|
|
|
|
Assert.Equal(5.0f, ax[0], precision: 5);
|
|
|
|
|
Assert.Equal(7.5f, ax[1], precision: 5);
|
|
|
|
|
Assert.Equal(0.0f, ax[2], precision: 5);
|
|
|
|
|
Assert.Equal(0.0f, ay[0], precision: 5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void BatchEulerIntegrationHonorsDynamicMask()
|
|
|
|
|
{
|
|
|
|
|
var dynamicMask = new[] { 1.0f, 0.0f };
|
|
|
|
|
var px = new[] { 0.0f, 0.0f };
|
|
|
|
|
var py = new[] { 0.0f, 0.0f };
|
|
|
|
|
var vx = new[] { 1.0f, 1.0f };
|
|
|
|
|
var vy = new[] { 0.0f, 0.0f };
|
|
|
|
|
var ax = new[] { 2.0f, 2.0f };
|
|
|
|
|
var ay = new[] { 0.0f, 4.0f };
|
|
|
|
|
|
|
|
|
|
BatchMath.IntegrateSemiImplicitEuler2D(0.5f, dynamicMask, px, py, vx, vy, ax, ay);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(2.0f, vx[0], precision: 5);
|
|
|
|
|
Assert.Equal(1.0f, px[0], precision: 5);
|
|
|
|
|
Assert.Equal(1.0f, vx[1], precision: 5);
|
|
|
|
|
Assert.Equal(0.0f, px[1], precision: 5);
|
|
|
|
|
Assert.Equal(0.0f, py[1], precision: 5);
|
|
|
|
|
}
|
2026-05-08 01:49:44 +01:00
|
|
|
}
|