@johnmorrisdotca/kyogijo-stadiums 0.57.0 → 0.59.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/NOTICE.md +2 -0
  3. package/README.md +1 -1
  4. package/catalogue.json +1 -1
  5. package/dist/areas.d.ts +8 -3
  6. package/dist/areas.js +14 -5
  7. package/dist/athletics/plan.d.ts +2 -0
  8. package/dist/athletics/plan.js +20 -4
  9. package/dist/catalogue.data.js +1 -1
  10. package/dist/circuits-entry.d.ts +1 -1
  11. package/dist/circuits-entry.js +1 -1
  12. package/dist/circuits.draw.js +7 -4
  13. package/dist/circuits.held.d.ts +1 -0
  14. package/dist/circuits.held.js +13 -0
  15. package/dist/circuits.index.d.ts +15 -0
  16. package/dist/circuits.index.js +28 -0
  17. package/dist/circuits.race.dom.d.ts +25 -5
  18. package/dist/circuits.race.dom.js +22 -7
  19. package/dist/circuits.race.draw.d.ts +1 -1
  20. package/dist/circuits.race.draw.js +4 -4
  21. package/dist/circuits.turn.d.ts +8 -0
  22. package/dist/circuits.turn.js +29 -21
  23. package/dist/circuits.types.d.ts +2 -0
  24. package/dist/circuits.words.d.ts +4 -0
  25. package/dist/circuits.words.js +6 -0
  26. package/dist/cityctx-entry.d.ts +1 -1
  27. package/dist/cityctx-entry.js +1 -1
  28. package/dist/cityctx.d.ts +2 -2
  29. package/dist/cityctx.data.d.ts +2 -1
  30. package/dist/cityctx.data.js +3 -2
  31. package/dist/cityctx.js +9 -3
  32. package/dist/cityctx.las-vegas.data.d.ts +3 -0
  33. package/dist/cityctx.las-vegas.data.js +1387 -0
  34. package/dist/context.d.ts +11 -1
  35. package/dist/context.js +18 -4
  36. package/dist/crowd.d.ts +18 -0
  37. package/dist/crowd.js +88 -18
  38. package/dist/district-entry.d.ts +1 -0
  39. package/dist/district-entry.js +2 -0
  40. package/dist/district.api.d.ts +9 -2
  41. package/dist/district.api.js +7 -1
  42. package/dist/district.circuit.d.ts +34 -0
  43. package/dist/district.circuit.js +13 -0
  44. package/dist/district.d.ts +27 -3
  45. package/dist/district.js +85 -15
  46. package/dist/district.race.d.ts +40 -0
  47. package/dist/district.race.js +60 -0
  48. package/dist/district.turn.js +27 -1
  49. package/dist/draw.js +22 -7
  50. package/dist/landmarks.d.ts +105 -0
  51. package/dist/landmarks.js +160 -0
  52. package/dist/surfaces/artistic.js +3 -1
  53. package/dist/surfaces/athletics.js +2 -0
  54. package/dist/surfaces/hockey.js +3 -1
  55. package/dist/surfaces/surface.d.ts +7 -0
  56. package/dist/surfaces/surface.js +9 -0
  57. package/dist/turn.dom.js +5 -5
  58. package/dist/turn.markup.js +12 -1
  59. package/dist/turn.style.js +1 -0
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/package.json +2 -1
@@ -0,0 +1,160 @@
1
+ import { FEET_PER_METRE } from "./geometry.js";
2
+ import { bar, n, plane } from "./turn.kit.js";
3
+ import { hullOf, inside, metresFrom, smallestBox } from "./venues.geo.js";
4
+ export const LANDMARK_SHAPES = ["dome", "wheel", "fountains", "named"];
5
+ const RAD = Math.PI / 180;
6
+ const rotate = ([x, y], degrees) => [x * Math.cos(degrees * RAD) - y * Math.sin(degrees * RAD), x * Math.sin(degrees * RAD) + y * Math.cos(degrees * RAD)];
7
+ const middleOf = (ring) => [ring.reduce((s, p) => s + p[0], 0) / ring.length, ring.reduce((s, p) => s + p[1], 0) / ring.length];
8
+ /** The landmarks of some areas placed in a district: turned as the plan is and kept where they stand in the frame (`frame` is the plan's box in feet). */
9
+ export function placeLandmarks(areas, origin, turn, frame) {
10
+ const placed = [];
11
+ for (const area of areas) {
12
+ const [east, north] = metresFrom(origin, [area.origin])[0];
13
+ for (const l of area.landmarks ?? []) {
14
+ const ring = l.ring.map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
15
+ const at = middleOf(ring);
16
+ if (at[0] < frame.x || at[0] > frame.x + frame.w || at[1] < frame.y || at[1] > frame.y + frame.h)
17
+ continue;
18
+ // The long way of the outline, on the page: `smallestBox` reads east and north, and the page's y runs down.
19
+ const heading = smallestBox(ring.map(([x, y]) => [x, -y])).heading;
20
+ const along = [Math.sin(heading * RAD), -Math.cos(heading * RAD)];
21
+ const label = l.top === undefined ? at : rotate([(l.top[0] + east) * FEET_PER_METRE, -(l.top[1] + north) * FEET_PER_METRE], turn);
22
+ placed.push({ key: l.key, name: l.name, shape: l.shape, ring, at, label, height: l.height * FEET_PER_METRE, ...(l.width === undefined ? {} : { width: l.width * FEET_PER_METRE }), along });
23
+ }
24
+ }
25
+ return placed;
26
+ }
27
+ /** Whether a building's outline is a landmark's own (most of it inside a dome's footprint): the dome draws it, and the block is left out. */
28
+ export const ownedByLandmark = (landmarks, ring) => landmarks.some((l) => l.shape === "dome" && (inside(hullOf([...l.ring]), middleOf(ring)) || ring.filter((p) => inside(l.ring, p)).length * 10 >= ring.length * 7));
29
+ /** Whether a building stands under a named landmark (its middle inside the landmark's outline): it is raised in the 3D view whatever the count of blocks. */
30
+ export const underLandmark = (landmarks, ring) => landmarks.some((l) => l.shape === "named" && inside(l.ring, middleOf(ring)));
31
+ const num = (value) => String(Math.round(value * 10) / 10);
32
+ /** The jets of fountains, in feet: where each stands along the long way of the water (from its middle) and how high it reaches, as a share of the most. Ours: the published figure is the most they reach, the row is a pattern. */
33
+ export const FOUNTAIN_JETS = [[-0.36, 0.3], [-0.27, 0.48], [-0.18, 0.66], [-0.09, 0.84], [0, 1], [0.09, 0.84], [0.18, 0.66], [0.27, 0.48], [0.36, 0.3]];
34
+ /** The extent of an outline along a unit direction from its middle: the least and the most. */
35
+ const extent = (ring, at, along) => {
36
+ const t = ring.map((p) => (p[0] - at[0]) * along[0] + (p[1] - at[1]) * along[1]);
37
+ return [Math.min(...t), Math.max(...t)];
38
+ };
39
+ /** What the floor has of the landmarks: a dome's footprint and a wheel's line on the ground, in the tone of the tallest buildings, and the jets' feet as dots on the water. Empty for none. */
40
+ export function landmarksFloor(landmarks) {
41
+ const parts = [];
42
+ for (const l of landmarks) {
43
+ if (l.shape === "dome")
44
+ parts.push(`<path class="ky-dist-block ky-dist-b3 ky-dist-landmark" data-landmark="${l.key}" d="M${l.ring.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z"/>`);
45
+ if (l.shape === "wheel") {
46
+ const r = (l.width ?? 0) / 2;
47
+ parts.push(`<path class="ky-dist-ink ky-dist-landmark" data-landmark="${l.key}" stroke-width="${num(Math.max(r * 0.06, 3))}" d="M${num(l.at[0] - l.along[0] * r)} ${num(l.at[1] - l.along[1] * r)}L${num(l.at[0] + l.along[0] * r)} ${num(l.at[1] + l.along[1] * r)}"/>`);
48
+ }
49
+ if (l.shape === "fountains") {
50
+ // The water the jets stand in, as the land cover draws water (an area's lake mapped as the fountains' own outline is not in its land cover).
51
+ parts.push(`<path class="ky-dist-land ky-dist-water" data-landmark="${l.key}" d="M${l.ring.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z"/>`);
52
+ const [t0, t1] = extent(l.ring, l.at, l.along);
53
+ const long = t1 - t0;
54
+ const d = FOUNTAIN_JETS.map(([s]) => `M${num(l.at[0] + l.along[0] * (t0 + long * (0.5 + s)))} ${num(l.at[1] + l.along[1] * (t0 + long * (0.5 + s)))}h0`).join("");
55
+ parts.push(`<path class="ky-dist-jet ky-dist-landmark" data-landmark="${l.key}" stroke-width="${num(Math.max(long * 0.02, 6))}" d="${d}"/>`);
56
+ }
57
+ }
58
+ return parts.length === 0 ? "" : `<g class="ky-dist-landmarks" aria-hidden="true">${parts.join("")}</g>`;
59
+ }
60
+ /** A plane through three or more points of one flat polygon in 3D, painted with `cls`, and `cull` set so that its face away from `inward` is the one drawn (it is hidden from inside). */
61
+ function facet(id, points, cls, inward) {
62
+ const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
63
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
64
+ const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
65
+ const unit = (a) => {
66
+ const l = Math.hypot(...a);
67
+ return l < 1e-9 ? [0, 0, 0] : [a[0] / l, a[1] / l, a[2] / l];
68
+ };
69
+ const [p0, p1] = points;
70
+ const u = unit(sub(p1, p0));
71
+ const normal = unit(cross(sub(p1, p0), sub(points[points.length - 1], p0)));
72
+ if (Math.hypot(...normal) < 0.5)
73
+ return undefined;
74
+ const v = unit(cross(normal, u));
75
+ const local = points.map((p) => [dot(sub(p, p0), u), dot(sub(p, p0), v)]);
76
+ const [a0, a1] = [Math.min(...local.map((p) => p[0])), Math.max(...local.map((p) => p[0]))];
77
+ const [b0, b1] = [Math.min(...local.map((p) => p[1])), Math.max(...local.map((p) => p[1]))];
78
+ const o = [p0[0] + u[0] * a0 + v[0] * b0, p0[1] + u[1] * a0 + v[1] * b0, p0[2] + u[2] * a0 + v[2] * b0];
79
+ const svg = `<polygon class="${cls}" points="${local.map(([a, b]) => `${n(a - a0)},${n(b - b0)}`).join(" ")}"/>`;
80
+ const out = sub(points[0], inward);
81
+ // `u` x `v` is the normal; the face that is drawn is the one the normal points out of.
82
+ return { ...plane(id, "block", o, [u[0] * (a1 - a0), u[1] * (a1 - a0), u[2] * (a1 - a0)], [v[0] * (b1 - b0), v[1] * (b1 - b0), v[2] * (b1 - b0)], svg), cull: dot(normal, out) >= 0 ? 1 : -1 };
83
+ }
84
+ /** The tone of a facet by the way it faces the light (from up the page, to the left, and above): three tones, as a building's walls have. */
85
+ const toneOf = (normal) => {
86
+ const lit = normal[0] * -0.4 + normal[1] * -0.5 + normal[2] * 0.75;
87
+ return lit > 0.45 ? "u-block-a" : lit > 0 ? "u-block-b" : "u-block-c";
88
+ };
89
+ /** How finely a dome is cut: sides round it and bands up it. Fewer in the lower tiers. */
90
+ export const DOME_CUT = Object.freeze({ high: { sides: 16, bands: 6 }, simple: { sides: 10, bands: 4 } });
91
+ /** The planes of a dome: a sphere `width` across whose top is `height` over the ground, cut by the ground, as bands of flat facets, each lit by the way it faces. */
92
+ export function domePlanes(prefix, at, height, width, cut) {
93
+ const r = width / 2;
94
+ const zc = height - r;
95
+ const low = Math.asin(Math.max(-1, Math.min(1, -zc / r)));
96
+ const centre = [at[0], at[1], zc];
97
+ const point = (side, band) => {
98
+ const lat = low + ((Math.PI / 2 - low) * band) / cut.bands;
99
+ const lon = (2 * Math.PI * side) / cut.sides;
100
+ return [at[0] + r * Math.cos(lat) * Math.cos(lon), at[1] + r * Math.cos(lat) * Math.sin(lon), zc + r * Math.sin(lat)];
101
+ };
102
+ const planes = [];
103
+ for (let band = 0; band < cut.bands; band += 1) {
104
+ for (let side = 0; side < cut.sides; side += 1) {
105
+ const top = band + 1 === cut.bands;
106
+ const pts = top ? [point(side, band), point(side + 1, band), point(side, band + 1)] : [point(side, band), point(side + 1, band), point(side + 1, band + 1), point(side, band + 1)];
107
+ const mid = [pts.reduce((s, p) => s + p[0], 0) / pts.length - centre[0], pts.reduce((s, p) => s + p[1], 0) / pts.length - centre[1], pts.reduce((s, p) => s + p[2], 0) / pts.length - centre[2]];
108
+ const l = Math.hypot(...mid);
109
+ const f = facet(`${prefix}d${band}-${side}`, pts, `u-block ${toneOf([mid[0] / l, mid[1] / l, mid[2] / l])}`, centre);
110
+ if (f !== undefined)
111
+ planes.push(f);
112
+ }
113
+ }
114
+ return planes;
115
+ }
116
+ /** How a wheel is drawn: segments round its rim and spokes to its hub. */
117
+ export const WHEEL_CUT = Object.freeze({ high: { rim: 24, spokes: 12 }, simple: { rim: 12, spokes: 6 } });
118
+ /** The planes of a big wheel standing upright along `along`: its rim, its spokes and the two legs from its hub to the ground, each a thin bar seen from every side. */
119
+ export function wheelPlanes(prefix, at, along, height, width, cut) {
120
+ const r = width / 2;
121
+ const hub = [at[0], at[1], height - r];
122
+ const on = (angle, radius = r) => [at[0] + along[0] * radius * Math.cos(angle), at[1] + along[1] * radius * Math.cos(angle), hub[2] + radius * Math.sin(angle)];
123
+ const thick = Math.max(width * 0.025, 2);
124
+ const planes = [];
125
+ for (let i = 0; i < cut.rim; i += 1)
126
+ planes.push(...bar(`${prefix}rim${i}`, "pole", on((2 * Math.PI * i) / cut.rim), on((2 * Math.PI * (i + 1)) / cut.rim), thick, "u-leg"));
127
+ for (let i = 0; i < cut.spokes; i += 1)
128
+ planes.push(...bar(`${prefix}spoke${i}`, "pole", hub, on((2 * Math.PI * i) / cut.spokes), thick * 0.35, "u-leg"));
129
+ // Two legs, leaning out across the wheel's plane to the ground.
130
+ const across = [-along[1], along[0]];
131
+ for (const [i, side] of [[0, 1], [1, -1]])
132
+ planes.push(...bar(`${prefix}leg${i}`, "pole", hub, [at[0] + across[0] * side * r * 0.3, at[1] + across[1] * side * r * 0.3, 0], thick * 1.4, "u-leg"));
133
+ return planes;
134
+ }
135
+ /** The planes of fountains: a row of jets along the long way of the water, each a thin upright strip reaching to its share of `height`. */
136
+ export function fountainPlanes(prefix, l) {
137
+ const [t0, t1] = extent(l.ring, l.at, l.along);
138
+ const long = t1 - t0;
139
+ const planes = [];
140
+ FOUNTAIN_JETS.forEach(([s, share], i) => {
141
+ const t = t0 + long * (0.5 + s);
142
+ const [x, y] = [l.at[0] + l.along[0] * t, l.at[1] + l.along[1] * t];
143
+ planes.push(...bar(`${prefix}jet${i}`, "pole", [x, y, 0], [x, y, l.height * share], Math.max(long * 0.012, 3), "u-jet"));
144
+ });
145
+ return planes;
146
+ }
147
+ /** The 3D view's landmarks: each one's shape standing where it is, cut more coarsely in a simple tier. A named landmark has no shape of its own: its buildings are the area's. */
148
+ export function landmarkUprights(landmarks, simple) {
149
+ const items = [];
150
+ for (const l of landmarks) {
151
+ const prefix = `lm-${l.key}-`;
152
+ if (l.shape === "dome" && l.width !== undefined)
153
+ items.push({ id: `lm-${l.key}`, kind: "blocks", at: l.at, height: l.height, planes: domePlanes(prefix, l.at, l.height, l.width, simple ? DOME_CUT.simple : DOME_CUT.high) });
154
+ if (l.shape === "wheel" && l.width !== undefined)
155
+ items.push({ id: `lm-${l.key}`, kind: "flags", at: l.at, height: l.height, planes: wheelPlanes(prefix, l.at, l.along, l.height, l.width, simple ? WHEEL_CUT.simple : WHEEL_CUT.high) });
156
+ if (l.shape === "fountains" && !simple)
157
+ items.push({ id: `lm-${l.key}`, kind: "flags", at: l.at, height: l.height, planes: fountainPlanes(prefix, l) });
158
+ }
159
+ return items;
160
+ }
@@ -59,7 +59,9 @@ export function artistic(o, plan, ctx = {}) {
59
59
  }
60
60
  const top = -jy + 0.4;
61
61
  const bottom = sy + start.depth + 0.4 - W;
62
- const view0 = { x: -m, y: -feet(top) - m * 0.4, w: feet(L) + 2 * m, h: feet(W) + feet(top + bottom) + m * 0.8 };
62
+ // The deck is narrower along the sides than at the ends; a margin that is asked for (the ground the stands of a crowd stand on) is the same all round.
63
+ const across = o.margin === undefined ? m * 0.4 : m;
64
+ const view0 = { x: -m, y: -feet(top) - across, w: feet(L) + 2 * m, h: feet(W) + feet(top + bottom) + 2 * across };
63
65
  base = (ctx.bare === true ? "" : deckMarkup(o, ids, plan.deck, view0.x, view0.y, view0.w, view0.h)) + base + deck;
64
66
  const Lf = feet(L);
65
67
  const Wf = feet(W);
@@ -96,6 +96,8 @@ export function athleticsSurface(o, plan, embedded, margin) {
96
96
  onColour: false,
97
97
  };
98
98
  surface.box = { x: 0, y: 0, w: W, h: H };
99
+ if (plan.outline !== undefined && sv === undefined)
100
+ surface.floor = plan.outline.map(([x, y]) => [(x - ox) * K, (y - oy) * K]);
99
101
  surface.lineRef = { width: thick, feet: K };
100
102
  const ranges = {};
101
103
  for (const [name, r] of Object.entries(plan.ranges))
@@ -2,7 +2,7 @@ import { el, num } from "../format.js";
2
2
  import { rulesFor } from "../eras.js";
3
3
  import { NHL } from "../geometry.js";
4
4
  import { NHL_RINK } from "../leagues/hockey.js";
5
- import { atEnds, edges, ft, quarterTurn, roundedRect } from "./surface.js";
5
+ import { atEnds, edges, ft, quarterTurn, roundedOutline, roundedRect } from "./surface.js";
6
6
  /** Where the boards' curve is at a distance x from the end: the y at which it meets a line across the rink. The corner's radius is the NHL's unless given. */
7
7
  export function boardsY(x, radius = NHL.cornerRadius) {
8
8
  const r = radius;
@@ -128,6 +128,8 @@ export function hockey(o, era, rink = NHL_RINK) {
128
128
  };
129
129
  surface.lineRef = { width: thin, feet: 1 };
130
130
  surface.box = { x: 0, y: 0, w: L, h: W };
131
+ // The floor the stands of a crowd clear: the boards round the rounded corners, not the apron's square corners.
132
+ surface.floor = roundedOutline(-bw, -bw, L + 2 * bw, W + 2 * bw, r + bw);
131
133
  surface.centreRadius = rink.circleRadius;
132
134
  surface.anchors = { kind: "hockey", length: L, width: W, blueLine, goalLine: g, cornerRadius: r, net: net_ };
133
135
  // The zones of the ice: from the end boards to a blue line, and between the blue lines.
@@ -153,6 +153,11 @@ export interface Surface {
153
153
  width: number;
154
154
  feet: number;
155
155
  };
156
+ /**
157
+ * For a surface whose floor is not the rectangle of its view (an athletics track, its sprint straight and runways): the outline of everything it draws round its playing area, as points in the wide frame
158
+ * in feet. The stands of a crowd clear it.
159
+ */
160
+ floor?: (readonly [number, number])[];
156
161
  /** For a surface with a crowd: the margin it was drawn with, which holds the stands. */
157
162
  crowdMargin?: number;
158
163
  /** A real stadium of more than one configuration laid out in the venue's own frame: where the middle of its field is in the surface's frame (the surface is shifted to lie there), and the ground, building, stands and crowd under it, in the venue's frame. */
@@ -254,6 +259,8 @@ export declare function quarterTurn(view: {
254
259
  };
255
260
  /** A box from its edges: the left, the top, the right and the bottom. */
256
261
  export declare const edges: (x0: number, y0: number, x1: number, y1: number) => Box;
262
+ /** A rounded rectangle's outline as points (each corner in 12 steps), for a surface's `floor`. */
263
+ export declare function roundedOutline(x: number, y: number, w: number, h: number, r: number): [number, number][];
257
264
  /** A range that has an end: its box at the left end and at the right, made by a function of the end. */
258
265
  export declare const atEnds: (box: (end: "left" | "right") => Box) => {
259
266
  left: Box;
@@ -45,5 +45,14 @@ export function quarterTurn(view) {
45
45
  }
46
46
  /** A box from its edges: the left, the top, the right and the bottom. */
47
47
  export const edges = (x0, y0, x1, y1) => ({ x: Math.min(x0, x1), y: Math.min(y0, y1), w: Math.abs(x1 - x0), h: Math.abs(y1 - y0) });
48
+ /** A rounded rectangle's outline as points (each corner in 12 steps), for a surface's `floor`. */
49
+ export function roundedOutline(x, y, w, h, r) {
50
+ const rr = Math.max(0, Math.min(r, w / 2, h / 2));
51
+ const out = [];
52
+ for (const [cx, cy, a0] of [[x + w - rr, y + rr, -90], [x + w - rr, y + h - rr, 0], [x + rr, y + h - rr, 90], [x + rr, y + rr, 180]])
53
+ for (let i = 0; i <= 12; i += 1)
54
+ out.push([cx + rr * Math.cos(((a0 + i * 7.5) * Math.PI) / 180), cy + rr * Math.sin(((a0 + i * 7.5) * Math.PI) / 180)]);
55
+ return out;
56
+ }
48
57
  /** A range that has an end: its box at the left end and at the right, made by a function of the end. */
49
58
  export const atEnds = (box) => ({ left: box("left"), right: box("right") });
package/dist/turn.dom.js CHANGED
@@ -212,6 +212,8 @@ export function mountTurn(host, options, settings, build, replayable = false, le
212
212
  root.dataset.quality = quality;
213
213
  root.dataset.tier = tier;
214
214
  root.dataset.share = String(ceiling);
215
+ // ---- One frame request a frame: the auto-turn, a replay or a race on the view, and the quality's timing of the frames ask through this, and share one animation frame when they run together. ----
216
+ const frames = frameBatch(win);
215
217
  // ---- Quality: in `auto`, the frames the view makes while it turns decide the tier. ----
216
218
  let governor = quality === "auto" && win !== null && model.tiered === true ? createGovernor(tier, settings.qualityTuning) : undefined;
217
219
  let meterFrame;
@@ -230,7 +232,7 @@ export function mountTurn(host, options, settings, build, replayable = false, le
230
232
  };
231
233
  const stopMeter = () => {
232
234
  if (meterFrame !== undefined)
233
- win?.cancelAnimationFrame(meterFrame);
235
+ frames.cancel(meterFrame);
234
236
  meterFrame = undefined;
235
237
  lastStamp = undefined;
236
238
  };
@@ -252,7 +254,7 @@ export function mountTurn(host, options, settings, build, replayable = false, le
252
254
  if (next !== undefined)
253
255
  retier(next);
254
256
  if (meterFrame === undefined)
255
- meterFrame = win.requestAnimationFrame(meterTick);
257
+ meterFrame = frames.request(meterTick);
256
258
  };
257
259
  /** The view was painted: the frames are timed from now until it is still. */
258
260
  const touched = () => {
@@ -260,7 +262,7 @@ export function mountTurn(host, options, settings, build, replayable = false, le
260
262
  return;
261
263
  lastPaint = win.performance.now();
262
264
  if (meterFrame === undefined)
263
- meterFrame = win.requestAnimationFrame(meterTick);
265
+ meterFrame = frames.request(meterTick);
264
266
  };
265
267
  /** Draw the world again, from the model of the tier and the options now: the camera, the auto-turn and the pointer are not touched (the elements are the only thing replaced). */
266
268
  const renderWorld = () => {
@@ -447,8 +449,6 @@ export function mountTurn(host, options, settings, build, replayable = false, le
447
449
  go(opening, 560, "none");
448
450
  say(turnWords(model).reset);
449
451
  };
450
- // ---- One frame request a frame: the auto-turn and a replay on the view ask through this, and share one animation frame when both run. ----
451
- const frames = frameBatch(win);
452
452
  const requestFrame = (callback) => frames.request(callback);
453
453
  const cancelFrame = (id) => frames.cancel(id);
454
454
  // ---- The auto-turn: a trophy case's slow turn. It runs only when asked, holds while the pointer holds the view, and does nothing while the page is hidden. ----
@@ -56,6 +56,8 @@ export function groupRanks(groups, yaw) {
56
56
  return rank;
57
57
  }
58
58
  const RAD = Math.PI / 180;
59
+ /** The things of a venue's bowl: they stand on the ground round the surface, never under a raised floor. */
60
+ const BOWL = new Set(["stands", "wall-seats", "gallery", "roof", "ribbon-boards", "hung-board"]);
59
61
  const prepared = new WeakMap();
60
62
  function prepare(model) {
61
63
  const known = prepared.get(model.planes);
@@ -79,7 +81,8 @@ function prepare(model) {
79
81
  const zs = [p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]];
80
82
  mid.push([(p.o[0] + (p.u[0] + p.v[0]) / 2 - centre[0]) * s, (p.o[1] + (p.u[1] + p.v[1]) / 2 - centre[1]) * s, (p.o[2] + (p.u[2] + p.v[2]) / 2) * s, Math.min(...zs) * s, Math.max(...zs) * s]);
81
83
  }
82
- const made = { planes: model.planes, scale: s, centre, base, mid };
84
+ const ofBowl = new Set(model.uprights.items.filter((item) => BOWL.has(item.kind)).flatMap((item) => item.planes));
85
+ const made = { planes: model.planes, scale: s, centre, base, mid, bowl: model.planes.map((p) => ofBowl.has(p)) };
83
86
  prepared.set(model.planes, made);
84
87
  return made;
85
88
  }
@@ -159,6 +162,14 @@ export function layoutOf(model, view, width, height, lens, cull = false, raster
159
162
  order.length = 0;
160
163
  for (let i = 0, j = 0; i < own.length || j < apart.length;)
161
164
  order.push(j >= apart.length || (i < own.length && own[i].depth <= apart[j].depth) ? own[i++] : apart[j++]);
165
+ // A raised floor (a table's top) carries the ground the bowl stands on, so it is sorted by its middle among the surface's own things only: the parts of the bowl that are farther than its middle
166
+ // come after it, still in their own order, or the floor would be drawn over the far stands.
167
+ if (raised) {
168
+ const at = order.findIndex((one) => one.i === 0);
169
+ const far = order.slice(0, at).filter((one) => known.bowl[one.i - 1]);
170
+ if (far.length > 0)
171
+ order.splice(0, at + 1, ...order.slice(0, at).filter((one) => !known.bowl[one.i - 1]), order[at], ...far);
172
+ }
162
173
  const stack = new Array(count + 1).fill(0);
163
174
  stack[0] = 1;
164
175
  order.forEach((one, rank) => {
@@ -28,6 +28,7 @@ export const KYOGIJO_TURN_STYLE = `
28
28
  .ky-turn .u-orange{fill:var(--ky-up-orange)}
29
29
  .ky-turn .u-wood{fill:var(--ky-up-wood)}
30
30
  .ky-turn .u-leg{fill:var(--ky-up-leg)}
31
+ .ky-turn .u-jet{fill:var(--ky-up-jet,rgba(222,240,252,.82))}
31
32
  .ky-turn .u-pad{fill:var(--ky-up-pad)}
32
33
  .ky-turn .u-table{fill:var(--ky-up-table)}
33
34
  .ky-turn .u-foot{fill:var(--ky-up-foot)}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export declare const VERSION = "0.57.0";
2
+ export declare const VERSION = "0.59.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package's version: the same as package.json's, and a test holds them to each other. */
2
- export const VERSION = "0.57.0";
2
+ export const VERSION = "0.59.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/kyogijo-stadiums",
3
- "version": "0.57.0",
3
+ "version": "0.59.0",
4
4
  "description": "Kyogijo Stadiums 競技場, 40 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, the MMA cage, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",
@@ -75,6 +75,7 @@
75
75
  "./dist/grounds.js",
76
76
  "./dist/venues.all.js",
77
77
  "./dist/walk.full.js",
78
+ "./dist/circuits.held.js",
78
79
  "./dist/strings.js",
79
80
  "./dist/strings.en.js",
80
81
  "./dist/strings.ja.js"