@seatlayer/core 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -22
- package/dist/chunk-FGS67GT3.js +1252 -0
- package/dist/chunk-FGS67GT3.js.map +1 -0
- package/dist/index.cjs +572 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +269 -909
- package/dist/index.js.map +1 -1
- package/dist/{types-B-tpUFqz.d.cts → types-CG2pEDoI.d.cts} +56 -0
- package/dist/{types-B-tpUFqz.d.ts → types-CG2pEDoI.d.ts} +56 -0
- package/dist/view3d/index.cjs +2315 -99
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +236 -27
- package/dist/view3d/index.d.ts +236 -27
- package/dist/view3d/index.js +1912 -98
- package/dist/view3d/index.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-5MLADB2N.js +0 -53
- package/dist/chunk-5MLADB2N.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,832 +1,41 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ACCESSIBILITY_RING_COLOR,
|
|
3
|
+
ACCESSIBILITY_TYPES,
|
|
4
|
+
CHART_STORAGE_KEY,
|
|
2
5
|
CHART_UNITS_PER_METRE,
|
|
6
|
+
LABEL_STYLE_MAX_SIZE,
|
|
7
|
+
LABEL_STYLE_MIN_SIZE,
|
|
3
8
|
METRES_PER_CHART_UNIT,
|
|
4
9
|
SEATED_EYE_HEIGHT_M,
|
|
10
|
+
SURROUNDINGS_SHAPE_ROLES,
|
|
5
11
|
TIER_HEIGHT_M,
|
|
12
|
+
accessibilityMeta,
|
|
13
|
+
accessibilityRingColor,
|
|
14
|
+
allObjects,
|
|
15
|
+
chartBounds,
|
|
16
|
+
expandBooth,
|
|
17
|
+
expandChart,
|
|
18
|
+
expandRow,
|
|
19
|
+
expandRowSlots,
|
|
20
|
+
expandTable,
|
|
21
|
+
expandTableSlots,
|
|
22
|
+
floorObjects,
|
|
23
|
+
floorsOf,
|
|
24
|
+
layerOf,
|
|
25
|
+
objectCenter,
|
|
26
|
+
owningSectionForObject,
|
|
27
|
+
pointInPolygon,
|
|
28
|
+
pointInPolygonWithHoles,
|
|
29
|
+
polygonLabelPoint,
|
|
30
|
+
rowInventoryCount,
|
|
31
|
+
rowSeatPositions,
|
|
32
|
+
seatLabelPart,
|
|
6
33
|
sectionElevationTier,
|
|
7
|
-
sectionGeometry
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{ key: "wheelchair", label: "Wheelchair space", short: "Wheelchair", icon: "\u267F" },
|
|
13
|
-
{ key: "companion", label: "Companion seat", short: "Companion", icon: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" },
|
|
14
|
-
{ key: "semi-ambulatory", label: "Semi-ambulatory (limited mobility)", short: "Limited mobility", icon: "\u{1F9AF}" },
|
|
15
|
-
{ key: "hearing", label: "Assistive listening", short: "Hearing", icon: "\u{1F9BB}" },
|
|
16
|
-
{ key: "cart", label: "CART live-caption view", short: "CART captions", icon: "CC" },
|
|
17
|
-
{ key: "sign-language", label: "Sign-language view", short: "Sign language", icon: "\u{1F91F}" },
|
|
18
|
-
{ key: "plus-size", label: "Plus-size seat", short: "Plus-size", icon: "\u{1F4BA}" },
|
|
19
|
-
{ key: "lift-armrest", label: "Lift-up armrest", short: "Lift armrest", icon: "\u2195\uFE0F" }
|
|
20
|
-
];
|
|
21
|
-
var ACCESSIBILITY_LABEL = new Map(ACCESSIBILITY_TYPES.map((a) => [a.key, a]));
|
|
22
|
-
function accessibilityMeta(key) {
|
|
23
|
-
return ACCESSIBILITY_LABEL.get(key);
|
|
24
|
-
}
|
|
25
|
-
var ACCESSIBILITY_RING_COLOR = {
|
|
26
|
-
wheelchair: "#3b82f6",
|
|
27
|
-
companion: "#8b5cf6",
|
|
28
|
-
"semi-ambulatory": "#0ea5e9",
|
|
29
|
-
hearing: "#14b8a6",
|
|
30
|
-
cart: "#7c3aed",
|
|
31
|
-
"sign-language": "#f59e0b",
|
|
32
|
-
"plus-size": "#ec4899",
|
|
33
|
-
"lift-armrest": "#22c55e"
|
|
34
|
-
};
|
|
35
|
-
function accessibilityRingColor(types) {
|
|
36
|
-
const primary = types?.[0];
|
|
37
|
-
return primary && ACCESSIBILITY_RING_COLOR[primary] || "#3b82f6";
|
|
38
|
-
}
|
|
39
|
-
var LABEL_STYLE_MIN_SIZE = 8;
|
|
40
|
-
var LABEL_STYLE_MAX_SIZE = 24;
|
|
41
|
-
var SURROUNDINGS_SHAPE_ROLES = [
|
|
42
|
-
"reference-focal",
|
|
43
|
-
"bar",
|
|
44
|
-
"entrance",
|
|
45
|
-
"exit",
|
|
46
|
-
"restroom",
|
|
47
|
-
"screen",
|
|
48
|
-
"sound",
|
|
49
|
-
"concession",
|
|
50
|
-
"coat",
|
|
51
|
-
"wall"
|
|
52
|
-
];
|
|
53
|
-
var SURROUNDINGS_SHAPE_ROLE_SET = new Set(SURROUNDINGS_SHAPE_ROLES);
|
|
54
|
-
function layerOf(obj) {
|
|
55
|
-
switch (obj.type) {
|
|
56
|
-
case "row":
|
|
57
|
-
case "table":
|
|
58
|
-
case "gaArea":
|
|
59
|
-
case "booth":
|
|
60
|
-
case "section":
|
|
61
|
-
return "interactive";
|
|
62
|
-
// Raster décor follows its explicit authored z-layer. Absence retains the
|
|
63
|
-
// legacy Background default; bitmap content is never guessed semantically.
|
|
64
|
-
case "decorImage":
|
|
65
|
-
return obj.layer === "foreground" ? "foreground" : "background";
|
|
66
|
-
// Source-backed focal geometry and curated venue landmarks help an author
|
|
67
|
-
// orient around the sellable plan. A stage and an ordinary authored shape
|
|
68
|
-
// remain Background even when they carry an arbitrary/custom role.
|
|
69
|
-
case "shape":
|
|
70
|
-
return obj.role && SURROUNDINGS_SHAPE_ROLE_SET.has(obj.role) ? "surroundings" : "background";
|
|
71
|
-
// Free text — including semantic icon text — is background furniture.
|
|
72
|
-
case "text":
|
|
73
|
-
return "background";
|
|
74
|
-
default:
|
|
75
|
-
return "interactive";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
var CHART_STORAGE_KEY = "seatmap.chart";
|
|
79
|
-
|
|
80
|
-
// src/core/complexGeometry.ts
|
|
81
|
-
function cubicPoint(path, t2) {
|
|
82
|
-
const u = 1 - t2;
|
|
83
|
-
const a = u * u * u;
|
|
84
|
-
const b = 3 * u * u * t2;
|
|
85
|
-
const c = 3 * u * t2 * t2;
|
|
86
|
-
const d = t2 * t2 * t2;
|
|
87
|
-
return {
|
|
88
|
-
x: a * path.start.x + b * path.control1.x + c * path.control2.x + d * path.end.x,
|
|
89
|
-
y: a * path.start.y + b * path.control1.y + c * path.control2.y + d * path.end.y
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function distributeAlongCubic(path, count, resolution = 192) {
|
|
93
|
-
if (!Number.isInteger(count) || count < 1) throw new Error("Path point count must be a positive integer");
|
|
94
|
-
if (count === 1) return [cubicPoint(path, 0.5)];
|
|
95
|
-
const samples = Array.from({ length: resolution + 1 }, (_, index) => cubicPoint(path, index / resolution));
|
|
96
|
-
const lengths = new Float64Array(samples.length);
|
|
97
|
-
for (let index = 1; index < samples.length; index += 1) {
|
|
98
|
-
const dx = samples[index].x - samples[index - 1].x;
|
|
99
|
-
const dy = samples[index].y - samples[index - 1].y;
|
|
100
|
-
lengths[index] = lengths[index - 1] + Math.hypot(dx, dy);
|
|
101
|
-
}
|
|
102
|
-
const total = lengths[lengths.length - 1];
|
|
103
|
-
if (total <= 1e-9) return Array.from({ length: count }, () => ({ ...path.start }));
|
|
104
|
-
const output = [];
|
|
105
|
-
let segment = 1;
|
|
106
|
-
for (let index = 0; index < count; index += 1) {
|
|
107
|
-
const target = total * index / (count - 1);
|
|
108
|
-
while (segment < lengths.length - 1 && lengths[segment] < target) segment += 1;
|
|
109
|
-
const before = lengths[segment - 1];
|
|
110
|
-
const after = lengths[segment];
|
|
111
|
-
const ratio = after === before ? 0 : (target - before) / (after - before);
|
|
112
|
-
output.push({
|
|
113
|
-
x: samples[segment - 1].x + (samples[segment].x - samples[segment - 1].x) * ratio,
|
|
114
|
-
y: samples[segment - 1].y + (samples[segment].y - samples[segment - 1].y) * ratio
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
return output;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// src/core/sectionPath.ts
|
|
121
|
-
var TAU = Math.PI * 2;
|
|
122
|
-
function translateSectionOutlinePath(path, dx, dy) {
|
|
123
|
-
const translate = (point) => ({ x: point.x + dx, y: point.y + dy });
|
|
124
|
-
return transformSectionOutlinePath(path, translate);
|
|
125
|
-
}
|
|
126
|
-
function transformSectionOutlinePath(path, transform, radiusScale = 1, reflected = false) {
|
|
127
|
-
return {
|
|
128
|
-
...path,
|
|
129
|
-
start: transform(path.start),
|
|
130
|
-
segments: path.segments.map((segment) => segment.kind === "line" ? { ...segment, end: transform(segment.end) } : segment.kind === "arc" ? {
|
|
131
|
-
...segment,
|
|
132
|
-
center: transform(segment.center),
|
|
133
|
-
radius: segment.radius * Math.abs(radiusScale),
|
|
134
|
-
clockwise: reflected ? !segment.clockwise : segment.clockwise,
|
|
135
|
-
end: transform(segment.end)
|
|
136
|
-
} : {
|
|
137
|
-
...segment,
|
|
138
|
-
control1: transform(segment.control1),
|
|
139
|
-
control2: transform(segment.control2),
|
|
140
|
-
end: transform(segment.end)
|
|
141
|
-
})
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/core/labeling.ts
|
|
146
|
-
var FULL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
147
|
-
var LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
148
|
-
function toBijectiveBase(index, alphabet) {
|
|
149
|
-
const base = alphabet.length;
|
|
150
|
-
let n = index + 1;
|
|
151
|
-
let out = "";
|
|
152
|
-
while (n > 0) {
|
|
153
|
-
n -= 1;
|
|
154
|
-
const rem = n % base;
|
|
155
|
-
out = alphabet[rem] + out;
|
|
156
|
-
n = Math.floor(n / base);
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
function toLetters(value, lower = false) {
|
|
161
|
-
const alphabet = lower ? LOWER_ALPHABET : FULL_ALPHABET;
|
|
162
|
-
return toBijectiveBase(Math.max(0, Math.floor(value) - 1), alphabet);
|
|
163
|
-
}
|
|
164
|
-
var ROMAN_TABLE = [
|
|
165
|
-
[1e3, "M"],
|
|
166
|
-
[900, "CM"],
|
|
167
|
-
[500, "D"],
|
|
168
|
-
[400, "CD"],
|
|
169
|
-
[100, "C"],
|
|
170
|
-
[90, "XC"],
|
|
171
|
-
[50, "L"],
|
|
172
|
-
[40, "XL"],
|
|
173
|
-
[10, "X"],
|
|
174
|
-
[9, "IX"],
|
|
175
|
-
[5, "V"],
|
|
176
|
-
[4, "IV"],
|
|
177
|
-
[1, "I"]
|
|
178
|
-
];
|
|
179
|
-
function toRoman(value) {
|
|
180
|
-
if (!Number.isFinite(value) || value <= 0) return String(value);
|
|
181
|
-
let remaining = Math.floor(value);
|
|
182
|
-
let out = "";
|
|
183
|
-
for (const [n, sym] of ROMAN_TABLE) {
|
|
184
|
-
while (remaining >= n) {
|
|
185
|
-
out += sym;
|
|
186
|
-
remaining -= n;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return out;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/core/layout.ts
|
|
193
|
-
function overrideAccessibility(o) {
|
|
194
|
-
if (!o) return [];
|
|
195
|
-
if (o.accessibility && o.accessibility.length) {
|
|
196
|
-
return o.wheelchairSpaceType && !o.accessibility.includes("wheelchair") ? ["wheelchair", ...o.accessibility] : o.accessibility;
|
|
197
|
-
}
|
|
198
|
-
if (o.wheelchairSpaceType) return ["wheelchair"];
|
|
199
|
-
return o.accessible ? ["wheelchair"] : [];
|
|
200
|
-
}
|
|
201
|
-
var DEG = Math.PI / 180;
|
|
202
|
-
var SEAT_R = 9;
|
|
203
|
-
var TABLE_SEAT_OFFSET = 16;
|
|
204
|
-
function place(lx, ly, deg, origin) {
|
|
205
|
-
const a = deg * DEG;
|
|
206
|
-
const cos = Math.cos(a);
|
|
207
|
-
const sin = Math.sin(a);
|
|
208
|
-
return {
|
|
209
|
-
x: origin.x + lx * cos - ly * sin,
|
|
210
|
-
y: origin.y + lx * sin + ly * cos
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function rowSeatPositions(row) {
|
|
214
|
-
const { seatCount, seatSpacing, curve, rotation, origin } = row;
|
|
215
|
-
const out = [];
|
|
216
|
-
if (row.path) return distributeAlongCubic(row.path, seatCount);
|
|
217
|
-
if (seatCount <= 1) {
|
|
218
|
-
if (seatCount === 1) out.push({ x: origin.x, y: origin.y });
|
|
219
|
-
return out;
|
|
220
|
-
}
|
|
221
|
-
if (curve === 0) {
|
|
222
|
-
for (let i = 0; i < seatCount; i++) out.push(place(i * seatSpacing, 0, rotation, origin));
|
|
223
|
-
return out;
|
|
224
|
-
}
|
|
225
|
-
const arcStep = curve / (seatCount - 1) * DEG;
|
|
226
|
-
const radius = seatSpacing / (2 * Math.sin(Math.abs(arcStep) / 2));
|
|
227
|
-
for (let i = 0; i < seatCount; i++) {
|
|
228
|
-
const phi = i * arcStep;
|
|
229
|
-
const lx = radius * Math.sin(phi);
|
|
230
|
-
const ly = -radius + radius * Math.cos(phi);
|
|
231
|
-
out.push(place(lx, ly, rotation, origin));
|
|
232
|
-
}
|
|
233
|
-
return out;
|
|
234
|
-
}
|
|
235
|
-
function overrideMap(row) {
|
|
236
|
-
const m = /* @__PURE__ */ new Map();
|
|
237
|
-
if (row.overrides) for (const o of row.overrides) m.set(o.index, o);
|
|
238
|
-
return m;
|
|
239
|
-
}
|
|
240
|
-
function rowInventoryCount(row) {
|
|
241
|
-
const skipped = new Set((row.overrides ?? []).filter((override) => override.skip && Number.isInteger(override.index) && override.index >= 0 && override.index < row.seatCount).map((override) => override.index));
|
|
242
|
-
return Math.max(0, row.seatCount - skipped.size);
|
|
243
|
-
}
|
|
244
|
-
function centerRank(n) {
|
|
245
|
-
const rank = new Array(n);
|
|
246
|
-
Array.from({ length: n }, (_, i) => i).sort((a, b) => Math.abs(2 * a - (n - 1)) - Math.abs(2 * b - (n - 1)) || a - b).forEach((idx, k) => rank[idx] = k);
|
|
247
|
-
return rank;
|
|
248
|
-
}
|
|
249
|
-
function seatLabelPart(row, i) {
|
|
250
|
-
const rawStart = row.seatLabelStart ?? 1;
|
|
251
|
-
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
252
|
-
const step = row.seatNumbering?.step ?? 1;
|
|
253
|
-
const scheme = row.seatNumbering?.scheme ?? "decimal";
|
|
254
|
-
const prefix = row.seatNumbering?.prefix ?? "";
|
|
255
|
-
const endAt = row.seatNumbering?.endAt;
|
|
256
|
-
const n = row.seatCount;
|
|
257
|
-
if (scheme === "updown" || scheme === "updown-descending") {
|
|
258
|
-
const half = Math.ceil(n / 2);
|
|
259
|
-
const core2 = scheme === "updown" ? i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i) : i < half ? rawStart + 2 * (half - 1 - i) : rawStart + 1 + 2 * (i - half);
|
|
260
|
-
return `${prefix}${core2}`;
|
|
261
|
-
}
|
|
262
|
-
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
263
|
-
const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;
|
|
264
|
-
const p = dir === "center" ? centerRank(n)[i] : dir === "rtl" ? n - 1 - i : i;
|
|
265
|
-
let core;
|
|
266
|
-
switch (scheme) {
|
|
267
|
-
case "odd": {
|
|
268
|
-
const firstOdd = start % 2 === 1 ? start : start + 1;
|
|
269
|
-
core = String(firstOdd + p * 2);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
case "even": {
|
|
273
|
-
const firstEven = start % 2 === 0 ? start : start + 1;
|
|
274
|
-
core = String(firstEven + p * 2);
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
case "roman":
|
|
278
|
-
core = toRoman(start + p * step);
|
|
279
|
-
break;
|
|
280
|
-
case "letters-upper":
|
|
281
|
-
core = toLetters(start + p * step, false);
|
|
282
|
-
break;
|
|
283
|
-
case "letters-lower":
|
|
284
|
-
core = toLetters(start + p * step, true);
|
|
285
|
-
break;
|
|
286
|
-
case "decimal":
|
|
287
|
-
default:
|
|
288
|
-
core = String(start + p * step);
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
return `${prefix}${core}`;
|
|
292
|
-
}
|
|
293
|
-
function expandRowSlots(row) {
|
|
294
|
-
const ov = overrideMap(row);
|
|
295
|
-
return rowSeatPositions(row).map((p, i) => {
|
|
296
|
-
const o = ov.get(i);
|
|
297
|
-
const accessibility = overrideAccessibility(o);
|
|
298
|
-
const part = seatLabelPart(row, i);
|
|
299
|
-
const inventoryLabel = o?.label ?? `${row.label}-${part}`;
|
|
300
|
-
const displayPrefix = row.displayLabel ?? row.label;
|
|
301
|
-
const commercial = { ...row.commercial, ...o?.commercial };
|
|
302
|
-
return {
|
|
303
|
-
index: i,
|
|
304
|
-
x: p.x + (o?.dx ?? 0),
|
|
305
|
-
y: p.y + (o?.dy ?? 0),
|
|
306
|
-
label: inventoryLabel,
|
|
307
|
-
displayLabel: o?.displayLabel ?? `${displayPrefix}-${part}`,
|
|
308
|
-
categoryKey: o?.categoryKey ?? row.categoryKey,
|
|
309
|
-
skipped: !!o?.skip,
|
|
310
|
-
accessible: accessibility.length > 0,
|
|
311
|
-
accessibility,
|
|
312
|
-
wheelchairSpaceType: o?.wheelchairSpaceType,
|
|
313
|
-
commercial: Object.values(commercial).some((value) => value !== void 0 && value !== false && value !== "") ? commercial : void 0,
|
|
314
|
-
viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,
|
|
315
|
-
labelStyle: o?.labelStyle
|
|
316
|
-
};
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
function expandRow(row) {
|
|
320
|
-
const seats = [];
|
|
321
|
-
for (const slot of expandRowSlots(row)) {
|
|
322
|
-
if (slot.skipped) continue;
|
|
323
|
-
seats.push({
|
|
324
|
-
id: `${row.id}:${slot.index}`,
|
|
325
|
-
label: slot.label,
|
|
326
|
-
displayLabel: slot.displayLabel === slot.label ? void 0 : slot.displayLabel,
|
|
327
|
-
x: slot.x,
|
|
328
|
-
y: slot.y,
|
|
329
|
-
rowId: row.id,
|
|
330
|
-
categoryKey: slot.categoryKey,
|
|
331
|
-
accessible: slot.accessible || void 0,
|
|
332
|
-
accessibility: slot.accessibility.length ? slot.accessibility : void 0,
|
|
333
|
-
wheelchairSpaceType: slot.wheelchairSpaceType,
|
|
334
|
-
commercial: slot.commercial,
|
|
335
|
-
viewUrl: slot.viewUrl,
|
|
336
|
-
labelStyle: slot.labelStyle
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
return seats;
|
|
340
|
-
}
|
|
341
|
-
function tableSeatCountsBySide(t2) {
|
|
342
|
-
if (t2.seatCountsBySide) return { ...t2.seatCountsBySide };
|
|
343
|
-
const enabled = t2.sides && t2.sides.length ? t2.sides : ["top", "bottom"];
|
|
344
|
-
const order = ["top", "bottom", "left", "right"].filter((side) => enabled.includes(side));
|
|
345
|
-
const counts = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
346
|
-
if (!order.length) return counts;
|
|
347
|
-
const n = Math.max(0, Math.round(t2.seatCount));
|
|
348
|
-
for (let index = 0; index < n; index++) counts[order[index % order.length]] += 1;
|
|
349
|
-
return counts;
|
|
350
|
-
}
|
|
351
|
-
function expandTableSlots(t2) {
|
|
352
|
-
const seats = [];
|
|
353
|
-
const n = Math.max(0, Math.round(t2.seatCount));
|
|
354
|
-
if (n === 0) return seats;
|
|
355
|
-
const overrides2 = new Map((t2.overrides ?? []).map((override) => [override.index, override]));
|
|
356
|
-
const mk = (index, x, y, side) => {
|
|
357
|
-
const override = overrides2.get(index);
|
|
358
|
-
const accessibility = overrideAccessibility(override);
|
|
359
|
-
const label = override?.label ?? `${t2.label}-${index + 1}`;
|
|
360
|
-
const displayPrefix = t2.displayLabel ?? t2.label;
|
|
361
|
-
return {
|
|
362
|
-
index,
|
|
363
|
-
label,
|
|
364
|
-
displayLabel: override?.displayLabel ?? `${displayPrefix}-${index + 1}`,
|
|
365
|
-
x: x + (override?.dx ?? 0),
|
|
366
|
-
y: y + (override?.dy ?? 0),
|
|
367
|
-
categoryKey: override?.categoryKey ?? t2.categoryKey,
|
|
368
|
-
skipped: !!override?.skip,
|
|
369
|
-
accessible: accessibility.length > 0,
|
|
370
|
-
accessibility,
|
|
371
|
-
wheelchairSpaceType: override?.wheelchairSpaceType,
|
|
372
|
-
commercial: override?.commercial,
|
|
373
|
-
viewUrl: override?.viewFromSeatUrl,
|
|
374
|
-
labelStyle: override?.labelStyle,
|
|
375
|
-
...side ? { side } : {}
|
|
376
|
-
};
|
|
377
|
-
};
|
|
378
|
-
if (t2.shape === "round") {
|
|
379
|
-
const R = (t2.radius ?? 40) + TABLE_SEAT_OFFSET;
|
|
380
|
-
const base = t2.rotation * DEG;
|
|
381
|
-
const arc = Math.max(0, Math.min(360, t2.seatArc ?? 360));
|
|
382
|
-
if (arc >= 360 || n === 1) {
|
|
383
|
-
for (let i = 0; i < n; i++) {
|
|
384
|
-
const a = base + i / n * 2 * Math.PI;
|
|
385
|
-
seats.push(mk(i, t2.center.x + R * Math.cos(a), t2.center.y + R * Math.sin(a)));
|
|
386
|
-
}
|
|
387
|
-
return seats;
|
|
388
|
-
}
|
|
389
|
-
const arcRad = arc * DEG;
|
|
390
|
-
const halfGap = (2 * Math.PI - arcRad) / 2;
|
|
391
|
-
const start = base + halfGap;
|
|
392
|
-
for (let i = 0; i < n; i++) {
|
|
393
|
-
const a = start + i / (n - 1) * arcRad;
|
|
394
|
-
seats.push(mk(i, t2.center.x + R * Math.cos(a), t2.center.y + R * Math.sin(a)));
|
|
395
|
-
}
|
|
396
|
-
return seats;
|
|
397
|
-
}
|
|
398
|
-
const w = t2.width ?? 80;
|
|
399
|
-
const h = t2.height ?? 50;
|
|
400
|
-
const counts = tableSeatCountsBySide(t2);
|
|
401
|
-
const order = ["top", "bottom", "left", "right"];
|
|
402
|
-
let idx = 0;
|
|
403
|
-
for (const side of order) {
|
|
404
|
-
const count = counts[side];
|
|
405
|
-
for (let j = 0; j < count; j++) {
|
|
406
|
-
let localX;
|
|
407
|
-
let localY;
|
|
408
|
-
if (side === "top") {
|
|
409
|
-
localX = -w / 2 + (j + 0.5) * w / count;
|
|
410
|
-
localY = -h / 2 - TABLE_SEAT_OFFSET;
|
|
411
|
-
} else if (side === "bottom") {
|
|
412
|
-
localX = -w / 2 + (j + 0.5) * w / count;
|
|
413
|
-
localY = h / 2 + TABLE_SEAT_OFFSET;
|
|
414
|
-
} else if (side === "left") {
|
|
415
|
-
localX = -w / 2 - TABLE_SEAT_OFFSET;
|
|
416
|
-
localY = -h / 2 + (j + 0.5) * h / count;
|
|
417
|
-
} else {
|
|
418
|
-
localX = w / 2 + TABLE_SEAT_OFFSET;
|
|
419
|
-
localY = -h / 2 + (j + 0.5) * h / count;
|
|
420
|
-
}
|
|
421
|
-
const point = place(localX, localY, t2.rotation, t2.center);
|
|
422
|
-
seats.push(mk(idx++, point.x, point.y, side));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return seats;
|
|
426
|
-
}
|
|
427
|
-
function tableInventoryCount(t2) {
|
|
428
|
-
if (t2.bookAsWhole || t2.variableOccupancy) return Math.max(0, Math.round(t2.seatCount));
|
|
429
|
-
return expandTableSlots(t2).filter((slot) => !slot.skipped).length;
|
|
430
|
-
}
|
|
431
|
-
function expandTable(t2) {
|
|
432
|
-
return expandTableSlots(t2).filter((slot) => !slot.skipped).map((slot) => ({
|
|
433
|
-
id: `${t2.id}:${slot.index}`,
|
|
434
|
-
label: slot.label,
|
|
435
|
-
...slot.displayLabel !== slot.label ? { displayLabel: slot.displayLabel } : {},
|
|
436
|
-
x: slot.x,
|
|
437
|
-
y: slot.y,
|
|
438
|
-
rowId: t2.id,
|
|
439
|
-
categoryKey: slot.categoryKey,
|
|
440
|
-
...slot.accessible ? { accessible: true } : {},
|
|
441
|
-
...slot.accessibility.length ? { accessibility: slot.accessibility } : {},
|
|
442
|
-
...slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {},
|
|
443
|
-
...slot.commercial ? { commercial: slot.commercial } : {},
|
|
444
|
-
...slot.viewUrl ? { viewUrl: slot.viewUrl } : {},
|
|
445
|
-
...slot.labelStyle ? { labelStyle: slot.labelStyle } : {}
|
|
446
|
-
}));
|
|
447
|
-
}
|
|
448
|
-
function expandBooth(b) {
|
|
449
|
-
return [
|
|
450
|
-
{
|
|
451
|
-
id: `${b.id}:0`,
|
|
452
|
-
label: b.label,
|
|
453
|
-
...b.displayLabel && b.displayLabel !== b.label ? { displayLabel: b.displayLabel } : {},
|
|
454
|
-
x: b.center.x,
|
|
455
|
-
y: b.center.y,
|
|
456
|
-
rowId: b.id,
|
|
457
|
-
categoryKey: b.categoryKey,
|
|
458
|
-
kind: "booth"
|
|
459
|
-
}
|
|
460
|
-
];
|
|
461
|
-
}
|
|
462
|
-
function pointInPolygon(p, poly) {
|
|
463
|
-
let inside = false;
|
|
464
|
-
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
465
|
-
const xi = poly[i].x;
|
|
466
|
-
const yi = poly[i].y;
|
|
467
|
-
const xj = poly[j].x;
|
|
468
|
-
const yj = poly[j].y;
|
|
469
|
-
const hit = yi > p.y !== yj > p.y && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi;
|
|
470
|
-
if (hit) inside = !inside;
|
|
471
|
-
}
|
|
472
|
-
return inside;
|
|
473
|
-
}
|
|
474
|
-
function pointOnPolygonBoundary(p, poly) {
|
|
475
|
-
return poly.some((start, index) => {
|
|
476
|
-
const end = poly[(index + 1) % poly.length];
|
|
477
|
-
const cross = (p.y - start.y) * (end.x - start.x) - (p.x - start.x) * (end.y - start.y);
|
|
478
|
-
if (Math.abs(cross) > 1e-7) return false;
|
|
479
|
-
const dot = (p.x - start.x) * (end.x - start.x) + (p.y - start.y) * (end.y - start.y);
|
|
480
|
-
const lengthSquared = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;
|
|
481
|
-
return dot >= -1e-7 && dot <= lengthSquared + 1e-7;
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
function pointInPolygonWithHoles(p, outer, holes) {
|
|
485
|
-
return pointInPolygon(p, outer) && !(holes ?? []).some((hole) => pointInPolygon(p, hole) || pointOnPolygonBoundary(p, hole));
|
|
486
|
-
}
|
|
487
|
-
function polygonLabelPoint(outer, holes) {
|
|
488
|
-
if (!outer.length) return { x: 0, y: 0 };
|
|
489
|
-
const xs = outer.map((point) => point.x);
|
|
490
|
-
const ys = outer.map((point) => point.y);
|
|
491
|
-
const bounds2 = { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };
|
|
492
|
-
const centroid2 = polygonCentroid(outer);
|
|
493
|
-
if (pointInPolygonWithHoles(centroid2, outer, holes)) return centroid2;
|
|
494
|
-
let best = outer[0];
|
|
495
|
-
let bestScore = -Infinity;
|
|
496
|
-
const rings = [outer, ...holes ?? []];
|
|
497
|
-
for (let row = 1; row < 24; row += 1) {
|
|
498
|
-
for (let column = 1; column < 24; column += 1) {
|
|
499
|
-
const point = {
|
|
500
|
-
x: bounds2.minX + (bounds2.maxX - bounds2.minX) * column / 24,
|
|
501
|
-
y: bounds2.minY + (bounds2.maxY - bounds2.minY) * row / 24
|
|
502
|
-
};
|
|
503
|
-
if (!pointInPolygonWithHoles(point, outer, holes)) continue;
|
|
504
|
-
const score = Math.min(...rings.flatMap((ring) => ring.map((start, index) => {
|
|
505
|
-
const end = ring[(index + 1) % ring.length];
|
|
506
|
-
const dx = end.x - start.x;
|
|
507
|
-
const dy = end.y - start.y;
|
|
508
|
-
const denominator = dx * dx + dy * dy;
|
|
509
|
-
const projection = denominator ? ((point.x - start.x) * dx + (point.y - start.y) * dy) / denominator : 0;
|
|
510
|
-
const t2 = Math.max(0, Math.min(1, projection));
|
|
511
|
-
return Math.hypot(point.x - (start.x + t2 * dx), point.y - (start.y + t2 * dy));
|
|
512
|
-
})));
|
|
513
|
-
if (score > bestScore) {
|
|
514
|
-
best = point;
|
|
515
|
-
bestScore = score;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return best;
|
|
520
|
-
}
|
|
521
|
-
function polygonCentroid(pts) {
|
|
522
|
-
if (!pts.length) return { x: 0, y: 0 };
|
|
523
|
-
let x = 0;
|
|
524
|
-
let y = 0;
|
|
525
|
-
for (const p of pts) {
|
|
526
|
-
x += p.x;
|
|
527
|
-
y += p.y;
|
|
528
|
-
}
|
|
529
|
-
return { x: x / pts.length, y: y / pts.length };
|
|
530
|
-
}
|
|
531
|
-
function objectCenter(o) {
|
|
532
|
-
switch (o.type) {
|
|
533
|
-
case "row": {
|
|
534
|
-
const seats = expandRow(o);
|
|
535
|
-
if (!seats.length) return { x: o.origin.x, y: o.origin.y };
|
|
536
|
-
let x = 0;
|
|
537
|
-
let y = 0;
|
|
538
|
-
for (const s of seats) {
|
|
539
|
-
x += s.x;
|
|
540
|
-
y += s.y;
|
|
541
|
-
}
|
|
542
|
-
return { x: x / seats.length, y: y / seats.length };
|
|
543
|
-
}
|
|
544
|
-
case "table":
|
|
545
|
-
case "booth":
|
|
546
|
-
return { x: o.center.x, y: o.center.y };
|
|
547
|
-
case "gaArea":
|
|
548
|
-
return polygonCentroid(o.points);
|
|
549
|
-
case "section":
|
|
550
|
-
return polygonCentroid(o.outline);
|
|
551
|
-
case "text":
|
|
552
|
-
return { x: o.position.x, y: o.position.y };
|
|
553
|
-
case "shape":
|
|
554
|
-
if (o.points && o.points.length) return polygonCentroid(o.points);
|
|
555
|
-
if (o.x != null && o.y != null && o.width != null && o.height != null) {
|
|
556
|
-
return { x: o.x + o.width / 2, y: o.y + o.height / 2 };
|
|
557
|
-
}
|
|
558
|
-
return { x: o.x ?? 0, y: o.y ?? 0 };
|
|
559
|
-
case "decorImage":
|
|
560
|
-
return { x: o.x + o.width / 2, y: o.y + o.height / 2 };
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
function samePoints(left, right) {
|
|
564
|
-
return left.length === right.length && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);
|
|
565
|
-
}
|
|
566
|
-
function sameGASurfaceAsSection(object, section) {
|
|
567
|
-
if (object.type !== "gaArea" || !samePoints(object.points, section.outline)) return false;
|
|
568
|
-
const objectHoles = object.holes ?? [];
|
|
569
|
-
const sectionHoles = section.holes ?? [];
|
|
570
|
-
return objectHoles.length === sectionHoles.length && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));
|
|
571
|
-
}
|
|
572
|
-
function owningSectionForObject(objects, object) {
|
|
573
|
-
const sections = objects.filter((candidate) => candidate.type === "section");
|
|
574
|
-
const referencedLogicalId = "referenceInventorySource" in object ? object.referenceInventorySource?.logicalSectionId : void 0;
|
|
575
|
-
const center = objectCenter(object);
|
|
576
|
-
const referencedOwner = referencedLogicalId ? sections.find((section) => (section.logicalSectionId ?? section.id) === referencedLogicalId && (sameGASurfaceAsSection(object, section) || pointInPolygonWithHoles(center, section.outline, section.holes))) : void 0;
|
|
577
|
-
return referencedOwner ?? sections.find((section) => pointInPolygonWithHoles(center, section.outline, section.holes));
|
|
578
|
-
}
|
|
579
|
-
function floorsOf(doc) {
|
|
580
|
-
if (doc.floors && doc.floors.length) return doc.floors;
|
|
581
|
-
return [{
|
|
582
|
-
id: "floor-0",
|
|
583
|
-
name: "Main",
|
|
584
|
-
objects: doc.objects,
|
|
585
|
-
focalPoint: doc.focalPoint,
|
|
586
|
-
referenceImage: doc.referenceImage,
|
|
587
|
-
backgroundImage: doc.backgroundImage
|
|
588
|
-
}];
|
|
589
|
-
}
|
|
590
|
-
function floorObjects(doc, floorId) {
|
|
591
|
-
const floors = floorsOf(doc);
|
|
592
|
-
return (floorId ? floors.find((f) => f.id === floorId) : floors[0])?.objects ?? [];
|
|
593
|
-
}
|
|
594
|
-
function allObjects(doc) {
|
|
595
|
-
return doc.floors && doc.floors.length ? doc.floors.flatMap((f) => f.objects) : doc.objects;
|
|
596
|
-
}
|
|
597
|
-
function translateObject(o, dx, dy) {
|
|
598
|
-
const p = (pt) => ({ x: pt.x + dx, y: pt.y + dy });
|
|
599
|
-
const pts = (a) => a.map(p);
|
|
600
|
-
switch (o.type) {
|
|
601
|
-
case "row":
|
|
602
|
-
return { ...o, origin: p(o.origin) };
|
|
603
|
-
case "table":
|
|
604
|
-
case "booth":
|
|
605
|
-
return { ...o, center: p(o.center) };
|
|
606
|
-
case "gaArea":
|
|
607
|
-
return { ...o, points: pts(o.points), ...o.holes ? { holes: o.holes.map(pts) } : {} };
|
|
608
|
-
case "section":
|
|
609
|
-
return {
|
|
610
|
-
...o,
|
|
611
|
-
outline: pts(o.outline),
|
|
612
|
-
...o.outlinePath ? { outlinePath: translateSectionOutlinePath(o.outlinePath, dx, dy) } : {},
|
|
613
|
-
...o.holes ? { holes: o.holes.map(pts) } : {}
|
|
614
|
-
};
|
|
615
|
-
case "text":
|
|
616
|
-
return { ...o, position: p(o.position) };
|
|
617
|
-
case "shape":
|
|
618
|
-
return {
|
|
619
|
-
...o,
|
|
620
|
-
...o.points ? { points: pts(o.points) } : {},
|
|
621
|
-
...o.x != null ? { x: o.x + dx } : {},
|
|
622
|
-
...o.y != null ? { y: o.y + dy } : {}
|
|
623
|
-
};
|
|
624
|
-
case "decorImage":
|
|
625
|
-
return { ...o, x: o.x + dx, y: o.y + dy };
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function stackFloors(doc, spread = 900) {
|
|
629
|
-
if (!doc.floors || doc.floors.length < 2) return doc;
|
|
630
|
-
const objects = [];
|
|
631
|
-
doc.floors.forEach((f, i) => {
|
|
632
|
-
const dy = -i * spread;
|
|
633
|
-
for (const o of f.objects) objects.push(dy === 0 ? o : translateObject(o, 0, dy));
|
|
634
|
-
});
|
|
635
|
-
return { ...doc, objects, floors: void 0 };
|
|
636
|
-
}
|
|
637
|
-
function expandFloorObjects(objects, zones, fallbackFocal) {
|
|
638
|
-
const out = [];
|
|
639
|
-
const segmented = /* @__PURE__ */ new Map();
|
|
640
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
641
|
-
for (const object of objects) {
|
|
642
|
-
if (object.type !== "row" || !object.segmentedRow) continue;
|
|
643
|
-
const list = grouped.get(object.segmentedRow.groupId) ?? [];
|
|
644
|
-
list.push(object);
|
|
645
|
-
grouped.set(object.segmentedRow.groupId, list);
|
|
646
|
-
}
|
|
647
|
-
for (const [groupId, members] of grouped) {
|
|
648
|
-
const ordered = members.slice().sort((left, right) => left.segmentedRow.componentIndex - right.segmentedRow.componentIndex);
|
|
649
|
-
const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;
|
|
650
|
-
const first = ordered[0]?.segmentedRow;
|
|
651
|
-
if (!first) continue;
|
|
652
|
-
const valid = expectedCount >= 2 && ordered.length === expectedCount && first?.boundaryBefore === "start" && ordered.every((row, index) => row.segmentedRow?.kind === "segmented-row-v1" && row.segmentedRow.groupId === groupId && row.segmentedRow.componentCount === expectedCount && row.segmentedRow.componentIndex === index && (index === 0 ? row.segmentedRow.boundaryBefore === "start" : row.segmentedRow.boundaryBefore !== "start") && row.segmentedRow.displayLabel === first.displayLabel);
|
|
653
|
-
if (!valid) continue;
|
|
654
|
-
const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);
|
|
655
|
-
let adjacencyOffset = 0;
|
|
656
|
-
let displayOffset = 0;
|
|
657
|
-
for (const row of ordered) {
|
|
658
|
-
if (row.segmentedRow.boundaryBefore === "break") adjacencyOffset += 1;
|
|
659
|
-
segmented.set(row.id, {
|
|
660
|
-
groupId,
|
|
661
|
-
adjacencyOffset,
|
|
662
|
-
displayOffset,
|
|
663
|
-
displayLabel: first.displayLabel,
|
|
664
|
-
totalSeats,
|
|
665
|
-
canonical: ordered[0],
|
|
666
|
-
viewFromSeatUrl: first.viewFromSeatUrl
|
|
667
|
-
});
|
|
668
|
-
adjacencyOffset += row.seatCount;
|
|
669
|
-
displayOffset += row.seatCount;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
for (const obj of objects) {
|
|
673
|
-
let seats = [];
|
|
674
|
-
if (obj.type === "row") seats = expandRow(obj);
|
|
675
|
-
else if (obj.type === "table") seats = expandTable(obj);
|
|
676
|
-
else if (obj.type === "booth") seats = expandBooth(obj);
|
|
677
|
-
if (!seats.length) continue;
|
|
678
|
-
if (obj.type === "row") {
|
|
679
|
-
const logical = segmented.get(obj.id);
|
|
680
|
-
if (logical) {
|
|
681
|
-
const overrides2 = new Map((obj.overrides ?? []).map((override) => [override.index, override]));
|
|
682
|
-
for (const seat of seats) {
|
|
683
|
-
const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(":") + 1));
|
|
684
|
-
if (!Number.isInteger(physicalIndex)) continue;
|
|
685
|
-
const displayOrdinal = logical.displayOffset + physicalIndex;
|
|
686
|
-
seat.logicalRowId = logical.groupId;
|
|
687
|
-
seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;
|
|
688
|
-
if (!overrides2.get(physicalIndex)?.displayLabel) {
|
|
689
|
-
const numberingRow = {
|
|
690
|
-
...logical.canonical,
|
|
691
|
-
seatCount: logical.totalSeats,
|
|
692
|
-
label: logical.displayLabel,
|
|
693
|
-
displayLabel: logical.displayLabel
|
|
694
|
-
};
|
|
695
|
-
seat.displayLabel = `${logical.displayLabel}-${seatLabelPart(numberingRow, displayOrdinal)}`;
|
|
696
|
-
}
|
|
697
|
-
seat.viewUrl ??= logical.viewFromSeatUrl;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
const owner = owningSectionForObject(objects, obj);
|
|
702
|
-
const inheritedView = owner?.viewFromSeatUrl;
|
|
703
|
-
const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : void 0;
|
|
704
|
-
const resolvedFocal = zone?.focalPoint ?? fallbackFocal;
|
|
705
|
-
for (const seat of seats) {
|
|
706
|
-
if (inheritedView) seat.viewUrl ??= inheritedView;
|
|
707
|
-
if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;
|
|
708
|
-
if (owner?.zone) seat.zoneId = owner.zone;
|
|
709
|
-
if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };
|
|
710
|
-
}
|
|
711
|
-
out.push(...seats);
|
|
712
|
-
}
|
|
713
|
-
return out;
|
|
714
|
-
}
|
|
715
|
-
function expandChart(doc, options = {}) {
|
|
716
|
-
if (doc.floors?.length) {
|
|
717
|
-
const out2 = [];
|
|
718
|
-
for (const floor of doc.floors) {
|
|
719
|
-
const floorFocal = floor.focalPoint ?? doc.focalPoint;
|
|
720
|
-
const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal);
|
|
721
|
-
assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
|
|
722
|
-
out2.push(...seats);
|
|
723
|
-
}
|
|
724
|
-
return out2;
|
|
725
|
-
}
|
|
726
|
-
const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint);
|
|
727
|
-
assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
|
|
728
|
-
return out;
|
|
729
|
-
}
|
|
730
|
-
function assignEyeHeights(objects, focal, floorBaseHeightM, seats) {
|
|
731
|
-
const sections = objects.filter((o) => o.type === "section");
|
|
732
|
-
const hasGeometry = floorBaseHeightM > 0 || sections.some((s) => s.height !== void 0 || s.rake !== void 0 || (s.elevation ?? 0) > 0);
|
|
733
|
-
if (!sections.length || !hasGeometry || !focal) {
|
|
734
|
-
for (const seat of seats) seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
const owner = new Array(seats.length);
|
|
738
|
-
const geo = /* @__PURE__ */ new Map();
|
|
739
|
-
const frontDistU = /* @__PURE__ */ new Map();
|
|
740
|
-
for (let i = 0; i < seats.length; i++) {
|
|
741
|
-
const seat = seats[i];
|
|
742
|
-
const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;
|
|
743
|
-
owner[i] = sec;
|
|
744
|
-
if (!sec) continue;
|
|
745
|
-
if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));
|
|
746
|
-
const seatFocal = seat.focalPoint ?? focal;
|
|
747
|
-
const d = Math.hypot(seat.x - seatFocal.x, seat.y - seatFocal.y);
|
|
748
|
-
const cur = frontDistU.get(sec.id);
|
|
749
|
-
if (cur === void 0 || d < cur) frontDistU.set(sec.id, d);
|
|
750
|
-
}
|
|
751
|
-
for (let i = 0; i < seats.length; i++) {
|
|
752
|
-
const seat = seats[i];
|
|
753
|
-
const sec = owner[i];
|
|
754
|
-
if (!sec) {
|
|
755
|
-
seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
const g = geo.get(sec.id);
|
|
759
|
-
let riseM = 0;
|
|
760
|
-
if (g.rake > 0) {
|
|
761
|
-
const seatFocal = seat.focalPoint ?? focal;
|
|
762
|
-
const d = Math.hypot(seat.x - seatFocal.x, seat.y - seatFocal.y);
|
|
763
|
-
const depthU = Math.max(0, d - (frontDistU.get(sec.id) ?? d));
|
|
764
|
-
riseM = depthU * METRES_PER_CHART_UNIT * Math.tan(g.rake * Math.PI / 180);
|
|
765
|
-
}
|
|
766
|
-
seat.eyeHeightM = g.height + riseM + SEATED_EYE_HEIGHT_M;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
var PAD = 40;
|
|
770
|
-
function chartBounds(doc) {
|
|
771
|
-
let minX = Infinity;
|
|
772
|
-
let minY = Infinity;
|
|
773
|
-
let maxX = -Infinity;
|
|
774
|
-
let maxY = -Infinity;
|
|
775
|
-
const acc = (x, y) => {
|
|
776
|
-
if (x < minX) minX = x;
|
|
777
|
-
if (y < minY) minY = y;
|
|
778
|
-
if (x > maxX) maxX = x;
|
|
779
|
-
if (y > maxY) maxY = y;
|
|
780
|
-
};
|
|
781
|
-
for (const s of expandChart(doc)) acc(s.x, s.y);
|
|
782
|
-
for (const obj of allObjects(doc)) {
|
|
783
|
-
if (obj.type === "gaArea") {
|
|
784
|
-
for (const p of obj.points) acc(p.x, p.y);
|
|
785
|
-
} else if (obj.type === "section") {
|
|
786
|
-
for (const p of obj.outline) acc(p.x, p.y);
|
|
787
|
-
} else if (obj.type === "decorImage") {
|
|
788
|
-
acc(obj.x, obj.y);
|
|
789
|
-
acc(obj.x + obj.width, obj.y + obj.height);
|
|
790
|
-
} else if (obj.type === "shape") {
|
|
791
|
-
if (obj.points && obj.points.length) {
|
|
792
|
-
for (const p of obj.points) acc(p.x, p.y);
|
|
793
|
-
} else if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {
|
|
794
|
-
acc(obj.x, obj.y);
|
|
795
|
-
acc(obj.x + obj.width, obj.y + obj.height);
|
|
796
|
-
}
|
|
797
|
-
} else if (obj.type === "table") {
|
|
798
|
-
const off = TABLE_SEAT_OFFSET + SEAT_R;
|
|
799
|
-
const ext = obj.shape === "round" ? (obj.radius ?? 40) + off : Math.max((obj.width ?? 80) / 2, (obj.height ?? 50) / 2) + off;
|
|
800
|
-
acc(obj.center.x - ext, obj.center.y - ext);
|
|
801
|
-
acc(obj.center.x + ext, obj.center.y + ext);
|
|
802
|
-
} else if (obj.type === "booth") {
|
|
803
|
-
const ext = Math.max(obj.width, obj.height) / 2;
|
|
804
|
-
acc(obj.center.x - ext, obj.center.y - ext);
|
|
805
|
-
acc(obj.center.x + ext, obj.center.y + ext);
|
|
806
|
-
} else if (obj.type === "text") {
|
|
807
|
-
const w = obj.fontSize * obj.text.length * 0.6;
|
|
808
|
-
acc(obj.position.x, obj.position.y);
|
|
809
|
-
acc(obj.position.x + w, obj.position.y + obj.fontSize);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
for (const image of [doc.referenceImage, doc.backgroundImage]) {
|
|
813
|
-
if (!image) continue;
|
|
814
|
-
const { center, width } = image;
|
|
815
|
-
const bh = width * 3 / 4;
|
|
816
|
-
acc(center.x - width / 2, center.y - bh / 2);
|
|
817
|
-
acc(center.x + width / 2, center.y + bh / 2);
|
|
818
|
-
}
|
|
819
|
-
if (!isFinite(minX)) {
|
|
820
|
-
const f = doc.focalPoint ?? { x: 0, y: 0 };
|
|
821
|
-
return { x: f.x - 200, y: f.y - 200, width: 400, height: 400 };
|
|
822
|
-
}
|
|
823
|
-
return {
|
|
824
|
-
x: minX - PAD,
|
|
825
|
-
y: minY - PAD,
|
|
826
|
-
width: maxX - minX + PAD * 2,
|
|
827
|
-
height: maxY - minY + PAD * 2
|
|
828
|
-
};
|
|
829
|
-
}
|
|
34
|
+
sectionGeometry,
|
|
35
|
+
stackFloors,
|
|
36
|
+
tableInventoryCount,
|
|
37
|
+
tableSeatCountsBySide
|
|
38
|
+
} from "./chunk-FGS67GT3.js";
|
|
830
39
|
|
|
831
40
|
// src/core/ga.ts
|
|
832
41
|
var PREFIX = "__sl_ga__";
|
|
@@ -8474,7 +7683,7 @@ function stageSightlinePitch(eyeHeightM, distM) {
|
|
|
8474
7683
|
var W = 2048;
|
|
8475
7684
|
var H = 1024;
|
|
8476
7685
|
var UNIT = METRES_PER_CHART_UNIT;
|
|
8477
|
-
var
|
|
7686
|
+
var TAU = Math.PI * 2;
|
|
8478
7687
|
var PX_PER_DEG = H / 180;
|
|
8479
7688
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
8480
7689
|
var pitchToY = (pitchDeg) => (90 - pitchDeg) / 180 * H;
|
|
@@ -8642,7 +7851,7 @@ function classifyScene(_seat, focal, neighbors) {
|
|
|
8642
7851
|
const dM = Math.hypot(dx, dy) * UNIT;
|
|
8643
7852
|
if (dM < 0.5) continue;
|
|
8644
7853
|
const bearing = Math.atan2(dx, -dy);
|
|
8645
|
-
let idx = Math.floor((bearing + Math.PI) /
|
|
7854
|
+
let idx = Math.floor((bearing + Math.PI) / TAU * NB);
|
|
8646
7855
|
if (idx < 0) idx = 0;
|
|
8647
7856
|
else if (idx >= NB) idx = NB - 1;
|
|
8648
7857
|
occ[idx] = true;
|
|
@@ -8672,34 +7881,34 @@ function drawHair(ctx, r, kind) {
|
|
|
8672
7881
|
switch (kind) {
|
|
8673
7882
|
case 1:
|
|
8674
7883
|
ctx.beginPath();
|
|
8675
|
-
ctx.ellipse(0, -r * 0.14, r * 1.16, r * 1.16, 0, 0,
|
|
7884
|
+
ctx.ellipse(0, -r * 0.14, r * 1.16, r * 1.16, 0, 0, TAU);
|
|
8676
7885
|
ctx.fill();
|
|
8677
7886
|
break;
|
|
8678
7887
|
case 2:
|
|
8679
7888
|
ctx.beginPath();
|
|
8680
|
-
ctx.ellipse(0, -r * 1.02, r * 0.42, r * 0.42, 0, 0,
|
|
7889
|
+
ctx.ellipse(0, -r * 1.02, r * 0.42, r * 0.42, 0, 0, TAU);
|
|
8681
7890
|
ctx.fill();
|
|
8682
7891
|
ctx.beginPath();
|
|
8683
|
-
ctx.ellipse(0, -r * 0.2, r * 0.98, r * 1.02, 0, 0,
|
|
7892
|
+
ctx.ellipse(0, -r * 0.2, r * 0.98, r * 1.02, 0, 0, TAU);
|
|
8684
7893
|
ctx.fill();
|
|
8685
7894
|
break;
|
|
8686
7895
|
case 3:
|
|
8687
7896
|
ctx.beginPath();
|
|
8688
|
-
ctx.ellipse(0, -r * 0.36, r * 1.04, r * 0.6, 0, Math.PI,
|
|
7897
|
+
ctx.ellipse(0, -r * 0.36, r * 1.04, r * 0.6, 0, Math.PI, TAU);
|
|
8689
7898
|
ctx.fill();
|
|
8690
7899
|
ctx.beginPath();
|
|
8691
|
-
ctx.ellipse(0, -r * 0.52, r * 1.22, r * 0.24, 0, 0,
|
|
7900
|
+
ctx.ellipse(0, -r * 0.52, r * 1.22, r * 0.24, 0, 0, TAU);
|
|
8692
7901
|
ctx.fill();
|
|
8693
7902
|
break;
|
|
8694
7903
|
case 4:
|
|
8695
7904
|
ctx.beginPath();
|
|
8696
|
-
ctx.ellipse(-r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0,
|
|
7905
|
+
ctx.ellipse(-r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU);
|
|
8697
7906
|
ctx.fill();
|
|
8698
7907
|
ctx.beginPath();
|
|
8699
|
-
ctx.ellipse(r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0,
|
|
7908
|
+
ctx.ellipse(r * 0.66, r * 0.5, r * 0.5, r * 1.35, 0, 0, TAU);
|
|
8700
7909
|
ctx.fill();
|
|
8701
7910
|
ctx.beginPath();
|
|
8702
|
-
ctx.ellipse(0, -r * 0.08, r * 1.06, r * 1.08, 0, 0,
|
|
7911
|
+
ctx.ellipse(0, -r * 0.08, r * 1.06, r * 1.08, 0, 0, TAU);
|
|
8703
7912
|
ctx.fill();
|
|
8704
7913
|
break;
|
|
8705
7914
|
default:
|
|
@@ -8716,24 +7925,44 @@ function drawSeated(ctx, hx, hy, r, o) {
|
|
|
8716
7925
|
const skin = `rgba(${o.tone[0]},${o.tone[1]},${o.tone[2]},${o.alpha})`;
|
|
8717
7926
|
ctx.save();
|
|
8718
7927
|
ctx.translate(hx, hy);
|
|
7928
|
+
const leanPx = o.lean * r;
|
|
8719
7929
|
ctx.fillStyle = clo;
|
|
8720
7930
|
ctx.beginPath();
|
|
8721
7931
|
ctx.moveTo(-neckHalf, neckTopY);
|
|
8722
|
-
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - r * 0.6, -shHalf, shoulderY);
|
|
7932
|
+
ctx.quadraticCurveTo(-neckHalf * 1.18, shoulderY - leanPx - r * 0.6, -shHalf, shoulderY - leanPx);
|
|
8723
7933
|
ctx.quadraticCurveTo(-shHalf * 1.05, shoulderY + r * 1.2, -shHalf * 0.94, botY);
|
|
8724
7934
|
ctx.lineTo(shHalf * 0.94, botY);
|
|
8725
|
-
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY);
|
|
8726
|
-
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY - r * 0.6, neckHalf, neckTopY);
|
|
7935
|
+
ctx.quadraticCurveTo(shHalf * 1.05, shoulderY + r * 1.2, shHalf, shoulderY + leanPx);
|
|
7936
|
+
ctx.quadraticCurveTo(neckHalf * 1.18, shoulderY + leanPx - r * 0.6, neckHalf, neckTopY);
|
|
8727
7937
|
ctx.closePath();
|
|
8728
7938
|
ctx.fill();
|
|
8729
7939
|
ctx.fillStyle = skin;
|
|
8730
7940
|
ctx.fillRect(-neckHalf * 0.82, r * 0.5, neckHalf * 1.64, r * 0.9);
|
|
8731
7941
|
ctx.save();
|
|
8732
7942
|
ctx.rotate(o.tilt);
|
|
7943
|
+
ctx.translate(o.turn * r, 0);
|
|
8733
7944
|
ctx.fillStyle = skin;
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
7945
|
+
if (r >= 18) {
|
|
7946
|
+
ctx.beginPath();
|
|
7947
|
+
ctx.moveTo(-r * 0.9, -r * 0.15);
|
|
7948
|
+
ctx.quadraticCurveTo(-r * 0.96, -r * 1.12, 0, -r * 1.06);
|
|
7949
|
+
ctx.quadraticCurveTo(r * 0.96, -r * 1.12, r * 0.9, -r * 0.15);
|
|
7950
|
+
ctx.quadraticCurveTo(r * 0.86, r * 0.55, r * 0.36, r * 0.98);
|
|
7951
|
+
ctx.quadraticCurveTo(0, r * 1.14, -r * 0.36, r * 0.98);
|
|
7952
|
+
ctx.quadraticCurveTo(-r * 0.86, r * 0.55, -r * 0.9, -r * 0.15);
|
|
7953
|
+
ctx.closePath();
|
|
7954
|
+
ctx.fill();
|
|
7955
|
+
if (r >= 22) {
|
|
7956
|
+
ctx.beginPath();
|
|
7957
|
+
ctx.ellipse(-r * 0.9, r * 0.18, r * 0.13, r * 0.22, 0, 0, TAU);
|
|
7958
|
+
ctx.ellipse(r * 0.9, r * 0.18, r * 0.13, r * 0.22, 0, 0, TAU);
|
|
7959
|
+
ctx.fill();
|
|
7960
|
+
}
|
|
7961
|
+
} else {
|
|
7962
|
+
ctx.beginPath();
|
|
7963
|
+
ctx.ellipse(0, 0, r * 0.9, r * 1.06, 0, 0, TAU);
|
|
7964
|
+
ctx.fill();
|
|
7965
|
+
}
|
|
8737
7966
|
drawHair(ctx, r, o.hair);
|
|
8738
7967
|
if (o.rim > 0.01) {
|
|
8739
7968
|
ctx.strokeStyle = `rgba(245,205,150,${(0.5 * o.rim).toFixed(3)})`;
|
|
@@ -8757,15 +7986,17 @@ function drawSeated(ctx, hx, hy, r, o) {
|
|
|
8757
7986
|
function drawSeatedMid(ctx, hx, hy, r, clothing, tone, alpha) {
|
|
8758
7987
|
ctx.fillStyle = `rgba(${clothing[0]},${clothing[1]},${clothing[2]},${alpha})`;
|
|
8759
7988
|
ctx.beginPath();
|
|
8760
|
-
ctx.moveTo(hx - r * 1.
|
|
8761
|
-
ctx.quadraticCurveTo(hx - r * 1.
|
|
8762
|
-
ctx.
|
|
8763
|
-
ctx.
|
|
7989
|
+
ctx.moveTo(hx - r * 1.72, hy + r * 4);
|
|
7990
|
+
ctx.quadraticCurveTo(hx - r * 1.78, hy + r * 1.55, hx - r * 0.46, hy + r * 1.02);
|
|
7991
|
+
ctx.lineTo(hx - r * 0.4, hy + r * 0.62);
|
|
7992
|
+
ctx.lineTo(hx + r * 0.4, hy + r * 0.62);
|
|
7993
|
+
ctx.lineTo(hx + r * 0.46, hy + r * 1.02);
|
|
7994
|
+
ctx.quadraticCurveTo(hx + r * 1.78, hy + r * 1.55, hx + r * 1.72, hy + r * 4);
|
|
8764
7995
|
ctx.closePath();
|
|
8765
7996
|
ctx.fill();
|
|
8766
7997
|
ctx.fillStyle = `rgba(${tone[0]},${tone[1]},${tone[2]},${alpha})`;
|
|
8767
7998
|
ctx.beginPath();
|
|
8768
|
-
ctx.ellipse(hx, hy, r * 0.
|
|
7999
|
+
ctx.ellipse(hx, hy, r * 0.9, r * 1.05, 0, 0, TAU);
|
|
8769
8000
|
ctx.fill();
|
|
8770
8001
|
}
|
|
8771
8002
|
function drawPerformer(ctx, x, footY, h, o) {
|
|
@@ -8783,7 +8014,7 @@ function drawPerformer(ctx, x, footY, h, o) {
|
|
|
8783
8014
|
ctx.scale(1, 0.28);
|
|
8784
8015
|
ctx.fillStyle = sh;
|
|
8785
8016
|
ctx.beginPath();
|
|
8786
|
-
ctx.ellipse(x, footY / 0.28, h * 0.24, h * 0.24, 0, 0,
|
|
8017
|
+
ctx.ellipse(x, footY / 0.28, h * 0.24, h * 0.24, 0, 0, TAU);
|
|
8787
8018
|
ctx.fill();
|
|
8788
8019
|
ctx.restore();
|
|
8789
8020
|
ctx.fillStyle = skin;
|
|
@@ -8827,7 +8058,7 @@ function drawPerformer(ctx, x, footY, h, o) {
|
|
|
8827
8058
|
ctx.stroke();
|
|
8828
8059
|
ctx.fillStyle = skin;
|
|
8829
8060
|
ctx.beginPath();
|
|
8830
|
-
ctx.ellipse(x + hipHalf * 1.5, hipY - h * 0.04, h * 0.07, h * 0.09, -0.5, 0,
|
|
8061
|
+
ctx.ellipse(x + hipHalf * 1.5, hipY - h * 0.04, h * 0.07, h * 0.09, -0.5, 0, TAU);
|
|
8831
8062
|
ctx.fill();
|
|
8832
8063
|
} else if (o.pose === 3) {
|
|
8833
8064
|
ctx.beginPath();
|
|
@@ -8852,7 +8083,7 @@ function drawPerformer(ctx, x, footY, h, o) {
|
|
|
8852
8083
|
ctx.fillStyle = skin;
|
|
8853
8084
|
ctx.fillRect(x - headR * 0.34, headCy + headR * 0.6, headR * 0.68, headR * 0.9);
|
|
8854
8085
|
ctx.beginPath();
|
|
8855
|
-
ctx.ellipse(x, headCy, headR * 0.86, headR * 1.05, 0, 0,
|
|
8086
|
+
ctx.ellipse(x, headCy, headR * 0.86, headR * 1.05, 0, 0, TAU);
|
|
8856
8087
|
ctx.fill();
|
|
8857
8088
|
if (o.rimStrength > 0.01) {
|
|
8858
8089
|
ctx.strokeStyle = `rgba(${o.rim[0]},${o.rim[1]},${o.rim[2]},${(0.6 * o.rimStrength).toFixed(3)})`;
|
|
@@ -8889,11 +8120,11 @@ function drawFixture(ctx, x, y, r, tint) {
|
|
|
8889
8120
|
g.addColorStop(1, `rgba(${tint},0)`);
|
|
8890
8121
|
ctx.fillStyle = g;
|
|
8891
8122
|
ctx.beginPath();
|
|
8892
|
-
ctx.arc(x, y, r * 3, 0,
|
|
8123
|
+
ctx.arc(x, y, r * 3, 0, TAU);
|
|
8893
8124
|
ctx.fill();
|
|
8894
8125
|
ctx.fillStyle = `rgba(${tint},1)`;
|
|
8895
8126
|
ctx.beginPath();
|
|
8896
|
-
ctx.arc(x, y, r, 0,
|
|
8127
|
+
ctx.arc(x, y, r, 0, TAU);
|
|
8897
8128
|
ctx.fill();
|
|
8898
8129
|
}
|
|
8899
8130
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
@@ -8948,7 +8179,7 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
8948
8179
|
const x = (i + 0.5) / rigCount * W;
|
|
8949
8180
|
const y = pitchToY(scene.mode === "sport" ? 58 : 54 + i * 7 % 3 * 6);
|
|
8950
8181
|
ctx.beginPath();
|
|
8951
|
-
ctx.arc(x, y, scene.mode === "sport" ? 2.6 : 3.2, 0,
|
|
8182
|
+
ctx.arc(x, y, scene.mode === "sport" ? 2.6 : 3.2, 0, TAU);
|
|
8952
8183
|
ctx.fill();
|
|
8953
8184
|
}
|
|
8954
8185
|
drawAudience(ctx, seat, neighborSeats, focalPoint, stageBearing, eyeM, dx, dy, hasRelief, scene);
|
|
@@ -9107,40 +8338,73 @@ function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
|
9107
8338
|
riser.addColorStop(1, "rgba(0,0,0,0)");
|
|
9108
8339
|
ctx.fillStyle = riser;
|
|
9109
8340
|
ctx.fillRect(cx - halfW, topCy, halfW * 2, yBase - topCy);
|
|
8341
|
+
const FORMATIONS = [
|
|
8342
|
+
// frontman + guitarist + one at the back
|
|
8343
|
+
[{ u: -0.24, v: 0.4, lead: true, pose: 3 }, { u: 0.34, v: -0.04, pose: 2 }, { u: -0.52, v: -0.38, pose: 0 }],
|
|
8344
|
+
// duo — singer forward, instrument behind the shoulder
|
|
8345
|
+
[{ u: 0.2, v: 0.34, lead: true, pose: 3 }, { u: -0.3, v: -0.08, pose: 2 }],
|
|
8346
|
+
// 4-piece — lead just off-centre, band staggered around the far half
|
|
8347
|
+
[{ u: -0.1, v: 0.28, lead: true, pose: 1 }, { u: 0.44, v: -0.2, pose: 2 }, { u: -0.46, v: -0.14, pose: 2 }, { u: 0.08, v: -0.46, pose: 0 }]
|
|
8348
|
+
];
|
|
8349
|
+
const form = FORMATIONS[Math.floor(rand() * FORMATIONS.length)];
|
|
8350
|
+
const mirror = rand() < 0.5 ? -1 : 1;
|
|
8351
|
+
const spotX = (p) => cx + p.u * mirror * halfW * 0.84;
|
|
8352
|
+
const spotY = (p) => topCy + p.v * topRy * 0.8;
|
|
8353
|
+
const lead = form.find((p) => p.lead) ?? form[0];
|
|
8354
|
+
const leadX = spotX(lead);
|
|
9110
8355
|
const deckTop = ctx.createLinearGradient(0, topCy - topRy, 0, topCy + topRy);
|
|
9111
|
-
deckTop.addColorStop(0, "#
|
|
9112
|
-
deckTop.addColorStop(1, "#
|
|
8356
|
+
deckTop.addColorStop(0, "#3d4a72");
|
|
8357
|
+
deckTop.addColorStop(1, "#232b47");
|
|
9113
8358
|
ctx.fillStyle = deckTop;
|
|
9114
8359
|
ctx.beginPath();
|
|
9115
|
-
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0,
|
|
8360
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU);
|
|
9116
8361
|
ctx.fill();
|
|
9117
|
-
const deckGlow = ctx.createRadialGradient(
|
|
9118
|
-
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.
|
|
8362
|
+
const deckGlow = ctx.createRadialGradient(leadX, topCy, 2, leadX, topCy, halfW);
|
|
8363
|
+
deckGlow.addColorStop(0, `rgba(255,214,160,${(0.5 * nearGlow).toFixed(3)})`);
|
|
9119
8364
|
deckGlow.addColorStop(1, "rgba(255,214,160,0)");
|
|
9120
8365
|
ctx.save();
|
|
9121
8366
|
ctx.globalCompositeOperation = "lighter";
|
|
9122
8367
|
ctx.fillStyle = deckGlow;
|
|
9123
8368
|
ctx.beginPath();
|
|
9124
|
-
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0,
|
|
8369
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU);
|
|
9125
8370
|
ctx.fill();
|
|
9126
8371
|
ctx.restore();
|
|
9127
8372
|
ctx.strokeStyle = `rgba(${rigTintStr},0.7)`;
|
|
9128
8373
|
ctx.lineWidth = 2;
|
|
9129
8374
|
ctx.beginPath();
|
|
9130
|
-
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0,
|
|
8375
|
+
ctx.ellipse(cx, topCy, halfW, topRy, 0, 0, TAU);
|
|
9131
8376
|
ctx.stroke();
|
|
9132
|
-
const perfCount = 2 + Math.floor(rand() * 3);
|
|
9133
8377
|
const perfH = Math.max(24, (yBase - yFarTop) * 1.3);
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
const
|
|
9137
|
-
const
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
8378
|
+
const gearN = form.length >= 3 ? 3 : 2;
|
|
8379
|
+
for (let i = 0; i < gearN; i++) {
|
|
8380
|
+
const gu = (-0.62 + i * 0.52 + (rand() - 0.5) * 0.12) * -mirror;
|
|
8381
|
+
const gx = cx + gu * halfW * 0.7;
|
|
8382
|
+
const gy = topCy - topRy * (0.45 + rand() * 0.18);
|
|
8383
|
+
const gw = halfW * (0.07 + rand() * 0.04);
|
|
8384
|
+
const gh = perfH * (0.26 + rand() * 0.12);
|
|
8385
|
+
ctx.fillStyle = "#0a0e18";
|
|
8386
|
+
ctx.fillRect(gx - gw / 2, gy - gh, gw, gh);
|
|
8387
|
+
ctx.fillStyle = `rgba(${rigTintStr},0.3)`;
|
|
8388
|
+
ctx.fillRect(gx - gw / 2, gy - gh, gw, 1.5);
|
|
8389
|
+
}
|
|
8390
|
+
if (form.length >= 3) {
|
|
8391
|
+
const drumX = cx - lead.u * mirror * halfW * 0.4;
|
|
8392
|
+
ctx.fillStyle = "#0b0f1a";
|
|
8393
|
+
ctx.beginPath();
|
|
8394
|
+
ctx.ellipse(drumX, topCy - topRy * 0.34, halfW * 0.08, topRy * 0.1, 0, 0, TAU);
|
|
8395
|
+
ctx.fill();
|
|
8396
|
+
ctx.strokeStyle = `rgba(${rigTintStr},0.35)`;
|
|
8397
|
+
ctx.lineWidth = 1;
|
|
8398
|
+
ctx.stroke();
|
|
8399
|
+
}
|
|
8400
|
+
for (const p of [...form].sort((a, b) => a.v - b.v)) {
|
|
8401
|
+
const depthScale = 0.82 + (p.v + 1) * 0.14;
|
|
8402
|
+
drawPerformer(ctx, spotX(p), spotY(p), perfH * depthScale * (p.lead ? 1.06 : 0.92), {
|
|
8403
|
+
tone: [18, 21, 34],
|
|
8404
|
+
alpha: 0.95,
|
|
8405
|
+
pose: p.pose,
|
|
8406
|
+
rim: hslToRgb(40, 0.72, 0.8),
|
|
8407
|
+
rimStrength: p.lead ? 1.4 : 0.9
|
|
9144
8408
|
});
|
|
9145
8409
|
}
|
|
9146
8410
|
const trussY = pitchToY(Math.min(60, farTopPitch + 34));
|
|
@@ -9148,19 +8412,64 @@ function drawArenaScene(ctx, distM, eyeM, scene, nearGlow, rigTintStr, rand) {
|
|
|
9148
8412
|
ctx.strokeStyle = "rgba(60,68,92,0.85)";
|
|
9149
8413
|
ctx.lineWidth = 3;
|
|
9150
8414
|
ctx.beginPath();
|
|
9151
|
-
ctx.ellipse(cx, trussY, halfW * 1.15, trussRy, 0, 0,
|
|
8415
|
+
ctx.ellipse(cx, trussY, halfW * 1.15, trussRy, 0, 0, TAU);
|
|
9152
8416
|
ctx.stroke();
|
|
9153
8417
|
const fixtures = 4 + Math.floor(rand() * 3);
|
|
9154
8418
|
for (let i = 0; i < fixtures; i++) {
|
|
9155
|
-
const a = i / fixtures *
|
|
8419
|
+
const a = i / fixtures * TAU;
|
|
9156
8420
|
const fxX = cx + Math.cos(a) * halfW * 1.15;
|
|
9157
8421
|
const fxY = trussY + Math.sin(a) * trussRy;
|
|
9158
8422
|
if (Math.sin(a) > -0.2) {
|
|
9159
8423
|
const tx = cx + (rand() - 0.5) * halfW * 0.8;
|
|
9160
|
-
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.
|
|
8424
|
+
drawBeam(ctx, fxX, fxY, tx, topCy, 3, halfW * 0.32, rigTintStr, 0.2 * (0.7 + nearGlow * 0.4));
|
|
9161
8425
|
}
|
|
9162
|
-
drawFixture(ctx, fxX, fxY,
|
|
8426
|
+
drawFixture(ctx, fxX, fxY, 3, rigTintStr);
|
|
9163
8427
|
}
|
|
8428
|
+
const scoreY = pitchToY(Math.min(52, farTopPitch + 24));
|
|
8429
|
+
const scoreW = Math.max(60, halfW * 0.4);
|
|
8430
|
+
const scoreH = Math.max(24, scoreW * 0.34);
|
|
8431
|
+
ctx.strokeStyle = "rgba(70,78,102,0.6)";
|
|
8432
|
+
ctx.lineWidth = 1.5;
|
|
8433
|
+
ctx.beginPath();
|
|
8434
|
+
ctx.moveTo(cx - scoreW * 0.3, scoreY - scoreH / 2);
|
|
8435
|
+
ctx.lineTo(cx - scoreW * 0.16, scoreY - scoreH * 2.2);
|
|
8436
|
+
ctx.moveTo(cx + scoreW * 0.3, scoreY - scoreH / 2);
|
|
8437
|
+
ctx.lineTo(cx + scoreW * 0.16, scoreY - scoreH * 2.2);
|
|
8438
|
+
ctx.stroke();
|
|
8439
|
+
ctx.fillStyle = "#0d1220";
|
|
8440
|
+
ctx.fillRect(cx - scoreW / 2, scoreY - scoreH / 2, scoreW, scoreH);
|
|
8441
|
+
const screen = ctx.createLinearGradient(0, scoreY - scoreH * 0.3, 0, scoreY + scoreH * 0.42);
|
|
8442
|
+
screen.addColorStop(0, `rgba(${rigTintStr},0.5)`);
|
|
8443
|
+
screen.addColorStop(1, "rgba(255,214,160,0.32)");
|
|
8444
|
+
ctx.fillStyle = screen;
|
|
8445
|
+
ctx.fillRect(cx - scoreW * 0.42, scoreY - scoreH * 0.3, scoreW * 0.84, scoreH * 0.72);
|
|
8446
|
+
ctx.fillStyle = "rgba(255,214,160,0.55)";
|
|
8447
|
+
ctx.fillRect(cx - scoreW / 2, scoreY + scoreH / 2 - 2, scoreW, 2);
|
|
8448
|
+
const scoreGlow = ctx.createRadialGradient(cx, scoreY, 4, cx, scoreY, scoreW * 1.1);
|
|
8449
|
+
scoreGlow.addColorStop(0, `rgba(${rigTintStr},0.2)`);
|
|
8450
|
+
scoreGlow.addColorStop(1, "rgba(0,0,0,0)");
|
|
8451
|
+
ctx.save();
|
|
8452
|
+
ctx.globalCompositeOperation = "lighter";
|
|
8453
|
+
ctx.fillStyle = scoreGlow;
|
|
8454
|
+
ctx.fillRect(cx - scoreW * 1.1, scoreY - scoreW * 0.8, scoreW * 2.2, scoreW * 1.6);
|
|
8455
|
+
const airGlow = ctx.createRadialGradient(cx, (trussY + topCy) / 2, 6, cx, (trussY + topCy) / 2, halfW * 1.5);
|
|
8456
|
+
airGlow.addColorStop(0, `rgba(${rigTintStr},${(0.1 * (0.6 + nearGlow * 0.5)).toFixed(3)})`);
|
|
8457
|
+
airGlow.addColorStop(1, "rgba(0,0,0,0)");
|
|
8458
|
+
ctx.fillStyle = airGlow;
|
|
8459
|
+
ctx.fillRect(cx - halfW * 1.5, trussY - 40, halfW * 3, topCy - trussY + 80);
|
|
8460
|
+
ctx.restore();
|
|
8461
|
+
const keyX = cx + Math.sign(leadX - cx || 1) * halfW * 0.5;
|
|
8462
|
+
drawBeam(ctx, keyX, trussY - trussRy * 0.4, leadX, spotY(lead), 2.5, halfW * 0.16, "255,224,178", 0.42 * (0.7 + nearGlow * 0.4));
|
|
8463
|
+
const pool2 = ctx.createRadialGradient(leadX, spotY(lead), 1, leadX, spotY(lead), halfW * 0.2);
|
|
8464
|
+
pool2.addColorStop(0, `rgba(255,228,185,${(0.4 * nearGlow).toFixed(3)})`);
|
|
8465
|
+
pool2.addColorStop(1, "rgba(255,228,185,0)");
|
|
8466
|
+
ctx.save();
|
|
8467
|
+
ctx.globalCompositeOperation = "lighter";
|
|
8468
|
+
ctx.fillStyle = pool2;
|
|
8469
|
+
ctx.beginPath();
|
|
8470
|
+
ctx.ellipse(leadX, spotY(lead), halfW * 0.2, topRy * 0.24, 0, 0, TAU);
|
|
8471
|
+
ctx.fill();
|
|
8472
|
+
ctx.restore();
|
|
9164
8473
|
}
|
|
9165
8474
|
function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr, rand) {
|
|
9166
8475
|
const cx = W / 2;
|
|
@@ -9181,7 +8490,7 @@ function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr,
|
|
|
9181
8490
|
surfCol.addColorStop(1, "#8fa3c4");
|
|
9182
8491
|
ctx.save();
|
|
9183
8492
|
ctx.beginPath();
|
|
9184
|
-
ctx.ellipse(cx, cy, halfW, ry, 0, 0,
|
|
8493
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU);
|
|
9185
8494
|
ctx.clip();
|
|
9186
8495
|
ctx.fillStyle = surfCol;
|
|
9187
8496
|
ctx.fillRect(cx - halfW, cy - ry, halfW * 2, ry * 2);
|
|
@@ -9197,13 +8506,13 @@ function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr,
|
|
|
9197
8506
|
ctx.stroke();
|
|
9198
8507
|
ctx.strokeStyle = "rgba(180,90,110,0.45)";
|
|
9199
8508
|
ctx.beginPath();
|
|
9200
|
-
ctx.ellipse(cx, cy, ry * 0.9, ry * 0.5, 0, 0,
|
|
8509
|
+
ctx.ellipse(cx, cy, ry * 0.9, ry * 0.5, 0, 0, TAU);
|
|
9201
8510
|
ctx.stroke();
|
|
9202
8511
|
ctx.restore();
|
|
9203
8512
|
ctx.strokeStyle = "rgba(210,225,245,0.6)";
|
|
9204
8513
|
ctx.lineWidth = 2.5;
|
|
9205
8514
|
ctx.beginPath();
|
|
9206
|
-
ctx.ellipse(cx, cy, halfW, ry, 0, 0,
|
|
8515
|
+
ctx.ellipse(cx, cy, halfW, ry, 0, 0, TAU);
|
|
9207
8516
|
ctx.stroke();
|
|
9208
8517
|
ctx.fillStyle = "rgba(20,26,40,0.8)";
|
|
9209
8518
|
for (let i = 0; i < 5; i++) {
|
|
@@ -9211,7 +8520,7 @@ function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr,
|
|
|
9211
8520
|
const py = cy + (rand() - 0.5) * ry * 1.1;
|
|
9212
8521
|
const s = Math.max(2, ry * 0.06);
|
|
9213
8522
|
ctx.beginPath();
|
|
9214
|
-
ctx.ellipse(px, py, s * 0.6, s, 0, 0,
|
|
8523
|
+
ctx.ellipse(px, py, s * 0.6, s, 0, 0, TAU);
|
|
9215
8524
|
ctx.fill();
|
|
9216
8525
|
}
|
|
9217
8526
|
const wash = ctx.createRadialGradient(cx, cy, 4, cx, cy, halfW * 1.4);
|
|
@@ -9254,7 +8563,7 @@ function drawSportScene(ctx, distM, eyeM, scene, _nearGlow, baseHue, rigTintStr,
|
|
|
9254
8563
|
for (let i = 0; i < 8; i++) {
|
|
9255
8564
|
const lx = cx - sbW * 0.6 + sbW * 1.2 * i / 7;
|
|
9256
8565
|
ctx.beginPath();
|
|
9257
|
-
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0,
|
|
8566
|
+
ctx.arc(lx, sbY + sbH * 0.62, 2.4, 0, TAU);
|
|
9258
8567
|
ctx.fill();
|
|
9259
8568
|
}
|
|
9260
8569
|
}
|
|
@@ -9268,7 +8577,7 @@ function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM,
|
|
|
9268
8577
|
const oy = other.y - seat.y;
|
|
9269
8578
|
const d = Math.hypot(ox, oy) * UNIT;
|
|
9270
8579
|
if (own && other.sectionId === own && d < 3 && ox * dx + oy * dy > 0) seatAhead = true;
|
|
9271
|
-
if (d < 0.3 || d >
|
|
8580
|
+
if (d < 0.3 || d > 32) continue;
|
|
9272
8581
|
const bearing = Math.atan2(ox, -oy) - stageBearing;
|
|
9273
8582
|
const yaw = (bearing * 180 / Math.PI + 540) % 360 - 180;
|
|
9274
8583
|
const rise = hasRelief ? (other.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - eyeM : 0;
|
|
@@ -9278,40 +8587,91 @@ function drawAudience(ctx, seat, neighborSeats, _focalPoint, stageBearing, eyeM,
|
|
|
9278
8587
|
for (let k = 0; k < other.id.length; k++) hh = hh * 31 + other.id.charCodeAt(k) & 65535;
|
|
9279
8588
|
const jitter = 0.9 + (hh & 15) / 40;
|
|
9280
8589
|
const r = Math.min(78, Math.atan2(0.16, d) * 180 / Math.PI * PX_PER_DEG * jitter);
|
|
9281
|
-
if (r < 2) continue;
|
|
8590
|
+
if (r < 1.2) continue;
|
|
9282
8591
|
figs.push({ hx: yawToX(yaw), hy: pitchToY(headPitch), r, d, hash: hh });
|
|
9283
8592
|
}
|
|
9284
8593
|
figs.sort((a, b) => b.d - a.d);
|
|
8594
|
+
const figRows = [];
|
|
8595
|
+
{
|
|
8596
|
+
const byY = [...figs].sort((a, b) => a.hy - b.hy);
|
|
8597
|
+
let cur = [];
|
|
8598
|
+
const flush = () => {
|
|
8599
|
+
if (!cur.length) return;
|
|
8600
|
+
const d = cur.reduce((s, f) => s + f.d, 0) / cur.length;
|
|
8601
|
+
const hy = cur.reduce((s, f) => s + f.hy, 0) / cur.length;
|
|
8602
|
+
const r = cur.reduce((s, f) => s + f.r, 0) / cur.length;
|
|
8603
|
+
figRows.push({ figs: cur.sort((a, b) => b.d - a.d), d, hy, r });
|
|
8604
|
+
cur = [];
|
|
8605
|
+
};
|
|
8606
|
+
for (const f of byY) {
|
|
8607
|
+
const last = cur[cur.length - 1];
|
|
8608
|
+
if (last && Math.abs(f.hy - last.hy) > Math.max(5, (last.r + f.r) / 2 * 1.7)) flush();
|
|
8609
|
+
cur.push(f);
|
|
8610
|
+
}
|
|
8611
|
+
flush();
|
|
8612
|
+
}
|
|
8613
|
+
figRows.sort((a, b) => b.d - a.d);
|
|
9285
8614
|
const CLOTHES = [[22, 26, 40], [28, 27, 42], [24, 31, 46], [31, 30, 47], [20, 24, 36]];
|
|
9286
|
-
for (const
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
8615
|
+
for (const row of figRows) {
|
|
8616
|
+
if (row.figs.length >= 3 && row.r >= 1.6 && row.r < 26) {
|
|
8617
|
+
const runs = [];
|
|
8618
|
+
const byX = [...row.figs].sort((a, b) => a.hx - b.hx);
|
|
8619
|
+
let run = [];
|
|
8620
|
+
for (const f of byX) {
|
|
8621
|
+
const last = run[run.length - 1];
|
|
8622
|
+
if (last && f.hx - last.hx > Math.max(14, f.r * 7)) {
|
|
8623
|
+
if (run.length >= 3) runs.push(run);
|
|
8624
|
+
run = [];
|
|
8625
|
+
}
|
|
8626
|
+
run.push(f);
|
|
8627
|
+
}
|
|
8628
|
+
if (run.length >= 3) runs.push(run);
|
|
8629
|
+
const stripAlpha = Math.min(0.42, 0.5 * (1 - row.d / 42));
|
|
8630
|
+
if (stripAlpha > 0.05) {
|
|
8631
|
+
ctx.fillStyle = `rgba(15, 19, 31, ${stripAlpha.toFixed(3)})`;
|
|
8632
|
+
for (const seg of runs) {
|
|
8633
|
+
const x0 = seg[0].hx - row.r * 1.6;
|
|
8634
|
+
const x1 = seg[seg.length - 1].hx + row.r * 1.6;
|
|
8635
|
+
ctx.beginPath();
|
|
8636
|
+
ctx.roundRect(x0, row.hy + row.r * 0.8, x1 - x0, row.r * 2.4, row.r * 0.8);
|
|
8637
|
+
ctx.fill();
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
for (const f of row.figs) {
|
|
8642
|
+
const alpha = f.d < 12 ? 0.95 : f.d < 17 ? Math.max(0.66, 0.95 - (f.d - 12) / 18) : Math.max(0.3, 0.68 - (f.d - 17) / 34);
|
|
8643
|
+
const lift = Math.round((1 - Math.min(1, f.d / 16)) * 10);
|
|
8644
|
+
const headTone = [14 + (f.hash >> 4 & 6) + lift, 17 + (f.hash >> 4 & 6) + lift, 25 + (f.hash >> 4 & 6) + lift];
|
|
8645
|
+
const clo0 = CLOTHES[f.hash % CLOTHES.length];
|
|
8646
|
+
const clothing = [clo0[0] + lift, clo0[1] + lift, clo0[2] + lift];
|
|
8647
|
+
if (f.r >= 13) {
|
|
8648
|
+
const rim = f.d < 9 ? 0.16 * (1 - f.d / 9) + (scene.mode === "proscenium" ? 0.06 : 0) : 0;
|
|
8649
|
+
drawSeated(ctx, f.hx, f.hy, f.r, {
|
|
8650
|
+
tone: headTone,
|
|
8651
|
+
clothing,
|
|
8652
|
+
alpha,
|
|
8653
|
+
tilt: ((f.hash >> 2 & 7) - 3.5) * 0.018,
|
|
8654
|
+
// ±~7°
|
|
8655
|
+
shoulderK: 1.72 + (f.hash >> 5 & 7) / 22,
|
|
8656
|
+
// ~1.72..2.04
|
|
8657
|
+
hair: f.hash % HAIR_KINDS,
|
|
8658
|
+
rim,
|
|
8659
|
+
turn: ((f.hash >> 8 & 7) - 3.5) * 0.034,
|
|
8660
|
+
// ±~0.12 head-radii sideways
|
|
8661
|
+
lean: ((f.hash >> 10 & 3) - 1.5) * 0.09
|
|
8662
|
+
// one shoulder rides higher
|
|
8663
|
+
});
|
|
8664
|
+
} else if (f.r >= 5.5) {
|
|
8665
|
+
drawSeatedMid(ctx, f.hx, f.hy, f.r, clothing, headTone, alpha);
|
|
8666
|
+
} else {
|
|
8667
|
+
ctx.fillStyle = `rgba(${headTone[0]},${headTone[1]},${headTone[2]},${alpha.toFixed(3)})`;
|
|
8668
|
+
ctx.beginPath();
|
|
8669
|
+
ctx.arc(f.hx, f.hy, f.r, 0, TAU);
|
|
8670
|
+
ctx.fill();
|
|
8671
|
+
ctx.beginPath();
|
|
8672
|
+
ctx.ellipse(f.hx, f.hy + f.r * 1.4, f.r * 1.85, f.r * 0.95, 0, Math.PI, 0, true);
|
|
8673
|
+
ctx.fill();
|
|
8674
|
+
}
|
|
9315
8675
|
}
|
|
9316
8676
|
}
|
|
9317
8677
|
if (seatAhead) {
|
|
@@ -9449,7 +8809,7 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
9449
8809
|
ctx.fillStyle = `rgba(${rigTintStr},0.75)`;
|
|
9450
8810
|
for (let i = 0; i < 8; i++) {
|
|
9451
8811
|
ctx.beginPath();
|
|
9452
|
-
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0,
|
|
8812
|
+
ctx.arc((i + 0.5) / 8 * TW, pitchToTY(HALF_VFOV - 4 - i % 2 * 3), 2.2, 0, TAU);
|
|
9453
8813
|
ctx.fill();
|
|
9454
8814
|
}
|
|
9455
8815
|
ctx.fillStyle = "rgba(10,13,20,0.9)";
|