diff --git a/src/GameCult.Caching.MessagePack/README.md b/src/GameCult.Caching.MessagePack/README.md index c69ab887..277d0cb2 100644 --- a/src/GameCult.Caching.MessagePack/README.md +++ b/src/GameCult.Caching.MessagePack/README.md @@ -9,6 +9,7 @@ format for the attribute-first `GameCult.Caching` stack. - `CultDocumentMessagePackSerialization` - `CultDocumentResolver` - `CultRecordRefFormatter` +- `CultMathResolver`: the canonical component-array encoding of every public CultMath value type, part of the default resolver chain Cult document payloads serialize through `MessagePackSerializer` with the options of the document's assembly (`CultDocumentMessagePackSerialization.OptionsFor`), diff --git a/src/GameCult.Geometry/CultGeometryIsoSurface.cs b/src/GameCult.Geometry/CultGeometryIsoSurface.cs index 583fdd87..b0967ece 100644 --- a/src/GameCult.Geometry/CultGeometryIsoSurface.cs +++ b/src/GameCult.Geometry/CultGeometryIsoSurface.cs @@ -39,7 +39,7 @@ namespace GameCult.Geometry public static CultGeometryTriangleMesh Extract( float[,,] samples, float isoValue = 0f, - CultVec3 origin = default, + float3 origin = default, float cellSize = 1f) { if (samples == null) throw new ArgumentNullException(nameof(samples)); @@ -56,7 +56,6 @@ namespace GameCult.Geometry var positions = new List(); var normals = new List(); var indices = new List(); - var originValue = new float3(origin.X, origin.Y, origin.Z); var cornerPositions = new float3[8]; var cornerValues = new float[8]; var tetraPositions = new float3[4]; @@ -66,7 +65,7 @@ namespace GameCult.Geometry for (var y = 0; y < samples.GetLength(1) - 1; y++) for (var z = 0; z < samples.GetLength(2) - 1; z++) { - var cubeOrigin = originValue + new float3(x, y, z) * cellSize; + var cubeOrigin = origin + new float3(x, y, z) * cellSize; for (var corner = 0; corner < CubeCorners.Length; corner++) { var offset = CubeCorners[corner]; diff --git a/src/GameCult.Geometry/CultGeometryPrimitives.cs b/src/GameCult.Geometry/CultGeometryPrimitives.cs index 99587b8e..dafcca68 100644 --- a/src/GameCult.Geometry/CultGeometryPrimitives.cs +++ b/src/GameCult.Geometry/CultGeometryPrimitives.cs @@ -1,197 +1,10 @@ using System; using System.Text.Json.Serialization; +using CultMath; using MessagePack; namespace GameCult.Geometry { - /// - /// Deterministic two-dimensional float vector for cross-runtime geometry and query contracts. - /// - [MessagePackObject] - public readonly struct CultVec2 : IEquatable - { - /// Creates a two-dimensional vector. - public CultVec2(float x, float y) - { - X = x; - Y = y; - } - - /// Gets the x component. - [Key(0)] - public float X { get; } - - /// Gets the y component. - [Key(1)] - public float Y { get; } - - /// Gets the zero vector. - [IgnoreMember] - [JsonIgnore] - public static CultVec2 Zero => new(0f, 0f); - - /// Gets the squared vector length. - [IgnoreMember] - [JsonIgnore] - public float LengthSquared => (X * X) + (Y * Y); - - /// Returns whether this vector is equal to another vector. - public bool Equals(CultVec2 other) => X.Equals(other.X) && Y.Equals(other.Y); - - /// - public override bool Equals(object? obj) => obj is CultVec2 other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(X, Y); - - /// - public override string ToString() => $"({X}, {Y})"; - - public static CultVec2 operator +(CultVec2 left, CultVec2 right) => new(left.X + right.X, left.Y + right.Y); - public static CultVec2 operator -(CultVec2 left, CultVec2 right) => new(left.X - right.X, left.Y - right.Y); - public static CultVec2 operator *(CultVec2 value, float scalar) => new(value.X * scalar, value.Y * scalar); - public static CultVec2 operator /(CultVec2 value, float scalar) => new(value.X / scalar, value.Y / scalar); - public static bool operator ==(CultVec2 left, CultVec2 right) => left.Equals(right); - public static bool operator !=(CultVec2 left, CultVec2 right) => !left.Equals(right); - } - - /// - /// Deterministic three-dimensional float vector for cross-runtime geometry and query contracts. - /// - [MessagePackObject] - public readonly struct CultVec3 : IEquatable - { - /// Creates a three-dimensional vector. - public CultVec3(float x, float y, float z) - { - X = x; - Y = y; - Z = z; - } - - /// Gets the x component. - [Key(0)] - public float X { get; } - - /// Gets the y component. - [Key(1)] - public float Y { get; } - - /// Gets the z component. - [Key(2)] - public float Z { get; } - - /// Gets the zero vector. - [IgnoreMember] - [JsonIgnore] - public static CultVec3 Zero => new(0f, 0f, 0f); - - /// Gets the XY plane projection. - [IgnoreMember] - [JsonIgnore] - public CultVec2 Xy => new(X, Y); - - /// Gets the XZ plane projection for Unity compatibility adapters. - [IgnoreMember] - [JsonIgnore] - public CultVec2 Xz => new(X, Z); - - /// Gets the squared vector length. - [IgnoreMember] - [JsonIgnore] - public float LengthSquared => (X * X) + (Y * Y) + (Z * Z); - - /// Returns whether this vector is equal to another vector. - public bool Equals(CultVec3 other) => X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z); - - /// - public override bool Equals(object? obj) => obj is CultVec3 other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(X, Y, Z); - - /// - public override string ToString() => $"({X}, {Y}, {Z})"; - - public static CultVec3 operator +(CultVec3 left, CultVec3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z); - public static CultVec3 operator -(CultVec3 left, CultVec3 right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z); - public static CultVec3 operator *(CultVec3 value, float scalar) => new(value.X * scalar, value.Y * scalar, value.Z * scalar); - public static CultVec3 operator /(CultVec3 value, float scalar) => new(value.X / scalar, value.Y / scalar, value.Z / scalar); - public static bool operator ==(CultVec3 left, CultVec3 right) => left.Equals(right); - public static bool operator !=(CultVec3 left, CultVec3 right) => !left.Equals(right); - } - - /// - /// Axis-aligned two-dimensional rectangle represented by canonical min and max corners. - /// - [MessagePackObject] - public readonly struct CultRect : IEquatable - { - /// Creates a rectangle and canonicalizes min/max corners. - public CultRect(CultVec2 min, CultVec2 max) - { - Min = new CultVec2(Math.Min(min.X, max.X), Math.Min(min.Y, max.Y)); - Max = new CultVec2(Math.Max(min.X, max.X), Math.Max(min.Y, max.Y)); - } - - /// Creates a rectangle and canonicalizes min/max corners. - public CultRect(float minX, float minY, float maxX, float maxY) - : this(new CultVec2(minX, minY), new CultVec2(maxX, maxY)) - { - } - - /// Gets the canonical minimum corner. - [Key(0)] - public CultVec2 Min { get; } - - /// Gets the canonical maximum corner. - [Key(1)] - public CultVec2 Max { get; } - - /// Gets the rectangle size. - [IgnoreMember] - [JsonIgnore] - public CultVec2 Size => Max - Min; - - /// Gets the rectangle center. - [IgnoreMember] - [JsonIgnore] - public CultVec2 Center => (Min + Max) * 0.5f; - - /// Returns whether the point lies inside or on the rectangle edge. - public bool Contains(CultVec2 point) - { - return point.X >= Min.X && - point.X <= Max.X && - point.Y >= Min.Y && - point.Y <= Max.Y; - } - - /// Returns whether this rectangle intersects another rectangle. - public bool Intersects(CultRect other) - { - return Min.X <= other.Max.X && - Max.X >= other.Min.X && - Min.Y <= other.Max.Y && - Max.Y >= other.Min.Y; - } - - /// Returns whether this rectangle is equal to another rectangle. - public bool Equals(CultRect other) => Min == other.Min && Max == other.Max; - - /// - public override bool Equals(object? obj) => obj is CultRect other && Equals(other); - - /// - public override int GetHashCode() => HashCode.Combine(Min, Max); - - /// - public override string ToString() => $"[{Min}..{Max}]"; - - public static bool operator ==(CultRect left, CultRect right) => left.Equals(right); - public static bool operator !=(CultRect left, CultRect right) => !left.Equals(right); - } - /// /// Two-dimensional circle for spatial queries. /// @@ -199,7 +12,7 @@ namespace GameCult.Geometry public readonly struct CultCircle : IEquatable { /// Creates a circle. - public CultCircle(CultVec2 center, float radius) + public CultCircle(float2 center, float radius) { if (radius < 0f) { @@ -212,7 +25,7 @@ namespace GameCult.Geometry /// Gets the circle center. [Key(0)] - public CultVec2 Center { get; } + public float2 Center { get; } /// Gets the circle radius. [Key(1)] @@ -221,29 +34,29 @@ namespace GameCult.Geometry /// Gets the circle bounds. [IgnoreMember] [JsonIgnore] - public CultRect Bounds => new( - Center.X - Radius, - Center.Y - Radius, - Center.X + Radius, - Center.Y + Radius); + public rect Bounds => new( + Center.x - Radius, + Center.y - Radius, + Center.x + Radius, + Center.y + Radius); /// Returns whether the point lies inside or on the circle edge. - public bool Contains(CultVec2 point) + public bool Contains(float2 point) { var delta = point - Center; - return delta.LengthSquared <= Radius * Radius; + return math.lengthsq(delta) <= Radius * Radius; } /// Returns whether this circle intersects a rectangle. - public bool Intersects(CultRect rect) + public bool Intersects(rect rect) { - var clampedX = Math.Max(rect.Min.X, Math.Min(Center.X, rect.Max.X)); - var clampedY = Math.Max(rect.Min.Y, Math.Min(Center.Y, rect.Max.Y)); - return Contains(new CultVec2(clampedX, clampedY)); + var clampedX = Math.Max(rect.min.x, Math.Min(Center.x, rect.max.x)); + var clampedY = Math.Max(rect.min.y, Math.Min(Center.y, rect.max.y)); + return Contains(new float2(clampedX, clampedY)); } /// Returns whether this circle is equal to another circle. - public bool Equals(CultCircle other) => Center == other.Center && Radius.Equals(other.Radius); + public bool Equals(CultCircle other) => Center.Equals(other.Center) && Radius.Equals(other.Radius); /// public override bool Equals(object? obj) => obj is CultCircle other && Equals(other); @@ -262,7 +75,7 @@ namespace GameCult.Geometry public readonly struct CultSphere : IEquatable { /// Creates a sphere. - public CultSphere(CultVec3 center, float radius) + public CultSphere(float3 center, float radius) { if (radius < 0f) { @@ -275,7 +88,7 @@ namespace GameCult.Geometry /// Gets the sphere center. [Key(0)] - public CultVec3 Center { get; } + public float3 Center { get; } /// Gets the sphere radius. [Key(1)] @@ -284,17 +97,17 @@ namespace GameCult.Geometry /// Gets the XY circle projection. [IgnoreMember] [JsonIgnore] - public CultCircle XyCircle => new(Center.Xy, Radius); + public CultCircle XyCircle => new(Center.xy, Radius); /// Returns whether the point lies inside or on the sphere surface. - public bool Contains(CultVec3 point) + public bool Contains(float3 point) { var delta = point - Center; - return delta.LengthSquared <= Radius * Radius; + return math.lengthsq(delta) <= Radius * Radius; } /// Returns whether this sphere is equal to another sphere. - public bool Equals(CultSphere other) => Center == other.Center && Radius.Equals(other.Radius); + public bool Equals(CultSphere other) => Center.Equals(other.Center) && Radius.Equals(other.Radius); /// public override bool Equals(object? obj) => obj is CultSphere other && Equals(other); diff --git a/src/GameCult.Geometry/CultGeometrySurfaceNets.cs b/src/GameCult.Geometry/CultGeometrySurfaceNets.cs index 45c3f1ec..bd78e053 100644 --- a/src/GameCult.Geometry/CultGeometrySurfaceNets.cs +++ b/src/GameCult.Geometry/CultGeometrySurfaceNets.cs @@ -44,7 +44,7 @@ namespace GameCult.Geometry public static CultGeometryQuadMesh Extract( float[,,] samples, float isoValue = 0f, - CultVec3 origin = default, + float3 origin = default, float cellSize = 1f) { if (samples == null) throw new ArgumentNullException(nameof(samples)); @@ -66,7 +66,7 @@ namespace GameCult.Geometry throw new ArgumentException("The isovalue must be finite.", nameof(isoValue)); } - if (!float.IsFinite(origin.X) || !float.IsFinite(origin.Y) || !float.IsFinite(origin.Z)) + if (!float.IsFinite(origin.x) || !float.IsFinite(origin.y) || !float.IsFinite(origin.z)) { throw new ArgumentException("The origin must be finite.", nameof(origin)); } @@ -80,7 +80,6 @@ namespace GameCult.Geometry var sizeY = samples.GetLength(1); var sizeZ = samples.GetLength(2); var cellCounts = new[] { sizeX - 1, sizeY - 1, sizeZ - 1 }; - var originValue = new float3(origin.X, origin.Y, origin.Z); var vertexIndex = new Dictionary<(int, int, int), uint>(); var positions = new List(); @@ -90,7 +89,7 @@ namespace GameCult.Geometry for (var cz = 0; cz < cellCounts[2]; cz++) { var cellCoord = new[] { cx, cy, cz }; - var vertex = CellVertex(samples, cellCoord, isoValue, originValue, cellSize); + var vertex = CellVertex(samples, cellCoord, isoValue, origin, cellSize); if (vertex == null) continue; vertexIndex[(cx, cy, cz)] = (uint)(positions.Count / 3); diff --git a/src/GameCult.Geometry/README.md b/src/GameCult.Geometry/README.md index 2d12dc25..9929e387 100644 --- a/src/GameCult.Geometry/README.md +++ b/src/GameCult.Geometry/README.md @@ -31,12 +31,9 @@ The package is deliberately substrate-shaped: ## Value Primitives The small value layer is for cross-runtime query contracts, physics probes, -native slices, and UI surfaces: +native slices, and UI surfaces. Vectors and rectangles are CultMath's `float2`, +`float3` and `rect`, and `GameCult.Caching.MessagePack` serializes them. -- `CultVec2` and `CultVec3`: whole-vector values for positions, velocities, - accelerations, and query inputs. SoA layouts may store them efficiently, but - the semantic API does not force callers to split x/y/z into unrelated fields. -- `CultRect`: canonical XY viewport/query rectangle stored as min/max corners. - `CultCircle`: 2D influence/query brush with rect intersection helpers. - `CultSphere`: 3D query primitive with whole-vector center and XY projection. @@ -85,8 +82,8 @@ cache miss; that is split-brain geometry. ## Documents -- `CultVec2`, `CultVec3`, `CultRect`, `CultCircle`, `CultSphere`: shared value - primitives for query and view contracts. +- `CultCircle`, `CultSphere`: shared value primitives for query and view + contracts. - `CultGeometryDomainDocument`: one hierarchical domain tree, suitable for Ragnarok/Fensalir-style feature DSL output. - `CultGeometryBuildRequest`: one LOD/frustum/budget request for workers. diff --git a/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs index 302f53f3..daae992a 100644 --- a/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs +++ b/tests/GameCult.Geometry.Tests/GeometryDocumentTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using CultMath; using GameCult.Caching; using GameCult.Caching.MessagePack; using GameCult.Mesh; @@ -19,54 +20,50 @@ namespace GameCult.Geometry.Tests [Test] public void GeometryPrimitives_CanonicalizeAndIntersect() { - var viewport = new CultRect(8f, 4f, -8f, -4f); + var viewport = new rect(8f, 4f, -8f, -4f); - viewport.Min.Should().Be(new CultVec2(-8f, -4f)); - viewport.Max.Should().Be(new CultVec2(8f, 4f)); - viewport.Center.Should().Be(CultVec2.Zero); - viewport.Contains(new CultVec2(2f, 3f)).Should().BeTrue(); - viewport.Intersects(new CultRect(7f, 3f, 10f, 5f)).Should().BeTrue(); - viewport.Intersects(new CultRect(9f, 5f, 10f, 6f)).Should().BeFalse(); + viewport.min.Should().Be(new float2(-8f, -4f)); + viewport.max.Should().Be(new float2(8f, 4f)); + viewport.center.Should().Be(float2.zero); + viewport.Contains(new float2(2f, 3f)).Should().BeTrue(); + viewport.Intersects(new rect(7f, 3f, 10f, 5f)).Should().BeTrue(); + viewport.Intersects(new rect(9f, 5f, 10f, 6f)).Should().BeFalse(); - var gravityBrush = new CultCircle(new CultVec2(10f, 0f), 3f); + var gravityBrush = new CultCircle(new float2(10f, 0f), 3f); gravityBrush.Intersects(viewport).Should().BeTrue(); - gravityBrush.Contains(new CultVec2(12f, 0f)).Should().BeTrue(); - gravityBrush.Contains(new CultVec2(14f, 0f)).Should().BeFalse(); + gravityBrush.Contains(new float2(12f, 0f)).Should().BeTrue(); + gravityBrush.Contains(new float2(14f, 0f)).Should().BeFalse(); } [Test] public void GeometryPrimitives_PreserveWholeVectorsForSoaAndPhysicsQueries() { - var sphere = new CultSphere(new CultVec3(1f, 2f, 3f), 5f); + var sphere = new CultSphere(new float3(1f, 2f, 3f), 5f); - sphere.Contains(new CultVec3(1f, 6f, 3f)).Should().BeTrue(); - sphere.Contains(new CultVec3(1f, 8f, 3f)).Should().BeFalse(); - sphere.Center.Xy.Should().Be(new CultVec2(1f, 2f)); - sphere.Center.Xz.Should().Be(new CultVec2(1f, 3f)); - sphere.XyCircle.Bounds.Should().Be(new CultRect(-4f, -3f, 6f, 7f)); + sphere.Contains(new float3(1f, 6f, 3f)).Should().BeTrue(); + sphere.Contains(new float3(1f, 8f, 3f)).Should().BeFalse(); + sphere.Center.xy.Should().Be(new float2(1f, 2f)); + sphere.Center.xz.Should().Be(new float2(1f, 3f)); + sphere.XyCircle.Bounds.Should().Be(new rect(-4f, -3f, 6f, 7f)); } + // The bytes CultCircle and CultSphere produced when they held CultVec2/CultVec3 (probed at 65311c9). [Test] - public void GeometryPrimitives_RoundTripThroughMessagePack() + public void GeometryPrimitives_KeepTheirPreCollapseBytes() { - var rect = new CultRect(new CultVec2(5f, -2f), new CultVec2(-1f, 7f)); - var payload = MessagePackSerializer.Serialize(rect); - var decoded = MessagePackSerializer.Deserialize(payload); + var options = CultDocumentMessagePackSerialization.Options; + var circle = new CultCircle(new float2(1f, 2f), 3f); + var sphere = new CultSphere(new float3(1f, 2f, 3f), 5f); - decoded.Should().Be(rect); - decoded.Min.Should().Be(new CultVec2(-1f, -2f)); - decoded.Max.Should().Be(new CultVec2(5f, 7f)); - } + Convert.ToHexString(MessagePackSerializer.Serialize(circle, options)) + .Should().Be("9292CA3F800000CA40000000CA40400000"); + Convert.ToHexString(MessagePackSerializer.Serialize(sphere, options)) + .Should().Be("9293CA3F800000CA40000000CA40400000CA40A00000"); - [Test] - public void GeometryPrimitives_JsonOmitsComputedConvenienceProperties() - { - var json = JsonSerializer.Serialize(new CultVec3(1f, 2f, 3f)); - - json.Should().Be("""{"X":1,"Y":2,"Z":3}"""); - json.Should().NotContain("LengthSquared"); - json.Should().NotContain("Xy"); - json.Should().NotContain("Xz"); + MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(circle, options), options) + .Should().Be(circle); + MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(sphere, options), options) + .Should().Be(sphere); } [Test] diff --git a/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs b/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs index 63b794b3..52f7219e 100644 --- a/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs +++ b/tests/GameCult.Geometry.Tests/IsoSurfaceTests.cs @@ -1,4 +1,5 @@ using System; +using CultMath; using FluentAssertions; using NUnit.Framework; @@ -77,8 +78,8 @@ namespace GameCult.Geometry.Tests samples[0, 1, 1] = 1f; samples[1, 1, 1] = 1f; - var first = CultGeometryIsoSurface.Extract(samples, origin: new CultVec3(10f, 20f, 30f), cellSize: 2f); - var second = CultGeometryIsoSurface.Extract(samples, origin: new CultVec3(10f, 20f, 30f), cellSize: 2f); + var first = CultGeometryIsoSurface.Extract(samples, origin: new float3(10f, 20f, 30f), cellSize: 2f); + var second = CultGeometryIsoSurface.Extract(samples, origin: new float3(10f, 20f, 30f), cellSize: 2f); second.Positions.Should().Equal(first.Positions); second.Normals.Should().Equal(first.Normals); diff --git a/tests/GameCult.Geometry.Tests/SurfaceNetsTests.cs b/tests/GameCult.Geometry.Tests/SurfaceNetsTests.cs index 3e3b3892..82c8fd43 100644 --- a/tests/GameCult.Geometry.Tests/SurfaceNetsTests.cs +++ b/tests/GameCult.Geometry.Tests/SurfaceNetsTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using CultMath; using FluentAssertions; using NUnit.Framework; @@ -73,28 +74,28 @@ namespace GameCult.Geometry.Tests for (var quad = 0; quad < mesh.QuadCount; quad++) { var edge = mesh.QuadEdges[quad]; - var insideCoord = new CultVec3(1f, 1f, 1f); - var edgeStart = new CultVec3(edge.X, edge.Y, edge.Z); + var insideCoord = new float3(1f, 1f, 1f); + var edgeStart = new float3(edge.X, edge.Y, edge.Z); var edgeEnd = OtherEndpoint(edge); // The single inside sample is (1,1,1); the edge's other endpoint is outside. var outsideCoord = edgeStart.Equals(insideCoord) ? edgeEnd : edgeStart; - var outward = Subtract(outsideCoord, insideCoord); + var outward = outsideCoord - insideCoord; var baseIndex = quad * 4; var v0 = Position(mesh.Positions, mesh.Quads[baseIndex]); var v1 = Position(mesh.Positions, mesh.Quads[baseIndex + 1]); var v2 = Position(mesh.Positions, mesh.Quads[baseIndex + 2]); var v3 = Position(mesh.Positions, mesh.Quads[baseIndex + 3]); - var cross = Cross(Subtract(v2, v0), Subtract(v3, v1)); + var cross = math.cross(v2 - v0, v3 - v1); // Restricted to non-degenerate (non-zero-area) quads: the spec's geometric // winding rule is only meaningful there. Degenerate/collapsed quads are covered // separately by AssertEdgeDirectionsBalance, since a zero cross product carries // no orientation information to check here. - if (cross.X == 0f && cross.Y == 0f && cross.Z == 0f) continue; + if (cross.Equals(float3.zero)) continue; - Dot(cross, outward).Should().BePositive($"quad for edge {edge} should wind outward"); + math.dot(cross, outward).Should().BePositive($"quad for edge {edge} should wind outward"); } } @@ -209,7 +210,7 @@ namespace GameCult.Geometry.Tests samples[1, 1, 1] = -1f; - var mesh = CultGeometrySurfaceNets.Extract(samples, origin: new CultVec3(10f, 20f, 30f), cellSize: 2f); + var mesh = CultGeometrySurfaceNets.Extract(samples, origin: new float3(10f, 20f, 30f), cellSize: 2f); mesh.VertexCount.Should().Be(1); mesh.Positions[0].Should().BeApproximately(10f + (2.9f / 3f * 2f), 1e-5f); @@ -387,10 +388,10 @@ namespace GameCult.Geometry.Tests // documented c0..c3 order is not swapped. var expected = new[] { - new CultVec3(1.5f, 0.5f, 0.5f), // c0 = (1, 0, 0) - new CultVec3(1.5f, 1.5f, 0.5f), // c1 = (1, 1, 0) - new CultVec3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) - new CultVec3(1.5f, 0.5f, 1.5f), // c3 = (1, 0, 1) + new float3(1.5f, 0.5f, 0.5f), // c0 = (1, 0, 0) + new float3(1.5f, 1.5f, 0.5f), // c1 = (1, 1, 0) + new float3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) + new float3(1.5f, 0.5f, 1.5f), // c3 = (1, 0, 1) }; AssertQuadCorners(mesh, edge, expected); @@ -413,10 +414,10 @@ namespace GameCult.Geometry.Tests // v3 trade places relative to the raw c0..c3 listing. var expected = new[] { - new CultVec3(0.5f, 1.5f, 0.5f), // c0 = (0, 1, 0) - new CultVec3(0.5f, 1.5f, 1.5f), // c3 = (0, 1, 1), swapped into v1 - new CultVec3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) - new CultVec3(1.5f, 1.5f, 0.5f), // c1 = (1, 1, 0), swapped into v3 + new float3(0.5f, 1.5f, 0.5f), // c0 = (0, 1, 0) + new float3(0.5f, 1.5f, 1.5f), // c3 = (0, 1, 1), swapped into v1 + new float3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) + new float3(1.5f, 1.5f, 0.5f), // c1 = (1, 1, 0), swapped into v3 }; AssertQuadCorners(mesh, edge, expected); @@ -438,10 +439,10 @@ namespace GameCult.Geometry.Tests // mirrored, so the documented c0..c3 order is not swapped. var expected = new[] { - new CultVec3(0.5f, 0.5f, 1.5f), // c0 = (0, 0, 1) - new CultVec3(1.5f, 0.5f, 1.5f), // c1 = (1, 0, 1) - new CultVec3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) - new CultVec3(0.5f, 1.5f, 1.5f), // c3 = (0, 1, 1) + new float3(0.5f, 0.5f, 1.5f), // c0 = (0, 0, 1) + new float3(1.5f, 0.5f, 1.5f), // c1 = (1, 0, 1) + new float3(1.5f, 1.5f, 1.5f), // c2 = (1, 1, 1) + new float3(0.5f, 1.5f, 1.5f), // c3 = (0, 1, 1) }; AssertQuadCorners(mesh, edge, expected); @@ -476,7 +477,7 @@ namespace GameCult.Geometry.Tests // balance regardless. var samples = Sphere(64); - var mesh = CultGeometrySurfaceNets.Extract(samples, origin: new CultVec3(1e4f, 1e4f, 1e4f), cellSize: 0.001f); + var mesh = CultGeometrySurfaceNets.Extract(samples, origin: new float3(1e4f, 1e4f, 1e4f), cellSize: 0.001f); mesh.QuadCount.Should().BeGreaterThan(0); AssertEdgeDirectionsBalance(mesh); @@ -651,7 +652,7 @@ namespace GameCult.Geometry.Tests { var samples = new float[2, 2, 2]; - Action act = () => CultGeometrySurfaceNets.Extract(samples, origin: new CultVec3(x, y, z)); + Action act = () => CultGeometrySurfaceNets.Extract(samples, origin: new float3(x, y, z)); act.Should().Throw(); } @@ -725,28 +726,17 @@ namespace GameCult.Geometry.Tests return usage; } - private static CultVec3 OtherEndpoint(CultGeometryGridEdge edge) => edge.Axis switch + private static float3 OtherEndpoint(CultGeometryGridEdge edge) => edge.Axis switch { - 0 => new CultVec3(edge.X + 1, edge.Y, edge.Z), - 1 => new CultVec3(edge.X, edge.Y + 1, edge.Z), - _ => new CultVec3(edge.X, edge.Y, edge.Z + 1), + 0 => new float3(edge.X + 1, edge.Y, edge.Z), + 1 => new float3(edge.X, edge.Y + 1, edge.Z), + _ => new float3(edge.X, edge.Y, edge.Z + 1), }; - private static CultVec3 Subtract(CultVec3 left, CultVec3 right) => - new(left.X - right.X, left.Y - right.Y, left.Z - right.Z); - - private static CultVec3 Cross(CultVec3 left, CultVec3 right) => new( - (left.Y * right.Z) - (left.Z * right.Y), - (left.Z * right.X) - (left.X * right.Z), - (left.X * right.Y) - (left.Y * right.X)); - - private static float Dot(CultVec3 left, CultVec3 right) => - (left.X * right.X) + (left.Y * right.Y) + (left.Z * right.Z); - - private static CultVec3 Position(float[] positions, uint index) + private static float3 Position(float[] positions, uint index) { var offset = (int)index * 3; - return new CultVec3(positions[offset], positions[offset + 1], positions[offset + 2]); + return new float3(positions[offset], positions[offset + 1], positions[offset + 2]); } // Hand-derived (double precision, independent of Interpolate) expected position for a @@ -760,7 +750,7 @@ namespace GameCult.Geometry.Tests return (float)(amount / 3d); } - private static void AssertQuadCorners(CultGeometryQuadMesh mesh, CultGeometryGridEdge edge, CultVec3[] expected) + private static void AssertQuadCorners(CultGeometryQuadMesh mesh, CultGeometryGridEdge edge, float3[] expected) { var quadIndex = Array.IndexOf(mesh.QuadEdges, edge); quadIndex.Should().BeGreaterThanOrEqualTo(0, $"a quad for edge {edge} should exist");