/// <summary> /// Get convex hull for 2D point cloud. /// Returns null if less than 2 points provided. /// Returns two edges with surface area of 0 if exactly two points provided. /// </summary> /// <param name="points">Point cloud, list of Vector2 elements.</param> /// <returns>List of Edges defining the convex hull. Edges are not sorted but will have normals pointing away from the convex hull.</returns> public static List <Edge> GetHull(List <Vector2> points) { ReViewFeedManager debug = ReViewFeedManager.Instance; if (points.Count < 2) { // Not enough points to create a convex hull -> return null return(null); } // Final list of convex hull edges List <Edge> edges = new List <Edge>(); List <int> pointIndicesToProcess = new List <int>(); // Find min and max vertex from X axis int minIndex = -1; int maxIndex = -1; double maxX = -Double.MaxValue; double minX = Double.MaxValue; for (int i = 0; i < points.Count; i++) { pointIndicesToProcess.Add(i); if (points[i].X > maxX) { maxX = points[i].X; maxIndex = i; } if (points[i].X < minX) { minX = points[i].X; minIndex = i; } } // Random points -> Introduces a bug /* * minIndex = SRandom.Index(points.Count); * maxIndex = SRandom.Index(points.Count); * if (maxIndex == minIndex) * { * maxIndex = (maxIndex + 1) % points.Count; * } */ // Add initial two edges to processing list List <Edge> edgesToProcess = new List <Edge>(); edgesToProcess.Add(new Edge(minIndex, maxIndex)); edgesToProcess.Add(new Edge(maxIndex, minIndex)); pointIndicesToProcess.Remove(minIndex); pointIndicesToProcess.Remove(maxIndex); #region Debug_Region_1 // // REVIEW DEBUG RENDERING // Add boxes for vertices and for initial lines in the processing list // for (int i = 0; i < points.Count; i++) { debug.AddBox(0, -1, new Matrix4x4(), new Vector3(points[i].x, points[i].y, 0.0), new Vector3(0.005, 0.005, 0.005), new Color32(255, 255, 0, 255)); } debug.AdvanceDebugTimer(); debug.MapID(edgesToProcess[0], debug.AddLine(debug.DebugTimer, -1, points[minIndex], points[maxIndex], new Color32(200, 255, 0, 255))); debug.MapID(edgesToProcess[1], debug.AddLine(debug.DebugTimer, -1, points[maxIndex], points[minIndex], new Color32(200, 255, 0, 255))); debug.AdvanceDebugTimer(); #endregion // Iterate until no more edges to process while (edgesToProcess.Count > 0) { // Remove edge from processing list (it will either end up in the final edges list or get ignored) Edge edgeToProcess = edgesToProcess[0]; edgesToProcess.RemoveAt(0); #region Debug_Region_2 // // REVIEW DEBUG RENDERING // Remove edge being processed and add it with different color for this frame // debug.RemovePrimitive(debug.FindID(edgeToProcess), debug.DebugTimer); debug.AddLine(debug.DebugTimer, debug.DebugTimerStep, points[edgeToProcess.A], points[edgeToProcess.B], new Color32(0, 0, 0, 255)); #endregion double furthestDistance = -1.0; int candidateIndex = -1; int pointsInFrontCount = 0; for (int i = 0; i < pointIndicesToProcess.Count; i++) { int pointIndex = pointIndicesToProcess[i]; Vector2 point = points[pointIndex]; if (!edgeToProcess.Contains(pointIndex) && edgeToProcess.PointInFront(points, point)) { #region Debug_Region_3 // // REVIEW DEBUG RENDERING // Color vertex being checked to be "in front" // debug.AddBox(debug.DebugTimer, debug.DebugTimerStep, new Matrix4x4(), new Vector3(point.x, point.y, 0.0), new Vector3(0.0075, 0.0075, 0.0075), new Color32(0, 255, 0, 255)); #endregion // This point is not part of edge and it is on the "front" side of the edge -> Check distance and mark as candidate double distanceToEdge = edgeToProcess.DistanceToPoint(points, point); if (distanceToEdge > furthestDistance) { furthestDistance = distanceToEdge; candidateIndex = pointIndex; } pointsInFrontCount++; } #region Debug_Region_4 else { // // REVIEW DEBUG RENDERING // Color vertex being checked to not be "in front" // debug.AddBox(debug.DebugTimer, debug.DebugTimerStep, new Matrix4x4(), new Vector3(point.x, point.y, 0.0), new Vector3(0.0075, 0.0075, 0.0075), new Color32(255, 0, 0, 255)); } #endregion } #region Debug_Region_5 debug.AdvanceDebugTimer(); #endregion if (candidateIndex >= 0) { #region Debug_Region_6 // // REVIEW DEBUG RENDERING // Color vertex being selected as the furthest and "in front" // debug.AddBox(debug.DebugTimer, debug.DebugTimerStep, new Matrix4x4(), points[candidateIndex], new Vector3(0.0075, 0.0075, 0.0075), new Color32(0, 0, 0, 255)); #endregion if (!edgeToProcess.PointInLine(points, points[candidateIndex])) { // Remove all points inside the new triangle Triangle t = new Triangle(edgeToProcess.A, edgeToProcess.B, candidateIndex); for (int i = 0; i < pointIndicesToProcess.Count; i++) { int pointIndex = pointIndicesToProcess[i]; if (t.Inside(points, points[pointIndex])) { pointIndicesToProcess.RemoveAt(i); i--; pointsInFrontCount--; } } } if (pointsInFrontCount == 0) { // New lines are part of the convex hull -> Don't add edgesToProcess edges.Add(new Edge(edgeToProcess.A, candidateIndex)); edges.Add(new Edge(candidateIndex, edgeToProcess.B)); #region Debug_Region_7 // // REVIEW DEBUG RENDERING // Show convex hull lines added // long line_id_1 = debug.AddLine(debug.DebugTimer, -1, points[edgeToProcess.A], points[candidateIndex], new Color32(255, 255, 255, 255)); long line_id_2 = debug.AddLine(debug.DebugTimer, -1, points[candidateIndex], points[edgeToProcess.B], new Color32(255, 255, 255, 255)); debug.AddAnnotation(line_id_1, debug.DebugTimer, -1, "Hull Edge #" + (edges.Count - 1), new Color32(255, 255, 255, 255)); debug.AddAnnotation(line_id_2, debug.DebugTimer, -1, "Hull Edge #" + edges.Count, new Color32(255, 255, 255, 255)); #endregion } else { // Found point, add two new edges to process edgesToProcess.Add(new Edge(edgeToProcess.A, candidateIndex)); edgesToProcess.Add(new Edge(candidateIndex, edgeToProcess.B)); #region Debug_Region_8 // // REVIEW DEBUG RENDERING // Show new lines added to be processed // debug.MapID(edgesToProcess[edgesToProcess.Count - 2], debug.AddLine(debug.DebugTimer, -1, points[edgeToProcess.A], points[candidateIndex], new Color32(200, 255, 0, 255))); debug.MapID(edgesToProcess[edgesToProcess.Count - 1], debug.AddLine(debug.DebugTimer, -1, points[candidateIndex], points[edgeToProcess.B], new Color32(200, 255, 0, 255))); #endregion } } else { // No candidates found -> This is part of convex hull edges.Add(new Edge(edgeToProcess.A, edgeToProcess.B)); #region Debug_Region_9 long line_id = debug.AddLine(debug.DebugTimer, -1, points[edgeToProcess.A], points[edgeToProcess.B], new Color32(255, 255, 255, 255)); debug.AddAnnotation(line_id, debug.DebugTimer, -1, "Hull Edge #" + edges.Count, new Color32(255, 255, 255, 255)); #endregion } #region Debug_Region_10 debug.AdvanceDebugTimer(); #endregion } #region Debug_Region_11 debug.RemoveAllAnnotations(debug.DebugTimer); #endregion return(edges); }
/// <summary> /// Delaunay triangulate point cloud with given convex hull. /// Returns null if less than 3 points provided. /// Using Tenamura-Merriam algorithm which does 'advancing front' starting with convexHull edges. /// UNOPTIMAL IMPLEMENTATION! This will not deal with regular grids. /// </summary> /// <param name="points">Point cloud, list of Vector2 elements.</param> /// <param name="points">Edge list, convex hull for point cloud.</param> /// <returns>List of Edges defining the convex hull. Edges are not sorted but will have normals pointing away from the convex hull.</returns> public static List <Triangle> Triangulate(List <Vector2> points, List <Edge> convexHull) { if (points.Count < 3) { // Not enough points to create triangulation -> return null return(null); } #region Debug_Region_1 Vector3 LINE_ZOFFS = new Vector3(0, 0, 0.002); int triCount = 0; ReViewFeedManager debug = ReViewFeedManager.Instance; long mesh_id = debug.AddMesh(debug.DebugTimer, -1, new Matrix4x4(), new Vector3(0, 0, 0.001), true); #endregion // Final list of convex hull edges List <Triangle> triangles = new List <Triangle>(); List <Edge> edgesToProcess = new List <Edge>(); foreach (Edge edge in convexHull) { edgesToProcess.Add(edge.GetFlipped()); } while (edgesToProcess.Count > 0) { Edge edgeToProcess = edgesToProcess[0]; edgesToProcess.RemoveAt(0); int circumCircleFailCount = 0; bool triangleAdded = false; // Try to find third vertex for the edgeToProcess to form a triangle List <int> testedCandidates = new List <int>(); for (int i = 0; i < points.Count; i++) { if (edgeToProcess.Contains(i)) { // This point is part of the edge already -> Skip continue; } if (!edgeToProcess.PointInFront(points, points[i])) { // This point is not in front -> Skip continue; } if (testedCandidates.Contains(i)) { // Already processed -> Skip continue; } testedCandidates.Add(i); Triangle t = new Triangle(edgeToProcess.A, edgeToProcess.B, i); if (GeometryMath.IsTriangleDegenerate(points[t.A], points[t.B], points[t.C])) { // Degenerate triangle (no surface area so points are all in a line) -> Ignore this and continue continue; } // Check circumcircle validity for this triangle bool pointsInCircumCircle = false; for (int j = 0; j < points.Count; j++) { if (i == j || edgeToProcess.Contains(j)) { continue; } Vector2 otherPoint = points[j]; // Some other point -> Check if in circumcircle if (t.PointInCircumcircle(points, otherPoint)) { #region Debug_Region_2 // Add debug drawing for failed circumcircle tests Vector2 center; double radius; if (GeometryMath.GetCircumcircle(points[edgeToProcess.A], points[edgeToProcess.B], points[i], out center, out radius)) { Color32 debugColor = Color32.FromHSVA((double)circumCircleFailCount / 16.0 % 1.0, 0.9, 1.0, 1.0); // Circumcircle debug.AddCircle(debug.DebugTimer, debug.DebugTimerStep, (Vector3)center + LINE_ZOFFS, radius, new Vector3(0, 0, 1), 64, debugColor); // Point that was inside (fails the test) debug.AddBox(debug.DebugTimer, debug.DebugTimerStep, new Matrix4x4(new Quaternion(new Vector3(0, 0, 1), Math.PI * 0.25)), new Vector3(points[j].x, points[j].y, 0.0), new Vector3(0.015, 0.015, 0.015), debugColor); // Lines to show triangle tested debug.AddLine(debug.DebugTimer, debug.DebugTimerStep, (Vector3)points[edgeToProcess.A] + LINE_ZOFFS, (Vector3)points[edgeToProcess.B] + LINE_ZOFFS, new Color32(0, 0, 0, 255)); debug.AddLine(debug.DebugTimer, debug.DebugTimerStep, (Vector3)points[edgeToProcess.B] + LINE_ZOFFS, (Vector3)points[i] + LINE_ZOFFS, debugColor); debug.AddLine(debug.DebugTimer, debug.DebugTimerStep, (Vector3)points[i] + LINE_ZOFFS, (Vector3)points[edgeToProcess.A] + LINE_ZOFFS, debugColor); debug.AdvanceDebugTimer(); } #endregion circumCircleFailCount++; if (!testedCandidates.Contains(j)) { i = j - 1; } pointsInCircumCircle = true; break; } } if (pointsInCircumCircle) { // Points in circumcircle -> Skip continue; } // Update edgesToProcess list by adding two new edges if they are not one of the convex hull edges Edge newEdgeA = new Edge(edgeToProcess.A, i); Edge newEdgeB = new Edge(i, edgeToProcess.B); // Check if new edges would be the same as unprocessed convex-hull edges for (int j = 0; j < edgesToProcess.Count; j++) { Edge convexEdge = edgesToProcess[j]; if (convexEdge == newEdgeA) { // Don't add newEdgeA and remove convexEdge newEdgeA = null; edgesToProcess.RemoveAt(j); j--; } else if (convexEdge == newEdgeB) { // Don't add newEdgeA and remove convexEdge newEdgeB = null; edgesToProcess.RemoveAt(j); j--; } } // Check if new edges would be shared with existing triangle edges for (int j = 0; j < triangles.Count; j++) { if (triangles[j].Contains(newEdgeA)) { newEdgeA = null; } if (triangles[j].Contains(newEdgeB)) { newEdgeB = null; } if (newEdgeA == null && newEdgeB == null) { break; } } if (newEdgeA != null) { edgesToProcess.Add(newEdgeA); } if (newEdgeB != null) { edgesToProcess.Add(newEdgeB); } // Valid triangle -> Add to delaunay set triangles.Add(t); #region Debug_Region_3 // Debug graphics for triangle to be added Vector2 triCenter; double triRadius; // Add circumcircle GeometryMath.GetCircumcircle(points[t.A], points[t.B], points[t.C], out triCenter, out triRadius); debug.AddCircle(debug.DebugTimer, debug.DebugTimerStep, (Vector3)triCenter + LINE_ZOFFS, triRadius, new Vector3(0, 0, 1), 64, new Color32(255, 255, 0, 255)); // Add triangle lines debug.AddLine(debug.DebugTimer, -1, (Vector3)points[t.A] + LINE_ZOFFS, (Vector3)points[t.B] + LINE_ZOFFS, new Color32(0, 0, 0, 255)); debug.AddLine(debug.DebugTimer, -1, (Vector3)points[t.B] + LINE_ZOFFS, (Vector3)points[t.C] + LINE_ZOFFS, new Color32(0, 0, 0, 255)); debug.AddLine(debug.DebugTimer, -1, (Vector3)points[t.C] + LINE_ZOFFS, (Vector3)points[t.A] + LINE_ZOFFS, new Color32(0, 0, 0, 255)); // Add triangle debug.AddTriangle(mesh_id, debug.DebugTimer, points[t.A], points[t.B], points[t.C], new Color32(32, 255, 32, 128)); // Progress for console window triCount++; if (triCount % 100 == 0) { Console.WriteLine("TriCount {0}", triCount); } debug.AdvanceDebugTimer(); #endregion triangleAdded = true; // Processed 'edgeToProcess' -> Proceed to next in the list break; } #region Debug_Region_4 // Debug graphics for the case when no triangle could be added for given edge if (!triangleAdded) { long line_id = debug.AddLine(debug.DebugTimer, debug.DebugTimerStep, (Vector3)points[edgeToProcess.A] + LINE_ZOFFS * 2, (Vector3)points[edgeToProcess.B] + LINE_ZOFFS * 2, new Color32(255, 0, 0, 255)); debug.AddAnnotation(line_id, debug.DebugTimer, debug.DebugTimerStep, "No valid triangle could be generated!", new Color32(255, 255, 255, 255)); debug.AdvanceDebugTimer(); } #endregion } Console.WriteLine("Done triangles"); return(triangles); }