@seatlayer/core 0.28.3 → 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 +921 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1463
- package/dist/index.d.ts +51 -1463
- package/dist/index.js +930 -161
- 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);
|
|
@@ -7406,8 +7371,12 @@ var PickerController = class {
|
|
|
7406
7371
|
this.emitSelectionChange();
|
|
7407
7372
|
return true;
|
|
7408
7373
|
}
|
|
7409
|
-
/**
|
|
7410
|
-
|
|
7374
|
+
/**
|
|
7375
|
+
* Atomically replace an active variable-table hold with a new guest count.
|
|
7376
|
+
* `ttlMs` keeps the host's checkout window across the replacement — without it
|
|
7377
|
+
* a guest-count change silently re-based the countdown on the server default.
|
|
7378
|
+
*/
|
|
7379
|
+
async replaceTableQuantity(label, quantity, ttlMs) {
|
|
7411
7380
|
const table = this.groupedTablesByLabel.get(label);
|
|
7412
7381
|
const hold = this.hold_;
|
|
7413
7382
|
if (!table || table.mode !== "variable" || !hold?.labels.includes(label)) return null;
|
|
@@ -7424,7 +7393,7 @@ var PickerController = class {
|
|
|
7424
7393
|
quantity: item.label === label ? q : item.quantity
|
|
7425
7394
|
}));
|
|
7426
7395
|
if (!selections.some((item) => item.label === label)) return null;
|
|
7427
|
-
const result = await this.api.hold(this.key, selections,
|
|
7396
|
+
const result = await this.api.hold(this.key, selections, ttlMs, hold.holdId);
|
|
7428
7397
|
this.tableQuantities.set(label, q);
|
|
7429
7398
|
this.setHold({
|
|
7430
7399
|
holdId: result.holdId,
|
|
@@ -7708,6 +7677,10 @@ var PickerController = class {
|
|
|
7708
7677
|
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
7709
7678
|
* locally; when none matches the requested quantity it falls back to the
|
|
7710
7679
|
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
7680
|
+
*
|
|
7681
|
+
* `ttlMs` is the host's checkout window and MUST reach both branches: a hold
|
|
7682
|
+
* born here is the same buyer commitment as one made by clicking seats, so
|
|
7683
|
+
* omitting it silently handed the buyer the server default instead.
|
|
7711
7684
|
*/
|
|
7712
7685
|
async bestAvailable(qty, categoryKey, opts = {}) {
|
|
7713
7686
|
const r = this.renderer;
|
|
@@ -7722,7 +7695,7 @@ var PickerController = class {
|
|
|
7722
7695
|
const resolved = seat ? this.toSeat(seat) : null;
|
|
7723
7696
|
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
7724
7697
|
});
|
|
7725
|
-
const result = await this.api.hold(this.key, selection);
|
|
7698
|
+
const result = await this.api.hold(this.key, selection, opts.ttlMs);
|
|
7726
7699
|
r.clearSelection();
|
|
7727
7700
|
const ids = this.idsForLabels(block);
|
|
7728
7701
|
if (ids.length) r.setStatus(ids, "held");
|
|
@@ -7741,7 +7714,7 @@ var PickerController = class {
|
|
|
7741
7714
|
}
|
|
7742
7715
|
if (this.hold_ && !await this.release()) return null;
|
|
7743
7716
|
try {
|
|
7744
|
-
const result = await this.api.bestAvailable(this.key, qty, categoryKey, opts.zoneId);
|
|
7717
|
+
const result = await this.api.bestAvailable(this.key, qty, categoryKey, opts.zoneId, opts.ttlMs);
|
|
7745
7718
|
r.clearSelection();
|
|
7746
7719
|
const ids = this.idsForLabels(result.labels);
|
|
7747
7720
|
if (ids.length) r.setStatus(ids, "held");
|
|
@@ -7762,8 +7735,10 @@ var PickerController = class {
|
|
|
7762
7735
|
* cart can hold best-available seats that aren't in the renderer selection);
|
|
7763
7736
|
* defaults to the renderer selection. Reuses an existing exact-match hold, else
|
|
7764
7737
|
* holds first. Returns the labels booked, or null on failure (conflicts painted).
|
|
7738
|
+
* `ttlMs` only matters on the hold-first path: if the book call then fails, the
|
|
7739
|
+
* seats sit held for the host's window rather than the longer server default.
|
|
7765
7740
|
*/
|
|
7766
|
-
async book(bookingRef, labelsArg) {
|
|
7741
|
+
async book(bookingRef, labelsArg, ttlMs) {
|
|
7767
7742
|
const r = this.renderer;
|
|
7768
7743
|
if (!r) return null;
|
|
7769
7744
|
if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
|
|
@@ -7786,7 +7761,7 @@ var PickerController = class {
|
|
|
7786
7761
|
...this.tableQuantityRequest(resolved)
|
|
7787
7762
|
};
|
|
7788
7763
|
}),
|
|
7789
|
-
|
|
7764
|
+
ttlMs,
|
|
7790
7765
|
replaceHoldId
|
|
7791
7766
|
);
|
|
7792
7767
|
holdId = h.holdId;
|
|
@@ -8499,8 +8474,428 @@ function stageSightlinePitch(eyeHeightM, distM) {
|
|
|
8499
8474
|
var W = 2048;
|
|
8500
8475
|
var H = 1024;
|
|
8501
8476
|
var UNIT = METRES_PER_CHART_UNIT;
|
|
8477
|
+
var TAU2 = Math.PI * 2;
|
|
8478
|
+
var PX_PER_DEG = H / 180;
|
|
8502
8479
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
8503
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
|
+
}
|
|
8504
8899
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
8505
8900
|
const canvas = document.createElement("canvas");
|
|
8506
8901
|
canvas.width = W;
|
|
@@ -8510,102 +8905,428 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
8510
8905
|
const dy = focalPoint.y - seat.y;
|
|
8511
8906
|
const distU = Math.hypot(dx, dy);
|
|
8512
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]}`;
|
|
8513
8914
|
const sky = ctx.createLinearGradient(0, 0, 0, H);
|
|
8514
|
-
sky.addColorStop(0, "#
|
|
8515
|
-
sky.addColorStop(0.42, "#
|
|
8516
|
-
sky.addColorStop(0.5, "#
|
|
8517
|
-
sky.addColorStop(0.56, "#
|
|
8518
|
-
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");
|
|
8519
8921
|
ctx.fillStyle = sky;
|
|
8520
8922
|
ctx.fillRect(0, 0, W, H);
|
|
8521
|
-
const
|
|
8522
|
-
|
|
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));
|
|
8523
8934
|
const sightline = stageSightlinePitch(eyeM, distM);
|
|
8935
|
+
const stageHalfYaw = Math.min(80, Math.atan2(4, distM) * 180 / Math.PI);
|
|
8524
8936
|
const stageTopPitch = Math.min(45, sightline.topPitch);
|
|
8525
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) {
|
|
8526
8965
|
const sx0 = yawToX(-stageHalfYaw);
|
|
8527
8966
|
const sx1 = yawToX(stageHalfYaw);
|
|
8528
8967
|
const sy0 = pitchToY(stageTopPitch);
|
|
8529
8968
|
const sy1 = pitchToY(stageBasePitch);
|
|
8530
|
-
const
|
|
8531
|
-
|
|
8532
|
-
|
|
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)");
|
|
8533
8977
|
glow.addColorStop(1, "rgba(99, 102, 241, 0)");
|
|
8534
8978
|
ctx.fillStyle = glow;
|
|
8535
|
-
ctx.fillRect(sx0 -
|
|
8536
|
-
|
|
8537
|
-
|
|
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();
|
|
8538
8987
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8539
|
-
backdrop.addColorStop(0,
|
|
8540
|
-
backdrop.addColorStop(0.5,
|
|
8541
|
-
backdrop.addColorStop(1,
|
|
8542
|
-
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;
|
|
8543
8992
|
ctx.fillStyle = backdrop;
|
|
8544
|
-
|
|
8545
|
-
ctx.fillRect(sx0 + inset, sy0 + (sy1 - sy0) * 0.12, sx1 - sx0 - inset * 2, (sy1 - sy0) * 0.62);
|
|
8993
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8546
8994
|
ctx.globalAlpha = 1;
|
|
8547
|
-
ctx.
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
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
|
+
});
|
|
8558
9015
|
}
|
|
8559
|
-
ctx.
|
|
8560
|
-
ctx.
|
|
8561
|
-
|
|
8562
|
-
|
|
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
|
+
});
|
|
9145
|
+
}
|
|
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);
|
|
8563
9213
|
ctx.beginPath();
|
|
8564
|
-
ctx.
|
|
8565
|
-
ctx.lineTo(bx - (sx1 - sx0) * 0.09, sy1);
|
|
8566
|
-
ctx.lineTo(bx + (sx1 - sx0) * 0.09, sy1);
|
|
8567
|
-
ctx.lineTo(bx + 8, pitchToY(Math.min(60, stageTopPitch + 25)));
|
|
8568
|
-
ctx.closePath();
|
|
9214
|
+
ctx.ellipse(px, py, s * 0.6, s, 0, 0, TAU2);
|
|
8569
9215
|
ctx.fill();
|
|
8570
9216
|
}
|
|
8571
|
-
ctx.
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
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;
|
|
8576
9256
|
ctx.beginPath();
|
|
8577
|
-
ctx.arc(
|
|
9257
|
+
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0, TAU2);
|
|
8578
9258
|
ctx.fill();
|
|
8579
9259
|
}
|
|
8580
|
-
|
|
8581
|
-
|
|
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 = [];
|
|
8582
9265
|
for (const other of neighborSeats) {
|
|
8583
9266
|
if (other.id === seat.id) continue;
|
|
8584
9267
|
const ox = other.x - seat.x;
|
|
8585
9268
|
const oy = other.y - seat.y;
|
|
8586
9269
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
8587
|
-
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;
|
|
8588
9272
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8589
9273
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8590
|
-
const
|
|
8591
|
-
const
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
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
|
+
}
|
|
8600
9329
|
}
|
|
8601
|
-
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
8602
|
-
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
8603
|
-
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
8604
|
-
poleFade.addColorStop(0.88, "rgba(0,0,0,0)");
|
|
8605
|
-
poleFade.addColorStop(1, "rgba(0,0,0,0.85)");
|
|
8606
|
-
ctx.fillStyle = poleFade;
|
|
8607
|
-
ctx.fillRect(0, 0, W, H);
|
|
8608
|
-
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
8609
9330
|
}
|
|
8610
9331
|
var TW = 480;
|
|
8611
9332
|
var TH = 270;
|
|
@@ -8621,12 +9342,16 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8621
9342
|
const dx = focalPoint.x - seat.x;
|
|
8622
9343
|
const dy = focalPoint.y - seat.y;
|
|
8623
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]}`;
|
|
8624
9349
|
const sky = ctx.createLinearGradient(0, 0, 0, TH);
|
|
8625
|
-
sky.addColorStop(0, "#
|
|
8626
|
-
sky.addColorStop(0.44, "#
|
|
8627
|
-
sky.addColorStop(0.5, "#
|
|
8628
|
-
sky.addColorStop(0.58, "#
|
|
8629
|
-
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");
|
|
8630
9355
|
ctx.fillStyle = sky;
|
|
8631
9356
|
ctx.fillRect(0, 0, TW, TH);
|
|
8632
9357
|
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
@@ -8640,58 +9365,102 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8640
9365
|
const sy1 = pitchToTY(stageBasePitch);
|
|
8641
9366
|
const sw = sx1 - sx0;
|
|
8642
9367
|
const sh = sy1 - sy0;
|
|
8643
|
-
const
|
|
8644
|
-
|
|
8645
|
-
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)");
|
|
8646
9374
|
glow.addColorStop(1, "rgba(99,102,241,0)");
|
|
8647
9375
|
ctx.fillStyle = glow;
|
|
8648
9376
|
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
8649
|
-
|
|
8650
|
-
|
|
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();
|
|
8651
9384
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8652
|
-
backdrop.addColorStop(0,
|
|
8653
|
-
backdrop.addColorStop(0.5,
|
|
8654
|
-
backdrop.addColorStop(1,
|
|
8655
|
-
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;
|
|
8656
9389
|
ctx.fillStyle = backdrop;
|
|
8657
|
-
|
|
8658
|
-
ctx.fillRect(sx0 + inset, sy0 + sh * 0.12, sw - inset * 2, sh * 0.62);
|
|
9390
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8659
9391
|
ctx.globalAlpha = 1;
|
|
8660
|
-
ctx.
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
ctx.lineTo(bx - sw * 0.09, sy1);
|
|
8679
|
-
ctx.lineTo(bx + sw * 0.09, sy1);
|
|
8680
|
-
ctx.lineTo(bx + 5, pitchToTY(Math.min(HALF_VFOV, stageTopPitch + 18)));
|
|
8681
|
-
ctx.closePath();
|
|
8682
|
-
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
|
+
});
|
|
8683
9410
|
}
|
|
8684
|
-
ctx.
|
|
8685
|
-
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)`;
|
|
8686
9450
|
for (let i = 0; i < 8; i++) {
|
|
8687
9451
|
ctx.beginPath();
|
|
8688
|
-
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);
|
|
8689
9453
|
ctx.fill();
|
|
8690
9454
|
}
|
|
8691
9455
|
ctx.fillStyle = "rgba(10,13,20,0.9)";
|
|
8692
9456
|
ctx.fillRect(0, TH - 26, TW, 26);
|
|
8693
9457
|
ctx.fillStyle = "rgba(30,37,54,0.9)";
|
|
8694
|
-
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
|
+
}
|
|
8695
9464
|
return { url: canvas.toDataURL("image/jpeg", 0.8), distanceM: Math.round(distM) };
|
|
8696
9465
|
}
|
|
8697
9466
|
|