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>
This commit is contained in:
parent
8be174b82c
commit
d514efebba
29 changed files with 1609 additions and 75 deletions
|
|
@ -73,11 +73,64 @@ float cultmath_smootherstep(float value)
|
|||
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
||||
}
|
||||
|
||||
float2 cultmath_simplex_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||
float3 cultmath_simplex_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||
float3 cultmath_simplex_permute(float3 value) { return cultmath_simplex_mod289(((value * 34.0) + 1.0) * value); }
|
||||
float2 cultmath_snoise_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||
float3 cultmath_snoise_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||
float4 cultmath_snoise_mod289(float4 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||
|
||||
float cultmath_simplex_noise(float2 value)
|
||||
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
|
||||
// order as the C# math.snoise(float3) mirror.
|
||||
float cultmath_snoise(float3 value)
|
||||
{
|
||||
const float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(value + dot(value, c.yyy));
|
||||
float3 x0 = value - i + dot(i, c.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
float3 x1 = x0 - i1 + c.xxx;
|
||||
float3 x2 = x0 - i2 + c.yyy;
|
||||
float3 x3 = x0 - 0.5;
|
||||
|
||||
i = cultmath_snoise_mod289(i);
|
||||
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
||||
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
||||
|
||||
const float n_ = 0.142857142857;
|
||||
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
||||
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
||||
float4 x_ = floor(j * ns.z);
|
||||
float4 y_ = floor(j - 7.0 * x_);
|
||||
float4 x = x_ * ns.x + ns.yyyy;
|
||||
float4 y = y_ * ns.x + ns.yyyy;
|
||||
float4 h = 1.0 - abs(x) - abs(y);
|
||||
|
||||
float4 b0 = float4(x.xy, y.xy);
|
||||
float4 b1 = float4(x.zw, y.zw);
|
||||
float4 s0 = floor(b0) * 2.0 + 1.0;
|
||||
float4 s1 = floor(b1) * 2.0 + 1.0;
|
||||
float4 sh = -step(h, 0.0);
|
||||
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
||||
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
||||
|
||||
float3 p0 = float3(a0.xy, h.x);
|
||||
float3 p1 = float3(a0.zw, h.y);
|
||||
float3 p2 = float3(a1.xy, h.z);
|
||||
float3 p3 = float3(a1.zw, h.w);
|
||||
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
|
||||
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
||||
m = m * m;
|
||||
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||
}
|
||||
|
||||
float cultmath_snoise(float2 value)
|
||||
{
|
||||
const float4 c = float4(
|
||||
0.211324865405187,
|
||||
|
|
|
|||
|
|
@ -58,5 +58,9 @@ public struct Random
|
|||
return new float2(math.cos(angle), math.sin(angle));
|
||||
}
|
||||
|
||||
public float3 NextFloat3(float3 min, float3 max) => min + new float3(NextFloat(), NextFloat(), NextFloat()) * (max - min);
|
||||
public float3 NextFloat3() => new(NextFloat(), NextFloat(), NextFloat());
|
||||
|
||||
public float3 NextFloat3(float3 max) => NextFloat3() * max;
|
||||
|
||||
public float3 NextFloat3(float3 min, float3 max) => min + NextFloat3() * (max - min);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,50 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct bool2(bool x, bool y)
|
||||
public struct bool2 : IEquatable<bool2>
|
||||
{
|
||||
public static readonly bool2 @false = new(false, false);
|
||||
public static readonly bool2 @true = new(true, true);
|
||||
|
||||
public bool this[int index] => index switch
|
||||
public bool x;
|
||||
public bool y;
|
||||
|
||||
public bool2(bool x, bool y)
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator bool2(bool value) => new(value, value);
|
||||
|
||||
public static bool2 operator !(bool2 value) => new(!value.x, !value.y);
|
||||
public static bool2 operator &(bool2 left, bool2 right) => new(left.x & right.x, left.y & right.y);
|
||||
public static bool2 operator |(bool2 left, bool2 right) => new(left.x | right.x, left.y | right.y);
|
||||
public static bool2 operator ^(bool2 left, bool2 right) => new(left.x ^ right.x, left.y ^ right.y);
|
||||
public static bool2 operator ==(bool2 left, bool2 right) => new(left.x == right.x, left.y == right.y);
|
||||
public static bool2 operator !=(bool2 left, bool2 right) => new(left.x != right.x, left.y != right.y);
|
||||
|
||||
public readonly bool Equals(bool2 other) => x == other.x && y == other.y;
|
||||
public override readonly bool Equals(object? obj) => obj is bool2 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||
public override readonly string ToString() => $"bool2({x}, {y})";
|
||||
}
|
||||
|
|
|
|||
55
packages/cultmath/src/CultMath/bool3.cs
Normal file
55
packages/cultmath/src/CultMath/bool3.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public struct bool3 : IEquatable<bool3>
|
||||
{
|
||||
public static readonly bool3 @false = new(false, false, false);
|
||||
public static readonly bool3 @true = new(true, true, true);
|
||||
|
||||
public bool x;
|
||||
public bool y;
|
||||
public bool z;
|
||||
|
||||
public bool3(bool x, bool y, bool z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator bool3(bool value) => new(value, value, value);
|
||||
|
||||
public static bool3 operator !(bool3 value) => new(!value.x, !value.y, !value.z);
|
||||
public static bool3 operator &(bool3 left, bool3 right) => new(left.x & right.x, left.y & right.y, left.z & right.z);
|
||||
public static bool3 operator |(bool3 left, bool3 right) => new(left.x | right.x, left.y | right.y, left.z | right.z);
|
||||
public static bool3 operator ^(bool3 left, bool3 right) => new(left.x ^ right.x, left.y ^ right.y, left.z ^ right.z);
|
||||
public static bool3 operator ==(bool3 left, bool3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||
public static bool3 operator !=(bool3 left, bool3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||
|
||||
public readonly bool Equals(bool3 other) => x == other.x && y == other.y && z == other.z;
|
||||
public override readonly bool Equals(object? obj) => obj is bool3 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||
public override readonly string ToString() => $"bool3({x}, {y}, {z})";
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/bool3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/bool3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 323ec670fc374c8ba4d87b4bf4c91f34
|
||||
59
packages/cultmath/src/CultMath/bool4.cs
Normal file
59
packages/cultmath/src/CultMath/bool4.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public struct bool4 : IEquatable<bool4>
|
||||
{
|
||||
public static readonly bool4 @false = new(false, false, false, false);
|
||||
public static readonly bool4 @true = new(true, true, true, true);
|
||||
|
||||
public bool x;
|
||||
public bool y;
|
||||
public bool z;
|
||||
public bool w;
|
||||
|
||||
public bool4(bool x, bool y, bool z, bool w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
3 => w,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
case 3: w = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator bool4(bool value) => new(value, value, value, value);
|
||||
|
||||
public static bool4 operator !(bool4 value) => new(!value.x, !value.y, !value.z, !value.w);
|
||||
public static bool4 operator &(bool4 left, bool4 right) => new(left.x & right.x, left.y & right.y, left.z & right.z, left.w & right.w);
|
||||
public static bool4 operator |(bool4 left, bool4 right) => new(left.x | right.x, left.y | right.y, left.z | right.z, left.w | right.w);
|
||||
public static bool4 operator ^(bool4 left, bool4 right) => new(left.x ^ right.x, left.y ^ right.y, left.z ^ right.z, left.w ^ right.w);
|
||||
public static bool4 operator ==(bool4 left, bool4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||
public static bool4 operator !=(bool4 left, bool4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||
|
||||
public readonly bool Equals(bool4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
public override readonly bool Equals(object? obj) => obj is bool4 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||
public override readonly string ToString() => $"bool4({x}, {y}, {z}, {w})";
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/bool4.cs.meta
Normal file
2
packages/cultmath/src/CultMath/bool4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d38b9f70d88c459584441b85ecbfd92a
|
||||
|
|
@ -2,17 +2,38 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct double2(double x, double y)
|
||||
public struct double2 : IEquatable<double2>
|
||||
{
|
||||
public static readonly double2 zero = new(0.0, 0.0);
|
||||
public static readonly double2 one = new(1.0, 1.0);
|
||||
|
||||
public double this[int index] => index switch
|
||||
public double x;
|
||||
public double y;
|
||||
|
||||
public double2(double x, double y)
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public double this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator double2(double value) => new(value, value);
|
||||
|
||||
|
|
@ -22,4 +43,16 @@ public record struct double2(double x, double y)
|
|||
public static double2 operator -(double2 left, double2 right) => new(left.x - right.x, left.y - right.y);
|
||||
public static double2 operator *(double2 left, double2 right) => new(left.x * right.x, left.y * right.y);
|
||||
public static double2 operator /(double2 left, double2 right) => new(left.x / right.x, left.y / right.y);
|
||||
|
||||
public static bool2 operator <(double2 left, double2 right) => new(left.x < right.x, left.y < right.y);
|
||||
public static bool2 operator >(double2 left, double2 right) => new(left.x > right.x, left.y > right.y);
|
||||
public static bool2 operator <=(double2 left, double2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||
public static bool2 operator >=(double2 left, double2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||
public static bool2 operator ==(double2 left, double2 right) => new(left.x == right.x, left.y == right.y);
|
||||
public static bool2 operator !=(double2 left, double2 right) => new(left.x != right.x, left.y != right.y);
|
||||
|
||||
public readonly bool Equals(double2 other) => x.Equals(other.x) && y.Equals(other.y);
|
||||
public override readonly bool Equals(object? obj) => obj is double2 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"double2({x:R}, {y:R})");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,25 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct double3(double x, double y, double z)
|
||||
public struct double3 : IEquatable<double3>
|
||||
{
|
||||
public static readonly double3 zero = new(0.0, 0.0, 0.0);
|
||||
public static readonly double3 one = new(1.0, 1.0, 1.0);
|
||||
|
||||
public double x;
|
||||
public double y;
|
||||
public double z;
|
||||
|
||||
public double3(double x, double y, double z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public double2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
readonly get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
|
|
@ -17,13 +28,26 @@ public record struct double3(double x, double y, double z)
|
|||
}
|
||||
}
|
||||
|
||||
public double this[int index] => index switch
|
||||
public double this[int index]
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator double3(double value) => new(value, value, value);
|
||||
|
||||
|
|
@ -33,4 +57,16 @@ public record struct double3(double x, double y, double z)
|
|||
public static double3 operator -(double3 left, double3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
public static double3 operator *(double3 left, double3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||
public static double3 operator /(double3 left, double3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||
|
||||
public static bool3 operator <(double3 left, double3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||
public static bool3 operator >(double3 left, double3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||
public static bool3 operator <=(double3 left, double3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||
public static bool3 operator >=(double3 left, double3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||
public static bool3 operator ==(double3 left, double3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||
public static bool3 operator !=(double3 left, double3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||
|
||||
public readonly bool Equals(double3 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z);
|
||||
public override readonly bool Equals(object? obj) => obj is double3 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"double3({x:R}, {y:R}, {z:R})");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,48 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float2(float x, float y)
|
||||
public struct float2 : IEquatable<float2>
|
||||
{
|
||||
public static readonly float2 zero = new(0.0f, 0.0f);
|
||||
public static readonly float2 one = new(1.0f, 1.0f);
|
||||
|
||||
public float this[int index] => index switch
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public float2(float x, float y)
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public float2 yx
|
||||
{
|
||||
readonly get => new(y, x);
|
||||
set
|
||||
{
|
||||
y = value.x;
|
||||
x = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
public float this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator float2(float value) => new(value, value);
|
||||
public static explicit operator System.Numerics.Vector2(float2 value) => new(value.x, value.y);
|
||||
|
|
@ -20,9 +51,44 @@ public record struct float2(float x, float y)
|
|||
|
||||
public static float2 operator +(float2 value) => value;
|
||||
public static float2 operator -(float2 value) => new(-value.x, -value.y);
|
||||
|
||||
public static float2 operator +(float2 left, float2 right) => new(left.x + right.x, left.y + right.y);
|
||||
public static float2 operator +(float2 left, float right) => new(left.x + right, left.y + right);
|
||||
public static float2 operator +(float left, float2 right) => new(left + right.x, left + right.y);
|
||||
public static float2 operator -(float2 left, float2 right) => new(left.x - right.x, left.y - right.y);
|
||||
public static float2 operator -(float2 left, float right) => new(left.x - right, left.y - right);
|
||||
public static float2 operator -(float left, float2 right) => new(left - right.x, left - right.y);
|
||||
public static float2 operator *(float2 left, float2 right) => new(left.x * right.x, left.y * right.y);
|
||||
public static float2 operator *(float2 left, float right) => new(left.x * right, left.y * right);
|
||||
public static float2 operator *(float left, float2 right) => new(left * right.x, left * right.y);
|
||||
public static float2 operator /(float2 left, float2 right) => new(left.x / right.x, left.y / right.y);
|
||||
public static float2 operator /(float2 left, float right) => new(left.x / right, left.y / right);
|
||||
public static float2 operator /(float left, float2 right) => new(left / right.x, left / right.y);
|
||||
public static float2 operator %(float2 left, float2 right) => new(left.x % right.x, left.y % right.y);
|
||||
public static float2 operator %(float2 left, float right) => new(left.x % right, left.y % right);
|
||||
public static float2 operator %(float left, float2 right) => new(left % right.x, left % right.y);
|
||||
|
||||
public static bool2 operator <(float2 left, float2 right) => new(left.x < right.x, left.y < right.y);
|
||||
public static bool2 operator <(float2 left, float right) => new(left.x < right, left.y < right);
|
||||
public static bool2 operator <(float left, float2 right) => new(left < right.x, left < right.y);
|
||||
public static bool2 operator >(float2 left, float2 right) => new(left.x > right.x, left.y > right.y);
|
||||
public static bool2 operator >(float2 left, float right) => new(left.x > right, left.y > right);
|
||||
public static bool2 operator >(float left, float2 right) => new(left > right.x, left > right.y);
|
||||
public static bool2 operator <=(float2 left, float2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||
public static bool2 operator <=(float2 left, float right) => new(left.x <= right, left.y <= right);
|
||||
public static bool2 operator <=(float left, float2 right) => new(left <= right.x, left <= right.y);
|
||||
public static bool2 operator >=(float2 left, float2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||
public static bool2 operator >=(float2 left, float right) => new(left.x >= right, left.y >= right);
|
||||
public static bool2 operator >=(float left, float2 right) => new(left >= right.x, left >= right.y);
|
||||
public static bool2 operator ==(float2 left, float2 right) => new(left.x == right.x, left.y == right.y);
|
||||
public static bool2 operator ==(float2 left, float right) => new(left.x == right, left.y == right);
|
||||
public static bool2 operator ==(float left, float2 right) => new(left == right.x, left == right.y);
|
||||
public static bool2 operator !=(float2 left, float2 right) => new(left.x != right.x, left.y != right.y);
|
||||
public static bool2 operator !=(float2 left, float right) => new(left.x != right, left.y != right);
|
||||
public static bool2 operator !=(float left, float2 right) => new(left != right.x, left != right.y);
|
||||
|
||||
public readonly bool Equals(float2 other) => x.Equals(other.x) && y.Equals(other.y);
|
||||
public override readonly bool Equals(object? obj) => obj is float2 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"float2({x:R}, {y:R})");
|
||||
}
|
||||
|
|
|
|||
69
packages/cultmath/src/CultMath/float2x2.cs
Normal file
69
packages/cultmath/src/CultMath/float2x2.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
/// <summary>
|
||||
/// HLSL float2x2: row-major. Constructors take rows, <c>m[i]</c> is row i, and
|
||||
/// <c>_mRC</c> names row R, column C exactly as HLSL does. Use <see cref="math.mul(float2x2, float2)"/>
|
||||
/// for products; there is no <c>*</c> matrix product because HLSL's <c>*</c> is component-wise.
|
||||
/// </summary>
|
||||
public struct float2x2 : IEquatable<float2x2>
|
||||
{
|
||||
public static readonly float2x2 identity = new(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
public static readonly float2x2 zero = new(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
public float _m00;
|
||||
public float _m01;
|
||||
public float _m10;
|
||||
public float _m11;
|
||||
|
||||
public float2x2(float m00, float m01, float m10, float m11)
|
||||
{
|
||||
_m00 = m00;
|
||||
_m01 = m01;
|
||||
_m10 = m10;
|
||||
_m11 = m11;
|
||||
}
|
||||
|
||||
public float2x2(float2 row0, float2 row1)
|
||||
: this(row0.x, row0.y, row1.x, row1.y)
|
||||
{
|
||||
}
|
||||
|
||||
public float2 this[int row]
|
||||
{
|
||||
readonly get => row switch
|
||||
{
|
||||
0 => new float2(_m00, _m01),
|
||||
1 => new float2(_m10, _m11),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(row)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case 0: _m00 = value.x; _m01 = value.y; break;
|
||||
case 1: _m10 = value.x; _m11 = value.y; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counter-clockwise rotation for column vectors: <c>mul(Rotate(a), v)</c> equals
|
||||
/// <c>math.rotate(v, a)</c>; <c>mul(v, Rotate(a))</c> rotates the other way.
|
||||
/// </summary>
|
||||
public static float2x2 Rotate(float radians)
|
||||
{
|
||||
var s = MathF.Sin(radians);
|
||||
var c = MathF.Cos(radians);
|
||||
return new float2x2(c, -s, s, c);
|
||||
}
|
||||
|
||||
public readonly bool Equals(float2x2 other) =>
|
||||
_m00.Equals(other._m00) && _m01.Equals(other._m01) && _m10.Equals(other._m10) && _m11.Equals(other._m11);
|
||||
|
||||
public override readonly bool Equals(object? obj) => obj is float2x2 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(_m00, _m01, _m10, _m11);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"float2x2({_m00:R}, {_m01:R}, {_m10:R}, {_m11:R})");
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/float2x2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float2x2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c0dcec14c0b7432dbd8ba0bebb0a7c2f
|
||||
|
|
@ -2,11 +2,22 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float3(float x, float y, float z)
|
||||
public struct float3 : IEquatable<float3>
|
||||
{
|
||||
public static readonly float3 zero = new(0.0f, 0.0f, 0.0f);
|
||||
public static readonly float3 one = new(1.0f, 1.0f, 1.0f);
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
|
||||
public float3(float x, float y, float z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public float3(float2 xy, float z)
|
||||
: this(xy.x, xy.y, z)
|
||||
{
|
||||
|
|
@ -14,7 +25,7 @@ public record struct float3(float x, float y, float z)
|
|||
|
||||
public float2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
readonly get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
|
|
@ -24,7 +35,7 @@ public record struct float3(float x, float y, float z)
|
|||
|
||||
public float2 xz
|
||||
{
|
||||
get => new(x, z);
|
||||
readonly get => new(x, z);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
|
|
@ -32,9 +43,19 @@ public record struct float3(float x, float y, float z)
|
|||
}
|
||||
}
|
||||
|
||||
public float2 yx
|
||||
{
|
||||
readonly get => new(y, x);
|
||||
set
|
||||
{
|
||||
y = value.x;
|
||||
x = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
public float2 yz
|
||||
{
|
||||
get => new(y, z);
|
||||
readonly get => new(y, z);
|
||||
set
|
||||
{
|
||||
y = value.x;
|
||||
|
|
@ -44,7 +65,7 @@ public record struct float3(float x, float y, float z)
|
|||
|
||||
public float2 zy
|
||||
{
|
||||
get => new(z, y);
|
||||
readonly get => new(z, y);
|
||||
set
|
||||
{
|
||||
z = value.x;
|
||||
|
|
@ -52,28 +73,115 @@ public record struct float3(float x, float y, float z)
|
|||
}
|
||||
}
|
||||
|
||||
public float3 yzx => new(y, z, x);
|
||||
public float3 zyx => new(z, y, x);
|
||||
|
||||
public float this[int index] => index switch
|
||||
public float3 xyz
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
readonly get => new(x, y, z);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
z = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float3 xzy
|
||||
{
|
||||
readonly get => new(x, z, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
z = value.y;
|
||||
y = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float3 yzx
|
||||
{
|
||||
readonly get => new(y, z, x);
|
||||
set
|
||||
{
|
||||
y = value.x;
|
||||
z = value.y;
|
||||
x = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float3 zyx
|
||||
{
|
||||
readonly get => new(z, y, x);
|
||||
set
|
||||
{
|
||||
z = value.x;
|
||||
y = value.y;
|
||||
x = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator float3(float value) => new(value, value, value);
|
||||
public static explicit operator System.Numerics.Vector3(float3 value) => new(value.x, value.y, value.z);
|
||||
public static explicit operator float3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z);
|
||||
|
||||
public override readonly string ToString() => $"float3({x:R}, {y:R}, {z:R})";
|
||||
|
||||
public static float3 operator +(float3 value) => value;
|
||||
public static float3 operator -(float3 value) => new(-value.x, -value.y, -value.z);
|
||||
|
||||
public static float3 operator +(float3 left, float3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
public static float3 operator +(float3 left, float right) => new(left.x + right, left.y + right, left.z + right);
|
||||
public static float3 operator +(float left, float3 right) => new(left + right.x, left + right.y, left + right.z);
|
||||
public static float3 operator -(float3 left, float3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
public static float3 operator -(float3 left, float right) => new(left.x - right, left.y - right, left.z - right);
|
||||
public static float3 operator -(float left, float3 right) => new(left - right.x, left - right.y, left - right.z);
|
||||
public static float3 operator *(float3 left, float3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||
public static float3 operator *(float3 left, float right) => new(left.x * right, left.y * right, left.z * right);
|
||||
public static float3 operator *(float left, float3 right) => new(left * right.x, left * right.y, left * right.z);
|
||||
public static float3 operator /(float3 left, float3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||
public static float3 operator /(float3 left, float right) => new(left.x / right, left.y / right, left.z / right);
|
||||
public static float3 operator /(float left, float3 right) => new(left / right.x, left / right.y, left / right.z);
|
||||
public static float3 operator %(float3 left, float3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
||||
public static float3 operator %(float3 left, float right) => new(left.x % right, left.y % right, left.z % right);
|
||||
public static float3 operator %(float left, float3 right) => new(left % right.x, left % right.y, left % right.z);
|
||||
|
||||
public static bool3 operator <(float3 left, float3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||
public static bool3 operator <(float3 left, float right) => new(left.x < right, left.y < right, left.z < right);
|
||||
public static bool3 operator <(float left, float3 right) => new(left < right.x, left < right.y, left < right.z);
|
||||
public static bool3 operator >(float3 left, float3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||
public static bool3 operator >(float3 left, float right) => new(left.x > right, left.y > right, left.z > right);
|
||||
public static bool3 operator >(float left, float3 right) => new(left > right.x, left > right.y, left > right.z);
|
||||
public static bool3 operator <=(float3 left, float3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||
public static bool3 operator <=(float3 left, float right) => new(left.x <= right, left.y <= right, left.z <= right);
|
||||
public static bool3 operator <=(float left, float3 right) => new(left <= right.x, left <= right.y, left <= right.z);
|
||||
public static bool3 operator >=(float3 left, float3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||
public static bool3 operator >=(float3 left, float right) => new(left.x >= right, left.y >= right, left.z >= right);
|
||||
public static bool3 operator >=(float left, float3 right) => new(left >= right.x, left >= right.y, left >= right.z);
|
||||
public static bool3 operator ==(float3 left, float3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||
public static bool3 operator ==(float3 left, float right) => new(left.x == right, left.y == right, left.z == right);
|
||||
public static bool3 operator ==(float left, float3 right) => new(left == right.x, left == right.y, left == right.z);
|
||||
public static bool3 operator !=(float3 left, float3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||
public static bool3 operator !=(float3 left, float right) => new(left.x != right, left.y != right, left.z != right);
|
||||
public static bool3 operator !=(float left, float3 right) => new(left != right.x, left != right.y, left != right.z);
|
||||
|
||||
public readonly bool Equals(float3 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z);
|
||||
public override readonly bool Equals(object? obj) => obj is float3 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"float3({x:R}, {y:R}, {z:R})");
|
||||
}
|
||||
|
|
|
|||
125
packages/cultmath/src/CultMath/float3x3.cs
Normal file
125
packages/cultmath/src/CultMath/float3x3.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
/// <summary>
|
||||
/// HLSL float3x3: row-major. Constructors take rows, <c>m[i]</c> is row i, and
|
||||
/// <c>_mRC</c> names row R, column C exactly as HLSL does. Use <see cref="math.mul(float3x3, float3)"/>
|
||||
/// for products; there is no <c>*</c> matrix product because HLSL's <c>*</c> is component-wise.
|
||||
/// </summary>
|
||||
public struct float3x3 : IEquatable<float3x3>
|
||||
{
|
||||
public static readonly float3x3 identity = new(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||
public static readonly float3x3 zero = new(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
public float _m00;
|
||||
public float _m01;
|
||||
public float _m02;
|
||||
public float _m10;
|
||||
public float _m11;
|
||||
public float _m12;
|
||||
public float _m20;
|
||||
public float _m21;
|
||||
public float _m22;
|
||||
|
||||
public float3x3(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22)
|
||||
{
|
||||
_m00 = m00;
|
||||
_m01 = m01;
|
||||
_m02 = m02;
|
||||
_m10 = m10;
|
||||
_m11 = m11;
|
||||
_m12 = m12;
|
||||
_m20 = m20;
|
||||
_m21 = m21;
|
||||
_m22 = m22;
|
||||
}
|
||||
|
||||
public float3x3(float3 row0, float3 row1, float3 row2)
|
||||
: this(row0.x, row0.y, row0.z, row1.x, row1.y, row1.z, row2.x, row2.y, row2.z)
|
||||
{
|
||||
}
|
||||
|
||||
public float3 this[int row]
|
||||
{
|
||||
readonly get => row switch
|
||||
{
|
||||
0 => new float3(_m00, _m01, _m02),
|
||||
1 => new float3(_m10, _m11, _m12),
|
||||
2 => new float3(_m20, _m21, _m22),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(row)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case 0: _m00 = value.x; _m01 = value.y; _m02 = value.z; break;
|
||||
case 1: _m10 = value.x; _m11 = value.y; _m12 = value.z; break;
|
||||
case 2: _m20 = value.x; _m21 = value.y; _m22 = value.z; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Rotation about +X for column vectors (<c>mul(m, v)</c>).</summary>
|
||||
public static float3x3 RotateX(float radians)
|
||||
{
|
||||
var s = MathF.Sin(radians);
|
||||
var c = MathF.Cos(radians);
|
||||
return new float3x3(1.0f, 0.0f, 0.0f, 0.0f, c, -s, 0.0f, s, c);
|
||||
}
|
||||
|
||||
/// <summary>Rotation about +Y for column vectors (<c>mul(m, v)</c>).</summary>
|
||||
public static float3x3 RotateY(float radians)
|
||||
{
|
||||
var s = MathF.Sin(radians);
|
||||
var c = MathF.Cos(radians);
|
||||
return new float3x3(c, 0.0f, s, 0.0f, 1.0f, 0.0f, -s, 0.0f, c);
|
||||
}
|
||||
|
||||
/// <summary>Rotation about +Z for column vectors (<c>mul(m, v)</c>).</summary>
|
||||
public static float3x3 RotateZ(float radians)
|
||||
{
|
||||
var s = MathF.Sin(radians);
|
||||
var c = MathF.Cos(radians);
|
||||
return new float3x3(c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extrinsic Euler rotation in radians. The order names the axis applied first:
|
||||
/// <see cref="math.RotationOrder.YXZ"/> rotates about Y, then X, then Z, so the
|
||||
/// matrix is <c>mul(RotateZ, mul(RotateX, RotateY))</c> for column vectors.
|
||||
/// </summary>
|
||||
public static float3x3 Euler(float3 xyz, math.RotationOrder order = math.RotationOrder.Default)
|
||||
{
|
||||
var x = RotateX(xyz.x);
|
||||
var y = RotateY(xyz.y);
|
||||
var z = RotateZ(xyz.z);
|
||||
return order switch
|
||||
{
|
||||
math.RotationOrder.XYZ => math.mul(z, math.mul(y, x)),
|
||||
math.RotationOrder.XZY => math.mul(y, math.mul(z, x)),
|
||||
math.RotationOrder.YXZ => math.mul(z, math.mul(x, y)),
|
||||
math.RotationOrder.YZX => math.mul(x, math.mul(z, y)),
|
||||
math.RotationOrder.ZXY => math.mul(y, math.mul(x, z)),
|
||||
math.RotationOrder.ZYX => math.mul(x, math.mul(y, z)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(order)),
|
||||
};
|
||||
}
|
||||
|
||||
public static float3x3 Euler(float x, float y, float z, math.RotationOrder order = math.RotationOrder.Default) =>
|
||||
Euler(new float3(x, y, z), order);
|
||||
|
||||
public readonly bool Equals(float3x3 other) =>
|
||||
_m00.Equals(other._m00) && _m01.Equals(other._m01) && _m02.Equals(other._m02) &&
|
||||
_m10.Equals(other._m10) && _m11.Equals(other._m11) && _m12.Equals(other._m12) &&
|
||||
_m20.Equals(other._m20) && _m21.Equals(other._m21) && _m22.Equals(other._m22);
|
||||
|
||||
public override readonly bool Equals(object? obj) => obj is float3x3 other && Equals(other);
|
||||
|
||||
public override readonly int GetHashCode() =>
|
||||
HashCode.Combine(HashCode.Combine(_m00, _m01, _m02), HashCode.Combine(_m10, _m11, _m12), HashCode.Combine(_m20, _m21, _m22));
|
||||
|
||||
public override readonly string ToString() =>
|
||||
FormattableString.Invariant($"float3x3({_m00:R}, {_m01:R}, {_m02:R}, {_m10:R}, {_m11:R}, {_m12:R}, {_m20:R}, {_m21:R}, {_m22:R})");
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/float3x3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float3x3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9ca36a9bfff144a9b51478a6d7b19c8e
|
||||
|
|
@ -2,11 +2,24 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float4(float x, float y, float z, float w)
|
||||
public struct float4 : IEquatable<float4>
|
||||
{
|
||||
public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public float w;
|
||||
|
||||
public float4(float x, float y, float z, float w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public float4(float2 xy, float z, float w)
|
||||
: this(xy.x, xy.y, z, w)
|
||||
{
|
||||
|
|
@ -24,25 +37,69 @@ public record struct float4(float x, float y, float z, float w)
|
|||
|
||||
public float2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
readonly get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
}
|
||||
}
|
||||
public float3 xyw => new(x, y, w);
|
||||
public float3 yzx => new(y, z, x);
|
||||
public float3 xyz => new(x, y, z);
|
||||
|
||||
public float this[int index] => index switch
|
||||
public float3 xyz
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
3 => w,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
readonly get => new(x, y, z);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
z = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float3 xyw
|
||||
{
|
||||
readonly get => new(x, y, w);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
w = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float3 yzx
|
||||
{
|
||||
readonly get => new(y, z, x);
|
||||
set
|
||||
{
|
||||
y = value.x;
|
||||
z = value.y;
|
||||
x = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public float this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
3 => w,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
case 3: w = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator float4(float value) => new(value, value, value, value);
|
||||
public static explicit operator System.Numerics.Vector4(float4 value) => new(value.x, value.y, value.z, value.w);
|
||||
|
|
@ -50,9 +107,44 @@ public record struct float4(float x, float y, float z, float w)
|
|||
|
||||
public static float4 operator +(float4 value) => value;
|
||||
public static float4 operator -(float4 value) => new(-value.x, -value.y, -value.z, -value.w);
|
||||
|
||||
public static float4 operator +(float4 left, float4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
|
||||
public static float4 operator +(float4 left, float right) => new(left.x + right, left.y + right, left.z + right, left.w + right);
|
||||
public static float4 operator +(float left, float4 right) => new(left + right.x, left + right.y, left + right.z, left + right.w);
|
||||
public static float4 operator -(float4 left, float4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
|
||||
public static float4 operator -(float4 left, float right) => new(left.x - right, left.y - right, left.z - right, left.w - right);
|
||||
public static float4 operator -(float left, float4 right) => new(left - right.x, left - right.y, left - right.z, left - right.w);
|
||||
public static float4 operator *(float4 left, float4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w);
|
||||
public static float4 operator *(float4 left, float right) => new(left.x * right, left.y * right, left.z * right, left.w * right);
|
||||
public static float4 operator *(float left, float4 right) => new(left * right.x, left * right.y, left * right.z, left * right.w);
|
||||
public static float4 operator /(float4 left, float4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w);
|
||||
public static float4 operator /(float4 left, float right) => new(left.x / right, left.y / right, left.z / right, left.w / right);
|
||||
public static float4 operator /(float left, float4 right) => new(left / right.x, left / right.y, left / right.z, left / right.w);
|
||||
public static float4 operator %(float4 left, float4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
||||
public static float4 operator %(float4 left, float right) => new(left.x % right, left.y % right, left.z % right, left.w % right);
|
||||
public static float4 operator %(float left, float4 right) => new(left % right.x, left % right.y, left % right.z, left % right.w);
|
||||
|
||||
public static bool4 operator <(float4 left, float4 right) => new(left.x < right.x, left.y < right.y, left.z < right.z, left.w < right.w);
|
||||
public static bool4 operator <(float4 left, float right) => new(left.x < right, left.y < right, left.z < right, left.w < right);
|
||||
public static bool4 operator <(float left, float4 right) => new(left < right.x, left < right.y, left < right.z, left < right.w);
|
||||
public static bool4 operator >(float4 left, float4 right) => new(left.x > right.x, left.y > right.y, left.z > right.z, left.w > right.w);
|
||||
public static bool4 operator >(float4 left, float right) => new(left.x > right, left.y > right, left.z > right, left.w > right);
|
||||
public static bool4 operator >(float left, float4 right) => new(left > right.x, left > right.y, left > right.z, left > right.w);
|
||||
public static bool4 operator <=(float4 left, float4 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z, left.w <= right.w);
|
||||
public static bool4 operator <=(float4 left, float right) => new(left.x <= right, left.y <= right, left.z <= right, left.w <= right);
|
||||
public static bool4 operator <=(float left, float4 right) => new(left <= right.x, left <= right.y, left <= right.z, left <= right.w);
|
||||
public static bool4 operator >=(float4 left, float4 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z, left.w >= right.w);
|
||||
public static bool4 operator >=(float4 left, float right) => new(left.x >= right, left.y >= right, left.z >= right, left.w >= right);
|
||||
public static bool4 operator >=(float left, float4 right) => new(left >= right.x, left >= right.y, left >= right.z, left >= right.w);
|
||||
public static bool4 operator ==(float4 left, float4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||
public static bool4 operator ==(float4 left, float right) => new(left.x == right, left.y == right, left.z == right, left.w == right);
|
||||
public static bool4 operator ==(float left, float4 right) => new(left == right.x, left == right.y, left == right.z, left == right.w);
|
||||
public static bool4 operator !=(float4 left, float4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||
public static bool4 operator !=(float4 left, float right) => new(left.x != right, left.y != right, left.z != right, left.w != right);
|
||||
public static bool4 operator !=(float left, float4 right) => new(left != right.x, left != right.y, left != right.z, left != right.w);
|
||||
|
||||
public readonly bool Equals(float4 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z) && w.Equals(other.w);
|
||||
public override readonly bool Equals(object? obj) => obj is float4 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"float4({x:R}, {y:R}, {z:R}, {w:R})");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,38 @@ using System;
|
|||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct int2(int x, int y)
|
||||
public struct int2 : IEquatable<int2>
|
||||
{
|
||||
public static readonly int2 zero = new(0, 0);
|
||||
public static readonly int2 one = new(1, 1);
|
||||
|
||||
public int this[int index] => index switch
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
public int2(int x, int y)
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator int2(int value) => new(value, value);
|
||||
public static implicit operator float2(int2 value) => new(value.x, value.y);
|
||||
|
|
@ -26,4 +47,16 @@ public record struct int2(int x, int y)
|
|||
public static int2 operator *(int2 left, int2 right) => new(left.x * right.x, left.y * right.y);
|
||||
public static int2 operator /(int2 left, int2 right) => new(left.x / right.x, left.y / right.y);
|
||||
public static int2 operator %(int2 left, int2 right) => new(left.x % right.x, left.y % right.y);
|
||||
|
||||
public static bool2 operator <(int2 left, int2 right) => new(left.x < right.x, left.y < right.y);
|
||||
public static bool2 operator >(int2 left, int2 right) => new(left.x > right.x, left.y > right.y);
|
||||
public static bool2 operator <=(int2 left, int2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||
public static bool2 operator >=(int2 left, int2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||
public static bool2 operator ==(int2 left, int2 right) => new(left.x == right.x, left.y == right.y);
|
||||
public static bool2 operator !=(int2 left, int2 right) => new(left.x != right.x, left.y != right.y);
|
||||
|
||||
public readonly bool Equals(int2 other) => x == other.x && y == other.y;
|
||||
public override readonly bool Equals(object? obj) => obj is int2 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"int2({x}, {y})");
|
||||
}
|
||||
|
|
|
|||
64
packages/cultmath/src/CultMath/int3.cs
Normal file
64
packages/cultmath/src/CultMath/int3.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public struct int3 : IEquatable<int3>
|
||||
{
|
||||
public static readonly int3 zero = new(0, 0, 0);
|
||||
public static readonly int3 one = new(1, 1, 1);
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
public int z;
|
||||
|
||||
public int3(int x, int y, int z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator int3(int value) => new(value, value, value);
|
||||
public static implicit operator float3(int3 value) => new(value.x, value.y, value.z);
|
||||
|
||||
public static int3 operator +(int3 value) => value;
|
||||
public static int3 operator -(int3 value) => new(-value.x, -value.y, -value.z);
|
||||
public static int3 operator +(int3 left, int3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||
public static int3 operator -(int3 left, int3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||
public static int3 operator *(int3 left, int3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||
public static int3 operator /(int3 left, int3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||
public static int3 operator %(int3 left, int3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
||||
|
||||
public static bool3 operator <(int3 left, int3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||
public static bool3 operator >(int3 left, int3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||
public static bool3 operator <=(int3 left, int3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||
public static bool3 operator >=(int3 left, int3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||
public static bool3 operator ==(int3 left, int3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||
public static bool3 operator !=(int3 left, int3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||
|
||||
public readonly bool Equals(int3 other) => x == other.x && y == other.y && z == other.z;
|
||||
public override readonly bool Equals(object? obj) => obj is int3 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"int3({x}, {y}, {z})");
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/int3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/int3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 42ee98aace354db19b3ae87bdcbc1df9
|
||||
68
packages/cultmath/src/CultMath/int4.cs
Normal file
68
packages/cultmath/src/CultMath/int4.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public struct int4 : IEquatable<int4>
|
||||
{
|
||||
public static readonly int4 zero = new(0, 0, 0, 0);
|
||||
public static readonly int4 one = new(1, 1, 1, 1);
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
public int z;
|
||||
public int w;
|
||||
|
||||
public int4(int x, int y, int z, int w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public int this[int index]
|
||||
{
|
||||
readonly get => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
3 => w,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: x = value; break;
|
||||
case 1: y = value; break;
|
||||
case 2: z = value; break;
|
||||
case 3: w = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator int4(int value) => new(value, value, value, value);
|
||||
public static implicit operator float4(int4 value) => new(value.x, value.y, value.z, value.w);
|
||||
|
||||
public static int4 operator +(int4 value) => value;
|
||||
public static int4 operator -(int4 value) => new(-value.x, -value.y, -value.z, -value.w);
|
||||
public static int4 operator +(int4 left, int4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
|
||||
public static int4 operator -(int4 left, int4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
|
||||
public static int4 operator *(int4 left, int4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w);
|
||||
public static int4 operator /(int4 left, int4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w);
|
||||
public static int4 operator %(int4 left, int4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
||||
|
||||
public static bool4 operator <(int4 left, int4 right) => new(left.x < right.x, left.y < right.y, left.z < right.z, left.w < right.w);
|
||||
public static bool4 operator >(int4 left, int4 right) => new(left.x > right.x, left.y > right.y, left.z > right.z, left.w > right.w);
|
||||
public static bool4 operator <=(int4 left, int4 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z, left.w <= right.w);
|
||||
public static bool4 operator >=(int4 left, int4 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z, left.w >= right.w);
|
||||
public static bool4 operator ==(int4 left, int4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||
public static bool4 operator !=(int4 left, int4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||
|
||||
public readonly bool Equals(int4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
public override readonly bool Equals(object? obj) => obj is int4 other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"int4({x}, {y}, {z}, {w})");
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/int4.cs.meta
Normal file
2
packages/cultmath/src/CultMath/int4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1a313709f76a430eb83901bb2d05c74e
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
|
|
@ -8,6 +8,22 @@ public static class math
|
|||
public const float TAU = MathF.PI * 2.0f;
|
||||
public const float HALF_PI = MathF.PI * 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Extrinsic Euler order for <see cref="CultMath.float3x3.Euler(CultMath.float3, RotationOrder)"/>:
|
||||
/// the first letter is the axis applied first. Nested here so <c>using static CultMath.math;</c>
|
||||
/// brings it into scope with the rest of the shader vocabulary.
|
||||
/// </summary>
|
||||
public enum RotationOrder : byte
|
||||
{
|
||||
XYZ,
|
||||
XZY,
|
||||
YXZ,
|
||||
YZX,
|
||||
ZXY,
|
||||
ZYX,
|
||||
Default = ZXY,
|
||||
}
|
||||
|
||||
public static float2 float2(float x, float y) => new(x, y);
|
||||
public static float2 float2(float value) => new(value, value);
|
||||
public static bool2 bool2(bool x, bool y) => new(x, y);
|
||||
|
|
@ -22,6 +38,23 @@ public static class math
|
|||
public static float4 float4(float3 xyz, float w) => new(xyz, w);
|
||||
public static float4 float4(float x, float3 yzw) => new(x, yzw);
|
||||
public static float4 float4(float value) => new(value, value, value, value);
|
||||
public static bool3 bool3(bool x, bool y, bool z) => new(x, y, z);
|
||||
public static bool3 bool3(bool value) => new(value, value, value);
|
||||
public static bool4 bool4(bool x, bool y, bool z, bool w) => new(x, y, z, w);
|
||||
public static bool4 bool4(bool value) => new(value, value, value, value);
|
||||
public static int3 int3(int x, int y, int z) => new(x, y, z);
|
||||
public static int3 int3(int value) => new(value, value, value);
|
||||
public static int4 int4(int x, int y, int z, int w) => new(x, y, z, w);
|
||||
public static int4 int4(int value) => new(value, value, value, value);
|
||||
|
||||
// Matrix constructor functions take rows, as HLSL does. Under `using static CultMath.math;`
|
||||
// these method groups hide the type names in member access, so static members need a
|
||||
// qualified type: `CultMath.float3x3.Euler(...)`, `CultMath.float2x2.Rotate(...)`.
|
||||
public static float2x2 float2x2(float m00, float m01, float m10, float m11) => new(m00, m01, m10, m11);
|
||||
public static float2x2 float2x2(float2 row0, float2 row1) => new(row0, row1);
|
||||
public static float3x3 float3x3(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) =>
|
||||
new(m00, m01, m02, m10, m11, m12, m20, m21, m22);
|
||||
public static float3x3 float3x3(float3 row0, float3 row1, float3 row2) => new(row0, row1, row2);
|
||||
public static float radians(float degrees) => degrees * (PI / 180.0f);
|
||||
public static float degrees(float radians) => radians * (180.0f / PI);
|
||||
public static float sin(float value) => MathF.Sin(value);
|
||||
|
|
@ -324,9 +357,149 @@ public static class math
|
|||
public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f)));
|
||||
public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f)));
|
||||
|
||||
public static float log(float value) => MathF.Log(value);
|
||||
public static float2 log(float2 value) => new(log(value.x), log(value.y));
|
||||
public static float3 log(float3 value) => new(log(value.x), log(value.y), log(value.z));
|
||||
public static float4 log(float4 value) => new(log(value.x), log(value.y), log(value.z), log(value.w));
|
||||
|
||||
public static float2 pow(float2 value, float2 power) => new(pow(value.x, power.x), pow(value.y, power.y));
|
||||
public static float3 pow(float3 value, float3 power) => new(pow(value.x, power.x), pow(value.y, power.y), pow(value.z, power.z));
|
||||
public static float4 pow(float4 value, float4 power) => new(pow(value.x, power.x), pow(value.y, power.y), pow(value.z, power.z), pow(value.w, power.w));
|
||||
|
||||
public static float2 sqrt(float2 value) => new(sqrt(value.x), sqrt(value.y));
|
||||
public static float3 sqrt(float3 value) => new(sqrt(value.x), sqrt(value.y), sqrt(value.z));
|
||||
public static float4 sqrt(float4 value) => new(sqrt(value.x), sqrt(value.y), sqrt(value.z), sqrt(value.w));
|
||||
|
||||
public static float2 sin(float2 value) => new(sin(value.x), sin(value.y));
|
||||
public static float3 sin(float3 value) => new(sin(value.x), sin(value.y), sin(value.z));
|
||||
public static float4 sin(float4 value) => new(sin(value.x), sin(value.y), sin(value.z), sin(value.w));
|
||||
|
||||
public static float2 cos(float2 value) => new(cos(value.x), cos(value.y));
|
||||
public static float3 cos(float3 value) => new(cos(value.x), cos(value.y), cos(value.z));
|
||||
public static float4 cos(float4 value) => new(cos(value.x), cos(value.y), cos(value.z), cos(value.w));
|
||||
|
||||
public static float2 acos(float2 value) => new(acos(value.x), acos(value.y));
|
||||
public static float3 acos(float3 value) => new(acos(value.x), acos(value.y), acos(value.z));
|
||||
public static float4 acos(float4 value) => new(acos(value.x), acos(value.y), acos(value.z), acos(value.w));
|
||||
|
||||
public static float2 atan2(float2 y, float2 x) => new(atan2(y.x, x.x), atan2(y.y, x.y));
|
||||
public static float3 atan2(float3 y, float3 x) => new(atan2(y.x, x.x), atan2(y.y, x.y), atan2(y.z, x.z));
|
||||
public static float4 atan2(float4 y, float4 x) => new(atan2(y.x, x.x), atan2(y.y, x.y), atan2(y.z, x.z), atan2(y.w, x.w));
|
||||
|
||||
public static double min(double left, double right) => Math.Min(left, right);
|
||||
public static double max(double left, double right) => Math.Max(left, right);
|
||||
|
||||
public static bool any(bool2 value) => value.x || value.y;
|
||||
public static bool any(bool3 value) => value.x || value.y || value.z;
|
||||
public static bool any(bool4 value) => value.x || value.y || value.z || value.w;
|
||||
public static bool all(bool2 value) => value.x && value.y;
|
||||
public static bool all(bool3 value) => value.x && value.y && value.z;
|
||||
public static bool all(bool4 value) => value.x && value.y && value.z && value.w;
|
||||
|
||||
// HLSL 2021 argument order: select(condition, whenTrue, whenFalse).
|
||||
// Unity.Mathematics' select(falseValue, trueValue, condition) is the reverse.
|
||||
public static float select(bool condition, float whenTrue, float whenFalse) => condition ? whenTrue : whenFalse;
|
||||
public static float2 select(bool2 condition, float2 whenTrue, float2 whenFalse) =>
|
||||
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y);
|
||||
public static float3 select(bool3 condition, float3 whenTrue, float3 whenFalse) =>
|
||||
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y, condition.z ? whenTrue.z : whenFalse.z);
|
||||
public static float4 select(bool4 condition, float4 whenTrue, float4 whenFalse) =>
|
||||
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y, condition.z ? whenTrue.z : whenFalse.z, condition.w ? whenTrue.w : whenFalse.w);
|
||||
|
||||
// HLSL mul: mul(m, v) treats v as a column vector, mul(v, m) as a row vector.
|
||||
public static float mul(float left, float right) => left * right;
|
||||
|
||||
public static float2 mul(float2x2 m, float2 v) =>
|
||||
new(m._m00 * v.x + m._m01 * v.y, m._m10 * v.x + m._m11 * v.y);
|
||||
|
||||
public static float2 mul(float2 v, float2x2 m) =>
|
||||
new(v.x * m._m00 + v.y * m._m10, v.x * m._m01 + v.y * m._m11);
|
||||
|
||||
public static float2x2 mul(float2x2 a, float2x2 b) =>
|
||||
new(
|
||||
a._m00 * b._m00 + a._m01 * b._m10, a._m00 * b._m01 + a._m01 * b._m11,
|
||||
a._m10 * b._m00 + a._m11 * b._m10, a._m10 * b._m01 + a._m11 * b._m11);
|
||||
|
||||
public static float3 mul(float3x3 m, float3 v) =>
|
||||
new(
|
||||
m._m00 * v.x + m._m01 * v.y + m._m02 * v.z,
|
||||
m._m10 * v.x + m._m11 * v.y + m._m12 * v.z,
|
||||
m._m20 * v.x + m._m21 * v.y + m._m22 * v.z);
|
||||
|
||||
public static float3 mul(float3 v, float3x3 m) =>
|
||||
new(
|
||||
v.x * m._m00 + v.y * m._m10 + v.z * m._m20,
|
||||
v.x * m._m01 + v.y * m._m11 + v.z * m._m21,
|
||||
v.x * m._m02 + v.y * m._m12 + v.z * m._m22);
|
||||
|
||||
public static float3x3 mul(float3x3 a, float3x3 b) =>
|
||||
new(
|
||||
a._m00 * b._m00 + a._m01 * b._m10 + a._m02 * b._m20,
|
||||
a._m00 * b._m01 + a._m01 * b._m11 + a._m02 * b._m21,
|
||||
a._m00 * b._m02 + a._m01 * b._m12 + a._m02 * b._m22,
|
||||
a._m10 * b._m00 + a._m11 * b._m10 + a._m12 * b._m20,
|
||||
a._m10 * b._m01 + a._m11 * b._m11 + a._m12 * b._m21,
|
||||
a._m10 * b._m02 + a._m11 * b._m12 + a._m12 * b._m22,
|
||||
a._m20 * b._m00 + a._m21 * b._m10 + a._m22 * b._m20,
|
||||
a._m20 * b._m01 + a._m21 * b._m11 + a._m22 * b._m21,
|
||||
a._m20 * b._m02 + a._m21 * b._m12 + a._m22 * b._m22);
|
||||
|
||||
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), mirrored by cultmath_snoise(float3)
|
||||
// in shaders/CultMath.hlsl with the same float32 evaluation order.
|
||||
public static float snoise(float3 value)
|
||||
{
|
||||
const float cx = 1.0f / 6.0f;
|
||||
const float cy = 1.0f / 3.0f;
|
||||
var i = floor(value + dot(value, new float3(cy, cy, cy)));
|
||||
var x0 = value - i + dot(i, new float3(cx, cx, cx));
|
||||
var g = step(new float3(x0.y, x0.z, x0.x), x0);
|
||||
var l = 1.0f - g;
|
||||
var lzxy = new float3(l.z, l.x, l.y);
|
||||
var i1 = min(g, lzxy);
|
||||
var i2 = max(g, lzxy);
|
||||
var x1 = x0 - i1 + cx;
|
||||
var x2 = x0 - i2 + cy;
|
||||
var x3 = x0 - 0.5f;
|
||||
|
||||
i = snoise_mod289(i);
|
||||
var p = snoise_permute(snoise_permute(snoise_permute(
|
||||
i.z + new float4(0.0f, i1.z, i2.z, 1.0f)) + i.y + new float4(0.0f, i1.y, i2.y, 1.0f)) + i.x + new float4(0.0f, i1.x, i2.x, 1.0f));
|
||||
|
||||
const float n = 0.142857142857f;
|
||||
var ns = new float3(2.0f * n, 0.5f * n - 1.0f, n);
|
||||
var j = p - 49.0f * floor(p * ns.z * ns.z);
|
||||
var xs = floor(j * ns.z);
|
||||
var ys = floor(j - 7.0f * xs);
|
||||
var x = xs * ns.x + ns.y;
|
||||
var y = ys * ns.x + ns.y;
|
||||
var h = 1.0f - abs(x) - abs(y);
|
||||
|
||||
var b0 = new float4(x.x, x.y, y.x, y.y);
|
||||
var b1 = new float4(x.z, x.w, y.z, y.w);
|
||||
var s0 = floor(b0) * 2.0f + 1.0f;
|
||||
var s1 = floor(b1) * 2.0f + 1.0f;
|
||||
var sh = -step(h, 0.0f);
|
||||
var a0 = new float4(b0.x, b0.z, b0.y, b0.w) + new float4(s0.x, s0.z, s0.y, s0.w) * new float4(sh.x, sh.x, sh.y, sh.y);
|
||||
var a1 = new float4(b1.x, b1.z, b1.y, b1.w) + new float4(s1.x, s1.z, s1.y, s1.w) * new float4(sh.z, sh.z, sh.w, sh.w);
|
||||
|
||||
var p0 = new float3(a0.x, a0.y, h.x);
|
||||
var p1 = new float3(a0.z, a0.w, h.y);
|
||||
var p2 = new float3(a1.x, a1.y, h.z);
|
||||
var p3 = new float3(a1.z, a1.w, h.w);
|
||||
var norm = 1.79284291400159f - 0.85373472095314f * new float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
|
||||
var m = max(0.6f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
|
||||
m = m * m;
|
||||
return 42.0f * dot(m * m, new float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||
}
|
||||
|
||||
// Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so
|
||||
// the C# and HLSL mirrors preserve the same float32 evaluation order.
|
||||
public static float simplex_noise(float2 value)
|
||||
public static float snoise(float2 value)
|
||||
{
|
||||
var c = new float4(
|
||||
0.211324865405187f,
|
||||
|
|
@ -342,8 +515,8 @@ public static class math
|
|||
x0.x + c.z,
|
||||
x0.y + c.z);
|
||||
|
||||
i = simplex_mod289(i);
|
||||
var p = simplex_permute(simplex_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f));
|
||||
i = snoise_mod289(i);
|
||||
var p = snoise_permute(snoise_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f));
|
||||
var m = max(0.5f - new float3(
|
||||
dot(x0, x0),
|
||||
x12.x * x12.x + x12.y * x12.y,
|
||||
|
|
@ -364,9 +537,11 @@ public static class math
|
|||
return 130.0f * dot(m, g);
|
||||
}
|
||||
|
||||
private static float2 simplex_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||
private static float3 simplex_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||
private static float3 simplex_permute(float3 value) => simplex_mod289(((value * 34.0f) + 1.0f) * value);
|
||||
private static float2 snoise_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||
private static float3 snoise_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||
private static float4 snoise_mod289(float4 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||
private static float3 snoise_permute(float3 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
|
||||
private static float4 snoise_permute(float4 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
|
||||
|
||||
public static float value_noise(float2 position)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct quaternion(float x, float y, float z, float w)
|
||||
/// <summary>
|
||||
/// Not an HLSL type: shaders carry rotations as float4. Equality therefore stays
|
||||
/// scalar (<c>==</c> returns bool) instead of following the component-wise vector rule.
|
||||
/// </summary>
|
||||
public struct quaternion : IEquatable<quaternion>
|
||||
{
|
||||
public static readonly quaternion identity = new(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public float w;
|
||||
|
||||
public quaternion(float x, float y, float z, float w)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public static quaternion LookRotation(float3 forward, float3 up)
|
||||
{
|
||||
forward = math.normalize(forward);
|
||||
|
|
@ -65,4 +84,12 @@ public record struct quaternion(float x, float y, float z, float w)
|
|||
|
||||
public static implicit operator quaternion(float4 value) => new(value.x, value.y, value.z, value.w);
|
||||
public static implicit operator float4(quaternion value) => new(value.x, value.y, value.z, value.w);
|
||||
|
||||
public static bool operator ==(quaternion left, quaternion right) => left.Equals(right);
|
||||
public static bool operator !=(quaternion left, quaternion right) => !left.Equals(right);
|
||||
|
||||
public readonly bool Equals(quaternion other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z) && w.Equals(other.w);
|
||||
public override readonly bool Equals(object? obj) => obj is quaternion other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||
public override readonly string ToString() => FormattableString.Invariant($"quaternion({x:R}, {y:R}, {z:R}, {w:R})");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ public sealed class HlslMirrorTests
|
|||
"cultmath_cubic_bezier",
|
||||
"cultmath_hash",
|
||||
"cultmath_value_noise",
|
||||
"cultmath_simplex_noise",
|
||||
"cultmath_snoise(float2",
|
||||
"cultmath_snoise(float3",
|
||||
"cultmath_value_noise_bicubic",
|
||||
"cultmath_value_noise_texture",
|
||||
"cultmath_value_noise_texture_bicubic",
|
||||
|
|
|
|||
332
packages/cultmath/tests/CultMath.Tests/HlslSemanticsTests.cs
Normal file
332
packages/cultmath/tests/CultMath.Tests/HlslSemanticsTests.cs
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace CultMath.Tests;
|
||||
|
||||
public sealed class HlslSemanticsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComponentsAndIndexersAreWritable()
|
||||
{
|
||||
var value = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||
value.w = 9.0f;
|
||||
value[0] = -1.0f;
|
||||
|
||||
Assert.Equal(new float4(-1.0f, 2.0f, 3.0f, 9.0f), value);
|
||||
|
||||
var cell = new int3(1, 2, 3);
|
||||
cell[2] = 7;
|
||||
Assert.Equal(new int3(1, 2, 7), cell);
|
||||
|
||||
ref var component = ref value.y;
|
||||
component = 20.0f;
|
||||
Assert.Equal(20.0f, value.y);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Float2SwizzlesReadAndWrite()
|
||||
{
|
||||
var value = new float2(1.0f, 2.0f);
|
||||
Assert.Equal(new float2(2.0f, 1.0f), value.yx);
|
||||
|
||||
value.yx = new float2(5.0f, 6.0f);
|
||||
Assert.Equal(new float2(6.0f, 5.0f), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Float3SwizzlesReadAndWrite()
|
||||
{
|
||||
var source = new float3(1.0f, 2.0f, 3.0f);
|
||||
Assert.Equal(new float2(1.0f, 2.0f), source.xy);
|
||||
Assert.Equal(new float2(1.0f, 3.0f), source.xz);
|
||||
Assert.Equal(new float2(2.0f, 1.0f), source.yx);
|
||||
Assert.Equal(new float2(2.0f, 3.0f), source.yz);
|
||||
Assert.Equal(new float2(3.0f, 2.0f), source.zy);
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), source.xyz);
|
||||
Assert.Equal(new float3(1.0f, 3.0f, 2.0f), source.xzy);
|
||||
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), source.yzx);
|
||||
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), source.zyx);
|
||||
|
||||
Assert.Equal(new float3(8.0f, 9.0f, 3.0f), Write(source, (ref float3 v) => v.xy = new float2(8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(8.0f, 2.0f, 9.0f), Write(source, (ref float3 v) => v.xz = new float2(8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(9.0f, 8.0f, 3.0f), Write(source, (ref float3 v) => v.yx = new float2(8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(1.0f, 8.0f, 9.0f), Write(source, (ref float3 v) => v.yz = new float2(8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(1.0f, 9.0f, 8.0f), Write(source, (ref float3 v) => v.zy = new float2(8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(7.0f, 8.0f, 9.0f), Write(source, (ref float3 v) => v.xyz = new float3(7.0f, 8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(7.0f, 9.0f, 8.0f), Write(source, (ref float3 v) => v.xzy = new float3(7.0f, 8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(9.0f, 7.0f, 8.0f), Write(source, (ref float3 v) => v.yzx = new float3(7.0f, 8.0f, 9.0f)));
|
||||
Assert.Equal(new float3(9.0f, 8.0f, 7.0f), Write(source, (ref float3 v) => v.zyx = new float3(7.0f, 8.0f, 9.0f)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Float4SwizzlesReadAndWrite()
|
||||
{
|
||||
var source = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||
Assert.Equal(new float2(1.0f, 2.0f), source.xy);
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), source.xyz);
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 4.0f), source.xyw);
|
||||
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), source.yzx);
|
||||
|
||||
var a = source;
|
||||
a.xy = new float2(8.0f, 9.0f);
|
||||
Assert.Equal(new float4(8.0f, 9.0f, 3.0f, 4.0f), a);
|
||||
|
||||
var b = source;
|
||||
b.xyz = new float3(7.0f, 8.0f, 9.0f);
|
||||
Assert.Equal(new float4(7.0f, 8.0f, 9.0f, 4.0f), b);
|
||||
|
||||
var c = source;
|
||||
c.xyw = new float3(7.0f, 8.0f, 9.0f);
|
||||
Assert.Equal(new float4(7.0f, 8.0f, 3.0f, 9.0f), c);
|
||||
|
||||
var d = source;
|
||||
d.yzx = new float3(7.0f, 8.0f, 9.0f);
|
||||
Assert.Equal(new float4(9.0f, 7.0f, 8.0f, 4.0f), d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComparisonOperatorsReturnComponentWiseBoolVectors()
|
||||
{
|
||||
var a = new float3(1.0f, 2.0f, 3.0f);
|
||||
var b = new float3(3.0f, 2.0f, 1.0f);
|
||||
|
||||
Assert.Equal(new bool3(true, false, false), a < b);
|
||||
Assert.Equal(new bool3(false, false, true), a > b);
|
||||
Assert.Equal(new bool3(true, true, false), a <= b);
|
||||
Assert.Equal(new bool3(false, true, true), a >= b);
|
||||
Assert.Equal(new bool3(false, true, false), a == b);
|
||||
Assert.Equal(new bool3(true, false, true), a != b);
|
||||
|
||||
Assert.Equal(new bool3(true, false, false), a < 2.0f);
|
||||
Assert.Equal(new bool3(false, false, true), 2.0f < a);
|
||||
Assert.Equal(new bool2(true, false), new float2(0.0f, 1.0f) == 0.0f);
|
||||
Assert.Equal(new bool4(false, true, true, true), new float4(0.0f, 1.0f, 2.0f, 3.0f) >= 1.0f);
|
||||
Assert.Equal(new bool2(false, true), new int2(1, 2) > new int2(1, 1));
|
||||
Assert.Equal(new bool3(true, false, true), new int3(1, 2, 3) != new int3(0, 2, 4));
|
||||
Assert.Equal(new bool4(true, true, false, false), new int4(1, 2, 3, 4) <= new int4(1, 2, 2, 2));
|
||||
Assert.Equal(new bool2(true, false), new double2(0.5, 1.0) < new double2(1.0, 1.0));
|
||||
Assert.Equal(new bool3(false, false, true), new double3(1.0, 1.0, 2.0) > new double3(1.0, 1.0, 1.0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScalarOperatorsApplyPerComponentOnBothSides()
|
||||
{
|
||||
var value = new float3(1.0f, 2.0f, 4.0f);
|
||||
|
||||
Assert.Equal(new float3(3.0f, 2.0f, 0.0f), 4.0f - value);
|
||||
Assert.Equal(new float3(0.0f, 1.0f, 3.0f), value - 1.0f);
|
||||
Assert.Equal(new float3(2.0f, 3.0f, 5.0f), 1.0f + value);
|
||||
Assert.Equal(new float3(4.0f, 2.0f, 1.0f), 4.0f / value);
|
||||
Assert.Equal(new float3(0.5f, 1.0f, 2.0f), value / 2.0f);
|
||||
Assert.Equal(new float3(2.0f, 4.0f, 8.0f), 2.0f * value);
|
||||
Assert.Equal(new float3(1.0f, 0.0f, 0.0f), value % 2.0f);
|
||||
Assert.Equal(new float2(1.5f, 2.5f), new float2(1.0f, 2.0f) + 0.5f);
|
||||
Assert.Equal(new float4(0.0f, 1.0f, 2.0f, 3.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f) - 1.0f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualsAndHashCodeStayValueBasedForDictionaryKeys()
|
||||
{
|
||||
var map = new Dictionary<float3, string> { [new float3(1.0f, 2.0f, 3.0f)] = "hit" };
|
||||
Assert.Equal("hit", map[new float3(1.0f, 2.0f, 3.0f)]);
|
||||
Assert.False(map.ContainsKey(new float3(1.0f, 2.0f, 3.5f)));
|
||||
|
||||
var cells = new HashSet<int2> { new int2(3, 4) };
|
||||
Assert.Contains(new int2(3, 4), cells);
|
||||
Assert.Contains(new int3(1, 2, 3), new HashSet<int3> { new int3(1, 2, 3) });
|
||||
|
||||
var nan = new float2(float.NaN, 0.0f);
|
||||
var sameNan = nan;
|
||||
Assert.True(nan.Equals(sameNan));
|
||||
Assert.False((nan == sameNan).x);
|
||||
|
||||
Assert.True(new quaternion(0.0f, 0.0f, 0.0f, 1.0f) == quaternion.identity);
|
||||
Assert.Equal(float2x2.identity, math.float2x2(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
Assert.Equal("float3(1, 2.5, -3)", new float3(1.0f, 2.5f, -3.0f).ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnyAllAndSelectFollowHlsl()
|
||||
{
|
||||
Assert.True(math.any(new bool2(false, true)));
|
||||
Assert.False(math.any(bool3.@false));
|
||||
Assert.True(math.all(bool4.@true));
|
||||
Assert.False(math.all(new bool3(true, false, true)));
|
||||
|
||||
var mask = new bool3(true, false, true);
|
||||
Assert.Equal(new float3(1.0f, 20.0f, 3.0f), math.select(mask, new float3(1.0f, 2.0f, 3.0f), new float3(10.0f, 20.0f, 30.0f)));
|
||||
Assert.Equal(7.0f, math.select(true, 7.0f, 8.0f));
|
||||
Assert.Equal(new float2(0.0f, 5.0f), math.select(new bool2(false, true), math.float2(5.0f), math.float2(0.0f)));
|
||||
Assert.Equal(new float4(1.0f, 0.0f, 1.0f, 0.0f), math.select(new bool4(true, false, true, false), 1.0f, 0.0f));
|
||||
|
||||
Assert.Equal(new bool2(false, true), !new bool2(true, false));
|
||||
Assert.Equal(new bool3(true, false, false), new bool3(true, true, false) & new bool3(true, false, true));
|
||||
Assert.Equal(new bool3(true, true, true), new bool3(true, true, false) | new bool3(true, false, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddedIntrinsicsAreComponentWise()
|
||||
{
|
||||
Assert.Equal(MathF.Log(3.0f), math.log(3.0f));
|
||||
Assert.Equal(new float3(0.0f, MathF.Log(2.0f), MathF.Log(4.0f)), math.log(new float3(1.0f, 2.0f, 4.0f)));
|
||||
Assert.Equal(new float2(8.0f, 9.0f), math.pow(new float2(2.0f, 3.0f), new float2(3.0f, 2.0f)));
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.sqrt(new float4(1.0f, 4.0f, 9.0f, 16.0f)));
|
||||
var sines = math.sin(new float3(0.0f, math.HALF_PI, math.PI));
|
||||
Assert.Equal(0.0f, sines.x, precision: 5);
|
||||
Assert.Equal(1.0f, sines.y, precision: 5);
|
||||
Assert.Equal(0.0f, sines.z, precision: 5);
|
||||
var cosines = math.cos(new float2(0.0f, math.PI));
|
||||
Assert.Equal(1.0f, cosines.x, precision: 5);
|
||||
Assert.Equal(-1.0f, cosines.y, precision: 5);
|
||||
var arccosines = math.acos(new float3(1.0f, 0.0f, -1.0f));
|
||||
Assert.Equal(0.0f, arccosines.x, precision: 5);
|
||||
Assert.Equal(math.HALF_PI, arccosines.y, precision: 5);
|
||||
Assert.Equal(math.PI, arccosines.z, precision: 5);
|
||||
Assert.Equal(math.atan2(1.0f, -1.0f), math.atan2(new float2(1.0f, 0.0f), new float2(-1.0f, 1.0f)).x);
|
||||
Assert.Equal(0.0f, math.atan2(new float4(1.0f, 0.0f, 1.0f, 0.0f), new float4(1.0f, 1.0f, 1.0f, 1.0f)).y);
|
||||
Assert.Equal(2.5, math.min(2.5, 3.5));
|
||||
Assert.Equal(3.5, math.max(2.5, 3.5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntVectorsPromoteToFloatVectors()
|
||||
{
|
||||
float3 promoted3 = new int3(1, 2, 3);
|
||||
float4 promoted4 = new int4(1, 2, 3, 4);
|
||||
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), promoted3);
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), promoted4);
|
||||
Assert.Equal(new int3(2, 4, 6), new int3(1, 2, 3) * 2);
|
||||
Assert.Equal(new int4(1, 0, 1, 0), new int4(3, 4, 5, 6) % 2);
|
||||
Assert.Equal(new int3(1, 2, 3), math.int3(1, 2, 3));
|
||||
Assert.Equal(new int4(5, 5, 5, 5), math.int4(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatrixMulFollowsHlslRowMajorConvention()
|
||||
{
|
||||
var m = math.float2x2(1.0f, 2.0f, 3.0f, 4.0f);
|
||||
var v = new float2(5.0f, 6.0f);
|
||||
|
||||
Assert.Equal(new float2(1.0f, 2.0f), m[0]);
|
||||
Assert.Equal(new float2(17.0f, 39.0f), math.mul(m, v));
|
||||
Assert.Equal(new float2(23.0f, 34.0f), math.mul(v, m));
|
||||
Assert.Equal(math.float2x2(7.0f, 10.0f, 15.0f, 22.0f), math.mul(m, m));
|
||||
|
||||
var m3 = math.float3x3(new float3(1.0f, 2.0f, 3.0f), new float3(4.0f, 5.0f, 6.0f), new float3(7.0f, 8.0f, 9.0f));
|
||||
var v3 = new float3(1.0f, 0.0f, -1.0f);
|
||||
Assert.Equal(new float3(-2.0f, -2.0f, -2.0f), math.mul(m3, v3));
|
||||
Assert.Equal(new float3(-6.0f, -6.0f, -6.0f), math.mul(v3, m3));
|
||||
Assert.Equal(math.float3x3(30.0f, 36.0f, 42.0f, 66.0f, 81.0f, 96.0f, 102.0f, 126.0f, 150.0f), math.mul(m3, m3));
|
||||
Assert.Equal(m3, math.mul(m3, float3x3.identity));
|
||||
|
||||
m3[1] = new float3(0.0f, 1.0f, 0.0f);
|
||||
Assert.Equal(1.0f, m3._m11);
|
||||
Assert.Equal(12.0f, math.mul(3.0f, 4.0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Float2x2RotateMatchesRotateAndUnityMathematics()
|
||||
{
|
||||
var rotation = float2x2.Rotate(0.5f);
|
||||
var column = math.mul(rotation, new float2(1.0f, 0.0f));
|
||||
var row = math.mul(new float2(1.0f, 0.0f), rotation);
|
||||
var reference = math.rotate(new float2(1.0f, 0.0f), 0.5f);
|
||||
|
||||
Assert.Equal(reference.x, column.x, precision: 6);
|
||||
Assert.Equal(reference.y, column.y, precision: 6);
|
||||
|
||||
// Unity.Mathematics 1.x reference values: mul(float2(1, 0), float2x2.Rotate(0.5f)).
|
||||
Assert.Equal(0.87758255f, row.x, precision: 6);
|
||||
Assert.Equal(-0.47942555f, row.y, precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Float3x3EulerMatchesUnityMathematicsReference()
|
||||
{
|
||||
// Unity.Mathematics 1.x reference values captured from float3x3.Euler.
|
||||
AssertRows(
|
||||
float3x3.Euler(new float3(0.4f, -0.7f, 1.3f), math.RotationOrder.YXZ),
|
||||
0.44632244f, -0.8874959f, 0.114662126f,
|
||||
0.6698625f, 0.24638279f, -0.700414f,
|
||||
0.59336376f, 0.38941833f, 0.70446634f);
|
||||
AssertRows(
|
||||
float3x3.Euler(new float3(0.4f, -0.7f, 1.3f)),
|
||||
-0.037133574f, -0.8040775f, -0.59336376f,
|
||||
0.8874959f, 0.24638279f, -0.38941833f,
|
||||
0.45931715f, -0.5410684f, 0.70446634f);
|
||||
|
||||
// Aetheria ActionGameManager view direction: mul(float3(0, 0, 1), Euler(float3(yawPitch.yx, 0), YXZ)).
|
||||
var yawPitch = new float2(0.3f, -1.1f);
|
||||
var view = math.mul(new float3(0.0f, 0.0f, 1.0f), float3x3.Euler(new float3(yawPitch.yx, 0.0f), math.RotationOrder.YXZ));
|
||||
Assert.Equal(-0.13404681f, view.x, precision: 5);
|
||||
Assert.Equal(-0.8912074f, view.y, precision: 5);
|
||||
Assert.Equal(0.4333369f, view.z, precision: 5);
|
||||
|
||||
var composed = math.mul(float3x3.RotateZ(0.2f), math.mul(float3x3.RotateY(0.3f), float3x3.RotateX(0.1f)));
|
||||
AssertRows(float3x3.Euler(0.1f, 0.3f, 0.2f, math.RotationOrder.XYZ),
|
||||
composed._m00, composed._m01, composed._m02,
|
||||
composed._m10, composed._m11, composed._m12,
|
||||
composed._m20, composed._m21, composed._m22);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.0f, 0.0f, 0.0f, -0.41219875f)]
|
||||
[InlineData(0.25f, -0.5f, 1.75f, 0.5250269f)]
|
||||
[InlineData(12.25f, -4.5f, 3.125f, 0.008141451f)]
|
||||
[InlineData(-7.3f, 2.9f, 101.4f, 0.578015f)]
|
||||
public void Snoise3MatchesUnityMathematicsReference(float x, float y, float z, float expected)
|
||||
{
|
||||
Assert.Equal(expected, math.snoise(new float3(x, y, z)), precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RandomFloat3OverloadsShareOneDrawOrder()
|
||||
{
|
||||
var a = new Random(99);
|
||||
var b = new Random(99);
|
||||
var c = new Random(99);
|
||||
|
||||
var unit = a.NextFloat3();
|
||||
Assert.Equal(new float3(b.NextFloat(), b.NextFloat(), b.NextFloat()), unit);
|
||||
Assert.Equal(unit * new float3(2.0f, 4.0f, 8.0f), c.NextFloat3(new float3(2.0f, 4.0f, 8.0f)));
|
||||
|
||||
var d = new Random(99);
|
||||
Assert.Equal(math.float3(-1.0f) + unit * 2.0f, d.NextFloat3(math.float3(-1.0f), math.float3(1.0f)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CoreAssemblyStaysEngineFree()
|
||||
{
|
||||
var references = typeof(math).Assembly.GetReferencedAssemblies();
|
||||
Assert.DoesNotContain(references, reference => reference.Name!.StartsWith("UnityEngine", StringComparison.Ordinal));
|
||||
|
||||
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (root is not null && !File.Exists(Path.Combine(root.FullName, "packages", "cultmath", "src", "CultMath", "CultMath.asmdef")))
|
||||
{
|
||||
root = root.Parent;
|
||||
}
|
||||
|
||||
Assert.NotNull(root);
|
||||
var asmdefPath = Path.Combine(root!.FullName, "packages", "cultmath", "src", "CultMath", "CultMath.asmdef");
|
||||
using var asmdef = JsonDocument.Parse(File.ReadAllText(asmdefPath));
|
||||
Assert.True(asmdef.RootElement.GetProperty("noEngineReferences").GetBoolean());
|
||||
}
|
||||
|
||||
private static float3 Write(float3 value, WriteAction action)
|
||||
{
|
||||
action(ref value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private delegate void WriteAction(ref float3 value);
|
||||
|
||||
private static void AssertRows(float3x3 m, params float[] expected)
|
||||
{
|
||||
var actual = new[] { m._m00, m._m01, m._m02, m._m10, m._m11, m._m12, m._m20, m._m21, m._m22 };
|
||||
for (var index = 0; index < expected.Length; index++)
|
||||
{
|
||||
Assert.Equal(expected[index], actual[index], precision: 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7164d00a023e43578a31897347d8f106
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 942fbcb9c28a4e559e5baf435dc24cf1
|
||||
|
|
@ -33,7 +33,7 @@ public sealed class MathTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void VectorsExposeCommonReadOnlySwizzles()
|
||||
public void VectorsExposeCommonSwizzles()
|
||||
{
|
||||
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);
|
||||
|
|
@ -277,7 +277,7 @@ public sealed class MathTests
|
|||
[InlineData(536.5106f, 536.5106f, -0.6901103f)]
|
||||
public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected)
|
||||
{
|
||||
Assert.Equal(expected, math.simplex_noise(new float2(x, y)), precision: 6);
|
||||
Assert.Equal(expected, math.snoise(new float2(x, y)), precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue