@mark1russell7/highways 0.1.2 → 0.1.4
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 +109 -0
- package/build/index.d.ts +468 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1882 -255
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bench/curves.ts +98 -0
- package/src/bench/links.ts +191 -0
- package/src/bench/room.ts +122 -0
- package/src/bench/run.ts +28 -0
- package/src/cases.ts +139 -0
- package/src/curves/arc/index.ts +71 -0
- package/src/curves/index.ts +48 -0
- package/src/curves/model.ts +64 -0
- package/src/curves/rounded/index.ts +60 -0
- package/src/curves/sample.ts +73 -0
- package/src/curves/space.ts +91 -0
- package/src/curves/spline/index.ts +77 -0
- package/src/curves/straight/index.ts +7 -0
- package/src/grid.ts +61 -2
- package/src/index.ts +6 -0
- package/src/levels.ts +151 -14
- package/src/room/index.ts +334 -0
- package/src/routers/sections.ts +33 -6
- package/src/visibility.ts +334 -92
package/src/visibility.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dist, inflate, segmentHitsRect, type Point } from "./geom.ts";
|
|
1
|
+
import { dist, inflate, segmentHitsRect, type Box, type Point } from "./geom.ts";
|
|
2
2
|
import { Grid } from "./grid.ts";
|
|
3
3
|
import type { Node } from "./model.ts";
|
|
4
4
|
|
|
@@ -14,14 +14,21 @@ export class Obstacles {
|
|
|
14
14
|
|
|
15
15
|
/** This method tells if the segment `ab` stays out of each box, made larger by `pad`. The nodes in `skip` do not count. */
|
|
16
16
|
clear(a: Point, b: Point, pad: number, skip?: ReadonlySet<Node>): boolean {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
return this.hit(a, b, pad, skip) === undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This method gives a node whose box, made larger by `pad`, the segment `ab` goes into, or `undefined` when the segment is clear.
|
|
22
|
+
* The nodes in `skip` do not count. The test stops at the first such node.
|
|
23
|
+
*/
|
|
24
|
+
hit(a: Point, b: Point, pad: number, skip?: ReadonlySet<Node>): Node | undefined {
|
|
25
|
+
let found: Node | undefined;
|
|
26
|
+
this.#grid.someSegment(a, b, pad, (n) => {
|
|
27
|
+
if (skip?.has(n) || !hits(a, b, n, pad)) return false;
|
|
28
|
+
found = n;
|
|
29
|
+
return true;
|
|
23
30
|
});
|
|
24
|
-
return
|
|
31
|
+
return found;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
/** This method tells if the polyline stays out of each box, made larger by `pad`. The nodes in `skip` do not count. */
|
|
@@ -30,6 +37,11 @@ export class Obstacles {
|
|
|
30
37
|
return true;
|
|
31
38
|
}
|
|
32
39
|
|
|
40
|
+
/** This method gives each node whose bucket the box touches to `visit`. A node can be outside the box. */
|
|
41
|
+
query(box: Box, visit: (n: Node) => void): void {
|
|
42
|
+
this.#grid.query(box, visit);
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/** This method tells if the point is inside a box, made larger by `pad`. The node `skip` does not count. */
|
|
34
46
|
inside(p: Point, pad: number, skip?: Node): boolean {
|
|
35
47
|
let hit = false;
|
|
@@ -43,6 +55,26 @@ export class Obstacles {
|
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
|
|
58
|
+
/** This function tells if the segment `ab` goes into the box of the node, made larger by `pad`. */
|
|
59
|
+
function hits(a: Point, b: Point, n: Node, pad: number): boolean {
|
|
60
|
+
const x = n.box;
|
|
61
|
+
return segmentHitsRect(a, b, x.x - pad, x.y - pad, x.x + x.w + pad, x.y + x.h + pad);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A free point at an end of a search of `CornerGraph.pathAmong`, and the cost to reach it. */
|
|
65
|
+
export interface Terminal {
|
|
66
|
+
readonly point: Point;
|
|
67
|
+
readonly cost: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A path of `CornerGraph.pathAmong`: its points, the index of its first point in `from`, the index of its last point in `to`, and its cost. */
|
|
71
|
+
export interface Among {
|
|
72
|
+
readonly points: Point[];
|
|
73
|
+
readonly from: number;
|
|
74
|
+
readonly to: number;
|
|
75
|
+
readonly cost: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
46
78
|
/** The options of the corner graph. */
|
|
47
79
|
export interface CornerGraphOptions {
|
|
48
80
|
/** The distance from a box to its corners. */
|
|
@@ -54,6 +86,11 @@ export interface CornerGraphOptions {
|
|
|
54
86
|
* The lattice links groups of nodes across empty space. Without it, the nearest corners of a group are all in that group.
|
|
55
87
|
*/
|
|
56
88
|
readonly lattice?: number;
|
|
89
|
+
/**
|
|
90
|
+
* With `cones`, each corner also links to the nearest corner that it can see in each of four cones around the axes.
|
|
91
|
+
* Then a path can go along a long corridor between rows or columns of boxes. The default is false.
|
|
92
|
+
*/
|
|
93
|
+
readonly cones?: boolean;
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
/**
|
|
@@ -78,11 +115,18 @@ export class CornerGraph {
|
|
|
78
115
|
#open = new Int32Array(0);
|
|
79
116
|
#closed = new Int32Array(0);
|
|
80
117
|
#stamp = 0;
|
|
81
|
-
// The
|
|
82
|
-
#
|
|
83
|
-
|
|
118
|
+
// The corners in each bucket of the point grid, by the key of the bucket. A corner is in one bucket: the bucket of its point.
|
|
119
|
+
readonly #buckets = new Map<number, number[]>();
|
|
120
|
+
// The distance from the point of the last call of nearestVisible to each corner that the call found.
|
|
121
|
+
#dist = new Float64Array(0);
|
|
122
|
+
// The corners of one ring of nearestVisible, nearest first.
|
|
123
|
+
readonly #near = new MinHeap();
|
|
84
124
|
// The corners that each end of a path sees, by the coordinates of the end.
|
|
85
125
|
readonly #endCache = new Map<string, number[]>();
|
|
126
|
+
// The corners that each end of a path of `pathAmong` sees. Such a path has several ends, so each end takes fewer corners.
|
|
127
|
+
readonly #fewCache = new Map<string, number[]>();
|
|
128
|
+
// The connected part of the graph of each corner. The first call of `path` makes it.
|
|
129
|
+
#part: Int32Array | undefined;
|
|
86
130
|
|
|
87
131
|
/** This constructor makes the graph of the nodes. */
|
|
88
132
|
constructor(nodes: readonly Node[], obstacles: Obstacles, options: CornerGraphOptions) {
|
|
@@ -125,7 +169,8 @@ export class CornerGraph {
|
|
|
125
169
|
}
|
|
126
170
|
const links: Set<number>[] = this.points.map(() => new Set<number>());
|
|
127
171
|
for (let i = 0; i < this.points.length; i++) {
|
|
128
|
-
|
|
172
|
+
// The k nearest corners of a corner in a long corridor are often all on one side. The cones add a link along the corridor.
|
|
173
|
+
for (const j of this.#nearestVisible(this.points[i]!, this.#k, i, this.#pad, options.cones ?? false)) {
|
|
129
174
|
links[i]!.add(j);
|
|
130
175
|
links[j]!.add(i);
|
|
131
176
|
}
|
|
@@ -138,7 +183,12 @@ export class CornerGraph {
|
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
#addPoint(c: Point): void {
|
|
141
|
-
this
|
|
186
|
+
const j = this.points.length;
|
|
187
|
+
this.#pointGrid.add(j, { x: c.x, y: c.y, w: 0, h: 0 });
|
|
188
|
+
const key = bucketKey(Math.floor(c.x / this.#cell), Math.floor(c.y / this.#cell));
|
|
189
|
+
const list = this.#buckets.get(key);
|
|
190
|
+
if (list === undefined) this.#buckets.set(key, [j]);
|
|
191
|
+
else list.push(j);
|
|
142
192
|
this.points.push(c);
|
|
143
193
|
}
|
|
144
194
|
|
|
@@ -172,24 +222,91 @@ export class CornerGraph {
|
|
|
172
222
|
return this.#nearestVisible(p, k, self, this.#pad);
|
|
173
223
|
}
|
|
174
224
|
|
|
175
|
-
|
|
225
|
+
/**
|
|
226
|
+
* This method tests the corners in rings of radius `r` = 1, 2, 4 and 8 cells. In each ring, it tests the corners
|
|
227
|
+
* within `r` that an earlier ring did not test, nearest first, and equal distances by index.
|
|
228
|
+
* The ring of `r` takes the corners of the buckets that the square of side `2r` around `p` touches.
|
|
229
|
+
* A corner of these buckets beyond `r` waits for the next ring. So the method visits each bucket one time.
|
|
230
|
+
* With `cones`, the method also gives the nearest visible corner of each of four cones around the axes that the `k` nearest corners leave empty.
|
|
231
|
+
*/
|
|
232
|
+
#nearestVisible(p: Point, k: number, self: number, pad: number, cones = false): number[] {
|
|
176
233
|
const out: number[] = [];
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
// The bits of the full cones: 1 is +x, 2 is +y, 4 is -x and 8 is -y.
|
|
235
|
+
let full = 0;
|
|
236
|
+
if (this.#dist.length < this.points.length) this.#dist = new Float64Array(this.points.length);
|
|
237
|
+
const dd = this.#dist;
|
|
238
|
+
const points = this.points;
|
|
239
|
+
const cell = this.#cell;
|
|
240
|
+
const reach = CONE_CELLS * cell;
|
|
241
|
+
const blockers: Node[] = [];
|
|
242
|
+
const now = this.#near;
|
|
243
|
+
// The corners that the rings found beyond their radius. The next ring takes them.
|
|
244
|
+
let wait: number[] = [];
|
|
245
|
+
// The bucket range of the ring before. The first ring has none.
|
|
246
|
+
let pi0 = 1;
|
|
247
|
+
let pi1 = 0;
|
|
248
|
+
let pj0 = 1;
|
|
249
|
+
let pj1 = 0;
|
|
250
|
+
for (let r = cell; r <= cell * 8; r *= 2) {
|
|
251
|
+
// The ring of `r` holds the corners beyond `r / 2`. With the k nearest corners, the cones take no corner beyond `reach`.
|
|
252
|
+
if (out.length >= k && r / 2 >= reach) return out;
|
|
253
|
+
// These bounds are the bounds of a query of the box at (p.x - r, p.y - r) with the side 2r.
|
|
254
|
+
const x0 = p.x - r;
|
|
255
|
+
const y0 = p.y - r;
|
|
256
|
+
const i0 = Math.floor(x0 / cell);
|
|
257
|
+
const j0 = Math.floor(y0 / cell);
|
|
258
|
+
const i1 = Math.floor((x0 + 2 * r) / cell);
|
|
259
|
+
const j1 = Math.floor((y0 + 2 * r) / cell);
|
|
260
|
+
const later: number[] = [];
|
|
261
|
+
now.clear();
|
|
262
|
+
for (const j of wait) {
|
|
263
|
+
if (dd[j]! > r) later.push(j);
|
|
264
|
+
else now.add(dd[j]!, j);
|
|
265
|
+
}
|
|
266
|
+
for (let bi = i0; bi <= i1; bi++) {
|
|
267
|
+
const before = bi >= pi0 && bi <= pi1;
|
|
268
|
+
for (let bj = j0; bj <= j1; bj++) {
|
|
269
|
+
if (before && bj >= pj0 && bj <= pj1) continue;
|
|
270
|
+
const list = this.#buckets.get(bucketKey(bi, bj));
|
|
271
|
+
if (list === undefined) continue;
|
|
272
|
+
for (const j of list) {
|
|
273
|
+
if (j === self) continue;
|
|
274
|
+
const d = dist(p, points[j]!);
|
|
275
|
+
dd[j] = d;
|
|
276
|
+
if (d > r) later.push(j);
|
|
277
|
+
else now.add(d, j);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// The heap gives the corners nearest first, and equal distances by index. A call often stops after a few corners.
|
|
282
|
+
now.build();
|
|
283
|
+
while (now.size > 0) {
|
|
284
|
+
const j = now.pop();
|
|
285
|
+
// A node that stopped an earlier segment often stops this segment too, so the test takes these nodes first.
|
|
286
|
+
// The order of the nodes does not change the result.
|
|
287
|
+
const q = points[j]!;
|
|
288
|
+
const cone = cones ? coneOf(q.x - p.x, q.y - p.y) : 0;
|
|
289
|
+
if (out.length >= k) {
|
|
290
|
+
// After the k nearest corners, only a corner of an empty cone counts, up to `CONE_CELLS` cells from `p`.
|
|
291
|
+
if (dd[j]! > reach) return out;
|
|
292
|
+
if ((full & cone) !== 0) continue;
|
|
293
|
+
}
|
|
294
|
+
let blocked = false;
|
|
295
|
+
for (let b = 0; b < blockers.length && !blocked; b++) blocked = hits(p, q, blockers[b]!, pad);
|
|
296
|
+
if (blocked) continue;
|
|
297
|
+
const hit = this.#obstacles.hit(p, q, pad);
|
|
298
|
+
if (hit !== undefined) blockers.push(hit);
|
|
299
|
+
else {
|
|
300
|
+
out.push(j);
|
|
301
|
+
full |= cone;
|
|
302
|
+
if (out.length >= k && (!cones || full === 15)) return out;
|
|
303
|
+
}
|
|
192
304
|
}
|
|
305
|
+
wait = later;
|
|
306
|
+
pi0 = i0;
|
|
307
|
+
pi1 = i1;
|
|
308
|
+
pj0 = j0;
|
|
309
|
+
pj1 = j1;
|
|
193
310
|
}
|
|
194
311
|
return out;
|
|
195
312
|
}
|
|
@@ -202,6 +319,35 @@ export class CornerGraph {
|
|
|
202
319
|
*/
|
|
203
320
|
path(from: Point, to: Point, cost?: (i: number, j: number, d: number) => number, floor: number = 1): Point[] | undefined {
|
|
204
321
|
if (this.#obstacles.clear(from, to, this.#pad)) return [from, to];
|
|
322
|
+
return this.#search([{ point: from, cost: 0 }], [{ point: to, cost: 0 }], cost, floor, this.#k * 2)?.points;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* This method gives the shortest path from a point of `from` to a point of `to`, or `undefined` when the graph has none.
|
|
327
|
+
* The cost of a path includes the costs of its two points. One search finds the path, so this method is faster than a call of `path` for each pair.
|
|
328
|
+
*/
|
|
329
|
+
pathAmong(from: readonly Terminal[], to: readonly Terminal[]): Among | undefined {
|
|
330
|
+
const found = this.#search(from, to, undefined, 1, this.#k);
|
|
331
|
+
// A straight line between two points that costs less than the found path is better. The test takes the lines in order of cost.
|
|
332
|
+
const lines: Among[] = [];
|
|
333
|
+
from.forEach((a, i) =>
|
|
334
|
+
to.forEach((b, k) => {
|
|
335
|
+
const c = a.cost + dist(a.point, b.point) + b.cost;
|
|
336
|
+
if (!found || c < found.cost) lines.push({ points: [a.point, b.point], from: i, to: k, cost: c });
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
lines.sort((u, v) => u.cost - v.cost);
|
|
340
|
+
return lines.find((l) => this.#obstacles.clear(l.points[0]!, l.points[1]!, this.#pad)) ?? found;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** This method is the search (A*) of `path` and `pathAmong`. */
|
|
344
|
+
#search(
|
|
345
|
+
from: readonly Terminal[],
|
|
346
|
+
to: readonly Terminal[],
|
|
347
|
+
cost: ((i: number, j: number, d: number) => number) | undefined,
|
|
348
|
+
floor: number,
|
|
349
|
+
count: number,
|
|
350
|
+
): Among | undefined {
|
|
205
351
|
const n = this.points.length;
|
|
206
352
|
if (this.#g.length < n) {
|
|
207
353
|
this.#g = new Float64Array(n);
|
|
@@ -210,28 +356,54 @@ export class CornerGraph {
|
|
|
210
356
|
this.#closed = new Int32Array(n);
|
|
211
357
|
}
|
|
212
358
|
const stamp = ++this.#stamp;
|
|
359
|
+
const points = this.points;
|
|
360
|
+
// The cost from each corner near a point of `to` to that point, and the index of that point.
|
|
213
361
|
const goal = new Map<number, number>();
|
|
214
|
-
|
|
362
|
+
const goalOf = new Map<number, number>();
|
|
363
|
+
to.forEach((b, k) => {
|
|
364
|
+
for (const j of this.#ends(b.point, count)) {
|
|
365
|
+
const tail = dist(points[j]!, b.point) + b.cost;
|
|
366
|
+
const now = goal.get(j);
|
|
367
|
+
if (now === undefined || tail < now) {
|
|
368
|
+
goal.set(j, tail);
|
|
369
|
+
goalOf.set(j, k);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
215
373
|
if (goal.size === 0) return undefined;
|
|
374
|
+
const starts = from.map((a) => this.#ends(a.point, count));
|
|
375
|
+
// Without a start and a goal in one connected part, A* finds no path. This test stops it before it visits the whole part.
|
|
376
|
+
if (!starts.some((list) => this.#joined(list, goal))) return undefined;
|
|
216
377
|
const heap = new MinHeap();
|
|
217
|
-
const
|
|
378
|
+
const G = this.#g;
|
|
379
|
+
const parentOf = this.#parent;
|
|
380
|
+
const open = this.#open;
|
|
381
|
+
const closed = this.#closed;
|
|
382
|
+
const estimate = (p: Point): number => {
|
|
383
|
+
let h = Infinity;
|
|
384
|
+
for (const b of to) h = Math.min(h, dist(p, b.point));
|
|
385
|
+
return floor * h;
|
|
386
|
+
};
|
|
218
387
|
const relax = (j: number, g: number, parent: number): void => {
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
heap.push(g +
|
|
388
|
+
if (closed[j] === stamp) return;
|
|
389
|
+
if (open[j] === stamp && G[j]! <= g) return;
|
|
390
|
+
open[j] = stamp;
|
|
391
|
+
G[j] = g;
|
|
392
|
+
parentOf[j] = parent;
|
|
393
|
+
heap.push(g + estimate(points[j]!), j);
|
|
225
394
|
};
|
|
226
|
-
|
|
395
|
+
// The parent of a first corner is -1 - i, where i is the index of its point in `from`.
|
|
396
|
+
from.forEach((a, i) => {
|
|
397
|
+
for (const j of starts[i]!) relax(j, a.cost + dist(a.point, points[j]!), -1 - i);
|
|
398
|
+
});
|
|
227
399
|
let best = Infinity;
|
|
228
400
|
let bestEnd = -1;
|
|
229
401
|
while (heap.size > 0) {
|
|
230
402
|
if (heap.topKey >= best) break;
|
|
231
403
|
const i = heap.pop();
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
const gi =
|
|
404
|
+
if (closed[i] === stamp) continue;
|
|
405
|
+
closed[i] = stamp;
|
|
406
|
+
const gi = G[i]!;
|
|
235
407
|
const tail = goal.get(i);
|
|
236
408
|
if (tail !== undefined && gi + tail < best) {
|
|
237
409
|
best = gi + tail;
|
|
@@ -241,29 +413,67 @@ export class CornerGraph {
|
|
|
241
413
|
const len = this.#len[i]!;
|
|
242
414
|
for (let m = 0; m < adj.length; m++) {
|
|
243
415
|
const j = adj[m]!;
|
|
416
|
+
if (closed[j] === stamp) continue;
|
|
244
417
|
const d = len[m]!;
|
|
245
418
|
relax(j, gi + (cost ? cost(i, j, d) : d), i);
|
|
246
419
|
}
|
|
247
420
|
}
|
|
248
421
|
if (bestEnd < 0) return undefined;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
422
|
+
const k = goalOf.get(bestEnd)!;
|
|
423
|
+
const out: Point[] = [to[k]!.point];
|
|
424
|
+
for (let i = bestEnd; ; i = parentOf[i]!) {
|
|
425
|
+
out.push(points[i]!);
|
|
426
|
+
if (parentOf[i]! < 0) {
|
|
427
|
+
const f = -1 - parentOf[i]!;
|
|
428
|
+
out.push(from[f]!.point);
|
|
429
|
+
return { points: out.reverse(), from: f, to: k, cost: best };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** This method tells if a corner of `starts` and a corner of `goal` are in one connected part of the graph. */
|
|
435
|
+
#joined(starts: readonly number[], goal: ReadonlyMap<number, number>): boolean {
|
|
436
|
+
const part = (this.#part ??= this.#parts());
|
|
437
|
+
const goals = new Set<number>();
|
|
438
|
+
for (const j of goal.keys()) goals.add(part[j]!);
|
|
439
|
+
for (const j of starts) if (goals.has(part[j]!)) return true;
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** This method gives the number of the connected part of each corner. */
|
|
444
|
+
#parts(): Int32Array {
|
|
445
|
+
const part = new Int32Array(this.points.length).fill(-1);
|
|
446
|
+
const stack: number[] = [];
|
|
447
|
+
let count = 0;
|
|
448
|
+
for (let i = 0; i < part.length; i++) {
|
|
449
|
+
if (part[i] !== -1) continue;
|
|
450
|
+
part[i] = count;
|
|
451
|
+
stack.push(i);
|
|
452
|
+
while (stack.length > 0) {
|
|
453
|
+
for (const j of this.#adj[stack.pop()!]!) {
|
|
454
|
+
if (part[j] !== -1) continue;
|
|
455
|
+
part[j] = count;
|
|
456
|
+
stack.push(j);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
count++;
|
|
460
|
+
}
|
|
461
|
+
return part;
|
|
253
462
|
}
|
|
254
463
|
|
|
255
464
|
/**
|
|
256
|
-
* This method gives
|
|
465
|
+
* This method gives at most `count` corners that an end of a path can see. An end near a box sees no corner with the clearance.
|
|
257
466
|
* Then the end takes the corners that it sees without the clearance, so the path does not cut through the boxes.
|
|
258
467
|
* The graph does not change, so the method keeps the corners of each end.
|
|
259
468
|
*/
|
|
260
|
-
#ends(p: Point): number[] {
|
|
469
|
+
#ends(p: Point, count: number): number[] {
|
|
470
|
+
const cache = count === this.#k * 2 ? this.#endCache : this.#fewCache;
|
|
261
471
|
const key = `${p.x},${p.y}`;
|
|
262
|
-
let out =
|
|
472
|
+
let out = cache.get(key);
|
|
263
473
|
if (!out) {
|
|
264
|
-
out = this.nearestVisible(p,
|
|
265
|
-
if (out.length === 0) out = this.#nearestVisible(p,
|
|
266
|
-
|
|
474
|
+
out = this.nearestVisible(p, count);
|
|
475
|
+
if (out.length === 0) out = this.#nearestVisible(p, count, -1, 0);
|
|
476
|
+
cache.set(key, out);
|
|
267
477
|
}
|
|
268
478
|
return out;
|
|
269
479
|
}
|
|
@@ -283,65 +493,97 @@ export class CornerGraph {
|
|
|
283
493
|
}
|
|
284
494
|
}
|
|
285
495
|
|
|
286
|
-
/** A
|
|
287
|
-
|
|
288
|
-
readonly #k: number[] = [];
|
|
289
|
-
readonly #v: number[] = [];
|
|
496
|
+
/** A link of a cone is at most this number of cells long. A longer search costs time on a dense level and finds few new links. */
|
|
497
|
+
const CONE_CELLS = 3;
|
|
290
498
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
499
|
+
/** This function gives the bit of the cone of the offset (`dx`, `dy`): 1 is +x, 2 is +y, 4 is -x and 8 is -y. A diagonal offset is in a cone of the x axis. */
|
|
500
|
+
const coneOf = (dx: number, dy: number): number => (Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? 1 : 4) : dy > 0 ? 2 : 8);
|
|
294
501
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
502
|
+
/** This function packs the two indices of a bucket into one number. Each index stays within ±2^20. */
|
|
503
|
+
const bucketKey = (i: number, j: number): number => (i + 1_048_576) * 2_097_152 + (j + 1_048_576);
|
|
298
504
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
505
|
+
/**
|
|
506
|
+
* A binary min-heap of (key, value) pairs in typed arrays. The pairs come out by key, and equal keys by value, so the order is deterministic.
|
|
507
|
+
* The method `push` adds a pair to the heap. The method `add` only appends a pair, and the method `build` then makes the heap of all the pairs.
|
|
508
|
+
*/
|
|
509
|
+
class MinHeap {
|
|
510
|
+
#d = new Float64Array(64);
|
|
511
|
+
#j = new Int32Array(64);
|
|
512
|
+
size = 0;
|
|
307
513
|
|
|
308
514
|
/** The smallest key. */
|
|
309
515
|
get topKey(): number {
|
|
310
|
-
return this.#
|
|
516
|
+
return this.#d[0]!;
|
|
311
517
|
}
|
|
312
518
|
|
|
313
519
|
push(key: number, value: number): void {
|
|
314
|
-
this
|
|
315
|
-
this.#
|
|
316
|
-
|
|
520
|
+
this.add(key, value);
|
|
521
|
+
const d = this.#d;
|
|
522
|
+
const jj = this.#j;
|
|
523
|
+
let i = this.size - 1;
|
|
317
524
|
while (i > 0) {
|
|
318
525
|
const p = (i - 1) >> 1;
|
|
319
|
-
if (!
|
|
320
|
-
|
|
526
|
+
if (!(key < d[p]! || (key === d[p] && value < jj[p]!))) break;
|
|
527
|
+
d[i] = d[p]!;
|
|
528
|
+
jj[i] = jj[p]!;
|
|
321
529
|
i = p;
|
|
322
530
|
}
|
|
531
|
+
d[i] = key;
|
|
532
|
+
jj[i] = value;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
clear(): void {
|
|
536
|
+
this.size = 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
add(d: number, j: number): void {
|
|
540
|
+
if (this.size === this.#d.length) {
|
|
541
|
+
const dd = new Float64Array(2 * this.size);
|
|
542
|
+
dd.set(this.#d);
|
|
543
|
+
this.#d = dd;
|
|
544
|
+
const jj = new Int32Array(2 * this.size);
|
|
545
|
+
jj.set(this.#j);
|
|
546
|
+
this.#j = jj;
|
|
547
|
+
}
|
|
548
|
+
this.#d[this.size] = d;
|
|
549
|
+
this.#j[this.size] = j;
|
|
550
|
+
this.size++;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
build(): void {
|
|
554
|
+
for (let i = (this.size >> 1) - 1; i >= 0; i--) this.#down(i);
|
|
323
555
|
}
|
|
324
556
|
|
|
325
557
|
/** This method removes the pair with the smallest key and gives its value. */
|
|
326
558
|
pop(): number {
|
|
327
|
-
const top = this.#
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.#
|
|
332
|
-
this.#
|
|
333
|
-
let i = 0;
|
|
334
|
-
for (;;) {
|
|
335
|
-
const l = 2 * i + 1;
|
|
336
|
-
const r = l + 1;
|
|
337
|
-
let m = i;
|
|
338
|
-
if (l < this.#k.length && this.#less(l, m)) m = l;
|
|
339
|
-
if (r < this.#k.length && this.#less(r, m)) m = r;
|
|
340
|
-
if (m === i) break;
|
|
341
|
-
this.#swap(i, m);
|
|
342
|
-
i = m;
|
|
343
|
-
}
|
|
559
|
+
const top = this.#j[0]!;
|
|
560
|
+
this.size--;
|
|
561
|
+
if (this.size > 0) {
|
|
562
|
+
this.#d[0] = this.#d[this.size]!;
|
|
563
|
+
this.#j[0] = this.#j[this.size]!;
|
|
564
|
+
this.#down(0);
|
|
344
565
|
}
|
|
345
566
|
return top;
|
|
346
567
|
}
|
|
568
|
+
|
|
569
|
+
#down(i: number): void {
|
|
570
|
+
const d = this.#d;
|
|
571
|
+
const jj = this.#j;
|
|
572
|
+
const n = this.size;
|
|
573
|
+
const di = d[i]!;
|
|
574
|
+
const ji = jj[i]!;
|
|
575
|
+
for (;;) {
|
|
576
|
+
const l = 2 * i + 1;
|
|
577
|
+
if (l >= n) break;
|
|
578
|
+
const r = l + 1;
|
|
579
|
+
let m = l;
|
|
580
|
+
if (r < n && (d[r]! < d[l]! || (d[r] === d[l] && jj[r]! < jj[l]!))) m = r;
|
|
581
|
+
if (!(d[m]! < di || (d[m] === di && jj[m]! < ji))) break;
|
|
582
|
+
d[i] = d[m]!;
|
|
583
|
+
jj[i] = jj[m]!;
|
|
584
|
+
i = m;
|
|
585
|
+
}
|
|
586
|
+
d[i] = di;
|
|
587
|
+
jj[i] = ji;
|
|
588
|
+
}
|
|
347
589
|
}
|