@johnmorrisdotca/dougu-sports 0.0.1 → 0.2.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 (338) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/LICENSES/README.md +4 -3
  3. package/NOTICE.md +54 -14
  4. package/README.md +251 -80
  5. package/dist/emblems/award/badge.d.ts +2 -0
  6. package/dist/emblems/award/badge.js +14 -0
  7. package/dist/emblems/award/crown.d.ts +2 -0
  8. package/dist/emblems/award/crown.js +15 -0
  9. package/dist/emblems/award/medal.d.ts +1 -1
  10. package/dist/emblems/award/medal.js +10 -15
  11. package/dist/emblems/award/shield.d.ts +2 -0
  12. package/dist/emblems/award/shield.js +11 -0
  13. package/dist/emblems/award/star.d.ts +2 -0
  14. package/dist/emblems/award/star.js +11 -0
  15. package/dist/emblems/ball/ball.afl.d.ts +1 -0
  16. package/dist/emblems/ball/ball.afl.js +24 -0
  17. package/dist/emblems/ball/ball.baseball.d.ts +1 -0
  18. package/dist/emblems/ball/ball.baseball.js +14 -0
  19. package/dist/emblems/ball/ball.basketball.d.ts +7 -0
  20. package/dist/emblems/ball/ball.basketball.js +32 -0
  21. package/dist/emblems/ball/ball.basketball3x3.d.ts +1 -0
  22. package/dist/emblems/ball/ball.basketball3x3.js +10 -0
  23. package/dist/emblems/ball/ball.basketball6.d.ts +1 -0
  24. package/dist/emblems/ball/ball.basketball6.js +10 -0
  25. package/dist/emblems/ball/ball.core.d.ts +111 -0
  26. package/dist/emblems/ball/ball.core.js +79 -0
  27. package/dist/emblems/ball/ball.cricket.d.ts +5 -0
  28. package/dist/emblems/ball/ball.cricket.js +31 -0
  29. package/dist/emblems/ball/ball.curlingstone.d.ts +3 -0
  30. package/dist/emblems/ball/ball.curlingstone.js +46 -0
  31. package/dist/emblems/ball/ball.discus.d.ts +1 -0
  32. package/dist/emblems/ball/ball.discus.js +24 -0
  33. package/dist/emblems/ball/ball.engine.d.ts +114 -0
  34. package/dist/emblems/ball/ball.engine.js +342 -0
  35. package/dist/emblems/ball/ball.family.dotted.d.ts +27 -0
  36. package/dist/emblems/ball/ball.family.dotted.js +20 -0
  37. package/dist/emblems/ball/ball.family.panelled.d.ts +7 -0
  38. package/dist/emblems/ball/ball.family.panelled.js +60 -0
  39. package/dist/emblems/ball/ball.family.prolate.d.ts +16 -0
  40. package/dist/emblems/ball/ball.family.prolate.js +29 -0
  41. package/dist/emblems/ball/ball.family.seamed.d.ts +31 -0
  42. package/dist/emblems/ball/ball.family.seamed.js +26 -0
  43. package/dist/emblems/ball/ball.fieldhockey.d.ts +1 -0
  44. package/dist/emblems/ball/ball.fieldhockey.js +15 -0
  45. package/dist/emblems/ball/ball.football.d.ts +3 -0
  46. package/dist/emblems/ball/ball.football.js +51 -0
  47. package/dist/emblems/ball/ball.gaelic.d.ts +2 -0
  48. package/dist/emblems/ball/ball.gaelic.js +30 -0
  49. package/dist/emblems/ball/ball.golf.d.ts +1 -0
  50. package/dist/emblems/ball/ball.golf.js +15 -0
  51. package/dist/emblems/ball/ball.hammer.d.ts +1 -0
  52. package/dist/emblems/ball/ball.hammer.js +31 -0
  53. package/dist/emblems/ball/ball.handball.d.ts +1 -0
  54. package/dist/emblems/ball/ball.handball.js +25 -0
  55. package/dist/emblems/ball/ball.index.d.ts +48 -0
  56. package/dist/emblems/ball/ball.index.js +103 -0
  57. package/dist/emblems/ball/ball.math.d.ts +38 -0
  58. package/dist/emblems/ball/ball.math.js +105 -0
  59. package/dist/emblems/ball/ball.object.d.ts +38 -0
  60. package/dist/emblems/ball/ball.object.js +20 -0
  61. package/dist/emblems/ball/ball.padel.d.ts +1 -0
  62. package/dist/emblems/ball/ball.padel.js +13 -0
  63. package/dist/emblems/ball/ball.pickleball.d.ts +2 -0
  64. package/dist/emblems/ball/ball.pickleball.js +26 -0
  65. package/dist/emblems/ball/ball.polyhedra.d.ts +15 -0
  66. package/dist/emblems/ball/ball.polyhedra.js +63 -0
  67. package/dist/emblems/ball/ball.puck.d.ts +3 -0
  68. package/dist/emblems/ball/ball.puck.js +22 -0
  69. package/dist/emblems/ball/ball.roundnet.d.ts +1 -0
  70. package/dist/emblems/ball/ball.roundnet.js +15 -0
  71. package/dist/emblems/ball/ball.rugby.d.ts +1 -0
  72. package/dist/emblems/ball/ball.rugby.js +35 -0
  73. package/dist/emblems/ball/ball.sepaktakraw.d.ts +1 -0
  74. package/dist/emblems/ball/ball.sepaktakraw.js +48 -0
  75. package/dist/emblems/ball/ball.shuttlecock.d.ts +6 -0
  76. package/dist/emblems/ball/ball.shuttlecock.js +93 -0
  77. package/dist/emblems/ball/ball.sliotar.d.ts +1 -0
  78. package/dist/emblems/ball/ball.sliotar.js +14 -0
  79. package/dist/emblems/ball/ball.smooth.d.ts +4 -0
  80. package/dist/emblems/ball/ball.smooth.js +51 -0
  81. package/dist/emblems/ball/ball.soccer.d.ts +2 -0
  82. package/dist/emblems/ball/ball.soccer.js +54 -0
  83. package/dist/emblems/ball/ball.softball.d.ts +1 -0
  84. package/dist/emblems/ball/ball.softball.js +14 -0
  85. package/dist/emblems/ball/ball.squash.d.ts +7 -0
  86. package/dist/emblems/ball/ball.squash.js +27 -0
  87. package/dist/emblems/ball/ball.stack.d.ts +48 -0
  88. package/dist/emblems/ball/ball.stack.js +127 -0
  89. package/dist/emblems/ball/ball.stitch.d.ts +10 -0
  90. package/dist/emblems/ball/ball.stitch.js +59 -0
  91. package/dist/emblems/ball/ball.tennis.d.ts +1 -0
  92. package/dist/emblems/ball/ball.tennis.js +13 -0
  93. package/dist/emblems/ball/ball.teqball.d.ts +1 -0
  94. package/dist/emblems/ball/ball.teqball.js +27 -0
  95. package/dist/emblems/ball/ball.volleyball.d.ts +3 -0
  96. package/dist/emblems/ball/ball.volleyball.js +56 -0
  97. package/dist/emblems/ball/ball.waterpolo.d.ts +1 -0
  98. package/dist/emblems/ball/ball.waterpolo.js +31 -0
  99. package/dist/emblems/crest/diamond.d.ts +2 -0
  100. package/dist/emblems/crest/diamond.js +18 -0
  101. package/dist/emblems/crest/pennant.d.ts +2 -0
  102. package/dist/emblems/crest/pennant.js +18 -0
  103. package/dist/emblems/crest/roundel.d.ts +2 -0
  104. package/dist/emblems/crest/roundel.js +18 -0
  105. package/dist/emblems/crest/shield.d.ts +2 -0
  106. package/dist/emblems/crest/shield.js +17 -0
  107. package/dist/emblems/emblem.api.d.ts +77 -9
  108. package/dist/emblems/emblem.api.js +56 -10
  109. package/dist/emblems/emblem.compose.d.ts +31 -0
  110. package/dist/emblems/emblem.compose.js +91 -0
  111. package/dist/emblems/emblem.core.d.ts +89 -7
  112. package/dist/emblems/emblem.core.js +85 -11
  113. package/dist/emblems/emblem.descriptions.d.ts +7 -0
  114. package/dist/emblems/emblem.descriptions.js +60 -0
  115. package/dist/emblems/emblem.display.d.ts +75 -0
  116. package/dist/emblems/emblem.display.js +214 -0
  117. package/dist/emblems/emblem.engrave.d.ts +50 -0
  118. package/dist/emblems/emblem.engrave.js +145 -0
  119. package/dist/emblems/emblem.equipment.d.ts +24 -0
  120. package/dist/emblems/emblem.equipment.js +57 -0
  121. package/dist/emblems/emblem.flip.d.ts +10 -0
  122. package/dist/emblems/emblem.flip.js +45 -0
  123. package/dist/emblems/emblem.ids.d.ts +44 -30
  124. package/dist/emblems/emblem.ids.js +51 -17
  125. package/dist/emblems/emblem.lathe.d.ts +62 -0
  126. package/dist/emblems/emblem.lathe.js +426 -0
  127. package/dist/emblems/emblem.medal.d.ts +6 -0
  128. package/dist/emblems/emblem.medal.js +38 -0
  129. package/dist/emblems/emblem.pennant.d.ts +3 -0
  130. package/dist/emblems/emblem.pennant.js +79 -0
  131. package/dist/emblems/emblem.registry.js +162 -4
  132. package/dist/emblems/emblem.shapes.d.ts +48 -12
  133. package/dist/emblems/emblem.shapes.js +138 -31
  134. package/dist/emblems/emblem.sprite.d.ts +38 -0
  135. package/dist/emblems/emblem.sprite.js +65 -0
  136. package/dist/emblems/emblem.uri.d.ts +22 -0
  137. package/dist/emblems/emblem.uri.js +29 -0
  138. package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -0
  139. package/dist/emblems/gear/gear.badmintonracket.js +19 -0
  140. package/dist/emblems/gear/gear.baseballglove.d.ts +1 -0
  141. package/dist/emblems/gear/gear.baseballglove.js +21 -0
  142. package/dist/emblems/gear/gear.budo.d.ts +17 -0
  143. package/dist/emblems/gear/gear.budo.js +60 -0
  144. package/dist/emblems/gear/gear.core.d.ts +64 -0
  145. package/dist/emblems/gear/gear.core.js +127 -0
  146. package/dist/emblems/gear/gear.cricketbat.d.ts +1 -0
  147. package/dist/emblems/gear/gear.cricketbat.js +17 -0
  148. package/dist/emblems/gear/gear.field.d.ts +6 -0
  149. package/dist/emblems/gear/gear.field.js +81 -0
  150. package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -0
  151. package/dist/emblems/gear/gear.fieldhockeystick.js +16 -0
  152. package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -0
  153. package/dist/emblems/gear/gear.footballhelmet.js +16 -0
  154. package/dist/emblems/gear/gear.goaliemask.d.ts +1 -0
  155. package/dist/emblems/gear/gear.goaliemask.js +23 -0
  156. package/dist/emblems/gear/gear.goalnet.d.ts +1 -0
  157. package/dist/emblems/gear/gear.goalnet.js +25 -0
  158. package/dist/emblems/gear/gear.hockeystick.d.ts +1 -0
  159. package/dist/emblems/gear/gear.hockeystick.js +15 -0
  160. package/dist/emblems/gear/gear.index.d.ts +22 -0
  161. package/dist/emblems/gear/gear.index.js +49 -0
  162. package/dist/emblems/gear/gear.kendo.d.ts +2 -0
  163. package/dist/emblems/gear/gear.kendo.js +41 -0
  164. package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
  165. package/dist/emblems/gear/gear.kyudo.js +31 -0
  166. package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -0
  167. package/dist/emblems/gear/gear.lacrossestick.js +26 -0
  168. package/dist/emblems/gear/gear.rackets.d.ts +4 -0
  169. package/dist/emblems/gear/gear.rackets.js +60 -0
  170. package/dist/emblems/gear/gear.skates.d.ts +3 -0
  171. package/dist/emblems/gear/gear.skates.js +39 -0
  172. package/dist/emblems/gear/gear.sticks.d.ts +3 -0
  173. package/dist/emblems/gear/gear.sticks.js +34 -0
  174. package/dist/emblems/gear/gear.sumo.d.ts +2 -0
  175. package/dist/emblems/gear/gear.sumo.js +43 -0
  176. package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -0
  177. package/dist/emblems/gear/gear.tabletennispaddle.js +16 -0
  178. package/dist/emblems/gear/gear.tennisracket.d.ts +1 -0
  179. package/dist/emblems/gear/gear.tennisracket.js +17 -0
  180. package/dist/emblems/gear/gear.track.d.ts +4 -0
  181. package/dist/emblems/gear/gear.track.js +45 -0
  182. package/dist/emblems/gear/gear.water.d.ts +2 -0
  183. package/dist/emblems/gear/gear.water.js +25 -0
  184. package/dist/emblems/medal/bronze.d.ts +2 -0
  185. package/dist/emblems/medal/bronze.js +4 -0
  186. package/dist/emblems/medal/gold.d.ts +2 -0
  187. package/dist/emblems/medal/gold.js +4 -0
  188. package/dist/emblems/medal/place-1.d.ts +2 -0
  189. package/dist/emblems/medal/place-1.js +4 -0
  190. package/dist/emblems/medal/place-2.d.ts +2 -0
  191. package/dist/emblems/medal/place-2.js +4 -0
  192. package/dist/emblems/medal/place-3.d.ts +2 -0
  193. package/dist/emblems/medal/place-3.js +4 -0
  194. package/dist/emblems/medal/silver.d.ts +2 -0
  195. package/dist/emblems/medal/silver.js +4 -0
  196. package/dist/emblems/pennant/championship.d.ts +2 -0
  197. package/dist/emblems/pennant/championship.js +4 -0
  198. package/dist/emblems/sport/afl.d.ts +2 -0
  199. package/dist/emblems/sport/afl.js +12 -0
  200. package/dist/emblems/sport/artisticswimming.d.ts +2 -0
  201. package/dist/emblems/sport/artisticswimming.js +16 -0
  202. package/dist/emblems/sport/athletics.d.ts +2 -0
  203. package/dist/emblems/sport/athletics.js +12 -0
  204. package/dist/emblems/sport/badminton.d.ts +2 -0
  205. package/dist/emblems/sport/badminton.js +13 -0
  206. package/dist/emblems/sport/cfl.d.ts +2 -0
  207. package/dist/emblems/sport/cfl.js +5 -0
  208. package/dist/emblems/sport/cricket.d.ts +2 -0
  209. package/dist/emblems/sport/cricket.js +14 -0
  210. package/dist/emblems/sport/curling.d.ts +2 -0
  211. package/dist/emblems/sport/curling.js +14 -0
  212. package/dist/emblems/sport/diving.d.ts +2 -0
  213. package/dist/emblems/sport/diving.js +18 -0
  214. package/dist/emblems/sport/fieldhockey.d.ts +2 -0
  215. package/dist/emblems/sport/fieldhockey.js +11 -0
  216. package/dist/emblems/sport/gaelic.d.ts +2 -0
  217. package/dist/emblems/sport/gaelic.js +11 -0
  218. package/dist/emblems/sport/handball.d.ts +2 -0
  219. package/dist/emblems/sport/handball.js +15 -0
  220. package/dist/emblems/sport/ifaf.d.ts +2 -0
  221. package/dist/emblems/sport/ifaf.js +5 -0
  222. package/dist/emblems/sport/judo.d.ts +2 -0
  223. package/dist/emblems/sport/judo.js +13 -0
  224. package/dist/emblems/sport/karate.d.ts +2 -0
  225. package/dist/emblems/sport/karate.js +13 -0
  226. package/dist/emblems/sport/kendo.d.ts +2 -0
  227. package/dist/emblems/sport/kendo.js +14 -0
  228. package/dist/emblems/sport/kyudo.d.ts +2 -0
  229. package/dist/emblems/sport/kyudo.js +15 -0
  230. package/dist/emblems/sport/lacrosse.d.ts +2 -0
  231. package/dist/emblems/sport/lacrosse.js +13 -0
  232. package/dist/emblems/sport/mlb.d.ts +2 -0
  233. package/dist/emblems/sport/mlb.js +28 -0
  234. package/dist/emblems/sport/nba.d.ts +2 -0
  235. package/dist/emblems/sport/nba.js +12 -0
  236. package/dist/emblems/sport/netball.d.ts +2 -0
  237. package/dist/emblems/sport/netball.js +14 -0
  238. package/dist/emblems/sport/nfl.d.ts +3 -0
  239. package/dist/emblems/sport/nfl.js +15 -0
  240. package/dist/emblems/sport/nhl.d.ts +2 -0
  241. package/dist/emblems/sport/nhl.js +13 -0
  242. package/dist/emblems/sport/padel.d.ts +2 -0
  243. package/dist/emblems/sport/padel.js +14 -0
  244. package/dist/emblems/sport/pickleball.d.ts +2 -0
  245. package/dist/emblems/sport/pickleball.js +13 -0
  246. package/dist/emblems/sport/racquetball.d.ts +2 -0
  247. package/dist/emblems/sport/racquetball.js +13 -0
  248. package/dist/emblems/sport/roundnet.d.ts +2 -0
  249. package/dist/emblems/sport/roundnet.js +15 -0
  250. package/dist/emblems/sport/rugby.d.ts +2 -0
  251. package/dist/emblems/sport/rugby.js +12 -0
  252. package/dist/emblems/sport/sepaktakraw.d.ts +2 -0
  253. package/dist/emblems/sport/sepaktakraw.js +11 -0
  254. package/dist/emblems/sport/skating.d.ts +2 -0
  255. package/dist/emblems/sport/skating.js +14 -0
  256. package/dist/emblems/sport/soccer.d.ts +2 -0
  257. package/dist/emblems/sport/soccer.js +31 -0
  258. package/dist/emblems/sport/squash.d.ts +2 -0
  259. package/dist/emblems/sport/squash.js +15 -0
  260. package/dist/emblems/sport/sumo.d.ts +2 -0
  261. package/dist/emblems/sport/sumo.js +15 -0
  262. package/dist/emblems/sport/swimming.d.ts +2 -0
  263. package/dist/emblems/sport/swimming.js +15 -0
  264. package/dist/emblems/sport/tabletennis.d.ts +2 -0
  265. package/dist/emblems/sport/tabletennis.js +12 -0
  266. package/dist/emblems/sport/tennis.d.ts +2 -0
  267. package/dist/emblems/sport/tennis.js +15 -0
  268. package/dist/emblems/sport/teqball.d.ts +2 -0
  269. package/dist/emblems/sport/teqball.js +15 -0
  270. package/dist/emblems/sport/volleyball.d.ts +2 -0
  271. package/dist/emblems/sport/volleyball.js +25 -0
  272. package/dist/emblems/sport/waterpolo.d.ts +2 -0
  273. package/dist/emblems/sport/waterpolo.js +15 -0
  274. package/dist/emblems/trophy/banner.d.ts +2 -0
  275. package/dist/emblems/trophy/banner.js +20 -0
  276. package/dist/emblems/trophy/baseball.d.ts +2 -0
  277. package/dist/emblems/trophy/baseball.js +15 -0
  278. package/dist/emblems/trophy/basketball.d.ts +2 -0
  279. package/dist/emblems/trophy/basketball.js +17 -0
  280. package/dist/emblems/trophy/belt.d.ts +2 -0
  281. package/dist/emblems/trophy/belt.js +17 -0
  282. package/dist/emblems/trophy/bowl.d.ts +2 -0
  283. package/dist/emblems/trophy/bowl.js +8 -0
  284. package/dist/emblems/trophy/canadian-bowl.d.ts +2 -0
  285. package/dist/emblems/trophy/canadian-bowl.js +23 -0
  286. package/dist/emblems/trophy/championship-banner.d.ts +2 -0
  287. package/dist/emblems/trophy/championship-banner.js +67 -0
  288. package/dist/emblems/trophy/cricket-urn.d.ts +2 -0
  289. package/dist/emblems/trophy/cricket-urn.js +23 -0
  290. package/dist/emblems/trophy/cup.d.ts +2 -0
  291. package/dist/emblems/trophy/cup.js +16 -0
  292. package/dist/emblems/trophy/football.d.ts +2 -0
  293. package/dist/emblems/trophy/football.js +11 -0
  294. package/dist/emblems/trophy/globe.d.ts +2 -0
  295. package/dist/emblems/trophy/globe.js +9 -0
  296. package/dist/emblems/trophy/hockey-cup.d.ts +2 -0
  297. package/dist/emblems/trophy/hockey-cup.js +22 -0
  298. package/dist/emblems/trophy/laurel.d.ts +2 -0
  299. package/dist/emblems/trophy/laurel.js +40 -0
  300. package/dist/emblems/trophy/lidded-cup.d.ts +2 -0
  301. package/dist/emblems/trophy/lidded-cup.js +21 -0
  302. package/dist/emblems/trophy/plaque.d.ts +2 -0
  303. package/dist/emblems/trophy/plaque.js +17 -0
  304. package/dist/emblems/trophy/ring.d.ts +2 -0
  305. package/dist/emblems/trophy/ring.js +30 -0
  306. package/dist/emblems/trophy/rosette.d.ts +2 -0
  307. package/dist/emblems/trophy/rosette.js +27 -0
  308. package/dist/emblems/trophy/rugby-cup.d.ts +2 -0
  309. package/dist/emblems/trophy/rugby-cup.js +23 -0
  310. package/dist/emblems/trophy/salver.d.ts +2 -0
  311. package/dist/emblems/trophy/salver.js +9 -0
  312. package/dist/emblems/trophy/soccer-ball.d.ts +2 -0
  313. package/dist/emblems/trophy/soccer-ball.js +9 -0
  314. package/dist/emblems/trophy/soccer-globe.d.ts +2 -0
  315. package/dist/emblems/trophy/soccer-globe.js +18 -0
  316. package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
  317. package/dist/emblems/trophy/sumo-cup.js +28 -0
  318. package/dist/emblems/trophy/tennis-cup.d.ts +2 -0
  319. package/dist/emblems/trophy/tennis-cup.js +24 -0
  320. package/dist/emblems/trophy/tennis-plate.d.ts +2 -0
  321. package/dist/emblems/trophy/tennis-plate.js +16 -0
  322. package/dist/emblems-entry.d.ts +20 -7
  323. package/dist/emblems-entry.js +8 -2
  324. package/dist/index.d.ts +131 -1
  325. package/dist/index.js +131 -3
  326. package/dist/turn-control.d.ts +101 -0
  327. package/dist/turn-control.js +363 -0
  328. package/dist/turn-entry.d.ts +2 -0
  329. package/dist/turn-entry.js +1 -0
  330. package/dist/version.d.ts +1 -1
  331. package/dist/version.js +1 -1
  332. package/package.json +33 -7
  333. package/LICENSES/Apache-2.0.txt +0 -55
  334. package/LICENSES/CC-BY-3.0.md +0 -15
  335. package/dist/emblems/ball/soccer.d.ts +0 -2
  336. package/dist/emblems/ball/soccer.js +0 -9
  337. package/dist/emblems/emblem.ball.d.ts +0 -28
  338. package/dist/emblems/emblem.ball.js +0 -70
@@ -0,0 +1,26 @@
1
+ // Balls with the seam of a baseball or a tennis ball: one closed curve that winds twice round the sphere, x=(1-b)cos t + b cos 3t, y=(1-b)sin t - b sin 3t, z=2sqrt(b(1-b)) sin 2t.
2
+ // A baseball and a softball have red stitches that cross it in Vs; a tennis or padel ball has a white rubber strip along it; a sliotar a raised rib.
3
+ import { defineBall } from "./ball.engine.js";
4
+ import { seamCurve } from "./ball.math.js";
5
+ import { stitches } from "./ball.stitch.js";
6
+ export function seamedBall(o) {
7
+ const spec = {
8
+ view: o.view ?? { yaw: 0, pitch: 0, roll: 0 },
9
+ base: o.base,
10
+ mat: o.mat,
11
+ draw: (s) => {
12
+ const seam = seamCurve(o.b, 160);
13
+ let out = "";
14
+ if (o.groove !== undefined)
15
+ out += s.stroke(seam, o.groove, 0.8, { closed: true, opacity: 0.55 });
16
+ if (o.strip !== undefined)
17
+ out += o.strip.raised === true ? s.seam(seam, o.strip.colour, o.strip.width, { closed: true }) : s.stroke(seam, o.strip.colour, o.strip.width, { closed: true });
18
+ if (o.stitch !== undefined)
19
+ out += s.strokes(stitches(seam, true, o.stitch.count, o.stitch.half, { v: true, lean: o.stitch.lean }), o.stitch.colour, o.stitch.width, { ink: false });
20
+ if (o.strip === undefined && o.stitch === undefined)
21
+ out += s.stroke(seam, "currentColor", 1.6, { closed: true });
22
+ return out;
23
+ },
24
+ };
25
+ return defineBall({ id: o.id, en: o.en, ja: o.ja, spec, size: o.size });
26
+ }
@@ -0,0 +1 @@
1
+ export declare const BALL_FIELD_HOCKEY: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,15 @@
1
+ // Field hockey ball: a hard white plastic ball with dimples.
2
+ import { dottedBall } from "./ball.family.dotted.js";
3
+ export const BALL_FIELD_HOCKEY = /* @__PURE__ */ dottedBall({
4
+ id: "ball:field-hockey",
5
+ en: "Field hockey ball",
6
+ ja: "フィールドホッケーボール",
7
+ size: { extentMm: 73.0, diameterMm: 73.0, circumferenceMm: 229.5, massG: [156, 163], rule: "FIH Rules of Hockey, Equipment section 3 Ball, 3.1 a-e", note: "Diameter worked out from the middle of the circumference range." },
8
+ base: "#f6f6f2",
9
+ count: 110,
10
+ radius: 0.088,
11
+ depth: 0.22,
12
+ light: false,
13
+ mark: "#6a7080",
14
+ mat: { gloss: 0.85, shade: "#2a3350", grain: undefined },
15
+ });
@@ -0,0 +1,3 @@
1
+ export declare const BALL_FOOTBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
2
+ export declare const BALL_FOOTBALL_CFL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
3
+ export declare const BALL_FOOTBALL_IFAF: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,51 @@
1
+ // American and Canadian football: a pebbled leather ball drawn as a prolate shape whose ends come to a point (`point` in the spec), a ball ("natural tan" in the NFL and IFAF rules), with four panels and a row of laces on one seam. The Canadian ball has a
2
+ // 1-inch white stripe all the way round near each end (CFL Rule 1, Section 2); the IFAF ball has a stripe near each end on the two panels next to the laces only (IFAF Rule 1, Section 3).
3
+ import { defineBall } from "./ball.engine.js";
4
+ import { footballSeams, laces, tipRing } from "./ball.family.prolate.js";
5
+ import { mix } from "./ball.core.js";
6
+ /** The part of a ring that lies on the laces' side (y at or above 0), as one run: the ring is turned to start below it. */
7
+ const upperArc = (ring) => {
8
+ const k = ring.findIndex((p) => p[1] < 0);
9
+ const turned = [...ring.slice(k), ...ring.slice(0, k)];
10
+ const run = [];
11
+ for (const p of turned)
12
+ if (p[1] >= 0)
13
+ run.push(p);
14
+ return run;
15
+ };
16
+ const BROWN = "#8b4a21";
17
+ const AXES = [1, 0.55, 0.55];
18
+ const VIEW = { yaw: -20, pitch: 35, roll: -35 };
19
+ const MAT = { gloss: 0.5, shade: "#2a1006", grain: { f: 0.95, a: 0.42 } };
20
+ function football(id, en, ja, size, stripes, colour) {
21
+ return defineBall({
22
+ id,
23
+ en,
24
+ ja,
25
+ size,
26
+ spec: {
27
+ axes: AXES,
28
+ point: 0.85,
29
+ view: VIEW,
30
+ base: colour,
31
+ mat: MAT,
32
+ draw: (s, base) => {
33
+ let out = "";
34
+ if (stripes === "ring")
35
+ for (const sign of [1, -1])
36
+ out += s.stroke(tipRing(sign, 0.99), "#f4f0e6", 7, { closed: true, ink: false });
37
+ if (stripes === "top")
38
+ for (const sign of [1, -1])
39
+ out += s.stroke(upperArc(tipRing(sign, 1.1, 96)), "#f4f0e6", 7, { ink: false });
40
+ out += s.strokes(footballSeams(), mix(base, -0.55), 1.1);
41
+ out += s.strokes(footballSeams(), mix(base, 0.4), 0.45, { deco: true, opacity: 0.45, dx: 0.25, dy: 0.3 });
42
+ out += laces(s, "#f4f0e6", mix(base, -0.7));
43
+ return out;
44
+ },
45
+ },
46
+ });
47
+ }
48
+ const SIZE = { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "NFL Rulebook, Rule 2 The Ball, Section 1 (Dimensions)" };
49
+ export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "アメフトボール(NFL)", SIZE, "none", BROWN);
50
+ export const BALL_FOOTBALL_CFL = /* @__PURE__ */ football("ball:football-cfl", "Football (CFL)", "フットボール(CFL)", { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "CFL Official Playing Rules, Rule 1 Conduct of the Game, Section 2 The Ball" }, "ring", BROWN);
51
+ export const BALL_FOOTBALL_IFAF = /* @__PURE__ */ football("ball:football-ifaf", "Football (IFAF)", "アメフトボール(IFAF)", { extentMm: 283.4, lengthMm: 283.4, circumferenceMm: 533.4, longCircumferenceMm: 714.38, massG: [396.89, 425.24], rule: "IFAF Football Rules and Interpretations 2026, Rule 1 (The Game, Field, Players and Equipment), Section 3 The Ball, Article 1 a-i", note: "The IFAF figures are in a diagram of the rules, read from the rendered page." }, "top", "#a8693a");
@@ -0,0 +1,2 @@
1
+ export declare const BALL_GAELIC_FOOTBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
2
+ export declare const BALL_NETBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,30 @@
1
+ // Gaelic football and netball: panelled spheres. The panelling is not set by the rules; ours is the 18-panel layout (six faces of three strips), the Gaelic ball white with a green strip on
2
+ // each face and the netball in orange and cream.
3
+ import { defineBall } from "./ball.engine.js";
4
+ import { strips18 } from "./ball.family.panelled.js";
5
+ export const BALL_GAELIC_FOOTBALL = /* @__PURE__ */ defineBall({
6
+ id: "ball:gaelic-football",
7
+ en: "Gaelic football",
8
+ ja: "ゲーリックフットボール",
9
+ size: { extentMm: 219.6, diameterMm: 219.6, circumferenceMm: 690.0, massG: [480, 500], rule: "GAA Official Guide Part 2, Rules of Specification, Rule 4.4(ii)", note: "Diameter worked out from the middle of the circumference range." },
10
+ spec: {
11
+ view: { yaw: 28, pitch: 22, roll: 0 },
12
+ base: "#13935a",
13
+ ground: "#f7f6f0",
14
+ mat: { gloss: 0.6, shade: "#18304a", grain: { f: 1.2, a: 0.2 } },
15
+ draw: (s, base) => strips18(s, [["#f7f6f0", base]], "#5a6a60", 1.25),
16
+ },
17
+ });
18
+ export const BALL_NETBALL = /* @__PURE__ */ defineBall({
19
+ id: "ball:netball",
20
+ en: "Netball",
21
+ ja: "ネットボール",
22
+ size: { extentMm: 222.8, diameterMm: 222.8, circumferenceMm: 700.0, massG: [400, 450], rule: "World Netball Rules of Netball, Rule 1 (Equipment: Ball), item 15 a-d", note: "Diameter worked out from the middle of the circumference range." },
23
+ spec: {
24
+ view: { yaw: 28, pitch: 22, roll: 0 },
25
+ base: "#ea8a2a",
26
+ ground: "#fbeed8",
27
+ mat: { gloss: 0.5, shade: "#4a2208", grain: { f: 1.2, a: 0.22 } },
28
+ draw: (s, base) => strips18(s, [[base, "#fbeed8"], ["#fbeed8", base]], "#7a4a28", 1.25),
29
+ },
30
+ });
@@ -0,0 +1 @@
1
+ export declare const BALL_GOLF: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,15 @@
1
+ // Golf ball (not one of our sports; a bonus): white, with a dimple pattern. The rules fix the size and mass, not the dimples.
2
+ import { dottedBall } from "./ball.family.dotted.js";
3
+ export const BALL_GOLF = /* @__PURE__ */ dottedBall({
4
+ id: "ball:golf",
5
+ en: "Golf ball",
6
+ ja: "ゴルフボール",
7
+ size: { extentMm: 42.67, diameterMm: 42.67, massG: [0, 45.93], rule: "Rules of Equipment (R&A / USGA), Part 4 Conformance of Balls, 4.2 Weight and 4.3 Size", note: "A minimum diameter and a maximum mass: the rules set no maximum size and no minimum weight." },
8
+ base: "#f7f7f5",
9
+ count: 190,
10
+ radius: 0.066,
11
+ depth: 0.2,
12
+ light: false,
13
+ mark: "#5a6070",
14
+ mat: { gloss: 0.85, shade: "#2a3350", grain: undefined },
15
+ });
@@ -0,0 +1 @@
1
+ export declare const BALL_HAMMER: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,31 @@
1
+ // Hammer (the athletics throw): a steel ball, a wire and a grip. The wire is drawn shorter than it is (the rule allows 1.215 m overall), so that the head is still big enough to read.
2
+ import { defineBall } from "./ball.engine.js";
3
+ import { add, cross, mul, norm } from "./ball.math.js";
4
+ const D = norm([0.72, -0.66, 0.2]);
5
+ const U = norm(cross(D, [0, 0, 1]));
6
+ const HEAD = mul(D, -1.25);
7
+ const WIRE_START = -0.25;
8
+ const WIRE_END = 1.25;
9
+ export const BALL_HAMMER = /* @__PURE__ */ defineBall({
10
+ id: "ball:hammer",
11
+ en: "Hammer",
12
+ ja: "ハンマー(ハンマー投)",
13
+ size: { extentMm: 273, diameterMm: 120, massG: [7260, 7260], rule: "World Athletics Technical Rules (C2.1), Rule 36 Hammer Throw - 'The hammer shall conform to the following specifications' and construction text", note: "Men's hammer, whose head is 110 to 130 mm across. The wire is drawn about a fifth of its length, so the scale is that of the head; the rules give only a maximum overall length (1215 mm)." },
14
+ spec: {
15
+ fit: 0.44,
16
+ at: HEAD,
17
+ view: { yaw: 0, pitch: 0, roll: 0 },
18
+ base: "#7e8791",
19
+ mat: { gloss: 0.95, shade: "#0a0e14", metal: true },
20
+ draw: () => "",
21
+ attach: (s) => {
22
+ const p = (v) => s.space(v);
23
+ const a = p(mul(D, WIRE_START)), b = p(mul(D, WIRE_END));
24
+ const g1 = p(add(mul(D, WIRE_END), mul(U, 0.42))), g2 = p(add(mul(D, WIRE_END), mul(U, -0.42))), g3 = p(add(mul(D, WIRE_END + 0.5), mul(U, 0)));
25
+ const wire = `<path d="M${a[0].toFixed(1)} ${a[1].toFixed(1)}L${b[0].toFixed(1)} ${b[1].toFixed(1)}" stroke="${s.line ? "currentColor" : "#aeb5be"}" stroke-width="${Math.max(0.9, s.minWidth)}" stroke-linecap="round" fill="none"/>`;
26
+ const grip = `<path d="M${g1[0].toFixed(1)} ${g1[1].toFixed(1)}L${g3[0].toFixed(1)} ${g3[1].toFixed(1)}L${g2[0].toFixed(1)} ${g2[1].toFixed(1)}Z" fill="none" stroke="${s.line ? "currentColor" : "#5a6270"}" stroke-width="${Math.max(2, s.minWidth * 1.5)}" stroke-linejoin="round" stroke-linecap="round"/>`;
27
+ const out = wire + grip;
28
+ return (a[2] + b[2]) / 2 < 0 ? { behind: out, front: "" } : { behind: "", front: out };
29
+ },
30
+ },
31
+ });
@@ -0,0 +1 @@
1
+ export declare const BALL_HANDBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,25 @@
1
+ // Handball: a 32-panel ball (twelve pentagons, twenty hexagons) with a sticky matt grip, here in navy and yellow of our own choosing.
2
+ import { defineBall } from "./ball.engine.js";
3
+ import { truncatedIcosahedron } from "./ball.polyhedra.js";
4
+ import { mix } from "./ball.core.js";
5
+ export const BALL_HANDBALL = /* @__PURE__ */ defineBall({
6
+ id: "ball:handball",
7
+ en: "Handball",
8
+ ja: "ハンドボール",
9
+ size: { extentMm: 187.8, diameterMm: 187.8, circumferenceMm: 590.0, massG: [425, 475], rule: "IHF Rules of the Game (Indoor Handball), Rule 3 The Ball, 3.1-3.3; sizes 0 and 00 and panel count from IHF Ball Regulations (XIII) Articles 2 and 3", note: "IHF size 3 with resin (men and youths from 16). Diameter worked out from the middle of the circumference range. The IHF Ball Regulations ask for at least 32 panels on a leather ball." },
10
+ spec: {
11
+ view: { yaw: 30, pitch: 24, roll: 8 },
12
+ base: "#1f3f8f",
13
+ ground: "#f1c21b",
14
+ mat: { gloss: 0.3, shade: "#1a1a30", grain: { f: 1.3, a: 0.22 } },
15
+ draw: (s, base) => {
16
+ const t = truncatedIcosahedron();
17
+ let out = "";
18
+ for (const p of t.pentagons)
19
+ out += s.panel(p, base, { ink: true });
20
+ out += s.strokes(t.edges, mix(base, -0.45), 1.15);
21
+ out += s.strokes(t.edges, "#fff", 0.4, { deco: true, opacity: 0.35, dx: 0.3, dy: 0.35 });
22
+ return out;
23
+ },
24
+ },
25
+ });
@@ -0,0 +1,48 @@
1
+ import type { BallFrameOptions, Piece, PieceOptions, PieceSize } from "./ball.core.ts";
2
+ /** Every ball, in the order a gallery shows them. */
3
+ export declare const BALLS: readonly Piece[];
4
+ /** The id of every ball: `"ball:soccer"`, `"ball:cricket-red"`. */
5
+ export declare const BALL_IDS: readonly string[];
6
+ /** A ball by its id, or by its name without the `ball:` (`"soccer"`). An unknown name is an error that lists the ids. */
7
+ export declare function ballOf(name: string): Piece;
8
+ /** True when the text is the id of a ball (or a ball's name without `ball:`). */
9
+ export declare const isBallId: (name: unknown) => name is string;
10
+ /** A ball as a document of SVG: `renderBall("soccer", { size: 64 })`, or in the line style `renderBall("ball:puck", { style: "line" })`. The same arguments give the same string. */
11
+ export declare function renderBall(name: string, options?: PieceOptions): string;
12
+ /**
13
+ * A ball turned: `yaw` degrees about its own vertical axis, `pitch` of tilt toward you and `roll` about the line of sight, all added to the ball's default 3/4 view, so `yaw` from 0 to 360 is one turn
14
+ * of a turntable. The grain stays out of a turning ball. Every ball is drawn in true 3D: the seams, panels and marks are on the sphere and turn with it.
15
+ * @example
16
+ * ```ts
17
+ * import { ballFrame } from "@johnmorrisdotca/kyogijo-stadiums/balls";
18
+ * const frames = [0, 60, 120, 180, 240, 300].map((yaw) => ballFrame("soccer", { yaw, size: 48 }));
19
+ * console.log(frames.length, frames[0].startsWith("<svg"));
20
+ * // 6 true
21
+ * ```
22
+ */
23
+ export declare function ballFrame(name: string, options?: BallFrameOptions): string;
24
+ /** The official size of a ball, with the rule it comes from. */
25
+ export declare function ballSize(name: string): PieceSize | undefined;
26
+ export interface LineupOptions extends PieceOptions {
27
+ /** `true` (the default): each ball at its real size next to the others. `same`: every ball as large as the largest. */
28
+ scale?: "true" | "same";
29
+ /** The height of the tallest ball in pixels (96 by default). */
30
+ height?: number;
31
+ /** The gap between two balls, in pixels (8 by default). */
32
+ gap?: number;
33
+ /** `bottom` (the default: they stand on one line) or `centre`. */
34
+ align?: "bottom" | "centre";
35
+ }
36
+ /**
37
+ * Several balls side by side. At the true scale each ball is as big, next to the others, as the rulebook makes it: a puck beside a basketball looks right. `extentMm` is what a ball's drawing is
38
+ * scaled by (the ball's largest size in a box of 92 units), so the sizes are the rules' sizes, not a guess. The result is one document of SVG.
39
+ * @example
40
+ * ```ts
41
+ * import { lineup } from "@johnmorrisdotca/kyogijo-stadiums/balls";
42
+ * const svg = lineup(["golf", "tennis", "soccer", "puck"], { scale: "true", height: 80 });
43
+ * console.log(svg.includes("<title>"));
44
+ * // true
45
+ * ```
46
+ */
47
+ export declare function lineup(names: readonly string[], options?: LineupOptions): string;
48
+ export type { BallFrameOptions, Piece, PieceOptions, PieceSize, PieceStyle, PieceTheme } from "./ball.core.ts";
@@ -0,0 +1,103 @@
1
+ import { escape, num, resolveOptions, PIECE_INK } from "./ball.core.js";
2
+ import { BALL_AFL } from "./ball.afl.js";
3
+ import { BALL_BASEBALL } from "./ball.baseball.js";
4
+ import { BALL_BASKETBALL } from "./ball.basketball.js";
5
+ import { BALL_BASKETBALL_3X3 } from "./ball.basketball3x3.js";
6
+ import { BALL_BASKETBALL_6 } from "./ball.basketball6.js";
7
+ import { BALL_CRICKET_RED, BALL_CRICKET_WHITE, BALL_CRICKET_PINK } from "./ball.cricket.js";
8
+ import { BALL_CURLING_STONE } from "./ball.curlingstone.js";
9
+ import { BALL_DISCUS } from "./ball.discus.js";
10
+ import { BALL_FIELD_HOCKEY } from "./ball.fieldhockey.js";
11
+ import { BALL_FOOTBALL, BALL_FOOTBALL_CFL, BALL_FOOTBALL_IFAF } from "./ball.football.js";
12
+ import { BALL_GAELIC_FOOTBALL, BALL_NETBALL } from "./ball.gaelic.js";
13
+ import { BALL_GOLF } from "./ball.golf.js";
14
+ import { BALL_HAMMER } from "./ball.hammer.js";
15
+ import { BALL_HANDBALL } from "./ball.handball.js";
16
+ import { BALL_PADEL } from "./ball.padel.js";
17
+ import { BALL_PICKLEBALL_OUTDOOR, BALL_PICKLEBALL_INDOOR } from "./ball.pickleball.js";
18
+ import { BALL_PUCK } from "./ball.puck.js";
19
+ import { BALL_ROUNDNET } from "./ball.roundnet.js";
20
+ import { BALL_RUGBY } from "./ball.rugby.js";
21
+ import { BALL_SEPAK_TAKRAW } from "./ball.sepaktakraw.js";
22
+ import { BALL_SHUTTLECOCK_FEATHER, BALL_SHUTTLECOCK_NYLON } from "./ball.shuttlecock.js";
23
+ import { BALL_SLIOTAR } from "./ball.sliotar.js";
24
+ import { BALL_LACROSSE, BALL_RACQUETBALL, BALL_TABLE_TENNIS, BALL_SHOT } from "./ball.smooth.js";
25
+ import { BALL_SOCCER, BALL_SOCCER_COLOUR } from "./ball.soccer.js";
26
+ import { BALL_SOFTBALL } from "./ball.softball.js";
27
+ import { BALL_SQUASH_DOUBLE_YELLOW, BALL_SQUASH_YELLOW, BALL_SQUASH_RED, BALL_SQUASH_BLUE, BALL_SQUASH_GREEN } from "./ball.squash.js";
28
+ import { BALL_TENNIS } from "./ball.tennis.js";
29
+ import { BALL_TEQBALL } from "./ball.teqball.js";
30
+ import { BALL_VOLLEYBALL, BALL_VOLLEYBALL_8, BALL_BEACH_VOLLEYBALL } from "./ball.volleyball.js";
31
+ import { BALL_WATER_POLO } from "./ball.waterpolo.js";
32
+ /** Every ball, in the order a gallery shows them. */
33
+ export const BALLS = [BALL_SOCCER, BALL_SOCCER_COLOUR, BALL_TEQBALL, BALL_BASKETBALL, BALL_BASKETBALL_6, BALL_BASKETBALL_3X3, BALL_BASEBALL, BALL_SOFTBALL, BALL_TENNIS, BALL_PADEL, BALL_SLIOTAR, BALL_VOLLEYBALL, BALL_VOLLEYBALL_8, BALL_BEACH_VOLLEYBALL, BALL_CRICKET_RED, BALL_CRICKET_WHITE, BALL_CRICKET_PINK, BALL_FOOTBALL, BALL_FOOTBALL_CFL, BALL_FOOTBALL_IFAF, BALL_RUGBY, BALL_AFL, BALL_GAELIC_FOOTBALL, BALL_NETBALL, BALL_PICKLEBALL_OUTDOOR, BALL_PICKLEBALL_INDOOR, BALL_SQUASH_DOUBLE_YELLOW, BALL_SQUASH_YELLOW, BALL_SQUASH_GREEN, BALL_SQUASH_RED, BALL_SQUASH_BLUE, BALL_RACQUETBALL, BALL_LACROSSE, BALL_FIELD_HOCKEY, BALL_TABLE_TENNIS, BALL_HANDBALL, BALL_WATER_POLO, BALL_GOLF, BALL_PUCK, BALL_CURLING_STONE, BALL_SHOT, BALL_DISCUS, BALL_HAMMER, BALL_SEPAK_TAKRAW, BALL_ROUNDNET, BALL_SHUTTLECOCK_FEATHER, BALL_SHUTTLECOCK_NYLON];
34
+ /** The id of every ball: `"ball:soccer"`, `"ball:cricket-red"`. */
35
+ export const BALL_IDS = BALLS.map((b) => b.id);
36
+ const TABLE = new Map(BALLS.map((b) => [b.id, b]));
37
+ /** A ball by its id, or by its name without the `ball:` (`"soccer"`). An unknown name is an error that lists the ids. */
38
+ export function ballOf(name) {
39
+ const found = TABLE.get(name.startsWith("ball:") ? name : `ball:${name}`);
40
+ if (found === undefined)
41
+ throw new Error(`Unknown ball "${name}". The ids are ${BALL_IDS.join(", ")}.`);
42
+ return found;
43
+ }
44
+ /** True when the text is the id of a ball (or a ball's name without `ball:`). */
45
+ export const isBallId = (name) => typeof name === "string" && TABLE.has(name.startsWith("ball:") ? name : `ball:${name}`);
46
+ /** A ball as a document of SVG: `renderBall("soccer", { size: 64 })`, or in the line style `renderBall("ball:puck", { style: "line" })`. The same arguments give the same string. */
47
+ export function renderBall(name, options) {
48
+ return ballOf(name).svg(options);
49
+ }
50
+ /**
51
+ * A ball turned: `yaw` degrees about its own vertical axis, `pitch` of tilt toward you and `roll` about the line of sight, all added to the ball's default 3/4 view, so `yaw` from 0 to 360 is one turn
52
+ * of a turntable. The grain stays out of a turning ball. Every ball is drawn in true 3D: the seams, panels and marks are on the sphere and turn with it.
53
+ * @example
54
+ * ```ts
55
+ * import { ballFrame } from "@johnmorrisdotca/kyogijo-stadiums/balls";
56
+ * const frames = [0, 60, 120, 180, 240, 300].map((yaw) => ballFrame("soccer", { yaw, size: 48 }));
57
+ * console.log(frames.length, frames[0].startsWith("<svg"));
58
+ * // 6 true
59
+ * ```
60
+ */
61
+ export function ballFrame(name, options) {
62
+ return ballOf(name).frame(options);
63
+ }
64
+ /** The official size of a ball, with the rule it comes from. */
65
+ export function ballSize(name) {
66
+ return ballOf(name).size;
67
+ }
68
+ /**
69
+ * Several balls side by side. At the true scale each ball is as big, next to the others, as the rulebook makes it: a puck beside a basketball looks right. `extentMm` is what a ball's drawing is
70
+ * scaled by (the ball's largest size in a box of 92 units), so the sizes are the rules' sizes, not a guess. The result is one document of SVG.
71
+ * @example
72
+ * ```ts
73
+ * import { lineup } from "@johnmorrisdotca/kyogijo-stadiums/balls";
74
+ * const svg = lineup(["golf", "tennis", "soccer", "puck"], { scale: "true", height: 80 });
75
+ * console.log(svg.includes("<title>"));
76
+ * // true
77
+ * ```
78
+ */
79
+ export function lineup(names, options) {
80
+ const pieces = names.map(ballOf);
81
+ const height = typeof options?.height === "number" && Number.isFinite(options.height) && options.height > 0 ? Math.min(options.height, 2000) : 96;
82
+ const gap = typeof options?.gap === "number" && Number.isFinite(options.gap) && options.gap >= 0 ? Math.min(options.gap, 400) : 8;
83
+ const trueScale = options?.scale !== "same";
84
+ const largest = Math.max(...pieces.map((p) => p.size?.extentMm ?? 1), 1);
85
+ // Pixels per box unit: the largest piece's box is `height` tall.
86
+ const boxes = pieces.map((p) => (trueScale ? (height * (p.size?.extentMm ?? largest)) / largest : height));
87
+ const width = boxes.reduce((sum, b) => sum + b, 0) + gap * Math.max(0, pieces.length - 1);
88
+ const resolved = resolveOptions(options);
89
+ const colour = resolved.theme === "dark" ? PIECE_INK.dark : resolved.theme === "inherit" ? "" : PIECE_INK.light;
90
+ const auto = resolved.theme === "auto" ? `<style>@media(prefers-color-scheme:dark){svg{color:${PIECE_INK.dark}}}</style>` : "";
91
+ let x = 0;
92
+ const parts = pieces.map((p, i) => {
93
+ const box = boxes[i];
94
+ const doc = p.svg({ ...options, size: Math.max(1, box) });
95
+ const inner = doc.slice(doc.indexOf(">") + 1, doc.lastIndexOf("</svg>"));
96
+ const y = options?.align === "centre" ? (height - box) / 2 : height - box;
97
+ const out = `<g transform="translate(${num(x, 2)} ${num(y, 2)}) scale(${num(box / 100, 4)})">${inner}</g>`;
98
+ x += box + gap;
99
+ return out;
100
+ });
101
+ const title = `Balls at ${trueScale ? "their true relative sizes" : "one size"}: ${pieces.map((p) => p.en).join(", ")}`;
102
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${num(width, 2)} ${num(height, 2)}" width="${num(width, 2)}" height="${num(height, 2)}" role="img"${colour === "" ? "" : ` color="${colour}"`}><title>${escape(title)}</title>${auto}${parts.join("")}</svg>`;
103
+ }
@@ -0,0 +1,38 @@
1
+ export type V3 = readonly [number, number, number];
2
+ export type M3 = readonly [V3, V3, V3];
3
+ export declare const dot: (a: V3, b: V3) => number;
4
+ export declare const add: (a: V3, b: V3) => V3;
5
+ export declare const sub: (a: V3, b: V3) => V3;
6
+ export declare const mul: (a: V3, k: number) => V3;
7
+ export declare const cross: (a: V3, b: V3) => V3;
8
+ export declare const len: (a: V3) => number;
9
+ export declare const norm: (a: V3) => V3;
10
+ /** The point `t` of the way along the great circle from `a` to `b` (unit vectors): a spherical linear interpolation. */
11
+ export declare function slerp(a: V3, b: V3, t: number): V3;
12
+ /** A unit vector from longitude and latitude in radians: longitude 0 faces +z, 90 degrees faces +x, latitude is toward +y. */
13
+ export declare const lonLat: (lon: number, lat: number) => V3;
14
+ /** The rotation of a view: `yaw` about the vertical axis, then `pitch` about the horizontal one, then `roll` about the line of sight. */
15
+ export declare function rotation(yaw: number, pitch: number, roll: number): M3;
16
+ export declare function matmul(a: M3, b: M3): M3;
17
+ export declare const apply: (m: M3, v: V3) => V3;
18
+ export declare const transpose: (m: M3) => M3;
19
+ /** Points along the great circle with this unit normal, `n` of them, starting from a chosen direction (or any) around the circle. */
20
+ export declare function greatCircle(normal: V3, n?: number, from?: V3): V3[];
21
+ /** Points of the small circle at angular distance `angle` (radians) from the unit axis. */
22
+ export declare function smallCircle(axis: V3, angle: number, n?: number, from?: V3): V3[];
23
+ /** The arc of a great circle from `a` to `b`, `n` steps (n + 1 points). */
24
+ export declare const arc: (a: V3, b: V3, n: number) => V3[];
25
+ /** A basis (u, w) of the plane perpendicular to the unit vector. */
26
+ export declare function basis(a: V3): [V3, V3];
27
+ /** Evenly spread points on a sphere (a Fibonacci spiral), `n` of them; `phase` turns the whole set about the y axis. */
28
+ export declare function spread(n: number, phase?: number): V3[];
29
+ /** The seam of a baseball or a tennis ball: the curve x=(1-b)cos t + b cos 3t, y=(1-b)sin t - b sin 3t, z=2sqrt(b(1-b)) sin 2t. It lies on the unit sphere for any b. */
30
+ export declare function seamCurve(b: number, n?: number): V3[];
31
+ /** The singular values and the angle of the ellipse that a 2 by 3 matrix makes of the unit sphere: its semi-axes (largest first) and the direction of the first, in radians. */
32
+ export declare function ellipseOf(rows: readonly [V3, V3]): {
33
+ rx: number;
34
+ ry: number;
35
+ angle: number;
36
+ };
37
+ /** A ring's edges followed along the sphere: each edge from a vertex to the next is sampled `n` times (n = 1 keeps the vertices). */
38
+ export declare function sampleRing(ring: readonly V3[], n: number): V3[];
@@ -0,0 +1,105 @@
1
+ // The geometry of a ball: vectors, the rotation, the curves a seam is made of. Pure functions on numbers; nothing here writes SVG.
2
+ export const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
3
+ export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
4
+ export const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
5
+ export const mul = (a, k) => [a[0] * k, a[1] * k, a[2] * k];
6
+ 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]];
7
+ export const len = (a) => Math.hypot(a[0], a[1], a[2]);
8
+ export const norm = (a) => {
9
+ const l = len(a) || 1;
10
+ return [a[0] / l, a[1] / l, a[2] / l];
11
+ };
12
+ /** The point `t` of the way along the great circle from `a` to `b` (unit vectors): a spherical linear interpolation. */
13
+ export function slerp(a, b, t) {
14
+ const d = Math.max(-1, Math.min(1, dot(a, b)));
15
+ const omega = Math.acos(d);
16
+ if (omega < 1e-6)
17
+ return a;
18
+ const s = Math.sin(omega);
19
+ return add(mul(a, Math.sin((1 - t) * omega) / s), mul(b, Math.sin(t * omega) / s));
20
+ }
21
+ /** A unit vector from longitude and latitude in radians: longitude 0 faces +z, 90 degrees faces +x, latitude is toward +y. */
22
+ export const lonLat = (lon, lat) => [Math.sin(lon) * Math.cos(lat), Math.sin(lat), Math.cos(lon) * Math.cos(lat)];
23
+ /** The rotation of a view: `yaw` about the vertical axis, then `pitch` about the horizontal one, then `roll` about the line of sight. */
24
+ export function rotation(yaw, pitch, roll) {
25
+ const cy = Math.cos(yaw), sy = Math.sin(yaw), cp = Math.cos(pitch), sp = Math.sin(pitch), cr = Math.cos(roll), sr = Math.sin(roll);
26
+ const ry = [[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]];
27
+ const rx = [[1, 0, 0], [0, cp, -sp], [0, sp, cp]];
28
+ const rz = [[cr, -sr, 0], [sr, cr, 0], [0, 0, 1]];
29
+ return matmul(rz, matmul(rx, ry));
30
+ }
31
+ export function matmul(a, b) {
32
+ const col = (j) => [b[0][j], b[1][j], b[2][j]];
33
+ const row = (i) => [dot(a[i], col(0)), dot(a[i], col(1)), dot(a[i], col(2))];
34
+ return [row(0), row(1), row(2)];
35
+ }
36
+ export const apply = (m, v) => [dot(m[0], v), dot(m[1], v), dot(m[2], v)];
37
+ export const transpose = (m) => [[m[0][0], m[1][0], m[2][0]], [m[0][1], m[1][1], m[2][1]], [m[0][2], m[1][2], m[2][2]]];
38
+ /** Points along the great circle with this unit normal, `n` of them, starting from a chosen direction (or any) around the circle. */
39
+ export function greatCircle(normal, n = 96, from) {
40
+ const a = norm(normal);
41
+ const ref = from ?? (Math.abs(a[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]);
42
+ const u = norm(sub(ref, mul(a, dot(ref, a))));
43
+ const w = cross(a, u);
44
+ return Array.from({ length: n }, (_, i) => {
45
+ const t = (i / n) * 2 * Math.PI;
46
+ return add(mul(u, Math.cos(t)), mul(w, Math.sin(t)));
47
+ });
48
+ }
49
+ /** Points of the small circle at angular distance `angle` (radians) from the unit axis. */
50
+ export function smallCircle(axis, angle, n = 96, from) {
51
+ const a = norm(axis);
52
+ const c = Math.cos(angle), s = Math.sin(angle);
53
+ return greatCircle(a, n, from).map((p) => add(mul(a, c), mul(p, s)));
54
+ }
55
+ /** The arc of a great circle from `a` to `b`, `n` steps (n + 1 points). */
56
+ export const arc = (a, b, n) => Array.from({ length: n + 1 }, (_, i) => slerp(a, b, i / n));
57
+ /** A basis (u, w) of the plane perpendicular to the unit vector. */
58
+ export function basis(a) {
59
+ const ref = Math.abs(a[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
60
+ const u = norm(sub(ref, mul(a, dot(ref, a))));
61
+ return [u, cross(a, u)];
62
+ }
63
+ /** Evenly spread points on a sphere (a Fibonacci spiral), `n` of them; `phase` turns the whole set about the y axis. */
64
+ export function spread(n, phase = 0) {
65
+ const golden = Math.PI * (3 - Math.sqrt(5));
66
+ return Array.from({ length: n }, (_, i) => {
67
+ const y = 1 - ((i + 0.5) / n) * 2;
68
+ const r = Math.sqrt(1 - y * y);
69
+ const t = i * golden + phase;
70
+ return [Math.cos(t) * r, y, Math.sin(t) * r];
71
+ });
72
+ }
73
+ /** The seam of a baseball or a tennis ball: the curve x=(1-b)cos t + b cos 3t, y=(1-b)sin t - b sin 3t, z=2sqrt(b(1-b)) sin 2t. It lies on the unit sphere for any b. */
74
+ export function seamCurve(b, n = 120) {
75
+ const o = [];
76
+ const z = 2 * Math.sqrt(b * (1 - b));
77
+ for (let i = 0; i < n; i++) {
78
+ const t = (i / n) * 2 * Math.PI;
79
+ o.push([(1 - b) * Math.cos(t) + b * Math.cos(3 * t), (1 - b) * Math.sin(t) - b * Math.sin(3 * t), z * Math.sin(2 * t)]);
80
+ }
81
+ return o;
82
+ }
83
+ /** The singular values and the angle of the ellipse that a 2 by 3 matrix makes of the unit sphere: its semi-axes (largest first) and the direction of the first, in radians. */
84
+ export function ellipseOf(rows) {
85
+ const a = dot(rows[0], rows[0]);
86
+ const b = dot(rows[0], rows[1]);
87
+ const d = dot(rows[1], rows[1]);
88
+ const tr = a + d;
89
+ const disc = Math.sqrt(Math.max(0, ((a - d) / 2) ** 2 + b * b));
90
+ const l1 = tr / 2 + disc;
91
+ const l2 = Math.max(0, tr / 2 - disc);
92
+ return { rx: Math.sqrt(l1), ry: Math.sqrt(l2), angle: 0.5 * Math.atan2(2 * b, a - d) };
93
+ }
94
+ /** A ring's edges followed along the sphere: each edge from a vertex to the next is sampled `n` times (n = 1 keeps the vertices). */
95
+ export function sampleRing(ring, n) {
96
+ if (n <= 1)
97
+ return [...ring];
98
+ const out = [];
99
+ for (let i = 0; i < ring.length; i++) {
100
+ const a = ring[i], b = ring[(i + 1) % ring.length];
101
+ for (let j = 0; j < n; j++)
102
+ out.push(slerp(a, b, j / n));
103
+ }
104
+ return out;
105
+ }
@@ -0,0 +1,38 @@
1
+ import { type Piece, type PieceSize, type ResolvedOptions } from "./ball.core.ts";
2
+ import { type M3, type V3 } from "./ball.math.ts";
3
+ export interface ObjectView {
4
+ yaw: number;
5
+ pitch: number;
6
+ roll: number;
7
+ }
8
+ /** What an object's `draw` is given. */
9
+ export interface ObjectContext {
10
+ rot: M3;
11
+ opts: ResolvedOptions;
12
+ /** True when the ball is turning: no grain. */
13
+ turning: boolean;
14
+ /** An id prefix that no other drawing of this piece in the page shares. */
15
+ id: string;
16
+ /** The thinnest line that shows at this size, in box units. */
17
+ minWidth: number;
18
+ /** A point of the object in its own space to the box: x, y and depth (positive toward you); one unit is 46 box units. */
19
+ at(p: V3): [number, number, number];
20
+ }
21
+ export interface ObjectDef<Model> {
22
+ id: string;
23
+ en: string;
24
+ ja: string;
25
+ size: PieceSize;
26
+ view: ObjectView;
27
+ /** How much of the box the object takes: its own unit is `46 * scale` box units. */
28
+ scale?: number;
29
+ /** A point of the object to put at the centre of the box. */
30
+ shift?: V3;
31
+ model: Model;
32
+ /** The drawing: its definitions and its body. */
33
+ draw: (c: ObjectContext) => {
34
+ defs: string;
35
+ body: string;
36
+ };
37
+ }
38
+ export declare function defineObject<Model>(def: ObjectDef<Model>): Piece<Model>;
@@ -0,0 +1,20 @@
1
+ // Objects that are not an ellipsoid: a puck, a stone, a shuttlecock. They share what a ball has (the rotation, the options, the shading palette and the `Piece` shape) and draw their own geometry.
2
+ import { escape, hashOf, labelled, radians, resolveOptions, wrapPiece } from "./ball.core.js";
3
+ import { apply, rotation } from "./ball.math.js";
4
+ const finite = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
5
+ export function defineObject(def) {
6
+ const render = (o, turning) => {
7
+ const opts = resolveOptions(o);
8
+ const rot = rotation(radians(def.view.yaw + finite(o?.yaw), 0), radians(def.view.pitch + finite(o?.pitch), 0), radians(def.view.roll + finite(o?.roll), 0));
9
+ const id = `k${hashOf(`${def.id}|${opts.style}|${opts.tint ?? ""}|${opts.accent ?? ""}|${rot.map((r) => r.map((x) => x.toFixed(2)).join()).join()}`)}`;
10
+ const at = (p) => {
11
+ const sh = def.shift ?? [0, 0, 0];
12
+ const k = def.scale ?? 1;
13
+ const q = apply(rot, [(p[0] - sh[0]) * k, (p[1] - sh[1]) * k, (p[2] - sh[2]) * k]);
14
+ return [50 + 46 * q[0], 50 - 46 * q[1], q[2]];
15
+ };
16
+ const { defs, body } = def.draw({ rot, opts, turning, id, minWidth: 80 / opts.size, at });
17
+ return wrapPiece({ title: escape(def.en), size: opts.size, theme: opts.theme, defs, body });
18
+ };
19
+ return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: def.model, size: def.size, svg: labelled(def.en, def.ja, (o) => render(o, false)), frame: labelled(def.en, def.ja, (o) => render(o, true)) };
20
+ }
@@ -0,0 +1 @@
1
+ export declare const BALL_PADEL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
@@ -0,0 +1,13 @@
1
+ // Padel ball: a yellow felt ball a little larger than a tennis ball's, with a seam of its own proportions.
2
+ import { seamedBall } from "./ball.family.seamed.js";
3
+ export const BALL_PADEL = /* @__PURE__ */ seamedBall({
4
+ id: "ball:padel",
5
+ en: "Padel ball",
6
+ ja: "パデルボール",
7
+ size: { extentMm: 65.6, diameterMm: 65.6, massG: [56.0, 59.4], rule: "FIP Rules of Padel, 'The Ball' article (as extracted in FIP Game Ball Certification Process, section B) and Appendix 'Technical specifications of the padel ball'" },
8
+ base: "#ecdc2c",
9
+ b: 0.36,
10
+ view: { yaw: 25, pitch: 40, roll: 15 },
11
+ mat: { gloss: 0.1, shade: "#5a4d06", grain: { f: 1.6, a: 0.5, seed: 5 } },
12
+ strip: { colour: "#fbfaf0", width: 2.2 },
13
+ });
@@ -0,0 +1,2 @@
1
+ export declare const BALL_PICKLEBALL_OUTDOOR: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
2
+ export declare const BALL_PICKLEBALL_INDOOR: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;