@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/NOTICE.md +4 -0
- package/README.md +71 -16
- package/dist/annotate.js +1 -1
- package/dist/athletics/describe.d.ts +8 -0
- package/dist/athletics/describe.js +41 -0
- package/dist/athletics/events.d.ts +17 -0
- package/dist/athletics/events.js +48 -0
- package/dist/athletics/facilities.d.ts +80 -0
- package/dist/athletics/facilities.js +176 -0
- package/dist/athletics/infield.d.ts +79 -0
- package/dist/athletics/infield.js +166 -0
- package/dist/athletics/layout.d.ts +13 -0
- package/dist/athletics/layout.js +98 -0
- package/dist/athletics/oval.d.ts +57 -0
- package/dist/athletics/oval.js +116 -0
- package/dist/athletics/plan.d.ts +97 -0
- package/dist/athletics/plan.js +355 -0
- package/dist/athletics/races.d.ts +102 -0
- package/dist/athletics/races.js +355 -0
- package/dist/athletics/steeplechase.d.ts +72 -0
- package/dist/athletics/steeplechase.js +196 -0
- package/dist/athletics/stencil.d.ts +13 -0
- package/dist/athletics/stencil.js +85 -0
- package/dist/athletics/track.d.ts +54 -0
- package/dist/athletics/track.js +105 -0
- package/dist/athletics/types.d.ts +77 -0
- package/dist/athletics/types.js +1 -0
- package/dist/athletics-entry.d.ts +17 -0
- package/dist/athletics-entry.js +11 -0
- package/dist/athletics.style.d.ts +16 -0
- package/dist/athletics.style.js +51 -0
- package/dist/atlas-entry.d.ts +4 -2
- package/dist/atlas-entry.js +3 -2
- package/dist/atlas.d.ts +83 -7
- package/dist/atlas.js +108 -8
- package/dist/charts-entry.d.ts +6 -1
- package/dist/charts-entry.js +3 -0
- package/dist/charts.athletics.d.ts +183 -0
- package/dist/charts.athletics.js +455 -0
- package/dist/charts.curling.heat.d.ts +24 -0
- package/dist/charts.curling.heat.js +116 -0
- package/dist/charts.curling.js +14 -3
- package/dist/charts.pool.d.ts +67 -0
- package/dist/charts.pool.js +76 -0
- package/dist/charts.skating.d.ts +204 -0
- package/dist/charts.skating.js +426 -0
- package/dist/charts.types.d.ts +53 -1
- package/dist/court.d.ts +85 -2
- package/dist/court.js +102 -7
- package/dist/court.more.d.ts +46 -0
- package/dist/court.more.js +219 -0
- package/dist/courts.style.d.ts +14 -2
- package/dist/courts.style.js +51 -4
- package/dist/crowd.d.ts +154 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +198 -0
- package/dist/crowd.js +465 -0
- package/dist/crowd.park.d.ts +19 -0
- package/dist/crowd.park.js +153 -0
- package/dist/curling.stats.d.ts +271 -0
- package/dist/curling.stats.js +301 -0
- package/dist/draw.d.ts +16 -0
- package/dist/draw.js +318 -35
- package/dist/element-entry.d.ts +1 -1
- package/dist/element-entry.js +1 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +77 -3
- package/dist/features.js +131 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +18 -4
- package/dist/frames.providers.js +63 -8
- package/dist/geojson.js +3 -0
- package/dist/geometry-entry.d.ts +2 -1
- package/dist/geometry-entry.js +2 -1
- package/dist/geometry.d.ts +20 -0
- package/dist/geometry.js +78 -1
- package/dist/grounds.d.ts +4 -0
- package/dist/grounds.data.js +12 -0
- package/dist/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +4 -3
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +2 -2
- package/dist/heat.zones.js +154 -6
- package/dist/homeside.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +10 -1
- package/dist/layers.d.ts +1 -1
- package/dist/layers.env.d.ts +3 -1
- package/dist/layers.env.js +4 -2
- package/dist/layers.js +4 -4
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +20 -0
- package/dist/leagues/types.d.ts +1 -1
- package/dist/leagues-entry.d.ts +4 -2
- package/dist/leagues-entry.js +2 -1
- package/dist/leagues.d.ts +14 -4
- package/dist/leagues.js +146 -5
- package/dist/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +30 -2
- package/dist/pageant.d.ts +120 -0
- package/dist/pageant.draw.d.ts +21 -0
- package/dist/pageant.draw.js +171 -0
- package/dist/pageant.js +207 -0
- package/dist/pageant.style.d.ts +8 -0
- package/dist/pageant.style.js +42 -0
- package/dist/png.d.ts +8 -1
- package/dist/png.js +21 -1
- package/dist/pool.d.ts +176 -0
- package/dist/pool.figures.d.ts +304 -0
- package/dist/pool.figures.js +144 -0
- package/dist/pool.js +304 -0
- package/dist/pool.kit.d.ts +48 -0
- package/dist/pool.kit.js +141 -0
- package/dist/pool.labels.d.ts +20 -0
- package/dist/pool.labels.js +257 -0
- package/dist/pool.race.d.ts +65 -0
- package/dist/pool.race.js +140 -0
- package/dist/pool.replay.d.ts +46 -0
- package/dist/pool.replay.js +127 -0
- package/dist/pool.section.d.ts +13 -0
- package/dist/pool.section.js +83 -0
- package/dist/pool.style.d.ts +20 -0
- package/dist/pool.style.js +169 -0
- package/dist/pool.types.d.ts +145 -0
- package/dist/pool.types.js +3 -0
- package/dist/pool.words.d.ts +13 -0
- package/dist/pool.words.js +101 -0
- package/dist/ranges.names.js +47 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- package/dist/score.js +70 -5
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- package/dist/skating/describe.d.ts +7 -0
- package/dist/skating/describe.js +44 -0
- package/dist/skating/events.d.ts +24 -0
- package/dist/skating/events.js +47 -0
- package/dist/skating/figure.d.ts +24 -0
- package/dist/skating/figure.js +37 -0
- package/dist/skating/long.d.ts +54 -0
- package/dist/skating/long.js +111 -0
- package/dist/skating/parts.d.ts +43 -0
- package/dist/skating/parts.js +40 -0
- package/dist/skating/plan.d.ts +55 -0
- package/dist/skating/plan.js +197 -0
- package/dist/skating/short.d.ts +47 -0
- package/dist/skating/short.js +89 -0
- package/dist/skating/track.d.ts +66 -0
- package/dist/skating/track.js +94 -0
- package/dist/skating/types.d.ts +57 -0
- package/dist/skating/types.js +3 -0
- package/dist/skating-entry.d.ts +17 -0
- package/dist/skating-entry.js +11 -0
- package/dist/skating.style.d.ts +4 -0
- package/dist/skating.style.js +24 -0
- package/dist/strings.d.ts +296 -0
- package/dist/strings.js +328 -11
- package/dist/style.d.ts +2 -0
- package/dist/style.js +11 -1
- package/dist/surfaces/artistic.d.ts +9 -0
- package/dist/surfaces/artistic.js +94 -0
- package/dist/surfaces/athletics.d.ts +16 -0
- package/dist/surfaces/athletics.js +132 -0
- package/dist/surfaces/baseball.js +15 -0
- package/dist/surfaces/basketball.floor.d.ts +27 -0
- package/dist/surfaces/basketball.floor.js +83 -0
- package/dist/surfaces/basketball.gear.d.ts +27 -0
- package/dist/surfaces/basketball.gear.js +90 -0
- package/dist/surfaces/basketball.js +43 -21
- package/dist/surfaces/curling.d.ts +16 -0
- package/dist/surfaces/curling.fold.d.ts +67 -0
- package/dist/surfaces/curling.fold.js +107 -0
- package/dist/surfaces/curling.js +37 -3
- package/dist/surfaces/dive.d.ts +9 -0
- package/dist/surfaces/dive.js +128 -0
- package/dist/surfaces/field.d.ts +7 -2
- package/dist/surfaces/field.js +15 -9
- package/dist/surfaces/hockey.js +1 -0
- package/dist/surfaces/net.d.ts +2 -1
- package/dist/surfaces/net.js +160 -15
- package/dist/surfaces/polo.d.ts +12 -0
- package/dist/surfaces/polo.js +159 -0
- package/dist/surfaces/pool.d.ts +27 -0
- package/dist/surfaces/pool.js +198 -0
- package/dist/surfaces/skating.d.ts +6 -0
- package/dist/surfaces/skating.js +62 -0
- package/dist/surfaces/soccer.js +2 -1
- package/dist/surfaces/surface.d.ts +44 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- package/dist/tenants.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +945 -0
- package/dist/tenants.js +46 -0
- package/dist/turn-entry.d.ts +12 -0
- package/dist/turn-entry.js +7 -0
- package/dist/turn.camera.d.ts +105 -0
- package/dist/turn.camera.js +168 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +351 -0
- package/dist/turn.kit.d.ts +86 -0
- package/dist/turn.kit.js +110 -0
- package/dist/turn.markup.d.ts +48 -0
- package/dist/turn.markup.js +108 -0
- package/dist/turn.model.d.ts +78 -0
- package/dist/turn.model.js +91 -0
- package/dist/turn.style.d.ts +5 -0
- package/dist/turn.style.js +55 -0
- package/dist/types.d.ts +183 -18
- package/dist/units.d.ts +1 -1
- package/dist/units.js +15 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -0
- package/dist/uprights.d.ts +37 -0
- package/dist/uprights.fields.d.ts +32 -0
- package/dist/uprights.fields.js +172 -0
- package/dist/uprights.goal.d.ts +49 -0
- package/dist/uprights.goal.js +69 -0
- package/dist/uprights.js +160 -0
- package/dist/uprights.more.d.ts +7 -0
- package/dist/uprights.more.js +183 -0
- package/dist/uprights.net.d.ts +12 -0
- package/dist/uprights.net.js +109 -0
- package/dist/uprights.netkit.d.ts +28 -0
- package/dist/uprights.netkit.js +67 -0
- package/dist/uprights.pools.d.ts +8 -0
- package/dist/uprights.pools.js +190 -0
- package/dist/uprights.rinks.d.ts +27 -0
- package/dist/uprights.rinks.js +106 -0
- package/dist/uprights.track.d.ts +50 -0
- package/dist/uprights.track.js +246 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +19 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Area, Shape } from "../fields.types.ts";
|
|
2
|
+
import { ISU_FIGURE_RINK, ISU_LONG_TRACK, ISU_SHORT_TRACK, type SkatingFigures, type SkatingLeague } from "../leagues/skating.ts";
|
|
3
|
+
import type { SurfaceOptions } from "../types.ts";
|
|
4
|
+
import { LONG_TRACK_RACES, SHORT_TRACK_RACES } from "./events.ts";
|
|
5
|
+
import { type FigureRink } from "./figure.ts";
|
|
6
|
+
import { type RaceMarks } from "./long.ts";
|
|
7
|
+
import { type MBox, type MMark, type MRange } from "./parts.ts";
|
|
8
|
+
import { type ShortOval } from "./short.ts";
|
|
9
|
+
import { type LongOval } from "./track.ts";
|
|
10
|
+
import type { ShortTrackRace, SkatingDiscipline, SkatingFurniture, SkatingRace, SkatingStyle } from "./types.ts";
|
|
11
|
+
export type { MBox, MMark, MRange };
|
|
12
|
+
/** What a skating drawing is. */
|
|
13
|
+
export interface SkatingPlan {
|
|
14
|
+
sport: "skating";
|
|
15
|
+
league: string;
|
|
16
|
+
discipline: SkatingDiscipline;
|
|
17
|
+
figures: SkatingFigures;
|
|
18
|
+
/** The oval of the long track, or `undefined`. */
|
|
19
|
+
oval: LongOval | undefined;
|
|
20
|
+
/** The short track's oval and rink, or `undefined`. */
|
|
21
|
+
short: ShortOval | undefined;
|
|
22
|
+
/** The figure skating rink, or `undefined`. */
|
|
23
|
+
rink: FigureRink | undefined;
|
|
24
|
+
/** The playing surface: the ice out to the padding (the long track), the rink (the others). The data frame's own box. */
|
|
25
|
+
box: MBox;
|
|
26
|
+
/** Everything drawn: the box and what lies beyond it (the furniture of a figure skating rink). */
|
|
27
|
+
extent: MBox;
|
|
28
|
+
areas: Area[];
|
|
29
|
+
shapes: Shape[];
|
|
30
|
+
/** The long track's races marked, with where each starts. */
|
|
31
|
+
races: RaceMarks[];
|
|
32
|
+
/** The short track's races marked. */
|
|
33
|
+
shortRaces: ShortTrackRace[];
|
|
34
|
+
/** The number of tracks of the short track. */
|
|
35
|
+
tracks: 5 | 7;
|
|
36
|
+
furniture: SkatingFurniture[];
|
|
37
|
+
/** The width the ordinary line is painted at by the rules, and the width it is drawn at unless `exactLines` says otherwise. */
|
|
38
|
+
lineWidth: number;
|
|
39
|
+
drawnLine: number;
|
|
40
|
+
ranges: Readonly<Record<string, MRange>>;
|
|
41
|
+
marks: MMark[];
|
|
42
|
+
/** The race a close-up of a start (`range: "start"`) shows. */
|
|
43
|
+
start: SkatingRace;
|
|
44
|
+
}
|
|
45
|
+
/** The league a set of options asks for: the long track (the sport's own), the short track or the figure skating rink. */
|
|
46
|
+
export declare function skatingLeague(o: Pick<SurfaceOptions, "sport" | "league">): SkatingLeague;
|
|
47
|
+
/** The discipline of a set of options. */
|
|
48
|
+
export declare const disciplineOf: (o: Pick<SurfaceOptions, "sport" | "league">) => SkatingDiscipline;
|
|
49
|
+
/** The races the options ask to be marked, for a discipline. @throws Error for a name that is not a race, RangeError for one the discipline does not have. */
|
|
50
|
+
export declare function racesOf(style: SkatingStyle | undefined, discipline: SkatingDiscipline): SkatingRace[];
|
|
51
|
+
/** The furniture the options ask for. @throws Error for a name that is not furniture. */
|
|
52
|
+
export declare function furnitureOf(style: SkatingStyle | undefined): SkatingFurniture[];
|
|
53
|
+
/** The skating plan the options ask for. */
|
|
54
|
+
export declare function skatingPlan(o: Pick<SurfaceOptions, "sport" | "league" | "skating" | "range" | "detail">): SkatingPlan;
|
|
55
|
+
export { LONG_TRACK_RACES, SHORT_TRACK_RACES, ISU_FIGURE_RINK, ISU_LONG_TRACK, ISU_SHORT_TRACK };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { ISU_FIGURE_RINK, ISU_LONG_TRACK, ISU_SHORT_TRACK, SKATING_LEAGUES } from "../leagues/skating.js";
|
|
2
|
+
import { chooseLeague } from "../leagues.js";
|
|
3
|
+
import { LONG_TRACK_RACE_IDS, LONG_TRACK_RACES, RACE_IDS, SHORT_TRACK_RACE_IDS, SHORT_TRACK_RACES, raceById } from "./events.js";
|
|
4
|
+
import { figureParts, FURNITURE, furnitureBoxes } from "./figure.js";
|
|
5
|
+
import { longParts, LONG_DEFAULTS, iceRadius, markRace } from "./long.js";
|
|
6
|
+
import { grow, union } from "./parts.js";
|
|
7
|
+
import { shortOval, shortParts, trackLines } from "./short.js";
|
|
8
|
+
import { longOval, laneEdges, outerEdge } from "./track.js";
|
|
9
|
+
const isSkatingLeague = (l) => typeof l === "object" && l !== null && l.family === "skating";
|
|
10
|
+
/** The league a set of options asks for: the long track (the sport's own), the short track or the figure skating rink. */
|
|
11
|
+
export function skatingLeague(o) {
|
|
12
|
+
const { league } = chooseLeague(o);
|
|
13
|
+
return isSkatingLeague(league) ? league : SKATING_LEAGUES[0];
|
|
14
|
+
}
|
|
15
|
+
/** The discipline of a set of options. */
|
|
16
|
+
export const disciplineOf = (o) => skatingLeague(o).dimensions.kind;
|
|
17
|
+
/** A size within the range the rules give, or the drawn one. @throws RangeError for a size outside it. */
|
|
18
|
+
function sizeIn(asked, span, label, rule) {
|
|
19
|
+
if (asked === undefined)
|
|
20
|
+
return span.drawn;
|
|
21
|
+
if (typeof asked !== "number" || !Number.isFinite(asked))
|
|
22
|
+
throw new TypeError(`${label} is a number of metres`);
|
|
23
|
+
if (asked < span.min || asked > span.max)
|
|
24
|
+
throw new RangeError(`${label} of ${asked} m is outside what ${rule} allows: ${span.min} m to ${span.max} m`);
|
|
25
|
+
return asked;
|
|
26
|
+
}
|
|
27
|
+
/** The races the options ask to be marked, for a discipline. @throws Error for a name that is not a race, RangeError for one the discipline does not have. */
|
|
28
|
+
export function racesOf(style, discipline) {
|
|
29
|
+
const asked = style?.races;
|
|
30
|
+
if (asked === undefined)
|
|
31
|
+
return [];
|
|
32
|
+
const own = discipline === "long-track" ? LONG_TRACK_RACE_IDS : discipline === "short-track" ? SHORT_TRACK_RACE_IDS : [];
|
|
33
|
+
if (asked === "all")
|
|
34
|
+
return [...own];
|
|
35
|
+
if (!Array.isArray(asked))
|
|
36
|
+
throw new TypeError('skating.races is "all" or a list of races');
|
|
37
|
+
for (const r of asked)
|
|
38
|
+
if (raceById(r) === undefined)
|
|
39
|
+
throw new Error(`Unknown race "${String(r)}": use one of ${RACE_IDS.join(", ")}`);
|
|
40
|
+
for (const r of asked)
|
|
41
|
+
if (!own.includes(r))
|
|
42
|
+
throw new RangeError(`The ${discipline} rink does not mark the ${String(r)}: use ${own.length === 0 ? "none: a figure skating rink has no races" : `one of ${own.join(", ")}`}`);
|
|
43
|
+
return own.filter((r) => asked.includes(r));
|
|
44
|
+
}
|
|
45
|
+
/** The furniture the options ask for. @throws Error for a name that is not furniture. */
|
|
46
|
+
export function furnitureOf(style) {
|
|
47
|
+
const asked = style?.furniture;
|
|
48
|
+
if (asked === undefined || asked === true)
|
|
49
|
+
return [...FURNITURE];
|
|
50
|
+
if (asked === false)
|
|
51
|
+
return [];
|
|
52
|
+
if (!Array.isArray(asked))
|
|
53
|
+
throw new TypeError("skating.furniture is true, false or a list of judges, referee, technical-panel and kiss-and-cry");
|
|
54
|
+
for (const f of asked)
|
|
55
|
+
if (!FURNITURE.includes(f))
|
|
56
|
+
throw new Error(`Unknown furniture "${String(f)}": use one of ${FURNITURE.join(", ")}`);
|
|
57
|
+
return FURNITURE.filter((f) => asked.includes(f));
|
|
58
|
+
}
|
|
59
|
+
/** The width a line is drawn at unless the drawing is at true widths: a little under two thousandths of the surface, so that a lane line stays in sight in a drawing that holds the whole track. */
|
|
60
|
+
const drawn = (box) => Math.max(0.1, Math.round((box.x1 - box.x0) * 0.0018 * 100) / 100);
|
|
61
|
+
const flag = (v, def, label) => {
|
|
62
|
+
if (v === undefined)
|
|
63
|
+
return def;
|
|
64
|
+
if (typeof v !== "boolean")
|
|
65
|
+
throw new TypeError(`${label} is true or false`);
|
|
66
|
+
return v;
|
|
67
|
+
};
|
|
68
|
+
/** The skating plan the options ask for. */
|
|
69
|
+
export function skatingPlan(o) {
|
|
70
|
+
const league = skatingLeague(o);
|
|
71
|
+
const style = o.skating;
|
|
72
|
+
const discipline = league.dimensions.kind;
|
|
73
|
+
const base = { sport: "skating", league: league.id, discipline, figures: league.dimensions };
|
|
74
|
+
const given = racesOf(style, discipline);
|
|
75
|
+
const start = style?.start ?? "500m";
|
|
76
|
+
// A close-up of a start marks the start's race, whether or not the options name it.
|
|
77
|
+
const named = typeof o.range === "string" ? o.range : typeof o.range === "object" && o.range !== null && "name" in o.range ? o.range.name : undefined;
|
|
78
|
+
const own = discipline === "long-track" ? LONG_TRACK_RACE_IDS : discipline === "short-track" ? SHORT_TRACK_RACE_IDS : [];
|
|
79
|
+
const asked = named === "start" && own.includes(start) && !given.includes(start) ? [...given, start] : given;
|
|
80
|
+
if (style?.start !== undefined && raceById(style.start) === undefined)
|
|
81
|
+
throw new Error(`Unknown race "${String(style.start)}": use one of ${RACE_IDS.join(", ")}`);
|
|
82
|
+
const blocks = flag(style?.blocks, LONG_DEFAULTS.blocks, "skating.blocks");
|
|
83
|
+
const padding = flag(style?.padding, LONG_DEFAULTS.padding, "skating.padding");
|
|
84
|
+
if (discipline === "long-track") {
|
|
85
|
+
const fig = league.dimensions;
|
|
86
|
+
if (style?.infield !== undefined && style.infield !== "floor" && style.infield !== "ice")
|
|
87
|
+
throw new Error('skating.infield is "floor" or "ice"');
|
|
88
|
+
const oval = longOval(style?.innerRadius, style?.laneWidth, fig);
|
|
89
|
+
const { parts, marks: races, box } = longParts(oval, { fig, warmUp: flag(style?.warmUp, LONG_DEFAULTS.warmUp, "skating.warmUp"), infield: style?.infield ?? LONG_DEFAULTS.infield, blocks, coaches: flag(style?.coaches, LONG_DEFAULTS.coaches, "skating.coaches"), padding, races: asked });
|
|
90
|
+
const plan = { ...base, oval, short: undefined, rink: undefined, box, extent: box, areas: parts.areas, shapes: parts.shapes, races, shortRaces: [], tracks: 5, furniture: [], lineWidth: fig.line, drawnLine: drawn(box), ranges: {}, marks: [], start };
|
|
91
|
+
return { ...plan, ranges: longRanges(plan), marks: longMarks(plan) };
|
|
92
|
+
}
|
|
93
|
+
if (discipline === "short-track") {
|
|
94
|
+
const fig = league.dimensions;
|
|
95
|
+
const tracks = style?.tracks ?? 5;
|
|
96
|
+
if (tracks !== 5 && tracks !== 7)
|
|
97
|
+
throw new RangeError("skating.tracks is 5 or 7");
|
|
98
|
+
const length = style?.rink?.length ?? fig.rink.length;
|
|
99
|
+
const width = style?.rink?.width ?? fig.rink.width;
|
|
100
|
+
if (typeof length !== "number" || length < fig.rink.length || length > 70)
|
|
101
|
+
throw new RangeError(`The rink's length of ${String(length)} m is outside what Rule 280 allows: at least ${fig.rink.length} m (and, for this drawing, at most 70 m)`);
|
|
102
|
+
if (typeof width !== "number" || width < fig.rink.width || width > 40)
|
|
103
|
+
throw new RangeError(`The rink's width of ${String(width)} m is outside what Rule 280 allows: at least ${fig.rink.width} m (and, for this drawing, at most 40 m)`);
|
|
104
|
+
const oval = shortOval(length, width, fig);
|
|
105
|
+
const { parts, box } = shortParts(oval, { fig, tracks, blocks, padding, races: asked, corner: 8.5 });
|
|
106
|
+
const plan = { ...base, oval: undefined, short: oval, rink: { length, width }, box, extent: box, areas: parts.areas, shapes: parts.shapes, races: [], shortRaces: asked, tracks, furniture: [], lineWidth: fig.line, drawnLine: drawn(box), ranges: {}, marks: [], start };
|
|
107
|
+
return { ...plan, ranges: shortRanges(plan), marks: shortMarks(plan) };
|
|
108
|
+
}
|
|
109
|
+
const fig = league.dimensions;
|
|
110
|
+
const length = sizeIn(style?.rink?.length, fig.length, "The rink's length", "Rule 342.1");
|
|
111
|
+
const width = sizeIn(style?.rink?.width, fig.width, "The rink's width", "Rule 342.1");
|
|
112
|
+
const rink = { length, width };
|
|
113
|
+
const furniture = furnitureOf(style);
|
|
114
|
+
const { parts, box, marks } = figureParts(rink, { fig, furniture });
|
|
115
|
+
const boards = { x0: -length / 2, y0: -width / 2, x1: length / 2, y1: width / 2 };
|
|
116
|
+
const extra = furniture.length === 0 ? [] : furniture.flatMap((k) => furnitureBoxes(rink, fig)[k]);
|
|
117
|
+
const plan = { ...base, oval: undefined, short: undefined, rink, box: boards, extent: union([box, ...extra.map((b) => grow(b, 0.3))]), areas: parts.areas, shapes: parts.shapes, races: [], shortRaces: [], tracks: 5, furniture, lineWidth: 0.05, drawnLine: drawn(box), ranges: {}, marks: [...marks, { id: "ice", x: 0, y: -width / 4, side: "top" }, { id: "boards", x: -length / 4, y: -width / 2, side: "top" }], start };
|
|
118
|
+
return { ...plan, ranges: figureRanges(plan) };
|
|
119
|
+
}
|
|
120
|
+
/** The ranges of the long track. */
|
|
121
|
+
function longRanges(plan) {
|
|
122
|
+
const o = plan.oval;
|
|
123
|
+
const f = o.straight;
|
|
124
|
+
const edges = laneEdges(o, 4);
|
|
125
|
+
const out = outerEdge(o);
|
|
126
|
+
const R = iceRadius(o);
|
|
127
|
+
const r = {};
|
|
128
|
+
r["home-straight"] = { x0: -f / 2, y0: edges.inner - 2, x1: f / 2, y1: out + 2 };
|
|
129
|
+
r["crossing-straight"] = { x0: -f / 2, y0: -out - 2, x1: f / 2, y1: -edges.inner + 2 };
|
|
130
|
+
r["first-bend"] = { x0: f / 2, y0: -R, x1: f / 2 + R, y1: R };
|
|
131
|
+
r["second-bend"] = { x0: -f / 2 - R, y0: -R, x1: -f / 2, y1: R };
|
|
132
|
+
r.finish = { x0: -f / 2 + 100 - 18, y0: edges.inner - 2, x1: -f / 2 + 100 + 12, y1: out + 2 };
|
|
133
|
+
r.infield = { x0: -f / 2 - edges.warmUp, y0: -edges.warmUp, x1: f / 2 + edges.warmUp, y1: edges.warmUp };
|
|
134
|
+
const place = LONG_TRACK_RACE_IDS.includes(plan.start) ? markRace(o, plan.start) : undefined;
|
|
135
|
+
if (place !== undefined) {
|
|
136
|
+
const xs = place.starts.map((s) => s.x);
|
|
137
|
+
const ys = place.starts.map((s) => s.y);
|
|
138
|
+
r.start = { x0: Math.min(...xs) - 14, y0: Math.min(...ys, ...ys.map((y) => y)) - 6, x1: Math.max(...xs) + 14, y1: Math.max(...ys) + 6 };
|
|
139
|
+
}
|
|
140
|
+
else
|
|
141
|
+
r.start = { x0: -f / 2 - 14, y0: edges.inner - 6, x1: -f / 2 + 14, y1: out + 2 };
|
|
142
|
+
return r;
|
|
143
|
+
}
|
|
144
|
+
function longMarks(plan) {
|
|
145
|
+
const o = plan.oval;
|
|
146
|
+
const f = o.straight;
|
|
147
|
+
const edges = laneEdges(o, 4);
|
|
148
|
+
return [
|
|
149
|
+
{ id: "finish-line", x: -f / 2 + 100, y: edges.outer, side: "bottom" },
|
|
150
|
+
{ id: "inner-lane", x: -f / 4, y: (edges.inner + edges.between) / 2, side: "bottom" },
|
|
151
|
+
{ id: "outer-lane", x: f / 4, y: (edges.between + edges.outer) / 2, side: "bottom" },
|
|
152
|
+
{ id: "warm-up-lane", x: -f / 4, y: -(edges.warmUp + edges.inner) / 2, side: "top" },
|
|
153
|
+
{ id: "crossing-straight", x: 0, y: -edges.outer, side: "top" },
|
|
154
|
+
{ id: "padding", x: f / 4, y: -iceRadius(o), side: "top" },
|
|
155
|
+
{ id: "infield", x: 0, y: 0, side: "top" },
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
/** The ranges of the short track. */
|
|
159
|
+
function shortRanges(plan) {
|
|
160
|
+
const o = plan.short;
|
|
161
|
+
const S = o.straight;
|
|
162
|
+
const W = o.width / 2;
|
|
163
|
+
const r = {};
|
|
164
|
+
r["home-straight"] = { x0: -S / 2 - 3, y0: o.blocks - 2, x1: S / 2 + 3, y1: W };
|
|
165
|
+
r["crossing-straight"] = { x0: -S / 2 - 3, y0: -W, x1: S / 2 + 3, y1: -o.blocks + 2 };
|
|
166
|
+
r["first-bend"] = { x0: S / 2, y0: -W, x1: S / 2 + o.radius + 3, y1: W };
|
|
167
|
+
r["second-bend"] = { x0: -S / 2 - o.radius - 3, y0: -W, x1: -S / 2, y1: W };
|
|
168
|
+
r.finish = { x0: -10, y0: o.blocks - 4, x1: 10, y1: W };
|
|
169
|
+
const lines = trackLines(plan.tracks);
|
|
170
|
+
r.start = { x0: Math.min(...lines) - 8, y0: -W, x1: Math.max(...lines) + 8, y1: -o.blocks + 4 };
|
|
171
|
+
r.infield = { x0: -S / 2 - o.blocks, y0: -o.blocks, x1: S / 2 + o.blocks, y1: o.blocks };
|
|
172
|
+
return r;
|
|
173
|
+
}
|
|
174
|
+
function shortMarks(plan) {
|
|
175
|
+
const o = plan.short;
|
|
176
|
+
const S = o.straight;
|
|
177
|
+
return [
|
|
178
|
+
{ id: "finish-line", x: 0, y: o.width / 2, side: "bottom" },
|
|
179
|
+
{ id: "blocks", x: S / 2 + o.blocks * 0.7071, y: -o.blocks * 0.7071, side: "top" },
|
|
180
|
+
{ id: "track-line", x: -S / 4, y: o.radius, side: "bottom" },
|
|
181
|
+
{ id: "start-lines", x: 0, y: -o.width / 2, side: "top" },
|
|
182
|
+
{ id: "infield", x: 0, y: 0, side: "top" },
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
/** The ranges of the figure skating rink. */
|
|
186
|
+
function figureRanges(plan) {
|
|
187
|
+
const r = plan.rink;
|
|
188
|
+
const L = r.length / 2;
|
|
189
|
+
const W = r.width / 2;
|
|
190
|
+
const out = { half: { left: { x0: -L, y0: -W, x1: 0, y1: W }, right: { x0: 0, y0: -W, x1: L, y1: W } } };
|
|
191
|
+
const boxes = furnitureBoxes(r);
|
|
192
|
+
const judges = plan.furniture.includes("judges") ? union([...boxes.judges, ...(plan.furniture.includes("referee") ? boxes.referee : []), ...(plan.furniture.includes("technical-panel") ? boxes["technical-panel"] : [])].map((b) => grow(b, 1.5))) : { x0: -8, y0: W - 4, x1: 8, y1: W + 3 };
|
|
193
|
+
out.judges = { ...judges, y0: Math.min(judges.y0, W - 3) };
|
|
194
|
+
out["kiss-and-cry"] = grow(plan.furniture.includes("kiss-and-cry") ? boxes["kiss-and-cry"][0] : { x0: -L + 3, y0: W + 0.6, x1: -L + 6.5, y1: W + 2.2 }, 4);
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
export { LONG_TRACK_RACES, SHORT_TRACK_RACES, ISU_FIGURE_RINK, ISU_LONG_TRACK, ISU_SHORT_TRACK };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Shape } from "../fields.types.ts";
|
|
2
|
+
import { type ShortTrackFigures } from "../leagues/skating.ts";
|
|
3
|
+
import { type MBox, type Parts } from "./parts.ts";
|
|
4
|
+
import type { ShortTrackRace } from "./types.ts";
|
|
5
|
+
/** The oval of the standard track. */
|
|
6
|
+
export interface ShortOval {
|
|
7
|
+
straight: number;
|
|
8
|
+
radius: number;
|
|
9
|
+
/** The radius of the blocks, 0.5 m inside the line of the track. */
|
|
10
|
+
blocks: number;
|
|
11
|
+
lap: number;
|
|
12
|
+
/** The rink: its length and width. */
|
|
13
|
+
length: number;
|
|
14
|
+
width: number;
|
|
15
|
+
}
|
|
16
|
+
/** The oval for a rink. */
|
|
17
|
+
export declare function shortOval(length?: number, width?: number, fig?: ShortTrackFigures): ShortOval;
|
|
18
|
+
/** The pose of a skater `v` metres along a lap that begins at the left end of the finishing straight (the bottom one), `out` metres outside the line of the track: the skaters go counter-clockwise. */
|
|
19
|
+
export declare function shortPose(o: ShortOval, v: number, out?: number): {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
heading: number;
|
|
23
|
+
};
|
|
24
|
+
/** Where each race starts: the half-lap races (500 m and 1500 m) on the starting lines of the straight opposite the finishing line, the others on the finishing line (the diagram's laps: 4 1/2, 9, 13 1/2, 18, 27 and 45). */
|
|
25
|
+
export declare const startsOnOppositeStraight: (race: ShortTrackRace) => boolean;
|
|
26
|
+
/** The x of the starting line of each of the tracks, from the one at the left: a track moved `shift` along the straight starts 2 x shift on from the standard track's line (the diagram). */
|
|
27
|
+
export declare function trackLines(tracks: 5 | 7, fig?: ShortTrackFigures): number[];
|
|
28
|
+
/** The starting positions of the 500 m and the 1000 m: five dots every 1.3 m, from 50 cm out from the inner side of the track (Rule 280.2c), on the line at `x` and the side `side` (-1 the top, 1 the bottom). */
|
|
29
|
+
export declare function startingPositions(o: ShortOval, x: number, side: 1 | -1, fig?: ShortTrackFigures): {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
}[];
|
|
33
|
+
export interface ShortOptions {
|
|
34
|
+
fig: ShortTrackFigures;
|
|
35
|
+
tracks: 5 | 7;
|
|
36
|
+
blocks: boolean;
|
|
37
|
+
padding: boolean;
|
|
38
|
+
races: readonly ShortTrackRace[];
|
|
39
|
+
/** The corner radius of the rink: ours. */
|
|
40
|
+
corner: number;
|
|
41
|
+
}
|
|
42
|
+
/** The parts of the short track. */
|
|
43
|
+
export declare function shortParts(o: ShortOval, opt: ShortOptions): {
|
|
44
|
+
parts: Parts;
|
|
45
|
+
box: MBox;
|
|
46
|
+
};
|
|
47
|
+
export type { Shape };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// The short track: the rink, the padding, the oval of 111.12 m with its seven blocks in each curve, the finishing line, the starting lines of the tracks laid out beside the standard one, and the starting
|
|
2
|
+
// positions of the 500 m and the 1000 m. Every figure is the ISU's (Special Regulations and Technical Rules Short Track Speed Skating, Rule 280 and its diagram), in metres. Nothing here draws SVG.
|
|
3
|
+
import { line } from "../fields.kit.js";
|
|
4
|
+
import { ISU_SHORT_TRACK } from "../leagues/skating.js";
|
|
5
|
+
import { addParts, curvePoint, emptyParts, stadiumD } from "./parts.js";
|
|
6
|
+
/** The oval for a rink. */
|
|
7
|
+
export function shortOval(length = ISU_SHORT_TRACK.rink.length, width = ISU_SHORT_TRACK.rink.width, fig = ISU_SHORT_TRACK) {
|
|
8
|
+
return { straight: fig.straight, radius: fig.radius, blocks: fig.blocks, lap: 2 * fig.straight + 2 * Math.PI * fig.radius, length, width };
|
|
9
|
+
}
|
|
10
|
+
/** The pose of a skater `v` metres along a lap that begins at the left end of the finishing straight (the bottom one), `out` metres outside the line of the track: the skaters go counter-clockwise. */
|
|
11
|
+
export function shortPose(o, v, out = 0) {
|
|
12
|
+
const S = o.straight;
|
|
13
|
+
const r = o.radius + out;
|
|
14
|
+
const L = 2 * S + 2 * Math.PI * o.radius;
|
|
15
|
+
let s = ((v % L) + L) % L;
|
|
16
|
+
if (s < S)
|
|
17
|
+
return { x: -S / 2 + s, y: r, heading: 0 };
|
|
18
|
+
s -= S;
|
|
19
|
+
const half = Math.PI * o.radius;
|
|
20
|
+
if (s < half) {
|
|
21
|
+
const t = s / o.radius;
|
|
22
|
+
const [x, y] = curvePoint(S, "right", r, t);
|
|
23
|
+
return { x, y, heading: -t };
|
|
24
|
+
}
|
|
25
|
+
s -= half;
|
|
26
|
+
if (s < S)
|
|
27
|
+
return { x: S / 2 - s, y: -r, heading: Math.PI };
|
|
28
|
+
s -= S;
|
|
29
|
+
const t = s / o.radius;
|
|
30
|
+
const [x, y] = curvePoint(S, "left", r, t);
|
|
31
|
+
return { x, y, heading: Math.PI - t };
|
|
32
|
+
}
|
|
33
|
+
/** Where each race starts: the half-lap races (500 m and 1500 m) on the starting lines of the straight opposite the finishing line, the others on the finishing line (the diagram's laps: 4 1/2, 9, 13 1/2, 18, 27 and 45). */
|
|
34
|
+
export const startsOnOppositeStraight = (race) => race === "500m" || race === "1500m";
|
|
35
|
+
/** The x of the starting line of each of the tracks, from the one at the left: a track moved `shift` along the straight starts 2 x shift on from the standard track's line (the diagram). */
|
|
36
|
+
export function trackLines(tracks, fig = ISU_SHORT_TRACK) {
|
|
37
|
+
const t = tracks === 5 ? fig.tracks.five : fig.tracks.seven;
|
|
38
|
+
return Array.from({ length: tracks }, (_, i) => (i - (tracks - 1) / 2) * t.lines);
|
|
39
|
+
}
|
|
40
|
+
/** The starting positions of the 500 m and the 1000 m: five dots every 1.3 m, from 50 cm out from the inner side of the track (Rule 280.2c), on the line at `x` and the side `side` (-1 the top, 1 the bottom). */
|
|
41
|
+
export function startingPositions(o, x, side, fig = ISU_SHORT_TRACK) {
|
|
42
|
+
return Array.from({ length: fig.dots.count }, (_, i) => ({ x, y: side * (o.blocks + fig.dots.from + i * fig.dots.step) }));
|
|
43
|
+
}
|
|
44
|
+
/** The parts of the short track. */
|
|
45
|
+
export function shortParts(o, opt) {
|
|
46
|
+
const { fig } = opt;
|
|
47
|
+
const parts = emptyParts();
|
|
48
|
+
const S = o.straight;
|
|
49
|
+
const W = o.width / 2;
|
|
50
|
+
const Lh = o.length / 2;
|
|
51
|
+
const roundD = (hx, hy, r) => `M${-hx + r} ${-hy}H${hx - r}A${r} ${r} 0 0 1 ${hx} ${-hy + r}V${hy - r}A${r} ${r} 0 0 1 ${hx - r} ${hy}H${-hx + r}A${r} ${r} 0 0 1 ${-hx} ${hy - r}V${-hy + r}A${r} ${r} 0 0 1 ${-hx + r} ${-hy}Z`;
|
|
52
|
+
const rd = (n) => Math.round(n * 100) / 100;
|
|
53
|
+
parts.areas.push({ id: "ice", feature: "ice", cls: "ky-ska-ice", d: roundD(rd(Lh), rd(W), opt.corner) });
|
|
54
|
+
if (opt.padding)
|
|
55
|
+
parts.areas.push({ id: "padding", feature: "padding", cls: "ky-ska-pad", d: `${roundD(rd(Lh + 0.5), rd(W + 0.5), opt.corner + 0.5)}${roundD(rd(Lh), rd(W), opt.corner)}`, evenodd: true });
|
|
56
|
+
// The inner surface of the track, when it is coloured: inside the blocks by the finishing line's extra 1.5 m (Rule 280.2d and the diagram).
|
|
57
|
+
parts.areas.push({ id: "inner", feature: "infield", cls: "ky-ska-warm", d: stadiumD(S, o.blocks - fig.inner) });
|
|
58
|
+
// The line of the standard track, 111.12 m round, and the line between the last block of a curve and the first of the next (Rule 280.1g).
|
|
59
|
+
parts.shapes.push({ kind: "path", id: "track-line", feature: "track-line", d: stadiumD(S, o.radius), w: 0.02, cls: "ky-ska-b" });
|
|
60
|
+
for (const side of [-1, 1])
|
|
61
|
+
parts.shapes.push(line(`straight-line-${side}`, "track-line", -S / 2, side * o.blocks, S / 2, side * o.blocks, { w: 0.02, cls: "ky-ska-k" }));
|
|
62
|
+
// The blocks: seven in each curve (Rule 280.4), the first and the last at the ends of the straights and the apex in the middle, 30 degrees apart.
|
|
63
|
+
if (opt.blocks) {
|
|
64
|
+
for (const side of ["right", "left"]) {
|
|
65
|
+
for (let k = 0; k < fig.blocksPerCurve; k += 1) {
|
|
66
|
+
const [cx, cy] = curvePoint(S, side, o.blocks, (k * Math.PI) / (fig.blocksPerCurve - 1));
|
|
67
|
+
parts.shapes.push({ kind: "dot", id: `block-${side}-${k}`, feature: "blocks", cx, cy, r: 0.12, paint: k === 0 || k === 3 ? "cone" : "block" });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// The finishing line, from the barrier across the width of the straight and 1.5 m more (Rule 280.2b), 2 cm wide.
|
|
72
|
+
parts.shapes.push(line("finish-line", "finish-line", 0, W, 0, o.blocks - fig.finishExtra, { w: fig.line, cls: "ky-ska-r" }));
|
|
73
|
+
// The starting lines of the half-lap races, one for each track, from the barrier across the width of the straight (Rule 280.2b).
|
|
74
|
+
const lines = trackLines(opt.tracks, fig);
|
|
75
|
+
const wanted = opt.races.some(startsOnOppositeStraight);
|
|
76
|
+
if (wanted)
|
|
77
|
+
lines.forEach((x, i) => parts.shapes.push(line(`start-line-${i + 1}`, "start-lines", x, -W, x, -o.blocks, { w: fig.line, cls: "ky-ska-r" })));
|
|
78
|
+
// The starting positions of the 500 m (on the standard track's line) and of the 1000 m (at the finishing line): five dots.
|
|
79
|
+
const dotsFor = (x, side, id) => {
|
|
80
|
+
for (const [i, p] of startingPositions(o, x, side, fig).entries())
|
|
81
|
+
parts.shapes.push({ kind: "dot", id: `${id}-${i + 1}`, feature: "start-positions", cx: p.x, cy: p.y, r: 0.1, paint: "block" });
|
|
82
|
+
};
|
|
83
|
+
if (opt.races.includes("500m"))
|
|
84
|
+
dotsFor(0, -1, "position-500m");
|
|
85
|
+
if (opt.races.includes("1000m"))
|
|
86
|
+
dotsFor(0, 1, "position-1000m");
|
|
87
|
+
void addParts;
|
|
88
|
+
return { parts, box: { x0: -Lh - 0.5, y0: -W - 0.5, x1: Lh + 0.5, y1: W + 0.5 } };
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type LongTrackFigures } from "../leagues/skating.ts";
|
|
2
|
+
/** The lane of a skater on the finishing straight: the inner or the outer (the one on the left, nearer the infield). */
|
|
3
|
+
export type Lane = 0 | 1;
|
|
4
|
+
/** The shape of a long track, from the radius of the inner edge, the width of the outer lane and the lap. */
|
|
5
|
+
export interface LongOval {
|
|
6
|
+
/** The radius of the inner edge of the inner lane (a in the table). */
|
|
7
|
+
a: number;
|
|
8
|
+
/** The widths of the inner lane and of the outer lane. */
|
|
9
|
+
w1: number;
|
|
10
|
+
w2: number;
|
|
11
|
+
/** The radii of the lines each lane is measured on (d and e in the table): 0.5 m outside the inner edge of the lane. */
|
|
12
|
+
d: number;
|
|
13
|
+
e: number;
|
|
14
|
+
/** The length of each straight, from the end of one curve to the beginning of the next (f in the table). */
|
|
15
|
+
straight: number;
|
|
16
|
+
/** The crossing: from the inner lane's line at one end of the straight to the outer lane's at the other. */
|
|
17
|
+
crossing: number;
|
|
18
|
+
lap: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The track for an inner radius `a` and an outer lane `w2` wide: the straight is the one that makes a lap 400 m, f + sqrt(f^2 + w1^2) + pi (d + e) = lap, where f is the straight skated twice a lap (once
|
|
22
|
+
* on the finishing straight and once crossing). For a = 25 m, 25.5 m and 26 m with lanes of 4 m this is the table's 113.57 m, 112.00 m and 110.43 m.
|
|
23
|
+
* @throws RangeError for a radius or a lane outside what Rule 203 allows.
|
|
24
|
+
*/
|
|
25
|
+
export declare function longOval(a?: number, w2?: number, fig?: LongTrackFigures): LongOval;
|
|
26
|
+
/** The radius of the line a lane is measured on: 0 inner, 1 outer. */
|
|
27
|
+
export declare const laneRadius: (o: LongOval, lane: Lane) => number;
|
|
28
|
+
/** The radius of the edges of the lanes, from the inner edge of the warm-up lane out to the outer edge of the outer lane: the warm-up lane's inner edge, the inner edge, the line between the lanes, the outer edge. */
|
|
29
|
+
export declare const laneEdges: (o: LongOval, warmUp: number) => {
|
|
30
|
+
warmUp: number;
|
|
31
|
+
inner: number;
|
|
32
|
+
between: number;
|
|
33
|
+
outer: number;
|
|
34
|
+
};
|
|
35
|
+
/** The radius of the outer edge of the outer lane. */
|
|
36
|
+
export declare const outerEdge: (o: LongOval) => number;
|
|
37
|
+
/** Where a skater is: a point of the track, the heading (0 is to the right, growing clockwise as the eye sees it), the part of the lap and how far across the lanes (0 on the inner line, 1 on the outer). */
|
|
38
|
+
export interface Pose {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
heading: number;
|
|
42
|
+
part: "finishing-straight" | "first-curve" | "crossing" | "second-curve";
|
|
43
|
+
/** The lap's number, from the lap the coordinate zero begins. */
|
|
44
|
+
lap: number;
|
|
45
|
+
/** 0 on the inner lane's line, 1 on the outer's, between on the crossing. */
|
|
46
|
+
across: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The pose `v` metres along the skater's own way, `v` measured from the beginning of the finishing straight (its left end, x = -f/2) of the lap the skater starts in, the lap 0. `lane` is the skater's
|
|
50
|
+
* lane there; it alternates every lap (Rule 254.5a). In a lap the skater skates the finishing straight and the first curve in the lap's lane, the crossing from it to the other lane, and the second
|
|
51
|
+
* curve in the other. With `noChange` the crossing of the lap 0 keeps the lane, as the first straight of the 1000 m and the 1500 m does (Rule 254.5b): the lap 0 has one lane, and the laps alternate on.
|
|
52
|
+
*/
|
|
53
|
+
export declare function pose(o: LongOval, lane: Lane, v: number, noChange?: boolean): Pose;
|
|
54
|
+
/** The coordinate of the finishing line on the lap 0's finishing straight, from the straight's beginning: the 400 m mark is 100 m on (Rule 227.2 and the layout plan: h = f - 100). */
|
|
55
|
+
export declare const finishAt: (o: LongOval, line: "400m" | "1000m") => number;
|
|
56
|
+
/** The x of a coordinate on a straight of the lap 0 (the finishing straight), or `undefined` where it is not on it. */
|
|
57
|
+
export declare const xOfFinishing: (o: LongOval, u: number) => number;
|
|
58
|
+
/**
|
|
59
|
+
* Where a skater in lane `lane` starts a race of `distance` metres that finishes at `finish` metres along the finishing straight (`finishAt`): back from the finishing line along the skater's own way.
|
|
60
|
+
* `noChange` says the race's first straight keeps its lane (the 1000 m and the 1500 m, Rule 254.5b). Returns the skater's coordinate `v0` at the start, inside the lap it starts in, and the pose there; a
|
|
61
|
+
* skater who has skated `s` metres is at `pose(o, lane, v0 + s, noChange)`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function startOf(o: LongOval, lane: Lane, distance: number, finish: number, noChange?: boolean): {
|
|
64
|
+
v0: number;
|
|
65
|
+
pose: Pose;
|
|
66
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// The geometry of the standard 400 m speed skating track as the ISU's Special Regulations and Technical Rules Speed Skating lay it out (Rules 203, 226, 227 and 254 and "H. Track": the layout plan and the
|
|
2
|
+
// measurements of standard 400 m speed skating tracks), in metres. The origin is the middle of the track, x along the straights (positive to the right as the track is drawn wide), y down the page. The
|
|
3
|
+
// skaters go counter-clockwise (Rule 254.1): to the right along the finishing straight at the bottom, round the right-hand curve, to the left along the crossing straight at the top. A skater changes lanes
|
|
4
|
+
// once a lap, on the crossing straight (Rule 254.5a), so that a lap is a straight, an inner curve, a straight, an outer curve and the crossing, 400 m on the line measured 0.5 m outside each lane's inner
|
|
5
|
+
// edge (Rule 227.1). Nothing here draws.
|
|
6
|
+
import { ISU_LONG_TRACK } from "../leagues/skating.js";
|
|
7
|
+
/**
|
|
8
|
+
* The track for an inner radius `a` and an outer lane `w2` wide: the straight is the one that makes a lap 400 m, f + sqrt(f^2 + w1^2) + pi (d + e) = lap, where f is the straight skated twice a lap (once
|
|
9
|
+
* on the finishing straight and once crossing). For a = 25 m, 25.5 m and 26 m with lanes of 4 m this is the table's 113.57 m, 112.00 m and 110.43 m.
|
|
10
|
+
* @throws RangeError for a radius or a lane outside what Rule 203 allows.
|
|
11
|
+
*/
|
|
12
|
+
export function longOval(a = ISU_LONG_TRACK.innerRadius.drawn, w2 = ISU_LONG_TRACK.outerLane.drawn, fig = ISU_LONG_TRACK) {
|
|
13
|
+
if (typeof a !== "number" || !Number.isFinite(a) || a < fig.innerRadius.min || a > fig.innerRadius.max)
|
|
14
|
+
throw new RangeError(`The inner radius of ${String(a)} m is outside what Rule 203 allows: ${fig.innerRadius.min} m to ${fig.innerRadius.max} m`);
|
|
15
|
+
if (typeof w2 !== "number" || !Number.isFinite(w2) || w2 < fig.outerLane.min || w2 > fig.outerLane.max)
|
|
16
|
+
throw new RangeError(`The outer lane of ${String(w2)} m is outside what Rule 203 allows: ${fig.outerLane.min} m to ${fig.outerLane.max} m`);
|
|
17
|
+
const w1 = fig.innerLane;
|
|
18
|
+
const d = a + fig.measure;
|
|
19
|
+
const e = a + w1 + fig.measure;
|
|
20
|
+
const need = fig.lap - Math.PI * (d + e);
|
|
21
|
+
// f + sqrt(f^2 + w1^2) rises with f: bisect.
|
|
22
|
+
let lo = 1;
|
|
23
|
+
let hi = fig.lap;
|
|
24
|
+
for (let i = 0; i < 80; i += 1) {
|
|
25
|
+
const mid = (lo + hi) / 2;
|
|
26
|
+
if (mid + Math.hypot(mid, w1) < need)
|
|
27
|
+
lo = mid;
|
|
28
|
+
else
|
|
29
|
+
hi = mid;
|
|
30
|
+
}
|
|
31
|
+
const f = (lo + hi) / 2;
|
|
32
|
+
return { a, w1, w2, d, e, straight: f, crossing: Math.hypot(f, w1), lap: fig.lap };
|
|
33
|
+
}
|
|
34
|
+
/** The radius of the line a lane is measured on: 0 inner, 1 outer. */
|
|
35
|
+
export const laneRadius = (o, lane) => (lane === 0 ? o.d : o.e);
|
|
36
|
+
/** The radius of the edges of the lanes, from the inner edge of the warm-up lane out to the outer edge of the outer lane: the warm-up lane's inner edge, the inner edge, the line between the lanes, the outer edge. */
|
|
37
|
+
export const laneEdges = (o, warmUp) => ({ warmUp: o.a - warmUp, inner: o.a, between: o.a + o.w1, outer: o.a + o.w1 + o.w2 });
|
|
38
|
+
/** The radius of the outer edge of the outer lane. */
|
|
39
|
+
export const outerEdge = (o) => o.a + o.w1 + o.w2;
|
|
40
|
+
const mod2 = (n) => (((n % 2) + 2) % 2);
|
|
41
|
+
/**
|
|
42
|
+
* The pose `v` metres along the skater's own way, `v` measured from the beginning of the finishing straight (its left end, x = -f/2) of the lap the skater starts in, the lap 0. `lane` is the skater's
|
|
43
|
+
* lane there; it alternates every lap (Rule 254.5a). In a lap the skater skates the finishing straight and the first curve in the lap's lane, the crossing from it to the other lane, and the second
|
|
44
|
+
* curve in the other. With `noChange` the crossing of the lap 0 keeps the lane, as the first straight of the 1000 m and the 1500 m does (Rule 254.5b): the lap 0 has one lane, and the laps alternate on.
|
|
45
|
+
*/
|
|
46
|
+
export function pose(o, lane, v, noChange = false) {
|
|
47
|
+
const f = o.straight;
|
|
48
|
+
const laneOfLap = (j) => (noChange ? (j <= 0 ? lane : mod2(lane + j - 1)) : mod2(lane + j));
|
|
49
|
+
const first = laneRadius(o, lane);
|
|
50
|
+
// The lap 0 of a noChange race is two straights and two curves in one lane; every other lap is the standard 400 m.
|
|
51
|
+
const L0 = noChange ? 2 * f + 2 * Math.PI * first : o.lap;
|
|
52
|
+
const lapNo = v < L0 ? 0 : 1 + Math.floor((v - L0) / o.lap);
|
|
53
|
+
let s = lapNo === 0 ? v : v - L0 - (lapNo - 1) * o.lap;
|
|
54
|
+
const laneIn = laneOfLap(lapNo);
|
|
55
|
+
const laneOut = laneOfLap(lapNo + 1);
|
|
56
|
+
const rIn = laneRadius(o, laneIn);
|
|
57
|
+
const rOut = laneRadius(o, laneOut);
|
|
58
|
+
const across = (r) => (r === o.d ? 0 : 1);
|
|
59
|
+
if (s < f)
|
|
60
|
+
return { x: -f / 2 + s, y: rIn, heading: 0, part: "finishing-straight", lap: lapNo, across: across(rIn) };
|
|
61
|
+
s -= f;
|
|
62
|
+
if (s < Math.PI * rIn) {
|
|
63
|
+
const t = s / rIn;
|
|
64
|
+
return { x: f / 2 + rIn * Math.sin(t), y: rIn * Math.cos(t), heading: -t, part: "first-curve", lap: lapNo, across: across(rIn) };
|
|
65
|
+
}
|
|
66
|
+
s -= Math.PI * rIn;
|
|
67
|
+
const cross = rIn === rOut ? f : o.crossing;
|
|
68
|
+
if (s < cross) {
|
|
69
|
+
const t = s / cross;
|
|
70
|
+
return { x: f / 2 - f * t, y: -(rIn + (rOut - rIn) * t), heading: Math.PI + Math.atan2(rIn - rOut, f) * -1, part: "crossing", lap: lapNo, across: across(rIn) + (across(rOut) - across(rIn)) * t };
|
|
71
|
+
}
|
|
72
|
+
s -= cross;
|
|
73
|
+
const t = Math.min(Math.PI, s / rOut);
|
|
74
|
+
return { x: -f / 2 - rOut * Math.sin(t), y: -rOut * Math.cos(t), heading: Math.PI - t, part: "second-curve", lap: lapNo, across: across(rOut) };
|
|
75
|
+
}
|
|
76
|
+
/** The coordinate of the finishing line on the lap 0's finishing straight, from the straight's beginning: the 400 m mark is 100 m on (Rule 227.2 and the layout plan: h = f - 100). */
|
|
77
|
+
export const finishAt = (o, line) => (line === "400m" ? 100 : o.straight / 2);
|
|
78
|
+
/** The x of a coordinate on a straight of the lap 0 (the finishing straight), or `undefined` where it is not on it. */
|
|
79
|
+
export const xOfFinishing = (o, u) => -o.straight / 2 + u;
|
|
80
|
+
/**
|
|
81
|
+
* Where a skater in lane `lane` starts a race of `distance` metres that finishes at `finish` metres along the finishing straight (`finishAt`): back from the finishing line along the skater's own way.
|
|
82
|
+
* `noChange` says the race's first straight keeps its lane (the 1000 m and the 1500 m, Rule 254.5b). Returns the skater's coordinate `v0` at the start, inside the lap it starts in, and the pose there; a
|
|
83
|
+
* skater who has skated `s` metres is at `pose(o, lane, v0 + s, noChange)`.
|
|
84
|
+
*/
|
|
85
|
+
export function startOf(o, lane, distance, finish, noChange = false) {
|
|
86
|
+
const L0 = noChange ? 2 * o.straight + 2 * Math.PI * laneRadius(o, lane) : o.lap;
|
|
87
|
+
// The finishing line is on the finishing straight of the lap n, which begins at T(n); the start is `distance` before it.
|
|
88
|
+
const T = (n) => (n === 0 ? 0 : L0 + o.lap * (n - 1));
|
|
89
|
+
let n = 0;
|
|
90
|
+
while (T(n) + finish - distance < 0)
|
|
91
|
+
n += 1;
|
|
92
|
+
const v0 = T(n) + finish - distance;
|
|
93
|
+
return { v0, pose: pose(o, lane, v0, noChange) };
|
|
94
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** The disciplines a drawing can be of: the standard 400 m speed skating track, the short track oval or the figure skating rink. `league` says it too: `isu-long-track`, `isu-short-track`, `isu-figure`. */
|
|
2
|
+
export type SkatingDiscipline = "long-track" | "short-track" | "figure";
|
|
3
|
+
/** The races of the long track whose lines are marked: the distances, the mass start and the two team races. A race is named by its distance, in kebab case. */
|
|
4
|
+
export type LongTrackRace = "500m" | "1000m" | "1500m" | "3000m" | "5000m" | "10000m" | "mass-start" | "team-pursuit" | "team-sprint";
|
|
5
|
+
/** The races of the short track whose lines are marked. */
|
|
6
|
+
export type ShortTrackRace = "500m" | "1000m" | "1500m" | "3000m" | "3000m-relay" | "5000m-relay" | "2000m-mixed-relay";
|
|
7
|
+
/** Every race a drawing can mark. */
|
|
8
|
+
export type SkatingRace = LongTrackRace | ShortTrackRace;
|
|
9
|
+
/** The things that stand at the rink of a figure skating competition: the judges and the referee at the rinkboard, the technical panel, and the kiss and cry. */
|
|
10
|
+
export type SkatingFurniture = "judges" | "referee" | "technical-panel" | "kiss-and-cry";
|
|
11
|
+
/** What lies inside the long track: the ice of the warm-up lane carried on to the infield, or the floor of the arena. */
|
|
12
|
+
export type SkatingInfield = "floor" | "ice";
|
|
13
|
+
/**
|
|
14
|
+
* What a skating drawing takes beyond a surface. Every part is optional: the standard 400 m track with an inner radius of 25 m, two lanes of 4 m, the warm-up lane, the blocks and the padding is drawn
|
|
15
|
+
* without any, and so are the short track's standard oval with its five tracks and the figure skating rink of 60 m by 30 m.
|
|
16
|
+
*/
|
|
17
|
+
export interface SkatingStyle {
|
|
18
|
+
/** Long track: the radius of the inner edge of the inner lane, 25 m to 26 m (25 m by default). The straight follows, so that a lap is 400 m. */
|
|
19
|
+
innerRadius?: number;
|
|
20
|
+
/** Long track: the width of the outer lane, 4 m or more (4 m by default). */
|
|
21
|
+
laneWidth?: number;
|
|
22
|
+
/** Long track: the warm-up lane inside the competition lanes. On by default. */
|
|
23
|
+
warmUp?: boolean;
|
|
24
|
+
/** Long track: what lies inside the warm-up lane. `floor` by default. */
|
|
25
|
+
infield?: SkatingInfield;
|
|
26
|
+
/** Long and short track: the blocks of the curves. On by default. */
|
|
27
|
+
blocks?: boolean;
|
|
28
|
+
/** Long track: the coaches' area on the crossing straight. On by default. */
|
|
29
|
+
coaches?: boolean;
|
|
30
|
+
/** The padding round the track. On by default. */
|
|
31
|
+
padding?: boolean;
|
|
32
|
+
/** The races whose starting and finishing lines are drawn: none by default, `all` or a list. The finishing line is always drawn. */
|
|
33
|
+
races?: "all" | readonly SkatingRace[];
|
|
34
|
+
/** The race of a close-up of a start (`range: "start"`): the 500 m by default. */
|
|
35
|
+
start?: SkatingRace;
|
|
36
|
+
/** Short track: the number of tracks laid out beside the standard one, 5 or 7 (5 by default). */
|
|
37
|
+
tracks?: 5 | 7;
|
|
38
|
+
/** Short track and figure skating: the size of the rink, within what the Rules allow. */
|
|
39
|
+
rink?: {
|
|
40
|
+
length?: number;
|
|
41
|
+
width?: number;
|
|
42
|
+
};
|
|
43
|
+
/** Figure skating: the furniture drawn: all by default, `false` for none or a list. */
|
|
44
|
+
furniture?: boolean | readonly SkatingFurniture[];
|
|
45
|
+
}
|
|
46
|
+
/** A race, as the package knows it. */
|
|
47
|
+
export interface RaceInfo {
|
|
48
|
+
id: SkatingRace;
|
|
49
|
+
/** The distance in metres; the mass start's is its laps (16 laps of 400 m). */
|
|
50
|
+
distance: number;
|
|
51
|
+
/** The words of its name, in English, Japanese and French. */
|
|
52
|
+
name: {
|
|
53
|
+
en: string;
|
|
54
|
+
ja: string;
|
|
55
|
+
fr: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { skatingPairs, skatingGrid, skatingResults, skatingReplay, figureElements, formatTime, distanceSkated } from "./charts.skating.ts";
|
|
2
|
+
export type { DrawTotals, ElementTotals, FigureElementsOptions, SkatingElement, SkatingEntry, SkatingPair, SkatingRaceOptions, SkatingReplay, SkatingReplayOptions, SkatingResult, SkatingResultsTotals, SkatingRunner, SkatingStarter } from "./charts.skating.ts";
|
|
3
|
+
export { ELEMENT_KINDS, ELEMENT_NAMES, LONG_TRACK_RACES, LONG_TRACK_RACE_IDS, RACES, RACE_IDS, SHORT_TRACK_RACES, SHORT_TRACK_RACE_IDS, raceById } from "./skating/events.ts";
|
|
4
|
+
export type { ElementKind } from "./skating/events.ts";
|
|
5
|
+
export { disciplineOf, furnitureOf, racesOf, skatingLeague, skatingPlan } from "./skating/plan.ts";
|
|
6
|
+
export type { MBox, MMark, MRange, SkatingPlan } from "./skating/plan.ts";
|
|
7
|
+
export { finishAt, laneEdges, laneRadius, longOval, outerEdge, pose, startOf } from "./skating/track.ts";
|
|
8
|
+
export type { Lane, LongOval, Pose } from "./skating/track.ts";
|
|
9
|
+
export { markRace } from "./skating/long.ts";
|
|
10
|
+
export type { RaceMarks, StartPlacement } from "./skating/long.ts";
|
|
11
|
+
export { shortOval, shortPose, startingPositions, startsOnOppositeStraight, trackLines } from "./skating/short.ts";
|
|
12
|
+
export type { ShortOval } from "./skating/short.ts";
|
|
13
|
+
export { FURNITURE, JUDGES, furnitureBoxes } from "./skating/figure.ts";
|
|
14
|
+
export { ISU_FIGURE_RINK, ISU_LONG_TRACK, ISU_LONG_TRACK_TABLE, ISU_SHORT_TRACK, SKATING_LEAGUES } from "./leagues/skating.ts";
|
|
15
|
+
export type { FigureRinkFigures, LongTrackFigures, ShortTrackFigures, SkatingFigures, SkatingLeague } from "./leagues/skating.ts";
|
|
16
|
+
export { KYOGIJO_SKATING_STYLE } from "./skating.style.ts";
|
|
17
|
+
export type { LongTrackRace, RaceInfo, ShortTrackRace, SkatingDiscipline, SkatingFurniture, SkatingInfield, SkatingRace, SkatingStyle } from "./skating/types.ts";
|