diff --git a/src/NwcIO/Model/Grids/NwcGridSystem.cs b/src/NwcIO/Model/Grids/NwcGridSystem.cs index 3b9f958..bbf8e44 100644 --- a/src/NwcIO/Model/Grids/NwcGridSystem.cs +++ b/src/NwcIO/Model/Grids/NwcGridSystem.cs @@ -17,8 +17,6 @@ public sealed class NwcGridSystem public List Lines { get; } = []; - private List? _cachedIntersections; - public NwcGridSystem() { } public NwcGridSystem(string name) @@ -33,7 +31,6 @@ public NwcGridLine AddLine(NwcGridLine line) { ArgumentNullException.ThrowIfNull(line); Lines.Add(line); - _cachedIntersections = null; return line; } @@ -51,11 +48,6 @@ public NwcGridLine AddLine(string name, Vector3d start, Vector3d end, double ele /// public IReadOnlyList GetIntersections() { - if (_cachedIntersections is not null) - { - return _cachedIntersections; - } - var result = new List(); for (int i = 0; i < Lines.Count; i++) @@ -78,7 +70,6 @@ public IReadOnlyList GetIntersections() } } - _cachedIntersections = result; return result; } diff --git a/src/NwcIO/Model/NwcMesh.cs b/src/NwcIO/Model/NwcMesh.cs index 7c263d5..06f6607 100644 --- a/src/NwcIO/Model/NwcMesh.cs +++ b/src/NwcIO/Model/NwcMesh.cs @@ -23,7 +23,15 @@ public sealed class NwcMesh public BoundingBox3d? BoundingBox { get; init; } /// Optional 3D world transformation associated with this mesh instance. - public NwcTransform? Transform { get; set; } + public NwcTransform? Transform + { + get => _transform; + set + { + _transform = value; + _cachedWorldBounds = null; + } + } /// Surface material and color assigned to this mesh. public NwcMaterial? Material { get; set; } @@ -34,6 +42,7 @@ public sealed class NwcMesh /// Number of triangle faces in this mesh. public int TriangleCount => Indices.Count / 3; + private NwcTransform? _transform; private BoundingBox3d? _cachedWorldBounds; /// diff --git a/tests/NwcIO.Tests/GridSystemTests.cs b/tests/NwcIO.Tests/GridSystemTests.cs index 2a3c513..5f22df6 100644 --- a/tests/NwcIO.Tests/GridSystemTests.cs +++ b/tests/NwcIO.Tests/GridSystemTests.cs @@ -7,6 +7,68 @@ namespace NwcIO.Tests; public sealed class GridSystemTests { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GridSystem_AfterEndpointEdit_UpdatesIntersectionsAndLocation(bool editStart) + { + var grids = new NwcGridSystem(); + var line = grids.AddLine("A", new Vector3d(0, -10, 0), new Vector3d(0, 10, 0)); + grids.AddLine("B", new Vector3d(5, -10, 0), new Vector3d(5, 10, 0)); + grids.AddLine("1", new Vector3d(-10, 0, 0), new Vector3d(20, 0, 0)); + Assert.Equal(0.0, grids.GetIntersections()[0].IntersectionPoint.X); + Assert.Equal("B / 1", grids.FormatLocation(new Vector3d(5, 0, 0))); + + if (editStart) + { + line.Start = new Vector3d(20, -10, 0); + } + else + { + line.End = new Vector3d(20, 10, 0); + } + + var intersection = Assert.Single(grids.GetIntersections(), i => i.Line1Name == "A" && i.Line2Name == "1"); + Assert.Equal(10.0, intersection.IntersectionPoint.X); + Assert.Equal(0.0, intersection.IntersectionPoint.Y); + Assert.Equal("A / 1", grids.FormatLocation(new Vector3d(10, 0, 0))); + } + + [Fact] + public void GridSystem_AfterDirectLineAddition_UpdatesIntersectionsAndLocation() + { + var grids = new NwcGridSystem(); + grids.AddLine("A", new Vector3d(0, -10, 0), new Vector3d(0, 10, 0)); + grids.AddLine("1", new Vector3d(-10, 0, 0), new Vector3d(30, 0, 0)); + Assert.Single(grids.GetIntersections()); + Assert.Equal("A (+20.00m) / 1", grids.FormatLocation(new Vector3d(20, 0, 0))); + + grids.Lines.Add(new NwcGridLine("B", new Vector3d(20, -10, 0), new Vector3d(20, 10, 0))); + + Assert.Equal(2, grids.GetIntersections().Count); + var intersection = Assert.Single(grids.GetIntersections(), i => i.Line2Name == "B"); + Assert.Equal(20.0, intersection.IntersectionPoint.X); + Assert.Equal("1 / B", grids.FormatLocation(new Vector3d(20, 0, 0))); + } + + [Fact] + public void GridSystem_AfterDirectLineRemoval_UpdatesIntersectionsAndLocation() + { + var grids = new NwcGridSystem(); + var line = grids.AddLine("A", new Vector3d(0, -10, 0), new Vector3d(0, 10, 0)); + grids.AddLine("B", new Vector3d(20, -10, 0), new Vector3d(20, 10, 0)); + grids.AddLine("1", new Vector3d(-10, 0, 0), new Vector3d(30, 0, 0)); + Assert.Equal(2, grids.GetIntersections().Count); + Assert.Equal("A / 1", grids.FormatLocation(new Vector3d(0, 0, 0))); + + Assert.True(grids.Lines.Remove(line)); + + var intersection = Assert.Single(grids.GetIntersections()); + Assert.Equal("B / 1", intersection.DisplayName); + Assert.Equal(20.0, intersection.IntersectionPoint.X); + Assert.Equal("B (-20.00m) / 1", grids.FormatLocation(new Vector3d(0, 0, 0))); + } + [Fact] public void GridLine_CalculatesDistanceAndIntersection() { diff --git a/tests/NwcIO.Tests/MeshParserTests.cs b/tests/NwcIO.Tests/MeshParserTests.cs index fcca00a..e258350 100644 --- a/tests/NwcIO.Tests/MeshParserTests.cs +++ b/tests/NwcIO.Tests/MeshParserTests.cs @@ -8,6 +8,56 @@ namespace NwcIO.Tests; public class MeshParserTests { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Mesh_GetWorldBoundingBox_AfterTransformAssignment_UsesCurrentTransform(bool initiallyTransformed) + { + var mesh = new NwcMesh + { + Id = 1, + Vertices = [new Vector3d(0, 0, 0), new Vector3d(1, 2, 3)], + Indices = [], + BoundingBox = new BoundingBox3d(new Vector3d(0, 0, 0), new Vector3d(1, 2, 3)), + Transform = initiallyTransformed + ? new NwcTransform { Id = 1, Translation = new Vector3d(10, 20, 30) } + : null, + }; + + Assert.Equal(initiallyTransformed ? 10.0 : 0.0, mesh.GetWorldBoundingBox().Min.X); + + mesh.Transform = new NwcTransform { Id = 2, Translation = new Vector3d(100, 200, 300) }; + + var bounds = mesh.GetWorldBoundingBox(); + var vertices = mesh.GetWorldVertices(); + Assert.Equal(new Vector3d(100, 200, 300), bounds.Min); + Assert.Equal(new Vector3d(101, 202, 303), bounds.Max); + Assert.Equal(vertices[0], bounds.Min); + Assert.Equal(vertices[1], bounds.Max); + } + + [Fact] + public void Mesh_GetWorldBoundingBox_AfterTransformRemoval_UsesLocalBounds() + { + var mesh = new NwcMesh + { + Id = 1, + Vertices = [new Vector3d(0, 0, 0), new Vector3d(1, 2, 3)], + Indices = [], + BoundingBox = new BoundingBox3d(new Vector3d(0, 0, 0), new Vector3d(1, 2, 3)), + Transform = new NwcTransform { Id = 1, Translation = new Vector3d(100, 200, 300) }, + }; + + Assert.Equal(100.0, mesh.GetWorldBoundingBox().Min.X); + + mesh.Transform = null; + + var bounds = mesh.GetWorldBoundingBox(); + Assert.Equal(mesh.BoundingBox.Value, bounds); + Assert.Equal(mesh.GetWorldVertices()[0], bounds.Min); + Assert.Equal(mesh.GetWorldVertices()[1], bounds.Max); + } + [Fact] public void Mesh_TriangleCountAndIndices_AreCalculatedCorrectly() {