@aethervtt/shard-core 0.0.1 → 0.2.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/dist/ecs/host-write.d.ts +5 -0
- package/dist/ecs/host-write.js +24 -0
- package/dist/ecs/index.d.ts +1 -0
- package/dist/ecs/index.js +10 -0
- package/dist/ecs/owners.d.ts +113 -0
- package/dist/ecs/owners.js +297 -0
- package/dist/ecs/world.d.ts +31 -0
- package/dist/ecs/world.js +67 -0
- package/dist/math/index.d.ts +1 -0
- package/dist/math/index.js +2 -0
- package/dist/math/polygon.d.ts +17 -0
- package/dist/math/polygon.js +307 -0
- package/dist/scheduler/capture/capture.d.ts +192 -0
- package/dist/scheduler/capture/capture.js +410 -0
- package/dist/scheduler/capture/index.d.ts +3 -0
- package/dist/scheduler/capture/index.js +17 -0
- package/dist/scheduler/capture/recorder.d.ts +83 -0
- package/dist/scheduler/capture/recorder.js +224 -0
- package/dist/scheduler/capture/samples.d.ts +49 -0
- package/dist/scheduler/capture/samples.js +91 -0
- package/dist/scheduler/index.d.ts +3 -1
- package/dist/scheduler/index.js +29 -2
- package/dist/scheduler/profiler.d.ts +180 -5
- package/dist/scheduler/profiler.js +430 -17
- package/dist/scheduler/schedule.d.ts +5 -1
- package/dist/scheduler/schedule.js +33 -3
- package/dist/schema/component.d.ts +8 -0
- package/dist/schema/component.js +3 -2
- package/dist/schema/field.d.ts +12 -0
- package/dist/schema/field.js +1 -0
- package/dist/schema/resource.d.ts +9 -0
- package/dist/schema/resource.js +3 -1
- package/dist/test-env.d.ts +107 -10
- package/dist/test-env.js +72 -4
- package/package.json +6 -2
package/dist/ecs/world.js
CHANGED
|
@@ -3,12 +3,14 @@ import { Registry } from "../schema/registry.js";
|
|
|
3
3
|
import { formatEntity, MAX_ENTITIES, makeEntity } from "./entity.js";
|
|
4
4
|
import { EventQueue, EventReader } from "./events.js";
|
|
5
5
|
import { ChildOf, Children } from "./hierarchy.js";
|
|
6
|
+
import { assertHostWrite } from "./host-write.js";
|
|
6
7
|
import {
|
|
7
8
|
ObserverTable,
|
|
8
9
|
onAdd,
|
|
9
10
|
onRemove,
|
|
10
11
|
onSet
|
|
11
12
|
} from "./observers.js";
|
|
13
|
+
import { Owners } from "./owners.js";
|
|
12
14
|
import { Query, queryKey } from "./query.js";
|
|
13
15
|
import { Table } from "./table.js";
|
|
14
16
|
const FREE = 4294967295;
|
|
@@ -19,6 +21,14 @@ class World {
|
|
|
19
21
|
/** Change-detection clock. The scheduler advances it once per system run. */
|
|
20
22
|
tick = 1;
|
|
21
23
|
registry = new Registry();
|
|
24
|
+
/**
|
|
25
|
+
* While true, the next write (`set`, `add`, `remove`, `spawn`, `despawn`, `insertResource`,
|
|
26
|
+
* `removeResource`, `send`, `patchResource`, `touchResource`) clears it and calls `onWake`. The
|
|
27
|
+
* on-demand runner sets it when a frame ends and nothing asks for another (0052).
|
|
28
|
+
*/
|
|
29
|
+
asleep = false;
|
|
30
|
+
/** Called by the first write while `asleep`. Set by the on-demand runner. */
|
|
31
|
+
onWake = void 0;
|
|
22
32
|
generations = new Uint32Array(1024);
|
|
23
33
|
tableOf = new Uint32Array(1024).fill(FREE);
|
|
24
34
|
rowOf = new Uint32Array(1024);
|
|
@@ -30,18 +40,36 @@ class World {
|
|
|
30
40
|
emptyTable;
|
|
31
41
|
queries = /* @__PURE__ */ new Map();
|
|
32
42
|
resources = /* @__PURE__ */ new Map();
|
|
43
|
+
/** Change tick per resource id: set by insertResource, patchResource, and touchResource. */
|
|
44
|
+
resourceTicks = [];
|
|
33
45
|
eventQueues = /* @__PURE__ */ new Map();
|
|
34
46
|
observers = new ObserverTable();
|
|
35
47
|
registered = [];
|
|
48
|
+
owners_;
|
|
36
49
|
constructor() {
|
|
37
50
|
this.emptyTable = this.getOrCreateTable([]);
|
|
38
51
|
this.installHierarchy();
|
|
39
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Who made what (0061): owners hold entities and asset leases, and release them together.
|
|
55
|
+
* Only code holding an `Owner` can spawn into it, lease for it, or release it.
|
|
56
|
+
*/
|
|
57
|
+
get owners() {
|
|
58
|
+
this.owners_ ??= new Owners(this);
|
|
59
|
+
return this.owners_;
|
|
60
|
+
}
|
|
40
61
|
incrementTick() {
|
|
41
62
|
return ++this.tick;
|
|
42
63
|
}
|
|
64
|
+
/** Wakes the world as a write would: if it's asleep, clears `asleep` and calls `onWake`. */
|
|
65
|
+
wake() {
|
|
66
|
+
if (!this.asleep) return;
|
|
67
|
+
this.asleep = false;
|
|
68
|
+
this.onWake?.();
|
|
69
|
+
}
|
|
43
70
|
// --- entities --------------------------------------------------------------
|
|
44
71
|
spawn(...inits) {
|
|
72
|
+
if (this.asleep) this.wake();
|
|
45
73
|
const entity = this.allocate();
|
|
46
74
|
this.insert(entity, inits);
|
|
47
75
|
return entity;
|
|
@@ -55,6 +83,7 @@ class World {
|
|
|
55
83
|
if (index >= this.nextIndex || this.tableOf[index] !== PENDING || this.generations[index] !== Math.floor(entity / MAX_ENTITIES)) {
|
|
56
84
|
throw new ShardError("ecs/dead-entity", `Entity ${formatEntity(entity)} is not reserved`);
|
|
57
85
|
}
|
|
86
|
+
if (this.asleep) this.wake();
|
|
58
87
|
this.insert(entity, inits);
|
|
59
88
|
}
|
|
60
89
|
isAlive(entity) {
|
|
@@ -69,6 +98,7 @@ class World {
|
|
|
69
98
|
/** Despawns the entity and, if it has children, all of its descendants. */
|
|
70
99
|
despawn(entity) {
|
|
71
100
|
this.locate(entity);
|
|
101
|
+
if (this.asleep) this.wake();
|
|
72
102
|
const children = this.tryGet(entity, Children);
|
|
73
103
|
if (children) {
|
|
74
104
|
for (const child of children.entities) {
|
|
@@ -80,6 +110,7 @@ class World {
|
|
|
80
110
|
/** Despawns only this entity. Its children lose their `ChildOf` and become roots. */
|
|
81
111
|
despawnSingle(entity) {
|
|
82
112
|
this.locate(entity);
|
|
113
|
+
if (this.asleep) this.wake();
|
|
83
114
|
const children = this.tryGet(entity, Children);
|
|
84
115
|
if (children) {
|
|
85
116
|
for (const child of children.entities) {
|
|
@@ -105,7 +136,9 @@ class World {
|
|
|
105
136
|
/** Adds a component (defaults for missing fields). If present, replaces it and fires `onSet`. */
|
|
106
137
|
add(entity, component, value) {
|
|
107
138
|
const def = component;
|
|
139
|
+
if (def.hostOnly) assertHostWrite(def);
|
|
108
140
|
const index = this.locate(entity);
|
|
141
|
+
if (this.asleep) this.wake();
|
|
109
142
|
this.ensureRegistered(def);
|
|
110
143
|
const table = this.tables[this.tableOf[index]];
|
|
111
144
|
const row = this.rowOf[index];
|
|
@@ -134,8 +167,10 @@ class World {
|
|
|
134
167
|
}
|
|
135
168
|
/** Removes a component. Returns false if the entity didn't have it. */
|
|
136
169
|
remove(entity, component) {
|
|
170
|
+
if (component.hostOnly) assertHostWrite(component);
|
|
137
171
|
let index = this.locate(entity);
|
|
138
172
|
if (!this.tables[this.tableOf[index]].has(component)) return false;
|
|
173
|
+
if (this.asleep) this.wake();
|
|
139
174
|
this.fire("remove", entity, component, void 0);
|
|
140
175
|
index = this.locate(entity);
|
|
141
176
|
const table = this.tables[this.tableOf[index]];
|
|
@@ -162,9 +197,11 @@ class World {
|
|
|
162
197
|
/** Writes the given fields and marks the component changed. */
|
|
163
198
|
set(entity, component, values) {
|
|
164
199
|
const def = component;
|
|
200
|
+
if (def.hostOnly) assertHostWrite(def);
|
|
165
201
|
const index = this.locate(entity);
|
|
166
202
|
const table = this.tables[this.tableOf[index]];
|
|
167
203
|
if (!table.has(def)) throw missingComponent(entity, def.name);
|
|
204
|
+
if (this.asleep) this.wake();
|
|
168
205
|
const row = this.rowOf[index];
|
|
169
206
|
const previous = this.hasObservers("set", def) ? table.readComponent(def, row) : void 0;
|
|
170
207
|
table.writeComponent(def, row, values);
|
|
@@ -207,7 +244,32 @@ class World {
|
|
|
207
244
|
// --- resources -------------------------------------------------------------
|
|
208
245
|
insertResource(def, value) {
|
|
209
246
|
this.registry.register(def);
|
|
247
|
+
if (this.asleep) this.wake();
|
|
210
248
|
this.resources.set(def.id, value);
|
|
249
|
+
this.resourceTicks[def.id] = this.tick;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Assigns `partial`'s fields onto the resource, marks it changed, and wakes the world. How code
|
|
253
|
+
* outside a frame writes a resource; a bare field assignment is invisible to both (0052).
|
|
254
|
+
*/
|
|
255
|
+
patchResource(def, partial) {
|
|
256
|
+
const value = this.resource(def);
|
|
257
|
+
Object.assign(value, partial);
|
|
258
|
+
this.touchResource(def);
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
/** Marks the resource changed and wakes the world, after editing it in place. */
|
|
262
|
+
touchResource(def) {
|
|
263
|
+
if (!this.resources.has(def.id)) this.resource(def);
|
|
264
|
+
this.resourceTicks[def.id] = this.tick;
|
|
265
|
+
if (this.asleep) this.wake();
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Whether the resource was inserted, patched, or touched after tick `since` (a system passes
|
|
269
|
+
* `ctx.lastRunTick`), like component change detection.
|
|
270
|
+
*/
|
|
271
|
+
resourceChanged(def, since) {
|
|
272
|
+
return (this.resourceTicks[def.id] ?? 0) > since;
|
|
211
273
|
}
|
|
212
274
|
/** Inserts the resource from its `init` if it isn't present yet, and returns it. */
|
|
213
275
|
initResource(def) {
|
|
@@ -240,10 +302,14 @@ class World {
|
|
|
240
302
|
return this.resources.has(def.id);
|
|
241
303
|
}
|
|
242
304
|
removeResource(def) {
|
|
305
|
+
if (!this.resources.has(def.id)) return false;
|
|
306
|
+
if (this.asleep) this.wake();
|
|
307
|
+
this.resourceTicks[def.id] = this.tick;
|
|
243
308
|
return this.resources.delete(def.id);
|
|
244
309
|
}
|
|
245
310
|
// --- events ----------------------------------------------------------------
|
|
246
311
|
send(def, ...data) {
|
|
312
|
+
if (this.asleep) this.wake();
|
|
247
313
|
this.eventQueue(def).send(data[0]);
|
|
248
314
|
}
|
|
249
315
|
reader(def) {
|
|
@@ -350,6 +416,7 @@ class World {
|
|
|
350
416
|
for (let i = 0; i < inits.length; i++) {
|
|
351
417
|
const init = inits[i];
|
|
352
418
|
const def = Array.isArray(init) ? init[0] : init;
|
|
419
|
+
if (def.hostOnly) assertHostWrite(def);
|
|
353
420
|
this.ensureRegistered(def);
|
|
354
421
|
table = this.tableWith(table, def);
|
|
355
422
|
}
|
package/dist/math/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * as frustum from './frustum.js';
|
|
|
5
5
|
export { hash32, hashSeed, LATTICE_PRIMES, mix32 } from './hash.js';
|
|
6
6
|
export * as mat3 from './mat3.js';
|
|
7
7
|
export * as mat4 from './mat4.js';
|
|
8
|
+
export * as polygon from './polygon.js';
|
|
8
9
|
export * as quat from './quat.js';
|
|
9
10
|
export * as ray from './ray.js';
|
|
10
11
|
export { Rng } from './rng.js';
|
package/dist/math/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import * as frustum from "./frustum.js";
|
|
|
5
5
|
import { hash32, hashSeed, LATTICE_PRIMES, mix32 } from "./hash.js";
|
|
6
6
|
import * as mat3 from "./mat3.js";
|
|
7
7
|
import * as mat4 from "./mat4.js";
|
|
8
|
+
import * as polygon from "./polygon.js";
|
|
8
9
|
import * as quat from "./quat.js";
|
|
9
10
|
import * as ray from "./ray.js";
|
|
10
11
|
import { Rng } from "./rng.js";
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
mat3,
|
|
26
27
|
mat4,
|
|
27
28
|
mix32,
|
|
29
|
+
polygon,
|
|
28
30
|
quat,
|
|
29
31
|
ray,
|
|
30
32
|
vec2,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The signed area of a ring. Positive when counter-clockwise (y up). */
|
|
2
|
+
export declare function signedArea(points: ArrayLike<number>, start?: number, end?: number): number;
|
|
3
|
+
/** The total area of triangles given by `indices` into flat xy `points`. */
|
|
4
|
+
export declare function trianglesArea(points: ArrayLike<number>, indices: ArrayLike<number>): number;
|
|
5
|
+
/**
|
|
6
|
+
* Triangulates a simple polygon, with optional holes, by ear clipping. `points` holds the outer
|
|
7
|
+
* ring first, then each hole; `holes` lists the vertex index where each hole starts. Either
|
|
8
|
+
* winding works for any ring. Returns vertex indices, three per triangle, counter-clockwise.
|
|
9
|
+
* Holes are bridged to the outer ring first, so the result covers the polygon minus its holes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function triangulate(points: ArrayLike<number>, holes?: ArrayLike<number>): number[];
|
|
12
|
+
/**
|
|
13
|
+
* Clips a convex polygon (flat xy, `n` points, in `src`) to the rectangle
|
|
14
|
+
* `[minX, maxX] × [minY, maxY]` (Sutherland–Hodgman). Writes the result to `out` and returns its
|
|
15
|
+
* point count (0 when nothing is inside). `out` and `scratch` need room for `n + 4` points.
|
|
16
|
+
*/
|
|
17
|
+
export declare function clipToRect(src: ArrayLike<number>, n: number, minX: number, minY: number, maxX: number, maxY: number, out: Float64Array, scratch: Float64Array): number;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
function signedArea(points, start = 0, end = points.length >> 1) {
|
|
2
|
+
let area = 0;
|
|
3
|
+
for (let i = start, j = end - 1; i < end; j = i++) {
|
|
4
|
+
area += points[j * 2] * points[i * 2 + 1] - points[i * 2] * points[j * 2 + 1];
|
|
5
|
+
}
|
|
6
|
+
return area / 2;
|
|
7
|
+
}
|
|
8
|
+
function trianglesArea(points, indices) {
|
|
9
|
+
let area = 0;
|
|
10
|
+
for (let i = 0; i + 2 < indices.length; i += 3) {
|
|
11
|
+
const a = indices[i] * 2;
|
|
12
|
+
const b = indices[i + 1] * 2;
|
|
13
|
+
const c = indices[i + 2] * 2;
|
|
14
|
+
area += Math.abs(
|
|
15
|
+
(points[b] - points[a]) * (points[c + 1] - points[a + 1]) - (points[c] - points[a]) * (points[b + 1] - points[a + 1])
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return area / 2;
|
|
19
|
+
}
|
|
20
|
+
function triangulate(points, holes = []) {
|
|
21
|
+
const out = [];
|
|
22
|
+
const count = points.length >> 1;
|
|
23
|
+
const outerEnd = holes.length > 0 ? holes[0] : count;
|
|
24
|
+
let outer = ring(points, 0, outerEnd, true);
|
|
25
|
+
if (!outer || outer.next === outer.prev) return out;
|
|
26
|
+
if (holes.length > 0) outer = eliminateHoles(points, holes, outer, count);
|
|
27
|
+
earcut(outer, out, 0);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function ring(points, start, end, ccw) {
|
|
31
|
+
if (end - start < 3) return void 0;
|
|
32
|
+
const forward = signedArea(points, start, end) > 0 === ccw;
|
|
33
|
+
let last;
|
|
34
|
+
if (forward) for (let i = start; i < end; i++) last = insert(i, points, last);
|
|
35
|
+
else for (let i = end - 1; i >= start; i--) last = insert(i, points, last);
|
|
36
|
+
if (last && last.x === last.next.x && last.y === last.next.y) {
|
|
37
|
+
const next = last.next;
|
|
38
|
+
remove(last);
|
|
39
|
+
last = next;
|
|
40
|
+
}
|
|
41
|
+
return last;
|
|
42
|
+
}
|
|
43
|
+
function insert(i, points, last) {
|
|
44
|
+
const node = { i, x: points[i * 2], y: points[i * 2 + 1], gone: false };
|
|
45
|
+
if (!last) {
|
|
46
|
+
node.prev = node;
|
|
47
|
+
node.next = node;
|
|
48
|
+
} else {
|
|
49
|
+
node.next = last.next;
|
|
50
|
+
node.prev = last;
|
|
51
|
+
last.next.prev = node;
|
|
52
|
+
last.next = node;
|
|
53
|
+
}
|
|
54
|
+
return node;
|
|
55
|
+
}
|
|
56
|
+
function remove(node) {
|
|
57
|
+
node.next.prev = node.prev;
|
|
58
|
+
node.prev.next = node.next;
|
|
59
|
+
node.gone = true;
|
|
60
|
+
}
|
|
61
|
+
function area2(a, b, c) {
|
|
62
|
+
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
63
|
+
}
|
|
64
|
+
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
65
|
+
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && (ax - px) * (by - py) >= (bx - px) * (ay - py) && (bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
66
|
+
}
|
|
67
|
+
function isEar(ear) {
|
|
68
|
+
const a = ear.prev;
|
|
69
|
+
const b = ear;
|
|
70
|
+
const c = ear.next;
|
|
71
|
+
if (area2(a, b, c) <= 0) return false;
|
|
72
|
+
for (let p = c.next; p !== a; p = p.next) {
|
|
73
|
+
if (p.x === a.x && p.y === a.y || p.x === b.x && p.y === b.y || p.x === c.x && p.y === c.y)
|
|
74
|
+
continue;
|
|
75
|
+
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area2(p.prev, p, p.next) <= 0)
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
function earcut(start, out, pass) {
|
|
81
|
+
let ear = start;
|
|
82
|
+
let stop = ear;
|
|
83
|
+
while (ear.prev !== ear.next) {
|
|
84
|
+
const prev = ear.prev;
|
|
85
|
+
const next = ear.next;
|
|
86
|
+
if (isEar(ear)) {
|
|
87
|
+
out.push(prev.i, ear.i, next.i);
|
|
88
|
+
remove(ear);
|
|
89
|
+
ear = next.next;
|
|
90
|
+
stop = next.next;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
ear = next;
|
|
94
|
+
if (ear === stop) {
|
|
95
|
+
if (pass === 0) earcut(filterPoints(ear), out, 1);
|
|
96
|
+
else if (pass === 1) earcut(cureLocalIntersections(filterPoints(ear), out), out, 2);
|
|
97
|
+
else if (pass === 2) splitEarcut(ear, out);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function filterPoints(start) {
|
|
103
|
+
let p = start;
|
|
104
|
+
let end = start;
|
|
105
|
+
let again;
|
|
106
|
+
do {
|
|
107
|
+
again = false;
|
|
108
|
+
if (p.x === p.next.x && p.y === p.next.y || area2(p.prev, p, p.next) === 0) {
|
|
109
|
+
remove(p);
|
|
110
|
+
p = end = p.prev;
|
|
111
|
+
if (p === p.next) break;
|
|
112
|
+
again = true;
|
|
113
|
+
} else p = p.next;
|
|
114
|
+
} while (again || p !== end);
|
|
115
|
+
return end;
|
|
116
|
+
}
|
|
117
|
+
function intersects(p1, q1, p2, q2) {
|
|
118
|
+
const o1 = Math.sign(area2(p1, q1, p2));
|
|
119
|
+
const o2 = Math.sign(area2(p1, q1, q2));
|
|
120
|
+
const o3 = Math.sign(area2(p2, q2, p1));
|
|
121
|
+
const o4 = Math.sign(area2(p2, q2, q1));
|
|
122
|
+
return o1 !== o2 && o3 !== o4;
|
|
123
|
+
}
|
|
124
|
+
function locallyInside(a, b) {
|
|
125
|
+
return area2(a.prev, a, a.next) < 0 ? area2(a, b, a.next) >= 0 && area2(a, a.prev, b) >= 0 : area2(a, b, a.prev) < 0 || area2(a, a.next, b) < 0;
|
|
126
|
+
}
|
|
127
|
+
function cureLocalIntersections(start, out) {
|
|
128
|
+
let p = start;
|
|
129
|
+
do {
|
|
130
|
+
const a = p.prev;
|
|
131
|
+
const b = p.next.next;
|
|
132
|
+
if (!(a.x === b.x && a.y === b.y) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
133
|
+
out.push(a.i, p.i, b.i);
|
|
134
|
+
remove(p);
|
|
135
|
+
remove(p.next);
|
|
136
|
+
p = start = b;
|
|
137
|
+
}
|
|
138
|
+
p = p.next;
|
|
139
|
+
} while (p !== start);
|
|
140
|
+
return filterPoints(p);
|
|
141
|
+
}
|
|
142
|
+
function middleInside(a, b) {
|
|
143
|
+
let p = a;
|
|
144
|
+
let inside = false;
|
|
145
|
+
const px = (a.x + b.x) / 2;
|
|
146
|
+
const py = (a.y + b.y) / 2;
|
|
147
|
+
do {
|
|
148
|
+
if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)
|
|
149
|
+
inside = !inside;
|
|
150
|
+
p = p.next;
|
|
151
|
+
} while (p !== a);
|
|
152
|
+
return inside;
|
|
153
|
+
}
|
|
154
|
+
function intersectsPolygon(a, b) {
|
|
155
|
+
let p = a;
|
|
156
|
+
do {
|
|
157
|
+
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b))
|
|
158
|
+
return true;
|
|
159
|
+
p = p.next;
|
|
160
|
+
} while (p !== a);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
function isValidDiagonal(a, b) {
|
|
164
|
+
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
|
|
165
|
+
}
|
|
166
|
+
function splitPolygon(a, b) {
|
|
167
|
+
const a2 = { i: a.i, x: a.x, y: a.y, gone: false };
|
|
168
|
+
const b2 = { i: b.i, x: b.x, y: b.y, gone: false };
|
|
169
|
+
const an = a.next;
|
|
170
|
+
const bp = b.prev;
|
|
171
|
+
a.next = b;
|
|
172
|
+
b.prev = a;
|
|
173
|
+
a2.next = an;
|
|
174
|
+
an.prev = a2;
|
|
175
|
+
b2.next = a2;
|
|
176
|
+
a2.prev = b2;
|
|
177
|
+
bp.next = b2;
|
|
178
|
+
b2.prev = bp;
|
|
179
|
+
return b2;
|
|
180
|
+
}
|
|
181
|
+
function splitEarcut(start, out) {
|
|
182
|
+
let a = start;
|
|
183
|
+
do {
|
|
184
|
+
let b = a.next.next;
|
|
185
|
+
while (b !== a.prev) {
|
|
186
|
+
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
187
|
+
let c = splitPolygon(a, b);
|
|
188
|
+
a = filterPoints(a);
|
|
189
|
+
c = filterPoints(c);
|
|
190
|
+
earcut(a, out, 0);
|
|
191
|
+
earcut(c, out, 0);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
b = b.next;
|
|
195
|
+
}
|
|
196
|
+
a = a.next;
|
|
197
|
+
} while (a !== start);
|
|
198
|
+
}
|
|
199
|
+
function eliminateHoles(points, holes, outer, count) {
|
|
200
|
+
const queue = [];
|
|
201
|
+
for (let h = 0; h < holes.length; h++) {
|
|
202
|
+
const start = holes[h];
|
|
203
|
+
const end = h + 1 < holes.length ? holes[h + 1] : count;
|
|
204
|
+
const list = ring(points, start, end, false);
|
|
205
|
+
if (!list) continue;
|
|
206
|
+
let left = list;
|
|
207
|
+
let p = list;
|
|
208
|
+
do {
|
|
209
|
+
if (p.x < left.x || p.x === left.x && p.y < left.y) left = p;
|
|
210
|
+
p = p.next;
|
|
211
|
+
} while (p !== list);
|
|
212
|
+
queue.push(left);
|
|
213
|
+
}
|
|
214
|
+
queue.sort((a, b) => a.x - b.x || a.y - b.y);
|
|
215
|
+
for (const hole of queue) outer = filterPoints(eliminateHole(hole, outer));
|
|
216
|
+
return outer;
|
|
217
|
+
}
|
|
218
|
+
function eliminateHole(hole, outer) {
|
|
219
|
+
const bridge = findHoleBridge(hole, outer);
|
|
220
|
+
if (!bridge) return outer;
|
|
221
|
+
const reverse = splitPolygon(bridge, hole);
|
|
222
|
+
filterPoints(reverse);
|
|
223
|
+
return filterPoints(bridge);
|
|
224
|
+
}
|
|
225
|
+
function findHoleBridge(hole, outer) {
|
|
226
|
+
let p = outer;
|
|
227
|
+
const hx = hole.x;
|
|
228
|
+
const hy = hole.y;
|
|
229
|
+
let qx = -Infinity;
|
|
230
|
+
let m;
|
|
231
|
+
do {
|
|
232
|
+
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
233
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
234
|
+
if (x <= hx && x > qx) {
|
|
235
|
+
qx = x;
|
|
236
|
+
m = p.x < p.next.x ? p : p.next;
|
|
237
|
+
if (x === hx) return m;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
p = p.next;
|
|
241
|
+
} while (p !== outer);
|
|
242
|
+
if (!m) return void 0;
|
|
243
|
+
const stop = m;
|
|
244
|
+
const mx = m.x;
|
|
245
|
+
const my = m.y;
|
|
246
|
+
let tanMin = Infinity;
|
|
247
|
+
p = m;
|
|
248
|
+
do {
|
|
249
|
+
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)) {
|
|
250
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x);
|
|
251
|
+
if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) {
|
|
252
|
+
m = p;
|
|
253
|
+
tanMin = tan;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
p = p.next;
|
|
257
|
+
} while (p !== stop);
|
|
258
|
+
return m;
|
|
259
|
+
}
|
|
260
|
+
function sectorContainsSector(m, p) {
|
|
261
|
+
return area2(m.prev, m, p.prev) < 0 && area2(p.next, m, m.next) < 0;
|
|
262
|
+
}
|
|
263
|
+
function clipToRect(src, n, minX, minY, maxX, maxY, out, scratch) {
|
|
264
|
+
let count = clipEdge(src, n, scratch, 0, minX, 1);
|
|
265
|
+
count = clipEdge(scratch, count, out, 0, maxX, -1);
|
|
266
|
+
count = clipEdge(out, count, scratch, 1, minY, 1);
|
|
267
|
+
count = clipEdge(scratch, count, out, 1, maxY, -1);
|
|
268
|
+
return count;
|
|
269
|
+
}
|
|
270
|
+
function clipEdge(src, n, dst, axis, at, sign) {
|
|
271
|
+
let count = 0;
|
|
272
|
+
if (n === 0) return 0;
|
|
273
|
+
let px = src[(n - 1) * 2];
|
|
274
|
+
let py = src[(n - 1) * 2 + 1];
|
|
275
|
+
let pd = sign * ((axis === 0 ? px : py) - at);
|
|
276
|
+
for (let i = 0; i < n; i++) {
|
|
277
|
+
const x = src[i * 2];
|
|
278
|
+
const y = src[i * 2 + 1];
|
|
279
|
+
const d = sign * ((axis === 0 ? x : y) - at);
|
|
280
|
+
if (d >= 0) {
|
|
281
|
+
if (pd < 0) {
|
|
282
|
+
const t = pd / (pd - d);
|
|
283
|
+
dst[count * 2] = px + (x - px) * t;
|
|
284
|
+
dst[count * 2 + 1] = py + (y - py) * t;
|
|
285
|
+
count++;
|
|
286
|
+
}
|
|
287
|
+
dst[count * 2] = x;
|
|
288
|
+
dst[count * 2 + 1] = y;
|
|
289
|
+
count++;
|
|
290
|
+
} else if (pd >= 0) {
|
|
291
|
+
const t = pd / (pd - d);
|
|
292
|
+
dst[count * 2] = px + (x - px) * t;
|
|
293
|
+
dst[count * 2 + 1] = py + (y - py) * t;
|
|
294
|
+
count++;
|
|
295
|
+
}
|
|
296
|
+
px = x;
|
|
297
|
+
py = y;
|
|
298
|
+
pd = d;
|
|
299
|
+
}
|
|
300
|
+
return count;
|
|
301
|
+
}
|
|
302
|
+
export {
|
|
303
|
+
clipToRect,
|
|
304
|
+
signedArea,
|
|
305
|
+
trianglesArea,
|
|
306
|
+
triangulate
|
|
307
|
+
};
|