/// <summary> /// Triangulates the shape. /// </summary> /// <returns>The output list.</returns> public List<float[]> Triangulate(float[] offset, float scale = 1.0f) { var output = new List<float[]>(); Points.Reverse(); var context = new SweepContext(); context.AddPoints(Points); // Hole edges foreach (Shape h in Holes) context.AddHole(h.Points); context.InitTriangulation(); var sweep = new Sweep(); sweep.Triangulate(context); var triangles = context.GetTriangles(); foreach (Triangle tri in triangles) { //tri.ReversePointFlow(); output.Add(((float[])tri.Points[0]).VectorScale(scale).Add(offset)); output.Add(((float[])tri.Points[2]).VectorScale(scale).Add(offset)); output.Add(((float[])tri.Points[1]).VectorScale(scale).Add(offset)); } return output; }
private void FillEdgeEvent(SweepContext tcx, Edge edge, Node node) { if (tcx.EdgeEvent.Right) { FillRightAboveEdgeEvent(tcx, edge, node); } else { FillLeftAboveEdgeEvent(tcx, edge, node); } }
public void Triangulate(SweepContext tcx) { _nodes = new List <Node>(); tcx.InitTriangulation(); tcx.CreateAdvancingFront(_nodes); // Sweep points; build mesh SweepPoints(tcx); // Clean up FinalizationPolygon(tcx); }
private void FinalizationPolygon(SweepContext tcx) { // Get an Internal triangle to start with Triangle t = tcx.Front._head.Next.Triangle; TriPoint p = tcx.Front._head.Next.Point; while (!t.GetConstrainedEdgeCW(p)) { t = t.NeighborCCW(p); } tcx.MeshClean(t); }
private void FillBasinReq(SweepContext tcx, Node node) { // if shallow stop filling if (IsShallow(tcx, node)) { return; } Fill(tcx, node); if (node.Prev == tcx.Basin.LeftNode && node.Next == tcx.Basin.RightNode) { return; } else if (node.Prev == tcx.Basin.LeftNode) { Winding o = TriUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point); if (o == Winding.CW) { return; } node = node.Next; } else if (node.Next == tcx.Basin.RightNode) { Winding o = TriUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point); if (o == Winding.CCW) { return; } node = node.Prev; } else { // Continue with the neighbor node with lowest Y value if (node.Prev.Point.Y < node.Next.Point.Y) { node = node.Prev; } else { node = node.Next; } } FillBasinReq(tcx, node); }
private void FillLeftAboveEdgeEvent(SweepContext tcx, Edge edge, Node node) { while (node.Prev.Point.X > edge.P.X) { // Check if next node is below the edge if (TriUtil.Orient2d(edge.Q, node.Prev.Point, edge.P) == Winding.CW) { FillLeftBelowEdgeEvent(tcx, edge, node); } else { node = node.Prev; } } }
private void SweepPoints(SweepContext tcx) { for (int i = 1; i < tcx.PointCount(); i++) { TriPoint point = tcx.GetPoint(i); Node node = PointEvent(tcx, point); if (point.EdgeList != null) { for (int j = 0; j < point.EdgeList.Count; j++) { EdgeEvent(tcx, point.EdgeList[j], node); } } } }
private void EdgeEvent(SweepContext tcx, Edge edge, Node node) { tcx.EdgeEvent.ConstrainedEdge = edge; tcx.EdgeEvent.Right = (edge.P.X > edge.Q.X); if (IsEdgeSideOfTriangle(node.Triangle, edge.P, edge.Q)) { return; } // For now we will do all needed filling // TODO: integrate with flip process might give some better performance // but for now this avoid the issue with cases that needs both flips and fills FillEdgeEvent(tcx, edge, node); EdgeEvent(tcx, edge.P, edge.Q, node.Triangle, edge.Q); }
private Node PointEvent(SweepContext tcx, TriPoint point) { Node node = tcx.LocateNode(point); Node new_node = NewFrontTriangle(tcx, point, node); // Only need to check +epsilon since point never have smaller // x value than node due to how we fetch nodes from the front if (point.X <= node.Point.X + TriUtil.EPSILON) { Fill(tcx, node); } //tcx.AddNode(new_node); FillAdvancingFront(tcx, new_node); return(new_node); }
private void FillLeftBelowEdgeEvent(SweepContext tcx, Edge edge, Node node) { if (node.Point.X > edge.P.X) { if (TriUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Winding.CW) { // Concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Convex FillLeftConvexEdgeEvent(tcx, edge, node); // Retry this one FillLeftBelowEdgeEvent(tcx, edge, node); } } }
/// <summary> /// Triangulates the shape. /// </summary> /// <returns>The output list.</returns> public List<Triangle> Triangulate() { var context = new SweepContext(); var distinctPoints = Points.Distinct().ToList(); context.AddPoints(distinctPoints); foreach (var hole in Holes) context.AddHole(hole.Points); context.InitTriangulation(); var sweep = new Sweep(); sweep.Triangulate(context); return context.GetTriangles(); }
private void Fill(SweepContext tcx, Node node) { Triangle triangle = new Triangle(node.Prev.Point, node.Point, node.Next.Point); triangle.MarkNeighbor(node.Prev.Triangle); triangle.MarkNeighbor(node.Triangle); tcx.AddToMap(triangle); // Update the advancing front node.Prev.Next = node.Next; node.Next.Prev = node.Prev; // If it was legalized the triangle has already been mapped if (!Legalize(tcx, triangle)) { tcx.MapTriangleToNodes(triangle); } }
private void FillBasin(SweepContext tcx, Node node) { if (TriUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Winding.CCW) { tcx.Basin.LeftNode = node.Next.Next; } else { tcx.Basin.LeftNode = node.Next; } // Find the bottom and right node tcx.Basin.BottomNode = tcx.Basin.LeftNode; while (tcx.Basin.BottomNode.Next != null && tcx.Basin.BottomNode.Point.Y >= tcx.Basin.BottomNode.Next.Point.Y) { tcx.Basin.BottomNode = tcx.Basin.BottomNode.Next; } if (tcx.Basin.BottomNode == tcx.Basin.LeftNode) { // No valid basin return; } tcx.Basin.RightNode = tcx.Basin.BottomNode; while (tcx.Basin.RightNode.Next != null && tcx.Basin.RightNode.Point.Y < tcx.Basin.RightNode.Next.Point.Y) { tcx.Basin.RightNode = tcx.Basin.RightNode.Next; } if (tcx.Basin.RightNode == tcx.Basin.BottomNode) { // No valid basins return; } tcx.Basin.Width = tcx.Basin.RightNode.Point.X - tcx.Basin.LeftNode.Point.X; tcx.Basin.LeftHighest = tcx.Basin.LeftNode.Point.Y > tcx.Basin.RightNode.Point.Y; FillBasinReq(tcx, tcx.Basin.BottomNode); }
private void FillLeftConcaveEdgeEvent(SweepContext tcx, Edge edge, Node node) { Fill(tcx, node.Prev); if (node.Prev.Point != edge.P) { // Next above or below edge? if (TriUtil.Orient2d(edge.Q, node.Prev.Point, edge.P) == Winding.CW) { // Below if (TriUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Winding.CW) { // Next is concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Next is convex } } } }
private bool IsShallow(SweepContext tcx, Node node) { double height; if (tcx.Basin.LeftHighest) { height = tcx.Basin.LeftNode.Point.Y - node.Point.Y; } else { height = tcx.Basin.RightNode.Point.Y - node.Point.Y; } // if shallow stop filling if (tcx.Basin.Width > height) { return(true); } return(false); }
private void FlipEdgeEvent(SweepContext tcx, TriPoint ep, TriPoint eq, Triangle t, TriPoint p) { Triangle ot = t.NeighborAcross(p); TriPoint op = ot.OppositePoint(t, p); if (TriUtil.InScanArea(p, t.PointCCW(p), t.PointCW(p), op)) { // Lets rotate shared edge one vertex CW RotateTrianglePair(t, p, ot, op); tcx.MapTriangleToNodes(t); tcx.MapTriangleToNodes(ot); if (p == eq && op == ep) { if (eq == tcx.EdgeEvent.ConstrainedEdge.Q && ep == tcx.EdgeEvent.ConstrainedEdge.P) { t.MarkConstrainedEdge(ep, eq); ot.MarkConstrainedEdge(ep, eq); Legalize(tcx, t); Legalize(tcx, ot); } else { // XXX: I think one of the triangles should be legalized here? } } else { Winding o = TriUtil.Orient2d(eq, op, ep); t = NextFlipTriangle(tcx, o, t, ot, p, op); FlipEdgeEvent(tcx, ep, eq, t, p); } } else { TriPoint newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, t, ot, newP); EdgeEvent(tcx, ep, eq, t, p); } }
private void FillAdvancingFront(SweepContext tcx, Node n) { // Fill right holes Node node = n.Next; while (node.Next != null) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) { break; } Fill(tcx, node); node = node.Next; } // Fill left holes node = n.Prev; while (node.Prev != null) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) { break; } Fill(tcx, node); node = node.Prev; } // Fill right basins if (n.Next != null && n.Next.Next != null) { double angle = BasinAngle(n); if (angle < TriUtil.PI_3div4) { FillBasin(tcx, n); } } }
private Triangle NextFlipTriangle(SweepContext tcx, Winding o, Triangle t, Triangle ot, TriPoint p, TriPoint op) { int edge_index; if (o == Winding.CCW) { // ot is not crossing edge after flip edge_index = ot.EdgeIndex(p, op); ot.DelaunayEdge[edge_index] = true; Legalize(tcx, ot); ot.ClearDelaunayEdges(); return(t); } // t is not crossing edge after flip edge_index = t.EdgeIndex(p, op); t.DelaunayEdge[edge_index] = true; Legalize(tcx, t); t.ClearDelaunayEdges(); return(ot); }
private Node NewFrontTriangle(SweepContext tcx, TriPoint point, Node node) { Triangle triangle = new Triangle(point, node.Point, node.Next.Point); triangle.MarkNeighbor(node.Triangle); tcx.AddToMap(triangle); Node new_node = new Node(point); _nodes.Add(new_node); new_node.Next = node.Next; new_node.Prev = node; node.Next.Prev = new_node; node.Next = new_node; if (!Legalize(tcx, triangle)) { tcx.MapTriangleToNodes(triangle); } return(new_node); }
private void FillRightConvexEdgeEvent(SweepContext tcx, Edge edge, Node node) { // Next concave or convex? if (TriUtil.Orient2d(node.Next.Point, node.Next.Next.Point, node.Next.Next.Next.Point) == Winding.CCW) { // Concave FillRightConcaveEdgeEvent(tcx, edge, node.Next); } else { // Convex // Next above or below edge? if (TriUtil.Orient2d(edge.Q, node.Next.Next.Point, edge.P) == Winding.CCW) { // Below FillRightConvexEdgeEvent(tcx, edge, node.Next); } else { // Above } } }
private void FlipScanEdgeEvent(SweepContext tcx, TriPoint ep, TriPoint eq, Triangle flip_triangle, Triangle t, TriPoint p) { Triangle ot = t.NeighborAcross(p); TriPoint op = ot.OppositePoint(t, p); if (TriUtil.InScanArea(eq, flip_triangle.PointCCW(eq), flip_triangle.PointCW(eq), op)) { // flip with new edge op->eq FlipEdgeEvent(tcx, eq, op, ot, op); // TODO: Actually I just figured out that it should be possible to // improve this by getting the next ot and op before the the above // flip and continue the flipScanEdgeEvent here // set new ot and op here and loop back to inScanArea test // also need to set a new flip_triangle first // Turns out at first glance that this is somewhat complicated // so it will have to wait. } else { TriPoint newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, flip_triangle, ot, newP); } }
private void EdgeEvent(SweepContext tcx, TriPoint ep, TriPoint eq, Triangle triangle, TriPoint point) { if (IsEdgeSideOfTriangle(triangle, ep, eq)) { return; } TriPoint p1 = triangle.PointCCW(point); Winding o1 = TriUtil.Orient2d(eq, p1, ep); if (o1 == Winding.Collinear) { if (triangle.Contains(eq, p1)) { triangle.MarkConstrainedEdge(eq, p1); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.EdgeEvent.ConstrainedEdge.Q = p1; triangle = triangle.NeighborAcross(point); EdgeEvent(tcx, ep, p1, triangle, p1); } else { throw new NotSupportedException("EdgeEvent - collinear points not supported"); } return; } TriPoint p2 = triangle.PointCW(point); Winding o2 = TriUtil.Orient2d(eq, p2, ep); if (o2 == Winding.Collinear) { if (triangle.Contains(eq, p2)) { triangle.MarkConstrainedEdge(eq, p2); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.EdgeEvent.ConstrainedEdge.Q = p2; triangle = triangle.NeighborAcross(point); EdgeEvent(tcx, ep, p2, triangle, p2); } else { throw new NotSupportedException("EdgeEvent - collinear points not supported"); } return; } if (o1 == o2) { // Need to decide if we are rotating CW or CCW to get to a triangle // that will cross edge if (o1 == Winding.CW) { triangle = triangle.NeighborCCW(point); } else { triangle = triangle.NeighborCW(point); } EdgeEvent(tcx, ep, eq, triangle, point); } else { // This triangle crosses constraint so lets flippin start! FlipEdgeEvent(tcx, ep, eq, triangle, point); } }
private bool Legalize(SweepContext tcx, Triangle t) { // To legalize a triangle we start by finding if any of the three edges // violate the Delaunay condition for (int i = 0; i < 3; i++) { if (t.DelaunayEdge[i]) { continue; } Triangle ot = t.GetNeighbor(i); if (ot != null) { TriPoint p = t.Points[i]; TriPoint op = ot.OppositePoint(t, p); int oi = ot.Index(op); // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) // then we should not try to legalize if (ot.ConstrainedEdge[oi] || ot.DelaunayEdge[oi]) { t.ConstrainedEdge[i] = ot.ConstrainedEdge[oi]; continue; } bool inside = Incircle(p, t.PointCCW(p), t.PointCW(p), op); if (inside) { // Lets mark this shared edge as Delaunay t.DelaunayEdge[i] = true; ot.DelaunayEdge[oi] = true; // Lets rotate shared edge one vertex CW to legalize it RotateTrianglePair(t, p, ot, op); // We now got one valid Delaunay Edge shared by two triangles // This gives us 4 new edges to check for Delaunay // Make sure that triangle to node mapping is done only one time for a specific triangle bool not_legalized = !Legalize(tcx, t); if (not_legalized) { tcx.MapTriangleToNodes(t); } not_legalized = !Legalize(tcx, ot); if (not_legalized) { tcx.MapTriangleToNodes(ot); } // Reset the Delaunay edges, since they only are valid Delaunay edges // until we add a new triangle or point. // XXX: need to think about this. Can these edges be tried after we // return to previous recursive level? t.DelaunayEdge[i] = false; ot.DelaunayEdge[oi] = false; // If triangle have been legalized no need to check the other edges since // the recursive legalization will handles those so we can end here. return(true); } } } return(false); }