@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.6.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 +133 -0
- package/NOTICE.md +182 -2
- package/README.md +106 -19
- package/dist/annotate.js +1 -1
- package/dist/arenas-entry.d.ts +4 -0
- package/dist/arenas-entry.js +3 -0
- package/dist/arenas.d.ts +125 -0
- package/dist/arenas.data.d.ts +2 -0
- package/dist/arenas.data.js +3763 -0
- package/dist/arenas.js +72 -0
- 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 +6 -2
- package/dist/atlas-entry.js +4 -2
- package/dist/atlas.d.ts +117 -23
- package/dist/atlas.js +197 -12
- package/dist/background.js +2 -1
- package/dist/budo/describe.d.ts +13 -0
- package/dist/budo/describe.js +120 -0
- package/dist/budo/dojo.api.d.ts +64 -0
- package/dist/budo/dojo.api.js +46 -0
- package/dist/budo/dojo.d.ts +72 -0
- package/dist/budo/dojo.js +300 -0
- package/dist/budo/judo.d.ts +61 -0
- package/dist/budo/judo.js +109 -0
- package/dist/budo/karate.d.ts +29 -0
- package/dist/budo/karate.js +80 -0
- package/dist/budo/kendo.d.ts +18 -0
- package/dist/budo/kendo.js +44 -0
- package/dist/budo/kimarite.d.ts +19 -0
- package/dist/budo/kimarite.js +33 -0
- package/dist/budo/kit.d.ts +16 -0
- package/dist/budo/kit.js +24 -0
- package/dist/budo/kyudo.d.ts +28 -0
- package/dist/budo/kyudo.js +66 -0
- package/dist/budo/looks.d.ts +28 -0
- package/dist/budo/looks.js +58 -0
- package/dist/budo/parts.d.ts +32 -0
- package/dist/budo/parts.js +10 -0
- package/dist/budo/plan.d.ts +64 -0
- package/dist/budo/plan.js +139 -0
- package/dist/budo/room.d.ts +133 -0
- package/dist/budo/room.js +111 -0
- package/dist/budo/sumo.d.ts +31 -0
- package/dist/budo/sumo.js +71 -0
- package/dist/budo/tatami.d.ts +55 -0
- package/dist/budo/tatami.js +156 -0
- package/dist/budo/types.d.ts +192 -0
- package/dist/budo/types.js +2 -0
- package/dist/budo-entry.d.ts +22 -0
- package/dist/budo-entry.js +13 -0
- package/dist/budo.board.d.ts +28 -0
- package/dist/budo.board.js +81 -0
- package/dist/budo.score.d.ts +18 -0
- package/dist/budo.score.js +52 -0
- package/dist/budo.style.d.ts +6 -0
- package/dist/budo.style.js +67 -0
- 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.baseball.js +1 -1
- 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.js +4 -4
- 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.soccer.js +3 -3
- package/dist/charts.types.d.ts +63 -1
- package/dist/colour.d.ts +4 -4
- package/dist/colour.js +4 -4
- 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 +164 -0
- package/dist/crowd.draw.d.ts +17 -0
- package/dist/crowd.draw.js +204 -0
- package/dist/crowd.js +480 -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 +462 -54
- package/dist/element-entry.d.ts +3 -1
- package/dist/element-entry.js +2 -1
- package/dist/element.d.ts +13 -2
- package/dist/element.js +79 -4
- package/dist/emblems-source.d.ts +50 -0
- package/dist/emblems-source.js +56 -0
- package/dist/eras.d.ts +3 -1
- package/dist/eras.js +8 -3
- package/dist/features.js +204 -0
- package/dist/field.data.d.ts +16 -0
- package/dist/field.data.js +50 -0
- package/dist/fields.kit.d.ts +2 -2
- package/dist/fields.types.d.ts +24 -4
- package/dist/formations.js +18 -7
- package/dist/formations.sports.d.ts +8 -0
- package/dist/formations.sports.js +251 -0
- package/dist/frames.providers.js +71 -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/halls.d.ts +65 -0
- package/dist/halls.js +122 -0
- package/dist/hardwood.d.ts +31 -0
- package/dist/hardwood.js +157 -0
- package/dist/heat.js +21 -13
- package/dist/heat.types.d.ts +5 -0
- package/dist/heat.zones.d.ts +4 -2
- package/dist/heat.zones.js +207 -7
- package/dist/homeside.js +24 -0
- package/dist/index.d.ts +30 -4
- package/dist/index.js +18 -2
- package/dist/layers-entry.d.ts +2 -1
- package/dist/layers-entry.js +1 -0
- package/dist/layers.d.ts +2 -1
- package/dist/layers.env.d.ts +12 -1
- package/dist/layers.env.js +36 -4
- package/dist/layers.js +32 -13
- package/dist/layers.types.d.ts +9 -2
- package/dist/leagues/athletics.d.ts +63 -0
- package/dist/leagues/athletics.js +87 -0
- package/dist/leagues/budo.d.ts +196 -0
- package/dist/leagues/budo.js +105 -0
- package/dist/leagues/courts.d.ts +269 -4
- package/dist/leagues/courts.js +155 -1
- package/dist/leagues/hockey.js +41 -19
- package/dist/leagues/skating.d.ts +145 -0
- package/dist/leagues/skating.js +73 -0
- package/dist/leagues/sources.js +41 -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 +17 -4
- package/dist/leagues.js +246 -5
- package/dist/lineup.codes.d.ts +23 -0
- package/dist/lineup.codes.js +122 -0
- package/dist/logo.d.ts +25 -0
- package/dist/logo.js +47 -0
- package/dist/look.d.ts +3 -3
- package/dist/look.js +39 -6
- package/dist/outline.d.ts +1 -1
- package/dist/outline.js +46 -4
- 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 +211 -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/pointer-entry.d.ts +2 -0
- package/dist/pointer-entry.js +2 -0
- package/dist/pointer.d.ts +113 -0
- package/dist/pointer.js +151 -0
- package/dist/pool.d.ts +176 -0
- package/dist/pool.figures.d.ts +306 -0
- package/dist/pool.figures.js +148 -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 +258 -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 +53 -3
- package/dist/replay-entry.d.ts +3 -0
- package/dist/replay-entry.js +2 -0
- package/dist/restyle.js +1 -1
- package/dist/road.draw.js +3 -2
- package/dist/road.js +2 -1
- package/dist/road.types.d.ts +3 -0
- package/dist/rules.d.ts +138 -0
- package/dist/rules.js +511 -0
- package/dist/rules.types.d.ts +4 -0
- package/dist/rules.types.js +64 -0
- package/dist/score.d.ts +3 -0
- package/dist/score.js +93 -6
- package/dist/sheet.d.ts +3 -0
- package/dist/sheet.js +7 -3
- package/dist/situation.d.ts +135 -0
- package/dist/situation.draw.d.ts +34 -0
- package/dist/situation.draw.js +202 -0
- package/dist/situation.js +337 -0
- package/dist/situation.style.d.ts +2 -0
- package/dist/situation.style.js +45 -0
- package/dist/situation.types.d.ts +77 -0
- package/dist/situation.types.js +3 -0
- package/dist/situation.widgets.d.ts +39 -0
- package/dist/situation.widgets.js +129 -0
- 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 +470 -0
- package/dist/strings.js +674 -13
- package/dist/style.d.ts +2 -0
- package/dist/style.js +19 -2
- 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 +17 -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 +44 -22
- package/dist/surfaces/budo.d.ts +6 -0
- package/dist/surfaces/budo.js +104 -0
- 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/football.js +7 -0
- package/dist/surfaces/hockey.js +4 -2
- 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 +76 -0
- package/dist/surfaces/venue.d.ts +15 -0
- package/dist/surfaces/venue.js +95 -0
- package/dist/target-entry.d.ts +2 -0
- package/dist/target-entry.js +2 -0
- package/dist/target.d.ts +200 -0
- package/dist/target.js +525 -0
- package/dist/team.common.d.ts +1 -1
- package/dist/team.common.js +3 -3
- package/dist/team.formation.d.ts +3 -1
- package/dist/team.formation.js +3 -3
- package/dist/team.layers.d.ts +2 -2
- package/dist/team.layers.js +4 -1
- package/dist/team.lineup.d.ts +27 -0
- package/dist/team.lineup.js +207 -0
- package/dist/team.network.js +6 -5
- package/dist/team.shape.js +5 -5
- package/dist/team.types.d.ts +40 -1
- package/dist/tenants.d.ts +77 -0
- package/dist/tenants.data.d.ts +8 -0
- package/dist/tenants.data.js +1793 -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 +116 -0
- package/dist/turn.camera.js +184 -0
- package/dist/turn.dom.d.ts +40 -0
- package/dist/turn.dom.js +353 -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 +56 -0
- package/dist/types.d.ts +221 -20
- package/dist/units.d.ts +1 -1
- package/dist/units.js +22 -1
- package/dist/uprights.board.d.ts +17 -0
- package/dist/uprights.board.js +59 -0
- package/dist/uprights.budo.d.ts +17 -0
- package/dist/uprights.budo.js +87 -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 +169 -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/dist/zones-entry.d.ts +2 -0
- package/dist/zones-entry.js +2 -0
- package/package.json +56 -3
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// What stands on a budō surface, in 3D: the dohyō's clay mound (the clay square stands 34 cm to 60 cm over the floor of the hall, Sumo Rules, Dohyō Regulations, Article 1), the azuchi of a kyūdōjō with the
|
|
2
|
+
// targets on it (36 cm across and 27 cm over the mound's base, tilted 5 degrees; 100 cm, 97 cm and 15 degrees for enteki), and the walls of a dojo. A judo, karate or kendo surface lies flat: nothing stands on it
|
|
3
|
+
// (a judo platform's height is not in the rules: "it depends on the sightlines"). The plans are in metres in the frame of their own origin, and the drawing's frame is feet from the corner of the plan's
|
|
4
|
+
// box, so every point goes through `at`. The mound's height is the option's; the azuchi's (1.5 m or 1.8 m) and a dojo wall's (3 m) are ours: no rule gives them.
|
|
5
|
+
import { budoPlan } from "./budo/plan.js";
|
|
6
|
+
import { n, plane, shy, wall } from "./turn.kit.js";
|
|
7
|
+
const K = 1 / 0.3048;
|
|
8
|
+
/** The walls of a dojo, 3 m high (ours). */
|
|
9
|
+
export const DOJO_WALL = 3;
|
|
10
|
+
/** The planes of the dohyō's clay mound: its four sides, from the floor of the hall up to the clay. */
|
|
11
|
+
function moundPlanes(plan, at, height) {
|
|
12
|
+
const L = plan.sumo;
|
|
13
|
+
if (L === undefined)
|
|
14
|
+
return [];
|
|
15
|
+
const b = L.platform;
|
|
16
|
+
const corners = [at(b.x0, b.y0), at(b.x1, b.y0), at(b.x1, b.y1), at(b.x0, b.y1)];
|
|
17
|
+
const planes = [];
|
|
18
|
+
corners.forEach((a, i) => {
|
|
19
|
+
const c = corners[(i + 1) % 4];
|
|
20
|
+
planes.push(wall(`mound-${i + 1}`, "wall", a[0], a[1], c[0], c[1], 0, height, `<rect width="${n(Math.hypot(c[0] - a[0], c[1] - a[1]))}" height="${n(height)}" class="u-clay"/>`));
|
|
21
|
+
});
|
|
22
|
+
return planes;
|
|
23
|
+
}
|
|
24
|
+
/** The azuchi's face, a slope from the foot of the mound up to its top, and the target of each shooter standing against it. */
|
|
25
|
+
function azuchiPlanes(plan, at) {
|
|
26
|
+
const L = plan.kyudo;
|
|
27
|
+
if (L === undefined)
|
|
28
|
+
return { mound: [], targets: [], height: 0 };
|
|
29
|
+
const fig = plan.figures;
|
|
30
|
+
const h = fig.azuchi.height * K;
|
|
31
|
+
const a = L.azuchi;
|
|
32
|
+
// The face runs from the foot, in front of the target's line, up to the top at the target's line: a slope of about 50 degrees reaches 1.5 m in 1.26 m.
|
|
33
|
+
const run = (fig.azuchi.height / Math.tan((50 * Math.PI) / 180)) * K;
|
|
34
|
+
const [fx, fy0] = at(a.x0, a.y0);
|
|
35
|
+
const [, fy1] = at(a.x0, a.y1);
|
|
36
|
+
const topX = fx + run;
|
|
37
|
+
const mound = plane("azuchi-face", "wall", [topX, fy0, h], [0, fy1 - fy0, 0], [-run, 0, -h], `<rect width="${n(fy1 - fy0)}" height="${n(Math.hypot(run, h))}" class="u-clay"/>`);
|
|
38
|
+
const d = fig.target.diameter * K;
|
|
39
|
+
const tilt = (fig.target.tilt * Math.PI) / 180;
|
|
40
|
+
const centre = fig.target.centre * K;
|
|
41
|
+
const targets = L.lanes.map((y, i) => {
|
|
42
|
+
const [tx, ty] = at(L.target, y);
|
|
43
|
+
// The face leans back at the top: its top is further from the shooters than its bottom.
|
|
44
|
+
const top = [tx + (d / 2) * Math.sin(tilt), ty - d / 2, centre + (d / 2) * Math.cos(tilt)];
|
|
45
|
+
return plane(`target-${i + 1}`, "target", top, [0, d, 0], [-d * Math.sin(tilt), 0, -d * Math.cos(tilt)], `<circle cx="${n(d / 2)}" cy="${n(d / 2)}" r="${n(d / 2)}" class="u-white"/><circle cx="${n(d / 2)}" cy="${n(d / 2)}" r="${n(d * 0.33)}" class="u-outline" fill="none"/><circle cx="${n(d / 2)}" cy="${n(d / 2)}" r="${n(d * 0.16)}" class="u-outline" fill="none"/>`);
|
|
46
|
+
});
|
|
47
|
+
return { mound: [mound], targets, height: h };
|
|
48
|
+
}
|
|
49
|
+
/** What stands on the budō surface the options draw. */
|
|
50
|
+
export function budoUprights(o, thin) {
|
|
51
|
+
const plan = budoPlan(o);
|
|
52
|
+
const ox = plan.box.x0;
|
|
53
|
+
const oy = plan.box.y0;
|
|
54
|
+
const at = (x, y) => [(x - ox) * K, (y - oy) * K];
|
|
55
|
+
const area = { x0: 0, y0: 0, x1: (plan.box.x1 - ox) * K, y1: (plan.box.y1 - oy) * K };
|
|
56
|
+
const items = [];
|
|
57
|
+
let lift = 0;
|
|
58
|
+
if (plan.sumo !== undefined) {
|
|
59
|
+
lift = plan.sumo.height * K;
|
|
60
|
+
items.push({ id: "mound", kind: "mound", at: at(plan.sumo.centre[0], plan.sumo.centre[1]), height: lift, planes: moundPlanes(plan, at, lift) });
|
|
61
|
+
}
|
|
62
|
+
if (plan.kyudo !== undefined) {
|
|
63
|
+
const built = azuchiPlanes(plan, at);
|
|
64
|
+
items.push({ id: "azuchi", kind: "azuchi", at: at(plan.kyudo.target, plan.kyudo.lanes[0]), height: built.height, planes: built.mound });
|
|
65
|
+
if (built.targets.length > 0)
|
|
66
|
+
items.push({ id: "targets", kind: "targets", at: at(plan.kyudo.target, plan.kyudo.lanes[0]), height: Math.max(...built.targets.map((p) => p.o[2])), planes: built.targets });
|
|
67
|
+
}
|
|
68
|
+
if (plan.dojo !== undefined && plan.dojo.wall > 0) {
|
|
69
|
+
const f = plan.dojo.room;
|
|
70
|
+
const h = DOJO_WALL * K;
|
|
71
|
+
const w = plan.dojo.wall;
|
|
72
|
+
const centre = [area.x1 / 2, area.y1 / 2];
|
|
73
|
+
const planes = [
|
|
74
|
+
[f.x0 - w / 2, f.y0 - w / 2, f.x1 + w / 2, f.y0 - w / 2],
|
|
75
|
+
[f.x1 + w / 2, f.y0 - w / 2, f.x1 + w / 2, f.y1 + w / 2],
|
|
76
|
+
[f.x1 + w / 2, f.y1 + w / 2, f.x0 - w / 2, f.y1 + w / 2],
|
|
77
|
+
[f.x0 - w / 2, f.y1 + w / 2, f.x0 - w / 2, f.y0 - w / 2],
|
|
78
|
+
].map(([x0, y0, x1, y1], i) => {
|
|
79
|
+
const [a, b] = [at(x0, y0), at(x1, y1)];
|
|
80
|
+
const run = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
81
|
+
return shy(wall(`wall-${i + 1}`, "wall", a[0], a[1], b[0], b[1], 0, h, `<rect width="${n(run)}" height="${n(h)}" class="u-wall"/>`), centre);
|
|
82
|
+
});
|
|
83
|
+
items.push({ id: "walls", kind: "walls", at: centre, height: h, planes });
|
|
84
|
+
}
|
|
85
|
+
void thin;
|
|
86
|
+
return { items, lift, area };
|
|
87
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Plane, type Upright, type UprightKind } from "./turn.kit.ts";
|
|
2
|
+
import { type Unit } from "./units.ts";
|
|
3
|
+
import type { Language, SurfaceOptions } from "./types.ts";
|
|
4
|
+
/** What can be asked of the uprights. */
|
|
5
|
+
export interface UprightOptions {
|
|
6
|
+
/** Volleyball: the net at the men's height (2.43 m, the default) or the women's (2.24 m). */
|
|
7
|
+
netHeight?: "men" | "women";
|
|
8
|
+
/** Where the score of a score drawing goes: `board` (the default) hangs it up as a scoreboard (over the middle of a court or a rink, behind an end of a field) and takes the panel off the floor; `floor` leaves it on the floor. */
|
|
9
|
+
score?: "board" | "floor";
|
|
10
|
+
/** Leave the boards and the glass off a rink, and the walls and fence off a padel, squash or racquetball court, so that it can be seen into. On by default. */
|
|
11
|
+
boards?: boolean;
|
|
12
|
+
/** Only these kinds of thing; every kind the surface has when missing. */
|
|
13
|
+
only?: readonly UprightKind[];
|
|
14
|
+
}
|
|
15
|
+
/** What stands on a surface. */
|
|
16
|
+
export interface Uprights {
|
|
17
|
+
items: Upright[];
|
|
18
|
+
/** How far the surface itself stands over the ground, in feet: a table tennis table's top, 0 on every other surface. */
|
|
19
|
+
lift: number;
|
|
20
|
+
/** The thinnest line or strip drawn on this surface, in feet. */
|
|
21
|
+
thin: number;
|
|
22
|
+
/** The playing surface itself in feet, the part the view is fitted to (the ground round it may run out of the picture): `undefined` for a surface with no uprights, which is fitted whole. */
|
|
23
|
+
area?: {
|
|
24
|
+
x0: number;
|
|
25
|
+
y0: number;
|
|
26
|
+
x1: number;
|
|
27
|
+
y1: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** All the planes of all the things. */
|
|
31
|
+
export declare const planesOf: (u: Pick<Uprights, "items">) => Plane[];
|
|
32
|
+
/** What stands on the surface the options draw: nothing on a curling sheet, which is flat. */
|
|
33
|
+
export declare function uprightsFor(o: SurfaceOptions, settings?: UprightOptions): Uprights;
|
|
34
|
+
/** The unit the heights are said in: the options' own, else the rulebook's of the sport. */
|
|
35
|
+
export declare function heightUnit(o: Pick<SurfaceOptions, "sport" | "league" | "units">): Unit;
|
|
36
|
+
/** What stands on the surface, in a sentence, with heights: for a reader who cannot see the 3D view. An empty string when nothing stands on it. */
|
|
37
|
+
export declare function describeUprights(o: SurfaceOptions, uprights: Uprights, language?: Language): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FieldPlan } from "./fields.types.ts";
|
|
2
|
+
import { type FencePoint, type FootballSpec } from "./geometry.ts";
|
|
3
|
+
import type { SoccerSpec } from "./surfaces/soccer-spec.ts";
|
|
4
|
+
import type { ParkWall } from "./parks.ts";
|
|
5
|
+
import { type Plane, type Upright } from "./turn.kit.ts";
|
|
6
|
+
/**
|
|
7
|
+
* The crossbar of a football post is 10 ft over the ground (NFL 2026 Rule 1-3-1: "the top face of which is 10 feet above the ground"; CFL 2026 Rule 1-1-1:
|
|
8
|
+
* "a height of 10 feet"; NCAA 2025 Rule 1-2-5-a: "the top of which is 10 feet above the ground"). The uprights reach 35 ft over the bar in the NFL (Rule 1-3-2: 45 ft
|
|
9
|
+
* in all), 40 ft above the ground in the CFL (Rule 1-1-1: "extend 40 feet above ground level"), and at least 30 ft above the ground in the NCAA (Rule 1-2-5-a), the
|
|
10
|
+
* figure drawn for every other league.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FOOTBALL_CROSSBAR = 10;
|
|
13
|
+
/** The pylon is 18 in high: the NFL's Rule 1-2-3 names pylons but not their size; BAFA's Appendix D (14) recommends 4 in by 4 in and 18 in. */
|
|
14
|
+
export declare const PYLON_HEIGHT = 1.5;
|
|
15
|
+
/** A football field's uprights: the post at each end and the eight pylons. `shape` is the post of the season, `top` the height of the uprights above the ground. */
|
|
16
|
+
export declare function footballUprights(spec: FootballSpec, shape: "h" | "slingshot", top: number, thin: number): Upright[];
|
|
17
|
+
/** A soccer pitch's uprights: both goals and, unless the options turn them off, the four corner flags. */
|
|
18
|
+
export declare function soccerUprights(spec: SoccerSpec, flags: boolean, thin: number): Upright[];
|
|
19
|
+
/** The rugby posts, Gaelic goal posts, Australian football posts and cricket stumps; a goal with a net for the sports that have one. */
|
|
20
|
+
export declare function fieldUprights(plan: FieldPlan, thin: number): Upright[];
|
|
21
|
+
/** The fence's height where no wall is listed: 8 feet. */
|
|
22
|
+
export declare const FENCE_HEIGHT = 8;
|
|
23
|
+
/** How high the wall stands at an angle from dead centre, the wall that names it or the usual 8 feet. */
|
|
24
|
+
export declare const wallHeightAt: (walls: readonly ParkWall[], angle: number) => number;
|
|
25
|
+
/** The planes of a fence: one standing wall over each stretch of its line, as high as the wall there. */
|
|
26
|
+
export declare function fencePlanes(points: readonly FencePoint[], walls: readonly ParkWall[], thin: number): Plane[];
|
|
27
|
+
/**
|
|
28
|
+
* A ballpark's uprights: the fence, the two foul poles where the foul lines meet it, and the backstop behind home plate. Official Baseball Rules 2025, 2.01, gives no height
|
|
29
|
+
* for a fence or a foul pole, and only recommends the backstop 60 ft or more from home base: the 8 ft of the fence where no wall is listed, the poles' 30 ft and the
|
|
30
|
+
* screen's 70 ft by 20 ft are ours.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ballparkUprights(points: readonly FencePoint[], walls: readonly ParkWall[], thin: number): Upright[];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { FEET_PER_METRE, fenceAt, footballLength } from "./geometry.js";
|
|
2
|
+
import { fenceOutline } from "./surfaces/baseball.js";
|
|
3
|
+
import { flagPole, goalCage, postPair } from "./uprights.goal.js";
|
|
4
|
+
import { mesh, n, pole, threads, wall } from "./turn.kit.js";
|
|
5
|
+
const M = FEET_PER_METRE;
|
|
6
|
+
/**
|
|
7
|
+
* The crossbar of a football post is 10 ft over the ground (NFL 2026 Rule 1-3-1: "the top face of which is 10 feet above the ground"; CFL 2026 Rule 1-1-1:
|
|
8
|
+
* "a height of 10 feet"; NCAA 2025 Rule 1-2-5-a: "the top of which is 10 feet above the ground"). The uprights reach 35 ft over the bar in the NFL (Rule 1-3-2: 45 ft
|
|
9
|
+
* in all), 40 ft above the ground in the CFL (Rule 1-1-1: "extend 40 feet above ground level"), and at least 30 ft above the ground in the NCAA (Rule 1-2-5-a), the
|
|
10
|
+
* figure drawn for every other league.
|
|
11
|
+
*/
|
|
12
|
+
export const FOOTBALL_CROSSBAR = 10;
|
|
13
|
+
/** The pylon is 18 in high: the NFL's Rule 1-2-3 names pylons but not their size; BAFA's Appendix D (14) recommends 4 in by 4 in and 18 in. */
|
|
14
|
+
export const PYLON_HEIGHT = 1.5;
|
|
15
|
+
/** A football field's uprights: the post at each end and the eight pylons. `shape` is the post of the season, `top` the height of the uprights above the ground. */
|
|
16
|
+
export function footballUprights(spec, shape, top, thin) {
|
|
17
|
+
const L = footballLength(spec);
|
|
18
|
+
const W = spec.width;
|
|
19
|
+
const e = spec.endZone;
|
|
20
|
+
const items = [];
|
|
21
|
+
const post = Math.max(thin * 2.6, 0.8);
|
|
22
|
+
for (const end of ["left", "right"]) {
|
|
23
|
+
const left = end === "left";
|
|
24
|
+
const x = spec.posts === "end-line" ? (left ? 0 : L) : left ? e : L - e;
|
|
25
|
+
items.push(postPair({ id: `posts-${end}`, x, y: W / 2, out: left ? -1 : 1, gap: spec.postWidth ?? 18.5, crossbar: FOOTBALL_CROSSBAR, top, post, support: shape === "slingshot" ? "slingshot" : "centre", stem: 4, cls: "u-yellow" }));
|
|
26
|
+
}
|
|
27
|
+
if (spec.pylons !== false) {
|
|
28
|
+
// Eight pylons: at the four corners of the goal lines and two on each end line (NFL Rule 1-2-3), as the drawing puts them.
|
|
29
|
+
const corners = [[e, 0], [e, W], [L - e, 0], [L - e, W], [0, 0], [0, W], [L, 0], [L, W]];
|
|
30
|
+
const side = Math.max(thin * 1.6, 0.33);
|
|
31
|
+
items.push({ id: "pylons", kind: "pylons", at: [L / 2, W / 2], height: PYLON_HEIGHT, planes: corners.flatMap(([x, y], i) => pole(`pylon-${i}`, "pylon", x, y, 0, PYLON_HEIGHT, side, "u-orange")) });
|
|
32
|
+
}
|
|
33
|
+
return items;
|
|
34
|
+
}
|
|
35
|
+
/** A soccer pitch's uprights: both goals and, unless the options turn them off, the four corner flags. */
|
|
36
|
+
export function soccerUprights(spec, flags, thin) {
|
|
37
|
+
const L = spec.length * M;
|
|
38
|
+
const W = spec.width * M;
|
|
39
|
+
const items = [];
|
|
40
|
+
const depth = spec.goalDepth * M;
|
|
41
|
+
for (const end of ["left", "right"]) {
|
|
42
|
+
const left = end === "left";
|
|
43
|
+
items.push(goalCage({ id: `goal-${end}`, x: left ? 0 : L, y: W / 2, out: left ? -1 : 1, width: spec.goalWidth * M, height: spec.goalHeight * M, depth, top: depth * 0.55, post: Math.max(thin * 1.3, 0.35), cell: Math.max(thin * 6, 0.9), tone: "white" }));
|
|
44
|
+
}
|
|
45
|
+
if (flags) {
|
|
46
|
+
// A corner flagpost is at least 1.5 m (5 ft) high (IFAB Laws of the Game 2025/26, Law 1, section 8); the goal is 7.32 m wide and 2.44 m to the crossbar's lower edge (section 10).
|
|
47
|
+
const planes = [[0, 0], [L, 0], [0, W], [L, W]].flatMap(([x, y], i) => flagPole(`flag-${i}`, x, y, 1.5 * M, Math.max(thin * 0.9, 0.15)));
|
|
48
|
+
items.push({ id: "flags", kind: "flags", at: [L / 2, W / 2], height: 1.5 * M, planes });
|
|
49
|
+
}
|
|
50
|
+
return items;
|
|
51
|
+
}
|
|
52
|
+
/** The dots of a plan with these ids: the posts, flags and stumps the plan places. */
|
|
53
|
+
const dots = (plan, ...ids) => plan.shapes.flatMap((s) => (s.kind === "dot" && ids.includes(s.id) ? [{ x: s.cx, y: s.cy }] : []));
|
|
54
|
+
/** The pairs of posts a plan places by their x: two dots at one x, the uprights of one end. */
|
|
55
|
+
function pairs(points) {
|
|
56
|
+
const by = new Map();
|
|
57
|
+
for (const p of points)
|
|
58
|
+
by.set(n(p.x), [...(by.get(n(p.x)) ?? []), p]);
|
|
59
|
+
return [...by.values()].filter((list) => list.length === 2).map(([a, b]) => ({ x: a.x, y: (a.y + b.y) / 2, gap: Math.abs(a.y - b.y) }));
|
|
60
|
+
}
|
|
61
|
+
/** The rugby posts, Gaelic goal posts, Australian football posts and cricket stumps; a goal with a net for the sports that have one. */
|
|
62
|
+
export function fieldUprights(plan, thin) {
|
|
63
|
+
const items = [];
|
|
64
|
+
const post = Math.max(thin * 1.4, 0.3);
|
|
65
|
+
const midX = plan.length / 2;
|
|
66
|
+
const outOf = (x) => (x < midX ? -1 : 1);
|
|
67
|
+
const flags = (points, height, id) => {
|
|
68
|
+
if (points.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
items.push({ id, kind: "flags", at: [midX, plan.width / 2], height, planes: points.flatMap((p, i) => flagPole(`${id}-${i}`, p.x, p.y, height, Math.max(thin * 0.9, 0.15))) });
|
|
71
|
+
};
|
|
72
|
+
switch (plan.sport) {
|
|
73
|
+
case "rugby": {
|
|
74
|
+
// Union (World Rugby 2026 Law 1, goal posts and crossbar): the crossbar's top edge 3 m over the ground, a minimum post height of 3.4 m, which the Law's diagram
|
|
75
|
+
// draws over the bar and is drawn so (6.4 m); the flag posts at least 1.2 m (Law 1, flag posts 8). League (NRL Section 18, IRL Section 1): posts 16 m high.
|
|
76
|
+
const league = plan.league === "nrl" || plan.league === "irl";
|
|
77
|
+
for (const p of pairs(dots(plan, "goal-post")))
|
|
78
|
+
items.push(postPair({ id: p.x < midX ? "posts-left" : "posts-right", x: p.x, y: p.y, out: outOf(p.x), gap: p.gap, crossbar: 3 * M, top: league ? 16 * M : 3 * M + 3.4 * M, post, support: "none", cls: "u-white" }));
|
|
79
|
+
flags(dots(plan, "flag-post", "corner-post"), 1.2 * M, "flags");
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "gaelic": {
|
|
83
|
+
// GAA Official Guide Part 2 (2026) Rule 1.3 (i), (ii): posts not less than 7 m above the ground and 6.5 m apart, the crossbar 2.5 m above the ground. Rule 1.7 gives
|
|
84
|
+
// the flags no height; 1.5 m is ours.
|
|
85
|
+
for (const p of pairs(dots(plan, "goalpost")))
|
|
86
|
+
items.push(postPair({ id: p.x < midX ? "posts-left" : "posts-right", x: p.x, y: p.y, out: outOf(p.x), gap: p.gap, crossbar: 2.5 * M, top: 7 * M, post, support: "none", cls: "u-white" }));
|
|
87
|
+
flags(dots(plan, "flag"), 1.5 * M, "flags");
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "afl": {
|
|
91
|
+
// Laws of Australian Football 2026, Law 3.3.1: goal posts 6 to 15 m high and 6.4 m apart, behind posts 3 to 10 m high, 6.4 m either side of them: the least is drawn.
|
|
92
|
+
const goals = dots(plan, "goal-post");
|
|
93
|
+
const behinds = dots(plan, "behind-post");
|
|
94
|
+
for (const end of ["left", "right"]) {
|
|
95
|
+
const left = end === "left";
|
|
96
|
+
const mine = (p) => (p.x < midX) === left;
|
|
97
|
+
const planes = [
|
|
98
|
+
...goals.filter(mine).flatMap((p, i) => pole(`goal-post-${i}`, "post", p.x, p.y, 0, 6 * M, post, "u-white")),
|
|
99
|
+
...behinds.filter(mine).flatMap((p, i) => pole(`behind-post-${i}`, "post", p.x, p.y, 0, 3 * M, post, "u-white")),
|
|
100
|
+
];
|
|
101
|
+
items.push({ id: `posts-${end}`, kind: "goalposts", at: [left ? 0 : plan.length, plan.width / 2], height: 6 * M, low: 3 * M, planes });
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "cricket": {
|
|
106
|
+
// MCC Laws of Cricket, Law 8.1 and 8.2: three stumps across a wicket 9 in wide, their tops 28 in over the playing surface.
|
|
107
|
+
for (const s of plan.shapes) {
|
|
108
|
+
if (s.kind !== "bar" || s.id !== "wicket")
|
|
109
|
+
continue;
|
|
110
|
+
const x = (s.x0 + s.x1) / 2;
|
|
111
|
+
const ys = [s.y0, (s.y0 + s.y1) / 2, s.y1];
|
|
112
|
+
items.push({ id: x < midX ? "stumps-left" : "stumps-right", kind: "stumps", at: [x, (s.y0 + s.y1) / 2], height: 28 / 12, planes: ys.flatMap((y, i) => pole(`stump-${i}`, "stump", x, y, 0, 28 / 12, Math.max(thin * 0.9, 0.12), "u-wood")) });
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "lacrosse":
|
|
117
|
+
case "fieldhockey":
|
|
118
|
+
case "handball": {
|
|
119
|
+
// The goal: World Lacrosse 6 ft apart and high on a field, 4 ft high in a box; FIH 2.14 m to the crossbar's lower edge; IHF interior height 2 m (docs/sources.md).
|
|
120
|
+
const box = plan.league === "nll" || plan.league === "world-box";
|
|
121
|
+
for (const g of plan.goals) {
|
|
122
|
+
const height = plan.sport === "lacrosse" ? (box ? 4 : 6) : plan.sport === "handball" ? 2 * M : 2.14 * M;
|
|
123
|
+
items.push(goalCage({ id: `goal-${g.end}`, x: g.x, y: g.y, out: g.out, width: g.width, height, depth: g.depth, top: g.depth * (plan.sport === "lacrosse" ? 0.7 : 0.85), post: Math.max(thin * 1.3, 0.3), cell: Math.max(thin * 6, 0.5), tone: "white" }));
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
}
|
|
129
|
+
return items;
|
|
130
|
+
}
|
|
131
|
+
/** The fence's height where no wall is listed: 8 feet. */
|
|
132
|
+
export const FENCE_HEIGHT = 8;
|
|
133
|
+
/** How high the wall stands at an angle from dead centre, the wall that names it or the usual 8 feet. */
|
|
134
|
+
export const wallHeightAt = (walls, angle) => walls.find((w) => angle >= Math.min(w.from, w.to) && angle <= Math.max(w.from, w.to))?.height_ft ?? FENCE_HEIGHT;
|
|
135
|
+
/** The planes of a fence: one standing wall over each stretch of its line, as high as the wall there. */
|
|
136
|
+
export function fencePlanes(points, walls, thin) {
|
|
137
|
+
const line = fenceOutline(points, 3).map(([u, v]) => [u, -v]);
|
|
138
|
+
const planes = [];
|
|
139
|
+
for (let i = 0; i < line.length - 1; i += 1) {
|
|
140
|
+
const [x0, y0] = line[i];
|
|
141
|
+
const [x1, y1] = line[i + 1];
|
|
142
|
+
const run = Math.hypot(x1 - x0, y1 - y0);
|
|
143
|
+
if (run < 1e-6)
|
|
144
|
+
continue;
|
|
145
|
+
const angle = Math.atan2((x0 + x1) / 2, -(y0 + y1) / 2) * (180 / Math.PI);
|
|
146
|
+
const height = wallHeightAt(walls, angle);
|
|
147
|
+
planes.push(wall(`fence-${i}`, "wall", x0, y0, x1, y1, 0, height, `<rect width="${n(run)}" height="${n(height)}" class="u-wall"/><rect width="${n(run)}" height="${n(Math.max(thin * 0.8, 0.2))}" class="u-cap"/>`));
|
|
148
|
+
}
|
|
149
|
+
return planes;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A ballpark's uprights: the fence, the two foul poles where the foul lines meet it, and the backstop behind home plate. Official Baseball Rules 2025, 2.01, gives no height
|
|
153
|
+
* for a fence or a foul pole, and only recommends the backstop 60 ft or more from home base: the 8 ft of the fence where no wall is listed, the poles' 30 ft and the
|
|
154
|
+
* screen's 70 ft by 20 ft are ours.
|
|
155
|
+
*/
|
|
156
|
+
export function ballparkUprights(points, walls, thin) {
|
|
157
|
+
const items = [];
|
|
158
|
+
const fence = fencePlanes(points, walls, thin);
|
|
159
|
+
const tops = fence.map((p) => -p.v[2]);
|
|
160
|
+
items.push({ id: "fence", kind: "fence", at: [0, -400], height: Math.min(...tops), low: Math.max(...tops), planes: fence });
|
|
161
|
+
const poles = [];
|
|
162
|
+
// The foul poles stand where the foul lines meet the fence, 45 degrees either side of dead centre.
|
|
163
|
+
for (const angle of [-45, 45]) {
|
|
164
|
+
const d = fenceAt(points, angle);
|
|
165
|
+
const t = (angle * Math.PI) / 180;
|
|
166
|
+
poles.push(...pole(`foul-pole-${angle < 0 ? "left" : "right"}`, "pole", d * Math.sin(t), -d * Math.cos(t), 0, 30, Math.max(thin * 1.6, 0.5), "u-yellow"));
|
|
167
|
+
}
|
|
168
|
+
items.push({ id: "foul-poles", kind: "foul-poles", at: [0, -330], height: 30, planes: poles });
|
|
169
|
+
// The backstop: the rules put it at least 60 feet behind home plate. A screen 70 feet wide and 20 high.
|
|
170
|
+
items.push({ id: "backstop", kind: "backstop", at: [0, 60], height: 20, planes: [wall("backstop", "screen", -35, 60, 35, 60, 0, 20, `<rect width="70" height="20" class="u-netfill"/>${threads(mesh(70, 20, 3), 3)}`)] });
|
|
171
|
+
return items;
|
|
172
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Plane, type Upright } from "./turn.kit.ts";
|
|
2
|
+
/** A goal with a net: the mouth on the line x, the net behind it. */
|
|
3
|
+
export interface GoalCage {
|
|
4
|
+
id: string;
|
|
5
|
+
/** Where the middle of the mouth stands on its goal line, and which way the net lies behind it: +1 to larger x, -1 to smaller. */
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
out: 1 | -1;
|
|
9
|
+
/** The mouth: its width across the surface and its height. */
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
/** How far the net reaches behind the line at the ground, and at the top of the back. */
|
|
13
|
+
depth: number;
|
|
14
|
+
top: number;
|
|
15
|
+
/** The frame's thickness, and the mesh's cell, in feet. */
|
|
16
|
+
post: number;
|
|
17
|
+
cell: number;
|
|
18
|
+
/** The frame's paint: white, or red as a hockey goal's. */
|
|
19
|
+
tone: "white" | "red";
|
|
20
|
+
}
|
|
21
|
+
/** A goal: its mouth as three strips (two posts and the crossbar), and its net as four planes, framed. */
|
|
22
|
+
export declare function goalCage(g: GoalCage): Upright;
|
|
23
|
+
/** A pair of uprights with a crossbar between them, standing on the line x (a rugby, Gaelic football or football post). */
|
|
24
|
+
export interface PostPair {
|
|
25
|
+
id: string;
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
/** Which way is behind the goal line, for a post's support. */
|
|
29
|
+
out: 1 | -1;
|
|
30
|
+
/** The space between the two uprights, to the middle of each. */
|
|
31
|
+
gap: number;
|
|
32
|
+
/** The crossbar's height, and the uprights' top, above the surface. */
|
|
33
|
+
crossbar: number;
|
|
34
|
+
top: number;
|
|
35
|
+
/** The strips' thickness. */
|
|
36
|
+
post: number;
|
|
37
|
+
/** `slingshot`: one support behind the goal, bent to carry the crossbar (a football post); `centre`: one pole straight under the crossbar's middle; `none`: the uprights alone. */
|
|
38
|
+
support: "slingshot" | "centre" | "none";
|
|
39
|
+
/** How far behind the goal line a slingshot's support stands. */
|
|
40
|
+
stem?: number;
|
|
41
|
+
/** The class of the paint: `u-yellow` for a football post, `u-white` for the others. */
|
|
42
|
+
cls: string;
|
|
43
|
+
}
|
|
44
|
+
/** A pair of uprights, a crossbar, and the support under them. */
|
|
45
|
+
export declare function postPair(p: PostPair): Upright;
|
|
46
|
+
/** A flag on a pole, the flag lying along x. */
|
|
47
|
+
export declare function flagPole(id: string, x: number, y: number, height: number, post: number, cls?: string): Plane[];
|
|
48
|
+
/** A short rectangle lying flat, such as the foot of a stanchion or the shadow of a table: used where a pole needs a base. */
|
|
49
|
+
export declare function foot(id: string, x: number, y: number, w: number, d: number): Plane;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The parts that more than one sport has: a goal with its net behind it (soccer, hockey, handball, field hockey, lacrosse), a pair of upright posts with a
|
|
2
|
+
// crossbar (football, rugby, Gaelic football), the posts of Australian football, a flag on a pole. Every number is in feet, in the drawing's own frame.
|
|
3
|
+
import { bar, flat, mesh, n, plane, pole, polygon, threads, wall } from "./turn.kit.js";
|
|
4
|
+
/** The mesh over the side of a net, a trapezoid: the front edge is straight up, the back leans out from `top` at the head to `depth` at the ground. */
|
|
5
|
+
function sideMesh(depth, top, height, cell) {
|
|
6
|
+
const wide = Math.max(1, Math.round(depth / cell));
|
|
7
|
+
const high = Math.max(1, Math.round(height / cell));
|
|
8
|
+
let d = "";
|
|
9
|
+
for (let i = 1; i < wide; i += 1)
|
|
10
|
+
d += `M${n((top * i) / wide)} 0L${n((depth * i) / wide)} ${n(height)}`;
|
|
11
|
+
for (let j = 1; j < high; j += 1) {
|
|
12
|
+
const b = (height * j) / high;
|
|
13
|
+
d += `M0 ${n(b)}H${n(top + ((depth - top) * j) / high)}`;
|
|
14
|
+
}
|
|
15
|
+
return d;
|
|
16
|
+
}
|
|
17
|
+
/** A goal: its mouth as three strips (two posts and the crossbar), and its net as four planes, framed. */
|
|
18
|
+
export function goalCage(g) {
|
|
19
|
+
const { id, x, y, out, width, height, depth, top, post, cell } = g;
|
|
20
|
+
const half = width / 2;
|
|
21
|
+
const cls = g.tone === "red" ? "u-red" : "u-white";
|
|
22
|
+
const line = `class="u-frame u-frame-${g.tone}" stroke-width="${n(post * 0.7)}" fill="none" stroke-linejoin="round"`;
|
|
23
|
+
const planes = [];
|
|
24
|
+
// The mouth.
|
|
25
|
+
planes.push(...pole(`${id}-post-a`, "post", x, y - half, 0, height, post, cls), ...pole(`${id}-post-b`, "post", x, y + half, 0, height, post, cls));
|
|
26
|
+
planes.push(...bar(`${id}-bar`, "post", [x, y - half, height], [x, y + half, height], post, cls));
|
|
27
|
+
// The two sides: a trapezoid hanging from the head, leaning out to the back.
|
|
28
|
+
for (const [tag, ys] of [["a", y - half], ["b", y + half]]) {
|
|
29
|
+
const shape = polygon([[0, 0], [top, 0], [depth, height], [0, height]]);
|
|
30
|
+
planes.push(plane(`${id}-side-${tag}`, "goal", [x, ys, height], [out * depth, 0, 0], [0, 0, -height], `<path d="${shape}" class="u-netfill"/>${threads(sideMesh(depth, top, height, cell), cell)}<path d="${shape}" ${line}/>`));
|
|
31
|
+
}
|
|
32
|
+
// The back leans from the head's depth out to the ground's.
|
|
33
|
+
const lean = out * (depth - top);
|
|
34
|
+
planes.push(plane(`${id}-back`, "goal", [x + out * top, y - half, height], [0, width, 0], [lean, 0, -height], `<rect width="${n(width)}" height="${n(Math.hypot(lean, height))}" class="u-netfill"/>${threads(mesh(width, Math.hypot(lean, height), cell), cell)}<rect width="${n(width)}" height="${n(Math.hypot(lean, height))}" ${line}/>`));
|
|
35
|
+
// The roof.
|
|
36
|
+
planes.push(plane(`${id}-roof`, "goal", [x, y - half, height], [0, width, 0], [out * top, 0, 0], `<rect width="${n(width)}" height="${n(top)}" class="u-netfill"/>${threads(mesh(width, top, cell), cell)}<rect width="${n(width)}" height="${n(top)}" ${line}/>`));
|
|
37
|
+
return { id, kind: "goal", at: [x, y], height, planes };
|
|
38
|
+
}
|
|
39
|
+
/** A pair of uprights, a crossbar, and the support under them. */
|
|
40
|
+
export function postPair(p) {
|
|
41
|
+
const { id, x, y, gap, crossbar, top, post, cls } = p;
|
|
42
|
+
const half = gap / 2;
|
|
43
|
+
// A slingshot's uprights begin at the crossbar; the others stand on the ground.
|
|
44
|
+
const from = p.support === "slingshot" ? crossbar : 0;
|
|
45
|
+
const planes = [
|
|
46
|
+
...pole(`${id}-upright-a`, "post", x, y - half, from, top, post, cls),
|
|
47
|
+
...pole(`${id}-upright-b`, "post", x, y + half, from, top, post, cls),
|
|
48
|
+
...bar(`${id}-bar`, "post", [x, y - half, crossbar], [x, y + half, crossbar], post, cls),
|
|
49
|
+
];
|
|
50
|
+
if (p.support === "slingshot") {
|
|
51
|
+
const sx = x + p.out * (p.stem ?? 4);
|
|
52
|
+
planes.push(...pole(`${id}-stem`, "stanchion", sx, y, 0, crossbar, post * 1.4, cls), ...bar(`${id}-neck`, "stanchion", [sx, y, crossbar], [x, y, crossbar], post, cls));
|
|
53
|
+
}
|
|
54
|
+
else if (p.support === "centre")
|
|
55
|
+
planes.push(...pole(`${id}-stem`, "stanchion", x, y, 0, crossbar, post * 1.4, cls));
|
|
56
|
+
return { id, kind: "goalposts", at: [x, y], height: top, low: crossbar, planes };
|
|
57
|
+
}
|
|
58
|
+
/** A flag on a pole, the flag lying along x. */
|
|
59
|
+
export function flagPole(id, x, y, height, post, cls = "u-white") {
|
|
60
|
+
const flag = Math.max(height * 0.28, post * 3);
|
|
61
|
+
return [
|
|
62
|
+
...pole(id, "pole", x, y, 0, height, post, cls),
|
|
63
|
+
wall(`${id}-flag`, "flag", x, y, x + flag, y, height - flag * 0.7, height, `<path d="${polygon([[0, 0], [flag, flag * 0.35], [0, flag * 0.7]])}" class="u-flag"/>`),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/** A short rectangle lying flat, such as the foot of a stanchion or the shadow of a table: used where a pole needs a base. */
|
|
67
|
+
export function foot(id, x, y, w, d) {
|
|
68
|
+
return flat(id, "leg", x - w / 2, y - d / 2, x + w / 2, y + d / 2, 0.01, `<rect width="${n(w)}" height="${n(d)}" class="u-foot"/>`);
|
|
69
|
+
}
|
package/dist/uprights.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// What stands on a surface, in 3D: `uprightsFor` reads the options as `drawKyogijo` does (the sport, the league, the season, the park) and says what stands on the
|
|
2
|
+
// surface, where and how high, as planes in the drawing's own frame (x along the surface and y across it, in feet, z the height above it). Pure: no page, no
|
|
3
|
+
// stylesheet. `describeUprights` says the same in words, for a reader who cannot see it.
|
|
4
|
+
import { courtFor, courtGeometry, isNetSport } from "./court.js";
|
|
5
|
+
import { drawingId, feetFence, lengthUnit } from "./common.js";
|
|
6
|
+
import { drawKyogijo, pitchOf } from "./draw.js";
|
|
7
|
+
import { rulesFor, footballIn } from "./eras.js";
|
|
8
|
+
import { fieldPlan, isFieldSport } from "./fields.js";
|
|
9
|
+
import { footballLength } from "./geometry.js";
|
|
10
|
+
import { diamondFence } from "./leagues/baseball.js";
|
|
11
|
+
import { NBA_COURT } from "./leagues/basketball.js";
|
|
12
|
+
import { NHL_RINK } from "./leagues/hockey.js";
|
|
13
|
+
import { chooseLeague, courtOf, diamondOf, fieldOf, rinkOf } from "./leagues.js";
|
|
14
|
+
import { fenceFor, parkById, parkWallsIn } from "./parks.js";
|
|
15
|
+
import { fenceOutline } from "./surfaces/baseball.js";
|
|
16
|
+
import { KYOGIJO_STRINGS, fill } from "./strings.js";
|
|
17
|
+
import { soccerSpec } from "./surfaces/soccer-spec.js";
|
|
18
|
+
import { thinnest } from "./turn.kit.js";
|
|
19
|
+
import { scoreboard } from "./uprights.board.js";
|
|
20
|
+
import { ballparkUprights, fieldUprights, footballUprights, soccerUprights } from "./uprights.fields.js";
|
|
21
|
+
import { netUprights } from "./uprights.net.js";
|
|
22
|
+
import { isPoolSport, poolPlan, feet as poolFeet } from "./pool.js";
|
|
23
|
+
import { poolUprights } from "./uprights.pools.js";
|
|
24
|
+
import { athleticsUprights, skatingUprights } from "./uprights.track.js";
|
|
25
|
+
import { budoUprights } from "./uprights.budo.js";
|
|
26
|
+
import { isBudoSport } from "./budo/plan.js";
|
|
27
|
+
import { courtUprights, rinkUprights } from "./uprights.rinks.js";
|
|
28
|
+
import { convertLength } from "./units.js";
|
|
29
|
+
import { sheetFor } from "./sheet.js";
|
|
30
|
+
/** All the planes of all the things. */
|
|
31
|
+
export const planesOf = (u) => u.items.flatMap((item) => item.planes);
|
|
32
|
+
/** The unit each sport's rulebook gives its heights in, when the options name none. */
|
|
33
|
+
const RULEBOOK_UNIT = { volleyball: "m", badminton: "m", tabletennis: "m", padel: "m", squash: "m", athletics: "m", skating: "m", judo: "m", karate: "m", kendo: "m", sumo: "m", kyudo: "m", dojo: "m", swimming: "m", waterpolo: "m", diving: "m", artisticswimming: "m", sepaktakraw: "m", teqball: "m", roundnet: "m", netball: "m", soccer: "m", handball: "m", fieldhockey: "m", rugby: "m", afl: "m", gaelic: "m" };
|
|
34
|
+
/** What stands on the surface the options draw: nothing on a curling sheet, which is flat. */
|
|
35
|
+
export function uprightsFor(o, settings = {}) {
|
|
36
|
+
const { sport, league } = chooseLeague(o);
|
|
37
|
+
const unit = lengthUnit(o);
|
|
38
|
+
let items = [];
|
|
39
|
+
let lift = 0;
|
|
40
|
+
let thin = 0.2;
|
|
41
|
+
let area;
|
|
42
|
+
if (sport === "nfl" || sport === "cfl" || sport === "ifaf") {
|
|
43
|
+
const spec = league?.family === "football" ? fieldOf(league, o.year) : footballIn(sport, o.year);
|
|
44
|
+
const shape = league?.family === "football" || sport === "ifaf" ? "slingshot" : rulesFor(sport, o.year).rules.postShape;
|
|
45
|
+
thin = thinnest(footballLength(spec), spec.width);
|
|
46
|
+
area = { x0: 0, y0: 0, x1: footballLength(spec), y1: spec.width };
|
|
47
|
+
// Over the ground: the NFL's 10 + 35 ft, the CFL's 40 ft, and the NCAA's least 30 ft for the rest.
|
|
48
|
+
items = footballUprights(spec, shape, league !== undefined ? 30 : sport === "nfl" ? 45 : sport === "cfl" ? 40 : 30, thin);
|
|
49
|
+
}
|
|
50
|
+
else if (sport === "nhl") {
|
|
51
|
+
const rink = league?.family === "hockey" ? rinkOf(league, o.year) : NHL_RINK;
|
|
52
|
+
const goalLine = league?.family === "hockey" ? rink.goalLine : rulesFor("nhl", o.year).rules.goalLine;
|
|
53
|
+
thin = thinnest(rink.length, rink.width);
|
|
54
|
+
area = { x0: 0, y0: 0, x1: rink.length, y1: rink.width };
|
|
55
|
+
items = rinkUprights(rink, goalLine, thin, settings.boards !== false);
|
|
56
|
+
}
|
|
57
|
+
else if (sport === "nba") {
|
|
58
|
+
const court = league?.family === "basketball" ? courtOf(league, o.year) : NBA_COURT;
|
|
59
|
+
thin = thinnest(court.length, court.width);
|
|
60
|
+
area = { x0: 0, y0: 0, x1: court.length, y1: court.width };
|
|
61
|
+
items = courtUprights(court, thin, league?.id === "fiba");
|
|
62
|
+
}
|
|
63
|
+
else if (sport === "mlb") {
|
|
64
|
+
const diamond = league?.family === "baseball" ? diamondOf(league, o.year) : undefined;
|
|
65
|
+
const park = parkById(o.park);
|
|
66
|
+
const points = fenceFor(park, feetFence(o.fence, unit), o.year, diamond === undefined ? undefined : diamondFence(diamond));
|
|
67
|
+
thin = 0.4;
|
|
68
|
+
items = ballparkUprights(points, park === undefined ? [] : parkWallsIn(park, o.year), thin);
|
|
69
|
+
// The surface is the field out to the fence and the backstop behind home plate.
|
|
70
|
+
const out = fenceOutline(points, 3).map(([u, v]) => [u, -v]);
|
|
71
|
+
area = { x0: Math.min(...out.map((p) => p[0])), x1: Math.max(...out.map((p) => p[0])), y0: Math.min(...out.map((p) => p[1])), y1: 70 };
|
|
72
|
+
}
|
|
73
|
+
else if (sport === "soccer") {
|
|
74
|
+
const spec = soccerSpec(pitchOf(o), o.units === undefined ? "m" : unit);
|
|
75
|
+
thin = thinnest(spec.length * 3.2808, spec.width * 3.2808);
|
|
76
|
+
area = { x0: 0, y0: 0, x1: spec.length * 3.2808, y1: spec.width * 3.2808 };
|
|
77
|
+
items = soccerUprights(spec, o.cornerFlags !== false, thin);
|
|
78
|
+
}
|
|
79
|
+
else if (isNetSport(sport)) {
|
|
80
|
+
const plan = courtFor({ ...o, sport });
|
|
81
|
+
const geo = courtGeometry(plan);
|
|
82
|
+
thin = thinnest(geo.length, geo.width);
|
|
83
|
+
area = { x0: 0, y0: 0, x1: geo.length, y1: geo.width };
|
|
84
|
+
const built = netUprights(plan, geo, { netHeight: settings.netHeight, boards: settings.boards }, thin);
|
|
85
|
+
items = built.items;
|
|
86
|
+
lift = built.lift;
|
|
87
|
+
}
|
|
88
|
+
else if (isPoolSport(sport)) {
|
|
89
|
+
const plan = poolPlan({ sport, pool: o.pool });
|
|
90
|
+
const [w, h] = [poolFeet(plan.length), poolFeet(plan.width)];
|
|
91
|
+
thin = thinnest(w, h);
|
|
92
|
+
area = { x0: 0, y0: 0, x1: w, y1: h };
|
|
93
|
+
items = poolUprights({ ...o, sport }, thin).items;
|
|
94
|
+
}
|
|
95
|
+
else if (sport === "athletics") {
|
|
96
|
+
const built = athleticsUprights({ ...o, sport }, 0.3);
|
|
97
|
+
area = built.area;
|
|
98
|
+
thin = thinnest(area.x1, area.y1);
|
|
99
|
+
items = athleticsUprights({ ...o, sport }, thin).items;
|
|
100
|
+
}
|
|
101
|
+
else if (sport === "skating") {
|
|
102
|
+
const built = skatingUprights({ ...o, sport }, 0.3);
|
|
103
|
+
area = built.area;
|
|
104
|
+
thin = thinnest(area.x1, area.y1);
|
|
105
|
+
items = skatingUprights({ ...o, sport }, thin).items;
|
|
106
|
+
}
|
|
107
|
+
else if (isBudoSport(sport)) {
|
|
108
|
+
const built = budoUprights({ ...o, sport }, 0.3);
|
|
109
|
+
area = built.area;
|
|
110
|
+
thin = thinnest(area.x1, area.y1);
|
|
111
|
+
items = budoUprights({ ...o, sport }, thin).items;
|
|
112
|
+
lift = built.lift;
|
|
113
|
+
}
|
|
114
|
+
else if (isFieldSport(sport)) {
|
|
115
|
+
const plan = fieldPlan({ ...o, sport });
|
|
116
|
+
thin = thinnest(plan.length, plan.width);
|
|
117
|
+
area = { x0: 0, y0: 0, x1: plan.length, y1: plan.width };
|
|
118
|
+
items = fieldUprights(plan, thin);
|
|
119
|
+
}
|
|
120
|
+
if (sport === "curling") {
|
|
121
|
+
const sheet = sheetFor(o).spec;
|
|
122
|
+
thin = thinnest(sheet.length, sheet.width);
|
|
123
|
+
area = { x0: 0, y0: 0, x1: sheet.length, y1: sheet.width };
|
|
124
|
+
}
|
|
125
|
+
// The score, hung up: the badge on a board that turns to face the eye.
|
|
126
|
+
const scored = o.home !== undefined && o.away !== undefined && o.size !== "badge";
|
|
127
|
+
if (scored && settings.score !== "floor" && area !== undefined) {
|
|
128
|
+
const board = scoreboard(drawKyogijo({ ...o, size: "badge", style: "none", id: `${drawingId(o)}-board` }), sport, area, thin);
|
|
129
|
+
if (board !== undefined)
|
|
130
|
+
items.push(board);
|
|
131
|
+
}
|
|
132
|
+
if (settings.only !== undefined)
|
|
133
|
+
items = items.filter((item) => settings.only.includes(item.kind));
|
|
134
|
+
return { items, lift, thin, area };
|
|
135
|
+
}
|
|
136
|
+
/** A length in feet as a rulebook would print it in `unit`: metres to the centimetre, feet to a tenth. */
|
|
137
|
+
function said(feet, unit, language) {
|
|
138
|
+
const value = convertLength(feet, "ft", unit);
|
|
139
|
+
const text = String(Math.round(value * (unit === "m" ? 100 : 10)) / (unit === "m" ? 100 : 10));
|
|
140
|
+
const units = KYOGIJO_STRINGS[language].league.units;
|
|
141
|
+
return `${text} ${units[unit === "m" || unit === "yd" ? unit : "ft"]}`;
|
|
142
|
+
}
|
|
143
|
+
/** The unit the heights are said in: the options' own, else the rulebook's of the sport. */
|
|
144
|
+
export function heightUnit(o) {
|
|
145
|
+
if (o.units !== undefined)
|
|
146
|
+
return lengthUnit(o);
|
|
147
|
+
const { sport, league } = chooseLeague(o);
|
|
148
|
+
return league?.unit ?? RULEBOOK_UNIT[sport] ?? "ft";
|
|
149
|
+
}
|
|
150
|
+
/** What stands on the surface, in a sentence, with heights: for a reader who cannot see the 3D view. An empty string when nothing stands on it. */
|
|
151
|
+
export function describeUprights(o, uprights, language = "en") {
|
|
152
|
+
const words = KYOGIJO_STRINGS[language].turn;
|
|
153
|
+
const unit = heightUnit(o);
|
|
154
|
+
// One entry for each kind, and for a kind that has two (a goal at each end) the first: both are the same.
|
|
155
|
+
const seen = new Set();
|
|
156
|
+
const list = [];
|
|
157
|
+
for (const item of uprights.items) {
|
|
158
|
+
if (seen.has(item.kind))
|
|
159
|
+
continue;
|
|
160
|
+
seen.add(item.kind);
|
|
161
|
+
if (item.height <= 0)
|
|
162
|
+
continue;
|
|
163
|
+
const name = words.parts[item.kind];
|
|
164
|
+
// A rink's boards are said by the top of the glass; a table by the height of its top.
|
|
165
|
+
const height = item.kind === "boards" ? (item.low ?? item.height) : item.height;
|
|
166
|
+
list.push(item.kind === "net" && item.low !== undefined && Math.abs(item.low - item.height) > 0.02 ? fill(words.itemSag, { name, h: said(height, unit, language), low: said(item.low, unit, language) }) : fill(words.item, { name, h: said(height, unit, language) }));
|
|
167
|
+
}
|
|
168
|
+
return list.length === 0 ? "" : fill(words.standing, { list: list.join(KYOGIJO_STRINGS[language].layers.listSep) });
|
|
169
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CourtGeometry, CourtPlan } from "./court.ts";
|
|
2
|
+
import type { NetEnd } from "./uprights.net.ts";
|
|
3
|
+
/** The uprights of the court sports that have walls, a table, a ring or a net of their own: padel, squash, racquetball, sepak takraw, teqball, roundnet and netball. */
|
|
4
|
+
export declare function moreUprights(plan: CourtPlan, geo: CourtGeometry, settings: {
|
|
5
|
+
netHeight?: "men" | "women";
|
|
6
|
+
boards?: boolean;
|
|
7
|
+
}, thin: number): NetEnd;
|