@seatlayer/core 0.28.4 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5MLADB2N.js +53 -0
- package/dist/chunk-5MLADB2N.js.map +1 -0
- package/dist/index.cjs +904 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -1459
- package/dist/index.d.ts +36 -1459
- package/dist/index.js +913 -154
- package/dist/index.js.map +1 -1
- package/dist/types-B-tpUFqz.d.cts +1552 -0
- package/dist/types-B-tpUFqz.d.ts +1552 -0
- package/dist/view3d/index.cjs +1972 -0
- package/dist/view3d/index.cjs.map +1 -0
- package/dist/view3d/index.d.cts +176 -0
- package/dist/view3d/index.d.ts +176 -0
- package/dist/view3d/index.js +1906 -0
- package/dist/view3d/index.js.map +1 -0
- package/package.json +22 -2
package/dist/index.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);
|
|
@@ -8788,8 +8788,428 @@ function stageSightlinePitch(eyeHeightM, distM) {
|
|
|
8788
8788
|
var W = 2048;
|
|
8789
8789
|
var H = 1024;
|
|
8790
8790
|
var UNIT = METRES_PER_CHART_UNIT;
|
|
8791
|
+
var TAU2 = Math.PI * 2;
|
|
8792
|
+
var PX_PER_DEG = H / 180;
|
|
8791
8793
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
8792
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
|
+
}
|
|
8793
9213
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
8794
9214
|
const canvas = document.createElement("canvas");
|
|
8795
9215
|
canvas.width = W;
|
|
@@ -8799,102 +9219,428 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
8799
9219
|
const dy = focalPoint.y - seat.y;
|
|
8800
9220
|
const distU = Math.hypot(dx, dy);
|
|
8801
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]}`;
|
|
8802
9228
|
const sky = ctx.createLinearGradient(0, 0, 0, H);
|
|
8803
|
-
sky.addColorStop(0, "#
|
|
8804
|
-
sky.addColorStop(0.42, "#
|
|
8805
|
-
sky.addColorStop(0.5, "#
|
|
8806
|
-
sky.addColorStop(0.56, "#
|
|
8807
|
-
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");
|
|
8808
9235
|
ctx.fillStyle = sky;
|
|
8809
9236
|
ctx.fillRect(0, 0, W, H);
|
|
8810
|
-
const
|
|
8811
|
-
|
|
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));
|
|
8812
9248
|
const sightline = stageSightlinePitch(eyeM, distM);
|
|
9249
|
+
const stageHalfYaw = Math.min(80, Math.atan2(4, distM) * 180 / Math.PI);
|
|
8813
9250
|
const stageTopPitch = Math.min(45, sightline.topPitch);
|
|
8814
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) {
|
|
8815
9279
|
const sx0 = yawToX(-stageHalfYaw);
|
|
8816
9280
|
const sx1 = yawToX(stageHalfYaw);
|
|
8817
9281
|
const sy0 = pitchToY(stageTopPitch);
|
|
8818
9282
|
const sy1 = pitchToY(stageBasePitch);
|
|
8819
|
-
const
|
|
8820
|
-
|
|
8821
|
-
|
|
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)");
|
|
8822
9291
|
glow.addColorStop(1, "rgba(99, 102, 241, 0)");
|
|
8823
9292
|
ctx.fillStyle = glow;
|
|
8824
|
-
ctx.fillRect(sx0 -
|
|
8825
|
-
|
|
8826
|
-
|
|
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();
|
|
8827
9301
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8828
|
-
backdrop.addColorStop(0,
|
|
8829
|
-
backdrop.addColorStop(0.5,
|
|
8830
|
-
backdrop.addColorStop(1,
|
|
8831
|
-
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;
|
|
8832
9306
|
ctx.fillStyle = backdrop;
|
|
8833
|
-
|
|
8834
|
-
ctx.fillRect(sx0 + inset, sy0 + (sy1 - sy0) * 0.12, sx1 - sx0 - inset * 2, (sy1 - sy0) * 0.62);
|
|
9307
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8835
9308
|
ctx.globalAlpha = 1;
|
|
8836
|
-
ctx.
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
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
|
+
});
|
|
8847
9459
|
}
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
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);
|
|
8852
9527
|
ctx.beginPath();
|
|
8853
|
-
ctx.
|
|
8854
|
-
ctx.lineTo(bx - (sx1 - sx0) * 0.09, sy1);
|
|
8855
|
-
ctx.lineTo(bx + (sx1 - sx0) * 0.09, sy1);
|
|
8856
|
-
ctx.lineTo(bx + 8, pitchToY(Math.min(60, stageTopPitch + 25)));
|
|
8857
|
-
ctx.closePath();
|
|
9528
|
+
ctx.ellipse(px, py, s * 0.6, s, 0, 0, TAU2);
|
|
8858
9529
|
ctx.fill();
|
|
8859
9530
|
}
|
|
8860
|
-
ctx.
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
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;
|
|
8865
9570
|
ctx.beginPath();
|
|
8866
|
-
ctx.arc(
|
|
9571
|
+
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0, TAU2);
|
|
8867
9572
|
ctx.fill();
|
|
8868
9573
|
}
|
|
8869
|
-
|
|
8870
|
-
|
|
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 = [];
|
|
8871
9579
|
for (const other of neighborSeats) {
|
|
8872
9580
|
if (other.id === seat.id) continue;
|
|
8873
9581
|
const ox = other.x - seat.x;
|
|
8874
9582
|
const oy = other.y - seat.y;
|
|
8875
9583
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
8876
|
-
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;
|
|
8877
9586
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
8878
9587
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
8879
|
-
const
|
|
8880
|
-
const
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
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
|
+
}
|
|
8889
9643
|
}
|
|
8890
|
-
const poleFade = ctx.createLinearGradient(0, 0, 0, H);
|
|
8891
|
-
poleFade.addColorStop(0, "rgba(0,0,0,0.85)");
|
|
8892
|
-
poleFade.addColorStop(0.12, "rgba(0,0,0,0)");
|
|
8893
|
-
poleFade.addColorStop(0.88, "rgba(0,0,0,0)");
|
|
8894
|
-
poleFade.addColorStop(1, "rgba(0,0,0,0.85)");
|
|
8895
|
-
ctx.fillStyle = poleFade;
|
|
8896
|
-
ctx.fillRect(0, 0, W, H);
|
|
8897
|
-
return { url: canvas.toDataURL("image/jpeg", 0.82), distanceM: Math.round(distM) };
|
|
8898
9644
|
}
|
|
8899
9645
|
var TW = 480;
|
|
8900
9646
|
var TH = 270;
|
|
@@ -8910,12 +9656,16 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8910
9656
|
const dx = focalPoint.x - seat.x;
|
|
8911
9657
|
const dy = focalPoint.y - seat.y;
|
|
8912
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]}`;
|
|
8913
9663
|
const sky = ctx.createLinearGradient(0, 0, 0, TH);
|
|
8914
|
-
sky.addColorStop(0, "#
|
|
8915
|
-
sky.addColorStop(0.44, "#
|
|
8916
|
-
sky.addColorStop(0.5, "#
|
|
8917
|
-
sky.addColorStop(0.58, "#
|
|
8918
|
-
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");
|
|
8919
9669
|
ctx.fillStyle = sky;
|
|
8920
9670
|
ctx.fillRect(0, 0, TW, TH);
|
|
8921
9671
|
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
@@ -8929,58 +9679,102 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
8929
9679
|
const sy1 = pitchToTY(stageBasePitch);
|
|
8930
9680
|
const sw = sx1 - sx0;
|
|
8931
9681
|
const sh = sy1 - sy0;
|
|
8932
|
-
const
|
|
8933
|
-
|
|
8934
|
-
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)");
|
|
8935
9688
|
glow.addColorStop(1, "rgba(99,102,241,0)");
|
|
8936
9689
|
ctx.fillStyle = glow;
|
|
8937
9690
|
ctx.fillRect(sx0 - sw * 0.6, sy0 - sh * 1.2, sw * 2.2, sh * 3.4);
|
|
8938
|
-
|
|
8939
|
-
|
|
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();
|
|
8940
9698
|
const backdrop = ctx.createLinearGradient(0, sy0, 0, sy1);
|
|
8941
|
-
backdrop.addColorStop(0,
|
|
8942
|
-
backdrop.addColorStop(0.5,
|
|
8943
|
-
backdrop.addColorStop(1,
|
|
8944
|
-
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;
|
|
8945
9703
|
ctx.fillStyle = backdrop;
|
|
8946
|
-
|
|
8947
|
-
ctx.fillRect(sx0 + inset, sy0 + sh * 0.12, sw - inset * 2, sh * 0.62);
|
|
9704
|
+
ctx.fillRect(sx0, sy0, sw, sh);
|
|
8948
9705
|
ctx.globalAlpha = 1;
|
|
8949
|
-
ctx.
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
ctx.lineTo(bx - sw * 0.09, sy1);
|
|
8968
|
-
ctx.lineTo(bx + sw * 0.09, sy1);
|
|
8969
|
-
ctx.lineTo(bx + 5, pitchToTY(Math.min(HALF_VFOV, stageTopPitch + 18)));
|
|
8970
|
-
ctx.closePath();
|
|
8971
|
-
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
|
+
});
|
|
8972
9724
|
}
|
|
8973
|
-
ctx.
|
|
8974
|
-
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)`;
|
|
8975
9764
|
for (let i = 0; i < 8; i++) {
|
|
8976
9765
|
ctx.beginPath();
|
|
8977
|
-
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);
|
|
8978
9767
|
ctx.fill();
|
|
8979
9768
|
}
|
|
8980
9769
|
ctx.fillStyle = "rgba(10,13,20,0.9)";
|
|
8981
9770
|
ctx.fillRect(0, TH - 26, TW, 26);
|
|
8982
9771
|
ctx.fillStyle = "rgba(30,37,54,0.9)";
|
|
8983
|
-
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
|
+
}
|
|
8984
9778
|
return { url: canvas.toDataURL("image/jpeg", 0.8), distanceM: Math.round(distM) };
|
|
8985
9779
|
}
|
|
8986
9780
|
|