/// <summary> /// Find the holes and infect them. Find the area constraints and infect /// them. Infect the convex hull. Spread the infection and kill triangles. /// Spread the area constraints. /// </summary> public void CarveHoles() { Otri searchtri = default(Otri); Vertex searchorg, searchdest; LocateResult intersect; var dummytri = mesh.dummytri; if (!mesh.behavior.Convex) { // Mark as infected any unprotected triangles on the boundary. // This is one way by which concavities are created. InfectHull(); } if (!mesh.behavior.NoHoles) { // Infect each triangle in which a hole lies. foreach (var hole in mesh.holes) { // Ignore holes that aren't within the bounds of the mesh. if (mesh.bounds.Contains(hole)) { // Start searching from some triangle on the outer boundary. searchtri.tri = dummytri; searchtri.orient = 0; searchtri.Sym(); // Ensure that the hole is to the left of this boundary edge; // otherwise, locate() will falsely report that the hole // falls within the starting triangle. searchorg = searchtri.Org(); searchdest = searchtri.Dest(); if (RobustPredicates.CounterClockwise(searchorg, searchdest, hole) > 0.0) { // Find a triangle that contains the hole. intersect = mesh.locator.Locate(hole, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Infect the triangle. This is done by marking the triangle // as infected and including the triangle in the virus pool. searchtri.Infect(); viri.Add(searchtri.tri); } } } } } if (viri.Count > 0) { // Carve the holes and concavities. Plague(); } // Free up memory (virus pool should be empty anyway). viri.Clear(); }
private SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, Vertex pa, Vertex pb, Vertex pc, double topy) { double ccwabc; double xac, yac, xbc, ybc; double aclen2, bclen2; Point searchpoint = new Point(); // TODO: mesh.nextras Otri dummytri = default(Otri); ccwabc = RobustPredicates.CounterClockwise(pa, pb, pc); xac = pa.X - pc.X; yac = pa.Y - pc.Y; xbc = pb.X - pc.X; ybc = pb.Y - pc.Y; aclen2 = xac * xac + yac * yac; bclen2 = xbc * xbc + ybc * ybc; searchpoint.X = pc.X - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); searchpoint.Y = topy; return(SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint)); }
private static Point FindPointInPolygon(List <Vertex> contour, int limit, double eps) { var bounds = new Rectangle(); bounds.Expand(contour.ConvertAll(v => new Point(v.x, v.y, v.label))); int length = contour.Count; var test = new Point(); Point a, b, c; // Current corner points. double bx, by; double dx, dy; double h; var predicates = new RobustPredicates(); a = contour[0]; b = contour[1]; for (int i = 0; i < length; i++) { c = contour[(i + 2) % length]; // Corner point. bx = b.x; by = b.y; // NOTE: if we knew the contour points were in counterclockwise order, we // could skip concave corners and search only in one direction. h = predicates.CounterClockwise(a, b, c); if (Math.Abs(h) < eps) { // Points are nearly co-linear. Use perpendicular direction. dx = (c.y - a.y) / 2; dy = (a.x - c.x) / 2; } else { // Direction [midpoint(a-c) -> corner point] dx = (a.x + c.x) / 2 - bx; dy = (a.y + c.y) / 2 - by; } // Move around the contour. a = b; b = c; h = 1.0; for (int j = 0; j < limit; j++) { // Search in direction. test.x = bx + dx * h; test.y = by + dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { return(test); } // Search in opposite direction (see NOTE above). test.x = bx - dx * h; test.y = by - dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { return(test); } h = h / 2; } } throw new Exception(); }
/// <summary> /// Test the mesh for topological consistency. /// </summary> public static bool IsConsistent(Mesh mesh) { Otri tri = default(Otri); Otri oppotri = default(Otri), oppooppotri = default(Otri); Vertex org, dest, apex; Vertex oppoorg, oppodest; var logger = Log.Instance; // Temporarily turn on exact arithmetic if it's off. bool saveexact = Behavior.NoExact; Behavior.NoExact = false; int horrors = 0; // Run through the list of triangles, checking each one. foreach (var t in mesh.triangles) { tri.tri = t; // Check all three edges of the triangle. for (tri.orient = 0; tri.orient < 3; tri.orient++) { org = tri.Org(); dest = tri.Dest(); if (tri.orient == 0) { // Only test for inversion once. // Test if the triangle is flat or inverted. apex = tri.Apex(); if (predicates.CounterClockwise(org, dest, apex) <= 0.0) { if (Log.Verbose) { logger.Warning(String.Format("Triangle is flat or inverted (ID {0}).", t.id), "MeshValidator.IsConsistent()"); } horrors++; } } // Find the neighboring triangle on this edge. tri.Sym(ref oppotri); if (oppotri.tri.id != Mesh.DUMMY) { // Check that the triangle's neighbor knows it's a neighbor. oppotri.Sym(ref oppooppotri); if ((tri.tri != oppooppotri.tri) || (tri.orient != oppooppotri.orient)) { if (tri.tri == oppooppotri.tri && Log.Verbose) { logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)", "MeshValidator.IsConsistent()"); } horrors++; } // Check that both triangles agree on the identities // of their shared vertices. oppoorg = oppotri.Org(); oppodest = oppotri.Dest(); if ((org != oppodest) || (dest != oppoorg)) { if (Log.Verbose) { logger.Warning("Mismatched edge coordinates between two triangles.", "MeshValidator.IsConsistent()"); } horrors++; } } } } // Check for unconnected vertices mesh.MakeVertexMap(); foreach (var v in mesh.vertices.Values) { if (v.tri.tri == null && Log.Verbose) { logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", "MeshValidator.IsConsistent()"); } } // Restore the status of exact arithmetic. Behavior.NoExact = saveexact; return(horrors == 0); }
/// <summary> /// Force a segment into a constrained Delaunay triangulation by deleting the /// triangles it intersects, and triangulating the polygons that form on each /// side of it. /// </summary> /// <param name="starttri"></param> /// <param name="endpoint2"></param> /// <param name="newmark"></param> /// <remarks> /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the /// boundary marker of the segment. /// To insert a segment, every triangle whose interior intersects the /// segment is deleted. The union of these deleted triangles is a polygon /// (which is not necessarily monotone, but is close enough), which is /// divided into two polygons by the new segment. This routine's task is /// to generate the Delaunay triangulation of these two polygons. /// You might think of this routine's behavior as a two-step process. The /// first step is to walk from endpoint1 to endpoint2, flipping each edge /// encountered. This step creates a fan of edges connected to endpoint1, /// including the desired edge to endpoint2. The second step enforces the /// Delaunay condition on each side of the segment in an incremental manner: /// proceeding along the polygon from endpoint1 to endpoint2 (this is done /// independently on each side of the segment), each vertex is "enforced" /// as if it had just been inserted, but affecting only the previous /// vertices. The result is the same as if the vertices had been inserted /// in the order they appear on the polygon, so the result is Delaunay. /// In truth, ConstrainedEdge() interleaves these two steps. The procedure /// walks from endpoint1 to endpoint2, and each time an edge is encountered /// and flipped, the newly exposed vertex (at the far end of the flipped /// edge) is "enforced" upon the previously flipped edges, usually affecting /// only one side of the polygon (depending upon which side of the segment /// the vertex falls on). /// The algorithm is complicated by the need to handle polygons that are not /// convex. Although the polygon is not necessarily monotone, it can be /// triangulated in a manner similar to the stack-based algorithms for /// monotone polygons. For each reflex vertex (local concavity) of the /// polygon, there will be an inverted triangle formed by one of the edge /// flips. (An inverted triangle is one with negative area - that is, its /// vertices are arranged in clockwise order - and is best thought of as a /// wrinkle in the fabric of the mesh.) Each inverted triangle can be /// thought of as a reflex vertex pushed on the stack, waiting to be fixed /// later. /// A reflex vertex is popped from the stack when a vertex is inserted that /// is visible to the reflex vertex. (However, if the vertex behind the /// reflex vertex is not visible to the reflex vertex, a new inverted /// triangle will take its place on the stack.) These details are handled /// by the DelaunayFixup() routine above. /// </remarks> private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, ushort newmark) { Otri fixuptri = default(Otri), fixuptri2 = default(Otri); Osub crosssubseg = default(Osub); Vertex endpoint1; Vertex farvertex; double area; bool collision; bool done; endpoint1 = starttri.Org(); starttri.Lnext(ref fixuptri); mesh.Flip(ref fixuptri); // 'collision' indicates whether we have found a vertex directly // between endpoint1 and endpoint2. collision = false; done = false; do { farvertex = fixuptri.Org(); // 'farvertex' is the extreme point of the polygon we are "digging" // to get from endpoint1 to endpoint2. if ((farvertex.X == endpoint2.X) && (farvertex.Y == endpoint2.Y)) { fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around endpoint2. DelaunayFixup(ref fixuptri, false); DelaunayFixup(ref fixuptri2, true); done = true; } else { // Check whether farvertex is to the left or right of the segment being // inserted, to decide which edge of fixuptri to dig through next. area = RobustPredicates.CounterClockwise(endpoint1, endpoint2, farvertex); if (area == 0.0) { // We've collided with a vertex between endpoint1 and endpoint2. collision = true; fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around farvertex. DelaunayFixup(ref fixuptri, false); DelaunayFixup(ref fixuptri2, true); done = true; } else { if (area > 0.0) { // farvertex is to the left of the segment. fixuptri.Oprev(ref fixuptri2); // Enforce the Delaunay condition around farvertex, on the // left side of the segment only. DelaunayFixup(ref fixuptri2, true); // Flip the edge that crosses the segment. After the edge is // flipped, one of its endpoints is the fan vertex, and the // destination of fixuptri is the fan vertex. fixuptri.Lprev(); } else { // farvertex is to the right of the segment. DelaunayFixup(ref fixuptri, false); // Flip the edge that crosses the segment. After the edge is // flipped, one of its endpoints is the fan vertex, and the // destination of fixuptri is the fan vertex. fixuptri.Oprev(); } // Check for two intersecting segments. fixuptri.Pivot(ref crosssubseg); if (crosssubseg.seg.hash == Mesh.DUMMY) { mesh.Flip(ref fixuptri); // May create inverted triangle at left. } else { // We've collided with a segment between endpoint1 and endpoint2. collision = true; // Insert a vertex at the intersection. SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); done = true; } } } } while (!done); // Insert a subsegment to make the segment permanent. mesh.InsertSubseg(ref fixuptri, newmark); // If there was a collision with an interceding vertex, install another // segment connecting that vertex with endpoint2. if (collision) { // Insert the remainder of the segment. if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) { ConstrainedEdge(ref fixuptri, endpoint2, newmark); } } }
/// <summary> /// Enforce the Delaunay condition at an edge, fanning out recursively from /// an existing vertex. Pay special attention to stacking inverted triangles. /// </summary> /// <param name="fixuptri"></param> /// <param name="leftside"> /// Indicates whether or not fixuptri is to the left of /// the segment being inserted. (Imagine that the segment is pointing up from /// endpoint1 to endpoint2.) /// </param> /// <remarks> /// This is a support routine for inserting segments into a constrained /// Delaunay triangulation. /// The origin of fixuptri is treated as if it has just been inserted, and /// the local Delaunay condition needs to be enforced. It is only enforced /// in one sector, however, that being the angular range defined by /// fixuptri. /// This routine also needs to make decisions regarding the "stacking" of /// triangles. (Read the description of ConstrainedEdge() below before /// reading on here, so you understand the algorithm.) If the position of /// the new vertex (the origin of fixuptri) indicates that the vertex before /// it on the polygon is a reflex vertex, then "stack" the triangle by /// doing nothing. (fixuptri is an inverted triangle, which is how stacked /// triangles are identified.) /// Otherwise, check whether the vertex before that was a reflex vertex. /// If so, perform an edge flip, thereby eliminating an inverted triangle /// (popping it off the stack). The edge flip may result in the creation /// of a new inverted triangle, depending on whether or not the new vertex /// is visible to the vertex three edges behind on the polygon. /// If neither of the two vertices behind the new vertex are reflex /// vertices, fixuptri and fartri, the triangle opposite it, are not /// inverted; hence, ensure that the edge between them is locally Delaunay. /// </remarks> private void DelaunayFixup(ref Otri fixuptri, bool leftside) { Otri neartri = default(Otri); Otri fartri = default(Otri); Osub faredge = default(Osub); Vertex nearvertex, leftvertex, rightvertex, farvertex; fixuptri.Lnext(ref neartri); neartri.Sym(ref fartri); // Check if the edge opposite the origin of fixuptri can be flipped. if (fartri.tri.Id == Mesh.DUMMY) { return; } neartri.Pivot(ref faredge); if (faredge.seg.hash != Mesh.DUMMY) { return; } // Find all the relevant vertices. nearvertex = neartri.Apex(); leftvertex = neartri.Org(); rightvertex = neartri.Dest(); farvertex = fartri.Apex(); // Check whether the previous polygon vertex is a reflex vertex. if (leftside) { if (RobustPredicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) { // leftvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } else { if (RobustPredicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) { // rightvertex is a reflex vertex too. Nothing can // be done until a convex section is found. return; } } if (RobustPredicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) { // fartri is not an inverted triangle, and farvertex is not a reflex // vertex. As there are no reflex vertices, fixuptri isn't an // inverted triangle, either. Hence, test the edge between the // triangles to ensure it is locally Delaunay. if (RobustPredicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) { return; } // Not locally Delaunay; go on to an edge flip. } // else fartri is inverted; remove it from the stack by flipping. mesh.Flip(ref neartri); fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. // Recursively process the two triangles that result from the flip. DelaunayFixup(ref fixuptri, leftside); DelaunayFixup(ref fartri, leftside); }
/// <summary> /// Find the first triangle on the path from one point to another. /// </summary> /// <param name="searchtri"></param> /// <param name="searchpoint"></param> /// <returns> /// The return value notes whether the destination or apex of the found /// triangle is collinear with the two points in question. /// </returns> /// <remarks> /// Finds the triangle that intersects a line segment drawn from the /// origin of 'searchtri' to the point 'searchpoint', and returns the result /// in 'searchtri'. The origin of 'searchtri' does not change, even though /// the triangle returned may differ from the one passed in. This routine /// is used to find the direction to move in to get from one point to /// another. /// </remarks> private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) { Otri checktri = default(Otri); Vertex startvertex; Vertex leftvertex, rightvertex; double leftccw, rightccw; bool leftflag, rightflag; startvertex = searchtri.Org(); rightvertex = searchtri.Dest(); leftvertex = searchtri.Apex(); // Is 'searchpoint' to the left? leftccw = RobustPredicates.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; // Is 'searchpoint' to the right? rightccw = RobustPredicates.CounterClockwise(startvertex, searchpoint, rightvertex); rightflag = rightccw > 0.0; if (leftflag && rightflag) { // 'searchtri' faces directly away from 'searchpoint'. We could go left // or right. Ask whether it's a triangle or a boundary on the left. searchtri.Onext(ref checktri); if (checktri.tri.Id == Mesh.DUMMY) { leftflag = false; } else { rightflag = false; } } while (leftflag) { // Turn left until satisfied. searchtri.Onext(); if (searchtri.tri.Id == Mesh.DUMMY) { throw new Exception("Unable to find a triangle on path."); } leftvertex = searchtri.Apex(); rightccw = leftccw; leftccw = RobustPredicates.CounterClockwise(searchpoint, startvertex, leftvertex); leftflag = leftccw > 0.0; } while (rightflag) { // Turn right until satisfied. searchtri.Oprev(); if (searchtri.tri.Id == Mesh.DUMMY) { throw new Exception("Unable to find a triangle on path."); } rightvertex = searchtri.Dest(); leftccw = rightccw; rightccw = RobustPredicates.CounterClockwise(startvertex, searchpoint, rightvertex); rightflag = rightccw > 0.0; } if (leftccw == 0.0) { return(FindDirectionResult.Leftcollinear); } if (rightccw == 0.0) { return(FindDirectionResult.Rightcollinear); } return(FindDirectionResult.Within); }
private static Point FindPointInPolygon(List<Vertex> contour, int limit, double eps) { var bounds = new Rectangle(); bounds.Expand(contour.ToPoints()); int length = contour.Count; var test = new Point(); Point a, b, c; // Current corner points. double bx, by; double dx, dy; double h; var predicates = new RobustPredicates(); a = contour[0]; b = contour[1]; for (int i = 0; i < length; i++) { c = contour[(i + 2) % length]; // Corner point. bx = b.x; by = b.y; // NOTE: if we knew the contour points were in counterclockwise order, we // could skip concave corners and search only in one direction. h = predicates.CounterClockwise(a, b, c); if (Math.Abs(h) < eps) { // Points are nearly co-linear. Use perpendicular direction. dx = (c.y - a.y) / 2; dy = (a.x - c.x) / 2; } else { // Direction [midpoint(a-c) -> corner point] dx = (a.x + c.x) / 2 - bx; dy = (a.y + c.y) / 2 - by; } // Move around the contour. a = b; b = c; h = 1.0; for (int j = 0; j < limit; j++) { // Search in direction. test.x = bx + dx * h; test.y = by + dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { return test; } // Search in opposite direction (see NOTE above). test.x = bx - dx * h; test.y = by - dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { return test; } h = h / 2; } } throw new Exception(); }
/// <summary> /// Split all the encroached subsegments. /// </summary> /// <param name="triflaws"> /// A flag that specifies whether one should take /// note of new bad triangles that result from inserting vertices to repair /// encroached subsegments. /// </param> /// <remarks> /// Each encroached subsegment is repaired by splitting it - inserting a /// vertex at or near its midpoint. Newly inserted vertices may encroach /// upon other subsegments; these are also repaired. /// </remarks> private void SplitEncSegs(bool triflaws) { Otri enctri = default(Otri); Otri testtri = default(Otri); Osub testsh = default(Osub); Osub currentenc = default(Osub); BadSubseg seg; Vertex eorg, edest, eapex; Vertex newvertex; InsertVertexResult success; double segmentlength, nearestpoweroftwo; double split; double multiplier, divisor; bool acuteorg, acuteorg2, acutedest, acutedest2; // Note that steinerleft == -1 if an unlimited number // of Steiner points is allowed. while (badsubsegs.Count > 0) { if (mesh.steinerleft == 0) { break; } seg = badsubsegs.Dequeue(); currentenc = seg.subseg; eorg = currentenc.Org(); edest = currentenc.Dest(); // Make sure that this segment is still the same segment it was // when it was determined to be encroached. If the segment was // enqueued multiple times (because several newly inserted // vertices encroached it), it may have already been split. if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest)) { // To decide where to split a segment, we need to know if the // segment shares an endpoint with an adjacent segment. // The concern is that, if we simply split every encroached // segment in its center, two adjacent segments with a small // angle between them might lead to an infinite loop; each // vertex added to split one segment will encroach upon the // other segment, which must then be split with a vertex that // will encroach upon the first segment, and so on forever. // To avoid this, imagine a set of concentric circles, whose // radii are powers of two, about each segment endpoint. // These concentric circles determine where the segment is // split. (If both endpoints are shared with adjacent // segments, split the segment in the middle, and apply the // concentric circles for later splittings.) // Is the origin shared with another segment? currentenc.Pivot(ref enctri); enctri.Lnext(ref testtri); testtri.Pivot(ref testsh); acuteorg = testsh.seg.hash != Mesh.DUMMY; // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest = testsh.seg.hash != Mesh.DUMMY; // If we're using Chew's algorithm (rather than Ruppert's) // to define encroachment, delete free vertices from the // subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) { eapex = enctri.Apex(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.X - eapex.X) * (edest.X - eapex.X) + (eorg.Y - eapex.Y) * (edest.Y - eapex.Y) < 0.0)) { mesh.DeleteVertex(ref testtri); currentenc.Pivot(ref enctri); eapex = enctri.Apex(); enctri.Lprev(ref testtri); } } // Now, check the other side of the segment, if there's a triangle there. enctri.Sym(ref testtri); if (testtri.tri.Id != Mesh.DUMMY) { // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest2 = testsh.seg.hash != Mesh.DUMMY; acutedest = acutedest || acutedest2; // Is the origin shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acuteorg2 = testsh.seg.hash != Mesh.DUMMY; acuteorg = acuteorg || acuteorg2; // Delete free vertices from the subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) { eapex = testtri.Org(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.X - eapex.X) * (edest.X - eapex.X) + (eorg.Y - eapex.Y) * (edest.Y - eapex.Y) < 0.0)) { mesh.DeleteVertex(ref testtri); enctri.Sym(ref testtri); eapex = testtri.Apex(); testtri.Lprev(); } } } // Use the concentric circles if exactly one endpoint is shared // with another adjacent segment. if (acuteorg || acutedest) { segmentlength = Math.Sqrt((edest.X - eorg.X) * (edest.X - eorg.X) + (edest.Y - eorg.Y) * (edest.Y - eorg.Y)); // Find the power of two that most evenly splits the segment. // The worst case is a 2:1 ratio between subsegment lengths. nearestpoweroftwo = 1.0; while (segmentlength > 3.0 * nearestpoweroftwo) { nearestpoweroftwo *= 2.0; } while (segmentlength < 1.5 * nearestpoweroftwo) { nearestpoweroftwo *= 0.5; } // Where do we split the segment? split = nearestpoweroftwo / segmentlength; if (acutedest) { split = 1.0 - split; } } else { // If we're not worried about adjacent segments, split // this segment in the middle. split = 0.5; } // Create the new vertex (interpolate coordinates). newvertex = new Vertex( eorg.X + split * (edest.X - eorg.X), eorg.Y + split * (edest.Y - eorg.Y), currentenc.seg.boundary); newvertex.type = VertexType.SegmentVertex; newvertex.Id = mesh.hash_vtx++; mesh.vertices.Add(newvertex.Id, newvertex); if (!Behavior.NoExact) { // Roundoff in the above calculation may yield a 'newvertex' // that is not precisely collinear with 'eorg' and 'edest'. // Improve collinearity by one step of iterative refinement. multiplier = RobustPredicates.CounterClockwise(eorg, edest, newvertex); divisor = ((eorg.X - edest.X) * (eorg.X - edest.X) + (eorg.Y - edest.Y) * (eorg.Y - edest.Y)); if ((multiplier != 0.0) && (divisor != 0.0)) { multiplier = multiplier / divisor; // Watch out for NANs. if (!double.IsNaN(multiplier)) { newvertex.X += multiplier * (edest.Y - eorg.Y); newvertex.Y += multiplier * (eorg.X - edest.X); } } } // Check whether the new vertex lies on an endpoint. if (((newvertex.X == eorg.X) && (newvertex.Y == eorg.Y)) || ((newvertex.X == edest.X) && (newvertex.Y == edest.Y))) { throw new Exception("Ran out of precision"); } // Insert the splitting vertex. This should always succeed. success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) { throw new Exception("Failure to split a segment."); } if (mesh.steinerleft > 0) { mesh.steinerleft--; } // Check the two new subsegments to see if they're encroached. CheckSeg4Encroach(ref currentenc); currentenc.Next(); CheckSeg4Encroach(ref currentenc); } // Set subsegment's origin to NULL. This makes it possible to detect dead // badsubsegs when traversing the list of all badsubsegs. seg.org = null; } }
public Mesh Triangulate(List <Vertex> points) { mesh = TrianglePool.AllocMesh(); mesh.TransferNodes(points); // Nonexistent x value used as a flag to mark circle events in sweepline // Delaunay algorithm. xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right; SweepEvent[] eventheap; SweepEvent nextevent; SweepEvent newevent; SplayNode splayroot; Otri bottommost = default(Otri); Otri searchtri = default(Otri); Otri fliptri; Otri lefttri = default(Otri); Otri righttri = default(Otri); Otri farlefttri = default(Otri); Otri farrighttri = default(Otri); Otri inserttri = default(Otri); Vertex firstvertex, secondvertex; Vertex nextvertex, lastvertex; Vertex connectvertex; Vertex leftvertex, midvertex, rightvertex; double lefttest, righttest; int heapsize; bool check4events, farrightflag = false; splaynodes = new List <SplayNode>(); splayroot = null; CreateHeap(out eventheap); //, out events, out freeevents); heapsize = mesh.invertices; mesh.MakeTriangle(ref lefttri); mesh.MakeTriangle(ref righttri); lefttri.Bond(ref righttri); lefttri.Lnext(); righttri.Lprev(); lefttri.Bond(ref righttri); lefttri.Lnext(); righttri.Lprev(); lefttri.Bond(ref righttri); firstvertex = eventheap[0].vertexEvent; HeapDelete(eventheap, heapsize, 0); heapsize--; do { if (heapsize == 0) { throw new Exception("Input vertices are all identical."); } secondvertex = eventheap[0].vertexEvent; HeapDelete(eventheap, heapsize, 0); heapsize--; if ((firstvertex.X == secondvertex.X) && (firstvertex.Y == secondvertex.Y)) { secondvertex.type = VertexType.UndeadVertex; mesh.undeads++; } } while ((firstvertex.X == secondvertex.X) && (firstvertex.Y == secondvertex.Y)); lefttri.SetOrg(firstvertex); lefttri.SetDest(secondvertex); righttri.SetOrg(secondvertex); righttri.SetDest(firstvertex); lefttri.Lprev(ref bottommost); lastvertex = secondvertex; while (heapsize > 0) { nextevent = eventheap[0]; HeapDelete(eventheap, heapsize, 0); heapsize--; check4events = true; if (nextevent.xkey < mesh.bounds.Left) { fliptri = nextevent.otriEvent; fliptri.Oprev(ref farlefttri); Check4DeadEvent(ref farlefttri, eventheap, ref heapsize); fliptri.Onext(ref farrighttri); Check4DeadEvent(ref farrighttri, eventheap, ref heapsize); if (farlefttri.Equal(bottommost)) { fliptri.Lprev(ref bottommost); } mesh.Flip(ref fliptri); fliptri.SetApex(null); fliptri.Lprev(ref lefttri); fliptri.Lnext(ref righttri); lefttri.Sym(ref farlefttri); if (randomnation(SAMPLERATE) == 0) { fliptri.Sym(); leftvertex = fliptri.Dest(); midvertex = fliptri.Apex(); rightvertex = fliptri.Org(); splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey); } } else { nextvertex = nextevent.vertexEvent; if ((nextvertex.X == lastvertex.X) && (nextvertex.Y == lastvertex.Y)) { nextvertex.type = VertexType.UndeadVertex; mesh.undeads++; check4events = false; } else { lastvertex = nextvertex; splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag); //bottommost.Copy(ref searchtri); //farrightflag = false; //while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex)) //{ // searchtri.OnextSelf(); // farrightflag = searchtri.Equal(bottommost); //} Check4DeadEvent(ref searchtri, eventheap, ref heapsize); searchtri.Copy(ref farrighttri); searchtri.Sym(ref farlefttri); mesh.MakeTriangle(ref lefttri); mesh.MakeTriangle(ref righttri); connectvertex = farrighttri.Dest(); lefttri.SetOrg(connectvertex); lefttri.SetDest(nextvertex); righttri.SetOrg(nextvertex); righttri.SetDest(connectvertex); lefttri.Bond(ref righttri); lefttri.Lnext(); righttri.Lprev(); lefttri.Bond(ref righttri); lefttri.Lnext(); righttri.Lprev(); lefttri.Bond(ref farlefttri); righttri.Bond(ref farrighttri); if (!farrightflag && farrighttri.Equal(bottommost)) { lefttri.Copy(ref bottommost); } if (randomnation(SAMPLERATE) == 0) { splayroot = SplayInsert(splayroot, lefttri, nextvertex); } else if (randomnation(SAMPLERATE) == 0) { righttri.Lnext(ref inserttri); splayroot = SplayInsert(splayroot, inserttri, nextvertex); } } } if (check4events) { leftvertex = farlefttri.Apex(); midvertex = lefttri.Dest(); rightvertex = lefttri.Apex(); lefttest = RobustPredicates.CounterClockwise(leftvertex, midvertex, rightvertex); if (lefttest > 0.0) { newevent = new SweepEvent(); newevent.xkey = xminextreme; newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest); newevent.otriEvent = lefttri; HeapInsert(eventheap, heapsize, newevent); heapsize++; lefttri.SetOrg(new SweepEventVertex(newevent)); } leftvertex = righttri.Apex(); midvertex = righttri.Org(); rightvertex = farrighttri.Apex(); righttest = RobustPredicates.CounterClockwise(leftvertex, midvertex, rightvertex); if (righttest > 0.0) { newevent = new SweepEvent(); newevent.xkey = xminextreme; newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest); newevent.otriEvent = farrighttri; HeapInsert(eventheap, heapsize, newevent); heapsize++; farrighttri.SetOrg(new SweepEventVertex(newevent)); } } } splaynodes.Clear(); bottommost.Lprev(); mesh.hullsize = RemoveGhosts(ref bottommost); return(mesh); }
public static TriangleNet.Geometry.Point FindPointInPolygon(SectionContour contour, List <SectionContour> otherContours, int limit, double eps = 2e-5) { List <Vertex> poly = contour.Points.Select(p => new Vertex(p.X, p.Y)).ToList(); var bounds = new TriangleNet.Geometry.Rectangle(); bounds.Expand(poly); int length = poly.Count; var test = new TriangleNet.Geometry.Point(); TriangleNet.Geometry.Point a, b, c; // Current corner points. double bx, by; double dx, dy; double h; var predicates = new RobustPredicates(); a = poly[0]; b = poly[1]; for (int i = 0; i < length; i++) { c = poly[(i + 2) % length]; // Corner point. bx = b.X; by = b.Y; // NOTE: if we knew the contour points were in counterclockwise order, we // could skip concave corners and search only in one direction. h = predicates.CounterClockwise(a, b, c); if (Math.Abs(h) < eps) { // Points are nearly co-linear. Use perpendicular direction. dx = (c.Y - a.Y) / 2; dy = (a.X - c.X) / 2; } else { // Direction [midpoint(a-c) -> corner point] dx = (a.X + c.X) / 2 - bx; dy = (a.Y + c.Y) / 2 - by; } // Move around the contour. a = b; b = c; h = 1.0; for (int j = 0; j < limit; j++) { // Search in direction. test.X = bx + dx * h; test.Y = by + dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour, otherContours)) { return(test); } // Search in opposite direction (see NOTE above). test.X = bx - dx * h; test.Y = by - dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour, otherContours)) { return(test); } h = h / 2; } } throw new Exception(); }
/// <summary> /// Recursively form a Delaunay triangulation by the divide-and-conquer method. /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <param name="axis"></param> /// <param name="farleft"></param> /// <param name="farright"></param> /// <remarks> /// Recursively breaks down the problem into smaller pieces, which are /// knitted together by mergehulls(). The base cases (problems of two or /// three vertices) are handled specially here. /// /// On completion, 'farleft' and 'farright' are bounding triangles such that /// the origin of 'farleft' is the leftmost vertex (breaking ties by /// choosing the highest leftmost vertex), and the destination of /// 'farright' is the rightmost vertex (breaking ties by choosing the /// lowest rightmost vertex). /// </remarks> void DivconqRecurse(int left, int right, int axis, ref Otri farleft, ref Otri farright) { Otri midtri = default(Otri); Otri tri1 = default(Otri); Otri tri2 = default(Otri); Otri tri3 = default(Otri); Otri innerleft = default(Otri), innerright = default(Otri); double area; int vertices = right - left + 1; int divider; if (vertices == 2) { // The triangulation of two vertices is an edge. An edge is // represented by two bounding triangles. mesh.MakeTriangle(ref farleft); farleft.SetOrg(sortarray[left]); farleft.SetDest(sortarray[left + 1]); // The apex is intentionally left NULL. mesh.MakeTriangle(ref farright); farright.SetOrg(sortarray[left + 1]); farright.SetDest(sortarray[left]); // The apex is intentionally left NULL. farleft.Bond(ref farright); farleft.Lprev(); farright.Lnext(); farleft.Bond(ref farright); farleft.Lprev(); farright.Lnext(); farleft.Bond(ref farright); // Ensure that the origin of 'farleft' is sortarray[0]. farright.Lprev(ref farleft); return; } else if (vertices == 3) { // The triangulation of three vertices is either a triangle (with // three bounding triangles) or two edges (with four bounding // triangles). In either case, four triangles are created. mesh.MakeTriangle(ref midtri); mesh.MakeTriangle(ref tri1); mesh.MakeTriangle(ref tri2); mesh.MakeTriangle(ref tri3); area = RobustPredicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]); if (area == 0.0) { // Three collinear vertices; the triangulation is two edges. midtri.SetOrg(sortarray[left]); midtri.SetDest(sortarray[left + 1]); tri1.SetOrg(sortarray[left + 1]); tri1.SetDest(sortarray[left]); tri2.SetOrg(sortarray[left + 2]); tri2.SetDest(sortarray[left + 1]); tri3.SetOrg(sortarray[left + 1]); tri3.SetDest(sortarray[left + 2]); // All apices are intentionally left NULL. midtri.Bond(ref tri1); tri2.Bond(ref tri3); midtri.Lnext(); tri1.Lprev(); tri2.Lnext(); tri3.Lprev(); midtri.Bond(ref tri3); tri1.Bond(ref tri2); midtri.Lnext(); tri1.Lprev(); tri2.Lnext(); tri3.Lprev(); midtri.Bond(ref tri1); tri2.Bond(ref tri3); // Ensure that the origin of 'farleft' is sortarray[0]. tri1.Copy(ref farleft); // Ensure that the destination of 'farright' is sortarray[2]. tri2.Copy(ref farright); } else { // The three vertices are not collinear; the triangulation is one // triangle, namely 'midtri'. midtri.SetOrg(sortarray[left]); tri1.SetDest(sortarray[left]); tri3.SetOrg(sortarray[left]); // Apices of tri1, tri2, and tri3 are left NULL. if (area > 0.0) { // The vertices are in counterclockwise order. midtri.SetDest(sortarray[left + 1]); tri1.SetOrg(sortarray[left + 1]); tri2.SetDest(sortarray[left + 1]); midtri.SetApex(sortarray[left + 2]); tri2.SetOrg(sortarray[left + 2]); tri3.SetDest(sortarray[left + 2]); } else { // The vertices are in clockwise order. midtri.SetDest(sortarray[left + 2]); tri1.SetOrg(sortarray[left + 2]); tri2.SetDest(sortarray[left + 2]); midtri.SetApex(sortarray[left + 1]); tri2.SetOrg(sortarray[left + 1]); tri3.SetDest(sortarray[left + 1]); } // The topology does not depend on how the vertices are ordered. midtri.Bond(ref tri1); midtri.Lnext(); midtri.Bond(ref tri2); midtri.Lnext(); midtri.Bond(ref tri3); tri1.Lprev(); tri2.Lnext(); tri1.Bond(ref tri2); tri1.Lprev(); tri3.Lprev(); tri1.Bond(ref tri3); tri2.Lnext(); tri3.Lprev(); tri2.Bond(ref tri3); // Ensure that the origin of 'farleft' is sortarray[0]. tri1.Copy(ref farleft); // Ensure that the destination of 'farright' is sortarray[2]. if (area > 0.0) { tri2.Copy(ref farright); } else { farleft.Lnext(ref farright); } } return; } else { // Split the vertices in half. divider = vertices >> 1; // Recursively triangulate each half. DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); //DebugWriter.Session.Write(mesh, true); DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); //DebugWriter.Session.Write(mesh, true); // Merge the two triangulations into one. MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); //DebugWriter.Session.Write(mesh, true); } }
/// <summary> /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. /// </summary> /// <param name="farleft">Bounding triangles of the left triangulation.</param> /// <param name="innerleft">Bounding triangles of the left triangulation.</param> /// <param name="innerright">Bounding triangles of the right triangulation.</param> /// <param name="farright">Bounding triangles of the right triangulation.</param> /// <param name="axis"></param> /// <remarks> /// This is similar to the algorithm given by Guibas and Stolfi, but uses /// a triangle-based, rather than edge-based, data structure. /// /// The algorithm walks up the gap between the two triangulations, knitting /// them together. As they are merged, some of their bounding triangles /// are converted into real triangles of the triangulation. The procedure /// pulls each hull's bounding triangles apart, then knits them together /// like the teeth of two gears. The Delaunay property determines, at each /// step, whether the next "tooth" is a bounding triangle of the left hull /// or the right. When a bounding triangle becomes real, its apex is /// changed from NULL to a real vertex. /// /// Only two new triangles need to be allocated. These become new bounding /// triangles at the top and bottom of the seam. They are used to connect /// the remaining bounding triangles (those that have not been converted /// into real triangles) into a single fan. /// /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left /// triangulation. The origin of 'farleft' is the leftmost vertex, and /// the destination of 'innerleft' is the rightmost vertex of the /// triangulation. Similarly, 'innerright' and 'farright' are bounding /// triangles of the right triangulation. The origin of 'innerright' and /// destination of 'farright' are the leftmost and rightmost vertices. /// /// On completion, the origin of 'farleft' is the leftmost vertex of the /// merged triangulation, and the destination of 'farright' is the rightmost /// vertex. /// </remarks> void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright, ref Otri farright, int axis) { Otri leftcand = default(Otri), rightcand = default(Otri); Otri nextedge = default(Otri); Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri); Otri checkedge = default(Otri); Otri baseedge = default(Otri); Vertex innerleftdest; Vertex innerrightorg; Vertex innerleftapex, innerrightapex; Vertex farleftpt, farrightpt; Vertex farleftapex, farrightapex; Vertex lowerleft, lowerright; Vertex upperleft, upperright; Vertex nextapex; Vertex checkvertex; bool changemade; bool badedge; bool leftfinished, rightfinished; innerleftdest = innerleft.Dest(); innerleftapex = innerleft.Apex(); innerrightorg = innerright.Org(); innerrightapex = innerright.Apex(); // Special treatment for horizontal cuts. if (UseDwyer && (axis == 1)) { farleftpt = farleft.Org(); farleftapex = farleft.Apex(); farrightpt = farright.Dest(); farrightapex = farright.Apex(); // The pointers to the extremal vertices are shifted to point to the // topmost and bottommost vertex of each hull, rather than the // leftmost and rightmost vertices. while (farleftapex.Y < farleftpt.Y) { farleft.Lnext(); farleft.Sym(); farleftpt = farleftapex; farleftapex = farleft.Apex(); } innerleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); while (checkvertex.Y > innerleftdest.Y) { checkedge.Lnext(ref innerleft); innerleftapex = innerleftdest; innerleftdest = checkvertex; innerleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); } while (innerrightapex.Y < innerrightorg.Y) { innerright.Lnext(); innerright.Sym(); innerrightorg = innerrightapex; innerrightapex = innerright.Apex(); } farright.Sym(ref checkedge); checkvertex = checkedge.Apex(); while (checkvertex.Y > farrightpt.Y) { checkedge.Lnext(ref farright); farrightapex = farrightpt; farrightpt = checkvertex; farright.Sym(ref checkedge); checkvertex = checkedge.Apex(); } } // Find a line tangent to and below both hulls. do { changemade = false; // Make innerleftdest the "bottommost" vertex of the left hull. if (RobustPredicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) { innerleft.Lprev(); innerleft.Sym(); innerleftdest = innerleftapex; innerleftapex = innerleft.Apex(); changemade = true; } // Make innerrightorg the "bottommost" vertex of the right hull. if (RobustPredicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) { innerright.Lnext(); innerright.Sym(); innerrightorg = innerrightapex; innerrightapex = innerright.Apex(); changemade = true; } } while (changemade); // Find the two candidates to be the next "gear tooth." innerleft.Sym(ref leftcand); innerright.Sym(ref rightcand); // Create the bottom new bounding triangle. mesh.MakeTriangle(ref baseedge); // Connect it to the bounding boxes of the left and right triangulations. baseedge.Bond(ref innerleft); baseedge.Lnext(); baseedge.Bond(ref innerright); baseedge.Lnext(); baseedge.SetOrg(innerrightorg); baseedge.SetDest(innerleftdest); // Apex is intentionally left NULL. // Fix the extreme triangles if necessary. farleftpt = farleft.Org(); if (innerleftdest == farleftpt) { baseedge.Lnext(ref farleft); } farrightpt = farright.Dest(); if (innerrightorg == farrightpt) { baseedge.Lprev(ref farright); } // The vertices of the current knitting edge. lowerleft = innerleftdest; lowerright = innerrightorg; // The candidate vertices for knitting. upperleft = leftcand.Apex(); upperright = rightcand.Apex(); // Walk up the gap between the two triangulations, knitting them together. while (true) { // Have we reached the top? (This isn't quite the right question, // because even though the left triangulation might seem finished now, // moving up on the right triangulation might reveal a new vertex of // the left triangulation. And vice-versa.) leftfinished = RobustPredicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; rightfinished = RobustPredicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0; if (leftfinished && rightfinished) { // Create the top new bounding triangle. mesh.MakeTriangle(ref nextedge); nextedge.SetOrg(lowerleft); nextedge.SetDest(lowerright); // Apex is intentionally left NULL. // Connect it to the bounding boxes of the two triangulations. nextedge.Bond(ref baseedge); nextedge.Lnext(); nextedge.Bond(ref rightcand); nextedge.Lnext(); nextedge.Bond(ref leftcand); // Special treatment for horizontal cuts. if (UseDwyer && (axis == 1)) { farleftpt = farleft.Org(); farleftapex = farleft.Apex(); farrightpt = farright.Dest(); farrightapex = farright.Apex(); farleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); // The pointers to the extremal vertices are restored to the // leftmost and rightmost vertices (rather than topmost and // bottommost). while (checkvertex.X < farleftpt.X) { checkedge.Lprev(ref farleft); farleftapex = farleftpt; farleftpt = checkvertex; farleft.Sym(ref checkedge); checkvertex = checkedge.Apex(); } while (farrightapex.X > farrightpt.X) { farright.Lprev(); farright.Sym(); farrightpt = farrightapex; farrightapex = farright.Apex(); } } return; } // Consider eliminating edges from the left triangulation. if (!leftfinished) { // What vertex would be exposed if an edge were deleted? leftcand.Lprev(ref nextedge); nextedge.Sym(); nextapex = nextedge.Apex(); // If nextapex is NULL, then no vertex would be exposed; the // triangulation would have been eaten right through. if (nextapex != null) { // Check whether the edge is Delaunay. badedge = RobustPredicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; while (badedge) { // Eliminate the edge with an edge flip. As a result, the // left triangulation will have one more boundary triangle. nextedge.Lnext(); nextedge.Sym(ref topcasing); nextedge.Lnext(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); leftcand.Bond(ref sidecasing); leftcand.Lnext(); leftcand.Sym(ref outercasing); nextedge.Lprev(); nextedge.Bond(ref outercasing); // Correct the vertices to reflect the edge flip. leftcand.SetOrg(lowerleft); leftcand.SetDest(null); leftcand.SetApex(nextapex); nextedge.SetOrg(null); nextedge.SetDest(upperleft); nextedge.SetApex(nextapex); // Consider the newly exposed vertex. upperleft = nextapex; // What vertex would be exposed if another edge were deleted? sidecasing.Copy(ref nextedge); nextapex = nextedge.Apex(); if (nextapex != null) { // Check whether the edge is Delaunay. badedge = RobustPredicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } // Consider eliminating edges from the right triangulation. if (!rightfinished) { // What vertex would be exposed if an edge were deleted? rightcand.Lnext(ref nextedge); nextedge.Sym(); nextapex = nextedge.Apex(); // If nextapex is NULL, then no vertex would be exposed; the // triangulation would have been eaten right through. if (nextapex != null) { // Check whether the edge is Delaunay. badedge = RobustPredicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; while (badedge) { // Eliminate the edge with an edge flip. As a result, the // right triangulation will have one more boundary triangle. nextedge.Lprev(); nextedge.Sym(ref topcasing); nextedge.Lprev(); nextedge.Sym(ref sidecasing); nextedge.Bond(ref topcasing); rightcand.Bond(ref sidecasing); rightcand.Lprev(); rightcand.Sym(ref outercasing); nextedge.Lnext(); nextedge.Bond(ref outercasing); // Correct the vertices to reflect the edge flip. rightcand.SetOrg(null); rightcand.SetDest(lowerright); rightcand.SetApex(nextapex); nextedge.SetOrg(upperright); nextedge.SetDest(null); nextedge.SetApex(nextapex); // Consider the newly exposed vertex. upperright = nextapex; // What vertex would be exposed if another edge were deleted? sidecasing.Copy(ref nextedge); nextapex = nextedge.Apex(); if (nextapex != null) { // Check whether the edge is Delaunay. badedge = RobustPredicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; } else { // Avoid eating right through the triangulation. badedge = false; } } } } if (leftfinished || (!rightfinished && (RobustPredicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) { // Knit the triangulations, adding an edge from 'lowerleft' // to 'upperright'. baseedge.Bond(ref rightcand); rightcand.Lprev(ref baseedge); baseedge.SetDest(lowerleft); lowerright = upperright; baseedge.Sym(ref rightcand); upperright = rightcand.Apex(); } else { // Knit the triangulations, adding an edge from 'upperleft' // to 'lowerright'. baseedge.Bond(ref leftcand); leftcand.Lnext(ref baseedge); baseedge.SetOrg(lowerright); lowerleft = upperleft; baseedge.Sym(ref leftcand); upperleft = leftcand.Apex(); } } }
/// <summary> Find a triangle or edge containing a given point. </summary> /// <param name="searchpoint">The point to locate.</param> /// <param name="searchtri">The triangle to start the search at.</param> /// <param name="stopatsubsegment"> /// If 'stopatsubsegment' is set, the search /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. /// </param> /// <returns>Location information.</returns> /// <remarks> /// Begins its search from 'searchtri'. It is important that 'searchtri' /// be a handle with the property that 'searchpoint' is strictly to the left /// of the edge denoted by 'searchtri', or is collinear with that edge and /// does not intersect that edge. (In particular, 'searchpoint' should not /// be the origin or destination of that edge.) /// These conditions are imposed because preciselocate() is normally used in /// one of two situations: /// (1) To try to find the location to insert a new point. Normally, we /// know an edge that the point is strictly to the left of. In the /// incremental Delaunay algorithm, that edge is a bounding box edge. /// In Ruppert's Delaunay refinement algorithm for quality meshing, /// that edge is the shortest edge of the triangle whose circumcenter /// is being inserted. /// (2) To try to find an existing point. In this case, any edge on the /// convex hull is a good starting edge. You must screen out the /// possibility that the vertex sought is an endpoint of the starting /// edge before you call preciselocate(). /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. /// This implementation differs from that given by Guibas and Stolfi. It /// walks from triangle to triangle, crossing an edge only if 'searchpoint' /// is on the other side of the line containing that edge. After entering /// a triangle, there are two edges by which one can leave that triangle. /// If both edges are valid ('searchpoint' is on the other side of both /// edges), one of the two is chosen by drawing a line perpendicular to /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' /// falls on, an exit edge is chosen. /// This implementation is empirically faster than the Guibas and Stolfi /// point location routine (which I originally used), which tends to spiral /// in toward its target. /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' /// is a handle whose origin is the existing vertex. /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a /// handle whose primary edge is the edge on which the point lies. /// Returns INTRIANGLE if the point lies strictly within a triangle. /// 'searchtri' is a handle on the triangle that contains the point. /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a /// handle whose primary edge the point is to the right of. This might /// occur when the circumcenter of a triangle falls just slightly outside /// the mesh due to floating-point roundoff error. It also occurs when /// seeking a hole or region point that a foolish user has placed outside /// the mesh. /// WARNING: This routine is designed for convex triangulations, and will /// not generally work after the holes and concavities have been carved. /// However, it can still be used to find the circumcenter of a triangle, as /// long as the search is begun from the triangle in question. /// </remarks> public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, bool stopatsubsegment) { Otri backtracktri = default(Otri); Osub checkedge = default(Osub); Point forg, fdest, fapex; double orgorient, destorient; bool moveleft; // Where are we? forg = searchtri.Org(); fdest = searchtri.Dest(); fapex = searchtri.Apex(); while (true) { // Check whether the apex is the point we seek. if ((fapex.X == searchpoint.X) && (fapex.Y == searchpoint.Y)) { searchtri.Lprev(); return(LocateResult.OnVertex); } // Does the point lie on the other side of the line defined by the // triangle edge opposite the triangle's destination? destorient = RobustPredicates.CounterClockwise(forg, fapex, searchpoint); // Does the point lie on the other side of the line defined by the // triangle edge opposite the triangle's origin? orgorient = RobustPredicates.CounterClockwise(fapex, fdest, searchpoint); if (destorient > 0.0) { if (orgorient > 0.0) { // Move left if the inner product of (fapex - searchpoint) and // (fdest - forg) is positive. This is equivalent to drawing // a line perpendicular to the line (forg, fdest) and passing // through 'fapex', and determining which side of this line // 'searchpoint' falls on. moveleft = (fapex.X - searchpoint.X) * (fdest.X - forg.X) + (fapex.Y - searchpoint.Y) * (fdest.Y - forg.Y) > 0.0; } else { moveleft = true; } } else { if (orgorient > 0.0) { moveleft = false; } else { // The point we seek must be on the boundary of or inside this // triangle. if (destorient == 0.0) { searchtri.Lprev(); return(LocateResult.OnEdge); } if (orgorient == 0.0) { searchtri.Lnext(); return(LocateResult.OnEdge); } return(LocateResult.InTriangle); } } // Move to another triangle. Leave a trace 'backtracktri' in case // floating-point roundoff or some such bogey causes us to walk // off a boundary of the triangulation. if (moveleft) { searchtri.Lprev(ref backtracktri); fdest = fapex; } else { searchtri.Lnext(ref backtracktri); forg = fapex; } backtracktri.Sym(ref searchtri); if (mesh.checksegments && stopatsubsegment) { // Check for walking through a subsegment. backtracktri.Pivot(ref checkedge); if (checkedge.seg.hash != Mesh.DUMMY) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return(LocateResult.Outside); } } // Check for walking right out of the triangulation. if (searchtri.tri.Id == Mesh.DUMMY) { // Go back to the last triangle. backtracktri.Copy(ref searchtri); return(LocateResult.Outside); } fapex = searchtri.Apex(); } }
/// <summary> Find a triangle or edge containing a given point. </summary> /// <param name="searchpoint">The point to locate.</param> /// <param name="searchtri">The triangle to start the search at.</param> /// <returns>Location information.</returns> /// <remarks> /// Searching begins from one of: the input 'searchtri', a recently /// encountered triangle 'recenttri', or from a triangle chosen from a /// random sample. The choice is made by determining which triangle's /// origin is closest to the point we are searching for. Normally, /// 'searchtri' should be a handle on the convex hull of the triangulation. /// Details on the random sampling method can be found in the Mucke, Saias, /// and Zhu paper cited in the header of this code. /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' /// is a handle whose origin is the existing vertex. /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a /// handle whose primary edge is the edge on which the point lies. /// Returns INTRIANGLE if the point lies strictly within a triangle. /// 'searchtri' is a handle on the triangle that contains the point. /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a /// handle whose primary edge the point is to the right of. This might /// occur when the circumcenter of a triangle falls just slightly outside /// the mesh due to floating-point roundoff error. It also occurs when /// seeking a hole or region point that a foolish user has placed outside /// the mesh. /// WARNING: This routine is designed for convex triangulations, and will /// not generally work after the holes and concavities have been carved. /// </remarks> public LocateResult Locate(Point searchpoint, ref Otri searchtri) { Otri sampletri = default(Otri); Vertex torg, tdest; double searchdist, dist; double ahead; // Record the distance from the suggested starting triangle to the // point we seek. torg = searchtri.Org(); searchdist = (searchpoint.X - torg.X) * (searchpoint.X - torg.X) + (searchpoint.Y - torg.Y) * (searchpoint.Y - torg.Y); // If a recently encountered triangle has been recorded and has not been // deallocated, test it as a good starting point. if (recenttri.tri != null) { if (!Otri.IsDead(recenttri.tri)) { torg = recenttri.Org(); if ((torg.X == searchpoint.X) && (torg.Y == searchpoint.Y)) { recenttri.Copy(ref searchtri); return(LocateResult.OnVertex); } dist = (searchpoint.X - torg.X) * (searchpoint.X - torg.X) + (searchpoint.Y - torg.Y) * (searchpoint.Y - torg.Y); if (dist < searchdist) { recenttri.Copy(ref searchtri); searchdist = dist; } } } // TODO: Improve sampling. sampler.Update(mesh); var samples = sampler.GetSamples(mesh); foreach (var key in samples) { sampletri.tri = mesh.triangles[key]; if (!Otri.IsDead(sampletri.tri)) { torg = sampletri.Org(); dist = (searchpoint.X - torg.X) * (searchpoint.X - torg.X) + (searchpoint.Y - torg.Y) * (searchpoint.Y - torg.Y); if (dist < searchdist) { sampletri.Copy(ref searchtri); searchdist = dist; } } } // Where are we? torg = searchtri.Org(); tdest = searchtri.Dest(); // Check the starting triangle's vertices. if ((torg.X == searchpoint.X) && (torg.Y == searchpoint.Y)) { return(LocateResult.OnVertex); } if ((tdest.X == searchpoint.X) && (tdest.Y == searchpoint.Y)) { searchtri.Lnext(); return(LocateResult.OnVertex); } // Orient 'searchtri' to fit the preconditions of calling preciselocate(). ahead = RobustPredicates.CounterClockwise(torg, tdest, searchpoint); if (ahead < 0.0) { // Turn around so that 'searchpoint' is to the left of the // edge specified by 'searchtri'. searchtri.Sym(); } else if (ahead == 0.0) { // Check if 'searchpoint' is between 'torg' and 'tdest'. if (((torg.X < searchpoint.X) == (searchpoint.X < tdest.X)) && ((torg.Y < searchpoint.Y) == (searchpoint.Y < tdest.Y))) { return(LocateResult.OnEdge); } } return(PreciseLocate(searchpoint, ref searchtri, false)); }