From 646ed34bf13b1c315686defd2935d88c6776fbe3 Mon Sep 17 00:00:00 2001 From: Ross Nordby Date: Wed, 2 Oct 2024 00:02:30 -0500 Subject: [PATCH] In the middle of fixing ye olde convex hull bug with a drizzling of brute force. Works now, but needs some cleanup, debug improvements, and regression testing. --- BepuPhysics/Collidables/ConvexHullHelper.cs | 507 +++++++++---------- Demos/DemoSet.cs | 2 + Demos/SpecializedTests/ConvexHullTestDemo.cs | 500 ++++++++++++------ 3 files changed, 572 insertions(+), 437 deletions(-) diff --git a/BepuPhysics/Collidables/ConvexHullHelper.cs b/BepuPhysics/Collidables/ConvexHullHelper.cs index a186a08f..c09e075b 100644 --- a/BepuPhysics/Collidables/ConvexHullHelper.cs +++ b/BepuPhysics/Collidables/ConvexHullHelper.cs @@ -472,60 +472,6 @@ public override string ToString() } } - /// - /// Tracks the faces associated with a detected surface edge. - /// During hull calculation, surface edges that have only one face associated with them should have an outstanding entry in the edges to visit set. - /// Surface edges can never validly have more than two faces associated with them. Two means that an edge is 'complete', or should be. - /// Detecting a third edge implies an error condition. By tracking *which* faces were associated with an edge, we can attempt to fix the error. - /// This type of error tends to occur when there is a numerical disagreement about what vertices are coplanar with a face. - /// One iteration could find what it thinks is a complete face, and a later iteration ends up finding more vertices *including* the ones already contained in the previous face. - /// That's a recipe for excessive edge faces, but it's also a direct indicator that we should merge the involved faces for being close enough to coplanar that the error happened in the first place. - /// - struct EdgeFaceIndices - { - public int FaceA; - public int FaceB; - public bool Complete => FaceA >= 0 && FaceB >= 0; - - public EdgeFaceIndices(int initialFaceIndex) - { - FaceA = initialFaceIndex; - FaceB = -1; - } - - - } - - static void RemoveFaceFromEdge(int a, int b, int faceIndex, ref QuickDictionary facesForEdges) - { - EdgeEndpoints edge; - edge.A = a; - edge.B = b; - var exists = facesForEdges.GetTableIndices(ref edge, out _, out var index); - Debug.Assert(exists, "Whoa something weird happened here! A face was created, but there's a missing edge for its face?"); - ref var facesForEdge = ref facesForEdges.Values[index]; - Debug.Assert(faceIndex == facesForEdge.FaceA || faceIndex == facesForEdge.FaceB, "If you're trying to remove a face index from the edge, it better be in the edge!"); - if (facesForEdge.FaceA == faceIndex) - { - facesForEdge.FaceA = facesForEdge.FaceB; - facesForEdge.FaceB = -1; - if (facesForEdge.FaceA == -1) - { - //This edge no longer has any faces associated with it. - facesForEdges.FastRemove(ref edge); - } - } - else - { - facesForEdge.FaceB = -1; - } - //Note that we do *not* add the edge back into the 'edges to test' list when transitioning from 2 faces to 1 face for the edge. - //This function is invoked when an edge is being deleted because it's related to a deleted face. - //There are two possible outcomes: - //1. The completion of the merge will see a face added back to this edge, - //2. it's an orphaned internal edge that should not be tested. - } - internal struct EarlyFace { public QuickList VertexIndices; @@ -548,13 +494,12 @@ static void AddFace(ref QuickList faces, BufferPool pool, Vector3 nor face.VertexIndices.AddRangeUnsafely(vertexIndices); } - static void AddFaceToEdgesAndTestList(BufferPool pool, + static void AddFaceEdgesToTestList(BufferPool pool, ref QuickList reducedFaceIndices, ref QuickList edgesToTest, - ref QuickDictionary facesForEdges, + ref QuickSet submittedEdgeTests, Vector3 faceNormal, int newFaceIndex) { - facesForEdges.EnsureCapacity(facesForEdges.Count + reducedFaceIndices.Count, pool); var previousIndex = reducedFaceIndices[reducedFaceIndices.Count - 1]; for (int i = 0; i < reducedFaceIndices.Count; ++i) { @@ -562,27 +507,12 @@ static void AddFaceToEdgesAndTestList(BufferPool pool, endpoints.A = previousIndex; endpoints.B = reducedFaceIndices[i]; previousIndex = endpoints.B; - if (facesForEdges.GetTableIndices(ref endpoints, out var tableIndex, out var elementIndex)) - { - ref var edgeFaceCount = ref facesForEdges.Values[elementIndex]; - Debug.Assert(edgeFaceCount.FaceB == -1, - "While we let execution continue, this is an error condition and implies overlapping triangles are being generated." + - "This tends to happen when there are many near-coplanar vertices, so numerical tolerances across different faces cannot consistently agree."); - edgeFaceCount.FaceB = newFaceIndex; - } - else - { - //This edge is not yet claimed by any edge. Claim it for the new face and add the edge for further testing. - EdgeToTest nextEdgeToTest; - nextEdgeToTest.Endpoints = endpoints; - nextEdgeToTest.FaceNormal = faceNormal; - nextEdgeToTest.FaceIndex = newFaceIndex; - facesForEdges.Keys[facesForEdges.Count] = nextEdgeToTest.Endpoints; - facesForEdges.Values[facesForEdges.Count] = new EdgeFaceIndices(newFaceIndex); - //Use the encoding- all indices are offset by 1 since 0 represents 'empty'. - facesForEdges.Table[tableIndex] = ++facesForEdges.Count; - edgesToTest.Allocate(pool) = nextEdgeToTest; - } + EdgeToTest nextEdgeToTest; + nextEdgeToTest.Endpoints = endpoints; + nextEdgeToTest.FaceNormal = faceNormal; + nextEdgeToTest.FaceIndex = newFaceIndex; + edgesToTest.Allocate(pool) = nextEdgeToTest; + submittedEdgeTests.Add(endpoints, pool); } } @@ -592,70 +522,70 @@ static void AddIfNotPresent(ref QuickList list, int value, BufferPool pool) list.Allocate(pool) = value; } - //public struct DebugStep - //{ - // public EdgeEndpoints SourceEdge; - // public List Raw; - // public List Reduced; - // public bool[] AllowVertex; - // public Vector3 FaceNormal; - // public Vector3 BasisX; - // public Vector3 BasisY; - // public List FaceStarts; - // public List FaceIndices; - // public bool[] FaceDeleted; - // public int[] MergedFaceIndices; - // public int FaceIndex; - // public Vector3[] FaceNormals; - - // internal DebugStep(EdgeEndpoints sourceEdge, ref QuickList raw, Vector3 faceNormal, Vector3 basisX, Vector3 basisY, ref QuickList reduced, ref Buffer allowVertex, ref QuickList faces, Span mergedFaceIndices, int faceIndex) - // { - // SourceEdge = sourceEdge; - // FaceNormal = faceNormal; - // BasisX = basisX; - // BasisY = basisY; - // Raw = new List(); - // for (int i = 0; i < raw.Count; ++i) - // { - // Raw.Add(raw[i]); - // } - // Reduced = new List(); - // for (int i = 0; i < reduced.Count; ++i) - // { - // Reduced.Add(reduced[i]); - // } - // AllowVertex = new bool[allowVertex.Length]; - // for (int i = 0; i < allowVertex.Length; ++i) - // { - // AllowVertex[i] = allowVertex[i] != 0; - // } - // FaceStarts = new List(faces.Count); - // FaceIndices = new List(); - // FaceDeleted = new bool[faces.Count]; - // FaceNormals = new Vector3[faces.Count]; - // for (int i = 0; i < faces.Count; ++i) - // { - // ref var face = ref faces[i]; - // FaceStarts.Add(FaceIndices.Count); - // for (int j = 0; j < face.VertexIndices.Count; ++j) - // FaceIndices.Add(face.VertexIndices[j]); - // FaceDeleted[i] = face.Deleted; - // FaceNormals[i] = face.Normal; - // } - // MergedFaceIndices = mergedFaceIndices.ToArray(); - // FaceIndex = faceIndex; - // } - //} - ///// - ///// Computes the convex hull of a set of points. - ///// - ///// Point set to compute the convex hull of. - ///// Buffer pool to pull memory from when creating the hull. - ///// Convex hull of the input point set. - //public static void ComputeHull(Span points, BufferPool pool, out HullData hullData) - //{ - // ComputeHull(points, pool, out hullData, out _); - //} + public struct DebugStep + { + public EdgeEndpoints SourceEdge; + public List Raw; + public List Reduced; + public bool[] AllowVertex; + public Vector3 FaceNormal; + public Vector3 BasisX; + public Vector3 BasisY; + public List FaceStarts; + public List FaceIndices; + public bool[] FaceDeleted; + public int[] MergedFaceIndices; + public int FaceIndex; + public Vector3[] FaceNormals; + + internal DebugStep(EdgeEndpoints sourceEdge, ref QuickList raw, Vector3 faceNormal, Vector3 basisX, Vector3 basisY, ref QuickList reduced, ref Buffer allowVertex, ref QuickList faces, Span mergedFaceIndices, int faceIndex) + { + SourceEdge = sourceEdge; + FaceNormal = faceNormal; + BasisX = basisX; + BasisY = basisY; + Raw = new List(); + for (int i = 0; i < raw.Count; ++i) + { + Raw.Add(raw[i]); + } + Reduced = new List(); + for (int i = 0; i < reduced.Count; ++i) + { + Reduced.Add(reduced[i]); + } + AllowVertex = new bool[allowVertex.Length]; + for (int i = 0; i < allowVertex.Length; ++i) + { + AllowVertex[i] = allowVertex[i] != 0; + } + FaceStarts = new List(faces.Count); + FaceIndices = new List(); + FaceDeleted = new bool[faces.Count]; + FaceNormals = new Vector3[faces.Count]; + for (int i = 0; i < faces.Count; ++i) + { + ref var face = ref faces[i]; + FaceStarts.Add(FaceIndices.Count); + for (int j = 0; j < face.VertexIndices.Count; ++j) + FaceIndices.Add(face.VertexIndices[j]); + FaceDeleted[i] = face.Deleted; + FaceNormals[i] = face.Normal; + } + MergedFaceIndices = mergedFaceIndices.ToArray(); + FaceIndex = faceIndex; + } + } + /// + /// Computes the convex hull of a set of points. + /// + /// Point set to compute the convex hull of. + /// Buffer pool to pull memory from when creating the hull. + /// Convex hull of the input point set. + public static void ComputeHull(Span points, BufferPool pool, out HullData hullData) + { + ComputeHull(points, pool, out hullData, out _); + } /// @@ -664,9 +594,9 @@ static void AddIfNotPresent(ref QuickList list, int value, BufferPool pool) /// Point set to compute the convex hull of. /// Buffer pool to pull memory from when creating the hull. /// Convex hull of the input point set. - public static void ComputeHull(Span points, BufferPool pool, out HullData hullData)//, out List steps) + public static void ComputeHull(Span points, BufferPool pool, out HullData hullData, out List steps) { - //steps = new List(); + steps = new List(); if (points.Length <= 0) { hullData = default; @@ -789,7 +719,7 @@ public static void ComputeHull(Span points, BufferPool pool, out HullDa var faces = new QuickList(points.Length, pool); var edgesToTest = new QuickList(points.Length, pool); - var facesForEdges = new QuickDictionary(points.Length, pool); + var submittedEdgeTests = new QuickSet(points.Length, pool); var facesNeedingMerge = new QuickList(32, pool); if (reducedFaceIndices.Count >= 3) { @@ -802,7 +732,6 @@ public static void ComputeHull(Span points, BufferPool pool, out HullDa edgeToAdd.Endpoints.B = reducedFaceIndices[i]; edgeToAdd.FaceNormal = initialFaceNormal; edgeToAdd.FaceIndex = 0; - facesForEdges.Add(ref edgeToAdd.Endpoints, new EdgeFaceIndices(0), pool); } //Since an actual face was found, we go ahead and output it into the face set. AddFace(ref faces, pool, initialFaceNormal, reducedFaceIndices); @@ -823,19 +752,15 @@ public static void ComputeHull(Span points, BufferPool pool, out HullDa if (Vector3.Dot(basisX, edgeToAdd.FaceNormal) > 0) Helpers.Swap(ref edgeToAdd.Endpoints.A, ref edgeToAdd.Endpoints.B); } - //Vector3Wide.ReadFirst(initialBasisX, out var debugInitialBasisX); - //Vector3Wide.ReadFirst(initialBasisY, out var debugInitialBasisY); - //steps.Add(new DebugStep(initialSourceEdge, ref rawFaceVertexIndices, initialFaceNormal, debugInitialBasisX, debugInitialBasisY, ref reducedFaceIndices, ref allowVertices, ref faces, default, reducedFaceIndices.Count >= 3 ? 0 : -1)); + Vector3Wide.ReadFirst(initialBasisX, out var debugInitialBasisX); + Vector3Wide.ReadFirst(initialBasisY, out var debugInitialBasisY); + steps.Add(new DebugStep(initialSourceEdge, ref rawFaceVertexIndices, initialFaceNormal, debugInitialBasisX, debugInitialBasisY, ref reducedFaceIndices, ref allowVertices, ref faces, default, reducedFaceIndices.Count >= 3 ? 0 : -1)); int facesDeletedCount = 0; while (edgesToTest.Count > 0) { edgesToTest.Pop(out var edgeToTest); - //Make sure the new edge hasn't already been filled by another traversal. - var faceCountIndex = facesForEdges.IndexOf(edgeToTest.Endpoints); - if (faceCountIndex >= 0 && facesForEdges.Values[faceCountIndex].Complete) - continue; ref var edgeA = ref points[edgeToTest.Endpoints.A]; ref var edgeB = ref points[edgeToTest.Endpoints.B]; @@ -858,140 +783,184 @@ public static void ComputeHull(Span points, BufferPool pool, out HullDa if (reducedFaceIndices.Count < 3) { - //steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, default, -1)); + steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, default, -1)); //Degenerate face found; don't bother creating work for it. continue; } - var faceCountPriorToAdd = faces.Count; - - facesNeedingMerge.Count = 0; - while (true) + // Brute force scan all the faces to see if the new face is coplanar with any of them. + Console.WriteLine($"step count: {steps.Count}"); + bool mergedFace = false; + for (int i = 0; i < faces.Count; ++i) { - //This implementation bites the bullet pretty hard on numerical problems. They most frequently arise from near coplanar vertices. - //It's possible that two iterations see different subsets of 'coplanar' vertices, either causing two faces with near equal normal or - //even causing an edge to have more than two faces associated with it. - //Before making any modifications to the existing data, iterate over all the edges in the current face to check for any such edges. - - //While the usual flow here will be to reduce the vertices we just found and accept the result immediately, - //it's possible for the just-detected face to end up coplanar with an existing face because of numerical issues that prevented detecting the whole face earlier. - //Rather than using more precise math, we detect the failure and merge faces after the fact. - //Unfortunately, this can happen recursively! Merging two redundant faces could reveal a third face that also needs to be merged. - //This is uncommon, but it needs to be covered. - //So, we just stick everything into a while loop and let it iterate until it's done. - //The good news is that the merging process won't loop forever- every merge results in a face that is at least as large as any contributor, and any internal points - //that get reduced out of consideration are marked with allowVertex[whateverInteriorPointIndex] = false. - int previousFaceMergeCount = facesNeedingMerge.Count; - var previousEndpointIndex = reducedFaceIndices[reducedFaceIndices.Count - 1]; - for (int i = 0; i < reducedFaceIndices.Count; ++i) + ref var face = ref faces[i]; + if (face.Deleted) + continue; + if (Vector3.Dot(face.Normal, faceNormal) > normalCoplanarityEpsilon) { - EdgeEndpoints edgeEndpoints; - edgeEndpoints.A = previousEndpointIndex; - edgeEndpoints.B = reducedFaceIndices[i]; - previousEndpointIndex = edgeEndpoints.B; - - if (facesForEdges.GetTableIndices(ref edgeEndpoints, out var tableIndex, out var elementIndex)) + Console.WriteLine($"Merging face {i} with new face, dot {Vector3.Dot(face.Normal, faceNormal)}:"); + Console.WriteLine($"Existing face: {face.Normal}"); + Console.WriteLine($"Candidate: {faceNormal}"); + // The new face is coplanar with an existing face. Merge the new face into the old face. + rawFaceVertexIndices.EnsureCapacity(reducedFaceIndices.Count + face.VertexIndices.Count, pool); + rawFaceVertexIndices.Count = reducedFaceIndices.Count; + reducedFaceIndices.Span.CopyTo(0, rawFaceVertexIndices.Span, 0, reducedFaceIndices.Count); + for (int j = 0; j < face.VertexIndices.Count; ++j) { - //There is already at least one face associated with this edge. Do we need to merge? - ref var edgeFaces = ref facesForEdges.Values[elementIndex]; - Debug.Assert(edgeFaces.FaceA >= 0); - var aDot = Vector3.Dot(faces[edgeFaces.FaceA].Normal, faceNormal); - if (edgeFaces.FaceB >= 0) + var vertexIndex = face.VertexIndices[j]; + // Only testing the original set of reduced face indices for duplicates when merging; we know the face's point set isn't redundant. + if (allowVertices[vertexIndex] != 0 && !reducedFaceIndices.Contains(vertexIndex)) { - //The edge already has two faces associated with it. This is definitely a numerical error; separate coplanar faces were generated. - //We *must* merge at least one pair of faces. - //Note that technically both faces can end up being included, even though we've already tested A and B; - //the new face could be a bridge that's close enough to both, even if they were barely too far from each other. - var bDot = Vector3.Dot(faces[edgeFaces.FaceB].Normal, faceNormal); - if (aDot >= normalCoplanarityEpsilon && bDot >= normalCoplanarityEpsilon) - { - AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceA, pool); - AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceB, pool); - } - else - { - //If both aren't merge candidates, then just pick the one that's closer. - AddIfNotPresent(ref facesNeedingMerge, aDot > bDot ? edgeFaces.FaceA : edgeFaces.FaceB, pool); - } - } - else - { - //Only one face already present. Check if it's coplanar. - if (aDot >= normalCoplanarityEpsilon) - AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceA, pool); + rawFaceVertexIndices.AllocateUnsafely() = vertexIndex; } } + // Rerun reduction for the merged face. + face.VertexIndices.Count = 0; + facePoints.Count = 0; + //steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, facesNeedingMerge, i)); + face.VertexIndices.EnsureCapacity(rawFaceVertexIndices.Count, pool); + ReduceFace(ref rawFaceVertexIndices, faceNormal, points, planeEpsilonNarrow, ref facePoints, ref allowVertices, ref face.VertexIndices); + mergedFace = true; + break; } + } + var faceCountPriorToAdd = faces.Count; - if (facesNeedingMerge.Count > previousFaceMergeCount) - { - //This iteration has found more faces which should be merged together. - //Accumulate the vertices. - for (int i = previousFaceMergeCount; i < facesNeedingMerge.Count; ++i) - { - ref var sourceFace = ref faces[facesNeedingMerge[i]]; - for (int j = 0; j < sourceFace.VertexIndices.Count; ++j) - { - var vertexIndex = sourceFace.VertexIndices[j]; - if (allowVertices[vertexIndex] != 0 && !rawFaceVertexIndices.Contains(vertexIndex)) - { - rawFaceVertexIndices.Allocate(pool) = vertexIndex; - } - } - } + //facesNeedingMerge.Count = 0; + //while (true) + //{ + // //This implementation bites the bullet pretty hard on numerical problems. They most frequently arise from near coplanar vertices. + // //It's possible that two iterations see different subsets of 'coplanar' vertices, either causing two faces with near equal normal or + // //even causing an edge to have more than two faces associated with it. + // //Before making any modifications to the existing data, iterate over all the edges in the current face to check for any such edges. + + // //While the usual flow here will be to reduce the vertices we just found and accept the result immediately, + // //it's possible for the just-detected face to end up coplanar with an existing face because of numerical issues that prevented detecting the whole face earlier. + // //Rather than using more precise math, we detect the failure and merge faces after the fact. + // //Unfortunately, this can happen recursively! Merging two redundant faces could reveal a third face that also needs to be merged. + // //This is uncommon, but it needs to be covered. + // //So, we just stick everything into a while loop and let it iterate until it's done. + // //The good news is that the merging process won't loop forever- every merge results in a face that is at least as large as any contributor, and any internal points + // //that get reduced out of consideration are marked with allowVertex[whateverInteriorPointIndex] = false. + // int previousFaceMergeCount = facesNeedingMerge.Count; + // var previousEndpointIndex = reducedFaceIndices[reducedFaceIndices.Count - 1]; + // for (int i = 0; i < reducedFaceIndices.Count; ++i) + // { + // EdgeEndpoints edgeEndpoints; + // edgeEndpoints.A = previousEndpointIndex; + // edgeEndpoints.B = reducedFaceIndices[i]; + // previousEndpointIndex = edgeEndpoints.B; - //Re-reduce. - reducedFaceIndices.Count = 0; - facePoints.Count = 0; - ReduceFace(ref rawFaceVertexIndices, faceNormal, points, planeEpsilonNarrow, ref facePoints, ref allowVertices, ref reducedFaceIndices); - } - else - { - //No more faces need to be merged! Go ahead and apply the changes. - for (int i = 0; i < facesNeedingMerge.Count; ++i) - { - //Note that we do not merge into an existing face; for simplicity, we just merge into the face we're going to append and mark the old faces as being deleted. - //Note that we do *not* remove deleted faces from the faces list. That would break all the face indices that we've accumulated. - var faceIndex = facesNeedingMerge[i]; - ref var faceToRemove = ref faces[faceIndex]; - faceToRemove.Deleted = true; - - //For every edge of any face we're merging, remove the face from the edge lists. - var previousIndex = faceToRemove.VertexIndices[faceToRemove.VertexIndices.Count - 1]; - for (int j = 0; j < faceToRemove.VertexIndices.Count; ++j) - { - RemoveFaceFromEdge(previousIndex, faceToRemove.VertexIndices[j], faceIndex, ref facesForEdges); - previousIndex = faceToRemove.VertexIndices[j]; - } - //Remove any references to this face in the edges to test. - int removedEdgesToTestCount = 0; - for (int j = 0; j < edgesToTest.Count; ++j) - { - if (edgesToTest[j].FaceIndex == faceIndex) - { - ++removedEdgesToTestCount; - } - else if (removedEdgesToTestCount > 0) - { - //Scoot edges to test back. - edgesToTest[j - removedEdgesToTestCount] = edgesToTest[j]; - } - } - edgesToTest.Count -= removedEdgesToTestCount; - } + // if (facesForEdges.GetTableIndices(ref edgeEndpoints, out var tableIndex, out var elementIndex)) + // { + // //There is already at least one face associated with this edge. Do we need to merge? + // ref var edgeFaces = ref facesForEdges.Values[elementIndex]; + // Debug.Assert(edgeFaces.FaceA >= 0); + // var aDot = Vector3.Dot(faces[edgeFaces.FaceA].Normal, faceNormal); + // if (edgeFaces.FaceB >= 0) + // { + // //The edge already has two faces associated with it. This is definitely a numerical error; separate coplanar faces were generated. + // //We *must* merge at least one pair of faces. + // //Note that technically both faces can end up being included, even though we've already tested A and B; + // //the new face could be a bridge that's close enough to both, even if they were barely too far from each other. + // var bDot = Vector3.Dot(faces[edgeFaces.FaceB].Normal, faceNormal); + // if (aDot >= normalCoplanarityEpsilon && bDot >= normalCoplanarityEpsilon) + // { + // AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceA, pool); + // AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceB, pool); + // } + // else + // { + // //If both aren't merge candidates, then just pick the one that's closer. + // AddIfNotPresent(ref facesNeedingMerge, aDot > bDot ? edgeFaces.FaceA : edgeFaces.FaceB, pool); + // } + // } + // else + // { + // //Only one face already present. Check if it's coplanar. + // if (aDot >= normalCoplanarityEpsilon) + // AddIfNotPresent(ref facesNeedingMerge, edgeFaces.FaceA, pool); + // } + // } + // } - //Retain a count of the deleted faces so the post process can handle them more easily. - facesDeletedCount += facesNeedingMerge.Count; + // if (facesNeedingMerge.Count > previousFaceMergeCount) + // { + // //This iteration has found more faces which should be merged together. + // //Accumulate the vertices. + // for (int i = previousFaceMergeCount; i < facesNeedingMerge.Count; ++i) + // { + // ref var sourceFace = ref faces[facesNeedingMerge[i]]; + // for (int j = 0; j < sourceFace.VertexIndices.Count; ++j) + // { + // var vertexIndex = sourceFace.VertexIndices[j]; + // if (allowVertices[vertexIndex] != 0 && !rawFaceVertexIndices.Contains(vertexIndex)) + // { + // rawFaceVertexIndices.Allocate(pool) = vertexIndex; + // } + // } + // } - AddFace(ref faces, pool, faceNormal, reducedFaceIndices); - AddFaceToEdgesAndTestList(pool, ref reducedFaceIndices, ref edgesToTest, ref facesForEdges, faceNormal, faceCountPriorToAdd); - //steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, facesNeedingMerge, faceCountPriorToAdd)); - break; - } + // //Re-reduce. + // reducedFaceIndices.Count = 0; + // facePoints.Count = 0; + // ReduceFace(ref rawFaceVertexIndices, faceNormal, points, planeEpsilonNarrow, ref facePoints, ref allowVertices, ref reducedFaceIndices); + // } + // else + // { + // //No more faces need to be merged! Go ahead and apply the changes. + // for (int i = 0; i < facesNeedingMerge.Count; ++i) + // { + // //Note that we do not merge into an existing face; for simplicity, we just merge into the face we're going to append and mark the old faces as being deleted. + // //Note that we do *not* remove deleted faces from the faces list. That would break all the face indices that we've accumulated. + // var faceIndex = facesNeedingMerge[i]; + // ref var faceToRemove = ref faces[faceIndex]; + // faceToRemove.Deleted = true; + + // //For every edge of any face we're merging, remove the face from the edge lists. + // var previousIndex = faceToRemove.VertexIndices[faceToRemove.VertexIndices.Count - 1]; + // for (int j = 0; j < faceToRemove.VertexIndices.Count; ++j) + // { + // RemoveFaceFromEdge(previousIndex, faceToRemove.VertexIndices[j], faceIndex, ref facesForEdges); + // previousIndex = faceToRemove.VertexIndices[j]; + // } + // //Remove any references to this face in the edges to test. + // int removedEdgesToTestCount = 0; + // for (int j = 0; j < edgesToTest.Count; ++j) + // { + // if (edgesToTest[j].FaceIndex == faceIndex) + // { + // ++removedEdgesToTestCount; + // } + // else if (removedEdgesToTestCount > 0) + // { + // //Scoot edges to test back. + // edgesToTest[j - removedEdgesToTestCount] = edgesToTest[j]; + // } + // } + // edgesToTest.Count -= removedEdgesToTestCount; + // } + + // //Retain a count of the deleted faces so the post process can handle them more easily. + // facesDeletedCount += facesNeedingMerge.Count; + + // AddFace(ref faces, pool, faceNormal, reducedFaceIndices); + // AddFaceToEdgesAndTestList(pool, ref reducedFaceIndices, ref edgesToTest, ref facesForEdges, faceNormal, faceCountPriorToAdd); + // steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, facesNeedingMerge, faceCountPriorToAdd)); + // break; + // } + //} + if (!mergedFace) + { + AddFace(ref faces, pool, faceNormal, reducedFaceIndices); + AddFaceEdgesToTestList(pool, ref reducedFaceIndices, ref edgesToTest, ref submittedEdgeTests, faceNormal, faceCountPriorToAdd); + steps.Add(new DebugStep(edgeToTest.Endpoints, ref rawFaceVertexIndices, faceNormal, basisX, basisY, ref reducedFaceIndices, ref allowVertices, ref faces, facesNeedingMerge, faceCountPriorToAdd)); } + + if (steps.Count > 500) + break; } edgesToTest.Dispose(pool); - facesForEdges.Dispose(pool); facePoints.Dispose(pool); reducedFaceIndices.Dispose(pool); rawFaceVertexIndices.Dispose(pool); diff --git a/Demos/DemoSet.cs b/Demos/DemoSet.cs index 67590f28..f29c8163 100644 --- a/Demos/DemoSet.cs +++ b/Demos/DemoSet.cs @@ -6,6 +6,7 @@ using Demos.Demos.Dancers; using Demos.Demos.Sponsors; using Demos.Demos.Tanks; +using Demos.SpecializedTests; using System; using System.Collections.Generic; @@ -43,6 +44,7 @@ struct Option public DemoSet() { + AddOption(); AddOption(); AddOption(); AddOption(); diff --git a/Demos/SpecializedTests/ConvexHullTestDemo.cs b/Demos/SpecializedTests/ConvexHullTestDemo.cs index 318350be..90396e2d 100644 --- a/Demos/SpecializedTests/ConvexHullTestDemo.cs +++ b/Demos/SpecializedTests/ConvexHullTestDemo.cs @@ -13,6 +13,9 @@ using BepuUtilities.Memory; using System.Text.Json; using System.IO; +using DemoRenderer.Constraints; +using DemoUtilities; +using DemoRenderer.UI; namespace Demos.SpecializedTests; @@ -32,6 +35,140 @@ Buffer CreateRandomConvexHullPoints() return points; } + + + Vector3[] CreateBwaa() + { + var points = new Vector3[] + { + new(-0.637357891f, 0.347849399f, -0.303436399f), + new(-0.636290252f, 0.345867455f, -0.301366687f), + new(-0.992014945f, 0.348357588f, -0.3031407f), + new(-1.00909662f, 0.386065364f, -0.303337872f), + new(0.637357891f, 0.347849399f, -0.303436399f), + new(-0.636290252f, 0.345918268f, 0.701366544f), + new(-0.636503756f, 0.345918268f, 0.700873733f), + new(-0.992655516f, 0.346578926f, 0.701070845f), + new(-0.992655516f, 0.346578926f, -0.301070988f), + new(0.636290252f, 0.345867455f, -0.301366687f), + new(-0.995858312f, 0.348510057f, -0.301859498f), + new(-1.01272643f, 0.385912925f, -0.302056611f), + new(-1.01037765f, 0.390029252f, -0.302746475f), + new(-0.637357891f, 0.389521062f, -0.302845061f), + new(1.00909662f, 0.386065364f, -0.303337872f), + new(0.992014945f, 0.348357588f, -0.3031407f), + new(-0.637357891f, 0.347849399f, 0.703436255f), + new(-0.992014945f, 0.348357588f, 0.703140557f), + new(0.636290252f, 0.345918268f, 0.701366544f), + new(-0.995858312f, 0.348510057f, 0.701859355f), + new(-1.02553761f, 0.351406753f, 0.678599536f), + new(-1.0251106f, 0.35013628f, 0.675938487f), + new(-1.0251106f, 0.35013628f, -0.2759386f), + new(-1.02553761f, 0.351406753f, -0.278599679f), + new(0.992655516f, 0.346578926f, -0.301070988f), + new(0.992655516f, 0.346578926f, 0.701070845f), + new(0.636503756f, 0.345918268f, 0.700873733f), + new(-1.04432738f, 0.37869662f, -0.274558783f), + new(-1.01400757f, 0.389673531f, -0.301465213f), + new(-1.04582202f, 0.382304758f, -0.273770332f), + new(-1.0582062f, 0.67344743f, -0.220745891f), + new(-1.0545764f, 0.674260557f, -0.22183004f), + new(-0.637144327f, 0.674158931f, -0.221928596f), + new(1.01037765f, 0.390029252f, -0.302746475f), + new(0.637357891f, 0.389521062f, -0.302845061f), + new(1.01272643f, 0.385912925f, -0.302056611f), + new(0.995858312f, 0.348510057f, -0.301859498f), + new(-1.00909662f, 0.386065364f, 0.703337729f), + new(0.637357891f, 0.347849399f, 0.703436255f), + new(0.992014945f, 0.348357588f, 0.703140557f), + new(-1.01272643f, 0.385912925f, 0.702056468f), + new(-1.04432738f, 0.37869662f, 0.67455864f), + new(-1.04582202f, 0.380170345f, 0.671404779f), + new(-1.04582202f, 0.380170345f, -0.271404922f), + new(1.02553761f, 0.351406753f, -0.278599679f), + new(1.0251106f, 0.35013628f, -0.2759386f), + new(1.0251106f, 0.35013628f, 0.675938487f), + new(1.02553761f, 0.351406753f, 0.678599536f), + new(0.995858312f, 0.348510057f, 0.701859355f), + new(-1.08980727f, 0.656575501f, -0.196303427f), + new(-1.09023428f, 0.656982064f, -0.193346679f), + new(-1.0584197f, 0.67675066f, -0.21867618f), + new(-1.0550034f, 0.677512944f, -0.219858885f), + new(-1.09023428f, 0.659827888f, -0.194233686f), + new(0.637144327f, 0.674158931f, -0.221928596f), + new(1.01400757f, 0.389673531f, -0.301465213f), + new(1.0545764f, 0.674260557f, -0.22183004f), + new(1.0582062f, 0.67344743f, -0.220745891f), + new(1.04582202f, 0.382304758f, -0.273770332f), + new(1.04432738f, 0.37869662f, -0.274558783f), + new(-0.637357891f, 0.389521062f, 0.702844918f), + new(-1.01037765f, 0.390029252f, 0.702746332f), + new(1.00909662f, 0.386065364f, 0.703337729f), + new(-1.01400757f, 0.389673531f, 0.70146507f), + new(-1.04582202f, 0.382304758f, 0.673770189f), + new(-1.09023428f, 0.656982064f, 0.593346536f), + new(1.04582202f, 0.380170345f, -0.271404922f), + new(1.04582202f, 0.380170345f, 0.671404779f), + new(1.04432738f, 0.37869662f, 0.67455864f), + new(1.01272643f, 0.385912925f, 0.702056468f), + new(-1.09066129f, 0.832155526f, 0.199999928f), + new(-1.0584197f, 0.86234206f, -0.0161386579f), + new(-1.0550034f, 0.863663316f, -0.0167300105f), + new(1.0550034f, 0.677512944f, -0.219858885f), + new(-1.09023428f, 0.833781719f, -0.00135488808f), + new(1.08980727f, 0.656575501f, -0.196303427f), + new(1.0584197f, 0.67675066f, -0.21867618f), + new(1.09023428f, 0.659827888f, -0.194233686f), + new(1.09023428f, 0.656982064f, -0.193346679f), + new(0.637357891f, 0.389521062f, 0.702844918f), + new(1.01037765f, 0.390029252f, 0.702746332f), + new(-0.637144327f, 0.674158931f, 0.621928453f), + new(-1.0545764f, 0.674260557f, 0.621829867f), + new(-1.0582062f, 0.67344743f, 0.620745778f), + new(-1.08980727f, 0.656575501f, 0.596303284f), + new(-1.09023428f, 0.659827888f, 0.594233513f), + new(1.04582202f, 0.382304758f, 0.673770189f), + new(1.09023428f, 0.656982064f, 0.593346536f), + new(1.01400757f, 0.389673531f, 0.70146507f), + new(-1.09044778f, 0.834950566f, 0.199999928f), + new(-1.09023428f, 0.833781719f, 0.40135473f), + new(-1.0584197f, 0.863663316f, -0.0124919862f), + new(-1.0550034f, 0.865035474f, -0.0132804662f), + new(1.0550034f, 0.863663316f, -0.0167300105f), + new(-1.09002078f, 0.835204661f, 0.00219321251f), + new(1.0584197f, 0.86234206f, -0.0161386579f), + new(1.09023428f, 0.833781719f, -0.00135488808f), + new(1.09066129f, 0.832155526f, 0.199999928f), + new(1.0582062f, 0.67344743f, 0.620745778f), + new(1.0545764f, 0.674260557f, 0.621829867f), + new(0.637144327f, 0.674158931f, 0.621928453f), + new(-1.0550034f, 0.677512944f, 0.619858742f), + new(-1.0584197f, 0.67675066f, 0.618676066f), + new(-1.0584197f, 0.86234206f, 0.41613853f), + new(1.08980727f, 0.656575501f, 0.596303284f), + new(1.09023428f, 0.659827888f, 0.594233513f), + new(-1.09002078f, 0.835204661f, 0.397806644f), + new(-1.05863321f, 0.863612533f, 0.199999928f), + new(-1.0584197f, 0.863663316f, 0.412491858f), + new(-1.0550034f, 0.865035474f, 0.413280308f), + new(1.0550034f, 0.865035474f, -0.0132804662f), + new(1.0584197f, 0.863663316f, -0.0124919862f), + new(1.09002078f, 0.835204661f, 0.00219321251f), + new(1.09044778f, 0.834950566f, 0.199999928f), + new(1.09023428f, 0.833781719f, 0.40135473f), + new(1.0584197f, 0.67675066f, 0.618676066f), + new(1.0550034f, 0.677512944f, 0.619858742f), + new(-1.0550034f, 0.863663316f, 0.416729867f), + new(1.0584197f, 0.86234206f, 0.41613853f), + new(1.0550034f, 0.865035474f, 0.413280308f), + new(1.05863321f, 0.863612533f, 0.199999928f), + new(1.09002078f, 0.835204661f, 0.397806644f), + new(1.0584197f, 0.863663316f, 0.412491858f), + new(1.0550034f, 0.8636633f, 0.41672987f), + }; + return points; + } + Buffer CreatePlaneish() { var points = new Buffer(12, BufferPool); @@ -385,7 +522,7 @@ public override void Initialize(ContentArchive content, Camera camera) camera.Yaw = 0; camera.Pitch = 0; - Simulation = Simulation.Create(BufferPool, new DemoNarrowPhaseCallbacks(new SpringSettings(30, 1)), new DemoPoseIntegratorCallbacks(new Vector3(0, -10, 0)), new SolveDescription(8, 1)); + Simulation = Simulation.Create(BufferPool, new DemoNarrowPhaseCallbacks(new SpringSettings(30, 1)), new DemoPoseIntegratorCallbacks(new Vector3(0, -0, 0)), new SolveDescription(8, 1)); //var hullPoints = CreateJSONSourcedConvexHull(@"Content/testHull.json"); //for (int i = 0; i < hullPoints.Length; ++i) //{ @@ -393,7 +530,8 @@ public override void Initialize(ContentArchive content, Camera camera) //} //var hullPoints = CreateRandomConvexHullPoints(); //var hullPoints = CreateMeshConvexHull(content.Load(@"Content\newt.obj"), new Vector3(1, 1.5f, 1f)); - var hullPoints = CreateHellCube(200); + //var hullPoints = CreateHellCube(200); + var hullPoints = CreateBwaa(); //var hullPoints = CreatePlaneish(); //var hullPoints = CreateDistantPlane(); //var hullPoints = CreateTestConvexHull(); @@ -419,52 +557,52 @@ public override void Initialize(ContentArchive content, Camera camera) //} //Console.WriteLine($"Largest error: {largestError}"); - //ConvexHullHelper.ComputeHull(hullPoints, BufferPool, out var hullData, out debugSteps); - //this.points = hullPoints; + ConvexHullHelper.ComputeHull(hullPoints, BufferPool, out var hullData, out debugSteps); + this.points = hullPoints; var boxHullPoints = CreateBoxConvexHull(2); var boxHullShape = new ConvexHull(boxHullPoints, BufferPool, out _); - Matrix3x3.CreateScale(new Vector3(5, 0.5f, 3), out var scale); - var transform = Matrix3x3.CreateFromAxisAngle(Vector3.Normalize(new Vector3(3, 2, 1)), 1207) * scale; - const int transformCount = 10000; - var transformStart = Stopwatch.GetTimestamp(); - for (int i = 0; i < transformCount; ++i) - { - CreateTransformedCopy(hullShape, transform, BufferPool, out var transformedHullShape); - transformedHullShape.Dispose(BufferPool); - } - var transformEnd = Stopwatch.GetTimestamp(); - Console.WriteLine($"Transform hull computation time (us): {(transformEnd - transformStart) * 1e6 / (transformCount * Stopwatch.Frequency)}"); + //Matrix3x3.CreateScale(new Vector3(5, 0.5f, 3), out var scale); + //var transform = Matrix3x3.CreateFromAxisAngle(Vector3.Normalize(new Vector3(3, 2, 1)), 1207) * scale; + //const int transformCount = 10000; + //var transformStart = Stopwatch.GetTimestamp(); + //for (int i = 0; i < transformCount; ++i) + //{ + // CreateTransformedCopy(hullShape, transform, BufferPool, out var transformedHullShape); + // transformedHullShape.Dispose(BufferPool); + //} + //var transformEnd = Stopwatch.GetTimestamp(); + //Console.WriteLine($"Transform hull computation time (us): {(transformEnd - transformStart) * 1e6 / (transformCount * Stopwatch.Frequency)}"); - hullShape.RayTest(RigidPose.Identity, new Vector3(0, 1, 0), -Vector3.UnitY, out var t, out var normal); + //hullShape.RayTest(RigidPose.Identity, new Vector3(0, 1, 0), -Vector3.UnitY, out var t, out var normal); - const int rayIterationCount = 10000; - var rayPose = RigidPose.Identity; - var rayOrigin = new Vector3(0, 2, 0); - var rayDirection = new Vector3(0, -1, 0); + //const int rayIterationCount = 10000; + //var rayPose = RigidPose.Identity; + //var rayOrigin = new Vector3(0, 2, 0); + //var rayDirection = new Vector3(0, -1, 0); - int hitCounter = 0; - var start = Stopwatch.GetTimestamp(); - for (int i = 0; i < rayIterationCount; ++i) - { - if (hullShape.RayTest(rayPose, rayOrigin, rayDirection, out _, out _)) - { - ++hitCounter; - } - } - var end = Stopwatch.GetTimestamp(); - Console.WriteLine($"Hit counter: {hitCounter}, computation time (us): {(end - start) * 1e6 / (rayIterationCount * Stopwatch.Frequency)}"); + //int hitCounter = 0; + //var start = Stopwatch.GetTimestamp(); + //for (int i = 0; i < rayIterationCount; ++i) + //{ + // if (hullShape.RayTest(rayPose, rayOrigin, rayDirection, out _, out _)) + // { + // ++hitCounter; + // } + //} + //var end = Stopwatch.GetTimestamp(); + //Console.WriteLine($"Hit counter: {hitCounter}, computation time (us): {(end - start) * 1e6 / (rayIterationCount * Stopwatch.Frequency)}"); - const int iterationCount = 100; - start = Stopwatch.GetTimestamp(); - for (int i = 0; i < iterationCount; ++i) - { - CreateShape(hullPoints, BufferPool, out _, out var perfTestShape); - perfTestShape.Dispose(BufferPool); - } - end = Stopwatch.GetTimestamp(); - Console.WriteLine($"Hull computation time (us): {(end - start) * 1e6 / (iterationCount * Stopwatch.Frequency)}"); + //const int iterationCount = 100; + //start = Stopwatch.GetTimestamp(); + //for (int i = 0; i < iterationCount; ++i) + //{ + // CreateShape(hullPoints, BufferPool, out _, out var perfTestShape); + // perfTestShape.Dispose(BufferPool); + //} + //end = Stopwatch.GetTimestamp(); + //Console.WriteLine($"Hull computation time (us): {(end - start) * 1e6 / (iterationCount * Stopwatch.Frequency)}"); var hullShapeIndex = Simulation.Shapes.Add(hullShape); var boxHullShapeIndex = Simulation.Shapes.Add(boxHullShape); @@ -528,132 +666,158 @@ void TestConvexHullCreation() } //Buffer points; - //List debugSteps; - - //int stepIndex = 0; - - //public override void Update(Window window, Camera camera, Input input, float dt) - //{ - // if (input.TypedCharacters.Contains('x')) - // { - // stepIndex = Math.Max(stepIndex - 1, 0); - // } - // if (input.TypedCharacters.Contains('c')) - // { - // stepIndex = Math.Min(stepIndex + 1, debugSteps.Count - 1); - // } - // if (input.WasPushed(OpenTK.Input.Key.P)) - // { - // showWireframe = !showWireframe; - // } - // if (input.WasPushed(OpenTK.Input.Key.U)) - // { - // showDeleted = !showDeleted; - // } - // if (input.WasPushed(OpenTK.Input.Key.Y)) - // { - // showVertexIndices = !showVertexIndices; - // } - // if (input.WasPushed(OpenTK.Input.Key.H)) - // { - // showFaceVertexStatuses = !showFaceVertexStatuses; - // } - // base.Update(window, camera, input, dt); - //} - - //bool showWireframe; - //bool showDeleted; - //bool showVertexIndices; - //bool showFaceVertexStatuses = true; - //public override void Render(Renderer renderer, Camera camera, Input input, TextBuilder text, Font font) - //{ - // var step = debugSteps[stepIndex]; - // var scale = 5f; - // var renderOffset = new Vector3(-15, 25, 0); - // for (int i = 0; i < points.Length; ++i) - // { - // var pose = new RigidPose(renderOffset + points[i] * scale); - // renderer.Shapes.AddShape(new Box(0.1f, 0.1f, 0.1f), Simulation.Shapes, pose, new Vector3(0.5f, 0.5f, 0.5f)); - // if (!step.AllowVertex[i] && showFaceVertexStatuses) - // renderer.Shapes.AddShape(new Box(0.6f, 0.25f, 0.25f), Simulation.Shapes, pose, new Vector3(1, 0, 0)); - // } - // if (showFaceVertexStatuses) - // { - // for (int i = 0; i < step.Raw.Count; ++i) - // { - // var pose = new RigidPose(renderOffset + points[step.Raw[i]] * scale); - // renderer.Shapes.AddShape(new Box(0.25f, 0.6f, 0.25f), Simulation.Shapes, pose, new Vector3(0, 0, 1)); - // } - // for (int i = 0; i < step.Reduced.Count; ++i) - // { - // var pose = new RigidPose(renderOffset + points[step.Reduced[i]] * scale); - // renderer.Shapes.AddShape(new Box(0.25f, 0.25f, 0.6f), Simulation.Shapes, pose, new Vector3(0, 1, 0)); - // } - // } - - // { - // var pose = new RigidPose(renderOffset); - // for (int i = 0; i < step.FaceStarts.Count; ++i) - // { - // if (showDeleted || !step.FaceDeleted[i]) - // { - // var faceStart = step.FaceStarts[i]; - // var faceEnd = i + 1 < step.FaceStarts.Count ? step.FaceStarts[i + 1] : step.FaceIndices.Count; - // var count = faceEnd - faceStart; - // var color = step.FaceDeleted[i] ? new Vector3(0.25f, 0.25f, 0.25f) : step.FaceIndex == i ? new Vector3(1, 0, 0.5f) : new Vector3(1, 0, 1); - // var deletionInducedScale = step.FaceDeleted[i] ? new Vector3(1.1f) : new Vector3(1f); - - // var offset = step.FaceDeleted[i] ? step.FaceNormals[i] * 0.25f : new Vector3(); - // if (showWireframe) - // { - // var previousIndex = faceEnd - 1; - // for (int q = faceStart; q < faceEnd; ++q) - // { - // var a = points[step.FaceIndices[q]] * scale + pose.Position + offset; - // var b = points[step.FaceIndices[previousIndex]] * scale + pose.Position + offset; - // previousIndex = q; - // renderer.Lines.Allocate() = new LineInstance(a, b, color, Vector3.Zero); - // } - // } - // else - // { - // for (int k = faceStart + 2; k < faceEnd; ++k) - // { - // renderer.Shapes.AddShape(new Triangle - // { - // A = points[step.FaceIndices[faceStart]] * scale + offset, - // B = points[step.FaceIndices[k]] * scale + offset, - // C = points[step.FaceIndices[k - 1]] * scale + offset - // }, Simulation.Shapes, pose, color); - // } - // } - // } - // } - // } - - // if (showVertexIndices) - // { - // for (int i = 0; i < points.Length; ++i) - // { - // if (DemoRenderer.Helpers.GetScreenLocation(points[i] * scale + renderOffset, camera.ViewProjection, renderer.Surface.Resolution, out var location)) - // { - // renderer.TextBatcher.Write(text.Clear().Append(i), location, 10, new Vector3(1), font); - // } - // } - // } - - // var edgeMidpoint = renderOffset + (points[step.SourceEdge.A] + points[step.SourceEdge.B]) * scale * 0.5f; - // renderer.Lines.Allocate() = new LineInstance(edgeMidpoint, edgeMidpoint + step.BasisX * scale * 0.5f, new Vector3(1, 1, 0), new Vector3()); - // renderer.Lines.Allocate() = new LineInstance(edgeMidpoint, edgeMidpoint + step.BasisY * scale * 0.5f, new Vector3(0, 1, 0), new Vector3()); - // renderer.TextBatcher.Write( - // text.Clear().Append($"Enumerate step with X and C. Current step: ").Append(stepIndex + 1).Append(" out of ").Append(debugSteps.Count), - // new Vector2(32, renderer.Surface.Resolution.Y - 140), 20, new Vector3(1), font); - // renderer.TextBatcher.Write(text.Clear().Append("Show wireframe: P ").Append(showWireframe ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 120), 20, new Vector3(1), font); - // renderer.TextBatcher.Write(text.Clear().Append("Show deleted: U ").Append(showDeleted ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 100), 20, new Vector3(1), font); - // renderer.TextBatcher.Write(text.Clear().Append("Show vertex indices: Y ").Append(showVertexIndices ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 80), 20, new Vector3(1), font); - // renderer.TextBatcher.Write(text.Clear().Append("Show face vertex statuses: H ").Append(showFaceVertexStatuses ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 60), 20, new Vector3(1), font); - - - // base.Render(renderer, camera, input, text, font); - //} + Vector3[] points; + List debugSteps; + + int stepIndex = 0; + + public override void Update(Window window, Camera camera, Input input, float dt) + { + if (input.TypedCharacters.Contains('x')) + { + stepIndex = Math.Max(stepIndex - 1, 0); + } + if (input.TypedCharacters.Contains('c')) + { + stepIndex = Math.Min(stepIndex + 1, debugSteps.Count - 1); + } + if (input.WasPushed(OpenTK.Input.Key.P)) + { + showWireframe = !showWireframe; + } + if (input.WasPushed(OpenTK.Input.Key.U)) + { + showDeleted = !showDeleted; + } + if (input.WasPushed(OpenTK.Input.Key.Y)) + { + showVertexIndices = !showVertexIndices; + } + if (input.WasPushed(OpenTK.Input.Key.H)) + { + showFaceVertexStatuses = !showFaceVertexStatuses; + } + base.Update(window, camera, input, dt); + } + + bool showWireframe; + bool showDeleted; + bool showVertexIndices; + bool showFaceVertexStatuses = true; + public override void Render(Renderer renderer, Camera camera, Input input, TextBuilder text, Font font) + { + var step = debugSteps[stepIndex]; + var scale = 15f; + var renderOffset = new Vector3(-15, 25, 0); + + void DrawVertexIndex(int i, Vector3 color, Vector2 offset = default) + { + if (DemoRenderer.Helpers.GetScreenLocation(points[i] * scale + renderOffset, camera.ViewProjection, renderer.Surface.Resolution, out var location)) + { + float fontSize = 10; + float spacing = 12; + renderer.TextBatcher.Write(text.Clear().Append(i), location + offset * spacing, fontSize, color, font); + } + } + + for (int i = 0; i < points.Length; ++i) + { + var pose = new RigidPose(renderOffset + points[i] * scale); + renderer.Shapes.AddShape(new Box(0.1f, 0.1f, 0.1f), Simulation.Shapes, pose, new Vector3(0.5f, 0.5f, 0.5f)); + if (!step.AllowVertex[i] && showFaceVertexStatuses) + { + var color = new Vector3(1, 0, 0); + renderer.Shapes.AddShape(new Box(0.6f, 0.25f, 0.25f), Simulation.Shapes, pose, color); + if (showVertexIndices) + DrawVertexIndex(i, color, new Vector2(0, 1)); + } + } + if (showFaceVertexStatuses) + { + for (int i = 0; i < step.Raw.Count; ++i) + { + var color = new Vector3(0.3f, 0.3f, 1); + var pose = new RigidPose(renderOffset + points[step.Raw[i]] * scale); + renderer.Shapes.AddShape(new Box(0.25f, 0.6f, 0.25f), Simulation.Shapes, pose, color); + if (showVertexIndices) + DrawVertexIndex(step.Raw[i], color, new Vector2(0, 2)); + } + for (int i = 0; i < step.Reduced.Count; ++i) + { + var color = new Vector3(0, 1, 0); + var pose = new RigidPose(renderOffset + points[step.Reduced[i]] * scale); + renderer.Shapes.AddShape(new Box(0.25f, 0.25f, 0.6f), Simulation.Shapes, pose, color); + if (showVertexIndices) + DrawVertexIndex(step.Reduced[i], color, new Vector2(0, 3)); + } + } + + { + var pose = new RigidPose(renderOffset); + for (int i = 0; i < step.FaceStarts.Count; ++i) + { + if (showDeleted || !step.FaceDeleted[i]) + { + var faceStart = step.FaceStarts[i]; + var faceEnd = i + 1 < step.FaceStarts.Count ? step.FaceStarts[i + 1] : step.FaceIndices.Count; + var count = faceEnd - faceStart; + var color = step.FaceDeleted[i] ? new Vector3(0.25f, 0.25f, 0.25f) : step.FaceIndex == i ? new Vector3(1, 0, 0.5f) : new Vector3(1, 0, 1); + var deletionInducedScale = step.FaceDeleted[i] ? new Vector3(1.1f) : new Vector3(1f); + + var offset = step.FaceDeleted[i] ? step.FaceNormals[i] * 0.25f : new Vector3(); + if (showWireframe) + { + var previousIndex = faceEnd - 1; + for (int q = faceStart; q < faceEnd; ++q) + { + var a = points[step.FaceIndices[q]] * scale + pose.Position + offset; + var b = points[step.FaceIndices[previousIndex]] * scale + pose.Position + offset; + previousIndex = q; + renderer.Lines.Allocate() = new LineInstance(a, b, color, Vector3.Zero); + } + } + else + { + for (int k = faceStart + 2; k < faceEnd; ++k) + { + renderer.Shapes.AddShape(new Triangle + { + A = points[step.FaceIndices[faceStart]] * scale + offset, + B = points[step.FaceIndices[k]] * scale + offset, + C = points[step.FaceIndices[k - 1]] * scale + offset + }, Simulation.Shapes, pose, color); + } + } + } + } + } + //Console.WriteLine("Current step edges: "); + //for (int i = 0; i < step.Reduced.Count; ++i) + //{ + // Console.WriteLine($" Edge {i}: ({step.Reduced[i]}, {step.Reduced[(i + 1) % step.Reduced.Count]})"); + //} + + + if (showVertexIndices) + { + for (int i = 0; i < points.Length; ++i) + { + DrawVertexIndex(i, Vector3.One); + } + } + + var edgeMidpoint = renderOffset + (points[step.SourceEdge.A] + points[step.SourceEdge.B]) * scale * 0.5f; + renderer.Lines.Allocate() = new LineInstance(edgeMidpoint, edgeMidpoint + step.BasisX * scale * 0.5f, new Vector3(1, 1, 0), new Vector3()); + renderer.Lines.Allocate() = new LineInstance(edgeMidpoint, edgeMidpoint + step.BasisY * scale * 0.5f, new Vector3(0, 1, 0), new Vector3()); + renderer.TextBatcher.Write( + text.Clear().Append($"Enumerate step with X and C. Current step: ").Append(stepIndex + 1).Append(" out of ").Append(debugSteps.Count), + new Vector2(32, renderer.Surface.Resolution.Y - 140), 20, new Vector3(1), font); + renderer.TextBatcher.Write(text.Clear().Append("Show wireframe: P ").Append(showWireframe ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 120), 20, new Vector3(1), font); + renderer.TextBatcher.Write(text.Clear().Append("Show deleted: U ").Append(showDeleted ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 100), 20, new Vector3(1), font); + renderer.TextBatcher.Write(text.Clear().Append("Show vertex indices: Y ").Append(showVertexIndices ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 80), 20, new Vector3(1), font); + renderer.TextBatcher.Write(text.Clear().Append("Show face vertex statuses: H ").Append(showFaceVertexStatuses ? "(on)" : "(off)"), new Vector2(32, renderer.Surface.Resolution.Y - 60), 20, new Vector3(1), font); + + + base.Render(renderer, camera, input, text, font); + } }