@xeokit/xeokit-sdk 2.6.65 → 2.6.67
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/{LICENSE.txt → LICENSE} +30 -27
- package/README.md +13 -6
- package/dist/xeokit-sdk.cjs.js +50322 -25848
- package/dist/xeokit-sdk.es.js +50322 -25848
- package/dist/xeokit-sdk.es5.js +7982 -4807
- package/dist/xeokit-sdk.min.cjs.js +5 -5
- package/dist/xeokit-sdk.min.es.js +5 -5
- package/dist/xeokit-sdk.min.es5.js +4 -4
- package/package.json +34 -32
- package/src/extras/PointerCircle/PointerCircle.js +1 -1
- package/src/plugins/AnnotationsPlugin/Annotation.js +45 -5
- package/src/plugins/CityJSONLoaderPlugin/CityJSONLoaderPlugin.js +1 -1
- package/src/plugins/GLTFLoaderPlugin/GLTFSceneModelLoader.js +3 -2
- package/src/plugins/LASLoaderPlugin/LASLoaderPlugin.js +1 -1
- package/src/plugins/SectionPlanesPlugin/Control.js +11 -12
- package/src/plugins/XKTLoaderPlugin/XKTLoaderPlugin.js +57 -2
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV10.js +6 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV11.js +6 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV12.js +822 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV6.js +6 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV7.js +6 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV8.js +6 -0
- package/src/plugins/XKTLoaderPlugin/parsers/ParserV9.js +6 -0
- package/src/{plugins/lib → viewer/scene/libs}/earcut.js +194 -189
- package/src/viewer/scene/materials/EmphasisMaterial.js +5 -1
- package/src/viewer/scene/math/math.js +6 -2
- package/src/viewer/scene/model/SceneModel.js +1 -0
- package/src/viewer/scene/model/SceneModelEntity.js +26 -0
- package/src/viewer/scene/model/SceneModelMesh.js +4 -0
- package/src/viewer/scene/model/dtx/triangles/DTXTrianglesLayer.js +1 -1
- package/src/viewer/scene/model/vbo/batching/triangles/VBOBatchingTrianglesLayer.js +1 -1
- package/src/viewer/scene/model/vbo/batching/triangles/renderers/TrianglesColorRenderer.js +4 -1
- package/src/viewer/scene/model/vbo/instancing/triangles/VBOInstancingTrianglesLayer.js +1 -1
- package/src/viewer/scene/model/vbo/instancing/triangles/renderers/TrianglesColorRenderer.js +4 -1
- package/src/viewer/scene/scene/Scene.js +50 -6
- package/src/viewer/scene/sectionCaps/SectionCaps.js +774 -0
- package/src/viewer/scene/sectionCaps/index.js +1 -0
- package/src/viewer/scene/sectionPlane/SectionPlane.js +79 -1
- package/src/viewer/scene/webgl/Renderer.js +19 -10
- package/types/extras/PointerCircle/PointerCircle.d.ts +50 -0
- package/types/extras/PointerCircle/index.d.ts +1 -0
- package/types/plugins/WebIFCLoaderPlugin/WebIFCLoaderPlugin.d.ts +2 -1
- package/types/plugins/XKTLoaderPlugin/XKTLoaderPlugin.d.ts +28 -0
- package/types/plugins/index.d.ts +1 -0
- package/types/plugins/lib/ui/index.d.ts +12 -0
- package/types/viewer/scene/models/SceneModelMesh.d.ts +7 -0
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
/** @private */
|
|
2
|
-
function earcut(data, holeIndices, dim) {
|
|
3
1
|
|
|
4
|
-
|
|
2
|
+
export default function earcut(data, holeIndices, dim = 2) {
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
5
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
6
|
+
let outerNode = linkedList(data, 0, outerLen, dim, true);
|
|
7
|
+
const triangles = [];
|
|
10
8
|
|
|
11
9
|
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
let minX, minY, invSize;
|
|
14
12
|
|
|
15
13
|
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
16
14
|
|
|
17
15
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
18
16
|
if (data.length > 80 * dim) {
|
|
19
|
-
minX =
|
|
20
|
-
minY =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
minX = Infinity;
|
|
18
|
+
minY = Infinity;
|
|
19
|
+
let maxX = -Infinity;
|
|
20
|
+
let maxY = -Infinity;
|
|
21
|
+
|
|
22
|
+
for (let i = dim; i < outerLen; i += dim) {
|
|
23
|
+
const x = data[i];
|
|
24
|
+
const y = data[i + 1];
|
|
25
25
|
if (x < minX) minX = x;
|
|
26
26
|
if (y < minY) minY = y;
|
|
27
27
|
if (x > maxX) maxX = x;
|
|
@@ -30,22 +30,22 @@ function earcut(data, holeIndices, dim) {
|
|
|
30
30
|
|
|
31
31
|
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
|
32
32
|
invSize = Math.max(maxX - minX, maxY - minY);
|
|
33
|
-
invSize = invSize !== 0 ?
|
|
33
|
+
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
|
|
36
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
37
37
|
|
|
38
38
|
return triangles;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// create a circular doubly linked list from polygon points in the specified winding order
|
|
42
42
|
function linkedList(data, start, end, dim, clockwise) {
|
|
43
|
-
|
|
43
|
+
let last;
|
|
44
44
|
|
|
45
45
|
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
|
|
46
|
-
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
46
|
+
for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
|
|
47
47
|
} else {
|
|
48
|
-
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
48
|
+
for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
if (last && equals(last, last.next)) {
|
|
@@ -61,7 +61,7 @@ function filterPoints(start, end) {
|
|
|
61
61
|
if (!start) return start;
|
|
62
62
|
if (!end) end = start;
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
let p = start,
|
|
65
65
|
again;
|
|
66
66
|
do {
|
|
67
67
|
again = false;
|
|
@@ -87,19 +87,15 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
87
87
|
// interlink polygon nodes in z-order
|
|
88
88
|
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
prev, next;
|
|
90
|
+
let stop = ear;
|
|
92
91
|
|
|
93
92
|
// iterate through ears, slicing them one by one
|
|
94
93
|
while (ear.prev !== ear.next) {
|
|
95
|
-
prev = ear.prev;
|
|
96
|
-
next = ear.next;
|
|
94
|
+
const prev = ear.prev;
|
|
95
|
+
const next = ear.next;
|
|
97
96
|
|
|
98
97
|
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
99
|
-
// cut off the triangle
|
|
100
|
-
triangles.push(prev.i / dim);
|
|
101
|
-
triangles.push(ear.i / dim);
|
|
102
|
-
triangles.push(next.i / dim);
|
|
98
|
+
triangles.push(prev.i, ear.i, next.i); // cut off the triangle
|
|
103
99
|
|
|
104
100
|
removeNode(ear);
|
|
105
101
|
|
|
@@ -118,12 +114,12 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
118
114
|
if (!pass) {
|
|
119
115
|
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
|
|
120
116
|
|
|
121
|
-
|
|
117
|
+
// if this didn't work, try curing all small self-intersections locally
|
|
122
118
|
} else if (pass === 1) {
|
|
123
|
-
ear = cureLocalIntersections(filterPoints(ear), triangles
|
|
119
|
+
ear = cureLocalIntersections(filterPoints(ear), triangles);
|
|
124
120
|
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
|
|
125
121
|
|
|
126
|
-
|
|
122
|
+
// as a last resort, try splitting the remaining polygon into two
|
|
127
123
|
} else if (pass === 2) {
|
|
128
124
|
splitEarcut(ear, triangles, dim, minX, minY, invSize);
|
|
129
125
|
}
|
|
@@ -135,17 +131,25 @@ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
|
135
131
|
|
|
136
132
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
|
137
133
|
function isEar(ear) {
|
|
138
|
-
|
|
134
|
+
const a = ear.prev,
|
|
139
135
|
b = ear,
|
|
140
136
|
c = ear.next;
|
|
141
137
|
|
|
142
138
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
143
139
|
|
|
144
140
|
// now make sure we don't have other points inside the potential ear
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
142
|
+
|
|
143
|
+
// triangle bbox
|
|
144
|
+
const x0 = Math.min(ax, bx, cx),
|
|
145
|
+
y0 = Math.min(ay, by, cy),
|
|
146
|
+
x1 = Math.max(ax, bx, cx),
|
|
147
|
+
y1 = Math.max(ay, by, cy);
|
|
148
|
+
|
|
149
|
+
let p = c.next;
|
|
150
|
+
while (p !== a) {
|
|
151
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
|
|
152
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
|
|
149
153
|
area(p.prev, p, p.next) >= 0) return false;
|
|
150
154
|
p = p.next;
|
|
151
155
|
}
|
|
@@ -154,51 +158,49 @@ function isEar(ear) {
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
function isEarHashed(ear, minX, minY, invSize) {
|
|
157
|
-
|
|
161
|
+
const a = ear.prev,
|
|
158
162
|
b = ear,
|
|
159
163
|
c = ear.next;
|
|
160
164
|
|
|
161
165
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
168
|
+
|
|
169
|
+
// triangle bbox
|
|
170
|
+
const x0 = Math.min(ax, bx, cx),
|
|
171
|
+
y0 = Math.min(ay, by, cy),
|
|
172
|
+
x1 = Math.max(ax, bx, cx),
|
|
173
|
+
y1 = Math.max(ay, by, cy);
|
|
168
174
|
|
|
169
175
|
// z-order range for the current triangle bbox;
|
|
170
|
-
|
|
171
|
-
maxZ = zOrder(
|
|
176
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize),
|
|
177
|
+
maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
172
178
|
|
|
173
|
-
|
|
179
|
+
let p = ear.prevZ,
|
|
174
180
|
n = ear.nextZ;
|
|
175
181
|
|
|
176
182
|
// look for points inside the triangle in both directions
|
|
177
183
|
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
178
|
-
if (p
|
|
179
|
-
|
|
180
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
184
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
185
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
181
186
|
p = p.prevZ;
|
|
182
187
|
|
|
183
|
-
if (n
|
|
184
|
-
|
|
185
|
-
area(n.prev, n, n.next) >= 0) return false;
|
|
188
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
189
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
186
190
|
n = n.nextZ;
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
// look for remaining points in decreasing z-order
|
|
190
194
|
while (p && p.z >= minZ) {
|
|
191
|
-
if (p
|
|
192
|
-
|
|
193
|
-
area(p.prev, p, p.next) >= 0) return false;
|
|
195
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
196
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
194
197
|
p = p.prevZ;
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
// look for remaining points in increasing z-order
|
|
198
201
|
while (n && n.z <= maxZ) {
|
|
199
|
-
if (n
|
|
200
|
-
|
|
201
|
-
area(n.prev, n, n.next) >= 0) return false;
|
|
202
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
203
|
+
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
202
204
|
n = n.nextZ;
|
|
203
205
|
}
|
|
204
206
|
|
|
@@ -206,17 +208,15 @@ function isEarHashed(ear, minX, minY, invSize) {
|
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
// go through all polygon nodes and cure small local self-intersections
|
|
209
|
-
function cureLocalIntersections(start, triangles
|
|
210
|
-
|
|
211
|
+
function cureLocalIntersections(start, triangles) {
|
|
212
|
+
let p = start;
|
|
211
213
|
do {
|
|
212
|
-
|
|
214
|
+
const a = p.prev,
|
|
213
215
|
b = p.next.next;
|
|
214
216
|
|
|
215
217
|
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
216
218
|
|
|
217
|
-
triangles.push(a.i
|
|
218
|
-
triangles.push(p.i / dim);
|
|
219
|
-
triangles.push(b.i / dim);
|
|
219
|
+
triangles.push(a.i, p.i, b.i);
|
|
220
220
|
|
|
221
221
|
// remove two nodes involved
|
|
222
222
|
removeNode(p);
|
|
@@ -233,21 +233,21 @@ function cureLocalIntersections(start, triangles, dim) {
|
|
|
233
233
|
// try splitting polygon into two and triangulate them independently
|
|
234
234
|
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
235
235
|
// look for a valid diagonal that divides the polygon into two
|
|
236
|
-
|
|
236
|
+
let a = start;
|
|
237
237
|
do {
|
|
238
|
-
|
|
238
|
+
let b = a.next.next;
|
|
239
239
|
while (b !== a.prev) {
|
|
240
240
|
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
241
241
|
// split the polygon in two by the diagonal
|
|
242
|
-
|
|
242
|
+
let c = splitPolygon(a, b);
|
|
243
243
|
|
|
244
244
|
// filter colinear points around the cuts
|
|
245
245
|
a = filterPoints(a, a.next);
|
|
246
246
|
c = filterPoints(c, c.next);
|
|
247
247
|
|
|
248
248
|
// run earcut on each half
|
|
249
|
-
earcutLinked(a, triangles, dim, minX, minY, invSize);
|
|
250
|
-
earcutLinked(c, triangles, dim, minX, minY, invSize);
|
|
249
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
|
|
250
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
253
|
b = b.next;
|
|
@@ -258,64 +258,75 @@ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
|
258
258
|
|
|
259
259
|
// link every hole into the outer loop, producing a single-ring polygon without holes
|
|
260
260
|
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
261
|
-
|
|
262
|
-
i, len, start, end, list;
|
|
261
|
+
const queue = [];
|
|
263
262
|
|
|
264
|
-
for (i = 0, len = holeIndices.length; i < len; i++) {
|
|
265
|
-
start = holeIndices[i] * dim;
|
|
266
|
-
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
267
|
-
list = linkedList(data, start, end, dim, false);
|
|
263
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
264
|
+
const start = holeIndices[i] * dim;
|
|
265
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
266
|
+
const list = linkedList(data, start, end, dim, false);
|
|
268
267
|
if (list === list.next) list.steiner = true;
|
|
269
268
|
queue.push(getLeftmost(list));
|
|
270
269
|
}
|
|
271
270
|
|
|
272
|
-
queue.sort(
|
|
271
|
+
queue.sort(compareXYSlope);
|
|
273
272
|
|
|
274
273
|
// process holes from left to right
|
|
275
|
-
for (i = 0; i < queue.length; i++) {
|
|
276
|
-
eliminateHole(queue[i], outerNode);
|
|
277
|
-
outerNode = filterPoints(outerNode, outerNode.next);
|
|
274
|
+
for (let i = 0; i < queue.length; i++) {
|
|
275
|
+
outerNode = eliminateHole(queue[i], outerNode);
|
|
278
276
|
}
|
|
279
277
|
|
|
280
278
|
return outerNode;
|
|
281
279
|
}
|
|
282
280
|
|
|
283
|
-
function
|
|
284
|
-
|
|
281
|
+
function compareXYSlope(a, b) {
|
|
282
|
+
let result = a.x - b.x;
|
|
283
|
+
// when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find
|
|
284
|
+
// the bridge to the outer shell is always the point that they meet at.
|
|
285
|
+
if (result === 0) {
|
|
286
|
+
result = a.y - b.y;
|
|
287
|
+
if (result === 0) {
|
|
288
|
+
const aSlope = (a.next.y - a.y) / (a.next.x - a.x);
|
|
289
|
+
const bSlope = (b.next.y - b.y) / (b.next.x - b.x);
|
|
290
|
+
result = aSlope - bSlope;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
285
294
|
}
|
|
286
295
|
|
|
287
296
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
288
297
|
function eliminateHole(hole, outerNode) {
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// filter collinear points around the cuts
|
|
294
|
-
filterPoints(outerNode, outerNode.next);
|
|
295
|
-
filterPoints(b, b.next);
|
|
298
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
299
|
+
if (!bridge) {
|
|
300
|
+
return outerNode;
|
|
296
301
|
}
|
|
302
|
+
|
|
303
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
304
|
+
|
|
305
|
+
// filter collinear points around the cuts
|
|
306
|
+
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
307
|
+
return filterPoints(bridge, bridge.next);
|
|
297
308
|
}
|
|
298
309
|
|
|
299
310
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
300
311
|
function findHoleBridge(hole, outerNode) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
312
|
+
let p = outerNode;
|
|
313
|
+
const hx = hole.x;
|
|
314
|
+
const hy = hole.y;
|
|
315
|
+
let qx = -Infinity;
|
|
316
|
+
let m;
|
|
306
317
|
|
|
307
318
|
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
|
308
319
|
// segment's endpoint with lesser x will be potential connection point
|
|
320
|
+
// unless they intersect at a vertex, then choose the vertex
|
|
321
|
+
if (equals(hole, p)) return p;
|
|
309
322
|
do {
|
|
310
|
-
if (
|
|
311
|
-
|
|
323
|
+
if (equals(hole, p.next)) return p.next;
|
|
324
|
+
else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
325
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
312
326
|
if (x <= hx && x > qx) {
|
|
313
327
|
qx = x;
|
|
314
|
-
if (x === hx) {
|
|
315
|
-
if (hy === p.y) return p;
|
|
316
|
-
if (hy === p.next.y) return p.next;
|
|
317
|
-
}
|
|
318
328
|
m = p.x < p.next.x ? p : p.next;
|
|
329
|
+
if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
319
330
|
}
|
|
320
331
|
}
|
|
321
332
|
p = p.next;
|
|
@@ -323,25 +334,22 @@ function findHoleBridge(hole, outerNode) {
|
|
|
323
334
|
|
|
324
335
|
if (!m) return null;
|
|
325
336
|
|
|
326
|
-
if (hx === qx) return m; // hole touches outer segment; pick leftmost endpoint
|
|
327
|
-
|
|
328
337
|
// look for points inside the triangle of hole point, segment intersection and endpoint;
|
|
329
338
|
// if there are no points found, we have a valid connection;
|
|
330
339
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
331
340
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
tan;
|
|
341
|
+
const stop = m;
|
|
342
|
+
const mx = m.x;
|
|
343
|
+
const my = m.y;
|
|
344
|
+
let tanMin = Infinity;
|
|
337
345
|
|
|
338
346
|
p = m;
|
|
339
347
|
|
|
340
348
|
do {
|
|
341
349
|
if (hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
342
|
-
|
|
350
|
+
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
343
351
|
|
|
344
|
-
tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
352
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
|
|
345
353
|
|
|
346
354
|
if (locallyInside(p, hole) &&
|
|
347
355
|
(tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
|
|
@@ -363,9 +371,9 @@ function sectorContainsSector(m, p) {
|
|
|
363
371
|
|
|
364
372
|
// interlink polygon nodes in z-order
|
|
365
373
|
function indexCurve(start, minX, minY, invSize) {
|
|
366
|
-
|
|
374
|
+
let p = start;
|
|
367
375
|
do {
|
|
368
|
-
if (p.z ===
|
|
376
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
369
377
|
p.prevZ = p.prev;
|
|
370
378
|
p.nextZ = p.next;
|
|
371
379
|
p = p.next;
|
|
@@ -380,25 +388,26 @@ function indexCurve(start, minX, minY, invSize) {
|
|
|
380
388
|
// Simon Tatham's linked list merge sort algorithm
|
|
381
389
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
382
390
|
function sortLinked(list) {
|
|
383
|
-
|
|
384
|
-
|
|
391
|
+
let numMerges;
|
|
392
|
+
let inSize = 1;
|
|
385
393
|
|
|
386
394
|
do {
|
|
387
|
-
p = list;
|
|
395
|
+
let p = list;
|
|
396
|
+
let e;
|
|
388
397
|
list = null;
|
|
389
|
-
tail = null;
|
|
398
|
+
let tail = null;
|
|
390
399
|
numMerges = 0;
|
|
391
400
|
|
|
392
401
|
while (p) {
|
|
393
402
|
numMerges++;
|
|
394
|
-
q = p;
|
|
395
|
-
pSize = 0;
|
|
396
|
-
for (i = 0; i < inSize; i++) {
|
|
403
|
+
let q = p;
|
|
404
|
+
let pSize = 0;
|
|
405
|
+
for (let i = 0; i < inSize; i++) {
|
|
397
406
|
pSize++;
|
|
398
407
|
q = q.nextZ;
|
|
399
408
|
if (!q) break;
|
|
400
409
|
}
|
|
401
|
-
qSize = inSize;
|
|
410
|
+
let qSize = inSize;
|
|
402
411
|
|
|
403
412
|
while (pSize > 0 || (qSize > 0 && q)) {
|
|
404
413
|
|
|
@@ -433,8 +442,8 @@ function sortLinked(list) {
|
|
|
433
442
|
// z-order of a point given coords and inverse of the longer side of data bbox
|
|
434
443
|
function zOrder(x, y, minX, minY, invSize) {
|
|
435
444
|
// coords are transformed into non-negative 15-bit integer range
|
|
436
|
-
x =
|
|
437
|
-
y =
|
|
445
|
+
x = (x - minX) * invSize | 0;
|
|
446
|
+
y = (y - minY) * invSize | 0;
|
|
438
447
|
|
|
439
448
|
x = (x | (x << 8)) & 0x00FF00FF;
|
|
440
449
|
x = (x | (x << 4)) & 0x0F0F0F0F;
|
|
@@ -451,7 +460,7 @@ function zOrder(x, y, minX, minY, invSize) {
|
|
|
451
460
|
|
|
452
461
|
// find the leftmost node of a polygon ring
|
|
453
462
|
function getLeftmost(start) {
|
|
454
|
-
|
|
463
|
+
let p = start,
|
|
455
464
|
leftmost = start;
|
|
456
465
|
do {
|
|
457
466
|
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
|
|
@@ -463,15 +472,20 @@ function getLeftmost(start) {
|
|
|
463
472
|
|
|
464
473
|
// check if a point lies within a convex triangle
|
|
465
474
|
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
466
|
-
return (cx - px) * (ay - py)
|
|
467
|
-
|
|
468
|
-
|
|
475
|
+
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
|
|
476
|
+
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
|
|
477
|
+
(bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// check if a point lies within a convex triangle but false if its equal to the first point of the triangle
|
|
481
|
+
function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
|
|
482
|
+
return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py);
|
|
469
483
|
}
|
|
470
484
|
|
|
471
485
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
472
486
|
function isValidDiagonal(a, b) {
|
|
473
487
|
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
|
|
474
|
-
|
|
488
|
+
(locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
|
|
475
489
|
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
|
|
476
490
|
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
|
|
477
491
|
}
|
|
@@ -488,10 +502,10 @@ function equals(p1, p2) {
|
|
|
488
502
|
|
|
489
503
|
// check if two segments intersect
|
|
490
504
|
function intersects(p1, q1, p2, q2) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
505
|
+
const o1 = sign(area(p1, q1, p2));
|
|
506
|
+
const o2 = sign(area(p1, q1, q2));
|
|
507
|
+
const o3 = sign(area(p2, q2, p1));
|
|
508
|
+
const o4 = sign(area(p2, q2, q1));
|
|
495
509
|
|
|
496
510
|
if (o1 !== o2 && o3 !== o4) return true; // general case
|
|
497
511
|
|
|
@@ -514,10 +528,10 @@ function sign(num) {
|
|
|
514
528
|
|
|
515
529
|
// check if a polygon diagonal intersects any polygon segments
|
|
516
530
|
function intersectsPolygon(a, b) {
|
|
517
|
-
|
|
531
|
+
let p = a;
|
|
518
532
|
do {
|
|
519
533
|
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
520
|
-
|
|
534
|
+
intersects(p, p.next, a, b)) return true;
|
|
521
535
|
p = p.next;
|
|
522
536
|
} while (p !== a);
|
|
523
537
|
|
|
@@ -533,13 +547,13 @@ function locallyInside(a, b) {
|
|
|
533
547
|
|
|
534
548
|
// check if the middle point of a polygon diagonal is inside the polygon
|
|
535
549
|
function middleInside(a, b) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
550
|
+
let p = a;
|
|
551
|
+
let inside = false;
|
|
552
|
+
const px = (a.x + b.x) / 2;
|
|
553
|
+
const py = (a.y + b.y) / 2;
|
|
540
554
|
do {
|
|
541
555
|
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
|
|
542
|
-
|
|
556
|
+
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
|
|
543
557
|
inside = !inside;
|
|
544
558
|
p = p.next;
|
|
545
559
|
} while (p !== a);
|
|
@@ -550,8 +564,8 @@ function middleInside(a, b) {
|
|
|
550
564
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
551
565
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
552
566
|
function splitPolygon(a, b) {
|
|
553
|
-
|
|
554
|
-
b2 =
|
|
567
|
+
const a2 = createNode(a.i, a.x, a.y),
|
|
568
|
+
b2 = createNode(b.i, b.x, b.y),
|
|
555
569
|
an = a.next,
|
|
556
570
|
bp = b.prev;
|
|
557
571
|
|
|
@@ -572,7 +586,7 @@ function splitPolygon(a, b) {
|
|
|
572
586
|
|
|
573
587
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
574
588
|
function insertNode(i, x, y, last) {
|
|
575
|
-
|
|
589
|
+
const p = createNode(i, x, y);
|
|
576
590
|
|
|
577
591
|
if (!last) {
|
|
578
592
|
p.prev = p;
|
|
@@ -595,49 +609,39 @@ function removeNode(p) {
|
|
|
595
609
|
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
596
610
|
}
|
|
597
611
|
|
|
598
|
-
function
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
// z-order curve value
|
|
611
|
-
this.z = null;
|
|
612
|
-
|
|
613
|
-
// previous and next nodes in z-order
|
|
614
|
-
this.prevZ = null;
|
|
615
|
-
this.nextZ = null;
|
|
616
|
-
|
|
617
|
-
// indicates whether this is a steiner point
|
|
618
|
-
this.steiner = false;
|
|
612
|
+
function createNode(i, x, y) {
|
|
613
|
+
return {
|
|
614
|
+
i, // vertex index in coordinates array
|
|
615
|
+
x, y, // vertex coordinates
|
|
616
|
+
prev: null, // previous and next vertex nodes in a polygon ring
|
|
617
|
+
next: null,
|
|
618
|
+
z: 0, // z-order curve value
|
|
619
|
+
prevZ: null, // previous and next nodes in z-order
|
|
620
|
+
nextZ: null,
|
|
621
|
+
steiner: false // indicates whether this is a steiner point
|
|
622
|
+
};
|
|
619
623
|
}
|
|
620
624
|
|
|
621
625
|
// return a percentage difference between the polygon area and its triangulation area;
|
|
622
626
|
// used to verify correctness of triangulation
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
627
|
+
export function deviation(data, holeIndices, dim, triangles) {
|
|
628
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
629
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
626
630
|
|
|
627
|
-
|
|
631
|
+
let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
|
|
628
632
|
if (hasHoles) {
|
|
629
|
-
for (
|
|
630
|
-
|
|
631
|
-
|
|
633
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
634
|
+
const start = holeIndices[i] * dim;
|
|
635
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
632
636
|
polygonArea -= Math.abs(signedArea(data, start, end, dim));
|
|
633
637
|
}
|
|
634
638
|
}
|
|
635
639
|
|
|
636
|
-
|
|
637
|
-
for (i = 0; i < triangles.length; i += 3) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
640
|
+
let trianglesArea = 0;
|
|
641
|
+
for (let i = 0; i < triangles.length; i += 3) {
|
|
642
|
+
const a = triangles[i] * dim;
|
|
643
|
+
const b = triangles[i + 1] * dim;
|
|
644
|
+
const c = triangles[i + 2] * dim;
|
|
641
645
|
trianglesArea += Math.abs(
|
|
642
646
|
(data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
|
|
643
647
|
(data[a] - data[b]) * (data[c + 1] - data[a + 1]));
|
|
@@ -645,11 +649,11 @@ earcut.deviation = function (data, holeIndices, dim, triangles) {
|
|
|
645
649
|
|
|
646
650
|
return polygonArea === 0 && trianglesArea === 0 ? 0 :
|
|
647
651
|
Math.abs((trianglesArea - polygonArea) / polygonArea);
|
|
648
|
-
}
|
|
652
|
+
}
|
|
649
653
|
|
|
650
654
|
function signedArea(data, start, end, dim) {
|
|
651
|
-
|
|
652
|
-
for (
|
|
655
|
+
let sum = 0;
|
|
656
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
653
657
|
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
654
658
|
j = i;
|
|
655
659
|
}
|
|
@@ -657,21 +661,22 @@ function signedArea(data, start, end, dim) {
|
|
|
657
661
|
}
|
|
658
662
|
|
|
659
663
|
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
664
|
+
export function flatten(data) {
|
|
665
|
+
const vertices = [];
|
|
666
|
+
const holes = [];
|
|
667
|
+
const dimensions = data[0][0].length;
|
|
668
|
+
let holeIndex = 0;
|
|
669
|
+
let prevLen = 0;
|
|
670
|
+
|
|
671
|
+
for (const ring of data) {
|
|
672
|
+
for (const p of ring) {
|
|
673
|
+
for (let d = 0; d < dimensions; d++) vertices.push(p[d]);
|
|
668
674
|
}
|
|
669
|
-
if (
|
|
670
|
-
holeIndex +=
|
|
671
|
-
|
|
675
|
+
if (prevLen) {
|
|
676
|
+
holeIndex += prevLen;
|
|
677
|
+
holes.push(holeIndex);
|
|
672
678
|
}
|
|
679
|
+
prevLen = ring.length;
|
|
673
680
|
}
|
|
674
|
-
return
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
export {earcut};
|
|
681
|
+
return {vertices, holes, dimensions};
|
|
682
|
+
}
|