using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Numerics; namespace NaturalNeighbor.Internal { sealed class SubDiv2D_Mutable : SubDiv2D_Base { public SubDiv2D_Mutable(Bounds bounds) : base(bounds) { InitDelaunay(); } public IEnumerable GetVoronoiFacets() { var sequence = _vertices.Skip(4).Select((it, idx) => new NodeId(idx + 4)); return GetVoronoiFacets(sequence); } public IEnumerable GetVoronoiFacets(IEnumerable vertices) { CalcVoronoi(); var buff = ImmutableArray.CreateBuilder(); foreach (var nodeId in vertices) { var facet = CreateVoronoiFacet(nodeId, buff); yield return facet; } } private VoronoiFacet CreateVoronoiFacet(NodeId nodeId, ImmutableArray.Builder buffer) { var v = _vertices[(int) nodeId]; if (v.IsFree || v.IsVirtual) { return new VoronoiFacet(nodeId, v.pt); } buffer.Clear(); int edge = RotateEdge(v.firstEdge, 1); var t = edge; do { var pt = _vertices[EdgeOrg(t)].pt; t = GetEdge(t, TargetEdgeType.NEXT_AROUND_LEFT); int cnt = buffer.Count; if (cnt != 0) { var prevPt = buffer[cnt - 1]; double dx = pt.X - prevPt.X; double dy = pt.Y - prevPt.Y; // Remove duplicate points if (dx * dx + dy * dy <= float.Epsilon) { continue; } // Check convex hull if (cnt > 1 && Utils.IsLeft(buffer[cnt - 2], prevPt, pt) <= 0) { buffer[cnt - 1] = pt; continue; } if (t == edge) { dx = pt.X - buffer[0].X; dy = pt.Y - buffer[0].Y; if (dx * dx + dy * dy < float.Epsilon) { continue; } if (cnt > 1 && Utils.IsLeft(prevPt, pt, buffer[0]) <= 0) { buffer[0] = pt; continue; } } } buffer.Add(pt); } while (t != edge); return new VoronoiFacet(nodeId, v.pt, buffer.ToImmutable()); } public IEnumerable<(Vector2, Vector2)> GetEdges() { for (int i = 4; i < _quadEdges.Count; i++) { var q = _quadEdges[i]; if (!q.IsFree && q.pts[0] > 0 && q.pts[2] > 0) { Vector2 org = _vertices[q.pts[0]].pt; Vector2 dst = _vertices[q.pts[2]].pt; yield return (org, dst); } } } public IEnumerable GetTriangles() { int total = _quadEdges.Count * 4; var edgemask = new bool[total]; for (var i = 4; i < total; i += 2) { if (edgemask[i]) continue; Vector2 a, b, c; int edge_a = i; EdgeOrg(edge_a, out a); if (!Bounds.Contains(a)) continue; int edge_b = GetEdge(edge_a, TargetEdgeType.NEXT_AROUND_LEFT); EdgeOrg(edge_b, out b); if (!Bounds.Contains(b)) continue; int edge_c = GetEdge(edge_b, TargetEdgeType.NEXT_AROUND_LEFT); EdgeOrg(edge_c, out c); if (!Bounds.Contains(c)) continue; edgemask[edge_a] = true; edgemask[edge_b] = true; edgemask[edge_c] = true; yield return new Triangle(a, b, c); } } public IEnumerable GetNodes() { for (int i = 4; i < _vertices.Count; ++i) { var v = _vertices[i]; if (v.IsVirtual || v.IsFree) { continue; } yield return new NodeId(i); } } public int GetFirstEdge(NodeId nodeId) { return _vertices[(int) nodeId].firstEdge; } public List GetBowyerWatsonEnvelope(double x, double y, int locatorEdge) { if (locatorEdge == 0) { return null; } int v2 = EdgeDst(NextEdge(locatorEdge)); double h; int c = locatorEdge; var list = new List(); var stack = new Stack(); while (true) { int n0 = SymEdge(c); int n1 = GetEdge(n0, TargetEdgeType.NEXT_AROUND_LEFT); // nInCircle++ EdgeOrg(n0, out var p1); EdgeOrg(n1, out var p2); EdgeDst(n1, out var p3); double a11 = p1.X - x; double a21 = p2.X - x; double a31 = p3.X - x; double a12 = p1.Y - y; double a22 = p2.Y - y; double a32 = p3.Y - y; h = (a11 * a11 + a12 * a12) * (a21 * a32 - a31 * a22) + (a21 * a21 + a22 * a22) * (a31 * a12 - a11 * a32) + (a31 * a31 + a32 * a32) * (a11 * a22 - a21 * a12); if (h >= 0) { // The vertex is within the circumcircle the associated // triangle. The Thiessen triangle will extend to include // that triangle and, perhaps, its neighbors. // So continue the search. stack.Push(n0); c = n1; } else { if (list.Count > 0 && list[0] == c) { throw new InvalidOperationException("Infininte loop detected."); } list.Add(c); c = GetEdge(c, TargetEdgeType.NEXT_AROUND_LEFT); if (stack.Count > 0) { var p = stack.Peek(); while (c == p) { stack.Pop(); c = GetEdge(SymEdge(c), TargetEdgeType.NEXT_AROUND_LEFT); if (stack.Count == 0) { break; } p = stack.Peek(); } } if (c == locatorEdge) { break; } } } //DumpEnvelope(list, x, y); return list; } //private void DumpEnvelope(List list, double x, double y) //{ // System.Text.StringBuilder sb = new System.Text.StringBuilder(); // sb.AppendLine($"Enevelope({x}\ty={y})"); // for (int i = 0; i < list.Count; ++i) // { // EdgeOrg(list[i], out var pt); // sb.AppendLine($"p[{i}]\t{pt.X}\t{pt.Y}"); // } // Trace.Write(sb.ToString()); //} public double[] GetBarycentricCoordinates(List envelope, double x, double y) { double[] weights = new double[envelope.Count]; var g2 = default(Circumcircle); var c1 = default(Circumcircle); double wSum = 0; for (int i0 = 0, nEdge = envelope.Count; i0 < nEdge; i0++) { int i1 = (i0 + 1) % nEdge; // next edge in the polygon var e0 = envelope[i0]; var e1 = envelope[i1]; EdgeOrg(e0, out var a); var nodeB = EdgeOrg(e1, out var b); // Same as EdgeDst(e0) var currentNode = new NodeId(nodeB); EdgeDst(e1, out var c); Circumcircle g1; double ax = a.X - x; double ay = a.Y - y; double bx = b.X - x; double by = b.Y - y; double cx = c.X - x; double cy = c.Y - y; double xm1 = (ax + bx) * 0.5; double ym1 = (ay + by) * 0.5; double xm2 = (bx + cx) * 0.5; double ym2 = (by + cy) * 0.5; // for the first edge processed, the code needs to initialize values // for c0 and c3. But after that, the code can reuse values from // the previous calculation. if (i0 == 0) { g1 = Utils.ComputeCurcumcircle(ax, ay, bx, by, 0, 0); if (!currentNode.IsSentinel) { EdgeDst(GetEdge(e0, TargetEdgeType.NEXT_AROUND_ORG), out var pt); c1 = Utils.ComputeCurcumcircle(ax, ay, bx, by, pt.X - x, pt.Y - y); } } else { g1 = g2; } g2 = Utils.ComputeCurcumcircle(bx, by, cx, cy, 0, 0); if (currentNode.IsSentinel) { EdgeDst(GetEdge(e1, TargetEdgeType.NEXT_AROUND_LEFT), out var pt); c1 = Utils.ComputeCurcumcircle(bx, by, cx, cy, pt.X - x, pt.Y - y); continue; } // compute the reduced "component area" of the Theissen polygon // constructed around point B, the second point of edge[i0]. double wXY = (xm1 * g1.CenterY - g1.CenterX * ym1) + (g1.CenterX * g2.CenterY - g2.CenterX * g1.CenterY) + (g2.CenterX * ym2 - xm2 * g2.CenterY); // compute the full "component area" of the Theissen polygon // constructed around point B, the second point of edge[i0] double wThiessen = xm1 * c1.CenterY- c1.CenterX * ym1; var n = GetEdge(e0, TargetEdgeType.NEXT_AROUND_LEFT); while (n != e1) { var n1 = SymEdge(n); n = GetEdge(n1, TargetEdgeType.NEXT_AROUND_LEFT); EdgeOrg(n1, out a); // EdgeOrg(n, out b); same as original b EdgeDst(n, out c); ax = a.X - x; ay = a.Y - y; bx = b.X - x; by = b.Y - y; cx = c.X - x; cy = c.Y - y; Circumcircle c2 = Utils.ComputeCurcumcircle(ax, ay, bx, by, cx, cy); wThiessen += c1.CenterX * c2.CenterY - c2.CenterX * c1.CenterY; c1 = c2; } wThiessen += c1.CenterX * ym2 - xm2 * c1.CenterY; // Compute wDelta, the amount of area that the Theissen polygon // constructed around vertex B would yield to an insertion at // the query point. // for convenience, both the wXY and wThiessen weights were // computed in a clockwise order, which means they are the // negative of what we need for the weight computation, so // negate them and -(wTheissen-wXY) becomes wXY-wTheissen // Also, there would normally be a divide by 2 factor from the // shoelace area formula, but that is ommitted because it will // drop out when we unitize the sum of the set of the weights. double wDelta = wXY - wThiessen; wSum += wDelta; weights[i1] = wDelta; } // Normalize the weights for (int i = 0; i < weights.Length; ++i) { weights[i] /= wSum; } return weights; } public NodeId EdgeOrigin(int edge) => new NodeId(EdgeOrg(edge)); public NodeId EdgeDestination(int edge) => new NodeId(EdgeDst(edge)); public NodeId? FindNearest(Vector2 pt, SearchContext context, out Vector2 nearestVertexPt) { if (!_validGeometry) { CalcVoronoi(); } // Find the closest delaunay triangle (one of it's corners) var loc = Locate(pt, context); if (loc == PointLocationType.Vertex) { nearestVertexPt = _vertices[context.Vertex].pt; return new NodeId(context.Vertex); } if (loc != PointLocationType.Edge && loc != PointLocationType.Inside) { nearestVertexPt = default(Vector2); return null; } int vertex = 0; EdgeOrg(context.Edge, out var start); Vector2 diff = pt - start; // Switch to voronoi space int edge = RotateEdge(context.Edge, 1); int total = _vertices.Count; for (int i = 0; i < total; ++i) { Vector2 t; while (true) { var dest = EdgeDst(edge, out t); Debug.Assert((int) dest > 0); if (IsRightOf2(t, start, diff) >= 0) break; edge = GetEdge(edge, TargetEdgeType.NEXT_AROUND_LEFT); } while (true) { var org = EdgeOrg(edge, out t); Debug.Assert((int) org > 0); if (IsRightOf2(t, start, diff) < 0) break; edge = GetEdge(edge, TargetEdgeType.PREV_AROUND_LEFT); } EdgeDst(edge, out var tempDest); EdgeOrg(edge, out t); if (IsRightOf2(pt, t, tempDest - t) >= 0) { vertex = EdgeOrg(RotateEdge(edge, 3)); break; } edge = SymEdge(edge); } if (vertex > 0) { nearestVertexPt = _vertices[vertex].pt; return new NodeId(vertex); } else { nearestVertexPt = default(Vector2); return null; } } public (Triangle, NodeId, NodeId, NodeId) GetTriangle(int edge) { int edge1 = GetEdge(edge, TargetEdgeType.NEXT_AROUND_LEFT); int edge2 = GetEdge(edge1, TargetEdgeType.NEXT_AROUND_LEFT); var n1 = EdgeOrg(edge1, out var p1); var n2 = EdgeDst(edge1, out var p2); var n3 = EdgeDst(edge2, out var p3); return (new Triangle(p1, p2, p3), new NodeId(n1), new NodeId(n2), new NodeId(n3)); } public SubDiv2D_Immutable ToImmutable() { var vertices = this._vertices.Select((it, idx) => new KeyValuePair(idx, new ImmutableVertexData(it.pt, it.type, it.firstEdge))); var quadEdges = this._quadEdges.Select((it, idx) => new KeyValuePair(idx, new ImmutableQuadEdgeData(next: ImmutableArray.CreateRange(it.next), pts: ImmutableArray.CreateRange(it.pts)))); return new SubDiv2D_Immutable(this.Bounds, vertices.ToImmutableDictionary(), quadEdges.ToImmutableDictionary()); } private void ClearVoronoi() { int i, total = _quadEdges.Count; for (i = 0; i < total; i++) { var pts = _quadEdges[i].pts; pts[1] = pts[3] = 0; } total = _vertices.Count; for (i = 0; i < total; i++) { if (_vertices[i].IsVirtual) { DeletePoint(i); } } _validGeometry = false; } internal void CalcVoronoi() { if (_validGeometry) { return; } ClearVoronoi(); int total = _quadEdges.Count; // loop through all quad-edges, except for the first 4 entries (#1, #2, #3 - boundaries, and #0 is a sentinel node) for (int i = 4; i < total; i++) { QuadEdgeData quadedge = _quadEdges[i]; if (quadedge.IsFree) continue; int edge0 = (i * 4); Vector2 org0, dst0, org1, dst1; if (quadedge.pts[3] == 0) { int edge1 = GetEdge(edge0, TargetEdgeType.NEXT_AROUND_LEFT); int edge2 = GetEdge(edge1, TargetEdgeType.NEXT_AROUND_LEFT); EdgeOrg(edge0, out org0); EdgeDst(edge0, out dst0); EdgeOrg(edge1, out org1); EdgeDst(edge1, out dst1); Vector2 virt_point = Utils.ComputeVoronoiPoint(org0, dst0, org1, dst1); if (Math.Abs(virt_point.X) < float.MaxValue * 0.5 && Math.Abs(virt_point.Y) < float.MaxValue * 0.5) { quadedge.pts[3] = _quadEdges[edge1 >> 2].pts[3 - (edge1 & 2)] = _quadEdges[edge2 >> 2].pts[3 - (edge2 & 2)] = NewPoint(virt_point, true); } } if (quadedge.pts[1] == 0) { int edge1 = GetEdge(edge0, TargetEdgeType.NEXT_AROUND_RIGHT); int edge2 = GetEdge(edge1, TargetEdgeType.NEXT_AROUND_RIGHT); EdgeOrg(edge0, out org0); EdgeDst(edge0, out dst0); EdgeOrg(edge1, out org1); EdgeDst(edge1, out dst1); var virt_point = Utils.ComputeVoronoiPoint(org0, dst0, org1, dst1); if (Math.Abs(virt_point.X) < float.MaxValue * 0.5 && Math.Abs(virt_point.Y) < float.MaxValue * 0.5) { quadedge.pts[1] = _quadEdges[edge1 >> 2].pts[1 + (edge1 & 2)] = _quadEdges[edge2 >> 2].pts[1 + (edge2 & 2)] = NewPoint(virt_point, true); } } } _validGeometry = true; } private readonly List _vertices = new List(); private readonly List _quadEdges = new List(); private int _freeQEdge; private int _freePoint; private bool _validGeometry; private void InitDelaunay() { _vertices.Clear(); _quadEdges.Clear(); _validGeometry = false; float rx = Bounds.MinValue.X; float ry = Bounds.MinValue.Y; float big_coord = 3.0f * Math.Max(Bounds.Width, Bounds.Height); var ppA = new Vector2(rx + big_coord, ry); var ppB = new Vector2(rx, ry + big_coord); var ppC = new Vector2(rx - big_coord, ry - big_coord); _vertices.Add(new VertexData()); _quadEdges.Add(new QuadEdgeData()); _freeQEdge = 0; _freePoint = 0; int pA = NewPoint(ppA, false); int pB = NewPoint(ppB, false); int pC = NewPoint(ppC, false); int edge_AB = NewEdge(); int edge_BC = NewEdge(); int edge_CA = NewEdge(); SetEdgePoints(edge_AB, pA, pB); SetEdgePoints(edge_BC, pB, pC); SetEdgePoints(edge_CA, pC, pA); Splice(edge_AB, SymEdge(edge_CA)); Splice(edge_BC, SymEdge(edge_AB)); Splice(edge_CA, SymEdge(edge_BC)); } protected override int EdgeDst(int edge) { return _quadEdges[edge >> 2].pts[(edge + 2) & 3]; } protected override int EdgeOrg(int edge) { return _quadEdges[edge >> 2].pts[edge & 3]; } protected override int GetQuadEdgesCount() { return _quadEdges.Count; } protected override void InvalidateGeometry() { _validGeometry = false; } protected override Vector2 GetVertexLocation(int vidx) { return _vertices[vidx].pt; } protected override int NextEdge(int edge) { var result = _quadEdges[edge >> 2].next[edge & 3]; return result; } protected override int GetEdge(int edge, TargetEdgeType targetType) { int tmpEdge = _quadEdges[edge >> 2].next[(edge + (int) targetType) & 3]; var result = (tmpEdge & ~3) + ((tmpEdge + ((int) targetType >> 4)) & 3); return result; } protected override int NewPoint(Vector2 pt, bool isvirtual, int firstEdge = 0) { if (_freePoint == 0) { _vertices.Add(new VertexData()); _freePoint = _vertices.Count - 1; } int vidx = _freePoint; _freePoint = _vertices[vidx].firstEdge; _vertices[vidx] = new VertexData(pt, isvirtual, firstEdge); return vidx; } protected override void SetEdgePoints(int edge, int orgPt, int dstPt) { var pts = _quadEdges[edge >> 2].pts; pts[edge & 3] = orgPt; pts[(edge + 2) & 3] = dstPt; _vertices[orgPt].firstEdge = edge; _vertices[dstPt].firstEdge = edge ^ 2; } protected override int NewEdge() { if (_freeQEdge <= 0) { _quadEdges.Add(new QuadEdgeData()); _freeQEdge = _quadEdges.Count - 1; } int edge = _freeQEdge * 4; _freeQEdge = _quadEdges[edge >> 2].next[1]; _quadEdges[edge >> 2] = new QuadEdgeData(edge); return edge; } protected override void Splice(int edgeA, int edgeB) { int[] a_nextArray = _quadEdges[edgeA >> 2].next; int[] b_nextArray = _quadEdges[edgeB >> 2].next; int a_nextIndex = edgeA & 3; int b_nextIndex = edgeB & 3; int a_rot = RotateEdge(a_nextArray[a_nextIndex], 1); int b_rot = RotateEdge(b_nextArray[b_nextIndex], 1); int[] a_rot_nextArray = _quadEdges[a_rot >> 2].next; int[] b_rot_nextArray = _quadEdges[b_rot >> 2].next; int a_rot_nextIndex = a_rot & 3; int b_rot_nextIndex = b_rot & 3; var tmp = a_nextArray[a_nextIndex]; a_nextArray[a_nextIndex] = b_nextArray[b_nextIndex]; b_nextArray[b_nextIndex] = tmp; tmp = a_rot_nextArray[a_rot_nextIndex]; a_rot_nextArray[a_rot_nextIndex] = b_rot_nextArray[b_rot_nextIndex]; b_rot_nextArray[b_rot_nextIndex] = tmp; } protected override void DeletePoint(int vidx) { _vertices[vidx].firstEdge = _freePoint; _vertices[vidx].type = -1; _freePoint = vidx; } protected override void EdgeMarkDeleted(int edge) { edge >>= 2; _quadEdges[edge].next[0] = 0; _quadEdges[edge].next[1] = _freeQEdge; _freeQEdge = edge; } class VertexData { public VertexData() { firstEdge = 0; type = -1; pt = new Vector2(float.NaN, float.NaN); } public VertexData(Vector2 pt, bool isvirtual, int firstEdge) { this.firstEdge = firstEdge; this.type = isvirtual ? 1 : 0; this.pt = pt; } public bool IsVirtual => type > 0; public bool IsFree => type < 0; public int firstEdge; public int type; public Vector2 pt; } class QuadEdgeData { public QuadEdgeData() { } public QuadEdgeData(int edgeidx) { next[0] = edgeidx; next[1] = edgeidx + 3; next[2] = edgeidx + 2; next[3] = edgeidx + 1; } public bool IsFree => next[0] <= 0; public readonly int[] next = new int[4]; public readonly int[] pts = new int[4]; } } internal class SearchContext { const int DEFAULT_EDGE_ID = 4; public int RecentEdge { get; set; } public int Edge{ get; set; } public int Vertex { get; set; } public SearchContext() { Clear(); } public void Clear() { RecentEdge = DEFAULT_EDGE_ID; Edge = 0; Vertex = 0; } } }