@seatlayer/core 0.29.0 → 0.30.1
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 +102 -22
- package/dist/chunk-FGS67GT3.js +1252 -0
- package/dist/chunk-FGS67GT3.js.map +1 -0
- package/dist/index.cjs +572 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +269 -909
- package/dist/index.js.map +1 -1
- package/dist/{types-B-tpUFqz.d.cts → types-CG2pEDoI.d.cts} +56 -0
- package/dist/{types-B-tpUFqz.d.ts → types-CG2pEDoI.d.ts} +56 -0
- package/dist/view3d/index.cjs +2315 -99
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +236 -27
- package/dist/view3d/index.d.ts +236 -27
- package/dist/view3d/index.js +1912 -98
- package/dist/view3d/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-5MLADB2N.js +0 -53
- package/dist/chunk-5MLADB2N.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -411,6 +411,7 @@ function transformSectionOutlinePath(path, transform, radiusScale = 1, reflected
|
|
|
411
411
|
})
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
|
+
var SAGITTA_TO_CONTROL = 4 / 3;
|
|
414
415
|
|
|
415
416
|
// src/core/labeling.ts
|
|
416
417
|
var FULL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
@@ -503,6 +504,345 @@ function sectionGeometry(section, context = {}) {
|
|
|
503
504
|
return { height, rake };
|
|
504
505
|
}
|
|
505
506
|
|
|
507
|
+
// src/core/venueStructure.ts
|
|
508
|
+
var BLOCK_LINK_RATIO = 1.8;
|
|
509
|
+
function probesOf(pts) {
|
|
510
|
+
const n = pts.length;
|
|
511
|
+
if (n <= 3) return [...pts];
|
|
512
|
+
return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];
|
|
513
|
+
}
|
|
514
|
+
function distanceToPolyline(pts, x, y) {
|
|
515
|
+
if (!pts.length) return Infinity;
|
|
516
|
+
if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);
|
|
517
|
+
let best = Infinity;
|
|
518
|
+
for (let i = 0; i + 1 < pts.length; i++) {
|
|
519
|
+
const a = pts[i], b = pts[i + 1];
|
|
520
|
+
const vx = b.x - a.x, vy = b.y - a.y;
|
|
521
|
+
const len2 = vx * vx + vy * vy;
|
|
522
|
+
let t2 = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;
|
|
523
|
+
if (t2 < 0) t2 = 0;
|
|
524
|
+
else if (t2 > 1) t2 = 1;
|
|
525
|
+
const d = Math.hypot(x - (a.x + t2 * vx), y - (a.y + t2 * vy));
|
|
526
|
+
if (d < best) best = d;
|
|
527
|
+
}
|
|
528
|
+
return best;
|
|
529
|
+
}
|
|
530
|
+
function boundOf(pts) {
|
|
531
|
+
let cx = 0, cy = 0;
|
|
532
|
+
for (const p of pts) {
|
|
533
|
+
cx += p.x;
|
|
534
|
+
cy += p.y;
|
|
535
|
+
}
|
|
536
|
+
const n = pts.length || 1;
|
|
537
|
+
cx /= n;
|
|
538
|
+
cy /= n;
|
|
539
|
+
let r = 0;
|
|
540
|
+
for (const p of pts) {
|
|
541
|
+
const d = Math.hypot(p.x - cx, p.y - cy);
|
|
542
|
+
if (d > r) r = d;
|
|
543
|
+
}
|
|
544
|
+
return { cx, cy, r };
|
|
545
|
+
}
|
|
546
|
+
function boundGap(a, b) {
|
|
547
|
+
return Math.hypot(a.cx - b.cx, a.cy - b.cy) - a.r - b.r;
|
|
548
|
+
}
|
|
549
|
+
function rowGap(a, b) {
|
|
550
|
+
let best = Infinity;
|
|
551
|
+
for (const p of probesOf(a)) {
|
|
552
|
+
const d = distanceToPolyline(b, p.x, p.y);
|
|
553
|
+
if (d < best) best = d;
|
|
554
|
+
}
|
|
555
|
+
for (const p of probesOf(b)) {
|
|
556
|
+
const d = distanceToPolyline(a, p.x, p.y);
|
|
557
|
+
if (d < best) best = d;
|
|
558
|
+
}
|
|
559
|
+
return best;
|
|
560
|
+
}
|
|
561
|
+
function orderAlongRow(pts, indices) {
|
|
562
|
+
if (pts.length < 3) return { pts, seatIndices: indices };
|
|
563
|
+
let cx = 0, cy = 0;
|
|
564
|
+
for (const p of pts) {
|
|
565
|
+
cx += p.x;
|
|
566
|
+
cy += p.y;
|
|
567
|
+
}
|
|
568
|
+
cx /= pts.length;
|
|
569
|
+
cy /= pts.length;
|
|
570
|
+
let far = -1, ax = pts[0];
|
|
571
|
+
for (const p of pts) {
|
|
572
|
+
const d = Math.hypot(p.x - cx, p.y - cy);
|
|
573
|
+
if (d > far) {
|
|
574
|
+
far = d;
|
|
575
|
+
ax = p;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
let dx = ax.x - cx, dy = ax.y - cy;
|
|
579
|
+
const len = Math.hypot(dx, dy);
|
|
580
|
+
if (len > 1e-9) {
|
|
581
|
+
dx /= len;
|
|
582
|
+
dy /= len;
|
|
583
|
+
} else {
|
|
584
|
+
dx = 1;
|
|
585
|
+
dy = 0;
|
|
586
|
+
}
|
|
587
|
+
const order = pts.map((p, i) => ({ p, i, t: (p.x - cx) * dx + (p.y - cy) * dy })).sort((u, v) => u.t - v.t);
|
|
588
|
+
return { pts: order.map((o) => o.p), seatIndices: order.map((o) => indices[o.i]) };
|
|
589
|
+
}
|
|
590
|
+
function fitCircle(pts) {
|
|
591
|
+
const n = pts.length;
|
|
592
|
+
if (n < 3) return null;
|
|
593
|
+
let mx = 0, my = 0;
|
|
594
|
+
for (const p of pts) {
|
|
595
|
+
mx += p.x;
|
|
596
|
+
my += p.y;
|
|
597
|
+
}
|
|
598
|
+
mx /= n;
|
|
599
|
+
my /= n;
|
|
600
|
+
let suu = 0, suv = 0, svv = 0, suuu = 0, svvv = 0, suvv = 0, svuu = 0;
|
|
601
|
+
for (const p of pts) {
|
|
602
|
+
const u = p.x - mx, v = p.y - my;
|
|
603
|
+
suu += u * u;
|
|
604
|
+
svv += v * v;
|
|
605
|
+
suv += u * v;
|
|
606
|
+
suuu += u * u * u;
|
|
607
|
+
svvv += v * v * v;
|
|
608
|
+
suvv += u * v * v;
|
|
609
|
+
svuu += v * u * u;
|
|
610
|
+
}
|
|
611
|
+
const det = suu * svv - suv * suv;
|
|
612
|
+
if (Math.abs(det) < 1e-9) return null;
|
|
613
|
+
const b1 = (suuu + suvv) / 2;
|
|
614
|
+
const b2 = (svvv + svuu) / 2;
|
|
615
|
+
const uc = (b1 * svv - b2 * suv) / det;
|
|
616
|
+
const vc = (b2 * suu - b1 * suv) / det;
|
|
617
|
+
const cx = uc + mx, cy = vc + my;
|
|
618
|
+
if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;
|
|
619
|
+
return { cx, cy };
|
|
620
|
+
}
|
|
621
|
+
var CIRCLE_ROW_SPREAD_LIMIT = 0.15;
|
|
622
|
+
function fitBlockAxis(group) {
|
|
623
|
+
if (group.length < 2) return null;
|
|
624
|
+
const cents = group.map((r) => {
|
|
625
|
+
let x = 0, y = 0;
|
|
626
|
+
for (const p of r.pts) {
|
|
627
|
+
x += p.x;
|
|
628
|
+
y += p.y;
|
|
629
|
+
}
|
|
630
|
+
return { x: x / r.pts.length, y: y / r.pts.length };
|
|
631
|
+
});
|
|
632
|
+
let ox = 0, oy = 0;
|
|
633
|
+
for (const c of cents) {
|
|
634
|
+
ox += c.x;
|
|
635
|
+
oy += c.y;
|
|
636
|
+
}
|
|
637
|
+
ox /= cents.length;
|
|
638
|
+
oy /= cents.length;
|
|
639
|
+
let sxx = 0, sxy = 0, syy = 0;
|
|
640
|
+
for (const c of cents) {
|
|
641
|
+
const dx = c.x - ox, dy = c.y - oy;
|
|
642
|
+
sxx += dx * dx;
|
|
643
|
+
sxy += dx * dy;
|
|
644
|
+
syy += dy * dy;
|
|
645
|
+
}
|
|
646
|
+
const tr = sxx + syy;
|
|
647
|
+
const det = sxx * syy - sxy * sxy;
|
|
648
|
+
const lambda = tr / 2 + Math.sqrt(Math.max(0, tr * tr / 4 - det));
|
|
649
|
+
let ax, ay;
|
|
650
|
+
if (Math.abs(sxy) > 1e-12) {
|
|
651
|
+
ax = lambda - syy;
|
|
652
|
+
ay = sxy;
|
|
653
|
+
} else if (sxx >= syy) {
|
|
654
|
+
ax = 1;
|
|
655
|
+
ay = 0;
|
|
656
|
+
} else {
|
|
657
|
+
ax = 0;
|
|
658
|
+
ay = 1;
|
|
659
|
+
}
|
|
660
|
+
const len = Math.hypot(ax, ay);
|
|
661
|
+
if (!(len > 1e-12)) return null;
|
|
662
|
+
ax /= len;
|
|
663
|
+
ay /= len;
|
|
664
|
+
let lo = Infinity, hi = -Infinity;
|
|
665
|
+
const widths = [];
|
|
666
|
+
for (const r of group) {
|
|
667
|
+
let rlo = Infinity, rhi = -Infinity;
|
|
668
|
+
for (const p of r.pts) {
|
|
669
|
+
const t2 = p.x * ax + p.y * ay;
|
|
670
|
+
if (t2 < rlo) rlo = t2;
|
|
671
|
+
if (t2 > rhi) rhi = t2;
|
|
672
|
+
if (t2 < lo) lo = t2;
|
|
673
|
+
if (t2 > hi) hi = t2;
|
|
674
|
+
}
|
|
675
|
+
widths.push(rhi - rlo);
|
|
676
|
+
}
|
|
677
|
+
const range = hi - lo;
|
|
678
|
+
if (!(range > 1e-9)) return null;
|
|
679
|
+
widths.sort((a, b) => a - b);
|
|
680
|
+
const p90 = widths[Math.min(widths.length - 1, Math.floor(widths.length * 0.9))];
|
|
681
|
+
return p90 / range <= AXIS_ROW_WIDTH_LIMIT ? { x: ax, y: ay } : null;
|
|
682
|
+
}
|
|
683
|
+
var AXIS_ROW_WIDTH_LIMIT = 0.35;
|
|
684
|
+
function resolveSection(sectionId, seats, seatIndices, focal) {
|
|
685
|
+
const groups = /* @__PURE__ */ new Map();
|
|
686
|
+
for (const i of seatIndices) {
|
|
687
|
+
const s = seats[i];
|
|
688
|
+
const key = s.rowId || `__seat-${i}`;
|
|
689
|
+
let g = groups.get(key);
|
|
690
|
+
if (!g) {
|
|
691
|
+
g = { pts: [], idx: [] };
|
|
692
|
+
groups.set(key, g);
|
|
693
|
+
}
|
|
694
|
+
g.pts.push({ x: s.x, y: s.y });
|
|
695
|
+
g.idx.push(i);
|
|
696
|
+
}
|
|
697
|
+
if (!groups.size) return { sectionId, rows: [], blockCount: 0 };
|
|
698
|
+
const rows = [];
|
|
699
|
+
for (const [id, g] of groups) {
|
|
700
|
+
const ordered = orderAlongRow(g.pts, g.idx);
|
|
701
|
+
let sum = 0;
|
|
702
|
+
for (const p of ordered.pts) sum += Math.hypot(p.x - focal.x, p.y - focal.y);
|
|
703
|
+
rows.push({
|
|
704
|
+
id,
|
|
705
|
+
pts: ordered.pts,
|
|
706
|
+
seatIndices: ordered.seatIndices,
|
|
707
|
+
blockId: -1,
|
|
708
|
+
ordinal: 0,
|
|
709
|
+
blockDepth: 0,
|
|
710
|
+
focalDistance: sum / ordered.pts.length
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const bounds2 = rows.map((r) => boundOf(r.pts));
|
|
714
|
+
const nearest = [];
|
|
715
|
+
for (let i = 0; i < rows.length; i++) {
|
|
716
|
+
let best = Infinity;
|
|
717
|
+
for (let j = 0; j < rows.length; j++) {
|
|
718
|
+
if (i === j) continue;
|
|
719
|
+
if (boundGap(bounds2[i], bounds2[j]) >= best) continue;
|
|
720
|
+
const d = rowGap(rows[i].pts, rows[j].pts);
|
|
721
|
+
if (d < best) best = d;
|
|
722
|
+
}
|
|
723
|
+
if (Number.isFinite(best)) nearest.push(best);
|
|
724
|
+
}
|
|
725
|
+
nearest.sort((a, b) => a - b);
|
|
726
|
+
const typicalGap = nearest.length ? nearest[Math.floor(nearest.length / 2)] : 1;
|
|
727
|
+
const linkDistance = Math.max(typicalGap * BLOCK_LINK_RATIO, 1e-6);
|
|
728
|
+
const adjacency = rows.map(() => []);
|
|
729
|
+
for (let i = 0; i < rows.length; i++) {
|
|
730
|
+
for (let j = i + 1; j < rows.length; j++) {
|
|
731
|
+
if (boundGap(bounds2[i], bounds2[j]) > linkDistance) continue;
|
|
732
|
+
if (rowGap(rows[i].pts, rows[j].pts) <= linkDistance) {
|
|
733
|
+
adjacency[i].push(j);
|
|
734
|
+
adjacency[j].push(i);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
let blockCount = 0;
|
|
739
|
+
for (let i = 0; i < rows.length; i++) {
|
|
740
|
+
if (rows[i].blockId !== -1) continue;
|
|
741
|
+
const id = blockCount++;
|
|
742
|
+
const stack = [i];
|
|
743
|
+
rows[i].blockId = id;
|
|
744
|
+
while (stack.length) {
|
|
745
|
+
const k = stack.pop();
|
|
746
|
+
for (const n of adjacency[k]) {
|
|
747
|
+
if (rows[n].blockId !== -1) continue;
|
|
748
|
+
rows[n].blockId = id;
|
|
749
|
+
stack.push(n);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const byBlock = /* @__PURE__ */ new Map();
|
|
754
|
+
for (const r of rows) {
|
|
755
|
+
const a = byBlock.get(r.blockId) ?? [];
|
|
756
|
+
a.push(r);
|
|
757
|
+
byBlock.set(r.blockId, a);
|
|
758
|
+
}
|
|
759
|
+
const centres = [];
|
|
760
|
+
for (const r of rows) {
|
|
761
|
+
if (r.pts.length < 4) continue;
|
|
762
|
+
const c = fitCircle(r.pts);
|
|
763
|
+
if (!c) continue;
|
|
764
|
+
if (!Number.isFinite(c.cx) || !Number.isFinite(c.cy)) continue;
|
|
765
|
+
centres.push({ x: c.cx, y: c.cy });
|
|
766
|
+
}
|
|
767
|
+
const centre = centres.length >= 2 ? (() => {
|
|
768
|
+
const xs = centres.map((c) => c.x).sort((a, b) => a - b);
|
|
769
|
+
const ys = centres.map((c) => c.y).sort((a, b) => a - b);
|
|
770
|
+
const mid = Math.floor(centres.length / 2);
|
|
771
|
+
return { cx: xs[mid], cy: ys[mid] };
|
|
772
|
+
})() : null;
|
|
773
|
+
if (centre) {
|
|
774
|
+
const radiusOf = (p) => Math.hypot(p.x - centre.cx, p.y - centre.cy);
|
|
775
|
+
let lo = Infinity, hi = -Infinity;
|
|
776
|
+
const spreads = [];
|
|
777
|
+
for (const r of rows) {
|
|
778
|
+
let rlo = Infinity, rhi = -Infinity;
|
|
779
|
+
for (const p of r.pts) {
|
|
780
|
+
const d = radiusOf(p);
|
|
781
|
+
if (d < rlo) rlo = d;
|
|
782
|
+
if (d > rhi) rhi = d;
|
|
783
|
+
}
|
|
784
|
+
spreads.push(rhi - rlo);
|
|
785
|
+
if (rlo < lo) lo = rlo;
|
|
786
|
+
if (rhi > hi) hi = rhi;
|
|
787
|
+
}
|
|
788
|
+
const range = hi - lo;
|
|
789
|
+
spreads.sort((a, b) => a - b);
|
|
790
|
+
const p90 = spreads[Math.min(spreads.length - 1, Math.floor(spreads.length * 0.9))];
|
|
791
|
+
if (range > 1e-9 && p90 / range <= CIRCLE_ROW_SPREAD_LIMIT) {
|
|
792
|
+
const withRadius = rows.map((r) => {
|
|
793
|
+
let sum = 0;
|
|
794
|
+
for (const p of r.pts) sum += radiusOf(p);
|
|
795
|
+
return { row: r, radius: sum / r.pts.length };
|
|
796
|
+
});
|
|
797
|
+
let minR = Infinity;
|
|
798
|
+
for (const w of withRadius) if (w.radius < minR) minR = w.radius;
|
|
799
|
+
withRadius.sort((a, b) => a.radius - b.radius);
|
|
800
|
+
let ordinal = -1, lastRadius = -Infinity;
|
|
801
|
+
const tolerance = range / Math.max(1, withRadius.length) * 0.5;
|
|
802
|
+
const ordered = [];
|
|
803
|
+
for (const w of withRadius) {
|
|
804
|
+
if (w.radius - lastRadius > tolerance) {
|
|
805
|
+
ordinal++;
|
|
806
|
+
lastRadius = w.radius;
|
|
807
|
+
}
|
|
808
|
+
w.row.ordinal = ordinal;
|
|
809
|
+
w.row.blockDepth = w.radius - minR;
|
|
810
|
+
ordered.push(w.row);
|
|
811
|
+
}
|
|
812
|
+
return { sectionId, rows: ordered, blockCount };
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const out = [];
|
|
816
|
+
for (const [, group] of byBlock) {
|
|
817
|
+
const axis = fitBlockAxis(group);
|
|
818
|
+
if (axis) {
|
|
819
|
+
const key = (r) => {
|
|
820
|
+
let sum = 0;
|
|
821
|
+
for (const p of r.pts) sum += p.x * axis.x + p.y * axis.y;
|
|
822
|
+
return sum / r.pts.length;
|
|
823
|
+
};
|
|
824
|
+
group.sort((a, b) => key(a) - key(b));
|
|
825
|
+
let depth = 0;
|
|
826
|
+
for (let i = 0; i < group.length; i++) {
|
|
827
|
+
if (i > 0) depth += rowGap(group[i - 1].pts, group[i].pts);
|
|
828
|
+
group[i].ordinal = i;
|
|
829
|
+
group[i].blockDepth = depth;
|
|
830
|
+
out.push(group[i]);
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
let front = Infinity;
|
|
834
|
+
for (const r of group) if (r.focalDistance < front) front = r.focalDistance;
|
|
835
|
+
group.sort((a, b) => a.focalDistance - b.focalDistance);
|
|
836
|
+
for (let i = 0; i < group.length; i++) {
|
|
837
|
+
group[i].ordinal = i;
|
|
838
|
+
group[i].blockDepth = Math.max(0, group[i].focalDistance - front);
|
|
839
|
+
out.push(group[i]);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return { sectionId, rows: out, blockCount };
|
|
844
|
+
}
|
|
845
|
+
|
|
506
846
|
// src/core/layout.ts
|
|
507
847
|
function overrideAccessibility(o) {
|
|
508
848
|
if (!o) return [];
|
|
@@ -948,7 +1288,7 @@ function stackFloors(doc, spread = 900) {
|
|
|
948
1288
|
});
|
|
949
1289
|
return { ...doc, objects, floors: void 0 };
|
|
950
1290
|
}
|
|
951
|
-
function expandFloorObjects(objects, zones, fallbackFocal) {
|
|
1291
|
+
function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
|
|
952
1292
|
const out = [];
|
|
953
1293
|
const segmented = /* @__PURE__ */ new Map();
|
|
954
1294
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -1018,6 +1358,7 @@ function expandFloorObjects(objects, zones, fallbackFocal) {
|
|
|
1018
1358
|
const resolvedFocal = zone?.focalPoint ?? fallbackFocal;
|
|
1019
1359
|
for (const seat of seats) {
|
|
1020
1360
|
if (inheritedView) seat.viewUrl ??= inheritedView;
|
|
1361
|
+
if (viewFallback) seat.viewUrl ??= viewFallback;
|
|
1021
1362
|
if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;
|
|
1022
1363
|
if (owner?.zone) seat.zoneId = owner.zone;
|
|
1023
1364
|
if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };
|
|
@@ -1031,13 +1372,14 @@ function expandChart(doc, options = {}) {
|
|
|
1031
1372
|
const out2 = [];
|
|
1032
1373
|
for (const floor of doc.floors) {
|
|
1033
1374
|
const floorFocal = floor.focalPoint ?? doc.focalPoint;
|
|
1034
|
-
const
|
|
1375
|
+
const floorView = floor.viewFromSeatUrl ?? doc.viewFromSeatUrl;
|
|
1376
|
+
const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal, floorView);
|
|
1035
1377
|
assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
|
|
1036
1378
|
out2.push(...seats);
|
|
1037
1379
|
}
|
|
1038
1380
|
return out2;
|
|
1039
1381
|
}
|
|
1040
|
-
const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint);
|
|
1382
|
+
const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint, doc.viewFromSeatUrl);
|
|
1041
1383
|
assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
|
|
1042
1384
|
return out;
|
|
1043
1385
|
}
|
|
@@ -1050,17 +1392,28 @@ function assignEyeHeights(objects, focal, floorBaseHeightM, seats) {
|
|
|
1050
1392
|
}
|
|
1051
1393
|
const owner = new Array(seats.length);
|
|
1052
1394
|
const geo = /* @__PURE__ */ new Map();
|
|
1053
|
-
const
|
|
1395
|
+
const seatsBySection = /* @__PURE__ */ new Map();
|
|
1054
1396
|
for (let i = 0; i < seats.length; i++) {
|
|
1055
1397
|
const seat = seats[i];
|
|
1056
1398
|
const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;
|
|
1057
1399
|
owner[i] = sec;
|
|
1058
1400
|
if (!sec) continue;
|
|
1059
1401
|
if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1402
|
+
const list = seatsBySection.get(sec.id);
|
|
1403
|
+
if (list) list.push(i);
|
|
1404
|
+
else seatsBySection.set(sec.id, [i]);
|
|
1405
|
+
}
|
|
1406
|
+
const seatLevel = new Array(seats.length).fill(void 0);
|
|
1407
|
+
for (const [secId, indices] of seatsBySection) {
|
|
1408
|
+
const g = geo.get(secId);
|
|
1409
|
+
if (!g) continue;
|
|
1410
|
+
const rakeTan = g.rake > 0 ? Math.tan(g.rake * Math.PI / 180) : 0;
|
|
1411
|
+
const structure = resolveSection(secId, seats, indices, focal);
|
|
1412
|
+
for (const row of structure.rows) {
|
|
1413
|
+
const riseM = row.blockDepth * METRES_PER_CHART_UNIT * rakeTan;
|
|
1414
|
+
const level = g.height + riseM;
|
|
1415
|
+
for (const si of row.seatIndices) seatLevel[si] = level;
|
|
1416
|
+
}
|
|
1064
1417
|
}
|
|
1065
1418
|
for (let i = 0; i < seats.length; i++) {
|
|
1066
1419
|
const seat = seats[i];
|
|
@@ -1070,14 +1423,7 @@ function assignEyeHeights(objects, focal, floorBaseHeightM, seats) {
|
|
|
1070
1423
|
continue;
|
|
1071
1424
|
}
|
|
1072
1425
|
const g = geo.get(sec.id);
|
|
1073
|
-
|
|
1074
|
-
if (g.rake > 0) {
|
|
1075
|
-
const seatFocal = seat.focalPoint ?? focal;
|
|
1076
|
-
const d = Math.hypot(seat.x - seatFocal.x, seat.y - seatFocal.y);
|
|
1077
|
-
const depthU = Math.max(0, d - (frontDistU.get(sec.id) ?? d));
|
|
1078
|
-
riseM = depthU * METRES_PER_CHART_UNIT * Math.tan(g.rake * Math.PI / 180);
|
|
1079
|
-
}
|
|
1080
|
-
seat.eyeHeightM = g.height + riseM + SEATED_EYE_HEIGHT_M;
|
|
1426
|
+
seat.eyeHeightM = (seatLevel[i] ?? g.height) + SEATED_EYE_HEIGHT_M;
|
|
1081
1427
|
}
|
|
1082
1428
|
}
|
|
1083
1429
|
var PAD = 40;
|
|
@@ -9030,24 +9376,44 @@ function drawSeated(ctx, hx, hy, r, o) {
|
|
|
9030
9376
|
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
9031
9377
|
ctx.save();
|
|
9032
9378
|
ctx.translate(hx, hy);
|
|
9379
|
+
const leanPx = o.lean * r;
|
|
9033
9380
|
ctx.fillStyle = clo;
|
|
9034
9381
|
ctx.beginPath();
|
|
9035
9382
|
ctx.moveTo(-neckHalf, neckTopY);
|
|
9036
|
-
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - r * 0.6, -shHalf, shoulderY);
|
|
9383
|
+
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - leanPx - r * 0.6, -shHalf, shoulderY - leanPx);
|
|
9037
9384
|
ctx.quadraticCurveTo(-shHalf * 1.05, shoulderY + r * 1.2, -shHalf * 0.94, botY);
|
|
9038
9385
|
ctx.lineTo(shHalf * 0.94, botY);
|
|
9039
|
-
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY);
|
|
9040
|
-
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY - r * 0.6, neckHalf, neckTopY);
|
|
9386
|
+
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY + leanPx);
|
|
9387
|
+
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY + leanPx - r * 0.6, neckHalf, neckTopY);
|
|
9041
9388
|
ctx.closePath();
|
|
9042
9389
|
ctx.fill();
|
|
9043
9390
|
ctx.fillStyle = skin;
|
|
9044
9391
|
ctx.fillRect(-neckHalf * 0.82, r * 0.5, neckHalf * 1.64, r * 0.9);
|
|
9045
9392
|
ctx.save();
|
|
9046
9393
|
ctx.rotate(o.tilt);
|
|
9394
|
+
ctx.translate(o.turn * r, 0);
|
|
9047
9395
|
ctx.fillStyle = skin;
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9396
|
+
if (r >= 18) {
|
|
9397
|
+
ctx.beginPath();
|
|
9398
|
+
ctx.moveTo(-r * 0.9, -r * 0.15);
|
|
9399
|
+
ctx.quadraticCurveTo(-r * 0.96, -r * 1.12, 0, -r * 1.06);
|
|
9400
|
+
ctx.quadraticCurveTo(r * 0.96, -r * 1.12, r * 0.9, -r * 0.15);
|
|
9401
|
+
ctx.quadraticCurveTo(r * 0.86, r * 0.55, r * 0.36, r * 0.98);
|
|
9402
|
+
ctx.quadraticCurveTo(0, r * 1.14, -r * 0.36, r * 0.98);
|
|
9403
|
+
ctx.quadraticCurveTo(-r * 0.86, r * 0.55, -r * 0.9, -r * 0.15);
|
|
9404
|
+
ctx.closePath();
|
|
9405
|
+
ctx.fill();
|
|
9406
|
+
if (r >= 22) {
|
|
9407
|
+
ctx.beginPath();
|
|
9408
|
+
ctx.ellipse(-r * 0.9, r * 0.18, r * 0.13, r * 0.22, 0, 0, TAU2);
|
|
9409
|
+
ctx.ellipse(r * 0.9, r * 0.18, r * 0.13, r * 0.22, 0, 0, TAU2);
|
|
9410
|
+
ctx.fill();
|
|
9411
|
+
}
|
|
9412
|
+
} else {
|
|
9413
|
+
ctx.beginPath();
|
|
9414
|
+
ctx.ellipse(0, 0, r * 0.9, r * 1.06, 0, 0, TAU2);
|
|
9415
|
+
ctx.fill();
|
|
9416
|
+
}
|
|
9051
9417
|
drawHair(ctx, r, o.hair);
|
|
9052
9418
|
if (o.rim > 0.01) {
|
|
9053
9419
|
ctx.strokeStyle = `rgba(245,205,150,${(0.5 * o.rim).toFixed(3)})`;
|
|
@@ -9071,15 +9437,17 @@ function drawSeated(ctx, hx, hy, r, o) {
|
|
|
9071
9437
|
function drawSeatedMid(ctx, hx, hy, r, clothing, tone, alpha) {
|
|
9072
9438
|
ctx.fillStyle = `rgba(${clothing[0]},${clothing[1]},${clothing[2]},${alpha})`;
|
|
9073
9439
|
ctx.beginPath();
|
|
9074
|
-
ctx.moveTo(hx - r * 1.
|
|
9075
|
-
ctx.quadraticCurveTo(hx - r * 1.
|
|
9076
|
-
ctx.
|
|
9077
|
-
ctx.
|
|
9440
|
+
ctx.moveTo(hx - r * 1.72, hy + r * 4);
|
|
9441
|
+
ctx.quadraticCurveTo(hx - r * 1.78, hy + r * 1.55, hx - r * 0.46, hy + r * 1.02);
|
|
9442
|
+
ctx.lineTo(hx - r * 0.4, hy + r * 0.62);
|
|
9443
|
+
ctx.lineTo(hx + r * 0.4, hy + r * 0.62);
|
|
9444
|
+
ctx.lineTo(hx + r * 0.46, hy + r * 1.02);
|
|
9445
|
+
ctx.quadraticCurveTo(hx + r * 1.78, hy + r * 1.55, hx + r * 1.72, hy + r * 4);
|
|
9078
9446
|
ctx.closePath();
|
|
9079
9447
|
ctx.fill();
|
|
9080
9448
|
ctx.fillStyle = `rgba(${tone[0]},${tone[1]},${tone[2]},${alpha})`;
|
|
9081
9449
|
ctx.beginPath();
|
|
9082
|
-
ctx.ellipse(hx, hy, r * 0.
|
|
9450
|
+
ctx.ellipse(hx, hy, r * 0.9, r * 1.05, 0, 0, TAU2);
|
|
9083
9451
|
ctx.fill();
|
|
9084
9452
|
}
|
|
9085
9453
|
function drawPerformer(ctx, x, footY, h, o) {
|
|
@@ -9421,15 +9789,29 @@ function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
|
9421
9789
|
riser.addColorStop(1, "rgba(0,0,0,0)");
|
|
9422
9790
|
ctx.fillStyle = riser;
|
|
9423
9791
|
ctx.fillRect(cx - halfW, topCy, halfW * 2, yBase - topCy);
|
|
9792
|
+
const FORMATIONS = [
|
|
9793
|
+
// frontman + guitarist + one at the back
|
|
9794
|
+
[{ u: -0.24, v: 0.4, lead: true, pose: 3 }, { u: 0.34, v: -0.04, pose: 2 }, { u: -0.52, v: -0.38, pose: 0 }],
|
|
9795
|
+
// duo — singer forward, instrument behind the shoulder
|
|
9796
|
+
[{ u: 0.2, v: 0.34, lead: true, pose: 3 }, { u: -0.3, v: -0.08, pose: 2 }],
|
|
9797
|
+
// 4-piece — lead just off-centre, band staggered around the far half
|
|
9798
|
+
[{ u: -0.1, v: 0.28, lead: true, pose: 1 }, { u: 0.44, v: -0.2, pose: 2 }, { u: -0.46, v: -0.14, pose: 2 }, { u: 0.08, v: -0.46, pose: 0 }]
|
|
9799
|
+
];
|
|
9800
|
+
const form = FORMATIONS[Math.floor(rand() * FORMATIONS.length)];
|
|
9801
|
+
const mirror = rand() < 0.5 ? -1 : 1;
|
|
9802
|
+
const spotX = (p) => cx + p.u * mirror * halfW * 0.84;
|
|
9803
|
+
const spotY = (p) => topCy + p.v * topRy * 0.8;
|
|
9804
|
+
const lead = form.find((p) => p.lead) ?? form[0];
|
|
9805
|
+
const leadX = spotX(lead);
|
|
9424
9806
|
const deckTop = ctx.createLinearGradient(0, topCy - topRy, 0, topCy + topRy);
|
|
9425
|
-
deckTop.addColorStop(0, "#
|
|
9426
|
-
deckTop.addColorStop(1, "#
|
|
9807
|
+
deckTop.addColorStop(0, "#3d4a72");
|
|
9808
|
+
deckTop.addColorStop(1, "#232b47");
|
|
9427
9809
|
ctx.fillStyle = deckTop;
|
|
9428
9810
|
ctx.beginPath();
|
|
9429
9811
|
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9430
9812
|
ctx.fill();
|
|
9431
|
-
const deckGlow = ctx.createRadialGradient(
|
|
9432
|
-
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.
|
|
9813
|
+
const deckGlow = ctx.createRadialGradient(leadX, topCy, 2, leadX, topCy, halfW);
|
|
9814
|
+
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9433
9815
|
deckGlow.addColorStop(1, "rgba(255,214,160,0)");
|
|
9434
9816
|
ctx.save();
|
|
9435
9817
|
ctx.globalCompositeOperation = "lighter";
|
|
@@ -9443,18 +9825,37 @@ function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
|
9443
9825
|
ctx.beginPath();
|
|
9444
9826
|
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9445
9827
|
ctx.stroke();
|
|
9446
|
-
const perfCount = 2 + Math.floor(rand() * 3);
|
|
9447
9828
|
const perfH = Math.max(24, (yBase - yFarTop) * 1.3);
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
const
|
|
9451
|
-
const
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9829
|
+
const gearN = form.length >= 3 ? 3 : 2;
|
|
9830
|
+
for (let i = 0; i < gearN; i++) {
|
|
9831
|
+
const gu = (-0.62 + i * 0.52 + (rand() - 0.5) * 0.12) * -mirror;
|
|
9832
|
+
const gx = cx + gu * halfW * 0.7;
|
|
9833
|
+
const gy = topCy - topRy * (0.45 + rand() * 0.18);
|
|
9834
|
+
const gw = halfW * (0.07 + rand() * 0.04);
|
|
9835
|
+
const gh = perfH * (0.26 + rand() * 0.12);
|
|
9836
|
+
ctx.fillStyle = "#0a0e18";
|
|
9837
|
+
ctx.fillRect(gx - gw / 2, gy - gh, gw, gh);
|
|
9838
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.3)`;
|
|
9839
|
+
ctx.fillRect(gx - gw / 2, gy - gh, gw, 1.5);
|
|
9840
|
+
}
|
|
9841
|
+
if (form.length >= 3) {
|
|
9842
|
+
const drumX = cx - lead.u * mirror * halfW * 0.4;
|
|
9843
|
+
ctx.fillStyle = "#0b0f1a";
|
|
9844
|
+
ctx.beginPath();
|
|
9845
|
+
ctx.ellipse(drumX, topCy - topRy * 0.34, halfW * 0.08, topRy * 0.1, 0, 0, TAU2);
|
|
9846
|
+
ctx.fill();
|
|
9847
|
+
ctx.strokeStyle = `rgba(${rigTintStr},0.35)`;
|
|
9848
|
+
ctx.lineWidth = 1;
|
|
9849
|
+
ctx.stroke();
|
|
9850
|
+
}
|
|
9851
|
+
for (const p of [...form].sort((a, b) => a.v - b.v)) {
|
|
9852
|
+
const depthScale = 0.82 + (p.v + 1) * 0.14;
|
|
9853
|
+
drawPerformer(ctx, spotX(p), spotY(p), perfH * depthScale * (p.lead ? 1.06 : 0.92), {
|
|
9854
|
+
tone: [18, 21, 34],
|
|
9855
|
+
alpha: 0.95,
|
|
9856
|
+
pose: p.pose,
|
|
9857
|
+
rim: hslToRgb(40, 0.72, 0.8),
|
|
9858
|
+
rimStrength: p.lead ? 1.4 : 0.9
|
|
9458
9859
|
});
|
|
9459
9860
|
}
|
|
9460
9861
|
const trussY = pitchToY(Math.min(60, farTopPitch + 34));
|
|
@@ -9471,10 +9872,55 @@ function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
|
9471
9872
|
const fxY = trussY + Math.sin(a) * trussRy;
|
|
9472
9873
|
if (Math.sin(a) > -0.2) {
|
|
9473
9874
|
const tx = cx + (rand() - 0.5) * halfW * 0.8;
|
|
9474
|
-
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.
|
|
9875
|
+
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.2 * (0.7 + nearGlow * 0.4));
|
|
9475
9876
|
}
|
|
9476
|
-
drawFixture(ctx, fxX, fxY,
|
|
9877
|
+
drawFixture(ctx, fxX, fxY, 3, rigTintStr);
|
|
9477
9878
|
}
|
|
9879
|
+
const scoreY = pitchToY(Math.min(52, farTopPitch + 24));
|
|
9880
|
+
const scoreW = Math.max(60, halfW * 0.4);
|
|
9881
|
+
const scoreH = Math.max(24, scoreW * 0.34);
|
|
9882
|
+
ctx.strokeStyle = "rgba(70,78,102,0.6)";
|
|
9883
|
+
ctx.lineWidth = 1.5;
|
|
9884
|
+
ctx.beginPath();
|
|
9885
|
+
ctx.moveTo(cx - scoreW * 0.3, scoreY - scoreH / 2);
|
|
9886
|
+
ctx.lineTo(cx - scoreW * 0.16, scoreY - scoreH * 2.2);
|
|
9887
|
+
ctx.moveTo(cx + scoreW * 0.3, scoreY - scoreH / 2);
|
|
9888
|
+
ctx.lineTo(cx + scoreW * 0.16, scoreY - scoreH * 2.2);
|
|
9889
|
+
ctx.stroke();
|
|
9890
|
+
ctx.fillStyle = "#0d1220";
|
|
9891
|
+
ctx.fillRect(cx - scoreW / 2, scoreY - scoreH / 2, scoreW, scoreH);
|
|
9892
|
+
const screen = ctx.createLinearGradient(0, scoreY - scoreH * 0.3, 0, scoreY + scoreH * 0.42);
|
|
9893
|
+
screen.addColorStop(0, `rgba(${rigTintStr},0.5)`);
|
|
9894
|
+
screen.addColorStop(1, "rgba(255,214,160,0.32)");
|
|
9895
|
+
ctx.fillStyle = screen;
|
|
9896
|
+
ctx.fillRect(cx - scoreW * 0.42, scoreY - scoreH * 0.3, scoreW * 0.84, scoreH * 0.72);
|
|
9897
|
+
ctx.fillStyle = "rgba(255,214,160,0.55)";
|
|
9898
|
+
ctx.fillRect(cx - scoreW / 2, scoreY + scoreH / 2 - 2, scoreW, 2);
|
|
9899
|
+
const scoreGlow = ctx.createRadialGradient(cx, scoreY, 4, cx, scoreY, scoreW * 1.1);
|
|
9900
|
+
scoreGlow.addColorStop(0, `rgba(${rigTintStr},0.2)`);
|
|
9901
|
+
scoreGlow.addColorStop(1, "rgba(0,0,0,0)");
|
|
9902
|
+
ctx.save();
|
|
9903
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9904
|
+
ctx.fillStyle = scoreGlow;
|
|
9905
|
+
ctx.fillRect(cx - scoreW * 1.1, scoreY - scoreW * 0.8, scoreW * 2.2, scoreW * 1.6);
|
|
9906
|
+
const airGlow = ctx.createRadialGradient(cx, (trussY + topCy) / 2, 6, cx, (trussY + topCy) / 2, halfW * 1.5);
|
|
9907
|
+
airGlow.addColorStop(0, `rgba(${rigTintStr},${(0.1 * (0.6 + nearGlow * 0.5)).toFixed(3)})`);
|
|
9908
|
+
airGlow.addColorStop(1, "rgba(0,0,0,0)");
|
|
9909
|
+
ctx.fillStyle = airGlow;
|
|
9910
|
+
ctx.fillRect(cx - halfW * 1.5, trussY - 40, halfW * 3, topCy - trussY + 80);
|
|
9911
|
+
ctx.restore();
|
|
9912
|
+
const keyX = cx + Math.sign(leadX - cx || 1) * halfW * 0.5;
|
|
9913
|
+
drawBeam(ctx, keyX, trussY - trussRy * 0.4, leadX, spotY(lead), 2.5, halfW * 0.16, "255,224,178", 0.42 * (0.7 + nearGlow * 0.4));
|
|
9914
|
+
const pool2 = ctx.createRadialGradient(leadX, spotY(lead), 1, leadX, spotY(lead), halfW * 0.2);
|
|
9915
|
+
pool2.addColorStop(0, `rgba(255,228,185,${(0.4 * nearGlow).toFixed(3)})`);
|
|
9916
|
+
pool2.addColorStop(1, "rgba(255,228,185,0)");
|
|
9917
|
+
ctx.save();
|
|
9918
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9919
|
+
ctx.fillStyle = pool2;
|
|
9920
|
+
ctx.beginPath();
|
|
9921
|
+
ctx.ellipse(leadX, spotY(lead), halfW * 0.2, topRy * 0.24, 0, 0, TAU2);
|
|
9922
|
+
ctx.fill();
|
|
9923
|
+
ctx.restore();
|
|
9478
9924
|
}
|
|
9479
9925
|
function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr, rand) {
|
|
9480
9926
|
const cx = W / 2;
|
|
@@ -9582,7 +10028,7 @@ function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM,
|
|
|
9582
10028
|
const oy = other.y - seat.y;
|
|
9583
10029
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
9584
10030
|
if (own && other.sectionId === own && d < 3 && ox * dx + oy * dy > 0) seatAhead = true;
|
|
9585
|
-
if (d < 0.3 || d >
|
|
10031
|
+
if (d < 0.3 || d > 32) continue;
|
|
9586
10032
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
9587
10033
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
9588
10034
|
const rise = hasRelief ? (other.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - eyeM : 0;
|
|
@@ -9592,40 +10038,91 @@ function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM,
|
|
|
9592
10038
|
for (let k = 0; k < other.id.length; k++) hh = hh * 31 + other.id.charCodeAt(k) & 65535;
|
|
9593
10039
|
const jitter = 0.9 + (hh & 15) / 40;
|
|
9594
10040
|
const r = Math.min(78, Math.atan2(0.16, d) * 180 / Math.PI * PX_PER_DEG * jitter);
|
|
9595
|
-
if (r < 2) continue;
|
|
10041
|
+
if (r < 1.2) continue;
|
|
9596
10042
|
figs.push({ hx: yawToX(yaw), hy: pitchToY(headPitch), r, d, hash: hh });
|
|
9597
10043
|
}
|
|
9598
10044
|
figs.sort((a, b) => b.d - a.d);
|
|
10045
|
+
const figRows = [];
|
|
10046
|
+
{
|
|
10047
|
+
const byY = [...figs].sort((a, b) => a.hy - b.hy);
|
|
10048
|
+
let cur = [];
|
|
10049
|
+
const flush = () => {
|
|
10050
|
+
if (!cur.length) return;
|
|
10051
|
+
const d = cur.reduce((s, f) => s + f.d, 0) / cur.length;
|
|
10052
|
+
const hy = cur.reduce((s, f) => s + f.hy, 0) / cur.length;
|
|
10053
|
+
const r = cur.reduce((s, f) => s + f.r, 0) / cur.length;
|
|
10054
|
+
figRows.push({ figs: cur.sort((a, b) => b.d - a.d), d, hy, r });
|
|
10055
|
+
cur = [];
|
|
10056
|
+
};
|
|
10057
|
+
for (const f of byY) {
|
|
10058
|
+
const last = cur[cur.length - 1];
|
|
10059
|
+
if (last && Math.abs(f.hy - last.hy) > Math.max(5, (last.r + f.r) / 2 * 1.7)) flush();
|
|
10060
|
+
cur.push(f);
|
|
10061
|
+
}
|
|
10062
|
+
flush();
|
|
10063
|
+
}
|
|
10064
|
+
figRows.sort((a, b) => b.d - a.d);
|
|
9599
10065
|
const CLOTHES = [[22, 26, 40], [28, 27, 42], [24, 31, 46], [31, 30, 47], [20, 24, 36]];
|
|
9600
|
-
for (const
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
10066
|
+
for (const row of figRows) {
|
|
10067
|
+
if (row.figs.length >= 3 && row.r >= 1.6 && row.r < 26) {
|
|
10068
|
+
const runs = [];
|
|
10069
|
+
const byX = [...row.figs].sort((a, b) => a.hx - b.hx);
|
|
10070
|
+
let run = [];
|
|
10071
|
+
for (const f of byX) {
|
|
10072
|
+
const last = run[run.length - 1];
|
|
10073
|
+
if (last && f.hx - last.hx > Math.max(14, f.r * 7)) {
|
|
10074
|
+
if (run.length >= 3) runs.push(run);
|
|
10075
|
+
run = [];
|
|
10076
|
+
}
|
|
10077
|
+
run.push(f);
|
|
10078
|
+
}
|
|
10079
|
+
if (run.length >= 3) runs.push(run);
|
|
10080
|
+
const stripAlpha = Math.min(0.42, 0.5 * (1 - row.d / 42));
|
|
10081
|
+
if (stripAlpha > 0.05) {
|
|
10082
|
+
ctx.fillStyle = `rgba(15, 19, 31, ${stripAlpha.toFixed(3)})`;
|
|
10083
|
+
for (const seg of runs) {
|
|
10084
|
+
const x0 = seg[0].hx - row.r * 1.6;
|
|
10085
|
+
const x1 = seg[seg.length - 1].hx + row.r * 1.6;
|
|
10086
|
+
ctx.beginPath();
|
|
10087
|
+
ctx.roundRect(x0, row.hy + row.r * 0.8, x1 - x0, row.r * 2.4, row.r * 0.8);
|
|
10088
|
+
ctx.fill();
|
|
10089
|
+
}
|
|
10090
|
+
}
|
|
10091
|
+
}
|
|
10092
|
+
for (const f of row.figs) {
|
|
10093
|
+
const alpha = f.d < 12 ? 0.95 : f.d < 17 ? Math.max(0.66, 0.95 - (f.d - 12) / 18) : Math.max(0.3, 0.68 - (f.d - 17) / 34);
|
|
10094
|
+
const lift = Math.round((1 - Math.min(1, f.d / 16)) * 10);
|
|
10095
|
+
const headTone = [14 + (f.hash >> 4 & 6) + lift, 17 + (f.hash >> 4 & 6) + lift, 25 + (f.hash >> 4 & 6) + lift];
|
|
10096
|
+
const clo0 = CLOTHES[f.hash % CLOTHES.length];
|
|
10097
|
+
const clothing = [clo0[0] + lift, clo0[1] + lift, clo0[2] + lift];
|
|
10098
|
+
if (f.r >= 13) {
|
|
10099
|
+
const rim = f.d < 9 ? 0.16 * (1 - f.d / 9) + (scene.mode === "proscenium" ? 0.06 : 0) : 0;
|
|
10100
|
+
drawSeated(ctx, f.hx, f.hy, f.r, {
|
|
10101
|
+
tone: headTone,
|
|
10102
|
+
clothing,
|
|
10103
|
+
alpha,
|
|
10104
|
+
tilt: ((f.hash >> 2 & 7) - 3.5) * 0.018,
|
|
10105
|
+
// ±~7°
|
|
10106
|
+
shoulderK: 1.72 + (f.hash >> 5 & 7) / 22,
|
|
10107
|
+
// ~1.72..2.04
|
|
10108
|
+
hair: f.hash % HAIR_KINDS,
|
|
10109
|
+
rim,
|
|
10110
|
+
turn: ((f.hash >> 8 & 7) - 3.5) * 0.034,
|
|
10111
|
+
// ±~0.12 head-radii sideways
|
|
10112
|
+
lean: ((f.hash >> 10 & 3) - 1.5) * 0.09
|
|
10113
|
+
// one shoulder rides higher
|
|
10114
|
+
});
|
|
10115
|
+
} else if (f.r >= 5.5) {
|
|
10116
|
+
drawSeatedMid(ctx, f.hx, f.hy, f.r, clothing, headTone, alpha);
|
|
10117
|
+
} else {
|
|
10118
|
+
ctx.fillStyle = `rgba(${headTone[0]},${headTone[1]},${headTone[2]},${alpha.toFixed(3)})`;
|
|
10119
|
+
ctx.beginPath();
|
|
10120
|
+
ctx.arc(f.hx, f.hy, f.r, 0, TAU2);
|
|
10121
|
+
ctx.fill();
|
|
10122
|
+
ctx.beginPath();
|
|
10123
|
+
ctx.ellipse(f.hx, f.hy + f.r * 1.4, f.r * 1.85, f.r * 0.95, 0, Math.PI, 0, true);
|
|
10124
|
+
ctx.fill();
|
|
10125
|
+
}
|
|
9629
10126
|
}
|
|
9630
10127
|
}
|
|
9631
10128
|
if (seatAhead) {
|