@jbrowse/bandage-core 11.12.0 → 11.13.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.
Files changed (31) hide show
  1. package/dist/chunks/{chunk-5UIYXKTA.js → chunk-7AZ57C5C.js} +5 -5
  2. package/dist/chunks/{chunk-QOY6SPOS.js → chunk-ANWUCJZB.js} +2 -2
  3. package/dist/chunks/{chunk-Q2AMKXAX.js → chunk-C5FEIJFK.js} +6 -6
  4. package/dist/chunks/{chunk-JQYK6GVJ.js → chunk-EOB4Y3EM.js} +1 -1
  5. package/dist/chunks/{chunk-6ROK45LO.js → chunk-HL4U73TC.js} +2 -2
  6. package/dist/chunks/{chunk-EH5OIV2E.js → chunk-JUYPCLF4.js} +102 -36
  7. package/dist/chunks/{chunk-GGFR4SYO.js → chunk-K4RGSISK.js} +1 -1
  8. package/dist/chunks/{chunk-TOGXPJK7.js → chunk-OCYQIHFA.js} +1 -1
  9. package/dist/chunks/{chunk-A6ABMJR5.js → chunk-OYBYCF2O.js} +1 -1
  10. package/dist/chunks/{chunk-T2LYWZ5Q.js → chunk-QL673EK5.js} +1 -1
  11. package/dist/chunks/chunk-YUIKEUKY.js +919 -0
  12. package/dist/chunks/{chunk-UFF34SGO.js → chunk-ZHCQOC67.js} +1 -1
  13. package/dist/cli/figure.js +10 -10
  14. package/dist/figure.js +9 -9
  15. package/dist/figureData.js +7 -7
  16. package/dist/figureScene.js +5 -5
  17. package/dist/index.js +12 -12
  18. package/dist/layout/walkRowDraw.js +3 -3
  19. package/dist/layout/walkStrip.js +4 -4
  20. package/dist/referenceStrip.js +3 -3
  21. package/dist/renderer/GeometryBuilder.js +2 -2
  22. package/dist/renderer/walkLanes.js +3 -1
  23. package/dist/tubeMap/nodeColors.js +3 -3
  24. package/dist/types/renderer/types.d.ts +1 -0
  25. package/dist/types/renderer/walkLanes.d.ts +32 -18
  26. package/dist/types/version.d.ts +1 -1
  27. package/dist/version.js +1 -1
  28. package/dist/walkEncoding.js +3 -3
  29. package/dist/walkHighlight.js +4 -4
  30. package/package.json +1 -1
  31. package/dist/chunks/chunk-4KBZ7WPP.js +0 -579
@@ -0,0 +1,919 @@
1
+ import {
2
+ slideAt
3
+ } from "./chunk-JJV2GT2Z.js";
4
+
5
+ // src/renderer/walkLanes.ts
6
+ var THREAD_PX = 2;
7
+ var THREAD_GAP_PX = 1.5;
8
+ var SLOT_GAP_PX = 4;
9
+ var LANE_SMOOTHING_PX = 12;
10
+ var THREAD_REACH_PX = 64;
11
+ var THREAD_TAPER_PX = 24;
12
+ var MAX_BAND_PX = 48;
13
+ var PASSES_KEY = "a thread per pass through a node";
14
+ var threadsPasses = (walks) => walks.length > 1 && walks.some((w) => w.mostPasses > 1);
15
+ var threadsWidth = (threads) => threads * THREAD_PX + Math.max(0, threads - 1) * THREAD_GAP_PX;
16
+ var sum = (values) => values.reduce((a, b) => a + b, 0);
17
+ function slotLayout(spans, mostPasses, laneWidth) {
18
+ const widths = spans.map(
19
+ (m, i) => mostPasses[i] > 1 ? threadsWidth(m) : laneWidth * Math.min(1, m)
20
+ );
21
+ let seen = 0;
22
+ const gaps = spans.map((m) => {
23
+ const share = Math.min(1, m);
24
+ const gap = SLOT_GAP_PX * share * seen;
25
+ seen = Math.max(seen, share);
26
+ return gap;
27
+ });
28
+ return { widths, gaps, band: sum(widths) + sum(gaps) };
29
+ }
30
+ var THREAD_PITCH_PX = THREAD_PX + THREAD_GAP_PX;
31
+ function slotCentres(spans, mostPasses, held = spans) {
32
+ const { widths, gaps, band } = slotLayout(spans, mostPasses, THREAD_PX);
33
+ let at = -band / 2;
34
+ return widths.map((width, i) => {
35
+ at += gaps[i];
36
+ const slack = mostPasses[i] > 1 ? Math.max(0, width - threadsWidth(held[i])) : 0;
37
+ const centre = at + slack / 2 + THREAD_PX / 2;
38
+ at += width;
39
+ return centre;
40
+ });
41
+ }
42
+ var bandWidthPx = (mostPasses) => slotLayout(mostPasses, mostPasses, THREAD_PX).band;
43
+ var bandScale = (mostPasses) => Math.min(1, MAX_BAND_PX / (bandWidthPx(mostPasses) || 1));
44
+ var passesKey = (walks) => bandScale(walks.map((w) => w.mostPasses)) < 1 ? `${PASSES_KEY}, thinned to fit` : PASSES_KEY;
45
+ function laneSlideAt(start, end, along) {
46
+ const vector = ({ normal, offsetX: offsetX2 }) => ({
47
+ dx: normal.nx * offsetX2,
48
+ dy: normal.ny * offsetX2
49
+ });
50
+ const { dx, dy } = slideAt(vector(start), vector(end), along);
51
+ const offsetX = Math.hypot(dx, dy);
52
+ return offsetX > 0 ? { normal: { nx: dx / offsetX, ny: dy / offsetX }, offsetX } : { normal: { nx: 0, ny: 0 }, offsetX: 0 };
53
+ }
54
+ function pointIndex(run, p) {
55
+ const i = run.stepOf[p];
56
+ const k = p - run.starts[i];
57
+ return run.forward[i] ? k : run.starts[i + 1] - run.starts[i] - 1 - k;
58
+ }
59
+ var gapOf = (a, b) => {
60
+ const dx = b.x - a.x;
61
+ const dy = b.y - a.y;
62
+ return Math.sqrt(dx * dx + dy * dy);
63
+ };
64
+ function arcOf(run) {
65
+ const s = new Float64Array(run.length);
66
+ for (let i = 1; i < run.length; i++) {
67
+ s[i] = s[i - 1] + gapOf(run[i - 1], run[i]);
68
+ }
69
+ return s;
70
+ }
71
+ function walkSteps(walkRoutes, nodePositions) {
72
+ const seen = /* @__PURE__ */ new Set();
73
+ return walkRoutes.map((routes) => {
74
+ const visits = /* @__PURE__ */ new Map();
75
+ return routes.map(
76
+ (route) => route.map((id) => {
77
+ const visit = visits.get(id) ?? 0;
78
+ visits.set(id, visit + 1);
79
+ const segments = nodePositions[id];
80
+ const owned = !!segments?.length && !seen.has(id);
81
+ if (owned) {
82
+ seen.add(id);
83
+ }
84
+ return { id, visit, owned, segments };
85
+ })
86
+ );
87
+ });
88
+ }
89
+ function laneRuns(steps, yToX, jump) {
90
+ const runs = [];
91
+ steps.forEach((pieces, walk) => {
92
+ let total = 0;
93
+ for (const piece of pieces) {
94
+ for (const { segments } of piece) {
95
+ total += segments?.length ?? 0;
96
+ }
97
+ }
98
+ const xs = new Float64Array(total);
99
+ const ys = new Float64Array(total);
100
+ let at = 0;
101
+ let from = 0;
102
+ let run = fresh();
103
+ function fresh() {
104
+ return {
105
+ walk,
106
+ ids: [],
107
+ visits: [],
108
+ forward: [],
109
+ owned: [],
110
+ starts: [0]
111
+ };
112
+ }
113
+ const breakRun = () => {
114
+ if (run.ids.length) {
115
+ const stepOf = new Int32Array(at - from);
116
+ for (let i = 0; i < run.ids.length; i++) {
117
+ stepOf.fill(i, run.starts[i], run.starts[i + 1]);
118
+ }
119
+ runs.push({
120
+ ...run,
121
+ xs: xs.subarray(from, at),
122
+ ys: ys.subarray(from, at),
123
+ stepOf
124
+ });
125
+ }
126
+ run = fresh();
127
+ from = at;
128
+ };
129
+ const gap = (x, y, p) => {
130
+ const dx = p.x - x;
131
+ const dy = p.y * yToX - y;
132
+ return Math.sqrt(dx * dx + dy * dy);
133
+ };
134
+ for (const piece of pieces) {
135
+ piece.forEach(({ id, visit, owned, segments }, k) => {
136
+ if (!segments?.length) {
137
+ breakRun();
138
+ return;
139
+ }
140
+ const first = segments[0];
141
+ const last = segments.at(-1);
142
+ let forward;
143
+ if (at > from) {
144
+ const x = xs[at - 1];
145
+ const y = ys[at - 1];
146
+ const toFirst = gap(x, y, first);
147
+ const toLast = gap(x, y, last);
148
+ forward = toFirst <= toLast;
149
+ if (Math.min(toFirst, toLast) > jump) {
150
+ breakRun();
151
+ }
152
+ } else {
153
+ const next = piece[k + 1]?.segments;
154
+ const toNext = (p) => next?.length ? Math.min(
155
+ gap(p.x, p.y * yToX, next[0]),
156
+ gap(p.x, p.y * yToX, next.at(-1))
157
+ ) : 0;
158
+ forward = toNext(last) <= toNext(first);
159
+ }
160
+ run.ids.push(id);
161
+ run.visits.push(visit);
162
+ run.forward.push(forward);
163
+ run.owned.push(owned);
164
+ for (let j = 0; j < segments.length; j++) {
165
+ const p = segments[forward ? j : segments.length - 1 - j];
166
+ xs[at] = p.x;
167
+ ys[at] = p.y * yToX;
168
+ at++;
169
+ }
170
+ run.starts.push(at - from);
171
+ });
172
+ breakRun();
173
+ }
174
+ });
175
+ return runs;
176
+ }
177
+ function ownedWindows(s, owned, margin) {
178
+ const windows = [];
179
+ for (let i = 0; i < owned.length; i++) {
180
+ if (!owned[i]) {
181
+ continue;
182
+ }
183
+ let j = i;
184
+ while (j + 1 < owned.length && owned[j + 1]) {
185
+ j++;
186
+ }
187
+ const last = windows.at(-1);
188
+ const floor = last ? last[1] : 0;
189
+ let lo = i;
190
+ while (lo > floor && s[i] - s[lo - 1] <= margin) {
191
+ lo--;
192
+ }
193
+ let hi = j;
194
+ while (hi + 1 < s.length && s[hi + 1] - s[j] <= margin) {
195
+ hi++;
196
+ }
197
+ if (last && lo <= last[1] + 1) {
198
+ last[1] = hi;
199
+ } else {
200
+ windows.push([lo, hi]);
201
+ }
202
+ i = j;
203
+ }
204
+ return windows;
205
+ }
206
+ function smoothedNormals(run, reach) {
207
+ const s = arcOf(run);
208
+ const end = s.at(-1);
209
+ const pointAt = (at, j) => {
210
+ const a = run[j];
211
+ const b = run[Math.min(j + 1, run.length - 1)];
212
+ const span = s[Math.min(j + 1, run.length - 1)] - s[j];
213
+ const f = span > 0 ? (at - s[j]) / span : 0;
214
+ return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
215
+ };
216
+ let lo = 0;
217
+ let hi = 0;
218
+ return run.map((_, i) => {
219
+ const back = Math.max(0, s[i] - reach / 2);
220
+ const ahead = Math.min(end, s[i] + reach / 2);
221
+ while (lo < run.length - 1 && s[lo + 1] <= back) {
222
+ lo++;
223
+ }
224
+ while (hi < run.length - 1 && s[hi + 1] <= ahead) {
225
+ hi++;
226
+ }
227
+ const a = pointAt(back, lo);
228
+ const b = pointAt(ahead, hi);
229
+ const len = gapOf(a, b);
230
+ return len > 0 ? { nx: -(b.y - a.y) / len, ny: (b.x - a.x) / len } : void 0;
231
+ });
232
+ }
233
+ function cutCurls(run, close, length) {
234
+ const s = arcOf(run);
235
+ const out = run.slice();
236
+ const picks = [];
237
+ s.forEach((at, k) => {
238
+ const last = picks.at(-1);
239
+ if (last === void 0 || at - s[last] >= close / 4) {
240
+ picks.push(k);
241
+ }
242
+ });
243
+ if (picks.at(-1) !== run.length - 1) {
244
+ picks.push(run.length - 1);
245
+ }
246
+ let a = 0;
247
+ while (a < picks.length) {
248
+ const i = picks[a];
249
+ let to = -1;
250
+ for (let b = a + 1; b < picks.length && s[picks[b]] - s[i] <= length; b++) {
251
+ const j2 = picks[b];
252
+ if (s[j2] - s[i] > Math.PI * close && gapOf(run[i], run[j2]) <= close) {
253
+ to = b;
254
+ }
255
+ }
256
+ if (to < 0) {
257
+ a++;
258
+ continue;
259
+ }
260
+ const j = picks[to];
261
+ const p = run[i];
262
+ const q = run[j];
263
+ for (let k = i + 1; k < j; k++) {
264
+ const f = (s[k] - s[i]) / (s[j] - s[i]);
265
+ out[k] = {
266
+ ...run[k],
267
+ x: p.x + (q.x - p.x) * f,
268
+ y: p.y + (q.y - p.y) * f
269
+ };
270
+ }
271
+ a = to;
272
+ }
273
+ return out;
274
+ }
275
+ function rounded(run, half) {
276
+ const n = run.length;
277
+ const s = arcOf(run);
278
+ const end = s[n - 1];
279
+ const sumW = new Float64Array(n + 1);
280
+ const sumX = new Float64Array(n + 1);
281
+ const sumY = new Float64Array(n + 1);
282
+ for (let i = 0; i < n; i++) {
283
+ const p = run[i];
284
+ const w = (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2;
285
+ sumW[i + 1] = sumW[i] + w;
286
+ sumX[i + 1] = sumX[i] + w * p.x;
287
+ sumY[i + 1] = sumY[i] + w * p.y;
288
+ }
289
+ const out = new Array(n);
290
+ let lo = 0;
291
+ let hi = 0;
292
+ for (let i = 0; i < n; i++) {
293
+ const p = run[i];
294
+ const reach = Math.min(half, s[i], end - s[i]);
295
+ const from = s[i] - reach;
296
+ const to = s[i] + reach + 1e-9 * (1 + end);
297
+ while (lo < n && s[lo] < from) {
298
+ lo++;
299
+ }
300
+ while (hi < n && s[hi] < to) {
301
+ hi++;
302
+ }
303
+ const w = sumW[hi] - sumW[lo];
304
+ out[i] = w > 0 ? { x: (sumX[hi] - sumX[lo]) / w, y: (sumY[hi] - sumY[lo]) / w } : { x: p.x, y: p.y };
305
+ }
306
+ return out;
307
+ }
308
+ function tightPoints(line, radius) {
309
+ const n = line.length;
310
+ const s = arcOf(line);
311
+ const end = s[n - 1];
312
+ const tight = new Uint8Array(n);
313
+ const along = (j, at) => {
314
+ const a = line[j];
315
+ const b = line[j + 1];
316
+ if (!b) {
317
+ return a;
318
+ }
319
+ const span = s[j + 1] - s[j];
320
+ const f = span > 0 ? (at - s[j]) / span : 0;
321
+ return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
322
+ };
323
+ let lo = 0;
324
+ let hi = 0;
325
+ for (let i = 0; i < n; i++) {
326
+ if (s[i] < radius || end - s[i] < radius) {
327
+ continue;
328
+ }
329
+ while (lo + 1 < n && s[lo + 1] <= s[i] - radius) {
330
+ lo++;
331
+ }
332
+ while (hi + 1 < n && s[hi + 1] <= s[i] + radius) {
333
+ hi++;
334
+ }
335
+ const p = line[i];
336
+ const a = along(lo, s[i] - radius);
337
+ const b = along(hi, s[i] + radius);
338
+ const twice = Math.abs(
339
+ (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)
340
+ );
341
+ tight[i] = twice > 0 && gapOf(a, b) * gapOf(a, p) * gapOf(p, b) / (2 * twice) < radius ? 1 : 0;
342
+ }
343
+ return { tight, s };
344
+ }
345
+ function loosened(line, radius) {
346
+ const out = line.slice();
347
+ const { tight, s } = tightPoints(out, radius);
348
+ let windows = ownedWindows(
349
+ s,
350
+ Array.from(tight, (t) => t > 0),
351
+ 3 * radius
352
+ );
353
+ for (let pass = 0; pass < 8 && windows.length; pass++) {
354
+ windows = windows.filter(([lo, hi]) => {
355
+ const part = out.slice(lo, hi + 1);
356
+ const local = tightPoints(part, radius);
357
+ if (!local.tight.includes(1)) {
358
+ return false;
359
+ }
360
+ const weight = windowMean(local.s, local.tight, radius);
361
+ const smoother = rounded(part, radius);
362
+ for (let k = 0; k < part.length; k++) {
363
+ const w = Math.min(1, 3 * weight[k]);
364
+ if (w > 0) {
365
+ const p = part[k];
366
+ const q = smoother[k];
367
+ out[lo + k] = { x: p.x + (q.x - p.x) * w, y: p.y + (q.y - p.y) * w };
368
+ }
369
+ }
370
+ return true;
371
+ });
372
+ }
373
+ return out;
374
+ }
375
+ function runSigns(s, own, canonical, forward, settle, half) {
376
+ const n = own.length;
377
+ const agreement = new Float64Array(n);
378
+ let hi = 0;
379
+ let lo = 0;
380
+ let sum2 = 0;
381
+ let decided = 0;
382
+ let pending = 0;
383
+ for (let i = 0; i < n; i++) {
384
+ while (hi < n && s[hi] - s[i] <= half) {
385
+ const theirs = canonical(hi);
386
+ const mine = own[hi];
387
+ agreement[hi] = theirs && mine ? theirs.nx * mine.nx + theirs.ny * mine.ny : 0;
388
+ sum2 += agreement[hi];
389
+ hi++;
390
+ }
391
+ while (s[i] - s[lo] > half) {
392
+ sum2 -= agreement[lo++];
393
+ }
394
+ if (Math.abs(sum2) > 1e-9) {
395
+ decided = sum2 < 0 ? -1 : 1;
396
+ }
397
+ if (!decided) {
398
+ continue;
399
+ }
400
+ for (; pending <= i; pending++) {
401
+ settle(pending, decided);
402
+ }
403
+ }
404
+ if (pending < n) {
405
+ let along = 0;
406
+ for (let k = 0; k < n; k++) {
407
+ along += forward(k) ? 1 : -1;
408
+ }
409
+ for (; pending < n; pending++) {
410
+ settle(pending, along < 0 ? -1 : 1);
411
+ }
412
+ }
413
+ }
414
+ function laneLines(runs, yToX, reach, band, smooth = true) {
415
+ const turn = Math.max(reach, 2 * band);
416
+ const curl = Math.PI * band;
417
+ const context = smooth ? curl + 3 * band + turn / 2 : turn / 2;
418
+ const frames = /* @__PURE__ */ new Map();
419
+ const firsts = /* @__PURE__ */ new Map();
420
+ const arcs = [];
421
+ const stepFrames = [];
422
+ const newFrame = (length) => ({
423
+ normals: new Array(length),
424
+ shifts: smooth ? new Array(length) : []
425
+ });
426
+ const place = (id, walk, visit, f) => {
427
+ let byWalk = frames.get(id);
428
+ if (!byWalk) {
429
+ byWalk = [];
430
+ frames.set(id, byWalk);
431
+ }
432
+ ;
433
+ (byWalk[walk] ??= [])[visit] = f;
434
+ };
435
+ runs.forEach((run, r) => {
436
+ const { walk, ids, visits, owned, starts, xs, ys, stepOf } = run;
437
+ const steps = ids.length;
438
+ const n = xs.length;
439
+ const own = new Array(steps);
440
+ for (let i = 0; i < steps; i++) {
441
+ if (owned[i]) {
442
+ own[i] = newFrame(starts[i + 1] - starts[i]);
443
+ firsts.set(ids[i], { run: r, step: i, frame: own[i] });
444
+ place(ids[i], walk, visits[i], own[i]);
445
+ }
446
+ }
447
+ const raw = new Float64Array(n);
448
+ for (let p = 1; p < n; p++) {
449
+ const dx = xs[p] - xs[p - 1];
450
+ const dy = ys[p] - ys[p - 1];
451
+ raw[p] = raw[p - 1] + Math.sqrt(dx * dx + dy * dy);
452
+ }
453
+ const end = raw[n - 1];
454
+ const firstRun = new Int32Array(steps).fill(-1);
455
+ const firstStep = new Int32Array(steps);
456
+ const firstFrame = new Array(steps);
457
+ for (let i = 0; i < steps; i++) {
458
+ const first = owned[i] ? void 0 : firsts.get(ids[i]);
459
+ if (first) {
460
+ firstRun[i] = first.run;
461
+ firstStep[i] = first.step;
462
+ firstFrame[i] = first.frame;
463
+ }
464
+ }
465
+ const plus = new Int32Array(steps);
466
+ const minus = new Int32Array(steps);
467
+ for (let i = 0; i + 1 < steps; i++) {
468
+ const same = firstRun[i] >= 0 && firstRun[i + 1] === firstRun[i];
469
+ const d = same ? firstStep[i + 1] - firstStep[i] : 0;
470
+ plus[i + 1] = plus[i] + (d === 1 ? 1 : 0);
471
+ minus[i + 1] = minus[i] + (d === -1 ? 1 : 0);
472
+ }
473
+ const shared = new Array(steps);
474
+ let reachFrom = 0;
475
+ let reachTo = 0;
476
+ for (let i = 0; i < steps; i++) {
477
+ if (!smooth) {
478
+ shared[i] = !owned[i];
479
+ continue;
480
+ }
481
+ const from = raw[starts[i]] - context;
482
+ const to = raw[starts[i + 1] - 1] + context;
483
+ while (raw[reachFrom] < from) {
484
+ reachFrom++;
485
+ }
486
+ while (reachTo + 1 < n && raw[reachTo + 1] <= to) {
487
+ reachTo++;
488
+ }
489
+ const lo = stepOf[Math.max(0, reachFrom - 1)];
490
+ const hi = stepOf[Math.min(n - 1, reachTo + 1)];
491
+ const span = hi - lo;
492
+ const forward = plus[hi] - plus[lo];
493
+ const backward = minus[hi] - minus[lo];
494
+ shared[i] = !owned[i] && from >= 0 && to <= end && span > 0 && (forward === span || backward === span);
495
+ }
496
+ for (let i = 0; i < steps; i++) {
497
+ if (owned[i]) {
498
+ continue;
499
+ }
500
+ const first = firstFrame[i];
501
+ own[i] = !smooth && shared[i] ? first : shared[i] ? { normals: first.normals, shifts: first.shifts } : newFrame(first.normals.length);
502
+ place(ids[i], walk, visits[i], own[i]);
503
+ }
504
+ stepFrames.push(own);
505
+ const apart = new Array(n);
506
+ for (let p = 0; p < n; p++) {
507
+ apart[p] = !shared[stepOf[p]];
508
+ }
509
+ for (const [from, to] of ownedWindows(raw, apart, context)) {
510
+ const points = [];
511
+ for (let p = from; p <= to; p++) {
512
+ points.push({ x: xs[p], y: ys[p] });
513
+ }
514
+ const line = smooth ? loosened(
515
+ rounded(cutCurls(points, band / 2, curl), band / 2),
516
+ band / 2
517
+ ) : points;
518
+ const normals = smoothedNormals(line, turn);
519
+ runSigns(
520
+ arcOf(line),
521
+ normals,
522
+ (k) => {
523
+ const i = stepOf[from + k];
524
+ return (owned[i] ? own[i] : firstFrame[i])?.normals[pointIndex(run, from + k)];
525
+ },
526
+ (k) => run.forward[stepOf[from + k]],
527
+ (k, sign) => {
528
+ const p = from + k;
529
+ const i = stepOf[p];
530
+ if (shared[i]) {
531
+ return;
532
+ }
533
+ const index = pointIndex(run, p);
534
+ const m = normals[k];
535
+ own[i].normals[index] = m && (sign > 0 ? m : { nx: -m.nx, ny: -m.ny });
536
+ if (smooth) {
537
+ own[i].shifts[index] = {
538
+ dx: line[k].x - points[k].x,
539
+ dy: (line[k].y - points[k].y) / yToX
540
+ };
541
+ }
542
+ },
543
+ turn
544
+ );
545
+ }
546
+ if (!smooth) {
547
+ arcs.push(raw);
548
+ return;
549
+ }
550
+ const s = new Float64Array(n);
551
+ let px = 0;
552
+ let py = 0;
553
+ for (let p = 0; p < n; p++) {
554
+ const shift = own[stepOf[p]].shifts[pointIndex(run, p)];
555
+ const x = xs[p] + (shift?.dx ?? 0);
556
+ const y = ys[p] + (shift?.dy ?? 0) * yToX;
557
+ s[p] = p ? s[p - 1] + Math.sqrt((x - px) ** 2 + (y - py) ** 2) : 0;
558
+ px = x;
559
+ py = y;
560
+ }
561
+ arcs.push(s);
562
+ });
563
+ return { frames, arcs, stepFrames };
564
+ }
565
+ function windowed(s, v, half, sign) {
566
+ const out = new Array(v.length);
567
+ const deque = [];
568
+ let head = 0;
569
+ let next = 0;
570
+ for (let i = 0; i < v.length; i++) {
571
+ while (next < v.length && s[next] - s[i] <= half) {
572
+ while (deque.length > head && sign * v[deque.at(-1)] <= sign * v[next]) {
573
+ deque.pop();
574
+ }
575
+ deque.push(next++);
576
+ }
577
+ while (s[i] - s[deque[head]] > half) {
578
+ head++;
579
+ }
580
+ out[i] = v[deque[head]];
581
+ }
582
+ return out;
583
+ }
584
+ function windowMean(s, v, half, counted = () => true) {
585
+ const n = v.length;
586
+ const sumW = new Float64Array(n + 1);
587
+ const sumWV = new Float64Array(n + 1);
588
+ for (let i = 0; i < n; i++) {
589
+ const w = !counted(v[i]) ? 0 : n > 1 ? (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2 : 1;
590
+ sumW[i + 1] = sumW[i] + w;
591
+ sumWV[i + 1] = sumWV[i] + w * v[i];
592
+ }
593
+ const out = new Float64Array(n);
594
+ let lo = 0;
595
+ let hi = 0;
596
+ for (let i = 0; i < n; i++) {
597
+ while (s[i] - s[lo] > half) {
598
+ lo++;
599
+ }
600
+ while (hi < n && s[hi] - s[i] <= half) {
601
+ hi++;
602
+ }
603
+ const w = sumW[hi] - sumW[lo];
604
+ out[i] = w > 0 ? (sumWV[hi] - sumWV[lo]) / w : v[i];
605
+ }
606
+ return out;
607
+ }
608
+ var ONE_THREAD = [0];
609
+ function repeats(passes) {
610
+ for (const p of passes.values()) {
611
+ if (p > 1) {
612
+ return true;
613
+ }
614
+ }
615
+ return false;
616
+ }
617
+ function walkThreads(route, passes, nodePositions, yToX, reach) {
618
+ if (!repeats(passes)) {
619
+ return {
620
+ span: passes,
621
+ nodeThreads: /* @__PURE__ */ new Map(),
622
+ threadPasses: /* @__PURE__ */ new Map(),
623
+ threadAt: /* @__PURE__ */ new Map(),
624
+ linkThreads: /* @__PURE__ */ new Map()
625
+ };
626
+ }
627
+ const index = /* @__PURE__ */ new Map();
628
+ const ids = [];
629
+ const widths = [];
630
+ const visits = [];
631
+ const steps = [];
632
+ const stepVisits = [];
633
+ const stepWants = [];
634
+ const stepGlides = [];
635
+ for (const piece of route) {
636
+ const points = [];
637
+ const nodes = [];
638
+ for (const id of piece) {
639
+ let k = index.get(id);
640
+ if (k === void 0) {
641
+ k = ids.length;
642
+ index.set(id, k);
643
+ ids.push(id);
644
+ widths.push(0);
645
+ visits.push(0);
646
+ }
647
+ nodes.push(k);
648
+ const segments = nodePositions[id];
649
+ const mid = segments?.[Math.floor(segments.length / 2)];
650
+ points.push(
651
+ mid ? { x: mid.x, y: mid.y * yToX } : points.at(-1) ?? { x: 0, y: 0 }
652
+ );
653
+ }
654
+ const s = arcOf(points);
655
+ const p = piece.map((id) => passes.get(id) ?? 1);
656
+ const most = windowed(s, p, reach / 2, 1);
657
+ const copy = nodes.map((k) => ++visits[k]);
658
+ const n = piece.length;
659
+ const before = new Int32Array(n);
660
+ let last = -1;
661
+ for (let i = 0; i < n; i++) {
662
+ last = p[i] === most[i] ? i : last;
663
+ before[i] = last;
664
+ }
665
+ const after = new Int32Array(n);
666
+ last = -1;
667
+ for (let i = n - 1; i >= 0; i--) {
668
+ last = p[i] === most[i] ? i : last;
669
+ after[i] = last;
670
+ }
671
+ const wants = new Array(n);
672
+ const glides = new Array(n).fill(NaN);
673
+ for (let i = 0; i < n; i++) {
674
+ const b = before[i];
675
+ const a = after[i];
676
+ const nearest = b < 0 ? a : a < 0 ? b : s[i] - s[b] <= s[a] - s[i] ? b : a;
677
+ const want = (k) => Math.min(copy[k], most[i]) - 1;
678
+ wants[i] = want(nearest);
679
+ if (p[i] === 1 && b >= 0 && a >= 0 && want(a) !== want(b)) {
680
+ const f = (s[i] - s[b]) / (s[a] - s[b] || 1);
681
+ glides[i] = want(b) + (want(a) - want(b)) * f;
682
+ }
683
+ widths[nodes[i]] = Math.max(widths[nodes[i]], most[i]);
684
+ }
685
+ steps.push(nodes);
686
+ stepVisits.push(copy);
687
+ stepWants.push(wants);
688
+ stepGlides.push(glides);
689
+ }
690
+ const taken = [];
691
+ const threadOf = [];
692
+ const free = (k, want) => {
693
+ const width = widths[k];
694
+ const own = taken[k] ??= /* @__PURE__ */ new Set();
695
+ for (let d = 0; d < width; d++) {
696
+ for (const c of [want - d, want + d]) {
697
+ if (c >= 0 && c < width && !own.has(c)) {
698
+ return c;
699
+ }
700
+ }
701
+ }
702
+ return want;
703
+ };
704
+ const glideOf = [];
705
+ steps.forEach((nodes, piece) => {
706
+ nodes.forEach((k, i) => {
707
+ if (widths[k] > 1) {
708
+ const glide = stepGlides[piece][i];
709
+ const t = free(
710
+ k,
711
+ Number.isNaN(glide) ? stepWants[piece][i] : Math.round(glide)
712
+ );
713
+ taken[k].add(t);
714
+ (threadOf[k] ??= [])[stepVisits[piece][i]] = t;
715
+ if (!Number.isNaN(glide)) {
716
+ ;
717
+ (glideOf[k] ??= [])[t] = glide;
718
+ }
719
+ }
720
+ });
721
+ });
722
+ const span = /* @__PURE__ */ new Map();
723
+ const nodeThreads = /* @__PURE__ */ new Map();
724
+ const threadPasses = /* @__PURE__ */ new Map();
725
+ const threadAt = /* @__PURE__ */ new Map();
726
+ ids.forEach((id, k) => {
727
+ if (glideOf[k]) {
728
+ threadAt.set(id, glideOf[k]);
729
+ }
730
+ span.set(id, widths[k]);
731
+ nodeThreads.set(
732
+ id,
733
+ taken[k] ? [...taken[k]].sort((a, b) => a - b) : ONE_THREAD
734
+ );
735
+ const passOf = [];
736
+ threadOf[k]?.forEach((thread, visit) => {
737
+ passOf[thread] = visit - 1;
738
+ });
739
+ threadPasses.set(id, passOf);
740
+ });
741
+ const linkThreads = /* @__PURE__ */ new Map();
742
+ steps.forEach((nodes, piece) => {
743
+ const visit = stepVisits[piece];
744
+ for (let i = 1; i < nodes.length; i++) {
745
+ const from = nodes[i - 1];
746
+ const to = nodes[i];
747
+ const pair = [
748
+ threadOf[from]?.[visit[i - 1]] ?? 0,
749
+ threadOf[to]?.[visit[i]] ?? 0
750
+ ];
751
+ let ahead = linkThreads.get(ids[from]);
752
+ if (!ahead) {
753
+ ahead = /* @__PURE__ */ new Map();
754
+ linkThreads.set(ids[from], ahead);
755
+ }
756
+ const list = ahead.get(ids[to]);
757
+ if (!list) {
758
+ ahead.set(ids[to], [pair]);
759
+ } else if (!list.some(([a, b]) => a === pair[0] && b === pair[1])) {
760
+ list.push(pair);
761
+ }
762
+ }
763
+ });
764
+ return { span, nodeThreads, threadPasses, threadAt, linkThreads };
765
+ }
766
+ function bandSpans(runs, arcs, spans, { reach, taper }) {
767
+ const out = /* @__PURE__ */ new Map();
768
+ runs.forEach((run, r) => {
769
+ const s = arcs[r];
770
+ const along = run.ids.map((_, i) => {
771
+ const length = run.starts[i + 1] - run.starts[i];
772
+ const middle = Math.floor(length / 2);
773
+ return s[run.starts[i] + (run.forward[i] ? middle : length - 1 - middle)];
774
+ });
775
+ for (const [lo, hi] of ownedWindows(
776
+ along,
777
+ run.owned,
778
+ reach / 2 + taper / 2
779
+ )) {
780
+ const at = along.slice(lo, hi + 1);
781
+ const ids = run.ids.slice(lo, hi + 1);
782
+ const eased = spans.map((own) => {
783
+ const raw = ids.map((id) => own.get(id) ?? 0);
784
+ return raw.every((v) => v === raw[0]) ? { slot: raw, held: raw } : {
785
+ slot: windowMean(at, windowed(at, raw, reach / 2, 1), taper / 2),
786
+ held: windowMean(at, raw, taper / 2, (v) => v > 0)
787
+ };
788
+ });
789
+ ids.forEach((id, i) => {
790
+ if (run.owned[lo + i]) {
791
+ out.set(id, {
792
+ slot: eased.map((e) => e.slot[i]),
793
+ held: eased.map((e) => e.held[i])
794
+ });
795
+ }
796
+ });
797
+ }
798
+ });
799
+ return out;
800
+ }
801
+ function easeOffsets(runs, stepFrames, arcs, spans, passes, threads, half) {
802
+ const plain = slotCentres(passes, passes);
803
+ const byNode = /* @__PURE__ */ new Map();
804
+ const centresAt = (id) => {
805
+ let centres = byNode.get(id);
806
+ if (!centres) {
807
+ const own = spans.get(id);
808
+ centres = own ? slotCentres(own.slot, passes, own.held) : plain;
809
+ byNode.set(id, centres);
810
+ }
811
+ return centres;
812
+ };
813
+ runs.forEach((run, r) => {
814
+ const { walk, ids, visits, starts } = run;
815
+ const { threadPasses, threadAt } = threads[walk];
816
+ const raw = new Float64Array(run.xs.length);
817
+ ids.forEach((id, i) => {
818
+ const thread = Math.max(0, threadPasses.get(id)?.indexOf(visits[i]) ?? 0);
819
+ const place = threadAt.get(id)?.[thread] ?? thread;
820
+ raw.fill(
821
+ centresAt(id)[walk] + place * THREAD_PITCH_PX,
822
+ starts[i],
823
+ starts[i + 1]
824
+ );
825
+ });
826
+ const eased = windowMean(arcs[r], raw, half);
827
+ stepFrames[r].forEach((frame, i) => {
828
+ const from = starts[i];
829
+ const to = starts[i + 1];
830
+ let constant = true;
831
+ for (let p = from + 1; constant && p < to; p++) {
832
+ constant = Math.abs(eased[p] - eased[from]) < 1e-9;
833
+ }
834
+ if (constant) {
835
+ frame.offset = eased[from];
836
+ return;
837
+ }
838
+ const offsets = new Array(to - from);
839
+ for (let p = from; p < to; p++) {
840
+ offsets[pointIndex(run, p)] = eased[p];
841
+ }
842
+ frame.offsets = offsets;
843
+ });
844
+ });
845
+ }
846
+ function liftLanes(walks, nodePositions, yToX, worldPerScreenPx, steps = walkSteps(
847
+ walks.map((w) => w.route),
848
+ nodePositions
849
+ )) {
850
+ const threaded = threadsPasses(walks);
851
+ const passes = walks.map((w) => w.mostPasses);
852
+ const reach = THREAD_REACH_PX * worldPerScreenPx;
853
+ const runs = laneRuns(steps, yToX, LANE_SMOOTHING_PX * worldPerScreenPx);
854
+ const band = bandWidthPx(passes) * (threaded ? bandScale(passes) : 1) * worldPerScreenPx;
855
+ const lines = laneLines(
856
+ runs,
857
+ yToX,
858
+ LANE_SMOOTHING_PX * worldPerScreenPx,
859
+ band,
860
+ threaded
861
+ );
862
+ const threads = threaded ? walks.map((w) => walkThreads(w.route, w.passes, nodePositions, yToX, reach)) : void 0;
863
+ const spans = threads ? bandSpans(
864
+ runs,
865
+ lines.arcs,
866
+ threads.map((t) => t.span),
867
+ { reach, taper: THREAD_TAPER_PX * worldPerScreenPx }
868
+ ) : void 0;
869
+ if (spans && threads) {
870
+ easeOffsets(
871
+ runs,
872
+ lines.stepFrames,
873
+ lines.arcs,
874
+ spans,
875
+ passes,
876
+ threads,
877
+ THREAD_TAPER_PX / 2 * worldPerScreenPx
878
+ );
879
+ }
880
+ const furthestShift = { dx: 0, dy: 0 };
881
+ const seenShifts = /* @__PURE__ */ new Set();
882
+ for (const byWalk of lines.frames.values()) {
883
+ for (const { shifts } of byWalk.flat()) {
884
+ if (seenShifts.has(shifts)) {
885
+ continue;
886
+ }
887
+ seenShifts.add(shifts);
888
+ for (const shift of shifts) {
889
+ furthestShift.dx = Math.max(furthestShift.dx, Math.abs(shift?.dx ?? 0));
890
+ furthestShift.dy = Math.max(furthestShift.dy, Math.abs(shift?.dy ?? 0));
891
+ }
892
+ }
893
+ }
894
+ return { lines, threads, spans, furthestShift };
895
+ }
896
+
897
+ export {
898
+ THREAD_PX,
899
+ THREAD_GAP_PX,
900
+ SLOT_GAP_PX,
901
+ LANE_SMOOTHING_PX,
902
+ THREAD_REACH_PX,
903
+ THREAD_TAPER_PX,
904
+ MAX_BAND_PX,
905
+ PASSES_KEY,
906
+ threadsPasses,
907
+ THREAD_PITCH_PX,
908
+ slotCentres,
909
+ bandWidthPx,
910
+ bandScale,
911
+ passesKey,
912
+ laneSlideAt,
913
+ walkSteps,
914
+ laneRuns,
915
+ laneLines,
916
+ walkThreads,
917
+ bandSpans,
918
+ liftLanes
919
+ };