reladraw 0.9.0 → 0.11.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 +1 -1
- package/SYNTAX.md +43 -16
- package/dist/ast.d.ts +1 -1
- package/dist/ast.js +1 -1
- package/dist/grammar.d.ts +6 -3
- package/dist/grammar.js +6 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +14 -3
- package/dist/lexer.d.ts +4 -1
- package/dist/lexer.js +4 -2
- package/dist/parser.d.ts +26 -1
- package/dist/parser.js +92 -3
- package/dist/render.js +431 -330
- package/dist/search.d.ts +69 -0
- package/dist/search.js +339 -0
- package/package.json +1 -1
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding a line's way through the open space between boxes.
|
|
3
|
+
*
|
|
4
|
+
* Every box is a wall. A line may turn only where two tracks cross, and the
|
|
5
|
+
* tracks are few: a line a little way out from each side of each wall, one
|
|
6
|
+
* down the middle of each gap between walls, and the lines through the two
|
|
7
|
+
* ends. That is the whole of the open space a right-angled line ever needs,
|
|
8
|
+
* reduced to a grid of a few hundred points for a diagram of ordinary size.
|
|
9
|
+
*
|
|
10
|
+
* The line leaves its start heading straight out and arrives at its end heading
|
|
11
|
+
* straight in, with a straight stretch at each end at least `stub` long, so an
|
|
12
|
+
* arrowhead always lands on a straight piece. Between those two stretches it
|
|
13
|
+
* takes the cheapest way through the grid, where cost is length plus a charge
|
|
14
|
+
* for every turn. On a right-angled grid many ways tie on length — a staircase
|
|
15
|
+
* is exactly as long as one bend — and the turn charge breaks those ties toward
|
|
16
|
+
* the fewest bends, which is the line a person would draw. What still ties
|
|
17
|
+
* after that goes over the top rather than under, and round the right rather
|
|
18
|
+
* than the left.
|
|
19
|
+
*
|
|
20
|
+
* A way through a box is not a losing option here. It is not a way at all.
|
|
21
|
+
*/
|
|
22
|
+
export interface SearchPoint {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
/** A box the line may not enter, as its extent. Touching its border is allowed. */
|
|
27
|
+
export interface SearchWall {
|
|
28
|
+
minX: number;
|
|
29
|
+
minY: number;
|
|
30
|
+
maxX: number;
|
|
31
|
+
maxY: number;
|
|
32
|
+
}
|
|
33
|
+
/** One end of the line: a point on a box's side, and the unit direction out of it. */
|
|
34
|
+
export interface SearchEnd {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
dx: number;
|
|
38
|
+
dy: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SearchOptions {
|
|
41
|
+
/** How far the line runs straight out of each end before it may turn. */
|
|
42
|
+
stub: number;
|
|
43
|
+
/** How far outside a wall a track runs beside it, where there is room. */
|
|
44
|
+
clear: number;
|
|
45
|
+
/** What a turn costs, in the same units as length. */
|
|
46
|
+
turn: number;
|
|
47
|
+
/**
|
|
48
|
+
* Walls the stretches at the two ends may pass through: the line's own boxes,
|
|
49
|
+
* which it has to cross the margin of to leave, and any box holding an end.
|
|
50
|
+
*/
|
|
51
|
+
ownStart: SearchWall[];
|
|
52
|
+
ownEnd: SearchWall[];
|
|
53
|
+
/**
|
|
54
|
+
* Lines already placed. A line may cross one but not run along it within
|
|
55
|
+
* `lane` of it; tracks are added a lane either side of each, so a later line
|
|
56
|
+
* sharing a stretch takes the next lane out.
|
|
57
|
+
*/
|
|
58
|
+
taken: SearchPoint[][];
|
|
59
|
+
lane: number;
|
|
60
|
+
}
|
|
61
|
+
export interface SearchResult {
|
|
62
|
+
points: SearchPoint[];
|
|
63
|
+
cost: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The cheapest right-angled line from `start` to `end` that enters no wall, or
|
|
67
|
+
* undefined when there is none.
|
|
68
|
+
*/
|
|
69
|
+
export declare function searchRoute(start: SearchEnd, end: SearchEnd, walls: SearchWall[], options: SearchOptions): SearchResult | undefined;
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding a line's way through the open space between boxes.
|
|
3
|
+
*
|
|
4
|
+
* Every box is a wall. A line may turn only where two tracks cross, and the
|
|
5
|
+
* tracks are few: a line a little way out from each side of each wall, one
|
|
6
|
+
* down the middle of each gap between walls, and the lines through the two
|
|
7
|
+
* ends. That is the whole of the open space a right-angled line ever needs,
|
|
8
|
+
* reduced to a grid of a few hundred points for a diagram of ordinary size.
|
|
9
|
+
*
|
|
10
|
+
* The line leaves its start heading straight out and arrives at its end heading
|
|
11
|
+
* straight in, with a straight stretch at each end at least `stub` long, so an
|
|
12
|
+
* arrowhead always lands on a straight piece. Between those two stretches it
|
|
13
|
+
* takes the cheapest way through the grid, where cost is length plus a charge
|
|
14
|
+
* for every turn. On a right-angled grid many ways tie on length — a staircase
|
|
15
|
+
* is exactly as long as one bend — and the turn charge breaks those ties toward
|
|
16
|
+
* the fewest bends, which is the line a person would draw. What still ties
|
|
17
|
+
* after that goes over the top rather than under, and round the right rather
|
|
18
|
+
* than the left.
|
|
19
|
+
*
|
|
20
|
+
* A way through a box is not a losing option here. It is not a way at all.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The cheapest right-angled line from `start` to `end` that enters no wall, or
|
|
24
|
+
* undefined when there is none.
|
|
25
|
+
*/
|
|
26
|
+
export function searchRoute(start, end, walls, options) {
|
|
27
|
+
const { clear, turn } = options;
|
|
28
|
+
// The stretches at the ends are fixed. Each crosses its own box's margin and
|
|
29
|
+
// runs `stub` further, or, where another box is closer than that, stops in
|
|
30
|
+
// the middle of the gap between the two margins: a node placed a `tight` gap
|
|
31
|
+
// away still leaves room to get out.
|
|
32
|
+
const others = (own) => walls.filter((wall) => !own.includes(wall));
|
|
33
|
+
const reach = (from, own) => {
|
|
34
|
+
const exit = Math.max(0, ...own.map((wall) => depth(from, wall)));
|
|
35
|
+
const wanted = Math.max(options.stub, exit + 1);
|
|
36
|
+
const hit = Math.min(Infinity, ...others(own).map((wall) => distanceTo(from, wall, wanted)));
|
|
37
|
+
const length = hit >= wanted ? wanted : (exit + hit) / 2;
|
|
38
|
+
if (length <= exit)
|
|
39
|
+
return undefined;
|
|
40
|
+
return { x: snap(from.x + from.dx * length), y: snap(from.y + from.dy * length) };
|
|
41
|
+
};
|
|
42
|
+
const out = reach(start, options.ownStart);
|
|
43
|
+
const back = reach(end, options.ownEnd);
|
|
44
|
+
if (!out || !back)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (inside(out, walls) || inside(back, walls))
|
|
47
|
+
return undefined;
|
|
48
|
+
const xs = tracks(walls, 'x', clear, [out.x, back.x], options.taken, options.lane);
|
|
49
|
+
const ys = tracks(walls, 'y', clear, [out.y, back.y], options.taken, options.lane);
|
|
50
|
+
const ix = new Map(xs.map((x, index) => [x, index]));
|
|
51
|
+
const iy = new Map(ys.map((y, index) => [y, index]));
|
|
52
|
+
const width = xs.length;
|
|
53
|
+
const height = ys.length;
|
|
54
|
+
const open = (i, j) => !inside({ x: xs[i], y: ys[j] }, walls);
|
|
55
|
+
// Four headings: 0 right, 1 down, 2 left, 3 up.
|
|
56
|
+
const STEP = [
|
|
57
|
+
[1, 0],
|
|
58
|
+
[0, 1],
|
|
59
|
+
[-1, 0],
|
|
60
|
+
[0, -1],
|
|
61
|
+
];
|
|
62
|
+
const heading = (dx, dy) => (dx > 0 ? 0 : dy > 0 ? 1 : dx < 0 ? 2 : 3);
|
|
63
|
+
const first = heading(start.dx, start.dy);
|
|
64
|
+
// The last stretch travels into the end, against the way its side faces.
|
|
65
|
+
const last = heading(-end.dx, -end.dy);
|
|
66
|
+
const si = ix.get(out.x);
|
|
67
|
+
const sj = iy.get(out.y);
|
|
68
|
+
const ti = ix.get(back.x);
|
|
69
|
+
const tj = iy.get(back.y);
|
|
70
|
+
// Cost is compared first on length and turns, then on a tie-break that
|
|
71
|
+
// prefers a stretch higher up the page, or further right, so that of two
|
|
72
|
+
// ways round that are exactly as long the line goes over the top.
|
|
73
|
+
const size = width * height * 4;
|
|
74
|
+
const cost = new Float64Array(size).fill(Infinity);
|
|
75
|
+
const tie = new Float64Array(size).fill(Infinity);
|
|
76
|
+
const from = new Int32Array(size).fill(-1);
|
|
77
|
+
const state = (i, j, h) => (j * width + i) * 4 + h;
|
|
78
|
+
const heap = new MinHeap();
|
|
79
|
+
const begin = state(si, sj, first);
|
|
80
|
+
cost[begin] = 0;
|
|
81
|
+
tie[begin] = 0;
|
|
82
|
+
heap.push(begin, 0, 0);
|
|
83
|
+
let goal = -1;
|
|
84
|
+
while (heap.size > 0) {
|
|
85
|
+
const [current, c, t] = heap.pop();
|
|
86
|
+
if (c > cost[current] || (c === cost[current] && t > tie[current]))
|
|
87
|
+
continue;
|
|
88
|
+
const h = current % 4;
|
|
89
|
+
const cell = (current - h) / 4;
|
|
90
|
+
const i = cell % width;
|
|
91
|
+
const j = (cell - i) / width;
|
|
92
|
+
if (i === ti && j === tj && h === last) {
|
|
93
|
+
goal = current;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
for (let next = 0; next < 4; next += 1) {
|
|
97
|
+
// A line never doubles straight back on itself.
|
|
98
|
+
if ((next + 2) % 4 === h)
|
|
99
|
+
continue;
|
|
100
|
+
if (next !== h) {
|
|
101
|
+
// Turning on the spot costs a turn and moves nowhere.
|
|
102
|
+
relax(current, state(i, j, next), c + turn, t);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const ni = i + STEP[next][0];
|
|
106
|
+
const nj = j + STEP[next][1];
|
|
107
|
+
if (ni < 0 || nj < 0 || ni >= width || nj >= height || !open(ni, nj))
|
|
108
|
+
continue;
|
|
109
|
+
const a = { x: xs[i], y: ys[j] };
|
|
110
|
+
const b = { x: xs[ni], y: ys[nj] };
|
|
111
|
+
if (blocked(a, b, walls) || alongTaken(a, b, options.taken, options.lane))
|
|
112
|
+
continue;
|
|
113
|
+
// Running closer to a wall than `clear` counts double: the line may pass
|
|
114
|
+
// a narrow gap, but where there is room it keeps its distance.
|
|
115
|
+
const length = Math.abs(b.x - a.x) + Math.abs(b.y - a.y) + crowding(a, b, walls, clear);
|
|
116
|
+
// Higher up for a stretch across, further right for one down: a smaller
|
|
117
|
+
// tie-break is preferred, so the across level counts as it is and the
|
|
118
|
+
// down level counts negated.
|
|
119
|
+
const lean = next % 2 === 0 ? a.y * length : -a.x * length;
|
|
120
|
+
relax(current, state(ni, nj, next), c + length, t + lean);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (goal < 0)
|
|
124
|
+
return undefined;
|
|
125
|
+
function relax(previous, next, c, t) {
|
|
126
|
+
if (c < cost[next] - 1e-9 || (Math.abs(c - cost[next]) <= 1e-9 && t < tie[next])) {
|
|
127
|
+
cost[next] = c;
|
|
128
|
+
tie[next] = t;
|
|
129
|
+
from[next] = previous;
|
|
130
|
+
heap.push(next, c, t);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const path = [];
|
|
134
|
+
for (let at = goal; at >= 0; at = from[at]) {
|
|
135
|
+
const cell = (at - (at % 4)) / 4;
|
|
136
|
+
const i = cell % width;
|
|
137
|
+
const j = (cell - i) / width;
|
|
138
|
+
const point = { x: xs[i], y: ys[j] };
|
|
139
|
+
const previous = path[path.length - 1];
|
|
140
|
+
if (!previous || previous.x !== point.x || previous.y !== point.y)
|
|
141
|
+
path.push(point);
|
|
142
|
+
}
|
|
143
|
+
path.reverse();
|
|
144
|
+
return { points: straightRuns([start, ...path, end]), cost: cost[goal] };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The tracks on one axis: beside each wall at `clear` where that is open,
|
|
148
|
+
* down the middle of every gap between walls, through the ends, and a lane
|
|
149
|
+
* either side of each line already placed.
|
|
150
|
+
*/
|
|
151
|
+
function tracks(walls, axis, clear, ends, taken, lane) {
|
|
152
|
+
const lo = (wall) => (axis === 'x' ? wall.minX : wall.minY);
|
|
153
|
+
const hi = (wall) => (axis === 'x' ? wall.maxX : wall.maxY);
|
|
154
|
+
// The other axis, to tell a real gap — two walls facing each other across
|
|
155
|
+
// it — from two edges that merely happen to lie near each other on this one.
|
|
156
|
+
const olo = (wall) => (axis === 'x' ? wall.minY : wall.minX);
|
|
157
|
+
const ohi = (wall) => (axis === 'x' ? wall.maxY : wall.maxX);
|
|
158
|
+
const found = new Set(ends);
|
|
159
|
+
for (const wall of walls) {
|
|
160
|
+
found.add(lo(wall) - clear);
|
|
161
|
+
found.add(hi(wall) + clear);
|
|
162
|
+
}
|
|
163
|
+
for (const a of walls) {
|
|
164
|
+
for (const b of walls) {
|
|
165
|
+
if (hi(a) < lo(b) && olo(a) < ohi(b) && olo(b) < ohi(a))
|
|
166
|
+
found.add((hi(a) + lo(b)) / 2);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const line of taken) {
|
|
170
|
+
for (let index = 0; index + 1 < line.length; index += 1) {
|
|
171
|
+
const [a, b] = [line[index], line[index + 1]];
|
|
172
|
+
// A stretch across sits at a level on y; one down, at a level on x.
|
|
173
|
+
const across = Math.abs(a.y - b.y) < 0.5;
|
|
174
|
+
if ((axis === 'y') !== across)
|
|
175
|
+
continue;
|
|
176
|
+
// A lane either side of a placed line. One that runs close beside a box
|
|
177
|
+
// is paid for as crowding, like any other track.
|
|
178
|
+
const level = axis === 'y' ? a.y : a.x;
|
|
179
|
+
found.add(level - lane);
|
|
180
|
+
found.add(level + lane);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return [...new Set([...found].map(snap))].sort((p, q) => p - q);
|
|
184
|
+
}
|
|
185
|
+
/** Coordinates to a hundredth, so a track and the end it runs through compare equal. */
|
|
186
|
+
function snap(value) {
|
|
187
|
+
return Math.round(value * 100) / 100;
|
|
188
|
+
}
|
|
189
|
+
/** How far along its heading an end runs before it leaves a wall it starts inside; 0 if it is outside. */
|
|
190
|
+
function depth(from, wall) {
|
|
191
|
+
if (!(from.x >= wall.minX && from.x <= wall.maxX && from.y >= wall.minY && from.y <= wall.maxY))
|
|
192
|
+
return 0;
|
|
193
|
+
if (from.dx > 0)
|
|
194
|
+
return wall.maxX - from.x;
|
|
195
|
+
if (from.dx < 0)
|
|
196
|
+
return from.x - wall.minX;
|
|
197
|
+
if (from.dy > 0)
|
|
198
|
+
return wall.maxY - from.y;
|
|
199
|
+
return from.y - wall.minY;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* How far along its heading an end runs before it meets a wall it is outside
|
|
203
|
+
* of, looking no further than `limit`; Infinity if it meets none.
|
|
204
|
+
*/
|
|
205
|
+
function distanceTo(from, wall, limit) {
|
|
206
|
+
const tip = { x: from.x + from.dx * limit, y: from.y + from.dy * limit };
|
|
207
|
+
if (!blocked(from, tip, [wall]))
|
|
208
|
+
return Infinity;
|
|
209
|
+
if (from.dx > 0)
|
|
210
|
+
return wall.minX - from.x;
|
|
211
|
+
if (from.dx < 0)
|
|
212
|
+
return from.x - wall.maxX;
|
|
213
|
+
if (from.dy > 0)
|
|
214
|
+
return wall.minY - from.y;
|
|
215
|
+
return from.y - wall.maxY;
|
|
216
|
+
}
|
|
217
|
+
/** Whether a point lies strictly inside any wall. */
|
|
218
|
+
function inside(point, walls) {
|
|
219
|
+
return walls.some((wall) => point.x > wall.minX && point.x < wall.maxX && point.y > wall.minY && point.y < wall.maxY);
|
|
220
|
+
}
|
|
221
|
+
/** Whether a level or upright stretch passes through the inside of any wall. */
|
|
222
|
+
function blocked(a, b, walls) {
|
|
223
|
+
const minX = Math.min(a.x, b.x);
|
|
224
|
+
const maxX = Math.max(a.x, b.x);
|
|
225
|
+
const minY = Math.min(a.y, b.y);
|
|
226
|
+
const maxY = Math.max(a.y, b.y);
|
|
227
|
+
return walls.some((wall) => {
|
|
228
|
+
if (minY === maxY)
|
|
229
|
+
return minY > wall.minY && minY < wall.maxY && maxX > wall.minX && minX < wall.maxX;
|
|
230
|
+
return minX > wall.minX && minX < wall.maxX && maxY > wall.minY && minY < wall.maxY;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* How much of a stretch runs within `clear` of a wall's side, alongside it:
|
|
235
|
+
* the longest such run against any one wall.
|
|
236
|
+
*/
|
|
237
|
+
function crowding(a, b, walls, clear) {
|
|
238
|
+
const across = Math.abs(a.y - b.y) < 0.5;
|
|
239
|
+
const level = across ? a.y : a.x;
|
|
240
|
+
const from = across ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
|
|
241
|
+
const to = across ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
|
|
242
|
+
let worst = 0;
|
|
243
|
+
for (const wall of walls) {
|
|
244
|
+
const [lo, hi] = across ? [wall.minY, wall.maxY] : [wall.minX, wall.maxX];
|
|
245
|
+
const [alo, ahi] = across ? [wall.minX, wall.maxX] : [wall.minY, wall.maxY];
|
|
246
|
+
const near = (level > lo - clear + 0.01 && level <= lo) || (level >= hi && level < hi + clear - 0.01);
|
|
247
|
+
if (!near)
|
|
248
|
+
continue;
|
|
249
|
+
worst = Math.max(worst, Math.min(to, ahi) - Math.max(from, alo));
|
|
250
|
+
}
|
|
251
|
+
return worst;
|
|
252
|
+
}
|
|
253
|
+
/** Whether a stretch runs along a placed line, closer than a lane, for any distance. */
|
|
254
|
+
function alongTaken(a, b, taken, lane) {
|
|
255
|
+
const across = Math.abs(a.y - b.y) < 0.5;
|
|
256
|
+
const level = across ? a.y : a.x;
|
|
257
|
+
const lo = across ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
|
|
258
|
+
const hi = across ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
|
|
259
|
+
for (const line of taken) {
|
|
260
|
+
for (let index = 0; index + 1 < line.length; index += 1) {
|
|
261
|
+
const [p, q] = [line[index], line[index + 1]];
|
|
262
|
+
const pAcross = Math.abs(p.y - q.y) < 0.5;
|
|
263
|
+
if (pAcross !== across)
|
|
264
|
+
continue;
|
|
265
|
+
const other = across ? p.y : p.x;
|
|
266
|
+
if (Math.abs(other - level) >= lane - 0.5)
|
|
267
|
+
continue;
|
|
268
|
+
const pLo = across ? Math.min(p.x, q.x) : Math.min(p.y, q.y);
|
|
269
|
+
const pHi = across ? Math.max(p.x, q.x) : Math.max(p.y, q.y);
|
|
270
|
+
if (Math.min(hi, pHi) - Math.max(lo, pLo) > 0.5)
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
/** The points with every one dropped that sits on a straight run between its neighbors. */
|
|
277
|
+
function straightRuns(points) {
|
|
278
|
+
const kept = [];
|
|
279
|
+
for (const point of points) {
|
|
280
|
+
const previous = kept[kept.length - 1];
|
|
281
|
+
if (previous && Math.abs(previous.x - point.x) < 0.01 && Math.abs(previous.y - point.y) < 0.01)
|
|
282
|
+
continue;
|
|
283
|
+
kept.push(point);
|
|
284
|
+
while (kept.length >= 3) {
|
|
285
|
+
const [a, b, c] = kept.slice(-3);
|
|
286
|
+
const level = Math.abs(a.y - b.y) < 0.01 && Math.abs(b.y - c.y) < 0.01;
|
|
287
|
+
const upright = Math.abs(a.x - b.x) < 0.01 && Math.abs(b.x - c.x) < 0.01;
|
|
288
|
+
if (!level && !upright)
|
|
289
|
+
break;
|
|
290
|
+
kept.splice(kept.length - 2, 1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return kept;
|
|
294
|
+
}
|
|
295
|
+
/** A binary heap of states, cheapest first, ties broken by the second cost. */
|
|
296
|
+
class MinHeap {
|
|
297
|
+
items = [];
|
|
298
|
+
get size() {
|
|
299
|
+
return this.items.length;
|
|
300
|
+
}
|
|
301
|
+
push(state, cost, tie) {
|
|
302
|
+
const items = this.items;
|
|
303
|
+
items.push([state, cost, tie]);
|
|
304
|
+
let at = items.length - 1;
|
|
305
|
+
while (at > 0) {
|
|
306
|
+
const parent = (at - 1) >> 1;
|
|
307
|
+
if (!before(items[at], items[parent]))
|
|
308
|
+
break;
|
|
309
|
+
[items[at], items[parent]] = [items[parent], items[at]];
|
|
310
|
+
at = parent;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
pop() {
|
|
314
|
+
const items = this.items;
|
|
315
|
+
const top = items[0];
|
|
316
|
+
const tail = items.pop();
|
|
317
|
+
if (items.length > 0) {
|
|
318
|
+
items[0] = tail;
|
|
319
|
+
let at = 0;
|
|
320
|
+
for (;;) {
|
|
321
|
+
const left = at * 2 + 1;
|
|
322
|
+
const right = left + 1;
|
|
323
|
+
let best = at;
|
|
324
|
+
if (left < items.length && before(items[left], items[best]))
|
|
325
|
+
best = left;
|
|
326
|
+
if (right < items.length && before(items[right], items[best]))
|
|
327
|
+
best = right;
|
|
328
|
+
if (best === at)
|
|
329
|
+
break;
|
|
330
|
+
[items[at], items[best]] = [items[best], items[at]];
|
|
331
|
+
at = best;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return top;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function before(p, q) {
|
|
338
|
+
return p[1] < q[1] - 1e-9 || (Math.abs(p[1] - q[1]) <= 1e-9 && p[2] < q[2]);
|
|
339
|
+
}
|