// try splitting polygon into two and triangulate them independently static void SplitEarcut(nativeLL aLL, int startID, NativeList <int> triangles, double minX, double minY, double invSize) { // look for a valid diagonal that divides the polygon into two int aID = startID; int aNextID, bID, aPrevID, cID, cNextID; Node a, b; do { aPrevID = aLL.PrevLists[aID]; aNextID = aLL.NextLists[aID]; bID = aLL.NextLists[aNextID]; while (bID != aPrevID) { a = aLL.NodeLists[aID]; b = aLL.NodeLists[bID]; if (a.i != b.i && IsValidDiagonal(aLL, aLL, aID, bID)) { // split the polygon in two by the diagonal cID = SplitPolygon(aLL, aLL, aID, bID); // filter colinear points around the cuts aNextID = aLL.NextLists[aID]; aID = FilterPoints(aLL, aID, aNextID); cNextID = aLL.NextLists[cID]; cID = FilterPoints(aLL, cID, cNextID); // run earcut on each half EarcutLinked(aLL, aID, triangles, minX, minY, invSize); EarcutLinked(aLL, cID, triangles, minX, minY, invSize); return; } bID = aLL.NextLists[bID]; } aID = aLL.NextLists[aID]; } while (aID != startID); }
// interlink polygon nodes in z-order static void IndexCurve(nativeLL aLL, int startID, double minX, double minY, double invSize) { int pID = startID; Node p; do { p = aLL.NodeLists[pID]; if (p.z == -1) { p.z = ZOrder(p.x, p.y, minX, minY, invSize); aLL.NodeLists[pID] = p; } aLL.PrevZLists[pID] = aLL.PrevLists[pID]; aLL.NextZLists[pID] = aLL.NextLists[pID]; pID = aLL.NextLists[pID]; } while (pID != startID); int pPrevZID = aLL.PrevZLists[pID]; aLL.NextZLists[pPrevZID] = -1; aLL.PrevZLists[pID] = -1; SortLinked(aLL, pID); }
// check whether a polygon node forms a valid ear with adjacent nodes static bool IsEar(nativeLL aLL, int earID) { int prevID = aLL.PrevLists[earID]; int nextID = aLL.NextLists[earID]; Node a = aLL.NodeLists[prevID]; Node b = aLL.NodeLists[earID]; Node c = aLL.NodeLists[nextID]; if (Area(a, b, c) >= 0) { return(false); // reflex, can't be an ear } // now make sure we don't have other points inside the potential ear int pID = aLL.NextLists[nextID]; int pPrevID = aLL.PrevLists[pID]; int pNextID; Node p, pPrev, pNext; while (pID != prevID) { p = aLL.NodeLists[pID]; pNextID = aLL.NextLists[pID]; pPrevID = aLL.PrevLists[pID]; pPrev = aLL.NodeLists[pPrevID]; pNext = aLL.NodeLists[pNextID]; if (PointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && Area(pPrev, p, pNext) >= 0) { return(false); } pID = aLL.NextLists[pID]; } return(true); }
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; // if one belongs to the outer ring and another to a hole, it merges it into a single ring static int SplitPolygon(nativeLL aLL, nativeLL bLL, int aID, int bID) { Node a2 = aLL.NodeLists[aID]; Node b2 = bLL.NodeLists[bID]; Node holeNode; int right = aID; int aNextID = aLL.NextLists[aID]; int bPrevID = bLL.PrevLists[bID]; int a2ID, b2ID; if (Equals(aLL.NodeLists, bLL.NodeLists)) ////split Polygon { //first Polygon aLL.NextLists[aID] = bID; aLL.PrevLists[bID] = aID; //second Polygon b2ID = AddNodeAfter(aLL, b2, bPrevID); a2ID = AddNodeBefore(aLL, a2, aNextID); aLL.PrevLists[a2ID] = b2ID; aLL.NextLists[b2ID] = a2ID; right = b2ID; } else //merge Polygon { int stop = bID; do { holeNode = bLL.NodeLists[bID]; InsertNode(aLL, aNextID, holeNode); bID = bLL.NextLists[bID]; } while (bID != stop); right = InsertNode(aLL, aNextID, b2); InsertNode(aLL, aNextID, a2); } return(right); }
// Simon Tatham's linked list merge sort algorithm // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html static int SortLinked(nativeLL aLL, int headID) { int i; int pID, pNextZID, qID, qNextZID, eID, tailID; qNextZID = -1; Node p; Node q; int numMerges; int pSize; int qSize; int inSize = 1; do { pID = headID; headID = -1; tailID = -1; numMerges = 0; while (pID != -1) { numMerges++; qID = pID; pSize = 0; for (i = 0; i < inSize; i++) { pSize++; qID = aLL.NextZLists[qID]; if (qID == -1) { break; } } qSize = inSize; while (pSize > 0 || (qSize > 0 && qID != -1)) { if (qID != -1) { q = aLL.NodeLists[qID]; qNextZID = aLL.NextZLists[qID]; } else { q = default; } p = aLL.NodeLists[pID]; pNextZID = aLL.NextZLists[pID]; if (pSize != 0 && (qSize == 0 || qID == -1 || p.z <= q.z)) { eID = pID; pID = pNextZID; pSize--; } else { eID = qID; qID = qNextZID; qSize--; } if (tailID != -1) { aLL.NextZLists[tailID] = eID; } else { headID = eID; } aLL.PrevZLists[eID] = tailID; tailID = eID; } pID = qID; } if (tailID != -1) { aLL.NextZLists[tailID] = -1; } inSize *= 2; } while (numMerges > 1); return(headID); }
// David Eberly's algorithm for finding a bridge between hole and outer polygon static int FindHoleBridge(nativeLL oLL, nativeLL hLL, int holeLeftmostID, int outerNodeID) { int nextID; int pID = outerNodeID; Node p, pNext, m; var hx = hLL.NodeLists[holeLeftmostID].x; var hy = hLL.NodeLists[holeLeftmostID].y; var qx = double.NegativeInfinity; int mID = -1; // find a segment intersected by a ray from the hole's leftmost point to the left; // segment's endpoint with lesser x will be potential connection point do { p = oLL.NodeLists[pID]; nextID = oLL.NextLists[pID]; pNext = oLL.NodeLists[nextID]; if (p.i == 8511) { } //When testing outer polygone p clockwise, then first bridge candiate to hole h has py < hy and next py > hy //otherwise py > hy and next py < hy. Will not happen, as LinkedList is constructed such that outer polygone = clockwise if (hy >= p.y && hy <= pNext.y && pNext.y != p.y) { var x = p.x + (hy - p.y) * (pNext.x - p.x) / (pNext.y - p.y); if (x <= hx && x > qx) { qx = x; if (x == hx) { if (hy == p.y) { return(pID); } if (hy == pNext.y) { return(nextID); } } mID = p.x < pNext.x ? pID : nextID; } } pID = oLL.NextLists[pID]; } while (pID != outerNodeID); if (mID == -1) { return(-1); } if (hx == qx) { return(mID); // hole touches outer segment; pick leftmost endpoint } // look for points inside the triangle of hole point, segment intersection and endpoint; // if there are no points found, we have a valid connection; // otherwise choose the point of the minimum angle with the ray as connection point var stop = mID; m = oLL.NodeLists[mID]; var mx = m.x; var my = m.y; var tanMin = double.PositiveInfinity; double tan; pID = mID; do { p = oLL.NodeLists[pID]; m = oLL.NodeLists[mID]; if (hx >= p.x && p.x >= mx && hx != p.x && PointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { tan = math.abs(hy - p.y) / (hx - p.x); // tangential if (LocallyInside(oLL, hLL, pID, holeLeftmostID) && (tan < tanMin || (tan == tanMin && p.x > m.x || (p.x == m.x && sectorContainsSector(oLL, oLL, mID, pID))))) { mID = pID; tanMin = tan; } } pID = oLL.NextLists[pID]; } while (pID != stop); return(mID); }
// link every hole into the outer loop, producing a single-ring polygon without holes static int EliminateHoles(nativeLL oLL, NativeArray <float2> data, NativeArray <int> holeIndices) { NativeList <PolygonPOI> queue = new NativeList <PolygonPOI>(Allocator.Temp); int outerNode = 0; float2 leftmost = new float2(); var len = holeIndices.Length; for (var i = 0; i < len; i++) { var start = holeIndices[i]; var end = i < len - 1 ? holeIndices[i + 1] : data.Length; int leftMostID = 0; leftmost = data[start]; for (int k = start; k < end; k++) { if (data[k].x < leftmost.x || (data[k].x == leftmost.x && data[k].y < leftmost.y)) { leftMostID = k - start; leftmost = data[k]; } } queue.Add(new PolygonPOI(new Node(leftMostID + start, leftmost.x, leftmost.y), leftMostID, i)); } CompareX compareX = new CompareX(); queue.Sort(compareX); nativeLL hLL = new nativeLL(); hLL.NodeLists = new NativeList <Node>(Allocator.Temp); hLL.PrevLists = new NativeList <int>(Allocator.Temp); hLL.NextLists = new NativeList <int>(Allocator.Temp); hLL.PrevZLists = new NativeList <int>(Allocator.Temp); hLL.NextZLists = new NativeList <int>(Allocator.Temp); // process holes from left to right for (var i = 0; i < queue.Length; i++) { int start = holeIndices[queue[i].PolygonID]; var end = queue[i].PolygonID < holeIndices.Length - 1 ? holeIndices[queue[i].PolygonID + 1] : data.Length; if (queue[i].PolygonID >= holeIndices.Length - 1) { } var firstNode = LinkedList(hLL, data, start, end, false); int nextID = hLL.NextLists[firstNode]; if (firstNode == nextID) { var temp = hLL.NodeLists[firstNode]; temp.steiner = true; hLL.NodeLists[firstNode] = temp; } var test1 = queue[i].poiID; EliminateHole(oLL, hLL, test1, outerNode); int outerNodeNextID = oLL.NextLists[outerNode]; outerNode = FilterPoints(oLL, outerNode, outerNodeNextID); hLL.NodeLists.Clear(); hLL.PrevLists.Clear(); hLL.NextLists.Clear(); } return(outerNode); }
public static void Tessellate(NativeArray <float2> data, NativeArray <int> holeIndices, NativeList <int> triangles, NativeArray <float3> normals) { bool hasHoles = holeIndices.Length > 0; int outerLen = hasHoles ? holeIndices[0] : data.Length; nativeLL oLL = new nativeLL(); oLL.NodeLists = new NativeList <Node>(Allocator.Temp); oLL.PrevLists = new NativeList <int>(Allocator.Temp); oLL.NextLists = new NativeList <int>(Allocator.Temp); oLL.PrevZLists = new NativeList <int>(Allocator.Temp); oLL.NextZLists = new NativeList <int>(Allocator.Temp); var outerNode = LinkedList(oLL, data, 0, outerLen, true); int prev = oLL.PrevLists[outerNode]; int next = oLL.NextLists[outerNode]; if (Equals(oLL.NodeLists[next], oLL.NodeLists[prev])) { return; } var minX = double.PositiveInfinity; var minY = double.PositiveInfinity; var maxX = double.NegativeInfinity; var maxY = double.NegativeInfinity; var invSize = default(double); if (hasHoles) { outerNode = EliminateHoles(oLL, data, holeIndices); } // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox if (data.Length > 80) { double x, y; for (int i = 0; i < outerLen; i++) { x = data[i].x; y = data[i].y; if (x < minX) { minX = x; } if (y < minY) { minY = y; } if (x > maxX) { maxX = x; } if (y > maxY) { maxY = y; } } // minX, minY and invSize are later used to transform coords into integers for z-order calculation invSize = math.max(maxX - minX, maxY - minY); invSize = invSize != 0 ? 1 / invSize : 0; } EarcutLinked(oLL, outerNode, triangles, minX, minY, invSize, 0); CalculateNormals(data, triangles, normals); return; }
static bool IsEarHashed(nativeLL aLL, int earID, double minX, double minY, double invSize) { int prevID = aLL.PrevLists[earID]; int nextID = aLL.NextLists[earID]; Node a = aLL.NodeLists[prevID]; Node b = aLL.NodeLists[earID]; Node c = aLL.NodeLists[nextID]; if (Area(a, b, c) >= 0) { return(false); // reflex, can't be an ear } // triangle bbox; min & max are calculated like this for speed var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x); var minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y); var maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x); var maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y); // z-order range for the current triangle bbox; var minZ = ZOrder(minTX, minTY, minX, minY, invSize); var maxZ = ZOrder(maxTX, maxTY, minX, minY, invSize); // look for points inside the triangle in both directions int pPrevID, pNextID, nPrevID, nNextID; Node p, pPrev, pNext, n, nPrev, nNext; int pID = aLL.PrevZLists[earID]; int nID = aLL.NextZLists[earID]; if (nID != -1) { n = aLL.NodeLists[nID]; } else { n = default; } if (pID != -1) { p = aLL.NodeLists[pID]; } else { p = default; } while (pID != -1 && p.z >= minZ && nID != -1 && n.z <= maxZ) { pPrevID = aLL.PrevLists[pID]; pNextID = aLL.NextLists[pID]; pPrev = aLL.NodeLists[pPrevID]; pNext = aLL.NodeLists[pNextID]; if (!Equals(p, a) && !Equals(p, c) && PointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && Area(pPrev, p, pNext) >= 0) { return(false); } pID = aLL.PrevZLists[pID]; if (pID != -1) { p = aLL.NodeLists[pID]; } nPrevID = aLL.PrevLists[nID]; nNextID = aLL.NextLists[nID]; nPrev = aLL.NodeLists[nPrevID]; nNext = aLL.NodeLists[nNextID]; if (!Equals(n, a) && !Equals(n, c) && PointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && Area(nPrev, n, nNext) >= 0) { return(false); } nID = aLL.NextZLists[nID]; if (nID != -1) { n = aLL.NodeLists[nID]; } } // look for remaining points in decreasing z-order while (pID != -1 && p.z >= minZ) { pPrevID = aLL.PrevLists[pID]; pNextID = aLL.NextLists[pID]; pPrev = aLL.NodeLists[pPrevID]; pNext = aLL.NodeLists[pNextID]; if (!Equals(p, a) && !Equals(p, c) && PointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && Area(pPrev, p, pNext) >= 0) { return(false); } pID = aLL.PrevZLists[pID]; if (pID != -1) { p = aLL.NodeLists[pID]; } } // look for remaining points in increasing z-order while (nID != -1 && n.z <= maxZ) { nPrevID = aLL.PrevLists[nID]; nNextID = aLL.NextLists[nID]; nPrev = aLL.NodeLists[nPrevID]; nNext = aLL.NodeLists[nNextID]; if (!Equals(n, a) && !Equals(n, c) && PointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && Area(nPrev, n, nNext) >= 0) { return(false); } nID = aLL.NextZLists[nID]; if (nID != -1) { n = aLL.NodeLists[nID]; } } return(true); }
// main ear slicing loop which triangulates a polygon (given as a linked list) static void EarcutLinked(nativeLL aLL, int earID, NativeList <int> triangles, double minX, double minY, double invSize, int pass = 0) { if (earID == -1) { return; } // interlink polygon nodes in z-order if (pass == 0 && invSize != 0) { IndexCurve(aLL, earID, minX, minY, invSize); } var stopID = earID; Node ear, prev, next; int earNextID = aLL.NextLists[earID]; int earPrevID = aLL.PrevLists[earID]; // iterate through ears, slicing them one by one while (earPrevID != earNextID) { ear = aLL.NodeLists[earID]; earNextID = aLL.NextLists[earID]; earPrevID = aLL.PrevLists[earID]; prev = aLL.NodeLists[earPrevID]; next = aLL.NodeLists[earNextID]; if (invSize != 0 ? IsEarHashed(aLL, earID, minX, minY, invSize) : IsEar(aLL, earID)) { // cut off the triangle triangles.Add(prev.i); triangles.Add(ear.i); triangles.Add(next.i); RemoveNode(aLL, earID); // skipping the next vertex leads to less sliver triangles earID = aLL.NextLists[earNextID]; stopID = aLL.NextLists[earNextID]; earNextID = aLL.NextLists[earID]; earPrevID = aLL.PrevLists[earID]; continue; } earID = earNextID; earNextID = aLL.NextLists[earID]; earPrevID = aLL.PrevLists[earID]; // if we looped through the whole remaining polygon and can't find any more ears if (earID == stopID) { // try filtering points and slicing again if (pass == 0) { EarcutLinked(aLL, FilterPoints(aLL, earID), triangles, minX, minY, invSize, 1); // if this didn't work, try curing all small self-intersections locally } else if (pass == 1) { EarcutLinked(aLL, earID, triangles, minX, minY, invSize, 2); // as a last resort, try splitting the remaining polygon into two } else if (pass == 2) { SplitEarcut(aLL, earID, triangles, minX, minY, invSize); } break; } } }