@thi.ng/geom-tessellate 2.1.131 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -1
- package/README.md +162 -8
- package/api.d.ts +81 -0
- package/api.js +0 -0
- package/earcut-complex.d.ts +74 -0
- package/earcut-complex.js +415 -0
- package/earcut.d.ts +2 -7
- package/earcut.js +28 -26
- package/edge-split.d.ts +8 -1
- package/edge-split.js +11 -23
- package/index.d.ts +5 -0
- package/index.js +5 -0
- package/inset.d.ts +2 -2
- package/inset.js +11 -19
- package/package.json +33 -14
- package/quad-fan.d.ts +1 -1
- package/quad-fan.js +13 -17
- package/rim-tris.d.ts +1 -1
- package/rim-tris.js +21 -26
- package/tessellate.d.ts +7 -4
- package/tessellate.js +24 -17
- package/tessellation.d.ts +26 -0
- package/tessellation.js +59 -0
- package/tri-fan-boundary.d.ts +3 -0
- package/tri-fan-boundary.js +11 -0
- package/tri-fan-split.d.ts +9 -0
- package/tri-fan-split.js +26 -0
- package/tri-fan.d.ts +1 -1
- package/tri-fan.js +7 -16
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { bounds2 } from "@thi.ng/geom-poly-utils/bounds";
|
|
2
|
+
import { sign } from "@thi.ng/math/abs";
|
|
3
|
+
import { mux2 } from "@thi.ng/morton/mux";
|
|
4
|
+
const earCutComplex = (holeIDs = [], hashThreshold = 80) => (tess, faces, pids) => {
|
|
5
|
+
let points = tess.pointsForIDs(pids);
|
|
6
|
+
const hasHoles = !!holeIDs.length;
|
|
7
|
+
const outerLen = hasHoles ? holeIDs[0] : points.length;
|
|
8
|
+
let scale = 0;
|
|
9
|
+
if (points.length >= hashThreshold) {
|
|
10
|
+
const [[minX, minY], [maxX, maxY]] = bounds2(points, 0, outerLen);
|
|
11
|
+
scale = Math.max(maxX - minX, maxY - minY);
|
|
12
|
+
if (scale > 0) {
|
|
13
|
+
scale = 65535 / scale;
|
|
14
|
+
points = points.map((p) => [
|
|
15
|
+
(p[0] - minX) * scale,
|
|
16
|
+
(p[1] - minY) * scale
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let outerNode = __buildVertexList(points, pids, 0, outerLen, true);
|
|
21
|
+
if (!outerNode || outerNode.n === outerNode.p) return faces;
|
|
22
|
+
if (hasHoles) {
|
|
23
|
+
outerNode = __eliminateHoles(points, pids, holeIDs, outerNode);
|
|
24
|
+
} else {
|
|
25
|
+
outerNode = __removeColinear(outerNode);
|
|
26
|
+
}
|
|
27
|
+
__earcutLinked(outerNode, faces, scale > 0 ? __isEarHashed : __isEar);
|
|
28
|
+
return faces;
|
|
29
|
+
};
|
|
30
|
+
const earCutComplexPrepare = (boundary, holes) => {
|
|
31
|
+
let points = boundary;
|
|
32
|
+
const holeIDs = [];
|
|
33
|
+
for (let hole of holes) {
|
|
34
|
+
holeIDs.push(points.length);
|
|
35
|
+
points = points.concat(hole);
|
|
36
|
+
}
|
|
37
|
+
return [points, holeIDs];
|
|
38
|
+
};
|
|
39
|
+
class Vertex {
|
|
40
|
+
i;
|
|
41
|
+
x;
|
|
42
|
+
y;
|
|
43
|
+
p = null;
|
|
44
|
+
n = null;
|
|
45
|
+
pz = null;
|
|
46
|
+
nz = null;
|
|
47
|
+
z = -1;
|
|
48
|
+
s = false;
|
|
49
|
+
constructor(i, x, y) {
|
|
50
|
+
this.i = i;
|
|
51
|
+
this.x = x;
|
|
52
|
+
this.y = y;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const __buildVertexList = (points, pids, start, end, clockwise) => {
|
|
56
|
+
let last;
|
|
57
|
+
if (clockwise === __signedArea(points, start, end) > 0) {
|
|
58
|
+
for (let i = start; i < end; i++)
|
|
59
|
+
last = __insertVertex(pids[i], points[i], last);
|
|
60
|
+
} else {
|
|
61
|
+
for (let i = end - 1; i >= start; i--)
|
|
62
|
+
last = __insertVertex(pids[i], points[i], last);
|
|
63
|
+
}
|
|
64
|
+
if (last && __equals(last, last.n)) {
|
|
65
|
+
__removeVertex(last);
|
|
66
|
+
last = last.n;
|
|
67
|
+
}
|
|
68
|
+
return last;
|
|
69
|
+
};
|
|
70
|
+
const __insertVertex = (i, [x, y], last) => {
|
|
71
|
+
const v = new Vertex(i, x, y);
|
|
72
|
+
if (!last) {
|
|
73
|
+
v.p = v.n = v;
|
|
74
|
+
} else {
|
|
75
|
+
v.n = last.n;
|
|
76
|
+
v.p = last;
|
|
77
|
+
last.n = last.n.p = v;
|
|
78
|
+
}
|
|
79
|
+
return v;
|
|
80
|
+
};
|
|
81
|
+
const __removeVertex = (v) => {
|
|
82
|
+
v.n.p = v.p;
|
|
83
|
+
v.p.n = v.n;
|
|
84
|
+
if (v.pz) v.pz.nz = v.nz;
|
|
85
|
+
if (v.nz) v.nz.pz = v.pz;
|
|
86
|
+
};
|
|
87
|
+
const __earcutLinked = (ear, triangles, pred, pass = 0) => {
|
|
88
|
+
if (!ear) return;
|
|
89
|
+
if (!pass && pred === __isEarHashed) __indexZCurve(ear);
|
|
90
|
+
let stop = ear;
|
|
91
|
+
while (ear.p !== ear.n) {
|
|
92
|
+
const { p: prev, n: next } = ear;
|
|
93
|
+
if (pred(ear)) {
|
|
94
|
+
triangles.push([prev.i, ear.i, next.i]);
|
|
95
|
+
__removeVertex(ear);
|
|
96
|
+
ear = stop = next.n;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
ear = next;
|
|
100
|
+
if (ear === stop) {
|
|
101
|
+
if (pass === 0) {
|
|
102
|
+
__earcutLinked(__removeColinear(ear), triangles, pred, 1);
|
|
103
|
+
} else if (pass === 1) {
|
|
104
|
+
ear = __cureLocalIntersections(
|
|
105
|
+
__removeColinear(ear),
|
|
106
|
+
triangles
|
|
107
|
+
);
|
|
108
|
+
__earcutLinked(ear, triangles, pred, 2);
|
|
109
|
+
} else if (pass === 2) {
|
|
110
|
+
__splitEarcut(ear, triangles, pred);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const __splitEarcut = (start, triangles, pred) => {
|
|
117
|
+
let a = start;
|
|
118
|
+
do {
|
|
119
|
+
let b = a.n.n;
|
|
120
|
+
while (b !== a.p) {
|
|
121
|
+
if (a.i !== b.i && __isValidDiagonal(a, b)) {
|
|
122
|
+
let c = __splitPolygon(a, b);
|
|
123
|
+
a = __removeColinear(a, a.n);
|
|
124
|
+
c = __removeColinear(c, c.n);
|
|
125
|
+
__earcutLinked(a, triangles, pred, 0);
|
|
126
|
+
__earcutLinked(c, triangles, pred, 0);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
b = b.n;
|
|
130
|
+
}
|
|
131
|
+
a = a.n;
|
|
132
|
+
} while (a !== start);
|
|
133
|
+
};
|
|
134
|
+
const __splitPolygon = (a, b) => {
|
|
135
|
+
const a2 = new Vertex(a.i, a.x, a.y);
|
|
136
|
+
const b2 = new Vertex(b.i, b.x, b.y);
|
|
137
|
+
const an = a.n;
|
|
138
|
+
const bp = b.p;
|
|
139
|
+
a.n = b;
|
|
140
|
+
b.p = a;
|
|
141
|
+
a2.n = an;
|
|
142
|
+
an.p = a2;
|
|
143
|
+
b2.n = a2;
|
|
144
|
+
a2.p = b2;
|
|
145
|
+
bp.n = b2;
|
|
146
|
+
b2.p = bp;
|
|
147
|
+
return b2;
|
|
148
|
+
};
|
|
149
|
+
const __isEdgeCentroidInside = (a, b) => {
|
|
150
|
+
const mx = (a.x + b.x) * 0.5;
|
|
151
|
+
const my = (a.y + b.y) * 0.5;
|
|
152
|
+
let v = a;
|
|
153
|
+
let inside = false;
|
|
154
|
+
do {
|
|
155
|
+
const { x: px, y: py, n: vn } = v;
|
|
156
|
+
if (vn.y !== py && py > my !== vn.y > my && mx < (vn.x - px) * (my - py) / (vn.y - py) + px)
|
|
157
|
+
inside = !inside;
|
|
158
|
+
v = vn;
|
|
159
|
+
} while (v !== a);
|
|
160
|
+
return inside;
|
|
161
|
+
};
|
|
162
|
+
const __isLocallyInside = (a, b) => __area(a.p, a, a.n) < 0 ? __area(a, b, a.n) >= 0 && __area(a, a.p, b) >= 0 : __area(a, b, a.p) < 0 || __area(a, a.n, b) < 0;
|
|
163
|
+
const __intersectsPolygon = (a, b) => {
|
|
164
|
+
let v = a;
|
|
165
|
+
const ai = a.i;
|
|
166
|
+
const bi = b.i;
|
|
167
|
+
do {
|
|
168
|
+
if (v.i !== ai && v.i !== bi && v.n.i !== ai && v.n.i !== bi && __intersects(v, v.n, a, b))
|
|
169
|
+
return true;
|
|
170
|
+
v = v.n;
|
|
171
|
+
} while (v !== a);
|
|
172
|
+
return false;
|
|
173
|
+
};
|
|
174
|
+
const __intersects = (p1, q1, p2, q2) => {
|
|
175
|
+
const o1 = sign(__area(p1, q1, p2));
|
|
176
|
+
const o2 = sign(__area(p1, q1, q2));
|
|
177
|
+
const o3 = sign(__area(p2, q2, p1));
|
|
178
|
+
const o4 = sign(__area(p2, q2, q1));
|
|
179
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
180
|
+
if (o1 === 0 && __onSegment(p1, p2, q1)) return true;
|
|
181
|
+
if (o2 === 0 && __onSegment(p1, q2, q1)) return true;
|
|
182
|
+
if (o3 === 0 && __onSegment(p2, p1, q2)) return true;
|
|
183
|
+
if (o4 === 0 && __onSegment(p2, q1, q2)) return true;
|
|
184
|
+
return false;
|
|
185
|
+
};
|
|
186
|
+
const __onSegment = ({ x: px, y: py }, { x: qx, y: qy }, { x: rx, y: ry }) => qx <= Math.max(px, rx) && qx >= Math.min(px, rx) && qy <= Math.max(py, ry) && qy >= Math.min(py, ry);
|
|
187
|
+
const __isValidDiagonal = (a, b) => a.n.i !== b.i && a.p.i !== b.i && !__intersectsPolygon(a, b) && (__isLocallyInside(a, b) && __isLocallyInside(b, a) && // locally visible
|
|
188
|
+
__isEdgeCentroidInside(a, b) && // does not create opposite-facing sectors
|
|
189
|
+
(__area(a.p, a, b.p) || __area(a, b.p, b)) || // special zero-length case
|
|
190
|
+
__equals(a, b) && __area(a.p, a, a.n) > 0 && __area(b.p, b, b.n) > 0);
|
|
191
|
+
const __isPointInTriangle = ({ x, y }, ax, ay, bx, by, cx, cy) => (cx - x) * (ay - y) >= (ax - x) * (cy - y) && (ax - x) * (by - y) >= (bx - x) * (ay - y) && (bx - x) * (cy - y) >= (cx - x) * (by - y);
|
|
192
|
+
const __isPointInRect = ({ x, y }, x0, y0, x1, y1) => x >= x0 && x <= x1 && y >= y0 && y <= y1;
|
|
193
|
+
const __findLeftmost = (start) => {
|
|
194
|
+
let left = start;
|
|
195
|
+
let v = start;
|
|
196
|
+
do {
|
|
197
|
+
if (v.x < left.x || v.x === left.x && v.y < left.y) left = v;
|
|
198
|
+
v = v.n;
|
|
199
|
+
} while (v !== start);
|
|
200
|
+
return left;
|
|
201
|
+
};
|
|
202
|
+
const __sortLinked = (list) => {
|
|
203
|
+
let numMerges;
|
|
204
|
+
let inSize = 1;
|
|
205
|
+
do {
|
|
206
|
+
let p = list;
|
|
207
|
+
let tail = list = null;
|
|
208
|
+
let q, e;
|
|
209
|
+
numMerges = 0;
|
|
210
|
+
while (p) {
|
|
211
|
+
numMerges++;
|
|
212
|
+
q = p;
|
|
213
|
+
let pSize = 0;
|
|
214
|
+
for (let i = 0; i < inSize; i++) {
|
|
215
|
+
pSize++;
|
|
216
|
+
q = q.nz;
|
|
217
|
+
if (!q) break;
|
|
218
|
+
}
|
|
219
|
+
let qSize = inSize;
|
|
220
|
+
while (pSize > 0 || qSize > 0 && q) {
|
|
221
|
+
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
|
|
222
|
+
e = p;
|
|
223
|
+
p = p.nz;
|
|
224
|
+
pSize--;
|
|
225
|
+
} else {
|
|
226
|
+
e = q;
|
|
227
|
+
q = q.nz;
|
|
228
|
+
qSize--;
|
|
229
|
+
}
|
|
230
|
+
if (tail) tail.nz = e;
|
|
231
|
+
else list = e;
|
|
232
|
+
e.pz = tail;
|
|
233
|
+
tail = e;
|
|
234
|
+
}
|
|
235
|
+
p = q;
|
|
236
|
+
}
|
|
237
|
+
tail.nz = null;
|
|
238
|
+
inSize *= 2;
|
|
239
|
+
} while (numMerges > 1);
|
|
240
|
+
return list;
|
|
241
|
+
};
|
|
242
|
+
const __indexZCurve = (start) => {
|
|
243
|
+
let v = start;
|
|
244
|
+
do {
|
|
245
|
+
if (v.z < 0) v.z = mux2(v.x, v.y);
|
|
246
|
+
v.pz = v.p;
|
|
247
|
+
v = v.nz = v.n;
|
|
248
|
+
} while (v !== start);
|
|
249
|
+
v.pz.nz = v.pz = null;
|
|
250
|
+
__sortLinked(v);
|
|
251
|
+
};
|
|
252
|
+
const __sectorContainsSector = (m, p) => __area(m.p, m, p.p) < 0 && __area(p.n, m, m.n) < 0;
|
|
253
|
+
const __findHoleBridge = (hole, outer) => {
|
|
254
|
+
const { x: hx, y: hy } = hole;
|
|
255
|
+
let v = outer;
|
|
256
|
+
let qx = -Infinity;
|
|
257
|
+
let px, py;
|
|
258
|
+
let pnx, pny;
|
|
259
|
+
let m;
|
|
260
|
+
do {
|
|
261
|
+
({ x: px, y: py } = v);
|
|
262
|
+
({ x: pnx, y: pny } = v.n);
|
|
263
|
+
if (hy <= py && hy >= pny && pny !== py) {
|
|
264
|
+
const x = px + (hy - py) * (pnx - px) / (pny - py);
|
|
265
|
+
if (x <= hx && x > qx) {
|
|
266
|
+
qx = x;
|
|
267
|
+
m = px < pnx ? v : v.n;
|
|
268
|
+
if (x === hx) return m;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
v = v.n;
|
|
272
|
+
} while (v !== outer);
|
|
273
|
+
if (!m) return null;
|
|
274
|
+
const stop = m;
|
|
275
|
+
const { x: mx, y: my } = m;
|
|
276
|
+
let tanMin = Infinity;
|
|
277
|
+
let tan;
|
|
278
|
+
v = m;
|
|
279
|
+
do {
|
|
280
|
+
({ x: px, y: py } = v);
|
|
281
|
+
if (hx >= px && px >= mx && hx !== px && __isPointInTriangle(
|
|
282
|
+
v,
|
|
283
|
+
hy < my ? hx : qx,
|
|
284
|
+
hy,
|
|
285
|
+
mx,
|
|
286
|
+
my,
|
|
287
|
+
hy < my ? qx : hx,
|
|
288
|
+
hy
|
|
289
|
+
)) {
|
|
290
|
+
tan = Math.abs(hy - py) / (hx - px);
|
|
291
|
+
if (__isLocallyInside(v, hole) && (tan < tanMin || tan === tanMin && (px > m.x || px === m.x && __sectorContainsSector(m, v)))) {
|
|
292
|
+
m = v;
|
|
293
|
+
tanMin = tan;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
v = v.n;
|
|
297
|
+
} while (v !== stop);
|
|
298
|
+
return m;
|
|
299
|
+
};
|
|
300
|
+
const __eliminateHole = (hole, outerNode) => {
|
|
301
|
+
const bridge = __findHoleBridge(hole, outerNode);
|
|
302
|
+
if (!bridge) return outerNode;
|
|
303
|
+
const bridgeReverse = __splitPolygon(bridge, hole);
|
|
304
|
+
__removeColinear(bridgeReverse, bridgeReverse.n);
|
|
305
|
+
return __removeColinear(bridge, bridge.n);
|
|
306
|
+
};
|
|
307
|
+
const __eliminateHoles = (points, pids, holeIndices, outerNode) => {
|
|
308
|
+
const queue = [];
|
|
309
|
+
for (let i = 0, num = holeIndices.length - 1; i <= num; i++) {
|
|
310
|
+
const start = holeIndices[i];
|
|
311
|
+
const end = i < num ? holeIndices[i + 1] : points.length;
|
|
312
|
+
const list = __buildVertexList(points, pids, start, end, false);
|
|
313
|
+
if (list === list.n) list.s = true;
|
|
314
|
+
queue.push(__findLeftmost(list));
|
|
315
|
+
}
|
|
316
|
+
queue.sort((a, b) => a.x - b.x);
|
|
317
|
+
for (let i = 0, n = queue.length; i < n; i++) {
|
|
318
|
+
outerNode = __eliminateHole(queue[i], outerNode);
|
|
319
|
+
}
|
|
320
|
+
return outerNode;
|
|
321
|
+
};
|
|
322
|
+
const __cureLocalIntersections = (start, triangles) => {
|
|
323
|
+
let v = start;
|
|
324
|
+
do {
|
|
325
|
+
const a = v.p;
|
|
326
|
+
const b = v.n.n;
|
|
327
|
+
if (!__equals(a, b) && __isLocallyInside(a, b) && __isLocallyInside(b, a) && __intersects(a, v, v.n, b)) {
|
|
328
|
+
triangles.push([a.i, v.i, b.i]);
|
|
329
|
+
__removeVertex(v);
|
|
330
|
+
__removeVertex(v.n);
|
|
331
|
+
v = start = b;
|
|
332
|
+
}
|
|
333
|
+
v = v.n;
|
|
334
|
+
} while (v !== start);
|
|
335
|
+
return __removeColinear(v);
|
|
336
|
+
};
|
|
337
|
+
const __isEar = (ear) => {
|
|
338
|
+
const { p: a, n: c } = ear;
|
|
339
|
+
const b = ear;
|
|
340
|
+
if (__area(a, b, c) >= 0) return false;
|
|
341
|
+
const { x: ax, y: ay } = a, { x: bx, y: by } = b, { x: cx, y: cy } = c;
|
|
342
|
+
const [x0, y0, x1, y1] = __triBounds(ax, ay, bx, by, cx, cy);
|
|
343
|
+
let v = c.n;
|
|
344
|
+
while (v !== a) {
|
|
345
|
+
if (__isPointInRect(v, x0, y0, x1, y1) && __isPointInTriangle(v, ax, ay, bx, by, cx, cy) && __area(v.p, v, v.n) >= 0)
|
|
346
|
+
return false;
|
|
347
|
+
v = v.n;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
};
|
|
351
|
+
const __isEarHashed = (ear) => {
|
|
352
|
+
const { p: a, n: c } = ear;
|
|
353
|
+
const b = ear;
|
|
354
|
+
if (__area(a, b, c) >= 0) return false;
|
|
355
|
+
const { x: ax, y: ay } = a;
|
|
356
|
+
const { x: bx, y: by } = b;
|
|
357
|
+
const { x: cx, y: cy } = c;
|
|
358
|
+
const [x0, y0, x1, y1] = __triBounds(ax, ay, bx, by, cx, cy);
|
|
359
|
+
const minZ = mux2(x0, y0);
|
|
360
|
+
const maxZ = mux2(x1, y1);
|
|
361
|
+
const check = (v) => v !== a && v !== c && __isPointInRect(v, x0, y0, x1, y1) && __isPointInTriangle(v, ax, ay, bx, by, cx, cy) && __area(v.p, v, v.n) >= 0;
|
|
362
|
+
let { pz: p, nz: n } = ear;
|
|
363
|
+
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
364
|
+
if (check(p) || check(n)) return false;
|
|
365
|
+
p = p.pz;
|
|
366
|
+
n = n.nz;
|
|
367
|
+
}
|
|
368
|
+
while (p && p.z >= minZ) {
|
|
369
|
+
if (check(p)) return false;
|
|
370
|
+
p = p.pz;
|
|
371
|
+
}
|
|
372
|
+
while (n && n.z <= maxZ) {
|
|
373
|
+
if (check(n)) return false;
|
|
374
|
+
n = n.nz;
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
};
|
|
378
|
+
const __removeColinear = (start, end = start) => {
|
|
379
|
+
if (!start) return start;
|
|
380
|
+
let v = start;
|
|
381
|
+
let repeat;
|
|
382
|
+
do {
|
|
383
|
+
repeat = false;
|
|
384
|
+
if (!v.s && (__equals(v, v.n) || sign(__area(v.p, v, v.n)) === 0)) {
|
|
385
|
+
__removeVertex(v);
|
|
386
|
+
v = end = v.p;
|
|
387
|
+
if (v === v.n) break;
|
|
388
|
+
repeat = true;
|
|
389
|
+
} else {
|
|
390
|
+
v = v.n;
|
|
391
|
+
}
|
|
392
|
+
} while (repeat || v !== end);
|
|
393
|
+
return end;
|
|
394
|
+
};
|
|
395
|
+
const __area = (a, { x: bx, y: by }, c) => (by - a.y) * (c.x - bx) - (bx - a.x) * (c.y - by);
|
|
396
|
+
const __signedArea = (points, start, end) => {
|
|
397
|
+
let sum = 0;
|
|
398
|
+
for (let i = start, j = end - 1; i < end; j = i, i++) {
|
|
399
|
+
const a = points[j];
|
|
400
|
+
const b = points[i];
|
|
401
|
+
sum += (a[0] - b[0]) * (a[1] + b[1]);
|
|
402
|
+
}
|
|
403
|
+
return sum;
|
|
404
|
+
};
|
|
405
|
+
const __triBounds = (ax, ay, bx, by, cx, cy) => [
|
|
406
|
+
ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx,
|
|
407
|
+
ay < by ? ay < cy ? ay : cy : by < cy ? by : cy,
|
|
408
|
+
ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx,
|
|
409
|
+
ay > by ? ay > cy ? ay : cy : by > cy ? by : cy
|
|
410
|
+
];
|
|
411
|
+
const __equals = (pa, b) => pa.x === b.x && pa.y === b.y;
|
|
412
|
+
export {
|
|
413
|
+
earCutComplex,
|
|
414
|
+
earCutComplexPrepare
|
|
415
|
+
};
|
package/earcut.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type { Tessellator } from "
|
|
2
|
-
|
|
3
|
-
* Tessellator for simple 2D polygons.
|
|
4
|
-
*
|
|
5
|
-
* @param points - polygon vertices
|
|
6
|
-
*/
|
|
7
|
-
export declare const earCut2: Tessellator;
|
|
1
|
+
import type { Tessellator } from "./api.js";
|
|
2
|
+
export declare const earCut: Tessellator;
|
|
8
3
|
//# sourceMappingURL=earcut.d.ts.map
|
package/earcut.js
CHANGED
|
@@ -2,45 +2,47 @@ import { pointInTriangle2 } from "@thi.ng/geom-isec/point";
|
|
|
2
2
|
import { polyArea2 } from "@thi.ng/geom-poly-utils/area";
|
|
3
3
|
import { range } from "@thi.ng/transducers/range";
|
|
4
4
|
import { signedArea2 } from "@thi.ng/vectors/signed-area";
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (i !== u && i !== v && i !== w) {
|
|
12
|
-
if (pointInTriangle2(points[ids[i]], a, b, c)) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return [a, b, c];
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const earCut2 = (points) => {
|
|
21
|
-
const tris = [];
|
|
22
|
-
let n = points.length;
|
|
23
|
-
const ids = [...polyArea2(points) > 0 ? range(n) : range(n - 1, -1, -1)];
|
|
5
|
+
const earCut = (tess, faces, pids) => {
|
|
6
|
+
let n = pids.length;
|
|
7
|
+
const points = tess.pointsForIDs(pids);
|
|
8
|
+
const order = [
|
|
9
|
+
...polyArea2(points) > 0 ? range(n) : range(n - 1, -1, -1)
|
|
10
|
+
];
|
|
24
11
|
let count = 2 * n - 1;
|
|
25
|
-
let v = n - 1, u, w
|
|
12
|
+
let v = n - 1, u, w;
|
|
26
13
|
while (count > 0 && n > 2) {
|
|
27
14
|
u = n <= v ? 0 : v;
|
|
28
15
|
v = u + 1;
|
|
29
16
|
v = n <= v ? 0 : v;
|
|
30
17
|
w = v + 1;
|
|
31
18
|
w = n <= w ? 0 : w;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ids.splice(v, 1);
|
|
19
|
+
if (__snip(points, u, v, w, n, order)) {
|
|
20
|
+
faces.push([pids[order[u]], pids[order[v]], pids[order[w]]]);
|
|
21
|
+
order.splice(v, 1);
|
|
36
22
|
n--;
|
|
37
23
|
count = 2 * n;
|
|
38
24
|
} else {
|
|
39
25
|
count--;
|
|
40
26
|
}
|
|
41
27
|
}
|
|
42
|
-
return
|
|
28
|
+
return faces;
|
|
29
|
+
};
|
|
30
|
+
const __snip = (points, u, v, w, n, ids) => {
|
|
31
|
+
const a = points[ids[u]];
|
|
32
|
+
const b = points[ids[v]];
|
|
33
|
+
const c = points[ids[w]];
|
|
34
|
+
if (signedArea2(a, b, c) > 0) {
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
if (i !== u && i !== v && i !== w) {
|
|
37
|
+
if (pointInTriangle2(points[ids[i]], a, b, c)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
43
45
|
};
|
|
44
46
|
export {
|
|
45
|
-
|
|
47
|
+
earCut
|
|
46
48
|
};
|
package/edge-split.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import type { Tessellator } from "
|
|
1
|
+
import type { Tessellator } from "./api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tessellates a polygon into sequence of triangles by splitting each boundary
|
|
4
|
+
* edge and constructing 2 triangles: `amc`, `mbc`, where `a` and `b` are edge
|
|
5
|
+
* endpoints, `m` is the edge centroid and `c` the face centroid.
|
|
6
|
+
*
|
|
7
|
+
* @param points
|
|
8
|
+
*/
|
|
2
9
|
export declare const edgeSplit: Tessellator;
|
|
3
10
|
//# sourceMappingURL=edge-split.d.ts.map
|
package/edge-split.js
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
import { centroid } from "@thi.ng/geom-poly-utils/centroid";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
partition(2, 1),
|
|
14
|
-
mapcat(([a, b]) => {
|
|
15
|
-
const m = mixN([], a, b, 0.5);
|
|
16
|
-
return [
|
|
17
|
-
[a, m, c],
|
|
18
|
-
[m, b, c]
|
|
19
|
-
];
|
|
20
|
-
})
|
|
21
|
-
),
|
|
22
|
-
push(),
|
|
23
|
-
wrapSides(points, 0, 1)
|
|
24
|
-
);
|
|
2
|
+
import { addmN } from "@thi.ng/vectors/addmn";
|
|
3
|
+
const edgeSplit = (tess, faces, pids) => {
|
|
4
|
+
const points = tess.pointsForIDs(pids);
|
|
5
|
+
const c = tess.addPoint(centroid(points));
|
|
6
|
+
const n = pids.length - 1;
|
|
7
|
+
for (let i = 0; i <= n; i++) {
|
|
8
|
+
const j = i < n ? i + 1 : 0;
|
|
9
|
+
const m = tess.addPoint(addmN([], points[i], points[j], 0.5));
|
|
10
|
+
faces.push([c, pids[i], m], [c, m, pids[j]]);
|
|
11
|
+
}
|
|
12
|
+
return faces;
|
|
25
13
|
};
|
|
26
14
|
export {
|
|
27
15
|
edgeSplit
|
package/index.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
export * from "./api.js";
|
|
1
2
|
export * from "./tessellate.js";
|
|
3
|
+
export * from "./tessellation.js";
|
|
2
4
|
export * from "./earcut.js";
|
|
5
|
+
export * from "./earcut-complex.js";
|
|
3
6
|
export * from "./edge-split.js";
|
|
4
7
|
export * from "./inset.js";
|
|
5
8
|
export * from "./quad-fan.js";
|
|
6
9
|
export * from "./rim-tris.js";
|
|
7
10
|
export * from "./tri-fan.js";
|
|
11
|
+
export * from "./tri-fan-boundary.js";
|
|
12
|
+
export * from "./tri-fan-split.js";
|
|
8
13
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export * from "./api.js";
|
|
1
2
|
export * from "./tessellate.js";
|
|
3
|
+
export * from "./tessellation.js";
|
|
2
4
|
export * from "./earcut.js";
|
|
5
|
+
export * from "./earcut-complex.js";
|
|
3
6
|
export * from "./edge-split.js";
|
|
4
7
|
export * from "./inset.js";
|
|
5
8
|
export * from "./quad-fan.js";
|
|
6
9
|
export * from "./rim-tris.js";
|
|
7
10
|
export * from "./tri-fan.js";
|
|
11
|
+
export * from "./tri-fan-boundary.js";
|
|
12
|
+
export * from "./tri-fan-split.js";
|
package/inset.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Tessellator } from "
|
|
2
|
-
export declare const
|
|
1
|
+
import type { Tessellator } from "./api.js";
|
|
2
|
+
export declare const inset: (inset?: number, keepInterior?: boolean) => Tessellator;
|
|
3
3
|
//# sourceMappingURL=inset.d.ts.map
|
package/inset.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { centroid } from "@thi.ng/geom-poly-utils/centroid";
|
|
2
|
-
import { comp } from "@thi.ng/transducers/comp";
|
|
3
|
-
import { map } from "@thi.ng/transducers/map";
|
|
4
|
-
import { partition } from "@thi.ng/transducers/partition";
|
|
5
|
-
import { push } from "@thi.ng/transducers/push";
|
|
6
|
-
import { transduce } from "@thi.ng/transducers/transduce";
|
|
7
|
-
import { wrapSides } from "@thi.ng/transducers/wrap-sides";
|
|
8
|
-
import { zip } from "@thi.ng/transducers/zip";
|
|
9
2
|
import { mixN } from "@thi.ng/vectors/mixn";
|
|
10
|
-
const
|
|
3
|
+
const inset = (inset2 = 0.5, keepInterior = false) => (tess, faces, pids) => {
|
|
4
|
+
const points = tess.pointsForIDs(pids);
|
|
11
5
|
const c = centroid(points);
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
wrapSides([...zip(points, inner)], 0, 1)
|
|
21
|
-
);
|
|
6
|
+
const insets = tess.addPoints(points.map((p) => mixN([], p, c, inset2)));
|
|
7
|
+
const n = pids.length - 1;
|
|
8
|
+
for (let i = 0; i <= n; i++) {
|
|
9
|
+
const j = i < n ? i + 1 : 0;
|
|
10
|
+
faces.push([pids[i], pids[j], insets[j], insets[i]]);
|
|
11
|
+
}
|
|
12
|
+
if (keepInterior) faces.push(insets);
|
|
13
|
+
return faces;
|
|
22
14
|
};
|
|
23
15
|
export {
|
|
24
|
-
|
|
16
|
+
inset
|
|
25
17
|
};
|