@johnmorrisdotca/kyogijo-stadiums 0.16.0 → 0.17.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 +21 -0
- package/README.md +45 -22
- package/dist/judging-entry.d.ts +12 -0
- package/dist/judging-entry.js +8 -0
- package/dist/judging.card.d.ts +21 -0
- package/dist/judging.card.js +34 -0
- package/dist/judging.d.ts +55 -0
- package/dist/judging.data.d.ts +14 -0
- package/dist/judging.data.js +24 -0
- package/dist/judging.draw.d.ts +66 -0
- package/dist/judging.draw.js +288 -0
- package/dist/judging.fit.d.ts +51 -0
- package/dist/judging.fit.js +95 -0
- package/dist/judging.js +135 -0
- package/dist/judging.style.d.ts +8 -0
- package/dist/judging.style.js +30 -0
- package/dist/judging.turn.d.ts +35 -0
- package/dist/judging.turn.js +152 -0
- package/dist/judging.types.d.ts +72 -0
- package/dist/judging.types.js +1 -0
- package/dist/judging.words.d.ts +45 -0
- package/dist/judging.words.js +40 -0
- package/dist/multi.draw.js +55 -8
- package/dist/multi.style.js +4 -0
- package/dist/multi.turn.js +1 -9
- package/dist/multi.types.d.ts +12 -0
- package/dist/multi.words.d.ts +7 -0
- package/dist/multi.words.js +4 -0
- package/dist/strings.d.ts +1 -1
- package/dist/strings.js +2 -2
- package/dist/turn.kit.d.ts +13 -2
- package/dist/turn.kit.js +9 -0
- package/dist/turn.markup.js +1 -1
- package/dist/turn.model.d.ts +2 -0
- package/dist/turn.style.js +15 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// A judging assignment in 3D: the 3D view of `turn.ts` over the package's own judo floor (`drawKyogijo`, with as many areas side by side as there are mats), with each judge's chair and the judges' table stood up, a name
|
|
2
|
+
// card on the table in front of every judge who has a name (turned to face the eye, so that it is read from wherever the view is) and the name of each mat on a sign over its judges' table. Nothing is drawn a second
|
|
3
|
+
// way: the card is the plan's card (`judging.card.ts`), fitted by the same rule. The model it makes is a `TurnModel`, so the camera, the controls, the pointer, the auto-turn and the tests of every other 3D view are this one's.
|
|
4
|
+
import { budoPlan } from "./budo/plan.js";
|
|
5
|
+
import { drawKyogijo } from "./draw.js";
|
|
6
|
+
import { hash } from "./format.js";
|
|
7
|
+
import { FEET_PER_METRE as FT } from "./geometry.js";
|
|
8
|
+
import { TURN_CARD, badge, card } from "./judging.card.js";
|
|
9
|
+
import { JUDGING_LEAGUE } from "./judging.data.js";
|
|
10
|
+
import { fitCard, fitSeats } from "./judging.fit.js";
|
|
11
|
+
import { benchOf, judgingSetting } from "./judging.js";
|
|
12
|
+
import { judgingDrawing, judgingText } from "./judging.draw.js";
|
|
13
|
+
import { JUDGING_WORDS } from "./judging.words.js";
|
|
14
|
+
import { mountTurn } from "./turn.dom.js";
|
|
15
|
+
import { turnMarkupOf } from "./turn.markup.js";
|
|
16
|
+
import { TURN_PRESETS } from "./turn.camera.js";
|
|
17
|
+
import { WORLD_PX, openingView, sceneBox } from "./turn.model.js";
|
|
18
|
+
import { box, n, plane, pole, rect, thinnest } from "./turn.kit.js";
|
|
19
|
+
import { planesOf } from "./uprights.js";
|
|
20
|
+
/** How high things stand, in metres (ours: no rule gives them): the table's top, a chair's seat, a name card on the table, the sign over it. */
|
|
21
|
+
export const JUDGING_HEIGHTS = Object.freeze({ table: 0.75, chair: 0.45, card: 0.95, sign: 0.7, signAbove: 0.25 });
|
|
22
|
+
/** The lens of a view close up on several benches at once: the scene turns about the middle of them, and a bench several metres from it would swell as it swung toward the eye, so the lens is long (the default is 8). */
|
|
23
|
+
const BENCHES_LENS = 60;
|
|
24
|
+
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
25
|
+
/**
|
|
26
|
+
* The four viewpoints of a judging view. A card stands upright, so a high eye squashes its type: the broadcast view is from the mat side, so that the judges' table is at the far side and the cards face the eye, at a
|
|
27
|
+
* pitch where the type is still two thirds as tall as it is wide; the others are as every surface's but for the pitch of the corner and the end.
|
|
28
|
+
*/
|
|
29
|
+
export const JUDGING_PRESETS = { overhead: TURN_PRESETS.overhead, broadcast: { yaw: 0, pitch: 42, zoom: 1 }, end: { yaw: -90, pitch: 50, zoom: 1 }, corner: { yaw: -35, pitch: 48, zoom: 1 } };
|
|
30
|
+
/**
|
|
31
|
+
* A judging assignment as a 3D model: the judo floor with every mat's judges' chairs and table, a name card on the table at each seat that has a name and fits one, a number badge at the others, and the mat's name on a sign.
|
|
32
|
+
* A `TurnModel` like any other: `turnMarkupOf` and `mountJudgingTurn` make a view of it. `mat` in the options builds one mat alone (a tab); without it the mats stand side by side.
|
|
33
|
+
*/
|
|
34
|
+
export function buildJudgingTurn(options, settings = {}) {
|
|
35
|
+
const setting = judgingSetting(options);
|
|
36
|
+
const language = setting.language;
|
|
37
|
+
const words = JUDGING_WORDS[language];
|
|
38
|
+
const league = JUDGING_LEAGUE[setting.style].league;
|
|
39
|
+
const count = setting.mats.length;
|
|
40
|
+
const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.preset]))}`);
|
|
41
|
+
const floorOptions = { sport: "judo", league, budo: { style: setting.style, officials: true, areas: count }, language, margin: 2, id: `${id}-floor`, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.css === "none" ? { style: "none" } : {}) };
|
|
42
|
+
const floor = drawKyogijo({ ...floorOptions, orientation: "landscape" });
|
|
43
|
+
const found = /viewBox="(-?[\d.]+) (-?[\d.]+) ([\d.]+) ([\d.]+)"/.exec(floor);
|
|
44
|
+
if (found === null)
|
|
45
|
+
throw new Error("the floor of a 3D view must be an SVG with a viewBox");
|
|
46
|
+
const [vx, vy, vw, vh] = found.slice(1).map(Number);
|
|
47
|
+
const plan = budoPlan({ sport: "judo", league, budo: { style: setting.style, officials: true, areas: count } });
|
|
48
|
+
const judges = plan.judo.judges;
|
|
49
|
+
const seatsPerMat = setting.mats[0].seats.length;
|
|
50
|
+
// The plan's own frame is metres from the top left of the tatami, and the drawing's is feet from the same corner.
|
|
51
|
+
const ft = (m) => m * FT;
|
|
52
|
+
const H = JUDGING_HEIGHTS;
|
|
53
|
+
const items = [];
|
|
54
|
+
const tableTop = ft(H.table);
|
|
55
|
+
const pitch = judges.seats.length > 1 ? judges.seats[1].x0 - judges.seats[0].x0 : 1.25;
|
|
56
|
+
const cardW = ft(pitch - 0.06);
|
|
57
|
+
const cardH = ft(H.card);
|
|
58
|
+
const pad = Math.max(0.08, cardW * 0.05);
|
|
59
|
+
const r = clamp(cardW * 0.2, 0.2, 0.45);
|
|
60
|
+
const innerW = cardW - 2 * pad;
|
|
61
|
+
const innerH = cardH - 2 * pad - r;
|
|
62
|
+
const minName = 0.62;
|
|
63
|
+
setting.mats.forEach((mat, mi) => {
|
|
64
|
+
const table = judges.tables[mi];
|
|
65
|
+
const fitted = fitSeats(mat.seats, { width: innerW, height: innerH, max: Math.min(cardW * 0.3, 0.75), min: minName, idLabel: words.chart.idLabel });
|
|
66
|
+
const furniture = [];
|
|
67
|
+
furniture.push(...box(`m${mat.n}-table`, "desk", rect(ft(table.x0), ft(table.y0), ft(table.x1 - table.x0), ft(table.y1 - table.y0)), tableTop, "u-bench"));
|
|
68
|
+
const cards = [];
|
|
69
|
+
mat.seats.forEach((a, si) => {
|
|
70
|
+
const chair = judges.seats[mi * seatsPerMat + si];
|
|
71
|
+
furniture.push(...box(`m${mat.n}-chair-${a.seat.n}`, "chair", rect(ft(chair.x0), ft(chair.y0), ft(chair.x1 - chair.x0), ft(chair.y1 - chair.y0)), ft(H.chair), "u-bench"));
|
|
72
|
+
const cx = ft((chair.x0 + chair.x1) / 2);
|
|
73
|
+
const cy = ft((table.y0 + table.y1) / 2);
|
|
74
|
+
const fit = fitted[si];
|
|
75
|
+
if (fit !== undefined) {
|
|
76
|
+
const p = plane(`m${mat.n}-card-${a.seat.n}`, "card", [cx - cardW / 2, cy, tableTop + cardH], [cardW, 0, 0], [0, 0, -cardH], card(TURN_CARD, fit, a.seat.n, 0, 0, cardW, cardH, pad, r, 0.1));
|
|
77
|
+
cards.push({ ...p, billboard: true });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Nobody in the seat, or a name that does not fit a card: the seat's number alone, on a small badge, with the name in the chart.
|
|
81
|
+
const br = 0.55;
|
|
82
|
+
const p = plane(`m${mat.n}-badge-${a.seat.n}`, "card", [cx - br, cy, tableTop + 2 * br], [2 * br, 0, 0], [0, 0, -2 * br], badge(TURN_CARD, br, br, br * 0.92, a.seat.n, a.assigned));
|
|
83
|
+
cards.push({ ...p, billboard: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// The mat's name on a sign over the whole bench, on a pole at each end of the table.
|
|
87
|
+
const midX = ft((table.x0 + table.x1) / 2);
|
|
88
|
+
const midY = ft((table.y0 + table.y1) / 2);
|
|
89
|
+
const signW = ft(pitch * seatsPerMat);
|
|
90
|
+
const signH = ft(H.sign);
|
|
91
|
+
const signBottom = tableTop + cardH + ft(H.signAbove);
|
|
92
|
+
const text = fitCard({ name: mat.label, id: "", idLabel: "", width: signW * 0.9, height: signH * 0.86, max: signH * 0.62, min: signH * 0.3, maxLines: 2, breakWords: true });
|
|
93
|
+
const lines = text?.name ?? [mat.label];
|
|
94
|
+
const size = text?.nameSize ?? signH * 0.3;
|
|
95
|
+
const label = lines.map((line, i) => `<text class="u-sign-text" x="${n(signW / 2)}" y="${n(signH / 2 + (i - (lines.length - 1) / 2) * size * 1.1 + size * 0.34)}" font-size="${n(size)}">${line.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}</text>`).join("");
|
|
96
|
+
const sign = { ...plane(`m${mat.n}-sign`, "sign", [midX - signW / 2, midY, signBottom + signH], [signW, 0, 0], [0, 0, -signH], `<rect class="u-sign" width="${n(signW)}" height="${n(signH)}" rx="${n(signH * 0.12)}"/>${label}`), billboard: true };
|
|
97
|
+
const poles = [table.x0, table.x1].flatMap((px, k) => pole(`m${mat.n}-sign-pole-${k + 1}`, "pole", ft(px), midY, tableTop, signBottom + signH * 0.5, ft(0.06), "u-pole"));
|
|
98
|
+
items.push({ id: `m${mat.n}-furniture`, kind: "officials", at: [midX, midY], height: tableTop, planes: furniture });
|
|
99
|
+
items.push({ id: `m${mat.n}-judges`, kind: "judges", at: [midX, midY], height: tableTop + cardH, planes: [...cards, ...poles, sign] });
|
|
100
|
+
});
|
|
101
|
+
// The view is fitted to the whole floor, or to the judges' bench close up (the scene then turns about the bench, so that the cards stay where they are read).
|
|
102
|
+
const near = setting.focus === "bench" ? benchOf(judges.seats.slice(0, count * seatsPerMat), judges.tables[0], pitch, plan.judo.contests[0].y0, 2.2) : undefined;
|
|
103
|
+
const area = near === undefined ? { x0: vx, y0: vy, x1: vx + vw, y1: vy + vh } : { x0: ft(near.x0), y0: ft(near.y0), x1: ft(near.x1), y1: ft(near.y1) };
|
|
104
|
+
const uprights = { items, lift: 0, thin: thinnest(vw, vh), area, whole: true };
|
|
105
|
+
const planes = planesOf(uprights);
|
|
106
|
+
const scale = WORLD_PX / Math.max(vw, vh);
|
|
107
|
+
const centre = [(area.x0 + area.x1) / 2, (area.y0 + area.y1) / 2];
|
|
108
|
+
const look = /^<svg[^>]* data-look="([^"]*)"/.exec(floor)?.[1];
|
|
109
|
+
const { title, description } = judgingText(setting, options, true);
|
|
110
|
+
const surfaceLike = { sport: "judo", language, ...(options.theme === undefined ? {} : { theme: options.theme }), ...(options.css === "none" ? { style: "none" } : {}) };
|
|
111
|
+
return {
|
|
112
|
+
id,
|
|
113
|
+
language,
|
|
114
|
+
options: surfaceLike,
|
|
115
|
+
title,
|
|
116
|
+
description: `${description}${language === "ja" ? "" : " "}${words.turn.standing}`,
|
|
117
|
+
standing: words.turn.standing,
|
|
118
|
+
floor,
|
|
119
|
+
viewBox: { x: vx, y: vy, w: vw, h: vh },
|
|
120
|
+
scale,
|
|
121
|
+
uprights,
|
|
122
|
+
defs: "",
|
|
123
|
+
crowdStyle: "",
|
|
124
|
+
venue: false,
|
|
125
|
+
look: { sport: "judo", look: look ?? "yellow-red" },
|
|
126
|
+
planes,
|
|
127
|
+
box: sceneBox(planes, uprights.area, 0, centre, scale),
|
|
128
|
+
centre,
|
|
129
|
+
view: openingView({ preset: "broadcast", ...settings }, JUDGING_PRESETS),
|
|
130
|
+
turnable: true,
|
|
131
|
+
presets: JUDGING_PRESETS,
|
|
132
|
+
...(near !== undefined && count > 1 ? { lens: BENCHES_LENS } : {}),
|
|
133
|
+
flat: judgingDrawing(options).svg,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Mount a judging assignment as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the judo floor with each mat's judges'
|
|
138
|
+
* table, the name cards on it and the mat's name over it. Where the browser cannot turn it, the flat picture with its chart is shown.
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts no-run
|
|
141
|
+
* import { mountJudgingTurn } from "@johnmorrisdotca/kyogijo-stadiums/judging";
|
|
142
|
+
* const view = mountJudgingTurn(document.querySelector("#mat")!, { style: "kata", mats: [{ officials: [{ seat: 1, name: "Aiko Mori", id: "J-1042" }] }] });
|
|
143
|
+
* view.set({ yaw: 40 });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export function mountJudgingTurn(host, options, settings = {}) {
|
|
147
|
+
return mountTurn(host, options, settings, buildJudgingTurn);
|
|
148
|
+
}
|
|
149
|
+
/** A judging assignment as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it. */
|
|
150
|
+
export function judgingTurnMarkup(options, settings = {}) {
|
|
151
|
+
return turnMarkupOf(buildJudgingTurn(options, settings), settings);
|
|
152
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Language } from "./types.ts";
|
|
2
|
+
/** The style a judo area is judged in. `kata` has five judges; `shiai`, the contest, is the next release. */
|
|
3
|
+
export type JudgingStyle = "kata";
|
|
4
|
+
/** One official: the seat, and who sits in it. The seat is its number (`3`, `"3"`), its key (`"judge-3"`) or `"central"` (the middle seat of a kata area). */
|
|
5
|
+
export interface JudgingOfficial {
|
|
6
|
+
seat: string | number;
|
|
7
|
+
/** The name as it is to be read, at most 120 characters; a seat with no name is shown as not assigned. */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** The official's identifier (a licence number, a registry id), shown under the name. At most 64 characters. */
|
|
10
|
+
id?: string | number;
|
|
11
|
+
}
|
|
12
|
+
/** One mat area: its name and its officials. */
|
|
13
|
+
export interface JudgingMat {
|
|
14
|
+
/** The name on the plan: `Mat 1`, `Mat 2` in English and `第1試合場`, `第2試合場` in Japanese when missing. At most 40 characters. */
|
|
15
|
+
label?: string;
|
|
16
|
+
/** Who sits where. A seat that is not named is shown as not assigned. */
|
|
17
|
+
officials?: readonly JudgingOfficial[];
|
|
18
|
+
}
|
|
19
|
+
/** A judging assignment as the drawing asks for it. */
|
|
20
|
+
export interface JudgingOptions {
|
|
21
|
+
/** The style the areas are judged in. */
|
|
22
|
+
style: JudgingStyle;
|
|
23
|
+
/** The mat areas, 1 to 6, in the order they are numbered. */
|
|
24
|
+
mats: readonly JudgingMat[];
|
|
25
|
+
/** Draw only this mat (counting from 1): one tab of a page that has a tab for each. All of them, stacked, when missing. */
|
|
26
|
+
mat?: number;
|
|
27
|
+
/** The chart of mat, seat, name and ID. `auto` (the default) draws it whenever a name or an ID does not fit in its seat, `always` and `never` as they say. */
|
|
28
|
+
chart?: "auto" | "always" | "never";
|
|
29
|
+
/** Where the chart is laid: beside the plan (`landscape`, the default) or under it (`portrait`). */
|
|
30
|
+
orientation?: "landscape" | "portrait";
|
|
31
|
+
/** What the plan shows: the whole `mat` (the default) or the judges' `bench`, close up, so that the names in the seats are as large as they can be. */
|
|
32
|
+
focus?: "mat" | "bench";
|
|
33
|
+
/** The width of the picture's own units, 480 to 2000 (1000 by default); the picture scales to whatever it is shown in, so this only sets how large its type is against its width. */
|
|
34
|
+
width?: number;
|
|
35
|
+
language?: Language;
|
|
36
|
+
theme?: "light" | "dark";
|
|
37
|
+
/** A prefix for the ids. Made from the options when missing. */
|
|
38
|
+
id?: string;
|
|
39
|
+
className?: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
/** `none` leaves the stylesheet to the page (`KYOGIJO_STYLE` and `KYOGIJO_JUDGING_STYLE`). */
|
|
43
|
+
css?: "embed" | "none";
|
|
44
|
+
}
|
|
45
|
+
/** A seat of a style: where it is in the order, and what it is called. */
|
|
46
|
+
export interface JudgingSeat {
|
|
47
|
+
/** The key: `judge-3`. */
|
|
48
|
+
id: string;
|
|
49
|
+
/** The number, from 1, in the order the seats are drawn (left to right as the plan lies, the shomen at the top). */
|
|
50
|
+
n: number;
|
|
51
|
+
/** `central` for the middle seat of a kata area. */
|
|
52
|
+
role: "judge" | "central";
|
|
53
|
+
label: {
|
|
54
|
+
en: string;
|
|
55
|
+
ja: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** One seat of one mat with who sits in it. */
|
|
59
|
+
export interface JudgingAssignment {
|
|
60
|
+
seat: JudgingSeat;
|
|
61
|
+
/** Empty when nobody is named. */
|
|
62
|
+
name: string;
|
|
63
|
+
id: string;
|
|
64
|
+
assigned: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** One mat with every seat of the style, in order. */
|
|
67
|
+
export interface JudgingResolved {
|
|
68
|
+
/** From 1. */
|
|
69
|
+
n: number;
|
|
70
|
+
label: string;
|
|
71
|
+
seats: JudgingAssignment[];
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Language } from "./types.ts";
|
|
2
|
+
export interface JudgingWords {
|
|
3
|
+
/** The name of a mat when the caller gives none, `{n}` its number. */
|
|
4
|
+
mat: string;
|
|
5
|
+
/** What a style is called on the plan, `{count}` the number of seats. */
|
|
6
|
+
styleName: {
|
|
7
|
+
kata: string;
|
|
8
|
+
};
|
|
9
|
+
/** The chart: its heading, its columns, the word before an ID and what a seat with nobody in it says. */
|
|
10
|
+
chart: {
|
|
11
|
+
heading: string;
|
|
12
|
+
seat: string;
|
|
13
|
+
name: string;
|
|
14
|
+
id: string;
|
|
15
|
+
idLabel: string;
|
|
16
|
+
vacant: string;
|
|
17
|
+
};
|
|
18
|
+
/** The sentences of the title and the description. */
|
|
19
|
+
say: {
|
|
20
|
+
title: string;
|
|
21
|
+
matsOne: string;
|
|
22
|
+
matsMany: string;
|
|
23
|
+
mat: string;
|
|
24
|
+
seat: string;
|
|
25
|
+
seatNoId: string;
|
|
26
|
+
seatVacant: string;
|
|
27
|
+
names: string;
|
|
28
|
+
chartOnly: string;
|
|
29
|
+
join: string;
|
|
30
|
+
};
|
|
31
|
+
/** The table that says the same as the picture. */
|
|
32
|
+
table: {
|
|
33
|
+
caption: string;
|
|
34
|
+
mat: string;
|
|
35
|
+
seat: string;
|
|
36
|
+
name: string;
|
|
37
|
+
id: string;
|
|
38
|
+
};
|
|
39
|
+
/** What the 3D view says of the seats' name cards. */
|
|
40
|
+
turn: {
|
|
41
|
+
standing: string;
|
|
42
|
+
board: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export declare const JUDGING_WORDS: Record<Language, JudgingWords>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const JUDGING_WORDS = {
|
|
2
|
+
en: {
|
|
3
|
+
mat: "Mat {n}",
|
|
4
|
+
styleName: { kata: "Kata, {count} judges" },
|
|
5
|
+
chart: { heading: "Judging assignments", seat: "Seat", name: "Name", id: "ID", idLabel: "ID", vacant: "Not assigned" },
|
|
6
|
+
say: {
|
|
7
|
+
title: "Judging assignments: {style}, {mats}",
|
|
8
|
+
matsOne: "1 mat",
|
|
9
|
+
matsMany: "{n} mats",
|
|
10
|
+
mat: "{label}: {seats}.",
|
|
11
|
+
seat: "{seat} {name}, ID {id}",
|
|
12
|
+
seatNoId: "{seat} {name}",
|
|
13
|
+
seatVacant: "{seat} not assigned",
|
|
14
|
+
names: "The names are in the seats where they fit, and in the chart of mat, seat, name and ID.",
|
|
15
|
+
chartOnly: "The names are in the chart of mat, seat, name and ID.",
|
|
16
|
+
join: "; ",
|
|
17
|
+
},
|
|
18
|
+
table: { caption: "{title}", mat: "Mat", seat: "Seat", name: "Name", id: "ID" },
|
|
19
|
+
turn: { standing: "A name card stands at each judge's seat, turned to face the eye, and the name of each mat stands over its judges' table.", board: "Mat" },
|
|
20
|
+
},
|
|
21
|
+
ja: {
|
|
22
|
+
mat: "第{n}試合場",
|
|
23
|
+
styleName: { kata: "形({count}人制)" },
|
|
24
|
+
chart: { heading: "審判員の配置", seat: "席", name: "氏名", id: "ID", idLabel: "ID", vacant: "未定" },
|
|
25
|
+
say: {
|
|
26
|
+
title: "審判員の配置:{style}、{mats}",
|
|
27
|
+
matsOne: "試合場1面",
|
|
28
|
+
matsMany: "試合場{n}面",
|
|
29
|
+
mat: "{label}:{seats}。",
|
|
30
|
+
seat: "{seat}は{name}(ID {id})",
|
|
31
|
+
seatNoId: "{seat}は{name}",
|
|
32
|
+
seatVacant: "{seat}は未定",
|
|
33
|
+
names: "氏名は、席に収まる場合は各席の中に表示し、試合場・席・氏名・IDの一覧表にも載せています。",
|
|
34
|
+
chartOnly: "氏名は、試合場・席・氏名・IDの一覧表に載せています。",
|
|
35
|
+
join: "、",
|
|
36
|
+
},
|
|
37
|
+
table: { caption: "{title}", mat: "試合場", seat: "席", name: "氏名", id: "ID" },
|
|
38
|
+
turn: { standing: "各審判員の席に名札を置き、見ている方向に向くようにしています。各試合場の名前は審判員席の上に掲げています。", board: "試合場" },
|
|
39
|
+
},
|
|
40
|
+
};
|
package/dist/multi.draw.js
CHANGED
|
@@ -27,6 +27,42 @@ function inner(svg) {
|
|
|
27
27
|
body = body.replace(/<rect class="ky-(?:apron|free)(?: [^"]*)?"[^>]*\/>/g, "");
|
|
28
28
|
return { body, style, look };
|
|
29
29
|
}
|
|
30
|
+
/** The invented event of a layout's sample content: the options' own, or the package's (`Kyogijo Open 2026, Osaka`). */
|
|
31
|
+
function sampleOf(o, words) {
|
|
32
|
+
if (o.sample === undefined || o.sample === false)
|
|
33
|
+
return undefined;
|
|
34
|
+
const given = o.sample === true ? {} : o.sample;
|
|
35
|
+
return { event: given.event ?? words.sample.event, year: String(given.year ?? words.sample.year), city: given.city ?? words.sample.city };
|
|
36
|
+
}
|
|
37
|
+
/** The line of a sample event: its name, year and city. */
|
|
38
|
+
const sampleLine = (e) => `${e.event} ${e.year} · ${e.city}`;
|
|
39
|
+
/**
|
|
40
|
+
* A simple emblem of the package's own, in a square of `size` feet centred on (cx, cy): a ring with a star, a diamond or waves, by `kind`. Plain geometry: no real mark, brand, federation or sponsor is drawn, and no one's logo is
|
|
41
|
+
* imitated.
|
|
42
|
+
*/
|
|
43
|
+
function emblem(cx, cy, size, kind) {
|
|
44
|
+
const r = size / 2;
|
|
45
|
+
const ring = el("circle", { class: "ky-mu-emblem", cx: num(cx), cy: num(cy), r: num(r * 0.92) });
|
|
46
|
+
const k = ((kind % 3) + 3) % 3;
|
|
47
|
+
if (k === 0) {
|
|
48
|
+
const pts = Array.from({ length: 10 }, (_, i) => {
|
|
49
|
+
const a = -Math.PI / 2 + (i * Math.PI) / 5;
|
|
50
|
+
const rr = i % 2 === 0 ? r * 0.6 : r * 0.26;
|
|
51
|
+
return `${num(cx + rr * Math.cos(a))} ${num(cy + rr * Math.sin(a))}`;
|
|
52
|
+
});
|
|
53
|
+
return ring + el("path", { class: "ky-mu-emblem-fill", d: `M${pts.join("L")}Z` });
|
|
54
|
+
}
|
|
55
|
+
if (k === 1) {
|
|
56
|
+
const d = r * 0.5;
|
|
57
|
+
return ring + el("path", { class: "ky-mu-emblem-fill", d: `M${num(cx)} ${num(cy - d)}L${num(cx + d)} ${num(cy)}L${num(cx)} ${num(cy + d)}L${num(cx - d)} ${num(cy)}Z` });
|
|
58
|
+
}
|
|
59
|
+
let waves = "";
|
|
60
|
+
for (const dy of [-0.28, 0, 0.28]) {
|
|
61
|
+
const y = cy + r * dy;
|
|
62
|
+
waves += `M${num(cx - r * 0.55)} ${num(y)}Q${num(cx - r * 0.275)} ${num(y - r * 0.2)} ${num(cx)} ${num(y)}T${num(cx + r * 0.55)} ${num(y)}`;
|
|
63
|
+
}
|
|
64
|
+
return ring + el("path", { class: "ky-mu-emblem", d: waves, fill: "none" });
|
|
65
|
+
}
|
|
30
66
|
/** The text of an empty place, sized to fit it. */
|
|
31
67
|
function slotText(slot, words) {
|
|
32
68
|
return slot.kind === "floor-text" ? words.slots.floorText : slot.kind === "floor-logo" ? words.slots.floorLogo : words.slots.board;
|
|
@@ -164,13 +200,21 @@ export function multiDrawing(options, raise, uprights = true) {
|
|
|
164
200
|
}
|
|
165
201
|
let slotsMarkup = "";
|
|
166
202
|
let slotTexts = "";
|
|
203
|
+
const sample = sampleOf(o, words);
|
|
167
204
|
if (withSlots) {
|
|
168
|
-
|
|
205
|
+
layout.slots.forEach((slot, index) => {
|
|
169
206
|
const board = slot.kind === "board";
|
|
170
|
-
|
|
171
|
-
if (board)
|
|
207
|
+
// With sample content a floor's places carry an invented event's words and emblems and are not drawn dashed; the boards keep their dark panels.
|
|
208
|
+
if (sample === undefined || board)
|
|
209
|
+
slotsMarkup += el("rect", { class: board ? "ky-mu-board" : "ky-mu-slot", x: num(slot.x), y: num(slot.y), width: num(slot.w), height: num(slot.h), "data-slot": slot.id });
|
|
210
|
+
if (board && sample === undefined)
|
|
172
211
|
slotsMarkup += el("rect", { class: "ky-mu-slot ky-mu-slot-board", x: num(slot.x + 0.3), y: num(slot.y + 0.3), width: num(slot.w - 0.6), height: num(slot.h - 0.6) });
|
|
173
|
-
|
|
212
|
+
if (sample !== undefined && slot.kind === "floor-logo") {
|
|
213
|
+
const [ex, ey] = to(slot.x + slot.w / 2, slot.y + slot.h / 2);
|
|
214
|
+
slotsMarkup += el("g", { class: "ky-mu-emblems", "data-slot": slot.id }, emblem(ex, ey, Math.min(slot.w, slot.h) * 0.9, index));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const text = sample === undefined ? slotText(slot, words) : board ? sampleLine(sample).toUpperCase() : sampleLine(sample);
|
|
174
218
|
const horizontal = slot.w >= slot.h;
|
|
175
219
|
const long = horizontal ? slot.w : slot.h;
|
|
176
220
|
const short = horizontal ? slot.h : slot.w;
|
|
@@ -178,8 +222,9 @@ export function multiDrawing(options, raise, uprights = true) {
|
|
|
178
222
|
const [tx, ty] = to(slot.x + slot.w / 2, slot.y + slot.h / 2);
|
|
179
223
|
// The words of a slot on a long side are laid along it; in a turned layout they turn back to read from the left.
|
|
180
224
|
const rot = (horizontal ? 0 : -90) + 0;
|
|
181
|
-
|
|
182
|
-
|
|
225
|
+
const cls = sample === undefined ? "ky-mu-slot-text" : "ky-mu-sample-text";
|
|
226
|
+
slotTexts += el("text", { class: `${cls}${board ? ` ${cls}-board` : ""}`, x: num(tx), y: num(ty + size * 0.34), "font-size": num(size), transform: rot === 0 ? undefined : `rotate(${rot} ${num(tx)} ${num(ty)})` }, esc(text));
|
|
227
|
+
});
|
|
183
228
|
}
|
|
184
229
|
let labelsMarkup = "";
|
|
185
230
|
if (withLabels) {
|
|
@@ -260,8 +305,10 @@ export function multiDrawing(options, raise, uprights = true) {
|
|
|
260
305
|
sentences.push(fill(words.say.stagger, { share: `${Math.round(spec.stagger * 100)}%` }));
|
|
261
306
|
if (layout.shared !== undefined)
|
|
262
307
|
sentences.push(words.say.shared);
|
|
263
|
-
if (withSlots && layout.slots.some((s) => s.kind !== "board"))
|
|
264
|
-
|
|
308
|
+
if (withSlots && layout.slots.some((s) => s.kind !== "board")) {
|
|
309
|
+
const filled = sampleOf(o, words);
|
|
310
|
+
sentences.push(filled === undefined ? fill(words.say.slots, { n: layout.slots.filter((s) => s.kind !== "board").length }) : fill(words.say.sample, filled));
|
|
311
|
+
}
|
|
265
312
|
if (spec.event !== undefined) {
|
|
266
313
|
const named = language === "ja" ? spec.event.ja : undefined;
|
|
267
314
|
sentences.push(fill(words.say.event, { event: named?.name ?? spec.event.name, year: spec.event.year, venue: named?.venue ?? spec.event.venue, city: named?.city ?? spec.event.city }));
|
package/dist/multi.style.js
CHANGED
|
@@ -40,6 +40,10 @@ ${Object.keys(FLOORS).map((name) => `.kyogijo .ky-mu-hf-${name}{fill:var(--ky-mu
|
|
|
40
40
|
.kyogijo .ky-mu-slot-board{stroke:var(--ky-mu-board-ink)}
|
|
41
41
|
.kyogijo .ky-mu-slot-text{fill:var(--ky-mu-slot);fill-opacity:.8;font-family:var(--ky-font);font-weight:700;text-anchor:middle}
|
|
42
42
|
.kyogijo .ky-mu-slot-text-board{fill:var(--ky-mu-board-ink)}
|
|
43
|
+
.kyogijo .ky-mu-sample-text{fill:var(--ky-mu-slot);font-family:var(--ky-font);font-weight:700;text-anchor:middle}
|
|
44
|
+
.kyogijo .ky-mu-sample-text-board{fill:var(--ky-mu-board-ink)}
|
|
45
|
+
.kyogijo .ky-mu-emblem{fill:none;stroke:var(--ky-mu-slot);stroke-width:.35;stroke-linejoin:round;stroke-linecap:round}
|
|
46
|
+
.kyogijo .ky-mu-emblem-fill{fill:var(--ky-mu-slot);stroke:none}
|
|
43
47
|
.kyogijo .ky-mu-label{fill:var(--ky-ink);font-family:var(--ky-font);font-weight:800;paint-order:stroke;stroke:var(--ky-halo);stroke-width:.5;stroke-linejoin:round}
|
|
44
48
|
.kyogijo .ky-mu-caption{fill:var(--ky-mu-sb-ink);font-family:var(--ky-font);font-weight:700;text-anchor:middle}
|
|
45
49
|
.kyogijo.ky-multi{--ky-cr-home:#8b94a3;--ky-cr-away:#8b94a3}
|
package/dist/multi.turn.js
CHANGED
|
@@ -7,7 +7,7 @@ import { multiDrawing } from "./multi.draw.js";
|
|
|
7
7
|
import { outlineOf } from "./outline.js";
|
|
8
8
|
import { hash } from "./format.js";
|
|
9
9
|
import { TURN_PRESETS, NET_PRESETS, wrap } from "./turn.camera.js";
|
|
10
|
-
import { flat, n, plane, shy, thinnest, wall } from "./turn.kit.js";
|
|
10
|
+
import { box, flat, n, plane, rect, shy, thinnest, wall } from "./turn.kit.js";
|
|
11
11
|
import { mountTurn } from "./turn.dom.js";
|
|
12
12
|
import { turnMarkupOf } from "./turn.markup.js";
|
|
13
13
|
import { openingView, sceneBox } from "./turn.model.js";
|
|
@@ -51,14 +51,6 @@ function place(p, m, id) {
|
|
|
51
51
|
}
|
|
52
52
|
return moved;
|
|
53
53
|
}
|
|
54
|
-
const rect = (x, y, w, h) => ({ x0: x, y0: y, x1: x + w, y1: y + h });
|
|
55
|
-
/** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
|
|
56
|
-
function box(id, kind, r, z1, cls) {
|
|
57
|
-
const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
|
|
58
|
-
const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, z1, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(z1)}" class="${cls}-side"/>`));
|
|
59
|
-
planes.push(flat(`${id}-top`, kind, r.x0, r.y0, r.x1, r.y1, z1, `<rect width="${n(r.x1 - r.x0)}" height="${n(r.y1 - r.y0)}" class="${cls}"/>`));
|
|
60
|
-
return planes;
|
|
61
|
-
}
|
|
62
54
|
/** The surround of one area: four sides over the rectangle, each in pieces of at most `SURROUND_PIECE`, facing out from the area's middle (it gives way to a nearly level eye, as the glass of a rink does, but only then). */
|
|
63
55
|
function surround(id, r) {
|
|
64
56
|
const centre = [(r.x0 + r.x1) / 2, (r.y0 + r.y1) / 2];
|
package/dist/multi.types.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface MultiEvent {
|
|
|
37
37
|
/** Keys of `MULTI_SOURCES`. */
|
|
38
38
|
sources: readonly string[];
|
|
39
39
|
}
|
|
40
|
+
/** The words of a sample event: any part left out is the package's own. */
|
|
41
|
+
export interface MultiSample {
|
|
42
|
+
event?: string;
|
|
43
|
+
year?: string | number;
|
|
44
|
+
city?: string;
|
|
45
|
+
}
|
|
40
46
|
/** What stands on or beside each playing surface, and over the floor. */
|
|
41
47
|
export interface MultiParts {
|
|
42
48
|
/** The barriers (surrounds) that enclose each playing area, drawn round the area's bay. */
|
|
@@ -244,6 +250,12 @@ export interface MultiOptions {
|
|
|
244
250
|
};
|
|
245
251
|
/** The empty places for the event's words and marks, and the boards: on by default; `false` draws neither. */
|
|
246
252
|
slots?: boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Fill the places for the event's words and marks with sample content, to show a layout as an event would dress it: an invented event name, year and city on the floor and the boards, and simple emblems of the package's own
|
|
255
|
+
* (a ring with a star, a diamond or waves) in the logo places, in place of the dashed empty places. `true` for "Kyogijo Open 2026, Osaka" (in Japanese, 競技場杯 2026, 大阪), or the event's name, year and city of
|
|
256
|
+
* your own. It names no real event, brand, federation or sponsor. Off by default: the places are drawn empty, as a layout is configured.
|
|
257
|
+
*/
|
|
258
|
+
sample?: boolean | MultiSample;
|
|
247
259
|
/** The names of the surfaces over them. On by default. */
|
|
248
260
|
labels?: boolean;
|
|
249
261
|
language?: "en" | "ja";
|
package/dist/multi.words.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface MultiWords {
|
|
|
21
21
|
floorLogo: string;
|
|
22
22
|
board: string;
|
|
23
23
|
};
|
|
24
|
+
/** The invented event of the sample content (`sample: true`): its name, its year and its city. */
|
|
25
|
+
sample: {
|
|
26
|
+
event: string;
|
|
27
|
+
year: string;
|
|
28
|
+
city: string;
|
|
29
|
+
};
|
|
24
30
|
/** The sentences of the title and the description. */
|
|
25
31
|
say: {
|
|
26
32
|
title: string;
|
|
@@ -29,6 +35,7 @@ export interface MultiWords {
|
|
|
29
35
|
stagger: string;
|
|
30
36
|
shared: string;
|
|
31
37
|
slots: string;
|
|
38
|
+
sample: string;
|
|
32
39
|
venue: string;
|
|
33
40
|
stadium: string;
|
|
34
41
|
event: string;
|
package/dist/multi.words.js
CHANGED
|
@@ -3,6 +3,7 @@ export const MULTI_WORDS = {
|
|
|
3
3
|
names: { table: "Table {n}", court: "Court {n}", sheet: "Sheet {n}", area: "Area {n}" },
|
|
4
4
|
parts: { chair: "Umpire's chair", table: "Score table", scoreboard: "Scoreboard", shared: "One scoreboard for all, hung over the middle", barrier: "Barrier", board: "Board", floor: "Floor", hall: "Hall floor", stands: "Stands" },
|
|
5
5
|
slots: { floorText: "Event name, year and city", floorLogo: "Logo", board: "Board" },
|
|
6
|
+
sample: { event: "Kyogijo Open", year: "2026", city: "Osaka" },
|
|
6
7
|
say: {
|
|
7
8
|
title: "{name}",
|
|
8
9
|
drawn: "Drawn as: {name}, {surfaces} playing surfaces to scale on one floor of {length} by {width} metres ({area} square metres).",
|
|
@@ -10,6 +11,7 @@ export const MULTI_WORDS = {
|
|
|
10
11
|
stagger: "Every other row is moved along by {share} of a bay.",
|
|
11
12
|
shared: "One scoreboard for all is hung over the middle of the floor.",
|
|
12
13
|
slots: "The empty places for the event's own words and marks are drawn dashed: {n}.",
|
|
14
|
+
sample: "The places for the event's words and marks carry sample content: an invented event, {event}, {year}, {city}, and simple emblems. Nothing real is printed.",
|
|
13
15
|
venue: "Set in {venue}: {fit}",
|
|
14
16
|
stadium: "Drawn inside a stadium to the same scale: walls, stands and crowd.",
|
|
15
17
|
event: "Tied to {event}, {year}, {venue}, {city}.",
|
|
@@ -31,6 +33,7 @@ export const MULTI_WORDS = {
|
|
|
31
33
|
names: { table: "第{n}台", court: "第{n}コート", sheet: "第{n}シート", area: "第{n}試合場" },
|
|
32
34
|
parts: { chair: "審判席", table: "記録台", scoreboard: "スコアボード", shared: "中央の真上に吊るした全面共用のスコアボード", barrier: "フェンス", board: "ボード", floor: "床", hall: "会場の床", stands: "スタンド" },
|
|
33
35
|
slots: { floorText: "大会名・年・開催地", floorLogo: "ロゴ", board: "ボード" },
|
|
36
|
+
sample: { event: "競技場杯", year: "2026", city: "大阪" },
|
|
34
37
|
say: {
|
|
35
38
|
title: "{name}",
|
|
36
39
|
drawn: "{name}。競技面を{surfaces}面、横{length}m×縦{width}m({area}平方メートル)の床1枚の上に縮尺どおりに描いています。",
|
|
@@ -38,6 +41,7 @@ export const MULTI_WORDS = {
|
|
|
38
41
|
stagger: "1行おきに、1区画の{share}だけずらしています。",
|
|
39
42
|
shared: "床の中央の上に、全面共用のスコアボードを1台吊るしています。",
|
|
40
43
|
slots: "大会の文字やマークを入れる空き枠を破線で描いています:{n}か所。",
|
|
44
|
+
sample: "大会名やマークを入れる枠には、見本として架空の大会「{event}」({year}年・{city})と簡単なエンブレムを入れています。実在の大会や団体のものは含まれていません。",
|
|
41
45
|
venue: "{venue}に置いた場合:{fit}",
|
|
42
46
|
stadium: "同じ縮尺で、壁・スタンド・観客を備えたスタジアムの中に描いています。",
|
|
43
47
|
event: "関連する大会:{event}({year}年、{venue}、{city})。",
|
package/dist/strings.d.ts
CHANGED
|
@@ -993,7 +993,7 @@ export interface KyogijoWords {
|
|
|
993
993
|
item: string;
|
|
994
994
|
itemSag: string;
|
|
995
995
|
/** The names of what stands on the surface, by kind. */
|
|
996
|
-
parts: Record<"scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials", string>;
|
|
996
|
+
parts: Record<"scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials" | "judges", string>;
|
|
997
997
|
};
|
|
998
998
|
/** The names of the zones that `zoneAt` answers with (rules.ts), by id. */
|
|
999
999
|
rules: Record<RuleId, string>;
|
package/dist/strings.js
CHANGED
|
@@ -432,7 +432,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
432
432
|
announce: "{name} view", announceAngle: "Turned to {yaw} degrees, seen from {pitch} degrees above the floor.",
|
|
433
433
|
flat: "This browser cannot draw the 3D view, so the flat drawing is shown.",
|
|
434
434
|
standing: "Standing on it, in the 3D view: {list}.", item: "{name} {h}", itemSag: "{name} {h} at the posts, {low} at the middle",
|
|
435
|
-
parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness", surrounds: "surrounds", officials: "umpires' chairs and score tables" },
|
|
435
|
+
parts: { scoreboard: "scoreboard (lower edge)", net: "net", antennae: "antennae", goalposts: "goal posts", netposts: "net posts", hoop: "baskets", goal: "goals", boards: "boards and glass", stumps: "stumps", pylons: "pylons", flags: "corner flags", fence: "fence", backstop: "backstop", "foul-poles": "foul poles", "table-legs": "table top", walls: "walls", "starting-blocks": "starting blocks", "backstroke-flags": "backstroke flags", "false-start-rope": "false start rope", springboards: "springboards", platforms: "platforms", "judges-stand": "judges' stand", hurdles: "hurdles", steeplechase: "steeplechase barriers", "high-jump": "high jump uprights and bar", "pole-vault": "pole vault uprights and bar", cage: "discus and hammer cage", padding: "padding", "rink-boards": "boards", mound: "clay mound", azuchi: "azuchi", targets: "targets", stands: "stands", "wall-seats": "seats on top of the wall", roof: "roof", gallery: "gallery", dais: "dais", banner: "banner", shrine: "shrine", "mat-edge": "mat thickness", surrounds: "surrounds", officials: "umpires' chairs and score tables", judges: "name cards on the judges' table" },
|
|
436
436
|
},
|
|
437
437
|
rules: {
|
|
438
438
|
"defensive-zone": "Defensive zone",
|
|
@@ -968,7 +968,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
968
968
|
announce: "{name}の視点", announceAngle: "向きは{yaw}度、見下ろす角度は{pitch}度です。",
|
|
969
969
|
flat: "このブラウザーでは3D表示ができないため、平面の図を表示しています。",
|
|
970
970
|
standing: "3D表示で立って見えるもの:{list}。", item: "{name} {h}", itemSag: "{name} 支柱の位置で{h}(中央は{low})",
|
|
971
|
-
parts: { scoreboard: "スコアボードの下端", net: "ネット", antennae: "アンテナ", goalposts: "ゴールポスト", netposts: "支柱", hoop: "バスケットゴール", goal: "ゴール", boards: "ボードとガラス", stumps: "ウィケットのスタンプ", pylons: "パイロン", flags: "コーナーフラッグ", fence: "外野フェンス", backstop: "バックネット", "foul-poles": "ファウルポール", "table-legs": "天板", walls: "壁", "starting-blocks": "スタート台", "backstroke-flags": "背泳ぎ用フラッグ", "false-start-rope": "フォルススタートロープ", springboards: "飛板", platforms: "固定台", "judges-stand": "ジャッジ台", hurdles: "ハードル", steeplechase: "障害物", "high-jump": "走高跳の支柱とバー", "pole-vault": "棒高跳の支柱とバー", cage: "円盤投・ハンマー投の囲い", padding: "防護マット", "rink-boards": "フェンス", mound: "土の台", azuchi: "安土", targets: "的", stands: "スタンド", "wall-seats": "壁の上の観客席", roof: "屋根", gallery: "見学席", dais: "上座の台", banner: "垂れ幕", shrine: "神棚", "mat-edge": "畳の厚み", surrounds: "競技エリアのフェンス", officials: "審判席と記録台" },
|
|
971
|
+
parts: { scoreboard: "スコアボードの下端", net: "ネット", antennae: "アンテナ", goalposts: "ゴールポスト", netposts: "支柱", hoop: "バスケットゴール", goal: "ゴール", boards: "ボードとガラス", stumps: "ウィケットのスタンプ", pylons: "パイロン", flags: "コーナーフラッグ", fence: "外野フェンス", backstop: "バックネット", "foul-poles": "ファウルポール", "table-legs": "天板", walls: "壁", "starting-blocks": "スタート台", "backstroke-flags": "背泳ぎ用フラッグ", "false-start-rope": "フォルススタートロープ", springboards: "飛板", platforms: "固定台", "judges-stand": "ジャッジ台", hurdles: "ハードル", steeplechase: "障害物", "high-jump": "走高跳の支柱とバー", "pole-vault": "棒高跳の支柱とバー", cage: "円盤投・ハンマー投の囲い", padding: "防護マット", "rink-boards": "フェンス", mound: "土の台", azuchi: "安土", targets: "的", stands: "スタンド", "wall-seats": "壁の上の観客席", roof: "屋根", gallery: "見学席", dais: "上座の台", banner: "垂れ幕", shrine: "神棚", "mat-edge": "畳の厚み", surrounds: "競技エリアのフェンス", officials: "審判席と記録台", judges: "審判員席の名札" },
|
|
972
972
|
},
|
|
973
973
|
rules: {
|
|
974
974
|
"defensive-zone": "ディフェンディングゾーン",
|
package/dist/turn.kit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** A point or a direction: x, y, z in feet. */
|
|
2
2
|
export type V3 = readonly [number, number, number];
|
|
3
3
|
/** What a plane is a part of: it is the class of its element (`ky-up-<kind>`) and what a test finds it by. */
|
|
4
|
-
export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine" | "surround" | "chair" | "desk";
|
|
4
|
+
export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target" | "seats" | "concourse" | "fascia" | "roof" | "banner" | "platform" | "rail" | "matedge" | "shrine" | "surround" | "chair" | "desk" | "card" | "sign";
|
|
5
5
|
/** One flat rectangle. Its own x runs along `u` (its whole length) and its own y along `v`; `o` is the corner both start from. `svg` is what is drawn on it, in feet along `u` and `v`. */
|
|
6
6
|
export interface Plane {
|
|
7
7
|
/** A name that says what it is and where: `net`, `post-left`, `goal-right-back`. */
|
|
@@ -32,7 +32,7 @@ export interface Plane {
|
|
|
32
32
|
fadeAt?: number;
|
|
33
33
|
}
|
|
34
34
|
/** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
|
|
35
|
-
export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials";
|
|
35
|
+
export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets" | "stands" | "wall-seats" | "roof" | "gallery" | "dais" | "banner" | "shrine" | "mat-edge" | "surrounds" | "officials" | "judges";
|
|
36
36
|
/** A thing that stands on the surface: where, how high, and the planes it is made of. */
|
|
37
37
|
export interface Upright {
|
|
38
38
|
id: string;
|
|
@@ -93,3 +93,14 @@ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
|
|
|
93
93
|
* from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
|
|
94
94
|
*/
|
|
95
95
|
export declare function fadeOf(p: Pick<Plane, "shy" | "fadeTo" | "fadeAt">, yaw: number, pitch: number): number;
|
|
96
|
+
/** A rectangle on the surface, in feet: its corners. */
|
|
97
|
+
export type Rect = {
|
|
98
|
+
x0: number;
|
|
99
|
+
y0: number;
|
|
100
|
+
x1: number;
|
|
101
|
+
y1: number;
|
|
102
|
+
};
|
|
103
|
+
/** A rectangle from its corner and its size. */
|
|
104
|
+
export declare const rect: (x: number, y: number, w: number, h: number) => Rect;
|
|
105
|
+
/** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
|
|
106
|
+
export declare function box(id: string, kind: Plane["kind"], r: Rect, z1: number, cls: string): Plane[];
|
package/dist/turn.kit.js
CHANGED
|
@@ -109,3 +109,12 @@ export function fadeOf(p, yaw, pitch) {
|
|
|
109
109
|
const t = Math.min(1, Math.max(0, (hides - from) / 0.25));
|
|
110
110
|
return Math.round((1 - (1 - (p.fadeTo ?? 0.08)) * t) * 1000) / 1000;
|
|
111
111
|
}
|
|
112
|
+
/** A rectangle from its corner and its size. */
|
|
113
|
+
export const rect = (x, y, w, h) => ({ x0: x, y0: y, x1: x + w, y1: y + h });
|
|
114
|
+
/** A solid box on the floor: four sides up to `z1` and a flat top. `cls` paints the top; the sides are one tone darker (`cls-side`). */
|
|
115
|
+
export function box(id, kind, r, z1, cls) {
|
|
116
|
+
const sides = [[r.x0, r.y0, r.x1, r.y0], [r.x1, r.y0, r.x1, r.y1], [r.x1, r.y1, r.x0, r.y1], [r.x0, r.y1, r.x0, r.y0]];
|
|
117
|
+
const planes = sides.map(([a, b, c, d], i) => wall(`${id}-${i + 1}`, kind, a, b, c, d, 0, z1, `<rect width="${n(Math.hypot(c - a, d - b))}" height="${n(z1)}" class="${cls}-side"/>`));
|
|
118
|
+
planes.push(flat(`${id}-top`, kind, r.x0, r.y0, r.x1, r.y1, z1, `<rect width="${n(r.x1 - r.x0)}" height="${n(r.y1 - r.y0)}" class="${cls}"/>`));
|
|
119
|
+
return planes;
|
|
120
|
+
}
|