@bruno00o/graph 0.1.0 → 0.2.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.
|
@@ -449,8 +449,92 @@ function createGraphModel(options = {}) {
|
|
|
449
449
|
}
|
|
450
450
|
};
|
|
451
451
|
}
|
|
452
|
+
// src/core/edge-curve.ts
|
|
453
|
+
const loopTurn = .6;
|
|
454
|
+
const loopGrowth = .5;
|
|
455
|
+
const maxBend = .12;
|
|
456
|
+
const spreadBend = .4;
|
|
457
|
+
function laneBend(position, count) {
|
|
458
|
+
if (count < 2) return 0;
|
|
459
|
+
const step = Math.min(maxBend, spreadBend / (count - 1));
|
|
460
|
+
return (position - (count - 1) / 2) * step;
|
|
461
|
+
}
|
|
462
|
+
function loopReach(radius, ordinal, minimum) {
|
|
463
|
+
return radius + Math.max(radius * 1.5, minimum) * (1 + loopGrowth * ordinal);
|
|
464
|
+
}
|
|
465
|
+
function parallelCurve(ax, ay, dx, dy, bend) {
|
|
466
|
+
const length = Math.hypot(dx - ax, dy - ay);
|
|
467
|
+
const nx = length > 0 ? -(dy - ay) / length : 0;
|
|
468
|
+
const ny = length > 0 ? (dx - ax) / length : 1;
|
|
469
|
+
const offset = 2 * bend * length;
|
|
470
|
+
const px = (ax + dx) / 2 + nx * offset;
|
|
471
|
+
const py = (ay + dy) / 2 + ny * offset;
|
|
472
|
+
return {
|
|
473
|
+
ax,
|
|
474
|
+
ay,
|
|
475
|
+
bx: ax + (px - ax) * 2 / 3,
|
|
476
|
+
by: ay + (py - ay) * 2 / 3,
|
|
477
|
+
cx: dx + (px - dx) * 2 / 3,
|
|
478
|
+
cy: dy + (py - dy) * 2 / 3,
|
|
479
|
+
dx,
|
|
480
|
+
dy
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function loopCurve(x, y, reach) {
|
|
484
|
+
const arm = reach / (.75 * Math.cos(loopTurn));
|
|
485
|
+
const ux = Math.SQRT1_2;
|
|
486
|
+
const uy = -Math.SQRT1_2;
|
|
487
|
+
const cos = Math.cos(loopTurn);
|
|
488
|
+
const sin = Math.sin(loopTurn);
|
|
489
|
+
return {
|
|
490
|
+
ax: x,
|
|
491
|
+
ay: y,
|
|
492
|
+
bx: x + arm * (ux * cos - uy * sin),
|
|
493
|
+
by: y + arm * (ux * sin + uy * cos),
|
|
494
|
+
cx: x + arm * (ux * cos + uy * sin),
|
|
495
|
+
cy: y + arm * (-ux * sin + uy * cos),
|
|
496
|
+
dx: x,
|
|
497
|
+
dy: y
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function pointOnCubic(curve, t) {
|
|
501
|
+
const u = 1 - t;
|
|
502
|
+
const a = u * u * u;
|
|
503
|
+
const b = 3 * u * u * t;
|
|
504
|
+
const c = 3 * u * t * t;
|
|
505
|
+
const d = t * t * t;
|
|
506
|
+
return {
|
|
507
|
+
x: a * curve.ax + b * curve.bx + c * curve.cx + d * curve.dx,
|
|
508
|
+
y: a * curve.ay + b * curve.by + c * curve.cy + d * curve.dy
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function sampleCubic(curve, segments) {
|
|
512
|
+
const points = new Float64Array((segments + 1) * 2);
|
|
513
|
+
for (let step = 0; step <= segments; step++) {
|
|
514
|
+
const point = pointOnCubic(curve, step / segments);
|
|
515
|
+
points[step * 2] = point.x;
|
|
516
|
+
points[step * 2 + 1] = point.y;
|
|
517
|
+
}
|
|
518
|
+
return points;
|
|
519
|
+
}
|
|
520
|
+
function distanceToCubic(curve, x, y, segments) {
|
|
521
|
+
const points = sampleCubic(curve, segments);
|
|
522
|
+
let best = Number.POSITIVE_INFINITY;
|
|
523
|
+
for (let step = 0; step < segments; step++) {
|
|
524
|
+
const ax = points[step * 2] ?? 0;
|
|
525
|
+
const ay = points[step * 2 + 1] ?? 0;
|
|
526
|
+
const vx = (points[step * 2 + 2] ?? 0) - ax;
|
|
527
|
+
const vy = (points[step * 2 + 3] ?? 0) - ay;
|
|
528
|
+
const squared = vx * vx + vy * vy;
|
|
529
|
+
const t = squared > 0 ? Math.min(1, Math.max(0, ((x - ax) * vx + (y - ay) * vy) / squared)) : 0;
|
|
530
|
+
best = Math.min(best, Math.hypot(x - (ax + vx * t), y - (ay + vy * t)));
|
|
531
|
+
}
|
|
532
|
+
return best;
|
|
533
|
+
}
|
|
452
534
|
// src/core/pick-index.ts
|
|
453
535
|
const minimumPickRadius = 2;
|
|
536
|
+
const pickSegments = 16;
|
|
537
|
+
const indexSegments = 8;
|
|
454
538
|
function createGrid(frame, count, visit) {
|
|
455
539
|
const cells = frame.cols * frame.rows;
|
|
456
540
|
const offsets = new Uint32Array(cells + 1);
|
|
@@ -505,6 +589,7 @@ var PickIndex = class {
|
|
|
505
589
|
#maxWorldRadius = 0;
|
|
506
590
|
#maxScreenRadius = 0;
|
|
507
591
|
#maxEdgeHalfWidth = 0;
|
|
592
|
+
#loops = [];
|
|
508
593
|
build(source) {
|
|
509
594
|
const { nodes, edges, positions } = source;
|
|
510
595
|
let count = 0;
|
|
@@ -530,8 +615,25 @@ var PickIndex = class {
|
|
|
530
615
|
if (count === 0) {
|
|
531
616
|
this.#nodes = null;
|
|
532
617
|
this.#edges = null;
|
|
618
|
+
this.#loops = [];
|
|
533
619
|
return;
|
|
534
620
|
}
|
|
621
|
+
this.#loops = [];
|
|
622
|
+
for (let index = 0; index < edges.capacity; index++) {
|
|
623
|
+
if (edges.alive[index] !== 1) continue;
|
|
624
|
+
if (edges.source[index] === edges.target[index]) {
|
|
625
|
+
this.#loops.push(index);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if ((edges.bend[index] ?? 0) === 0) continue;
|
|
629
|
+
const points = sampleCubic(this.#parallel(source, index), indexSegments);
|
|
630
|
+
for (let at = 0; at < points.length; at += 2) {
|
|
631
|
+
minX = Math.min(minX, points[at] ?? minX);
|
|
632
|
+
maxX = Math.max(maxX, points[at] ?? maxX);
|
|
633
|
+
minY = Math.min(minY, points[at + 1] ?? minY);
|
|
634
|
+
maxY = Math.max(maxY, points[at + 1] ?? maxY);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
535
637
|
const spanX = Math.max(maxX - minX, 1);
|
|
536
638
|
const spanY = Math.max(maxY - minY, 1);
|
|
537
639
|
let cell = Math.max(this.#maxWorldRadius * 2, Math.sqrt(spanX * spanY / count) * 2, .001);
|
|
@@ -554,9 +656,15 @@ var PickIndex = class {
|
|
|
554
656
|
this.#edges = createGrid(frame, edges.capacity, (index, emit) => {
|
|
555
657
|
if (edges.alive[index] !== 1) return;
|
|
556
658
|
this.#maxEdgeHalfWidth = Math.max(this.#maxEdgeHalfWidth, (edges.width[index] ?? 0) / 2);
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
|
|
659
|
+
const from = edges.source[index] ?? 0;
|
|
660
|
+
const to = edges.target[index] ?? 0;
|
|
661
|
+
if (from === to) return;
|
|
662
|
+
if ((edges.bend[index] ?? 0) === 0) {
|
|
663
|
+
traverse(frame, positions.x(from), positions.y(from), positions.x(to), positions.y(to), emit);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const points = sampleCubic(this.#parallel(source, index), indexSegments);
|
|
667
|
+
for (let at = 0; at + 3 < points.length; at += 2) traverse(frame, points[at] ?? 0, points[at + 1] ?? 0, points[at + 2] ?? 0, points[at + 3] ?? 0, emit);
|
|
560
668
|
});
|
|
561
669
|
}
|
|
562
670
|
pick(source, x, y, zoom, tolerance) {
|
|
@@ -582,6 +690,18 @@ var PickIndex = class {
|
|
|
582
690
|
for (let at = grid.offsets[cell] ?? 0; at < end; at++) visit(grid.items[at] ?? 0);
|
|
583
691
|
}
|
|
584
692
|
}
|
|
693
|
+
#parallel(source, index) {
|
|
694
|
+
const { edges, positions } = source;
|
|
695
|
+
const from = edges.source[index] ?? 0;
|
|
696
|
+
const to = edges.target[index] ?? 0;
|
|
697
|
+
return parallelCurve(positions.x(from), positions.y(from), positions.x(to), positions.y(to), edges.bend[index] ?? 0);
|
|
698
|
+
}
|
|
699
|
+
#loop(source, index, zoom) {
|
|
700
|
+
const node = source.edges.source[index] ?? 0;
|
|
701
|
+
const size = (source.nodes.size[node] ?? 0) / 2;
|
|
702
|
+
const reach = loopReach(Math.max(source.nodes.screenUnits[node] === 1 ? size : size * zoom, 1.25), source.edges.bend[index] ?? 0, 12) / zoom;
|
|
703
|
+
return loopCurve(source.positions.x(node), source.positions.y(node), reach);
|
|
704
|
+
}
|
|
585
705
|
#radius(source, index, zoom) {
|
|
586
706
|
const size = (source.nodes.size[index] ?? 0) / 2;
|
|
587
707
|
const radius = source.nodes.screenUnits[index] === 1 ? size / zoom : size;
|
|
@@ -612,10 +732,26 @@ var PickIndex = class {
|
|
|
612
732
|
let best = -1;
|
|
613
733
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
614
734
|
let bestOrder = Number.NEGATIVE_INFINITY;
|
|
735
|
+
const consider = (index, distance) => {
|
|
736
|
+
if (distance > ((edges.width[index] ?? 0) / 2 + tolerance) / zoom) return;
|
|
737
|
+
const order = source.order("edges", index);
|
|
738
|
+
if (distance < bestDistance || distance === bestDistance && order > bestOrder) {
|
|
739
|
+
best = index;
|
|
740
|
+
bestDistance = distance;
|
|
741
|
+
bestOrder = order;
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
const seen = /* @__PURE__ */ new Set();
|
|
615
745
|
this.#cells(grid, x, y, reach, (index) => {
|
|
616
746
|
if (edges.alive[index] !== 1) return;
|
|
617
747
|
const s = edges.source[index] ?? 0;
|
|
618
748
|
const t = edges.target[index] ?? 0;
|
|
749
|
+
if ((edges.bend[index] ?? 0) !== 0) {
|
|
750
|
+
if (seen.has(index)) return;
|
|
751
|
+
seen.add(index);
|
|
752
|
+
consider(index, distanceToCubic(this.#parallel(source, index), x, y, pickSegments));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
619
755
|
const ax = positions.x(s);
|
|
620
756
|
const ay = positions.y(s);
|
|
621
757
|
const dx = positions.x(t) - ax;
|
|
@@ -624,15 +760,12 @@ var PickIndex = class {
|
|
|
624
760
|
if (length === 0) return;
|
|
625
761
|
const along = ((x - ax) * dx + (y - ay) * dy) / length;
|
|
626
762
|
if (along < this.#radius(source, s, zoom) || along > length - this.#radius(source, t, zoom)) return;
|
|
627
|
-
|
|
628
|
-
if (distance > ((edges.width[index] ?? 0) / 2 + tolerance) / zoom) return;
|
|
629
|
-
const order = source.order("edges", index);
|
|
630
|
-
if (distance < bestDistance || distance === bestDistance && order > bestOrder) {
|
|
631
|
-
best = index;
|
|
632
|
-
bestDistance = distance;
|
|
633
|
-
bestOrder = order;
|
|
634
|
-
}
|
|
763
|
+
consider(index, Math.abs((x - ax) * dy - (y - ay) * dx) / length);
|
|
635
764
|
});
|
|
765
|
+
for (const index of this.#loops) {
|
|
766
|
+
if (edges.alive[index] !== 1) continue;
|
|
767
|
+
consider(index, distanceToCubic(this.#loop(source, index, zoom), x, y, pickSegments));
|
|
768
|
+
}
|
|
636
769
|
return best;
|
|
637
770
|
}
|
|
638
771
|
};
|
|
@@ -1091,6 +1224,7 @@ var EdgeColumns = class {
|
|
|
1091
1224
|
dash = /* @__PURE__ */ new Uint8Array(0);
|
|
1092
1225
|
arrow = /* @__PURE__ */ new Uint8Array(0);
|
|
1093
1226
|
opacity = /* @__PURE__ */ new Float32Array(0);
|
|
1227
|
+
bend = /* @__PURE__ */ new Float32Array(0);
|
|
1094
1228
|
label = [];
|
|
1095
1229
|
reserve(index) {
|
|
1096
1230
|
if (index < this.capacity) return;
|
|
@@ -1104,15 +1238,190 @@ var EdgeColumns = class {
|
|
|
1104
1238
|
this.dash = grow$1(this.dash, capacity);
|
|
1105
1239
|
this.arrow = grow$1(this.arrow, capacity);
|
|
1106
1240
|
this.opacity = grow$1(this.opacity, capacity);
|
|
1241
|
+
this.bend = grow$1(this.bend, capacity);
|
|
1107
1242
|
this.capacity = capacity;
|
|
1108
1243
|
}
|
|
1109
1244
|
clear(index) {
|
|
1110
1245
|
if (index >= this.capacity) return;
|
|
1111
1246
|
this.alive[index] = 0;
|
|
1247
|
+
this.bend[index] = 0;
|
|
1112
1248
|
this.keys[index] = void 0;
|
|
1113
1249
|
this.label[index] = null;
|
|
1114
1250
|
}
|
|
1115
1251
|
};
|
|
1252
|
+
// src/core/edge-lanes.ts
|
|
1253
|
+
const empty = -1;
|
|
1254
|
+
const removed = -2;
|
|
1255
|
+
function hash(low, high) {
|
|
1256
|
+
let h = Math.imul(low, 2654435761) ^ Math.imul(high + 2135587861, 2246822519);
|
|
1257
|
+
h ^= h >>> 15;
|
|
1258
|
+
return Math.imul(h, 739982445) ^ h >>> 12;
|
|
1259
|
+
}
|
|
1260
|
+
function grown(array, length) {
|
|
1261
|
+
const next = new Int32Array(length).fill(-1);
|
|
1262
|
+
next.set(array);
|
|
1263
|
+
return next;
|
|
1264
|
+
}
|
|
1265
|
+
function createEdgeLanes() {
|
|
1266
|
+
let mask = -1;
|
|
1267
|
+
let lows = /* @__PURE__ */ new Uint32Array(0);
|
|
1268
|
+
let highs = /* @__PURE__ */ new Uint32Array(0);
|
|
1269
|
+
let heads = /* @__PURE__ */ new Int32Array(0);
|
|
1270
|
+
let tails = /* @__PURE__ */ new Int32Array(0);
|
|
1271
|
+
let counts = /* @__PURE__ */ new Int32Array(0);
|
|
1272
|
+
let stamps = /* @__PURE__ */ new Uint32Array(0);
|
|
1273
|
+
let used = 0;
|
|
1274
|
+
let live = 0;
|
|
1275
|
+
let generation = 0;
|
|
1276
|
+
let slots = /* @__PURE__ */ new Int32Array(0);
|
|
1277
|
+
let nexts = /* @__PURE__ */ new Int32Array(0);
|
|
1278
|
+
let prevs = /* @__PURE__ */ new Int32Array(0);
|
|
1279
|
+
let touched = [];
|
|
1280
|
+
function reserve(index) {
|
|
1281
|
+
if (index < slots.length) return;
|
|
1282
|
+
const length = Math.max(64, slots.length * 2, index + 1);
|
|
1283
|
+
slots = grown(slots, length);
|
|
1284
|
+
nexts = grown(nexts, length);
|
|
1285
|
+
prevs = grown(prevs, length);
|
|
1286
|
+
}
|
|
1287
|
+
function locate(low, high) {
|
|
1288
|
+
let slot = hash(low, high) & mask;
|
|
1289
|
+
let free = -1;
|
|
1290
|
+
for (;;) {
|
|
1291
|
+
const head = heads[slot] ?? empty;
|
|
1292
|
+
if (head === empty) return -(free >= 0 ? free : slot) - 1;
|
|
1293
|
+
if (head === removed) {
|
|
1294
|
+
if (free < 0) free = slot;
|
|
1295
|
+
} else if (lows[slot] === low && highs[slot] === high) return slot;
|
|
1296
|
+
slot = slot + 1 & mask;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function resize(minimum) {
|
|
1300
|
+
let capacity = 16;
|
|
1301
|
+
while (capacity < minimum) capacity *= 2;
|
|
1302
|
+
const oldLows = lows;
|
|
1303
|
+
const oldHighs = highs;
|
|
1304
|
+
const oldHeads = heads;
|
|
1305
|
+
const oldTails = tails;
|
|
1306
|
+
const oldCounts = counts;
|
|
1307
|
+
mask = capacity - 1;
|
|
1308
|
+
lows = new Uint32Array(capacity);
|
|
1309
|
+
highs = new Uint32Array(capacity);
|
|
1310
|
+
heads = new Int32Array(capacity).fill(empty);
|
|
1311
|
+
tails = new Int32Array(capacity).fill(-1);
|
|
1312
|
+
counts = new Int32Array(capacity);
|
|
1313
|
+
stamps = new Uint32Array(capacity);
|
|
1314
|
+
generation = 0;
|
|
1315
|
+
used = 0;
|
|
1316
|
+
live = 0;
|
|
1317
|
+
for (let old = 0; old < oldHeads.length; old++) {
|
|
1318
|
+
const first = oldHeads[old] ?? empty;
|
|
1319
|
+
if (first < 0) continue;
|
|
1320
|
+
const low = oldLows[old] ?? 0;
|
|
1321
|
+
const high = oldHighs[old] ?? 0;
|
|
1322
|
+
const slot = -locate(low, high) - 1;
|
|
1323
|
+
lows[slot] = low;
|
|
1324
|
+
highs[slot] = high;
|
|
1325
|
+
heads[slot] = first;
|
|
1326
|
+
tails[slot] = oldTails[old] ?? -1;
|
|
1327
|
+
counts[slot] = oldCounts[old] ?? 0;
|
|
1328
|
+
used++;
|
|
1329
|
+
live++;
|
|
1330
|
+
for (let at = first; at >= 0; at = nexts[at] ?? -1) slots[at] = slot;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function unlink(index) {
|
|
1334
|
+
const slot = slots[index] ?? -1;
|
|
1335
|
+
if (slot < 0) return;
|
|
1336
|
+
const prev = prevs[index] ?? -1;
|
|
1337
|
+
const next = nexts[index] ?? -1;
|
|
1338
|
+
if (prev >= 0) nexts[prev] = next;
|
|
1339
|
+
else heads[slot] = next;
|
|
1340
|
+
if (next >= 0) prevs[next] = prev;
|
|
1341
|
+
else tails[slot] = prev;
|
|
1342
|
+
slots[index] = -1;
|
|
1343
|
+
const count = (counts[slot] ?? 1) - 1;
|
|
1344
|
+
counts[slot] = count;
|
|
1345
|
+
if (count === 0) {
|
|
1346
|
+
heads[slot] = removed;
|
|
1347
|
+
live--;
|
|
1348
|
+
} else touched.push(heads[slot] ?? -1);
|
|
1349
|
+
}
|
|
1350
|
+
function link(index, source, target) {
|
|
1351
|
+
reserve(index);
|
|
1352
|
+
const low = source < target ? source : target;
|
|
1353
|
+
const high = source < target ? target : source;
|
|
1354
|
+
const current = slots[index] ?? -1;
|
|
1355
|
+
if (current >= 0) {
|
|
1356
|
+
if (lows[current] === low && highs[current] === high) return;
|
|
1357
|
+
unlink(index);
|
|
1358
|
+
}
|
|
1359
|
+
if ((used + 1) * 2 > mask + 1) resize(Math.max(16, (live + 1) * 4));
|
|
1360
|
+
const found = locate(low, high);
|
|
1361
|
+
prevs[index] = -1;
|
|
1362
|
+
nexts[index] = -1;
|
|
1363
|
+
if (found >= 0) {
|
|
1364
|
+
const tail = tails[found] ?? -1;
|
|
1365
|
+
nexts[tail] = index;
|
|
1366
|
+
prevs[index] = tail;
|
|
1367
|
+
tails[found] = index;
|
|
1368
|
+
counts[found] = (counts[found] ?? 0) + 1;
|
|
1369
|
+
slots[index] = found;
|
|
1370
|
+
} else {
|
|
1371
|
+
const slot = -found - 1;
|
|
1372
|
+
if (heads[slot] === empty) used++;
|
|
1373
|
+
live++;
|
|
1374
|
+
lows[slot] = low;
|
|
1375
|
+
highs[slot] = high;
|
|
1376
|
+
heads[slot] = index;
|
|
1377
|
+
tails[slot] = index;
|
|
1378
|
+
counts[slot] = 1;
|
|
1379
|
+
slots[index] = slot;
|
|
1380
|
+
}
|
|
1381
|
+
touched.push(index);
|
|
1382
|
+
}
|
|
1383
|
+
function write(edges, dirty) {
|
|
1384
|
+
if (touched.length === 0) return;
|
|
1385
|
+
generation = generation + 1 >>> 0;
|
|
1386
|
+
if (generation === 0) {
|
|
1387
|
+
stamps.fill(0);
|
|
1388
|
+
generation = 1;
|
|
1389
|
+
}
|
|
1390
|
+
const bends = edges.bend;
|
|
1391
|
+
for (const edge of touched) {
|
|
1392
|
+
const slot = edge >= 0 ? slots[edge] ?? -1 : -1;
|
|
1393
|
+
if (slot < 0 || stamps[slot] === generation) continue;
|
|
1394
|
+
stamps[slot] = generation;
|
|
1395
|
+
const count = counts[slot] ?? 0;
|
|
1396
|
+
const loop = lows[slot] === highs[slot];
|
|
1397
|
+
if (count === 1 && !loop) {
|
|
1398
|
+
const only = heads[slot] ?? 0;
|
|
1399
|
+
if (bends[only] !== 0) {
|
|
1400
|
+
bends[only] = 0;
|
|
1401
|
+
dirty.add(only);
|
|
1402
|
+
}
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
let position = 0;
|
|
1406
|
+
for (let at = heads[slot] ?? -1; at >= 0; at = nexts[at] ?? -1) {
|
|
1407
|
+
const source = edges.source[at] ?? 0;
|
|
1408
|
+
const target = edges.target[at] ?? 0;
|
|
1409
|
+
const bend = loop ? position : Math.fround(laneBend(position, count) * (source < target ? 1 : -1)) || 0;
|
|
1410
|
+
if (bends[at] !== bend) {
|
|
1411
|
+
bends[at] = bend;
|
|
1412
|
+
dirty.add(at);
|
|
1413
|
+
}
|
|
1414
|
+
position++;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
touched = [];
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
link,
|
|
1421
|
+
unlink,
|
|
1422
|
+
write
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1116
1425
|
// src/core/palette.ts
|
|
1117
1426
|
var Palette = class {
|
|
1118
1427
|
#ids = /* @__PURE__ */ new Map();
|
|
@@ -1186,6 +1495,7 @@ function createScene() {
|
|
|
1186
1495
|
const nodes = new NodeColumns();
|
|
1187
1496
|
const edges = new EdgeColumns();
|
|
1188
1497
|
const palette = new Palette();
|
|
1498
|
+
const lanes = createEdgeLanes();
|
|
1189
1499
|
const slots = /* @__PURE__ */ new Map();
|
|
1190
1500
|
let layers = [];
|
|
1191
1501
|
let layersDirty = false;
|
|
@@ -1273,6 +1583,7 @@ function createScene() {
|
|
|
1273
1583
|
edges.slot[index] = slots.get(record.mark) ?? 0;
|
|
1274
1584
|
edges.source[index] = model.node(record.source)?.index ?? 0;
|
|
1275
1585
|
edges.target[index] = model.node(record.target)?.index ?? 0;
|
|
1586
|
+
lanes.link(index, edges.source[index] ?? 0, edges.target[index] ?? 0);
|
|
1276
1587
|
edges.color[index] = palette.id(color);
|
|
1277
1588
|
edges.width[index] = Number.isNaN(width) ? defaults.edgeWidth : widthScale.map(width, widthDomain);
|
|
1278
1589
|
edges.dash[index] = codeOf(dashCodes, mark.dash?.(datum));
|
|
@@ -1290,6 +1601,7 @@ function createScene() {
|
|
|
1290
1601
|
dirtyNodes.add(index);
|
|
1291
1602
|
}
|
|
1292
1603
|
function removeEdge(index) {
|
|
1604
|
+
lanes.unlink(index);
|
|
1293
1605
|
edges.clear(index);
|
|
1294
1606
|
widthValues[index] = NaN;
|
|
1295
1607
|
edgeColorValues[index] = void 0;
|
|
@@ -1381,6 +1693,7 @@ function createScene() {
|
|
|
1381
1693
|
}
|
|
1382
1694
|
for (const record of nodeRecords.values()) writeNode(record);
|
|
1383
1695
|
for (const record of edgeRecords.values()) writeEdge(model, record);
|
|
1696
|
+
lanes.write(edges, dirtyEdges);
|
|
1384
1697
|
},
|
|
1385
1698
|
takeDirty() {
|
|
1386
1699
|
const dirty = {
|
|
@@ -2187,6 +2500,10 @@ void main() {
|
|
|
2187
2500
|
gl_Position = hidden;
|
|
2188
2501
|
return;
|
|
2189
2502
|
}
|
|
2503
|
+
if (topology.x == topology.y || style.z != 0.0) {
|
|
2504
|
+
gl_Position = hidden;
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2190
2507
|
Look look = resolve(mask, Look(style.y, style.x, 0.0, 1.0, none, topology.z), 32);
|
|
2191
2508
|
ivec2 s = texel(topology.x);
|
|
2192
2509
|
ivec2 e = texel(topology.y);
|
|
@@ -2231,6 +2548,143 @@ void main() {
|
|
|
2231
2548
|
gl_Position = visible > 0.5 ? toClip(ps + dir * along + normal * across) : hidden;
|
|
2232
2549
|
}
|
|
2233
2550
|
`;
|
|
2551
|
+
const segments = 24 .toFixed(1);
|
|
2552
|
+
const curveVertex = `${header}${nodeData}${edgeData}
|
|
2553
|
+
in vec3 corner;
|
|
2554
|
+
in uint instance;
|
|
2555
|
+
out vec2 vPos;
|
|
2556
|
+
out float vStart;
|
|
2557
|
+
out float vEnd;
|
|
2558
|
+
out float vHalfWidth;
|
|
2559
|
+
out float vArrowLength;
|
|
2560
|
+
out float vArrowHalf;
|
|
2561
|
+
out float vHaloWidth;
|
|
2562
|
+
flat out uint vDash;
|
|
2563
|
+
flat out uint vArrow;
|
|
2564
|
+
flat out vec4 vColor;
|
|
2565
|
+
flat out float vOpacity;
|
|
2566
|
+
vec2 cubicAt(vec2 a, vec2 b, vec2 c, vec2 d, float t) {
|
|
2567
|
+
float u = 1.0 - t;
|
|
2568
|
+
return u * u * u * a + 3.0 * u * u * t * b + 3.0 * u * t * t * c + t * t * t * d;
|
|
2569
|
+
}
|
|
2570
|
+
vec2 cubicTangent(vec2 a, vec2 b, vec2 c, vec2 d, float t) {
|
|
2571
|
+
float u = 1.0 - t;
|
|
2572
|
+
return 3.0 * u * u * (b - a) + 6.0 * u * t * (c - b) + 3.0 * t * t * (d - c);
|
|
2573
|
+
}
|
|
2574
|
+
float crossing(vec2 a, vec2 b, vec2 c, vec2 d, vec2 point, float radius, float lo, float hi, bool rising) {
|
|
2575
|
+
for (int i = 0; i < 14; i++) {
|
|
2576
|
+
float mid = (lo + hi) * 0.5;
|
|
2577
|
+
bool inside = distance(cubicAt(a, b, c, d, mid), point) < radius;
|
|
2578
|
+
if (inside == rising) lo = mid; else hi = mid;
|
|
2579
|
+
}
|
|
2580
|
+
return (lo + hi) * 0.5;
|
|
2581
|
+
}
|
|
2582
|
+
float arcLength(vec2 a, vec2 b, vec2 c, vec2 d, float from, float to) {
|
|
2583
|
+
float total = 0.0;
|
|
2584
|
+
vec2 previous = cubicAt(a, b, c, d, from);
|
|
2585
|
+
for (int i = 1; i <= 8; i++) {
|
|
2586
|
+
vec2 next = cubicAt(a, b, c, d, mix(from, to, float(i) / 8.0));
|
|
2587
|
+
total += distance(previous, next);
|
|
2588
|
+
previous = next;
|
|
2589
|
+
}
|
|
2590
|
+
return total;
|
|
2591
|
+
}
|
|
2592
|
+
vec2 turn(vec2 v, float angle) {
|
|
2593
|
+
float c = cos(angle);
|
|
2594
|
+
float s = sin(angle);
|
|
2595
|
+
return vec2(v.x * c - v.y * s, v.x * s + v.y * c);
|
|
2596
|
+
}
|
|
2597
|
+
void main() {
|
|
2598
|
+
ivec2 t = texel(instance);
|
|
2599
|
+
uvec4 topology = texelFetch(edgeTopology, t, 0);
|
|
2600
|
+
vec4 style = texelFetch(edgeStyle, t, 0);
|
|
2601
|
+
uint packed = topology.w;
|
|
2602
|
+
uint mask = texelFetch(edgeState, t, 0).r;
|
|
2603
|
+
bool highlighted = (mask & highlightMask) != 0u;
|
|
2604
|
+
bool loop = topology.x == topology.y;
|
|
2605
|
+
if ((packed & 16u) == 0u || (!loop && style.z == 0.0) || (overlay == 0 && (int(packed >> 5u) != layerSlot || highlighted))) {
|
|
2606
|
+
gl_Position = hidden;
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
Look look = resolve(mask, Look(style.y, style.x, 0.0, 1.0, none, topology.z), 32);
|
|
2610
|
+
ivec2 s = texel(topology.x);
|
|
2611
|
+
ivec2 e = texel(topology.y);
|
|
2612
|
+
vec2 a = toScreen(texelFetch(nodePosition, s, 0).xy);
|
|
2613
|
+
vec2 d = toScreen(texelFetch(nodePosition, e, 0).xy);
|
|
2614
|
+
float sourceRadius = nodeRadius(texelFetch(nodeStyle, s, 0));
|
|
2615
|
+
float targetRadius = nodeRadius(texelFetch(nodeStyle, e, 0));
|
|
2616
|
+
vec2 b;
|
|
2617
|
+
vec2 c;
|
|
2618
|
+
if (loop) {
|
|
2619
|
+
float reach = sourceRadius + max(sourceRadius * 1.5, ${12 .toFixed(1)}) * (1.0 + ${loopGrowth.toFixed(2)} * style.z);
|
|
2620
|
+
float arm = reach / (0.75 * cos(${loopTurn.toFixed(4)}));
|
|
2621
|
+
vec2 u = vec2(0.70710678, -0.70710678);
|
|
2622
|
+
b = a + arm * turn(u, ${loopTurn.toFixed(4)});
|
|
2623
|
+
c = a + arm * turn(u, -${loopTurn.toFixed(4)});
|
|
2624
|
+
} else {
|
|
2625
|
+
vec2 delta = d - a;
|
|
2626
|
+
float len = length(delta);
|
|
2627
|
+
vec2 side = len > 0.0 ? vec2(-delta.y, delta.x) / len : vec2(0.0, 1.0);
|
|
2628
|
+
vec2 control = (a + d) * 0.5 + side * (2.0 * style.z * len);
|
|
2629
|
+
b = a + (control - a) * (2.0 / 3.0);
|
|
2630
|
+
c = d + (control - d) * (2.0 / 3.0);
|
|
2631
|
+
}
|
|
2632
|
+
float t0 = crossing(a, b, c, d, a, sourceRadius, 0.0, 0.5, true);
|
|
2633
|
+
float t1 = crossing(a, b, c, d, d, targetRadius, 0.5, 1.0, false);
|
|
2634
|
+
float visible = arcLength(a, b, c, d, t0, t1);
|
|
2635
|
+
float width = max(look.strokeWidth, 0.5);
|
|
2636
|
+
uint arrow = (packed >> 2u) & 3u;
|
|
2637
|
+
float arrowLength = arrow != 0u && visible > width * 8.0 ? max(width * 4.0, 6.0) : 0.0;
|
|
2638
|
+
float arrowHalf = max(width * 1.6, 3.0);
|
|
2639
|
+
float halo = look.halo > 0.5 ? haloWidth : 0.0;
|
|
2640
|
+
float margin = 0.5 / pixelRatio + 0.5;
|
|
2641
|
+
float startHead = (arrow & 1u) != 0u ? arrowLength : 0.0;
|
|
2642
|
+
float endHead = (arrow & 2u) != 0u ? arrowLength : 0.0;
|
|
2643
|
+
vec2 first = cubicAt(a, b, c, d, t0);
|
|
2644
|
+
vec2 last = cubicAt(a, b, c, d, t1);
|
|
2645
|
+
float ta = startHead > 0.0 ? crossing(a, b, c, d, first, startHead, t0, t1, true) : t0;
|
|
2646
|
+
float tb = endHead > 0.0 ? crossing(a, b, c, d, last, endHead, t0, t1, false) : t1;
|
|
2647
|
+
float body = width * 0.5 + halo + margin;
|
|
2648
|
+
float head = max(body, arrowHalf + halo + margin);
|
|
2649
|
+
int station = int(corner.x + 0.5);
|
|
2650
|
+
float at;
|
|
2651
|
+
float along;
|
|
2652
|
+
float push = 0.0;
|
|
2653
|
+
if (station == 0) {
|
|
2654
|
+
at = t0;
|
|
2655
|
+
along = -margin;
|
|
2656
|
+
push = -margin;
|
|
2657
|
+
} else if (station > int(${segments}) + 1) {
|
|
2658
|
+
at = t1;
|
|
2659
|
+
along = visible + margin;
|
|
2660
|
+
push = margin;
|
|
2661
|
+
} else {
|
|
2662
|
+
float f = float(station - 1) / ${segments};
|
|
2663
|
+
at = mix(ta, tb, f);
|
|
2664
|
+
along = mix(startHead, visible - endHead, f);
|
|
2665
|
+
}
|
|
2666
|
+
vec2 tangent = cubicTangent(a, b, c, d, at);
|
|
2667
|
+
float tangentLength = length(tangent);
|
|
2668
|
+
vec2 dir = tangentLength > 0.0 ? tangent / tangentLength : vec2(1.0, 0.0);
|
|
2669
|
+
vec2 normal = vec2(-dir.y, dir.x);
|
|
2670
|
+
float extent = corner.z < 0.5 ? body
|
|
2671
|
+
: corner.z < 1.5 ? (startHead > 0.0 ? head : body)
|
|
2672
|
+
: (endHead > 0.0 ? head : body);
|
|
2673
|
+
float across = corner.y * extent;
|
|
2674
|
+
vPos = vec2(along, across);
|
|
2675
|
+
vStart = 0.0;
|
|
2676
|
+
vEnd = visible;
|
|
2677
|
+
vHalfWidth = width * 0.5;
|
|
2678
|
+
vArrowLength = arrowLength;
|
|
2679
|
+
vArrowHalf = arrowHalf;
|
|
2680
|
+
vHaloWidth = halo;
|
|
2681
|
+
vDash = packed & 3u;
|
|
2682
|
+
vArrow = arrow;
|
|
2683
|
+
vColor = paletteColor(look.stroke);
|
|
2684
|
+
vOpacity = look.opacity;
|
|
2685
|
+
gl_Position = visible > 0.5 ? toClip(cubicAt(a, b, c, d, at) + dir * push + normal * across) : hidden;
|
|
2686
|
+
}
|
|
2687
|
+
`;
|
|
2234
2688
|
const edgeFragment = `${header}
|
|
2235
2689
|
in vec2 vPos;
|
|
2236
2690
|
in float vStart;
|
|
@@ -2387,6 +2841,27 @@ const cornerEdge = new Float32Array([
|
|
|
2387
2841
|
1,
|
|
2388
2842
|
2
|
|
2389
2843
|
]);
|
|
2844
|
+
function curveCorners() {
|
|
2845
|
+
const values = [
|
|
2846
|
+
0,
|
|
2847
|
+
-1,
|
|
2848
|
+
1,
|
|
2849
|
+
0,
|
|
2850
|
+
1,
|
|
2851
|
+
1,
|
|
2852
|
+
1,
|
|
2853
|
+
-1,
|
|
2854
|
+
1,
|
|
2855
|
+
1,
|
|
2856
|
+
1,
|
|
2857
|
+
1
|
|
2858
|
+
];
|
|
2859
|
+
for (let station = 1; station <= 25; station++) values.push(station, -1, 0, station, 1, 0);
|
|
2860
|
+
const end = 25;
|
|
2861
|
+
values.push(end, -1, 2, end, 1, 2, 26, -1, 2, 26, 1, 2);
|
|
2862
|
+
return new Float32Array(values);
|
|
2863
|
+
}
|
|
2864
|
+
const cornerCurve = curveCorners();
|
|
2390
2865
|
const cornerLabel = new Float32Array([
|
|
2391
2866
|
0,
|
|
2392
2867
|
0,
|
|
@@ -2487,26 +2962,30 @@ function createWebglRenderer(canvas) {
|
|
|
2487
2962
|
edgeProgram,
|
|
2488
2963
|
labelProgram
|
|
2489
2964
|
];
|
|
2490
|
-
|
|
2965
|
+
const locations = /* @__PURE__ */ new Map();
|
|
2966
|
+
function prepare(program) {
|
|
2491
2967
|
gl.useProgram(program);
|
|
2492
2968
|
for (const [name, unit] of Object.entries(units)) {
|
|
2493
2969
|
const location = gl.getUniformLocation(program, name);
|
|
2494
2970
|
if (location) gl.uniform1i(location, unit);
|
|
2495
2971
|
}
|
|
2972
|
+
locations.set(program, {
|
|
2973
|
+
layerSlot: gl.getUniformLocation(program, "layerSlot"),
|
|
2974
|
+
overlay: gl.getUniformLocation(program, "overlay")
|
|
2975
|
+
});
|
|
2496
2976
|
}
|
|
2977
|
+
for (const program of programs) prepare(program);
|
|
2497
2978
|
gl.useProgram(labelProgram);
|
|
2498
2979
|
gl.uniform1f(gl.getUniformLocation(labelProgram, "atlasFontSize"), 24);
|
|
2499
|
-
const locations = new Map(programs.map((program) => [program, {
|
|
2500
|
-
layerSlot: gl.getUniformLocation(program, "layerSlot"),
|
|
2501
|
-
overlay: gl.getUniformLocation(program, "overlay")
|
|
2502
|
-
}]));
|
|
2503
2980
|
const nodeCorner = createBuffer(gl, cornerNode, gl.STATIC_DRAW);
|
|
2504
2981
|
const edgeCorner = createBuffer(gl, cornerEdge, gl.STATIC_DRAW);
|
|
2982
|
+
const curveCorner = createBuffer(gl, cornerCurve, gl.STATIC_DRAW);
|
|
2505
2983
|
const labelCorner = createBuffer(gl, cornerLabel, gl.STATIC_DRAW);
|
|
2506
2984
|
const nodeIndices = createBuffer(gl, /* @__PURE__ */ new Uint32Array(0), gl.STATIC_DRAW);
|
|
2507
2985
|
const edgeIndices = createBuffer(gl, /* @__PURE__ */ new Uint32Array(0), gl.STATIC_DRAW);
|
|
2508
2986
|
const nodeOverlayIndices = createBuffer(gl, /* @__PURE__ */ new Uint32Array(0), gl.DYNAMIC_DRAW);
|
|
2509
2987
|
const edgeOverlayIndices = createBuffer(gl, /* @__PURE__ */ new Uint32Array(0), gl.DYNAMIC_DRAW);
|
|
2988
|
+
const curveIndices = createBuffer(gl, /* @__PURE__ */ new Uint32Array(0), gl.DYNAMIC_DRAW);
|
|
2510
2989
|
const labelMainBuffer = createBuffer(gl, /* @__PURE__ */ new Uint8Array(0), gl.DYNAMIC_DRAW);
|
|
2511
2990
|
const labelHighlightBuffer = createBuffer(gl, /* @__PURE__ */ new Uint8Array(0), gl.DYNAMIC_DRAW);
|
|
2512
2991
|
const warmIndex = createBuffer(gl, /* @__PURE__ */ new Uint32Array(1), gl.STATIC_DRAW);
|
|
@@ -2514,11 +2993,13 @@ function createWebglRenderer(canvas) {
|
|
|
2514
2993
|
const buffers = [
|
|
2515
2994
|
nodeCorner,
|
|
2516
2995
|
edgeCorner,
|
|
2996
|
+
curveCorner,
|
|
2517
2997
|
labelCorner,
|
|
2518
2998
|
nodeIndices,
|
|
2519
2999
|
edgeIndices,
|
|
2520
3000
|
nodeOverlayIndices,
|
|
2521
3001
|
edgeOverlayIndices,
|
|
3002
|
+
curveIndices,
|
|
2522
3003
|
labelMainBuffer,
|
|
2523
3004
|
labelHighlightBuffer,
|
|
2524
3005
|
warmIndex,
|
|
@@ -2595,10 +3076,26 @@ function createWebglRenderer(canvas) {
|
|
|
2595
3076
|
[labelProgram, cornerLabel.length / 2]
|
|
2596
3077
|
]);
|
|
2597
3078
|
let warmed = false;
|
|
3079
|
+
let curvePass = null;
|
|
3080
|
+
function curves() {
|
|
3081
|
+
if (curvePass) return curvePass;
|
|
3082
|
+
const program = createProgram(gl, curveVertex, edgeFragment, blocks);
|
|
3083
|
+
prepare(program);
|
|
3084
|
+
programs.push(program);
|
|
3085
|
+
vertexCounts.set(program, cornerCurve.length / 3);
|
|
3086
|
+
curvePass = {
|
|
3087
|
+
program,
|
|
3088
|
+
main: createVertexArray(gl, program, [corner(curveCorner, 3), instance(curveIndices)]),
|
|
3089
|
+
overlay: createVertexArray(gl, program, [corner(curveCorner, 3), instance(edgeOverlayIndices)])
|
|
3090
|
+
};
|
|
3091
|
+
return curvePass;
|
|
3092
|
+
}
|
|
2598
3093
|
let nodeCapacity = 0;
|
|
2599
3094
|
let edgeCapacity = 0;
|
|
2600
3095
|
let nodeOverlayCount = 0;
|
|
2601
3096
|
let edgeOverlayCount = 0;
|
|
3097
|
+
const curved = /* @__PURE__ */ new Set();
|
|
3098
|
+
let curvedChanged = false;
|
|
2602
3099
|
let input = null;
|
|
2603
3100
|
const atlas = new GlyphAtlas("system-ui, sans-serif");
|
|
2604
3101
|
const labels = new LabelLayer();
|
|
@@ -2629,6 +3126,14 @@ function createWebglRenderer(canvas) {
|
|
|
2629
3126
|
edgeTopology.data[at + 3] = (edges.dash[index] ?? 0) | (edges.arrow[index] ?? 0) << 2 | (edges.alive[index] ?? 0) << 4 | (edges.slot[index] ?? 0) << 5;
|
|
2630
3127
|
edgeStyle.data[at] = edges.width[index] ?? 0;
|
|
2631
3128
|
edgeStyle.data[at + 1] = edges.opacity[index] ?? 1;
|
|
3129
|
+
const bend = edges.bend[index] ?? 0;
|
|
3130
|
+
edgeStyle.data[at + 2] = bend;
|
|
3131
|
+
const isCurved = edges.alive[index] === 1 && (bend !== 0 || edges.source[index] === edges.target[index]);
|
|
3132
|
+
if (isCurved !== curved.has(index)) {
|
|
3133
|
+
if (isCurved) curved.add(index);
|
|
3134
|
+
else curved.delete(index);
|
|
3135
|
+
curvedChanged = true;
|
|
3136
|
+
}
|
|
2632
3137
|
}
|
|
2633
3138
|
function syncNodes(next) {
|
|
2634
3139
|
const nodes = next.scene.nodes;
|
|
@@ -2688,6 +3193,11 @@ function createWebglRenderer(canvas) {
|
|
|
2688
3193
|
}
|
|
2689
3194
|
edgeTopology.upload(first, last);
|
|
2690
3195
|
edgeStyle.upload(first, last);
|
|
3196
|
+
if (curvedChanged) {
|
|
3197
|
+
curvedChanged = false;
|
|
3198
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, curveIndices);
|
|
3199
|
+
gl.bufferData(gl.ARRAY_BUFFER, Uint32Array.from(curved), gl.DYNAMIC_DRAW);
|
|
3200
|
+
}
|
|
2691
3201
|
const states = next.states;
|
|
2692
3202
|
const range = states.edgeDirty.take();
|
|
2693
3203
|
if (next.full || grown) {
|
|
@@ -2849,10 +3359,16 @@ function createWebglRenderer(canvas) {
|
|
|
2849
3359
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
2850
3360
|
bind();
|
|
2851
3361
|
if (!warmed) warm();
|
|
2852
|
-
for (const layer of input?.scene.layers ?? []) if (layer.kind === "edges")
|
|
2853
|
-
|
|
3362
|
+
for (const layer of input?.scene.layers ?? []) if (layer.kind === "edges") {
|
|
3363
|
+
draw(edgeProgram, vertexArrays.edges, edgeCapacity, layer.slot, 0);
|
|
3364
|
+
if (curved.size > 0) {
|
|
3365
|
+
const pass = curves();
|
|
3366
|
+
draw(pass.program, pass.main, curved.size, layer.slot, 0);
|
|
3367
|
+
}
|
|
3368
|
+
} else draw(nodeProgram, vertexArrays.nodes, nodeCapacity, layer.slot, 0);
|
|
2854
3369
|
draw(labelProgram, vertexArrays.labels, labels.main.count, 0, 0);
|
|
2855
3370
|
draw(edgeProgram, vertexArrays.edgeOverlay, edgeOverlayCount, 0, 1);
|
|
3371
|
+
if (curvePass && curved.size > 0) draw(curvePass.program, curvePass.overlay, edgeOverlayCount, 0, 1);
|
|
2856
3372
|
draw(nodeProgram, vertexArrays.nodeOverlay, nodeOverlayCount, 0, 1);
|
|
2857
3373
|
draw(labelProgram, vertexArrays.labelHighlight, labels.highlight.count, 0, 1);
|
|
2858
3374
|
gl.bindVertexArray(null);
|
|
@@ -2868,6 +3384,10 @@ function createWebglRenderer(canvas) {
|
|
|
2868
3384
|
statesBuffer
|
|
2869
3385
|
]) gl.deleteBuffer(buffer);
|
|
2870
3386
|
for (const vao of Object.values(vertexArrays)) gl.deleteVertexArray(vao);
|
|
3387
|
+
if (curvePass) {
|
|
3388
|
+
gl.deleteVertexArray(curvePass.main);
|
|
3389
|
+
gl.deleteVertexArray(curvePass.overlay);
|
|
3390
|
+
}
|
|
2871
3391
|
for (const program of programs) gl.deleteProgram(program);
|
|
2872
3392
|
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
2873
3393
|
}
|
|
@@ -3820,4 +4340,4 @@ function mountGraph(container, options) {
|
|
|
3820
4340
|
}
|
|
3821
4341
|
export { freeStateNames as n, maxFreeStates as r, mountGraph as t };
|
|
3822
4342
|
|
|
3823
|
-
//# sourceMappingURL=mount-graph-
|
|
4343
|
+
//# sourceMappingURL=mount-graph-DYOgD_ql.js.map
|