@bitefoundry/retrieviewer 0.0.2 → 0.0.4

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 (261) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +75 -0
  6. package/INSTALLING.md +8 -5
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +110 -0
  11. package/README.md +119 -58
  12. package/RELEASING.md +27 -14
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/desktop/platform.mjs +27 -0
  17. package/integrations/linux-overlay/README.md +83 -0
  18. package/integrations/linux-overlay/overlay.py +1094 -0
  19. package/integrations/macos-overlay/README.md +83 -0
  20. package/integrations/macos-overlay/overlay.jxa +639 -0
  21. package/integrations/opencode/runtime.mjs +4 -4
  22. package/integrations/opencode/tui.tsx +1 -1
  23. package/integrations/windows-overlay/Overlay.cs +4 -4
  24. package/integrations/windows-overlay/desktop.mjs +40 -8
  25. package/integrations/windows-overlay/protocol.mjs +11 -7
  26. package/integrations/windows-overlay/worker.mjs +13 -7
  27. package/package.json +41 -14
  28. package/puppy-comfort-sprites.mjs +25 -0
  29. package/puppy-companion-comfort-q5.bin.gz +0 -0
  30. package/puppy-companion-comfort.json +77 -0
  31. package/puppy-companion-life.mjs +49 -16
  32. package/puppy-companion-objects.mjs +12 -0
  33. package/puppy-frame-predictive.mjs +15 -9
  34. package/puppy-frame-store.mjs +2 -2
  35. package/puppy-paper-world.mjs +14 -0
  36. package/puppy-paper.mjs +65 -14
  37. package/retrieviewer-cli.mjs +10 -3
  38. package/retrieviewer-plugin.d.mts +1 -1
  39. package/retrieviewer-plugin.mjs +1 -1
  40. package/retrieviewer.d.mts +10 -4
  41. package/retrieviewer.mjs +20 -14
  42. package/locomotion-core.mjs +0 -21
  43. package/locomotion-puppy.animal +0 -0
  44. package/vendor/procedural-animals/package.json +0 -40
  45. package/vendor/procedural-animals/src/animal.js +0 -146
  46. package/vendor/procedural-animals/src/bake.js +0 -51
  47. package/vendor/procedural-animals/src/build.js +0 -81
  48. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  49. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  50. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  51. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  52. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  53. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  54. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  55. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  56. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  57. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  58. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  59. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  60. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  61. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  62. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  63. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  64. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  65. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  66. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  67. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  68. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  69. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  70. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  71. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  72. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  73. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  74. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  75. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  76. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  77. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  79. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  80. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  81. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  82. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  83. package/vendor/procedural-animals/src/index.d.ts +0 -98
  84. package/vendor/procedural-animals/src/index.js +0 -29
  85. package/vendor/procedural-animals/src/registry.js +0 -48
  86. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  87. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  88. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  89. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  90. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  91. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  92. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  94. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  95. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  96. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  97. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  98. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  99. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  100. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  101. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  102. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  103. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  104. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  105. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  106. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  107. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  108. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  109. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  110. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  111. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  112. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  113. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  114. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  115. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  116. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  117. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  118. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  119. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  120. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  121. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  122. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  123. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  124. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  125. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  126. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  127. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  128. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  129. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  130. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  131. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  132. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  133. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  134. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  135. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  136. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  137. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  138. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  139. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  140. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  141. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  142. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  143. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  144. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  145. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  146. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  147. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  148. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  149. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  150. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  151. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  152. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  153. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  154. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  155. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  156. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  157. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  158. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  159. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  160. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  161. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  162. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  163. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  164. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  165. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  166. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  167. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  168. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  169. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  170. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  171. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  172. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  173. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  174. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  175. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  176. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  177. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  178. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  179. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  180. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  181. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  182. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  183. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  184. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  185. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  186. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  187. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  188. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  189. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  190. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  191. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  192. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  193. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  194. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  195. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  196. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  197. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  198. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  199. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  200. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  201. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  202. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  203. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  204. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  205. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  206. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  207. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  208. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  209. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  210. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  211. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  212. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  213. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  214. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  215. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  216. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  217. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  218. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  219. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  220. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  221. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  222. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  223. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  224. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  225. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  226. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  227. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  228. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  229. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  230. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  231. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  232. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  233. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  234. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  235. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  236. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  237. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  238. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  239. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  240. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  241. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  242. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  243. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  244. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  245. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  246. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  247. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  248. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  249. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  250. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  251. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  252. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  253. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  254. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  255. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  256. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  257. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  258. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  259. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  260. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  261. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,81 +0,0 @@
1
- // Cow motion data for the quadruped engine (schema: src/core/motion/quadruped.js).
2
- // Numbers are for the reference individual (params.size = 1, a 1.45 m Holstein cow); the engine
3
- // scales them by dynamic similarity for calves, Jerseys and bulls.
4
- //
5
- // Gaits: lateral-sequence 4-beat walk (preferred ~1.2 m/s, stride
6
- // ~0.8 Hz, duty 0.66), a short 2-beat trot (to feed, 2-3.5 m/s), and a clumsy canter / transverse
7
- // gallop (right lead) up to ~11 m/s that cattle rarely use. Walk -> trot at Froude ~0.4 (~1.8 m/s).
8
- import { behaviour } from './behaviour.js';
9
-
10
- const G = (name, v, f, D, off, o) => ({ name, v, f, D, off, ...o });
11
-
12
- export const motion = {
13
- maxSpeed: 11,
14
- accel: [2.2, 4],
15
- decel: 5,
16
- turnRate: 1.3,
17
- latAccelMax: 5,
18
- gears: { walk: 1.2, trot: 2.6, canter: 4.6, gallop: 8 },
19
- gallopBlend: [4.2, 8],
20
- // name v f D [FL, FR, HL, HR]
21
- gaits: [
22
- G('walk', 0.0, 0.72, 0.7, [0.25, 0.75, 0.0, 0.5], { liftF: 0.07, liftH: 0.06, bobF: 0.025, bobH: 0.03, flex: 0, lat: 0.03, fold: 0.6, heel: 28, tail: 0, drop: 0 }),
23
- G('walk', 1.5, 0.9, 0.64, [0.25, 0.75, 0.0, 0.5], { liftF: 0.09, liftH: 0.08, bobF: 0.045, bobH: 0.05, flex: 0, lat: 0.04, fold: 0.72, heel: 36, tail: 0.1, drop: 0 }),
24
- G('trot', 2.0, 1.35, 0.5, [0.52, 0.02, 0.0, 0.5], { liftF: 0.12, liftH: 0.1, bobF: 0.06, bobH: 0.06, flex: 0.01, lat: 0.0, fold: 0.95, heel: 45, tail: 0.3, drop: 0.015 }),
25
- G('trot', 3.3, 1.55, 0.43, [0.52, 0.02, 0.0, 0.5], { liftF: 0.15, liftH: 0.12, bobF: 0.07, bobH: 0.07, flex: 0.015, lat: 0.0, fold: 1.05, heel: 50, tail: 0.4, drop: 0.02 }),
26
- G('canter', 4.0, 1.7, 0.42, [0.3, 0.55, 0.0, 0.27], { liftF: 0.17, liftH: 0.14, bobF: 0.04, bobH: 0.055, flex: 0.05, lat: 0, fold: 1.1, heel: 50, tail: 0.55, drop: 0.03 }),
27
- G('canter', 5.6, 1.8, 0.37, [0.3, 0.55, 0.0, 0.25], { liftF: 0.19, liftH: 0.16, bobF: 0.045, bobH: 0.06, flex: 0.065, lat: 0, fold: 1.15, heel: 45, tail: 0.65, drop: 0.035 }),
28
- G('gallop', 7.0, 1.95, 0.31, [0.38, 0.52, 0.0, 0.12], { liftF: 0.21, liftH: 0.18, bobF: 0.05, bobH: 0.065, flex: 0.09, lat: 0, fold: 1.25, heel: 40, tail: 0.8, drop: 0.05 }),
29
- G('gallop', 11, 2.15, 0.25, [0.4, 0.52, 0.0, 0.13], { liftF: 0.23, liftH: 0.2, bobF: 0.05, bobH: 0.07, flex: 0.11, lat: 0, fold: 1.3, heel: 40, tail: 1.0, drop: 0.07 }),
30
- ],
31
- feet: {
32
- // cloven hooves on short pasterns: the fetlock sinks a little under load, the claws flip in swing
33
- front: { type: 'unguligrade', contact: 0.9, curl: 65, flex: 4, sink: 14, hoofFlex: 20, swingBack: 0.16 },
34
- hind: { type: 'unguligrade', contact: 0.9, curl: 65, flex: 4, sink: 12, hoofFlex: 20, swingBack: 0.04 },
35
- },
36
- stance: { width: 1, gallopWidth: 0.85, sprawl: 0, crouch: 0, scuff: [14, 24, 0] },
37
- // torso cross-sections (bind pose, reference size): centre height, half height, half width
38
- body: {
39
- chest: { y: 1.03, hh: 0.42, hw: 0.42 },
40
- mid: { y: 1.0, hh: 0.4, hw: 0.37 },
41
- pelvis: { y: 1.14, hh: 0.5, hw: 0.4 },
42
- back: 0.12,
43
- },
44
- head: {
45
- // [speed, raise (m), reach (m), pitch (rad, + = nose down)]: low and forward at the walk
46
- // (nodding, see the hook), a little higher at the trot, up and stretched in the gallop
47
- carriage: [[0, 0, 0, 0], [1.2, -0.05, 0.06, 0.04], [2.6, -0.02, 0.05, 0.0], [4.6, 0.02, 0.08, -0.05], [9, -0.04, 0.14, 0.0]],
48
- stab: [[0, 0], [1.2, 0.1], [2.6, 0.4], [4.6, 0.3], [9, 0.3]],
49
- neckBend: 34, neckPivot: 0.55, yaw: 0.7, pitchUp: 0.55, pitchDown: 0.9, mouth: 'jawTip',
50
- },
51
- tail: {
52
- // [speed, pitch per segment from horizontal (deg), tail head -> switch]: hangs straight down at
53
- // rest, lifts and streams out behind when running
54
- carriage: [
55
- [0.0, [-38, -68, -80, -85, -87, -88, -89, -89, -90, -90, -90, -90]],
56
- [1.2, [-34, -64, -78, -84, -86, -87, -88, -89, -89, -90, -90, -90]],
57
- [2.6, [-32, -48, -62, -71, -76, -80, -83, -85, -86, -87, -88, -88]],
58
- [4.6, [-30, -30, -42, -52, -58, -62, -66, -69, -72, -74, -76, -78]],
59
- [9, [-24, -10, -16, -24, -30, -34, -37, -40, -42, -44, -46, -48]],
60
- ],
61
- stiffness: 0.75, sway: 6, flick: 0, wag: 0, swish: 1, curl: 0, balance: 0,
62
- radius: [0.05, 0.05], // (bony tail, then the switch)
63
- },
64
- ears: { rest: [0, 0], mobility: 1.1, speedFlatten: 0.2, twitch: 1.3, prick: 0.4 },
65
- breath: { rate: 0.3, amp: 0.009, pant: false, pantRate: 1.4, pantAmp: 0.02, heatSpeed: 9 },
66
- actions: {
67
- // grazing / drinking: the long head hangs near vertical, the tongue wraps the grass (hook)
68
- eat: { style: 'graze', dropF: 0.05, dropH: 0, pitch: 1.0, ahead: 0.62 },
69
- drink: { dropF: 0.06, dropH: 0, pitch: 0.95, ahead: 0.62 },
70
- // cattle jump reluctantly: a low, heavy hop, landing on the forelegs
71
- jump: { height: 0.55, distance: 2.2, land: [0.22, 0.18] },
72
- sit: { dropH: 0.8 },
73
- // kneels on both front knees first, then lowers the hindquarters; rises hind end first
74
- lie: { down: 'front', up: 'hind' },
75
- sleep: { style: 'curl', down: 'front', up: 'hind' },
76
- // lying sternal: forelegs folded under the chest (carpus flexed, cannons back along the ground)
77
- fold: { front: { z: -0.24, k: 1.5, beta: 0.9 } },
78
- idle: { shift: 0.8 },
79
- },
80
- hooks: behaviour,
81
- };
@@ -1,98 +0,0 @@
1
- // Meshing plan: body at 12.2 mm (0.84 % of the withers height), head at 5.2 mm overlapping the body
2
- // across the upper neck and cross-faded, the four feet below the fetlocks at 5.5 mm (so the cleft
3
- // between the two claws and the dew claws read) cross-faded with the body, eyelid patches at 1.5 mm
4
- // (3 mm at medium: Safari and phones show medium), and rigid surfaces: the lower lip / chin (the mouth
5
- // opens and chews), the tongue, the horns, the ear tags and the nose ring.
6
- // Budgets (vertices, hero / high / medium / low / crowd: 130k / 80k / 32k / 15k / 8k): the cells of an
7
- // individual with more surface than the reference cow (a bull's crest, dewlap and thick legs, a beef
8
- // breed's width) grow with it, the small rigid parts (ear tags, nose ring) are dropped at low and crowd,
9
- // and the horns coarsen with the tier.
10
- import { EYE, hornPath } from './sculpt.js';
11
- import { HEAD_O, cowJoints } from './rig.js';
12
- import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
13
- import { norm, smoothstep } from '../../core/math/vec.js';
14
-
15
- const J = cowJoints();
16
- // the head hangs from the neck: cut just behind the poll - throat line
17
- export const NECK_CUT = { c: [0, 1.35, J.occiput[2] - 0.05], d: norm([0, 0.3, 1]), band: 0.024 };
18
- export const neckS = (x, y, z) => (x - NECK_CUT.c[0]) * NECK_CUT.d[0] + (y - NECK_CUT.c[1]) * NECK_CUT.d[1] + (z - NECK_CUT.c[2]) * NECK_CUT.d[2];
19
- // feet: below the fetlocks
20
- export const FOOT_CUT = { y: 0.115, band: 0.012 };
21
-
22
- export const EYE_PATCH = { R: 0.033, B: 0.006, h: 0.0015, centres: [1, -1].map((s) => eyeFrameOf(EYE, HEAD_O, s).c) };
23
- export function eyePatchWeight(x, y, z) {
24
- let dmin = 1e9;
25
- for (const c of EYE_PATCH.centres) dmin = Math.min(dmin, Math.hypot(x - c[0], y - c[1], z - c[2]));
26
- return 1 - smoothstep(EYE_PATCH.R - EYE_PATCH.B, EYE_PATCH.R + EYE_PATCH.B, dmin);
27
- }
28
-
29
- function boxOf(pts, pad) {
30
- const mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9];
31
- for (const p of pts) for (let i = 0; i < 3; i++) { mn[i] = Math.min(mn[i], p[i] - pad); mx[i] = Math.max(mx[i], p[i] + pad); }
32
- return [mn, mx];
33
- }
34
-
35
- export function cowRegions(rig, params, Q) {
36
- const B = NECK_CUT.band, r = Q.res, FB = FOOT_CUT.band, FY = FOOT_CUT.y;
37
- const crowd = r >= 4, far = r >= 2.5; // (low / crowd: no tongue, ear-tag or ring surfaces at crowd, none of the small ones at low)
38
- // eyelid patches at hero / high and at medium (coarser there)
39
- const patches = Q.eyePatches || r <= 2 ? EYE_PATCH.centres.map((c) => ({ c, R: EYE_PATCH.R, B: EYE_PATCH.B, h: EYE_PATCH.h * Math.max(1, r) })) : [];
40
- // an individual's extra surface over the reference cow: bulls (crest, dewlap, sheath, thick legs and
41
- // neck: +8-10 % vertices), beef breeds (a wider barrel: +3 %)
42
- const kS = 1 + 0.045 * (params.bull || 0) + 0.02 * (1 - (params.dairy ?? 1));
43
- const rigid = [];
44
- if (params.horns) {
45
- const pts = [];
46
- for (const s of [1, -1]) { const hp = hornPath(params, s); pts.push(...hp.pts); }
47
- const [bmin, bmax] = boxOf(pts, params.horns.r * 1.3 + 0.01);
48
- rigid.push({ name: 'horn', part: 'horn', bmin, bmax, h: Math.max(0.0035, params.horns.r * 0.15) * (crowd ? 2.6 : Math.min(r, 2.2)), F: 6, rigidBone: 'head' });
49
- }
50
- if (params.earTags && !far) {
51
- for (const S of ['L', 'R']) {
52
- const a = rig.J['earBase' + S], b = rig.J['earTip' + S];
53
- const [bmin, bmax] = boxOf([a, b], 0.08);
54
- rigid.push({ name: 'tag' + S, part: 'tag' + S, bmin, bmax, h: r > 1 ? 0.0032 : 0.003, F: 6, rigidBone: 'ear' + S });
55
- }
56
- }
57
- if (params.noseRing && !far) {
58
- const c = rig.J.nose;
59
- rigid.push({ name: 'ring', part: 'ring', bmin: [c[0] - 0.05, c[1] - 0.12, c[2] - 0.08], bmax: [c[0] + 0.05, c[1] + 0.03, c[2] + 0.08], h: 0.002 * Math.min(r, 1.6), F: 6, rigidBone: 'head' });
60
- }
61
- const wide = params.shaggy ? 0.1 : 0;
62
- // feet: the clip keeps cells by their centre, so the mesh can end up to 1.5 cells below it; the
63
- // feet surface must reach past the middle of the band (FY, fade 0.5) at every tier, or its open
64
- // top ring shows (crowd: 18 mm cells, 65-87 exposed edges at the fetlocks); the box ends two cells
65
- // above the clip so it never cuts the surface first
66
- const hF = crowd ? 0.02 : 0.0055 * Math.min(r, 3);
67
- const feetTop = Math.max(FY + FB, FY + 1.5 * hF + 0.004);
68
- // the neck band likewise: each surface is kept to past the middle of the band (fade 0.5 at the cut)
69
- // by 1.5 of its own cells (low / crowd: the body's 37-51 mm cells ended in its visible half along
70
- // the upper neck, 7 exposed edges)
71
- const hB = 0.0122 * r * kS, hH = 0.0052 * r * kS;
72
- const bodyTo = Math.max(B, 1.5 * hB + 0.004), headFrom = -Math.max(B, 1.5 * hH + 0.004);
73
- const bodyFrom = Math.min(FY - FB, FY - 1.5 * hB - 0.004); // (and at the fetlocks)
74
- return {
75
- jobs: [
76
- [{ name: 'body', part: 'body', bmin: [-0.5 - wide, bodyFrom - 2 * hB, -1.14], bmax: [0.5 + wide, 1.72, 1.4], h: hB, F: 4, clip: (x, y, z) => neckS(x, y, z) < bodyTo && y > bodyFrom }],
77
- [
78
- { name: 'head', part: 'body', bmin: [-0.36 - wide, 0.84, 0.86], bmax: [0.36 + wide, 1.66, 1.62], h: hH, F: 6, clip: (x, y, z) => neckS(x, y, z) > headFrom, patches },
79
- { name: 'jaw', part: 'jaw', bmin: [-0.1, 0.84, 1.1], bmax: [0.1, 1.36, 1.62], h: 0.0046 * r, F: 6, rigidBone: 'jaw' },
80
- ...(crowd ? [] : [{ name: 'tongue', part: 'tongue', bmin: [-0.06, 0.84, 1.1], bmax: [0.06, 1.36, 1.62], h: 0.0055 * Math.min(r, 2), F: 6, rigidBone: 'tongue' }]),
81
- ],
82
- [{ name: 'feet', part: 'body', bmin: [-0.3, -0.01, -0.75], bmax: [0.3, feetTop + 2 * hF, 0.86], h: hF, F: 6, clip: (x, y, z) => y < feetTop }, ...rigid],
83
- ],
84
- fade(x, y, z, name, patch) {
85
- if (name === 'body') {
86
- const sN = neckS(x, y, z);
87
- return (1 - smoothstep(-B, B, sN)) * smoothstep(FY - FB, FY + FB, y);
88
- }
89
- if (name === 'feet') return 1 - smoothstep(FY - FB, FY + FB, y);
90
- if (name === 'head') {
91
- let f = smoothstep(-B, B, neckS(x, y, z));
92
- if (patches.length) { const ew = eyePatchWeight(x, y, z); f *= patch ? ew : 1 - ew; }
93
- return f;
94
- }
95
- return 1;
96
- },
97
- };
98
- }
@@ -1,116 +0,0 @@
1
- // Cow skeleton in bind pose (metres). Forward = +Z, up = +Y, the animal's left = +X.
2
- // Reference individual: a Holstein-Friesian cow, 1.45 m at the withers, ~650 kg.
3
- // Landmarks measured on the side references (side1_holstein.jpg: 1 px ~ 2.2 mm; side_natural.jpg for
4
- // the relaxed head carriage) and bovine osteology: point of shoulder 1.00 m, elbow
5
- // 0.72, carpus 0.40, fore fetlock 0.13, stifle 0.80, hock 0.53 (point of hock 0.60), body length
6
- // (point of shoulder -> pin bone) 1.72 m, hooks (tuber coxae) 1.47 m and 0.58 m apart.
7
- //
8
- // Hooves: like the horse, a hoof bone per leg below the pastern (fpaw / hpaw = pastern: fetlock ->
9
- // coffin joint; fhoof / hhoof = the claw pair: coffin joint -> toe), so the engine keeps the claws
10
- // flat while the fetlock sinks under load. Cattle are cloven: the sculpt splits each hoof into two
11
- // claws (digits III and IV) with two dew claws behind the fetlock; they share one hoof bone.
12
- //
13
- // Extra bones: a tongue (on the jaw, meshed as its own rigid surface, hidden in the mouth until it
14
- // wraps grass or licks the nose).
15
- import { buildRig } from '../../core/rig/rig.js';
16
- import { quadrupedBones, quadrupedLimbs, tailChain } from '../../core/rig/quadruped.js';
17
-
18
- export const TAIL_SEGS = 12; // 10 caudal segments (the bony tail to the hocks) + 2 of switch hair
19
- export const BONE_SEGS = 10;
20
- export const TAIL_PITCH = [-38, -68, -80, -85, -87, -88, -89, -89, -90, -90, -90, -90];
21
- export const TAIL_LENS = [0.075, 0.072, 0.07, 0.068, 0.066, 0.064, 0.062, 0.06, 0.058, 0.056, 0.16, 0.16];
22
-
23
- // Head frame: the head is modelled along its own axis (poll -> muzzle), pitched 50 degrees nose down
24
- // in the bind pose (relaxed standing carriage, research 3: nasal line 50-70 deg below horizontal).
25
- // Head-local coordinates: x lateral, y dorsal (forehead side), z along the nasal line toward the
26
- // muzzle; origin on the axis 0.17 m in front of the poll (level with the eyes).
27
- export const HEAD_PITCH = (50 * Math.PI) / 180;
28
- export const HZ = [0, -Math.sin(HEAD_PITCH), Math.cos(HEAD_PITCH)];
29
- export const HY = [0, Math.cos(HEAD_PITCH), Math.sin(HEAD_PITCH)];
30
- const POLL = [0, 1.46, 1.13]; // top of the poll (skin), bind
31
- export const HEAD_O = [0, POLL[1] + 0.17 * HZ[1], POLL[2] + 0.17 * HZ[2]];
32
- export const hl = (v) => [HEAD_O[0] + v[0], HEAD_O[1] + v[1] * HY[1] + v[2] * HZ[1], HEAD_O[2] + v[1] * HY[2] + v[2] * HZ[2]];
33
-
34
- export function cowJoints() {
35
- const J = {
36
- nose: hl([0, -0.01, 0.37]),
37
- occiput: hl([0, -0.035, -0.14]),
38
- neckMid: [0, 1.3, 0.9],
39
- neckBase: [0, 1.2, 0.64],
40
- chestMid: [0, 1.3, 0.34],
41
- thoraxRear: [0, 1.33, 0.0],
42
- lumbarMid: [0, 1.355, -0.27],
43
- lumbosacral: [0, 1.37, -0.5],
44
- tailBase: [0, 1.37, -0.88],
45
- // fore: scapula slopes ~55 deg, humerus down and back, radius and cannon near vertical, short
46
- // pastern ~50 deg to the ground
47
- scapTopL: [0.12, 1.36, 0.42],
48
- shoulderL: [0.19, 1.0, 0.74],
49
- elbowL: [0.18, 0.72, 0.58],
50
- wristL: [0.155, 0.4, 0.63],
51
- mcpL: [0.15, 0.13, 0.65],
52
- fcoffinL: [0.15, 0.052, 0.705],
53
- ftoeL: [0.15, 0.004, 0.785],
54
- // hind: hip joint, stifle, hock, fetlock, coffin joint, toe (the stifle well forward under the
55
- // flank, the straight-ish dairy hock)
56
- hipL: [0.2, 1.2, -0.6],
57
- kneeL: [0.235, 0.8, -0.4],
58
- hockL: [0.17, 0.53, -0.71],
59
- mtpL: [0.17, 0.13, -0.665],
60
- hcoffinL: [0.17, 0.052, -0.61],
61
- htoeL: [0.17, 0.004, -0.53],
62
- jawHinge: hl([0, -0.065, -0.075]),
63
- jawTip: hl([0, -0.118, 0.335]),
64
- tongueBase: hl([0, -0.085, 0.12]),
65
- tongueTip: hl([0, -0.09, 0.3]),
66
- // ears stick out sideways below the poll, nearly horizontal, tipped a little down and back
67
- earBaseL: hl([0.085, 0.01, -0.125]),
68
- };
69
- J.earTipL = [J.earBaseL[0] + 0.2, J.earBaseL[1] - 0.025, J.earBaseL[2] - 0.045];
70
- // the tail head is set high between the pin bones; the tail hangs straight down to the hocks,
71
- // the switch below it
72
- tailChain(J, 'tailBase', TAIL_PITCH, TAIL_LENS);
73
- return J;
74
- }
75
-
76
- export function cowBones() {
77
- const b = quadrupedBones({ tailSegs: TAIL_SEGS, ears: true, jaw: true });
78
- for (const d of b) {
79
- if (d[0] === 'fpaw{S}') { d[2] = 'fcoffin{S}'; d[4] = { region: 'limb', group: 'F{S}' }; }
80
- if (d[0] === 'hpaw{S}') { d[2] = 'hcoffin{S}'; d[4] = { region: 'limb', group: 'H{S}' }; }
81
- }
82
- b.push(
83
- ['fhoof{S}', 'fcoffin{S}', 'ftoe{S}', 'fpaw{S}', { region: 'foot', group: 'F{S}' }],
84
- ['hhoof{S}', 'hcoffin{S}', 'htoe{S}', 'hpaw{S}', { region: 'foot', group: 'H{S}' }],
85
- ['tongue', 'tongueBase', 'tongueTip', 'jaw', { region: 'jaw', group: 'jaw' }],
86
- );
87
- return b;
88
- }
89
-
90
- export function cowRig(J = cowJoints()) {
91
- const limbs = quadrupedLimbs({
92
- front: { tCore: 0.55, aCore: 0.055, aBody: -0.1, midX: 0.0 },
93
- hind: { tCore: 0.5, aCore: 0.022, aBody: -0.08, midX: 0.02 },
94
- });
95
- for (const L of Object.values(limbs)) { L.bones.push((L.front ? 'fhoof' : 'hhoof') + L.S); L.distal.push((L.front ? 'fhoof' : 'hhoof') + L.S); }
96
- return buildRig({
97
- joints: J,
98
- unit: 1.45 / 0.76,
99
- headOrigin: HEAD_O,
100
- bones: cowBones(),
101
- axial: {
102
- points: ['nose', 'occiput', 'neckMid', 'neckBase', 'chestMid', 'thoraxRear', 'lumbarMid', 'lumbosacral', 'tailBase', ...Array.from({ length: TAIL_SEGS }, (_, i) => 'tail' + (i + 1))],
103
- bones: ['head', 'neck2', 'neck1', 'chest', 'spine3', 'spine2', 'spine1', 'pelvis', ...Array.from({ length: TAIL_SEGS }, (_, i) => 'tail' + i)],
104
- // the skull's influence reaches back into the throat (the deep jowl and throat skin), the
105
- // neck base blends broadly into the chest and the dewlap
106
- blend: { 1: [-0.06, 0.12, 5], 3: [-0.12, 0.12, 2] },
107
- bodyTail: 0, // the tail hangs between the pin bones: body skin blends into it only at the tail head
108
- },
109
- limbs,
110
- appendages: [
111
- { group: 'earL', bone: 'earL', blend: [-0.003, 0.006] },
112
- { group: 'earR', bone: 'earR', blend: [-0.003, 0.006] },
113
- ],
114
- webTags: ['flankfold', 'thighfront', 'scapmuscle', 'pectoral', 'forearmweb'],
115
- });
116
- }
@@ -1,339 +0,0 @@
1
- // The cow, sculpted as a signed distance field.
2
- // Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw). The head
3
- // is modelled in its own frame (rig.js: hl()), measured from the profile and frontal references; the
4
- // body relative to the joints so the sculpt follows the skeleton.
5
- //
6
- // Individual knobs (params, see index.js): dairy (0 beef .. 1 dairy: angular frame, prominent hooks
7
- // and pins, visible ribs, big udder), bull (0 cow .. 1 bull: crest over the neck and shoulders, deep
8
- // forehead, sheath and scrotum instead of an udder), udder (size), dewlap, horns ({ style, len, r }),
9
- // polled peak, ear tags, nose ring, shaggy (Highland coat volume).
10
- import { HEAD_O, HZ, HY, hl, TAIL_SEGS, BONE_SEGS } from './rig.js';
11
- import { add, sub, mul, lerp, norm, cross, rng } from '../../core/math/vec.js';
12
- import { sculptEyeSocket, apertureTiltAlong } from '../../core/sdf/eyeSocket.js';
13
-
14
- // Eye (head-local centre -> offset from HEAD_O). Globe ~34 mm axial, visible opening ~35 x 22 mm,
15
- // set at the edge of the broad flat forehead and bulging out of the head's outline in front view.
16
- const EYE_LOCAL = [0.111, 0.004, 0.0];
17
- const eyeDir = (() => {
18
- // lateral, 18 degrees forward along the nasal line, a little up
19
- const a = (18 * Math.PI) / 180;
20
- return norm(add(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))), mul(HY, 0.12)));
21
- })();
22
- const EYE_BASE = {
23
- c: sub(hl(EYE_LOCAL), HEAD_O), r: 0.02, back: 0.004, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
24
- lid: 0.0024, R: 0.0215, d: 0.0094, off: -0.0008, tilt: 0, irisZ: 0.011, irisR: 0.0146,
25
- };
26
- // the almond's roll: in the relaxed carriage (nasal line 50 deg below horizontal) the slit is about
27
- // level, the inner corner a little lower (side1_holstein.jpg, head raised to ~30 deg: the slit rises
28
- // ~20 deg toward the nose); i.e. 0.85 rad up from the nasal line, the upper lid up
29
- EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) - 0.85;
30
- export const EYE = EYE_BASE;
31
-
32
- const X = [1, 0, 0];
33
- const UP = [0, 1, 0];
34
- const FWD = [0, 0, 1];
35
-
36
- // horn base on the poll (head-local) and the horn's centre line (world, bind), per side
37
- export function hornPath(params, s) {
38
- const H = params.horns;
39
- if (!H) return null;
40
- const base = hl([0.082 * s, 0.035, -0.158]);
41
- const out = [s, 0, 0];
42
- // the second direction the horn curls toward: lyre (cow: up and forward), 'down' (Hereford:
43
- // forward and down), 'bull' (short, forward), 'wide' (Highland: out, then up)
44
- const tw = {
45
- lyre: norm([0, 0.8, 0.55]), down: norm([0, -0.55, 0.85]), bull: norm([0, 0.35, 0.95]), wide: norm([0, 0.95, 0.3]),
46
- }[H.style] || norm([0, 0.8, 0.55]);
47
- const a0 = (H.a0 ?? 12) * Math.PI / 180, a1 = (H.a1 ?? 95) * Math.PI / 180;
48
- const n = 7, pts = [], rads = [];
49
- let p = add(base, mul(out, -0.03)); // rooted inside the skull
50
- pts.push(p); rads.push(H.r * 1.05);
51
- for (let i = 1; i <= n; i++) {
52
- const t = (i - 0.5) / n;
53
- const a = a0 + (a1 - a0) * Math.pow(t, H.curlPow ?? 1.3);
54
- const d = norm(add(mul(out, Math.cos(a)), mul(tw, Math.sin(a))));
55
- const seg = (H.len + 0.03) / n;
56
- p = add(p, mul(d, seg));
57
- pts.push(p);
58
- const u = i / n;
59
- rads.push(Math.max(0.004, H.r * (1 - 0.86 * Math.pow(u, 1.25))));
60
- }
61
- return { pts, rads, base };
62
- }
63
-
64
- export function sculptCow(m, rig, params = {}) {
65
- const J = rig.J;
66
- const calf = params.age === 'juvenile';
67
- const dairy = params.dairy ?? 1;
68
- const bull = params.bull ?? 0;
69
- const beef = 1 - dairy;
70
- const R = rng(5113 + (params.coatSeed || 0));
71
-
72
- const ellY = (o) => {
73
- const y = norm(o.ydir);
74
- const lat = o.lateral || X;
75
- const z = norm(cross(lat, y));
76
- return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
77
- };
78
- // ellipsoid in head-local coordinates: r = [lateral, dorsal, along the head]
79
- const H = { bone: 'head' };
80
- const hdir = (v) => [v[0], v[1] * HY[1] + v[2] * HZ[1], v[1] * HY[2] + v[2] * HZ[2]];
81
- const hell = (o) => m.ell({ ...H, ...o, c: hl(o.c), axis: o.axisL ? norm(hdir(o.axisL)) : HZ, up: o.upL ? norm(hdir(o.upL)) : HY });
82
-
83
- // ---------------------------------------------------------------- TORSO
84
- // the barrel: deep (chest depth ~0.8 m, belly ~0.63 m above the ground), wide (0.62 m), deepest
85
- // behind the ribs; the dairy wedge: the body gets deeper and wider toward the rear
86
- const wB = 1 + 0.06 * beef + 0.04 * bull;
87
- m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 1.04, 0.12], r: [0.3 * wB, 0.4, 0.56], axis: norm([0, 0.04, 1]), k: 0 });
88
- m.ell({ tag: 'girth', bone: 'chest', c: [0, 1.02, 0.44], r: [0.25 * wB, 0.33, 0.24], k: 0.1 });
89
- m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 1.0, -0.22], r: [0.33 * wB, 0.37, 0.36], axis: norm([0, 0.1, -1]), k: 0.12 });
90
- m.ell({ tag: 'belly', bone: 'spine2', c: [0, 0.84, -0.08], r: [0.27 * wB, 0.2, 0.42], k: 0.16 });
91
- m.ell({ tag: 'flank', bone: 'spine1', c: [0, 1.17, -0.45], r: [0.28 * wB, 0.24, 0.24], k: 0.12 });
92
- // withers: the dorsal spines of T2-T6 between the scapulae, sharper in dairy cows
93
- m.ell({ tag: 'withers', bone: 'chest', c: [0, 1.36, 0.4], r: [0.085 + 0.03 * beef, 0.1, 0.24], axis: norm([0, -0.12, 1]), k: 0.1 });
94
- m.ell({ tag: 'back', bone: 'spine3', c: [0, 1.35, 0.04], r: [0.17 + 0.05 * beef, 0.08, 0.42], k: 0.12 });
95
- m.ell({ tag: 'loin', bone: 'spine1', c: [0, 1.38, -0.28], r: [0.21 + 0.04 * beef, 0.07, 0.24], k: 0.12 });
96
- // hindquarters: the rump runs level from the hooks (tuber coxae) to the pins (tuber ischii); the
97
- // tail head sits between the pins
98
- m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 1.25, -0.68], r: [0.25 * wB, 0.2, 0.28], k: 0.1 });
99
- m.ell({ tag: 'rump', bone: 'pelvis', c: [0, 1.38, -0.67], r: [0.2 + 0.05 * beef, 0.08, 0.28], axis: norm([0, -0.06, 1]), k: 0.1 });
100
- for (const s of [1, -1]) {
101
- m.sphere({ tag: 'hook', bone: 'pelvis', c: [0.27 * s, 1.37 + 0.012 * dairy, -0.46], rad: 0.055, k: 0.07 + 0.04 * beef });
102
- m.sphere({ tag: 'pin', bone: 'pelvis', c: [0.095 * s, 1.34, -0.915], rad: 0.05, k: 0.07 + 0.03 * beef });
103
- // quarters: the muscle between hook, pin and stifle (full and round in beef breeds)
104
- m.ell({ tag: 'quarter', bone: 'pelvis', c: [0.14 * s, 1.2, -0.78], r: [0.13 + 0.03 * beef, 0.2, 0.14 + 0.02 * beef], k: 0.1 });
105
- }
106
- m.ell({ tag: 'tailhead', bone: 'pelvis', c: [0, 1.39, -0.86], r: [0.06, 0.055, 0.08], k: 0.06 });
107
- // chest front: the brisket (sternum and its fat pad) between and in front of the forelegs
108
- m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.83, 0.6], r: [0.17 * wB, 0.17, 0.17], k: 0.1 });
109
- for (const s of [1, -1]) {
110
- m.ell({ tag: 'pectoral', bone: 'chest', c: [0.1 * s, 0.86, 0.7], r: [0.1, 0.15, 0.1], k: 0.08 });
111
- }
112
- if (!calf && bull < 0.5 && (params.udder ?? 1) > 0.05) sculptUdder(m, params.udder ?? 1);
113
- if (bull >= 0.5) {
114
- // sheath (prepuce) under the belly, scrotum between the hind legs
115
- m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.66, -0.05], r: [0.045, 0.06, 0.14], axis: norm([0, 0.35, 1]), k: 0.06 });
116
- m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.6, 0.08], r: [0.03, 0.04, 0.05], k: 0.04 });
117
- if (!calf) m.ell({ tag: 'scrotum', bone: 'pelvis', c: [0, 0.66, -0.66], r: [0.075, 0.13, 0.08], axis: norm([0, 0.1, 1]), k: 0.05 });
118
- }
119
-
120
- // ---------------------------------------------------------------- NECK
121
- // thin and long in dairy cows, short and thick in beef breeds, massive with a crest in bulls
122
- const nb = J.neckBase, nm = J.neckMid, occ = J.occiput;
123
- const nd1 = norm(sub(nm, nb)), nd2 = norm(sub(occ, nm));
124
- const nk = 1 + 0.1 * beef + 0.45 * bull;
125
- m.ell({ tag: 'neck', bone: 'neck1', c: [0, 1.16 + 0.03 * bull, 0.64], r: [0.2 * nk, 0.27 * (1 + 0.15 * bull), 0.24], axis: nd1, up: [0, 1, -0.3], k: 0.12 });
126
- m.ell({ tag: 'neck', bone: 'neck1', c: [0, 1.25, 0.84], r: [0.14 * nk, 0.21 * (1 + 0.1 * bull), 0.2], axis: nd1, up: [0, 1, -0.3], k: 0.1 });
127
- m.ell({ tag: 'neck', bone: 'neck2', c: [0, 1.31, 1.0], r: [0.11 * (1 + 0.15 * bull), 0.16, 0.14], axis: nd2, up: [0, 1, -0.2], k: 0.09 });
128
- // top of the neck from the withers to the poll; the bull's crest rises over the shoulders
129
- m.ell({ tag: 'crest', bone: 'neck1', c: [0, 1.37 + 0.07 * bull, 0.72], r: [0.08 + 0.12 * bull, 0.07 + 0.11 * bull, 0.3 + 0.04 * bull], axis: norm([0, 0.02, 1]), k: 0.1 + 0.04 * bull });
130
- m.ell({ tag: 'crest', bone: 'neck2', c: [0, 1.41 + 0.02 * bull, 0.98], r: [0.06 + 0.04 * bull, 0.05 + 0.03 * bull, 0.14], axis: FWD, k: 0.08 });
131
- if (bull > 0) m.ell({ tag: 'crest', bone: 'chest', c: [0, 1.37 + 0.03 * bull, 0.5], r: [0.2 * bull + 0.02, 0.13 * bull + 0.01, 0.28], k: 0.14 });
132
- // underline: trachea, then the dewlap, a hanging fold of skin from the throat to the brisket
133
- m.cone({ tag: 'throat', bone: 'neck1', a: [0, 1.02, 0.76], b: [0, 1.16, 0.98], ra: 0.09, rb: 0.075, k: 0.1 });
134
- m.cone({ tag: 'throat', bone: 'neck2', a: [0, 1.15, 0.98], b: hl([0, -0.16, -0.06]), ra: 0.07, rb: 0.06, k: 0.08 });
135
- const dw = params.dewlap ?? 0.5;
136
- if (dw > 0.02) {
137
- const a = [0, 0.99, 0.95], b = [0, 0.76, 0.66], mid = lerp(a, b, 0.55);
138
- const dd = norm(sub(b, a));
139
- m.ell({ tag: 'dewlap', bone: 'neck1', c: add(mid, [0, -0.04 * dw, 0.02 * dw]), r: [0.035 + 0.015 * dw, 0.07 + 0.08 * dw, 0.2], axis: dd, up: norm([0, 1, 0.9]), k: 0.07 });
140
- }
141
-
142
- // ---------------------------------------------------------------- HEAD (head-local, see rig.js)
143
- // landmarks (head_profile of face.jpg / side photos): poll ridge top y 0.06 at z -0.16, flat
144
- // forehead y 0.075 at z 0, nasal bridge 0.055 at 0.18, muzzle top 0.03 at 0.33, nose tip z 0.39,
145
- // chin y -0.14 at 0.3, jaw angle y -0.2 at z -0.03, eyes (+-0.098, 0.004, 0)
146
- const hb = 1 + 0.08 * bull; // broader, deeper bull head
147
- hell({ tag: 'poll', c: [0, 0.015, -0.14], r: [0.095 * hb, 0.055, 0.055], k: 0.05 });
148
- hell({ tag: 'cranium', c: [0, 0.0, -0.06], r: [0.1 * hb, 0.075, 0.11], k: 0.05 });
149
- hell({ tag: 'forehead', c: [0, 0.035, 0.0], r: [0.104 * hb, 0.04, 0.12], k: 0.04 });
150
- hell({ tag: 'face', c: [0, 0.01, 0.2], r: [0.08 * hb, 0.045, 0.19], k: 0.05 });
151
- hell({ tag: 'lowerface', c: [0, -0.07, 0.18], r: [0.078 * hb, 0.08, 0.17], k: 0.05 });
152
- hell({ tag: 'muzzle', c: [0, -0.04, 0.33], r: [0.086 * hb, 0.066, 0.064], k: 0.05 });
153
- hell({ tag: 'upperlip', c: [0, -0.1, 0.345], r: [0.068 * hb, 0.032, 0.046], k: 0.03 });
154
- if (params.polledPeak) hell({ tag: 'polltop', c: [0, 0.05, -0.15], r: [0.04, 0.035 + 0.01 * params.polledPeak, 0.04], k: 0.04 });
155
- if (bull > 0) hell({ tag: 'forehead', c: [0, 0.045, -0.06], r: [0.09, 0.03 * bull + 0.01, 0.1], k: 0.05 }); // curly bull forehead mat
156
- if (params.dished) hell({ tag: 'face', c: [0, 0.035, 0.1], r: [0.05, 0.02, 0.05], k: 0.03, carve: true });
157
- for (const s of [1, -1]) {
158
- // masseter: the big flat cheek; the mandible's lower border from the jaw angle to the chin
159
- hell({ tag: 'cheek', c: [0.06 * s * hb, -0.105, -0.0], r: [0.048, 0.1, 0.11], k: 0.05 });
160
- m.cone({ ...H, tag: 'mandible', a: hl([0.052 * s, -0.19, -0.04]), b: hl([0.036 * s, -0.135, 0.27]), ra: 0.036, rb: 0.024, k: 0.05 });
161
- // bony eye arch (the orbit rim stands out of the forehead's outline) and the temporal fossa
162
- hell({ tag: 'brow', c: [0.1 * s, 0.03, -0.006], r: [0.03, 0.022, 0.04], k: 0.03 });
163
- hell({ tag: 'nostrilwing', c: [0.055 * s, -0.03, 0.35], r: [0.03, 0.035, 0.038], k: 0.03 });
164
- // eye: orbit hollow, lids, almond aperture
165
- sculptEyeSocket(m, EYE, HEAD_O, s, { orbit: { r: [0.022, 0.016, 0.012], at: [0.0, 0.002, 0.022], k: 0.01 } });
166
- // nostrils: comma-shaped openings on the front corners of the moist muzzle plate
167
- hell({ tag: 'nostril', c: [0.043 * s, -0.035, 0.392], r: [0.011, 0.022, 0.02], axisL: [-0.35 * s, 0.1, 1], upL: [0.35 * s, 1, 0], k: 0.006, carve: true });
168
- // ear base muscles on the side of the poll
169
- m.sphere({ ...H, tag: 'earbase', c: add(J['earBase' + (s > 0 ? 'L' : 'R')], [-0.012 * s, 0, 0]), rad: 0.032, k: 0.03 });
170
- }
171
- // philtrum groove down the middle of the muzzle plate
172
- hell({ tag: 'philtrum', c: [0, -0.07, 0.405], r: [0.004, 0.03, 0.012], k: 0.006, carve: true });
173
- // mouth line: a shallow slit under the upper lip (the jaw part fills it)
174
- hell({ tag: 'mouthcut', c: [0, -0.13, 0.31], r: [0.06, 0.012, 0.07], k: 0.008, carve: true });
175
-
176
- // ---------------------------------------------------------------- JAW (lower lip and chin: the mouth opens, chews sideways)
177
- const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
178
- m.ell({ ...JW, tag: 'chin', c: hl([0, -0.138, 0.3]), r: [0.05, 0.038, 0.06], axis: HZ, up: HY, k: 0 });
179
- m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.125, 0.345]), r: [0.056, 0.026, 0.035], axis: HZ, up: HY, k: 0.02 });
180
- for (const s of [1, -1]) {
181
- m.cone({ ...JW, tag: 'mandible', a: hl([0.036 * s, -0.14, 0.2]), b: hl([0.03 * s, -0.135, 0.3]), ra: 0.022, rb: 0.028, k: 0.03 });
182
- }
183
- // tongue: its own rigid surface in the mouth (see regions.js); it wraps grass and licks the nose
184
- const TG = { bone: 'tongue', group: 'jaw', part: 'tongue' };
185
- m.cone({ ...TG, tag: 'tongue', a: J.tongueBase, b: J.tongueTip, ra: 0.03, rb: 0.024, k: 0.02 });
186
- m.ell({ ...TG, tag: 'tongue', c: lerp(J.tongueBase, J.tongueTip, 0.7), r: [0.036, 0.016, 0.07], axis: norm(sub(J.tongueTip, J.tongueBase)), up: HY, k: 0.02 });
187
-
188
- // ---------------------------------------------------------------- EARS (broad leaves, carried sideways)
189
- for (const S of ['L', 'R']) {
190
- const s = S === 'L' ? 1 : -1;
191
- const base = J['earBase' + S], tip = J['earTip' + S];
192
- const along = norm(sub(tip, base));
193
- // the pinna opens forward and a little down
194
- const facing = norm([0, -0.25, 1]);
195
- const lat = norm(cross(along, facing));
196
- const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
197
- const eK = calf ? 1.1 : 1;
198
- ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.5), ydir: along, lateral: lat, r: [0.056 * eK, 0.108 * eK, 0.018], k: 0.02 });
199
- ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.15), ydir: along, lateral: lat, r: [0.03, 0.05, 0.026], k: 0.02 });
200
- ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.55), mul(facing, 0.016)), ydir: along, lateral: lat, r: [0.042 * eK, 0.09 * eK, 0.013], k: 0.006, carve: true });
201
- }
202
-
203
- // ---------------------------------------------------------------- LEGS
204
- const legK = 1 + 0.08 * beef + 0.1 * bull; // bone and muscle thickness
205
- for (const S of ['L', 'R']) {
206
- const s = S === 'L' ? 1 : -1;
207
- const lat = [s, 0, 0];
208
- const sx = (v) => [v[0] * s, v[1], v[2]];
209
- // front
210
- const g = 'F' + S;
211
- const Sc = J['scapTop' + S], Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S], M = J['mcp' + S], C = J['fcoffin' + S], T = J['ftoe' + S];
212
- ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.06, 0, -0.02])), ydir: sub(Sh, Sc), lateral: lat, r: [0.08 + 0.03 * bull, 0.25, 0.15], k: 0.12, bias: 0.006 });
213
- m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.015, 0.0, 0.03])), rad: 0.075 * legK, k: 0.09 });
214
- m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.1 * legK, rb: 0.085 * legK, k: 0.09 });
215
- ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.01, 0.02, -0.09])), ydir: sub(E, Sh), lateral: lat, r: [0.1 * legK, 0.17, 0.12], k: 0.09 });
216
- m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.02, -0.07]), rad: 0.055, k: 0.05 });
217
- m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.01]), b: W, ra: 0.08 * legK, rb: 0.048 * legK, k: 0.05 });
218
- ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.25), sx([0.008, 0, 0.012])), ydir: sub(W, E), lateral: lat, r: [0.074 * legK, 0.14, 0.08 * legK], k: 0.05 });
219
- m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.04, 0.06, -0.02])), b: add(lerp(E, W, 0.3), sx([-0.02, 0, 0])), ra: 0.06, rb: 0.045, k: 0.06 });
220
- // knee (carpus): broad, the accessory carpal bone behind
221
- ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.004]), ydir: UP, lateral: lat, r: [0.053 * legK, 0.058, 0.048 * legK], k: 0.025 });
222
- m.sphere({ tag: 'accessory', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.03, -0.042]), rad: 0.024, k: 0.02 });
223
- // cannon (fused metacarpals III+IV): short, broad, flat in front
224
- m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.02, 0.0]), b: add(M, [0, 0.02, 0.0]), ra: 0.036 * legK, rb: 0.034 * legK, k: 0.02 });
225
- m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.03, -0.028]), b: add(M, [0, 0.03, -0.03]), ra: 0.022 * legK, rb: 0.026 * legK, k: 0.02 });
226
- ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.01]), ydir: [0, 1, 0.3], lateral: lat, r: [0.048 * legK, 0.048, 0.05], k: 0.02 });
227
- sculptDigits(m, 'fpaw' + S, 'fhoof' + S, g, M, C, T, s, 1.0 * legK);
228
-
229
- // hind
230
- const h = 'H' + S;
231
- const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S], Mt = J['mtp' + S], Ch = J['hcoffin' + S], Tt = J['htoe' + S];
232
- // thigh: flat and lean in dairy cows (the "incurving thigh"), full and round in beef breeds
233
- ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.02 + 0.02 * beef, 0, -0.05])), ydir: sub(K, Hp), lateral: lat, r: [0.08 + 0.04 * beef, 0.3, 0.2 + 0.03 * beef], k: 0.1, bias: 0.006 });
234
- m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.22, 1.25, -0.46]), b: add(K, sx([0.0, 0.06, 0.04])), ra: 0.11, rb: 0.07, k: 0.1 });
235
- m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.11, 1.26, -0.84]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.08]), ra: 0.1 + 0.02 * beef, rb: 0.06, k: 0.08 });
236
- ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.22, 0.92, -0.3]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.05, 0.15, 0.08], k: 0.1 });
237
- m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.01, 0.01, 0.03])), rad: 0.06, k: 0.07 });
238
- ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.32), sx([0.006, 0, -0.045])), ydir: sub(Hk, K), lateral: lat, r: [0.072 * legK, 0.16, 0.09 * legK], k: 0.06 });
239
- m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.06 * legK, rb: 0.046 * legK, k: 0.05 });
240
- m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.075]), b: add(Hk, [0, 0.07, -0.07]), ra: 0.03, rb: 0.024, k: 0.03 });
241
- m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.07, -0.066]), rad: 0.034, k: 0.025 });
242
- ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.01, -0.005]), ydir: [0, 1, 0.25], lateral: lat, r: [0.052 * legK, 0.075, 0.055], k: 0.03 });
243
- m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, 0.005]), b: add(Mt, [0, 0.02, 0.0]), ra: 0.036 * legK, rb: 0.033 * legK, k: 0.02 });
244
- m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, -0.034]), b: add(Mt, [0, 0.03, -0.032]), ra: 0.022 * legK, rb: 0.026 * legK, k: 0.02 });
245
- ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.01]), ydir: [0, 1, 0.3], lateral: lat, r: [0.047 * legK, 0.048, 0.05], k: 0.02 });
246
- sculptDigits(m, 'hpaw' + S, 'hhoof' + S, h, Mt, Ch, Tt, s, 0.97 * legK);
247
- }
248
-
249
- // ---------------------------------------------------------------- TAIL: tail head, bony tail, switch
250
- const tailK = calf ? 0.9 : 1;
251
- for (let i = 0; i < BONE_SEGS; i++) {
252
- const t0 = i / BONE_SEGS, t1 = (i + 1) / BONE_SEGS;
253
- const r = (t) => (0.05 - 0.032 * Math.pow(t, 0.7)) * tailK;
254
- m.cone({ tag: 'tail', bone: 'tail' + i, a: J['tail' + i], b: J['tail' + (i + 1)], ra: r(t0), rb: r(t1), k: i === 0 ? 0.06 : 0.01 });
255
- }
256
- // switch: a long tassel of hair from the end of the bony tail
257
- const sw = (params.switchLen ?? 1) * (calf ? 0.4 : 1);
258
- const b10 = J['tail' + BONE_SEGS], b11 = J['tail' + (BONE_SEGS + 1)], b12 = J['tail' + TAIL_SEGS];
259
- const sw1 = lerp(b10, b11, Math.min(1, sw)), sw2 = lerp(b11, b12, Math.max(0, Math.min(1, sw * 1.0)));
260
- m.cone({ tag: 'switch', bone: 'tail' + (BONE_SEGS - 1), a: lerp(J['tail' + (BONE_SEGS - 1)], b10, 0.3), b: b10, ra: 0.022, rb: 0.04, k: 0.015 });
261
- m.cone({ tag: 'switch', bone: 'tail' + BONE_SEGS, a: b10, b: sw1, ra: 0.042, rb: 0.048 * Math.max(0.6, sw), k: 0.015 });
262
- if (sw > 0.5) m.cone({ tag: 'switch', bone: 'tail' + (BONE_SEGS + 1), a: sw1, b: sw2, ra: 0.048, rb: 0.012, k: 0.015 });
263
-
264
- // ---------------------------------------------------------------- HORNS (own rigid surface on the head)
265
- if (params.horns) {
266
- for (const s of [1, -1]) {
267
- const hp = hornPath(params, s);
268
- for (let i = 0; i < hp.pts.length - 1; i++) {
269
- m.cone({ bone: 'head', group: 'axial', part: 'horn', tag: 'horn', a: hp.pts[i], b: hp.pts[i + 1], ra: hp.rads[i], rb: hp.rads[i + 1], k: 0.01 });
270
- }
271
- // horn base boss on the poll (skin)
272
- m.sphere({ ...H, tag: 'hornbase', c: add(hp.base, [0.006 * s, 0, 0]), rad: params.horns.r * 1.05, k: 0.03 });
273
- }
274
- }
275
-
276
- // ---------------------------------------------------------------- EAR TAGS, NOSE RING (own rigid surfaces)
277
- if (params.earTags) {
278
- for (const S of ['L', 'R']) {
279
- const s = S === 'L' ? 1 : -1;
280
- const base = J['earBase' + S], tip = J['earTip' + S];
281
- const q = add(lerp(base, tip, 0.42), [0, -0.01, 0.02]);
282
- m.cone({ bone: 'ear' + S, group: 'ear' + S, part: 'tag' + S, tag: 'eartag', a: add(q, [0, 0.012, 0]), b: add(q, [0, 0.012, 0.012]), ra: 0.007, rb: 0.007, k: 0.002 });
283
- m.ell({ bone: 'ear' + S, group: 'ear' + S, part: 'tag' + S, tag: 'eartag', c: add(q, [0, -0.028, 0.017]), r: [0.028, 0.034, 0.0028], axis: norm([0, 0.1, 1]), k: 0.004 });
284
- }
285
- }
286
- if (params.noseRing) {
287
- const c = hl([0, -0.078, 0.402]), rr = 0.03, n = 14;
288
- const pt = (i) => { const a = (i / n) * Math.PI * 2; return add(c, add(mul(HY, Math.cos(a) * rr), mul(HZ, Math.sin(a) * rr * 0.85))); };
289
- for (let i = 0; i < n; i++) m.cone({ bone: 'head', part: 'ring', tag: 'ring', a: pt(i), b: pt(i + 1), ra: 0.0042, rb: 0.0042, k: 0.001 });
290
- }
291
- void R;
292
- return m;
293
- }
294
-
295
- // Udder: four quarters under the pelvis between the thighs, rear attachment high up between the
296
- // thighs, fore udder blending forward into the belly, four teats.
297
- function sculptUdder(m, k) {
298
- const U = { bone: 'spine1', tag: 'udder' };
299
- const sz = 0.75 + 0.35 * k;
300
- m.ell({ ...U, c: [0, 0.73 + 0.04 * (1 - k), -0.55], r: [0.15 * sz, 0.15 * sz, 0.22 * sz], k: 0.06 });
301
- m.ell({ ...U, bone: 'pelvis', c: [0, 0.92, -0.74], r: [0.12 * sz, 0.2, 0.1], k: 0.08 });
302
- m.ell({ ...U, c: [0, 0.76, -0.36], r: [0.13 * sz, 0.1 * sz, 0.18 * sz], k: 0.1 });
303
- const bottom = 0.73 + 0.04 * (1 - k) - 0.15 * sz;
304
- for (const s of [1, -1]) {
305
- // quarters bulge a little on each side (the median suspensory ligament makes a groove)
306
- m.ell({ ...U, c: [0.075 * s, bottom + 0.1, -0.62], r: [0.085 * sz, 0.1 * sz, 0.12 * sz], k: 0.05 });
307
- m.ell({ ...U, c: [0.07 * s, bottom + 0.1, -0.45], r: [0.075 * sz, 0.09 * sz, 0.1 * sz], k: 0.05 });
308
- for (const z of [-0.64, -0.44]) {
309
- m.cone({ tag: 'teat', bone: 'spine1', c: undefined, a: [0.06 * s, bottom + 0.03, z], b: [0.062 * s, bottom - 0.05, z + 0.005], ra: 0.017, rb: 0.013, k: 0.02 });
310
- }
311
- }
312
- }
313
-
314
- // Cloven hoof: pastern with two dew claws behind the fetlock, then two claws (digits III and IV),
315
- // each a hoof-wall half-cone with its front wall parallel to the pastern, heel bulbs behind, flat on
316
- // the ground (carved at y = 0); the interdigital cleft is carved between them.
317
- function sculptDigits(m, paw, hoof, group, M, C, T, s, w) {
318
- m.cone({ tag: 'pastern', bone: paw, group, a: M, b: C, ra: 0.038 * w, rb: 0.04 * w, k: 0.02 });
319
- // dew claws: two small horny nubs behind and above the fetlock... (cattle: just above the heels)
320
- for (const d of [1, -1]) {
321
- m.ell({ tag: 'dewclaw', bone: paw, group, c: add(M, [0.022 * d, -0.035, -0.05]), r: [0.013, 0.017, 0.016], axis: norm([0, -0.5, -1]), k: 0.012 });
322
- }
323
- const zMid = (C[2] + T[2]) * 0.5;
324
- const dir = Math.sign(T[2] - C[2]);
325
- for (const d of [1, -1]) {
326
- const off = 0.026 * d * w;
327
- const top = add(C, [off, 0.018, -0.012 * dir]);
328
- const base = [C[0] + off * 1.1, 0.0, zMid - 0.004 * dir];
329
- m.cone({ tag: 'hoof', bone: hoof, group, a: top, b: base, ra: 0.026 * w, rb: 0.036 * w, k: 0.012 });
330
- // the toe: a pointed front of each claw
331
- m.ell({ tag: 'hoof', bone: hoof, group, c: [C[0] + off * 0.9, 0.022, T[2] - 0.028 * dir], r: [0.024 * w, 0.022, 0.034], axis: norm([0, -0.25, dir]), k: 0.012 });
332
- m.sphere({ tag: 'heelbulb', bone: hoof, group, c: add(C, [off * 0.9, -0.026, -0.042 * dir]), rad: 0.026 * w, k: 0.02 });
333
- }
334
- m.cone({ tag: 'coronet', bone: hoof, group, a: add(C, [0, 0.024, -0.02 * dir]), b: add(C, [0, 0.012, 0.02 * dir]), ra: 0.043 * w, rb: 0.043 * w, k: 0.015 });
335
- // interdigital cleft: a V opening toward the front and the ground
336
- m.ell({ tag: 'cleft', bone: hoof, group, c: [C[0], 0.0, T[2] - 0.004 * dir], r: [0.005, 0.045, 0.075], k: 0.004, carve: true });
337
- m.ell({ tag: 'cleft', bone: hoof, group, c: [C[0], -0.005, zMid], r: [0.004, 0.03, 0.08], k: 0.003, carve: true });
338
- m.ell({ tag: 'sole', bone: hoof, group, c: [C[0], -0.2 + 0.002, zMid], r: [0.2, 0.2, 0.2], k: 0.004, carve: true });
339
- }