// PROTECTED METHODS /// <summary> /// Merge left and right most edges together to construct a cycling triangulation. /// Needed for sphere for example after normal triangulation step. /// </summary> protected void CyclingMerge() { // @TODO Make sure CyclingMerge compute a valide triangulation QuadEdge <TEdge> baseEdge = _mesh.RightMostEdge.Rnext; QuadEdge <TEdge> lCand = baseEdge.Sym.Onext; QuadEdge <TEdge> rCand = baseEdge.Oprev; // Get edges CCW order from left extremum until reach right extremum // to complete the sphere triangulation // All edges must be stored first because pointer are updated // during construction and will eventually leads to infinite loop ... var toCheckEdge = rCand.LeftEdges(true).ToList(); foreach (QuadEdge <TEdge> rightEdge in toCheckEdge) { if (rightEdge.Destination != lCand.Destination) { // Connect rightEdge.Destination to baseEdge.Destination // Construct a fan baseEdge = QuadEdge <TEdge> .Connect(rightEdge, baseEdge.Sym); } else { break; } } }
/// <summary> /// Locates an edge e, such that either v is on e, or e is an edge of a triangle containing v. /// The search starts from the last located edge amd proceeds on the general direction of v. /// </summary> public QuadEdge Locate(Vertex v) { if (! _lastEdge.IsLive) { Init(); } QuadEdge e = _subdiv.LocateFromEdge(v, _lastEdge); _lastEdge = e; return e; }
/// <summary> /// Locates an edge e, such that either v is on e, or e is an edge of a triangle containing v. /// The search starts from the last located edge and proceeds on the general direction of v. /// </summary> public QuadEdge Locate(Vertex v) { if (!_lastEdge.IsLive) { Init(); } var e = _subdiv.LocateFromEdge(v, _lastEdge); _lastEdge = e; return(e); }
private static bool CanMoveForward(QuadEdge e, QuadEdge baseEdge) { if (e == baseEdge) { return(true); } if (e == baseEdge.Inverse) { return(false); } return(e.Origin.X > e.Destination.X); }
/// <summary> /// True if <paramref name="pos"/> inside the convex hull formed by the triangulation. /// </summary> /// <param name="pos">The position to test</param> public bool InsideConvexHull(Vec3 pos) { QuadEdge <T> boundEdge = RightMostEdge; foreach (QuadEdge <T> hullEdge in boundEdge.RightEdges(CCW:false)) { if (Geometry.RightOf(pos, hullEdge)) { // Must be outside because hullEdge right face is outside return(false); } } return(true); }
private async Task <Partition> Subdivide(int lidx, int ridx) { Debug.Assert(lidx != ridx); if (ridx - lidx == 1) //2 points { QuadEdge edge = QuadEdge.MakeEdge(this._points[lidx], this._points[lidx + 1]); return(new Partition { Left = edge, Right = edge.Inverse }); } if (ridx - lidx == 2) //3 points { QuadEdge a = QuadEdge.MakeEdge(this._points[lidx], this._points[lidx + 1]); QuadEdge b = QuadEdge.MakeEdge(this._points[lidx + 1], this._points[lidx + 2]); QuadEdge.Splice(a.Inverse, b); if (Point.IsCounterClockWise(this._points[lidx], this._points[lidx + 1], this._points[lidx + 2])) { QuadEdge c = QuadEdge.ConnectLeft(b, a); return(new Partition { Left = a, Right = b.Inverse }); } if (Point.IsCounterClockWise(this._points[lidx], this._points[lidx + 2], this._points[lidx + 1])) { QuadEdge c = QuadEdge.ConnectLeft(b, a); return(new Partition { Left = c.Inverse, Right = c }); } return(new Partition { Left = a, Right = b.Inverse }); } int midx = (lidx + ridx) / 2; Task <Partition> leftTask = Subdivide(lidx, midx); Task <Partition> rightTask = Subdivide(midx + 1, ridx); return(Merge(await leftTask, await rightTask)); }
/// <summary> /// Construct triangles based on Delaunay triangulation. /// </summary> protected override IEnumerable <Vec3> ExportTriangles() { // FIFO var queue = new Queue <QuadEdge <TEdge> >(); // Start at the far right QuadEdge <TEdge> first = _mesh.RightMostEdge; queue.Enqueue(first); // Visit all edge of the convex hull in CW order and // add opposite edges to queue foreach (QuadEdge <TEdge> hullEdge in first.RightEdges(CCW:false)) { // Enqueue same edge but with opposite direction queue.Enqueue(hullEdge.Sym); hullEdge.Tag = !_mesh.VisitedTagState; // Because mesh does not have any boundary this is also a triangle yield return(hullEdge.Origin); } // Convex hull now closed. Start triangles construction while (queue.Count > 0) { QuadEdge <TEdge> edge = queue.Dequeue(); if (edge.Tag == _mesh.VisitedTagState) { foreach (QuadEdge <TEdge> current in edge.RightEdges(CCW:false)) { if (current.Sym.Tag == _mesh.VisitedTagState) { queue.Enqueue(current.Sym); } current.Tag = !_mesh.VisitedTagState; yield return(current.Origin); } } } // Inverse flag to be able to traverse again at next call _mesh.SwitchInternalFlag(); }
private static List <Point> GetResult(Partition partition) { var result = new List <Point>(); QuadEdge baseEdge = QuadEdge.ConnectLeft(partition.Left.Inverse, partition.Right); QuadEdge e = baseEdge.DestinationNext.Inverse; Point u = baseEdge.Destination; while (true) { while (e != baseEdge && !CanMoveForward(e.DestinationNext, baseEdge)) { u = e.Destination; e = e.DestinationNext.Inverse; } if (e != baseEdge) { result.Add(e.Origin); result.Add(e.Destination); } while (!CanMoveForward(e.OriginNext, baseEdge)) { result.Add(u); if (u == baseEdge.Destination) { baseEdge.Delete(); return(result); } e = e.OriginNext.Inverse; while (CanMoveForward(e.DestinationNext, baseEdge)) { e = e.DestinationNext; } u = e.Origin; result.Add(e.Origin); result.Add(e.Destination); } e = e.OriginNext; } }
private static void LowestCommonTangent(Partition left, Partition right, out QuadEdge leftLowest, out QuadEdge rightLowest) { leftLowest = left.Right; rightLowest = right.Left; while (true) { if (rightLowest.Origin.IsLeftOf(leftLowest)) { leftLowest = leftLowest.LeftNext; } else if (leftLowest.Origin.IsRightOf(rightLowest)) { rightLowest = rightLowest.RightPrevious; } else { break; } } }
/// <summary> /// Find correct position for a voronoi site that should be at infinite /// Assume primalEdge.Rot.Origin as the vertex to compute, that is /// there should be no vertex on the right of primalEdge. /// Site computed is the destination of a segment in a direction normal to /// the tangent vector of the primalEdge (destination - origin) with /// its symetrical (primalEdge.RotSym.Origin) as origin. /// Radius should be choose higher enough to avoid neighbor voronoi points /// to be further on. A good guest is the maximal distance between non infinite /// voronoi vertices or five times the maximal distance between delaunay vertices. /// </summary> /// <remarks> /// If primalEdge.RotSym.Origin is null, then its value is computed first /// using CircumCenter2D because this vertex is always inside a delaunay triangle. /// </remarks> protected Vec3 ConstructAtInfinity(QuadEdge <TEdge> primalEdge, double radius, Func <Vec3, Vec3, Vec3, Vec3> centerCalculator) { var rotSym = primalEdge.RotSym; // Find previous voronoi site if (rotSym.Origin == null) { rotSym.Origin = centerCalculator(primalEdge.Origin, primalEdge.Destination, primalEdge.Onext.Destination); } double xCenter = rotSym.Origin.X; double yCenter = rotSym.Origin.Y; // Compute normalized tangent of primal edge scaled by radius double xTangent = primalEdge.Destination.X - primalEdge.Origin.X; double yTangent = primalEdge.Destination.Y - primalEdge.Origin.Y; double dist = Math.Sqrt(xTangent * xTangent + yTangent * yTangent); xTangent /= dist; yTangent /= dist; xTangent *= radius; yTangent *= radius; // Add vertex using edge dual destination as origin // in direction normal to the primal edge Vec3 normal = new Vec3(xCenter - yTangent, yCenter + xTangent, rotSym.Origin.Z); // If new voronoi vertex is on the left of the primal edge // we used the wrong normal vector --> get its opposite if (Geometry.LeftOf(normal, primalEdge)) { normal = new Vec3(xCenter + yTangent, yCenter - xTangent, rotSym.Origin.Z); } return(normal); }
private void Init() { _lastEdge = FindEdge(); }
/// <summary> /// Triangulate the set of points using a divide and conquer approach. /// </summary> private QuadEdge <T>[] Triangulate(Vec3[] pts) { QuadEdge <T> a, b, c, nextCand; // Only two points -> One edge if (pts.Length == 2) { a = QuadEdge <T> .MakeEdge(pts[0], pts[1]); return(new QuadEdge <T>[] { a, a.Sym }); } // Only tree points else if (pts.Length == 3) { a = QuadEdge <T> .MakeEdge(pts[0], pts[1]); b = QuadEdge <T> .MakeEdge(pts[1], pts[2]); QuadEdge <T> .Splice(a.Sym, b); // Closing triangle if (Geometry.Ccw(pts[0], pts[1], pts[2])) { c = QuadEdge <T> .Connect(b, a); return(new QuadEdge <T>[] { a, b.Sym }); } else if (Geometry.Ccw(pts[0], pts[2], pts[1])) { c = QuadEdge <T> .Connect(b, a); return(new QuadEdge <T>[] { c.Sym, c }); } else { // Points are collinear return(new QuadEdge <T>[] { a, b.Sym }); } } // SPLITTING // Divide them halfsize recursively int halfLength = (pts.Length + 1) / 2; QuadEdge <T>[] left = Triangulate(pts.Take(halfLength).ToArray()); QuadEdge <T>[] right = Triangulate(pts.Skip(halfLength).ToArray()); // MERGING // From left to right QuadEdge <T> ldo = left[0]; QuadEdge <T> ldi = left[1]; QuadEdge <T> rdi = right[0]; QuadEdge <T> rdo = right[1]; // Compute lower common tangent to be able to merge both triangulations bool crossEdgeNotFound = true; while (crossEdgeNotFound) { if (Geometry.LeftOf(rdi.Origin, ldi)) { ldi = ldi.Lnext; } else if (Geometry.RightOf(ldi.Origin, rdi)) { rdi = rdi.Rprev; } else { crossEdgeNotFound = false; } } // Start merging // 1) Creation of the baseEdge quad edge (See Fig.21) QuadEdge <T> baseEdge = QuadEdge <T> .Connect(rdi.Sym, ldi); if (ldi.Origin == ldo.Origin) { ldo = baseEdge.Sym; } if (rdi.Origin == rdo.Origin) { rdo = baseEdge; } // 2) Rising bubble (See Fig. 22) bool upperCommonTangentNotFound = true; while (upperCommonTangentNotFound) { // Locate the first L site (lCand.Destination) to be encountered // by the rising bubble, and delete L edges out of baseEdge.Destination // that fail the circle test. QuadEdge <T> lCand = baseEdge.Sym.Onext; if (IsValid(lCand, baseEdge)) { while (Geometry.InCircumCercle2D(lCand.Onext.Destination, baseEdge.Destination, baseEdge.Origin, lCand.Destination)) { nextCand = lCand.Onext; QuadEdge <T> .Delete(lCand); lCand = nextCand; } } // Same for the right part (Symetrically) QuadEdge <T> rCand = baseEdge.Oprev; if (IsValid(rCand, baseEdge)) { while (Geometry.InCircumCercle2D(rCand.Oprev.Destination, baseEdge.Destination, baseEdge.Origin, rCand.Destination)) { nextCand = rCand.Oprev; QuadEdge <T> .Delete(rCand); rCand = nextCand; } } // Upper common tangent is baseEdge if (!IsValid(lCand, baseEdge) && !IsValid(rCand, baseEdge)) { upperCommonTangentNotFound = false; } // Construct new cross edge between left and right // The next cross edge is to be connected to either lcand.Dest or rCand.Dest // If both are valid, then choose the appropriate one using the // Geometry.InCircumCercle2D test else if (!IsValid(lCand, baseEdge) || ( IsValid(rCand, baseEdge) && Geometry.InCircumCercle2D(rCand.Destination, lCand.Destination, lCand.Origin, rCand.Origin) ) ) { // Cross edge baseEdge added from rCand.Destination to basel.Destination baseEdge = QuadEdge <T> .Connect(rCand, baseEdge.Sym); } else { // Cross edge baseEdge added from baseEdge.Origin to lCand.Destination baseEdge = QuadEdge <T> .Connect(baseEdge.Sym, lCand.Sym); } } return(new QuadEdge <T>[] { ldo, rdo }); }
/// <summary> /// Insert a new site inside an existing delaunay triangulation. New site /// must be inside the convex hull of previoulsy added sites. /// Set <paramref name="safe"/> to true to first test if new site is correct. /// </summary> /// <param name="newPos">The position to of new site</param> /// <param name="edge">Edge used to start locate process. Can be used to speed up search.</param> /// <param name="safe">If true, check if <paramref name="newPos"/> inside the convex hull.</param> public bool Insert(Vec3 newPos, QuadEdge <T> edge = null, bool safe = false) { if (safe) { bool result = InsideConvexHull(newPos); if (!result) { // Cannot add site not already inside the convex hull return(false); } } // Start somewhere if no hint if (edge == null) { edge = _leftRightEdges[1]; } // Locate edge (must be inside the boundary) QuadEdge <T> foundE = Locate(newPos, edge, safe: false); // Site already triangulated if (Geometry.AlmostEquals(foundE.Origin, newPos) || Geometry.AlmostEquals(foundE.Destination, newPos)) { return(false); } // On an edge ? if (Geometry.AlmostColinear(foundE.Origin, foundE.Destination, newPos)) { var temp = foundE.Oprev; QuadEdge <T> .Delete(foundE); foundE = temp; } // Create new edge to connect new site to neighbors QuadEdge <T> baseE = QuadEdge <T> .MakeEdge(foundE.Origin, newPos); Vec3 first = baseE.Origin; QuadEdge <T> .Splice(baseE, foundE); // Up to 4 vertices if new site on an edge do { baseE = QuadEdge <T> .Connect(foundE, baseE.Sym); foundE = baseE.Oprev; } while (foundE.Destination != first); // Fill star shaped polygon and swap suspect edges // Adding a new point can break old condition about InCircle test foundE = baseE.Oprev; bool shouldExit = false; do { var tempE = foundE.Oprev; if (Geometry.RightOf(tempE.Destination, foundE) && Geometry.InCircumCercle2D(newPos, foundE.Origin, tempE.Destination, foundE.Destination)) { QuadEdge <T> .Swap(foundE); // tempE != foundE.Oprev after swap foundE = foundE.Oprev; } else if (foundE.Origin == first) { // No more suspect edge ... exit shouldExit = true; } else { // Get next suspect edge from top to bottom foundE = foundE.Onext.Lprev; } } while (!shouldExit); return(true); }
/// <summary> /// Locate the closest with respect to following constraints: /// - <paramref name="pos"/> is on the line of returned edge /// - <paramref name="pos"/> is inside the left face of returned edge /// /// If site outside the convex hull Locate will loop forever looking for /// a corresponding edge that does not exists ... unless you first check /// you are in the convex hull or set <paramref name="checkBoundFirst"/> to true. /// </summary> /// <param name="pos">The position to locate</param> /// <param name="edge">Edge used to start locate process. Can be used to speed up search.</param> /// <param name="safe">If true, first check if pos in convex hull of triangulation</param> public QuadEdge <TEdge> Locate(Vec3 pos, QuadEdge <TEdge> edge = null, bool safe = false) { return(_mesh.Locate(pos, edge, safe)); }
// PROTECTED METHOD /// <summary> /// Construct voronoi face based on Delaunay triangulation. Vertices at infinity /// are define based on radius parameter. It should be large enough to avoid /// some circumcenters (finite voronoi vertices) to be further on. /// </summary> /// <remarks> /// Each face is yield just after their construction. Then it's neighborhood /// is not guarantee to be constructed. /// </remarks> /// <param name="radius">Distance used to construct site that are at infinity.</param> protected IEnumerable <Face <TEdge, TFace> > ExportFaces(Func <Vec3, Vec3, Vec3, Vec3> centerCalculator, double radius) { // FIFO var queue = new Queue <QuadEdge <TEdge> >(); // Start at the far left QuadEdge <TEdge> first = _mesh.LeftMostEdge; // @TODO Bounds List <QuadEdge <TEdge> > bounds = new List <QuadEdge <TEdge> >(); // Visit all edge of the convex hull to compute dual vertices // at infinity by looping in a CW order over edges with same left face. foreach (QuadEdge <TEdge> hullEdge in first.LeftEdges(CCW:false)) { // Construct a new face // First infinite voronoi vertex if (hullEdge.Rot.Destination == null) { hullEdge.Rot.Destination = ConstructAtInfinity(hullEdge.Sym, radius, centerCalculator); } // Add other vertices by looping over hullEdge origin in CW order (Oprev) foreach (QuadEdge <TEdge> current in hullEdge.EdgesFrom(CCW:false)) { if (current.Rot.Origin == null) { // Delaunay edge on the boundary if (Geometry.LeftOf(current.Oprev.Destination, current)) { current.Rot.Origin = ConstructAtInfinity(current, radius, centerCalculator); } else { current.Rot.Origin = centerCalculator(current.Origin, current.Destination, current.Oprev.Destination); // Speed up computation of point coordinates // All edges sharing the same origin should have same // geometrical origin foreach (QuadEdge <TEdge> otherDual in current.Rot.EdgesFrom()) { otherDual.Origin = current.Rot.Origin; } } } if (current.Sym.Tag == _mesh.VisitedTagState) { queue.Enqueue(current.Sym); bounds.Add(current.Sym); } current.Tag = !_mesh.VisitedTagState; } // After face construction over yield return(new Face <TEdge, TFace>(hullEdge, true, true)); } // Convex hull now closed --> Construct bounded voronoi faces while (queue.Count > 0) { QuadEdge <TEdge> edge = queue.Dequeue(); if (edge.Tag == _mesh.VisitedTagState) { // Construct a new face foreach (QuadEdge <TEdge> current in edge.EdgesFrom(CCW:false)) { if (current.Rot.Origin == null) { current.Rot.Origin = centerCalculator(current.Origin, current.Destination, current.Oprev.Destination); // Speed up computation of point coordinates // All edges sharing the same origin have same // geometrical origin foreach (QuadEdge <TEdge> otherDual in current.Rot.EdgesFrom()) { otherDual.Origin = current.Rot.Origin; } } if (current.Sym.Tag == _mesh.VisitedTagState) { queue.Enqueue(current.Sym); } current.Tag = !_mesh.VisitedTagState; } // After face construction over if (bounds.Contains(edge)) { yield return(new Face <TEdge, TFace>(edge, true, false)); } else { yield return(new Face <TEdge, TFace>(edge, false, false)); } } } // Inverse flag to be able to traverse again at next call _mesh.SwitchInternalFlag(); }
/// <summary> /// Insert a new site inside an existing delaunay triangulation. New site /// must be inside the convex hull of previoulsy added sites. /// Set <paramref name="safe"/> to true to first test if new site is correct. /// </summary> /// <param name="newPos">The position to of new site</param> /// <param name="edge">Edge used to start locate process. Can be used to speed up search.</param> /// <param name="safe">If true, check if <paramref name="safe"/> inside the convex hull.</param> public bool Insert(Vec3 newPos, QuadEdge <TEdge> edge = null, bool safe = false) { return(_mesh.Insert(newPos, edge, safe)); }
/// <summary> /// Checks whether this instance is to the right of <paramref name="edge"/> /// </summary> /// <param name="edge"></param> /// <returns></returns> public bool IsRightOf(QuadEdge edge) { return(IsCounterClockWise(this, edge.Destination, edge.Origin)); }
/// <summary> /// Construct voronoi face based on Delaunay triangulation. Vertices at infinity /// are define based on radius parameter. It should be large enough to avoid /// some circumcenters (finite voronoi vertices) to be further on. /// </summary> /// <remarks> /// Each face is yield just after their construction. Then it's neighborhood /// is not guarantee to be constructed. /// </remarks> protected IEnumerable <Face <TEdge, TFace> > ExportFaces(Func <Vec3, Vec3, Vec3, Vec3> centerCalculator, double scaleFactor) { // FIFO var queue = new Queue <QuadEdge <TEdge> >(); // Start at the far left QuadEdge <TEdge> first = LeftMostEdge; // @TODO Make sure CyclingMerge compute a valide triangulation // Construct first face using Centroid because // triangulation is not necessary delaunay foreach (QuadEdge <TEdge> current in first.EdgesFrom(CCW:false)) { if (current.Rot.Origin == null) { current.Rot.Origin = Geometry.Centroid(Geometry.InvStereographicProjection(current.Origin), Geometry.InvStereographicProjection(current.Destination), Geometry.InvStereographicProjection(current.Oprev.Destination)); double invDistanceScaled = scaleFactor / current.Rot.Origin.Magnitude; current.Rot.Origin *= invDistanceScaled; // Speed up computation of point coordinates // All edges sharing the same origin have same // geometrical origin foreach (QuadEdge <TEdge> otherDual in current.Rot.EdgesFrom()) { otherDual.Origin = current.Rot.Origin; } } if (current.Sym.Tag == _mesh.VisitedTagState) { queue.Enqueue(current.Sym); } current.Tag = !_mesh.VisitedTagState; } yield return(new Face <TEdge, TFace>(first, false, false)); // Convex hull now closed --> Construct bounded voronoi faces while (queue.Count > 0) { QuadEdge <TEdge> edge = queue.Dequeue(); if (edge.Tag == _mesh.VisitedTagState) { // Construct a new face foreach (QuadEdge <TEdge> current in edge.EdgesFrom(CCW:false)) { if (current.Rot.Origin == null) { current.Rot.Origin = centerCalculator(Geometry.InvStereographicProjection(current.Origin), Geometry.InvStereographicProjection(current.Destination), Geometry.InvStereographicProjection(current.Oprev.Destination)); double invDistanceScaled = scaleFactor / current.Rot.Origin.Magnitude; current.Rot.Origin *= invDistanceScaled; // Speed up computation of point coordinates // All edges sharing the same origin have same // geometrical origin foreach (QuadEdge <TEdge> otherDual in current.Rot.EdgesFrom()) { otherDual.Origin = current.Rot.Origin; } } if (current.Sym.Tag == _mesh.VisitedTagState) { queue.Enqueue(current.Sym); } current.Tag = !_mesh.VisitedTagState; } yield return(new Face <TEdge, TFace>(edge, false, false)); } } // Inverse flag to be able to traverse again at next call _mesh.SwitchInternalFlag(); }
/// <summary> /// Locate the closest with respect to following constraints: /// - <paramref name="pos"/> is on the line of returned edge /// - <paramref name="pos"/> is inside the left face of returned edge /// /// If site outside the convex hull Locate will loop forever looking for /// a corresponding edge that does not exists ... unless you first check /// you are in the convex hull or set <paramref name="safe"/> to true. /// </summary> /// <param name="pos">The position to locate</param> /// <param name="edge">Edge used to start locate process. Can be used to speed up search.</param> /// <param name="safe">If true, first check if pos in convex hull of triangulation</param> public QuadEdge <T> Locate(Vec3 pos, QuadEdge <T> edge = null, bool safe = false) { // Check boundary first if (safe) { QuadEdge <T> result = ClosestBoundingEdge(pos); if (result != null) { // pos outside return(result); } } // Start somewhere if no hint if (edge == null) { edge = _leftRightEdges[1]; } // Assume it must be inside ... while (true) { if (pos == edge.Origin || pos == edge.Destination) { return(edge); } else if (Geometry.RightOf(pos, edge)) { edge = edge.Sym; } else if (Geometry.LeftOf(pos, edge.Onext)) { edge = edge.Onext; } else if (Geometry.LeftOf(pos, edge.Dprev)) { edge = edge.Dprev; } else { // Previous triangle edge QuadEdge <T> otherE = edge.Lprev; if (Geometry.AlmostColinear(pos, otherE.Origin, otherE.Destination)) { return(otherE); } // Next triangle edge otherE = edge.Lnext; if (Geometry.AlmostColinear(pos, otherE.Origin, otherE.Destination)) { return(otherE); } return(edge); } } }
/// <summary> /// Return true if Geometry.RightOf(edge.Destination, baseEdge) is true. /// </summary> protected bool IsValid(QuadEdge <T> edge, QuadEdge <T> baseEdge) { // Geometry.Ccw called directly. return(Geometry.Ccw(edge.Destination, baseEdge.Destination, baseEdge.Origin)); }
private static Partition Merge(Partition leftPartition, Partition rightPartition) { QuadEdge left = leftPartition.Left; //ldo QuadEdge right = rightPartition.Right; //rdo LowestCommonTangent(leftPartition, rightPartition, out QuadEdge lowLeft, out QuadEdge lowRight); //ldi, rdi QuadEdge edgeBase = QuadEdge.ConnectLeft(lowRight.Inverse, lowLeft); QuadEdge lcand = edgeBase.RightPrevious; QuadEdge rcand = edgeBase.OriginPrevious; if (edgeBase.Origin == right.Origin) { right = edgeBase; } if (edgeBase.Destination == left.Origin) { left = edgeBase.Inverse; } while (true) { QuadEdge temp = lcand.OriginNext; if (Point.IsCounterClockWise(edgeBase.Origin, temp.Destination, edgeBase.Destination)) { while (edgeBase.Origin.InCircle(lcand.Destination, temp.Destination, lcand.Origin)) { lcand.Delete(); lcand = temp; temp = lcand.OriginNext; } } temp = rcand.OriginPrevious; if (Point.IsCounterClockWise(edgeBase.Origin, temp.Destination, edgeBase.Destination)) { while (edgeBase.Destination.InCircle(temp.Destination, rcand.Destination, rcand.Origin)) { rcand.Delete(); rcand = temp; temp = rcand.OriginPrevious; } } bool leftValid = Point.IsCounterClockWise(edgeBase.Origin, lcand.Destination, edgeBase.Destination); bool rightValid = Point.IsCounterClockWise(edgeBase.Origin, rcand.Destination, edgeBase.Destination); if (!leftValid && !rightValid) { break; } if (!leftValid || rightValid && rcand.Destination.InCircle(lcand.Destination, lcand.Origin, rcand.Origin)) { edgeBase = QuadEdge.ConnectLeft(rcand, edgeBase.Inverse); rcand = edgeBase.Inverse.LeftNext; } else { edgeBase = QuadEdge.ConnectRight(lcand, edgeBase).Inverse; lcand = edgeBase.RightPrevious; } } return(new Partition { Left = left, Right = right }); }
/// <summary> /// Return true if pt is on the left of edge segment /// (edge.Origin -> edge.Destination) /// </summary> public static bool LeftOf <T>(Vec3 pt, QuadEdge <T> edge) { return(Ccw(pt, edge.Origin, edge.Destination)); }
} // End Function GetConcaveHull public Geometry ComputeConcaveHull() { ConformingDelaunayTriangulationBuilder cdtb = new ConformingDelaunayTriangulationBuilder(); cdtb.SetSites(this.geometries); QuadEdgeSubdivision qes = cdtb.GetSubdivision(); IList <QuadEdge> quadEdges = qes.GetEdges(); IList <QuadEdgeTriangle> qeTriangles = QuadEdgeTriangle.CreateOn(qes); IEnumerable <Vertex> qeVertices = qes.GetVertices(false); int iV = 0; foreach (Vertex v in qeVertices) { this.coordinates[v.Coordinate] = iV; this.vertices[iV] = new Vertex(iV, v.Coordinate); iV++; } List <QuadEdge> qeFrameBorder = new List <QuadEdge>(); List <QuadEdge> qeFrame = new List <QuadEdge>(); List <QuadEdge> qeBorder = new List <QuadEdge>(); // here each one more foreach (QuadEdge qe in quadEdges) { if (qes.IsFrameBorderEdge(qe)) { qeFrameBorder.Add(qe); } if (qes.IsFrameEdge(qe)) { qeFrame.Add(qe); } } // Next qe // border for (int j = 0; j < qeFrameBorder.Count; j++) { QuadEdge q = qeFrameBorder[j]; if (!qeFrame.Contains(q)) { qeBorder.Add(q); } } // Next j // deletion of exterior edges foreach (QuadEdge qe in qeFrame) { qes.Delete(qe); } Dictionary <QuadEdge, double> qeDistances = new Dictionary <QuadEdge, double>(); foreach (QuadEdge qe in quadEdges) { qeDistances.Add(qe, qe.ToLineSegment().Length); } DoubleComparator dc = new DoubleComparator(qeDistances); // This doesn't work with dictionary - missing duplicates ... List <KeyValuePair <QuadEdge, double> > qeSorted = new List <KeyValuePair <QuadEdge, double> >(); foreach (KeyValuePair <QuadEdge, double> thisDistance in qeDistances) { qeSorted.Add(thisDistance); } qeSorted.Sort(dc); // edges creation int i = 0; foreach (KeyValuePair <QuadEdge, double> kvp in qeSorted) { LineSegment s = kvp.Key.ToLineSegment(); s.Normalize(); int idS = this.coordinates[s.P0]; int idD = this.coordinates[s.P1]; Vertex oV = this.vertices[idS]; Vertex eV = this.vertices[idD]; Edge edge; if (qeBorder.Contains(kvp.Key)) { oV.IsBorder = true; eV.IsBorder = true; edge = new Edge(i, s, oV, eV, true); if (s.Length < this.threshold) { this.shortLengths[i] = edge; } else { this.lengths[i] = edge; } } else { edge = new Edge(i, s, oV, eV, false); } this.edges[i] = edge; this.segments[s] = i; i++; } // Next qe // hm of linesegment and hm of edges // with id as key // hm of triangles using hm of ls and connection with hm of edges i = 0; foreach (QuadEdgeTriangle qet in qeTriangles) { LineSegment sA = qet.GetEdge(0).ToLineSegment(); LineSegment sB = qet.GetEdge(1).ToLineSegment(); LineSegment sC = qet.GetEdge(2).ToLineSegment(); sA.Normalize(); sB.Normalize(); sC.Normalize(); Edge edgeA = this.edges[this.segments[sA]]; Edge edgeB = this.edges[this.segments[sB]]; Edge edgeC = this.edges[this.segments[sC]]; Triangle triangle = new Triangle(i, qet.IsBorder() ? true : false); triangle.AddEdge(edgeA); triangle.AddEdge(edgeB); triangle.AddEdge(edgeC); edgeA.AddTriangle(triangle); edgeB.AddTriangle(triangle); edgeC.AddTriangle(triangle); this.triangles[i] = triangle; i++; } // Next qet // add triangle neighbourood foreach (Edge edge in this.edges.Values) { if (edge.Triangles.Count != 1) { Triangle tA = edge.Triangles[0]; Triangle tB = edge.Triangles[1]; tA.AddNeighbour(tB); tB.AddNeighbour(tA); } } // concave hull algorithm int index = 0; while (index != -1) { index = -1; Edge e = null; // find the max length (smallest id so first entry) int si = this.lengths.Count; if (si != 0) { KeyValuePair <int, Edge> entry = this.lengths.First(); int ind = entry.Key; if (entry.Value.Geometry.Length > this.threshold) { index = ind; e = entry.Value; } } // End if (si != 0) if (index != -1) { Triangle triangle = e.Triangles[0]; List <Triangle> neighbours = triangle.Neighbours; // irregular triangle test if (neighbours.Count == 1) { this.shortLengths[e.Id] = e; this.lengths.Remove(e.Id); } else { Edge e0 = triangle.Edges[0]; Edge e1 = triangle.Edges[1]; // test if all the vertices are on the border if (e0.OV.IsBorder && e0.EV.IsBorder && e1.OV.IsBorder && e1.EV.IsBorder) { this.shortLengths[e.Id] = e; this.lengths.Remove(e.Id); } else { // management of triangles Triangle tA = neighbours[0]; Triangle tB = neighbours[1]; tA.Border = true; // FIXME not necessarily useful tB.Border = true; // FIXME not necessarily useful this.triangles.Remove(triangle.Id); tA.RemoveNeighbour(triangle); tB.RemoveNeighbour(triangle); // new edges List <Edge> ee = triangle.Edges; Edge eA = ee[0]; Edge eB = ee[1]; Edge eC = ee[2]; if (eA.Border) { this.edges.Remove(eA.Id); eB.Border = true; eB.OV.IsBorder = true; eB.EV.IsBorder = true; eC.Border = true; eC.OV.IsBorder = true; eC.EV.IsBorder = true; // clean the relationships with the triangle eB.RemoveTriangle(triangle); eC.RemoveTriangle(triangle); if (eB.Geometry.Length < this.threshold) { this.shortLengths[eB.Id] = eB; } else { this.lengths[eB.Id] = eB; } if (eC.Geometry.Length < this.threshold) { this.shortLengths[eC.Id] = eC; } else { this.lengths[eC.Id] = eC; } this.lengths.Remove(eA.Id); } // End if (eA.Border) else if (eB.Border) { this.edges.Remove(eB.Id); eA.Border = true; eA.OV.IsBorder = true; eA.EV.IsBorder = true; eC.Border = true; eC.OV.IsBorder = true; eC.EV.IsBorder = true; // clean the relationships with the triangle eA.RemoveTriangle(triangle); eC.RemoveTriangle(triangle); if (eA.Geometry.Length < this.threshold) { this.shortLengths[eA.Id] = eA; } else { this.lengths[eA.Id] = eA; } if (eC.Geometry.Length < this.threshold) { this.shortLengths[eC.Id] = eC; } else { this.lengths[eC.Id] = eC; } this.lengths.Remove(eB.Id); } // End else if (eB.Border) else { this.edges.Remove(eC.Id); eA.Border = true; eA.OV.IsBorder = true; eA.EV.IsBorder = true; eB.Border = true; eB.OV.IsBorder = true; eB.EV.IsBorder = true; // clean the relationships with the triangle eA.RemoveTriangle(triangle); eB.RemoveTriangle(triangle); if (eA.Geometry.Length < this.threshold) { this.shortLengths[eA.Id] = eA; } else { this.lengths[eA.Id] = eA; } if (eB.Geometry.Length < this.threshold) { this.shortLengths[eB.Id] = eB; } else { this.lengths[eB.Id] = eB; } this.lengths.Remove(eC.Id); } // End Else of if (e0.OV.Border && e0.EV.Border && e1.OV.Border && e1.EV.Border) } // End Else of if } // End Else of if (neighbours.Count == 1) } // End if (index != -1) } // Whend // concave hull creation List <LineString> edges = new List <LineString>(); foreach (Edge e in this.lengths.Values) { LineString l = e.Geometry.ToGeometry(this.geomFactory); edges.Add(l); } foreach (Edge e in this.shortLengths.Values) { LineString l = e.Geometry.ToGeometry(this.geomFactory); edges.Add(l); } // merge Operation.Linemerge.LineMerger lineMerger = new Operation.Linemerge.LineMerger(); lineMerger.Add(edges); LineString merge = null; using (IEnumerator <Geometry> en = lineMerger.GetMergedLineStrings().GetEnumerator()) { en.MoveNext(); merge = (LineString)en.Current; } if (merge.IsRing) { LinearRing lr = new LinearRing(merge.CoordinateSequence, this.geomFactory); Polygon concaveHull = new Polygon(lr, null, this.geomFactory); return(concaveHull); } return(merge); } // End Function ComputeConcaveHull