@seatlayer/core 0.28.4 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5MLADB2N.js +53 -0
- package/dist/chunk-5MLADB2N.js.map +1 -0
- package/dist/index.cjs +904 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -1459
- package/dist/index.d.ts +36 -1459
- package/dist/index.js +913 -154
- package/dist/index.js.map +1 -1
- package/dist/types-B-tpUFqz.d.cts +1552 -0
- package/dist/types-B-tpUFqz.d.ts +1552 -0
- package/dist/view3d/index.cjs +1972 -0
- package/dist/view3d/index.cjs.map +1 -0
- package/dist/view3d/index.d.cts +176 -0
- package/dist/view3d/index.d.ts +176 -0
- package/dist/view3d/index.js +1906 -0
- package/dist/view3d/index.js.map +1 -0
- package/package.json +22 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHART_UNITS_PER_METRE,
|
|
3
|
+
METRES_PER_CHART_UNIT,
|
|
4
|
+
SEATED_EYE_HEIGHT_M,
|
|
5
|
+
TIER_HEIGHT_M,
|
|
6
|
+
sectionElevationTier,
|
|
7
|
+
sectionGeometry
|
|
8
|
+
} from "./chunk-5MLADB2N.js";
|
|
9
|
+
|
|
1
10
|
// src/core/types.ts
|
|
2
11
|
var ACCESSIBILITY_TYPES = [
|
|
3
12
|
{ key: "wheelchair", label: "Wheelchair space", short: "Wheelchair", icon: "\u267F" },
|
|
@@ -180,50 +189,6 @@ function toRoman(value) {
|
|
|
180
189
|
return out;
|
|
181
190
|
}
|
|
182
191
|
|
|
183
|
-
// src/core/units.ts
|
|
184
|
-
var METRES_PER_CHART_UNIT = 0.55 / 24;
|
|
185
|
-
var CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;
|
|
186
|
-
var LIFT_PER_STEP_WORLD = 58;
|
|
187
|
-
var TIER_HEIGHT_M = LIFT_PER_STEP_WORLD * METRES_PER_CHART_UNIT;
|
|
188
|
-
var SECTION_ELEVATION_TIER_MIN = 0;
|
|
189
|
-
var SECTION_ELEVATION_TIER_MAX = 3;
|
|
190
|
-
var SECTION_HEIGHT_MIN_M = 0;
|
|
191
|
-
var SECTION_HEIGHT_MAX_M = 120;
|
|
192
|
-
var SECTION_RAKE_MIN_DEG = 0;
|
|
193
|
-
var SECTION_RAKE_MAX_DEG = 45;
|
|
194
|
-
var LEGACY_SECTION_ELEVATION_TIER_MAX = 7;
|
|
195
|
-
var SEATED_EYE_HEIGHT_M = 1.2;
|
|
196
|
-
function finiteClamped(value, min, max, fallback) {
|
|
197
|
-
return Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback;
|
|
198
|
-
}
|
|
199
|
-
function sectionElevationTier(value) {
|
|
200
|
-
if (!Number.isFinite(value)) return SECTION_ELEVATION_TIER_MIN;
|
|
201
|
-
return Math.max(
|
|
202
|
-
SECTION_ELEVATION_TIER_MIN,
|
|
203
|
-
Math.min(SECTION_ELEVATION_TIER_MAX, Math.round(value))
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
function compatibleAutomaticTier(value) {
|
|
207
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) return 0;
|
|
208
|
-
if (value <= LEGACY_SECTION_ELEVATION_TIER_MAX) return value;
|
|
209
|
-
return SECTION_ELEVATION_TIER_MAX;
|
|
210
|
-
}
|
|
211
|
-
function sectionGeometry(section, context = {}) {
|
|
212
|
-
const floorBaseHeightM = finiteClamped(
|
|
213
|
-
context.floorBaseHeightM,
|
|
214
|
-
SECTION_HEIGHT_MIN_M,
|
|
215
|
-
SECTION_HEIGHT_MAX_M,
|
|
216
|
-
0
|
|
217
|
-
);
|
|
218
|
-
const automaticHeight = Math.min(
|
|
219
|
-
SECTION_HEIGHT_MAX_M,
|
|
220
|
-
floorBaseHeightM + compatibleAutomaticTier(section.elevation) * TIER_HEIGHT_M
|
|
221
|
-
);
|
|
222
|
-
const height = section.height === void 0 ? automaticHeight : finiteClamped(section.height, SECTION_HEIGHT_MIN_M, SECTION_HEIGHT_MAX_M, automaticHeight);
|
|
223
|
-
const rake = finiteClamped(section.rake, SECTION_RAKE_MIN_DEG, SECTION_RAKE_MAX_DEG, 0);
|
|
224
|
-
return { height, rake };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
192
|
// src/core/layout.ts
|
|
228
193
|
function overrideAccessibility(o) {
|
|
229
194
|
if (!o) return [];
|
|
@@ -4359,7 +4324,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4359
4324
|
this.accessRingById.set(seat.id, ring);
|
|
4360
4325
|
target.add(ring);
|
|
4361
4326
|
}
|
|
4362
|
-
if (accessGlyphPath(seat.accessibility
|
|
4327
|
+
if (seat.accessibility?.includes("wheelchair") && accessGlyphPath(seat.accessibility)) {
|
|
4363
4328
|
const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
|
|
4364
4329
|
this.accessGlyphById.set(seat.id, glyph);
|
|
4365
4330
|
target.add(glyph);
|
|
@@ -8509,8 +8474,428 @@ function stageSightlinePitch(eyeHeightM, distM) {
|
|
|
8509
8474
|
var W = 2048;
|
|
8510
8475
|
var H = 1024;
|
|
8511
8476
|
var UNIT = METRES_PER_CHART_UNIT;
|
|
8477
|
+
var TAU2 = Math.PI * 2;
|
|
8478
|
+
var PX_PER_DEG = H / 180;
|
|
8512
8479
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
8513
8480
|
var pitchToY = (pitchDeg) => (90 - pitchDeg) / 180 * H;
|
|
8481
|
+
function fnv1a(str) {
|
|
8482
|
+
let h = 2166136261;
|
|
8483
|
+
for (let i = 0; i < str.length; i++) {
|
|
8484
|
+
h ^= str.charCodeAt(i);
|
|
8485
|
+
h = Math.imul(h, 16777619);
|
|
8486
|
+
}
|
|
8487
|
+
return h >>> 0;
|
|
8488
|
+
}
|
|
8489
|
+
function rng(seed) {
|
|
8490
|
+
let a = seed >>> 0;
|
|
8491
|
+
return () => {
|
|
8492
|
+
a |= 0;
|
|
8493
|
+
a = a + 1831565813 | 0;
|
|
8494
|
+
let t2 = Math.imul(a ^ a >>> 15, 1 | a);
|
|
8495
|
+
t2 = t2 + Math.imul(t2 ^ t2 >>> 7, 61 | t2) ^ t2;
|
|
8496
|
+
return ((t2 ^ t2 >>> 14) >>> 0) / 4294967296;
|
|
8497
|
+
};
|
|
8498
|
+
}
|
|
8499
|
+
var NBINS = 96;
|
|
8500
|
+
var BIN_DEG = 360 / NBINS;
|
|
8501
|
+
var FLAT_DELTA_M = 0.6;
|
|
8502
|
+
var HAZE_SKY = [17, 23, 42];
|
|
8503
|
+
function blendToSky(base, t2, a = 1) {
|
|
8504
|
+
const k = Math.max(0, Math.min(0.58, t2));
|
|
8505
|
+
const r = Math.round(base[0] + (HAZE_SKY[0] - base[0]) * k);
|
|
8506
|
+
const g = Math.round(base[1] + (HAZE_SKY[1] - base[1]) * k);
|
|
8507
|
+
const b = Math.round(base[2] + (HAZE_SKY[2] - base[2]) * k);
|
|
8508
|
+
return `rgba(${r},${g},${b},${a})`;
|
|
8509
|
+
}
|
|
8510
|
+
function mixRgb(a, b, t2) {
|
|
8511
|
+
return [
|
|
8512
|
+
Math.round(a[0] + (b[0] - a[0]) * t2),
|
|
8513
|
+
Math.round(a[1] + (b[1] - a[1]) * t2),
|
|
8514
|
+
Math.round(a[2] + (b[2] - a[2]) * t2)
|
|
8515
|
+
];
|
|
8516
|
+
}
|
|
8517
|
+
function hslToRgb(h, s, l) {
|
|
8518
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
8519
|
+
const hp = (h % 360 + 360) % 360 / 60;
|
|
8520
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
8521
|
+
let r = 0, g = 0, b = 0;
|
|
8522
|
+
if (hp < 1) [r, g, b] = [c, x, 0];
|
|
8523
|
+
else if (hp < 2) [r, g, b] = [x, c, 0];
|
|
8524
|
+
else if (hp < 3) [r, g, b] = [0, c, x];
|
|
8525
|
+
else if (hp < 4) [r, g, b] = [0, x, c];
|
|
8526
|
+
else if (hp < 5) [r, g, b] = [x, 0, c];
|
|
8527
|
+
else [r, g, b] = [c, 0, x];
|
|
8528
|
+
const m = l - c / 2;
|
|
8529
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
8530
|
+
}
|
|
8531
|
+
var tintCache = /* @__PURE__ */ new Map();
|
|
8532
|
+
function tintFromKey(key) {
|
|
8533
|
+
if (!key) return null;
|
|
8534
|
+
const cached = tintCache.get(key);
|
|
8535
|
+
if (cached) return cached;
|
|
8536
|
+
let h = 2166136261;
|
|
8537
|
+
for (let i = 0; i < key.length; i++) {
|
|
8538
|
+
h ^= key.charCodeAt(i);
|
|
8539
|
+
h = Math.imul(h, 16777619);
|
|
8540
|
+
}
|
|
8541
|
+
const hue = (h >>> 0) % 360;
|
|
8542
|
+
const rgb2 = hslToRgb(hue, 0.3, 0.42);
|
|
8543
|
+
tintCache.set(key, rgb2);
|
|
8544
|
+
return rgb2;
|
|
8545
|
+
}
|
|
8546
|
+
function buildStands(seat, neighborSeats, stageBearing, myEyeM) {
|
|
8547
|
+
const bins = new Array(NBINS).fill(null);
|
|
8548
|
+
let maxDelta = 0;
|
|
8549
|
+
const own = seat.sectionId;
|
|
8550
|
+
for (const other of neighborSeats) {
|
|
8551
|
+
if (other.id === seat.id) continue;
|
|
8552
|
+
const os = other.sectionId;
|
|
8553
|
+
if (!os || own && os === own) continue;
|
|
8554
|
+
const ox = other.x - seat.x;
|
|
8555
|
+
const oy = other.y - seat.y;
|
|
8556
|
+
const dM = Math.hypot(ox, oy) * UNIT;
|
|
8557
|
+
if (dM < 1.5 || dM > 95) continue;
|
|
8558
|
+
const otherEye = other.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
8559
|
+
const dTop = otherEye - myEyeM;
|
|
8560
|
+
if (Math.abs(dTop) > maxDelta) maxDelta = Math.abs(dTop);
|
|
8561
|
+
const top = Math.atan2(dTop, dM) * 180 / Math.PI;
|
|
8562
|
+
const base = Math.atan2(otherEye - SEATED_EYE_HEIGHT_M - myEyeM, dM) * 180 / Math.PI;
|
|
8563
|
+
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8564
|
+
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8565
|
+
const idx = Math.min(NBINS - 1, Math.max(0, Math.floor((yaw + 180) / BIN_DEG)));
|
|
8566
|
+
const overhead = top > 34 && dM < 16;
|
|
8567
|
+
const cur = bins[idx];
|
|
8568
|
+
if (!cur) bins[idx] = { top, base, nearM: dM, overhead, tint: tintFromKey(other.categoryKey) };
|
|
8569
|
+
else {
|
|
8570
|
+
if (top > cur.top) cur.top = top;
|
|
8571
|
+
if (base < cur.base) cur.base = base;
|
|
8572
|
+
if (dM < cur.nearM) {
|
|
8573
|
+
cur.nearM = dM;
|
|
8574
|
+
cur.tint = tintFromKey(other.categoryKey);
|
|
8575
|
+
}
|
|
8576
|
+
cur.overhead = cur.overhead || overhead;
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
return { bins, maxDelta };
|
|
8580
|
+
}
|
|
8581
|
+
function drawStands(ctx, bins, toX, toY) {
|
|
8582
|
+
for (let i = 0; i < NBINS; i++) {
|
|
8583
|
+
const b = bins[i];
|
|
8584
|
+
if (!b) continue;
|
|
8585
|
+
const yaw0 = -180 + i * BIN_DEG;
|
|
8586
|
+
const x0 = toX(yaw0);
|
|
8587
|
+
const x1 = toX(yaw0 + BIN_DEG);
|
|
8588
|
+
if (!(x1 > x0)) continue;
|
|
8589
|
+
const top = Math.min(72, b.top);
|
|
8590
|
+
const bottom = Math.min(b.base, -4);
|
|
8591
|
+
const yTop = toY(top);
|
|
8592
|
+
const yBot = toY(bottom);
|
|
8593
|
+
if (yBot <= yTop) continue;
|
|
8594
|
+
const haze = b.nearM / 95;
|
|
8595
|
+
const tint = b.tint;
|
|
8596
|
+
const crest = tint ? mixRgb([44, 54, 80], tint, 0.34) : [44, 54, 80];
|
|
8597
|
+
const seating = tint ? mixRgb([24, 30, 48], tint, 0.28) : [24, 30, 48];
|
|
8598
|
+
const grad = ctx.createLinearGradient(0, yTop, 0, yBot);
|
|
8599
|
+
grad.addColorStop(0, blendToSky(crest, haze));
|
|
8600
|
+
grad.addColorStop(0.24, blendToSky(seating, haze));
|
|
8601
|
+
grad.addColorStop(1, blendToSky([10, 13, 22], haze));
|
|
8602
|
+
ctx.fillStyle = grad;
|
|
8603
|
+
ctx.fillRect(x0, yTop, x1 - x0 + 1, yBot - yTop);
|
|
8604
|
+
ctx.fillStyle = `rgba(150,165,205,${0.35 * (1 - haze * 0.55)})`;
|
|
8605
|
+
ctx.fillRect(x0, yTop, x1 - x0 + 1, 1.5);
|
|
8606
|
+
const bandBottom = Math.min(yBot, toY(0));
|
|
8607
|
+
const bandSpan = bandBottom - yTop;
|
|
8608
|
+
if (bandSpan > 8) {
|
|
8609
|
+
const bands = 6;
|
|
8610
|
+
const contrast = 1 - haze * 0.4;
|
|
8611
|
+
for (let r = 1; r < bands; r++) {
|
|
8612
|
+
const y = yTop + bandSpan * r / bands;
|
|
8613
|
+
ctx.fillStyle = `rgba(0,0,0,${0.16 * contrast})`;
|
|
8614
|
+
ctx.fillRect(x0, y, x1 - x0 + 1, 1.5);
|
|
8615
|
+
ctx.fillStyle = `rgba(120,134,170,${0.1 * contrast})`;
|
|
8616
|
+
ctx.fillRect(x0, y + 1.5, x1 - x0 + 1, 1.2);
|
|
8617
|
+
}
|
|
8618
|
+
}
|
|
8619
|
+
if (b.overhead) {
|
|
8620
|
+
const lipY = toY(Math.min(86, top + 12));
|
|
8621
|
+
const g2 = ctx.createLinearGradient(0, lipY, 0, yTop);
|
|
8622
|
+
g2.addColorStop(0, "rgba(3,4,8,0.9)");
|
|
8623
|
+
g2.addColorStop(1, "rgba(3,4,8,0)");
|
|
8624
|
+
ctx.fillStyle = g2;
|
|
8625
|
+
ctx.fillRect(x0, lipY, x1 - x0 + 1, yTop - lipY);
|
|
8626
|
+
ctx.fillStyle = "rgba(120,130,160,0.14)";
|
|
8627
|
+
ctx.fillRect(x0, yTop - 1.5, x1 - x0 + 1, 1.5);
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
function classifyScene(_seat, focal, neighbors) {
|
|
8632
|
+
const NB = 72;
|
|
8633
|
+
const occ = new Array(NB).fill(false);
|
|
8634
|
+
const innerM = new Array(NB).fill(Infinity);
|
|
8635
|
+
const secSet = /* @__PURE__ */ new Set();
|
|
8636
|
+
let n = 0;
|
|
8637
|
+
for (const s of neighbors) {
|
|
8638
|
+
n++;
|
|
8639
|
+
if (s.sectionId) secSet.add(s.sectionId);
|
|
8640
|
+
const dx = s.x - focal.x;
|
|
8641
|
+
const dy = s.y - focal.y;
|
|
8642
|
+
const dM = Math.hypot(dx, dy) * UNIT;
|
|
8643
|
+
if (dM < 0.5) continue;
|
|
8644
|
+
const bearing = Math.atan2(dx, -dy);
|
|
8645
|
+
let idx = Math.floor((bearing + Math.PI) / TAU2 * NB);
|
|
8646
|
+
if (idx < 0) idx = 0;
|
|
8647
|
+
else if (idx >= NB) idx = NB - 1;
|
|
8648
|
+
occ[idx] = true;
|
|
8649
|
+
if (dM < innerM[idx]) innerM[idx] = dM;
|
|
8650
|
+
}
|
|
8651
|
+
let occN = 0;
|
|
8652
|
+
const inner = [];
|
|
8653
|
+
for (let i = 0; i < NB; i++) if (occ[i]) {
|
|
8654
|
+
occN++;
|
|
8655
|
+
inner.push(innerM[i]);
|
|
8656
|
+
}
|
|
8657
|
+
const coverage = occN / NB * 360;
|
|
8658
|
+
inner.sort((a, b) => a - b);
|
|
8659
|
+
const median = inner.length ? inner[inner.length >> 1] : 0;
|
|
8660
|
+
const lo = inner.length ? inner[Math.floor(inner.length * 0.15)] ?? median : 0;
|
|
8661
|
+
const hi = inner.length ? inner[Math.floor(inner.length * 0.85)] ?? median : 0;
|
|
8662
|
+
const aspect = lo > 0.5 ? hi / lo : 1;
|
|
8663
|
+
const seed = fnv1a(`${n}|${[...secSet].sort().join(",")}|${Math.round(focal.x)},${Math.round(focal.y)}`);
|
|
8664
|
+
let mode;
|
|
8665
|
+
if (coverage >= 300 && median > 11 && aspect > 1.35) mode = "sport";
|
|
8666
|
+
else if (coverage >= 285) mode = "arena";
|
|
8667
|
+
else mode = "proscenium";
|
|
8668
|
+
return { mode, seed, voidRadiusM: median, aspect };
|
|
8669
|
+
}
|
|
8670
|
+
var HAIR_KINDS = 5;
|
|
8671
|
+
function drawHair(ctx, r, kind) {
|
|
8672
|
+
switch (kind) {
|
|
8673
|
+
case 1:
|
|
8674
|
+
ctx.beginPath();
|
|
8675
|
+
ctx.ellipse(0, -r * 0.14, r * 1.16, r * 1.16, 0, 0, TAU2);
|
|
8676
|
+
ctx.fill();
|
|
8677
|
+
break;
|
|
8678
|
+
case 2:
|
|
8679
|
+
ctx.beginPath();
|
|
8680
|
+
ctx.ellipse(0, -r * 1.02, r * 0.42, r * 0.42, 0, 0, TAU2);
|
|
8681
|
+
ctx.fill();
|
|
8682
|
+
ctx.beginPath();
|
|
8683
|
+
ctx.ellipse(0, -r * 0.2, r * 0.98, r * 1.02, 0, 0, TAU2);
|
|
8684
|
+
ctx.fill();
|
|
8685
|
+
break;
|
|
8686
|
+
case 3:
|
|
8687
|
+
ctx.beginPath();
|
|
8688
|
+
ctx.ellipse(0, -r * 0.36, r * 1.04, r * 0.6, 0, Math.PI, TAU2);
|
|
8689
|
+
ctx.fill();
|
|
8690
|
+
ctx.beginPath();
|
|
8691
|
+
ctx.ellipse(0, -r * 0.52, r * 1.22, r * 0.24, 0, 0, TAU2);
|
|
8692
|
+
ctx.fill();
|
|
8693
|
+
break;
|
|
8694
|
+
case 4:
|
|
8695
|
+
ctx.beginPath();
|
|
8696
|
+
ctx.ellipse(-r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU2);
|
|
8697
|
+
ctx.fill();
|
|
8698
|
+
ctx.beginPath();
|
|
8699
|
+
ctx.ellipse(r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU2);
|
|
8700
|
+
ctx.fill();
|
|
8701
|
+
ctx.beginPath();
|
|
8702
|
+
ctx.ellipse(0, -r * 0.08, r * 1.06, r * 1.08, 0, 0, TAU2);
|
|
8703
|
+
ctx.fill();
|
|
8704
|
+
break;
|
|
8705
|
+
default:
|
|
8706
|
+
break;
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
function drawSeated(ctx, hx, hy, r, o) {
|
|
8710
|
+
const neckHalf = r * 0.44;
|
|
8711
|
+
const shHalf = r * o.shoulderK;
|
|
8712
|
+
const neckTopY = r * 0.66;
|
|
8713
|
+
const shoulderY = r * 1.68;
|
|
8714
|
+
const botY = r * 5.4;
|
|
8715
|
+
const clo = `rgba(${o.clothing[0]},${o.clothing[1]},${o.clothing[2]},${o.alpha})`;
|
|
8716
|
+
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
8717
|
+
ctx.save();
|
|
8718
|
+
ctx.translate(hx, hy);
|
|
8719
|
+
ctx.fillStyle = clo;
|
|
8720
|
+
ctx.beginPath();
|
|
8721
|
+
ctx.moveTo(-neckHalf, neckTopY);
|
|
8722
|
+
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - r * 0.6, -shHalf, shoulderY);
|
|
8723
|
+
ctx.quadraticCurveTo(-shHalf * 1.05, shoulderY + r * 1.2, -shHalf * 0.94, botY);
|
|
8724
|
+
ctx.lineTo(shHalf * 0.94, botY);
|
|
8725
|
+
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY);
|
|
8726
|
+
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY - r * 0.6, neckHalf, neckTopY);
|
|
8727
|
+
ctx.closePath();
|
|
8728
|
+
ctx.fill();
|
|
8729
|
+
ctx.fillStyle = skin;
|
|
8730
|
+
ctx.fillRect(-neckHalf * 0.82, r * 0.5, neckHalf * 1.64, r * 0.9);
|
|
8731
|
+
ctx.save();
|
|
8732
|
+
ctx.rotate(o.tilt);
|
|
8733
|
+
ctx.fillStyle = skin;
|
|
8734
|
+
ctx.beginPath();
|
|
8735
|
+
ctx.ellipse(0, 0, r * 0.9, r * 1.06, 0, 0, TAU2);
|
|
8736
|
+
ctx.fill();
|
|
8737
|
+
drawHair(ctx, r, o.hair);
|
|
8738
|
+
if (o.rim > 0.01) {
|
|
8739
|
+
ctx.strokeStyle = `rgba(245,205,150,${(0.5 * o.rim).toFixed(3)})`;
|
|
8740
|
+
ctx.lineWidth = Math.max(1, r * 0.14);
|
|
8741
|
+
ctx.beginPath();
|
|
8742
|
+
ctx.ellipse(0, -r * 0.06, r * 0.86, r * 1, 0, Math.PI * 1.12, Math.PI * 1.9);
|
|
8743
|
+
ctx.stroke();
|
|
8744
|
+
}
|
|
8745
|
+
ctx.restore();
|
|
8746
|
+
if (o.rim > 0.01) {
|
|
8747
|
+
ctx.strokeStyle = `rgba(240,200,150,${(0.28 * o.rim).toFixed(3)})`;
|
|
8748
|
+
ctx.lineWidth = Math.max(1, r * 0.12);
|
|
8749
|
+
ctx.beginPath();
|
|
8750
|
+
ctx.moveTo(-shHalf * 0.8, shoulderY - r * 0.05);
|
|
8751
|
+
ctx.quadraticCurveTo(-neckHalf, shoulderY - r * 0.75, 0, shoulderY - r * 0.82);
|
|
8752
|
+
ctx.quadraticCurveTo(neckHalf, shoulderY - r * 0.75, shHalf * 0.8, shoulderY - r * 0.05);
|
|
8753
|
+
ctx.stroke();
|
|
8754
|
+
}
|
|
8755
|
+
ctx.restore();
|
|
8756
|
+
}
|
|
8757
|
+
function drawSeatedMid(ctx, hx, hy, r, clothing, tone, alpha) {
|
|
8758
|
+
ctx.fillStyle = `rgba(${clothing[0]},${clothing[1]},${clothing[2]},${alpha})`;
|
|
8759
|
+
ctx.beginPath();
|
|
8760
|
+
ctx.moveTo(hx - r * 1.9, hy + r * 4);
|
|
8761
|
+
ctx.quadraticCurveTo(hx - r * 1.95, hy + r * 1.2, hx - r * 0.5, hy + r * 0.9);
|
|
8762
|
+
ctx.quadraticCurveTo(hx, hy + r * 0.4, hx + r * 0.5, hy + r * 0.9);
|
|
8763
|
+
ctx.quadraticCurveTo(hx + r * 1.95, hy + r * 1.2, hx + r * 1.9, hy + r * 4);
|
|
8764
|
+
ctx.closePath();
|
|
8765
|
+
ctx.fill();
|
|
8766
|
+
ctx.fillStyle = `rgba(${tone[0]},${tone[1]},${tone[2]},${alpha})`;
|
|
8767
|
+
ctx.beginPath();
|
|
8768
|
+
ctx.ellipse(hx, hy, r * 0.92, r * 1.05, 0, 0, TAU2);
|
|
8769
|
+
ctx.fill();
|
|
8770
|
+
}
|
|
8771
|
+
function drawPerformer(ctx, x, footY, h, o) {
|
|
8772
|
+
const headR = h * 0.1;
|
|
8773
|
+
const headCy = footY - h * 0.9;
|
|
8774
|
+
const shoulderY = footY - h * 0.74;
|
|
8775
|
+
const hipY = footY - h * 0.46;
|
|
8776
|
+
const shHalf = h * 0.13;
|
|
8777
|
+
const hipHalf = h * 0.09;
|
|
8778
|
+
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
8779
|
+
const sh = ctx.createRadialGradient(x, footY, 1, x, footY, h * 0.24);
|
|
8780
|
+
sh.addColorStop(0, "rgba(0,0,0,0.4)");
|
|
8781
|
+
sh.addColorStop(1, "rgba(0,0,0,0)");
|
|
8782
|
+
ctx.save();
|
|
8783
|
+
ctx.scale(1, 0.28);
|
|
8784
|
+
ctx.fillStyle = sh;
|
|
8785
|
+
ctx.beginPath();
|
|
8786
|
+
ctx.ellipse(x, footY / 0.28, h * 0.24, h * 0.24, 0, 0, TAU2);
|
|
8787
|
+
ctx.fill();
|
|
8788
|
+
ctx.restore();
|
|
8789
|
+
ctx.fillStyle = skin;
|
|
8790
|
+
ctx.beginPath();
|
|
8791
|
+
ctx.moveTo(x - hipHalf, hipY);
|
|
8792
|
+
ctx.lineTo(x - hipHalf * 0.7, footY);
|
|
8793
|
+
ctx.lineTo(x - hipHalf * 0.1, footY);
|
|
8794
|
+
ctx.lineTo(x - hipHalf * 0.1, hipY + h * 0.02);
|
|
8795
|
+
ctx.lineTo(x + hipHalf * 0.1, hipY + h * 0.02);
|
|
8796
|
+
ctx.lineTo(x + hipHalf * 0.1, footY);
|
|
8797
|
+
ctx.lineTo(x + hipHalf * 0.7, footY);
|
|
8798
|
+
ctx.lineTo(x + hipHalf, hipY);
|
|
8799
|
+
ctx.closePath();
|
|
8800
|
+
ctx.fill();
|
|
8801
|
+
ctx.beginPath();
|
|
8802
|
+
ctx.moveTo(x - shHalf, shoulderY);
|
|
8803
|
+
ctx.quadraticCurveTo(x - shHalf * 0.9, hipY - h * 0.12, x - hipHalf, hipY);
|
|
8804
|
+
ctx.lineTo(x + hipHalf, hipY);
|
|
8805
|
+
ctx.quadraticCurveTo(x + shHalf * 0.9, hipY - h * 0.12, x + shHalf, shoulderY);
|
|
8806
|
+
ctx.quadraticCurveTo(x, shoulderY - h * 0.05, x - shHalf, shoulderY);
|
|
8807
|
+
ctx.closePath();
|
|
8808
|
+
ctx.fill();
|
|
8809
|
+
ctx.lineCap = "round";
|
|
8810
|
+
ctx.lineJoin = "round";
|
|
8811
|
+
ctx.strokeStyle = skin;
|
|
8812
|
+
ctx.lineWidth = h * 0.055;
|
|
8813
|
+
const armY = shoulderY + h * 0.02;
|
|
8814
|
+
if (o.pose === 1) {
|
|
8815
|
+
ctx.beginPath();
|
|
8816
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
8817
|
+
ctx.lineTo(x - shHalf * 1.5, footY - h * 1.02);
|
|
8818
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
8819
|
+
ctx.lineTo(x + shHalf * 1.5, footY - h * 1.02);
|
|
8820
|
+
ctx.stroke();
|
|
8821
|
+
} else if (o.pose === 2) {
|
|
8822
|
+
ctx.beginPath();
|
|
8823
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
8824
|
+
ctx.lineTo(x + hipHalf * 1.4, hipY - h * 0.02);
|
|
8825
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
8826
|
+
ctx.lineTo(x - hipHalf * 0.4, hipY - h * 0.06);
|
|
8827
|
+
ctx.stroke();
|
|
8828
|
+
ctx.fillStyle = skin;
|
|
8829
|
+
ctx.beginPath();
|
|
8830
|
+
ctx.ellipse(x + hipHalf * 1.5, hipY - h * 0.04, h * 0.07, h * 0.09, -0.5, 0, TAU2);
|
|
8831
|
+
ctx.fill();
|
|
8832
|
+
} else if (o.pose === 3) {
|
|
8833
|
+
ctx.beginPath();
|
|
8834
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
8835
|
+
ctx.lineTo(x - shHalf * 0.2, headCy + headR * 0.6);
|
|
8836
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
8837
|
+
ctx.lineTo(x + shHalf * 1.1, hipY);
|
|
8838
|
+
ctx.stroke();
|
|
8839
|
+
ctx.lineWidth = h * 0.02;
|
|
8840
|
+
ctx.beginPath();
|
|
8841
|
+
ctx.moveTo(x - shHalf * 0.2, headCy + headR * 0.9);
|
|
8842
|
+
ctx.lineTo(x - shHalf * 0.2, footY - h * 0.02);
|
|
8843
|
+
ctx.stroke();
|
|
8844
|
+
} else {
|
|
8845
|
+
ctx.beginPath();
|
|
8846
|
+
ctx.moveTo(x - shHalf * 0.85, armY);
|
|
8847
|
+
ctx.lineTo(x - shHalf * 0.95, hipY + h * 0.02);
|
|
8848
|
+
ctx.moveTo(x + shHalf * 0.85, armY);
|
|
8849
|
+
ctx.lineTo(x + shHalf * 0.95, hipY + h * 0.02);
|
|
8850
|
+
ctx.stroke();
|
|
8851
|
+
}
|
|
8852
|
+
ctx.fillStyle = skin;
|
|
8853
|
+
ctx.fillRect(x - headR * 0.34, headCy + headR * 0.6, headR * 0.68, headR * 0.9);
|
|
8854
|
+
ctx.beginPath();
|
|
8855
|
+
ctx.ellipse(x, headCy, headR * 0.86, headR * 1.05, 0, 0, TAU2);
|
|
8856
|
+
ctx.fill();
|
|
8857
|
+
if (o.rimStrength > 0.01) {
|
|
8858
|
+
ctx.strokeStyle = `rgba(${o.rim[0]},${o.rim[1]},${o.rim[2]},${(0.6 * o.rimStrength).toFixed(3)})`;
|
|
8859
|
+
ctx.lineWidth = Math.max(1, h * 0.02);
|
|
8860
|
+
ctx.beginPath();
|
|
8861
|
+
ctx.moveTo(x - shHalf, shoulderY);
|
|
8862
|
+
ctx.lineTo(x - headR * 0.6, headCy + headR * 0.4);
|
|
8863
|
+
ctx.moveTo(x - headR * 0.7, headCy);
|
|
8864
|
+
ctx.ellipse(x, headCy, headR * 0.84, headR * 1.02, 0, Math.PI * 1.05, Math.PI * 1.55);
|
|
8865
|
+
ctx.stroke();
|
|
8866
|
+
}
|
|
8867
|
+
}
|
|
8868
|
+
function drawBeam(ctx, x0, y0, x1, y1, halfTop, halfBot, tint, strength) {
|
|
8869
|
+
ctx.save();
|
|
8870
|
+
ctx.globalCompositeOperation = "lighter";
|
|
8871
|
+
const g = ctx.createLinearGradient(0, y0, 0, y1);
|
|
8872
|
+
g.addColorStop(0, `rgba(${tint},${strength})`);
|
|
8873
|
+
g.addColorStop(0.5, `rgba(${tint},${(strength * 0.4).toFixed(3)})`);
|
|
8874
|
+
g.addColorStop(1, `rgba(${tint},0)`);
|
|
8875
|
+
ctx.fillStyle = g;
|
|
8876
|
+
ctx.beginPath();
|
|
8877
|
+
ctx.moveTo(x0 - halfTop, y0);
|
|
8878
|
+
ctx.lineTo(x1 - halfBot, y1);
|
|
8879
|
+
ctx.lineTo(x1 + halfBot, y1);
|
|
8880
|
+
ctx.lineTo(x0 + halfTop, y0);
|
|
8881
|
+
ctx.closePath();
|
|
8882
|
+
ctx.fill();
|
|
8883
|
+
ctx.restore();
|
|
8884
|
+
}
|
|
8885
|
+
function drawFixture(ctx, x, y, r, tint) {
|
|
8886
|
+
const g = ctx.createRadialGradient(x, y, 0, x, y, r * 3);
|
|
8887
|
+
g.addColorStop(0, `rgba(${tint},0.9)`);
|
|
8888
|
+
g.addColorStop(0.4, `rgba(${tint},0.35)`);
|
|
8889
|
+
g.addColorStop(1, `rgba(${tint},0)`);
|
|
8890
|
+
ctx.fillStyle = g;
|
|
8891
|
+
ctx.beginPath();
|
|
8892
|
+
ctx.arc(x, y, r * 3, 0, TAU2);
|
|
8893
|
+
ctx.fill();
|
|
8894
|
+
ctx.fillStyle = `rgba(${tint},1)`;
|
|
8895
|
+
ctx.beginPath();
|
|
8896
|
+
ctx.arc(x, y, r, 0, TAU2);
|
|
8897
|
+
ctx.fill();
|
|
8898
|
+
}
|
|
8514
8899
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
8515
8900
|
const canvas = document.createElement("canvas");
|
|
8516
8901
|
canvas.width = W;
|
|
@@ -8520,102 +8905,428 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
8520
8905
|
const dy = focalPoint.y - seat.y;
|
|
8521
8906
|
const distU = Math.hypot(dx, dy);
|
|
8522
8907
|
const distM = Math.max(2, distU * UNIT);
|
|
8908
|
+
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
8909
|
+
const scene = classifyScene(seat, focalPoint, neighborSeats);
|
|
8910
|
+
const rand = rng(scene.seed);
|
|
8911
|
+
const baseHue = 210 + rand() * 150;
|
|
8912
|
+
const rigTint = hslToRgb(220 + (rand() - 0.5) * 60, 0.55, 0.82);
|
|
8913
|
+
const rigTintStr = `${rigTint[0]},${rigTint[1]},${rigTint[2]}`;
|
|
8523
8914
|
const sky = ctx.createLinearGradient(0, 0, 0, H);
|
|
8524
|
-
sky.addColorStop(0, "#
|
|
8525
|
-
sky.addColorStop(0.42, "#
|
|
8526
|
-
sky.addColorStop(0.5, "#
|
|
8527
|
-
sky.addColorStop(0.56, "#
|
|
8528
|
-
sky.addColorStop(
|
|
8915
|
+
sky.addColorStop(0, "#080b12");
|
|
8916
|
+
sky.addColorStop(0.42, "#12182c");
|
|
8917
|
+
sky.addColorStop(0.5, "#1c2440");
|
|
8918
|
+
sky.addColorStop(0.56, "#151b2c");
|
|
8919
|
+
sky.addColorStop(0.78, "#171e30");
|
|
8920
|
+
sky.addColorStop(1, "#131829");
|
|
8529
8921
|
ctx.fillStyle = sky;
|
|
8530
8922
|
ctx.fillRect(0, 0, W, H);
|
|
8531
|
-
const
|
|
8532
|
-
|
|
8923
|
+
const floorAmb = ctx.createLinearGradient(0, pitchToY(-6), 0, H);
|
|
8924
|
+
floorAmb.addColorStop(0, "rgba(30, 37, 56, 0)");
|
|
8925
|
+
floorAmb.addColorStop(0.55, "rgba(30, 37, 56, 0.22)");
|
|
8926
|
+
floorAmb.addColorStop(1, "rgba(22, 28, 44, 0.12)");
|
|
8927
|
+
ctx.fillStyle = floorAmb;
|
|
8928
|
+
ctx.fillRect(0, pitchToY(-6), W, H - pitchToY(-6));
|
|
8929
|
+
const stageBearing = Math.atan2(dx, -dy);
|
|
8930
|
+
const stands = buildStands(seat, neighborSeats, stageBearing, eyeM);
|
|
8931
|
+
const hasRelief = stands.maxDelta >= FLAT_DELTA_M;
|
|
8932
|
+
if (hasRelief) drawStands(ctx, stands.bins, yawToX, pitchToY);
|
|
8933
|
+
const nearGlow = Math.max(0.3, Math.min(1, 1 - (distM - 6) / 60));
|
|
8533
8934
|
const sightline = stageSightlinePitch(eyeM, distM);
|
|
8935
|
+
const stageHalfYaw = Math.min(80, Math.atan2(4, distM) * 180 / Math.PI);
|
|
8534
8936
|
const stageTopPitch = Math.min(45, sightline.topPitch);
|
|
8535
8937
|
const stageBasePitch = sightline.basePitch;
|
|
8938
|
+
if (scene.mode === "sport") {
|
|
8939
|
+
drawSportScene(ctx, distM, eyeM, scene, nearGlow, baseHue, rigTintStr, rand);
|
|
8940
|
+
} else if (scene.mode === "arena") {
|
|
8941
|
+
drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand);
|
|
8942
|
+
} else {
|
|
8943
|
+
drawProsceniumScene(ctx, stageHalfYaw, stageTopPitch, stageBasePitch, nearGlow, baseHue, rigTintStr, rand);
|
|
8944
|
+
}
|
|
8945
|
+
const rigCount = scene.mode === "sport" ? 34 : 24 + Math.floor(rand() * 8);
|
|
8946
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.8)`;
|
|
8947
|
+
for (let i = 0; i < rigCount; i++) {
|
|
8948
|
+
const x = (i + 0.5) / rigCount * W;
|
|
8949
|
+
const y = pitchToY(scene.mode === "sport" ? 58 : 54 + i * 7 % 3 * 6);
|
|
8950
|
+
ctx.beginPath();
|
|
8951
|
+
ctx.arc(x, y, scene.mode === "sport" ? 2.6 : 3.2, 0, TAU2);
|
|
8952
|
+
ctx.fill();
|
|
8953
|
+
}
|
|
8954
|
+
drawAudience(ctx, seat, neighborSeats, focalPoint, stageBearing, eyeM, dx, dy, hasRelief, scene);
|
|
8955
|
+
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
8956
|
+
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
8957
|
+
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
8958
|
+
poleFade.addColorStop(0.9, "rgba(0,0,0,0)");
|
|
8959
|
+
poleFade.addColorStop(1, "rgba(0,0,0,0.55)");
|
|
8960
|
+
ctx.fillStyle = poleFade;
|
|
8961
|
+
ctx.fillRect(0, 0, W, H);
|
|
8962
|
+
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
8963
|
+
}
|
|
8964
|
+
function drawProsceniumScene(ctx, stageHalfYaw, stageTopPitch, stageBasePitch, nearGlow, baseHue, rigTintStr, rand) {
|
|
8536
8965
|
const sx0 = yawToX(-stageHalfYaw);
|
|
8537
8966
|
const sx1 = yawToX(stageHalfYaw);
|
|
8538
8967
|
const sy0 = pitchToY(stageTopPitch);
|
|
8539
8968
|
const sy1 = pitchToY(stageBasePitch);
|
|
8540
|
-
const
|
|
8541
|
-
|
|
8542
|
-
|
|
8969
|
+
const sw = sx1 - sx0;
|
|
8970
|
+
const sh = sy1 - sy0;
|
|
8971
|
+
const archTop = pitchToY(Math.min(50, stageTopPitch + 10));
|
|
8972
|
+
const glowR = sw * (1.1 + (1 - nearGlow) * 0.5);
|
|
8973
|
+
const glow = ctx.createRadialGradient(W / 2, (sy0 + sy1) / 2, 8, W / 2, (sy0 + sy1) / 2, glowR);
|
|
8974
|
+
glow.addColorStop(0, "rgba(255, 214, 150, 0.34)");
|
|
8975
|
+
glow.addColorStop(0.28, "rgba(150, 150, 230, 0.3)");
|
|
8976
|
+
glow.addColorStop(0.6, "rgba(99, 102, 241, 0.12)");
|
|
8543
8977
|
glow.addColorStop(1, "rgba(99, 102, 241, 0)");
|
|
8544
8978
|
ctx.fillStyle = glow;
|
|
8545
|
-
ctx.fillRect(sx0 -
|
|
8546
|
-
|
|
8547
|
-
|
|
8979
|
+
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
8980
|
+
const topCol = hslToRgb(baseHue, 0.62, 0.55);
|
|
8981
|
+
const midCol = hslToRgb(baseHue + 40, 0.7, 0.5);
|
|
8982
|
+
const botCol = hslToRgb(baseHue + 70, 0.78, 0.52);
|
|
8983
|
+
ctx.save();
|
|
8984
|
+
ctx.beginPath();
|
|
8985
|
+
ctx.rect(sx0, sy0, sw, sh);
|
|
8986
|
+
ctx.clip();
|
|
8548
8987
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8549
|
-
backdrop.addColorStop(0,
|
|
8550
|
-
backdrop.addColorStop(0.5,
|
|
8551
|
-
backdrop.addColorStop(1,
|
|
8552
|
-
ctx.globalAlpha = 0.
|
|
8988
|
+
backdrop.addColorStop(0, `rgb(${topCol[0]},${topCol[1]},${topCol[2]})`);
|
|
8989
|
+
backdrop.addColorStop(0.5, `rgb(${midCol[0]},${midCol[1]},${midCol[2]})`);
|
|
8990
|
+
backdrop.addColorStop(1, `rgb(${botCol[0]},${botCol[1]},${botCol[2]})`);
|
|
8991
|
+
ctx.globalAlpha = 0.8;
|
|
8553
8992
|
ctx.fillStyle = backdrop;
|
|
8554
|
-
|
|
8555
|
-
ctx.fillRect(sx0 + inset, sy0 + (sy1 - sy0) * 0.12, sx1 - sx0 - inset * 2, (sy1 - sy0) * 0.62);
|
|
8993
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8556
8994
|
ctx.globalAlpha = 1;
|
|
8557
|
-
ctx.
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8995
|
+
const fall = ctx.createRadialGradient(W / 2, (sy0 + sy1) / 2, sw * 0.12, W / 2, (sy0 + sy1) / 2, sw * 0.62);
|
|
8996
|
+
fall.addColorStop(0, "rgba(6,8,14,0)");
|
|
8997
|
+
fall.addColorStop(0.7, "rgba(6,8,14,0.15)");
|
|
8998
|
+
fall.addColorStop(1, "rgba(5,6,11,0.92)");
|
|
8999
|
+
ctx.fillStyle = fall;
|
|
9000
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
9001
|
+
ctx.restore();
|
|
9002
|
+
const perfCount = 3 + Math.floor(rand() * 3);
|
|
9003
|
+
const perfH = sh * 0.46;
|
|
9004
|
+
const floorY = sy1 - sh * 0.08;
|
|
9005
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9006
|
+
const t2 = (i + 1) / (perfCount + 1);
|
|
9007
|
+
const px = sx0 + sw * (0.22 + t2 * 0.56 + (rand() - 0.5) * 0.06);
|
|
9008
|
+
drawPerformer(ctx, px, floorY, perfH * (0.9 + rand() * 0.24), {
|
|
9009
|
+
tone: [8, 10, 16],
|
|
9010
|
+
alpha: 0.9,
|
|
9011
|
+
pose: Math.floor(rand() * 4),
|
|
9012
|
+
rim: hslToRgb(baseHue + 20, 0.5, 0.7),
|
|
9013
|
+
rimStrength: 0.7
|
|
9014
|
+
});
|
|
9015
|
+
}
|
|
9016
|
+
ctx.fillStyle = "#05070d";
|
|
9017
|
+
ctx.fillRect(sx0 - sw * 0.5, archTop, sw * 0.5, sy1 - archTop);
|
|
9018
|
+
ctx.fillRect(sx1, archTop, sw * 0.5, sy1 - archTop);
|
|
9019
|
+
const legBot = sw * 0.055 + 3;
|
|
9020
|
+
const legTop = legBot * 1.35;
|
|
9021
|
+
ctx.fillStyle = "#03050a";
|
|
9022
|
+
ctx.beginPath();
|
|
9023
|
+
ctx.moveTo(sx0, sy1);
|
|
9024
|
+
ctx.lineTo(sx0 - legBot, sy1);
|
|
9025
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9026
|
+
ctx.lineTo(sx0 + sw * 0.012, archTop);
|
|
9027
|
+
ctx.closePath();
|
|
9028
|
+
ctx.fill();
|
|
9029
|
+
ctx.beginPath();
|
|
9030
|
+
ctx.moveTo(sx1, sy1);
|
|
9031
|
+
ctx.lineTo(sx1 + legBot, sy1);
|
|
9032
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9033
|
+
ctx.lineTo(sx1 - sw * 0.012, archTop);
|
|
9034
|
+
ctx.closePath();
|
|
9035
|
+
ctx.fill();
|
|
9036
|
+
ctx.fillRect(sx0 - legTop, archTop, sw + legTop * 2, (sy1 - archTop) * 0.12);
|
|
9037
|
+
const valH = sh * 0.14;
|
|
9038
|
+
const valY = archTop + (sy1 - archTop) * 0.12;
|
|
9039
|
+
ctx.fillStyle = "rgba(4,6,12,0.96)";
|
|
9040
|
+
const scallops = 7;
|
|
9041
|
+
ctx.beginPath();
|
|
9042
|
+
ctx.moveTo(sx0 - legTop, valY);
|
|
9043
|
+
for (let i = 0; i <= scallops; i++) {
|
|
9044
|
+
const xx = sx0 - legTop + (sw + legTop * 2) * i / scallops;
|
|
9045
|
+
ctx.quadraticCurveTo(xx - (sw + legTop * 2) / scallops / 2, valY + valH, xx, valY);
|
|
9046
|
+
}
|
|
9047
|
+
ctx.lineTo(sx1 + legTop, valY);
|
|
9048
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9049
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9050
|
+
ctx.closePath();
|
|
9051
|
+
ctx.fill();
|
|
9052
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.12)`;
|
|
9053
|
+
ctx.fillRect(sx0 - 1.5, valY, 1.5, sy1 - valY);
|
|
9054
|
+
ctx.fillRect(sx1, valY, 1.5, sy1 - valY);
|
|
9055
|
+
ctx.fillStyle = "#0b0f18";
|
|
9056
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.05 + 3);
|
|
9057
|
+
const foot = ctx.createLinearGradient(0, sy1 - sh * 0.05, 0, sy1 + sh * 0.12);
|
|
9058
|
+
foot.addColorStop(0, `rgba(255,206,140,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9059
|
+
foot.addColorStop(1, "rgba(255,206,140,0)");
|
|
9060
|
+
ctx.fillStyle = foot;
|
|
9061
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.2);
|
|
9062
|
+
const beams = 3 + Math.floor(rand() * 2);
|
|
9063
|
+
for (let i = 0; i < beams; i++) {
|
|
9064
|
+
const t2 = (i + 1) / (beams + 1);
|
|
9065
|
+
const fx = sx0 + sw * (t2 + (rand() - 0.5) * 0.08);
|
|
9066
|
+
const fy = pitchToY(Math.min(62, stageTopPitch + 26));
|
|
9067
|
+
const skew = (rand() - 0.5) * sw * 0.16;
|
|
9068
|
+
drawBeam(ctx, fx, fy, fx + skew, sy1, 4, sw * 0.09, rigTintStr, 0.16 * (0.7 + nearGlow * 0.5));
|
|
9069
|
+
drawFixture(ctx, fx, fy, 2.4, rigTintStr);
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
9072
|
+
function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
9073
|
+
const stageR = Math.min(distM * 0.6, Math.max(3, scene.voidRadiusM));
|
|
9074
|
+
const halfYaw = Math.min(55, Math.atan2(stageR, distM) * 180 / Math.PI);
|
|
9075
|
+
const cx = W / 2;
|
|
9076
|
+
const deckRiserM = 1.2;
|
|
9077
|
+
const nearDist = Math.max(distM * 0.45, distM - stageR);
|
|
9078
|
+
const farDist = distM + stageR;
|
|
9079
|
+
const nearTopPitch = Math.atan2(deckRiserM - eyeM, nearDist) * 180 / Math.PI;
|
|
9080
|
+
const farTopPitch = Math.atan2(deckRiserM - eyeM, farDist) * 180 / Math.PI;
|
|
9081
|
+
const nearBasePitch = Math.atan2(-eyeM, nearDist) * 180 / Math.PI;
|
|
9082
|
+
const yNearTop = pitchToY(nearTopPitch);
|
|
9083
|
+
const yFarTop = pitchToY(farTopPitch);
|
|
9084
|
+
const yBase = pitchToY(nearBasePitch);
|
|
9085
|
+
const halfW = yawToX(halfYaw) - cx;
|
|
9086
|
+
const topRy = Math.max(6, (yNearTop - yFarTop) / 2);
|
|
9087
|
+
const topCy = (yNearTop + yFarTop) / 2;
|
|
9088
|
+
const pool = ctx.createRadialGradient(cx, yBase, 4, cx, yBase, halfW * 2.6);
|
|
9089
|
+
pool.addColorStop(0, `rgba(255,206,150,${(0.2 * nearGlow).toFixed(3)})`);
|
|
9090
|
+
pool.addColorStop(0.5, "rgba(150,140,190,0.08)");
|
|
9091
|
+
pool.addColorStop(1, "rgba(40,44,60,0)");
|
|
9092
|
+
ctx.save();
|
|
9093
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9094
|
+
ctx.fillStyle = pool;
|
|
9095
|
+
ctx.fillRect(cx - halfW * 2.6, yFarTop - topRy, halfW * 5.2, yBase - yFarTop + topRy + 40);
|
|
9096
|
+
ctx.restore();
|
|
9097
|
+
ctx.fillStyle = "#0c1019";
|
|
9098
|
+
ctx.beginPath();
|
|
9099
|
+
ctx.moveTo(cx - halfW, topCy);
|
|
9100
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, Math.PI, 0, false);
|
|
9101
|
+
ctx.lineTo(cx + halfW, yBase);
|
|
9102
|
+
ctx.ellipse(cx, yBase, halfW, topRy, 0, 0, Math.PI, false);
|
|
9103
|
+
ctx.closePath();
|
|
9104
|
+
ctx.fill();
|
|
9105
|
+
const riser = ctx.createLinearGradient(0, topCy, 0, yBase);
|
|
9106
|
+
riser.addColorStop(0, `rgba(${rigTintStr},0.14)`);
|
|
9107
|
+
riser.addColorStop(1, "rgba(0,0,0,0)");
|
|
9108
|
+
ctx.fillStyle = riser;
|
|
9109
|
+
ctx.fillRect(cx - halfW, topCy, halfW * 2, yBase - topCy);
|
|
9110
|
+
const deckTop = ctx.createLinearGradient(0, topCy - topRy, 0, topCy + topRy);
|
|
9111
|
+
deckTop.addColorStop(0, "#2b3350");
|
|
9112
|
+
deckTop.addColorStop(1, "#161b2c");
|
|
9113
|
+
ctx.fillStyle = deckTop;
|
|
9114
|
+
ctx.beginPath();
|
|
9115
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9116
|
+
ctx.fill();
|
|
9117
|
+
const deckGlow = ctx.createRadialGradient(cx, topCy, 2, cx, topCy, halfW);
|
|
9118
|
+
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.28 * nearGlow).toFixed(3)})`);
|
|
9119
|
+
deckGlow.addColorStop(1, "rgba(255,214,160,0)");
|
|
9120
|
+
ctx.save();
|
|
9121
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9122
|
+
ctx.fillStyle = deckGlow;
|
|
9123
|
+
ctx.beginPath();
|
|
9124
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9125
|
+
ctx.fill();
|
|
9126
|
+
ctx.restore();
|
|
9127
|
+
ctx.strokeStyle = `rgba(${rigTintStr},0.7)`;
|
|
9128
|
+
ctx.lineWidth = 2;
|
|
9129
|
+
ctx.beginPath();
|
|
9130
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9131
|
+
ctx.stroke();
|
|
9132
|
+
const perfCount = 2 + Math.floor(rand() * 3);
|
|
9133
|
+
const perfH = Math.max(24, (yBase - yFarTop) * 1.3);
|
|
9134
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9135
|
+
const t2 = perfCount === 1 ? 0.5 : i / (perfCount - 1);
|
|
9136
|
+
const px = cx + (t2 - 0.5) * 2 * halfW * 0.66;
|
|
9137
|
+
const footY = topCy + topRy * 0.5 * Math.cos((t2 - 0.5) * Math.PI) - topRy * 0.1;
|
|
9138
|
+
drawPerformer(ctx, px, footY, perfH * (0.85 + rand() * 0.3), {
|
|
9139
|
+
tone: [10, 12, 20],
|
|
9140
|
+
alpha: 0.92,
|
|
9141
|
+
pose: Math.floor(rand() * 4),
|
|
9142
|
+
rim: hslToRgb(40, 0.6, 0.72),
|
|
9143
|
+
rimStrength: 0.8
|
|
9144
|
+
});
|
|
8568
9145
|
}
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
9146
|
+
const trussY = pitchToY(Math.min(60, farTopPitch + 34));
|
|
9147
|
+
const trussRy = topRy * 1.1 + 6;
|
|
9148
|
+
ctx.strokeStyle = "rgba(60,68,92,0.85)";
|
|
9149
|
+
ctx.lineWidth = 3;
|
|
9150
|
+
ctx.beginPath();
|
|
9151
|
+
ctx.ellipse(cx, trussY, halfW * 1.15, trussRy, 0, 0, TAU2);
|
|
9152
|
+
ctx.stroke();
|
|
9153
|
+
const fixtures = 4 + Math.floor(rand() * 3);
|
|
9154
|
+
for (let i = 0; i < fixtures; i++) {
|
|
9155
|
+
const a = i / fixtures * TAU2;
|
|
9156
|
+
const fxX = cx + Math.cos(a) * halfW * 1.15;
|
|
9157
|
+
const fxY = trussY + Math.sin(a) * trussRy;
|
|
9158
|
+
if (Math.sin(a) > -0.2) {
|
|
9159
|
+
const tx = cx + (rand() - 0.5) * halfW * 0.8;
|
|
9160
|
+
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.14 * (0.7 + nearGlow * 0.4));
|
|
9161
|
+
}
|
|
9162
|
+
drawFixture(ctx, fxX, fxY, 2.2, rigTintStr);
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
9165
|
+
function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr, rand) {
|
|
9166
|
+
const cx = W / 2;
|
|
9167
|
+
const surfR = Math.max(10, scene.voidRadiusM);
|
|
9168
|
+
const nearEdge = Math.max(2, distM - surfR);
|
|
9169
|
+
const farEdge = distM + surfR;
|
|
9170
|
+
const halfYaw = Math.min(78, Math.atan2(surfR * 1.15, distM) * 180 / Math.PI);
|
|
9171
|
+
const nearPitch = Math.atan2(-eyeM, nearEdge) * 180 / Math.PI;
|
|
9172
|
+
const farPitch = Math.atan2(-eyeM, farEdge) * 180 / Math.PI;
|
|
9173
|
+
const yNear = pitchToY(nearPitch);
|
|
9174
|
+
const yFar = pitchToY(farPitch);
|
|
9175
|
+
const halfW = yawToX(halfYaw) - cx;
|
|
9176
|
+
const cy = (yNear + yFar) / 2;
|
|
9177
|
+
const ry = Math.max(10, (yNear - yFar) / 2);
|
|
9178
|
+
const surfCol = ctx.createRadialGradient(cx, cy, 4, cx, cy, halfW);
|
|
9179
|
+
surfCol.addColorStop(0, "#e8f0fb");
|
|
9180
|
+
surfCol.addColorStop(0.7, "#b9c9e2");
|
|
9181
|
+
surfCol.addColorStop(1, "#8fa3c4");
|
|
9182
|
+
ctx.save();
|
|
9183
|
+
ctx.beginPath();
|
|
9184
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU2);
|
|
9185
|
+
ctx.clip();
|
|
9186
|
+
ctx.fillStyle = surfCol;
|
|
9187
|
+
ctx.fillRect(cx - halfW, cy - ry, halfW * 2, ry * 2);
|
|
9188
|
+
ctx.strokeStyle = "rgba(90,120,170,0.5)";
|
|
9189
|
+
ctx.lineWidth = 3;
|
|
9190
|
+
ctx.beginPath();
|
|
9191
|
+
ctx.moveTo(cx, cy - ry);
|
|
9192
|
+
ctx.lineTo(cx, cy + ry);
|
|
9193
|
+
ctx.moveTo(cx - halfW * 0.5, cy - ry);
|
|
9194
|
+
ctx.lineTo(cx - halfW * 0.5, cy + ry);
|
|
9195
|
+
ctx.moveTo(cx + halfW * 0.5, cy - ry);
|
|
9196
|
+
ctx.lineTo(cx + halfW * 0.5, cy + ry);
|
|
9197
|
+
ctx.stroke();
|
|
9198
|
+
ctx.strokeStyle = "rgba(180,90,110,0.45)";
|
|
9199
|
+
ctx.beginPath();
|
|
9200
|
+
ctx.ellipse(cx, cy, ry * 0.9, ry * 0.5, 0, 0, TAU2);
|
|
9201
|
+
ctx.stroke();
|
|
9202
|
+
ctx.restore();
|
|
9203
|
+
ctx.strokeStyle = "rgba(210,225,245,0.6)";
|
|
9204
|
+
ctx.lineWidth = 2.5;
|
|
9205
|
+
ctx.beginPath();
|
|
9206
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU2);
|
|
9207
|
+
ctx.stroke();
|
|
9208
|
+
ctx.fillStyle = "rgba(20,26,40,0.8)";
|
|
9209
|
+
for (let i = 0; i < 5; i++) {
|
|
9210
|
+
const px = cx + (rand() - 0.5) * halfW * 1.4;
|
|
9211
|
+
const py = cy + (rand() - 0.5) * ry * 1.1;
|
|
9212
|
+
const s = Math.max(2, ry * 0.06);
|
|
8573
9213
|
ctx.beginPath();
|
|
8574
|
-
ctx.
|
|
8575
|
-
ctx.lineTo(bx - (sx1 - sx0) * 0.09, sy1);
|
|
8576
|
-
ctx.lineTo(bx + (sx1 - sx0) * 0.09, sy1);
|
|
8577
|
-
ctx.lineTo(bx + 8, pitchToY(Math.min(60, stageTopPitch + 25)));
|
|
8578
|
-
ctx.closePath();
|
|
9214
|
+
ctx.ellipse(px, py, s * 0.6, s, 0, 0, TAU2);
|
|
8579
9215
|
ctx.fill();
|
|
8580
9216
|
}
|
|
8581
|
-
ctx.
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
9217
|
+
const wash = ctx.createRadialGradient(cx, cy, 4, cx, cy, halfW * 1.4);
|
|
9218
|
+
wash.addColorStop(0, "rgba(200,220,255,0.12)");
|
|
9219
|
+
wash.addColorStop(1, "rgba(200,220,255,0)");
|
|
9220
|
+
ctx.save();
|
|
9221
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9222
|
+
ctx.fillStyle = wash;
|
|
9223
|
+
ctx.fillRect(cx - halfW * 1.4, yFar - ry, halfW * 2.8, yNear - yFar + ry * 2);
|
|
9224
|
+
ctx.restore();
|
|
9225
|
+
const sbY = pitchToY(Math.min(66, farPitch + 60));
|
|
9226
|
+
const sbW = Math.max(80, halfW * 0.7);
|
|
9227
|
+
const sbH = sbW * 0.5;
|
|
9228
|
+
const sbGlow = ctx.createRadialGradient(cx, sbY, 4, cx, sbY, sbW * 1.6);
|
|
9229
|
+
sbGlow.addColorStop(0, `rgba(${rigTintStr},0.3)`);
|
|
9230
|
+
sbGlow.addColorStop(1, `rgba(${rigTintStr},0)`);
|
|
9231
|
+
ctx.save();
|
|
9232
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9233
|
+
ctx.fillStyle = sbGlow;
|
|
9234
|
+
ctx.fillRect(cx - sbW * 1.6, sbY - sbW * 1.2, sbW * 3.2, sbW * 2.4);
|
|
9235
|
+
ctx.restore();
|
|
9236
|
+
ctx.fillStyle = "#080b12";
|
|
9237
|
+
ctx.beginPath();
|
|
9238
|
+
ctx.moveTo(cx - sbW / 2, sbY - sbH / 2);
|
|
9239
|
+
ctx.lineTo(cx + sbW / 2, sbY - sbH / 2);
|
|
9240
|
+
ctx.lineTo(cx + sbW * 0.6, sbY);
|
|
9241
|
+
ctx.lineTo(cx + sbW / 2, sbY + sbH / 2);
|
|
9242
|
+
ctx.lineTo(cx - sbW / 2, sbY + sbH / 2);
|
|
9243
|
+
ctx.lineTo(cx - sbW * 0.6, sbY);
|
|
9244
|
+
ctx.closePath();
|
|
9245
|
+
ctx.fill();
|
|
9246
|
+
const screenHue = baseHue;
|
|
9247
|
+
for (let i = 0; i < 3; i++) {
|
|
9248
|
+
const scol = hslToRgb(screenHue + i * 30, 0.5, 0.5);
|
|
9249
|
+
ctx.fillStyle = `rgba(${scol[0]},${scol[1]},${scol[2]},0.75)`;
|
|
9250
|
+
const swid = sbW * 0.26;
|
|
9251
|
+
ctx.fillRect(cx - sbW * 0.42 + i * (swid + sbW * 0.06), sbY - sbH * 0.32, swid, sbH * 0.64);
|
|
9252
|
+
}
|
|
9253
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.9)`;
|
|
9254
|
+
for (let i = 0; i < 8; i++) {
|
|
9255
|
+
const lx = cx - sbW * 0.6 + sbW * 1.2 * i / 7;
|
|
8586
9256
|
ctx.beginPath();
|
|
8587
|
-
ctx.arc(
|
|
9257
|
+
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0, TAU2);
|
|
8588
9258
|
ctx.fill();
|
|
8589
9259
|
}
|
|
8590
|
-
|
|
8591
|
-
|
|
9260
|
+
}
|
|
9261
|
+
function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM, dx, dy, hasRelief, scene) {
|
|
9262
|
+
const own = seat.sectionId;
|
|
9263
|
+
let seatAhead = false;
|
|
9264
|
+
const figs = [];
|
|
8592
9265
|
for (const other of neighborSeats) {
|
|
8593
9266
|
if (other.id === seat.id) continue;
|
|
8594
9267
|
const ox = other.x - seat.x;
|
|
8595
9268
|
const oy = other.y - seat.y;
|
|
8596
9269
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
8597
|
-
if (d <
|
|
9270
|
+
if (own && other.sectionId === own && d < 3 && ox * dx + oy * dy > 0) seatAhead = true;
|
|
9271
|
+
if (d < 0.3 || d > 17) continue;
|
|
8598
9272
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8599
9273
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8600
|
-
const
|
|
8601
|
-
const
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
9274
|
+
const rise = hasRelief ? (other.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - eyeM : 0;
|
|
9275
|
+
const headPitch = Math.atan2(rise - 0.28, d) * 180 / Math.PI;
|
|
9276
|
+
if (headPitch > 10 && d > 5) continue;
|
|
9277
|
+
let hh = 0;
|
|
9278
|
+
for (let k = 0; k < other.id.length; k++) hh = hh * 31 + other.id.charCodeAt(k) & 65535;
|
|
9279
|
+
const jitter = 0.9 + (hh & 15) / 40;
|
|
9280
|
+
const r = Math.min(78, Math.atan2(0.16, d) * 180 / Math.PI * PX_PER_DEG * jitter);
|
|
9281
|
+
if (r < 2) continue;
|
|
9282
|
+
figs.push({ hx: yawToX(yaw), hy: pitchToY(headPitch), r, d, hash: hh });
|
|
9283
|
+
}
|
|
9284
|
+
figs.sort((a, b) => b.d - a.d);
|
|
9285
|
+
const CLOTHES = [[22, 26, 40], [28, 27, 42], [24, 31, 46], [31, 30, 47], [20, 24, 36]];
|
|
9286
|
+
for (const f of figs) {
|
|
9287
|
+
const alpha = f.d < 12 ? 0.95 : Math.max(0.66, 0.95 - (f.d - 12) / 18);
|
|
9288
|
+
const lift = Math.round((1 - Math.min(1, f.d / 16)) * 10);
|
|
9289
|
+
const headTone = [14 + (f.hash >> 4 & 6) + lift, 17 + (f.hash >> 4 & 6) + lift, 25 + (f.hash >> 4 & 6) + lift];
|
|
9290
|
+
const clo0 = CLOTHES[f.hash % CLOTHES.length];
|
|
9291
|
+
const clothing = [clo0[0] + lift, clo0[1] + lift, clo0[2] + lift];
|
|
9292
|
+
if (f.r >= 13) {
|
|
9293
|
+
const rim = f.d < 9 ? 0.16 * (1 - f.d / 9) + (scene.mode === "proscenium" ? 0.06 : 0) : 0;
|
|
9294
|
+
drawSeated(ctx, f.hx, f.hy, f.r, {
|
|
9295
|
+
tone: headTone,
|
|
9296
|
+
clothing,
|
|
9297
|
+
alpha,
|
|
9298
|
+
tilt: ((f.hash >> 2 & 7) - 3.5) * 0.018,
|
|
9299
|
+
// ±~7°
|
|
9300
|
+
shoulderK: 1.72 + (f.hash >> 5 & 7) / 22,
|
|
9301
|
+
// ~1.72..2.04
|
|
9302
|
+
hair: f.hash % HAIR_KINDS,
|
|
9303
|
+
rim
|
|
9304
|
+
});
|
|
9305
|
+
} else if (f.r >= 5.5) {
|
|
9306
|
+
drawSeatedMid(ctx, f.hx, f.hy, f.r, clothing, headTone, alpha);
|
|
9307
|
+
} else {
|
|
9308
|
+
ctx.fillStyle = `rgba(${headTone[0]},${headTone[1]},${headTone[2]},${alpha.toFixed(3)})`;
|
|
9309
|
+
ctx.beginPath();
|
|
9310
|
+
ctx.arc(f.hx, f.hy, f.r, 0, TAU2);
|
|
9311
|
+
ctx.fill();
|
|
9312
|
+
ctx.beginPath();
|
|
9313
|
+
ctx.ellipse(f.hx, f.hy + f.r * 1.4, f.r * 1.85, f.r * 0.95, 0, Math.PI, 0, true);
|
|
9314
|
+
ctx.fill();
|
|
9315
|
+
}
|
|
9316
|
+
}
|
|
9317
|
+
if (seatAhead) {
|
|
9318
|
+
const railTop = pitchToY(-34);
|
|
9319
|
+
ctx.fillStyle = "rgba(6, 8, 14, 0.92)";
|
|
9320
|
+
ctx.fillRect(0, railTop, W, H - railTop);
|
|
9321
|
+
ctx.fillStyle = "rgba(24, 30, 46, 0.9)";
|
|
9322
|
+
const humps = 16;
|
|
9323
|
+
for (let i = 0; i < humps; i++) {
|
|
9324
|
+
const cx = (i + 0.5) / humps * W;
|
|
9325
|
+
ctx.beginPath();
|
|
9326
|
+
ctx.ellipse(cx, railTop + 6, W / humps * 0.42, 22, 0, Math.PI, 0, true);
|
|
9327
|
+
ctx.fill();
|
|
9328
|
+
}
|
|
8610
9329
|
}
|
|
8611
|
-
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
8612
|
-
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
8613
|
-
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
8614
|
-
poleFade.addColorStop(0.88, "rgba(0,0,0,0)");
|
|
8615
|
-
poleFade.addColorStop(1, "rgba(0,0,0,0.85)");
|
|
8616
|
-
ctx.fillStyle = poleFade;
|
|
8617
|
-
ctx.fillRect(0, 0, W, H);
|
|
8618
|
-
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
8619
9330
|
}
|
|
8620
9331
|
var TW = 480;
|
|
8621
9332
|
var TH = 270;
|
|
@@ -8631,12 +9342,16 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8631
9342
|
const dx = focalPoint.x - seat.x;
|
|
8632
9343
|
const dy = focalPoint.y - seat.y;
|
|
8633
9344
|
const distM = Math.max(2, Math.hypot(dx, dy) * UNIT);
|
|
9345
|
+
const rand = rng(fnv1a(`${seat.id}|${Math.round(focalPoint.x)},${Math.round(focalPoint.y)}`));
|
|
9346
|
+
const baseHue = 210 + rand() * 150;
|
|
9347
|
+
const rigTint = hslToRgb(220 + (rand() - 0.5) * 60, 0.55, 0.82);
|
|
9348
|
+
const rigTintStr = `${rigTint[0]},${rigTint[1]},${rigTint[2]}`;
|
|
8634
9349
|
const sky = ctx.createLinearGradient(0, 0, 0, TH);
|
|
8635
|
-
sky.addColorStop(0, "#
|
|
8636
|
-
sky.addColorStop(0.44, "#
|
|
8637
|
-
sky.addColorStop(0.5, "#
|
|
8638
|
-
sky.addColorStop(0.58, "#
|
|
8639
|
-
sky.addColorStop(1, "#
|
|
9350
|
+
sky.addColorStop(0, "#080b12");
|
|
9351
|
+
sky.addColorStop(0.44, "#151c32");
|
|
9352
|
+
sky.addColorStop(0.5, "#1d2644");
|
|
9353
|
+
sky.addColorStop(0.58, "#161c2e");
|
|
9354
|
+
sky.addColorStop(1, "#131829");
|
|
8640
9355
|
ctx.fillStyle = sky;
|
|
8641
9356
|
ctx.fillRect(0, 0, TW, TH);
|
|
8642
9357
|
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
@@ -8650,58 +9365,102 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8650
9365
|
const sy1 = pitchToTY(stageBasePitch);
|
|
8651
9366
|
const sw = sx1 - sx0;
|
|
8652
9367
|
const sh = sy1 - sy0;
|
|
8653
|
-
const
|
|
8654
|
-
|
|
8655
|
-
glow.
|
|
9368
|
+
const nearGlow = Math.max(0.35, Math.min(1, 1 - (distM - 6) / 60));
|
|
9369
|
+
const archTop = pitchToTY(Math.min(HALF_VFOV - 1, stageTopPitch + 8));
|
|
9370
|
+
const glow = ctx.createRadialGradient(TW / 2, (sy0 + sy1) / 2, 4, TW / 2, (sy0 + sy1) / 2, sw * 1.15);
|
|
9371
|
+
glow.addColorStop(0, "rgba(255,214,150,0.34)");
|
|
9372
|
+
glow.addColorStop(0.28, "rgba(150,150,230,0.3)");
|
|
9373
|
+
glow.addColorStop(0.6, "rgba(99,102,241,0.12)");
|
|
8656
9374
|
glow.addColorStop(1, "rgba(99,102,241,0)");
|
|
8657
9375
|
ctx.fillStyle = glow;
|
|
8658
9376
|
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
8659
|
-
|
|
8660
|
-
|
|
9377
|
+
const topCol = hslToRgb(baseHue, 0.62, 0.55);
|
|
9378
|
+
const midCol = hslToRgb(baseHue + 40, 0.7, 0.5);
|
|
9379
|
+
const botCol = hslToRgb(baseHue + 70, 0.78, 0.52);
|
|
9380
|
+
ctx.save();
|
|
9381
|
+
ctx.beginPath();
|
|
9382
|
+
ctx.rect(sx0, sy0, sw, sh);
|
|
9383
|
+
ctx.clip();
|
|
8661
9384
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8662
|
-
backdrop.addColorStop(0,
|
|
8663
|
-
backdrop.addColorStop(0.5,
|
|
8664
|
-
backdrop.addColorStop(1,
|
|
8665
|
-
ctx.globalAlpha = 0.
|
|
9385
|
+
backdrop.addColorStop(0, `rgb(${topCol[0]},${topCol[1]},${topCol[2]})`);
|
|
9386
|
+
backdrop.addColorStop(0.5, `rgb(${midCol[0]},${midCol[1]},${midCol[2]})`);
|
|
9387
|
+
backdrop.addColorStop(1, `rgb(${botCol[0]},${botCol[1]},${botCol[2]})`);
|
|
9388
|
+
ctx.globalAlpha = 0.8;
|
|
8666
9389
|
ctx.fillStyle = backdrop;
|
|
8667
|
-
|
|
8668
|
-
ctx.fillRect(sx0 + inset, sy0 + sh * 0.12, sw - inset * 2, sh * 0.62);
|
|
9390
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8669
9391
|
ctx.globalAlpha = 1;
|
|
8670
|
-
ctx.
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
ctx.lineTo(bx - sw * 0.09, sy1);
|
|
8689
|
-
ctx.lineTo(bx + sw * 0.09, sy1);
|
|
8690
|
-
ctx.lineTo(bx + 5, pitchToTY(Math.min(HALF_VFOV, stageTopPitch + 18)));
|
|
8691
|
-
ctx.closePath();
|
|
8692
|
-
ctx.fill();
|
|
9392
|
+
const fall = ctx.createRadialGradient(TW / 2, (sy0 + sy1) / 2, sw * 0.12, TW / 2, (sy0 + sy1) / 2, sw * 0.6);
|
|
9393
|
+
fall.addColorStop(0, "rgba(6,8,14,0)");
|
|
9394
|
+
fall.addColorStop(0.7, "rgba(6,8,14,0.15)");
|
|
9395
|
+
fall.addColorStop(1, "rgba(5,6,11,0.9)");
|
|
9396
|
+
ctx.fillStyle = fall;
|
|
9397
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
9398
|
+
ctx.restore();
|
|
9399
|
+
const perfCount = 3;
|
|
9400
|
+
const floorY = sy1 - sh * 0.08;
|
|
9401
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9402
|
+
const t2 = (i + 1) / (perfCount + 1);
|
|
9403
|
+
drawPerformer(ctx, sx0 + sw * (0.24 + t2 * 0.52), floorY, sh * 0.46 * (0.9 + rand() * 0.24), {
|
|
9404
|
+
tone: [8, 10, 16],
|
|
9405
|
+
alpha: 0.9,
|
|
9406
|
+
pose: Math.floor(rand() * 4),
|
|
9407
|
+
rim: hslToRgb(baseHue + 20, 0.5, 0.7),
|
|
9408
|
+
rimStrength: 0.7
|
|
9409
|
+
});
|
|
8693
9410
|
}
|
|
8694
|
-
ctx.
|
|
8695
|
-
ctx.
|
|
9411
|
+
ctx.fillStyle = "#05070d";
|
|
9412
|
+
ctx.fillRect(sx0 - sw * 0.5, archTop, sw * 0.5, sy1 - archTop);
|
|
9413
|
+
ctx.fillRect(sx1, archTop, sw * 0.5, sy1 - archTop);
|
|
9414
|
+
const legBot = sw * 0.05 + 2;
|
|
9415
|
+
const legTop = legBot * 1.35;
|
|
9416
|
+
ctx.fillStyle = "#03050a";
|
|
9417
|
+
ctx.beginPath();
|
|
9418
|
+
ctx.moveTo(sx0, sy1);
|
|
9419
|
+
ctx.lineTo(sx0 - legBot, sy1);
|
|
9420
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9421
|
+
ctx.lineTo(sx0 + sw * 0.012, archTop);
|
|
9422
|
+
ctx.closePath();
|
|
9423
|
+
ctx.fill();
|
|
9424
|
+
ctx.beginPath();
|
|
9425
|
+
ctx.moveTo(sx1, sy1);
|
|
9426
|
+
ctx.lineTo(sx1 + legBot, sy1);
|
|
9427
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9428
|
+
ctx.lineTo(sx1 - sw * 0.012, archTop);
|
|
9429
|
+
ctx.closePath();
|
|
9430
|
+
ctx.fill();
|
|
9431
|
+
ctx.fillRect(sx0 - legTop, archTop, sw + legTop * 2, (sy1 - archTop) * 0.13);
|
|
9432
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.12)`;
|
|
9433
|
+
ctx.fillRect(sx0 - 1.2, archTop, 1.2, sy1 - archTop);
|
|
9434
|
+
ctx.fillRect(sx1, archTop, 1.2, sy1 - archTop);
|
|
9435
|
+
ctx.fillStyle = "#0b0f18";
|
|
9436
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.05 + 2);
|
|
9437
|
+
const foot = ctx.createLinearGradient(0, sy1 - sh * 0.05, 0, sy1 + sh * 0.14);
|
|
9438
|
+
foot.addColorStop(0, `rgba(255,206,140,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9439
|
+
foot.addColorStop(1, "rgba(255,206,140,0)");
|
|
9440
|
+
ctx.fillStyle = foot;
|
|
9441
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.22);
|
|
9442
|
+
for (let i = 0; i < 3; i++) {
|
|
9443
|
+
const t2 = (i + 1) / 4;
|
|
9444
|
+
const fx = sx0 + sw * t2;
|
|
9445
|
+
const fy = pitchToTY(Math.min(HALF_VFOV - 1, stageTopPitch + 20));
|
|
9446
|
+
drawBeam(ctx, fx, fy, fx + (rand() - 0.5) * sw * 0.14, sy1, 3, sw * 0.09, rigTintStr, 0.17 * nearGlow);
|
|
9447
|
+
drawFixture(ctx, fx, fy, 1.8, rigTintStr);
|
|
9448
|
+
}
|
|
9449
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.75)`;
|
|
8696
9450
|
for (let i = 0; i < 8; i++) {
|
|
8697
9451
|
ctx.beginPath();
|
|
8698
|
-
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0,
|
|
9452
|
+
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0, TAU2);
|
|
8699
9453
|
ctx.fill();
|
|
8700
9454
|
}
|
|
8701
9455
|
ctx.fillStyle = "rgba(10,13,20,0.9)";
|
|
8702
9456
|
ctx.fillRect(0, TH - 26, TW, 26);
|
|
8703
9457
|
ctx.fillStyle = "rgba(30,37,54,0.9)";
|
|
8704
|
-
for (let i = 0; i < 9; i++)
|
|
9458
|
+
for (let i = 0; i < 9; i++) {
|
|
9459
|
+
const bx = i * (TW / 9) + TW / 18;
|
|
9460
|
+
ctx.beginPath();
|
|
9461
|
+
ctx.ellipse(bx, TH - 22, TW / 9 * 0.4, 15, 0, Math.PI, 0, true);
|
|
9462
|
+
ctx.fill();
|
|
9463
|
+
}
|
|
8705
9464
|
return { url: canvas.toDataURL("image/jpeg", 0.8), distanceM: Math.round(distM) };
|
|
8706
9465
|
}
|
|
8707
9466
|
|