@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/NOTICE.md +4 -0
- package/README.md +71 -16
- package/dist/annotate.js +1 -1
- package/dist/athletics/describe.d.ts +8 -0
- package/dist/athletics/describe.js +41 -0
- package/dist/athletics/events.d.ts +17 -0
- package/dist/athletics/events.js +48 -0
- package/dist/athletics/facilities.d.ts +80 -0
- package/dist/athletics/facilities.js +176 -0
- package/dist/athletics/infield.d.ts +79 -0
- package/dist/athletics/infield.js +166 -0
- package/dist/athletics/layout.d.ts +13 -0
- package/dist/athletics/layout.js +98 -0
- package/dist/athletics/oval.d.ts +57 -0
- package/dist/athletics/oval.js +116 -0
- package/dist/athletics/plan.d.ts +97 -0
- package/dist/athletics/plan.js +355 -0
- package/dist/athletics/races.d.ts +102 -0
- package/dist/athletics/races.js +355 -0
- package/dist/athletics/steeplechase.d.ts +72 -0
- package/dist/athletics/steeplechase.js +196 -0
- package/dist/athletics/stencil.d.ts +13 -0
- package/dist/athletics/stencil.js +85 -0
- package/dist/athletics/track.d.ts +54 -0
- package/dist/athletics/track.js +105 -0
- package/dist/athletics/types.d.ts +77 -0
- package/dist/athletics/types.js +1 -0
- package/dist/athletics-entry.d.ts +17 -0
- package/dist/athletics-entry.js +11 -0
- package/dist/athletics.style.d.ts +16 -0
- package/dist/athletics.style.js +51 -0
- package/dist/atlas-entry.d.ts +4 -2
- package/dist/atlas-entry.js +3 -2
- package/dist/atlas.d.ts +83 -7
- package/dist/atlas.js +108 -8
- package/dist/charts-entry.d.ts +6 -1
- package/dist/charts-entry.js +3 -0
- package/dist/charts.athletics.d.ts +183 -0
- package/dist/charts.athletics.js +455 -0
- package/dist/charts.curling.heat.d.ts +24 -0
- package/dist/charts.curling.heat.js +116 -0
- package/dist/charts.curling.js +14 -3
- package/dist/charts.pool.d.ts +67 -0
- package/dist/charts.pool.js +76 -0
- package/dist/charts.skating.d.ts +204 -0
- package/dist/charts.skating.js +426 -0
- package/dist/charts.types.d.ts +53 -1
- package/dist/court.d.ts +85 -2
- package/dist/court.js +102 -7
- package/dist/court.more.d.ts +46 -0
- package/dist/court.more.js +219 -0
- package/dist/courts.style.d.ts +14 -2
- package/dist/courts.style.js +51 -4
- package/dist/crowd.d.ts +154 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +198 -0
- package/dist/crowd.js +465 -0
- package/dist/crowd.park.d.ts +19 -0
- package/dist/crowd.park.js +153 -0
- package/dist/curling.stats.d.ts +271 -0
- package/dist/curling.stats.js +301 -0
- package/dist/draw.d.ts +16 -0
- package/dist/draw.js +318 -35
- package/dist/element-entry.d.ts +1 -1
- package/dist/element-entry.js +1 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +77 -3
- package/dist/features.js +131 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +18 -4
- package/dist/frames.providers.js +63 -8
- package/dist/geojson.js +3 -0
- package/dist/geometry-entry.d.ts +2 -1
- package/dist/geometry-entry.js +2 -1
- package/dist/geometry.d.ts +20 -0
- package/dist/geometry.js +78 -1
- package/dist/grounds.d.ts +4 -0
- package/dist/grounds.data.js +12 -0
- package/dist/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +4 -3
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +2 -2
- package/dist/heat.zones.js +154 -6
- package/dist/homeside.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +10 -1
- package/dist/layers.d.ts +1 -1
- package/dist/layers.env.d.ts +3 -1
- package/dist/layers.env.js +4 -2
- package/dist/layers.js +4 -4
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +20 -0
- package/dist/leagues/types.d.ts +1 -1
- package/dist/leagues-entry.d.ts +4 -2
- package/dist/leagues-entry.js +2 -1
- package/dist/leagues.d.ts +14 -4
- package/dist/leagues.js +146 -5
- package/dist/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +30 -2
- package/dist/pageant.d.ts +120 -0
- package/dist/pageant.draw.d.ts +21 -0
- package/dist/pageant.draw.js +171 -0
- package/dist/pageant.js +207 -0
- package/dist/pageant.style.d.ts +8 -0
- package/dist/pageant.style.js +42 -0
- package/dist/png.d.ts +8 -1
- package/dist/png.js +21 -1
- package/dist/pool.d.ts +176 -0
- package/dist/pool.figures.d.ts +304 -0
- package/dist/pool.figures.js +144 -0
- package/dist/pool.js +304 -0
- package/dist/pool.kit.d.ts +48 -0
- package/dist/pool.kit.js +141 -0
- package/dist/pool.labels.d.ts +20 -0
- package/dist/pool.labels.js +257 -0
- package/dist/pool.race.d.ts +65 -0
- package/dist/pool.race.js +140 -0
- package/dist/pool.replay.d.ts +46 -0
- package/dist/pool.replay.js +127 -0
- package/dist/pool.section.d.ts +13 -0
- package/dist/pool.section.js +83 -0
- package/dist/pool.style.d.ts +20 -0
- package/dist/pool.style.js +169 -0
- package/dist/pool.types.d.ts +145 -0
- package/dist/pool.types.js +3 -0
- package/dist/pool.words.d.ts +13 -0
- package/dist/pool.words.js +101 -0
- package/dist/ranges.names.js +47 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- package/dist/score.js +70 -5
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- package/dist/skating/describe.d.ts +7 -0
- package/dist/skating/describe.js +44 -0
- package/dist/skating/events.d.ts +24 -0
- package/dist/skating/events.js +47 -0
- package/dist/skating/figure.d.ts +24 -0
- package/dist/skating/figure.js +37 -0
- package/dist/skating/long.d.ts +54 -0
- package/dist/skating/long.js +111 -0
- package/dist/skating/parts.d.ts +43 -0
- package/dist/skating/parts.js +40 -0
- package/dist/skating/plan.d.ts +55 -0
- package/dist/skating/plan.js +197 -0
- package/dist/skating/short.d.ts +47 -0
- package/dist/skating/short.js +89 -0
- package/dist/skating/track.d.ts +66 -0
- package/dist/skating/track.js +94 -0
- package/dist/skating/types.d.ts +57 -0
- package/dist/skating/types.js +3 -0
- package/dist/skating-entry.d.ts +17 -0
- package/dist/skating-entry.js +11 -0
- package/dist/skating.style.d.ts +4 -0
- package/dist/skating.style.js +24 -0
- package/dist/strings.d.ts +296 -0
- package/dist/strings.js +328 -11
- package/dist/style.d.ts +2 -0
- package/dist/style.js +11 -1
- package/dist/surfaces/artistic.d.ts +9 -0
- package/dist/surfaces/artistic.js +94 -0
- package/dist/surfaces/athletics.d.ts +16 -0
- package/dist/surfaces/athletics.js +132 -0
- package/dist/surfaces/baseball.js +15 -0
- package/dist/surfaces/basketball.floor.d.ts +27 -0
- package/dist/surfaces/basketball.floor.js +83 -0
- package/dist/surfaces/basketball.gear.d.ts +27 -0
- package/dist/surfaces/basketball.gear.js +90 -0
- package/dist/surfaces/basketball.js +43 -21
- package/dist/surfaces/curling.d.ts +16 -0
- package/dist/surfaces/curling.fold.d.ts +67 -0
- package/dist/surfaces/curling.fold.js +107 -0
- package/dist/surfaces/curling.js +37 -3
- package/dist/surfaces/dive.d.ts +9 -0
- package/dist/surfaces/dive.js +128 -0
- package/dist/surfaces/field.d.ts +7 -2
- package/dist/surfaces/field.js +15 -9
- package/dist/surfaces/hockey.js +1 -0
- package/dist/surfaces/net.d.ts +2 -1
- package/dist/surfaces/net.js +160 -15
- package/dist/surfaces/polo.d.ts +12 -0
- package/dist/surfaces/polo.js +159 -0
- package/dist/surfaces/pool.d.ts +27 -0
- package/dist/surfaces/pool.js +198 -0
- package/dist/surfaces/skating.d.ts +6 -0
- package/dist/surfaces/skating.js +62 -0
- package/dist/surfaces/soccer.js +2 -1
- package/dist/surfaces/surface.d.ts +44 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- package/dist/tenants.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +945 -0
- package/dist/tenants.js +46 -0
- package/dist/turn-entry.d.ts +12 -0
- package/dist/turn-entry.js +7 -0
- package/dist/turn.camera.d.ts +105 -0
- package/dist/turn.camera.js +168 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +351 -0
- package/dist/turn.kit.d.ts +86 -0
- package/dist/turn.kit.js +110 -0
- package/dist/turn.markup.d.ts +48 -0
- package/dist/turn.markup.js +108 -0
- package/dist/turn.model.d.ts +78 -0
- package/dist/turn.model.js +91 -0
- package/dist/turn.style.d.ts +5 -0
- package/dist/turn.style.js +55 -0
- package/dist/types.d.ts +183 -18
- package/dist/units.d.ts +1 -1
- package/dist/units.js +15 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -0
- package/dist/uprights.d.ts +37 -0
- package/dist/uprights.fields.d.ts +32 -0
- package/dist/uprights.fields.js +172 -0
- package/dist/uprights.goal.d.ts +49 -0
- package/dist/uprights.goal.js +69 -0
- package/dist/uprights.js +160 -0
- package/dist/uprights.more.d.ts +7 -0
- package/dist/uprights.more.js +183 -0
- package/dist/uprights.net.d.ts +12 -0
- package/dist/uprights.net.js +109 -0
- package/dist/uprights.netkit.d.ts +28 -0
- package/dist/uprights.netkit.js +67 -0
- package/dist/uprights.pools.d.ts +8 -0
- package/dist/uprights.pools.js +190 -0
- package/dist/uprights.rinks.d.ts +27 -0
- package/dist/uprights.rinks.js +106 -0
- package/dist/uprights.track.d.ts +50 -0
- package/dist/uprights.track.js +246 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +19 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// The 3D view as markup: a box, and in it the floor (the flat drawing) and every plane that stands on it. Nothing is nested in 3D (no `preserve-3d`, no
|
|
2
|
+
// `perspective` on a parent): every element carries its whole place on the screen in one `matrix3d`, worked out here from the way the scene is looked at, and
|
|
3
|
+
// the elements are stacked far to near by `z-index`. A browser that cannot keep nested 3D layers (Safari on a phone can lose them) draws it as it is meant.
|
|
4
|
+
// The same text is what a page is given from `turnMarkup` and what the mount writes; the mount then makes it turn.
|
|
5
|
+
import { TURN_PRESET_NAMES, cameraMatrix, depthOf, eyeDistance, fit, matrix3d, multiply, presetOf } from "./turn.camera.js";
|
|
6
|
+
import { cross, facing, fadeOf, n, size, unit } from "./turn.kit.js";
|
|
7
|
+
import { buildTurn, turnWords } from "./turn.model.js";
|
|
8
|
+
import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
|
|
9
|
+
import { esc } from "./format.js";
|
|
10
|
+
const px = (value) => `${Math.round(value * 1000) / 1000}`;
|
|
11
|
+
/**
|
|
12
|
+
* The matrix that stands a plane in the scene, before the camera: its x along `u`, its y along `v`, its corner `o`, in pixels from the middle of the scene
|
|
13
|
+
* (`centre`, in feet).
|
|
14
|
+
*/
|
|
15
|
+
export function planeMatrix(p, scale, centre = [0, 0]) {
|
|
16
|
+
const u = unit(p.u);
|
|
17
|
+
const v = unit(p.v);
|
|
18
|
+
const w = unit(cross(u, v));
|
|
19
|
+
return [u[0], u[1], u[2], 0, v[0], v[1], v[2], 0, w[0], w[1], w[2], 0, (p.o[0] - centre[0]) * scale, (p.o[1] - centre[1]) * scale, p.o[2] * scale, 1];
|
|
20
|
+
}
|
|
21
|
+
/** Work a view out in a container `width` by `height` pixels. */
|
|
22
|
+
export function layoutOf(model, view, width, height, lens) {
|
|
23
|
+
const distance = eyeDistance(width, height, lens);
|
|
24
|
+
const at = fit(model.box, { ...view, zoom: 1 }, width, height, distance);
|
|
25
|
+
const camera = cameraMatrix(view, at, distance);
|
|
26
|
+
const s = model.scale;
|
|
27
|
+
const centre = model.centre;
|
|
28
|
+
const floor = { id: "floor", kind: "ground", o: [model.viewBox.x, model.viewBox.y, model.uprights.lift], u: [model.viewBox.w, 0, 0], v: [0, model.viewBox.h, 0], svg: "" };
|
|
29
|
+
// A billboard faces the eye at this view.
|
|
30
|
+
const planes = model.planes.map((p) => facing(p, view.yaw));
|
|
31
|
+
const transforms = [floor, ...planes].map((p) => matrix3d(multiply(camera, planeMatrix(p, s, centre))));
|
|
32
|
+
// Far to near: a plane's depth is that of its middle. The ground of a hall is under everything, the floor next.
|
|
33
|
+
const order = planes
|
|
34
|
+
.map((p, i) => ({ i, ground: p.kind === "ground", depth: depthOf([(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], view, at) }))
|
|
35
|
+
.filter((one) => !one.ground);
|
|
36
|
+
order.sort((a, b) => a.depth - b.depth);
|
|
37
|
+
const stack = new Array(model.planes.length + 1).fill(0);
|
|
38
|
+
stack[0] = 1;
|
|
39
|
+
order.forEach((one, rank) => {
|
|
40
|
+
stack[one.i + 1] = rank + 2;
|
|
41
|
+
});
|
|
42
|
+
const fade = [1, ...model.planes.map((p) => fadeOf(p, view.yaw, view.pitch))];
|
|
43
|
+
return { at, transforms, stack, fade };
|
|
44
|
+
}
|
|
45
|
+
/** One plane as an element: its box in pixels and the SVG drawn on it in feet. */
|
|
46
|
+
export function planeMarkup(p, scale, transform, stack, fade = 1) {
|
|
47
|
+
const [w, h] = size(p);
|
|
48
|
+
return `<div class="ky-up ky-up-${p.kind}" data-part="${esc(p.id)}" style="width:${px(w * scale)}px;height:${px(h * scale)}px;transform:${transform};z-index:${stack}${fade < 1 ? `;opacity:${n(fade)}` : ""}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n(w)} ${n(h)}" width="${px(w * scale)}" height="${px(h * scale)}" aria-hidden="true" focusable="false">${p.svg}</svg></div>`;
|
|
49
|
+
}
|
|
50
|
+
/** The floor as an element: the flat drawing, in the pixels of the scene. */
|
|
51
|
+
export function floorMarkup(model, transform, stack) {
|
|
52
|
+
const vb = model.viewBox;
|
|
53
|
+
return `<div class="ky-turn-floor" style="width:${px(vb.w * model.scale)}px;height:${px(vb.h * model.scale)}px;transform:${transform};z-index:${stack}">${model.floor}</div>`;
|
|
54
|
+
}
|
|
55
|
+
/** The floor and the planes of a model, laid out. */
|
|
56
|
+
export function worldMarkup(model, layout) {
|
|
57
|
+
return floorMarkup(model, layout.transforms[0], layout.stack[0]) + model.planes.map((p, i) => planeMarkup(p, model.scale, layout.transforms[i + 1], layout.stack[i + 1], layout.fade[i + 1])).join("");
|
|
58
|
+
}
|
|
59
|
+
/** The buttons under the view. */
|
|
60
|
+
export function controlsMarkup(model, view) {
|
|
61
|
+
const words = turnWords(model);
|
|
62
|
+
const here = presetOf(view, model.options.sport);
|
|
63
|
+
const presets = TURN_PRESET_NAMES.map((name, i) => `<button type="button" data-preset="${name}" aria-pressed="${here === name}" aria-keyshortcuts="${i + 1}">${esc(words[name])}</button>`).join("");
|
|
64
|
+
const tools = [
|
|
65
|
+
["turn-left", "↺", words.turnLeft],
|
|
66
|
+
["turn-right", "↻", words.turnRight],
|
|
67
|
+
["raise", "↑", words.raise],
|
|
68
|
+
["lower", "↓", words.lower],
|
|
69
|
+
["zoom-in", "+", words.zoomIn],
|
|
70
|
+
["zoom-out", "−", words.zoomOut],
|
|
71
|
+
];
|
|
72
|
+
const buttons = tools.map(([act, glyph, label]) => `<button type="button" data-act="${act}" aria-label="${esc(label)}" title="${esc(label)}">${glyph}</button>`).join("");
|
|
73
|
+
return `<div class="ky-turn-controls"><div class="ky-turn-group" role="group" aria-label="${esc(words.group)}">${presets}</div><div class="ky-turn-group">${buttons}<button type="button" data-act="reset" aria-keyshortcuts="0 Home">${esc(words.reset)}</button></div></div>`;
|
|
74
|
+
}
|
|
75
|
+
/** The size, in pixels, the first view is worked out for. */
|
|
76
|
+
export const sizeOf = (settings) => settings.size ?? { width: 960, height: 600 };
|
|
77
|
+
/** The 3D view of a model as markup, laid out for `settings.size`; a page's script then makes it turn. */
|
|
78
|
+
export function turnMarkupOf(model, settings = {}) {
|
|
79
|
+
const words = turnWords(model);
|
|
80
|
+
const { width, height } = sizeOf(settings);
|
|
81
|
+
const view = model.view;
|
|
82
|
+
const layout = layoutOf(model, view, width, height, settings.lens);
|
|
83
|
+
const keys = `${words.keys}${model.standing === "" ? "" : ` ${model.standing}`}`;
|
|
84
|
+
const sheet = model.options.style === "none" ? "" : `<style>${KYOGIJO_TURN_STYLE}</style>`;
|
|
85
|
+
const theme = model.options.theme;
|
|
86
|
+
const sport = model.options.sport;
|
|
87
|
+
return (`<div class="ky-turn" id="${esc(model.id)}" data-sport="${esc(String(sport))}" data-mode="3d" data-touch="${settings.touch === "turn" ? "turn" : "scroll"}" data-preset="${presetOf(view, sport) ?? "custom"}"${theme === "light" || theme === "dark" ? ` data-theme="${theme}"` : ""} lang="${model.language}">` +
|
|
88
|
+
sheet +
|
|
89
|
+
`<div class="ky-turn-view" tabindex="0" role="group" aria-roledescription="${esc(words.region)}" aria-label="${esc(model.title)}" aria-describedby="${esc(model.id)}-help" aria-keyshortcuts="ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1 2 3 4 Home"><div class="ky-turn-world">${worldMarkup(model, layout)}</div></div>` +
|
|
90
|
+
`<p class="ky-turn-help" id="${esc(model.id)}-help">${esc(keys)}</p>` +
|
|
91
|
+
(settings.controls === false ? "" : controlsMarkup(model, view)) +
|
|
92
|
+
`<div class="ky-turn-live" role="status" aria-live="polite"></div>` +
|
|
93
|
+
`</div>`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A surface as a 3D view, as markup: the flat drawing for a floor, what stands on it as planes, and the controls under it. Pure, so it can be made on a
|
|
97
|
+
* server; it is a still picture until `mountKyogijoTurn` (or the element) makes it turn in a page.
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { turnMarkup } from "@johnmorrisdotca/kyogijo-stadiums/turn";
|
|
101
|
+
* const html = turnMarkup({ sport: "volleyball" }, { preset: "end" });
|
|
102
|
+
* console.log(html.includes("ky-turn"), html.includes('data-part="net"'));
|
|
103
|
+
* // true true
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function turnMarkup(options, settings = {}) {
|
|
107
|
+
return turnMarkupOf(buildTurn(options, settings), settings);
|
|
108
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { KYOGIJO_STRINGS } from "./strings.ts";
|
|
2
|
+
import { type TurnPreset, type TurnView } from "./turn.camera.ts";
|
|
3
|
+
import type { Plane } from "./turn.kit.ts";
|
|
4
|
+
import { type UprightOptions, type Uprights } from "./uprights.ts";
|
|
5
|
+
import type { Box3 } from "./turn.camera.ts";
|
|
6
|
+
import type { Language, ScoreOptions, SurfaceOptions } from "./types.ts";
|
|
7
|
+
/** What can be asked of a 3D view. */
|
|
8
|
+
export interface TurnSettings {
|
|
9
|
+
/** The viewpoint it opens on: `overhead`, `broadcast`, `end` or `corner` (the default). */
|
|
10
|
+
preset?: TurnPreset;
|
|
11
|
+
/** An exact opening view, laid over the preset's. */
|
|
12
|
+
view?: Partial<TurnView>;
|
|
13
|
+
/** Whether the buttons under the view are drawn (a row of viewpoints, turn, raise, zoom and reset). True when left out; the keys and the pointer work either way. */
|
|
14
|
+
controls?: boolean;
|
|
15
|
+
/** What stands on the surface: `false` for the floor alone, or which kinds, whether the boards and the glass are drawn, the height of a volleyball net. True when left out. */
|
|
16
|
+
uprights?: boolean | UprightOptions;
|
|
17
|
+
/** Where the score of a score drawing goes: `board` (the default) hangs it up as a scoreboard that faces the eye (over the middle of a court or a rink, behind an end of a field, behind centre field in a ballpark) and takes the panel off the floor; `floor` leaves the panel on the floor. */
|
|
18
|
+
score?: "board" | "floor";
|
|
19
|
+
/** What a finger does on the view: `scroll` (the default) lets a vertical swipe scroll the page and a sideways one turn the view; `turn` keeps every touch for the view, which then also tips it. */
|
|
20
|
+
touch?: "scroll" | "turn";
|
|
21
|
+
/** `smooth` glides between viewpoints, `none` jumps; `auto` (the default) glides unless the reader prefers reduced motion. */
|
|
22
|
+
motion?: "auto" | "smooth" | "none";
|
|
23
|
+
/** The drawing the floor is, in place of `drawKyogijo`: a chart's `draw`, for instance. It is given the options with the view's own changes (the whole surface, lying wide). */
|
|
24
|
+
floor?: (options: SurfaceOptions | ScoreOptions) => string;
|
|
25
|
+
/** How long a lens the eye has, as a multiple of the view's longer side: 1.7 by default, and the larger it is the flatter the picture. */
|
|
26
|
+
lens?: number;
|
|
27
|
+
/** The size the first view is worked out for, in pixels, when the markup is made away from a page. 960 by 600 by default. */
|
|
28
|
+
size?: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
/** The prefix of the ids in the view, for a page with more than one. A hash of the options when missing. */
|
|
33
|
+
id?: string;
|
|
34
|
+
/** Called after the view has changed by a key, a button or the pointer, with the view and the viewpoint it is (`undefined` when it has been turned by hand). */
|
|
35
|
+
onChange?: (view: TurnView, preset: TurnPreset | undefined) => void;
|
|
36
|
+
}
|
|
37
|
+
/** The pixels in one foot of the scene at a zoom of 1: the floor is drawn this big, and scaled down to fit. A larger floor is sharper. */
|
|
38
|
+
export declare const WORLD_PX = 1400;
|
|
39
|
+
/** Everything a 3D view is made of. */
|
|
40
|
+
export interface TurnModel {
|
|
41
|
+
id: string;
|
|
42
|
+
language: Language;
|
|
43
|
+
/** The options the floor is drawn from: the whole surface, lying wide. */
|
|
44
|
+
options: SurfaceOptions | ScoreOptions;
|
|
45
|
+
title: string;
|
|
46
|
+
/** What the drawing says, and then what stands on it. */
|
|
47
|
+
description: string;
|
|
48
|
+
/** The sentence about what stands on the surface, or an empty string. */
|
|
49
|
+
standing: string;
|
|
50
|
+
/** The floor: the markup of the flat drawing. */
|
|
51
|
+
floor: string;
|
|
52
|
+
/** The floor's viewBox in feet. */
|
|
53
|
+
viewBox: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
w: number;
|
|
57
|
+
h: number;
|
|
58
|
+
};
|
|
59
|
+
/** Pixels in a foot. */
|
|
60
|
+
scale: number;
|
|
61
|
+
uprights: Uprights;
|
|
62
|
+
planes: Plane[];
|
|
63
|
+
/** The whole scene in pixels, from its middle: the floor's rectangle, and the height of the highest thing. */
|
|
64
|
+
box: Box3;
|
|
65
|
+
/** The middle of the floor, in feet. */
|
|
66
|
+
centre: [number, number];
|
|
67
|
+
view: TurnView;
|
|
68
|
+
/** False when there is nothing to turn: a badge is a strip, not a surface. */
|
|
69
|
+
turnable: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** The options with what makes the floor of a 3D view: the whole surface, lying wide, at its full size. */
|
|
72
|
+
export declare function floorOptions(options: SurfaceOptions | ScoreOptions): SurfaceOptions | ScoreOptions;
|
|
73
|
+
/** The view a set of settings opens on. */
|
|
74
|
+
export declare const openingView: (settings: Pick<TurnSettings, "preset" | "view">, sport?: string) => TurnView;
|
|
75
|
+
/** Work out a 3D view from the options of a drawing. */
|
|
76
|
+
export declare function buildTurn(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): TurnModel;
|
|
77
|
+
/** The words of the 3D view in the model's language. */
|
|
78
|
+
export declare const turnWords: (model: Pick<TurnModel, "language">) => (typeof KYOGIJO_STRINGS)["en"]["turn"];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The 3D view as data: the flat drawing that is its floor, what stands on it, and the sizes that put them in one scene. `buildTurn` works it all out from the
|
|
2
|
+
// drawing's own options, in Node or in a page; `turn.markup.ts` writes it as markup and `turn.dom.ts` makes it turn.
|
|
3
|
+
import { describe, drawKyogijo } from "./draw.js";
|
|
4
|
+
import { hash } from "./format.js";
|
|
5
|
+
import { KYOGIJO_STRINGS } from "./strings.js";
|
|
6
|
+
import { TURN_DEFAULT, clampView, presetView } from "./turn.camera.js";
|
|
7
|
+
import { describeUprights, planesOf, uprightsFor } from "./uprights.js";
|
|
8
|
+
/** The pixels in one foot of the scene at a zoom of 1: the floor is drawn this big, and scaled down to fit. A larger floor is sharper. */
|
|
9
|
+
export const WORLD_PX = 1400;
|
|
10
|
+
/** The options with what makes the floor of a 3D view: the whole surface, lying wide, at its full size. */
|
|
11
|
+
export function floorOptions(options) {
|
|
12
|
+
const rest = { ...options, orientation: "landscape" };
|
|
13
|
+
delete rest.range;
|
|
14
|
+
delete rest.half;
|
|
15
|
+
delete rest.aspect;
|
|
16
|
+
return rest;
|
|
17
|
+
}
|
|
18
|
+
const VIEWBOX = /viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"/;
|
|
19
|
+
/** The view a set of settings opens on. */
|
|
20
|
+
export const openingView = (settings, sport) => clampView({ ...(presetView(settings.preset, sport) ?? presetView(TURN_DEFAULT, sport)), ...settings.view });
|
|
21
|
+
/** Work out a 3D view from the options of a drawing. */
|
|
22
|
+
export function buildTurn(options, settings = {}) {
|
|
23
|
+
const asked = floorOptions(options);
|
|
24
|
+
const language = asked.language === "ja" ? "ja" : "en";
|
|
25
|
+
const wanted = settings.uprights;
|
|
26
|
+
const badge = asked.size === "badge";
|
|
27
|
+
const upOptions = wanted === false || wanted === true || wanted === undefined ? {} : wanted;
|
|
28
|
+
const uprights = wanted === false || badge ? { items: [], lift: 0, thin: 0.2 } : uprightsFor(asked, { ...upOptions, score: settings.score ?? upOptions.score });
|
|
29
|
+
// The score hung up as a scoreboard is not on the floor too.
|
|
30
|
+
const boarded = uprights.items.some((item) => item.kind === "scoreboard");
|
|
31
|
+
const o = boarded ? { ...asked, plate: false, size: asked.size === "compact" ? "full" : asked.size } : asked;
|
|
32
|
+
const floor = settings.floor === undefined ? drawKyogijo(o) : settings.floor(o);
|
|
33
|
+
const found = VIEWBOX.exec(floor);
|
|
34
|
+
if (found === null)
|
|
35
|
+
throw new Error("the floor of a 3D view must be an SVG with a viewBox");
|
|
36
|
+
const [vx, vy, vw, vh] = found.slice(1).map(Number);
|
|
37
|
+
const planes = planesOf(uprights);
|
|
38
|
+
const scale = WORLD_PX / Math.max(vw, vh);
|
|
39
|
+
const centre = [vx + vw / 2, vy + vh / 2];
|
|
40
|
+
// The scene's extent: the playing surface (the whole floor where it has no uprights), and whatever stands beyond it (a net behind a goal line). The ground
|
|
41
|
+
// round the surface is not counted, so that the view is fitted to what is played on and the ground runs out of the picture.
|
|
42
|
+
const area = uprights.area ?? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh };
|
|
43
|
+
let [x0, y0, x1, y1] = [area.x0, area.y0, area.x1, area.y1];
|
|
44
|
+
let top = uprights.lift;
|
|
45
|
+
for (const p of planes) {
|
|
46
|
+
// A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
|
|
47
|
+
const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
|
|
48
|
+
for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
|
|
49
|
+
const point = [p.o[0] + a * p.u[0] + b * p.v[0], p.o[1] + a * p.u[1] + b * p.v[1], p.o[2] + a * p.u[2] + b * p.v[2]];
|
|
50
|
+
if (reach > 0) {
|
|
51
|
+
const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
|
|
52
|
+
x0 = Math.min(x0, middle[0] - reach);
|
|
53
|
+
x1 = Math.max(x1, middle[0] + reach);
|
|
54
|
+
y0 = Math.min(y0, middle[1] - reach);
|
|
55
|
+
y1 = Math.max(y1, middle[1] + reach);
|
|
56
|
+
top = Math.max(top, point[2]);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (p.kind !== "ground") {
|
|
60
|
+
x0 = Math.min(x0, point[0]);
|
|
61
|
+
x1 = Math.max(x1, point[0]);
|
|
62
|
+
y0 = Math.min(y0, point[1]);
|
|
63
|
+
y1 = Math.max(y1, point[1]);
|
|
64
|
+
}
|
|
65
|
+
top = Math.max(top, point[2]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const box = { x0: (x0 - centre[0]) * scale, x1: (x1 - centre[0]) * scale, y0: (y0 - centre[1]) * scale, y1: (y1 - centre[1]) * scale, z0: 0, z1: top * scale };
|
|
69
|
+
const { title, description } = describe(o);
|
|
70
|
+
const standing = describeUprights(o, uprights, language);
|
|
71
|
+
const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.uprights, settings.preset]))}`);
|
|
72
|
+
return {
|
|
73
|
+
id,
|
|
74
|
+
language,
|
|
75
|
+
options: o,
|
|
76
|
+
title,
|
|
77
|
+
description: standing === "" ? description : `${description} ${standing}`,
|
|
78
|
+
standing,
|
|
79
|
+
floor,
|
|
80
|
+
viewBox: { x: vx, y: vy, w: vw, h: vh },
|
|
81
|
+
scale,
|
|
82
|
+
uprights,
|
|
83
|
+
planes,
|
|
84
|
+
box,
|
|
85
|
+
centre,
|
|
86
|
+
view: openingView(settings, o.sport),
|
|
87
|
+
turnable: !badge,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** The words of the 3D view in the model's language. */
|
|
91
|
+
export const turnWords = (model) => KYOGIJO_STRINGS[model.language].turn;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 3D view's stylesheet: the colours of what stands on the surface, in light and dark, and the rules that lay the scene out. Like the drawing's own, the
|
|
3
|
+
* defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
|
|
4
|
+
*/
|
|
5
|
+
export declare const KYOGIJO_TURN_STYLE: string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 3D view's stylesheet: the colours of what stands on the surface, in light and dark, and the rules that lay the scene out. Like the drawing's own, the
|
|
3
|
+
* defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
|
|
4
|
+
*/
|
|
5
|
+
export const KYOGIJO_TURN_STYLE = `
|
|
6
|
+
:where(.ky-turn){--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff}
|
|
7
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320}}
|
|
8
|
+
:where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-wall:#4d6b55;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320}
|
|
9
|
+
.ky-turn{position:relative;display:block;width:100%;color:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);background:var(--ky-turn-bg)}
|
|
10
|
+
.ky-turn-view{position:relative;isolation:isolate;width:100%;aspect-ratio:var(--ky-turn-ratio,16/10);min-height:260px;overflow:hidden;touch-action:pan-y;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:12px;outline-offset:2px}
|
|
11
|
+
.ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
|
|
12
|
+
.ky-turn[data-dragging] .ky-turn-view{cursor:grabbing}
|
|
13
|
+
.ky-turn-view:focus-visible{outline:3px solid var(--ky-turn-ink)}
|
|
14
|
+
.ky-turn-world{position:absolute;left:50%;top:50%;width:0;height:0}
|
|
15
|
+
.ky-turn-floor,.ky-up{position:absolute;left:0;top:0;transform-origin:0 0;pointer-events:none}
|
|
16
|
+
.ky-turn-floor>svg{display:block;width:100%;height:auto}
|
|
17
|
+
.ky-up>svg{display:block;overflow:visible}
|
|
18
|
+
@media (max-width:560px){.ky-turn-view{--ky-turn-ratio:5/4}.ky-turn-controls button{padding:0 10px;font-size:.85rem}}
|
|
19
|
+
.ky-turn .u-sbframe{fill:var(--ky-up-sbframe);stroke:var(--ky-up-sbedge);stroke-width:.25}
|
|
20
|
+
.ky-turn .u-sbline{stroke:var(--ky-up-sbline)}
|
|
21
|
+
.ky-turn .u-pole{fill:var(--ky-up-pole)}
|
|
22
|
+
.ky-turn .u-white{fill:var(--ky-up-white)}
|
|
23
|
+
.ky-turn .u-yellow{fill:var(--ky-up-yellow)}
|
|
24
|
+
.ky-turn .u-red{fill:var(--ky-up-red)}
|
|
25
|
+
.ky-turn .u-orange{fill:var(--ky-up-orange)}
|
|
26
|
+
.ky-turn .u-wood{fill:var(--ky-up-wood)}
|
|
27
|
+
.ky-turn .u-leg{fill:var(--ky-up-leg)}
|
|
28
|
+
.ky-turn .u-pad{fill:var(--ky-up-pad)}
|
|
29
|
+
.ky-turn .u-table{fill:var(--ky-up-table)}
|
|
30
|
+
.ky-turn .u-foot{fill:var(--ky-up-foot)}
|
|
31
|
+
.ky-turn .u-ground{fill:var(--ky-up-ground)}
|
|
32
|
+
.ky-turn .u-wall{fill:var(--ky-up-wall)}
|
|
33
|
+
.ky-turn .u-cap{fill:var(--ky-up-cap)}
|
|
34
|
+
.ky-turn .u-board{fill:var(--ky-up-board)}
|
|
35
|
+
.ky-turn .u-glass{fill:var(--ky-up-glass)}
|
|
36
|
+
.ky-turn .u-backboard{fill:var(--ky-up-backboard)}
|
|
37
|
+
.ky-turn .u-outline{stroke:var(--ky-up-outline)}
|
|
38
|
+
.ky-turn .u-rimline{stroke:var(--ky-up-rim)}
|
|
39
|
+
.ky-turn .u-netfill{fill:var(--ky-up-netfill)}
|
|
40
|
+
.ky-turn .u-mesh{stroke:var(--ky-up-mesh);stroke-linecap:round}
|
|
41
|
+
.ky-turn .u-tape{fill:var(--ky-up-tape)}
|
|
42
|
+
.ky-turn .u-tapeline{stroke:var(--ky-up-tape)}
|
|
43
|
+
.ky-turn .u-flag{fill:var(--ky-up-orange)}
|
|
44
|
+
.ky-turn .u-frame-white{stroke:var(--ky-up-white)}
|
|
45
|
+
.ky-turn .u-frame-red{stroke:var(--ky-up-red)}
|
|
46
|
+
.ky-turn-controls{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
|
|
47
|
+
.ky-turn-group{display:flex;flex-wrap:wrap;gap:6px}
|
|
48
|
+
.ky-turn-controls button{font:inherit;font-size:.9rem;font-weight:600;min-width:44px;min-height:44px;padding:0 12px;border-radius:999px;border:1px solid var(--ky-turn-rule);background:var(--ky-turn-chip);color:var(--ky-turn-ink);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
|
|
49
|
+
.ky-turn-controls button[aria-pressed=true]{background:var(--ky-turn-chip-on);color:var(--ky-turn-chip-on-ink);border-color:var(--ky-turn-chip-on)}
|
|
50
|
+
.ky-turn-controls button[data-act]:not([data-act=reset]){font-size:1.2rem}
|
|
51
|
+
.ky-turn-controls button:focus-visible{outline:3px solid var(--ky-turn-ink);outline-offset:2px}
|
|
52
|
+
.ky-turn-controls button:disabled{opacity:.4;cursor:default}
|
|
53
|
+
.ky-turn-help,.ky-turn-live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
|
|
54
|
+
.ky-turn-flat{margin:8px 0 0;font-size:.85rem}
|
|
55
|
+
`.trim();
|