@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.
Files changed (361) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/NOTICE.md +182 -2
  3. package/README.md +106 -19
  4. package/dist/annotate.js +1 -1
  5. package/dist/arenas-entry.d.ts +4 -0
  6. package/dist/arenas-entry.js +3 -0
  7. package/dist/arenas.d.ts +125 -0
  8. package/dist/arenas.data.d.ts +2 -0
  9. package/dist/arenas.data.js +3763 -0
  10. package/dist/arenas.js +72 -0
  11. package/dist/athletics/describe.d.ts +8 -0
  12. package/dist/athletics/describe.js +41 -0
  13. package/dist/athletics/events.d.ts +17 -0
  14. package/dist/athletics/events.js +48 -0
  15. package/dist/athletics/facilities.d.ts +80 -0
  16. package/dist/athletics/facilities.js +176 -0
  17. package/dist/athletics/infield.d.ts +79 -0
  18. package/dist/athletics/infield.js +166 -0
  19. package/dist/athletics/layout.d.ts +13 -0
  20. package/dist/athletics/layout.js +98 -0
  21. package/dist/athletics/oval.d.ts +57 -0
  22. package/dist/athletics/oval.js +116 -0
  23. package/dist/athletics/plan.d.ts +97 -0
  24. package/dist/athletics/plan.js +355 -0
  25. package/dist/athletics/races.d.ts +102 -0
  26. package/dist/athletics/races.js +355 -0
  27. package/dist/athletics/steeplechase.d.ts +72 -0
  28. package/dist/athletics/steeplechase.js +196 -0
  29. package/dist/athletics/stencil.d.ts +13 -0
  30. package/dist/athletics/stencil.js +85 -0
  31. package/dist/athletics/track.d.ts +54 -0
  32. package/dist/athletics/track.js +105 -0
  33. package/dist/athletics/types.d.ts +77 -0
  34. package/dist/athletics/types.js +1 -0
  35. package/dist/athletics-entry.d.ts +17 -0
  36. package/dist/athletics-entry.js +11 -0
  37. package/dist/athletics.style.d.ts +16 -0
  38. package/dist/athletics.style.js +51 -0
  39. package/dist/atlas-entry.d.ts +6 -2
  40. package/dist/atlas-entry.js +4 -2
  41. package/dist/atlas.d.ts +117 -23
  42. package/dist/atlas.js +197 -12
  43. package/dist/background.js +2 -1
  44. package/dist/budo/describe.d.ts +13 -0
  45. package/dist/budo/describe.js +120 -0
  46. package/dist/budo/dojo.api.d.ts +64 -0
  47. package/dist/budo/dojo.api.js +46 -0
  48. package/dist/budo/dojo.d.ts +72 -0
  49. package/dist/budo/dojo.js +300 -0
  50. package/dist/budo/judo.d.ts +61 -0
  51. package/dist/budo/judo.js +109 -0
  52. package/dist/budo/karate.d.ts +29 -0
  53. package/dist/budo/karate.js +80 -0
  54. package/dist/budo/kendo.d.ts +18 -0
  55. package/dist/budo/kendo.js +44 -0
  56. package/dist/budo/kimarite.d.ts +19 -0
  57. package/dist/budo/kimarite.js +33 -0
  58. package/dist/budo/kit.d.ts +16 -0
  59. package/dist/budo/kit.js +24 -0
  60. package/dist/budo/kyudo.d.ts +28 -0
  61. package/dist/budo/kyudo.js +66 -0
  62. package/dist/budo/looks.d.ts +28 -0
  63. package/dist/budo/looks.js +58 -0
  64. package/dist/budo/parts.d.ts +32 -0
  65. package/dist/budo/parts.js +10 -0
  66. package/dist/budo/plan.d.ts +64 -0
  67. package/dist/budo/plan.js +139 -0
  68. package/dist/budo/room.d.ts +133 -0
  69. package/dist/budo/room.js +111 -0
  70. package/dist/budo/sumo.d.ts +31 -0
  71. package/dist/budo/sumo.js +71 -0
  72. package/dist/budo/tatami.d.ts +55 -0
  73. package/dist/budo/tatami.js +156 -0
  74. package/dist/budo/types.d.ts +192 -0
  75. package/dist/budo/types.js +2 -0
  76. package/dist/budo-entry.d.ts +22 -0
  77. package/dist/budo-entry.js +13 -0
  78. package/dist/budo.board.d.ts +28 -0
  79. package/dist/budo.board.js +81 -0
  80. package/dist/budo.score.d.ts +18 -0
  81. package/dist/budo.score.js +52 -0
  82. package/dist/budo.style.d.ts +6 -0
  83. package/dist/budo.style.js +67 -0
  84. package/dist/charts-entry.d.ts +6 -1
  85. package/dist/charts-entry.js +3 -0
  86. package/dist/charts.athletics.d.ts +183 -0
  87. package/dist/charts.athletics.js +455 -0
  88. package/dist/charts.baseball.js +1 -1
  89. package/dist/charts.curling.heat.d.ts +24 -0
  90. package/dist/charts.curling.heat.js +116 -0
  91. package/dist/charts.curling.js +14 -3
  92. package/dist/charts.js +4 -4
  93. package/dist/charts.pool.d.ts +67 -0
  94. package/dist/charts.pool.js +76 -0
  95. package/dist/charts.skating.d.ts +204 -0
  96. package/dist/charts.skating.js +426 -0
  97. package/dist/charts.soccer.js +3 -3
  98. package/dist/charts.types.d.ts +63 -1
  99. package/dist/colour.d.ts +4 -4
  100. package/dist/colour.js +4 -4
  101. package/dist/court.d.ts +85 -2
  102. package/dist/court.js +102 -7
  103. package/dist/court.more.d.ts +46 -0
  104. package/dist/court.more.js +219 -0
  105. package/dist/courts.style.d.ts +14 -2
  106. package/dist/courts.style.js +51 -4
  107. package/dist/crowd.d.ts +164 -0
  108. package/dist/crowd.draw.d.ts +17 -0
  109. package/dist/crowd.draw.js +204 -0
  110. package/dist/crowd.js +480 -0
  111. package/dist/crowd.park.d.ts +19 -0
  112. package/dist/crowd.park.js +153 -0
  113. package/dist/curling.stats.d.ts +271 -0
  114. package/dist/curling.stats.js +301 -0
  115. package/dist/draw.d.ts +16 -0
  116. package/dist/draw.js +462 -54
  117. package/dist/element-entry.d.ts +3 -1
  118. package/dist/element-entry.js +2 -1
  119. package/dist/element.d.ts +13 -2
  120. package/dist/element.js +79 -4
  121. package/dist/emblems-source.d.ts +50 -0
  122. package/dist/emblems-source.js +56 -0
  123. package/dist/eras.d.ts +3 -1
  124. package/dist/eras.js +8 -3
  125. package/dist/features.js +204 -0
  126. package/dist/field.data.d.ts +16 -0
  127. package/dist/field.data.js +50 -0
  128. package/dist/fields.kit.d.ts +2 -2
  129. package/dist/fields.types.d.ts +24 -4
  130. package/dist/formations.js +18 -7
  131. package/dist/formations.sports.d.ts +8 -0
  132. package/dist/formations.sports.js +251 -0
  133. package/dist/frames.providers.js +71 -8
  134. package/dist/geojson.js +3 -0
  135. package/dist/geometry-entry.d.ts +2 -1
  136. package/dist/geometry-entry.js +2 -1
  137. package/dist/geometry.d.ts +20 -0
  138. package/dist/geometry.js +78 -1
  139. package/dist/grounds.d.ts +4 -0
  140. package/dist/grounds.data.js +12 -0
  141. package/dist/halls.d.ts +65 -0
  142. package/dist/halls.js +122 -0
  143. package/dist/hardwood.d.ts +31 -0
  144. package/dist/hardwood.js +157 -0
  145. package/dist/heat.js +21 -13
  146. package/dist/heat.types.d.ts +5 -0
  147. package/dist/heat.zones.d.ts +4 -2
  148. package/dist/heat.zones.js +207 -7
  149. package/dist/homeside.js +24 -0
  150. package/dist/index.d.ts +30 -4
  151. package/dist/index.js +18 -2
  152. package/dist/layers-entry.d.ts +2 -1
  153. package/dist/layers-entry.js +1 -0
  154. package/dist/layers.d.ts +2 -1
  155. package/dist/layers.env.d.ts +12 -1
  156. package/dist/layers.env.js +36 -4
  157. package/dist/layers.js +32 -13
  158. package/dist/layers.types.d.ts +9 -2
  159. package/dist/leagues/athletics.d.ts +63 -0
  160. package/dist/leagues/athletics.js +87 -0
  161. package/dist/leagues/budo.d.ts +196 -0
  162. package/dist/leagues/budo.js +105 -0
  163. package/dist/leagues/courts.d.ts +269 -4
  164. package/dist/leagues/courts.js +155 -1
  165. package/dist/leagues/hockey.js +41 -19
  166. package/dist/leagues/skating.d.ts +145 -0
  167. package/dist/leagues/skating.js +73 -0
  168. package/dist/leagues/sources.js +41 -0
  169. package/dist/leagues/types.d.ts +1 -1
  170. package/dist/leagues-entry.d.ts +4 -2
  171. package/dist/leagues-entry.js +2 -1
  172. package/dist/leagues.d.ts +17 -4
  173. package/dist/leagues.js +246 -5
  174. package/dist/lineup.codes.d.ts +23 -0
  175. package/dist/lineup.codes.js +122 -0
  176. package/dist/logo.d.ts +25 -0
  177. package/dist/logo.js +47 -0
  178. package/dist/look.d.ts +3 -3
  179. package/dist/look.js +39 -6
  180. package/dist/outline.d.ts +1 -1
  181. package/dist/outline.js +46 -4
  182. package/dist/pageant.d.ts +120 -0
  183. package/dist/pageant.draw.d.ts +21 -0
  184. package/dist/pageant.draw.js +171 -0
  185. package/dist/pageant.js +211 -0
  186. package/dist/pageant.style.d.ts +8 -0
  187. package/dist/pageant.style.js +42 -0
  188. package/dist/png.d.ts +8 -1
  189. package/dist/png.js +21 -1
  190. package/dist/pointer-entry.d.ts +2 -0
  191. package/dist/pointer-entry.js +2 -0
  192. package/dist/pointer.d.ts +113 -0
  193. package/dist/pointer.js +151 -0
  194. package/dist/pool.d.ts +176 -0
  195. package/dist/pool.figures.d.ts +306 -0
  196. package/dist/pool.figures.js +148 -0
  197. package/dist/pool.js +304 -0
  198. package/dist/pool.kit.d.ts +48 -0
  199. package/dist/pool.kit.js +141 -0
  200. package/dist/pool.labels.d.ts +20 -0
  201. package/dist/pool.labels.js +258 -0
  202. package/dist/pool.race.d.ts +65 -0
  203. package/dist/pool.race.js +140 -0
  204. package/dist/pool.replay.d.ts +46 -0
  205. package/dist/pool.replay.js +127 -0
  206. package/dist/pool.section.d.ts +13 -0
  207. package/dist/pool.section.js +83 -0
  208. package/dist/pool.style.d.ts +20 -0
  209. package/dist/pool.style.js +169 -0
  210. package/dist/pool.types.d.ts +145 -0
  211. package/dist/pool.types.js +3 -0
  212. package/dist/pool.words.d.ts +13 -0
  213. package/dist/pool.words.js +101 -0
  214. package/dist/ranges.names.js +53 -3
  215. package/dist/replay-entry.d.ts +3 -0
  216. package/dist/replay-entry.js +2 -0
  217. package/dist/restyle.js +1 -1
  218. package/dist/road.draw.js +3 -2
  219. package/dist/road.js +2 -1
  220. package/dist/road.types.d.ts +3 -0
  221. package/dist/rules.d.ts +138 -0
  222. package/dist/rules.js +511 -0
  223. package/dist/rules.types.d.ts +4 -0
  224. package/dist/rules.types.js +64 -0
  225. package/dist/score.d.ts +3 -0
  226. package/dist/score.js +93 -6
  227. package/dist/sheet.d.ts +3 -0
  228. package/dist/sheet.js +7 -3
  229. package/dist/situation.d.ts +135 -0
  230. package/dist/situation.draw.d.ts +34 -0
  231. package/dist/situation.draw.js +202 -0
  232. package/dist/situation.js +337 -0
  233. package/dist/situation.style.d.ts +2 -0
  234. package/dist/situation.style.js +45 -0
  235. package/dist/situation.types.d.ts +77 -0
  236. package/dist/situation.types.js +3 -0
  237. package/dist/situation.widgets.d.ts +39 -0
  238. package/dist/situation.widgets.js +129 -0
  239. package/dist/skating/describe.d.ts +7 -0
  240. package/dist/skating/describe.js +44 -0
  241. package/dist/skating/events.d.ts +24 -0
  242. package/dist/skating/events.js +47 -0
  243. package/dist/skating/figure.d.ts +24 -0
  244. package/dist/skating/figure.js +37 -0
  245. package/dist/skating/long.d.ts +54 -0
  246. package/dist/skating/long.js +111 -0
  247. package/dist/skating/parts.d.ts +43 -0
  248. package/dist/skating/parts.js +40 -0
  249. package/dist/skating/plan.d.ts +55 -0
  250. package/dist/skating/plan.js +197 -0
  251. package/dist/skating/short.d.ts +47 -0
  252. package/dist/skating/short.js +89 -0
  253. package/dist/skating/track.d.ts +66 -0
  254. package/dist/skating/track.js +94 -0
  255. package/dist/skating/types.d.ts +57 -0
  256. package/dist/skating/types.js +3 -0
  257. package/dist/skating-entry.d.ts +17 -0
  258. package/dist/skating-entry.js +11 -0
  259. package/dist/skating.style.d.ts +4 -0
  260. package/dist/skating.style.js +24 -0
  261. package/dist/strings.d.ts +470 -0
  262. package/dist/strings.js +674 -13
  263. package/dist/style.d.ts +2 -0
  264. package/dist/style.js +19 -2
  265. package/dist/surfaces/artistic.d.ts +9 -0
  266. package/dist/surfaces/artistic.js +94 -0
  267. package/dist/surfaces/athletics.d.ts +16 -0
  268. package/dist/surfaces/athletics.js +132 -0
  269. package/dist/surfaces/baseball.js +17 -0
  270. package/dist/surfaces/basketball.floor.d.ts +27 -0
  271. package/dist/surfaces/basketball.floor.js +83 -0
  272. package/dist/surfaces/basketball.gear.d.ts +27 -0
  273. package/dist/surfaces/basketball.gear.js +90 -0
  274. package/dist/surfaces/basketball.js +44 -22
  275. package/dist/surfaces/budo.d.ts +6 -0
  276. package/dist/surfaces/budo.js +104 -0
  277. package/dist/surfaces/curling.d.ts +16 -0
  278. package/dist/surfaces/curling.fold.d.ts +67 -0
  279. package/dist/surfaces/curling.fold.js +107 -0
  280. package/dist/surfaces/curling.js +37 -3
  281. package/dist/surfaces/dive.d.ts +9 -0
  282. package/dist/surfaces/dive.js +128 -0
  283. package/dist/surfaces/field.d.ts +7 -2
  284. package/dist/surfaces/field.js +15 -9
  285. package/dist/surfaces/football.js +7 -0
  286. package/dist/surfaces/hockey.js +4 -2
  287. package/dist/surfaces/net.d.ts +2 -1
  288. package/dist/surfaces/net.js +160 -15
  289. package/dist/surfaces/polo.d.ts +12 -0
  290. package/dist/surfaces/polo.js +159 -0
  291. package/dist/surfaces/pool.d.ts +27 -0
  292. package/dist/surfaces/pool.js +198 -0
  293. package/dist/surfaces/skating.d.ts +6 -0
  294. package/dist/surfaces/skating.js +62 -0
  295. package/dist/surfaces/soccer.js +2 -1
  296. package/dist/surfaces/surface.d.ts +76 -0
  297. package/dist/surfaces/venue.d.ts +15 -0
  298. package/dist/surfaces/venue.js +95 -0
  299. package/dist/target-entry.d.ts +2 -0
  300. package/dist/target-entry.js +2 -0
  301. package/dist/target.d.ts +200 -0
  302. package/dist/target.js +525 -0
  303. package/dist/team.common.d.ts +1 -1
  304. package/dist/team.common.js +3 -3
  305. package/dist/team.formation.d.ts +3 -1
  306. package/dist/team.formation.js +3 -3
  307. package/dist/team.layers.d.ts +2 -2
  308. package/dist/team.layers.js +4 -1
  309. package/dist/team.lineup.d.ts +27 -0
  310. package/dist/team.lineup.js +207 -0
  311. package/dist/team.network.js +6 -5
  312. package/dist/team.shape.js +5 -5
  313. package/dist/team.types.d.ts +40 -1
  314. package/dist/tenants.d.ts +77 -0
  315. package/dist/tenants.data.d.ts +8 -0
  316. package/dist/tenants.data.js +1793 -0
  317. package/dist/tenants.js +46 -0
  318. package/dist/turn-entry.d.ts +12 -0
  319. package/dist/turn-entry.js +7 -0
  320. package/dist/turn.camera.d.ts +116 -0
  321. package/dist/turn.camera.js +184 -0
  322. package/dist/turn.dom.d.ts +40 -0
  323. package/dist/turn.dom.js +353 -0
  324. package/dist/turn.kit.d.ts +86 -0
  325. package/dist/turn.kit.js +110 -0
  326. package/dist/turn.markup.d.ts +48 -0
  327. package/dist/turn.markup.js +108 -0
  328. package/dist/turn.model.d.ts +78 -0
  329. package/dist/turn.model.js +91 -0
  330. package/dist/turn.style.d.ts +5 -0
  331. package/dist/turn.style.js +56 -0
  332. package/dist/types.d.ts +221 -20
  333. package/dist/units.d.ts +1 -1
  334. package/dist/units.js +22 -1
  335. package/dist/uprights.board.d.ts +17 -0
  336. package/dist/uprights.board.js +59 -0
  337. package/dist/uprights.budo.d.ts +17 -0
  338. package/dist/uprights.budo.js +87 -0
  339. package/dist/uprights.d.ts +37 -0
  340. package/dist/uprights.fields.d.ts +32 -0
  341. package/dist/uprights.fields.js +172 -0
  342. package/dist/uprights.goal.d.ts +49 -0
  343. package/dist/uprights.goal.js +69 -0
  344. package/dist/uprights.js +169 -0
  345. package/dist/uprights.more.d.ts +7 -0
  346. package/dist/uprights.more.js +183 -0
  347. package/dist/uprights.net.d.ts +12 -0
  348. package/dist/uprights.net.js +109 -0
  349. package/dist/uprights.netkit.d.ts +28 -0
  350. package/dist/uprights.netkit.js +67 -0
  351. package/dist/uprights.pools.d.ts +8 -0
  352. package/dist/uprights.pools.js +190 -0
  353. package/dist/uprights.rinks.d.ts +27 -0
  354. package/dist/uprights.rinks.js +106 -0
  355. package/dist/uprights.track.d.ts +50 -0
  356. package/dist/uprights.track.js +246 -0
  357. package/dist/version.d.ts +1 -1
  358. package/dist/version.js +1 -1
  359. package/dist/zones-entry.d.ts +2 -0
  360. package/dist/zones-entry.js +2 -0
  361. package/package.json +56 -3
@@ -0,0 +1,353 @@
1
+ // The 3D view in a page: `mountKyogijoTurn` writes the scene (`turn.markup.ts`) into a container and makes it turn, as the cube of kyuubu turns: a drag turns and
2
+ // tips it, a pinch or Ctrl and the wheel zoom, the arrow keys step it, four viewpoints and a reset are a button or a number key away, a reader who prefers
3
+ // reduced motion gets jumps for glides, and where the browser cannot draw in 3D the flat drawing stays. Nothing is polled and nothing runs while the view is
4
+ // still: the elements' `transform`s are written when the view changes, and a glide between viewpoints is a short run of animation frames.
5
+ import { drawKyogijo } from "./draw.js";
6
+ import { STEP, TURN_PRESET_NAMES, clampView, drag, nearest, nudge, presetOf, presetView, step } from "./turn.camera.js";
7
+ import { buildTurn, floorOptions, openingView, turnWords } from "./turn.model.js";
8
+ import { controlsMarkup, layoutOf, turnMarkupOf, worldMarkup } from "./turn.markup.js";
9
+ import { KYOGIJO_TURN_STYLE } from "./turn.style.js";
10
+ import { esc } from "./format.js";
11
+ /** True where the page can draw a plane in 3D by one `matrix3d`: that is, nearly everywhere with a stylesheet. */
12
+ export function turnSupported(win = typeof window === "undefined" ? undefined : window) {
13
+ const css = win?.CSS;
14
+ if (css === undefined || css === null || typeof css.supports !== "function")
15
+ return false;
16
+ return css.supports("transform", "matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)");
17
+ }
18
+ const KEYS = { ArrowLeft: true, ArrowRight: true, ArrowUp: true, ArrowDown: true, "+": true, "=": true, "-": true, _: true, "0": true, Home: true, "1": true, "2": true, "3": true, "4": true };
19
+ /** The view part of the way from one to another, `t` from 0 to 1, the turn taken the short way round. */
20
+ const between = (from, to, t) => ({ yaw: from.yaw + (nearest(from.yaw, to.yaw) - from.yaw) * t, pitch: from.pitch + (to.pitch - from.pitch) * t, zoom: from.zoom * Math.pow(to.zoom / from.zoom, t) });
21
+ /**
22
+ * Mount a surface as a 3D view in a container, over the static drawing it would otherwise hold: the drawing is the floor, tilted and turned by the pointer, the
23
+ * touch, the wheel and the keys, with what stands on the surface (a net and its posts, a basket, a goal, the posts of a football field) standing on it.
24
+ * @example
25
+ * ```ts no-run
26
+ * import { mountKyogijoTurn } from "@johnmorrisdotca/kyogijo-stadiums/turn";
27
+ * const view = mountKyogijoTurn(document.querySelector("#court")!, { sport: "volleyball", home: { name: "Harbour City Gulls", score: 3 }, away: { name: "Prairie Ridge Bison", score: 2 } }, { preset: "end" });
28
+ * view.preset("corner");
29
+ * view.set({ yaw: 60, pitch: 40 });
30
+ * ```
31
+ */
32
+ export function mountKyogijoTurn(host, options, settings = {}) {
33
+ const doc = host.ownerDocument;
34
+ const win = doc.defaultView;
35
+ let model = buildTurn(options, settings);
36
+ const sport = () => model.options.sport;
37
+ const supported = turnSupported(win ?? undefined) && model.turnable;
38
+ let alive = true;
39
+ if (!supported) {
40
+ // The flat drawing, with a sentence that says why it does not turn when the browser is the reason.
41
+ const flat = (next) => {
42
+ host.innerHTML = `<div class="ky-turn" data-mode="flat"><style>${KYOGIJO_TURN_STYLE}</style>${drawKyogijo(floorOptions(next))}${model.turnable ? `<p class="ky-turn-flat">${esc(turnWords(model).flat)}</p>` : ""}</div>`;
43
+ };
44
+ flat(options);
45
+ return {
46
+ mode: "flat",
47
+ view: () => model.view,
48
+ set: () => undefined,
49
+ preset: () => undefined,
50
+ reset: () => undefined,
51
+ update: (next) => {
52
+ model = buildTurn(next, settings);
53
+ flat(next);
54
+ },
55
+ svg: () => (alive ? host.querySelector("svg.kyogijo") : null),
56
+ destroy: () => {
57
+ alive = false;
58
+ host.replaceChildren();
59
+ },
60
+ };
61
+ }
62
+ host.innerHTML = turnMarkupOf(model, settings);
63
+ const root = host.querySelector(".ky-turn");
64
+ const stage = root.querySelector(".ky-turn-view");
65
+ const live = root.querySelector(".ky-turn-live");
66
+ const media = win?.matchMedia?.("(prefers-reduced-motion: reduce)");
67
+ /** Where the view is going, and where it is on the screen now (the two differ while it glides). */
68
+ let view = model.view;
69
+ let shown = view;
70
+ let opening = model.view;
71
+ let flight;
72
+ let spoken;
73
+ const calm = () => settings.motion === "none" || (settings.motion !== "smooth" && media?.matches === true);
74
+ // The elements, floor first, and what was written to each, so that only a change is written.
75
+ let elements = [];
76
+ let written = [];
77
+ let stacked = [];
78
+ let faded = [];
79
+ const gather = () => {
80
+ elements = [...stage.querySelectorAll(".ky-turn-floor, .ky-up")];
81
+ written = elements.map((one) => one.style.transform);
82
+ stacked = elements.map((one) => one.style.zIndex);
83
+ faded = elements.map((one) => one.style.opacity);
84
+ };
85
+ gather();
86
+ const buttons = () => root.querySelectorAll("button[data-preset]");
87
+ /** Put a view on the screen. */
88
+ const paint = (now) => {
89
+ const width = stage.clientWidth || 960;
90
+ const height = stage.clientHeight || 600;
91
+ const layout = layoutOf(model, now, width, height, settings.lens);
92
+ elements.forEach((one, i) => {
93
+ if (written[i] !== layout.transforms[i])
94
+ one.style.transform = written[i] = layout.transforms[i];
95
+ const z = String(layout.stack[i]);
96
+ if (stacked[i] !== z)
97
+ one.style.zIndex = stacked[i] = z;
98
+ const o = layout.fade[i] < 1 ? String(layout.fade[i]) : "";
99
+ if (faded[i] !== o)
100
+ one.style.opacity = faded[i] = o;
101
+ });
102
+ };
103
+ const label = () => {
104
+ const here = presetOf(view, sport());
105
+ root.dataset.preset = here ?? "custom";
106
+ for (const button of buttons())
107
+ button.setAttribute("aria-pressed", String(button.dataset.preset === here));
108
+ };
109
+ paint(shown);
110
+ label();
111
+ const stop = () => {
112
+ if (flight !== undefined)
113
+ win?.cancelAnimationFrame(flight.frame);
114
+ flight = undefined;
115
+ };
116
+ /** Go to a view: by a glide of `ms` milliseconds, or at once. */
117
+ const show = (to, ms) => {
118
+ stop();
119
+ view = to;
120
+ label();
121
+ if (ms <= 0 || calm() || win === null) {
122
+ shown = to;
123
+ paint(shown);
124
+ return;
125
+ }
126
+ const from = shown;
127
+ const run = (stamp) => {
128
+ if (flight === undefined)
129
+ return;
130
+ const t = Math.min(1, (stamp - flight.start) / flight.ms);
131
+ const eased = 1 - Math.pow(1 - t, 3);
132
+ shown = t >= 1 ? flight.to : between(flight.from, flight.to, eased);
133
+ paint(shown);
134
+ if (t < 1)
135
+ flight.frame = win.requestAnimationFrame(run);
136
+ else
137
+ flight = undefined;
138
+ };
139
+ flight = { from, to, start: win.performance.now(), ms, frame: 0 };
140
+ flight.frame = win.requestAnimationFrame(run);
141
+ };
142
+ const say = (text, wait = 0) => {
143
+ clearTimeout(spoken);
144
+ const write = () => {
145
+ // A repeated sentence is cleared first, so that a screen reader says it again.
146
+ if (live.textContent === text)
147
+ live.textContent = "";
148
+ live.textContent = text;
149
+ };
150
+ if (wait === 0)
151
+ write();
152
+ else
153
+ spoken = setTimeout(write, wait);
154
+ };
155
+ const angles = () => turnWords(model).announceAngle.replace("{yaw}", String(Math.round(((view.yaw % 360) + 360) % 360))).replace("{pitch}", String(Math.round(90 - view.pitch)));
156
+ const changed = () => settings.onChange?.(view, presetOf(view, sport()));
157
+ /** Move to a view the controls asked for: glide, say where it is, tell the page. */
158
+ const go = (next, ms, speak = "angle") => {
159
+ // A turn takes the short way round, even from 170 degrees to -170.
160
+ show(clampView({ ...next, yaw: nearest(view.yaw, next.yaw) }), ms);
161
+ if (speak === "preset") {
162
+ const here = presetOf(view, sport());
163
+ if (here !== undefined)
164
+ say(turnWords(model).announce.replace("{name}", turnWords(model)[here]));
165
+ }
166
+ else if (speak === "angle")
167
+ say(angles(), 450);
168
+ changed();
169
+ };
170
+ const toPreset = (name) => go(presetView(name, sport()) ?? opening, 560, "preset");
171
+ const toOpening = () => {
172
+ go(opening, 560, "none");
173
+ say(turnWords(model).reset);
174
+ };
175
+ // ---- Buttons. ----
176
+ const onClick = (event) => {
177
+ const target = event.target?.closest("button");
178
+ if (target === null || target === undefined || !root.contains(target))
179
+ return;
180
+ const name = target.dataset.preset;
181
+ if (name !== undefined)
182
+ return toPreset(name);
183
+ const big = event.shiftKey === true;
184
+ switch (target.dataset.act) {
185
+ // The buttons take the surface toward their side as a drag and the arrow keys do.
186
+ case "turn-left": return go(nudge(view, "left", big ? STEP.yawBig : STEP.yaw * 2), 180);
187
+ case "turn-right": return go(nudge(view, "right", big ? STEP.yawBig : STEP.yaw * 2), 180);
188
+ case "raise": return go(nudge(view, "up", big ? STEP.pitchBig : STEP.pitch * 2), 180);
189
+ case "lower": return go(nudge(view, "down", big ? STEP.pitchBig : STEP.pitch * 2), 180);
190
+ case "zoom-in": return go(step(view, { zoom: STEP.zoom }), 180, "none");
191
+ case "zoom-out": return go(step(view, { zoom: 1 / STEP.zoom }), 180, "none");
192
+ case "reset": return toOpening();
193
+ default:
194
+ }
195
+ };
196
+ root.addEventListener("click", onClick);
197
+ // ---- Keys. ----
198
+ const onKey = (event) => {
199
+ if (event.ctrlKey || event.metaKey || event.altKey || KEYS[event.key] !== true)
200
+ return;
201
+ const big = event.shiftKey;
202
+ const yaw = big ? STEP.yawBig : STEP.yaw;
203
+ const pitch = big ? STEP.pitchBig : STEP.pitch;
204
+ event.preventDefault();
205
+ switch (event.key) {
206
+ // A key takes the surface toward its side as a drag toward that side does: the side nearest the eye goes to the right on ArrowRight, and ArrowUp tips it as a drag up does.
207
+ case "ArrowLeft": return go(nudge(view, "left", yaw), 120);
208
+ case "ArrowRight": return go(nudge(view, "right", yaw), 120);
209
+ case "ArrowUp": return go(nudge(view, "up", pitch), 120);
210
+ case "ArrowDown": return go(nudge(view, "down", pitch), 120);
211
+ case "+":
212
+ case "=": return go(step(view, { zoom: STEP.zoom }), 120, "none");
213
+ case "-":
214
+ case "_": return go(step(view, { zoom: 1 / STEP.zoom }), 120, "none");
215
+ case "0":
216
+ case "Home": return toOpening();
217
+ default: toPreset(TURN_PRESET_NAMES[Number(event.key) - 1]);
218
+ }
219
+ };
220
+ stage.addEventListener("keydown", onKey);
221
+ // ---- The pointer: one finger or the mouse turns and tips; two fingers pinch. ----
222
+ const down = new Map();
223
+ let pinch;
224
+ let moved = false;
225
+ const tilts = (type) => type !== "touch" || settings.touch === "turn";
226
+ /** The pointer takes the view over from a glide: from where it is on the screen. */
227
+ const grab = () => {
228
+ stop();
229
+ view = shown;
230
+ };
231
+ const onDown = (event) => {
232
+ if (event.pointerType === "mouse" && event.button !== 0)
233
+ return;
234
+ down.set(event.pointerId, { x: event.clientX, y: event.clientY });
235
+ try {
236
+ stage.setPointerCapture(event.pointerId);
237
+ }
238
+ catch {
239
+ /* A pointer that is already gone. */
240
+ }
241
+ moved = false;
242
+ grab();
243
+ if (down.size === 1)
244
+ root.setAttribute("data-dragging", "");
245
+ if (down.size === 2) {
246
+ const [a, b] = [...down.values()];
247
+ pinch = { distance: Math.hypot(a.x - b.x, a.y - b.y) || 1, zoom: view.zoom };
248
+ }
249
+ };
250
+ const onMove = (event) => {
251
+ const before = down.get(event.pointerId);
252
+ if (before === undefined)
253
+ return;
254
+ const here = { x: event.clientX, y: event.clientY };
255
+ down.set(event.pointerId, here);
256
+ if (down.size >= 2 && pinch !== undefined) {
257
+ const [a, b] = [...down.values()];
258
+ view = clampView({ ...view, zoom: pinch.zoom * ((Math.hypot(a.x - b.x, a.y - b.y) || 1) / pinch.distance) });
259
+ }
260
+ else {
261
+ view = drag(view, here.x - before.x, here.y - before.y, tilts(event.pointerType));
262
+ if (Math.abs(here.x - before.x) + Math.abs(here.y - before.y) > 0)
263
+ moved = true;
264
+ }
265
+ shown = view;
266
+ paint(shown);
267
+ };
268
+ const onUp = (event) => {
269
+ if (!down.delete(event.pointerId))
270
+ return;
271
+ if (down.size < 2)
272
+ pinch = undefined;
273
+ if (down.size === 0) {
274
+ root.removeAttribute("data-dragging");
275
+ label();
276
+ if (moved) {
277
+ say(angles(), 450);
278
+ changed();
279
+ }
280
+ }
281
+ };
282
+ stage.addEventListener("pointerdown", onDown);
283
+ stage.addEventListener("pointermove", onMove);
284
+ stage.addEventListener("pointerup", onUp);
285
+ stage.addEventListener("pointercancel", onUp);
286
+ stage.addEventListener("dblclick", toOpening);
287
+ // ---- The wheel: Ctrl and the wheel (a pinch on a trackpad) zoom; with the view focused the wheel tips and turns it; otherwise the page scrolls as ever. ----
288
+ const onWheel = (event) => {
289
+ if (event.ctrlKey) {
290
+ event.preventDefault();
291
+ grab();
292
+ view = clampView({ ...view, zoom: view.zoom * Math.exp(-event.deltaY * 0.01) });
293
+ }
294
+ else if (doc.activeElement === stage) {
295
+ event.preventDefault();
296
+ grab();
297
+ view = event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY) ? clampView({ ...view, yaw: view.yaw + (event.deltaX || event.deltaY) * 0.25 }) : clampView({ ...view, pitch: view.pitch + event.deltaY * 0.15 });
298
+ }
299
+ else
300
+ return;
301
+ shown = view;
302
+ paint(shown);
303
+ label();
304
+ };
305
+ stage.addEventListener("wheel", onWheel, { passive: false });
306
+ // ---- The size of the box. ----
307
+ let observer;
308
+ if (typeof ResizeObserver === "function") {
309
+ observer = new ResizeObserver(() => alive && paint(shown));
310
+ observer.observe(stage);
311
+ }
312
+ return {
313
+ mode: "3d",
314
+ view: () => ({ ...view }),
315
+ set(next, glide = true) {
316
+ go({ ...view, ...next }, glide ? 560 : 0, "angle");
317
+ },
318
+ preset: toPreset,
319
+ reset: toOpening,
320
+ update(next) {
321
+ if (!alive)
322
+ return;
323
+ model = buildTurn(next, { ...settings, preset: undefined, view });
324
+ view = clampView(view);
325
+ opening = openingView(settings, sport());
326
+ const layout = layoutOf(model, shown, stage.clientWidth || 960, stage.clientHeight || 600, settings.lens);
327
+ root.querySelector(".ky-turn-world").innerHTML = worldMarkup(model, layout);
328
+ gather();
329
+ stage.setAttribute("aria-label", model.title);
330
+ root.querySelector(".ky-turn-help").textContent = `${turnWords(model).keys}${model.standing === "" ? "" : ` ${model.standing}`}`;
331
+ const theme = model.options.theme;
332
+ if (theme === "light" || theme === "dark")
333
+ root.dataset.theme = theme;
334
+ else
335
+ root.removeAttribute("data-theme");
336
+ const controls = root.querySelector(".ky-turn-controls");
337
+ if (controls !== null)
338
+ controls.outerHTML = controlsMarkup(model, view);
339
+ paint(shown);
340
+ label();
341
+ },
342
+ svg: () => (alive ? root.querySelector(".ky-turn-floor svg.kyogijo") : null),
343
+ destroy() {
344
+ if (!alive)
345
+ return;
346
+ alive = false;
347
+ stop();
348
+ clearTimeout(spoken);
349
+ observer?.disconnect();
350
+ host.replaceChildren();
351
+ },
352
+ };
353
+ }
@@ -0,0 +1,86 @@
1
+ /** A point or a direction: x, y, z in feet. */
2
+ export type V3 = readonly [number, number, number];
3
+ /** What a plane is a part of: it is the class of its element (`ky-up-<kind>`) and what a test finds it by. */
4
+ export type PartKind = "post" | "net" | "tape" | "antenna" | "board" | "glass" | "rim" | "stanchion" | "frame" | "goal" | "flag" | "stump" | "leg" | "table" | "pylon" | "wall" | "screen" | "pole" | "ground" | "target";
5
+ /** One flat rectangle. Its own x runs along `u` (its whole length) and its own y along `v`; `o` is the corner both start from. `svg` is what is drawn on it, in feet along `u` and `v`. */
6
+ export interface Plane {
7
+ /** A name that says what it is and where: `net`, `post-left`, `goal-right-back`. */
8
+ id: string;
9
+ kind: PartKind;
10
+ o: V3;
11
+ u: V3;
12
+ v: V3;
13
+ svg: string;
14
+ /**
15
+ * A plane that turns to face the eye, about the vertical through its middle: a scoreboard is read from wherever the view is. `u` and `v` give its size and its
16
+ * place as it lies at a yaw of 0; `v` must be vertical.
17
+ */
18
+ billboard?: boolean;
19
+ /**
20
+ * A wall round the surface that gives way when it is between the eye and what it surrounds: the horizontal direction (x, y, a unit vector) that points from the surface out through the
21
+ * wall. The nearer the eye is to that side, and the lower it is, the more the wall is cut away (see `fadeOf`), so that the court is never hidden behind its own glass.
22
+ */
23
+ shy?: readonly [number, number];
24
+ }
25
+ /** What stands on the surface as a thing a reader would name: a net, a basket, a goal, a pair of posts. */
26
+ export type UprightKind = "scoreboard" | "net" | "antennae" | "goalposts" | "netposts" | "hoop" | "goal" | "boards" | "stumps" | "pylons" | "flags" | "fence" | "backstop" | "foul-poles" | "table-legs" | "walls" | "starting-blocks" | "backstroke-flags" | "false-start-rope" | "springboards" | "platforms" | "judges-stand" | "hurdles" | "steeplechase" | "high-jump" | "pole-vault" | "cage" | "padding" | "rink-boards" | "mound" | "azuchi" | "targets";
27
+ /** A thing that stands on the surface: where, how high, and the planes it is made of. */
28
+ export interface Upright {
29
+ id: string;
30
+ kind: UprightKind;
31
+ /** Where it stands on the surface, in feet. */
32
+ at: readonly [number, number];
33
+ /** The height of its top above the surface, in feet. */
34
+ height: number;
35
+ /** A second height worth saying (a net's height at the centre, a post's crossbar), in feet. */
36
+ low?: number;
37
+ planes: Plane[];
38
+ }
39
+ /** The four numbers a plane's SVG needs: its length along `u` and along `v`, in feet. */
40
+ export declare const size: (p: Pick<Plane, "u" | "v">) => [number, number];
41
+ export declare const add: (a: V3, b: V3) => V3;
42
+ export declare const sub: (a: V3, b: V3) => V3;
43
+ export declare const times: (a: V3, k: number) => V3;
44
+ export declare const length: (a: V3) => number;
45
+ export declare const unit: (a: V3) => V3;
46
+ export declare const cross: (a: V3, b: V3) => V3;
47
+ /** A number for markup: millimetres of a foot, no more. */
48
+ export declare const n: (value: number) => string;
49
+ /** A billboard turned to face an eye whose view is turned `yaw` degrees: its width runs along the screen's x, which in the scene is (cos yaw, -sin yaw, 0). */
50
+ export declare function facing(p: Plane, yaw: number): Plane;
51
+ /** A plane. */
52
+ export declare const plane: (id: string, kind: PartKind, o: V3, u: V3, v: V3, svg: string) => Plane;
53
+ /**
54
+ * A rectangle standing upright over a line on the surface, from (x0, y0) to (x1, y1), between the heights z0 and z1. Its own y runs down from z1, so
55
+ * that what is drawn on it hangs from the top as a net does.
56
+ */
57
+ export declare function wall(id: string, kind: PartKind, x0: number, y0: number, x1: number, y1: number, z0: number, z1: number, svg: string): Plane;
58
+ /** A rectangle lying flat at the height `z`, from (x0, y0) to (x1, y1). */
59
+ export declare function flat(id: string, kind: PartKind, x0: number, y0: number, x1: number, y1: number, z: number, svg: string): Plane;
60
+ /**
61
+ * A strip of width `w` along the line from `a` to `b`: a post, a crossbar, an arm. A single strip vanishes when the eye is level with it, so it is two,
62
+ * crossed, one the width of the other's thickness. `paint` draws on each (its length along the line and its width), a plain strip in `cls` if it is missing.
63
+ */
64
+ export declare function bar(id: string, kind: PartKind, a: V3, b: V3, w: number, cls: string, paint?: (long: number, wide: number) => string): Plane[];
65
+ /** An upright strip from the surface up to the height `top`. */
66
+ export declare const pole: (id: string, kind: PartKind, x: number, y: number, z0: number, z1: number, w: number, cls: string, paint?: (long: number, wide: number) => string) => Plane[];
67
+ /** The lines of a net's mesh over a rectangle w by h feet, one every `cell` feet each way: a path. */
68
+ export declare function mesh(w: number, h: number, cell: number): string;
69
+ /** The mesh of a net as a path element: threads a tenth of a cell thick. */
70
+ export declare const threads: (d: string, cell: number) => string;
71
+ /** A closed outline of points, as a path. */
72
+ export declare const polygon: (points: readonly (readonly [number, number])[]) => string;
73
+ /** A point `t` of the way from `a` to `b`. */
74
+ export declare const lerp: (a: number, b: number, t: number) => number;
75
+ /**
76
+ * The least thickness, in feet, of anything drawn thin on a surface this big: a post of 3 inches on a 500-foot ground would be a hair, so every strip and every
77
+ * line is at least a thousandth of the surface's diagonal.
78
+ */
79
+ export declare const thinnest: (w: number, h: number) => number;
80
+ /** A wall plane marked to give way: its outward direction is the way the wall faces away from `centre` (x, y in feet), which is where the surface's middle is. */
81
+ export declare function shy(p: Plane, centre: readonly [number, number]): Plane;
82
+ /**
83
+ * How much of a wall is left (1 whole, down to 0.08) at a view: none is cut away unless the wall is on the eye's side of the surface, and then more the lower the eye is, because a wall seen
84
+ * from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
85
+ */
86
+ export declare function fadeOf(p: Pick<Plane, "shy">, yaw: number, pitch: number): number;
@@ -0,0 +1,110 @@
1
+ // What the 3D view is built from: planes. A plane is a flat rectangle standing anywhere in space (a net, a backboard, a pane of glass, the two crossed
2
+ // strips that make a post), with a little SVG drawn on it. The floor is the drawing itself, flat at the height of the surface; everything that stands on it is a
3
+ // few planes, each placed by three vectors, so that the whole scene is a handful of elements and no model. Nothing here touches a page.
4
+ //
5
+ // The frame is the drawing's own: x along the surface and y across it, both in feet, as the SVG has them (y grows downwards on the page), and z the height
6
+ // above the surface, in feet, growing towards the eye of someone looking straight down.
7
+ /** The four numbers a plane's SVG needs: its length along `u` and along `v`, in feet. */
8
+ export const size = (p) => [Math.hypot(...p.u), Math.hypot(...p.v)];
9
+ export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
10
+ export const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
11
+ export const times = (a, k) => [a[0] * k, a[1] * k, a[2] * k];
12
+ export const length = (a) => Math.hypot(a[0], a[1], a[2]);
13
+ export const unit = (a) => {
14
+ const n = length(a);
15
+ return n === 0 ? [0, 0, 0] : [a[0] / n, a[1] / n, a[2] / n];
16
+ };
17
+ export const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
18
+ /** A number for markup: millimetres of a foot, no more. */
19
+ export const n = (value) => String(Math.round(value * 1000) / 1000);
20
+ /** A billboard turned to face an eye whose view is turned `yaw` degrees: its width runs along the screen's x, which in the scene is (cos yaw, -sin yaw, 0). */
21
+ export function facing(p, yaw) {
22
+ if (p.billboard !== true)
23
+ return p;
24
+ const [w, h] = size(p);
25
+ const a = (yaw * Math.PI) / 180;
26
+ const u = [w * Math.cos(a), -w * Math.sin(a), 0];
27
+ const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2, p.o[2] + (p.u[2] + p.v[2]) / 2];
28
+ const v = [0, 0, p.v[2] < 0 ? -h : h];
29
+ return { ...p, o: [middle[0] - (u[0] + v[0]) / 2, middle[1] - (u[1] + v[1]) / 2, middle[2] - (u[2] + v[2]) / 2], u, v };
30
+ }
31
+ /** A plane. */
32
+ export const plane = (id, kind, o, u, v, svg) => ({ id, kind, o, u, v, svg });
33
+ /**
34
+ * A rectangle standing upright over a line on the surface, from (x0, y0) to (x1, y1), between the heights z0 and z1. Its own y runs down from z1, so
35
+ * that what is drawn on it hangs from the top as a net does.
36
+ */
37
+ export function wall(id, kind, x0, y0, x1, y1, z0, z1, svg) {
38
+ return plane(id, kind, [x0, y0, z1], [x1 - x0, y1 - y0, 0], [0, 0, z0 - z1], svg);
39
+ }
40
+ /** A rectangle lying flat at the height `z`, from (x0, y0) to (x1, y1). */
41
+ export function flat(id, kind, x0, y0, x1, y1, z, svg) {
42
+ return plane(id, kind, [x0, y0, z], [x1 - x0, 0, 0], [0, y1 - y0, 0], svg);
43
+ }
44
+ /**
45
+ * A strip of width `w` along the line from `a` to `b`: a post, a crossbar, an arm. A single strip vanishes when the eye is level with it, so it is two,
46
+ * crossed, one the width of the other's thickness. `paint` draws on each (its length along the line and its width), a plain strip in `cls` if it is missing.
47
+ */
48
+ export function bar(id, kind, a, b, w, cls, paint) {
49
+ const d = sub(b, a);
50
+ const long = length(d);
51
+ if (long === 0)
52
+ return [];
53
+ const along = unit(d);
54
+ // A line that is nearly upright is crossed with the x axis; any other with the upright, so that no strip lies edge-on to a cross strip.
55
+ const first = Math.abs(along[2]) > 0.98 ? [1, 0, 0] : unit(cross(along, [0, 0, 1]));
56
+ const second = unit(cross(along, first));
57
+ const drawn = paint === undefined ? `<rect width="${n(long)}" height="${n(w)}" class="${cls}"/>` : paint(long, w);
58
+ return [first, second].map((side, i) => plane(`${id}${i === 0 ? "" : "-x"}`, kind, sub(a, times(side, w / 2)), d, times(side, w), drawn));
59
+ }
60
+ /** An upright strip from the surface up to the height `top`. */
61
+ export const pole = (id, kind, x, y, z0, z1, w, cls, paint) => bar(id, kind, [x, y, z0], [x, y, z1], w, cls, paint);
62
+ /** The lines of a net's mesh over a rectangle w by h feet, one every `cell` feet each way: a path. */
63
+ export function mesh(w, h, cell) {
64
+ const across = Math.max(1, Math.round(w / cell));
65
+ const down = Math.max(1, Math.round(h / cell));
66
+ let d = "";
67
+ for (let i = 1; i < across; i += 1)
68
+ d += `M${n((w * i) / across)} 0V${n(h)}`;
69
+ for (let j = 1; j < down; j += 1)
70
+ d += `M0 ${n((h * j) / down)}H${n(w)}`;
71
+ return d;
72
+ }
73
+ /** The mesh of a net as a path element: threads a tenth of a cell thick. */
74
+ export const threads = (d, cell) => `<path d="${d}" class="u-mesh" fill="none" stroke-width="${n(Math.max(cell * 0.09, 0.02))}"/>`;
75
+ /** A closed outline of points, as a path. */
76
+ export const polygon = (points) => `M${points.map(([x, y]) => `${n(x)} ${n(y)}`).join("L")}Z`;
77
+ /** A point `t` of the way from `a` to `b`. */
78
+ export const lerp = (a, b, t) => a + (b - a) * t;
79
+ /**
80
+ * The least thickness, in feet, of anything drawn thin on a surface this big: a post of 3 inches on a 500-foot ground would be a hair, so every strip and every
81
+ * line is at least a thousandth of the surface's diagonal.
82
+ */
83
+ export const thinnest = (w, h) => Math.hypot(w, h) * 0.0012;
84
+ /** A wall plane marked to give way: its outward direction is the way the wall faces away from `centre` (x, y in feet), which is where the surface's middle is. */
85
+ export function shy(p, centre) {
86
+ // The plane stands over a line on the surface: the horizontal part of `u` (or of `v` for one lying down) is that line, and its normal in the surface is the way out or the way in.
87
+ const line = Math.hypot(p.u[0], p.u[1]) >= Math.hypot(p.v[0], p.v[1]) ? [p.u[0], p.u[1]] : [p.v[0], p.v[1]];
88
+ const length = Math.hypot(line[0], line[1]);
89
+ if (length === 0)
90
+ return p;
91
+ let normal = [-line[1] / length, line[0] / length];
92
+ const middle = [p.o[0] + (p.u[0] + p.v[0]) / 2, p.o[1] + (p.u[1] + p.v[1]) / 2];
93
+ if (normal[0] * (middle[0] - centre[0]) + normal[1] * (middle[1] - centre[1]) < 0)
94
+ normal = [-normal[0], -normal[1]];
95
+ return { ...p, shy: normal };
96
+ }
97
+ /**
98
+ * How much of a wall is left (1 whole, down to 0.08) at a view: none is cut away unless the wall is on the eye's side of the surface, and then more the lower the eye is, because a wall seen
99
+ * from straight above hides nothing. `yaw` and `pitch` are the view's, in degrees.
100
+ */
101
+ export function fadeOf(p, yaw, pitch) {
102
+ if (p.shy === undefined)
103
+ return 1;
104
+ const a = (yaw * Math.PI) / 180;
105
+ // The horizontal direction from the surface to the eye (the same sum as the depth of a point).
106
+ const toward = p.shy[0] * Math.sin(a) + p.shy[1] * Math.cos(a);
107
+ const hides = toward * Math.sin((pitch * Math.PI) / 180);
108
+ const t = Math.min(1, Math.max(0, (hides - 0.1) / 0.25));
109
+ return Math.round((1 - 0.92 * t) * 1000) / 1000;
110
+ }
@@ -0,0 +1,48 @@
1
+ import { type Placement, type TurnView } from "./turn.camera.ts";
2
+ import { type Plane } from "./turn.kit.ts";
3
+ import { type TurnModel, type TurnSettings } from "./turn.model.ts";
4
+ import type { ScoreOptions, SurfaceOptions } from "./types.ts";
5
+ /**
6
+ * The matrix that stands a plane in the scene, before the camera: its x along `u`, its y along `v`, its corner `o`, in pixels from the middle of the scene
7
+ * (`centre`, in feet).
8
+ */
9
+ export declare function planeMatrix(p: Plane, scale: number, centre?: readonly [number, number]): number[];
10
+ /** Where everything is for a view in a container: the placement, the matrix of each element and its place in the stack. */
11
+ export interface Layout {
12
+ at: Placement;
13
+ /** The floor, then the planes in the model's order. */
14
+ transforms: string[];
15
+ /** The stack of each: 0 for the ground of a hall, 1 for the floor, then the planes far to near from 2. */
16
+ stack: number[];
17
+ /** How much of each is left: 1 for all but the walls that are between the eye and the surface, which are cut away as the view is low and on their side (`fadeOf`). */
18
+ fade: number[];
19
+ }
20
+ /** Work a view out in a container `width` by `height` pixels. */
21
+ export declare function layoutOf(model: TurnModel, view: TurnView, width: number, height: number, lens?: number): Layout;
22
+ /** One plane as an element: its box in pixels and the SVG drawn on it in feet. */
23
+ export declare function planeMarkup(p: Plane, scale: number, transform: string, stack: number, fade?: number): string;
24
+ /** The floor as an element: the flat drawing, in the pixels of the scene. */
25
+ export declare function floorMarkup(model: TurnModel, transform: string, stack: number): string;
26
+ /** The floor and the planes of a model, laid out. */
27
+ export declare function worldMarkup(model: TurnModel, layout: Layout): string;
28
+ /** The buttons under the view. */
29
+ export declare function controlsMarkup(model: TurnModel, view: TurnView): string;
30
+ /** The size, in pixels, the first view is worked out for. */
31
+ export declare const sizeOf: (settings: TurnSettings) => {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ /** The 3D view of a model as markup, laid out for `settings.size`; a page's script then makes it turn. */
36
+ export declare function turnMarkupOf(model: TurnModel, settings?: TurnSettings): string;
37
+ /**
38
+ * A surface as a 3D view, as markup: the flat drawing for a floor, what stands on it as planes, and the controls under it. Pure, so it can be made on a
39
+ * server; it is a still picture until `mountKyogijoTurn` (or the element) makes it turn in a page.
40
+ * @example
41
+ * ```ts
42
+ * import { turnMarkup } from "@johnmorrisdotca/kyogijo-stadiums/turn";
43
+ * const html = turnMarkup({ sport: "volleyball" }, { preset: "end" });
44
+ * console.log(html.includes("ky-turn"), html.includes('data-part="net"'));
45
+ * // true true
46
+ * ```
47
+ */
48
+ export declare function turnMarkup(options: SurfaceOptions | ScoreOptions, settings?: TurnSettings): string;