@mark1russell7/highways 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -9
- package/build/index.d.ts +2168 -101
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11624 -3422
- package/build/index.js.map +1 -1
- package/package.json +4 -1
- package/src/bench/compare.ts +180 -0
- package/src/bench/curves.ts +514 -2
- package/src/bench/links.ts +28 -6
- package/src/bench/measures/bundles.ts +119 -0
- package/src/bench/measures/curves.ts +211 -0
- package/src/bench/measures/groups.ts +122 -0
- package/src/bench/measures/index.ts +9 -0
- package/src/bench/measures/levels.ts +103 -0
- package/src/bench/measures/pareto.ts +37 -0
- package/src/bench/measures/room.ts +43 -0
- package/src/bench/measures/routes.ts +226 -0
- package/src/bench/measures/stability.ts +104 -0
- package/src/bench/measures/stats.ts +114 -0
- package/src/bench/properties.ts +18 -2
- package/src/bench/room.ts +209 -2
- package/src/bench/run.ts +86 -11
- package/src/bench/screen/forms.ts +372 -0
- package/src/bench/screen/generators.ts +54 -0
- package/src/bench/screen/hash.ts +129 -0
- package/src/bench/screen/index.ts +6 -0
- package/src/bench/screen/measures.ts +114 -0
- package/src/bench/screen/screen.ts +390 -0
- package/src/bench/screen/set.ts +136 -0
- package/src/bench/suite.ts +839 -0
- package/src/cases.ts +41 -5
- package/src/curves/clothoid/index.ts +150 -0
- package/src/curves/corner.ts +116 -0
- package/src/curves/fit.ts +79 -0
- package/src/curves/highway/index.ts +636 -0
- package/src/curves/index.ts +16 -1
- package/src/curves/model.ts +4 -2
- package/src/curves/smooth/index.ts +50 -0
- package/src/curves/space.ts +76 -0
- package/src/groups/cut/index.ts +292 -0
- package/src/groups/index.ts +2 -0
- package/src/groups/kd/index.ts +207 -0
- package/src/highways/corridors/index.ts +305 -0
- package/src/highways/follow.ts +155 -0
- package/src/highways/index.ts +7 -0
- package/src/highways/keys/index.ts +15 -0
- package/src/highways/model.ts +111 -0
- package/src/highways/result.ts +34 -0
- package/src/highways/router.ts +40 -0
- package/src/highways/shared-ways/index.ts +15 -0
- package/src/index.ts +6 -0
- package/src/levels.ts +163 -5
- package/src/room/bends.ts +203 -0
- package/src/room/index.ts +28 -0
- package/src/room/levels/index.ts +155 -0
- package/src/routers/sections.ts +15 -40
- package/src/routers/shared.ts +7 -2
- package/src/routers/trunks.ts +131 -6
- package/src/sections.ts +47 -1
- package/src/segments.ts +81 -31
- package/src/strategies/builtins.ts +206 -9
- package/src/strategies/index.ts +1 -0
- package/src/strategies/model.ts +55 -7
- package/src/strategies/options.ts +296 -0
- package/src/strategies/schemas.ts +234 -0
- package/src/visibility.ts +190 -1
package/src/visibility.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TOLERANCE } from "./bench/properties.ts";
|
|
2
|
+
import { dist, inflate, segmentHitsBox, segmentHitsRect, type Box, type Point } from "./geom.ts";
|
|
2
3
|
import { Grid } from "./grid.ts";
|
|
3
4
|
import type { Node } from "./model.ts";
|
|
4
5
|
|
|
@@ -37,6 +38,17 @@ export class Obstacles {
|
|
|
37
38
|
return true;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* This method gives each node whose box the segment `ab` goes through to `visit`, one time each.
|
|
43
|
+
* The segment goes through a box when a part of it longer than `TOLERANCE` is inside the box made smaller by `TOLERANCE`.
|
|
44
|
+
* The link measures (`measureLinks`) use the same test.
|
|
45
|
+
*/
|
|
46
|
+
through(a: Point, b: Point, visit: (n: Node) => void): void {
|
|
47
|
+
this.#grid.querySegment(a, b, 0, (n) => {
|
|
48
|
+
if (segmentHitsBox(a, b, inflate(n.box, -TOLERANCE), TOLERANCE)) visit(n);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
/** This method gives each node whose bucket the box touches to `visit`. A node can be outside the box. */
|
|
41
53
|
query(box: Box, visit: (n: Node) => void): void {
|
|
42
54
|
this.#grid.query(box, visit);
|
|
@@ -493,6 +505,183 @@ export class CornerGraph {
|
|
|
493
505
|
}
|
|
494
506
|
}
|
|
495
507
|
|
|
508
|
+
/** The options of the soft graph. */
|
|
509
|
+
export interface SoftGraphOptions {
|
|
510
|
+
/** The distance from a box to its corners. */
|
|
511
|
+
readonly margin: number;
|
|
512
|
+
/** The number of nearest neighbours of each vertex. */
|
|
513
|
+
readonly k: number;
|
|
514
|
+
/**
|
|
515
|
+
* The spacing of a lattice of points in the bounding box of the boxes, in units of the mean node size. The default is 3, and 0 turns it off.
|
|
516
|
+
* The lattice links groups of boxes across empty space.
|
|
517
|
+
*/
|
|
518
|
+
readonly lattice?: number;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* A graph whose links can go through the boxes. Its vertices are the corners of the boxes, made larger by `margin`,
|
|
523
|
+
* and a lattice of points in the bounding box of the boxes. Each vertex links to its `k` nearest vertices, in view or not.
|
|
524
|
+
* The graph keeps the boxes that each link goes through (`Obstacles.through`).
|
|
525
|
+
* A search gives each box a cost, so a path goes through few boxes, and only through boxes of a low cost.
|
|
526
|
+
*/
|
|
527
|
+
export class SoftGraph {
|
|
528
|
+
readonly points: Point[] = [];
|
|
529
|
+
readonly #nodes: readonly Node[];
|
|
530
|
+
readonly #obstacles: Obstacles;
|
|
531
|
+
readonly #adj: number[][] = [];
|
|
532
|
+
readonly #len: number[][] = [];
|
|
533
|
+
// The indices of the nodes that each link goes through, in the order of #adj.
|
|
534
|
+
readonly #through: (readonly number[])[][] = [];
|
|
535
|
+
readonly #grid: Grid<number>;
|
|
536
|
+
readonly #cell: number;
|
|
537
|
+
readonly #k: number;
|
|
538
|
+
|
|
539
|
+
/** This constructor makes the graph of the nodes. */
|
|
540
|
+
constructor(nodes: readonly Node[], obstacles: Obstacles, options: SoftGraphOptions) {
|
|
541
|
+
this.#nodes = nodes;
|
|
542
|
+
this.#obstacles = obstacles;
|
|
543
|
+
this.#k = options.k;
|
|
544
|
+
let size = 0;
|
|
545
|
+
for (const n of nodes) size += (n.box.w + n.box.h) / 2;
|
|
546
|
+
this.#cell = Math.max(1, size / Math.max(1, nodes.length) + 2 * options.margin);
|
|
547
|
+
this.#grid = new Grid<number>(this.#cell);
|
|
548
|
+
let x0 = Infinity;
|
|
549
|
+
let y0 = Infinity;
|
|
550
|
+
let x1 = -Infinity;
|
|
551
|
+
let y1 = -Infinity;
|
|
552
|
+
for (const n of nodes) {
|
|
553
|
+
const b = inflate(n.box, options.margin);
|
|
554
|
+
for (const c of [
|
|
555
|
+
{ x: b.x, y: b.y },
|
|
556
|
+
{ x: b.x + b.w, y: b.y },
|
|
557
|
+
{ x: b.x + b.w, y: b.y + b.h },
|
|
558
|
+
{ x: b.x, y: b.y + b.h },
|
|
559
|
+
])
|
|
560
|
+
this.#add(c);
|
|
561
|
+
x0 = Math.min(x0, n.box.x);
|
|
562
|
+
y0 = Math.min(y0, n.box.y);
|
|
563
|
+
x1 = Math.max(x1, n.box.x + n.box.w);
|
|
564
|
+
y1 = Math.max(y1, n.box.y + n.box.h);
|
|
565
|
+
}
|
|
566
|
+
// The lattice stays in the bounding box of the boxes, so a path does not go around the outer edge of the board.
|
|
567
|
+
const spacing = (options.lattice ?? 3) * this.#cell;
|
|
568
|
+
if (spacing > 0) for (let x = x0 + spacing / 2; x < x1; x += spacing) for (let y = y0 + spacing / 2; y < y1; y += spacing) this.#add({ x, y });
|
|
569
|
+
const links: Set<number>[] = this.points.map(() => new Set<number>());
|
|
570
|
+
for (let i = 0; i < this.points.length; i++)
|
|
571
|
+
for (const j of this.#nearest(this.points[i]!, this.#k, i)) {
|
|
572
|
+
links[i]!.add(j);
|
|
573
|
+
links[j]!.add(i);
|
|
574
|
+
}
|
|
575
|
+
const index = new Map(nodes.map((n, i) => [n, i]));
|
|
576
|
+
// The boxes of each link, found one time for the two directions of the link.
|
|
577
|
+
const known = new Map<number, readonly number[]>();
|
|
578
|
+
const count = this.points.length;
|
|
579
|
+
links.forEach((set, i) => {
|
|
580
|
+
const adj = [...set].sort((a, b) => a - b);
|
|
581
|
+
this.#adj.push(adj);
|
|
582
|
+
this.#len.push(adj.map((j) => dist(this.points[i]!, this.points[j]!)));
|
|
583
|
+
this.#through.push(
|
|
584
|
+
adj.map((j) => {
|
|
585
|
+
const key = Math.min(i, j) * count + Math.max(i, j);
|
|
586
|
+
let list = known.get(key);
|
|
587
|
+
if (list === undefined) {
|
|
588
|
+
const found: number[] = [];
|
|
589
|
+
obstacles.through(this.points[i]!, this.points[j]!, (n) => found.push(index.get(n)!));
|
|
590
|
+
list = found;
|
|
591
|
+
known.set(key, list);
|
|
592
|
+
}
|
|
593
|
+
return list;
|
|
594
|
+
}),
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
#add(c: Point): void {
|
|
600
|
+
this.#grid.add(this.points.length, { x: c.x, y: c.y, w: 0, h: 0 });
|
|
601
|
+
this.points.push(c);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** This method gives at most `k` vertices nearest to `p`, in view or not, nearest first, and equal distances by index. The vertex `self` does not count. */
|
|
605
|
+
#nearest(p: Point, k: number, self = -1): number[] {
|
|
606
|
+
const all = this.points.length - (self >= 0 ? 1 : 0);
|
|
607
|
+
const want = Math.min(k, all);
|
|
608
|
+
if (want <= 0) return [];
|
|
609
|
+
for (let r = this.#cell; ; r *= 2) {
|
|
610
|
+
const found: number[] = [];
|
|
611
|
+
this.#grid.query({ x: p.x - r, y: p.y - r, w: 2 * r, h: 2 * r }, (j) => {
|
|
612
|
+
if (j !== self && dist(p, this.points[j]!) <= r) found.push(j);
|
|
613
|
+
});
|
|
614
|
+
if (found.length >= want) {
|
|
615
|
+
const d = (j: number): number => dist(p, this.points[j]!);
|
|
616
|
+
return found.sort((a, b) => d(a) - d(b) || a - b).slice(0, want);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* This method gives a path of least cost from `from` to `to` (A*), with the two points as its ends.
|
|
623
|
+
* The cost of a path is its length, plus `cost(n)` for each link of the path and each box `n` of that link.
|
|
624
|
+
* Each cost must be 0 or more. The straight line from `from` to `to` is a path too, so the method always gives a path.
|
|
625
|
+
*/
|
|
626
|
+
path(from: Point, to: Point, cost: (n: Node) => number): Point[] {
|
|
627
|
+
const points = this.points;
|
|
628
|
+
const nodes = this.#nodes;
|
|
629
|
+
const extra = (a: Point, b: Point): number => {
|
|
630
|
+
let c = 0;
|
|
631
|
+
this.#obstacles.through(a, b, (m) => {
|
|
632
|
+
c += cost(m);
|
|
633
|
+
});
|
|
634
|
+
return c;
|
|
635
|
+
};
|
|
636
|
+
let best = dist(from, to) + extra(from, to);
|
|
637
|
+
let bestEnd = -1;
|
|
638
|
+
const goal = new Map<number, number>();
|
|
639
|
+
for (const j of this.#nearest(to, 2 * this.#k)) goal.set(j, dist(points[j]!, to) + extra(points[j]!, to));
|
|
640
|
+
const n = points.length;
|
|
641
|
+
const g = new Float64Array(n).fill(Infinity);
|
|
642
|
+
const parent = new Int32Array(n).fill(-1);
|
|
643
|
+
const closed = new Uint8Array(n);
|
|
644
|
+
const heap = new MinHeap();
|
|
645
|
+
for (const j of this.#nearest(from, 2 * this.#k)) {
|
|
646
|
+
const c = dist(from, points[j]!) + extra(from, points[j]!);
|
|
647
|
+
if (c < g[j]!) {
|
|
648
|
+
g[j] = c;
|
|
649
|
+
heap.push(c + dist(points[j]!, to), j);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
while (heap.size > 0) {
|
|
653
|
+
if (heap.topKey >= best) break;
|
|
654
|
+
const i = heap.pop();
|
|
655
|
+
if (closed[i] === 1) continue;
|
|
656
|
+
closed[i] = 1;
|
|
657
|
+
const gi = g[i]!;
|
|
658
|
+
const tail = goal.get(i);
|
|
659
|
+
if (tail !== undefined && gi + tail < best) {
|
|
660
|
+
best = gi + tail;
|
|
661
|
+
bestEnd = i;
|
|
662
|
+
}
|
|
663
|
+
const adj = this.#adj[i]!;
|
|
664
|
+
const len = this.#len[i]!;
|
|
665
|
+
const through = this.#through[i]!;
|
|
666
|
+
for (let m = 0; m < adj.length; m++) {
|
|
667
|
+
const j = adj[m]!;
|
|
668
|
+
if (closed[j] === 1) continue;
|
|
669
|
+
let c = gi + len[m]!;
|
|
670
|
+
for (const b of through[m]!) c += cost(nodes[b]!);
|
|
671
|
+
if (c < g[j]!) {
|
|
672
|
+
g[j] = c;
|
|
673
|
+
parent[j] = i;
|
|
674
|
+
heap.push(c + dist(points[j]!, to), j);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const out: Point[] = [to];
|
|
679
|
+
for (let i = bestEnd; i >= 0; i = parent[i]!) out.push(points[i]!);
|
|
680
|
+
out.push(from);
|
|
681
|
+
return out.reverse();
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
496
685
|
/** 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
686
|
const CONE_CELLS = 3;
|
|
498
687
|
|