CultLib/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs
Meta Vens d514efebba 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

85 lines
2.9 KiB
C#

using Xunit;
using static CultMath.math;
namespace CultMath.Tests;
/// <summary>
/// HLSL written against CultMath compiles as C# under <c>using static CultMath.math;</c>.
/// The only spelling C# forces is float literal suffixes (<c>0.5f</c>): an unsuffixed
/// literal is a double in C#.
/// </summary>
public sealed class HlslSourceCompatibilityTests
{
// ---- HLSL-shaped source: keep this block free of C#-only constructs. ----
static float3 shade(float3 position, float3 normal, float2 wind, float time)
{
float3 p = position;
p.xz = p.xz + wind * time;
p.y = saturate(p.y);
float3x3 basis = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
float3 n = normalize(mul(basis, normal));
float ndotl = saturate(dot(n, normalize(float3(0.3f, 1, 0.2f))));
float3 color = lerp(float3(0.1f, 0.1f, 0.2f), float3(1, 0.9f, 0.7f), ndotl);
if (any(p < 0.0f))
color *= 0.5f;
if (all(abs(n) <= 1.0f))
color.yzx = color.yzx * float3(1, 1, 1);
color = select(p > 10.0f, float3(0), color);
float2 uv = frac(p.xz * 0.25f);
color.xy = lerp(color.xy, uv, 0.1f);
return color;
}
static float2 steer(float2 direction, float torque)
{
float2x2 turn = float2x2(cos(torque), -sin(torque), sin(torque), cos(torque));
float2 turned = mul(direction, turn);
return normalize(turned) * pow(length(turned), 1.0f);
}
static float fbm(float3 p, int octaves)
{
float sum = 0.0f;
float amp = 0.5f;
float freq = 1.0f;
for (int i = 0; i < octaves; i++)
{
sum += abs(snoise(p * freq)) * amp;
freq *= 2.0f;
amp *= 0.5f;
}
return sum;
}
// ---- end HLSL-shaped source ----
[Fact]
public void ShaderStyleMathCompilesAndEvaluates()
{
var color = shade(new float3(1.0f, 0.5f, 2.0f), new float3(0.0f, 1.0f, 0.0f), new float2(1.0f, 0.0f), 0.0f);
Assert.True(color.x > 0.1f && color.x <= 1.0f);
Assert.True(float.IsFinite(color.y) && float.IsFinite(color.z));
var far = shade(new float3(20.0f, 20.0f, 20.0f), new float3(0.0f, 1.0f, 0.0f), float2(0.0f), 0.0f);
Assert.Equal(0.0f, far.z);
}
[Fact]
public void AetheriaStyleRotationAndNoiseCompile()
{
var steered = steer(new float2(1.0f, 0.0f), 0.5f);
var viaRotate = mul(new float2(1.0f, 0.0f), CultMath.float2x2.Rotate(0.5f));
Assert.Equal(viaRotate.x, steered.x, precision: 5);
Assert.Equal(viaRotate.y, steered.y, precision: 5);
var view = mul(float3(0, 0, 1), CultMath.float3x3.Euler(float3(float2(0.3f, -1.1f).yx, 0), RotationOrder.YXZ));
Assert.Equal(1.0f, length(view), precision: 5);
var noise = fbm(float3(1.5f, -2.25f, 0.75f), 4);
Assert.InRange(noise, 0.0f, 1.0f);
}
}