123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693 |
- // -----------------------------------------------------------------------
- // <copyright file="BoundedVoronoi.cs" company="">
- // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
- // </copyright>
- // -----------------------------------------------------------------------
- namespace UnityEngine.U2D.Animation.TriangleNet
- .Voronoi.Legacy
- {
- using System;
- using System.Collections.Generic;
- using Animation.TriangleNet.Topology;
- using Animation.TriangleNet.Geometry;
- /// <summary>
- /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation.
- /// </summary>
- /// <remarks>
- /// 2D Centroidal Voronoi Tessellations with Constraints, 2010,
- /// Jane Tournois, Pierre Alliez and Olivier Devillers
- /// </remarks>
- [Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")]
- internal class BoundedVoronoiLegacy : IVoronoi
- {
- IPredicates predicates = RobustPredicates.Default;
- Mesh mesh;
- Point[] points;
- List<VoronoiRegion> regions;
- // Used for new points on segments.
- List<Point> segPoints;
- int segIndex;
- Dictionary<int, SubSegment> subsegMap;
- bool includeBoundary = true;
- /// <summary>
- /// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
- /// </summary>
- /// <param name="mesh">Mesh instance.</param>
- public BoundedVoronoiLegacy(Mesh mesh)
- : this(mesh, true)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
- /// </summary>
- /// <param name="mesh">Mesh instance.</param>
- public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary)
- {
- this.mesh = mesh;
- this.includeBoundary = includeBoundary;
- Generate();
- }
- /// <summary>
- /// Gets the list of Voronoi vertices.
- /// </summary>
- public Point[] Points
- {
- get { return points; }
- }
- /// <summary>
- /// Gets the list of Voronoi regions.
- /// </summary>
- public ICollection<VoronoiRegion> Regions
- {
- get { return regions; }
- }
- public IEnumerable<IEdge> Edges
- {
- get { return EnumerateEdges(); }
- }
- /// <summary>
- /// Computes the bounded voronoi diagram.
- /// </summary>
- private void Generate()
- {
- mesh.Renumber();
- mesh.MakeVertexMap();
- // Allocate space for voronoi diagram
- this.regions = new List<VoronoiRegion>(mesh.vertices.Count);
- this.points = new Point[mesh.triangles.Count];
- this.segPoints = new List<Point>(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<Point>(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;
- }
- }
- /// <summary>
- /// Tag all blind triangles.
- /// </summary>
- /// <remarks>
- /// A triangle is said to be blind if the triangle and its circumcenter
- /// lie on two different sides of a constrained edge.
- /// </remarks>
- private void TagBlindTriangles()
- {
- int blinded = 0;
- Stack<Triangle> triangles;
- subsegMap = new Dictionary<int, SubSegment>();
- 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<Triangle>();
- // 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;
- }
- /// <summary>
- /// Check if given triangle is blinded by given segment.
- /// </summary>
- /// <param name="tri">Triangle.</param>
- /// <param name="seg">Segments</param>
- /// <returns>Returns true, if the triangle is blinded.</returns>
- 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<Point> vpoints = new List<Point>();
- // 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<Point> vpoints = new List<Point>();
- // 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);
- }
- /// <summary>
- /// Determines the intersection point of the line segment defined by points A and B with the
- /// line segment defined by points C and D.
- /// </summary>
- /// <param name="seg">The first segment AB.</param>
- /// <param name="pc">Endpoint C of second segment.</param>
- /// <param name="pd">Endpoint D of second segment.</param>
- /// <param name="p">Reference to the intersection point.</param>
- /// <param name="strictIntersect">If false, pa and pb represent a line.</param>
- /// <returns>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.
- /// </returns>
- 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<IEdge> EnumerateEdges()
- {
- // Copy edges
- Point first, last;
- var edges = new List<IEdge>(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;
- }
- }
- }
|