@johnmorrisdotca/dougu-sports 0.0.1 → 0.1.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 (296) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/LICENSES/Apache-2.0.txt +202 -55
  3. package/LICENSES/CC-BY-3.0.txt +319 -0
  4. package/LICENSES/README.md +4 -3
  5. package/LICENSES/upstream/game-icons.license.txt +45 -0
  6. package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +13 -0
  7. package/LICENSES/upstream/pictogrammers-mdi.LICENSE +20 -0
  8. package/NOTICE.md +92 -3
  9. package/README.md +104 -69
  10. package/dist/emblems/award/badge.d.ts +2 -0
  11. package/dist/emblems/award/badge.js +5 -0
  12. package/dist/emblems/award/crown.d.ts +2 -0
  13. package/dist/emblems/award/crown.js +5 -0
  14. package/dist/emblems/award/medal.d.ts +2 -2
  15. package/dist/emblems/award/medal.js +5 -19
  16. package/dist/emblems/award/shield.d.ts +2 -0
  17. package/dist/emblems/award/shield.js +5 -0
  18. package/dist/emblems/award/star.d.ts +2 -0
  19. package/dist/emblems/award/star.js +5 -0
  20. package/dist/emblems/ball/ball.afl.d.ts +1 -0
  21. package/dist/emblems/ball/ball.afl.js +24 -0
  22. package/dist/emblems/ball/ball.baseball.d.ts +1 -0
  23. package/dist/emblems/ball/ball.baseball.js +14 -0
  24. package/dist/emblems/ball/ball.basketball.d.ts +7 -0
  25. package/dist/emblems/ball/ball.basketball.js +32 -0
  26. package/dist/emblems/ball/ball.basketball3x3.d.ts +1 -0
  27. package/dist/emblems/ball/ball.basketball3x3.js +10 -0
  28. package/dist/emblems/ball/ball.basketball6.d.ts +1 -0
  29. package/dist/emblems/ball/ball.basketball6.js +10 -0
  30. package/dist/emblems/ball/ball.core.d.ts +105 -0
  31. package/dist/emblems/ball/ball.core.js +66 -0
  32. package/dist/emblems/ball/ball.cricket.d.ts +5 -0
  33. package/dist/emblems/ball/ball.cricket.js +31 -0
  34. package/dist/emblems/ball/ball.curlingstone.d.ts +3 -0
  35. package/dist/emblems/ball/ball.curlingstone.js +46 -0
  36. package/dist/emblems/ball/ball.discus.d.ts +1 -0
  37. package/dist/emblems/ball/ball.discus.js +24 -0
  38. package/dist/emblems/ball/ball.engine.d.ts +114 -0
  39. package/dist/emblems/ball/ball.engine.js +342 -0
  40. package/dist/emblems/ball/ball.family.dotted.d.ts +27 -0
  41. package/dist/emblems/ball/ball.family.dotted.js +20 -0
  42. package/dist/emblems/ball/ball.family.panelled.d.ts +7 -0
  43. package/dist/emblems/ball/ball.family.panelled.js +60 -0
  44. package/dist/emblems/ball/ball.family.prolate.d.ts +16 -0
  45. package/dist/emblems/ball/ball.family.prolate.js +29 -0
  46. package/dist/emblems/ball/ball.family.seamed.d.ts +31 -0
  47. package/dist/emblems/ball/ball.family.seamed.js +26 -0
  48. package/dist/emblems/ball/ball.fieldhockey.d.ts +1 -0
  49. package/dist/emblems/ball/ball.fieldhockey.js +15 -0
  50. package/dist/emblems/ball/ball.football.d.ts +3 -0
  51. package/dist/emblems/ball/ball.football.js +51 -0
  52. package/dist/emblems/ball/ball.gaelic.d.ts +2 -0
  53. package/dist/emblems/ball/ball.gaelic.js +30 -0
  54. package/dist/emblems/ball/ball.golf.d.ts +1 -0
  55. package/dist/emblems/ball/ball.golf.js +15 -0
  56. package/dist/emblems/ball/ball.hammer.d.ts +1 -0
  57. package/dist/emblems/ball/ball.hammer.js +31 -0
  58. package/dist/emblems/ball/ball.handball.d.ts +1 -0
  59. package/dist/emblems/ball/ball.handball.js +25 -0
  60. package/dist/emblems/ball/ball.index.d.ts +48 -0
  61. package/dist/emblems/ball/ball.index.js +103 -0
  62. package/dist/emblems/ball/ball.math.d.ts +38 -0
  63. package/dist/emblems/ball/ball.math.js +105 -0
  64. package/dist/emblems/ball/ball.object.d.ts +38 -0
  65. package/dist/emblems/ball/ball.object.js +20 -0
  66. package/dist/emblems/ball/ball.padel.d.ts +1 -0
  67. package/dist/emblems/ball/ball.padel.js +13 -0
  68. package/dist/emblems/ball/ball.pickleball.d.ts +2 -0
  69. package/dist/emblems/ball/ball.pickleball.js +26 -0
  70. package/dist/emblems/ball/ball.polyhedra.d.ts +15 -0
  71. package/dist/emblems/ball/ball.polyhedra.js +63 -0
  72. package/dist/emblems/ball/ball.puck.d.ts +3 -0
  73. package/dist/emblems/ball/ball.puck.js +22 -0
  74. package/dist/emblems/ball/ball.roundnet.d.ts +1 -0
  75. package/dist/emblems/ball/ball.roundnet.js +15 -0
  76. package/dist/emblems/ball/ball.rugby.d.ts +1 -0
  77. package/dist/emblems/ball/ball.rugby.js +35 -0
  78. package/dist/emblems/ball/ball.sepaktakraw.d.ts +1 -0
  79. package/dist/emblems/ball/ball.sepaktakraw.js +48 -0
  80. package/dist/emblems/ball/ball.shuttlecock.d.ts +6 -0
  81. package/dist/emblems/ball/ball.shuttlecock.js +93 -0
  82. package/dist/emblems/ball/ball.sliotar.d.ts +1 -0
  83. package/dist/emblems/ball/ball.sliotar.js +14 -0
  84. package/dist/emblems/ball/ball.smooth.d.ts +4 -0
  85. package/dist/emblems/ball/ball.smooth.js +51 -0
  86. package/dist/emblems/ball/ball.soccer.d.ts +2 -0
  87. package/dist/emblems/ball/ball.soccer.js +54 -0
  88. package/dist/emblems/ball/ball.softball.d.ts +1 -0
  89. package/dist/emblems/ball/ball.softball.js +14 -0
  90. package/dist/emblems/ball/ball.squash.d.ts +7 -0
  91. package/dist/emblems/ball/ball.squash.js +27 -0
  92. package/dist/emblems/ball/ball.stack.d.ts +48 -0
  93. package/dist/emblems/ball/ball.stack.js +127 -0
  94. package/dist/emblems/ball/ball.stitch.d.ts +10 -0
  95. package/dist/emblems/ball/ball.stitch.js +59 -0
  96. package/dist/emblems/ball/ball.tennis.d.ts +1 -0
  97. package/dist/emblems/ball/ball.tennis.js +13 -0
  98. package/dist/emblems/ball/ball.teqball.d.ts +1 -0
  99. package/dist/emblems/ball/ball.teqball.js +27 -0
  100. package/dist/emblems/ball/ball.volleyball.d.ts +3 -0
  101. package/dist/emblems/ball/ball.volleyball.js +56 -0
  102. package/dist/emblems/ball/ball.waterpolo.d.ts +1 -0
  103. package/dist/emblems/ball/ball.waterpolo.js +31 -0
  104. package/dist/emblems/crest/diamond.d.ts +2 -0
  105. package/dist/emblems/crest/diamond.js +18 -0
  106. package/dist/emblems/crest/pennant.d.ts +2 -0
  107. package/dist/emblems/crest/pennant.js +18 -0
  108. package/dist/emblems/crest/roundel.d.ts +2 -0
  109. package/dist/emblems/crest/roundel.js +18 -0
  110. package/dist/emblems/crest/shield.d.ts +2 -0
  111. package/dist/emblems/crest/shield.js +17 -0
  112. package/dist/emblems/emblem.api.d.ts +39 -9
  113. package/dist/emblems/emblem.api.js +38 -10
  114. package/dist/emblems/emblem.core.d.ts +42 -6
  115. package/dist/emblems/emblem.core.js +55 -11
  116. package/dist/emblems/emblem.ids.d.ts +33 -27
  117. package/dist/emblems/emblem.ids.js +35 -16
  118. package/dist/emblems/emblem.lathe.d.ts +59 -0
  119. package/dist/emblems/emblem.lathe.js +404 -0
  120. package/dist/emblems/emblem.material.d.ts +9 -0
  121. package/dist/emblems/emblem.material.data.d.ts +34 -0
  122. package/dist/emblems/emblem.material.data.js +85 -0
  123. package/dist/emblems/emblem.material.js +23 -0
  124. package/dist/emblems/emblem.medal.d.ts +6 -0
  125. package/dist/emblems/emblem.medal.js +38 -0
  126. package/dist/emblems/emblem.registry.js +142 -4
  127. package/dist/emblems/emblem.shapes.d.ts +31 -12
  128. package/dist/emblems/emblem.shapes.js +99 -33
  129. package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -0
  130. package/dist/emblems/gear/gear.badmintonracket.js +15 -0
  131. package/dist/emblems/gear/gear.baseballglove.d.ts +1 -0
  132. package/dist/emblems/gear/gear.baseballglove.js +15 -0
  133. package/dist/emblems/gear/gear.core.d.ts +83 -0
  134. package/dist/emblems/gear/gear.core.js +120 -0
  135. package/dist/emblems/gear/gear.cricketbat.d.ts +1 -0
  136. package/dist/emblems/gear/gear.cricketbat.js +15 -0
  137. package/dist/emblems/gear/gear.field.d.ts +6 -0
  138. package/dist/emblems/gear/gear.field.js +81 -0
  139. package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -0
  140. package/dist/emblems/gear/gear.fieldhockeystick.js +15 -0
  141. package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -0
  142. package/dist/emblems/gear/gear.footballhelmet.js +15 -0
  143. package/dist/emblems/gear/gear.goaliemask.d.ts +1 -0
  144. package/dist/emblems/gear/gear.goaliemask.js +17 -0
  145. package/dist/emblems/gear/gear.goalnet.d.ts +1 -0
  146. package/dist/emblems/gear/gear.goalnet.js +15 -0
  147. package/dist/emblems/gear/gear.hockeystick.d.ts +1 -0
  148. package/dist/emblems/gear/gear.hockeystick.js +15 -0
  149. package/dist/emblems/gear/gear.index.d.ts +22 -0
  150. package/dist/emblems/gear/gear.index.js +45 -0
  151. package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -0
  152. package/dist/emblems/gear/gear.lacrossestick.js +15 -0
  153. package/dist/emblems/gear/gear.rackets.d.ts +4 -0
  154. package/dist/emblems/gear/gear.rackets.js +60 -0
  155. package/dist/emblems/gear/gear.skates.d.ts +3 -0
  156. package/dist/emblems/gear/gear.skates.js +39 -0
  157. package/dist/emblems/gear/gear.sticks.d.ts +3 -0
  158. package/dist/emblems/gear/gear.sticks.js +34 -0
  159. package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -0
  160. package/dist/emblems/gear/gear.tabletennispaddle.js +15 -0
  161. package/dist/emblems/gear/gear.tennisracket.d.ts +1 -0
  162. package/dist/emblems/gear/gear.tennisracket.js +15 -0
  163. package/dist/emblems/gear/gear.track.d.ts +4 -0
  164. package/dist/emblems/gear/gear.track.js +45 -0
  165. package/dist/emblems/gear/gear.water.d.ts +2 -0
  166. package/dist/emblems/gear/gear.water.js +25 -0
  167. package/dist/emblems/medal/bronze.d.ts +2 -0
  168. package/dist/emblems/medal/bronze.js +4 -0
  169. package/dist/emblems/medal/gold.d.ts +2 -0
  170. package/dist/emblems/medal/gold.js +4 -0
  171. package/dist/emblems/medal/place-1.d.ts +2 -0
  172. package/dist/emblems/medal/place-1.js +4 -0
  173. package/dist/emblems/medal/place-2.d.ts +2 -0
  174. package/dist/emblems/medal/place-2.js +4 -0
  175. package/dist/emblems/medal/place-3.d.ts +2 -0
  176. package/dist/emblems/medal/place-3.js +4 -0
  177. package/dist/emblems/medal/silver.d.ts +2 -0
  178. package/dist/emblems/medal/silver.js +4 -0
  179. package/dist/emblems/sport/afl.d.ts +2 -0
  180. package/dist/emblems/sport/afl.js +12 -0
  181. package/dist/emblems/sport/artisticswimming.d.ts +2 -0
  182. package/dist/emblems/sport/artisticswimming.js +16 -0
  183. package/dist/emblems/sport/athletics.d.ts +2 -0
  184. package/dist/emblems/sport/athletics.js +12 -0
  185. package/dist/emblems/sport/badminton.d.ts +2 -0
  186. package/dist/emblems/sport/badminton.js +13 -0
  187. package/dist/emblems/sport/cfl.d.ts +2 -0
  188. package/dist/emblems/sport/cfl.js +5 -0
  189. package/dist/emblems/sport/cricket.d.ts +2 -0
  190. package/dist/emblems/sport/cricket.js +5 -0
  191. package/dist/emblems/sport/curling.d.ts +2 -0
  192. package/dist/emblems/sport/curling.js +14 -0
  193. package/dist/emblems/sport/diving.d.ts +2 -0
  194. package/dist/emblems/sport/diving.js +18 -0
  195. package/dist/emblems/sport/fieldhockey.d.ts +2 -0
  196. package/dist/emblems/sport/fieldhockey.js +11 -0
  197. package/dist/emblems/sport/gaelic.d.ts +2 -0
  198. package/dist/emblems/sport/gaelic.js +11 -0
  199. package/dist/emblems/sport/handball.d.ts +2 -0
  200. package/dist/emblems/sport/handball.js +5 -0
  201. package/dist/emblems/sport/ifaf.d.ts +2 -0
  202. package/dist/emblems/sport/ifaf.js +5 -0
  203. package/dist/emblems/sport/lacrosse.d.ts +2 -0
  204. package/dist/emblems/sport/lacrosse.js +13 -0
  205. package/dist/emblems/sport/mlb.d.ts +2 -0
  206. package/dist/emblems/sport/mlb.js +5 -0
  207. package/dist/emblems/sport/nba.d.ts +2 -0
  208. package/dist/emblems/sport/nba.js +5 -0
  209. package/dist/emblems/sport/netball.d.ts +2 -0
  210. package/dist/emblems/sport/netball.js +14 -0
  211. package/dist/emblems/sport/nfl.d.ts +2 -0
  212. package/dist/emblems/sport/nfl.js +5 -0
  213. package/dist/emblems/sport/nhl.d.ts +2 -0
  214. package/dist/emblems/sport/nhl.js +5 -0
  215. package/dist/emblems/sport/padel.d.ts +2 -0
  216. package/dist/emblems/sport/padel.js +14 -0
  217. package/dist/emblems/sport/pickleball.d.ts +2 -0
  218. package/dist/emblems/sport/pickleball.js +13 -0
  219. package/dist/emblems/sport/racquetball.d.ts +2 -0
  220. package/dist/emblems/sport/racquetball.js +13 -0
  221. package/dist/emblems/sport/roundnet.d.ts +2 -0
  222. package/dist/emblems/sport/roundnet.js +15 -0
  223. package/dist/emblems/sport/rugby.d.ts +2 -0
  224. package/dist/emblems/sport/rugby.js +5 -0
  225. package/dist/emblems/sport/sepaktakraw.d.ts +2 -0
  226. package/dist/emblems/sport/sepaktakraw.js +11 -0
  227. package/dist/emblems/sport/skating.d.ts +2 -0
  228. package/dist/emblems/sport/skating.js +14 -0
  229. package/dist/emblems/sport/soccer.d.ts +2 -0
  230. package/dist/emblems/sport/soccer.js +5 -0
  231. package/dist/emblems/sport/squash.d.ts +2 -0
  232. package/dist/emblems/sport/squash.js +15 -0
  233. package/dist/emblems/sport/swimming.d.ts +2 -0
  234. package/dist/emblems/sport/swimming.js +15 -0
  235. package/dist/emblems/sport/tabletennis.d.ts +2 -0
  236. package/dist/emblems/sport/tabletennis.js +12 -0
  237. package/dist/emblems/sport/tennis.d.ts +2 -0
  238. package/dist/emblems/sport/tennis.js +5 -0
  239. package/dist/emblems/sport/teqball.d.ts +2 -0
  240. package/dist/emblems/sport/teqball.js +15 -0
  241. package/dist/emblems/sport/volleyball.d.ts +2 -0
  242. package/dist/emblems/sport/volleyball.js +5 -0
  243. package/dist/emblems/sport/waterpolo.d.ts +2 -0
  244. package/dist/emblems/sport/waterpolo.js +15 -0
  245. package/dist/emblems/trophy/baseball.d.ts +2 -0
  246. package/dist/emblems/trophy/baseball.js +14 -0
  247. package/dist/emblems/trophy/basketball.d.ts +2 -0
  248. package/dist/emblems/trophy/basketball.js +16 -0
  249. package/dist/emblems/trophy/belt.d.ts +2 -0
  250. package/dist/emblems/trophy/belt.js +16 -0
  251. package/dist/emblems/trophy/bowl.d.ts +2 -0
  252. package/dist/emblems/trophy/bowl.js +7 -0
  253. package/dist/emblems/trophy/canadian-bowl.d.ts +2 -0
  254. package/dist/emblems/trophy/canadian-bowl.js +22 -0
  255. package/dist/emblems/trophy/cricket-urn.d.ts +2 -0
  256. package/dist/emblems/trophy/cricket-urn.js +22 -0
  257. package/dist/emblems/trophy/cup.d.ts +2 -0
  258. package/dist/emblems/trophy/cup.js +5 -0
  259. package/dist/emblems/trophy/football.d.ts +2 -0
  260. package/dist/emblems/trophy/football.js +10 -0
  261. package/dist/emblems/trophy/globe.d.ts +2 -0
  262. package/dist/emblems/trophy/globe.js +8 -0
  263. package/dist/emblems/trophy/hockey-cup.d.ts +2 -0
  264. package/dist/emblems/trophy/hockey-cup.js +21 -0
  265. package/dist/emblems/trophy/laurel.d.ts +2 -0
  266. package/dist/emblems/trophy/laurel.js +40 -0
  267. package/dist/emblems/trophy/lidded-cup.d.ts +2 -0
  268. package/dist/emblems/trophy/lidded-cup.js +20 -0
  269. package/dist/emblems/trophy/plaque.d.ts +2 -0
  270. package/dist/emblems/trophy/plaque.js +16 -0
  271. package/dist/emblems/trophy/rosette.d.ts +2 -0
  272. package/dist/emblems/trophy/rosette.js +27 -0
  273. package/dist/emblems/trophy/rugby-cup.d.ts +2 -0
  274. package/dist/emblems/trophy/rugby-cup.js +22 -0
  275. package/dist/emblems/trophy/salver.d.ts +2 -0
  276. package/dist/emblems/trophy/salver.js +8 -0
  277. package/dist/emblems/trophy/soccer-ball.d.ts +2 -0
  278. package/dist/emblems/trophy/soccer-ball.js +8 -0
  279. package/dist/emblems/trophy/soccer-globe.d.ts +2 -0
  280. package/dist/emblems/trophy/soccer-globe.js +17 -0
  281. package/dist/emblems/trophy/tennis-cup.d.ts +2 -0
  282. package/dist/emblems/trophy/tennis-cup.js +23 -0
  283. package/dist/emblems/trophy/tennis-plate.d.ts +2 -0
  284. package/dist/emblems/trophy/tennis-plate.js +15 -0
  285. package/dist/emblems-entry.d.ts +10 -6
  286. package/dist/emblems-entry.js +3 -2
  287. package/dist/index.d.ts +114 -1
  288. package/dist/index.js +116 -3
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +1 -1
  291. package/package.json +28 -8
  292. package/LICENSES/CC-BY-3.0.md +0 -15
  293. package/dist/emblems/ball/soccer.d.ts +0 -2
  294. package/dist/emblems/ball/soccer.js +0 -9
  295. package/dist/emblems/emblem.ball.d.ts +0 -28
  296. package/dist/emblems/emblem.ball.js +0 -70
@@ -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, 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: (o) => render(o, false), frame: (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>;
@@ -0,0 +1,26 @@
1
+ // Pickleball: a perforated plastic ball. The outdoor ball has 40 small holes; the indoor ball has 26 larger ones (the rulebook gives 26 to 40 holes; the split is how balls are sold).
2
+ import { dottedBall } from "./ball.family.dotted.js";
3
+ export const BALL_PICKLEBALL_OUTDOOR = /* @__PURE__ */ dottedBall({
4
+ id: "ball:pickleball-outdoor",
5
+ en: "Pickleball, outdoor",
6
+ ja: "ピックルボール(屋外用)",
7
+ size: { extentMm: 74.2, diameterMm: 74.2, massG: [22.1, 26.5], rule: "USA Pickleball Equipment Standards Manual (Jan 2025, Revision 3.0), 2.D.1 Design, 2.D.3 Size, 2.D.4 Weight, 2.D.8 Design (holes); USA Pickleball 2026 Official Rulebook rule 3.C Ball Specifications", note: "The rulebook does not tell indoor and outdoor balls apart: both are 26 to 40 holes; 40 small holes is the outdoor ball as sold." },
8
+ base: "#e6e83c",
9
+ count: 40,
10
+ radius: 0.13,
11
+ mat: { gloss: 0.55, shade: "#4a5008", grain: { f: 1.4, a: 0.1 } },
12
+ mark: "#454b0e",
13
+ depth: 0.75,
14
+ });
15
+ export const BALL_PICKLEBALL_INDOOR = /* @__PURE__ */ dottedBall({
16
+ id: "ball:pickleball-indoor",
17
+ en: "Pickleball, indoor",
18
+ ja: "ピックルボール(屋内用)",
19
+ size: { extentMm: 74.2, diameterMm: 74.2, massG: [22.1, 26.5], rule: "USA Pickleball Equipment Standards Manual (Jan 2025, Revision 3.0), 2.D.1 Design, 2.D.3 Size, 2.D.4 Weight, 2.D.8 Design (holes); USA Pickleball 2026 Official Rulebook rule 3.C Ball Specifications", note: "The rulebook does not tell indoor and outdoor balls apart: both are 26 to 40 holes; 26 large holes is the indoor ball as sold." },
20
+ base: "#f2f0e4",
21
+ count: 26,
22
+ radius: 0.19,
23
+ mat: { gloss: 0.6, shade: "#3a3a40", grain: { f: 1.4, a: 0.08 } },
24
+ mark: "#3d3d44",
25
+ depth: 0.7,
26
+ });
@@ -0,0 +1,15 @@
1
+ import { type V3 } from "./ball.math.ts";
2
+ export interface Truncated {
3
+ pentagons: V3[][];
4
+ hexagons: V3[][];
5
+ /** The centre of each pentagon and hexagon, same order. */
6
+ pentagonCentres: V3[];
7
+ hexagonCentres: V3[];
8
+ /** Every edge between two panels once, as a curve on the sphere. */
9
+ edges: V3[][];
10
+ /** For each hexagon, the midpoints of the three edges it shares with a pentagon (alternate edges). */
11
+ hexagonPentagonSides: V3[][];
12
+ }
13
+ export declare function truncatedIcosahedron(samples?: number): Truncated;
14
+ /** A ring pulled toward its centre by a fraction, on the sphere: the face inside the seam. */
15
+ export declare function shrink(ring: readonly V3[], centre: V3, by: number): V3[];
@@ -0,0 +1,63 @@
1
+ // The truncated icosahedron: the 32 panels of a classic soccer ball (12 pentagons and 20 hexagons), as rings of unit vectors. Each vertex is on an edge of an icosahedron, a third of the way
2
+ // along it; the edges between vertices follow the sphere (spherical interpolation), so the panels bulge as an inflated ball's do.
3
+ import { add, arc, cross, dot, mul, norm, slerp, sub } from "./ball.math.js";
4
+ const PHI = (1 + Math.sqrt(5)) / 2;
5
+ let cached;
6
+ export function truncatedIcosahedron(samples = 4) {
7
+ if (cached !== undefined && samples === 4)
8
+ return cached;
9
+ const verts = [];
10
+ for (const a of [-1, 1])
11
+ for (const b of [-PHI, PHI])
12
+ verts.push(norm([0, a, b]), norm([a, b, 0]), norm([b, 0, a]));
13
+ const near = (a, b) => Math.abs(dot(a, b) - 1 / Math.sqrt(5)) < 1e-6;
14
+ const third = (a, b) => slerp(verts[a], verts[b], 1 / 3);
15
+ const pentagons = [];
16
+ const pentagonCentres = [];
17
+ verts.forEach((c, i) => {
18
+ const nb = verts.map((_, j) => j).filter((j) => j !== i && near(c, verts[j]));
19
+ const ex = norm(sub(verts[nb[0]], mul(c, dot(verts[nb[0]], c))));
20
+ const ey = cross(c, ex);
21
+ nb.sort((p, q) => Math.atan2(dot(verts[p], ey), dot(verts[p], ex)) - Math.atan2(dot(verts[q], ey), dot(verts[q], ex)));
22
+ pentagons.push(nb.map((j) => third(i, j)));
23
+ pentagonCentres.push(c);
24
+ });
25
+ const hexagons = [];
26
+ const hexagonCentres = [];
27
+ const hexagonPentagonSides = [];
28
+ for (let i = 0; i < 12; i++) {
29
+ for (let j = i + 1; j < 12; j++) {
30
+ if (!near(verts[i], verts[j]))
31
+ continue;
32
+ for (let k = j + 1; k < 12; k++) {
33
+ if (!near(verts[i], verts[k]) || !near(verts[j], verts[k]))
34
+ continue;
35
+ hexagons.push([third(i, j), third(j, i), third(j, k), third(k, j), third(k, i), third(i, k)]);
36
+ hexagonCentres.push(norm(add(add(verts[i], verts[j]), verts[k])));
37
+ // The edges P(i,k)-P(i,j), P(j,i)-P(j,k) and P(k,j)-P(k,i) touch a pentagon (the one at i, j and k).
38
+ const mid = (a, b) => norm(add(a, b));
39
+ hexagonPentagonSides.push([mid(third(i, k), third(i, j)), mid(third(j, i), third(j, k)), mid(third(k, j), third(k, i))]);
40
+ }
41
+ }
42
+ }
43
+ const seen = new Set();
44
+ const edges = [];
45
+ for (const ring of [...pentagons, ...hexagons]) {
46
+ for (let i = 0; i < ring.length; i++) {
47
+ const a = ring[i], b = ring[(i + 1) % ring.length];
48
+ const key = [a, b].map((v) => v.map((x) => Math.round(x * 1000)).join(",")).sort().join("|");
49
+ if (seen.has(key))
50
+ continue;
51
+ seen.add(key);
52
+ edges.push(arc(a, b, samples));
53
+ }
54
+ }
55
+ const result = { pentagons, hexagons, pentagonCentres, hexagonCentres, edges, hexagonPentagonSides };
56
+ if (samples === 4)
57
+ cached = result;
58
+ return result;
59
+ }
60
+ /** A ring pulled toward its centre by a fraction, on the sphere: the face inside the seam. */
61
+ export function shrink(ring, centre, by) {
62
+ return ring.map((p) => norm(add(mul(p, 1 - by), mul(centre, by))));
63
+ }
@@ -0,0 +1,3 @@
1
+ export declare const BALL_PUCK: import("./ball.core.ts").Piece<{
2
+ slices: number;
3
+ }>;
@@ -0,0 +1,22 @@
1
+ // Ice hockey puck: vulcanised black rubber, 76.2 mm across and 25.4 mm thick (so 1 : 3), with a ridged edge. Drawn as a cylinder in true 3D, so it turns.
2
+ import { drawStack } from "./ball.stack.js";
3
+ import { defineObject } from "./ball.object.js";
4
+ const H = 25.4 / 76.2 / 2;
5
+ export const BALL_PUCK = /* @__PURE__ */ defineObject({
6
+ id: "ball:puck",
7
+ en: "Hockey puck",
8
+ ja: "アイスホッケーのパック",
9
+ size: { extentMm: 76, diameterMm: 76, thicknessMm: 25, massG: [156, 170], rule: "IIHF Official Rule Book, Rule 13 Puck, 13.1 Dimensions (the puck is Rule 13, not Rule 1)", note: "IIHF figures, rounded as the rule rounds them (the NHL puck is 3 in by 1 in). Colour is not stated in the rule." },
10
+ view: { yaw: 0, pitch: 36, roll: -8 },
11
+ model: { slices: 1 },
12
+ draw: (c) => drawStack(c, {
13
+ slices: [{ y0: -H, y1: H, r: 1, colour: "#26282d", outline: true }],
14
+ top: "#2e3036",
15
+ bottom: "#2e3036",
16
+ gloss: 0.5,
17
+ shade: "#000",
18
+ grain: { f: 1.6, a: 0.25 },
19
+ grooves: { count: 30, slices: [0], colour: "#000", opacity: 0.5 },
20
+ topRing: { r: 0.9, colour: "#4a4d56" },
21
+ }),
22
+ });
@@ -0,0 +1 @@
1
+ export declare const BALL_ROUNDNET: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;