@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,114 @@
1
+ import { type Piece, type PieceSize, type ResolvedOptions } from "./ball.core.ts";
2
+ import { type V3 } from "./ball.math.ts";
3
+ /** How a ball takes the light. */
4
+ export interface Material {
5
+ /** The colour of the shadowed side; by default the base colour made darker. */
6
+ shade?: string;
7
+ /** 0 (matt) to 1 (a hard bright highlight). */
8
+ gloss: number;
9
+ /** A polished metal: the sky above and the ground below are mirrored in it. */
10
+ metal?: boolean;
11
+ /** How dark the limb is, 0 to 1 (1 by default). */
12
+ rim?: number;
13
+ /** The grain: `f` is the noise's frequency per unit of the 100 by 100 box, `a` its relief. */
14
+ grain?: {
15
+ f: number;
16
+ a: number;
17
+ seed?: number;
18
+ };
19
+ }
20
+ /** What a ball's `draw` lays things with. Points are unit vectors on the sphere the ball is made on (an ellipsoid ball is stretched after). */
21
+ export interface Surface {
22
+ /** True in the line style: a panel with `ink` is filled with the ink, a stroke without `deco` is drawn in it, and the rest is left out. */
23
+ readonly line: boolean;
24
+ /** The ball's radius in the box (the longest semi-axis). */
25
+ readonly radius: number;
26
+ /** The width of the thinnest line that still shows at this size, in box units. */
27
+ readonly minWidth: number;
28
+ /** The screen position of a point and how squarely it faces you (1 facing, 0 on the limb, below 0 hidden). */
29
+ project(u: V3): [number, number, number];
30
+ /** A closed polygon on the surface, cut at the limb. Its edges follow the sphere. `n` is the samples an edge gets. */
31
+ panel(ring: readonly V3[], fill: string, o?: {
32
+ ink?: boolean;
33
+ opacity?: number;
34
+ n?: number;
35
+ }): string;
36
+ /** A curve on the surface: only what faces you. `closed` joins its ends; `deco` marks a line that is not part of the ball's form (a highlight), which the line style leaves out. */
37
+ stroke(pts: readonly V3[], colour: string, width: number, o?: StrokeOptions): string;
38
+ /** Several curves drawn as one path. */
39
+ strokes(curves: ReadonlyArray<readonly V3[]>, colour: string, width: number, o?: StrokeOptions): string;
40
+ /** A raised seam: a shadow, the colour and a highlight along the curve. */
41
+ seam(pts: readonly V3[], colour: string, width: number, o?: {
42
+ closed?: boolean;
43
+ light?: string;
44
+ opacity?: number;
45
+ }): string;
46
+ /** Small round marks lying on the surface (holes, dimples, dots): each is a cap of `radius` radians round its centre. */
47
+ dots(centres: readonly V3[], radius: number, fill: string, o?: {
48
+ opacity?: number;
49
+ ink?: boolean;
50
+ light?: boolean;
51
+ }): string;
52
+ /** The facing of a point: 1 straight at you, 0 on the limb. */
53
+ facing(u: V3): number;
54
+ /** A point of space, in units of the ball's radius from its centre (not on the surface): its screen position and depth (positive is toward you). */
55
+ space(p: V3): [number, number, number];
56
+ }
57
+ export interface StrokeOptions {
58
+ closed?: boolean;
59
+ ink?: boolean;
60
+ deco?: boolean;
61
+ opacity?: number;
62
+ dx?: number;
63
+ dy?: number;
64
+ dash?: string;
65
+ }
66
+ export interface BallSpec {
67
+ /** The semi-axes of the ellipsoid along x, y and z before the view turns it. A sphere is `[1, 1, 1]`; a football is longer along x. */
68
+ axes?: V3;
69
+ /**
70
+ * Makes a prolate ball pointed at the ends. The ball is a body of revolution about its long axis whose radius at a distance x from the middle (as a fraction of the half length) is `(1 - x^2)^point`
71
+ * times the cross-section's semi-axis: 0.5 is the ellipsoid, and nearer 1 the ends are drawn to a point (a football's). The cross-section's two semi-axes must be equal.
72
+ */
73
+ point?: number;
74
+ /** The default view in degrees. */
75
+ view: {
76
+ yaw: number;
77
+ pitch: number;
78
+ roll: number;
79
+ };
80
+ /** The main colour of the ball (what `tint` replaces). */
81
+ base: string;
82
+ /** The colour the whole ball is filled with before its panels are laid on, where it is not `base` (a soccer ball is white, its pentagons are `base`). */
83
+ ground?: string;
84
+ mat: Material;
85
+ /** Lays the panels and seams. `base` is the main colour, or the tint; `accent` is the second colour or undefined. */
86
+ draw: (s: Surface, base: string, accent: string | undefined) => string;
87
+ /** Where the ball's centre is in the 100 by 100 box (50, 50 by default): a hammer's head sits in a corner to leave room for the wire. */
88
+ centre?: readonly [number, number];
89
+ /** Where the ball is in space, in units of its radius from the middle of the whole (0, 0, 0 by default), turned with it: a hammer's head is at one end of the hammer, which turns about its middle. */
90
+ at?: V3;
91
+ /** The fraction of the box's radius the ball takes (1 by default): a hammer's head is small, to leave room for its wire. */
92
+ fit?: number;
93
+ /** What is attached to the ball and sticks out of it (a hammer's wire and handle): drawn behind the ball or in front of it, by what is nearer to you. */
94
+ attach?: (s: Surface, base: string) => {
95
+ behind: string;
96
+ front: string;
97
+ };
98
+ }
99
+ /** Draws a ball: the document for a spec, the options and a turn in radians. */
100
+ export declare function drawBall(title: string, key: string, spec: BallSpec, opts: ResolvedOptions, turn: {
101
+ yaw: number;
102
+ pitch: number;
103
+ roll: number;
104
+ }, turning: boolean): string;
105
+ /** What `defineBall` takes. */
106
+ export interface BallDef {
107
+ id: string;
108
+ en: string;
109
+ ja: string;
110
+ spec: BallSpec;
111
+ size: PieceSize;
112
+ }
113
+ /** A ball from its definition: the `Piece` the registry and the gallery take. */
114
+ export declare function defineBall(def: BallDef): Piece<BallSpec>;
@@ -0,0 +1,342 @@
1
+ // The ball renderer: one procedural engine for every sphere and every prolate ball. A ball is drawn in true 3D: a rotation, an orthographic projection, back faces dropped, panels cut at the limb
2
+ // along the limb itself, then radial-gradient shading and a grain that stays where it is while the ball turns. A ball is a `BallSpec`: its shape (the semi-axes of an ellipsoid), its
3
+ // view, its material and a `draw` function that lays panels, seams and dots on the unit sphere; the engine does the rest.
4
+ import { grainDef, escape, hashOf, labelled, mix, num, radians, resolveOptions, wrapPiece } from "./ball.core.js";
5
+ import { add, apply, basis, cross, dot, ellipseOf, len, mul, norm, rotation, sampleRing, sub } from "./ball.math.js";
6
+ const EPS = 1e-6;
7
+ const STEP = Math.PI / 18;
8
+ /** How a point of the unit sphere is carried to the ball's surface: an ellipsoid's scaling, and for a pointed ball a narrowing of the cross-section toward the ends. */
9
+ const shapeOf = (g, u) => {
10
+ const f = g.point === undefined ? 1 : Math.max(0, 1 - u[0] * u[0]) ** (g.point - 0.5);
11
+ return [u[0] * g.axes[0], u[1] * g.axes[1] * f, u[2] * g.axes[2] * f];
12
+ };
13
+ /** How squarely a point of the sphere faces the eye, before it is scaled: above 0 is the side you see. For an ellipsoid it is the dot of the point with `w`; for a pointed ball the surface's normal is worked out. */
14
+ const visOf = (g, u) => {
15
+ if (g.point === undefined)
16
+ return dot(u, g.w);
17
+ const rest = Math.max(0, 1 - u[0] * u[0]);
18
+ const a = g.axes[0];
19
+ const b = g.axes[1];
20
+ const nx = 2 * b * g.point * u[0] * rest ** (g.point - 0.5);
21
+ return (g.rz[0] * nx + g.rz[1] * a * u[1] + g.rz[2] * a * u[2]) / (a * b);
22
+ };
23
+ const project = (g, u) => {
24
+ const q = apply(g.rot, shapeOf(g, u));
25
+ return [g.cx + g.k * q[0], g.cy - g.k * q[1]];
26
+ };
27
+ const point = (g, u) => {
28
+ const [x, y] = project(g, u);
29
+ return `${num(x)} ${num(y)}`;
30
+ };
31
+ const crossing = (a, b, va, vb) => {
32
+ const t = va / (va - vb);
33
+ return norm(add(mul(a, 1 - t), mul(b, t)));
34
+ };
35
+ /** The outline of a ring on the surface, cut where it passes behind the limb and joined along the limb. */
36
+ function ringPath(g, ring) {
37
+ const vis = ring.map((p) => visOf(g, p));
38
+ const start = vis.findIndex((v) => v > EPS);
39
+ if (start < 0)
40
+ return "";
41
+ const n = ring.length;
42
+ const out = [];
43
+ let lastExit;
44
+ out.push(`M${point(g, ring[start])}`);
45
+ for (let j = 1; j <= n; j++) {
46
+ const i = (start + j) % n;
47
+ const p = (start + j - 1) % n;
48
+ const vp = vis[p], vi = vis[i];
49
+ if (vp > EPS && vi > EPS) {
50
+ if (j < n)
51
+ out.push(`L${point(g, ring[i])}`);
52
+ }
53
+ else if (vp > EPS) {
54
+ lastExit = crossing(ring[p], ring[i], vp, vi);
55
+ out.push(`L${point(g, lastExit)}`);
56
+ }
57
+ else if (vi > EPS && lastExit !== undefined) {
58
+ const entry = crossing(ring[p], ring[i], vp, vi);
59
+ for (const q of limbArc(g, lastExit, entry))
60
+ out.push(`L${point(g, q)}`);
61
+ out.push(`L${point(g, entry)}`);
62
+ if (j < n)
63
+ out.push(`L${point(g, ring[i])}`);
64
+ }
65
+ }
66
+ return `${out.join("")}Z`;
67
+ }
68
+ /** The points of the limb between two points on it, the short way round (the ends left out). */
69
+ function limbArc(g, from, to) {
70
+ const wn = norm(g.w);
71
+ const e1 = norm(sub(from, mul(wn, dot(from, wn))));
72
+ const e2 = cross(wn, e1);
73
+ let delta = Math.atan2(dot(to, e2), dot(to, e1));
74
+ if (delta === 0 && dot(to, e1) < 0)
75
+ delta = Math.PI;
76
+ const steps = Math.max(1, Math.ceil(Math.abs(delta) / STEP));
77
+ const out = [];
78
+ for (let i = 1; i < steps; i++) {
79
+ const t = (delta * i) / steps;
80
+ out.push(add(mul(e1, Math.cos(t)), mul(e2, Math.sin(t))));
81
+ }
82
+ return out;
83
+ }
84
+ /** The parts of a curve that face you, each as screen points with their facing. The curve is cut exactly at the limb. */
85
+ function visibleRuns(g, pts, closed) {
86
+ const n = pts.length;
87
+ const vis = pts.map((p) => visOf(g, p) / g.wl);
88
+ const at = (u, f) => {
89
+ const [x, y] = project(g, u);
90
+ return [x, y, f];
91
+ };
92
+ const runs = [];
93
+ let cur;
94
+ const total = closed ? n : n - 1;
95
+ // Start a closed curve at a hidden point so that no run is split at the start; a curve wholly facing you is one closed run.
96
+ let first = 0;
97
+ if (closed) {
98
+ const hidden = vis.findIndex((v) => v <= 0);
99
+ if (hidden < 0) {
100
+ return [{ pts: [...pts.map((p, i) => at(p, vis[i])), at(pts[0], vis[0])] }];
101
+ }
102
+ first = hidden;
103
+ }
104
+ for (let j = 0; j < total; j++) {
105
+ const i = (first + j) % n;
106
+ const m = (first + j + 1) % n;
107
+ const a = pts[i], b = pts[m];
108
+ const va = vis[i], vb = vis[m];
109
+ if (va > 0 && vb > 0) {
110
+ if (cur === undefined) {
111
+ cur = { pts: [at(a, va)] };
112
+ runs.push(cur);
113
+ }
114
+ cur.pts.push(at(b, vb));
115
+ }
116
+ else if (va > 0) {
117
+ const c = crossing(a, b, va * g.wl, vb * g.wl);
118
+ if (cur === undefined) {
119
+ cur = { pts: [at(a, va)] };
120
+ runs.push(cur);
121
+ }
122
+ cur.pts.push(at(c, 0));
123
+ cur = undefined;
124
+ }
125
+ else if (vb > 0) {
126
+ const c = crossing(a, b, va * g.wl, vb * g.wl);
127
+ cur = { pts: [at(c, 0), at(b, vb)] };
128
+ runs.push(cur);
129
+ }
130
+ }
131
+ return runs;
132
+ }
133
+ const pathOf = (pts) => pts.map((p, i) => `${i === 0 ? "M" : "L"}${num(p[0])} ${num(p[1])}`).join("");
134
+ /** A curve's visible parts as paths: a stroke is thinner where it turns toward the limb (three steps), so it does not sit on the ball like a wire. */
135
+ function runPaths(runs, colour, width, extra) {
136
+ const tiers = [[], [], []];
137
+ const factor = [1, 0.74, 0.5];
138
+ const tierOf = (f) => (f > 0.55 ? 0 : f > 0.25 ? 1 : 2);
139
+ for (const run of runs) {
140
+ let kind = -1;
141
+ let piece = [];
142
+ const flush = () => {
143
+ if (piece.length > 1)
144
+ tiers[kind].push(pathOf(piece));
145
+ piece = [];
146
+ };
147
+ for (const p of run.pts) {
148
+ const k = tierOf(p[2]);
149
+ if (k !== kind) {
150
+ const last = piece.at(-1);
151
+ flush();
152
+ kind = k;
153
+ piece = last === undefined ? [] : [last];
154
+ }
155
+ piece.push(p);
156
+ }
157
+ flush();
158
+ }
159
+ const make = (d, w) => (d.length === 0 ? "" : `<path d="${d.join("")}" fill="none" stroke="${colour}" stroke-width="${num(w, 2)}" stroke-linecap="round" stroke-linejoin="round"${extra}/>`);
160
+ return make(tiers[2], width * factor[2]) + make(tiers[1], width * factor[1]) + make(tiers[0], width);
161
+ }
162
+ function surfaceOf(g, line, minWidth) {
163
+ const INK = "currentColor";
164
+ const facing = (u) => visOf(g, u) / g.wl;
165
+ const s = {
166
+ line,
167
+ radius: g.k * Math.max(...g.axes),
168
+ minWidth,
169
+ project: (u) => {
170
+ const [x, y] = project(g, u);
171
+ return [x, y, facing(u)];
172
+ },
173
+ facing,
174
+ space: (p) => {
175
+ const q = apply(g.rot, p);
176
+ return [g.ox + g.k * q[0], g.oy - g.k * q[1], q[2]];
177
+ },
178
+ panel(ring, fill, o) {
179
+ if (line && o?.ink !== true)
180
+ return "";
181
+ const d = ringPath(g, sampleRing(ring, o?.n ?? 4));
182
+ if (d === "")
183
+ return "";
184
+ return `<path d="${d}" fill="${line ? INK : fill}"${o?.opacity === undefined ? "" : ` opacity="${o.opacity}"`}/>`;
185
+ },
186
+ stroke: (pts, colour, width, o) => s.strokes([pts], colour, width, o),
187
+ strokes(curves, colour, width, o) {
188
+ if (line && (o?.deco === true || o?.ink === false))
189
+ return "";
190
+ const runs = curves.flatMap((pts) => visibleRuns(g, pts, o?.closed === true));
191
+ if (runs.length === 0)
192
+ return "";
193
+ const extra = `${o?.opacity === undefined || line ? "" : ` opacity="${o.opacity}"`}${o?.dash === undefined ? "" : ` stroke-dasharray="${o.dash}"`}${o?.dx === undefined && o?.dy === undefined ? "" : ` transform="translate(${o.dx ?? 0} ${o.dy ?? 0})"`}`;
194
+ return runPaths(runs, line ? INK : colour, Math.max(width, minWidth), extra);
195
+ },
196
+ seam(pts, colour, width, o) {
197
+ if (line)
198
+ return s.stroke(pts, colour, width, { closed: o?.closed });
199
+ const closed = o?.closed === true;
200
+ const w = Math.max(width, minWidth);
201
+ return (s.stroke(pts, "#000", w * 1.25, { closed, opacity: 0.28, dx: w * 0.18, dy: w * 0.22 }) +
202
+ s.stroke(pts, colour, w, { closed, opacity: o?.opacity }) +
203
+ s.stroke(pts, o?.light ?? "#fff", w * 0.32, { closed, opacity: 0.38, dx: -w * 0.16, dy: -w * 0.2 }));
204
+ },
205
+ dots(centres, radius, fill, o) {
206
+ if (line && o?.ink !== true)
207
+ return "";
208
+ const sr = Math.sin(radius), cr = Math.cos(radius);
209
+ const d = [];
210
+ for (const m of centres) {
211
+ if (facing(m) < 0.08)
212
+ continue;
213
+ const [t1, t2] = basis(m);
214
+ const centre = project(g, mul(m, cr));
215
+ const pa = project(g, mul(t1, sr));
216
+ const pb = project(g, mul(t2, sr));
217
+ const p0 = project(g, [0, 0, 0]);
218
+ const da = [pa[0] - p0[0], pa[1] - p0[1]];
219
+ const db = [pb[0] - p0[0], pb[1] - p0[1]];
220
+ const { rx, ry, angle } = ellipseOf([[da[0], db[0], 0], [da[1], db[1], 0]]);
221
+ if (rx < 0.05)
222
+ continue;
223
+ const ax = Math.cos(angle) * rx, ay = Math.sin(angle) * rx;
224
+ const deg = num((angle * 180) / Math.PI, 0);
225
+ const arcs = `a${num(rx)} ${num(Math.max(ry, 0.05))} ${deg} 1 0`;
226
+ d.push(`M${num(centre[0] + ax)} ${num(centre[1] + ay)}${arcs}${num(-2 * ax)} ${num(-2 * ay)}${arcs}${num(2 * ax)} ${num(2 * ay)}`);
227
+ }
228
+ if (d.length === 0)
229
+ return "";
230
+ const path = d.join("");
231
+ const hi = o?.light === false || line ? "" : `<path d="${path}" fill="none" stroke="#fff" stroke-width="${num(Math.max(0.35, minWidth * 0.3), 2)}" opacity=".3" transform="translate(.25 .35)"/>`;
232
+ return `${hi}<path d="${path}" fill="${line ? INK : fill}"${o?.opacity === undefined ? "" : ` opacity="${o.opacity}"`}/>`;
233
+ },
234
+ };
235
+ return s;
236
+ }
237
+ /** The silhouette of the ball in the box: a circle for a sphere, an ellipse otherwise, as the shape every layer is clipped to and shaded on. */
238
+ function silhouette(g) {
239
+ if (g.point !== undefined) {
240
+ // A pointed ball is convex, so its outline is the hull of its projected surface.
241
+ const pts = [];
242
+ for (let i = 0; i <= 36; i++) {
243
+ const x = Math.cos((Math.PI * i) / 36);
244
+ const r = Math.sqrt(Math.max(0, 1 - x * x));
245
+ for (let j = 0; j < 36; j++)
246
+ pts.push(project(g, [x, r * Math.cos((2 * Math.PI * j) / 36), r * Math.sin((2 * Math.PI * j) / 36)]));
247
+ }
248
+ pts.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
249
+ const turn = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
250
+ const half = (list) => {
251
+ const hull = [];
252
+ for (const p of list) {
253
+ while (hull.length >= 2 && turn(hull[hull.length - 2], hull[hull.length - 1], p) <= 0)
254
+ hull.pop();
255
+ hull.push(p);
256
+ }
257
+ hull.pop();
258
+ return hull;
259
+ };
260
+ const hull = [...half(pts), ...half([...pts].reverse())];
261
+ return { tag: "", d: `M${hull.map((p) => `${num(p[0], 1)} ${num(p[1], 1)}`).join("L")}Z` };
262
+ }
263
+ const rows = [
264
+ [g.rot[0][0] * g.axes[0], g.rot[0][1] * g.axes[1], g.rot[0][2] * g.axes[2]],
265
+ [-g.rot[1][0] * g.axes[0], -g.rot[1][1] * g.axes[1], -g.rot[1][2] * g.axes[2]],
266
+ ];
267
+ const { rx, ry, angle } = ellipseOf(rows);
268
+ const a = g.k * rx, b = g.k * ry;
269
+ if (Math.abs(a - b) < 0.02)
270
+ return { tag: `cx="${num(g.cx, 2)}" cy="${num(g.cy, 2)}" r="${num(a, 2)}"`, d: "" };
271
+ const ax = Math.cos(angle) * a, ay = Math.sin(angle) * a;
272
+ const deg = num((angle * 180) / Math.PI, 1);
273
+ const arcs = `${num(a, 2)} ${num(b, 2)} ${deg} 1 0`;
274
+ return { tag: "", d: `M${num(g.cx + ax, 2)} ${num(g.cy + ay, 2)}A${arcs} ${num(g.cx - ax, 2)} ${num(g.cy - ay, 2)}A${arcs} ${num(g.cx + ax, 2)} ${num(g.cy + ay, 2)}Z` };
275
+ }
276
+ /** The sky and ground mirrored in a polished ball: bright above, a dark horizon, a lighter bounce below. */
277
+ const metalDef = (id, on) => on
278
+ ? `<linearGradient id="${id}m" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".6"/><stop offset=".3" stop-color="#fff" stop-opacity=".15"/><stop offset=".52" stop-color="#000" stop-opacity=".4"/><stop offset=".75" stop-color="#000" stop-opacity=".12"/><stop offset="1" stop-color="#fff" stop-opacity=".3"/></linearGradient>`
279
+ : "";
280
+ /** Draws a ball: the document for a spec, the options and a turn in radians. */
281
+ export function drawBall(title, key, spec, opts, turn, turning) {
282
+ const axes = spec.axes ?? [1, 1, 1];
283
+ const rot = rotation(turn.yaw, turn.pitch, turn.roll);
284
+ const rz = [rot[2][0], rot[2][1], rot[2][2]];
285
+ const w = [rz[0] / axes[0], rz[1] / axes[1], rz[2] / axes[2]];
286
+ const ox = spec.centre?.[0] ?? 50, oy = spec.centre?.[1] ?? 50;
287
+ const kk = (46 * (spec.fit ?? 1)) / Math.max(...axes);
288
+ const away = apply(rot, spec.at ?? [0, 0, 0]);
289
+ const g = { ox, oy, cx: ox + kk * away[0], cy: oy - kk * away[1], axes, rot, k: (46 * (spec.fit ?? 1)) / Math.max(...axes), w, wl: len(w), rz, point: spec.point };
290
+ const line = opts.style === "line";
291
+ const minWidth = 80 / opts.size;
292
+ const base = opts.tint ?? spec.base;
293
+ const surface = surfaceOf(g, line, minWidth);
294
+ const layers = spec.draw(surface, base, opts.accent);
295
+ const sil = silhouette(g);
296
+ const id = `k${hashOf(`${key}|${opts.style}|${opts.tint ?? ""}|${opts.accent ?? ""}|${sil.tag}${sil.d}`)}`;
297
+ // A long outline (the hull of a pointed ball) is written once, in the definitions, and used wherever the silhouette is.
298
+ const long = sil.d.length > 400;
299
+ const outline = long ? `<path id="${id}s" d="${sil.d}"/>` : "";
300
+ const shape = (attrs) => (sil.d === "" ? `<circle ${sil.tag} ${attrs}/>` : long ? `<use href="#${id}s" ${attrs}/>` : `<path d="${sil.d}" ${attrs}/>`);
301
+ if (line) {
302
+ const at = spec.attach?.(surface, base);
303
+ const body = `${at?.behind ?? ""}${shape(`fill="none" stroke="currentColor" stroke-width="${num(Math.max(3, minWidth * 1.6), 2)}"`)}<g>${layers}</g>${at?.front ?? ""}`;
304
+ return wrapPiece({ title, size: opts.size, theme: opts.theme, defs: outline, body: `<g fill="none" stroke-linecap="round" stroke-linejoin="round">${body}</g>` });
305
+ }
306
+ const m = spec.mat;
307
+ const shade = m.shade ?? mix(base, -0.62);
308
+ const grain = opts.grain ?? (opts.size >= 48 && !turning);
309
+ const gr = grain && m.grain !== undefined ? m.grain : undefined;
310
+ const rim = m.rim ?? 1;
311
+ const stop = (o, colour, a) => `<stop offset="${o}" stop-color="${colour}" stop-opacity="${num(a, 2)}"/>`;
312
+ const defs = outline +
313
+ `<clipPath id="${id}c">${shape("")}</clipPath>` +
314
+ `<radialGradient id="${id}d" cx=".42" cy=".36" r=".86" fx=".36" fy=".3">${stop(0, shade, 0)}${stop(0.42, shade, 0)}${stop(0.78, shade, 0.34 * rim)}${stop(1, shade, 0.78 * rim)}</radialGradient>` +
315
+ `<radialGradient id="${id}h" cx=".34" cy=".28" r=".4">${stop(0, "#fff", 0.28 + m.gloss * 0.62)}${stop(0.32, "#fff", 0.1 + m.gloss * 0.22)}${stop(1, "#fff", 0)}</radialGradient>` +
316
+ `<radialGradient id="${id}b" cx=".5" cy=".5" r=".5">${stop(0.8, "#fff", 0)}${stop(1, "#fff", 0.16)}</radialGradient>` +
317
+ (gr === undefined
318
+ ? ""
319
+ : grainDef(id, gr.f, gr.a, gr.seed));
320
+ const inner = `<g clip-path="url(#${id}c)"${gr === undefined ? "" : ` filter="url(#${id}n)"`}>${shape(`fill="${spec.ground ?? base}"`)}${layers}</g>`;
321
+ const metal = m.metal === true ? shape(`fill="url(#${id}m)"`) : "";
322
+ const overlays = shape(`fill="url(#${id}d)"`) + metal + shape(`fill="url(#${id}b)"`) + shape(`fill="url(#${id}h)"`);
323
+ const at = spec.attach?.(surface, base);
324
+ return wrapPiece({ title, size: opts.size, theme: opts.theme, defs: defs + metalDef(id, m.metal === true), body: `${at?.behind ?? ""}${inner}${overlays}${at?.front ?? ""}` });
325
+ }
326
+ const finite = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
327
+ /** A ball from its definition: the `Piece` the registry and the gallery take. */
328
+ export function defineBall(def) {
329
+ const view = def.spec.view;
330
+ const render = (o, turning) => drawBall(escape(def.en), def.id, def.spec, resolveOptions(o), { yaw: radians(view.yaw + finite(o?.yaw), 0), pitch: radians(view.pitch + finite(o?.pitch), 0), roll: radians(view.roll + finite(o?.roll), 0) }, turning);
331
+ return {
332
+ id: def.id,
333
+ en: def.en,
334
+ ja: def.ja,
335
+ source: "generated",
336
+ kind: "ball",
337
+ model: def.spec,
338
+ size: def.size,
339
+ svg: labelled(def.en, def.ja, (o) => render(o, false)),
340
+ frame: labelled(def.en, def.ja, (o) => render(o, true)),
341
+ };
342
+ }
@@ -0,0 +1,27 @@
1
+ import { type BallDef } from "./ball.engine.ts";
2
+ import { type V3 } from "./ball.math.ts";
3
+ import type { Piece } from "./ball.core.ts";
4
+ import type { BallSpec } from "./ball.engine.ts";
5
+ export interface DottedOptions {
6
+ id: string;
7
+ en: string;
8
+ ja: string;
9
+ size: BallDef["size"];
10
+ base: string;
11
+ count: number;
12
+ /** A cap's radius in radians. */
13
+ radius: number;
14
+ /** How dark a mark is against the ball: 0 to 1. */
15
+ depth?: number;
16
+ view?: BallSpec["view"];
17
+ mat: BallSpec["mat"];
18
+ /** A mark's own colour, instead of a darker ball colour (a hole shows what is inside). */
19
+ mark?: string;
20
+ /** A lit rim on each mark (not for a dimple pattern of two hundred, which is large enough already). */
21
+ light?: boolean;
22
+ /** Turns the spread, so a ball does not show its spiral's pole straight on. */
23
+ phase?: number;
24
+ }
25
+ /** The mark centres: `count` points, turned so that no pole faces the default view. */
26
+ export declare const dotCentres: (count: number, phase?: number) => V3[];
27
+ export declare function dottedBall(o: DottedOptions): Piece<BallSpec>;
@@ -0,0 +1,20 @@
1
+ // Balls covered in round marks: the holes of a pickleball, the dimples of a golf ball or a field hockey ball, the coloured dots of a squash ball. The centres are an even spread over the sphere
2
+ // (a Fibonacci spiral), and each mark is a true cap on the sphere, so it foreshortens toward the limb.
3
+ import { defineBall } from "./ball.engine.js";
4
+ import { mix } from "./ball.core.js";
5
+ import { spread } from "./ball.math.js";
6
+ /** The mark centres: `count` points, turned so that no pole faces the default view. */
7
+ export const dotCentres = (count, phase = 0) => spread(count, phase);
8
+ export function dottedBall(o) {
9
+ const centres = dotCentres(o.count, o.phase ?? 0);
10
+ const spec = {
11
+ view: o.view ?? { yaw: 20, pitch: 25, roll: 0 },
12
+ base: o.base,
13
+ mat: o.mat,
14
+ draw: (s, base) => {
15
+ const mark = o.mark ?? mix(base, -0.55);
16
+ return s.dots(centres, o.radius, mark, { opacity: o.depth ?? 0.55, ink: true, light: o.light ?? true });
17
+ },
18
+ };
19
+ return defineBall({ id: o.id, en: o.en, ja: o.ja, spec, size: o.size });
20
+ }
@@ -0,0 +1,7 @@
1
+ import type { Surface } from "./ball.engine.ts";
2
+ /** The 18 panels of a ball. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
3
+ export declare function strips18(s: Surface, colours: ReadonlyArray<readonly [string, string]>, seam: string, width?: number): string;
4
+ /** The 8 panels of an octahedral ball: the octants, coloured by `pick(sx, sy, sz)`. */
5
+ export declare function octants8(s: Surface, pick: (sx: number, sy: number, sz: number) => string, seam: string, width?: number): string;
6
+ /** A darker seam colour for a main colour. */
7
+ export declare const seamOf: (base: string, amount?: number) => string;
@@ -0,0 +1,60 @@
1
+ // Balls made of curved panels: the 18-panel layout of a volleyball (six groups of three strips; the strips of a face run square to those of its neighbours), and the 8 panels of an octahedral ball.
2
+ // The strips are the faces of a cube mapped onto the sphere (a straight line on a face becomes an arc of a great circle), so every seam is a true great-circle arc.
3
+ import { arc, greatCircle, norm } from "./ball.math.js";
4
+ import { mix } from "./ball.core.js";
5
+ /** The six faces of the cube as: the axis the face looks along, its sign, the axis its strips run along, and the axis across them. Neighbouring faces run square to each other. */
6
+ const FACES = [
7
+ { a: 2, sign: 1, p: 1, q: 0 },
8
+ { a: 2, sign: -1, p: 1, q: 0 },
9
+ { a: 0, sign: 1, p: 2, q: 1 },
10
+ { a: 0, sign: -1, p: 2, q: 1 },
11
+ { a: 1, sign: 1, p: 0, q: 2 },
12
+ { a: 1, sign: -1, p: 0, q: 2 },
13
+ ];
14
+ const SPLIT = 1 / 3;
15
+ const onFace = (f, p, q) => {
16
+ const v = [0, 0, 0];
17
+ v[f.a] = f.sign;
18
+ v[f.p] = p;
19
+ v[f.q] = q;
20
+ return norm(v);
21
+ };
22
+ /** The 18 panels of a ball. `colours[i]` is the three strips of face i, or one colour for the middle strip and one for the outer two. */
23
+ export function strips18(s, colours, seam, width = 1.1) {
24
+ let out = "";
25
+ FACES.forEach((f, fi) => {
26
+ const [outer, middle] = colours[fi % colours.length];
27
+ const bounds = [-1, -SPLIT, SPLIT, 1];
28
+ for (let i = 0; i < 3; i++) {
29
+ const ring = [onFace(f, -1, bounds[i]), onFace(f, 1, bounds[i]), onFace(f, 1, bounds[i + 1]), onFace(f, -1, bounds[i + 1])];
30
+ out += s.panel(ring, i === 1 ? middle : outer, { n: 6, ink: false });
31
+ }
32
+ });
33
+ const curves = [];
34
+ for (const f of FACES)
35
+ for (const q of [-SPLIT, SPLIT])
36
+ curves.push(arc(onFace(f, -1, q), onFace(f, 1, q), 10));
37
+ for (const a of [-1, 1])
38
+ for (const b of [-1, 1]) {
39
+ curves.push(arc(norm([a, b, -1]), norm([a, b, 1]), 12));
40
+ curves.push(arc(norm([a, -1, b]), norm([a, 1, b]), 12));
41
+ curves.push(arc(norm([-1, a, b]), norm([1, a, b]), 12));
42
+ }
43
+ out += s.strokes(curves, seam, width);
44
+ out += s.strokes(curves, "#fff", width * 0.35, { deco: true, opacity: 0.4, dx: 0.25, dy: 0.3 });
45
+ return out;
46
+ }
47
+ /** The 8 panels of an octahedral ball: the octants, coloured by `pick(sx, sy, sz)`. */
48
+ export function octants8(s, pick, seam, width = 1.1) {
49
+ let out = "";
50
+ for (const sx of [-1, 1])
51
+ for (const sy of [-1, 1])
52
+ for (const sz of [-1, 1])
53
+ out += s.panel([[sx, 0, 0], [0, sy, 0], [0, 0, sz]], pick(sx, sy, sz), { n: 12 });
54
+ const circles = [greatCircle([1, 0, 0], 48), greatCircle([0, 1, 0], 48), greatCircle([0, 0, 1], 48)];
55
+ out += s.strokes(circles, seam, width, { closed: true });
56
+ out += s.strokes(circles, "#fff", width * 0.35, { closed: true, deco: true, opacity: 0.4, dx: 0.25, dy: 0.3 });
57
+ return out;
58
+ }
59
+ /** A darker seam colour for a main colour. */
60
+ export const seamOf = (base, amount = -0.45) => mix(base, amount);
@@ -0,0 +1,16 @@
1
+ import type { V3 } from "./ball.math.ts";
2
+ import type { Surface } from "./ball.engine.ts";
3
+ /** The point on the meridian at angle `a` from the top (+y) toward +x, in the plane z = 0. */
4
+ export declare const meridian: (a: number) => V3;
5
+ /** The lace of an American or Canadian football: `count` laces across a slot along the top meridian. */
6
+ export declare function laces(s: Surface, lace: string, slot: string, count?: number, span?: number, reach?: number): string;
7
+ /** The four seams of a panelled football: two great circles through the tips, at right angles. */
8
+ export declare const footballSeams: (rotate?: number) => V3[][];
9
+ /** A ring round the ball, near one tip: the white stripes of a Canadian football. */
10
+ export declare const tipRing: (sign: 1 | -1, angle: number, n?: number) => V3[];
11
+ /** A point on the long axis' meridian at `lon` round the axis, `along` radians from the equator toward +x. */
12
+ export declare const onBall: (along: number, round: number) => V3;
13
+ /** The panel between the seam at `round` radians round the long axis and the seam a quarter turn on: a lune from tip to tip. Its first three points are the first seam. */
14
+ export declare const lune: (round: number) => V3[];
15
+ /** The seam at `round` radians round the long axis: from one tip to the other, sampled along the sphere. */
16
+ export declare const seamAt: (round: number) => V3[];
@@ -0,0 +1,29 @@
1
+ // The prolate balls: American and Canadian football, rugby and Australian football. Each is an ellipsoid longer along x, with four panels whose seams run tip to tip.
2
+ import { add, arc, greatCircle, norm, slerp, smallCircle } from "./ball.math.js";
3
+ /** The point on the meridian at angle `a` from the top (+y) toward +x, in the plane z = 0. */
4
+ export const meridian = (a) => [Math.sin(a), Math.cos(a), 0];
5
+ /** The lace of an American or Canadian football: `count` laces across a slot along the top meridian. */
6
+ export function laces(s, lace, slot, count = 8, span = 0.62, reach = 0.17) {
7
+ const top = Array.from({ length: 25 }, (_, i) => meridian(-span + (2 * span * i) / 24));
8
+ let out = s.stroke(top, slot, 1.2, { ink: true });
9
+ const cross = [];
10
+ for (let i = 0; i < count; i++) {
11
+ const a = -span * 0.82 + (2 * span * 0.82 * i) / (count - 1);
12
+ const m = meridian(a);
13
+ cross.push([norm(add(m, [0, 0, -reach])), norm(add(m, [0, 0, reach]))]);
14
+ }
15
+ out += s.stroke(top, lace, 1.7, { ink: true });
16
+ for (const c of cross)
17
+ out += s.seam([c[0], slerp(c[0], c[1], 0.5), c[1]], lace, 1.9);
18
+ return out;
19
+ }
20
+ /** The four seams of a panelled football: two great circles through the tips, at right angles. */
21
+ export const footballSeams = (rotate = 0) => [greatCircle([0, Math.cos(rotate), Math.sin(rotate)], 96), greatCircle([0, -Math.sin(rotate), Math.cos(rotate)], 96)];
22
+ /** A ring round the ball, near one tip: the white stripes of a Canadian football. */
23
+ export const tipRing = (sign, angle, n = 72) => smallCircle([sign, 0, 0], angle, n);
24
+ /** A point on the long axis' meridian at `lon` round the axis, `along` radians from the equator toward +x. */
25
+ export const onBall = (along, round) => norm([Math.sin(along), Math.cos(along) * Math.cos(round), Math.cos(along) * Math.sin(round)]);
26
+ /** The panel between the seam at `round` radians round the long axis and the seam a quarter turn on: a lune from tip to tip. Its first three points are the first seam. */
27
+ export const lune = (round) => [onBall(Math.PI / 2, round), onBall(0, round), onBall(-Math.PI / 2, round), onBall(0, round + Math.PI / 2)];
28
+ /** The seam at `round` radians round the long axis: from one tip to the other, sampled along the sphere. */
29
+ export const seamAt = (round) => [...arc(onBall(Math.PI / 2, round), onBall(0, round), 10), ...arc(onBall(0, round), onBall(-Math.PI / 2, round), 10).slice(1)];
@@ -0,0 +1,31 @@
1
+ import { type BallDef } from "./ball.engine.ts";
2
+ import type { Piece } from "./ball.core.ts";
3
+ import type { BallSpec } from "./ball.engine.ts";
4
+ export interface SeamedOptions {
5
+ id: string;
6
+ en: string;
7
+ ja: string;
8
+ size: BallDef["size"];
9
+ base: string;
10
+ /** The seam's parameter b. */
11
+ b: number;
12
+ view?: BallSpec["view"];
13
+ mat: BallSpec["mat"];
14
+ /** A strip along the seam: its colour and width. */
15
+ strip?: {
16
+ colour: string;
17
+ width: number;
18
+ raised?: boolean;
19
+ };
20
+ /** Stitches across the seam: colour, count, the half-length across it (radians) and the stroke width. */
21
+ stitch?: {
22
+ colour: string;
23
+ count: number;
24
+ half: number;
25
+ width: number;
26
+ lean: number;
27
+ };
28
+ /** A thin groove along the seam. */
29
+ groove?: string;
30
+ }
31
+ export declare function seamedBall(o: SeamedOptions): Piece<BallSpec>;