@mark1russell7/highways 0.1.2 → 0.1.3
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/build/index.d.ts +147 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +965 -264
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bench/links.ts +191 -0
- package/src/bench/run.ts +28 -0
- package/src/cases.ts +111 -0
- package/src/grid.ts +61 -2
- package/src/index.ts +2 -0
- package/src/levels.ts +150 -14
- package/src/routers/sections.ts +33 -6
- package/src/visibility.ts +309 -92
package/src/routers/sections.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BACKTRACK_MIN, goesOutAndBack } from "../bench/links.ts";
|
|
2
|
+
import { center, dist, pointSegmentDistance, polylineLength, type Box, type Point } from "../geom.ts";
|
|
2
3
|
import { levelLinks, type ItemPair } from "../levels.ts";
|
|
3
4
|
import { nodeIndex, type Graph, type Level, type LevelItem, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
|
|
4
5
|
import { placePlug } from "../plugs.ts";
|
|
@@ -23,12 +24,20 @@ export interface SectionsOptions extends WayOptions, SectionOptions {
|
|
|
23
24
|
|
|
24
25
|
const dedupe = (p: readonly Point[]): Point[] => p.filter((q, i) => i === 0 || dist(q, p[i - 1]!) > 1e-9);
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* A hub can be this many units farther from the target than the nearest approach of the way before it.
|
|
29
|
+
* A hub farther away goes out and back, so the way skips it.
|
|
30
|
+
*/
|
|
31
|
+
const HUB_SLACK = 2 * BACKTRACK_MIN;
|
|
32
|
+
|
|
26
33
|
/**
|
|
27
34
|
* This function makes a router with a tree of sections. Each section has a hub: the free corner nearest to the center of its nodes.
|
|
28
35
|
* An edge goes from its node up the hubs of its sections to the lowest common section, across, and down to the other node.
|
|
29
36
|
* Each way from hub to hub is found one time, so all the edges on it share its points.
|
|
30
37
|
* The edges in one section of the lowest level go straight around the nodes.
|
|
31
38
|
*
|
|
39
|
+
* An edge skips a hub that takes it away from its target. A way that still goes out and back gives way to the direct way.
|
|
40
|
+
*
|
|
32
41
|
* The result gives one zoom level for each depth of the tree.
|
|
33
42
|
* A link of a level goes from the boundary of one item to the boundary of the other, around the other items.
|
|
34
43
|
*/
|
|
@@ -66,6 +75,20 @@ export function sectionsRouter(options: SectionsOptions): Router {
|
|
|
66
75
|
}
|
|
67
76
|
return h;
|
|
68
77
|
};
|
|
78
|
+
// The sections of the chain whose hubs do not take the way away from its target. The test uses the straight lines from hub to hub.
|
|
79
|
+
const forward = (list: readonly Section[], from: Point, to: Point): Section[] => {
|
|
80
|
+
const kept: Section[] = [];
|
|
81
|
+
let near = dist(from, to);
|
|
82
|
+
let last = from;
|
|
83
|
+
for (const c of list) {
|
|
84
|
+
const h = hub(c);
|
|
85
|
+
if (dist(h, to) > near + HUB_SLACK) continue;
|
|
86
|
+
near = Math.min(near, pointSegmentDistance(to, last, h));
|
|
87
|
+
last = h;
|
|
88
|
+
kept.push(c);
|
|
89
|
+
}
|
|
90
|
+
return kept;
|
|
91
|
+
};
|
|
69
92
|
const up = (s: Section): Section[] => {
|
|
70
93
|
const out: Section[] = [];
|
|
71
94
|
for (let x: Section | undefined = s; x; x = parent.get(x)) out.push(x);
|
|
@@ -111,20 +134,24 @@ export function sectionsRouter(options: SectionsOptions): Router {
|
|
|
111
134
|
const lca = a.find((x) => common.has(x))!;
|
|
112
135
|
const keep = options.hubs ?? Infinity;
|
|
113
136
|
const side = (list: Section[]): Section[] => list.slice(Math.max(0, list.indexOf(lca) - keep), list.indexOf(lca));
|
|
114
|
-
const
|
|
137
|
+
const end = ways.out(pt);
|
|
138
|
+
const start = ways.out(ps);
|
|
139
|
+
const chain = forward([...side(a), ...side(b).reverse()], start, pt.point);
|
|
140
|
+
if (chain.length === 0) {
|
|
141
|
+
routes.push({ edge: e.id, points: ways.between(ps, pt, new Set<Node>([s, t])) });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
115
144
|
const out: Point[] = [ps.point];
|
|
116
145
|
const append = (p: readonly Point[]): void => {
|
|
117
146
|
for (const q of p) if (dist(q, out[out.length - 1]!) > 1e-9) out.push(q);
|
|
118
147
|
};
|
|
119
148
|
const used: string[] = [];
|
|
120
|
-
const start = ways.out(ps);
|
|
121
149
|
append(piece(at(start), start, chain[0]!.id, hub(chain[0]!), used));
|
|
122
150
|
for (let i = 1; i < chain.length; i++) append(piece(chain[i - 1]!.id, hub(chain[i - 1]!), chain[i]!.id, hub(chain[i]!), used));
|
|
123
|
-
const end = ways.out(pt);
|
|
124
151
|
append(piece(chain[chain.length - 1]!.id, hub(chain[chain.length - 1]!), at(end), end, used));
|
|
125
152
|
append([pt.point]);
|
|
126
|
-
// A way through the hubs that is too long gives way to the direct way.
|
|
127
|
-
if (polylineLength(out) > stretch * dist(ps.point, pt.point) + 4 * ways.margin) {
|
|
153
|
+
// A way through the hubs that is too long, or that goes out and back, gives way to the direct way.
|
|
154
|
+
if (polylineLength(out) > stretch * dist(ps.point, pt.point) + 4 * ways.margin || goesOutAndBack(out)) {
|
|
128
155
|
routes.push({ edge: e.id, points: ways.between(ps, pt, new Set<Node>([s, t])) });
|
|
129
156
|
continue;
|
|
130
157
|
}
|
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. */
|
|
@@ -78,11 +110,18 @@ export class CornerGraph {
|
|
|
78
110
|
#open = new Int32Array(0);
|
|
79
111
|
#closed = new Int32Array(0);
|
|
80
112
|
#stamp = 0;
|
|
81
|
-
// The
|
|
82
|
-
#
|
|
83
|
-
|
|
113
|
+
// 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.
|
|
114
|
+
readonly #buckets = new Map<number, number[]>();
|
|
115
|
+
// The distance from the point of the last call of nearestVisible to each corner that the call found.
|
|
116
|
+
#dist = new Float64Array(0);
|
|
117
|
+
// The corners of one ring of nearestVisible, nearest first.
|
|
118
|
+
readonly #near = new MinHeap();
|
|
84
119
|
// The corners that each end of a path sees, by the coordinates of the end.
|
|
85
120
|
readonly #endCache = new Map<string, number[]>();
|
|
121
|
+
// The corners that each end of a path of `pathAmong` sees. Such a path has several ends, so each end takes fewer corners.
|
|
122
|
+
readonly #fewCache = new Map<string, number[]>();
|
|
123
|
+
// The connected part of the graph of each corner. The first call of `path` makes it.
|
|
124
|
+
#part: Int32Array | undefined;
|
|
86
125
|
|
|
87
126
|
/** This constructor makes the graph of the nodes. */
|
|
88
127
|
constructor(nodes: readonly Node[], obstacles: Obstacles, options: CornerGraphOptions) {
|
|
@@ -138,7 +177,12 @@ export class CornerGraph {
|
|
|
138
177
|
}
|
|
139
178
|
|
|
140
179
|
#addPoint(c: Point): void {
|
|
141
|
-
this
|
|
180
|
+
const j = this.points.length;
|
|
181
|
+
this.#pointGrid.add(j, { x: c.x, y: c.y, w: 0, h: 0 });
|
|
182
|
+
const key = bucketKey(Math.floor(c.x / this.#cell), Math.floor(c.y / this.#cell));
|
|
183
|
+
const list = this.#buckets.get(key);
|
|
184
|
+
if (list === undefined) this.#buckets.set(key, [j]);
|
|
185
|
+
else list.push(j);
|
|
142
186
|
this.points.push(c);
|
|
143
187
|
}
|
|
144
188
|
|
|
@@ -172,24 +216,78 @@ export class CornerGraph {
|
|
|
172
216
|
return this.#nearestVisible(p, k, self, this.#pad);
|
|
173
217
|
}
|
|
174
218
|
|
|
219
|
+
/**
|
|
220
|
+
* This method tests the corners in rings of radius `r` = 1, 2, 4 and 8 cells. In each ring, it tests the corners
|
|
221
|
+
* within `r` that an earlier ring did not test, nearest first, and equal distances by index.
|
|
222
|
+
* The ring of `r` takes the corners of the buckets that the square of side `2r` around `p` touches.
|
|
223
|
+
* A corner of these buckets beyond `r` waits for the next ring. So the method visits each bucket one time.
|
|
224
|
+
*/
|
|
175
225
|
#nearestVisible(p: Point, k: number, self: number, pad: number): number[] {
|
|
176
226
|
const out: number[] = [];
|
|
177
|
-
if (this.#
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
227
|
+
if (this.#dist.length < this.points.length) this.#dist = new Float64Array(this.points.length);
|
|
228
|
+
const dd = this.#dist;
|
|
229
|
+
const points = this.points;
|
|
230
|
+
const cell = this.#cell;
|
|
231
|
+
const blockers: Node[] = [];
|
|
232
|
+
const now = this.#near;
|
|
233
|
+
// The corners that the rings found beyond their radius. The next ring takes them.
|
|
234
|
+
let wait: number[] = [];
|
|
235
|
+
// The bucket range of the ring before. The first ring has none.
|
|
236
|
+
let pi0 = 1;
|
|
237
|
+
let pi1 = 0;
|
|
238
|
+
let pj0 = 1;
|
|
239
|
+
let pj1 = 0;
|
|
240
|
+
for (let r = cell; r <= cell * 8; r *= 2) {
|
|
241
|
+
// These bounds are the bounds of a query of the box at (p.x - r, p.y - r) with the side 2r.
|
|
242
|
+
const x0 = p.x - r;
|
|
243
|
+
const y0 = p.y - r;
|
|
244
|
+
const i0 = Math.floor(x0 / cell);
|
|
245
|
+
const j0 = Math.floor(y0 / cell);
|
|
246
|
+
const i1 = Math.floor((x0 + 2 * r) / cell);
|
|
247
|
+
const j1 = Math.floor((y0 + 2 * r) / cell);
|
|
248
|
+
const later: number[] = [];
|
|
249
|
+
now.clear();
|
|
250
|
+
for (const j of wait) {
|
|
251
|
+
if (dd[j]! > r) later.push(j);
|
|
252
|
+
else now.add(dd[j]!, j);
|
|
253
|
+
}
|
|
254
|
+
for (let bi = i0; bi <= i1; bi++) {
|
|
255
|
+
const before = bi >= pi0 && bi <= pi1;
|
|
256
|
+
for (let bj = j0; bj <= j1; bj++) {
|
|
257
|
+
if (before && bj >= pj0 && bj <= pj1) continue;
|
|
258
|
+
const list = this.#buckets.get(bucketKey(bi, bj));
|
|
259
|
+
if (list === undefined) continue;
|
|
260
|
+
for (const j of list) {
|
|
261
|
+
if (j === self) continue;
|
|
262
|
+
const d = dist(p, points[j]!);
|
|
263
|
+
dd[j] = d;
|
|
264
|
+
if (d > r) later.push(j);
|
|
265
|
+
else now.add(d, j);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// The heap gives the corners nearest first, and equal distances by index. A call often stops after a few corners.
|
|
270
|
+
now.build();
|
|
271
|
+
while (now.size > 0) {
|
|
272
|
+
const j = now.pop();
|
|
273
|
+
// A node that stopped an earlier segment often stops this segment too, so the test takes these nodes first.
|
|
274
|
+
// The order of the nodes does not change the result.
|
|
275
|
+
const q = points[j]!;
|
|
276
|
+
let blocked = false;
|
|
277
|
+
for (let b = 0; b < blockers.length && !blocked; b++) blocked = hits(p, q, blockers[b]!, pad);
|
|
278
|
+
if (blocked) continue;
|
|
279
|
+
const hit = this.#obstacles.hit(p, q, pad);
|
|
280
|
+
if (hit !== undefined) blockers.push(hit);
|
|
281
|
+
else {
|
|
282
|
+
out.push(j);
|
|
283
|
+
if (out.length >= k) return out;
|
|
284
|
+
}
|
|
192
285
|
}
|
|
286
|
+
wait = later;
|
|
287
|
+
pi0 = i0;
|
|
288
|
+
pi1 = i1;
|
|
289
|
+
pj0 = j0;
|
|
290
|
+
pj1 = j1;
|
|
193
291
|
}
|
|
194
292
|
return out;
|
|
195
293
|
}
|
|
@@ -202,6 +300,35 @@ export class CornerGraph {
|
|
|
202
300
|
*/
|
|
203
301
|
path(from: Point, to: Point, cost?: (i: number, j: number, d: number) => number, floor: number = 1): Point[] | undefined {
|
|
204
302
|
if (this.#obstacles.clear(from, to, this.#pad)) return [from, to];
|
|
303
|
+
return this.#search([{ point: from, cost: 0 }], [{ point: to, cost: 0 }], cost, floor, this.#k * 2)?.points;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* This method gives the shortest path from a point of `from` to a point of `to`, or `undefined` when the graph has none.
|
|
308
|
+
* 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.
|
|
309
|
+
*/
|
|
310
|
+
pathAmong(from: readonly Terminal[], to: readonly Terminal[]): Among | undefined {
|
|
311
|
+
const found = this.#search(from, to, undefined, 1, this.#k);
|
|
312
|
+
// A straight line between two points that costs less than the found path is better. The test takes the lines in order of cost.
|
|
313
|
+
const lines: Among[] = [];
|
|
314
|
+
from.forEach((a, i) =>
|
|
315
|
+
to.forEach((b, k) => {
|
|
316
|
+
const c = a.cost + dist(a.point, b.point) + b.cost;
|
|
317
|
+
if (!found || c < found.cost) lines.push({ points: [a.point, b.point], from: i, to: k, cost: c });
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
lines.sort((u, v) => u.cost - v.cost);
|
|
321
|
+
return lines.find((l) => this.#obstacles.clear(l.points[0]!, l.points[1]!, this.#pad)) ?? found;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** This method is the search (A*) of `path` and `pathAmong`. */
|
|
325
|
+
#search(
|
|
326
|
+
from: readonly Terminal[],
|
|
327
|
+
to: readonly Terminal[],
|
|
328
|
+
cost: ((i: number, j: number, d: number) => number) | undefined,
|
|
329
|
+
floor: number,
|
|
330
|
+
count: number,
|
|
331
|
+
): Among | undefined {
|
|
205
332
|
const n = this.points.length;
|
|
206
333
|
if (this.#g.length < n) {
|
|
207
334
|
this.#g = new Float64Array(n);
|
|
@@ -210,28 +337,54 @@ export class CornerGraph {
|
|
|
210
337
|
this.#closed = new Int32Array(n);
|
|
211
338
|
}
|
|
212
339
|
const stamp = ++this.#stamp;
|
|
340
|
+
const points = this.points;
|
|
341
|
+
// The cost from each corner near a point of `to` to that point, and the index of that point.
|
|
213
342
|
const goal = new Map<number, number>();
|
|
214
|
-
|
|
343
|
+
const goalOf = new Map<number, number>();
|
|
344
|
+
to.forEach((b, k) => {
|
|
345
|
+
for (const j of this.#ends(b.point, count)) {
|
|
346
|
+
const tail = dist(points[j]!, b.point) + b.cost;
|
|
347
|
+
const now = goal.get(j);
|
|
348
|
+
if (now === undefined || tail < now) {
|
|
349
|
+
goal.set(j, tail);
|
|
350
|
+
goalOf.set(j, k);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
215
354
|
if (goal.size === 0) return undefined;
|
|
355
|
+
const starts = from.map((a) => this.#ends(a.point, count));
|
|
356
|
+
// Without a start and a goal in one connected part, A* finds no path. This test stops it before it visits the whole part.
|
|
357
|
+
if (!starts.some((list) => this.#joined(list, goal))) return undefined;
|
|
216
358
|
const heap = new MinHeap();
|
|
217
|
-
const
|
|
359
|
+
const G = this.#g;
|
|
360
|
+
const parentOf = this.#parent;
|
|
361
|
+
const open = this.#open;
|
|
362
|
+
const closed = this.#closed;
|
|
363
|
+
const estimate = (p: Point): number => {
|
|
364
|
+
let h = Infinity;
|
|
365
|
+
for (const b of to) h = Math.min(h, dist(p, b.point));
|
|
366
|
+
return floor * h;
|
|
367
|
+
};
|
|
218
368
|
const relax = (j: number, g: number, parent: number): void => {
|
|
219
|
-
if (
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
heap.push(g +
|
|
369
|
+
if (closed[j] === stamp) return;
|
|
370
|
+
if (open[j] === stamp && G[j]! <= g) return;
|
|
371
|
+
open[j] = stamp;
|
|
372
|
+
G[j] = g;
|
|
373
|
+
parentOf[j] = parent;
|
|
374
|
+
heap.push(g + estimate(points[j]!), j);
|
|
225
375
|
};
|
|
226
|
-
|
|
376
|
+
// The parent of a first corner is -1 - i, where i is the index of its point in `from`.
|
|
377
|
+
from.forEach((a, i) => {
|
|
378
|
+
for (const j of starts[i]!) relax(j, a.cost + dist(a.point, points[j]!), -1 - i);
|
|
379
|
+
});
|
|
227
380
|
let best = Infinity;
|
|
228
381
|
let bestEnd = -1;
|
|
229
382
|
while (heap.size > 0) {
|
|
230
383
|
if (heap.topKey >= best) break;
|
|
231
384
|
const i = heap.pop();
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
const gi =
|
|
385
|
+
if (closed[i] === stamp) continue;
|
|
386
|
+
closed[i] = stamp;
|
|
387
|
+
const gi = G[i]!;
|
|
235
388
|
const tail = goal.get(i);
|
|
236
389
|
if (tail !== undefined && gi + tail < best) {
|
|
237
390
|
best = gi + tail;
|
|
@@ -241,29 +394,67 @@ export class CornerGraph {
|
|
|
241
394
|
const len = this.#len[i]!;
|
|
242
395
|
for (let m = 0; m < adj.length; m++) {
|
|
243
396
|
const j = adj[m]!;
|
|
397
|
+
if (closed[j] === stamp) continue;
|
|
244
398
|
const d = len[m]!;
|
|
245
399
|
relax(j, gi + (cost ? cost(i, j, d) : d), i);
|
|
246
400
|
}
|
|
247
401
|
}
|
|
248
402
|
if (bestEnd < 0) return undefined;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
403
|
+
const k = goalOf.get(bestEnd)!;
|
|
404
|
+
const out: Point[] = [to[k]!.point];
|
|
405
|
+
for (let i = bestEnd; ; i = parentOf[i]!) {
|
|
406
|
+
out.push(points[i]!);
|
|
407
|
+
if (parentOf[i]! < 0) {
|
|
408
|
+
const f = -1 - parentOf[i]!;
|
|
409
|
+
out.push(from[f]!.point);
|
|
410
|
+
return { points: out.reverse(), from: f, to: k, cost: best };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** This method tells if a corner of `starts` and a corner of `goal` are in one connected part of the graph. */
|
|
416
|
+
#joined(starts: readonly number[], goal: ReadonlyMap<number, number>): boolean {
|
|
417
|
+
const part = (this.#part ??= this.#parts());
|
|
418
|
+
const goals = new Set<number>();
|
|
419
|
+
for (const j of goal.keys()) goals.add(part[j]!);
|
|
420
|
+
for (const j of starts) if (goals.has(part[j]!)) return true;
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** This method gives the number of the connected part of each corner. */
|
|
425
|
+
#parts(): Int32Array {
|
|
426
|
+
const part = new Int32Array(this.points.length).fill(-1);
|
|
427
|
+
const stack: number[] = [];
|
|
428
|
+
let count = 0;
|
|
429
|
+
for (let i = 0; i < part.length; i++) {
|
|
430
|
+
if (part[i] !== -1) continue;
|
|
431
|
+
part[i] = count;
|
|
432
|
+
stack.push(i);
|
|
433
|
+
while (stack.length > 0) {
|
|
434
|
+
for (const j of this.#adj[stack.pop()!]!) {
|
|
435
|
+
if (part[j] !== -1) continue;
|
|
436
|
+
part[j] = count;
|
|
437
|
+
stack.push(j);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
count++;
|
|
441
|
+
}
|
|
442
|
+
return part;
|
|
253
443
|
}
|
|
254
444
|
|
|
255
445
|
/**
|
|
256
|
-
* This method gives
|
|
446
|
+
* 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
447
|
* Then the end takes the corners that it sees without the clearance, so the path does not cut through the boxes.
|
|
258
448
|
* The graph does not change, so the method keeps the corners of each end.
|
|
259
449
|
*/
|
|
260
|
-
#ends(p: Point): number[] {
|
|
450
|
+
#ends(p: Point, count: number): number[] {
|
|
451
|
+
const cache = count === this.#k * 2 ? this.#endCache : this.#fewCache;
|
|
261
452
|
const key = `${p.x},${p.y}`;
|
|
262
|
-
let out =
|
|
453
|
+
let out = cache.get(key);
|
|
263
454
|
if (!out) {
|
|
264
|
-
out = this.nearestVisible(p,
|
|
265
|
-
if (out.length === 0) out = this.#nearestVisible(p,
|
|
266
|
-
|
|
455
|
+
out = this.nearestVisible(p, count);
|
|
456
|
+
if (out.length === 0) out = this.#nearestVisible(p, count, -1, 0);
|
|
457
|
+
cache.set(key, out);
|
|
267
458
|
}
|
|
268
459
|
return out;
|
|
269
460
|
}
|
|
@@ -283,65 +474,91 @@ export class CornerGraph {
|
|
|
283
474
|
}
|
|
284
475
|
}
|
|
285
476
|
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
-
readonly #k: number[] = [];
|
|
289
|
-
readonly #v: number[] = [];
|
|
290
|
-
|
|
291
|
-
get size(): number {
|
|
292
|
-
return this.#k.length;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
#less(a: number, b: number): boolean {
|
|
296
|
-
return this.#k[a]! < this.#k[b]! || (this.#k[a] === this.#k[b] && this.#v[a]! < this.#v[b]!);
|
|
297
|
-
}
|
|
477
|
+
/** This function packs the two indices of a bucket into one number. Each index stays within ±2^20. */
|
|
478
|
+
const bucketKey = (i: number, j: number): number => (i + 1_048_576) * 2_097_152 + (j + 1_048_576);
|
|
298
479
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
480
|
+
/**
|
|
481
|
+
* 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.
|
|
482
|
+
* 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.
|
|
483
|
+
*/
|
|
484
|
+
class MinHeap {
|
|
485
|
+
#d = new Float64Array(64);
|
|
486
|
+
#j = new Int32Array(64);
|
|
487
|
+
size = 0;
|
|
307
488
|
|
|
308
489
|
/** The smallest key. */
|
|
309
490
|
get topKey(): number {
|
|
310
|
-
return this.#
|
|
491
|
+
return this.#d[0]!;
|
|
311
492
|
}
|
|
312
493
|
|
|
313
494
|
push(key: number, value: number): void {
|
|
314
|
-
this
|
|
315
|
-
this.#
|
|
316
|
-
|
|
495
|
+
this.add(key, value);
|
|
496
|
+
const d = this.#d;
|
|
497
|
+
const jj = this.#j;
|
|
498
|
+
let i = this.size - 1;
|
|
317
499
|
while (i > 0) {
|
|
318
500
|
const p = (i - 1) >> 1;
|
|
319
|
-
if (!
|
|
320
|
-
|
|
501
|
+
if (!(key < d[p]! || (key === d[p] && value < jj[p]!))) break;
|
|
502
|
+
d[i] = d[p]!;
|
|
503
|
+
jj[i] = jj[p]!;
|
|
321
504
|
i = p;
|
|
322
505
|
}
|
|
506
|
+
d[i] = key;
|
|
507
|
+
jj[i] = value;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
clear(): void {
|
|
511
|
+
this.size = 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
add(d: number, j: number): void {
|
|
515
|
+
if (this.size === this.#d.length) {
|
|
516
|
+
const dd = new Float64Array(2 * this.size);
|
|
517
|
+
dd.set(this.#d);
|
|
518
|
+
this.#d = dd;
|
|
519
|
+
const jj = new Int32Array(2 * this.size);
|
|
520
|
+
jj.set(this.#j);
|
|
521
|
+
this.#j = jj;
|
|
522
|
+
}
|
|
523
|
+
this.#d[this.size] = d;
|
|
524
|
+
this.#j[this.size] = j;
|
|
525
|
+
this.size++;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
build(): void {
|
|
529
|
+
for (let i = (this.size >> 1) - 1; i >= 0; i--) this.#down(i);
|
|
323
530
|
}
|
|
324
531
|
|
|
325
532
|
/** This method removes the pair with the smallest key and gives its value. */
|
|
326
533
|
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
|
-
}
|
|
534
|
+
const top = this.#j[0]!;
|
|
535
|
+
this.size--;
|
|
536
|
+
if (this.size > 0) {
|
|
537
|
+
this.#d[0] = this.#d[this.size]!;
|
|
538
|
+
this.#j[0] = this.#j[this.size]!;
|
|
539
|
+
this.#down(0);
|
|
344
540
|
}
|
|
345
541
|
return top;
|
|
346
542
|
}
|
|
543
|
+
|
|
544
|
+
#down(i: number): void {
|
|
545
|
+
const d = this.#d;
|
|
546
|
+
const jj = this.#j;
|
|
547
|
+
const n = this.size;
|
|
548
|
+
const di = d[i]!;
|
|
549
|
+
const ji = jj[i]!;
|
|
550
|
+
for (;;) {
|
|
551
|
+
const l = 2 * i + 1;
|
|
552
|
+
if (l >= n) break;
|
|
553
|
+
const r = l + 1;
|
|
554
|
+
let m = l;
|
|
555
|
+
if (r < n && (d[r]! < d[l]! || (d[r] === d[l] && jj[r]! < jj[l]!))) m = r;
|
|
556
|
+
if (!(d[m]! < di || (d[m] === di && jj[m]! < ji))) break;
|
|
557
|
+
d[i] = d[m]!;
|
|
558
|
+
jj[i] = jj[m]!;
|
|
559
|
+
i = m;
|
|
560
|
+
}
|
|
561
|
+
d[i] = di;
|
|
562
|
+
jj[i] = ji;
|
|
563
|
+
}
|
|
347
564
|
}
|