@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.cjs
CHANGED
|
@@ -4638,7 +4638,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4638
4638
|
this.accessRingById.set(seat.id, ring);
|
|
4639
4639
|
target.add(ring);
|
|
4640
4640
|
}
|
|
4641
|
-
if (accessGlyphPath(seat.accessibility
|
|
4641
|
+
if (seat.accessibility?.includes("wheelchair") && accessGlyphPath(seat.accessibility)) {
|
|
4642
4642
|
const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
|
|
4643
4643
|
this.accessGlyphById.set(seat.id, glyph);
|
|
4644
4644
|
target.add(glyph);
|
|
@@ -7685,8 +7685,12 @@ var PickerController = class {
|
|
|
7685
7685
|
this.emitSelectionChange();
|
|
7686
7686
|
return true;
|
|
7687
7687
|
}
|
|
7688
|
-
/**
|
|
7689
|
-
|
|
7688
|
+
/**
|
|
7689
|
+
* Atomically replace an active variable-table hold with a new guest count.
|
|
7690
|
+
* `ttlMs` keeps the host's checkout window across the replacement — without it
|
|
7691
|
+
* a guest-count change silently re-based the countdown on the server default.
|
|
7692
|
+
*/
|
|
7693
|
+
async replaceTableQuantity(label, quantity, ttlMs) {
|
|
7690
7694
|
const table = this.groupedTablesByLabel.get(label);
|
|
7691
7695
|
const hold = this.hold_;
|
|
7692
7696
|
if (!table || table.mode !== "variable" || !hold?.labels.includes(label)) return null;
|
|
@@ -7703,7 +7707,7 @@ var PickerController = class {
|
|
|
7703
7707
|
quantity: item.label === label ? q : item.quantity
|
|
7704
7708
|
}));
|
|
7705
7709
|
if (!selections.some((item) => item.label === label)) return null;
|
|
7706
|
-
const result = await this.api.hold(this.key, selections,
|
|
7710
|
+
const result = await this.api.hold(this.key, selections, ttlMs, hold.holdId);
|
|
7707
7711
|
this.tableQuantities.set(label, q);
|
|
7708
7712
|
this.setHold({
|
|
7709
7713
|
holdId: result.holdId,
|
|
@@ -7987,6 +7991,10 @@ var PickerController = class {
|
|
|
7987
7991
|
* failure. With `preferPremium`, first tries to hold the best PREMIUM block
|
|
7988
7992
|
* locally; when none matches the requested quantity it falls back to the
|
|
7989
7993
|
* normal server pick (the widget surfaces a subtle note on that fallback).
|
|
7994
|
+
*
|
|
7995
|
+
* `ttlMs` is the host's checkout window and MUST reach both branches: a hold
|
|
7996
|
+
* born here is the same buyer commitment as one made by clicking seats, so
|
|
7997
|
+
* omitting it silently handed the buyer the server default instead.
|
|
7990
7998
|
*/
|
|
7991
7999
|
async bestAvailable(qty, categoryKey, opts = {}) {
|
|
7992
8000
|
const r = this.renderer;
|
|
@@ -8001,7 +8009,7 @@ var PickerController = class {
|
|
|
8001
8009
|
const resolved = seat ? this.toSeat(seat) : null;
|
|
8002
8010
|
return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
|
|
8003
8011
|
});
|
|
8004
|
-
const result = await this.api.hold(this.key, selection);
|
|
8012
|
+
const result = await this.api.hold(this.key, selection, opts.ttlMs);
|
|
8005
8013
|
r.clearSelection();
|
|
8006
8014
|
const ids = this.idsForLabels(block);
|
|
8007
8015
|
if (ids.length) r.setStatus(ids, "held");
|
|
@@ -8020,7 +8028,7 @@ var PickerController = class {
|
|
|
8020
8028
|
}
|
|
8021
8029
|
if (this.hold_ && !await this.release()) return null;
|
|
8022
8030
|
try {
|
|
8023
|
-
const result = await this.api.bestAvailable(this.key, qty, categoryKey, opts.zoneId);
|
|
8031
|
+
const result = await this.api.bestAvailable(this.key, qty, categoryKey, opts.zoneId, opts.ttlMs);
|
|
8024
8032
|
r.clearSelection();
|
|
8025
8033
|
const ids = this.idsForLabels(result.labels);
|
|
8026
8034
|
if (ids.length) r.setStatus(ids, "held");
|
|
@@ -8041,8 +8049,10 @@ var PickerController = class {
|
|
|
8041
8049
|
* cart can hold best-available seats that aren't in the renderer selection);
|
|
8042
8050
|
* defaults to the renderer selection. Reuses an existing exact-match hold, else
|
|
8043
8051
|
* holds first. Returns the labels booked, or null on failure (conflicts painted).
|
|
8052
|
+
* `ttlMs` only matters on the hold-first path: if the book call then fails, the
|
|
8053
|
+
* seats sit held for the host's window rather than the longer server default.
|
|
8044
8054
|
*/
|
|
8045
|
-
async book(bookingRef, labelsArg) {
|
|
8055
|
+
async book(bookingRef, labelsArg, ttlMs) {
|
|
8046
8056
|
const r = this.renderer;
|
|
8047
8057
|
if (!r) return null;
|
|
8048
8058
|
if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
|
|
@@ -8065,7 +8075,7 @@ var PickerController = class {
|
|
|
8065
8075
|
...this.tableQuantityRequest(resolved)
|
|
8066
8076
|
};
|
|
8067
8077
|
}),
|
|
8068
|
-
|
|
8078
|
+
ttlMs,
|
|
8069
8079
|
replaceHoldId
|
|
8070
8080
|
);
|
|
8071
8081
|
holdId = h.holdId;
|
|
@@ -8778,8 +8788,428 @@ function stageSightlinePitch(eyeHeightM, distM) {
|
|
|
8778
8788
|
var W = 2048;
|
|
8779
8789
|
var H = 1024;
|
|
8780
8790
|
var UNIT = METRES_PER_CHART_UNIT;
|
|
8791
|
+
var TAU2 = Math.PI * 2;
|
|
8792
|
+
var PX_PER_DEG = H / 180;
|
|
8781
8793
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
8782
8794
|
var pitchToY = (pitchDeg) => (90 - pitchDeg) / 180 * H;
|
|
8795
|
+
function fnv1a(str) {
|
|
8796
|
+
let h = 2166136261;
|
|
8797
|
+
for (let i = 0; i < str.length; i++) {
|
|
8798
|
+
h ^= str.charCodeAt(i);
|
|
8799
|
+
h = Math.imul(h, 16777619);
|
|
8800
|
+
}
|
|
8801
|
+
return h >>> 0;
|
|
8802
|
+
}
|
|
8803
|
+
function rng(seed) {
|
|
8804
|
+
let a = seed >>> 0;
|
|
8805
|
+
return () => {
|
|
8806
|
+
a |= 0;
|
|
8807
|
+
a = a + 1831565813 | 0;
|
|
8808
|
+
let t2 = Math.imul(a ^ a >>> 15, 1 | a);
|
|
8809
|
+
t2 = t2 + Math.imul(t2 ^ t2 >>> 7, 61 | t2) ^ t2;
|
|
8810
|
+
return ((t2 ^ t2 >>> 14) >>> 0) / 4294967296;
|
|
8811
|
+
};
|
|
8812
|
+
}
|
|
8813
|
+
var NBINS = 96;
|
|
8814
|
+
var BIN_DEG = 360 / NBINS;
|
|
8815
|
+
var FLAT_DELTA_M = 0.6;
|
|
8816
|
+
var HAZE_SKY = [17, 23, 42];
|
|
8817
|
+
function blendToSky(base, t2, a = 1) {
|
|
8818
|
+
const k = Math.max(0, Math.min(0.58, t2));
|
|
8819
|
+
const r = Math.round(base[0] + (HAZE_SKY[0] - base[0]) * k);
|
|
8820
|
+
const g = Math.round(base[1] + (HAZE_SKY[1] - base[1]) * k);
|
|
8821
|
+
const b = Math.round(base[2] + (HAZE_SKY[2] - base[2]) * k);
|
|
8822
|
+
return `rgba(${r},${g},${b},${a})`;
|
|
8823
|
+
}
|
|
8824
|
+
function mixRgb(a, b, t2) {
|
|
8825
|
+
return [
|
|
8826
|
+
Math.round(a[0] + (b[0] - a[0]) * t2),
|
|
8827
|
+
Math.round(a[1] + (b[1] - a[1]) * t2),
|
|
8828
|
+
Math.round(a[2] + (b[2] - a[2]) * t2)
|
|
8829
|
+
];
|
|
8830
|
+
}
|
|
8831
|
+
function hslToRgb(h, s, l) {
|
|
8832
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
8833
|
+
const hp = (h % 360 + 360) % 360 / 60;
|
|
8834
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
8835
|
+
let r = 0, g = 0, b = 0;
|
|
8836
|
+
if (hp < 1) [r, g, b] = [c, x, 0];
|
|
8837
|
+
else if (hp < 2) [r, g, b] = [x, c, 0];
|
|
8838
|
+
else if (hp < 3) [r, g, b] = [0, c, x];
|
|
8839
|
+
else if (hp < 4) [r, g, b] = [0, x, c];
|
|
8840
|
+
else if (hp < 5) [r, g, b] = [x, 0, c];
|
|
8841
|
+
else [r, g, b] = [c, 0, x];
|
|
8842
|
+
const m = l - c / 2;
|
|
8843
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
8844
|
+
}
|
|
8845
|
+
var tintCache = /* @__PURE__ */ new Map();
|
|
8846
|
+
function tintFromKey(key) {
|
|
8847
|
+
if (!key) return null;
|
|
8848
|
+
const cached = tintCache.get(key);
|
|
8849
|
+
if (cached) return cached;
|
|
8850
|
+
let h = 2166136261;
|
|
8851
|
+
for (let i = 0; i < key.length; i++) {
|
|
8852
|
+
h ^= key.charCodeAt(i);
|
|
8853
|
+
h = Math.imul(h, 16777619);
|
|
8854
|
+
}
|
|
8855
|
+
const hue = (h >>> 0) % 360;
|
|
8856
|
+
const rgb2 = hslToRgb(hue, 0.3, 0.42);
|
|
8857
|
+
tintCache.set(key, rgb2);
|
|
8858
|
+
return rgb2;
|
|
8859
|
+
}
|
|
8860
|
+
function buildStands(seat, neighborSeats, stageBearing, myEyeM) {
|
|
8861
|
+
const bins = new Array(NBINS).fill(null);
|
|
8862
|
+
let maxDelta = 0;
|
|
8863
|
+
const own = seat.sectionId;
|
|
8864
|
+
for (const other of neighborSeats) {
|
|
8865
|
+
if (other.id === seat.id) continue;
|
|
8866
|
+
const os = other.sectionId;
|
|
8867
|
+
if (!os || own && os === own) continue;
|
|
8868
|
+
const ox = other.x - seat.x;
|
|
8869
|
+
const oy = other.y - seat.y;
|
|
8870
|
+
const dM = Math.hypot(ox, oy) * UNIT;
|
|
8871
|
+
if (dM < 1.5 || dM > 95) continue;
|
|
8872
|
+
const otherEye = other.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
8873
|
+
const dTop = otherEye - myEyeM;
|
|
8874
|
+
if (Math.abs(dTop) > maxDelta) maxDelta = Math.abs(dTop);
|
|
8875
|
+
const top = Math.atan2(dTop, dM) * 180 / Math.PI;
|
|
8876
|
+
const base = Math.atan2(otherEye - SEATED_EYE_HEIGHT_M - myEyeM, dM) * 180 / Math.PI;
|
|
8877
|
+
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8878
|
+
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8879
|
+
const idx = Math.min(NBINS - 1, Math.max(0, Math.floor((yaw + 180) / BIN_DEG)));
|
|
8880
|
+
const overhead = top > 34 && dM < 16;
|
|
8881
|
+
const cur = bins[idx];
|
|
8882
|
+
if (!cur) bins[idx] = { top, base, nearM: dM, overhead, tint: tintFromKey(other.categoryKey) };
|
|
8883
|
+
else {
|
|
8884
|
+
if (top > cur.top) cur.top = top;
|
|
8885
|
+
if (base < cur.base) cur.base = base;
|
|
8886
|
+
if (dM < cur.nearM) {
|
|
8887
|
+
cur.nearM = dM;
|
|
8888
|
+
cur.tint = tintFromKey(other.categoryKey);
|
|
8889
|
+
}
|
|
8890
|
+
cur.overhead = cur.overhead || overhead;
|
|
8891
|
+
}
|
|
8892
|
+
}
|
|
8893
|
+
return { bins, maxDelta };
|
|
8894
|
+
}
|
|
8895
|
+
function drawStands(ctx, bins, toX, toY) {
|
|
8896
|
+
for (let i = 0; i < NBINS; i++) {
|
|
8897
|
+
const b = bins[i];
|
|
8898
|
+
if (!b) continue;
|
|
8899
|
+
const yaw0 = -180 + i * BIN_DEG;
|
|
8900
|
+
const x0 = toX(yaw0);
|
|
8901
|
+
const x1 = toX(yaw0 + BIN_DEG);
|
|
8902
|
+
if (!(x1 > x0)) continue;
|
|
8903
|
+
const top = Math.min(72, b.top);
|
|
8904
|
+
const bottom = Math.min(b.base, -4);
|
|
8905
|
+
const yTop = toY(top);
|
|
8906
|
+
const yBot = toY(bottom);
|
|
8907
|
+
if (yBot <= yTop) continue;
|
|
8908
|
+
const haze = b.nearM / 95;
|
|
8909
|
+
const tint = b.tint;
|
|
8910
|
+
const crest = tint ? mixRgb([44, 54, 80], tint, 0.34) : [44, 54, 80];
|
|
8911
|
+
const seating = tint ? mixRgb([24, 30, 48], tint, 0.28) : [24, 30, 48];
|
|
8912
|
+
const grad = ctx.createLinearGradient(0, yTop, 0, yBot);
|
|
8913
|
+
grad.addColorStop(0, blendToSky(crest, haze));
|
|
8914
|
+
grad.addColorStop(0.24, blendToSky(seating, haze));
|
|
8915
|
+
grad.addColorStop(1, blendToSky([10, 13, 22], haze));
|
|
8916
|
+
ctx.fillStyle = grad;
|
|
8917
|
+
ctx.fillRect(x0, yTop, x1 - x0 + 1, yBot - yTop);
|
|
8918
|
+
ctx.fillStyle = `rgba(150,165,205,${0.35 * (1 - haze * 0.55)})`;
|
|
8919
|
+
ctx.fillRect(x0, yTop, x1 - x0 + 1, 1.5);
|
|
8920
|
+
const bandBottom = Math.min(yBot, toY(0));
|
|
8921
|
+
const bandSpan = bandBottom - yTop;
|
|
8922
|
+
if (bandSpan > 8) {
|
|
8923
|
+
const bands = 6;
|
|
8924
|
+
const contrast = 1 - haze * 0.4;
|
|
8925
|
+
for (let r = 1; r < bands; r++) {
|
|
8926
|
+
const y = yTop + bandSpan * r / bands;
|
|
8927
|
+
ctx.fillStyle = `rgba(0,0,0,${0.16 * contrast})`;
|
|
8928
|
+
ctx.fillRect(x0, y, x1 - x0 + 1, 1.5);
|
|
8929
|
+
ctx.fillStyle = `rgba(120,134,170,${0.1 * contrast})`;
|
|
8930
|
+
ctx.fillRect(x0, y + 1.5, x1 - x0 + 1, 1.2);
|
|
8931
|
+
}
|
|
8932
|
+
}
|
|
8933
|
+
if (b.overhead) {
|
|
8934
|
+
const lipY = toY(Math.min(86, top + 12));
|
|
8935
|
+
const g2 = ctx.createLinearGradient(0, lipY, 0, yTop);
|
|
8936
|
+
g2.addColorStop(0, "rgba(3,4,8,0.9)");
|
|
8937
|
+
g2.addColorStop(1, "rgba(3,4,8,0)");
|
|
8938
|
+
ctx.fillStyle = g2;
|
|
8939
|
+
ctx.fillRect(x0, lipY, x1 - x0 + 1, yTop - lipY);
|
|
8940
|
+
ctx.fillStyle = "rgba(120,130,160,0.14)";
|
|
8941
|
+
ctx.fillRect(x0, yTop - 1.5, x1 - x0 + 1, 1.5);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
function classifyScene(_seat, focal, neighbors) {
|
|
8946
|
+
const NB = 72;
|
|
8947
|
+
const occ = new Array(NB).fill(false);
|
|
8948
|
+
const innerM = new Array(NB).fill(Infinity);
|
|
8949
|
+
const secSet = /* @__PURE__ */ new Set();
|
|
8950
|
+
let n = 0;
|
|
8951
|
+
for (const s of neighbors) {
|
|
8952
|
+
n++;
|
|
8953
|
+
if (s.sectionId) secSet.add(s.sectionId);
|
|
8954
|
+
const dx = s.x - focal.x;
|
|
8955
|
+
const dy = s.y - focal.y;
|
|
8956
|
+
const dM = Math.hypot(dx, dy) * UNIT;
|
|
8957
|
+
if (dM < 0.5) continue;
|
|
8958
|
+
const bearing = Math.atan2(dx, -dy);
|
|
8959
|
+
let idx = Math.floor((bearing + Math.PI) / TAU2 * NB);
|
|
8960
|
+
if (idx < 0) idx = 0;
|
|
8961
|
+
else if (idx >= NB) idx = NB - 1;
|
|
8962
|
+
occ[idx] = true;
|
|
8963
|
+
if (dM < innerM[idx]) innerM[idx] = dM;
|
|
8964
|
+
}
|
|
8965
|
+
let occN = 0;
|
|
8966
|
+
const inner = [];
|
|
8967
|
+
for (let i = 0; i < NB; i++) if (occ[i]) {
|
|
8968
|
+
occN++;
|
|
8969
|
+
inner.push(innerM[i]);
|
|
8970
|
+
}
|
|
8971
|
+
const coverage = occN / NB * 360;
|
|
8972
|
+
inner.sort((a, b) => a - b);
|
|
8973
|
+
const median = inner.length ? inner[inner.length >> 1] : 0;
|
|
8974
|
+
const lo = inner.length ? inner[Math.floor(inner.length * 0.15)] ?? median : 0;
|
|
8975
|
+
const hi = inner.length ? inner[Math.floor(inner.length * 0.85)] ?? median : 0;
|
|
8976
|
+
const aspect = lo > 0.5 ? hi / lo : 1;
|
|
8977
|
+
const seed = fnv1a(`${n}|${[...secSet].sort().join(",")}|${Math.round(focal.x)},${Math.round(focal.y)}`);
|
|
8978
|
+
let mode;
|
|
8979
|
+
if (coverage >= 300 && median > 11 && aspect > 1.35) mode = "sport";
|
|
8980
|
+
else if (coverage >= 285) mode = "arena";
|
|
8981
|
+
else mode = "proscenium";
|
|
8982
|
+
return { mode, seed, voidRadiusM: median, aspect };
|
|
8983
|
+
}
|
|
8984
|
+
var HAIR_KINDS = 5;
|
|
8985
|
+
function drawHair(ctx, r, kind) {
|
|
8986
|
+
switch (kind) {
|
|
8987
|
+
case 1:
|
|
8988
|
+
ctx.beginPath();
|
|
8989
|
+
ctx.ellipse(0, -r * 0.14, r * 1.16, r * 1.16, 0, 0, TAU2);
|
|
8990
|
+
ctx.fill();
|
|
8991
|
+
break;
|
|
8992
|
+
case 2:
|
|
8993
|
+
ctx.beginPath();
|
|
8994
|
+
ctx.ellipse(0, -r * 1.02, r * 0.42, r * 0.42, 0, 0, TAU2);
|
|
8995
|
+
ctx.fill();
|
|
8996
|
+
ctx.beginPath();
|
|
8997
|
+
ctx.ellipse(0, -r * 0.2, r * 0.98, r * 1.02, 0, 0, TAU2);
|
|
8998
|
+
ctx.fill();
|
|
8999
|
+
break;
|
|
9000
|
+
case 3:
|
|
9001
|
+
ctx.beginPath();
|
|
9002
|
+
ctx.ellipse(0, -r * 0.36, r * 1.04, r * 0.6, 0, Math.PI, TAU2);
|
|
9003
|
+
ctx.fill();
|
|
9004
|
+
ctx.beginPath();
|
|
9005
|
+
ctx.ellipse(0, -r * 0.52, r * 1.22, r * 0.24, 0, 0, TAU2);
|
|
9006
|
+
ctx.fill();
|
|
9007
|
+
break;
|
|
9008
|
+
case 4:
|
|
9009
|
+
ctx.beginPath();
|
|
9010
|
+
ctx.ellipse(-r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU2);
|
|
9011
|
+
ctx.fill();
|
|
9012
|
+
ctx.beginPath();
|
|
9013
|
+
ctx.ellipse(r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU2);
|
|
9014
|
+
ctx.fill();
|
|
9015
|
+
ctx.beginPath();
|
|
9016
|
+
ctx.ellipse(0, -r * 0.08, r * 1.06, r * 1.08, 0, 0, TAU2);
|
|
9017
|
+
ctx.fill();
|
|
9018
|
+
break;
|
|
9019
|
+
default:
|
|
9020
|
+
break;
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
function drawSeated(ctx, hx, hy, r, o) {
|
|
9024
|
+
const neckHalf = r * 0.44;
|
|
9025
|
+
const shHalf = r * o.shoulderK;
|
|
9026
|
+
const neckTopY = r * 0.66;
|
|
9027
|
+
const shoulderY = r * 1.68;
|
|
9028
|
+
const botY = r * 5.4;
|
|
9029
|
+
const clo = `rgba(${o.clothing[0]},${o.clothing[1]},${o.clothing[2]},${o.alpha})`;
|
|
9030
|
+
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
9031
|
+
ctx.save();
|
|
9032
|
+
ctx.translate(hx, hy);
|
|
9033
|
+
ctx.fillStyle = clo;
|
|
9034
|
+
ctx.beginPath();
|
|
9035
|
+
ctx.moveTo(-neckHalf, neckTopY);
|
|
9036
|
+
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - r * 0.6, -shHalf, shoulderY);
|
|
9037
|
+
ctx.quadraticCurveTo(-shHalf * 1.05, shoulderY + r * 1.2, -shHalf * 0.94, botY);
|
|
9038
|
+
ctx.lineTo(shHalf * 0.94, botY);
|
|
9039
|
+
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY);
|
|
9040
|
+
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY - r * 0.6, neckHalf, neckTopY);
|
|
9041
|
+
ctx.closePath();
|
|
9042
|
+
ctx.fill();
|
|
9043
|
+
ctx.fillStyle = skin;
|
|
9044
|
+
ctx.fillRect(-neckHalf * 0.82, r * 0.5, neckHalf * 1.64, r * 0.9);
|
|
9045
|
+
ctx.save();
|
|
9046
|
+
ctx.rotate(o.tilt);
|
|
9047
|
+
ctx.fillStyle = skin;
|
|
9048
|
+
ctx.beginPath();
|
|
9049
|
+
ctx.ellipse(0, 0, r * 0.9, r * 1.06, 0, 0, TAU2);
|
|
9050
|
+
ctx.fill();
|
|
9051
|
+
drawHair(ctx, r, o.hair);
|
|
9052
|
+
if (o.rim > 0.01) {
|
|
9053
|
+
ctx.strokeStyle = `rgba(245,205,150,${(0.5 * o.rim).toFixed(3)})`;
|
|
9054
|
+
ctx.lineWidth = Math.max(1, r * 0.14);
|
|
9055
|
+
ctx.beginPath();
|
|
9056
|
+
ctx.ellipse(0, -r * 0.06, r * 0.86, r * 1, 0, Math.PI * 1.12, Math.PI * 1.9);
|
|
9057
|
+
ctx.stroke();
|
|
9058
|
+
}
|
|
9059
|
+
ctx.restore();
|
|
9060
|
+
if (o.rim > 0.01) {
|
|
9061
|
+
ctx.strokeStyle = `rgba(240,200,150,${(0.28 * o.rim).toFixed(3)})`;
|
|
9062
|
+
ctx.lineWidth = Math.max(1, r * 0.12);
|
|
9063
|
+
ctx.beginPath();
|
|
9064
|
+
ctx.moveTo(-shHalf * 0.8, shoulderY - r * 0.05);
|
|
9065
|
+
ctx.quadraticCurveTo(-neckHalf, shoulderY - r * 0.75, 0, shoulderY - r * 0.82);
|
|
9066
|
+
ctx.quadraticCurveTo(neckHalf, shoulderY - r * 0.75, shHalf * 0.8, shoulderY - r * 0.05);
|
|
9067
|
+
ctx.stroke();
|
|
9068
|
+
}
|
|
9069
|
+
ctx.restore();
|
|
9070
|
+
}
|
|
9071
|
+
function drawSeatedMid(ctx, hx, hy, r, clothing, tone, alpha) {
|
|
9072
|
+
ctx.fillStyle = `rgba(${clothing[0]},${clothing[1]},${clothing[2]},${alpha})`;
|
|
9073
|
+
ctx.beginPath();
|
|
9074
|
+
ctx.moveTo(hx - r * 1.9, hy + r * 4);
|
|
9075
|
+
ctx.quadraticCurveTo(hx - r * 1.95, hy + r * 1.2, hx - r * 0.5, hy + r * 0.9);
|
|
9076
|
+
ctx.quadraticCurveTo(hx, hy + r * 0.4, hx + r * 0.5, hy + r * 0.9);
|
|
9077
|
+
ctx.quadraticCurveTo(hx + r * 1.95, hy + r * 1.2, hx + r * 1.9, hy + r * 4);
|
|
9078
|
+
ctx.closePath();
|
|
9079
|
+
ctx.fill();
|
|
9080
|
+
ctx.fillStyle = `rgba(${tone[0]},${tone[1]},${tone[2]},${alpha})`;
|
|
9081
|
+
ctx.beginPath();
|
|
9082
|
+
ctx.ellipse(hx, hy, r * 0.92, r * 1.05, 0, 0, TAU2);
|
|
9083
|
+
ctx.fill();
|
|
9084
|
+
}
|
|
9085
|
+
function drawPerformer(ctx, x, footY, h, o) {
|
|
9086
|
+
const headR = h * 0.1;
|
|
9087
|
+
const headCy = footY - h * 0.9;
|
|
9088
|
+
const shoulderY = footY - h * 0.74;
|
|
9089
|
+
const hipY = footY - h * 0.46;
|
|
9090
|
+
const shHalf = h * 0.13;
|
|
9091
|
+
const hipHalf = h * 0.09;
|
|
9092
|
+
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
9093
|
+
const sh = ctx.createRadialGradient(x, footY, 1, x, footY, h * 0.24);
|
|
9094
|
+
sh.addColorStop(0, "rgba(0,0,0,0.4)");
|
|
9095
|
+
sh.addColorStop(1, "rgba(0,0,0,0)");
|
|
9096
|
+
ctx.save();
|
|
9097
|
+
ctx.scale(1, 0.28);
|
|
9098
|
+
ctx.fillStyle = sh;
|
|
9099
|
+
ctx.beginPath();
|
|
9100
|
+
ctx.ellipse(x, footY / 0.28, h * 0.24, h * 0.24, 0, 0, TAU2);
|
|
9101
|
+
ctx.fill();
|
|
9102
|
+
ctx.restore();
|
|
9103
|
+
ctx.fillStyle = skin;
|
|
9104
|
+
ctx.beginPath();
|
|
9105
|
+
ctx.moveTo(x - hipHalf, hipY);
|
|
9106
|
+
ctx.lineTo(x - hipHalf * 0.7, footY);
|
|
9107
|
+
ctx.lineTo(x - hipHalf * 0.1, footY);
|
|
9108
|
+
ctx.lineTo(x - hipHalf * 0.1, hipY + h * 0.02);
|
|
9109
|
+
ctx.lineTo(x + hipHalf * 0.1, hipY + h * 0.02);
|
|
9110
|
+
ctx.lineTo(x + hipHalf * 0.1, footY);
|
|
9111
|
+
ctx.lineTo(x + hipHalf * 0.7, footY);
|
|
9112
|
+
ctx.lineTo(x + hipHalf, hipY);
|
|
9113
|
+
ctx.closePath();
|
|
9114
|
+
ctx.fill();
|
|
9115
|
+
ctx.beginPath();
|
|
9116
|
+
ctx.moveTo(x - shHalf, shoulderY);
|
|
9117
|
+
ctx.quadraticCurveTo(x - shHalf * 0.9, hipY - h * 0.12, x - hipHalf, hipY);
|
|
9118
|
+
ctx.lineTo(x + hipHalf, hipY);
|
|
9119
|
+
ctx.quadraticCurveTo(x + shHalf * 0.9, hipY - h * 0.12, x + shHalf, shoulderY);
|
|
9120
|
+
ctx.quadraticCurveTo(x, shoulderY - h * 0.05, x - shHalf, shoulderY);
|
|
9121
|
+
ctx.closePath();
|
|
9122
|
+
ctx.fill();
|
|
9123
|
+
ctx.lineCap = "round";
|
|
9124
|
+
ctx.lineJoin = "round";
|
|
9125
|
+
ctx.strokeStyle = skin;
|
|
9126
|
+
ctx.lineWidth = h * 0.055;
|
|
9127
|
+
const armY = shoulderY + h * 0.02;
|
|
9128
|
+
if (o.pose === 1) {
|
|
9129
|
+
ctx.beginPath();
|
|
9130
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
9131
|
+
ctx.lineTo(x - shHalf * 1.5, footY - h * 1.02);
|
|
9132
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
9133
|
+
ctx.lineTo(x + shHalf * 1.5, footY - h * 1.02);
|
|
9134
|
+
ctx.stroke();
|
|
9135
|
+
} else if (o.pose === 2) {
|
|
9136
|
+
ctx.beginPath();
|
|
9137
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
9138
|
+
ctx.lineTo(x + hipHalf * 1.4, hipY - h * 0.02);
|
|
9139
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
9140
|
+
ctx.lineTo(x - hipHalf * 0.4, hipY - h * 0.06);
|
|
9141
|
+
ctx.stroke();
|
|
9142
|
+
ctx.fillStyle = skin;
|
|
9143
|
+
ctx.beginPath();
|
|
9144
|
+
ctx.ellipse(x + hipHalf * 1.5, hipY - h * 0.04, h * 0.07, h * 0.09, -0.5, 0, TAU2);
|
|
9145
|
+
ctx.fill();
|
|
9146
|
+
} else if (o.pose === 3) {
|
|
9147
|
+
ctx.beginPath();
|
|
9148
|
+
ctx.moveTo(x - shHalf * 0.8, armY);
|
|
9149
|
+
ctx.lineTo(x - shHalf * 0.2, headCy + headR * 0.6);
|
|
9150
|
+
ctx.moveTo(x + shHalf * 0.8, armY);
|
|
9151
|
+
ctx.lineTo(x + shHalf * 1.1, hipY);
|
|
9152
|
+
ctx.stroke();
|
|
9153
|
+
ctx.lineWidth = h * 0.02;
|
|
9154
|
+
ctx.beginPath();
|
|
9155
|
+
ctx.moveTo(x - shHalf * 0.2, headCy + headR * 0.9);
|
|
9156
|
+
ctx.lineTo(x - shHalf * 0.2, footY - h * 0.02);
|
|
9157
|
+
ctx.stroke();
|
|
9158
|
+
} else {
|
|
9159
|
+
ctx.beginPath();
|
|
9160
|
+
ctx.moveTo(x - shHalf * 0.85, armY);
|
|
9161
|
+
ctx.lineTo(x - shHalf * 0.95, hipY + h * 0.02);
|
|
9162
|
+
ctx.moveTo(x + shHalf * 0.85, armY);
|
|
9163
|
+
ctx.lineTo(x + shHalf * 0.95, hipY + h * 0.02);
|
|
9164
|
+
ctx.stroke();
|
|
9165
|
+
}
|
|
9166
|
+
ctx.fillStyle = skin;
|
|
9167
|
+
ctx.fillRect(x - headR * 0.34, headCy + headR * 0.6, headR * 0.68, headR * 0.9);
|
|
9168
|
+
ctx.beginPath();
|
|
9169
|
+
ctx.ellipse(x, headCy, headR * 0.86, headR * 1.05, 0, 0, TAU2);
|
|
9170
|
+
ctx.fill();
|
|
9171
|
+
if (o.rimStrength > 0.01) {
|
|
9172
|
+
ctx.strokeStyle = `rgba(${o.rim[0]},${o.rim[1]},${o.rim[2]},${(0.6 * o.rimStrength).toFixed(3)})`;
|
|
9173
|
+
ctx.lineWidth = Math.max(1, h * 0.02);
|
|
9174
|
+
ctx.beginPath();
|
|
9175
|
+
ctx.moveTo(x - shHalf, shoulderY);
|
|
9176
|
+
ctx.lineTo(x - headR * 0.6, headCy + headR * 0.4);
|
|
9177
|
+
ctx.moveTo(x - headR * 0.7, headCy);
|
|
9178
|
+
ctx.ellipse(x, headCy, headR * 0.84, headR * 1.02, 0, Math.PI * 1.05, Math.PI * 1.55);
|
|
9179
|
+
ctx.stroke();
|
|
9180
|
+
}
|
|
9181
|
+
}
|
|
9182
|
+
function drawBeam(ctx, x0, y0, x1, y1, halfTop, halfBot, tint, strength) {
|
|
9183
|
+
ctx.save();
|
|
9184
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9185
|
+
const g = ctx.createLinearGradient(0, y0, 0, y1);
|
|
9186
|
+
g.addColorStop(0, `rgba(${tint},${strength})`);
|
|
9187
|
+
g.addColorStop(0.5, `rgba(${tint},${(strength * 0.4).toFixed(3)})`);
|
|
9188
|
+
g.addColorStop(1, `rgba(${tint},0)`);
|
|
9189
|
+
ctx.fillStyle = g;
|
|
9190
|
+
ctx.beginPath();
|
|
9191
|
+
ctx.moveTo(x0 - halfTop, y0);
|
|
9192
|
+
ctx.lineTo(x1 - halfBot, y1);
|
|
9193
|
+
ctx.lineTo(x1 + halfBot, y1);
|
|
9194
|
+
ctx.lineTo(x0 + halfTop, y0);
|
|
9195
|
+
ctx.closePath();
|
|
9196
|
+
ctx.fill();
|
|
9197
|
+
ctx.restore();
|
|
9198
|
+
}
|
|
9199
|
+
function drawFixture(ctx, x, y, r, tint) {
|
|
9200
|
+
const g = ctx.createRadialGradient(x, y, 0, x, y, r * 3);
|
|
9201
|
+
g.addColorStop(0, `rgba(${tint},0.9)`);
|
|
9202
|
+
g.addColorStop(0.4, `rgba(${tint},0.35)`);
|
|
9203
|
+
g.addColorStop(1, `rgba(${tint},0)`);
|
|
9204
|
+
ctx.fillStyle = g;
|
|
9205
|
+
ctx.beginPath();
|
|
9206
|
+
ctx.arc(x, y, r * 3, 0, TAU2);
|
|
9207
|
+
ctx.fill();
|
|
9208
|
+
ctx.fillStyle = `rgba(${tint},1)`;
|
|
9209
|
+
ctx.beginPath();
|
|
9210
|
+
ctx.arc(x, y, r, 0, TAU2);
|
|
9211
|
+
ctx.fill();
|
|
9212
|
+
}
|
|
8783
9213
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
8784
9214
|
const canvas = document.createElement("canvas");
|
|
8785
9215
|
canvas.width = W;
|
|
@@ -8789,102 +9219,428 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
8789
9219
|
const dy = focalPoint.y - seat.y;
|
|
8790
9220
|
const distU = Math.hypot(dx, dy);
|
|
8791
9221
|
const distM = Math.max(2, distU * UNIT);
|
|
9222
|
+
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
9223
|
+
const scene = classifyScene(seat, focalPoint, neighborSeats);
|
|
9224
|
+
const rand = rng(scene.seed);
|
|
9225
|
+
const baseHue = 210 + rand() * 150;
|
|
9226
|
+
const rigTint = hslToRgb(220 + (rand() - 0.5) * 60, 0.55, 0.82);
|
|
9227
|
+
const rigTintStr = `${rigTint[0]},${rigTint[1]},${rigTint[2]}`;
|
|
8792
9228
|
const sky = ctx.createLinearGradient(0, 0, 0, H);
|
|
8793
|
-
sky.addColorStop(0, "#
|
|
8794
|
-
sky.addColorStop(0.42, "#
|
|
8795
|
-
sky.addColorStop(0.5, "#
|
|
8796
|
-
sky.addColorStop(0.56, "#
|
|
8797
|
-
sky.addColorStop(
|
|
9229
|
+
sky.addColorStop(0, "#080b12");
|
|
9230
|
+
sky.addColorStop(0.42, "#12182c");
|
|
9231
|
+
sky.addColorStop(0.5, "#1c2440");
|
|
9232
|
+
sky.addColorStop(0.56, "#151b2c");
|
|
9233
|
+
sky.addColorStop(0.78, "#171e30");
|
|
9234
|
+
sky.addColorStop(1, "#131829");
|
|
8798
9235
|
ctx.fillStyle = sky;
|
|
8799
9236
|
ctx.fillRect(0, 0, W, H);
|
|
8800
|
-
const
|
|
8801
|
-
|
|
9237
|
+
const floorAmb = ctx.createLinearGradient(0, pitchToY(-6), 0, H);
|
|
9238
|
+
floorAmb.addColorStop(0, "rgba(30, 37, 56, 0)");
|
|
9239
|
+
floorAmb.addColorStop(0.55, "rgba(30, 37, 56, 0.22)");
|
|
9240
|
+
floorAmb.addColorStop(1, "rgba(22, 28, 44, 0.12)");
|
|
9241
|
+
ctx.fillStyle = floorAmb;
|
|
9242
|
+
ctx.fillRect(0, pitchToY(-6), W, H - pitchToY(-6));
|
|
9243
|
+
const stageBearing = Math.atan2(dx, -dy);
|
|
9244
|
+
const stands = buildStands(seat, neighborSeats, stageBearing, eyeM);
|
|
9245
|
+
const hasRelief = stands.maxDelta >= FLAT_DELTA_M;
|
|
9246
|
+
if (hasRelief) drawStands(ctx, stands.bins, yawToX, pitchToY);
|
|
9247
|
+
const nearGlow = Math.max(0.3, Math.min(1, 1 - (distM - 6) / 60));
|
|
8802
9248
|
const sightline = stageSightlinePitch(eyeM, distM);
|
|
9249
|
+
const stageHalfYaw = Math.min(80, Math.atan2(4, distM) * 180 / Math.PI);
|
|
8803
9250
|
const stageTopPitch = Math.min(45, sightline.topPitch);
|
|
8804
9251
|
const stageBasePitch = sightline.basePitch;
|
|
9252
|
+
if (scene.mode === "sport") {
|
|
9253
|
+
drawSportScene(ctx, distM, eyeM, scene, nearGlow, baseHue, rigTintStr, rand);
|
|
9254
|
+
} else if (scene.mode === "arena") {
|
|
9255
|
+
drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand);
|
|
9256
|
+
} else {
|
|
9257
|
+
drawProsceniumScene(ctx, stageHalfYaw, stageTopPitch, stageBasePitch, nearGlow, baseHue, rigTintStr, rand);
|
|
9258
|
+
}
|
|
9259
|
+
const rigCount = scene.mode === "sport" ? 34 : 24 + Math.floor(rand() * 8);
|
|
9260
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.8)`;
|
|
9261
|
+
for (let i = 0; i < rigCount; i++) {
|
|
9262
|
+
const x = (i + 0.5) / rigCount * W;
|
|
9263
|
+
const y = pitchToY(scene.mode === "sport" ? 58 : 54 + i * 7 % 3 * 6);
|
|
9264
|
+
ctx.beginPath();
|
|
9265
|
+
ctx.arc(x, y, scene.mode === "sport" ? 2.6 : 3.2, 0, TAU2);
|
|
9266
|
+
ctx.fill();
|
|
9267
|
+
}
|
|
9268
|
+
drawAudience(ctx, seat, neighborSeats, focalPoint, stageBearing, eyeM, dx, dy, hasRelief, scene);
|
|
9269
|
+
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
9270
|
+
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
9271
|
+
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
9272
|
+
poleFade.addColorStop(0.9, "rgba(0,0,0,0)");
|
|
9273
|
+
poleFade.addColorStop(1, "rgba(0,0,0,0.55)");
|
|
9274
|
+
ctx.fillStyle = poleFade;
|
|
9275
|
+
ctx.fillRect(0, 0, W, H);
|
|
9276
|
+
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
9277
|
+
}
|
|
9278
|
+
function drawProsceniumScene(ctx, stageHalfYaw, stageTopPitch, stageBasePitch, nearGlow, baseHue, rigTintStr, rand) {
|
|
8805
9279
|
const sx0 = yawToX(-stageHalfYaw);
|
|
8806
9280
|
const sx1 = yawToX(stageHalfYaw);
|
|
8807
9281
|
const sy0 = pitchToY(stageTopPitch);
|
|
8808
9282
|
const sy1 = pitchToY(stageBasePitch);
|
|
8809
|
-
const
|
|
8810
|
-
|
|
8811
|
-
|
|
9283
|
+
const sw = sx1 - sx0;
|
|
9284
|
+
const sh = sy1 - sy0;
|
|
9285
|
+
const archTop = pitchToY(Math.min(50, stageTopPitch + 10));
|
|
9286
|
+
const glowR = sw * (1.1 + (1 - nearGlow) * 0.5);
|
|
9287
|
+
const glow = ctx.createRadialGradient(W / 2, (sy0 + sy1) / 2, 8, W / 2, (sy0 + sy1) / 2, glowR);
|
|
9288
|
+
glow.addColorStop(0, "rgba(255, 214, 150, 0.34)");
|
|
9289
|
+
glow.addColorStop(0.28, "rgba(150, 150, 230, 0.3)");
|
|
9290
|
+
glow.addColorStop(0.6, "rgba(99, 102, 241, 0.12)");
|
|
8812
9291
|
glow.addColorStop(1, "rgba(99, 102, 241, 0)");
|
|
8813
9292
|
ctx.fillStyle = glow;
|
|
8814
|
-
ctx.fillRect(sx0 -
|
|
8815
|
-
|
|
8816
|
-
|
|
9293
|
+
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
9294
|
+
const topCol = hslToRgb(baseHue, 0.62, 0.55);
|
|
9295
|
+
const midCol = hslToRgb(baseHue + 40, 0.7, 0.5);
|
|
9296
|
+
const botCol = hslToRgb(baseHue + 70, 0.78, 0.52);
|
|
9297
|
+
ctx.save();
|
|
9298
|
+
ctx.beginPath();
|
|
9299
|
+
ctx.rect(sx0, sy0, sw, sh);
|
|
9300
|
+
ctx.clip();
|
|
8817
9301
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8818
|
-
backdrop.addColorStop(0,
|
|
8819
|
-
backdrop.addColorStop(0.5,
|
|
8820
|
-
backdrop.addColorStop(1,
|
|
8821
|
-
ctx.globalAlpha = 0.
|
|
9302
|
+
backdrop.addColorStop(0, `rgb(${topCol[0]},${topCol[1]},${topCol[2]})`);
|
|
9303
|
+
backdrop.addColorStop(0.5, `rgb(${midCol[0]},${midCol[1]},${midCol[2]})`);
|
|
9304
|
+
backdrop.addColorStop(1, `rgb(${botCol[0]},${botCol[1]},${botCol[2]})`);
|
|
9305
|
+
ctx.globalAlpha = 0.8;
|
|
8822
9306
|
ctx.fillStyle = backdrop;
|
|
8823
|
-
|
|
8824
|
-
ctx.fillRect(sx0 + inset, sy0 + (sy1 - sy0) * 0.12, sx1 - sx0 - inset * 2, (sy1 - sy0) * 0.62);
|
|
9307
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8825
9308
|
ctx.globalAlpha = 1;
|
|
8826
|
-
ctx.
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
9309
|
+
const fall = ctx.createRadialGradient(W / 2, (sy0 + sy1) / 2, sw * 0.12, W / 2, (sy0 + sy1) / 2, sw * 0.62);
|
|
9310
|
+
fall.addColorStop(0, "rgba(6,8,14,0)");
|
|
9311
|
+
fall.addColorStop(0.7, "rgba(6,8,14,0.15)");
|
|
9312
|
+
fall.addColorStop(1, "rgba(5,6,11,0.92)");
|
|
9313
|
+
ctx.fillStyle = fall;
|
|
9314
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
9315
|
+
ctx.restore();
|
|
9316
|
+
const perfCount = 3 + Math.floor(rand() * 3);
|
|
9317
|
+
const perfH = sh * 0.46;
|
|
9318
|
+
const floorY = sy1 - sh * 0.08;
|
|
9319
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9320
|
+
const t2 = (i + 1) / (perfCount + 1);
|
|
9321
|
+
const px = sx0 + sw * (0.22 + t2 * 0.56 + (rand() - 0.5) * 0.06);
|
|
9322
|
+
drawPerformer(ctx, px, floorY, perfH * (0.9 + rand() * 0.24), {
|
|
9323
|
+
tone: [8, 10, 16],
|
|
9324
|
+
alpha: 0.9,
|
|
9325
|
+
pose: Math.floor(rand() * 4),
|
|
9326
|
+
rim: hslToRgb(baseHue + 20, 0.5, 0.7),
|
|
9327
|
+
rimStrength: 0.7
|
|
9328
|
+
});
|
|
9329
|
+
}
|
|
9330
|
+
ctx.fillStyle = "#05070d";
|
|
9331
|
+
ctx.fillRect(sx0 - sw * 0.5, archTop, sw * 0.5, sy1 - archTop);
|
|
9332
|
+
ctx.fillRect(sx1, archTop, sw * 0.5, sy1 - archTop);
|
|
9333
|
+
const legBot = sw * 0.055 + 3;
|
|
9334
|
+
const legTop = legBot * 1.35;
|
|
9335
|
+
ctx.fillStyle = "#03050a";
|
|
9336
|
+
ctx.beginPath();
|
|
9337
|
+
ctx.moveTo(sx0, sy1);
|
|
9338
|
+
ctx.lineTo(sx0 - legBot, sy1);
|
|
9339
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9340
|
+
ctx.lineTo(sx0 + sw * 0.012, archTop);
|
|
9341
|
+
ctx.closePath();
|
|
9342
|
+
ctx.fill();
|
|
9343
|
+
ctx.beginPath();
|
|
9344
|
+
ctx.moveTo(sx1, sy1);
|
|
9345
|
+
ctx.lineTo(sx1 + legBot, sy1);
|
|
9346
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9347
|
+
ctx.lineTo(sx1 - sw * 0.012, archTop);
|
|
9348
|
+
ctx.closePath();
|
|
9349
|
+
ctx.fill();
|
|
9350
|
+
ctx.fillRect(sx0 - legTop, archTop, sw + legTop * 2, (sy1 - archTop) * 0.12);
|
|
9351
|
+
const valH = sh * 0.14;
|
|
9352
|
+
const valY = archTop + (sy1 - archTop) * 0.12;
|
|
9353
|
+
ctx.fillStyle = "rgba(4,6,12,0.96)";
|
|
9354
|
+
const scallops = 7;
|
|
9355
|
+
ctx.beginPath();
|
|
9356
|
+
ctx.moveTo(sx0 - legTop, valY);
|
|
9357
|
+
for (let i = 0; i <= scallops; i++) {
|
|
9358
|
+
const xx = sx0 - legTop + (sw + legTop * 2) * i / scallops;
|
|
9359
|
+
ctx.quadraticCurveTo(xx - (sw + legTop * 2) / scallops / 2, valY + valH, xx, valY);
|
|
9360
|
+
}
|
|
9361
|
+
ctx.lineTo(sx1 + legTop, valY);
|
|
9362
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9363
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9364
|
+
ctx.closePath();
|
|
9365
|
+
ctx.fill();
|
|
9366
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.12)`;
|
|
9367
|
+
ctx.fillRect(sx0 - 1.5, valY, 1.5, sy1 - valY);
|
|
9368
|
+
ctx.fillRect(sx1, valY, 1.5, sy1 - valY);
|
|
9369
|
+
ctx.fillStyle = "#0b0f18";
|
|
9370
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.05 + 3);
|
|
9371
|
+
const foot = ctx.createLinearGradient(0, sy1 - sh * 0.05, 0, sy1 + sh * 0.12);
|
|
9372
|
+
foot.addColorStop(0, `rgba(255,206,140,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9373
|
+
foot.addColorStop(1, "rgba(255,206,140,0)");
|
|
9374
|
+
ctx.fillStyle = foot;
|
|
9375
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.2);
|
|
9376
|
+
const beams = 3 + Math.floor(rand() * 2);
|
|
9377
|
+
for (let i = 0; i < beams; i++) {
|
|
9378
|
+
const t2 = (i + 1) / (beams + 1);
|
|
9379
|
+
const fx = sx0 + sw * (t2 + (rand() - 0.5) * 0.08);
|
|
9380
|
+
const fy = pitchToY(Math.min(62, stageTopPitch + 26));
|
|
9381
|
+
const skew = (rand() - 0.5) * sw * 0.16;
|
|
9382
|
+
drawBeam(ctx, fx, fy, fx + skew, sy1, 4, sw * 0.09, rigTintStr, 0.16 * (0.7 + nearGlow * 0.5));
|
|
9383
|
+
drawFixture(ctx, fx, fy, 2.4, rigTintStr);
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
9387
|
+
const stageR = Math.min(distM * 0.6, Math.max(3, scene.voidRadiusM));
|
|
9388
|
+
const halfYaw = Math.min(55, Math.atan2(stageR, distM) * 180 / Math.PI);
|
|
9389
|
+
const cx = W / 2;
|
|
9390
|
+
const deckRiserM = 1.2;
|
|
9391
|
+
const nearDist = Math.max(distM * 0.45, distM - stageR);
|
|
9392
|
+
const farDist = distM + stageR;
|
|
9393
|
+
const nearTopPitch = Math.atan2(deckRiserM - eyeM, nearDist) * 180 / Math.PI;
|
|
9394
|
+
const farTopPitch = Math.atan2(deckRiserM - eyeM, farDist) * 180 / Math.PI;
|
|
9395
|
+
const nearBasePitch = Math.atan2(-eyeM, nearDist) * 180 / Math.PI;
|
|
9396
|
+
const yNearTop = pitchToY(nearTopPitch);
|
|
9397
|
+
const yFarTop = pitchToY(farTopPitch);
|
|
9398
|
+
const yBase = pitchToY(nearBasePitch);
|
|
9399
|
+
const halfW = yawToX(halfYaw) - cx;
|
|
9400
|
+
const topRy = Math.max(6, (yNearTop - yFarTop) / 2);
|
|
9401
|
+
const topCy = (yNearTop + yFarTop) / 2;
|
|
9402
|
+
const pool = ctx.createRadialGradient(cx, yBase, 4, cx, yBase, halfW * 2.6);
|
|
9403
|
+
pool.addColorStop(0, `rgba(255,206,150,${(0.2 * nearGlow).toFixed(3)})`);
|
|
9404
|
+
pool.addColorStop(0.5, "rgba(150,140,190,0.08)");
|
|
9405
|
+
pool.addColorStop(1, "rgba(40,44,60,0)");
|
|
9406
|
+
ctx.save();
|
|
9407
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9408
|
+
ctx.fillStyle = pool;
|
|
9409
|
+
ctx.fillRect(cx - halfW * 2.6, yFarTop - topRy, halfW * 5.2, yBase - yFarTop + topRy + 40);
|
|
9410
|
+
ctx.restore();
|
|
9411
|
+
ctx.fillStyle = "#0c1019";
|
|
9412
|
+
ctx.beginPath();
|
|
9413
|
+
ctx.moveTo(cx - halfW, topCy);
|
|
9414
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, Math.PI, 0, false);
|
|
9415
|
+
ctx.lineTo(cx + halfW, yBase);
|
|
9416
|
+
ctx.ellipse(cx, yBase, halfW, topRy, 0, 0, Math.PI, false);
|
|
9417
|
+
ctx.closePath();
|
|
9418
|
+
ctx.fill();
|
|
9419
|
+
const riser = ctx.createLinearGradient(0, topCy, 0, yBase);
|
|
9420
|
+
riser.addColorStop(0, `rgba(${rigTintStr},0.14)`);
|
|
9421
|
+
riser.addColorStop(1, "rgba(0,0,0,0)");
|
|
9422
|
+
ctx.fillStyle = riser;
|
|
9423
|
+
ctx.fillRect(cx - halfW, topCy, halfW * 2, yBase - topCy);
|
|
9424
|
+
const deckTop = ctx.createLinearGradient(0, topCy - topRy, 0, topCy + topRy);
|
|
9425
|
+
deckTop.addColorStop(0, "#2b3350");
|
|
9426
|
+
deckTop.addColorStop(1, "#161b2c");
|
|
9427
|
+
ctx.fillStyle = deckTop;
|
|
9428
|
+
ctx.beginPath();
|
|
9429
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9430
|
+
ctx.fill();
|
|
9431
|
+
const deckGlow = ctx.createRadialGradient(cx, topCy, 2, cx, topCy, halfW);
|
|
9432
|
+
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.28 * nearGlow).toFixed(3)})`);
|
|
9433
|
+
deckGlow.addColorStop(1, "rgba(255,214,160,0)");
|
|
9434
|
+
ctx.save();
|
|
9435
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9436
|
+
ctx.fillStyle = deckGlow;
|
|
9437
|
+
ctx.beginPath();
|
|
9438
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9439
|
+
ctx.fill();
|
|
9440
|
+
ctx.restore();
|
|
9441
|
+
ctx.strokeStyle = `rgba(${rigTintStr},0.7)`;
|
|
9442
|
+
ctx.lineWidth = 2;
|
|
9443
|
+
ctx.beginPath();
|
|
9444
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU2);
|
|
9445
|
+
ctx.stroke();
|
|
9446
|
+
const perfCount = 2 + Math.floor(rand() * 3);
|
|
9447
|
+
const perfH = Math.max(24, (yBase - yFarTop) * 1.3);
|
|
9448
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9449
|
+
const t2 = perfCount === 1 ? 0.5 : i / (perfCount - 1);
|
|
9450
|
+
const px = cx + (t2 - 0.5) * 2 * halfW * 0.66;
|
|
9451
|
+
const footY = topCy + topRy * 0.5 * Math.cos((t2 - 0.5) * Math.PI) - topRy * 0.1;
|
|
9452
|
+
drawPerformer(ctx, px, footY, perfH * (0.85 + rand() * 0.3), {
|
|
9453
|
+
tone: [10, 12, 20],
|
|
9454
|
+
alpha: 0.92,
|
|
9455
|
+
pose: Math.floor(rand() * 4),
|
|
9456
|
+
rim: hslToRgb(40, 0.6, 0.72),
|
|
9457
|
+
rimStrength: 0.8
|
|
9458
|
+
});
|
|
8837
9459
|
}
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
9460
|
+
const trussY = pitchToY(Math.min(60, farTopPitch + 34));
|
|
9461
|
+
const trussRy = topRy * 1.1 + 6;
|
|
9462
|
+
ctx.strokeStyle = "rgba(60,68,92,0.85)";
|
|
9463
|
+
ctx.lineWidth = 3;
|
|
9464
|
+
ctx.beginPath();
|
|
9465
|
+
ctx.ellipse(cx, trussY, halfW * 1.15, trussRy, 0, 0, TAU2);
|
|
9466
|
+
ctx.stroke();
|
|
9467
|
+
const fixtures = 4 + Math.floor(rand() * 3);
|
|
9468
|
+
for (let i = 0; i < fixtures; i++) {
|
|
9469
|
+
const a = i / fixtures * TAU2;
|
|
9470
|
+
const fxX = cx + Math.cos(a) * halfW * 1.15;
|
|
9471
|
+
const fxY = trussY + Math.sin(a) * trussRy;
|
|
9472
|
+
if (Math.sin(a) > -0.2) {
|
|
9473
|
+
const tx = cx + (rand() - 0.5) * halfW * 0.8;
|
|
9474
|
+
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.14 * (0.7 + nearGlow * 0.4));
|
|
9475
|
+
}
|
|
9476
|
+
drawFixture(ctx, fxX, fxY, 2.2, rigTintStr);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr, rand) {
|
|
9480
|
+
const cx = W / 2;
|
|
9481
|
+
const surfR = Math.max(10, scene.voidRadiusM);
|
|
9482
|
+
const nearEdge = Math.max(2, distM - surfR);
|
|
9483
|
+
const farEdge = distM + surfR;
|
|
9484
|
+
const halfYaw = Math.min(78, Math.atan2(surfR * 1.15, distM) * 180 / Math.PI);
|
|
9485
|
+
const nearPitch = Math.atan2(-eyeM, nearEdge) * 180 / Math.PI;
|
|
9486
|
+
const farPitch = Math.atan2(-eyeM, farEdge) * 180 / Math.PI;
|
|
9487
|
+
const yNear = pitchToY(nearPitch);
|
|
9488
|
+
const yFar = pitchToY(farPitch);
|
|
9489
|
+
const halfW = yawToX(halfYaw) - cx;
|
|
9490
|
+
const cy = (yNear + yFar) / 2;
|
|
9491
|
+
const ry = Math.max(10, (yNear - yFar) / 2);
|
|
9492
|
+
const surfCol = ctx.createRadialGradient(cx, cy, 4, cx, cy, halfW);
|
|
9493
|
+
surfCol.addColorStop(0, "#e8f0fb");
|
|
9494
|
+
surfCol.addColorStop(0.7, "#b9c9e2");
|
|
9495
|
+
surfCol.addColorStop(1, "#8fa3c4");
|
|
9496
|
+
ctx.save();
|
|
9497
|
+
ctx.beginPath();
|
|
9498
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU2);
|
|
9499
|
+
ctx.clip();
|
|
9500
|
+
ctx.fillStyle = surfCol;
|
|
9501
|
+
ctx.fillRect(cx - halfW, cy - ry, halfW * 2, ry * 2);
|
|
9502
|
+
ctx.strokeStyle = "rgba(90,120,170,0.5)";
|
|
9503
|
+
ctx.lineWidth = 3;
|
|
9504
|
+
ctx.beginPath();
|
|
9505
|
+
ctx.moveTo(cx, cy - ry);
|
|
9506
|
+
ctx.lineTo(cx, cy + ry);
|
|
9507
|
+
ctx.moveTo(cx - halfW * 0.5, cy - ry);
|
|
9508
|
+
ctx.lineTo(cx - halfW * 0.5, cy + ry);
|
|
9509
|
+
ctx.moveTo(cx + halfW * 0.5, cy - ry);
|
|
9510
|
+
ctx.lineTo(cx + halfW * 0.5, cy + ry);
|
|
9511
|
+
ctx.stroke();
|
|
9512
|
+
ctx.strokeStyle = "rgba(180,90,110,0.45)";
|
|
9513
|
+
ctx.beginPath();
|
|
9514
|
+
ctx.ellipse(cx, cy, ry * 0.9, ry * 0.5, 0, 0, TAU2);
|
|
9515
|
+
ctx.stroke();
|
|
9516
|
+
ctx.restore();
|
|
9517
|
+
ctx.strokeStyle = "rgba(210,225,245,0.6)";
|
|
9518
|
+
ctx.lineWidth = 2.5;
|
|
9519
|
+
ctx.beginPath();
|
|
9520
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU2);
|
|
9521
|
+
ctx.stroke();
|
|
9522
|
+
ctx.fillStyle = "rgba(20,26,40,0.8)";
|
|
9523
|
+
for (let i = 0; i < 5; i++) {
|
|
9524
|
+
const px = cx + (rand() - 0.5) * halfW * 1.4;
|
|
9525
|
+
const py = cy + (rand() - 0.5) * ry * 1.1;
|
|
9526
|
+
const s = Math.max(2, ry * 0.06);
|
|
8842
9527
|
ctx.beginPath();
|
|
8843
|
-
ctx.
|
|
8844
|
-
ctx.lineTo(bx - (sx1 - sx0) * 0.09, sy1);
|
|
8845
|
-
ctx.lineTo(bx + (sx1 - sx0) * 0.09, sy1);
|
|
8846
|
-
ctx.lineTo(bx + 8, pitchToY(Math.min(60, stageTopPitch + 25)));
|
|
8847
|
-
ctx.closePath();
|
|
9528
|
+
ctx.ellipse(px, py, s * 0.6, s, 0, 0, TAU2);
|
|
8848
9529
|
ctx.fill();
|
|
8849
9530
|
}
|
|
8850
|
-
ctx.
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
9531
|
+
const wash = ctx.createRadialGradient(cx, cy, 4, cx, cy, halfW * 1.4);
|
|
9532
|
+
wash.addColorStop(0, "rgba(200,220,255,0.12)");
|
|
9533
|
+
wash.addColorStop(1, "rgba(200,220,255,0)");
|
|
9534
|
+
ctx.save();
|
|
9535
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9536
|
+
ctx.fillStyle = wash;
|
|
9537
|
+
ctx.fillRect(cx - halfW * 1.4, yFar - ry, halfW * 2.8, yNear - yFar + ry * 2);
|
|
9538
|
+
ctx.restore();
|
|
9539
|
+
const sbY = pitchToY(Math.min(66, farPitch + 60));
|
|
9540
|
+
const sbW = Math.max(80, halfW * 0.7);
|
|
9541
|
+
const sbH = sbW * 0.5;
|
|
9542
|
+
const sbGlow = ctx.createRadialGradient(cx, sbY, 4, cx, sbY, sbW * 1.6);
|
|
9543
|
+
sbGlow.addColorStop(0, `rgba(${rigTintStr},0.3)`);
|
|
9544
|
+
sbGlow.addColorStop(1, `rgba(${rigTintStr},0)`);
|
|
9545
|
+
ctx.save();
|
|
9546
|
+
ctx.globalCompositeOperation = "lighter";
|
|
9547
|
+
ctx.fillStyle = sbGlow;
|
|
9548
|
+
ctx.fillRect(cx - sbW * 1.6, sbY - sbW * 1.2, sbW * 3.2, sbW * 2.4);
|
|
9549
|
+
ctx.restore();
|
|
9550
|
+
ctx.fillStyle = "#080b12";
|
|
9551
|
+
ctx.beginPath();
|
|
9552
|
+
ctx.moveTo(cx - sbW / 2, sbY - sbH / 2);
|
|
9553
|
+
ctx.lineTo(cx + sbW / 2, sbY - sbH / 2);
|
|
9554
|
+
ctx.lineTo(cx + sbW * 0.6, sbY);
|
|
9555
|
+
ctx.lineTo(cx + sbW / 2, sbY + sbH / 2);
|
|
9556
|
+
ctx.lineTo(cx - sbW / 2, sbY + sbH / 2);
|
|
9557
|
+
ctx.lineTo(cx - sbW * 0.6, sbY);
|
|
9558
|
+
ctx.closePath();
|
|
9559
|
+
ctx.fill();
|
|
9560
|
+
const screenHue = baseHue;
|
|
9561
|
+
for (let i = 0; i < 3; i++) {
|
|
9562
|
+
const scol = hslToRgb(screenHue + i * 30, 0.5, 0.5);
|
|
9563
|
+
ctx.fillStyle = `rgba(${scol[0]},${scol[1]},${scol[2]},0.75)`;
|
|
9564
|
+
const swid = sbW * 0.26;
|
|
9565
|
+
ctx.fillRect(cx - sbW * 0.42 + i * (swid + sbW * 0.06), sbY - sbH * 0.32, swid, sbH * 0.64);
|
|
9566
|
+
}
|
|
9567
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.9)`;
|
|
9568
|
+
for (let i = 0; i < 8; i++) {
|
|
9569
|
+
const lx = cx - sbW * 0.6 + sbW * 1.2 * i / 7;
|
|
8855
9570
|
ctx.beginPath();
|
|
8856
|
-
ctx.arc(
|
|
9571
|
+
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0, TAU2);
|
|
8857
9572
|
ctx.fill();
|
|
8858
9573
|
}
|
|
8859
|
-
|
|
8860
|
-
|
|
9574
|
+
}
|
|
9575
|
+
function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM, dx, dy, hasRelief, scene) {
|
|
9576
|
+
const own = seat.sectionId;
|
|
9577
|
+
let seatAhead = false;
|
|
9578
|
+
const figs = [];
|
|
8861
9579
|
for (const other of neighborSeats) {
|
|
8862
9580
|
if (other.id === seat.id) continue;
|
|
8863
9581
|
const ox = other.x - seat.x;
|
|
8864
9582
|
const oy = other.y - seat.y;
|
|
8865
9583
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
8866
|
-
if (d <
|
|
9584
|
+
if (own && other.sectionId === own && d < 3 && ox * dx + oy * dy > 0) seatAhead = true;
|
|
9585
|
+
if (d < 0.3 || d > 17) continue;
|
|
8867
9586
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8868
9587
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8869
|
-
const
|
|
8870
|
-
const
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
9588
|
+
const rise = hasRelief ? (other.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - eyeM : 0;
|
|
9589
|
+
const headPitch = Math.atan2(rise - 0.28, d) * 180 / Math.PI;
|
|
9590
|
+
if (headPitch > 10 && d > 5) continue;
|
|
9591
|
+
let hh = 0;
|
|
9592
|
+
for (let k = 0; k < other.id.length; k++) hh = hh * 31 + other.id.charCodeAt(k) & 65535;
|
|
9593
|
+
const jitter = 0.9 + (hh & 15) / 40;
|
|
9594
|
+
const r = Math.min(78, Math.atan2(0.16, d) * 180 / Math.PI * PX_PER_DEG * jitter);
|
|
9595
|
+
if (r < 2) continue;
|
|
9596
|
+
figs.push({ hx: yawToX(yaw), hy: pitchToY(headPitch), r, d, hash: hh });
|
|
9597
|
+
}
|
|
9598
|
+
figs.sort((a, b) => b.d - a.d);
|
|
9599
|
+
const CLOTHES = [[22, 26, 40], [28, 27, 42], [24, 31, 46], [31, 30, 47], [20, 24, 36]];
|
|
9600
|
+
for (const f of figs) {
|
|
9601
|
+
const alpha = f.d < 12 ? 0.95 : Math.max(0.66, 0.95 - (f.d - 12) / 18);
|
|
9602
|
+
const lift = Math.round((1 - Math.min(1, f.d / 16)) * 10);
|
|
9603
|
+
const headTone = [14 + (f.hash >> 4 & 6) + lift, 17 + (f.hash >> 4 & 6) + lift, 25 + (f.hash >> 4 & 6) + lift];
|
|
9604
|
+
const clo0 = CLOTHES[f.hash % CLOTHES.length];
|
|
9605
|
+
const clothing = [clo0[0] + lift, clo0[1] + lift, clo0[2] + lift];
|
|
9606
|
+
if (f.r >= 13) {
|
|
9607
|
+
const rim = f.d < 9 ? 0.16 * (1 - f.d / 9) + (scene.mode === "proscenium" ? 0.06 : 0) : 0;
|
|
9608
|
+
drawSeated(ctx, f.hx, f.hy, f.r, {
|
|
9609
|
+
tone: headTone,
|
|
9610
|
+
clothing,
|
|
9611
|
+
alpha,
|
|
9612
|
+
tilt: ((f.hash >> 2 & 7) - 3.5) * 0.018,
|
|
9613
|
+
// ±~7°
|
|
9614
|
+
shoulderK: 1.72 + (f.hash >> 5 & 7) / 22,
|
|
9615
|
+
// ~1.72..2.04
|
|
9616
|
+
hair: f.hash % HAIR_KINDS,
|
|
9617
|
+
rim
|
|
9618
|
+
});
|
|
9619
|
+
} else if (f.r >= 5.5) {
|
|
9620
|
+
drawSeatedMid(ctx, f.hx, f.hy, f.r, clothing, headTone, alpha);
|
|
9621
|
+
} else {
|
|
9622
|
+
ctx.fillStyle = `rgba(${headTone[0]},${headTone[1]},${headTone[2]},${alpha.toFixed(3)})`;
|
|
9623
|
+
ctx.beginPath();
|
|
9624
|
+
ctx.arc(f.hx, f.hy, f.r, 0, TAU2);
|
|
9625
|
+
ctx.fill();
|
|
9626
|
+
ctx.beginPath();
|
|
9627
|
+
ctx.ellipse(f.hx, f.hy + f.r * 1.4, f.r * 1.85, f.r * 0.95, 0, Math.PI, 0, true);
|
|
9628
|
+
ctx.fill();
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
if (seatAhead) {
|
|
9632
|
+
const railTop = pitchToY(-34);
|
|
9633
|
+
ctx.fillStyle = "rgba(6, 8, 14, 0.92)";
|
|
9634
|
+
ctx.fillRect(0, railTop, W, H - railTop);
|
|
9635
|
+
ctx.fillStyle = "rgba(24, 30, 46, 0.9)";
|
|
9636
|
+
const humps = 16;
|
|
9637
|
+
for (let i = 0; i < humps; i++) {
|
|
9638
|
+
const cx = (i + 0.5) / humps * W;
|
|
9639
|
+
ctx.beginPath();
|
|
9640
|
+
ctx.ellipse(cx, railTop + 6, W / humps * 0.42, 22, 0, Math.PI, 0, true);
|
|
9641
|
+
ctx.fill();
|
|
9642
|
+
}
|
|
8879
9643
|
}
|
|
8880
|
-
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
8881
|
-
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
8882
|
-
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
8883
|
-
poleFade.addColorStop(0.88, "rgba(0,0,0,0)");
|
|
8884
|
-
poleFade.addColorStop(1, "rgba(0,0,0,0.85)");
|
|
8885
|
-
ctx.fillStyle = poleFade;
|
|
8886
|
-
ctx.fillRect(0, 0, W, H);
|
|
8887
|
-
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
8888
9644
|
}
|
|
8889
9645
|
var TW = 480;
|
|
8890
9646
|
var TH = 270;
|
|
@@ -8900,12 +9656,16 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8900
9656
|
const dx = focalPoint.x - seat.x;
|
|
8901
9657
|
const dy = focalPoint.y - seat.y;
|
|
8902
9658
|
const distM = Math.max(2, Math.hypot(dx, dy) * UNIT);
|
|
9659
|
+
const rand = rng(fnv1a(`${seat.id}|${Math.round(focalPoint.x)},${Math.round(focalPoint.y)}`));
|
|
9660
|
+
const baseHue = 210 + rand() * 150;
|
|
9661
|
+
const rigTint = hslToRgb(220 + (rand() - 0.5) * 60, 0.55, 0.82);
|
|
9662
|
+
const rigTintStr = `${rigTint[0]},${rigTint[1]},${rigTint[2]}`;
|
|
8903
9663
|
const sky = ctx.createLinearGradient(0, 0, 0, TH);
|
|
8904
|
-
sky.addColorStop(0, "#
|
|
8905
|
-
sky.addColorStop(0.44, "#
|
|
8906
|
-
sky.addColorStop(0.5, "#
|
|
8907
|
-
sky.addColorStop(0.58, "#
|
|
8908
|
-
sky.addColorStop(1, "#
|
|
9664
|
+
sky.addColorStop(0, "#080b12");
|
|
9665
|
+
sky.addColorStop(0.44, "#151c32");
|
|
9666
|
+
sky.addColorStop(0.5, "#1d2644");
|
|
9667
|
+
sky.addColorStop(0.58, "#161c2e");
|
|
9668
|
+
sky.addColorStop(1, "#131829");
|
|
8909
9669
|
ctx.fillStyle = sky;
|
|
8910
9670
|
ctx.fillRect(0, 0, TW, TH);
|
|
8911
9671
|
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
@@ -8919,58 +9679,102 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8919
9679
|
const sy1 = pitchToTY(stageBasePitch);
|
|
8920
9680
|
const sw = sx1 - sx0;
|
|
8921
9681
|
const sh = sy1 - sy0;
|
|
8922
|
-
const
|
|
8923
|
-
|
|
8924
|
-
glow.
|
|
9682
|
+
const nearGlow = Math.max(0.35, Math.min(1, 1 - (distM - 6) / 60));
|
|
9683
|
+
const archTop = pitchToTY(Math.min(HALF_VFOV - 1, stageTopPitch + 8));
|
|
9684
|
+
const glow = ctx.createRadialGradient(TW / 2, (sy0 + sy1) / 2, 4, TW / 2, (sy0 + sy1) / 2, sw * 1.15);
|
|
9685
|
+
glow.addColorStop(0, "rgba(255,214,150,0.34)");
|
|
9686
|
+
glow.addColorStop(0.28, "rgba(150,150,230,0.3)");
|
|
9687
|
+
glow.addColorStop(0.6, "rgba(99,102,241,0.12)");
|
|
8925
9688
|
glow.addColorStop(1, "rgba(99,102,241,0)");
|
|
8926
9689
|
ctx.fillStyle = glow;
|
|
8927
9690
|
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
8928
|
-
|
|
8929
|
-
|
|
9691
|
+
const topCol = hslToRgb(baseHue, 0.62, 0.55);
|
|
9692
|
+
const midCol = hslToRgb(baseHue + 40, 0.7, 0.5);
|
|
9693
|
+
const botCol = hslToRgb(baseHue + 70, 0.78, 0.52);
|
|
9694
|
+
ctx.save();
|
|
9695
|
+
ctx.beginPath();
|
|
9696
|
+
ctx.rect(sx0, sy0, sw, sh);
|
|
9697
|
+
ctx.clip();
|
|
8930
9698
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8931
|
-
backdrop.addColorStop(0,
|
|
8932
|
-
backdrop.addColorStop(0.5,
|
|
8933
|
-
backdrop.addColorStop(1,
|
|
8934
|
-
ctx.globalAlpha = 0.
|
|
9699
|
+
backdrop.addColorStop(0, `rgb(${topCol[0]},${topCol[1]},${topCol[2]})`);
|
|
9700
|
+
backdrop.addColorStop(0.5, `rgb(${midCol[0]},${midCol[1]},${midCol[2]})`);
|
|
9701
|
+
backdrop.addColorStop(1, `rgb(${botCol[0]},${botCol[1]},${botCol[2]})`);
|
|
9702
|
+
ctx.globalAlpha = 0.8;
|
|
8935
9703
|
ctx.fillStyle = backdrop;
|
|
8936
|
-
|
|
8937
|
-
ctx.fillRect(sx0 + inset, sy0 + sh * 0.12, sw - inset * 2, sh * 0.62);
|
|
9704
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8938
9705
|
ctx.globalAlpha = 1;
|
|
8939
|
-
ctx.
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
ctx.lineTo(bx - sw * 0.09, sy1);
|
|
8958
|
-
ctx.lineTo(bx + sw * 0.09, sy1);
|
|
8959
|
-
ctx.lineTo(bx + 5, pitchToTY(Math.min(HALF_VFOV, stageTopPitch + 18)));
|
|
8960
|
-
ctx.closePath();
|
|
8961
|
-
ctx.fill();
|
|
9706
|
+
const fall = ctx.createRadialGradient(TW / 2, (sy0 + sy1) / 2, sw * 0.12, TW / 2, (sy0 + sy1) / 2, sw * 0.6);
|
|
9707
|
+
fall.addColorStop(0, "rgba(6,8,14,0)");
|
|
9708
|
+
fall.addColorStop(0.7, "rgba(6,8,14,0.15)");
|
|
9709
|
+
fall.addColorStop(1, "rgba(5,6,11,0.9)");
|
|
9710
|
+
ctx.fillStyle = fall;
|
|
9711
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
9712
|
+
ctx.restore();
|
|
9713
|
+
const perfCount = 3;
|
|
9714
|
+
const floorY = sy1 - sh * 0.08;
|
|
9715
|
+
for (let i = 0; i < perfCount; i++) {
|
|
9716
|
+
const t2 = (i + 1) / (perfCount + 1);
|
|
9717
|
+
drawPerformer(ctx, sx0 + sw * (0.24 + t2 * 0.52), floorY, sh * 0.46 * (0.9 + rand() * 0.24), {
|
|
9718
|
+
tone: [8, 10, 16],
|
|
9719
|
+
alpha: 0.9,
|
|
9720
|
+
pose: Math.floor(rand() * 4),
|
|
9721
|
+
rim: hslToRgb(baseHue + 20, 0.5, 0.7),
|
|
9722
|
+
rimStrength: 0.7
|
|
9723
|
+
});
|
|
8962
9724
|
}
|
|
8963
|
-
ctx.
|
|
8964
|
-
ctx.
|
|
9725
|
+
ctx.fillStyle = "#05070d";
|
|
9726
|
+
ctx.fillRect(sx0 - sw * 0.5, archTop, sw * 0.5, sy1 - archTop);
|
|
9727
|
+
ctx.fillRect(sx1, archTop, sw * 0.5, sy1 - archTop);
|
|
9728
|
+
const legBot = sw * 0.05 + 2;
|
|
9729
|
+
const legTop = legBot * 1.35;
|
|
9730
|
+
ctx.fillStyle = "#03050a";
|
|
9731
|
+
ctx.beginPath();
|
|
9732
|
+
ctx.moveTo(sx0, sy1);
|
|
9733
|
+
ctx.lineTo(sx0 - legBot, sy1);
|
|
9734
|
+
ctx.lineTo(sx0 - legTop, archTop);
|
|
9735
|
+
ctx.lineTo(sx0 + sw * 0.012, archTop);
|
|
9736
|
+
ctx.closePath();
|
|
9737
|
+
ctx.fill();
|
|
9738
|
+
ctx.beginPath();
|
|
9739
|
+
ctx.moveTo(sx1, sy1);
|
|
9740
|
+
ctx.lineTo(sx1 + legBot, sy1);
|
|
9741
|
+
ctx.lineTo(sx1 + legTop, archTop);
|
|
9742
|
+
ctx.lineTo(sx1 - sw * 0.012, archTop);
|
|
9743
|
+
ctx.closePath();
|
|
9744
|
+
ctx.fill();
|
|
9745
|
+
ctx.fillRect(sx0 - legTop, archTop, sw + legTop * 2, (sy1 - archTop) * 0.13);
|
|
9746
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.12)`;
|
|
9747
|
+
ctx.fillRect(sx0 - 1.2, archTop, 1.2, sy1 - archTop);
|
|
9748
|
+
ctx.fillRect(sx1, archTop, 1.2, sy1 - archTop);
|
|
9749
|
+
ctx.fillStyle = "#0b0f18";
|
|
9750
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.05 + 2);
|
|
9751
|
+
const foot = ctx.createLinearGradient(0, sy1 - sh * 0.05, 0, sy1 + sh * 0.14);
|
|
9752
|
+
foot.addColorStop(0, `rgba(255,206,140,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9753
|
+
foot.addColorStop(1, "rgba(255,206,140,0)");
|
|
9754
|
+
ctx.fillStyle = foot;
|
|
9755
|
+
ctx.fillRect(sx0 - legBot, sy1 - sh * 0.05, sw + legBot * 2, sh * 0.22);
|
|
9756
|
+
for (let i = 0; i < 3; i++) {
|
|
9757
|
+
const t2 = (i + 1) / 4;
|
|
9758
|
+
const fx = sx0 + sw * t2;
|
|
9759
|
+
const fy = pitchToTY(Math.min(HALF_VFOV - 1, stageTopPitch + 20));
|
|
9760
|
+
drawBeam(ctx, fx, fy, fx + (rand() - 0.5) * sw * 0.14, sy1, 3, sw * 0.09, rigTintStr, 0.17 * nearGlow);
|
|
9761
|
+
drawFixture(ctx, fx, fy, 1.8, rigTintStr);
|
|
9762
|
+
}
|
|
9763
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.75)`;
|
|
8965
9764
|
for (let i = 0; i < 8; i++) {
|
|
8966
9765
|
ctx.beginPath();
|
|
8967
|
-
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0,
|
|
9766
|
+
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0, TAU2);
|
|
8968
9767
|
ctx.fill();
|
|
8969
9768
|
}
|
|
8970
9769
|
ctx.fillStyle = "rgba(10,13,20,0.9)";
|
|
8971
9770
|
ctx.fillRect(0, TH - 26, TW, 26);
|
|
8972
9771
|
ctx.fillStyle = "rgba(30,37,54,0.9)";
|
|
8973
|
-
for (let i = 0; i < 9; i++)
|
|
9772
|
+
for (let i = 0; i < 9; i++) {
|
|
9773
|
+
const bx = i * (TW / 9) + TW / 18;
|
|
9774
|
+
ctx.beginPath();
|
|
9775
|
+
ctx.ellipse(bx, TH - 22, TW / 9 * 0.4, 15, 0, Math.PI, 0, true);
|
|
9776
|
+
ctx.fill();
|
|
9777
|
+
}
|
|
8974
9778
|
return { url: canvas.toDataURL("image/jpeg", 0.8), distanceM: Math.round(distM) };
|
|
8975
9779
|
}
|
|
8976
9780
|
|