// ----------------------------------------------------------------------- // // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- namespace UnityEngine.U2D.Animation.TriangleNet .Voronoi.Legacy { using System; using System.Collections.Generic; using Animation.TriangleNet.Topology; using Animation.TriangleNet.Geometry; /// /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation. /// /// /// 2D Centroidal Voronoi Tessellations with Constraints, 2010, /// Jane Tournois, Pierre Alliez and Olivier Devillers /// [Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")] internal class BoundedVoronoiLegacy : IVoronoi { IPredicates predicates = RobustPredicates.Default; Mesh mesh; Point[] points; List regions; // Used for new points on segments. List segPoints; int segIndex; Dictionary subsegMap; bool includeBoundary = true; /// /// Initializes a new instance of the class. /// /// Mesh instance. public BoundedVoronoiLegacy(Mesh mesh) : this(mesh, true) { } /// /// Initializes a new instance of the class. /// /// Mesh instance. public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary) { this.mesh = mesh; this.includeBoundary = includeBoundary; Generate(); } /// /// Gets the list of Voronoi vertices. /// public Point[] Points { get { return points; } } /// /// Gets the list of Voronoi regions. /// public ICollection Regions { get { return regions; } } public IEnumerable Edges { get { return EnumerateEdges(); } } /// /// Computes the bounded voronoi diagram. /// private void Generate() { mesh.Renumber(); mesh.MakeVertexMap(); // Allocate space for voronoi diagram this.regions = new List(mesh.vertices.Count); this.points = new Point[mesh.triangles.Count]; this.segPoints = new List(mesh.subsegs.Count * 4); ComputeCircumCenters(); TagBlindTriangles(); foreach (var v in mesh.vertices.Values) { // TODO: Need a reliable way to check if a vertex is on a segment if (v.type == VertexType.FreeVertex || v.label == 0) { ConstructCell(v); } else if (includeBoundary) { ConstructBoundaryCell(v); } } // Add the new points on segments to the point array. int length = points.Length; Array.Resize(ref points, length + segPoints.Count); for (int i = 0; i < segPoints.Count; i++) { points[length + i] = segPoints[i]; } this.segPoints.Clear(); this.segPoints = null; } private void ComputeCircumCenters() { Otri tri = default(Otri); double xi = 0, eta = 0; Point pt; // Compue triangle circumcenters foreach (var item in mesh.triangles) { tri.tri = item; pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); pt.id = item.id; points[item.id] = pt; } } /// /// Tag all blind triangles. /// /// /// A triangle is said to be blind if the triangle and its circumcenter /// lie on two different sides of a constrained edge. /// private void TagBlindTriangles() { int blinded = 0; Stack triangles; subsegMap = new Dictionary(); Otri f = default(Otri); Otri f0 = default(Otri); Osub e = default(Osub); Osub sub1 = default(Osub); // Tag all triangles non-blind foreach (var t in mesh.triangles) { // Use the infected flag for 'blinded' attribute. t.infected = false; } // for each constrained edge e of cdt do foreach (var ss in mesh.subsegs.Values) { // Create a stack: triangles triangles = new Stack(); // for both adjacent triangles fe to e tagged non-blind do // Push fe into triangles e.seg = ss; e.orient = 0; e.Pivot(ref f); if (f.tri.id != Mesh.DUMMY && !f.tri.infected) { triangles.Push(f.tri); } e.Sym(); e.Pivot(ref f); if (f.tri.id != Mesh.DUMMY && !f.tri.infected) { triangles.Push(f.tri); } // while triangles is non-empty while (triangles.Count > 0) { // Pop f from stack triangles f.tri = triangles.Pop(); f.orient = 0; // if f is blinded by e (use P) then if (TriangleIsBlinded(ref f, ref e)) { // Tag f as blinded by e f.tri.infected = true; blinded++; // Store association triangle -> subseg subsegMap.Add(f.tri.hash, e.seg); // for each adjacent triangle f0 to f do for (f.orient = 0; f.orient < 3; f.orient++) { f.Sym(ref f0); f0.Pivot(ref sub1); // if f0 is finite and tagged non-blind & the common edge // between f and f0 is unconstrained then if (f0.tri.id != Mesh.DUMMY && !f0.tri.infected && sub1.seg.hash == Mesh.DUMMY) { // Push f0 into triangles. triangles.Push(f0.tri); } } } } } blinded = 0; } /// /// Check if given triangle is blinded by given segment. /// /// Triangle. /// Segments /// Returns true, if the triangle is blinded. private bool TriangleIsBlinded(ref Otri tri, ref Osub seg) { Point c, pt; Vertex torg = tri.Org(); Vertex tdest = tri.Dest(); Vertex tapex = tri.Apex(); Vertex sorg = seg.Org(); Vertex sdest = seg.Dest(); c = this.points[tri.tri.id]; if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true)) { return true; } if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true)) { return true; } if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true)) { return true; } return false; } private void ConstructCell(Vertex vertex) { VoronoiRegion region = new VoronoiRegion(vertex); regions.Add(region); Otri f = default(Otri); Otri f_init = default(Otri); Otri f_next = default(Otri); Osub sf = default(Osub); Osub sfn = default(Osub); Point cc_f, cc_f_next, p; int n = mesh.triangles.Count; // Call P the polygon (cell) in construction List vpoints = new List(); // Call f_init a triangle incident to x vertex.tri.Copy(ref f_init); if (f_init.Org() != vertex) { throw new Exception("ConstructCell: inconsistent topology."); } // Let f be initialized to f_init f_init.Copy(ref f); // Call f_next the next triangle counterclockwise around x f_init.Onext(ref f_next); // repeat ... until f = f_init do { // Call Lffnext the line going through the circumcenters of f and f_next cc_f = this.points[f.tri.id]; cc_f_next = this.points[f_next.tri.id]; // if f is tagged non-blind then if (!f.tri.infected) { // Insert the circumcenter of f into P vpoints.Add(cc_f); if (f_next.tri.infected) { // Call S_fnext the constrained edge blinding f_next sfn.seg = subsegMap[f_next.tri.hash]; // Insert point Lf,f_next /\ Sf_next into P if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } } else { // Call Sf the constrained edge blinding f sf.seg = subsegMap[f.tri.hash]; // if f_next is tagged non-blind then if (!f_next.tri.infected) { // Insert point Lf,f_next /\ Sf into P if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } else { // Call Sf_next the constrained edge blinding f_next sfn.seg = subsegMap[f_next.tri.hash]; // if Sf != Sf_next then if (!sf.Equal(sfn)) { // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } } } // f <- f_next f_next.Copy(ref f); // Call f_next the next triangle counterclockwise around x f_next.Onext(); } while (!f.Equals(f_init)); // Output: Bounded Voronoi cell of x in counterclockwise order. region.Add(vpoints); } private void ConstructBoundaryCell(Vertex vertex) { VoronoiRegion region = new VoronoiRegion(vertex); regions.Add(region); Otri f = default(Otri); Otri f_init = default(Otri); Otri f_next = default(Otri); Otri f_prev = default(Otri); Osub sf = default(Osub); Osub sfn = default(Osub); Vertex torg, tdest, tapex, sorg, sdest; Point cc_f, cc_f_next, p; int n = mesh.triangles.Count; // Call P the polygon (cell) in construction List vpoints = new List(); // Call f_init a triangle incident to x vertex.tri.Copy(ref f_init); if (f_init.Org() != vertex) { throw new Exception("ConstructBoundaryCell: inconsistent topology."); } // Let f be initialized to f_init f_init.Copy(ref f); // Call f_next the next triangle counterclockwise around x f_init.Onext(ref f_next); f_init.Oprev(ref f_prev); // Is the border to the left? if (f_prev.tri.id != Mesh.DUMMY) { // Go clockwise until we reach the border (or the initial triangle) while (f_prev.tri.id != Mesh.DUMMY && !f_prev.Equals(f_init)) { f_prev.Copy(ref f); f_prev.Oprev(); } f.Copy(ref f_init); f.Onext(ref f_next); } if (f_prev.tri.id == Mesh.DUMMY) { // For vertices on the domain boundaray, add the vertex. For // internal boundaries don't add it. p = new Point(vertex.x, vertex.y); p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } // Add midpoint of start triangles' edge. torg = f.Org(); tdest = f.Dest(); p = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); // repeat ... until f = f_init do { // Call Lffnext the line going through the circumcenters of f and f_next cc_f = this.points[f.tri.id]; if (f_next.tri.id == Mesh.DUMMY) { if (!f.tri.infected) { // Add last circumcenter vpoints.Add(cc_f); } // Add midpoint of last triangles' edge (chances are it has already // been added, so post process cell to remove duplicates???) torg = f.Org(); tapex = f.Apex(); p = new Point((torg.x + tapex.x) / 2, (torg.y + tapex.y) / 2); p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); break; } cc_f_next = this.points[f_next.tri.id]; // if f is tagged non-blind then if (!f.tri.infected) { // Insert the circumcenter of f into P vpoints.Add(cc_f); if (f_next.tri.infected) { // Call S_fnext the constrained edge blinding f_next sfn.seg = subsegMap[f_next.tri.hash]; // Insert point Lf,f_next /\ Sf_next into P if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } } else { // Call Sf the constrained edge blinding f sf.seg = subsegMap[f.tri.hash]; sorg = sf.Org(); sdest = sf.Dest(); // if f_next is tagged non-blind then if (!f_next.tri.infected) { tdest = f.Dest(); tapex = f.Apex(); // Both circumcenters lie on the blinded side, but we // have to add the intersection with the segment. // Center of f edge dest->apex Point bisec = new Point((tdest.x + tapex.x) / 2, (tdest.y + tapex.y) / 2); // Find intersection of seg with line through f's bisector and circumcenter if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } // Insert point Lf,f_next /\ Sf into P if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } else { // Call Sf_next the constrained edge blinding f_next sfn.seg = subsegMap[f_next.tri.hash]; // if Sf != Sf_next then if (!sf.Equal(sfn)) { // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } else { // Both circumcenters lie on the blinded side, but we // have to add the intersection with the segment. // Center of f_next edge org->dest Point bisec = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); // Find intersection of seg with line through f_next's bisector and circumcenter if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false)) { p.id = n + segIndex++; segPoints.Add(p); vpoints.Add(p); } } } } // f <- f_next f_next.Copy(ref f); // Call f_next the next triangle counterclockwise around x f_next.Onext(); } while (!f.Equals(f_init)); // Output: Bounded Voronoi cell of x in counterclockwise order. region.Add(vpoints); } /// /// Determines the intersection point of the line segment defined by points A and B with the /// line segment defined by points C and D. /// /// The first segment AB. /// Endpoint C of second segment. /// Endpoint D of second segment. /// Reference to the intersection point. /// If false, pa and pb represent a line. /// Returns true if the intersection point was found, and stores that point in X,Y. /// Returns false if there is no determinable intersection point, in which case X,Y will /// be unmodified. /// private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect) { p = null; // TODO: Voronoi SegmentsIntersect double Ax = p1.x, Ay = p1.y; double Bx = p2.x, By = p2.y; double Cx = p3.x, Cy = p3.y; double Dx = p4.x, Dy = p4.y; double distAB, theCos, theSin, newX, ABpos; // Fail if either line segment is zero-length. if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false; // Fail if the segments share an end-point. if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy || Ax == Dx && Ay == Dy || Bx == Dx && By == Dy) { return false; } // (1) Translate the system so that point A is on the origin. Bx -= Ax; By -= Ay; Cx -= Ax; Cy -= Ay; Dx -= Ax; Dy -= Ay; // Discover the length of segment A-B. distAB = Math.Sqrt(Bx * Bx + By * By); // (2) Rotate the system so that point B is on the positive X axis. theCos = Bx / distAB; theSin = By / distAB; newX = Cx * theCos + Cy * theSin; Cy = Cy * theCos - Cx * theSin; Cx = newX; newX = Dx * theCos + Dy * theSin; Dy = Dy * theCos - Dx * theSin; Dx = newX; // Fail if segment C-D doesn't cross line A-B. if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false; // (3) Discover the position of the intersection point along line A-B. ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy); // Fail if segment C-D crosses line A-B outside of segment A-B. if (ABpos < 0 || ABpos > distAB && strictIntersect) return false; // (4) Apply the discovered position to line A-B in the original coordinate system. p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin); // Success. return true; } // TODO: Voronoi enumerate edges private IEnumerable EnumerateEdges() { // Copy edges Point first, last; var edges = new List(this.Regions.Count * 2); foreach (var region in this.Regions) { first = null; last = null; foreach (var pt in region.Vertices) { if (first == null) { first = pt; last = pt; } else { edges.Add(new Edge(last.id, pt.id)); last = pt; } } if (region.Bounded && first != null) { edges.Add(new Edge(last.id, first.id)); } } return edges; } } }