@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,183 @@
|
|
|
1
|
+
import { bar, mesh, n, plane, pole, polygon, shy, threads, wall } from "./turn.kit.js";
|
|
2
|
+
import { netPlane, sag, tableBody } from "./uprights.netkit.js";
|
|
3
|
+
import { convertLength } from "./units.js";
|
|
4
|
+
const feet = (metres) => convertLength(metres, "m", "ft");
|
|
5
|
+
/** The walls of a court, each giving way when it is between the eye and the court. */
|
|
6
|
+
const shyAll = (planes, geo) => planes.map((p) => shy(p, [geo.length / 2, geo.width / 2]));
|
|
7
|
+
/** The posts of a court at the fixtures' places, `height` high. */
|
|
8
|
+
const postsOf = (geo, x, y0, y1, height, thin) => ({
|
|
9
|
+
id: "posts",
|
|
10
|
+
kind: "netposts",
|
|
11
|
+
at: [x, (y0 + y1) / 2],
|
|
12
|
+
height,
|
|
13
|
+
planes: geo.posts.flatMap((p, i) => pole(`post-${i === 0 ? "a" : "b"}`, "post", p.x, p.y, 0, height, Math.max(p.size, thin * 1.4), "u-pole")),
|
|
14
|
+
});
|
|
15
|
+
/** The line a wall stands on: the inner edge of the band that stands for it, from end to end of the court's side. */
|
|
16
|
+
function standsOn(band, L, W) {
|
|
17
|
+
const [a, b, c, d] = band.rect;
|
|
18
|
+
if (c - a < d - b) {
|
|
19
|
+
const x = c <= L / 2 ? c : a;
|
|
20
|
+
return [x, Math.max(b, 0), x, Math.min(d, W)];
|
|
21
|
+
}
|
|
22
|
+
const y = d <= W / 2 ? d : b;
|
|
23
|
+
return [Math.max(a, 0), y, Math.min(c, L), y];
|
|
24
|
+
}
|
|
25
|
+
/** A solid or glass wall between two heights along a line, with a cap along its top. */
|
|
26
|
+
function solidWall(id, x0, y0, x1, y1, z0, z1, cls, thin) {
|
|
27
|
+
const run = Math.hypot(x1 - x0, y1 - y0);
|
|
28
|
+
return wall(id, "wall", x0, y0, x1, y1, z0, z1, `<rect width="${n(run)}" height="${n(z1 - z0)}" class="${cls}"/><rect width="${n(run)}" height="${n(Math.max(thin * 0.8, 0.1))}" class="u-cap"/>`);
|
|
29
|
+
}
|
|
30
|
+
/** Metal fence between two heights: a mesh. */
|
|
31
|
+
function fencing(id, x0, y0, x1, y1, z0, z1, cell) {
|
|
32
|
+
const run = Math.hypot(x1 - x0, y1 - y0);
|
|
33
|
+
const h = z1 - z0;
|
|
34
|
+
return wall(id, "wall", x0, y0, x1, y1, z0, z1, `<rect width="${n(run)}" height="${n(h)}" class="u-netfill"/>${threads(mesh(run, h, cell), cell)}`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A padel court's enclosure (The Court: Enclosures): the end walls 3 m of wall with 1 m of metal fence over them, 4 m in all; at each end of each side a wall 3 m high and
|
|
38
|
+
* 4 m long, and the fence between. The part of the Rules read gives the fence along the sides no height of its own: it is drawn as high as the side walls, 3 m (ours).
|
|
39
|
+
*/
|
|
40
|
+
function padelWalls(f, geo, thin) {
|
|
41
|
+
const { length: L, width: W } = geo;
|
|
42
|
+
const cell = Math.max(feet(0.045), thin * 5);
|
|
43
|
+
const planes = [];
|
|
44
|
+
geo.bands.forEach((band, i) => {
|
|
45
|
+
const [x0, y0, x1, y1] = standsOn(band, L, W);
|
|
46
|
+
if (Math.hypot(x1 - x0, y1 - y0) <= 0)
|
|
47
|
+
return;
|
|
48
|
+
if (band.kind === "mesh")
|
|
49
|
+
planes.push(fencing(`fence-${i}`, x0, y0, x1, y1, 0, f.enclosure.sideHeight, cell));
|
|
50
|
+
else if (x0 === x1)
|
|
51
|
+
planes.push(solidWall(`wall-${i}`, x0, y0, x1, y1, 0, f.enclosure.endWall, "u-glass", thin), fencing(`fence-${i}`, x0, y0, x1, y1, f.enclosure.endWall, f.enclosure.endHeight, cell));
|
|
52
|
+
else
|
|
53
|
+
planes.push(solidWall(`wall-${i}`, x0, y0, x1, y1, 0, f.enclosure.sideHeight, "u-glass", thin));
|
|
54
|
+
});
|
|
55
|
+
return { id: "walls", kind: "walls", at: [L / 2, W / 2], height: f.enclosure.endHeight, planes: shyAll(planes, geo) };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A squash court's walls, up to their out lines: the front wall to the front wall line (4.57 m), the back wall to the back wall line (2.13 m), and each side wall between
|
|
59
|
+
* them, its top running straight from the one to the other (only its two ends are among the figures; the straight line is ours). The front wall is solid, the rest glass.
|
|
60
|
+
*/
|
|
61
|
+
function squashWalls(f, geo, thin) {
|
|
62
|
+
const { length: L, width: W } = geo;
|
|
63
|
+
const front = f.wall.frontWallLine;
|
|
64
|
+
const back = f.wall.backWallLine;
|
|
65
|
+
const planes = [solidWall("wall-front", 0, 0, 0, W, 0, front, "u-wall", thin), solidWall("wall-back", L, 0, L, W, 0, back, "u-glass", thin)];
|
|
66
|
+
const side = polygon([[0, 0], [L, front - back], [L, front], [0, front]]);
|
|
67
|
+
for (const [id, y] of [["wall-side-a", 0], ["wall-side-b", W]])
|
|
68
|
+
planes.push(wall(id, "wall", 0, y, L, y, 0, front, `<path d="${side}" class="u-glass"/>`));
|
|
69
|
+
return { id: "walls", kind: "walls", at: [L / 2, W / 2], height: front, planes: shyAll(planes, geo) };
|
|
70
|
+
}
|
|
71
|
+
/** A racquetball court's walls, 20 feet high all round (Rule 2.1(a): the court is 20 feet high, the back wall at least 12), the front wall solid and the others glass. */
|
|
72
|
+
function racquetballWalls(f, geo, thin) {
|
|
73
|
+
const { length: L, width: W } = geo;
|
|
74
|
+
const h = f.height;
|
|
75
|
+
const planes = [
|
|
76
|
+
solidWall("wall-front", 0, 0, 0, W, 0, h, "u-wall", thin),
|
|
77
|
+
solidWall("wall-back", L, 0, L, W, 0, h, "u-glass", thin),
|
|
78
|
+
solidWall("wall-side-a", 0, 0, L, 0, 0, h, "u-glass", thin),
|
|
79
|
+
solidWall("wall-side-b", 0, W, L, W, 0, h, "u-glass", thin),
|
|
80
|
+
];
|
|
81
|
+
return { id: "walls", kind: "walls", at: [L / 2, W / 2], height: h, planes: shyAll(planes, geo) };
|
|
82
|
+
}
|
|
83
|
+
/** A roundnet set: a net stretched in a ring 91.4 cm across, its rim 20.3 cm over the ground, on four legs (Rule 1.1.2). */
|
|
84
|
+
function roundnetSet(f, geo, thin) {
|
|
85
|
+
const solid = geo.solids.find((one) => one.kind === "set");
|
|
86
|
+
const cx = solid?.kind === "set" ? solid.cx : geo.length / 2;
|
|
87
|
+
const cy = solid?.kind === "set" ? solid.cy : geo.width / 2;
|
|
88
|
+
const r = f.set.diameter / 2;
|
|
89
|
+
const h = f.set.height;
|
|
90
|
+
const rim = Math.max(feet(0.02), thin * 1.5);
|
|
91
|
+
const ring = (rr) => `M${n(r - rr)} ${n(r)}a${n(rr)} ${n(rr)} 0 1 0 ${n(2 * rr)} 0a${n(rr)} ${n(rr)} 0 1 0 ${n(-2 * rr)} 0`;
|
|
92
|
+
let lines = [0.34, 0.67].map((k) => ring(r * k)).join("");
|
|
93
|
+
for (let i = 0; i < 8; i += 1) {
|
|
94
|
+
const a = (Math.PI * i) / 8;
|
|
95
|
+
lines += `M${n(r - r * Math.cos(a))} ${n(r - r * Math.sin(a))}L${n(r + r * Math.cos(a))} ${n(r + r * Math.sin(a))}`;
|
|
96
|
+
}
|
|
97
|
+
const top = plane("set-net", "net", [cx - r, cy - r, h], [2 * r, 0, 0], [0, 2 * r, 0], `<circle cx="${n(r)}" cy="${n(r)}" r="${n(r)}" class="u-netfill"/>${threads(lines, r / 3)}<circle cx="${n(r)}" cy="${n(r)}" r="${n(r - rim / 2)}" class="u-rimline" stroke-width="${n(rim)}" fill="none"/>`);
|
|
98
|
+
const legs = [];
|
|
99
|
+
for (let i = 0; i < 4; i += 1) {
|
|
100
|
+
const a = Math.PI / 4 + (Math.PI * i) / 2;
|
|
101
|
+
legs.push(...pole(`leg-${i}`, "leg", cx + r * 0.9 * Math.cos(a), cy + r * 0.9 * Math.sin(a), 0, h, Math.max(feet(0.02), thin * 1.2), "u-leg"));
|
|
102
|
+
}
|
|
103
|
+
return { id: "net", kind: "net", at: [cx, cy], height: h, planes: [top, ...legs] };
|
|
104
|
+
}
|
|
105
|
+
/** A netball goalpost, 3.05 m high, at the middle of each goal line, with its ring on a bar that projects 150 mm from the pole (Rule 1, items 13 and 14). */
|
|
106
|
+
function netballPosts(f, geo, thin) {
|
|
107
|
+
const { length: L, width: W } = geo;
|
|
108
|
+
const h = f.post.height;
|
|
109
|
+
const { inside, rod, bar: reach } = f.post.ring;
|
|
110
|
+
const d = f.post.poleDiameter;
|
|
111
|
+
const R = inside / 2 + rod;
|
|
112
|
+
const cy = W / 2;
|
|
113
|
+
const planes = [];
|
|
114
|
+
for (const left of [true, false]) {
|
|
115
|
+
const tag = left ? "left" : "right";
|
|
116
|
+
const sign = left ? 1 : -1;
|
|
117
|
+
const x = left ? d / 2 : L - d / 2;
|
|
118
|
+
const cx = x + sign * (d / 2 + reach + R);
|
|
119
|
+
planes.push(...pole(`pole-${tag}`, "post", x, cy, 0, h, Math.max(d, thin * 1.4), "u-pole"));
|
|
120
|
+
planes.push(...bar(`bar-${tag}`, "post", [x, cy, h], [cx - sign * R, cy, h], Math.max(rod, thin * 1.2), "u-pole"));
|
|
121
|
+
const line = Math.max(rod, thin * 1.6);
|
|
122
|
+
planes.push(plane(`ring-${tag}`, "rim", [cx - R, cy - R, h], [2 * R, 0, 0], [0, 2 * R, 0], `<circle cx="${n(R)}" cy="${n(R)}" r="${n(R - line / 2)}" class="u-rimline" stroke-width="${n(line)}" fill="none"/>`));
|
|
123
|
+
}
|
|
124
|
+
return { id: "goalposts", kind: "goalposts", at: [L / 2, cy], height: h, planes };
|
|
125
|
+
}
|
|
126
|
+
/** 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. */
|
|
127
|
+
export function moreUprights(plan, geo, settings, thin) {
|
|
128
|
+
const items = [];
|
|
129
|
+
const { x, y0, y1 } = geo.net ?? { x: geo.length / 2, y0: 0, y1: geo.width };
|
|
130
|
+
const at = [x, (y0 + y1) / 2];
|
|
131
|
+
const walls = settings.boards !== false;
|
|
132
|
+
switch (plan.sport) {
|
|
133
|
+
case "padel": {
|
|
134
|
+
const f = plan.figures;
|
|
135
|
+
const { heightCentre: c, heightEnds: e, postHeight: p } = f.net;
|
|
136
|
+
const cell = Math.max(feet(0.045), thin * 5);
|
|
137
|
+
items.push({ id: "net", kind: "net", at, height: e, low: c, planes: [netPlane({ id: "net", x, y0, y1, top: sag(y0, y1, c, e), bottom: () => 0, cell, tape: Math.max(feet(0.05), thin) }, 0)] });
|
|
138
|
+
items.push(postsOf(geo, x, y0, y1, p, thin));
|
|
139
|
+
if (walls)
|
|
140
|
+
items.push(padelWalls(f, geo, thin));
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "squash":
|
|
144
|
+
if (walls)
|
|
145
|
+
items.push(squashWalls(plan.figures, geo, thin));
|
|
146
|
+
break;
|
|
147
|
+
case "racquetball":
|
|
148
|
+
if (walls)
|
|
149
|
+
items.push(racquetballWalls(plan.figures, geo, thin));
|
|
150
|
+
break;
|
|
151
|
+
case "sepaktakraw": {
|
|
152
|
+
const f = plan.figures;
|
|
153
|
+
const women = settings.netHeight === "women";
|
|
154
|
+
const centre = women ? f.net.heightWomen : f.net.heightMen;
|
|
155
|
+
const post = women ? f.net.postWomen : f.net.postMen;
|
|
156
|
+
const top = sag(y0, y1, centre, post);
|
|
157
|
+
const cell = Math.max(feet(0.045), thin * 5);
|
|
158
|
+
items.push({ id: "net", kind: "net", at, height: post, low: centre, planes: [netPlane({ id: "net", x, y0, y1, top, bottom: (y) => top(y) - f.net.width, cell, tape: Math.max(feet(0.05), thin) }, 0)] });
|
|
159
|
+
items.push(postsOf(geo, x, y0, y1, post, thin));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "teqball": {
|
|
163
|
+
const f = plan.figures;
|
|
164
|
+
// The table is 900 mm high with its net (1.1.4) and the net stands 140 mm over it (1.2.3): the top is 760 mm up.
|
|
165
|
+
const lift = f.table.height - f.net.height;
|
|
166
|
+
const solid = geo.solids.find((one) => one.kind === "table");
|
|
167
|
+
const [tx0, ty0, tx1, ty1] = solid?.kind === "table" ? solid.rect : [0, 0, geo.length, geo.width];
|
|
168
|
+
const cell = Math.max(feet(0.02), thin * 3);
|
|
169
|
+
items.push({ id: "net", kind: "net", at, height: lift + f.net.height, planes: [netPlane({ id: "net", x, y0, y1, top: () => f.net.height, bottom: () => 0, cell, tape: Math.max(feet(0.02), thin * 0.8) }, lift)] });
|
|
170
|
+
items.push({ id: "table", kind: "table-legs", at: [(tx0 + tx1) / 2, (ty0 + ty1) / 2], height: lift, planes: tableBody(tx0, ty0, tx1, ty1, lift, thin, true) });
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case "roundnet":
|
|
174
|
+
items.push(roundnetSet(plan.figures, geo, thin));
|
|
175
|
+
break;
|
|
176
|
+
case "netball":
|
|
177
|
+
items.push(netballPosts(plan.figures, geo, thin));
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
return { items, lift: 0 };
|
|
183
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CourtGeometry, CourtPlan } from "./court.ts";
|
|
2
|
+
import { type Upright } from "./turn.kit.ts";
|
|
3
|
+
export interface NetEnd {
|
|
4
|
+
items: Upright[];
|
|
5
|
+
/** How high the surface stands over the ground: a table's top. */
|
|
6
|
+
lift: number;
|
|
7
|
+
}
|
|
8
|
+
/** The uprights of a court: what hangs on its posts. `thin` is the least thickness drawn, in feet. */
|
|
9
|
+
export declare function netUprights(plan: CourtPlan, geo: CourtGeometry, settings: {
|
|
10
|
+
netHeight?: "men" | "women";
|
|
11
|
+
boards?: boolean;
|
|
12
|
+
}, thin: number): NetEnd;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { bar, flat, n, pole } from "./turn.kit.js";
|
|
2
|
+
import { moreUprights } from "./uprights.more.js";
|
|
3
|
+
import { netPlane, sag, tableBody } from "./uprights.netkit.js";
|
|
4
|
+
import { convertLength } from "./units.js";
|
|
5
|
+
/** The uprights of a court: what hangs on its posts. `thin` is the least thickness drawn, in feet. */
|
|
6
|
+
export function netUprights(plan, geo, settings, thin) {
|
|
7
|
+
const { x, y0, y1 } = geo.net ?? { x: geo.length / 2, y0: 0, y1: geo.width };
|
|
8
|
+
const items = [];
|
|
9
|
+
const posts = (height, cls = "u-pole") => ({
|
|
10
|
+
id: "posts",
|
|
11
|
+
kind: "netposts",
|
|
12
|
+
at: [x, (y0 + y1) / 2],
|
|
13
|
+
height,
|
|
14
|
+
planes: geo.posts.flatMap((p, i) => pole(`post-${i === 0 ? "a" : "b"}`, "post", p.x, p.y, 0, height, Math.max(p.size, thin * 1.4), cls)),
|
|
15
|
+
});
|
|
16
|
+
switch (plan.sport) {
|
|
17
|
+
case "tennis": {
|
|
18
|
+
const f = plan.figures;
|
|
19
|
+
const { heightPosts: p, heightCentre: c } = f.net;
|
|
20
|
+
const cell = Math.max(0.25, thin * 6);
|
|
21
|
+
items.push({ id: "net", kind: "net", at: [x, (y0 + y1) / 2], height: p, low: c, planes: [netPlane({ id: "net", x, y0, y1, top: sag(y0, y1, c, p), bottom: () => 0, cell, tape: Math.max(0.17, thin) }, 0)] });
|
|
22
|
+
items.push(posts(p));
|
|
23
|
+
if (geo.sticks.length > 0)
|
|
24
|
+
items.push({ id: "sticks", kind: "netposts", at: [x, (y0 + y1) / 2], height: p, planes: geo.sticks.flatMap((s, i) => pole(`stick-${i === 0 ? "a" : "b"}`, "post", s.x, s.y, 0, p, Math.max(s.size, thin * 1.1), "u-tape")) });
|
|
25
|
+
return { items, lift: 0 };
|
|
26
|
+
}
|
|
27
|
+
case "volleyball": {
|
|
28
|
+
const f = plan.figures;
|
|
29
|
+
const top = settings.netHeight === "women" ? f.net.heightWomen : f.net.heightMen;
|
|
30
|
+
const strip = f.net.width;
|
|
31
|
+
const cell = Math.max(convertLength(0.1, "m", "ft"), thin * 6);
|
|
32
|
+
const side = Math.max(convertLength(0.05, "m", "ft"), thin * 1.2);
|
|
33
|
+
items.push({
|
|
34
|
+
id: "net",
|
|
35
|
+
kind: "net",
|
|
36
|
+
at: [x, (y0 + y1) / 2],
|
|
37
|
+
height: top,
|
|
38
|
+
planes: [netPlane({ id: "net", x, y0, y1, top: () => top, bottom: () => top - strip, cell, tape: Math.max(convertLength(0.07, "m", "ft"), thin * 1.6), bands: { at: [0, geo.width], width: side } }, 0)],
|
|
39
|
+
});
|
|
40
|
+
// The FIVB's posts are 2.55 m, 12 cm over the men's net; sitting volleyball's rules cited give their distance from the side lines only, so the same 12 cm is ours.
|
|
41
|
+
items.push(posts(f.net.heightMen + convertLength(0.12, "m", "ft")));
|
|
42
|
+
// An antenna is 1.8 m long and stands 80 cm over the net's top, in bands of 10 cm, red and white.
|
|
43
|
+
const reach = f.net.antenna.length;
|
|
44
|
+
const above = convertLength(0.8, "m", "ft");
|
|
45
|
+
const stripe = convertLength(0.1, "m", "ft");
|
|
46
|
+
const antenna = (long, wide) => {
|
|
47
|
+
let rects = "";
|
|
48
|
+
for (let a = 0, i = 0; a < long; a += stripe, i += 1)
|
|
49
|
+
rects += `<rect x="${n(a)}" width="${n(Math.min(stripe, long - a))}" height="${n(wide)}" class="${i % 2 === 0 ? "u-red" : "u-white"}"/>`;
|
|
50
|
+
return rects;
|
|
51
|
+
};
|
|
52
|
+
items.push({
|
|
53
|
+
id: "antennae",
|
|
54
|
+
kind: "antennae",
|
|
55
|
+
at: [x, (y0 + y1) / 2],
|
|
56
|
+
height: top + above,
|
|
57
|
+
planes: geo.antennae.flatMap((a, i) => bar(`antenna-${i === 0 ? "a" : "b"}`, "antenna", [a.x, a.y, top + above - reach], [a.x, a.y, top + above], Math.max(a.size, thin * 1.1), "u-red", antenna)),
|
|
58
|
+
});
|
|
59
|
+
return { items, lift: 0 };
|
|
60
|
+
}
|
|
61
|
+
case "badminton": {
|
|
62
|
+
const f = plan.figures;
|
|
63
|
+
const { postHeight: p, heightCentre: c, heightSides: s, depth } = f.net;
|
|
64
|
+
const cell = Math.max(convertLength(0.02, "m", "ft") * 4, thin * 5);
|
|
65
|
+
items.push({
|
|
66
|
+
id: "net",
|
|
67
|
+
kind: "net",
|
|
68
|
+
at: [x, (y0 + y1) / 2],
|
|
69
|
+
height: s,
|
|
70
|
+
low: c,
|
|
71
|
+
planes: [netPlane({ id: "net", x, y0, y1, top: sag(y0, y1, c, s), bottom: (y) => sag(y0, y1, c, s)(y) - depth, cell, tape: Math.max(convertLength(0.075, "m", "ft"), thin) }, 0)],
|
|
72
|
+
});
|
|
73
|
+
items.push(posts(p));
|
|
74
|
+
return { items, lift: 0 };
|
|
75
|
+
}
|
|
76
|
+
case "pickleball": {
|
|
77
|
+
const f = plan.figures;
|
|
78
|
+
const { heightSides: s, heightCentre: c } = f.net;
|
|
79
|
+
const cell = Math.max(0.25, thin * 6);
|
|
80
|
+
items.push({ id: "net", kind: "net", at: [x, (y0 + y1) / 2], height: s, low: c, planes: [netPlane({ id: "net", x, y0, y1, top: sag(y0, y1, c, s), bottom: () => 0, cell, tape: Math.max(0.17, thin) }, 0)] });
|
|
81
|
+
items.push(posts(s * 1.04));
|
|
82
|
+
return { items, lift: 0 };
|
|
83
|
+
}
|
|
84
|
+
case "tabletennis": {
|
|
85
|
+
const f = plan.figures;
|
|
86
|
+
const lift = f.height;
|
|
87
|
+
const { height: h, postHeight } = f.net;
|
|
88
|
+
const cell = Math.max(convertLength(0.01, "m", "ft") * 1.5, thin * 3);
|
|
89
|
+
items.push({ id: "net", kind: "net", at: [x, (y0 + y1) / 2], height: lift + h, planes: [netPlane({ id: "net", x, y0, y1, top: () => h, bottom: () => 0, cell, tape: Math.max(0.03, thin * 0.8) }, lift)] });
|
|
90
|
+
items.push({
|
|
91
|
+
id: "clamps",
|
|
92
|
+
kind: "netposts",
|
|
93
|
+
at: [x, (y0 + y1) / 2],
|
|
94
|
+
height: lift + postHeight,
|
|
95
|
+
planes: geo.posts.flatMap((p, i) => pole(`post-${i === 0 ? "a" : "b"}`, "post", p.x, p.y, lift, lift + postHeight, Math.max(p.size, thin * 1.6), "u-pole")),
|
|
96
|
+
});
|
|
97
|
+
// The table: a skirt under its top, and four legs set in from its ends.
|
|
98
|
+
const L = geo.length;
|
|
99
|
+
const W = geo.width;
|
|
100
|
+
items.push({ id: "table", kind: "table-legs", at: [L / 2, W / 2], height: lift, planes: tableBody(0, 0, L, W, lift, thin, false) });
|
|
101
|
+
// The floor of the hall, under the table.
|
|
102
|
+
const room = Math.max(L, W) * 5;
|
|
103
|
+
items.push({ id: "ground", kind: "table-legs", at: [L / 2, W / 2], height: 0, planes: [flat("ground", "ground", -room, -room, L + room, W + room, 0, `<rect width="${n(L + 2 * room)}" height="${n(W + 2 * room)}" class="u-ground"/>`)] });
|
|
104
|
+
return { items, lift };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
return moreUprights(plan, geo, settings, thin);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Plane } from "./turn.kit.ts";
|
|
2
|
+
/** What a net on a court is: where it hangs, between which posts, and the line of its top and of its bottom. */
|
|
3
|
+
export interface NetBody {
|
|
4
|
+
id: string;
|
|
5
|
+
x: number;
|
|
6
|
+
y0: number;
|
|
7
|
+
y1: number;
|
|
8
|
+
/** The height of the top edge and of the bottom edge over a place across the court, above the court's own surface. */
|
|
9
|
+
top: (y: number) => number;
|
|
10
|
+
bottom: (y: number) => number;
|
|
11
|
+
/** The mesh's cell, and the tape along the top, in feet. */
|
|
12
|
+
cell: number;
|
|
13
|
+
tape: number;
|
|
14
|
+
/** Vertical white bands across the net at these places across the court (volleyball's side bands), and their width. */
|
|
15
|
+
bands?: {
|
|
16
|
+
at: readonly number[];
|
|
17
|
+
width: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The net: a mesh between a top line and a bottom line, with its tape, on one plane standing over the line x. */
|
|
21
|
+
export declare function netPlane(b: NetBody, lift: number): Plane;
|
|
22
|
+
/** A parabola from the centre height to the post height: the sag of a net hung between two posts. */
|
|
23
|
+
export declare const sag: (y0: number, y1: number, centre: number, posts: number) => (y: number) => number;
|
|
24
|
+
/**
|
|
25
|
+
* A table: a skirt under its top and four legs set in from its ends, over the rectangle (x0, y0) to (x1, y1), its top `lift` feet over the floor. `top` also draws the top
|
|
26
|
+
* itself (a teqball table stands on a court; a table tennis table is the surface, which the drawing already shows).
|
|
27
|
+
*/
|
|
28
|
+
export declare function tableBody(x0: number, y0: number, x1: number, y1: number, lift: number, thin: number, top: boolean): Plane[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The pieces the net sports' uprights share: a net hung between a top line and a bottom line, the sag of a net between its posts, and a table lifted on its legs.
|
|
2
|
+
import { flat, n, plane, pole, threads, wall } from "./turn.kit.js";
|
|
3
|
+
import { convertLength } from "./units.js";
|
|
4
|
+
/** The net: a mesh between a top line and a bottom line, with its tape, on one plane standing over the line x. */
|
|
5
|
+
export function netPlane(b, lift) {
|
|
6
|
+
const { y0, y1 } = b;
|
|
7
|
+
const steps = 24;
|
|
8
|
+
const ys = Array.from({ length: steps + 1 }, (_, i) => y0 + ((y1 - y0) * i) / steps);
|
|
9
|
+
const high = Math.max(...ys.map(b.top));
|
|
10
|
+
const low = Math.min(...ys.map(b.bottom));
|
|
11
|
+
const span = y1 - y0;
|
|
12
|
+
const fromTop = (z) => high - z;
|
|
13
|
+
const edge = (f) => ys.map((y) => `${n(y - y0)} ${n(fromTop(f(y)))}`).join("L");
|
|
14
|
+
const top = ys.map((y) => `${n(y - y0)} ${n(fromTop(b.top(y)))}`);
|
|
15
|
+
const bottom = [...ys].reverse().map((y) => `${n(y - y0)} ${n(fromTop(b.bottom(y)))}`);
|
|
16
|
+
const outline = `M${top.join("L")}L${bottom.join("L")}Z`;
|
|
17
|
+
let lines = "";
|
|
18
|
+
// Upright threads, one every cell, from the top line to the bottom line.
|
|
19
|
+
const across = Math.max(2, Math.round(span / b.cell));
|
|
20
|
+
for (let i = 1; i < across; i += 1) {
|
|
21
|
+
const y = y0 + (span * i) / across;
|
|
22
|
+
lines += `M${n(y - y0)} ${n(fromTop(b.top(y)))}V${n(fromTop(b.bottom(y)))}`;
|
|
23
|
+
}
|
|
24
|
+
// Level threads, wherever the net reaches that height.
|
|
25
|
+
const levels = Math.max(1, Math.round((high - low) / b.cell));
|
|
26
|
+
for (let j = 1; j < levels; j += 1) {
|
|
27
|
+
const z = low + ((high - low) * j) / levels;
|
|
28
|
+
let pen = false;
|
|
29
|
+
for (const y of ys) {
|
|
30
|
+
const inside = z <= b.top(y) && z >= b.bottom(y);
|
|
31
|
+
lines += `${inside ? (pen ? "L" : "M") : ""}${inside ? `${n(y - y0)} ${n(fromTop(z))}` : ""}`;
|
|
32
|
+
pen = inside;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const tape = `<path d="M${edge(b.top)}" class="u-tapeline" fill="none" stroke-width="${n(b.tape)}"/>`;
|
|
36
|
+
const bands = (b.bands?.at ?? []).map((y) => `<rect x="${n(y - y0 - b.bands.width / 2)}" y="${n(fromTop(b.top(y)))}" width="${n(b.bands.width)}" height="${n(b.top(y) - b.bottom(y))}" class="u-tape"/>`).join("");
|
|
37
|
+
return plane(b.id, "net", [b.x, y0, lift + high], [0, span, 0], [0, 0, -(high - low)], `<path d="${outline}" class="u-netfill"/>${threads(lines, b.cell)}${bands}${tape}`);
|
|
38
|
+
}
|
|
39
|
+
/** A parabola from the centre height to the post height: the sag of a net hung between two posts. */
|
|
40
|
+
export const sag = (y0, y1, centre, posts) => (y) => {
|
|
41
|
+
const middle = (y0 + y1) / 2;
|
|
42
|
+
const t = (y - middle) / ((y1 - y0) / 2);
|
|
43
|
+
return centre + (posts - centre) * t * t;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* A table: a skirt under its top and four legs set in from its ends, over the rectangle (x0, y0) to (x1, y1), its top `lift` feet over the floor. `top` also draws the top
|
|
47
|
+
* itself (a teqball table stands on a court; a table tennis table is the surface, which the drawing already shows).
|
|
48
|
+
*/
|
|
49
|
+
export function tableBody(x0, y0, x1, y1, lift, thin, top) {
|
|
50
|
+
const L = x1 - x0;
|
|
51
|
+
const W = y1 - y0;
|
|
52
|
+
const skirt = Math.max(convertLength(0.05, "m", "ft"), thin * 2);
|
|
53
|
+
const rect = (w) => `<rect width="${n(w)}" height="${n(skirt)}" class="u-table"/>`;
|
|
54
|
+
const planes = [
|
|
55
|
+
wall("table-side-a", "table", x0, y0, x1, y0, lift - skirt, lift, rect(L)),
|
|
56
|
+
wall("table-side-b", "table", x0, y1, x1, y1, lift - skirt, lift, rect(L)),
|
|
57
|
+
wall("table-end-a", "table", x0, y0, x0, y1, lift - skirt, lift, rect(W)),
|
|
58
|
+
wall("table-end-b", "table", x1, y0, x1, y1, lift - skirt, lift, rect(W)),
|
|
59
|
+
];
|
|
60
|
+
if (top)
|
|
61
|
+
planes.unshift(flat("table-top", "table", x0, y0, x1, y1, lift, `<rect width="${n(L)}" height="${n(W)}" class="u-table"/>`));
|
|
62
|
+
const leg = Math.max(convertLength(0.05, "m", "ft"), thin * 2.2);
|
|
63
|
+
const inset = Math.max(L * 0.07, leg * 2);
|
|
64
|
+
for (const [i, [lx, ly]] of [[x0 + inset, y0 + inset], [x0 + inset, y1 - inset], [x1 - inset, y0 + inset], [x1 - inset, y1 - inset]].entries())
|
|
65
|
+
planes.push(...pole(`leg-${i}`, "leg", lx, ly, 0, lift - skirt, leg, "u-leg"));
|
|
66
|
+
return planes;
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PoolPlan } from "./pool.ts";
|
|
2
|
+
import { type Upright } from "./turn.kit.ts";
|
|
3
|
+
import type { SurfaceOptions } from "./types.ts";
|
|
4
|
+
/** What stands on a pool of the options' sport, as the drawing has it, with the plan's metres put into the drawing's feet. A natatorium of several pools has none. */
|
|
5
|
+
export declare function poolUprights(o: SurfaceOptions, thin: number): {
|
|
6
|
+
items: Upright[];
|
|
7
|
+
plan: PoolPlan;
|
|
8
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// What stands on the pools, in 3D: a swimming pool's starting blocks, its backstroke flag lines and its false start rope; a diving well's springboards and platforms on their stands; a water
|
|
2
|
+
// polo field's two floating goals; an artistic swimming area's judges' stand and starting platform. The heights are World Aquatics' (the Competition Regulations in force from February 2026, bar the few figures `pool.figures.ts` marks as the June 2025 edition's):
|
|
3
|
+
// docs/sources.md, "Heights in the 3D view", says which rule each comes from, and `pool.figures.ts` holds the figures. A plan is in metres and the drawing's frame in feet, with the pool's
|
|
4
|
+
// top left corner for the origin and the water level for the floor, so every length goes through `feet`. What the rules leave to the maker (the thickness of a stand, a rope, the sides of a
|
|
5
|
+
// box) is ours and is said so there.
|
|
6
|
+
import { ARTISTIC, DIVING, SWIMMING, WATER_POLO } from "./pool.figures.js";
|
|
7
|
+
import { feet, poolPlan } from "./pool.js";
|
|
8
|
+
import { GUTTER } from "./surfaces/pool.js";
|
|
9
|
+
import { goalCage } from "./uprights.goal.js";
|
|
10
|
+
import { flat, n, pole, polygon, wall } from "./turn.kit.js";
|
|
11
|
+
/** A box over the rectangle (x0, y0) to (x1, y1) between the heights z0 and z1: its top and the sides named, the top in `top` and the sides in `side`. */
|
|
12
|
+
function box(id, x0, y0, x1, y1, z0, z1, top, side, sides, withTop = true) {
|
|
13
|
+
const planes = [];
|
|
14
|
+
if (withTop)
|
|
15
|
+
planes.push(flat(`${id}-top`, "board", x0, y0, x1, y1, z1, `<rect width="${n(x1 - x0)}" height="${n(y1 - y0)}" class="${top}"/>`));
|
|
16
|
+
const face = (tag, ax, ay, bx, by) => wall(`${id}-${tag}`, "wall", ax, ay, bx, by, z0, z1, `<rect width="${n(Math.hypot(bx - ax, by - ay))}" height="${n(z1 - z0)}" class="${side}"/>`);
|
|
17
|
+
if (sides.includes("x0"))
|
|
18
|
+
planes.push(face("x0", x0, y0, x0, y1));
|
|
19
|
+
if (sides.includes("x1"))
|
|
20
|
+
planes.push(face("x1", x1, y0, x1, y1));
|
|
21
|
+
if (sides.includes("y0"))
|
|
22
|
+
planes.push(face("y0", x0, y0, x1, y0));
|
|
23
|
+
if (sides.includes("y1"))
|
|
24
|
+
planes.push(face("y1", x0, y1, x1, y1));
|
|
25
|
+
return planes;
|
|
26
|
+
}
|
|
27
|
+
/** The swimming pool: a block in each lane at each end, a flagged rope 5 m from each wall, the false start rope 15 m from the starting end, each rope on a standard at either side. */
|
|
28
|
+
function swimmingUprights(plan, thin) {
|
|
29
|
+
const items = [];
|
|
30
|
+
const L = plan.length;
|
|
31
|
+
const W = plan.width;
|
|
32
|
+
const standard = Math.max(feet(0.05), thin * 1.4);
|
|
33
|
+
const standards = (id, x, height, outside) => [-outside, W + outside].flatMap((y, i) => pole(`${id}-standard-${i === 0 ? "a" : "b"}`, "post", feet(x), feet(y), 0, height, standard, "u-pole"));
|
|
34
|
+
if (plan.blocks) {
|
|
35
|
+
const { width, length, height } = SWIMMING.block;
|
|
36
|
+
const planes = [];
|
|
37
|
+
for (const lane of plan.lanes) {
|
|
38
|
+
for (const end of [0, 1]) {
|
|
39
|
+
// 0.08 m of deck between the wall and the block, as the drawing has it (the drawing stands them 0.08 m behind the wall).
|
|
40
|
+
const x0 = end === 0 ? -0.08 - length : L + 0.08;
|
|
41
|
+
const id = `block-${lane.number}-${end === 0 ? "start" : "turn"}`;
|
|
42
|
+
const bx0 = feet(x0);
|
|
43
|
+
const bx1 = feet(x0 + length);
|
|
44
|
+
const by0 = feet(lane.y - width / 2);
|
|
45
|
+
const by1 = feet(lane.y + width / 2);
|
|
46
|
+
const number = `<text x="${n((bx1 - bx0) / 2)}" y="${n((by1 - by0) * 0.74)}" text-anchor="middle" font-size="${n((by1 - by0) * 0.66)}" font-weight="800" font-family="system-ui,sans-serif" fill="#1f2320">${lane.number}</text>`;
|
|
47
|
+
planes.push(flat(`${id}-top`, "board", bx0, by0, bx1, by1, feet(height), `<rect width="${n(bx1 - bx0)}" height="${n(by1 - by0)}" class="u-board"/>${number}`));
|
|
48
|
+
// The face towards the water.
|
|
49
|
+
const fx = end === 0 ? bx1 : bx0;
|
|
50
|
+
planes.push(wall(`${id}-front`, "wall", fx, by0, fx, by1, 0, feet(height), `<rect width="${n(by1 - by0)}" height="${n(feet(height))}" class="u-leg"/>`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
items.push({ id: "starting-blocks", kind: "starting-blocks", at: [feet(L) / 2, feet(W) / 2], height: feet(height), planes });
|
|
54
|
+
}
|
|
55
|
+
if (plan.flags) {
|
|
56
|
+
const { at, height, side } = SWIMMING.flags;
|
|
57
|
+
const hang = side * 0.87;
|
|
58
|
+
const planes = [];
|
|
59
|
+
for (const [i, x] of [at, L - at].entries()) {
|
|
60
|
+
const outside = 0.6;
|
|
61
|
+
const run = feet(W + 2 * outside);
|
|
62
|
+
const tri = feet(side);
|
|
63
|
+
const gap = feet(SWIMMING.flags.apart);
|
|
64
|
+
let flags = "";
|
|
65
|
+
for (let k = 0, a = gap / 2; a + tri <= run; k += 1, a += tri + gap)
|
|
66
|
+
flags += `<path d="${polygon([[a, 0], [a + tri, 0], [a + tri / 2, feet(hang)]])}" class="${k % 2 === 0 ? "u-red" : "u-white"}"/>`;
|
|
67
|
+
const rope = `<path d="M0 0H${n(run)}" class="u-tapeline" fill="none" stroke-width="${n(Math.max(feet(0.02), thin * 0.5))}"/>`;
|
|
68
|
+
planes.push(wall(`flags-${i === 0 ? "start" : "turn"}`, "tape", feet(x), -feet(outside), feet(x), feet(W + outside), feet(height - hang), feet(height), flags + rope), ...standards(`flags-${i === 0 ? "start" : "turn"}`, x, feet(height), outside));
|
|
69
|
+
}
|
|
70
|
+
items.push({ id: "backstroke-flags", kind: "backstroke-flags", at: [feet(L) / 2, feet(W) / 2], height: feet(height), planes });
|
|
71
|
+
}
|
|
72
|
+
if (plan.falseStart) {
|
|
73
|
+
const { at, height } = SWIMMING.falseStart;
|
|
74
|
+
const outside = 0.7;
|
|
75
|
+
const run = feet(W + 2 * outside);
|
|
76
|
+
const thick = Math.max(feet(0.06), thin * 1.1);
|
|
77
|
+
const rope = wall("false-start-rope", "tape", feet(at), -feet(outside), feet(at), feet(W + outside), feet(height) - thick, feet(height), `<rect width="${n(run)}" height="${n(thick)}" class="u-red"/>`);
|
|
78
|
+
items.push({ id: "false-start-rope", kind: "false-start-rope", at: [feet(at), feet(W) / 2], height: feet(height), planes: [rope, ...standards("false-start", at, feet(height), outside)] });
|
|
79
|
+
}
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
/** The two goals of a water polo field, floating on the goal lines: 3 m between the posts, the crossbar's underside 0.9 m over the water (Part Six 21.6.3.2 and 21.6.4.2), a net 0.30 m deep behind the line (21.6.5). */
|
|
83
|
+
function poloUprights(plan, thin) {
|
|
84
|
+
const { goal } = WATER_POLO;
|
|
85
|
+
const g0 = goal.netClear;
|
|
86
|
+
const g1 = plan.length - goal.netClear;
|
|
87
|
+
const posts = Math.max(feet(goal.post), thin * 1.2);
|
|
88
|
+
const items = [];
|
|
89
|
+
for (const [i, from] of ["left", "right"].entries()) {
|
|
90
|
+
const cage = goalCage({
|
|
91
|
+
id: `goal-${from}`,
|
|
92
|
+
x: feet(i === 0 ? g0 : g1),
|
|
93
|
+
y: feet(plan.width) / 2,
|
|
94
|
+
out: i === 0 ? -1 : 1,
|
|
95
|
+
width: feet(goal.width + goal.post),
|
|
96
|
+
height: feet(goal.crossbarHeight + goal.post),
|
|
97
|
+
depth: feet(goal.netClear),
|
|
98
|
+
top: feet(goal.netClear) * 0.5,
|
|
99
|
+
post: posts,
|
|
100
|
+
cell: Math.max(feet(0.1), thin * 3),
|
|
101
|
+
tone: "white",
|
|
102
|
+
});
|
|
103
|
+
// The height said is the clear one, to the underside of the bar.
|
|
104
|
+
items.push({ ...cage, height: feet(goal.crossbarHeight) });
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A diving well: each springboard 4.88 m long and 0.50 m wide at 1 m or 3 m, its plummet over the water, on a stand behind the wall (Part Four, Appendix 2); each platform at its height of 1, 3, 5, 7.5 or 10 m with
|
|
110
|
+
* the length and width of the table, on a tower behind the wall. The stands and towers are ours: the rules give where the plummet is and not what holds the board up.
|
|
111
|
+
*/
|
|
112
|
+
function diveUprights(plan) {
|
|
113
|
+
const springPlanes = [];
|
|
114
|
+
const platformPlanes = [];
|
|
115
|
+
let lowest = Infinity;
|
|
116
|
+
const stand = DIVING.springboard.width + 0.5;
|
|
117
|
+
for (const [i, b] of plan.boards.entries()) {
|
|
118
|
+
const x0 = feet(b.x - b.length);
|
|
119
|
+
const x1 = feet(b.x);
|
|
120
|
+
const y0 = feet(b.y - b.width / 2);
|
|
121
|
+
const y1 = feet(b.y + b.width / 2);
|
|
122
|
+
const h = feet(b.height);
|
|
123
|
+
const id = `${b.kind === "springboard" ? "springboard" : "platform"}-${String(b.height).replace(".", "-")}m-${i}`;
|
|
124
|
+
lowest = Math.min(lowest, b.height);
|
|
125
|
+
if (b.kind === "springboard") {
|
|
126
|
+
// The board, a stand under its rear half (from the rear end to the wall) as wide as the board and a little more.
|
|
127
|
+
const sy0 = feet(b.y - stand / 2);
|
|
128
|
+
const sy1 = feet(b.y + stand / 2);
|
|
129
|
+
springPlanes.push(...box(id, x0, y0, x1, y1, h - feet(0.1), h, "u-pad", "u-pad", ["y0", "y1"]));
|
|
130
|
+
springPlanes.push(...box(`${id}-stand`, x0 + feet(0.4), sy0, 0, sy1, 0, h - feet(0.1), "u-leg", "u-leg", ["x1", "y0", "y1"], false));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// The table, with its front lip over the water, and a tower 2.5 m deep behind the wall under its rear part, as wide as the table up to 2.2 m.
|
|
134
|
+
platformPlanes.push(...box(id, x0, y0, x1, y1, h - feet(0.25), h, "u-board", "u-board", ["x1"]));
|
|
135
|
+
const half = Math.min(b.width / 2, 1.1);
|
|
136
|
+
platformPlanes.push(...box(`${id}-tower`, Math.max(x0, -feet(2.5)), feet(b.y - half), 0, feet(b.y + half), 0, h - feet(0.25), "u-leg", "u-leg", ["x1", "y0", "y1"], false));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const items = [];
|
|
140
|
+
const tall = (kind) => Math.max(0, ...plan.boards.filter((b) => b.kind === kind).map((b) => b.height));
|
|
141
|
+
if (springPlanes.length > 0)
|
|
142
|
+
items.push({ id: "springboards", kind: "springboards", at: [0, feet(plan.width) / 2], height: feet(tall("springboard")), planes: springPlanes });
|
|
143
|
+
if (platformPlanes.length > 0)
|
|
144
|
+
items.push({ id: "platforms", kind: "platforms", at: [0, feet(plan.width) / 2], height: feet(tall("platform")), planes: platformPlanes });
|
|
145
|
+
return items;
|
|
146
|
+
}
|
|
147
|
+
/** An artistic swimming area: the judges' platform along the top side, 0.6 m high (Part Seven 12.2.5: at least 0.6 m, not more than 2 m from the edge), and the starting platform 0.7 m high (Part Seven 12.2.4). */
|
|
148
|
+
function artisticUprights(plan) {
|
|
149
|
+
const { judges, start } = ARTISTIC.drawn;
|
|
150
|
+
const L = plan.length;
|
|
151
|
+
const W = plan.width;
|
|
152
|
+
const jy = -GUTTER - 1 - judges.depth;
|
|
153
|
+
const jx0 = L / 2 - judges.length / 2;
|
|
154
|
+
const jh = feet(ARTISTIC.judges.height);
|
|
155
|
+
const sx = L / 2 - start.length / 2;
|
|
156
|
+
const sy = W + GUTTER + 0.4;
|
|
157
|
+
const sh = feet(ARTISTIC.start.heightOlympic);
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
id: "judges-stand",
|
|
161
|
+
kind: "judges-stand",
|
|
162
|
+
at: [feet(L / 2), feet(jy + judges.depth / 2)],
|
|
163
|
+
height: jh,
|
|
164
|
+
planes: box("judges", feet(jx0), feet(jy), feet(jx0 + judges.length), feet(jy + judges.depth), 0, jh, "u-board", "u-leg", ["x0", "x1", "y1"]),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "starting-platform",
|
|
168
|
+
kind: "starting-blocks",
|
|
169
|
+
at: [feet(L / 2), feet(sy + start.depth / 2)],
|
|
170
|
+
height: sh,
|
|
171
|
+
planes: box("start", feet(sx), feet(sy), feet(sx + start.length), feet(sy + start.depth), 0, sh, "u-board", "u-leg", ["x0", "x1", "y0"]),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
/** What stands on a pool of the options' sport, as the drawing has it, with the plan's metres put into the drawing's feet. A natatorium of several pools has none. */
|
|
176
|
+
export function poolUprights(o, thin) {
|
|
177
|
+
const plan = poolPlan(o);
|
|
178
|
+
if (o.pool?.venue !== undefined)
|
|
179
|
+
return { items: [], plan };
|
|
180
|
+
switch (plan.sport) {
|
|
181
|
+
case "swimming":
|
|
182
|
+
return { items: swimmingUprights(plan, thin), plan };
|
|
183
|
+
case "waterpolo":
|
|
184
|
+
return { items: poloUprights(plan, thin), plan };
|
|
185
|
+
case "diving":
|
|
186
|
+
return { items: diveUprights(plan), plan };
|
|
187
|
+
default:
|
|
188
|
+
return { items: artisticUprights(plan), plan };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CourtFigures } from "./leagues/basketball.ts";
|
|
2
|
+
import type { RinkFigures } from "./leagues/hockey.ts";
|
|
3
|
+
import { type Plane, type Upright } from "./turn.kit.ts";
|
|
4
|
+
/** The boards of a rink, 42 in (NHL Rule 1.3: 40 to 48 in, 42 the ideal), the glass over them 8 ft at each end and 6 ft along the sides (Rule 1.3: not less than 6 ft). */
|
|
5
|
+
export declare const BOARD_HEIGHT = 3.5;
|
|
6
|
+
export declare const GLASS_END = 8;
|
|
7
|
+
export declare const GLASS_SIDE = 6;
|
|
8
|
+
/** The height of a hockey goal, 4 feet (NHL Rule 2.1). */
|
|
9
|
+
export declare const HOCKEY_GOAL_HEIGHT = 4;
|
|
10
|
+
/** The line round a rink's ice, as points: the straight sides and the quarter circles of its corners. */
|
|
11
|
+
export declare function rinkOutline(length: number, width: number, radius: number, steps?: number): [number, number][];
|
|
12
|
+
/** The boards with the glass above them, one plane for each straight stretch of the line: the glass is 8 ft at the ends and in the corners and 6 ft along the sides. */
|
|
13
|
+
export declare function boardPlanes(length: number, width: number, radius: number, boards: number, thin: number): Plane[];
|
|
14
|
+
/** A rink's uprights: the boards and glass, and the two goals. `goalLine` is the distance of the goal line from the end boards. */
|
|
15
|
+
export declare function rinkUprights(rink: RinkFigures, goalLine: number, thin: number, boards: boolean): Upright[];
|
|
16
|
+
/** The ring's upper edge is 10 ft over the floor (NBA Rule 1 Section II) and 3,050 mm in FIBA's (1.2.4). */
|
|
17
|
+
export declare const RIM_HEIGHT = 10;
|
|
18
|
+
export declare const FIBA_RIM_HEIGHT: number;
|
|
19
|
+
/** The board's lower edge is 150 mm under the ring's top (FIBA 1.1.5); NBA Rule 1 gives the board's size and the ring's height but not the lower edge, which is drawn the same. */
|
|
20
|
+
export declare const BOARD_DROP: number;
|
|
21
|
+
/** The NBA's board is 3 1/2 ft tall, FIBA's 1,050 mm. */
|
|
22
|
+
export declare const BOARD_TALL = 3.5;
|
|
23
|
+
export declare const FIBA_BOARD_TALL: number;
|
|
24
|
+
/** The least distance of the support's padding behind the end line, FIBA 1.4.3 Level 1: 2,000 mm. */
|
|
25
|
+
export declare const SUPPORT_BEHIND: number;
|
|
26
|
+
/** A court's uprights: its two baskets. */
|
|
27
|
+
export declare const courtUprights: (court: CourtFigures, thin: number, fiba?: boolean) => Upright[];
|