@johnmorrisdotca/kyogijo-stadiums 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +2 -2
- package/dist/budo/judo.d.ts +4 -0
- package/dist/budo/judo.js +1 -1
- package/dist/judging-entry.d.ts +3 -3
- package/dist/judging-entry.js +2 -2
- package/dist/judging.d.ts +33 -12
- package/dist/judging.data.d.ts +4 -2
- package/dist/judging.data.js +40 -11
- package/dist/judging.draw.js +25 -18
- package/dist/judging.fit.d.ts +8 -5
- package/dist/judging.fit.js +7 -7
- package/dist/judging.js +100 -35
- package/dist/judging.turn.d.ts +1 -0
- package/dist/judging.turn.js +53 -36
- package/dist/judging.types.d.ts +17 -6
- package/dist/judging.words.d.ts +2 -0
- package/dist/judging.words.js +4 -4
- package/dist/surfaces/budo.d.ts +2 -0
- package/dist/surfaces/budo.js +17 -1
- package/dist/turn.kit.d.ts +6 -1
- package/dist/turn.kit.js +6 -2
- package/dist/turn.markup.js +1 -1
- package/dist/turn.model.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.18.1] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **The seats show their number, and the names are in the chart.** By default every seat of a judging assignment is a large round numbered badge (the chart's own), in 2D and in 3D, and the chart beside it is the key (`chart: "auto"` now always draws it, unless `seatNames` is on and every name fits). Putting the names on the seats is the new option `seatNames: true` (off by default), which sets them in the chart's own face and weight, scaled evenly, in the biggest size that fits and never below a readable floor, and falls back to the number alone when a name does not fit. The demo has a "Names on the seats too" box (`seatnames=1`).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Nothing is squeezed or stretched in a judging picture.** The 3D badges, cards and the mat's sign stood upright, so a tipped view foreshortened them and their type looked squeezed: they now lean back to face the eye squarely (`Plane.lean`, turned and never stretched), at every viewpoint. A test measures each one's shape on the screen against its own, and that no text anywhere has `textLength`, `lengthAdjust` or a two-number `scale`.
|
|
19
|
+
- **The tapes of a judo area were hairlines.** The kata area's centre and starting tapes (5 cm by 50 cm) and a contest area's two start tapes, and the sumo's shikiri-sen, are drawn at least 0.75 % of the plan's width across (10.5 cm on a kata area's 14 m, about 4 pixels on a plan 560 pixels across) unless `exactLines: true`, which draws the rule's 5 cm. The floor is a readability aid, not a measurement: a tape's length is always the rule's, and it is widened about its middle. In 3D (the same floor) and in tournament layouts too.
|
|
20
|
+
|
|
21
|
+
## [0.18.0] - 2026-10-20
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Shiai judging assignments** (`style: "shiai"` of `@johnmorrisdotca/kyogijo-stadiums/judging`). A contest area's officials by seat, with a name and an ID, drawn as the kata area's are (a card in the seat where the name fits, the chart of seat, role, name and ID that never cuts a name, 2D and 3D, light and dark, English and Japanese, `mat`, `focus`, `chart`, `orientation`, `judgingTable`). From the IJF Sport and Organisation Rules of 21 January 2026: **the referee**, on the tatami at its centre 2 m back from the line the athletes start on, facing the technical table (Article 9; the Refereeing Rules' Article 2); **the timekeeper and the scorekeeper**, the two technical officials a tatami should have for timing and scoring (the Refereeing Rules' opening article); **the referee supervisor**, on the technical table with the CARE replay system (the same article); and **two side judges**, for an event with no CARE system (Article 13, 5), drawn only with the new `sideJudges: true` or when a mat names them. Which chair at the table is whose, the side judges' places (two opposite corners of the contest area, 2 m out: the Rules give none), the chairs, the table and the cards are ours. In the close-up of the bench (`focus: "bench"`) only the table's seats are drawn and the referee's and side judges' names are in the chart; in 3D the referee's and a side judge's cards stand on posts. Seat words: `referee`, `timekeeper`, `scorekeeper`, `supervisor`, `side-judge-1`, `side-judge-2` (or their numbers 1 to 6, or `judge-1`, `scorer`, `jury`...). The demo, [judging assignments](https://johnmorrisdotca.github.io/kyogijo-stadiums/judging.html), has a Style choice (`style=shiai`), the side judges' checkbox and a row in its editor for each seat; the docs have [a section](./docs/judging.md#the-seats-of-a-contest) and the gallery [a contest](https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/gallery-judging.html#shiai).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `JudgingStyle` is `"kata" | "shiai"`; `JudgingSeat` has `where` (`table`, `mat` or `corner`) and `optional`, and its `role` the contest's roles; `seatsOf(style, "listed")` leaves out the optional seats; `judgingPlan(style, count)` gives each mat's `places` (a chair and a card for each seat, in metres) and its bench; `fitSeats` takes a box for each seat. A kata assignment draws byte for byte what it did.
|
|
30
|
+
- `JudoLayout` (the judo plan) also gives `desks` (the technical tables) and `free` (the free zone), as data only: no drawing changes.
|
|
31
|
+
|
|
10
32
|
## [0.17.0] - 2026-10-20
|
|
11
33
|
|
|
12
34
|
### Added
|
package/README.md
CHANGED
|
@@ -550,9 +550,9 @@ The [demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html) turns
|
|
|
550
550
|
<p align="center">
|
|
551
551
|
<picture>
|
|
552
552
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judging-3d-desk-dark.webp">
|
|
553
|
-
<img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judging-3d-desk-light.webp" alt="A kata area's judges' table in 3D,
|
|
553
|
+
<img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/judging-3d-desk-light.webp" alt="A kata area's judges' table in 3D, numbered badges, and the chart." width="640">
|
|
554
554
|
</picture>
|
|
555
|
-
<br><em>Judges in 3D.</em>
|
|
555
|
+
<br><em>Judges, in 3D.</em>
|
|
556
556
|
</p>
|
|
557
557
|
|
|
558
558
|
|
package/dist/budo/judo.d.ts
CHANGED
|
@@ -43,6 +43,10 @@ export interface JudoLayout {
|
|
|
43
43
|
seats: MBox[];
|
|
44
44
|
coaches: MBox[];
|
|
45
45
|
};
|
|
46
|
+
/** A contest area's technical tables (ours: 3 m by 0.8 m), one for each area of the top row; empty for a kata area. */
|
|
47
|
+
desks: MBox[];
|
|
48
|
+
/** The free zone round the whole competition area. */
|
|
49
|
+
free: MBox;
|
|
46
50
|
}
|
|
47
51
|
/** The mats of a `tatami` option, or the league's own when none. @throws Error for a name that is not a size of mat. */
|
|
48
52
|
export declare function matOf(asked: BudoStyle["tatami"], fig: JudoFigures): MatSize;
|
package/dist/budo/judo.js
CHANGED
|
@@ -139,7 +139,7 @@ export function judoParts(o) {
|
|
|
139
139
|
if (judges.coaches.length > 0)
|
|
140
140
|
marks.push({ id: "coach-chair", x: (judges.coaches[0].x0 + judges.coaches[0].x1) / 2, y: (judges.coaches[0].y0 + judges.coaches[0].y1) / 2, side: "bottom" });
|
|
141
141
|
const extent = union([free, ...desks.map((d) => grow(d, 0.3)), ...[...judges.tables, ...judges.seats, ...judges.coaches].map((d) => grow(d, 0.3))]);
|
|
142
|
-
return { parts, tatami, extent, contests, gap, pieces, mat, marks, danger: band, judges };
|
|
142
|
+
return { parts, tatami, extent, contests, gap, pieces, mat, marks, danger: band, judges, desks, free };
|
|
143
143
|
}
|
|
144
144
|
/** The number of mats of the competition area: a half mat counts a half. */
|
|
145
145
|
export const mats = (layout, mat) => Math.round(matCount(layout.pieces, mat) * 100) / 100;
|
package/dist/judging-entry.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { drawJudging, judgingChart, judgingDrawing, judgingTable, judgingText } from "./judging.draw.ts";
|
|
2
2
|
export { JUDGING_HEIGHTS, JUDGING_PRESETS, buildJudgingTurn, judgingTurnMarkup, mountJudgingTurn } from "./judging.turn.ts";
|
|
3
|
-
export { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_SEAT_COUNT, JUDGING_STYLES } from "./judging.data.ts";
|
|
4
|
-
export { JUDGING_LIMITS, judgingPlan, judgingSetting, matLabel, seatOf, seatsOf } from "./judging.ts";
|
|
3
|
+
export { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_SEAT_COUNT, JUDGING_SEAT_WORDS, JUDGING_STYLES } from "./judging.data.ts";
|
|
4
|
+
export { JUDGING_LIMITS, benchOf, judgingPlan, judgingSetting, matLabel, seatIn, seatOf, seatsOf } from "./judging.ts";
|
|
5
5
|
export { fitCard, fitChart, fitSeats, wrapText } from "./judging.fit.ts";
|
|
6
6
|
export { KYOGIJO_JUDGING_STYLE } from "./judging.style.ts";
|
|
7
7
|
export { JUDGING_WORDS } from "./judging.words.ts";
|
|
8
8
|
export type { JudgingWords } from "./judging.words.ts";
|
|
9
9
|
export type { JudgingAssignment, JudgingMat, JudgingOfficial, JudgingOptions, JudgingResolved, JudgingSeat, JudgingStyle } from "./judging.types.ts";
|
|
10
10
|
export type { JudgingDrawing } from "./judging.draw.ts";
|
|
11
|
-
export type { JudgingPlan, JudgingSetting } from "./judging.ts";
|
|
11
|
+
export type { JudgingPlan, JudgingSetting, MatPlaces, SeatPlace } from "./judging.ts";
|
|
12
12
|
export type { CardAsk, CardFit } from "./judging.fit.ts";
|
package/dist/judging-entry.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// A judging assignment: the officials of each mat area of a judo tournament, by seat, with their names and IDs, in the seat where they fit and in a chart where they do not; in 2D and in 3D.
|
|
2
2
|
export { drawJudging, judgingChart, judgingDrawing, judgingTable, judgingText } from "./judging.draw.js";
|
|
3
3
|
export { JUDGING_HEIGHTS, JUDGING_PRESETS, buildJudgingTurn, judgingTurnMarkup, mountJudgingTurn } from "./judging.turn.js";
|
|
4
|
-
export { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_SEAT_COUNT, JUDGING_STYLES } from "./judging.data.js";
|
|
5
|
-
export { JUDGING_LIMITS, judgingPlan, judgingSetting, matLabel, seatOf, seatsOf } from "./judging.js";
|
|
4
|
+
export { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_SEAT_COUNT, JUDGING_SEAT_WORDS, JUDGING_STYLES } from "./judging.data.js";
|
|
5
|
+
export { JUDGING_LIMITS, benchOf, judgingPlan, judgingSetting, matLabel, seatIn, seatOf, seatsOf } from "./judging.js";
|
|
6
6
|
export { fitCard, fitChart, fitSeats, wrapText } from "./judging.fit.js";
|
|
7
7
|
export { KYOGIJO_JUDGING_STYLE } from "./judging.style.js";
|
|
8
8
|
export { JUDGING_WORDS } from "./judging.words.js";
|
package/dist/judging.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ export declare const JUDGING_LIMITS: Readonly<{
|
|
|
7
7
|
id: 64;
|
|
8
8
|
label: 40;
|
|
9
9
|
}>;
|
|
10
|
-
/** The seats of a style. @throws TypeError for a style that is not one. */
|
|
11
|
-
export declare function seatsOf(style: unknown): readonly JudgingSeat[];
|
|
10
|
+
/** The seats of a style: every one, or without the optional ones (the side judges of a contest). @throws TypeError for a style that is not one. */
|
|
11
|
+
export declare function seatsOf(style: unknown, only?: "all" | "listed"): readonly JudgingSeat[];
|
|
12
12
|
/**
|
|
13
|
-
* The seat a name stands for: its number (`3`, `"3"`), its key (`"judge-3"`, `"Judge 3"`) or `"central"
|
|
13
|
+
* The seat a name stands for: its number (`3`, `"3"`), its key (`"judge-3"`, `"Judge 3"`, `"referee"`, `"timekeeper"`) or a word (`"central"`, `"scorer"`, `"jury"`).
|
|
14
14
|
* @throws RangeError for a seat the style does not have, naming the seats it has.
|
|
15
15
|
*/
|
|
16
16
|
export declare function seatOf(style: JudgingStyle, seat: unknown): JudgingSeat;
|
|
@@ -28,28 +28,49 @@ export interface JudgingSetting {
|
|
|
28
28
|
orientation: "landscape" | "portrait";
|
|
29
29
|
focus: "mat" | "bench";
|
|
30
30
|
width: number;
|
|
31
|
+
/** Whether the names are on the seats (`seatNames`). */
|
|
32
|
+
seatNames: boolean;
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* Hold the options to what they may be and put each official in a seat.
|
|
34
36
|
* @throws TypeError for an option of the wrong kind, RangeError for a number of mats outside 1 to 6, a mat that does not exist, a seat the style does not have, a seat named twice on one mat or a text that is too long.
|
|
35
37
|
*/
|
|
36
38
|
export declare function judgingSetting(options: JudgingOptions): JudgingSetting;
|
|
37
|
-
/** Where
|
|
39
|
+
/** Where one seat is on a mat's plan, in metres from the top left of the tatami: its chair (or the place where the official stands) and the card that carries the name, which may lie over the table in front of it. */
|
|
40
|
+
export interface SeatPlace {
|
|
41
|
+
chair: MBox;
|
|
42
|
+
card: MBox;
|
|
43
|
+
}
|
|
44
|
+
/** One mat's seats, in the order the style lists them (`JUDGING_SEATS`, optional seats included), its table and its bench. */
|
|
45
|
+
export interface MatPlaces {
|
|
46
|
+
places: SeatPlace[];
|
|
47
|
+
/** The judges' table or the technical table behind the seats at it. */
|
|
48
|
+
table: MBox;
|
|
49
|
+
/** The seats at the table close up: the picture of `focus: "bench"`. */
|
|
50
|
+
bench: MBox;
|
|
51
|
+
}
|
|
52
|
+
/** Where the seats are on the plan of a style, and the plan they are on. */
|
|
38
53
|
export interface JudgingPlan {
|
|
39
54
|
plan: BudoPlan;
|
|
40
|
-
/**
|
|
55
|
+
/** Each mat's seats, for the mats side by side the plan has (`count`). */
|
|
56
|
+
mats: MatPlaces[];
|
|
57
|
+
/** The first mat's seats, its table and its bench, as `mats[0]` has them (the chairs of the seats, in order). */
|
|
58
|
+
places: SeatPlace[];
|
|
41
59
|
chairs: MBox[];
|
|
42
60
|
table: MBox;
|
|
43
|
-
|
|
61
|
+
bench: MBox;
|
|
62
|
+
/** The distance between two seats at the table. */
|
|
44
63
|
pitch: number;
|
|
45
|
-
/** The whole picture of a mat (the free zone, the table, the seats,
|
|
64
|
+
/** The whole picture of a mat (the free zone, the table, the seats, their cards): what `focus: "mat"` shows, for the first mat. */
|
|
46
65
|
extent: MBox;
|
|
47
|
-
bench: MBox;
|
|
48
66
|
}
|
|
49
67
|
/**
|
|
50
|
-
* The judges' bench close up: the seats and
|
|
51
|
-
* contest area), so that the picture shows how the bench sits at the mat's edge; `front` instead says how far in front of the table the picture reaches, in metres (the 3D view, which turns, wants as little of the floor as
|
|
68
|
+
* The judges' bench close up: the seats and their table with a little round them and a few rows of mats in front, from the seats of one mat or of several side by side. `matTop` is where the mats start (the top of the first
|
|
69
|
+
* contest area), so that the picture shows how the bench sits at the mat's edge; `front` instead says how far in front of the table the picture reaches, in metres (the 3D view, which turns, wants as little of the floor as
|
|
70
|
+
* will do).
|
|
52
71
|
*/
|
|
53
72
|
export declare function benchOf(chairs: readonly MBox[], table: MBox, pitch: number, matTop: number, front?: number): MBox;
|
|
54
|
-
/** The plan of
|
|
55
|
-
export declare function judgingPlan(style: JudgingStyle): JudgingPlan;
|
|
73
|
+
/** The plan of `count` mats side by side of a style, with its seats: the package's own judo area, so that the seats named are the seats drawn. */
|
|
74
|
+
export declare function judgingPlan(style: JudgingStyle, count?: number): JudgingPlan;
|
|
75
|
+
/** Whether a seat is in a box: its card's middle is inside it. A seat that is not is not drawn in a close-up of the bench, and its name is in the chart only. */
|
|
76
|
+
export declare function seatIn(place: SeatPlace, box: MBox): boolean;
|
package/dist/judging.data.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { JudgingSeat, JudgingStyle } from "./judging.types.ts";
|
|
2
|
-
/** How many officials a style seats. */
|
|
2
|
+
/** How many officials a style seats at the most (a contest's two side judges included). */
|
|
3
3
|
export declare const JUDGING_SEAT_COUNT: Readonly<Record<JudgingStyle, number>>;
|
|
4
|
-
/** The seats of every style, in the order they are
|
|
4
|
+
/** The seats of every style, in the order they are listed. */
|
|
5
5
|
export declare const JUDGING_SEATS: Readonly<Record<JudgingStyle, readonly JudgingSeat[]>>;
|
|
6
|
+
/** The other words a seat is asked for by, besides its key and its number. */
|
|
7
|
+
export declare const JUDGING_SEAT_WORDS: Readonly<Record<string, readonly string[]>>;
|
|
6
8
|
/** The styles that can be asked for. */
|
|
7
9
|
export declare const JUDGING_STYLES: readonly JudgingStyle[];
|
|
8
10
|
/** The league each style is drawn in (`drawKyogijo`'s `league`), and the sources its seats are read from (keys of `LEAGUE_SOURCES`). */
|
package/dist/judging.data.js
CHANGED
|
@@ -1,24 +1,53 @@
|
|
|
1
|
-
// The seats of each judging style, as the rulebook has them.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The seats of each judging style, as the rulebook has them.
|
|
2
|
+
// A kata area is judged by five judges on the shomen side, 1.25 m apart, the central judge at the centre of the area, and no referee (IJF Sport and Organisation Rules of 21 January 2026, B1.8 and the judges' article; the
|
|
3
|
+
// Kata Commission's Tournament Handbook 2024). The rules do not number the judges: the numbers here are ours, from the left as the plan lies with the shomen at the top (so the central judge is 3).
|
|
4
|
+
// A contest (shiai) has one referee, on the tatami at its centre 2 m back from the line the athletes start on, facing the technical table (SOR, Refereeing Rules, Article 9 and Article 2); two technical officials a
|
|
5
|
+
// tatami for timing and scoring (the Refereeing Rules' opening article); the IJF Referee Supervisor(s) on the technical table with the CARE replay system, and the referees not officiating seated at it (Article 3);
|
|
6
|
+
// and, for an event with no CARE system, two side judges with a majority of three (Article 13, 5). The Rules give no place for a side judge: the corners of the contest area are ours. The positions are the plan's own
|
|
7
|
+
// (`budoPlan`) and the figures round them in `judging.ts`, so that what is drawn and what is named cannot differ.
|
|
4
8
|
import { IJF_KATA } from "./leagues/budo.js";
|
|
5
|
-
/** How many officials a style seats. */
|
|
6
|
-
export const JUDGING_SEAT_COUNT = Object.freeze({ kata: IJF_KATA.panel?.count ?? 5 });
|
|
9
|
+
/** How many officials a style seats at the most (a contest's two side judges included). */
|
|
10
|
+
export const JUDGING_SEAT_COUNT = Object.freeze({ kata: IJF_KATA.panel?.count ?? 5, shiai: 6 });
|
|
7
11
|
/** The seats of a kata area: five judges, the middle one the central judge. */
|
|
8
12
|
function kata() {
|
|
9
|
-
const count =
|
|
13
|
+
const count = IJF_KATA.panel?.count ?? 5;
|
|
10
14
|
const middle = Math.ceil(count / 2);
|
|
11
15
|
return Array.from({ length: count }, (_, i) => {
|
|
12
16
|
const n = i + 1;
|
|
13
17
|
const central = n === middle;
|
|
14
|
-
return { id: `judge-${n}`, n, role: central ? "central" : "judge", label: { en: central ? `Judge ${n}, central` : `Judge ${n}`, ja: central ? `審判員${n}(中央)` : `審判員${n}` } };
|
|
18
|
+
return { id: `judge-${n}`, n, role: central ? "central" : "judge", where: "table", label: { en: central ? `Judge ${n}, central` : `Judge ${n}`, ja: central ? `審判員${n}(中央)` : `審判員${n}` } };
|
|
15
19
|
});
|
|
16
20
|
}
|
|
17
|
-
/** The seats of
|
|
18
|
-
|
|
21
|
+
/** The seats of a contest: the referee, then the technical table's three, then the side judges (drawn only when asked for or named). */
|
|
22
|
+
function shiai() {
|
|
23
|
+
const seat = (n, id, role, where, en, ja, optional = false) => ({ id, n, role, where, label: { en, ja }, ...(optional ? { optional: true } : {}) });
|
|
24
|
+
return [
|
|
25
|
+
seat(1, "referee", "referee", "mat", "Referee", "主審"),
|
|
26
|
+
seat(2, "timekeeper", "timekeeper", "table", "Timekeeper", "計時係"),
|
|
27
|
+
seat(3, "scorekeeper", "scorekeeper", "table", "Scorekeeper", "記録係"),
|
|
28
|
+
seat(4, "supervisor", "supervisor", "table", "Referee supervisor", "審判委員"),
|
|
29
|
+
seat(5, "side-judge-1", "side-judge", "corner", "Side judge 1", "副審1", true),
|
|
30
|
+
seat(6, "side-judge-2", "side-judge", "corner", "Side judge 2", "副審2", true),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/** The seats of every style, in the order they are listed. */
|
|
34
|
+
export const JUDGING_SEATS = Object.freeze({ kata: Object.freeze(kata()), shiai: Object.freeze(shiai()) });
|
|
35
|
+
/** The other words a seat is asked for by, besides its key and its number. */
|
|
36
|
+
export const JUDGING_SEAT_WORDS = Object.freeze({
|
|
37
|
+
"judge-3": ["central", "centre", "center"],
|
|
38
|
+
referee: ["main", "main-referee", "central-referee", "shimpan"],
|
|
39
|
+
timekeeper: ["time", "timer", "timing"],
|
|
40
|
+
scorekeeper: ["score", "scorer", "scoring", "recorder"],
|
|
41
|
+
supervisor: ["referee-supervisor", "jury", "care"],
|
|
42
|
+
"side-judge-1": ["judge-1", "side-1"],
|
|
43
|
+
"side-judge-2": ["judge-2", "side-2"],
|
|
44
|
+
});
|
|
19
45
|
/** The styles that can be asked for. */
|
|
20
|
-
export const JUDGING_STYLES = Object.freeze(["kata"]);
|
|
46
|
+
export const JUDGING_STYLES = Object.freeze(["kata", "shiai"]);
|
|
21
47
|
/** The league each style is drawn in (`drawKyogijo`'s `league`), and the sources its seats are read from (keys of `LEAGUE_SOURCES`). */
|
|
22
|
-
export const JUDGING_LEAGUE = Object.freeze({
|
|
48
|
+
export const JUDGING_LEAGUE = Object.freeze({
|
|
49
|
+
kata: { league: "ijf-kata", sources: ["ijf-sor", "ijf-kata-handbook", "ijf-kata-criteria"] },
|
|
50
|
+
shiai: { league: "ijf", sources: ["ijf-sor"] },
|
|
51
|
+
});
|
|
23
52
|
/** The most mat areas an assignment names: the plan's own limit for areas side by side. */
|
|
24
53
|
export const JUDGING_MATS_MAX = 6;
|
package/dist/judging.draw.js
CHANGED
|
@@ -7,7 +7,7 @@ import { el, esc, hash, num } from "./format.js";
|
|
|
7
7
|
import { PLAN_CARD, badge, card } from "./judging.card.js";
|
|
8
8
|
import { fitChart, fitSeats } from "./judging.fit.js";
|
|
9
9
|
import { JUDGING_LEAGUE } from "./judging.data.js";
|
|
10
|
-
import { judgingPlan, judgingSetting } from "./judging.js";
|
|
10
|
+
import { judgingPlan, judgingSetting, seatIn } from "./judging.js";
|
|
11
11
|
import { KYOGIJO_JUDGING_STYLE } from "./judging.style.js";
|
|
12
12
|
import { JUDGING_WORDS } from "./judging.words.js";
|
|
13
13
|
import { fill } from "./strings.js";
|
|
@@ -42,7 +42,7 @@ export function judgingText(setting, options, chart) {
|
|
|
42
42
|
const mats = setting.total === 1 ? words.say.matsOne : fill(words.say.matsMany, { n: setting.total });
|
|
43
43
|
const title = options.title ?? fill(words.say.title, { style, mats });
|
|
44
44
|
const sentences = setting.mats.map((m) => fill(words.say.mat, { label: m.label, seats: m.seats.map((a) => seatSentence(a, setting)).join(words.say.join) }));
|
|
45
|
-
const description = options.description ?? [...sentences, chart ? words.say.names : words.say.chartOnly].join(setting.language === "ja" ? "" : " ");
|
|
45
|
+
const description = options.description ?? [...sentences, chart && setting.seatNames ? words.say.names : words.say.chartOnly].join(setting.language === "ja" ? "" : " ");
|
|
46
46
|
return { title, description };
|
|
47
47
|
}
|
|
48
48
|
const metricsOf = (width) => {
|
|
@@ -117,26 +117,32 @@ function planMarkup(mat, surface, geom, region, box, m, setting, clip) {
|
|
|
117
117
|
markup += el("rect", { class: "ky-apron", x, y, width: w, height: h });
|
|
118
118
|
markup += el("g", { class: "ky-ju-plan", "clip-path": `url(#${clip})`, transform: `translate(${num(x - region.x0 * FT * sf)} ${num(y - region.y0 * FT * sf)}) scale(${(sf).toFixed(5)})` }, surface.body);
|
|
119
119
|
markup += el("rect", { class: "ky-ju-plan-frame", x, y, width: w, height: h });
|
|
120
|
-
// The card of a seat is its chair and the table in front of it
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
// The card of a seat is the place the plan gives it: for a kata judge its chair and the table in front of it, for a contest's referee and side judges a card of their own.
|
|
121
|
+
const boxes = mat.seats.map((a) => {
|
|
122
|
+
const c = geom.places[a.seat.n - 1].card;
|
|
123
|
+
const cw = (c.x1 - c.x0) * s;
|
|
124
|
+
const ch = (c.y1 - c.y0) * s;
|
|
125
|
+
const pad = Math.max(2 * m.k, cw * 0.05);
|
|
126
|
+
const r = clamp(cw * 0.2, 6 * m.k, 14 * m.k);
|
|
127
|
+
return { cw, ch, pad, r, width: cw - 2 * pad, height: ch - 2 * pad - r, max: Math.min(cw * 0.3, 26 * m.k) };
|
|
128
|
+
});
|
|
129
|
+
// The names are on the seats only when asked for; otherwise every seat is its number, and the chart is the key.
|
|
130
|
+
const fitted = setting.seatNames ? fitSeats(mat.seats, boxes, { min: m.cardMin, idLabel: words.chart.idLabel }) : mat.seats.map(() => undefined);
|
|
131
|
+
// A seat outside the picture (the referee in a close-up of the bench) has its name in the chart only: it does not fit.
|
|
132
|
+
const shown = mat.seats.map((a) => seatIn(geom.places[a.seat.n - 1], region));
|
|
133
|
+
const fits = mat.seats.map((a, i) => !a.assigned || (shown[i] && fitted[i] !== undefined));
|
|
129
134
|
mat.seats.forEach((a, i) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
135
|
+
if (!shown[i])
|
|
136
|
+
return;
|
|
137
|
+
const place = geom.places[a.seat.n - 1];
|
|
138
|
+
const b = boxes[i];
|
|
133
139
|
const fit = fitted[i];
|
|
134
140
|
let g = "";
|
|
135
141
|
if (fit !== undefined)
|
|
136
|
-
g += card(PLAN_CARD, fit, a.seat.n,
|
|
142
|
+
g += card(PLAN_CARD, fit, a.seat.n, X(place.card.x0), Y(place.card.y0), b.cw, b.ch, b.pad, b.r, 3 * m.k);
|
|
137
143
|
else {
|
|
138
|
-
const br = clamp(
|
|
139
|
-
g += badge(PLAN_CARD,
|
|
144
|
+
const br = clamp(Math.min(b.cw, 1.25 * s) * 0.42, 10 * m.k, 30 * m.k);
|
|
145
|
+
g += badge(PLAN_CARD, X((place.chair.x0 + place.chair.x1) / 2), Y((place.chair.y0 + place.chair.y1) / 2), br, a.seat.n, a.assigned);
|
|
140
146
|
}
|
|
141
147
|
markup += el("g", { class: "ky-ju-seat", "data-mat": mat.n, "data-seat": a.seat.id, "data-card": fit === undefined ? "no" : "yes" }, g);
|
|
142
148
|
});
|
|
@@ -175,8 +181,9 @@ export function judgingDrawing(options, mode = "full") {
|
|
|
175
181
|
return setting.mats.flatMap((mat, i) => planMarkup(mat, surfaces[i], geom, region, { x: 0, y: 0, ...box }, m, setting, "t").fits);
|
|
176
182
|
};
|
|
177
183
|
let chart = mode === "chart" || setting.chart === "always";
|
|
184
|
+
// `auto`: the chart is the key to the numbers, so it is drawn unless the names are on the seats and every one fits there.
|
|
178
185
|
if (mode === "full" && setting.chart === "auto")
|
|
179
|
-
chart = !trial(false).every(Boolean);
|
|
186
|
+
chart = !setting.seatNames || !trial(false).every(Boolean);
|
|
180
187
|
if (mode === "full" && setting.chart === "never")
|
|
181
188
|
chart = false;
|
|
182
189
|
const cardsFit = mode === "chart" ? [] : trial(chart);
|
package/dist/judging.fit.d.ts
CHANGED
|
@@ -38,14 +38,17 @@ export declare function fitCard(ask: CardAsk): CardFit | undefined;
|
|
|
38
38
|
* It always fits, whatever the text, because the row grows.
|
|
39
39
|
*/
|
|
40
40
|
export declare function fitChart(ask: Omit<CardAsk, "maxLines" | "breakWords" | "height">): CardFit;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
* taken, and each is fitted again at that size. A seat whose name does not fit at the floor, or that has nobody in it, has no card (undefined).
|
|
44
|
-
*/
|
|
45
|
-
export declare function fitSeats(seats: readonly JudgingAssignment[], box: {
|
|
41
|
+
/** The box a card gives a seat's name and ID, and the biggest size its name may be set in. */
|
|
42
|
+
export interface SeatBox {
|
|
46
43
|
width: number;
|
|
47
44
|
height: number;
|
|
48
45
|
max: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fit the name and ID of every seat of a mat into its card, as one size for all of them, so that the row of cards reads evenly: each seat that has a name is fitted alone, the smallest size any of them needs is taken, and
|
|
49
|
+
* each is fitted again at that size. A seat whose name does not fit at the floor, or that has nobody in it, has no card (undefined). `boxes` has the card box of each seat, in order.
|
|
50
|
+
*/
|
|
51
|
+
export declare function fitSeats(seats: readonly JudgingAssignment[], boxes: readonly SeatBox[], limits: {
|
|
49
52
|
min: number;
|
|
50
53
|
idLabel: string;
|
|
51
54
|
}): (CardFit | undefined)[];
|
package/dist/judging.fit.js
CHANGED
|
@@ -83,13 +83,13 @@ export function fitChart(ask) {
|
|
|
83
83
|
return fit;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Fit the name and ID of every seat of a mat into
|
|
87
|
-
*
|
|
86
|
+
* Fit the name and ID of every seat of a mat into its card, as one size for all of them, so that the row of cards reads evenly: each seat that has a name is fitted alone, the smallest size any of them needs is taken, and
|
|
87
|
+
* each is fitted again at that size. A seat whose name does not fit at the floor, or that has nobody in it, has no card (undefined). `boxes` has the card box of each seat, in order.
|
|
88
88
|
*/
|
|
89
|
-
export function fitSeats(seats,
|
|
90
|
-
const ask = (a, max) => (a.assigned ? fitCard({ name: a.name, id: a.id, idLabel:
|
|
91
|
-
const alone = seats.map((a) => ask(a,
|
|
89
|
+
export function fitSeats(seats, boxes, limits) {
|
|
90
|
+
const ask = (a, i, max) => (a.assigned ? fitCard({ name: a.name, id: a.id, idLabel: limits.idLabel, width: boxes[i].width, height: boxes[i].height, max, min: limits.min, maxLines: 3, breakWords: false }) : undefined);
|
|
91
|
+
const alone = seats.map((a, i) => ask(a, i, boxes[i].max));
|
|
92
92
|
const sizes = alone.filter((f) => f !== undefined).map((f) => f.nameSize);
|
|
93
|
-
const common = sizes.length === 0 ?
|
|
94
|
-
return seats.map((a, i) => (alone[i] === undefined ? undefined : ask(a, common)));
|
|
93
|
+
const common = sizes.length === 0 ? Infinity : Math.min(...sizes);
|
|
94
|
+
return seats.map((a, i) => (alone[i] === undefined ? undefined : ask(a, i, Math.min(common, boxes[i].max))));
|
|
95
95
|
}
|
package/dist/judging.js
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
// A judging assignment worked out: the options held to what they may be, each mat's seats with who sits in them, and where the seats are on the plan. The plan is the package's own judo area (`budoPlan`), so that the
|
|
2
2
|
// seats named here are the seats drawn, in metres from the top left of the tatami. Nothing here draws.
|
|
3
3
|
import { budoPlan } from "./budo/plan.js";
|
|
4
|
-
import { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_STYLES } from "./judging.data.js";
|
|
4
|
+
import { JUDGING_LEAGUE, JUDGING_MATS_MAX, JUDGING_SEATS, JUDGING_SEAT_WORDS, JUDGING_STYLES } from "./judging.data.js";
|
|
5
5
|
import { JUDGING_WORDS } from "./judging.words.js";
|
|
6
6
|
import { fill } from "./strings.js";
|
|
7
7
|
/** The longest name, ID and mat label that are taken, in characters. */
|
|
8
8
|
export const JUDGING_LIMITS = Object.freeze({ name: 120, id: 64, label: 40 });
|
|
9
9
|
/** Text made safe to show: control characters become spaces, runs of spaces one, the ends trimmed. */
|
|
10
10
|
const clean = (text) => text.replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
|
|
11
|
-
/** The seats of a style. @throws TypeError for a style that is not one. */
|
|
12
|
-
export function seatsOf(style) {
|
|
11
|
+
/** The seats of a style: every one, or without the optional ones (the side judges of a contest). @throws TypeError for a style that is not one. */
|
|
12
|
+
export function seatsOf(style, only = "all") {
|
|
13
13
|
if (typeof style !== "string" || !JUDGING_STYLES.includes(style))
|
|
14
14
|
throw new TypeError(`judging style is ${JUDGING_STYLES.map((s) => `"${s}"`).join(" or ")}`);
|
|
15
|
-
|
|
15
|
+
const seats = JUDGING_SEATS[style];
|
|
16
|
+
return only === "all" ? seats : seats.filter((s) => s.optional !== true);
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
|
-
* The seat a name stands for: its number (`3`, `"3"`), its key (`"judge-3"`, `"Judge 3"`) or `"central"
|
|
19
|
+
* The seat a name stands for: its number (`3`, `"3"`), its key (`"judge-3"`, `"Judge 3"`, `"referee"`, `"timekeeper"`) or a word (`"central"`, `"scorer"`, `"jury"`).
|
|
19
20
|
* @throws RangeError for a seat the style does not have, naming the seats it has.
|
|
20
21
|
*/
|
|
21
22
|
export function seatOf(style, seat) {
|
|
22
23
|
const seats = seatsOf(style);
|
|
23
|
-
const text = typeof seat === "number" ? String(seat) : typeof seat === "string" ? seat.trim().toLowerCase() : "";
|
|
24
|
+
const text = typeof seat === "number" ? String(seat) : typeof seat === "string" ? seat.trim().toLowerCase().replace(/\s+/g, "-") : "";
|
|
24
25
|
let found;
|
|
25
|
-
if (text
|
|
26
|
-
found = seats.find((s) => s.
|
|
27
|
-
else
|
|
28
|
-
|
|
29
|
-
if (m !== null)
|
|
30
|
-
found = seats.find((s) => s.n === Number(m[1]));
|
|
31
|
-
}
|
|
26
|
+
if (/^\d{1,2}$/.test(text))
|
|
27
|
+
found = seats.find((s) => s.n === Number(text));
|
|
28
|
+
else
|
|
29
|
+
found = seats.find((s) => s.id === text) ?? seats.find((s) => JUDGING_SEAT_WORDS[s.id]?.includes(text) === true);
|
|
32
30
|
if (found === undefined)
|
|
33
|
-
throw new RangeError(`The ${style} area has no seat ${JSON.stringify(seat)}: its seats are ${seats.map((s) => s.id).join(", ")} (or their numbers
|
|
31
|
+
throw new RangeError(`The ${style} area has no seat ${JSON.stringify(seat)}: its seats are ${seats.map((s) => s.id).join(", ")} (or their numbers)`);
|
|
34
32
|
return found;
|
|
35
33
|
}
|
|
36
34
|
/** The label of a mat: the caller's, held to its length, else the language's `Mat {n}`. */
|
|
@@ -61,15 +59,15 @@ export function judgingSetting(options) {
|
|
|
61
59
|
if (!Array.isArray(options.mats) || options.mats.length < 1 || options.mats.length > JUDGING_MATS_MAX)
|
|
62
60
|
throw new RangeError(`An assignment has 1 to ${JUDGING_MATS_MAX} mats`);
|
|
63
61
|
const language = options.language === "ja" ? "ja" : "en";
|
|
64
|
-
const
|
|
62
|
+
const given = options.mats.map((mat, i) => {
|
|
65
63
|
if (typeof mat !== "object" || mat === null)
|
|
66
64
|
throw new TypeError("a mat is { label, officials }");
|
|
67
|
-
const
|
|
65
|
+
const byId = new Map();
|
|
68
66
|
for (const official of mat.officials ?? []) {
|
|
69
67
|
if (typeof official !== "object" || official === null)
|
|
70
68
|
throw new TypeError("an official is { seat, name, id }");
|
|
71
69
|
const seat = seatOf(style, official.seat);
|
|
72
|
-
if (
|
|
70
|
+
if (byId.has(seat.id))
|
|
73
71
|
throw new RangeError(`Mat ${i + 1} names seat ${seat.id} twice`);
|
|
74
72
|
if (official.name !== undefined && typeof official.name !== "string")
|
|
75
73
|
throw new TypeError("an official's name is text");
|
|
@@ -81,10 +79,17 @@ export function judgingSetting(options) {
|
|
|
81
79
|
throw new RangeError(`A name is at most ${JUDGING_LIMITS.name} characters`);
|
|
82
80
|
if ([...id].length > JUDGING_LIMITS.id)
|
|
83
81
|
throw new RangeError(`An ID is at most ${JUDGING_LIMITS.id} characters`);
|
|
84
|
-
|
|
82
|
+
byId.set(seat.id, { seat, name, id, assigned: name !== "" || id !== "" });
|
|
85
83
|
}
|
|
86
|
-
return
|
|
84
|
+
return byId;
|
|
87
85
|
});
|
|
86
|
+
if (options.sideJudges !== undefined && typeof options.sideJudges !== "boolean")
|
|
87
|
+
throw new TypeError("sideJudges is true or false");
|
|
88
|
+
if (options.seatNames !== undefined && typeof options.seatNames !== "boolean")
|
|
89
|
+
throw new TypeError("seatNames is true or false");
|
|
90
|
+
// The seats that are drawn: every one that is not optional, and an optional one when asked for or when a mat names it.
|
|
91
|
+
const shown = seats.filter((seat) => seat.optional !== true || options.sideJudges === true || given.some((m) => m.has(seat.id)));
|
|
92
|
+
const all = options.mats.map((mat, i) => ({ n: i + 1, label: matLabel(mat, i + 1, language), seats: shown.map((seat) => given[i].get(seat.id) ?? { seat, name: "", id: "", assigned: false }) }));
|
|
88
93
|
let mats = all;
|
|
89
94
|
if (options.mat !== undefined) {
|
|
90
95
|
if (!Number.isInteger(options.mat) || options.mat < 1 || options.mat > all.length)
|
|
@@ -103,33 +108,93 @@ export function judgingSetting(options) {
|
|
|
103
108
|
orientation: oneOf(options.orientation, ["landscape", "portrait"], "landscape", "orientation"),
|
|
104
109
|
focus: oneOf(options.focus, ["mat", "bench"], "mat", "focus"),
|
|
105
110
|
width,
|
|
111
|
+
seatNames: options.seatNames === true,
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
114
|
const plans = new Map();
|
|
115
|
+
/** A box of a given centre and size. */
|
|
116
|
+
const around = (cx, cy, w, h) => ({ x0: cx - w / 2, y0: cy - h / 2, x1: cx + w / 2, y1: cy + h / 2 });
|
|
117
|
+
const union = (boxes) => ({ x0: Math.min(...boxes.map((b) => b.x0)), y0: Math.min(...boxes.map((b) => b.y0)), x1: Math.max(...boxes.map((b) => b.x1)), y1: Math.max(...boxes.map((b) => b.y1)) });
|
|
118
|
+
/** A card for a seat that is not at a table (the referee on the mat, a side judge at a corner): 2.6 m by 1.8 m, since there is room round them (ours). */
|
|
119
|
+
const FREE_CARD = { w: 2.6, h: 1.8 };
|
|
109
120
|
/**
|
|
110
|
-
* The judges' bench close up: the seats and
|
|
111
|
-
* contest area), so that the picture shows how the bench sits at the mat's edge; `front` instead says how far in front of the table the picture reaches, in metres (the 3D view, which turns, wants as little of the floor as
|
|
121
|
+
* The judges' bench close up: the seats and their table with a little round them and a few rows of mats in front, from the seats of one mat or of several side by side. `matTop` is where the mats start (the top of the first
|
|
122
|
+
* contest area), so that the picture shows how the bench sits at the mat's edge; `front` instead says how far in front of the table the picture reaches, in metres (the 3D view, which turns, wants as little of the floor as
|
|
123
|
+
* will do).
|
|
112
124
|
*/
|
|
113
125
|
export function benchOf(chairs, table, pitch, matTop, front) {
|
|
114
126
|
const side = (pitch - 0.5) / 2 + 0.15;
|
|
115
127
|
return { x0: Math.min(...chairs.map((c) => c.x0)) - side, y0: chairs[0].y0 - 0.9, x1: Math.max(...chairs.map((c) => c.x1)) + side, y1: front === undefined ? Math.max(table.y1 + 1.8, matTop + 1.2) : table.y1 + front };
|
|
116
128
|
}
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
/** A kata area's seats: five chairs behind one table, each with the card that lies over its chair and the table in front of it. */
|
|
130
|
+
function kataPlaces(chairs, table, pitch) {
|
|
131
|
+
return chairs.map((chair) => {
|
|
132
|
+
const cx = (chair.x0 + chair.x1) / 2;
|
|
133
|
+
return { chair, card: { x0: cx - (pitch / 2 - 0.03), y0: chair.y0 - 0.05, x1: cx + (pitch / 2 - 0.03), y1: table.y1 } };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A contest's seats round one contest area (ours, but for the referee's: 2 m back from the line the athletes start on, at the centre, facing the table): the timekeeper, the scorekeeper and the referee supervisor in chairs
|
|
138
|
+
* behind the technical table, 1 m apart, and the side judges at two opposite corners of the contest area, 2 m out from them.
|
|
139
|
+
*/
|
|
140
|
+
function shiaiPlaces(contest, desk) {
|
|
141
|
+
const cx = (contest.x0 + contest.x1) / 2;
|
|
142
|
+
const cy = (contest.y0 + contest.y1) / 2;
|
|
143
|
+
const seat = (x, y, w, h) => ({ chair: around(x, y, 0.5, 0.5), card: around(x, y, w, h) });
|
|
144
|
+
const chairY = desk.y0 - 0.35;
|
|
145
|
+
const tableSeat = (x) => ({ chair: around(x, chairY, 0.5, 0.5), card: { x0: x - 0.47, y0: chairY - 0.3, x1: x + 0.47, y1: desk.y1 } });
|
|
146
|
+
return [
|
|
147
|
+
seat(cx, cy + 2, FREE_CARD.w, FREE_CARD.h),
|
|
148
|
+
tableSeat(cx - 1),
|
|
149
|
+
tableSeat(cx),
|
|
150
|
+
tableSeat(cx + 1),
|
|
151
|
+
seat(contest.x0 - 2, contest.y0 - 2, FREE_CARD.w, FREE_CARD.h),
|
|
152
|
+
seat(contest.x1 + 2, contest.y1 + 2, FREE_CARD.w, FREE_CARD.h),
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
/** The plan of `count` mats side by side of a style, with its seats: the package's own judo area, so that the seats named are the seats drawn. */
|
|
156
|
+
export function judgingPlan(style, count = 1) {
|
|
157
|
+
const key = `${style}:${count}`;
|
|
158
|
+
const hit = plans.get(key);
|
|
120
159
|
if (hit !== undefined)
|
|
121
160
|
return hit;
|
|
122
161
|
seatsOf(style);
|
|
123
|
-
const plan = budoPlan({ sport: "judo", league: JUDGING_LEAGUE[style].league, budo: { style, officials: true } });
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
const plan = budoPlan({ sport: "judo", league: JUDGING_LEAGUE[style].league, budo: { style, officials: true, areas: count } });
|
|
163
|
+
const judo = plan.judo;
|
|
164
|
+
const mats = [];
|
|
165
|
+
for (let mi = 0; mi < count; mi += 1) {
|
|
166
|
+
const contest = judo.contests[mi];
|
|
167
|
+
if (style === "kata") {
|
|
168
|
+
const per = JUDGING_SEATS.kata.length;
|
|
169
|
+
const chairs = judo.judges.seats.slice(mi * per, (mi + 1) * per);
|
|
170
|
+
const table = judo.judges.tables[mi];
|
|
171
|
+
if (chairs.length !== per || table === undefined)
|
|
172
|
+
throw new Error("The kata plan has no judges' seats");
|
|
173
|
+
const pitch = (chairs[per - 1].x0 - chairs[0].x0) / (per - 1);
|
|
174
|
+
mats.push({ places: kataPlaces(chairs, table, pitch), table, bench: benchOf(chairs, table, pitch, contest.y0) });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const desk = judo.desks[mi];
|
|
178
|
+
if (desk === undefined)
|
|
179
|
+
throw new Error("The contest plan has no technical table");
|
|
180
|
+
const places = shiaiPlaces(contest, desk);
|
|
181
|
+
const atTable = places.slice(1, 4);
|
|
182
|
+
// The bench of a contest is its technical table with the three officials at it; the referee and the side judges are on the mat and at its corners, and are not in the picture of the bench.
|
|
183
|
+
const bench = { x0: Math.min(...atTable.map((p) => p.card.x0)) - 0.4, y0: atTable[0].chair.y0 - 0.9, x1: Math.max(...atTable.map((p) => p.card.x1)) + 0.4, y1: desk.y1 + 1.8 };
|
|
184
|
+
mats.push({ places, table: desk, bench });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const first = mats[0];
|
|
188
|
+
const chairs = first.places.map((p) => p.chair);
|
|
189
|
+
const pitch = style === "kata" ? (first.places[first.places.length - 1].chair.x0 - first.places[0].chair.x0) / (first.places.length - 1) : 1;
|
|
190
|
+
const extent = union([plan.extent, ...first.places.map((p) => p.card), ...first.places.map((p) => p.chair)]);
|
|
191
|
+
const built = { plan, mats, places: first.places, chairs, table: first.table, bench: first.bench, pitch, extent };
|
|
192
|
+
plans.set(key, built);
|
|
134
193
|
return built;
|
|
135
194
|
}
|
|
195
|
+
/** Whether a seat is in a box: its card's middle is inside it. A seat that is not is not drawn in a close-up of the bench, and its name is in the chart only. */
|
|
196
|
+
export function seatIn(place, box) {
|
|
197
|
+
const x = (place.card.x0 + place.card.x1) / 2;
|
|
198
|
+
const y = (place.card.y0 + place.card.y1) / 2;
|
|
199
|
+
return x >= box.x0 && x <= box.x1 && y >= box.y0 && y <= box.y1;
|
|
200
|
+
}
|
package/dist/judging.turn.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const JUDGING_HEIGHTS: Readonly<{
|
|
|
9
9
|
card: 0.95;
|
|
10
10
|
sign: 0.7;
|
|
11
11
|
signAbove: 0.25;
|
|
12
|
+
post: 1;
|
|
12
13
|
}>;
|
|
13
14
|
/**
|
|
14
15
|
* 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
|
package/dist/judging.turn.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
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
1
|
import { drawKyogijo } from "./draw.js";
|
|
6
2
|
import { hash } from "./format.js";
|
|
7
3
|
import { FEET_PER_METRE as FT } from "./geometry.js";
|
|
8
4
|
import { TURN_CARD, badge, card } from "./judging.card.js";
|
|
9
5
|
import { JUDGING_LEAGUE } from "./judging.data.js";
|
|
10
6
|
import { fitCard, fitSeats } from "./judging.fit.js";
|
|
11
|
-
import { benchOf, judgingSetting } from "./judging.js";
|
|
7
|
+
import { benchOf, judgingPlan, judgingSetting, seatIn } from "./judging.js";
|
|
12
8
|
import { judgingDrawing, judgingText } from "./judging.draw.js";
|
|
13
9
|
import { JUDGING_WORDS } from "./judging.words.js";
|
|
14
10
|
import { mountTurn } from "./turn.dom.js";
|
|
@@ -18,9 +14,18 @@ import { WORLD_PX, openingView, sceneBox } from "./turn.model.js";
|
|
|
18
14
|
import { box, n, plane, pole, rect, thinnest } from "./turn.kit.js";
|
|
19
15
|
import { planesOf } from "./uprights.js";
|
|
20
16
|
/** 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 });
|
|
17
|
+
export const JUDGING_HEIGHTS = Object.freeze({ table: 0.75, chair: 0.45, card: 0.95, sign: 0.7, signAbove: 0.25, post: 1.0 });
|
|
22
18
|
/** 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
19
|
const BENCHES_LENS = 60;
|
|
20
|
+
/** The bench the 3D view is fitted to when it is close up: a kata's seats at their tables, a contest's technical tables and referees, all mats in one box, reaching as little of the floor in front as will do. */
|
|
21
|
+
function benchNear(style, geom, count) {
|
|
22
|
+
if (style === "shiai") {
|
|
23
|
+
const b = geom.mats.slice(0, count).map((m) => m.bench);
|
|
24
|
+
return { x0: Math.min(...b.map((x) => x.x0)), y0: Math.min(...b.map((x) => x.y0)), x1: Math.max(...b.map((x) => x.x1)), y1: Math.max(...b.map((x) => x.y1)) };
|
|
25
|
+
}
|
|
26
|
+
const chairs = geom.mats.slice(0, count).flatMap((m) => m.places.map((p) => p.chair));
|
|
27
|
+
return benchOf(chairs, geom.table, geom.pitch, geom.plan.judo.contests[0].y0, 2.2);
|
|
28
|
+
}
|
|
24
29
|
const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
25
30
|
/**
|
|
26
31
|
* 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
|
|
@@ -44,62 +49,74 @@ export function buildJudgingTurn(options, settings = {}) {
|
|
|
44
49
|
if (found === null)
|
|
45
50
|
throw new Error("the floor of a 3D view must be an SVG with a viewBox");
|
|
46
51
|
const [vx, vy, vw, vh] = found.slice(1).map(Number);
|
|
47
|
-
const
|
|
48
|
-
const judges = plan.judo.judges;
|
|
49
|
-
const seatsPerMat = setting.mats[0].seats.length;
|
|
52
|
+
const geom = judgingPlan(setting.style, count);
|
|
50
53
|
// 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
54
|
const ft = (m) => m * FT;
|
|
52
55
|
const H = JUDGING_HEIGHTS;
|
|
53
56
|
const items = [];
|
|
54
57
|
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
58
|
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
59
|
const minName = 0.62;
|
|
60
|
+
// Close up on the bench, only the seats at it stand (the referee and the side judges of a contest are out of the picture, and in the chart).
|
|
61
|
+
const near = setting.focus === "bench" ? benchNear(setting.style, geom, count) : undefined;
|
|
63
62
|
setting.mats.forEach((mat, mi) => {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
63
|
+
const places = geom.mats[mi];
|
|
64
|
+
const table = places.table;
|
|
65
|
+
const boxes = mat.seats.map((a) => {
|
|
66
|
+
const c = places.places[a.seat.n - 1].card;
|
|
67
|
+
const w = ft(c.x1 - c.x0);
|
|
68
|
+
const pad = Math.max(0.08, w * 0.05);
|
|
69
|
+
const r = clamp(w * 0.2, 0.2, 0.45);
|
|
70
|
+
return { w, pad, r, width: w - 2 * pad, height: cardH - 2 * pad - r, max: Math.min(w * 0.3, 0.75) };
|
|
71
|
+
});
|
|
72
|
+
const fitted = fitSeats(mat.seats, boxes, { min: minName, idLabel: words.chart.idLabel });
|
|
66
73
|
const furniture = [];
|
|
67
74
|
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
75
|
const cards = [];
|
|
76
|
+
const stands = [];
|
|
69
77
|
mat.seats.forEach((a, si) => {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
78
|
+
const place = places.places[a.seat.n - 1];
|
|
79
|
+
if (near !== undefined && !seatIn(place, near))
|
|
80
|
+
return;
|
|
81
|
+
const b = boxes[si];
|
|
82
|
+
// A chair for the seats that have one (the referee stands on the mat).
|
|
83
|
+
if (a.seat.role !== "referee")
|
|
84
|
+
furniture.push(...box(`m${mat.n}-chair-${a.seat.n}`, "chair", rect(ft(place.chair.x0), ft(place.chair.y0), ft(place.chair.x1 - place.chair.x0), ft(place.chair.y1 - place.chair.y0)), ft(H.chair), "u-bench"));
|
|
85
|
+
const cx = ft((place.card.x0 + place.card.x1) / 2);
|
|
86
|
+
// A card at a table stands on the table; any other on a post, at the height of a standing official's chest.
|
|
87
|
+
const atTable = a.seat.where === "table";
|
|
88
|
+
const cy = atTable ? ft((table.y0 + table.y1) / 2) : ft((place.card.y0 + place.card.y1) / 2);
|
|
89
|
+
const base = atTable ? tableTop : ft(H.post);
|
|
90
|
+
if (!atTable)
|
|
91
|
+
stands.push(...pole(`m${mat.n}-post-${a.seat.n}`, "pole", cx, cy, 0, base, ft(0.06), "u-pole"));
|
|
92
|
+
const fit = setting.seatNames ? fitted[si] : undefined;
|
|
75
93
|
if (fit !== undefined) {
|
|
76
|
-
const p = plane(`m${mat.n}-card-${a.seat.n}`, "card", [cx -
|
|
77
|
-
cards.push({ ...p, billboard: true });
|
|
94
|
+
const p = plane(`m${mat.n}-card-${a.seat.n}`, "card", [cx - b.w / 2, cy, base + cardH], [b.w, 0, 0], [0, 0, -cardH], card(TURN_CARD, fit, a.seat.n, 0, 0, b.w, cardH, b.pad, b.r, 0.1));
|
|
95
|
+
cards.push({ ...p, billboard: true, lean: true });
|
|
78
96
|
}
|
|
79
97
|
else {
|
|
80
|
-
//
|
|
81
|
-
const br = 0.
|
|
82
|
-
const p = plane(`m${mat.n}-badge-${a.seat.n}`, "card", [cx - br, cy,
|
|
83
|
-
cards.push({ ...p, billboard: true });
|
|
98
|
+
// The names are not on the seats, or this one does not fit a card: the seat's number alone, on a round badge like the chart's, with the name in the chart.
|
|
99
|
+
const br = 0.9;
|
|
100
|
+
const p = plane(`m${mat.n}-badge-${a.seat.n}`, "card", [cx - br, cy, base + 2 * br], [2 * br, 0, 0], [0, 0, -2 * br], badge(TURN_CARD, br, br, br * 0.92, a.seat.n, a.assigned));
|
|
101
|
+
cards.push({ ...p, billboard: true, lean: true });
|
|
84
102
|
}
|
|
85
103
|
});
|
|
86
|
-
// The mat's name on a sign over the whole
|
|
104
|
+
// The mat's name on a sign over the whole table, on a pole at each end of it.
|
|
87
105
|
const midX = ft((table.x0 + table.x1) / 2);
|
|
88
106
|
const midY = ft((table.y0 + table.y1) / 2);
|
|
89
|
-
const signW = ft(
|
|
107
|
+
const signW = ft(table.x1 - table.x0);
|
|
90
108
|
const signH = ft(H.sign);
|
|
91
109
|
const signBottom = tableTop + cardH + ft(H.signAbove);
|
|
92
110
|
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
111
|
const lines = text?.name ?? [mat.label];
|
|
94
112
|
const size = text?.nameSize ?? signH * 0.3;
|
|
95
113
|
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 };
|
|
114
|
+
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, lean: true };
|
|
97
115
|
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
116
|
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] });
|
|
117
|
+
items.push({ id: `m${mat.n}-judges`, kind: "judges", at: [midX, midY], height: tableTop + cardH, planes: [...cards, ...stands, ...poles, sign] });
|
|
100
118
|
});
|
|
101
|
-
// The view is fitted to the whole floor, or to the
|
|
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;
|
|
119
|
+
// The view is fitted to the whole floor, or to the officials' bench close up (the scene then turns about the bench, so that the cards stay where they are read).
|
|
103
120
|
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
121
|
const uprights = { items, lift: 0, thin: thinnest(vw, vh), area, whole: true };
|
|
105
122
|
const planes = planesOf(uprights);
|
|
@@ -113,8 +130,8 @@ export function buildJudgingTurn(options, settings = {}) {
|
|
|
113
130
|
language,
|
|
114
131
|
options: surfaceLike,
|
|
115
132
|
title,
|
|
116
|
-
description: `${description}${language === "ja" ? "" : " "}${words.turn.standing}`,
|
|
117
|
-
standing: words.turn.standing,
|
|
133
|
+
description: `${description}${language === "ja" ? "" : " "}${setting.seatNames ? words.turn.standing : words.turn.standingBadges}`,
|
|
134
|
+
standing: setting.seatNames ? words.turn.standing : words.turn.standingBadges,
|
|
118
135
|
floor,
|
|
119
136
|
viewBox: { x: vx, y: vy, w: vw, h: vh },
|
|
120
137
|
scale,
|
package/dist/judging.types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Language } from "./types.ts";
|
|
2
|
-
/** The style a judo area is
|
|
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"`
|
|
2
|
+
/** The style a judo area is officiated in: `kata` has five judges; `shiai`, the contest, a referee on the mat and the officials at the technical table. */
|
|
3
|
+
export type JudgingStyle = "kata" | "shiai";
|
|
4
|
+
/** One official: the seat, and who sits in it. The seat is its number (`3`, `"3"`), its key (`"judge-3"`, `"referee"`, `"timekeeper"`) or a word (`"central"` for the middle seat of a kata area). */
|
|
5
5
|
export interface JudgingOfficial {
|
|
6
6
|
seat: string | number;
|
|
7
7
|
/** The name as it is to be read, at most 120 characters; a seat with no name is shown as not assigned. */
|
|
@@ -26,6 +26,13 @@ export interface JudgingOptions {
|
|
|
26
26
|
mat?: number;
|
|
27
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
28
|
chart?: "auto" | "always" | "never";
|
|
29
|
+
/** Shiai: draw the two side judges' seats too (the IJF's rules for an event with no CARE system use them; its other events have none), as not assigned where nobody is named. A seat a mat names is drawn whatever this says. Off by default. */
|
|
30
|
+
sideJudges?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Put the names on the seats as well as in the chart. Off by default: a seat then shows its number alone, on the round badge the chart's rows have, and the chart is the key. On, a name is set in the chart's own face, scaled
|
|
33
|
+
* evenly (never stretched or condensed), in the biggest size that fits its card and no smaller than a readable one; a seat whose name does not fit shows its number alone.
|
|
34
|
+
*/
|
|
35
|
+
seatNames?: boolean;
|
|
29
36
|
/** Where the chart is laid: beside the plan (`landscape`, the default) or under it (`portrait`). */
|
|
30
37
|
orientation?: "landscape" | "portrait";
|
|
31
38
|
/** 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. */
|
|
@@ -46,10 +53,14 @@ export interface JudgingOptions {
|
|
|
46
53
|
export interface JudgingSeat {
|
|
47
54
|
/** The key: `judge-3`. */
|
|
48
55
|
id: string;
|
|
49
|
-
/** The number, from 1, in the order the seats are
|
|
56
|
+
/** The number, from 1, in the order the seats are listed (a kata area's from the left as the plan lies, the shomen at the top). */
|
|
50
57
|
n: number;
|
|
51
|
-
/** `central`
|
|
52
|
-
role: "judge" | "central";
|
|
58
|
+
/** What the seat is: a kata area's judges (`central` the middle one), or a contest's referee, side judges, timekeeper, scorekeeper and referee supervisor. */
|
|
59
|
+
role: "judge" | "central" | "referee" | "side-judge" | "timekeeper" | "scorekeeper" | "supervisor";
|
|
60
|
+
/** Where it is: at the judges' table of a kata area or the technical table of a contest (`table`), on the mat (`mat`: the referee) or at a corner of the area (`corner`: a side judge). */
|
|
61
|
+
where: "table" | "mat" | "corner";
|
|
62
|
+
/** True for a seat that is drawn only when asked for or named (the side judges). */
|
|
63
|
+
optional?: true;
|
|
53
64
|
label: {
|
|
54
65
|
en: string;
|
|
55
66
|
ja: string;
|
package/dist/judging.words.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface JudgingWords {
|
|
|
5
5
|
/** What a style is called on the plan, `{count}` the number of seats. */
|
|
6
6
|
styleName: {
|
|
7
7
|
kata: string;
|
|
8
|
+
shiai: string;
|
|
8
9
|
};
|
|
9
10
|
/** The chart: its heading, its columns, the word before an ID and what a seat with nobody in it says. */
|
|
10
11
|
chart: {
|
|
@@ -39,6 +40,7 @@ export interface JudgingWords {
|
|
|
39
40
|
/** What the 3D view says of the seats' name cards. */
|
|
40
41
|
turn: {
|
|
41
42
|
standing: string;
|
|
43
|
+
standingBadges: string;
|
|
42
44
|
board: string;
|
|
43
45
|
};
|
|
44
46
|
}
|
package/dist/judging.words.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const JUDGING_WORDS = {
|
|
2
2
|
en: {
|
|
3
3
|
mat: "Mat {n}",
|
|
4
|
-
styleName: { kata: "Kata, {count} judges" },
|
|
4
|
+
styleName: { kata: "Kata, {count} judges", shiai: "Shiai, {count} officials" },
|
|
5
5
|
chart: { heading: "Judging assignments", seat: "Seat", name: "Name", id: "ID", idLabel: "ID", vacant: "Not assigned" },
|
|
6
6
|
say: {
|
|
7
7
|
title: "Judging assignments: {style}, {mats}",
|
|
@@ -16,11 +16,11 @@ export const JUDGING_WORDS = {
|
|
|
16
16
|
join: "; ",
|
|
17
17
|
},
|
|
18
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" },
|
|
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.", standingBadges: "A numbered badge stands at each judge's seat, leaning back to face the eye, and the name of each mat stands over its judges' table.", board: "Mat" },
|
|
20
20
|
},
|
|
21
21
|
ja: {
|
|
22
22
|
mat: "第{n}試合場",
|
|
23
|
-
styleName: { kata: "形({count}人制)" },
|
|
23
|
+
styleName: { kata: "形({count}人制)", shiai: "試合({count}人体制)" },
|
|
24
24
|
chart: { heading: "審判員の配置", seat: "席", name: "氏名", id: "ID", idLabel: "ID", vacant: "未定" },
|
|
25
25
|
say: {
|
|
26
26
|
title: "審判員の配置:{style}、{mats}",
|
|
@@ -35,6 +35,6 @@ export const JUDGING_WORDS = {
|
|
|
35
35
|
join: "、",
|
|
36
36
|
},
|
|
37
37
|
table: { caption: "{title}", mat: "試合場", seat: "席", name: "氏名", id: "ID" },
|
|
38
|
-
turn: { standing: "各審判員の席に名札を置き、見ている方向に向くようにしています。各試合場の名前は審判員席の上に掲げています。", board: "試合場" },
|
|
38
|
+
turn: { standing: "各審判員の席に名札を置き、見ている方向に向くようにしています。各試合場の名前は審判員席の上に掲げています。", standingBadges: "各審判員の席に番号の丸い札を置き、見ている方向に向くようにしています。各試合場の名前は審判員席の上に掲げています。", board: "試合場" },
|
|
39
39
|
},
|
|
40
40
|
};
|
package/dist/surfaces/budo.d.ts
CHANGED
|
@@ -3,4 +3,6 @@ import { type BuildOptions, type Surface } from "./surface.ts";
|
|
|
3
3
|
/** Feet in a metre. */
|
|
4
4
|
export declare const K: number;
|
|
5
5
|
/** The surface of the plan lying wide. `margin` is the ground round what is drawn, in feet. */
|
|
6
|
+
/** The least width a tape is drawn at, as a share of the plan's width (0.75 %: about 4 pixels on a plan 560 pixels across), unless `exactLines` is set. */
|
|
7
|
+
export declare const TAPE_LEAST = 0.0075;
|
|
6
8
|
export declare function budoSurface(o: BuildOptions, plan: BudoPlan, margin: number): Surface;
|
package/dist/surfaces/budo.js
CHANGED
|
@@ -14,6 +14,18 @@ function rangeOf(r, ox, oy) {
|
|
|
14
14
|
return "left" in r ? atEnds((end) => feetBox(end === "left" ? r.left : r.right, ox, oy)) : feetBox(r, ox, oy);
|
|
15
15
|
}
|
|
16
16
|
/** The surface of the plan lying wide. `margin` is the ground round what is drawn, in feet. */
|
|
17
|
+
/** The least width a tape is drawn at, as a share of the plan's width (0.75 %: about 4 pixels on a plan 560 pixels across), unless `exactLines` is set. */
|
|
18
|
+
export const TAPE_LEAST = 0.0075;
|
|
19
|
+
/** A bar widened across its shorter side to at least `least` metres, evenly about its middle; a bar that is wide enough is the same bar. */
|
|
20
|
+
function widen(bar, least) {
|
|
21
|
+
const w = bar.x1 - bar.x0;
|
|
22
|
+
const h = bar.y1 - bar.y0;
|
|
23
|
+
if (Math.min(w, h) >= least)
|
|
24
|
+
return bar;
|
|
25
|
+
if (w < h)
|
|
26
|
+
return { ...bar, x0: (bar.x0 + bar.x1) / 2 - least / 2, x1: (bar.x0 + bar.x1) / 2 + least / 2 };
|
|
27
|
+
return { ...bar, y0: (bar.y0 + bar.y1) / 2 - least / 2, y1: (bar.y0 + bar.y1) / 2 + least / 2 };
|
|
28
|
+
}
|
|
17
29
|
export function budoSurface(o, plan, margin) {
|
|
18
30
|
const ox = plan.box.x0;
|
|
19
31
|
const oy = plan.box.y0;
|
|
@@ -81,7 +93,11 @@ export function budoSurface(o, plan, margin) {
|
|
|
81
93
|
const thick = o.exact ? plan.lineWidth : plan.drawnLine;
|
|
82
94
|
// The seams are the plan's finest lines: left out of a drawing that asks for the simple detail.
|
|
83
95
|
const shown = full ? plan : { ...plan, shapes: plan.shapes.filter((s) => s.feature !== "seams" && s.feature !== "boards") };
|
|
84
|
-
|
|
96
|
+
// A tape is 5 cm to 6 cm wide: at the size of a drawing that is a hairline. Unless the lines are asked for at their true widths (`exactLines`), a tape is drawn at least `TAPE_LEAST` of the plan's width across (a readability floor,
|
|
97
|
+
// not a measurement: its length is always the rule's), widened evenly about its middle.
|
|
98
|
+
const floor = (plan.box.x1 - plan.box.x0) * TAPE_LEAST;
|
|
99
|
+
const taped = o.exact ? shown : { ...shown, shapes: shown.shapes.map((s) => (s.kind === "bar" ? widen(s, floor) : s)) };
|
|
100
|
+
const lines = group(strokes(taped, o) + marks(taped, o), "ky-bud-lines");
|
|
85
101
|
const W = (plan.box.x1 - plan.box.x0) * K;
|
|
86
102
|
const H = (plan.box.y1 - plan.box.y0) * K;
|
|
87
103
|
const cx = (plan.centre.x - ox) * K;
|
package/dist/turn.kit.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface Plane {
|
|
|
16
16
|
* place as it lies at a yaw of 0; `v` must be vertical.
|
|
17
17
|
*/
|
|
18
18
|
billboard?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* A billboard that also leans back to face the eye squarely, as the pitch tips the view: a card that stands upright is seen foreshortened from above, and its type looks squeezed, so a plane that carries type leans
|
|
21
|
+
* till its face is square to the eye. It is turned only (never stretched): its width and height stay what they are. At an overhead view it lies flat. `v` must be vertical, as for a billboard.
|
|
22
|
+
*/
|
|
23
|
+
lean?: boolean;
|
|
19
24
|
/**
|
|
20
25
|
* A wall round the surface that gives way when it is between the eye and what it surrounds: the horizontal direction (x, y, a unit vector) that points from the surface out through the
|
|
21
26
|
* wall. The nearer the eye is to that side, and the lower it is, the more the wall is cut away (see `fadeOf`), so that the court is never hidden behind its own glass.
|
|
@@ -56,7 +61,7 @@ export declare const cross: (a: V3, b: V3) => V3;
|
|
|
56
61
|
/** A number for markup: millimetres of a foot, no more. */
|
|
57
62
|
export declare const n: (value: number) => string;
|
|
58
63
|
/** A billboard turned to face an eye whose view is turned `yaw` degrees: its width runs along the screen's x, which in the scene is (cos yaw, -sin yaw, 0). */
|
|
59
|
-
export declare function facing(p: Plane, yaw: number): Plane;
|
|
64
|
+
export declare function facing(p: Plane, yaw: number, pitch?: number): Plane;
|
|
60
65
|
/** A plane. */
|
|
61
66
|
export declare const plane: (id: string, kind: PartKind, o: V3, u: V3, v: V3, svg: string) => Plane;
|
|
62
67
|
/**
|
package/dist/turn.kit.js
CHANGED
|
@@ -18,14 +18,18 @@ export const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] *
|
|
|
18
18
|
/** A number for markup: millimetres of a foot, no more. */
|
|
19
19
|
export const n = (value) => String(Math.round(value * 1000) / 1000);
|
|
20
20
|
/** A billboard turned to face an eye whose view is turned `yaw` degrees: its width runs along the screen's x, which in the scene is (cos yaw, -sin yaw, 0). */
|
|
21
|
-
export function facing(p, yaw) {
|
|
21
|
+
export function facing(p, yaw, pitch = 0) {
|
|
22
22
|
if (p.billboard !== true)
|
|
23
23
|
return p;
|
|
24
24
|
const [w, h] = size(p);
|
|
25
25
|
const a = (yaw * Math.PI) / 180;
|
|
26
26
|
const u = [w * Math.cos(a), -w * Math.sin(a), 0];
|
|
27
27
|
const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2, p.o[2] + (p.u[2] + p.v[2]) / 2];
|
|
28
|
-
|
|
28
|
+
// A plane that leans has its height along the screen's vertical, which at a pitch tips back from the vertical: (cos p sin yaw, cos p cos yaw, -sin p) is the way down the screen, in the scene.
|
|
29
|
+
const b = (pitch * Math.PI) / 180;
|
|
30
|
+
const down = p.lean === true ? [Math.cos(b) * Math.sin(a), Math.cos(b) * Math.cos(a), -Math.sin(b)] : [0, 0, -1];
|
|
31
|
+
const sign = p.v[2] < 0 ? 1 : -1;
|
|
32
|
+
const v = [down[0] * h * sign, down[1] * h * sign, down[2] * h * sign];
|
|
29
33
|
return { ...p, o: [middle[0] - (u[0] + v[0]) / 2, middle[1] - (u[1] + v[1]) / 2, middle[2] - (u[2] + v[2]) / 2], u, v };
|
|
30
34
|
}
|
|
31
35
|
/** A plane. */
|
package/dist/turn.markup.js
CHANGED
|
@@ -27,7 +27,7 @@ export function layoutOf(model, view, width, height, lens) {
|
|
|
27
27
|
const centre = model.centre;
|
|
28
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
29
|
// A billboard faces the eye at this view.
|
|
30
|
-
const planes = model.planes.map((p) => facing(p, view.yaw));
|
|
30
|
+
const planes = model.planes.map((p) => facing(p, view.yaw, view.pitch));
|
|
31
31
|
const transforms = [floor, ...planes].map((p) => matrix3d(multiply(camera, planeMatrix(p, s, centre))));
|
|
32
32
|
// Far to near: a plane's depth is that of its middle. The ground of a hall is under everything. The floor is next, unless it stands raised on something (a table tennis table's top, on its legs): then it is
|
|
33
33
|
// sorted with the rest, by its middle, so that the legs and the skirt on the far side are drawn under it and the ones on the near side over it, and nothing under the top shows through it.
|
package/dist/turn.model.js
CHANGED
|
@@ -42,7 +42,7 @@ export function sceneBox(planes, area, lift, centre, scale) {
|
|
|
42
42
|
let top = lift;
|
|
43
43
|
for (const p of planes) {
|
|
44
44
|
// A billboard turns about its middle: whichever way it faces, it lies within a circle as wide as it is.
|
|
45
|
-
const reach = p.billboard === true ? Math.hypot(...p.u) / 2 : 0;
|
|
45
|
+
const reach = p.billboard === true ? (p.lean === true ? Math.hypot(...p.u, ...p.v) / 2 : Math.hypot(...p.u) / 2) : 0;
|
|
46
46
|
for (const [a, b] of [[0, 0], [1, 0], [0, 1], [1, 1]]) {
|
|
47
47
|
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]];
|
|
48
48
|
if (reach > 0) {
|
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.
|
|
2
|
+
export declare const VERSION = "0.18.1";
|
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.
|
|
2
|
+
export const VERSION = "0.18.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kyogijo-stadiums",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Kyogijo Stadiums 競技場, 39 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, 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",
|