@johnmorrisdotca/kyogijo-stadiums 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/NOTICE.md +4 -0
  3. package/README.md +71 -16
  4. package/dist/annotate.js +1 -1
  5. package/dist/athletics/describe.d.ts +8 -0
  6. package/dist/athletics/describe.js +41 -0
  7. package/dist/athletics/events.d.ts +17 -0
  8. package/dist/athletics/events.js +48 -0
  9. package/dist/athletics/facilities.d.ts +80 -0
  10. package/dist/athletics/facilities.js +176 -0
  11. package/dist/athletics/infield.d.ts +79 -0
  12. package/dist/athletics/infield.js +166 -0
  13. package/dist/athletics/layout.d.ts +13 -0
  14. package/dist/athletics/layout.js +98 -0
  15. package/dist/athletics/oval.d.ts +57 -0
  16. package/dist/athletics/oval.js +116 -0
  17. package/dist/athletics/plan.d.ts +97 -0
  18. package/dist/athletics/plan.js +355 -0
  19. package/dist/athletics/races.d.ts +102 -0
  20. package/dist/athletics/races.js +355 -0
  21. package/dist/athletics/steeplechase.d.ts +72 -0
  22. package/dist/athletics/steeplechase.js +196 -0
  23. package/dist/athletics/stencil.d.ts +13 -0
  24. package/dist/athletics/stencil.js +85 -0
  25. package/dist/athletics/track.d.ts +54 -0
  26. package/dist/athletics/track.js +105 -0
  27. package/dist/athletics/types.d.ts +77 -0
  28. package/dist/athletics/types.js +1 -0
  29. package/dist/athletics-entry.d.ts +17 -0
  30. package/dist/athletics-entry.js +11 -0
  31. package/dist/athletics.style.d.ts +16 -0
  32. package/dist/athletics.style.js +51 -0
  33. package/dist/atlas-entry.d.ts +4 -2
  34. package/dist/atlas-entry.js +3 -2
  35. package/dist/atlas.d.ts +83 -7
  36. package/dist/atlas.js +108 -8
  37. package/dist/charts-entry.d.ts +6 -1
  38. package/dist/charts-entry.js +3 -0
  39. package/dist/charts.athletics.d.ts +183 -0
  40. package/dist/charts.athletics.js +455 -0
  41. package/dist/charts.curling.heat.d.ts +24 -0
  42. package/dist/charts.curling.heat.js +116 -0
  43. package/dist/charts.curling.js +14 -3
  44. package/dist/charts.pool.d.ts +67 -0
  45. package/dist/charts.pool.js +76 -0
  46. package/dist/charts.skating.d.ts +204 -0
  47. package/dist/charts.skating.js +426 -0
  48. package/dist/charts.types.d.ts +53 -1
  49. package/dist/court.d.ts +85 -2
  50. package/dist/court.js +102 -7
  51. package/dist/court.more.d.ts +46 -0
  52. package/dist/court.more.js +219 -0
  53. package/dist/courts.style.d.ts +14 -2
  54. package/dist/courts.style.js +51 -4
  55. package/dist/crowd.d.ts +154 -0
  56. package/dist/crowd.draw.d.ts +17 -0
  57. package/dist/crowd.draw.js +198 -0
  58. package/dist/crowd.js +465 -0
  59. package/dist/crowd.park.d.ts +19 -0
  60. package/dist/crowd.park.js +153 -0
  61. package/dist/curling.stats.d.ts +271 -0
  62. package/dist/curling.stats.js +301 -0
  63. package/dist/draw.d.ts +16 -0
  64. package/dist/draw.js +318 -35
  65. package/dist/element-entry.d.ts +1 -1
  66. package/dist/element-entry.js +1 -1
  67. package/dist/element.d.ts +13 -2
  68. package/dist/element.js +77 -3
  69. package/dist/features.js +131 -0
  70. package/dist/fields.kit.d.ts +2 -2
  71. package/dist/fields.types.d.ts +18 -4
  72. package/dist/frames.providers.js +63 -8
  73. package/dist/geojson.js +3 -0
  74. package/dist/geometry-entry.d.ts +2 -1
  75. package/dist/geometry-entry.js +2 -1
  76. package/dist/geometry.d.ts +20 -0
  77. package/dist/geometry.js +78 -1
  78. package/dist/grounds.d.ts +4 -0
  79. package/dist/grounds.data.js +12 -0
  80. package/dist/hardwood.d.ts +31 -0
  81. package/dist/hardwood.js +157 -0
  82. package/dist/heat.js +4 -3
  83. package/dist/heat.types.d.ts +5 -0
  84. package/dist/heat.zones.d.ts +2 -2
  85. package/dist/heat.zones.js +154 -6
  86. package/dist/homeside.js +17 -0
  87. package/dist/index.d.ts +16 -2
  88. package/dist/index.js +10 -1
  89. package/dist/layers.d.ts +1 -1
  90. package/dist/layers.env.d.ts +3 -1
  91. package/dist/layers.env.js +4 -2
  92. package/dist/layers.js +4 -4
  93. package/dist/leagues/athletics.d.ts +63 -0
  94. package/dist/leagues/athletics.js +87 -0
  95. package/dist/leagues/courts.d.ts +269 -4
  96. package/dist/leagues/courts.js +155 -1
  97. package/dist/leagues/skating.d.ts +145 -0
  98. package/dist/leagues/skating.js +73 -0
  99. package/dist/leagues/sources.js +20 -0
  100. package/dist/leagues/types.d.ts +1 -1
  101. package/dist/leagues-entry.d.ts +4 -2
  102. package/dist/leagues-entry.js +2 -1
  103. package/dist/leagues.d.ts +14 -4
  104. package/dist/leagues.js +146 -5
  105. package/dist/look.d.ts +3 -3
  106. package/dist/look.js +39 -6
  107. package/dist/outline.d.ts +1 -1
  108. package/dist/outline.js +30 -2
  109. package/dist/pageant.d.ts +120 -0
  110. package/dist/pageant.draw.d.ts +21 -0
  111. package/dist/pageant.draw.js +171 -0
  112. package/dist/pageant.js +207 -0
  113. package/dist/pageant.style.d.ts +8 -0
  114. package/dist/pageant.style.js +42 -0
  115. package/dist/png.d.ts +8 -1
  116. package/dist/png.js +21 -1
  117. package/dist/pool.d.ts +176 -0
  118. package/dist/pool.figures.d.ts +304 -0
  119. package/dist/pool.figures.js +144 -0
  120. package/dist/pool.js +304 -0
  121. package/dist/pool.kit.d.ts +48 -0
  122. package/dist/pool.kit.js +141 -0
  123. package/dist/pool.labels.d.ts +20 -0
  124. package/dist/pool.labels.js +257 -0
  125. package/dist/pool.race.d.ts +65 -0
  126. package/dist/pool.race.js +140 -0
  127. package/dist/pool.replay.d.ts +46 -0
  128. package/dist/pool.replay.js +127 -0
  129. package/dist/pool.section.d.ts +13 -0
  130. package/dist/pool.section.js +83 -0
  131. package/dist/pool.style.d.ts +20 -0
  132. package/dist/pool.style.js +169 -0
  133. package/dist/pool.types.d.ts +145 -0
  134. package/dist/pool.types.js +3 -0
  135. package/dist/pool.words.d.ts +13 -0
  136. package/dist/pool.words.js +101 -0
  137. package/dist/ranges.names.js +47 -3
  138. package/dist/replay-entry.d.ts +3 -0
  139. package/dist/replay-entry.js +2 -0
  140. package/dist/restyle.js +1 -1
  141. package/dist/score.js +70 -5
  142. package/dist/sheet.d.ts +3 -0
  143. package/dist/sheet.js +7 -3
  144. package/dist/skating/describe.d.ts +7 -0
  145. package/dist/skating/describe.js +44 -0
  146. package/dist/skating/events.d.ts +24 -0
  147. package/dist/skating/events.js +47 -0
  148. package/dist/skating/figure.d.ts +24 -0
  149. package/dist/skating/figure.js +37 -0
  150. package/dist/skating/long.d.ts +54 -0
  151. package/dist/skating/long.js +111 -0
  152. package/dist/skating/parts.d.ts +43 -0
  153. package/dist/skating/parts.js +40 -0
  154. package/dist/skating/plan.d.ts +55 -0
  155. package/dist/skating/plan.js +197 -0
  156. package/dist/skating/short.d.ts +47 -0
  157. package/dist/skating/short.js +89 -0
  158. package/dist/skating/track.d.ts +66 -0
  159. package/dist/skating/track.js +94 -0
  160. package/dist/skating/types.d.ts +57 -0
  161. package/dist/skating/types.js +3 -0
  162. package/dist/skating-entry.d.ts +17 -0
  163. package/dist/skating-entry.js +11 -0
  164. package/dist/skating.style.d.ts +4 -0
  165. package/dist/skating.style.js +24 -0
  166. package/dist/strings.d.ts +296 -0
  167. package/dist/strings.js +328 -11
  168. package/dist/style.d.ts +2 -0
  169. package/dist/style.js +11 -1
  170. package/dist/surfaces/artistic.d.ts +9 -0
  171. package/dist/surfaces/artistic.js +94 -0
  172. package/dist/surfaces/athletics.d.ts +16 -0
  173. package/dist/surfaces/athletics.js +132 -0
  174. package/dist/surfaces/baseball.js +15 -0
  175. package/dist/surfaces/basketball.floor.d.ts +27 -0
  176. package/dist/surfaces/basketball.floor.js +83 -0
  177. package/dist/surfaces/basketball.gear.d.ts +27 -0
  178. package/dist/surfaces/basketball.gear.js +90 -0
  179. package/dist/surfaces/basketball.js +43 -21
  180. package/dist/surfaces/curling.d.ts +16 -0
  181. package/dist/surfaces/curling.fold.d.ts +67 -0
  182. package/dist/surfaces/curling.fold.js +107 -0
  183. package/dist/surfaces/curling.js +37 -3
  184. package/dist/surfaces/dive.d.ts +9 -0
  185. package/dist/surfaces/dive.js +128 -0
  186. package/dist/surfaces/field.d.ts +7 -2
  187. package/dist/surfaces/field.js +15 -9
  188. package/dist/surfaces/hockey.js +1 -0
  189. package/dist/surfaces/net.d.ts +2 -1
  190. package/dist/surfaces/net.js +160 -15
  191. package/dist/surfaces/polo.d.ts +12 -0
  192. package/dist/surfaces/polo.js +159 -0
  193. package/dist/surfaces/pool.d.ts +27 -0
  194. package/dist/surfaces/pool.js +198 -0
  195. package/dist/surfaces/skating.d.ts +6 -0
  196. package/dist/surfaces/skating.js +62 -0
  197. package/dist/surfaces/soccer.js +2 -1
  198. package/dist/surfaces/surface.d.ts +44 -0
  199. package/dist/surfaces/venue.d.ts +15 -0
  200. package/dist/surfaces/venue.js +95 -0
  201. package/dist/tenants.d.ts +77 -0
  202. package/dist/tenants.data.d.ts +8 -0
  203. package/dist/tenants.data.js +945 -0
  204. package/dist/tenants.js +46 -0
  205. package/dist/turn-entry.d.ts +12 -0
  206. package/dist/turn-entry.js +7 -0
  207. package/dist/turn.camera.d.ts +105 -0
  208. package/dist/turn.camera.js +168 -0
  209. package/dist/turn.dom.d.ts +40 -0
  210. package/dist/turn.dom.js +351 -0
  211. package/dist/turn.kit.d.ts +86 -0
  212. package/dist/turn.kit.js +110 -0
  213. package/dist/turn.markup.d.ts +48 -0
  214. package/dist/turn.markup.js +108 -0
  215. package/dist/turn.model.d.ts +78 -0
  216. package/dist/turn.model.js +91 -0
  217. package/dist/turn.style.d.ts +5 -0
  218. package/dist/turn.style.js +55 -0
  219. package/dist/types.d.ts +183 -18
  220. package/dist/units.d.ts +1 -1
  221. package/dist/units.js +15 -1
  222. package/dist/uprights.board.d.ts +17 -0
  223. package/dist/uprights.board.js +59 -0
  224. package/dist/uprights.d.ts +37 -0
  225. package/dist/uprights.fields.d.ts +32 -0
  226. package/dist/uprights.fields.js +172 -0
  227. package/dist/uprights.goal.d.ts +49 -0
  228. package/dist/uprights.goal.js +69 -0
  229. package/dist/uprights.js +160 -0
  230. package/dist/uprights.more.d.ts +7 -0
  231. package/dist/uprights.more.js +183 -0
  232. package/dist/uprights.net.d.ts +12 -0
  233. package/dist/uprights.net.js +109 -0
  234. package/dist/uprights.netkit.d.ts +28 -0
  235. package/dist/uprights.netkit.js +67 -0
  236. package/dist/uprights.pools.d.ts +8 -0
  237. package/dist/uprights.pools.js +190 -0
  238. package/dist/uprights.rinks.d.ts +27 -0
  239. package/dist/uprights.rinks.js +106 -0
  240. package/dist/uprights.track.d.ts +50 -0
  241. package/dist/uprights.track.js +246 -0
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +1 -1
  244. package/package.json +19 -2
@@ -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 25 June 2025):
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 (CR 18.3.3), a net 0.30 m deep behind the line. */
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 (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 (CR 27.2.8: at least 0.6 m, not more than 2 m from the edge), and the starting platform 0.7 m high (CR 27.2.7). */
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[];
@@ -0,0 +1,106 @@
1
+ import { goalCage } from "./uprights.goal.js";
2
+ import { bar, mesh, n, plane, polygon, pole, shy, threads, wall } from "./turn.kit.js";
3
+ /** 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). */
4
+ export const BOARD_HEIGHT = 3.5;
5
+ export const GLASS_END = 8;
6
+ export const GLASS_SIDE = 6;
7
+ /** The height of a hockey goal, 4 feet (NHL Rule 2.1). */
8
+ export const HOCKEY_GOAL_HEIGHT = 4;
9
+ /** The line round a rink's ice, as points: the straight sides and the quarter circles of its corners. */
10
+ export function rinkOutline(length, width, radius, steps = 6) {
11
+ const r = Math.min(radius, width / 2, length / 2);
12
+ const out = [];
13
+ const corner = (cx, cy, from) => {
14
+ for (let i = 0; i <= steps; i += 1) {
15
+ const a = ((from + (90 * i) / steps) * Math.PI) / 180;
16
+ out.push([cx + r * Math.cos(a), cy + r * Math.sin(a)]);
17
+ }
18
+ };
19
+ // Clockwise as the page shows it: the top left corner first.
20
+ corner(r, r, 180);
21
+ corner(length - r, r, 270);
22
+ corner(length - r, width - r, 0);
23
+ corner(r, width - r, 90);
24
+ return out;
25
+ }
26
+ /** 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. */
27
+ export function boardPlanes(length, width, radius, boards, thin) {
28
+ const points = rinkOutline(length, width, radius);
29
+ const planes = [];
30
+ const cap = Math.max(thin * 0.8, 0.12);
31
+ for (let i = 0; i < points.length; i += 1) {
32
+ const [x0, y0] = points[i];
33
+ const [x1, y1] = points[(i + 1) % points.length];
34
+ const run = Math.hypot(x1 - x0, y1 - y0);
35
+ if (run < 1e-6)
36
+ continue;
37
+ const along = Math.abs(y1 - y0) < 1e-6 && (Math.abs(y0) < 1e-6 || Math.abs(y0 - width) < 1e-6) && Math.min(x0, x1) >= radius - 1e-6;
38
+ const glass = along ? GLASS_SIDE : GLASS_END;
39
+ const svg = `<rect y="${n(glass)}" width="${n(run)}" height="${n(boards)}" class="u-board"/><rect width="${n(run)}" height="${n(glass)}" class="u-glass"/><rect y="${n(glass)}" width="${n(run)}" height="${n(cap)}" class="u-cap"/><rect width="${n(run)}" height="${n(cap)}" class="u-cap"/>`;
40
+ planes.push(wall(`board-${i}`, "glass", x0, y0, x1, y1, 0, boards + glass, svg));
41
+ }
42
+ return planes;
43
+ }
44
+ /** A rink's uprights: the boards and glass, and the two goals. `goalLine` is the distance of the goal line from the end boards. */
45
+ export function rinkUprights(rink, goalLine, thin, boards) {
46
+ const { length: L, width: W } = rink;
47
+ const items = [];
48
+ if (boards)
49
+ items.push({ id: "boards", kind: "boards", at: [L / 2, W / 2], height: BOARD_HEIGHT, low: BOARD_HEIGHT + GLASS_END, planes: boardPlanes(L, W, rink.cornerRadius, BOARD_HEIGHT, thin).map((p) => shy(p, [L / 2, W / 2])) });
50
+ const { width, depth } = rink.net;
51
+ for (const end of ["left", "right"]) {
52
+ const left = end === "left";
53
+ items.push(goalCage({ id: `goal-${end}`, x: left ? goalLine : L - goalLine, y: W / 2, out: left ? -1 : 1, width, height: HOCKEY_GOAL_HEIGHT, depth, top: depth * 0.55, post: Math.max(thin * 1.2, 0.17), cell: Math.max(thin * 5, 0.35), tone: "red" }));
54
+ }
55
+ return items;
56
+ }
57
+ /** 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). */
58
+ export const RIM_HEIGHT = 10;
59
+ export const FIBA_RIM_HEIGHT = 3.05 * 3.280839895;
60
+ /** 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. */
61
+ export const BOARD_DROP = 0.15 * 3.280839895;
62
+ /** The NBA's board is 3 1/2 ft tall, FIBA's 1,050 mm. */
63
+ export const BOARD_TALL = 3.5;
64
+ export const FIBA_BOARD_TALL = 1.05 * 3.280839895;
65
+ /** The least distance of the support's padding behind the end line, FIBA 1.4.3 Level 1: 2,000 mm. */
66
+ export const SUPPORT_BEHIND = 2 * 3.280839895;
67
+ /** One basket: the backboard, the rim and its net, and the stanchion behind the baseline that holds them. `fiba` draws the board and the net at FIBA's size. */
68
+ function basket(end, court, thin, fiba) {
69
+ const left = end === "left";
70
+ const sign = left ? 1 : -1;
71
+ const L = court.length;
72
+ const cy = court.width / 2;
73
+ const back = left ? court.backboard.fromBaseline : L - court.backboard.fromBaseline;
74
+ const rimX = left ? court.basket : L - court.basket;
75
+ const r = court.rim;
76
+ const bw = court.backboard.width;
77
+ const tall = fiba ? FIBA_BOARD_TALL : BOARD_TALL;
78
+ const rimTop = fiba ? FIBA_RIM_HEIGHT : RIM_HEIGHT;
79
+ const bottom = rimTop - BOARD_DROP;
80
+ const top = bottom + tall;
81
+ const line = Math.max(thin * 1.8, 0.2);
82
+ const id = `hoop-${end}`;
83
+ const planes = [];
84
+ // The backboard: glass in a frame, with the rectangle behind the ring, its base level with the ring's top (FIBA 1.1.5: 590 by 450 mm; NBA: 24 by 18 in).
85
+ const targetW = fiba ? 0.59 * 3.280839895 : 2;
86
+ const targetH = fiba ? 0.45 * 3.280839895 : 1.5;
87
+ const svg = `<rect width="${n(bw)}" height="${n(tall)}" class="u-backboard"/><rect x="${n(line / 2)}" y="${n(line / 2)}" width="${n(bw - line)}" height="${n(tall - line)}" class="u-outline" stroke-width="${n(line)}" fill="none"/><rect x="${n((bw - targetW) / 2)}" y="${n(top - (rimTop + targetH))}" width="${n(targetW)}" height="${n(targetH)}" class="u-outline" stroke-width="${n(line * 0.8)}" fill="none"/>`;
88
+ planes.push(wall(`${id}-backboard`, "board", back, cy - bw / 2, back, cy + bw / 2, bottom, top, svg));
89
+ // The rim, lying flat, and the net hanging from it: two crossed trapezoids, 18 in long (NBA Rule 1 Section II; FIBA 1.3.1: 400 to 450 mm).
90
+ planes.push(plane(`${id}-rim`, "rim", [rimX - r, cy - r, rimTop], [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 * 1.6)}" fill="none"/>`));
91
+ const drop = fiba ? 0.42 * 3.280839895 : 1.5;
92
+ const foot = r * 0.55;
93
+ const shape = polygon([[0, 0], [2 * r, 0], [r + foot, drop], [r - foot, drop]]);
94
+ const netSvg = `<path d="${shape}" class="u-netfill"/>${threads(mesh(2 * r, drop, 0.3), 0.3)}`;
95
+ planes.push(plane(`${id}-net-a`, "net", [rimX, cy - r, rimTop], [0, 2 * r, 0], [0, 0, -drop], netSvg), plane(`${id}-net-b`, "net", [rimX - r, cy, rimTop], [2 * r, 0, 0], [0, 0, -drop], netSvg));
96
+ // The stanchion: a padded base whose front is 2 m behind the end line, and an arm out to the back of the board.
97
+ const padWide = Math.max(1.1, thin * 5);
98
+ const stand = left ? -(SUPPORT_BEHIND + padWide / 2) : L + SUPPORT_BEHIND + padWide / 2;
99
+ const arm = top - 1.4;
100
+ planes.push(...pole(`${id}-pad`, "stanchion", stand, cy, 0, 7, padWide, "u-pad"));
101
+ planes.push(...pole(`${id}-mast`, "stanchion", stand, cy, 7, arm, Math.max(0.5, thin * 2.5), "u-pole"));
102
+ planes.push(...bar(`${id}-arm`, "stanchion", [stand, cy, arm], [back - sign * line, cy, arm], Math.max(0.4, thin * 2), "u-pole"));
103
+ return { id, kind: "hoop", at: [rimX, cy], height: rimTop, low: top, planes };
104
+ }
105
+ /** A court's uprights: its two baskets. */
106
+ export const courtUprights = (court, thin, fiba = false) => [basket("left", court, thin, fiba), basket("right", court, thin, fiba)];
@@ -0,0 +1,50 @@
1
+ import { type Upright } from "./turn.kit.ts";
2
+ import type { SurfaceOptions } from "./types.ts";
3
+ /** The hurdles' heights: the men's 110 m (and the covered 60 m) 1.067 m, the women's 100 m 0.838 m, the 400 m 0.914 m (Technical Rule 22); a steeplechase barrier 0.914 m (Rule 23.5). */
4
+ export declare const HURDLE_HEIGHT: {
5
+ readonly "110m-hurdles": 1.067;
6
+ readonly "60m-hurdles": 1.067;
7
+ readonly "100m-hurdles": 0.838;
8
+ readonly "400m-hurdles": 0.914;
9
+ readonly steeplechase: 0.914;
10
+ };
11
+ /** The cage's netting is at least 4 m high, and at least 6 m for the 3 m nearest the front of each side (Facilities Manual 2.4.1.3). */
12
+ export declare const CAGE: {
13
+ readonly low: 4;
14
+ readonly high: 6;
15
+ readonly highLength: 3;
16
+ };
17
+ /** The high jump's uprights are 4.02 m apart (Manual 2.3.3.3) and the pole vault's at least 5.20 m (2.3.4.3). The heights of the bars are ours. */
18
+ export declare const JUMPS: {
19
+ readonly highUprights: 4.02;
20
+ readonly highBar: 2;
21
+ readonly highPost: 2.5;
22
+ readonly vaultUprights: 5.2;
23
+ readonly vaultBar: 5;
24
+ readonly vaultPost: 6;
25
+ readonly vaultBarLength: 4.5;
26
+ };
27
+ /** The padding round a track is at least 0.80 m high and 0.30 m thick (ISU Rule 228.2). */
28
+ export declare const PADDING: {
29
+ readonly height: 0.8;
30
+ };
31
+ /** An athletics stadium's uprights, as the plan has its box. */
32
+ export declare function athleticsUprights(o: SurfaceOptions, thin: number): {
33
+ items: Upright[];
34
+ area: {
35
+ x0: number;
36
+ y0: number;
37
+ x1: number;
38
+ y1: number;
39
+ };
40
+ };
41
+ /** What stands at a skating rink: the padding round a long or a short track, 0.80 m high (ISU Rule 228.2); the boards of a figure skating rink, as high as a hockey rink's and with no glass over them. */
42
+ export declare function skatingUprights(o: SurfaceOptions, thin: number): {
43
+ items: Upright[];
44
+ area: {
45
+ x0: number;
46
+ y0: number;
47
+ x1: number;
48
+ y1: number;
49
+ };
50
+ };