diff --git a/CadRevealFbxProvider.Tests/BatchUtils/GeometryInstancerTests.cs b/CadRevealFbxProvider.Tests/BatchUtils/GeometryInstancerTests.cs index cbe31888..80e6eb50 100644 --- a/CadRevealFbxProvider.Tests/BatchUtils/GeometryInstancerTests.cs +++ b/CadRevealFbxProvider.Tests/BatchUtils/GeometryInstancerTests.cs @@ -5,7 +5,7 @@ namespace CadRevealFbxProvider.Tests.BatchUtils; using CadRevealComposer; using CadRevealComposer.Primitives; using CadRevealComposer.Tessellation; -using CadRevealFbxProvider.BatchUtils; +using CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; public class GeometryOptimizerTests { diff --git a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldOptimizerTests.cs b/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldOptimizerTests.cs index 70298b27..72d5f630 100644 --- a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldOptimizerTests.cs +++ b/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldOptimizerTests.cs @@ -6,12 +6,11 @@ namespace CadRevealFbxProvider.Tests.BatchUtils; using CadRevealComposer.Primitives; using CadRevealComposer.Tessellation; using CadRevealComposer.Utils; -using CadRevealFbxProvider.BatchUtils; -using ScaffoldPartOptimizers; +using CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; -public class ScaffoldOptimizerTests +static class MeshCreator { - private static Mesh CreateMesh( + public static Mesh Create( float x1, float y1, float z1, @@ -33,155 +32,477 @@ uint i3 ); } - private static (CadRevealNode node, List nodeMeshes, List boundingBoxes) CreateCadRevealNode( - string partName + public static void ValidateMesh( + APrimitive primitive, + float x1Truth, + float y1Truth, + float z1Truth, + float x2Truth, + float y2Truth, + float z2Truth, + float x3Truth, + float y3Truth, + float z3Truth, + uint i1Truth, + uint i2Truth, + uint i3Truth ) { - var mesh1 = CreateMesh(5, 5, 5, 7, 7, 7, 3, 3, 3, 0, 1, 2); - var mesh2 = CreateMesh(1, 2, 3, 6, 7, 8, 9, 10, 11, 0, 1, 2); - var mesh3 = CreateMesh(6, 5, 4, 1, 3, 2, 14, 15, 16, 0, 1, 2); - - var bbox1 = new BoundingBox(new Vector3(0, 0, 0), new Vector3(1, 1, 1)); - var bbox2 = new BoundingBox(new Vector3(1, 2, 3), new Vector3(4, 5, 6)); - - var node = new CadRevealNode + const float tolerance = 1.0E-6f; + List truthVertices = + [ + new Vector3(x1Truth, y1Truth, z1Truth), + new Vector3(x2Truth, y2Truth, z2Truth), + new Vector3(x3Truth, y3Truth, z3Truth) + ]; + List truthIndices = [i1Truth, i2Truth, i3Truth]; + Assert.Multiple(() => { - TreeIndex = 0, - Name = partName, - Parent = null, - Geometries = - [ - new InstancedMesh(1, mesh1, Matrix4x4.Identity, 1, Color.Black, bbox1), - new InstancedMesh(2, mesh2, Matrix4x4.Identity, 2, Color.Black, bbox1), - new TriangleMesh(mesh3, 3, Color.Black, bbox1), - new TriangleMesh(mesh1, 4, Color.Black, bbox1), - new Circle(Matrix4x4.Identity, new Vector3(1.0f, 8.0f, 2.0f), 5, Color.Black, bbox2) - ] - }; + switch (primitive) + { + case InstancedMesh instancedMesh: + Assert.That( + instancedMesh.TemplateMesh.Vertices, + Is.EqualTo(truthVertices).Using((a, b) => a.EqualsWithinTolerance(b, tolerance)) + ); + Assert.That(instancedMesh.TemplateMesh.Indices, Is.EqualTo(truthIndices)); + break; + case TriangleMesh triangleMesh: + Assert.That( + triangleMesh.Mesh.Vertices, + Is.EqualTo(truthVertices).Using((a, b) => a.EqualsWithinTolerance(b, tolerance)) + ); + Assert.That(triangleMesh.Mesh.Indices, Is.EqualTo(truthIndices)); + break; + } + }); + } +} - return (node, [mesh1, mesh2, mesh3, mesh1, null], [bbox1, bbox1, bbox1, bbox1, bbox2]); +class ScaffoldOptimizerOverride( + List trueNodeMeshes, + string trueName, + ScaffoldOptimizerOverride.EOptimizationRoutine optimizationRoutine +) : ScaffoldOptimizerBase +{ + public enum EOptimizationRoutine + { + CheckInputOptimizeToNull = 0, + NoInputCheckOptimizeToMeshesAndPrimitiveAndCopy, + NoInputCheckOptimizeToSplitMeshes } - private static void CheckMeshList( - Vector3[] resultVertices, - List truthVertices, - uint[] resultIndices, - List truthIndices + protected override List? OptimizeNode( + string nodeName, + Mesh?[] meshes, + APrimitive[] nodeGeometries, + Func requestChildMeshInstanceId ) { - const float tolerance = 1.0E-6f; - Assert.Multiple(() => + if (optimizationRoutine == EOptimizationRoutine.CheckInputOptimizeToNull) { - Assert.That(resultVertices, Has.Length.EqualTo(truthVertices.Count)); - Assert.That(resultIndices, Has.Length.EqualTo(truthIndices.Count)); - }); + Assert.Multiple(() => + { + Assert.That(nodeName, Is.EqualTo(trueName)); + Assert.That(meshes, Has.Length.EqualTo(trueNodeMeshes.Count)); + }); - Assert.Multiple(() => + Assert.Multiple(() => + { + Assert.That(meshes[0], Is.SameAs(trueNodeMeshes[0])); + Assert.That(meshes[1], Is.SameAs(trueNodeMeshes[1])); + Assert.That(meshes[2], Is.SameAs(trueNodeMeshes[2])); + Assert.That(meshes[3], Is.SameAs(trueNodeMeshes[3])); + Assert.That(meshes[4], Is.Null); + }); + } + + var results = new List(); + var mesh1 = MeshCreator.Create(52, 52, 52, 72, 72, 72, 32, 32, 32, 2, 12, 22); + var mesh2 = MeshCreator.Create(13, 23, 33, 63, 73, 83, 93, 103, 113, 3, 13, 23); + var mesh3 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh4 = MeshCreator.Create(52, 52, 52, 72, 72, 72, 32, 32, 32, 2, 12, 22); + var mesh5 = MeshCreator.Create(13, 23, 33, 63, 73, 83, 93, 103, 113, 3, 13, 23); + var mesh6 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh7 = MeshCreator.Create(13, 23, 33, 63, 73, 83, 93, 103, 113, 3, 13, 23); + var mesh8 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh9 = MeshCreator.Create(13, 23, 33, 63, 73, 83, 93, 103, 113, 3, 13, 23); + var mesh10 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh11 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh12 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh13 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh14 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh15 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + var mesh16 = MeshCreator.Create(64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + APrimitive prim1 = mesh1.CalculateAxisAlignedBoundingBox().ToBoxPrimitive(3847, Color.Black); + + switch (optimizationRoutine) { - Assert.That( - resultVertices, - Is.EqualTo(truthVertices).Using((a, b) => a.EqualsWithinTolerance(b, tolerance)) - ); - Assert.That(resultIndices, Is.EqualTo(truthIndices)); - }); + case EOptimizationRoutine.CheckInputOptimizeToNull: + return null; + case EOptimizationRoutine.NoInputCheckOptimizeToMeshesAndPrimitiveAndCopy: + results.Add(new ScaffoldOptimizerResult(nodeGeometries[0], mesh1, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[1], mesh2, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(prim1)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[3], mesh3, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[4])); + return results; + case EOptimizationRoutine.NoInputCheckOptimizeToSplitMeshes: + // Split mesh 1 with Instance ID = 1 into three parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[0], mesh1, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[0], mesh2, 1, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[0], mesh3, 2, requestChildMeshInstanceId)); + + // Split mesh 2 with Instance ID = 1 into three parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[1], mesh4, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[1], mesh5, 1, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[1], mesh6, 2, requestChildMeshInstanceId)); + + // Split mesh 3 with Instance ID = 2 into two parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[2], mesh7, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[2], mesh8, 1, requestChildMeshInstanceId)); + + // Split mesh 4 with Instance ID = 2 into two parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[3], mesh9, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[3], mesh10, 1, requestChildMeshInstanceId)); + + // Split mesh 5 with Instance ID = 2 into two parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[4], mesh11, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[4], mesh12, 1, requestChildMeshInstanceId)); + + // Split mesh 6 with Instance ID = 3 into four parts, remembering to assign an index for each new mesh in the split and use the correct base for the split + results.Add(new ScaffoldOptimizerResult(nodeGeometries[5], mesh13, 0, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[5], mesh14, 1, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[5], mesh15, 2, requestChildMeshInstanceId)); + results.Add(new ScaffoldOptimizerResult(nodeGeometries[5], mesh16, 3, requestChildMeshInstanceId)); + + // We make no changes to the last entry of the node, which is non-mesh geometry, and simply return it + results.Add(new ScaffoldOptimizerResult(nodeGeometries[6])); + return results; + default: + ArgumentOutOfRangeException? exception = new ArgumentOutOfRangeException(); + exception.HelpLink = null; + exception.HResult = 0; + exception.Source = null; + throw exception; + } } +} - private static void CheckPrimitive(APrimitive primitive, BoundingBox originalBoundingBox) +public class ScaffoldOptimizerTests +{ + private enum ETestPurpose { - Assert.That(primitive.AxisAlignedBoundingBox, Is.EqualTo(originalBoundingBox)); + TestGeometryAssignment = 0, + TestWithOnlyNonMeshPrimitives, + TestInstancing } - private static Mesh? ToMesh(APrimitive primitive) + private static ( + CadRevealNode node, + List nodeMeshes, + List boundingBoxes, + List<(int i1, int i2)> instancePairs + ) CreateCadRevealNode(string partName, ETestPurpose testPurpose) { - return primitive switch + var mesh1 = MeshCreator.Create(5, 5, 5, 7, 7, 7, 3, 3, 3, 0, 1, 2); + var mesh2 = MeshCreator.Create(1, 2, 3, 6, 7, 8, 9, 10, 11, 0, 1, 2); + var mesh3 = MeshCreator.Create(6, 5, 4, 1, 3, 2, 14, 15, 16, 0, 1, 2); + + var bbox1 = new BoundingBox(new Vector3(0, 0, 0), new Vector3(1, 1, 1)); + var bbox2 = new BoundingBox(new Vector3(1, 2, 3), new Vector3(4, 5, 6)); + + switch (testPurpose) { - TriangleMesh triangleMesh => triangleMesh.Mesh, - InstancedMesh instancedMesh => instancedMesh.TemplateMesh, - _ => null + case ETestPurpose.TestGeometryAssignment: + var node1 = new CadRevealNode + { + TreeIndex = 0, + Name = partName, + Parent = null, + Geometries = + [ + new InstancedMesh(1, mesh1, Matrix4x4.Identity, 1, Color.Black, bbox1), + new InstancedMesh(2, mesh2, Matrix4x4.Identity, 2, Color.Black, bbox1), + new TriangleMesh(mesh3, 3, Color.Black, bbox1), + new TriangleMesh(mesh1, 4, Color.Black, bbox1), + new Circle(Matrix4x4.Identity, new Vector3(1.0f, 8.0f, 2.0f), 5, Color.Black, bbox2) + ] + }; + return (node1, [mesh1, mesh2, mesh3, mesh1, null], [bbox1, bbox1, bbox1, bbox1, bbox2], []); + case ETestPurpose.TestWithOnlyNonMeshPrimitives: + var node3 = new CadRevealNode + { + TreeIndex = 0, + Name = partName, + Parent = null, + Geometries = + [ + new Circle(Matrix4x4.Identity, new Vector3(1.0f, 8.0f, 2.0f), 5, Color.Black, bbox2), + new Circle(Matrix4x4.Identity, new Vector3(1.2f, 8.2f, 2.2f), 5, Color.Black, bbox2), + new Circle(Matrix4x4.Identity, new Vector3(1.4f, 8.4f, 2.4f), 5, Color.Black, bbox2), + new Circle(Matrix4x4.Identity, new Vector3(1.6f, 8.6f, 2.6f), 5, Color.Black, bbox2) + ] + }; + return (node3, [null, null, null, null], [bbox1, bbox1, bbox1, bbox1], []); + case ETestPurpose.TestInstancing: + var node2 = new CadRevealNode + { + TreeIndex = 0, + Name = partName, + Parent = null, + Geometries = + [ + new InstancedMesh(1, mesh1, Matrix4x4.Identity, 1, Color.Black, bbox1), + new InstancedMesh(1, mesh1, Matrix4x4.Identity, 2, Color.Black, bbox1), + new InstancedMesh(2, mesh2, Matrix4x4.Identity, 1, Color.Black, bbox1), + new InstancedMesh(2, mesh2, Matrix4x4.Identity, 2, Color.Black, bbox1), + new InstancedMesh(2, mesh2, Matrix4x4.Identity, 1, Color.Black, bbox1), + new InstancedMesh(3, mesh3, Matrix4x4.Identity, 2, Color.Black, bbox1), + new Circle(Matrix4x4.Identity, new Vector3(1.0f, 8.0f, 2.0f), 5, Color.Black, bbox2) + ] + }; + return ( + node2, + [mesh1, mesh1, mesh2, mesh2, mesh2, mesh3, null], + [bbox1, bbox1, bbox1, bbox1, bbox1, bbox1, bbox2], + [(0, 1), (2, 3), (2, 4)] + ); + default: + throw new ArgumentOutOfRangeException(nameof(testPurpose), testPurpose, null); + } + } + + private static CadRevealNode CloneCadRevealNode(CadRevealNode node) + { + return new CadRevealNode + { + TreeIndex = node.TreeIndex, + Name = node.Name, + Parent = node.Parent, + Geometries = node.Geometries }; } - private static void CheckGeometries( - APrimitive[] primitives, - List truthVertices, - List truthIndices, - List originalBoundingBoxes - ) + [Test] + public void TestMeshExtraction_GivenMixedNodeContent_CheckThatMeshesAreCorrectlyExtracted() { - Assert.That(originalBoundingBoxes.ToArray(), Has.Length.EqualTo(primitives.Length)); - for (int i = 0; i < primitives.Length; i++) + ulong currentInstanceId = 0; + + // Prepare data + var nodeData = CreateCadRevealNode("Test A", ETestPurpose.TestGeometryAssignment); + + // Make copy of the node while keeping the node contents and references in its tables the same + CadRevealNode nodeCpy = CloneCadRevealNode(nodeData.node); + + // Perform action to test and validate input to the OptimizeNode() call + var optimizer = new ScaffoldOptimizerOverride( + nodeData.nodeMeshes, + "Test A", + ScaffoldOptimizerOverride.EOptimizationRoutine.CheckInputOptimizeToNull + ); + optimizer.OptimizeNode(nodeData.node, OnRequestNewInstanceId); + + // Validate the returned values from Optimize. Since we return null from ScaffoldOptimizerOverride1.OptimizeNode() + // above, we expect that NO changes are done to the node contents. + Assert.Multiple(() => { - APrimitive? primitive = primitives[i]; - Mesh? mesh = ToMesh(primitive); - if (mesh != null) - { - CheckMeshList(mesh.Vertices, truthVertices, mesh.Indices, truthIndices); - } - else - { - CheckPrimitive(primitive, originalBoundingBoxes[i]); - } - } + Assert.That(nodeData.node.Geometries[0], Is.SameAs(nodeCpy.Geometries[0])); + Assert.That(nodeData.node.Geometries[1], Is.SameAs(nodeCpy.Geometries[1])); + Assert.That(nodeData.node.Geometries[2], Is.SameAs(nodeCpy.Geometries[2])); + Assert.That(nodeData.node.Geometries[3], Is.SameAs(nodeCpy.Geometries[3])); + Assert.That(nodeData.node.Geometries[4], Is.SameAs(nodeCpy.Geometries[4])); + }); + + return; + ulong OnRequestNewInstanceId() => currentInstanceId++; } - private static void CheckThatPrimitivesHaveNotChanged( - List originalMeshList, - List originalBoundingBoxes, - APrimitive[] primitives - ) + [Test] + public void TestMeshOutput_GivenMixedNodeContent_CheckThatMeshesAreCorrectlyOutput() { + ulong currentInstanceId = 0; + + // Prepare data + var nodeData = CreateCadRevealNode("Test B", ETestPurpose.TestGeometryAssignment); + + // Make copy of the node while keeping the node contents and references in its tables the same + CadRevealNode nodeCpy = CloneCadRevealNode(nodeData.node); + + // Perform action to test + var optimizer = new ScaffoldOptimizerOverride( + nodeData.nodeMeshes, + "Test B", + ScaffoldOptimizerOverride.EOptimizationRoutine.NoInputCheckOptimizeToMeshesAndPrimitiveAndCopy + ); + optimizer.OptimizeNode(nodeData.node, OnRequestNewInstanceId); + + // Validate the returned values from Optimize. Since we return null from ScaffoldOptimizerOverride1.OptimizeNode() + // above, we expect that NO changes are done to the node contents. Assert.Multiple(() => { - Assert.That(originalMeshList.ToArray(), Has.Length.EqualTo(primitives.Length)); - Assert.That(originalBoundingBoxes.ToArray(), Has.Length.EqualTo(primitives.Length)); + MeshCreator.ValidateMesh(nodeData.node.Geometries[0], 52, 52, 52, 72, 72, 72, 32, 32, 32, 2, 12, 22); + MeshCreator.ValidateMesh(nodeData.node.Geometries[1], 13, 23, 33, 63, 73, 83, 93, 103, 113, 3, 13, 23); + Assert.That(nodeData.node.Geometries[2].TreeIndex, Is.EqualTo(3847)); + MeshCreator.ValidateMesh(nodeData.node.Geometries[3], 64, 54, 44, 14, 34, 24, 144, 154, 164, 4, 14, 24); + Assert.That(nodeData.node.Geometries[4], Is.SameAs(nodeCpy.Geometries[4])); }); - for (int i = 0; i < primitives.Length; i++) + return; + ulong OnRequestNewInstanceId() => currentInstanceId++; + } + + [Test] + public void TestMeshOutput_GivenOnlyNonMeshPrimitiveNodeContent_CheckThatMeshesAreCorrectlyOutput() + { + ulong currentInstanceId = 0; + + // Prepare data + var nodeData = CreateCadRevealNode("Test C", ETestPurpose.TestWithOnlyNonMeshPrimitives); + + // Make copy of the node while keeping the node contents and references in its tables the same + CadRevealNode nodeCpy = CloneCadRevealNode(nodeData.node); + + // Perform action to test + var optimizer = new ScaffoldOptimizerOverride( + nodeData.nodeMeshes, + "Test C", + ScaffoldOptimizerOverride.EOptimizationRoutine.NoInputCheckOptimizeToMeshesAndPrimitiveAndCopy + ); + optimizer.OptimizeNode(nodeData.node, OnRequestNewInstanceId); + + // Validate the returned values from Optimize. Since we extract only non-mesh primitives in ScaffoldOptimizerOverride1.OptimizeNode() + // above, we expect that NO changes are done to the node contents. + Assert.Multiple(() => { - Mesh? mesh = ToMesh(primitives[i]); - List? truthVertices = originalMeshList[i]?.Vertices.ToList(); - List? truthIndices = originalMeshList[i]?.Indices.ToList(); - if (mesh != null && truthVertices != null && truthIndices != null) - { - CheckMeshList(mesh.Vertices, truthVertices, mesh.Indices, truthIndices); - } - else - { - CheckPrimitive(primitives[i], originalBoundingBoxes[i]); - } - } + Assert.That(nodeData.node.Geometries[0], Is.SameAs(nodeCpy.Geometries[0])); + Assert.That(nodeData.node.Geometries[1], Is.SameAs(nodeCpy.Geometries[1])); + Assert.That(nodeData.node.Geometries[2], Is.SameAs(nodeCpy.Geometries[2])); + Assert.That(nodeData.node.Geometries[3], Is.SameAs(nodeCpy.Geometries[3])); + }); + + return; + ulong OnRequestNewInstanceId() => currentInstanceId++; } [Test] - public void CheckScaffoldOptimizerActivation_GivenTestPartOptimizers_VerifyingTheReturnedNode() + public void TestInstanceIDAssignmentWithoutMeshSplit_GivenMeshesWithInstanceIDsAndANonMesh_CheckThatInstanceIDsAreCorrectlyAssigned() { - // Set up the input - CadRevealNode nodeA = CreateCadRevealNode("TestNode, Test A test").node; - CadRevealNode nodeB = CreateCadRevealNode("TestNode, Test B test").node; - CadRevealNode nodeC = CreateCadRevealNode("TestNode, Another BTest test").node; - (CadRevealNode nodeD, List nodeDMeshes, List boundingBoxes) = CreateCadRevealNode( - "TestNode test" + ulong currentInstanceId = 0; + + // Prepare data + var nodeData1 = CreateCadRevealNode("Test D", ETestPurpose.TestGeometryAssignment); + var nodeData2 = CreateCadRevealNode("Test D", ETestPurpose.TestGeometryAssignment); + CadRevealNode[] nodeData = [nodeData1.node, nodeData2.node]; + + // Make copy of the nodes while keeping the node contents and references in its tables the same + CadRevealNode nodeCpy1 = CloneCadRevealNode(nodeData1.node); + CadRevealNode nodeCpy2 = CloneCadRevealNode(nodeData2.node); + + // Perform action to test + var optimizer = new ScaffoldOptimizerOverride( + nodeData1.nodeMeshes, + "Test D", + ScaffoldOptimizerOverride.EOptimizationRoutine.NoInputCheckOptimizeToMeshesAndPrimitiveAndCopy ); + optimizer.OptimizeNodes(nodeData.ToList(), OnRequestNewInstanceId); + + // Validate the returned instance ID values assigned to the objects returned from OptimizeNode + // Input to optimizer for both nodes: + // Indices into Geometry: 0 1 2 3 4 + // Instance IDs node 1: 1 2 X X X + // Output from optimizer for nodes should be + // Indices into Geometry: 0 1 2 3 4 + // Child mesh indices node 1: (0) (0) X X X + // Instance IDs node 1: (A) (B) X X X + // Child mesh indices node 1: (0) (0) X X X + // Instance IDs node 1: (A) (B) X X X + // + // X - non mesh primitive, () - signifies one mesh split + Assert.Multiple(() => + { + Assert.That(ToId(nodeData1.node, 0), Is.Not.EqualTo(ToId(nodeData1.node, 1))); + Assert.That(ToId(nodeData2.node, 0), Is.Not.EqualTo(ToId(nodeData2.node, 1))); + Assert.That(ToId(nodeData1.node, 0), Is.EqualTo(ToId(nodeData2.node, 0))); + Assert.That(ToId(nodeData1.node, 1), Is.EqualTo(ToId(nodeData2.node, 1))); + Assert.That(nodeData1.node.Geometries[4], Is.SameAs(nodeCpy1.Geometries[4])); + Assert.That(nodeData2.node.Geometries[4], Is.SameAs(nodeCpy2.Geometries[4])); + }); + + return; + ulong OnRequestNewInstanceId() => currentInstanceId++; + ulong? ToId(CadRevealNode node, int index) => (node.Geometries[index] as InstancedMesh)?.InstanceId; + } + + [Test] + public void TestInstanceIDAssignmentAfterMeshSplit_GivenMeshesWithInstanceIDsAndANonMesh_CheckThatInstanceIDsAreCorrectlyAssigned() + { + ulong currentInstanceId = 0; + + // Prepare data + var nodeData1 = CreateCadRevealNode("Test E", ETestPurpose.TestInstancing); + var nodeData2 = CreateCadRevealNode("Test E", ETestPurpose.TestInstancing); + CadRevealNode[] nodeData = [nodeData1.node, nodeData2.node]; + + // Make copy of the nodes while keeping the node contents and references in its tables the same + CadRevealNode nodeCpy1 = CloneCadRevealNode(nodeData1.node); + CadRevealNode nodeCpy2 = CloneCadRevealNode(nodeData2.node); + + // Perform action to test + var optimizer = new ScaffoldOptimizerOverride( + nodeData1.nodeMeshes, + "Test E", + ScaffoldOptimizerOverride.EOptimizationRoutine.NoInputCheckOptimizeToSplitMeshes + ); + optimizer.OptimizeNodes(nodeData.ToList(), OnRequestNewInstanceId); + + // Validate the returned instance ID values assigned to the objects returned from OptimizeNode + // Input to optimizer for both nodes: + // Indices into Geometry: 0 1 2 3 4 5 6 + // Instance IDs node 1: 1 1 2 2 2 3 X + // Output from optimizer for nodes should be + // Indices into Geometry: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + // Child mesh indices node 1: (0 1 2) (0 1 2) (0 1) (0 1) (0 1) (0 1 2 3) X + // Instance IDs node 1: (A B C) (A B C) (D E) (D E) (D E) (F G H I) X + // Child mesh indices node 2: (0 1 2) (0 1 2) (0 1) (0 1) (0 1) (0 1 2 3) X + // Instance IDs node 2: (A B C) (A B C) (D E) (D E) (D E) (F G H I) X + // + // X - non mesh primitive, () - signifies one mesh split + Assert.Multiple(() => + { + Assert.That(ToId(nodeData1.node, 0), Is.EqualTo(ToId(nodeData1.node, 3))); + Assert.That(ToId(nodeData1.node, 1), Is.EqualTo(ToId(nodeData1.node, 4))); + Assert.That(ToId(nodeData1.node, 2), Is.EqualTo(ToId(nodeData1.node, 5))); + Assert.That(ToId(nodeData1.node, 0), Is.Not.EqualTo(ToId(nodeData1.node, 1))); + Assert.That(ToId(nodeData1.node, 0), Is.Not.EqualTo(ToId(nodeData1.node, 2))); + Assert.That(ToId(nodeData1.node, 1), Is.Not.EqualTo(ToId(nodeData1.node, 2))); + + Assert.That(ToId(nodeData1.node, 6), Is.EqualTo(ToId(nodeData1.node, 8))); + Assert.That(ToId(nodeData1.node, 8), Is.EqualTo(ToId(nodeData1.node, 10))); + Assert.That(ToId(nodeData1.node, 7), Is.EqualTo(ToId(nodeData1.node, 9))); + Assert.That(ToId(nodeData1.node, 9), Is.EqualTo(ToId(nodeData1.node, 11))); + Assert.That(ToId(nodeData1.node, 6), Is.Not.EqualTo(ToId(nodeData1.node, 7))); + Assert.That(ToId(nodeData1.node, 8), Is.Not.EqualTo(ToId(nodeData1.node, 9))); + Assert.That(ToId(nodeData1.node, 10), Is.Not.EqualTo(ToId(nodeData1.node, 11))); + + Assert.That(ToId(nodeData1.node, 12), Is.Not.EqualTo(ToId(nodeData1.node, 13))); + Assert.That(ToId(nodeData1.node, 12), Is.Not.EqualTo(ToId(nodeData1.node, 14))); + Assert.That(ToId(nodeData1.node, 12), Is.Not.EqualTo(ToId(nodeData1.node, 15))); + Assert.That(ToId(nodeData1.node, 13), Is.Not.EqualTo(ToId(nodeData1.node, 14))); + Assert.That(ToId(nodeData1.node, 13), Is.Not.EqualTo(ToId(nodeData1.node, 15))); + Assert.That(ToId(nodeData1.node, 14), Is.Not.EqualTo(ToId(nodeData1.node, 15))); + + Assert.That(nodeData1.node.Geometries[16], Is.SameAs(nodeCpy1.Geometries[6])); + Assert.That(nodeData2.node.Geometries[16], Is.SameAs(nodeCpy2.Geometries[6])); + }); + + for (int i = 0; i < 16; i++) + { + Assert.That(ToId(nodeData1.node, i), Is.EqualTo(ToId(nodeData2.node, i))); + } - // Create two optimizers - var optimizerA = new ScaffoldPartOptimizerTestPartA(); - var optimizerB = new ScaffoldPartOptimizerTestPartB(); - - // Configure the optimizer for testing - var optimizer = new ScaffoldOptimizer(); - optimizer.AddPartOptimizer(optimizerA); - optimizer.AddPartOptimizer(optimizerB); - - // Invoke the optimizer - optimizer.OptimizeNode(nodeA); - optimizer.OptimizeNode(nodeB); - optimizer.OptimizeNode(nodeC); - optimizer.OptimizeNode(nodeD); - - // Check the results - CheckGeometries(nodeA.Geometries, optimizerA.GetVerticesTruth(), optimizerA.GetIndicesTruth(), boundingBoxes); - CheckGeometries(nodeB.Geometries, optimizerB.GetVerticesTruth(), optimizerB.GetIndicesTruth(), boundingBoxes); - CheckGeometries(nodeC.Geometries, optimizerB.GetVerticesTruth(), optimizerB.GetIndicesTruth(), boundingBoxes); - CheckThatPrimitivesHaveNotChanged(nodeDMeshes, boundingBoxes, nodeD.Geometries); + return; + ulong OnRequestNewInstanceId() => currentInstanceId++; + ulong? ToId(CadRevealNode node, int index) => (node.Geometries[index] as InstancedMesh)?.InstanceId; } } diff --git a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTest.cs b/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTest.cs deleted file mode 100644 index 9eba64b6..00000000 --- a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTest.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CadRevealFbxProvider.Tests.BatchUtils.ScaffoldPartOptimizers; - -using System.Numerics; -using CadRevealComposer.Tessellation; -using CadRevealFbxProvider.BatchUtils.ScaffoldPartOptimizers; - -public abstract class ScaffoldPartOptimizerTest : IScaffoldPartOptimizer -{ - public abstract string Name { get; } - public abstract Mesh[] Optimize(Mesh mesh); - public abstract string[] GetPartNameTriggerKeywords(); - - public abstract List GetVerticesTruth(); - public abstract List GetIndicesTruth(); -} diff --git a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartA.cs b/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartA.cs deleted file mode 100644 index 93db305a..00000000 --- a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartA.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace CadRevealFbxProvider.Tests.BatchUtils.ScaffoldPartOptimizers; - -using System.Numerics; -using CadRevealComposer.Tessellation; - -public class ScaffoldPartOptimizerTestPartA : ScaffoldPartOptimizerTest -{ - public override List GetVerticesTruth() - { - return [new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f)]; - } - - public override List GetIndicesTruth() - { - return [1, 0, 2]; - } - - public override string Name - { - get { return "Part A test optimizer"; } - } - - public override Mesh[] Optimize(Mesh mesh) - { - return [new Mesh(GetVerticesTruth().ToArray(), GetIndicesTruth().ToArray(), mesh.Error)]; - } - - public override string[] GetPartNameTriggerKeywords() - { - return ["Test A"]; - } -} diff --git a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartB.cs b/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartB.cs deleted file mode 100644 index cd991ee2..00000000 --- a/CadRevealFbxProvider.Tests/BatchUtils/ScaffoldPartOptimizers/ScaffoldPartOptimizerTestPartB.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace CadRevealFbxProvider.Tests.BatchUtils.ScaffoldPartOptimizers; - -using System.Numerics; -using CadRevealComposer.Tessellation; - -public class ScaffoldPartOptimizerTestPartB : ScaffoldPartOptimizerTest -{ - public override List GetVerticesTruth() - { - return [new Vector3(3.0f, 0.0f, 0.0f), new Vector3(0.0f, 4.0f, 0.0f), new Vector3(0.0f, 0.0f, 5.0f)]; - } - - public override List GetIndicesTruth() - { - return [2, 0, 1]; - } - - public override string Name - { - get { return "Part A test optimizer"; } - } - - public override Mesh[] Optimize(Mesh mesh) - { - return [new Mesh(GetVerticesTruth().ToArray(), GetIndicesTruth().ToArray(), mesh.Error)]; - } - - public override string[] GetPartNameTriggerKeywords() - { - return ["Test B", "Another BTest"]; - } -} diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer.cs deleted file mode 100644 index f7756617..00000000 --- a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace CadRevealFbxProvider.BatchUtils; - -using System.Linq; -using CadRevealComposer; -using CadRevealComposer.Primitives; -using CadRevealComposer.Tessellation; -using ScaffoldPartOptimizers; - -public class ScaffoldOptimizer -{ - public void AddPartOptimizer(IScaffoldPartOptimizer optimizer) - { - _partOptimizers.Add(optimizer); - } - - public void OptimizeNode(CadRevealNode node) - { - var name = node.Name; - - var newGeometries = new List(); - foreach (APrimitive primitive in node.Geometries) - { - APrimitive[]? newPrimitiveList = OptimizePrimitive(primitive, name); - newGeometries.AddRange(newPrimitiveList ?? [primitive]); - } - - node.Geometries = newGeometries.ToArray(); - } - - private APrimitive[]? OptimizePrimitive(APrimitive primitive, string name) - { - // Handle only primitives that have their own Mesh objects, then pull out the mesh and optimize. These are the ones that need to be optimized. - var primitiveList = new List(); - switch (primitive) - { - case InstancedMesh instancedMesh: - { - Mesh[] meshes = OptimizeMesh(instancedMesh.TemplateMesh, name); - primitiveList.AddRange( - meshes.Select(mesh => new InstancedMesh( - instancedMesh.InstanceId, - mesh, - instancedMesh.InstanceMatrix, - instancedMesh.TreeIndex, - instancedMesh.Color, - instancedMesh.AxisAlignedBoundingBox - )) - ); - break; - } - case TriangleMesh triangleMesh: - { - Mesh[] meshes = OptimizeMesh(triangleMesh.Mesh, name); - primitiveList.AddRange( - meshes.Select(mesh => new TriangleMesh( - mesh, - triangleMesh.TreeIndex, - triangleMesh.Color, - triangleMesh.AxisAlignedBoundingBox - )) - ); - break; - } - } - - return primitiveList.Count > 0 ? primitiveList.ToArray() : null; - } - - private Mesh[] OptimizeMesh(Mesh mesh, string name) - { - Mesh[] optimizedMesh = [mesh]; - var triggeredOptimizers = new List(); - foreach (IScaffoldPartOptimizer partOptimizer in _partOptimizers) - { - bool partNameContainsPartOptimizerTrigger = false; - foreach (string partNameTriggerKeyword in partOptimizer.GetPartNameTriggerKeywords()) - { - if (name.Contains(partNameTriggerKeyword)) - { - partNameContainsPartOptimizerTrigger = true; - } - } - - if (partNameContainsPartOptimizerTrigger) - { - if (triggeredOptimizers.Count == 0) - { - optimizedMesh = partOptimizer.Optimize(mesh); - } - triggeredOptimizers.Add(partOptimizer); - } - } - - if (triggeredOptimizers.Count > 1) - { - Console.WriteLine( - $"Warning, the '{name}' scaffold part triggered {triggeredOptimizers.Count} optimizers, where only the first was applied:" - ); - - foreach (IScaffoldPartOptimizer partOptimizer in triggeredOptimizers) - { - Console.WriteLine($" * Optimizer named '{partOptimizer.Name}' which triggers on:"); - foreach (string partNameTriggerKeyword in partOptimizer.GetPartNameTriggerKeywords()) - { - Console.WriteLine($" - String '{partNameTriggerKeyword}'"); - } - } - } - - return optimizedMesh; - } - - private readonly List _partOptimizers = - [ - // :TODO: Fill in the available part optimizers here - ]; -} diff --git a/CadRevealFbxProvider/BatchUtils/GeometryInstancer.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/GeometryInstancer.cs similarity index 97% rename from CadRevealFbxProvider/BatchUtils/GeometryInstancer.cs rename to CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/GeometryInstancer.cs index c76a3b8f..ed3c6c03 100644 --- a/CadRevealFbxProvider/BatchUtils/GeometryInstancer.cs +++ b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/GeometryInstancer.cs @@ -1,4 +1,4 @@ -namespace CadRevealFbxProvider.BatchUtils; +namespace CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; using CadRevealComposer; using CadRevealComposer.Primitives; diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/IScaffoldOptimizerResult.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/IScaffoldOptimizerResult.cs new file mode 100644 index 00000000..e4690109 --- /dev/null +++ b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/IScaffoldOptimizerResult.cs @@ -0,0 +1,8 @@ +namespace CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; + +using CadRevealComposer.Primitives; + +public interface IScaffoldOptimizerResult +{ + public APrimitive Get(); +} diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizer.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizer.cs new file mode 100644 index 00000000..560dab13 --- /dev/null +++ b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizer.cs @@ -0,0 +1,45 @@ +namespace CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; + +using CadRevealComposer.Primitives; +using CadRevealComposer.Tessellation; + +public class ScaffoldOptimizer : ScaffoldOptimizerBase +{ + protected override List? OptimizeNode( + string nodeName, + Mesh?[] meshes, + APrimitive[] nodeGeometries, + Func requestChildMeshInstanceId + ) + { + // + // The meshes variable contains all meshes from a single node. It has the same length as nodeGeometries and will + // contain null if the primitive did not contain a mesh. nodeGeometries contains all primitives of the node + // to be optimized, also the non-mesh ones. To optimize meshes, + // 1) select an appropriate set of trigger keywords from the scaffold part names using the string.ContainsAny() + // extension method, + // 2) perform the optimization, which includes using meshes and nodeGeometries as basis for creating a + // completely new combination of InstancedMesh, TriangleMesh, and available non-mesh primitives. It + // is also possible to keep meshes or nodePrimitives the same by passing them on into the new + // combination. + // 3) Add everything in the combination into the results list through ScaffoldOptimizerResult entries, + // which can take InstancesMesh, TriangleMesh, or non-mesh primitives as input. + // + + var results = new List(); + if (nodeName.ContainsAny(["WordA", "WordB"])) + { + // :TODO: Example if to be removed after the first optimization has been implemented! + } + else if (nodeName.ContainsAny(["WordC", "WordD"])) + { + // :TODO: Example if to be removed after the first optimization has been implemented! + } + else + { + return null; + } + + return results; + } +} diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerBase.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerBase.cs new file mode 100644 index 00000000..e1938466 --- /dev/null +++ b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerBase.cs @@ -0,0 +1,101 @@ +namespace CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; + +using System.Drawing; +using System.Linq; +using CadRevealComposer; +using CadRevealComposer.Primitives; +using CadRevealComposer.Tessellation; + +static class ScaffoldOptimizerExtensions +{ + public static bool ContainsAny(this string str, string[] keywordList) => keywordList.Any(str.Contains); +} + +public class ScaffoldOptimizerBase +{ + private static Mesh?[] ExtractMeshes(APrimitive[] primitives) + { + var meshes = new List(); + foreach (APrimitive primitive in primitives) + { + Mesh? mesh = primitive switch + { + InstancedMesh instancedMesh => instancedMesh.TemplateMesh, + TriangleMesh triangleMesh => triangleMesh.Mesh, + _ => null + }; + + meshes.Add(mesh); + } + + return meshes.ToArray(); + } + + public void OptimizeNodes(List nodes, Func requestNewInstanceId) + { + foreach (CadRevealNode node in nodes) + { + OptimizeNode(node, requestNewInstanceId); + } + GeometryInstancer.Invoke(nodes); + } + + public void OptimizeNode(CadRevealNode node, Func requestNewInstanceId) + { + Mesh?[] meshes = ExtractMeshes(node.Geometries); + if (meshes.All(u => u == null)) + { + // If we do not find any meshes in the primitive, then we have nothing to optimize and should not update the node primitives + return; + } + + var results = OptimizeNode(node.Name, meshes, node.Geometries, RequestChildMeshInstanceId); + if (results == null) + { + // If we do not have a result from the optimization, then we have not optimized anything and should not update the node primitives + return; + } + + var primitiveList = new List(); + primitiveList.AddRange(results.Select(result => result.Get())); + node.Geometries = primitiveList.ToArray(); + + return; + ulong RequestChildMeshInstanceId(ulong instanceIdParentMesh, int indexChildMesh) => + OnRequestChildMeshInstanceId(instanceIdParentMesh, indexChildMesh, requestNewInstanceId); + } + + protected virtual List? OptimizeNode( + string nodeName, + Mesh?[] meshes, + APrimitive[] nodeGeometries, + Func requestChildMeshInstanceId + ) + { + throw new NotImplementedException(); + } + + private ulong OnRequestChildMeshInstanceId( + ulong instanceIdParentMesh, + int indexChildMesh, + Func requestNewInstanceId + ) + { + if (_childMeshInstanceIdLookup.TryGetValue(instanceIdParentMesh, out Dictionary? retInstanceIdDict)) + { + if (retInstanceIdDict.TryGetValue(indexChildMesh, out ulong retInstanceId)) + { + return retInstanceId; + } + + _childMeshInstanceIdLookup[instanceIdParentMesh].Add(indexChildMesh, requestNewInstanceId()); + return _childMeshInstanceIdLookup[instanceIdParentMesh][indexChildMesh]; + } + + _childMeshInstanceIdLookup.Add(instanceIdParentMesh, new Dictionary()); + _childMeshInstanceIdLookup[instanceIdParentMesh].Add(indexChildMesh, requestNewInstanceId()); + return _childMeshInstanceIdLookup[instanceIdParentMesh][indexChildMesh]; + } + + private readonly Dictionary> _childMeshInstanceIdLookup = new(); +} diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerResult.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerResult.cs new file mode 100644 index 00000000..f1f56084 --- /dev/null +++ b/CadRevealFbxProvider/BatchUtils/ScaffoldOptimizer/ScaffoldOptimizerResult.cs @@ -0,0 +1,52 @@ +namespace CadRevealFbxProvider.BatchUtils.ScaffoldOptimizer; + +using CadRevealComposer.Primitives; +using CadRevealComposer.Tessellation; + +public class ScaffoldOptimizerResult : IScaffoldOptimizerResult +{ + public ScaffoldOptimizerResult( + APrimitive basePrimitive, // When we split a primitive/mesh into several pieces, this is the origin of that split (before splitting) + Mesh optimizedMesh, // This is the optimized mesh. In case of splitting into several pieces, there will be multiple ScaffoldOptimizerResult instances with the same basePrimitive, but different indexChildMesh + int indexChildMesh, // This is the zero based index of the meshes that are produced during a mesh splitting + Func requestChildMeshInstanceId + ) + { + switch (basePrimitive) + { + case InstancedMesh instancedMesh: + ulong instanceId = requestChildMeshInstanceId(instancedMesh.InstanceId, indexChildMesh); + _optimizedPrimitive = new InstancedMesh( + instanceId, + optimizedMesh, + instancedMesh.InstanceMatrix, + instancedMesh.TreeIndex, + instancedMesh.Color, + optimizedMesh.CalculateAxisAlignedBoundingBox() + ); + return; + case TriangleMesh triangleMesh: + _optimizedPrimitive = new TriangleMesh( + optimizedMesh, + triangleMesh.TreeIndex, + triangleMesh.Color, + optimizedMesh.CalculateAxisAlignedBoundingBox() + ); + return; + } + + _optimizedPrimitive = basePrimitive; + } + + public ScaffoldOptimizerResult(APrimitive optimizedPrimitive) + { + _optimizedPrimitive = optimizedPrimitive; + } + + public APrimitive Get() + { + return _optimizedPrimitive; + } + + private readonly APrimitive _optimizedPrimitive; +} diff --git a/CadRevealFbxProvider/BatchUtils/ScaffoldPartOptimizers/IScaffoldPartOptimizer.cs b/CadRevealFbxProvider/BatchUtils/ScaffoldPartOptimizers/IScaffoldPartOptimizer.cs deleted file mode 100644 index 0bb381ff..00000000 --- a/CadRevealFbxProvider/BatchUtils/ScaffoldPartOptimizers/IScaffoldPartOptimizer.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CadRevealFbxProvider.BatchUtils.ScaffoldPartOptimizers; - -using CadRevealComposer.Tessellation; - -public interface IScaffoldPartOptimizer -{ - public string Name { get; } - public Mesh[] Optimize(Mesh mesh); - public string[] GetPartNameTriggerKeywords(); -}