@weasel-js/diagram 1.11.0 → 1.11.1
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/README.md +696 -4
- package/dist/chunk-43LDZ6L4.js +2053 -0
- package/dist/chunk-43LDZ6L4.js.map +1 -0
- package/dist/chunk-CI3I3I6T.js +3600 -0
- package/dist/chunk-CI3I3I6T.js.map +1 -0
- package/dist/{chunk-NUMMW5BI.js → chunk-IM6DHWZO.js} +388 -33
- package/dist/chunk-IM6DHWZO.js.map +1 -0
- package/dist/chunk-QKSWZEJH.js +312 -0
- package/dist/chunk-QKSWZEJH.js.map +1 -0
- package/dist/chunk-T6DQT553.js +1431 -0
- package/dist/chunk-T6DQT553.js.map +1 -0
- package/dist/chunk-WDIE35AK.js +3 -0
- package/dist/chunk-WDIE35AK.js.map +1 -0
- package/dist/{fromData-GKqRgPFs.d.ts → dataTypes-B-iEwKZp.d.ts} +229 -55
- package/dist/effect-audio.d.ts +138 -0
- package/dist/effect-audio.js +527 -0
- package/dist/effect-audio.js.map +1 -0
- package/dist/effect.d.ts +486 -121
- package/dist/effect.js +1599 -340
- package/dist/effect.js.map +1 -1
- package/dist/icons.table.d.ts +6 -0
- package/dist/icons.table.js +32 -0
- package/dist/icons.table.js.map +1 -0
- package/dist/icons.table.json +24 -0
- package/dist/index.d.ts +2149 -88
- package/dist/index.js +1056 -2
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +3 -3
- package/dist/{layout.entry-BoWFcR4G.d.ts → layout.entry-CH9gc0CY.d.ts} +230 -3
- package/dist/layout.js +3 -1
- package/dist/stageTypes-DvnR8cKQ.d.ts +189 -0
- package/dist/{body-2lC7IiIA.d.ts → types-BbHunLXm.d.ts} +147 -7
- package/package.json +28 -7
- package/dist/chunk-C4NAI6NV.js +0 -2041
- package/dist/chunk-C4NAI6NV.js.map +0 -1
- package/dist/chunk-NUMMW5BI.js.map +0 -1
|
@@ -0,0 +1,1431 @@
|
|
|
1
|
+
import { labelCenter, samePoints } from './chunk-IM6DHWZO.js';
|
|
2
|
+
import { polylineFromPoints } from '@weasel-js/core/math';
|
|
3
|
+
|
|
4
|
+
// src/hops/types.ts
|
|
5
|
+
var HOPS_DOC_KEY = "diagram:hops";
|
|
6
|
+
var DEFAULT_HOP_SIZE = 5;
|
|
7
|
+
|
|
8
|
+
// src/polyline.ts
|
|
9
|
+
var EPS = 1e-6;
|
|
10
|
+
var same = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS;
|
|
11
|
+
function simplifyStops(points) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const p of points) {
|
|
14
|
+
if (out.length > 0 && same(out[out.length - 1], p)) continue;
|
|
15
|
+
while (out.length >= 2 && between(out[out.length - 2], out[out.length - 1], p)) out.pop();
|
|
16
|
+
out.push(p);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function between(a, m, b) {
|
|
21
|
+
const cross = (m.x - a.x) * (b.y - a.y) - (m.y - a.y) * (b.x - a.x);
|
|
22
|
+
const dot = (m.x - a.x) * (b.x - m.x) + (m.y - a.y) * (b.y - m.y);
|
|
23
|
+
return Math.abs(cross) <= EPS && dot > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/avoid/types.ts
|
|
27
|
+
var AVOID_DEFAULTS = Object.freeze({
|
|
28
|
+
clearance: 10,
|
|
29
|
+
bendPenalty: 40,
|
|
30
|
+
crossPenalty: 20,
|
|
31
|
+
spacing: 8,
|
|
32
|
+
margin: 100,
|
|
33
|
+
labelClearance: 4
|
|
34
|
+
});
|
|
35
|
+
var inflate = (box, by) => ({ x: box.x - by, y: box.y - by, width: box.width + 2 * by, height: box.height + 2 * by });
|
|
36
|
+
function blocks(obstacle, edge) {
|
|
37
|
+
if (obstacle.kind === "shape") return true;
|
|
38
|
+
if (obstacle.kind === "label") return obstacle.edge !== edge.id;
|
|
39
|
+
const members = obstacle.members;
|
|
40
|
+
return members === void 0 || !members.has(edge.from.node) && !members.has(edge.to.node);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/avoid/hash.ts
|
|
44
|
+
var CELL = 128;
|
|
45
|
+
var SpatialHash = class _SpatialHash {
|
|
46
|
+
constructor(cell = CELL) {
|
|
47
|
+
this.cell = cell;
|
|
48
|
+
}
|
|
49
|
+
cell;
|
|
50
|
+
cells = /* @__PURE__ */ new Map();
|
|
51
|
+
query_ = 0;
|
|
52
|
+
/** The key of cell `(i, j)`. Packs both into one number for the map. */
|
|
53
|
+
static key(i, j) {
|
|
54
|
+
return (i + 32768) * 65536 + (j + 32768);
|
|
55
|
+
}
|
|
56
|
+
range(box) {
|
|
57
|
+
const c = this.cell;
|
|
58
|
+
return [Math.floor(box.x / c), Math.floor(box.y / c), Math.floor((box.x + box.width) / c), Math.floor((box.y + box.height) / c)];
|
|
59
|
+
}
|
|
60
|
+
insert(item, box) {
|
|
61
|
+
const filed = { item, seen: 0 };
|
|
62
|
+
const [i0, j0, i1, j1] = this.range(box);
|
|
63
|
+
for (let i = i0; i <= i1; i++) {
|
|
64
|
+
for (let j = j0; j <= j1; j++) {
|
|
65
|
+
const k = _SpatialHash.key(i, j);
|
|
66
|
+
const list = this.cells.get(k);
|
|
67
|
+
if (list === void 0) this.cells.set(k, [filed]);
|
|
68
|
+
else list.push(filed);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Take `item`, filed under `box`, back out. */
|
|
73
|
+
remove(item, box) {
|
|
74
|
+
const [i0, j0, i1, j1] = this.range(box);
|
|
75
|
+
for (let i = i0; i <= i1; i++) {
|
|
76
|
+
for (let j = j0; j <= j1; j++) {
|
|
77
|
+
const list = this.cells.get(_SpatialHash.key(i, j));
|
|
78
|
+
const at = list?.findIndex((f) => f.item === item) ?? -1;
|
|
79
|
+
if (at >= 0) list.splice(at, 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Every item filed in a cell `box` touches, each once. */
|
|
84
|
+
query(box, out = []) {
|
|
85
|
+
const [i0, j0, i1, j1] = this.range(box);
|
|
86
|
+
const stamp = ++this.query_;
|
|
87
|
+
for (let i = i0; i <= i1; i++) {
|
|
88
|
+
for (let j = j0; j <= j1; j++) {
|
|
89
|
+
const list = this.cells.get(_SpatialHash.key(i, j));
|
|
90
|
+
if (list === void 0) continue;
|
|
91
|
+
for (const filed of list) {
|
|
92
|
+
if (filed.seen === stamp) continue;
|
|
93
|
+
filed.seen = stamp;
|
|
94
|
+
out.push(filed.item);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/avoid/segments.ts
|
|
103
|
+
var EPS2 = 1e-7;
|
|
104
|
+
var boxOf = (s) => ({
|
|
105
|
+
x: Math.min(s.x0, s.x1),
|
|
106
|
+
y: Math.min(s.y0, s.y1),
|
|
107
|
+
width: Math.abs(s.x1 - s.x0),
|
|
108
|
+
height: Math.abs(s.y1 - s.y0)
|
|
109
|
+
});
|
|
110
|
+
function touches(s, box) {
|
|
111
|
+
const b = boxOf(s);
|
|
112
|
+
return b.x <= box.x + box.width && box.x <= b.x + b.width && b.y <= box.y + box.height && box.y <= b.y + b.height;
|
|
113
|
+
}
|
|
114
|
+
var SegmentIndex = class {
|
|
115
|
+
hash = new SpatialHash();
|
|
116
|
+
scratch = [];
|
|
117
|
+
/** File every segment of `points`, connector `edge`'s polyline. */
|
|
118
|
+
add(edge, points) {
|
|
119
|
+
for (let i = 0; i + 1 < points.length; i++) {
|
|
120
|
+
const a = points[i];
|
|
121
|
+
const b = points[i + 1];
|
|
122
|
+
const s = { edge, x0: a.x, y0: a.y, x1: b.x, y1: b.y };
|
|
123
|
+
this.hash.insert(s, boxOf(s));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** The connectors with a segment touching `box`. */
|
|
127
|
+
edgesMeeting(box, into) {
|
|
128
|
+
for (const s of this.hash.query(box)) if (touches(s, box)) into.add(s.edge);
|
|
129
|
+
}
|
|
130
|
+
/** How many segments of connectors other than `self` the axis-aligned step
|
|
131
|
+
* from `(x0, y0)` to `(x1, y1)` crosses at right angles. */
|
|
132
|
+
crossings(self, x0, y0, x1, y1) {
|
|
133
|
+
const lx = Math.min(x0, x1);
|
|
134
|
+
const hx = Math.max(x0, x1);
|
|
135
|
+
const ly = Math.min(y0, y1);
|
|
136
|
+
const hy = Math.max(y0, y1);
|
|
137
|
+
const horizontal = ly === hy;
|
|
138
|
+
this.scratch.length = 0;
|
|
139
|
+
let n = 0;
|
|
140
|
+
for (const s of this.hash.query({ x: lx, y: ly, width: hx - lx, height: hy - ly }, this.scratch)) {
|
|
141
|
+
if (s.edge === self) continue;
|
|
142
|
+
if (horizontal) {
|
|
143
|
+
if (Math.abs(s.x0 - s.x1) > EPS2) continue;
|
|
144
|
+
const sl = Math.min(s.y0, s.y1);
|
|
145
|
+
const sh = Math.max(s.y0, s.y1);
|
|
146
|
+
if (s.x0 > lx + EPS2 && s.x0 < hx - EPS2 && ly > sl + EPS2 && ly < sh - EPS2) n++;
|
|
147
|
+
} else {
|
|
148
|
+
if (Math.abs(s.y0 - s.y1) > EPS2) continue;
|
|
149
|
+
const sl = Math.min(s.x0, s.x1);
|
|
150
|
+
const sh = Math.max(s.x0, s.x1);
|
|
151
|
+
if (s.y0 > ly + EPS2 && s.y0 < hy - EPS2 && lx > sl + EPS2 && lx < sh - EPS2) n++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return n;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/avoid/dirty.ts
|
|
159
|
+
var sameBox = (a, b) => a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
160
|
+
function sameObstacle(a, b) {
|
|
161
|
+
if (a.kind !== b.kind || !sameBox(a.box, b.box)) return false;
|
|
162
|
+
const am = a.members;
|
|
163
|
+
const bm = b.members;
|
|
164
|
+
if (am === bm) return true;
|
|
165
|
+
if (am === void 0 || bm === void 0 || am.size !== bm.size) return false;
|
|
166
|
+
for (const id of am) if (!bm.has(id)) return false;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
function changedObstacles(before, now2) {
|
|
170
|
+
const ids = /* @__PURE__ */ new Set();
|
|
171
|
+
const boxes = [];
|
|
172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const o of now2) {
|
|
174
|
+
seen.add(o.id);
|
|
175
|
+
const was = before.get(o.id);
|
|
176
|
+
if (was !== void 0 && sameObstacle(was, o)) continue;
|
|
177
|
+
ids.add(o.id);
|
|
178
|
+
boxes.push(o.box);
|
|
179
|
+
if (was !== void 0) boxes.push(was.box);
|
|
180
|
+
}
|
|
181
|
+
for (const [id, was] of before) {
|
|
182
|
+
if (seen.has(id)) continue;
|
|
183
|
+
ids.add(id);
|
|
184
|
+
boxes.push(was.box);
|
|
185
|
+
}
|
|
186
|
+
return { ids, boxes };
|
|
187
|
+
}
|
|
188
|
+
var portKey = (p) => `${p.point.x},${p.point.y},${p.normal.x},${p.normal.y}`;
|
|
189
|
+
var stopsKey = (e) => `${e.from.node}:${e.from.ports.map(portKey).join(";")}|${e.to.node}:${e.to.ports.map(portKey).join(";")}|${(e.waypoints ?? []).map((w) => `${w.x},${w.y}`).join(";")}`;
|
|
190
|
+
function facing(ports, toward) {
|
|
191
|
+
let best;
|
|
192
|
+
let score = -Infinity;
|
|
193
|
+
for (const p of ports) {
|
|
194
|
+
const dx = toward.x - p.point.x;
|
|
195
|
+
const dy = toward.y - p.point.y;
|
|
196
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
197
|
+
const s = (p.normal.x * dx + p.normal.y * dy) / len - len / 1e6;
|
|
198
|
+
if (s > score) {
|
|
199
|
+
score = s;
|
|
200
|
+
best = p;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return best;
|
|
204
|
+
}
|
|
205
|
+
var centroid = (ports) => ({
|
|
206
|
+
x: ports.reduce((s, p) => s + p.point.x, 0) / Math.max(1, ports.length),
|
|
207
|
+
y: ports.reduce((s, p) => s + p.point.y, 0) / Math.max(1, ports.length)
|
|
208
|
+
});
|
|
209
|
+
function fullRoute(edge) {
|
|
210
|
+
const route = edge.route ?? [];
|
|
211
|
+
const from = facing(edge.from.ports, route[0] ?? centroid(edge.to.ports));
|
|
212
|
+
const to = facing(edge.to.ports, route[route.length - 1] ?? centroid(edge.from.ports));
|
|
213
|
+
return [...from ? [from.point] : [], ...route, ...to ? [to.point] : []];
|
|
214
|
+
}
|
|
215
|
+
function dirtyEdges(edges, keys, changed, clearance, routes) {
|
|
216
|
+
const first = [];
|
|
217
|
+
const meeting = /* @__PURE__ */ new Set();
|
|
218
|
+
if (changed.boxes.length > 0) {
|
|
219
|
+
const index = new SegmentIndex();
|
|
220
|
+
for (const e of edges) index.add(e.id, routes(e));
|
|
221
|
+
for (const box of changed.boxes) index.edgesMeeting(inflate(box, clearance), meeting);
|
|
222
|
+
}
|
|
223
|
+
const rest = [];
|
|
224
|
+
for (const e of edges) {
|
|
225
|
+
const attached = changed.ids.has(e.from.node) || changed.ids.has(e.to.node);
|
|
226
|
+
if (attached || keys.get(e.id) !== stopsKey(e) || e.route === void 0) first.push(e);
|
|
227
|
+
else if (meeting.has(e.id)) rest.push(e);
|
|
228
|
+
}
|
|
229
|
+
return [...first, ...rest];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/avoid/obstacles.ts
|
|
233
|
+
var EPS3 = 1e-7;
|
|
234
|
+
var ObstacleIndex = class {
|
|
235
|
+
constructor(obstacles, clearance, labelClearance = clearance) {
|
|
236
|
+
this.clearance = clearance;
|
|
237
|
+
this.labelClearance = labelClearance;
|
|
238
|
+
for (const obstacle of obstacles) this.file(obstacle);
|
|
239
|
+
}
|
|
240
|
+
clearance;
|
|
241
|
+
labelClearance;
|
|
242
|
+
byId = /* @__PURE__ */ new Map();
|
|
243
|
+
hash = new SpatialHash();
|
|
244
|
+
file(obstacle) {
|
|
245
|
+
const item = { obstacle, box: inflate(obstacle.box, obstacle.kind === "label" ? this.labelClearance : this.clearance) };
|
|
246
|
+
this.byId.set(obstacle.id, item);
|
|
247
|
+
this.hash.insert(item, item.box);
|
|
248
|
+
}
|
|
249
|
+
/** Obstacle `id` as filed, inflated. */
|
|
250
|
+
get(id) {
|
|
251
|
+
return this.byId.get(id);
|
|
252
|
+
}
|
|
253
|
+
/** File `obstacle` in place of the one with its id, which leaves the hash. */
|
|
254
|
+
replace(obstacle) {
|
|
255
|
+
const old = this.byId.get(obstacle.id);
|
|
256
|
+
if (old !== void 0) this.hash.remove(old, old.box);
|
|
257
|
+
this.file(obstacle);
|
|
258
|
+
}
|
|
259
|
+
/** The inflated obstacles touching `box`. */
|
|
260
|
+
near(box, out = []) {
|
|
261
|
+
return this.hash.query(box, out);
|
|
262
|
+
}
|
|
263
|
+
/** Remove obstacle `id`, if it is filed. */
|
|
264
|
+
delete(id) {
|
|
265
|
+
const old = this.byId.get(id);
|
|
266
|
+
if (old === void 0) return;
|
|
267
|
+
this.hash.remove(old, old.box);
|
|
268
|
+
this.byId.delete(id);
|
|
269
|
+
}
|
|
270
|
+
/** A view of the index for connector `edge`. */
|
|
271
|
+
forEdge(edge) {
|
|
272
|
+
return new EdgeObstacles(this, edge);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
var EdgeObstacles = class {
|
|
276
|
+
constructor(index, edge) {
|
|
277
|
+
this.index = index;
|
|
278
|
+
this.edge = edge;
|
|
279
|
+
}
|
|
280
|
+
index;
|
|
281
|
+
edge;
|
|
282
|
+
scratch = [];
|
|
283
|
+
/** The inflated boxes blocking this connector that touch `box`. */
|
|
284
|
+
near(box) {
|
|
285
|
+
this.scratch.length = 0;
|
|
286
|
+
const out = [];
|
|
287
|
+
for (const o of this.index.near(box, this.scratch)) if (blocks(o.obstacle, this.edge)) out.push(o.box);
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
/** Whether `(x, y)` lies strictly inside a blocking obstacle. */
|
|
291
|
+
inside(x, y) {
|
|
292
|
+
this.scratch.length = 0;
|
|
293
|
+
for (const o of this.index.near({ x, y, width: 0, height: 0 }, this.scratch)) {
|
|
294
|
+
const b = o.box;
|
|
295
|
+
if (x > b.x + EPS3 && x < b.x + b.width - EPS3 && y > b.y + EPS3 && y < b.y + b.height - EPS3 && blocks(o.obstacle, this.edge)) return true;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
/** Whether the axis-aligned step from `(x0, y0)` to `(x1, y1)` crosses a
|
|
300
|
+
* blocking obstacle's interior. */
|
|
301
|
+
crosses(x0, y0, x1, y1) {
|
|
302
|
+
const lx = Math.min(x0, x1);
|
|
303
|
+
const hx = Math.max(x0, x1);
|
|
304
|
+
const ly = Math.min(y0, y1);
|
|
305
|
+
const hy = Math.max(y0, y1);
|
|
306
|
+
this.scratch.length = 0;
|
|
307
|
+
for (const o of this.index.near({ x: lx, y: ly, width: hx - lx, height: hy - ly }, this.scratch)) {
|
|
308
|
+
const b = o.box;
|
|
309
|
+
if (hx > b.x + EPS3 && lx < b.x + b.width - EPS3 && hy > b.y + EPS3 && ly < b.y + b.height - EPS3 && blocks(o.obstacle, this.edge)) return true;
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/avoid/dragIndex.ts
|
|
316
|
+
var sameBox2 = (a, b) => a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
317
|
+
function union(a, b) {
|
|
318
|
+
const x = Math.min(a.x, b.x);
|
|
319
|
+
const y = Math.min(a.y, b.y);
|
|
320
|
+
return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
321
|
+
}
|
|
322
|
+
var DragIndex = class {
|
|
323
|
+
constructor(input, routes, clearance, limit) {
|
|
324
|
+
this.clearance = clearance;
|
|
325
|
+
this.limit = limit;
|
|
326
|
+
for (const o of input.obstacles) this.committed.set(o.id, o);
|
|
327
|
+
this.index = new ObstacleIndex(input.obstacles, clearance);
|
|
328
|
+
for (const e of input.edges) {
|
|
329
|
+
for (const node of /* @__PURE__ */ new Set([e.from.node, e.to.node])) {
|
|
330
|
+
const list = this.attached.get(node);
|
|
331
|
+
if (list === void 0) this.attached.set(node, [e.id]);
|
|
332
|
+
else list.push(e.id);
|
|
333
|
+
}
|
|
334
|
+
const route = routes.get(e.id);
|
|
335
|
+
if (route !== void 0) this.segments.add(e.id, route);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
clearance;
|
|
339
|
+
limit;
|
|
340
|
+
committed = /* @__PURE__ */ new Map();
|
|
341
|
+
/** The obstacles this frame has moved off their committed place, as they now are. */
|
|
342
|
+
displaced = /* @__PURE__ */ new Map();
|
|
343
|
+
index;
|
|
344
|
+
segments = new SegmentIndex();
|
|
345
|
+
attached = /* @__PURE__ */ new Map();
|
|
346
|
+
/** Swap this frame's obstacles into the index where they moved, and answer
|
|
347
|
+
* what it reroutes. Diffs the frame's obstacles against the last frame's;
|
|
348
|
+
* touches the hash and the connectors only for the ones that moved. */
|
|
349
|
+
frame(obstacles) {
|
|
350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
351
|
+
for (const o of obstacles) {
|
|
352
|
+
const base = this.committed.get(o.id);
|
|
353
|
+
if (base === void 0) continue;
|
|
354
|
+
const now2 = this.displaced.get(o.id) ?? base;
|
|
355
|
+
if (sameBox2(now2.box, o.box)) {
|
|
356
|
+
if (this.displaced.has(o.id)) seen.add(o.id);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
this.index.replace(o);
|
|
360
|
+
if (sameBox2(base.box, o.box)) this.displaced.delete(o.id);
|
|
361
|
+
else {
|
|
362
|
+
this.displaced.set(o.id, o);
|
|
363
|
+
seen.add(o.id);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const [id] of this.displaced) {
|
|
367
|
+
if (seen.has(id)) continue;
|
|
368
|
+
this.index.replace(this.committed.get(id));
|
|
369
|
+
this.displaced.delete(id);
|
|
370
|
+
}
|
|
371
|
+
const first = /* @__PURE__ */ new Set();
|
|
372
|
+
const meeting = /* @__PURE__ */ new Set();
|
|
373
|
+
let limit = this.limit;
|
|
374
|
+
for (const [id, o] of this.displaced) {
|
|
375
|
+
for (const e of this.attached.get(id) ?? []) first.add(e);
|
|
376
|
+
this.segments.edgesMeeting(inflate(this.committed.get(id).box, this.clearance), meeting);
|
|
377
|
+
this.segments.edgesMeeting(inflate(o.box, this.clearance), meeting);
|
|
378
|
+
limit = union(limit, o.box);
|
|
379
|
+
}
|
|
380
|
+
return { index: this.index, dirty: [...first, ...[...meeting].filter((e) => !first.has(e))], limit };
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
function labelObstacle(edge, route) {
|
|
384
|
+
const label = edge.label;
|
|
385
|
+
if (label === void 0 || route.length < 2) return null;
|
|
386
|
+
const center = labelCenter(polylineFromPoints(route), label.placement, label.size);
|
|
387
|
+
if (center === null) return null;
|
|
388
|
+
const { width, height } = label.size;
|
|
389
|
+
return { id: label.id, kind: "label", edge: edge.id, box: { x: center.x - width / 2, y: center.y - height / 2, width, height } };
|
|
390
|
+
}
|
|
391
|
+
function labelObstacles(edges, routeOf) {
|
|
392
|
+
const out = [];
|
|
393
|
+
for (const edge of edges) {
|
|
394
|
+
if (edge.label === void 0) continue;
|
|
395
|
+
const o = labelObstacle(edge, routeOf(edge));
|
|
396
|
+
if (o !== null) out.push(o);
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/avoid/grid.ts
|
|
402
|
+
var EPS4 = 1e-7;
|
|
403
|
+
function searchWindow(points, margin) {
|
|
404
|
+
let x0 = Infinity;
|
|
405
|
+
let y0 = Infinity;
|
|
406
|
+
let x1 = -Infinity;
|
|
407
|
+
let y1 = -Infinity;
|
|
408
|
+
for (const p of points) {
|
|
409
|
+
x0 = Math.min(x0, p.x);
|
|
410
|
+
y0 = Math.min(y0, p.y);
|
|
411
|
+
x1 = Math.max(x1, p.x);
|
|
412
|
+
y1 = Math.max(y1, p.y);
|
|
413
|
+
}
|
|
414
|
+
return { x: x0 - margin, y: y0 - margin, width: x1 - x0 + 2 * margin, height: y1 - y0 + 2 * margin };
|
|
415
|
+
}
|
|
416
|
+
var within = (inner, outer) => inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.width <= outer.x + outer.width && inner.y + inner.height <= outer.y + outer.height;
|
|
417
|
+
var holds = (box, p) => p.x >= box.x - EPS4 && p.x <= box.x + box.width + EPS4 && p.y >= box.y - EPS4 && p.y <= box.y + box.height + EPS4;
|
|
418
|
+
function firstPast(sorted, v, key) {
|
|
419
|
+
let lo = 0;
|
|
420
|
+
let hi = sorted.length;
|
|
421
|
+
while (lo < hi) {
|
|
422
|
+
const mid = lo + hi >> 1;
|
|
423
|
+
if (key(sorted[mid]) <= v) lo = mid + 1;
|
|
424
|
+
else hi = mid;
|
|
425
|
+
}
|
|
426
|
+
return lo;
|
|
427
|
+
}
|
|
428
|
+
function channelMiddles(boxes, axis) {
|
|
429
|
+
const lo = (b) => axis === "x" ? b.x : b.y;
|
|
430
|
+
const size = (b) => axis === "x" ? b.width : b.height;
|
|
431
|
+
const crossLo = (b) => axis === "x" ? b.y : b.x;
|
|
432
|
+
const crossHi = (b) => axis === "x" ? b.y + b.height : b.x + b.width;
|
|
433
|
+
const sorted = [...boxes].sort((a, b) => lo(a) - lo(b));
|
|
434
|
+
const out = [];
|
|
435
|
+
for (const a of sorted) {
|
|
436
|
+
const end = lo(a) + size(a);
|
|
437
|
+
let k = firstPast(sorted, end, lo);
|
|
438
|
+
let nearest = null;
|
|
439
|
+
for (; k < sorted.length && nearest === null; k++) {
|
|
440
|
+
const b = sorted[k];
|
|
441
|
+
if (crossLo(a) < crossHi(b) && crossLo(b) < crossHi(a)) nearest = b;
|
|
442
|
+
}
|
|
443
|
+
if (nearest === null) continue;
|
|
444
|
+
const along2 = (end + lo(nearest)) / 2;
|
|
445
|
+
const across2 = (Math.max(crossLo(a), crossLo(nearest)) + Math.min(crossHi(a), crossHi(nearest))) / 2;
|
|
446
|
+
out.push(axis === "x" ? { x: along2, y: across2 } : { x: across2, y: along2 });
|
|
447
|
+
}
|
|
448
|
+
return out;
|
|
449
|
+
}
|
|
450
|
+
function interestingPoints(window, boxes, ends) {
|
|
451
|
+
const out = [...ends];
|
|
452
|
+
for (const b of boxes) {
|
|
453
|
+
for (const p of [
|
|
454
|
+
{ x: b.x, y: b.y },
|
|
455
|
+
{ x: b.x + b.width, y: b.y },
|
|
456
|
+
{ x: b.x, y: b.y + b.height },
|
|
457
|
+
{ x: b.x + b.width, y: b.y + b.height }
|
|
458
|
+
]) if (holds(window, p)) out.push(p);
|
|
459
|
+
}
|
|
460
|
+
for (const p of [...channelMiddles(boxes, "x"), ...channelMiddles(boxes, "y")]) if (holds(window, p)) out.push(p);
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/avoid/heap.ts
|
|
465
|
+
var MinHeap = class {
|
|
466
|
+
ids = [];
|
|
467
|
+
keys = [];
|
|
468
|
+
ties = [];
|
|
469
|
+
get size() {
|
|
470
|
+
return this.ids.length;
|
|
471
|
+
}
|
|
472
|
+
less(a, b) {
|
|
473
|
+
const ka = this.keys[a];
|
|
474
|
+
const kb = this.keys[b];
|
|
475
|
+
return ka < kb || ka === kb && this.ties[a] < this.ties[b];
|
|
476
|
+
}
|
|
477
|
+
swap(a, b) {
|
|
478
|
+
const id = this.ids[a];
|
|
479
|
+
this.ids[a] = this.ids[b];
|
|
480
|
+
this.ids[b] = id;
|
|
481
|
+
const key = this.keys[a];
|
|
482
|
+
this.keys[a] = this.keys[b];
|
|
483
|
+
this.keys[b] = key;
|
|
484
|
+
const tie = this.ties[a];
|
|
485
|
+
this.ties[a] = this.ties[b];
|
|
486
|
+
this.ties[b] = tie;
|
|
487
|
+
}
|
|
488
|
+
push(id, key, tie) {
|
|
489
|
+
this.ids.push(id);
|
|
490
|
+
this.keys.push(key);
|
|
491
|
+
this.ties.push(tie);
|
|
492
|
+
let i = this.ids.length - 1;
|
|
493
|
+
while (i > 0) {
|
|
494
|
+
const parent = i - 1 >> 1;
|
|
495
|
+
if (!this.less(i, parent)) break;
|
|
496
|
+
this.swap(i, parent);
|
|
497
|
+
i = parent;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/** The id with the least key, removed. Undefined when empty. */
|
|
501
|
+
pop() {
|
|
502
|
+
const n = this.ids.length;
|
|
503
|
+
if (n === 0) return void 0;
|
|
504
|
+
const top = this.ids[0];
|
|
505
|
+
this.swap(0, n - 1);
|
|
506
|
+
this.ids.pop();
|
|
507
|
+
this.keys.pop();
|
|
508
|
+
this.ties.pop();
|
|
509
|
+
let i = 0;
|
|
510
|
+
for (; ; ) {
|
|
511
|
+
const l = 2 * i + 1;
|
|
512
|
+
const r = l + 1;
|
|
513
|
+
let m = i;
|
|
514
|
+
if (l < n - 1 && this.less(l, m)) m = l;
|
|
515
|
+
if (r < n - 1 && this.less(r, m)) m = r;
|
|
516
|
+
if (m === i) break;
|
|
517
|
+
this.swap(i, m);
|
|
518
|
+
i = m;
|
|
519
|
+
}
|
|
520
|
+
return top;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/avoid/turns.ts
|
|
525
|
+
function dirOf(v) {
|
|
526
|
+
if (Math.abs(v.x) >= Math.abs(v.y)) return v.x >= 0 ? 0 : 2;
|
|
527
|
+
return v.y >= 0 ? 1 : 3;
|
|
528
|
+
}
|
|
529
|
+
var turnedX = (x, y, k) => k === 0 ? x : k === 1 ? y : k === 2 ? -x : -y;
|
|
530
|
+
var turnedY = (x, y, k) => k === 0 ? y : k === 1 ? -x : k === 2 ? -y : x;
|
|
531
|
+
var EPS5 = 1e-7;
|
|
532
|
+
var sign = (v) => v > EPS5 ? 1 : v < -EPS5 ? -1 : 0;
|
|
533
|
+
function minTurns(p, h, t, arrive) {
|
|
534
|
+
const dx = p.x - t.x;
|
|
535
|
+
const dy = p.y - t.y;
|
|
536
|
+
if (arrive === null) {
|
|
537
|
+
const sx2 = sign(turnedX(dx, dy, h));
|
|
538
|
+
if (sign(turnedY(dx, dy, h)) === 0) return sx2 <= 0 ? 0 : 3;
|
|
539
|
+
return sx2 <= 0 ? 1 : 2;
|
|
540
|
+
}
|
|
541
|
+
const heading = (h - arrive + 4) % 4;
|
|
542
|
+
const sx = sign(turnedX(dx, dy, arrive));
|
|
543
|
+
const sy = sign(turnedY(dx, dy, arrive));
|
|
544
|
+
switch (heading) {
|
|
545
|
+
case 0:
|
|
546
|
+
if (sy === 0) return sx <= 0 ? 0 : 4;
|
|
547
|
+
return sx < 0 ? 2 : 4;
|
|
548
|
+
case 2:
|
|
549
|
+
return sy === 0 ? 4 : 2;
|
|
550
|
+
case 1:
|
|
551
|
+
return sy <= 0 && sx < 0 ? 1 : 3;
|
|
552
|
+
default:
|
|
553
|
+
return sy >= 0 && sx < 0 ? 1 : 3;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/avoid/stateTable.ts
|
|
558
|
+
var StateTable = class {
|
|
559
|
+
g = new Float64Array(0);
|
|
560
|
+
from = new Int32Array(0);
|
|
561
|
+
costStamp = new Uint32Array(0);
|
|
562
|
+
doneStamp = new Uint32Array(0);
|
|
563
|
+
step = new Float64Array(0);
|
|
564
|
+
stepStamp = new Uint32Array(0);
|
|
565
|
+
gen = 0;
|
|
566
|
+
/** Forget every state. */
|
|
567
|
+
reset() {
|
|
568
|
+
if (this.gen >= 4294967280) {
|
|
569
|
+
this.costStamp.fill(0);
|
|
570
|
+
this.doneStamp.fill(0);
|
|
571
|
+
this.stepStamp.fill(0);
|
|
572
|
+
this.gen = 0;
|
|
573
|
+
}
|
|
574
|
+
this.gen++;
|
|
575
|
+
}
|
|
576
|
+
/** Make room for states `0` to `n - 1`, keeping what is there. */
|
|
577
|
+
ensure(n) {
|
|
578
|
+
if (n <= this.g.length) return;
|
|
579
|
+
const size = Math.max(n, this.g.length * 2, 1024);
|
|
580
|
+
const grow2 = (a, make) => {
|
|
581
|
+
const b = make(size);
|
|
582
|
+
b.set(a);
|
|
583
|
+
return b;
|
|
584
|
+
};
|
|
585
|
+
this.g = grow2(this.g, (k) => new Float64Array(k));
|
|
586
|
+
this.from = grow2(this.from, (k) => new Int32Array(k));
|
|
587
|
+
this.costStamp = grow2(this.costStamp, (k) => new Uint32Array(k));
|
|
588
|
+
this.doneStamp = grow2(this.doneStamp, (k) => new Uint32Array(k));
|
|
589
|
+
this.step = grow2(this.step, (k) => new Float64Array(k));
|
|
590
|
+
this.stepStamp = grow2(this.stepStamp, (k) => new Uint32Array(k));
|
|
591
|
+
}
|
|
592
|
+
cost(s) {
|
|
593
|
+
return this.costStamp[s] === this.gen ? this.g[s] : Infinity;
|
|
594
|
+
}
|
|
595
|
+
/** The state `s` was reached from, or -1 for a start. */
|
|
596
|
+
parent(s) {
|
|
597
|
+
return this.from[s];
|
|
598
|
+
}
|
|
599
|
+
reach(s, cost, parent) {
|
|
600
|
+
this.g[s] = cost;
|
|
601
|
+
this.from[s] = parent;
|
|
602
|
+
this.costStamp[s] = this.gen;
|
|
603
|
+
}
|
|
604
|
+
done(s) {
|
|
605
|
+
return this.doneStamp[s] === this.gen;
|
|
606
|
+
}
|
|
607
|
+
settle(s) {
|
|
608
|
+
this.doneStamp[s] = this.gen;
|
|
609
|
+
}
|
|
610
|
+
/** The cost remembered for step `key`, or NaN. */
|
|
611
|
+
stepCost(key) {
|
|
612
|
+
return this.stepStamp[key] === this.gen ? this.step[key] : NaN;
|
|
613
|
+
}
|
|
614
|
+
rememberStep(key, cost) {
|
|
615
|
+
this.step[key] = cost;
|
|
616
|
+
this.stepStamp[key] = this.gen;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// src/avoid/visibility.ts
|
|
621
|
+
var EPS6 = 1e-7;
|
|
622
|
+
var STRIP = 64;
|
|
623
|
+
function strips(boxes, horizontal) {
|
|
624
|
+
const n = boxes.length;
|
|
625
|
+
const s = {
|
|
626
|
+
lo: new Float64Array(n),
|
|
627
|
+
hi: new Float64Array(n),
|
|
628
|
+
aLo: new Float64Array(n),
|
|
629
|
+
aHi: new Float64Array(n),
|
|
630
|
+
start: Infinity,
|
|
631
|
+
size: STRIP,
|
|
632
|
+
cells: []
|
|
633
|
+
};
|
|
634
|
+
boxes.forEach((b, i) => {
|
|
635
|
+
s.lo[i] = horizontal ? b.y : b.x;
|
|
636
|
+
s.hi[i] = horizontal ? b.y + b.height : b.x + b.width;
|
|
637
|
+
s.aLo[i] = horizontal ? b.x : b.y;
|
|
638
|
+
s.aHi[i] = horizontal ? b.x + b.width : b.y + b.height;
|
|
639
|
+
s.start = Math.min(s.start, s.lo[i]);
|
|
640
|
+
});
|
|
641
|
+
for (let i = 0; i < n; i++) {
|
|
642
|
+
for (let k = Math.floor((s.lo[i] - s.start) / STRIP); k <= Math.floor((s.hi[i] - s.start) / STRIP); k++) {
|
|
643
|
+
(s.cells[k] ??= []).push(i);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return s;
|
|
647
|
+
}
|
|
648
|
+
function reach(at, c, s, lo, hi) {
|
|
649
|
+
const cell = s.cells[Math.floor((c - s.start) / STRIP)] ?? [];
|
|
650
|
+
for (const i of cell) {
|
|
651
|
+
if (c <= s.lo[i] + EPS6 || c >= s.hi[i] - EPS6) continue;
|
|
652
|
+
if (s.aHi[i] <= at + EPS6) lo = Math.max(lo, s.aHi[i]);
|
|
653
|
+
else if (s.aLo[i] >= at - EPS6) hi = Math.min(hi, s.aLo[i]);
|
|
654
|
+
else return null;
|
|
655
|
+
}
|
|
656
|
+
return { c, lo, hi };
|
|
657
|
+
}
|
|
658
|
+
function merged(runs) {
|
|
659
|
+
runs.sort((a, b) => a.c - b.c || a.lo - b.lo);
|
|
660
|
+
const out = [];
|
|
661
|
+
for (const r of runs) {
|
|
662
|
+
const last = out[out.length - 1];
|
|
663
|
+
if (last !== void 0 && last.c === r.c && r.lo <= last.hi + EPS6) last.hi = Math.max(last.hi, r.hi);
|
|
664
|
+
else out.push({ ...r });
|
|
665
|
+
}
|
|
666
|
+
return out;
|
|
667
|
+
}
|
|
668
|
+
function after(runs, c, orAt) {
|
|
669
|
+
let lo = 0;
|
|
670
|
+
let hi = runs.length;
|
|
671
|
+
while (lo < hi) {
|
|
672
|
+
const mid = lo + hi >> 1;
|
|
673
|
+
const m = runs[mid].c;
|
|
674
|
+
if (orAt ? m < c - EPS6 : m <= c + EPS6) lo = mid + 1;
|
|
675
|
+
else hi = mid;
|
|
676
|
+
}
|
|
677
|
+
return lo;
|
|
678
|
+
}
|
|
679
|
+
var covers = (r, at) => r.lo <= at + EPS6 && r.hi >= at - EPS6;
|
|
680
|
+
function runAt(runs, c, at) {
|
|
681
|
+
for (let k = after(runs, c, true); k < runs.length && runs[k].c <= c + EPS6; k++) if (covers(runs[k], at)) return k;
|
|
682
|
+
return -1;
|
|
683
|
+
}
|
|
684
|
+
function crossing(along2, from, step, across2) {
|
|
685
|
+
if (step > 0) {
|
|
686
|
+
for (let k = after(across2, from, false); k < across2.length && across2[k].c <= along2.hi + EPS6; k++) {
|
|
687
|
+
if (covers(across2[k], along2.c)) return k;
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
for (let k = after(across2, from, true) - 1; k >= 0 && across2[k].c >= along2.lo - EPS6; k--) {
|
|
691
|
+
if (covers(across2[k], along2.c)) return k;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return -1;
|
|
695
|
+
}
|
|
696
|
+
function castLines(points, boxes, window) {
|
|
697
|
+
const across2 = strips(boxes, true);
|
|
698
|
+
const down = strips(boxes, false);
|
|
699
|
+
const rows = [];
|
|
700
|
+
const cols = [];
|
|
701
|
+
for (const p of points) {
|
|
702
|
+
const h = reach(p.x, p.y, across2, window.x, window.x + window.width);
|
|
703
|
+
const v = h === null ? null : reach(p.y, p.x, down, window.y, window.y + window.height);
|
|
704
|
+
if (h === null || v === null) continue;
|
|
705
|
+
rows.push(h);
|
|
706
|
+
cols.push(v);
|
|
707
|
+
}
|
|
708
|
+
return { hs: merged(rows), vs: merged(cols) };
|
|
709
|
+
}
|
|
710
|
+
function visibilityGraph(points, boxes, window) {
|
|
711
|
+
const { hs, vs } = castLines(points, boxes, window);
|
|
712
|
+
const [x0, x1, y0, y1] = [window.x, window.x + window.width, window.y, window.y + window.height];
|
|
713
|
+
const toEdge = (r, lo, hi) => r.lo <= lo + EPS6 || r.hi >= hi - EPS6;
|
|
714
|
+
const row = [];
|
|
715
|
+
const col = [];
|
|
716
|
+
const near = [];
|
|
717
|
+
const ids = /* @__PURE__ */ new Map();
|
|
718
|
+
const idOf = (h, v) => {
|
|
719
|
+
const key = h * vs.length + v;
|
|
720
|
+
let id = ids.get(key);
|
|
721
|
+
if (id === void 0) {
|
|
722
|
+
id = row.length;
|
|
723
|
+
ids.set(key, id);
|
|
724
|
+
row.push(h);
|
|
725
|
+
col.push(v);
|
|
726
|
+
near.push(-2, -2, -2, -2);
|
|
727
|
+
}
|
|
728
|
+
return id;
|
|
729
|
+
};
|
|
730
|
+
return {
|
|
731
|
+
x: (v) => vs[col[v]].c,
|
|
732
|
+
y: (v) => hs[row[v]].c,
|
|
733
|
+
neighbor(v, d) {
|
|
734
|
+
const known = near[v * 4 + d];
|
|
735
|
+
if (known !== -2) return known;
|
|
736
|
+
const h = row[v];
|
|
737
|
+
const c = col[v];
|
|
738
|
+
let found = -1;
|
|
739
|
+
if (d === 0 || d === 2) {
|
|
740
|
+
const k = crossing(hs[h], vs[c].c, d === 0 ? 1 : -1, vs);
|
|
741
|
+
if (k >= 0) found = idOf(h, k);
|
|
742
|
+
} else {
|
|
743
|
+
const k = crossing(vs[c], hs[h].c, d === 1 ? 1 : -1, hs);
|
|
744
|
+
if (k >= 0) found = idOf(k, c);
|
|
745
|
+
}
|
|
746
|
+
near[v * 4 + d] = found;
|
|
747
|
+
return found;
|
|
748
|
+
},
|
|
749
|
+
at(p) {
|
|
750
|
+
const h = runAt(hs, p.y, p.x);
|
|
751
|
+
const v = runAt(vs, p.x, p.y);
|
|
752
|
+
return h < 0 || v < 0 ? -1 : idOf(h, v);
|
|
753
|
+
},
|
|
754
|
+
size: () => row.length,
|
|
755
|
+
open: (v) => toEdge(hs[row[v]], x0, x1) || toEdge(vs[col[v]], y0, y1)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/avoid/search.ts
|
|
760
|
+
var ENCLOSED = "enclosed";
|
|
761
|
+
function sealed(graph, starts) {
|
|
762
|
+
const seen = new Set(starts);
|
|
763
|
+
const queue = [...starts];
|
|
764
|
+
while (queue.length > 0) {
|
|
765
|
+
const v = queue.pop();
|
|
766
|
+
if (graph.open(v)) return false;
|
|
767
|
+
for (let d = 0; d < 4; d++) {
|
|
768
|
+
const w = graph.neighbor(v, d);
|
|
769
|
+
if (w >= 0 && !seen.has(w)) {
|
|
770
|
+
seen.add(w);
|
|
771
|
+
queue.push(w);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
var MAX_SETTLED = 5e4;
|
|
778
|
+
var TABLE = new StateTable();
|
|
779
|
+
function searchLeg(sources, targets, ctx) {
|
|
780
|
+
const { bendPenalty } = ctx;
|
|
781
|
+
const ends = [...sources.map((s) => s.point), ...targets.map((t) => t.point)];
|
|
782
|
+
const boxes = ctx.obstacles.near(ctx.window);
|
|
783
|
+
const graph = visibilityGraph(interestingPoints(ctx.window, boxes, ends), boxes, ctx.window);
|
|
784
|
+
const goals = /* @__PURE__ */ new Map();
|
|
785
|
+
targets.forEach((t, k) => {
|
|
786
|
+
const v = graph.at(t.point);
|
|
787
|
+
if (v >= 0) goals.set(v, [...goals.get(v) ?? [], k]);
|
|
788
|
+
});
|
|
789
|
+
if (goals.size === 0) return ENCLOSED;
|
|
790
|
+
const h = (v, d) => {
|
|
791
|
+
const p = { x: graph.x(v), y: graph.y(v) };
|
|
792
|
+
let best = Infinity;
|
|
793
|
+
for (const t of targets) {
|
|
794
|
+
const est = Math.abs(t.point.x - p.x) + Math.abs(t.point.y - p.y) + bendPenalty * minTurns(p, d, t.point, t.arrive);
|
|
795
|
+
if (est < best) best = est;
|
|
796
|
+
}
|
|
797
|
+
return best;
|
|
798
|
+
};
|
|
799
|
+
const table = TABLE;
|
|
800
|
+
table.reset();
|
|
801
|
+
const origin = /* @__PURE__ */ new Map();
|
|
802
|
+
const heap = new MinHeap();
|
|
803
|
+
sources.forEach((s, k) => {
|
|
804
|
+
const v = graph.at(s.point);
|
|
805
|
+
const state = v * 4 + s.heading;
|
|
806
|
+
if (v < 0) return;
|
|
807
|
+
table.ensure(graph.size() * 4);
|
|
808
|
+
if (table.cost(state) <= 0) return;
|
|
809
|
+
table.reach(state, 0, -1);
|
|
810
|
+
origin.set(state, k);
|
|
811
|
+
heap.push(state, h(v, s.heading), 0);
|
|
812
|
+
});
|
|
813
|
+
const starts = [...origin.keys()].map((s) => s >> 2);
|
|
814
|
+
if (starts.length === 0) return ENCLOSED;
|
|
815
|
+
const stepCost = (v, w, key) => {
|
|
816
|
+
let cost = table.stepCost(key);
|
|
817
|
+
if (!Number.isNaN(cost)) return cost;
|
|
818
|
+
const x0 = graph.x(v);
|
|
819
|
+
const y0 = graph.y(v);
|
|
820
|
+
const x1 = graph.x(w);
|
|
821
|
+
const y1 = graph.y(w);
|
|
822
|
+
cost = Math.abs(x1 - x0) + Math.abs(y1 - y0) + (ctx.crossings ? ctx.crossings.penalty * ctx.crossings.index.crossings(ctx.edge, x0, y0, x1, y1) : 0);
|
|
823
|
+
table.rememberStep(key, cost);
|
|
824
|
+
return cost;
|
|
825
|
+
};
|
|
826
|
+
let settled = 0;
|
|
827
|
+
while (heap.size > 0) {
|
|
828
|
+
if (settled >= MAX_SETTLED) return ENCLOSED;
|
|
829
|
+
const state = heap.pop();
|
|
830
|
+
if (table.done(state)) continue;
|
|
831
|
+
table.settle(state);
|
|
832
|
+
settled++;
|
|
833
|
+
const v = state >> 2;
|
|
834
|
+
const d = state & 3;
|
|
835
|
+
const hit = goals.get(v)?.find((k) => targets[k].arrive === null || targets[k].arrive === d);
|
|
836
|
+
if (hit !== void 0) return finish(state, hit);
|
|
837
|
+
const base = table.cost(state);
|
|
838
|
+
for (let nd = 0; nd < 4; nd = nd + 1) {
|
|
839
|
+
if (nd === (d + 2 & 3)) continue;
|
|
840
|
+
const w = graph.neighbor(v, nd);
|
|
841
|
+
if (w < 0) continue;
|
|
842
|
+
table.ensure(graph.size() * 4);
|
|
843
|
+
const cost = base + stepCost(v, w, v * 4 + nd) + (nd === d ? 0 : bendPenalty);
|
|
844
|
+
const to = w * 4 + nd;
|
|
845
|
+
if (cost >= table.cost(to)) continue;
|
|
846
|
+
table.reach(to, cost, state);
|
|
847
|
+
const rest = h(w, nd);
|
|
848
|
+
heap.push(to, cost + rest, rest);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (sealed(graph, starts) || sealed(graph, [...goals.keys()])) return ENCLOSED;
|
|
852
|
+
return null;
|
|
853
|
+
function finish(goal, target) {
|
|
854
|
+
const states = [goal];
|
|
855
|
+
while (table.parent(states[states.length - 1]) >= 0) states.push(table.parent(states[states.length - 1]));
|
|
856
|
+
states.reverse();
|
|
857
|
+
const source = origin.get(states[0]);
|
|
858
|
+
const points = [sources[source].point];
|
|
859
|
+
for (let k = 1; k < states.length; k++) {
|
|
860
|
+
const turnsHere = k + 1 < states.length && (states[k] & 3) !== (states[k + 1] & 3);
|
|
861
|
+
if (turnsHere) points.push({ x: graph.x(states[k] >> 2), y: graph.y(states[k] >> 2) });
|
|
862
|
+
}
|
|
863
|
+
points.push(targets[target].point);
|
|
864
|
+
return { points, source, target, arrive: goal & 3, cost: table.cost(goal) };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/avoid/edgeRoute.ts
|
|
869
|
+
function exitOf(port, clearance) {
|
|
870
|
+
const len = Math.hypot(port.normal.x, port.normal.y) || 1;
|
|
871
|
+
const k = (clearance + 1) / len;
|
|
872
|
+
return { x: port.point.x + port.normal.x * k, y: port.point.y + port.normal.y * k };
|
|
873
|
+
}
|
|
874
|
+
var reversed = (v) => ({ x: -v.x, y: -v.y });
|
|
875
|
+
function routeEdge(edge, ctx) {
|
|
876
|
+
const { clearance, bendPenalty, crossPenalty, margin } = ctx.options;
|
|
877
|
+
const fromPorts = edge.from.ports;
|
|
878
|
+
const toPorts = edge.to.ports;
|
|
879
|
+
if (fromPorts.length === 0 || toPorts.length === 0) return null;
|
|
880
|
+
const obstacles = ctx.index.forEdge(edge);
|
|
881
|
+
const waypoints = edge.waypoints ?? [];
|
|
882
|
+
const crossings = ctx.crossings === void 0 ? void 0 : { index: ctx.crossings, penalty: crossPenalty };
|
|
883
|
+
const legs = [];
|
|
884
|
+
let fromPort = null;
|
|
885
|
+
let toPort = null;
|
|
886
|
+
let heading = 0;
|
|
887
|
+
for (let k = 0; k <= waypoints.length; k++) {
|
|
888
|
+
const sources = k === 0 ? fromPorts.map((p) => ({ point: exitOf(p, clearance), heading: dirOf(p.normal) })) : [{ point: waypoints[k - 1], heading }];
|
|
889
|
+
const targets = k === waypoints.length ? toPorts.map((p) => ({ point: exitOf(p, clearance), arrive: dirOf(reversed(p.normal)) })) : [{ point: waypoints[k], arrive: null }];
|
|
890
|
+
const points = [...sources.map((s) => s.point), ...targets.map((t) => t.point)];
|
|
891
|
+
let grow2 = margin;
|
|
892
|
+
let found = null;
|
|
893
|
+
for (; ; ) {
|
|
894
|
+
const window = searchWindow(points, grow2);
|
|
895
|
+
found = searchLeg(sources, targets, { edge: edge.id, obstacles, window, bendPenalty, crossings });
|
|
896
|
+
if (found !== null || within(ctx.limit, window)) break;
|
|
897
|
+
grow2 *= 2;
|
|
898
|
+
}
|
|
899
|
+
if (found === null || found === ENCLOSED) return null;
|
|
900
|
+
if (k === 0) fromPort = fromPorts[found.source];
|
|
901
|
+
if (k === waypoints.length) toPort = toPorts[found.target];
|
|
902
|
+
heading = found.arrive;
|
|
903
|
+
legs.push(found.points);
|
|
904
|
+
}
|
|
905
|
+
legs[0] = [fromPort.point, ...legs[0]];
|
|
906
|
+
legs[legs.length - 1] = [...legs[legs.length - 1], toPort.point];
|
|
907
|
+
const out = [];
|
|
908
|
+
for (const leg of legs) {
|
|
909
|
+
const simple = simplifyStops(leg);
|
|
910
|
+
out.push(...out.length === 0 ? simple : simple.slice(1));
|
|
911
|
+
}
|
|
912
|
+
return out;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/avoid/nudge.ts
|
|
916
|
+
var EPS7 = 1e-6;
|
|
917
|
+
var along = (p, axis) => axis === "h" ? p.x : p.y;
|
|
918
|
+
var across = (p, axis) => axis === "h" ? p.y : p.x;
|
|
919
|
+
var sideOf = (v) => v > EPS7 ? 1 : v < -EPS7 ? -1 : 0;
|
|
920
|
+
var isWaypoint = (p, waypoints) => waypoints.some((w) => Math.abs(w.x - p.x) < EPS7 && Math.abs(w.y - p.y) < EPS7);
|
|
921
|
+
function runsOf(routes, axis) {
|
|
922
|
+
const out = [];
|
|
923
|
+
for (const [edge, { points, waypoints }] of routes) {
|
|
924
|
+
for (let i = 1; i + 2 < points.length; i++) {
|
|
925
|
+
const a = points[i];
|
|
926
|
+
const b = points[i + 1];
|
|
927
|
+
if (Math.abs(across(a, axis) - across(b, axis)) > EPS7 || Math.abs(along(a, axis) - along(b, axis)) < EPS7) continue;
|
|
928
|
+
if (isWaypoint(a, waypoints) || isWaypoint(b, waypoints)) continue;
|
|
929
|
+
const c = across(a, axis);
|
|
930
|
+
const forward = along(a, axis) < along(b, axis);
|
|
931
|
+
const [loEnd, loNext, hiEnd, hiNext] = forward ? [a, points[i - 1], b, points[i + 2]] : [b, points[i + 2], a, points[i - 1]];
|
|
932
|
+
out.push({
|
|
933
|
+
edge,
|
|
934
|
+
i,
|
|
935
|
+
c,
|
|
936
|
+
lo: along(loEnd, axis),
|
|
937
|
+
hi: along(hiEnd, axis),
|
|
938
|
+
sideLo: sideOf(across(loNext, axis) - c),
|
|
939
|
+
sideHi: sideOf(across(hiNext, axis) - c)
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return out;
|
|
944
|
+
}
|
|
945
|
+
function bundlesOf(runs) {
|
|
946
|
+
runs.sort((s, t) => s.c - t.c || s.lo - t.lo);
|
|
947
|
+
const out = [];
|
|
948
|
+
let current = [];
|
|
949
|
+
let reach2 = -Infinity;
|
|
950
|
+
const flush = () => {
|
|
951
|
+
if (new Set(current.map((r) => r.edge)).size > 1) out.push(current);
|
|
952
|
+
current = [];
|
|
953
|
+
};
|
|
954
|
+
for (const run of runs) {
|
|
955
|
+
const joins = current.length > 0 && Math.abs(run.c - current[0].c) < EPS7 && run.lo < reach2 - EPS7;
|
|
956
|
+
if (!joins) {
|
|
957
|
+
flush();
|
|
958
|
+
reach2 = -Infinity;
|
|
959
|
+
}
|
|
960
|
+
current.push(run);
|
|
961
|
+
reach2 = Math.max(reach2, run.hi);
|
|
962
|
+
}
|
|
963
|
+
flush();
|
|
964
|
+
return out;
|
|
965
|
+
}
|
|
966
|
+
function order(s, t) {
|
|
967
|
+
if (s.sideLo !== 0 && t.sideLo !== 0 && (s.sideLo !== t.sideLo || Math.abs(s.lo - t.lo) > EPS7)) {
|
|
968
|
+
if (s.sideLo !== t.sideLo) return s.sideLo - t.sideLo;
|
|
969
|
+
return (s.lo < t.lo ? 1 : -1) * -s.sideLo;
|
|
970
|
+
}
|
|
971
|
+
if (s.sideHi !== 0 && t.sideHi !== 0 && (s.sideHi !== t.sideHi || Math.abs(s.hi - t.hi) > EPS7)) {
|
|
972
|
+
if (s.sideHi !== t.sideHi) return s.sideHi - t.sideHi;
|
|
973
|
+
return (s.hi > t.hi ? 1 : -1) * -s.sideHi;
|
|
974
|
+
}
|
|
975
|
+
return s.edge < t.edge ? -1 : s.edge > t.edge ? 1 : s.i - t.i;
|
|
976
|
+
}
|
|
977
|
+
function freeSpan(obstacles, axis, c, lo, hi, reach2, edges) {
|
|
978
|
+
const probe = axis === "h" ? { x: lo, y: c - reach2, width: hi - lo, height: 2 * reach2 } : { x: c - reach2, y: lo, width: 2 * reach2, height: hi - lo };
|
|
979
|
+
let low = c - reach2;
|
|
980
|
+
let high = c + reach2;
|
|
981
|
+
for (const { obstacle } of obstacles.near(probe)) {
|
|
982
|
+
if (obstacle.kind === "label" && edges.has(obstacle.edge)) continue;
|
|
983
|
+
const b = obstacle.box;
|
|
984
|
+
const [bLo, bHi, bAcross, bSize] = axis === "h" ? [b.x, b.x + b.width, b.y, b.height] : [b.y, b.y + b.height, b.x, b.width];
|
|
985
|
+
if (bHi <= lo + EPS7 || bLo >= hi - EPS7) continue;
|
|
986
|
+
if (bAcross + bSize <= c + EPS7) low = Math.max(low, bAcross + bSize);
|
|
987
|
+
else if (bAcross >= c - EPS7) high = Math.min(high, bAcross);
|
|
988
|
+
}
|
|
989
|
+
return [low + 1, high - 1];
|
|
990
|
+
}
|
|
991
|
+
function spread(bundle, spacing, span) {
|
|
992
|
+
const n = bundle.length;
|
|
993
|
+
const c = bundle[0].c;
|
|
994
|
+
const room = span[1] - span[0];
|
|
995
|
+
if (room <= 0) return bundle.map(() => c);
|
|
996
|
+
const step = Math.min(spacing, room / (n - 1));
|
|
997
|
+
let at = bundle.map((_, k) => c + (k - (n - 1) / 2) * step);
|
|
998
|
+
const shift = at[0] < span[0] ? span[0] - at[0] : at[n - 1] > span[1] ? span[1] - at[n - 1] : 0;
|
|
999
|
+
if (shift !== 0) at = at.map((v) => v + shift);
|
|
1000
|
+
return at;
|
|
1001
|
+
}
|
|
1002
|
+
function nudgeAxis(routes, axis, spacing, obstacles) {
|
|
1003
|
+
for (const bundle of bundlesOf(runsOf(routes, axis))) {
|
|
1004
|
+
bundle.sort(order);
|
|
1005
|
+
const lo = Math.min(...bundle.map((r) => r.lo));
|
|
1006
|
+
const hi = Math.max(...bundle.map((r) => r.hi));
|
|
1007
|
+
const span = freeSpan(obstacles, axis, bundle[0].c, lo, hi, spacing * bundle.length, new Set(bundle.map((r) => r.edge)));
|
|
1008
|
+
spread(bundle, spacing, span).forEach((value, k) => {
|
|
1009
|
+
const run = bundle[k];
|
|
1010
|
+
const points = routes.get(run.edge).points;
|
|
1011
|
+
for (const j of [run.i, run.i + 1]) {
|
|
1012
|
+
points[j] = axis === "h" ? { x: points[j].x, y: value } : { x: value, y: points[j].y };
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function simplifyKeeping(points, waypoints) {
|
|
1018
|
+
const out = [];
|
|
1019
|
+
let start = 0;
|
|
1020
|
+
for (let k = 1; k < points.length; k++) {
|
|
1021
|
+
if (k !== points.length - 1 && !isWaypoint(points[k], waypoints)) continue;
|
|
1022
|
+
const piece = simplifyStops(points.slice(start, k + 1));
|
|
1023
|
+
out.push(...out.length === 0 ? piece : piece.slice(1));
|
|
1024
|
+
start = k;
|
|
1025
|
+
}
|
|
1026
|
+
return out;
|
|
1027
|
+
}
|
|
1028
|
+
function nudge(routes, spacing, obstacles) {
|
|
1029
|
+
const work = new Map([...routes].map(([id, r]) => [id, { points: [...r.points], waypoints: r.waypoints }]));
|
|
1030
|
+
nudgeAxis(work, "h", spacing, obstacles);
|
|
1031
|
+
nudgeAxis(work, "v", spacing, obstacles);
|
|
1032
|
+
return new Map([...work].map(([id, r]) => [id, simplifyKeeping(r.points, r.waypoints)]));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/avoid/router.ts
|
|
1036
|
+
var now = () => globalThis.performance?.now() ?? Date.now();
|
|
1037
|
+
function limitOf(input, margin) {
|
|
1038
|
+
let x0 = Infinity;
|
|
1039
|
+
let y0 = Infinity;
|
|
1040
|
+
let x1 = -Infinity;
|
|
1041
|
+
let y1 = -Infinity;
|
|
1042
|
+
const take = (x, y) => {
|
|
1043
|
+
x0 = Math.min(x0, x);
|
|
1044
|
+
y0 = Math.min(y0, y);
|
|
1045
|
+
x1 = Math.max(x1, x);
|
|
1046
|
+
y1 = Math.max(y1, y);
|
|
1047
|
+
};
|
|
1048
|
+
for (const { box } of input.obstacles) {
|
|
1049
|
+
take(box.x, box.y);
|
|
1050
|
+
take(box.x + box.width, box.y + box.height);
|
|
1051
|
+
}
|
|
1052
|
+
for (const e of input.edges) {
|
|
1053
|
+
for (const p of [...e.from.ports, ...e.to.ports]) take(p.point.x, p.point.y);
|
|
1054
|
+
for (const w of e.waypoints ?? []) take(w.x, w.y);
|
|
1055
|
+
}
|
|
1056
|
+
if (x0 === Infinity) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1057
|
+
return { x: x0 - 2 * margin, y: y0 - 2 * margin, width: x1 - x0 + 4 * margin, height: y1 - y0 + 4 * margin };
|
|
1058
|
+
}
|
|
1059
|
+
function createAvoidRouter(options = {}) {
|
|
1060
|
+
const opts = { ...AVOID_DEFAULTS, ...options };
|
|
1061
|
+
let committed = /* @__PURE__ */ new Map();
|
|
1062
|
+
const keys = /* @__PURE__ */ new Map();
|
|
1063
|
+
const raw = /* @__PURE__ */ new Map();
|
|
1064
|
+
const shown = /* @__PURE__ */ new Map();
|
|
1065
|
+
const previews = /* @__PURE__ */ new Map();
|
|
1066
|
+
let drag = null;
|
|
1067
|
+
let positions = /* @__PURE__ */ new Map();
|
|
1068
|
+
const routeOf = (edge) => {
|
|
1069
|
+
const mine = shown.get(edge.id);
|
|
1070
|
+
return mine !== void 0 && edge.route !== void 0 && samePoints(mine.slice(1, -1), edge.route) ? mine : fullRoute(edge);
|
|
1071
|
+
};
|
|
1072
|
+
const rawOf = (edge) => {
|
|
1073
|
+
const mine = shown.get(edge.id);
|
|
1074
|
+
return mine !== void 0 && raw.has(edge.id) && edge.route !== void 0 && samePoints(mine.slice(1, -1), edge.route) ? raw.get(edge.id) : routeOf(edge);
|
|
1075
|
+
};
|
|
1076
|
+
function forget(input) {
|
|
1077
|
+
const live = new Set(input.edges.map((e) => e.id));
|
|
1078
|
+
for (const map of [keys, raw, shown]) for (const id of [...map.keys()]) if (!live.has(id)) map.delete(id);
|
|
1079
|
+
}
|
|
1080
|
+
function indexForDrag(input) {
|
|
1081
|
+
const routes = new Map(input.edges.map((e) => [e.id, routeOf(e)]));
|
|
1082
|
+
drag = new DragIndex(input, routes, opts.clearance, limitOf(input, opts.margin));
|
|
1083
|
+
positions = new Map(input.edges.map((e, k) => [e.id, k]));
|
|
1084
|
+
}
|
|
1085
|
+
const edgeIn = (edges, id) => {
|
|
1086
|
+
const at = positions.get(id);
|
|
1087
|
+
return at !== void 0 && edges[at]?.id === id ? edges[at] : edges.find((e) => e.id === id);
|
|
1088
|
+
};
|
|
1089
|
+
function preview(input, budgetMs) {
|
|
1090
|
+
const start = now();
|
|
1091
|
+
if (drag === null) indexForDrag({ obstacles: [...committed.values()], edges: input.edges });
|
|
1092
|
+
const frame = drag.frame(input.obstacles);
|
|
1093
|
+
const out = /* @__PURE__ */ new Map();
|
|
1094
|
+
for (const id of frame.dirty) {
|
|
1095
|
+
if (now() - start > budgetMs) {
|
|
1096
|
+
const last = previews.get(id);
|
|
1097
|
+
if (last !== void 0) out.set(id, last);
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const edge = edgeIn(input.edges, id);
|
|
1101
|
+
if (edge === void 0) continue;
|
|
1102
|
+
const full = routeEdge(edge, { index: frame.index, options: opts, limit: frame.limit });
|
|
1103
|
+
const route = full === null ? null : full.slice(1, -1);
|
|
1104
|
+
previews.set(id, route);
|
|
1105
|
+
out.set(id, route);
|
|
1106
|
+
}
|
|
1107
|
+
return out;
|
|
1108
|
+
}
|
|
1109
|
+
function commit(input, dirty, obstacles) {
|
|
1110
|
+
previews.clear();
|
|
1111
|
+
const index = new ObstacleIndex(obstacles, opts.clearance, opts.labelClearance);
|
|
1112
|
+
const limit = limitOf(input, opts.margin);
|
|
1113
|
+
const dirtyIds = new Set(dirty.map((e) => e.id));
|
|
1114
|
+
const crossings = new SegmentIndex();
|
|
1115
|
+
const clean = new SegmentIndex();
|
|
1116
|
+
for (const e of input.edges) {
|
|
1117
|
+
if (dirtyIds.has(e.id)) continue;
|
|
1118
|
+
crossings.add(e.id, routeOf(e));
|
|
1119
|
+
clean.add(e.id, rawOf(e));
|
|
1120
|
+
}
|
|
1121
|
+
const fresh = routeDirty(dirty, { index, options: opts, limit, crossings });
|
|
1122
|
+
for (const edge of dirty) keys.set(edge.id, stopsKey(edge));
|
|
1123
|
+
const byId = new Map(input.edges.map((e) => [e.id, e]));
|
|
1124
|
+
const bundle = bundleOf(fresh, input.edges, clean, rawOf);
|
|
1125
|
+
const nudged = nudge(bundle, opts.spacing, index);
|
|
1126
|
+
const out = /* @__PURE__ */ new Map();
|
|
1127
|
+
for (const [id, full] of fresh) if (full === null) {
|
|
1128
|
+
raw.delete(id);
|
|
1129
|
+
shown.delete(id);
|
|
1130
|
+
out.set(id, null);
|
|
1131
|
+
}
|
|
1132
|
+
for (const [id, full] of nudged) {
|
|
1133
|
+
const was = dirtyIds.has(id) ? null : routeOf(byId.get(id));
|
|
1134
|
+
raw.set(id, bundle.get(id).points);
|
|
1135
|
+
shown.set(id, full);
|
|
1136
|
+
if (was === null || !samePoints(was, full)) out.set(id, full.slice(1, -1));
|
|
1137
|
+
}
|
|
1138
|
+
const edges = input.edges.map((e) => withRoute(e, out));
|
|
1139
|
+
const settled = [...input.obstacles, ...labelObstacles(edges, routeOf)];
|
|
1140
|
+
committed = new Map(settled.map((o) => [o.id, o]));
|
|
1141
|
+
indexForDrag({ obstacles: settled, edges });
|
|
1142
|
+
return out;
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
seed(input) {
|
|
1146
|
+
const obstacles = [...input.obstacles, ...labelObstacles(input.edges, fullRoute)];
|
|
1147
|
+
committed = new Map(obstacles.map((o) => [o.id, o]));
|
|
1148
|
+
keys.clear();
|
|
1149
|
+
raw.clear();
|
|
1150
|
+
shown.clear();
|
|
1151
|
+
for (const e of input.edges) keys.set(e.id, stopsKey(e));
|
|
1152
|
+
previews.clear();
|
|
1153
|
+
indexForDrag({ obstacles, edges: input.edges });
|
|
1154
|
+
},
|
|
1155
|
+
update(input, updateOpts = {}) {
|
|
1156
|
+
if (updateOpts.preview === true) return preview(input, updateOpts.budgetMs ?? Infinity);
|
|
1157
|
+
const obstacles = [...input.obstacles, ...labelObstacles(input.edges, routeOf)];
|
|
1158
|
+
const changed = changedObstacles(committed, obstacles);
|
|
1159
|
+
const dirty = dirtyEdges(input.edges, keys, changed, opts.clearance, routeOf);
|
|
1160
|
+
forget(input);
|
|
1161
|
+
return commit(input, dirty, obstacles);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function routeDirty(dirty, ctx) {
|
|
1166
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
1167
|
+
for (const edge of dirty) {
|
|
1168
|
+
const full = routeEdge(edge, ctx);
|
|
1169
|
+
fresh.set(edge.id, full);
|
|
1170
|
+
if (full !== null) ctx.crossings.add(edge.id, full);
|
|
1171
|
+
if (edge.label === void 0) continue;
|
|
1172
|
+
const label = labelObstacle(edge, full ?? fullRoute(edge));
|
|
1173
|
+
if (label === null) ctx.index.delete(edge.label.id);
|
|
1174
|
+
else ctx.index.replace(label);
|
|
1175
|
+
}
|
|
1176
|
+
return fresh;
|
|
1177
|
+
}
|
|
1178
|
+
function bundleOf(fresh, edges, clean, rawOf) {
|
|
1179
|
+
const bundle = /* @__PURE__ */ new Map();
|
|
1180
|
+
const mates = /* @__PURE__ */ new Set();
|
|
1181
|
+
const waypoints = new Map(edges.map((e) => [e.id, e.waypoints ?? []]));
|
|
1182
|
+
for (const [id, full] of fresh) {
|
|
1183
|
+
if (full === null) continue;
|
|
1184
|
+
bundle.set(id, { points: full, waypoints: waypoints.get(id) });
|
|
1185
|
+
for (let i = 1; i + 2 < full.length; i++) clean.edgesMeeting(segmentBox(full[i], full[i + 1]), mates);
|
|
1186
|
+
}
|
|
1187
|
+
for (const e of edges) {
|
|
1188
|
+
if (mates.has(e.id) && !fresh.has(e.id)) bundle.set(e.id, { points: rawOf(e), waypoints: e.waypoints ?? [] });
|
|
1189
|
+
}
|
|
1190
|
+
return bundle;
|
|
1191
|
+
}
|
|
1192
|
+
function withRoute(edge, routes) {
|
|
1193
|
+
const route = routes.get(edge.id);
|
|
1194
|
+
if (route === void 0) return edge;
|
|
1195
|
+
const { route: _r, ...rest } = edge;
|
|
1196
|
+
return route === null ? rest : { ...rest, route };
|
|
1197
|
+
}
|
|
1198
|
+
var segmentBox = (a, b) => ({
|
|
1199
|
+
x: Math.min(a.x, b.x),
|
|
1200
|
+
y: Math.min(a.y, b.y),
|
|
1201
|
+
width: Math.abs(a.x - b.x),
|
|
1202
|
+
height: Math.abs(a.y - b.y)
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
// src/hops/crossings.ts
|
|
1206
|
+
var EPS8 = 1e-9;
|
|
1207
|
+
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
1208
|
+
var boxOf2 = (a, b) => ({
|
|
1209
|
+
x: Math.min(a.x, b.x),
|
|
1210
|
+
y: Math.min(a.y, b.y),
|
|
1211
|
+
width: Math.abs(b.x - a.x),
|
|
1212
|
+
height: Math.abs(b.y - a.y)
|
|
1213
|
+
});
|
|
1214
|
+
function properCrossing(p, q) {
|
|
1215
|
+
const rx = p.b.x - p.a.x;
|
|
1216
|
+
const ry = p.b.y - p.a.y;
|
|
1217
|
+
const sx = q.b.x - q.a.x;
|
|
1218
|
+
const sy = q.b.y - q.a.y;
|
|
1219
|
+
const den = rx * sy - ry * sx;
|
|
1220
|
+
if (Math.abs(den) <= EPS8 * Math.hypot(rx, ry) * Math.hypot(sx, sy)) return null;
|
|
1221
|
+
const qx = q.a.x - p.a.x;
|
|
1222
|
+
const qy = q.a.y - p.a.y;
|
|
1223
|
+
const t = (qx * sy - qy * sx) / den;
|
|
1224
|
+
const u = (qx * ry - qy * rx) / den;
|
|
1225
|
+
return t > EPS8 && t < 1 - EPS8 && u > EPS8 && u < 1 - EPS8 ? t : null;
|
|
1226
|
+
}
|
|
1227
|
+
var far = (p, ends, d) => ends.every((e) => Math.hypot(p.x - e.x, p.y - e.y) >= d);
|
|
1228
|
+
function mergeAlong(seg, found, r) {
|
|
1229
|
+
found.sort((m, n) => m.t - n.t);
|
|
1230
|
+
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
1231
|
+
const out = [];
|
|
1232
|
+
let run = null;
|
|
1233
|
+
const flush = () => {
|
|
1234
|
+
if (run === null) return;
|
|
1235
|
+
const mid = (run.from + run.to) / 2 / len;
|
|
1236
|
+
out.push({
|
|
1237
|
+
at: { x: round(seg.a.x + (seg.b.x - seg.a.x) * mid), y: round(seg.a.y + (seg.b.y - seg.a.y) * mid) },
|
|
1238
|
+
over: run.over,
|
|
1239
|
+
r: round((run.to - run.from) / 2 + r)
|
|
1240
|
+
});
|
|
1241
|
+
};
|
|
1242
|
+
for (const c of found) {
|
|
1243
|
+
if (run !== null && c.t - run.to < 2 * r) {
|
|
1244
|
+
run.to = c.t;
|
|
1245
|
+
if (!run.over.includes(c.over)) run.over.push(c.over);
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
flush();
|
|
1249
|
+
run = { from: c.t, to: c.t, over: [c.over] };
|
|
1250
|
+
}
|
|
1251
|
+
flush();
|
|
1252
|
+
return out;
|
|
1253
|
+
}
|
|
1254
|
+
function edgeHops(polylines, order2, r, only) {
|
|
1255
|
+
const hash = new SpatialHash();
|
|
1256
|
+
const segments = /* @__PURE__ */ new Map();
|
|
1257
|
+
order2.forEach((edge, rank) => {
|
|
1258
|
+
const points = polylines.get(edge);
|
|
1259
|
+
if (points === void 0) return;
|
|
1260
|
+
const list = [];
|
|
1261
|
+
for (let i = 1; i < points.length; i++) {
|
|
1262
|
+
const seg = { edge, rank, a: points[i - 1], b: points[i] };
|
|
1263
|
+
if (seg.a.x === seg.b.x && seg.a.y === seg.b.y) continue;
|
|
1264
|
+
list.push(seg);
|
|
1265
|
+
hash.insert(seg, boxOf2(seg.a, seg.b));
|
|
1266
|
+
}
|
|
1267
|
+
segments.set(edge, list);
|
|
1268
|
+
});
|
|
1269
|
+
const endsOf = (edge) => {
|
|
1270
|
+
const p = polylines.get(edge);
|
|
1271
|
+
return [p[0], p[p.length - 1]];
|
|
1272
|
+
};
|
|
1273
|
+
const out = /* @__PURE__ */ new Map();
|
|
1274
|
+
const near = [];
|
|
1275
|
+
for (const edge of order2) {
|
|
1276
|
+
if (only !== void 0 && !only.has(edge)) continue;
|
|
1277
|
+
const hops = [];
|
|
1278
|
+
const ends = segments.has(edge) ? endsOf(edge) : [];
|
|
1279
|
+
for (const seg of segments.get(edge) ?? []) {
|
|
1280
|
+
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
1281
|
+
const found = [];
|
|
1282
|
+
near.length = 0;
|
|
1283
|
+
for (const other of hash.query(boxOf2(seg.a, seg.b), near)) {
|
|
1284
|
+
if (other.rank >= seg.rank) continue;
|
|
1285
|
+
const f = properCrossing(seg, other);
|
|
1286
|
+
if (f === null) continue;
|
|
1287
|
+
const t = f * len;
|
|
1288
|
+
const at = { x: seg.a.x + (seg.b.x - seg.a.x) * f, y: seg.a.y + (seg.b.y - seg.a.y) * f };
|
|
1289
|
+
if (t < r || len - t < r || !far(at, ends, 2 * r) || !far(at, endsOf(other.edge), 2 * r)) continue;
|
|
1290
|
+
found.push({ t, over: other.edge });
|
|
1291
|
+
}
|
|
1292
|
+
if (found.length > 0) hops.push(...mergeAlong(seg, found, r));
|
|
1293
|
+
}
|
|
1294
|
+
out.set(edge, hops);
|
|
1295
|
+
}
|
|
1296
|
+
return out;
|
|
1297
|
+
}
|
|
1298
|
+
var NAMED = { start: 0, mid: 0.5, end: 1 };
|
|
1299
|
+
var grow = (b, by) => ({ x: b.x - by, y: b.y - by, width: b.width + 2 * by, height: b.height + 2 * by });
|
|
1300
|
+
function overlapArea(a, b) {
|
|
1301
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
1302
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
1303
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
1304
|
+
}
|
|
1305
|
+
function crossesBox(a, b, box) {
|
|
1306
|
+
let t0 = 0;
|
|
1307
|
+
let t1 = 1;
|
|
1308
|
+
const dx = b.x - a.x;
|
|
1309
|
+
const dy = b.y - a.y;
|
|
1310
|
+
const clip = (p, q) => {
|
|
1311
|
+
if (p === 0) return q > 0;
|
|
1312
|
+
const r = q / p;
|
|
1313
|
+
if (p < 0) t0 = Math.max(t0, r);
|
|
1314
|
+
else t1 = Math.min(t1, r);
|
|
1315
|
+
return t0 < t1;
|
|
1316
|
+
};
|
|
1317
|
+
return clip(-dx, a.x - box.x) && clip(dx, box.x + box.width - a.x) && clip(-dy, a.y - box.y) && clip(dy, box.y + box.height - a.y);
|
|
1318
|
+
}
|
|
1319
|
+
function arcNearest(points, p) {
|
|
1320
|
+
let best = Infinity;
|
|
1321
|
+
let at = 0;
|
|
1322
|
+
let walked = 0;
|
|
1323
|
+
for (let i = 0; i + 1 < points.length; i++) {
|
|
1324
|
+
const a = points[i];
|
|
1325
|
+
const b = points[i + 1];
|
|
1326
|
+
const dx = b.x - a.x;
|
|
1327
|
+
const dy = b.y - a.y;
|
|
1328
|
+
const len = Math.hypot(dx, dy);
|
|
1329
|
+
const t = len === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / (len * len)));
|
|
1330
|
+
const d = Math.hypot(a.x + t * dx - p.x, a.y + t * dy - p.y);
|
|
1331
|
+
if (d < best) {
|
|
1332
|
+
best = d;
|
|
1333
|
+
at = walked + t * len;
|
|
1334
|
+
}
|
|
1335
|
+
walked += len;
|
|
1336
|
+
}
|
|
1337
|
+
return at;
|
|
1338
|
+
}
|
|
1339
|
+
function candidatesOf(label, route, tries, spacing) {
|
|
1340
|
+
const { at, offset = 0 } = label.placement;
|
|
1341
|
+
const out = [{ placement: { at, offset }, spot: null }];
|
|
1342
|
+
const length = route.length;
|
|
1343
|
+
if (length === 0) return out;
|
|
1344
|
+
const start = typeof at === "object" ? arcNearest(route.points, at.near) : (typeof at === "number" ? at : NAMED[at] ?? 0.5) * length;
|
|
1345
|
+
const sides = offset === 0 ? [0] : [offset, -offset];
|
|
1346
|
+
const push = (s, side) => {
|
|
1347
|
+
const spot = { at: s / length, offset: side };
|
|
1348
|
+
out.push({ placement: { at: spot.at, offset: side }, spot });
|
|
1349
|
+
};
|
|
1350
|
+
if (offset !== 0) push(start, -offset);
|
|
1351
|
+
const step = label.size.width + spacing;
|
|
1352
|
+
for (let k = 1; k <= tries; k++) {
|
|
1353
|
+
for (const dir of [1, -1]) {
|
|
1354
|
+
const s = start + dir * k * step;
|
|
1355
|
+
if (s < 0 || s > length) continue;
|
|
1356
|
+
for (const side of sides) push(s, side);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return out;
|
|
1360
|
+
}
|
|
1361
|
+
var boxAround = (c, size) => ({ x: c.x - size.width / 2, y: c.y - size.height / 2, width: size.width, height: size.height });
|
|
1362
|
+
function costOf(box, edge, field) {
|
|
1363
|
+
const probe = grow(box, field.gap);
|
|
1364
|
+
let cost = 0;
|
|
1365
|
+
for (const s of field.shapes.query(probe)) cost += 1e3 * overlapArea(probe, s);
|
|
1366
|
+
for (const l of field.labels.query(probe)) cost += 100 * overlapArea(probe, l);
|
|
1367
|
+
const covered = /* @__PURE__ */ new Set();
|
|
1368
|
+
for (const s of field.segments.query(probe)) if (s.edge !== edge && crossesBox(s.a, s.b, probe)) covered.add(s.edge);
|
|
1369
|
+
return cost + 10 * covered.size;
|
|
1370
|
+
}
|
|
1371
|
+
function settle(label, route, field, tries, spacing) {
|
|
1372
|
+
let best = null;
|
|
1373
|
+
let home = null;
|
|
1374
|
+
for (const c of candidatesOf(label, route, tries, spacing)) {
|
|
1375
|
+
const center = labelCenter(route.path, c.placement, label.size);
|
|
1376
|
+
if (center === null) continue;
|
|
1377
|
+
home ??= center;
|
|
1378
|
+
const box = boxAround(center, label.size);
|
|
1379
|
+
const score = costOf(box, label.edge, field) + Math.hypot(center.x - home.x, center.y - home.y);
|
|
1380
|
+
if (best === null || score < best.score) best = { spot: c.spot, box, score };
|
|
1381
|
+
if (score === 0) break;
|
|
1382
|
+
}
|
|
1383
|
+
return best;
|
|
1384
|
+
}
|
|
1385
|
+
function fileSegments(field, edge, run) {
|
|
1386
|
+
for (let i = 0; i + 1 < run.length; i++) {
|
|
1387
|
+
const a = run[i];
|
|
1388
|
+
const b = run[i + 1];
|
|
1389
|
+
field.segments.insert({ edge, a, b }, { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(a.x - b.x), height: Math.abs(a.y - b.y) });
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
function placeLabels(input, opts = {}) {
|
|
1393
|
+
const tries = opts.tries ?? 4;
|
|
1394
|
+
const spacing = opts.spacing ?? 8;
|
|
1395
|
+
const field = { shapes: new SpatialHash(), labels: new SpatialHash(), segments: new SpatialHash(), gap: opts.gap ?? 2 };
|
|
1396
|
+
for (const { box } of input.shapes) field.shapes.insert(box, box);
|
|
1397
|
+
const routes = /* @__PURE__ */ new Map();
|
|
1398
|
+
for (const { id, points, more = [] } of input.routes) {
|
|
1399
|
+
for (const run of [points, ...more]) fileSegments(field, id, run);
|
|
1400
|
+
if (points.length < 2) continue;
|
|
1401
|
+
let length = 0;
|
|
1402
|
+
for (let i = 0; i + 1 < points.length; i++) length += Math.hypot(points[i + 1].x - points[i].x, points[i + 1].y - points[i].y);
|
|
1403
|
+
routes.set(id, { path: polylineFromPoints(points), points, length });
|
|
1404
|
+
}
|
|
1405
|
+
const moving = [];
|
|
1406
|
+
for (const label of input.labels) {
|
|
1407
|
+
const route = routes.get(label.edge);
|
|
1408
|
+
if (route === void 0) continue;
|
|
1409
|
+
if (label.moving !== false && label.placement.dodge !== false) {
|
|
1410
|
+
moving.push(label);
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
const center = labelCenter(route.path, label.placement, label.size);
|
|
1414
|
+
if (center === null) continue;
|
|
1415
|
+
const box = boxAround(center, label.size);
|
|
1416
|
+
field.labels.insert(box, box);
|
|
1417
|
+
}
|
|
1418
|
+
moving.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1419
|
+
const out = /* @__PURE__ */ new Map();
|
|
1420
|
+
for (const label of moving) {
|
|
1421
|
+
const best = settle(label, routes.get(label.edge), field, tries, spacing);
|
|
1422
|
+
if (best === null) continue;
|
|
1423
|
+
out.set(label.id, best.spot);
|
|
1424
|
+
field.labels.insert(best.box, best.box);
|
|
1425
|
+
}
|
|
1426
|
+
return out;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export { AVOID_DEFAULTS, DEFAULT_HOP_SIZE, HOPS_DOC_KEY, createAvoidRouter, edgeHops, placeLabels, simplifyStops };
|
|
1430
|
+
//# sourceMappingURL=chunk-T6DQT553.js.map
|
|
1431
|
+
//# sourceMappingURL=chunk-T6DQT553.js.map
|