@bitefoundry/retrieviewer 0.0.3 → 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 (254) 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 +6 -1
  6. package/PLUGIN.md +13 -10
  7. package/PREREQUISITES.md +95 -0
  8. package/PUPPY-MEMORY.md +102 -1
  9. package/README.ko.md +110 -0
  10. package/README.md +117 -59
  11. package/RELEASING.md +23 -12
  12. package/THIRD-PARTY-NOTICES.md +5 -3
  13. package/braille.mjs +35 -9
  14. package/examples/live.mjs +2 -2
  15. package/integrations/linux-overlay/overlay.py +2 -2
  16. package/integrations/macos-overlay/overlay.jxa +8 -6
  17. package/integrations/opencode/runtime.mjs +4 -4
  18. package/integrations/opencode/tui.tsx +1 -1
  19. package/integrations/windows-overlay/Overlay.cs +4 -4
  20. package/integrations/windows-overlay/protocol.mjs +11 -7
  21. package/package.json +34 -13
  22. package/puppy-comfort-sprites.mjs +25 -0
  23. package/puppy-companion-comfort-q5.bin.gz +0 -0
  24. package/puppy-companion-comfort.json +77 -0
  25. package/puppy-companion-life.mjs +49 -16
  26. package/puppy-companion-objects.mjs +12 -0
  27. package/puppy-frame-predictive.mjs +15 -9
  28. package/puppy-frame-store.mjs +2 -2
  29. package/puppy-paper-world.mjs +14 -0
  30. package/puppy-paper.mjs +65 -14
  31. package/retrieviewer-plugin.d.mts +1 -1
  32. package/retrieviewer-plugin.mjs +1 -1
  33. package/retrieviewer.d.mts +10 -4
  34. package/retrieviewer.mjs +20 -14
  35. package/locomotion-core.mjs +0 -21
  36. package/locomotion-puppy.animal +0 -0
  37. package/vendor/procedural-animals/package.json +0 -40
  38. package/vendor/procedural-animals/src/animal.js +0 -146
  39. package/vendor/procedural-animals/src/bake.js +0 -51
  40. package/vendor/procedural-animals/src/build.js +0 -81
  41. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  42. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  43. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  44. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  45. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  46. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  47. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  48. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  49. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  50. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  51. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  52. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  53. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  54. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  55. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  56. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  57. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  58. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  59. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  60. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  61. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  62. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  63. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  64. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  65. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  66. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  67. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  68. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  69. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  70. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  71. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  72. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  73. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  74. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  75. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  76. package/vendor/procedural-animals/src/index.d.ts +0 -98
  77. package/vendor/procedural-animals/src/index.js +0 -29
  78. package/vendor/procedural-animals/src/registry.js +0 -48
  79. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  80. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  81. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  82. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  83. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  84. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  85. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  86. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  87. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  88. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  89. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  90. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  91. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  92. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  93. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  94. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  95. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  96. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  97. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  98. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  99. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  100. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  101. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  102. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  103. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  104. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  105. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  106. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  107. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  108. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  109. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  110. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  111. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  112. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  113. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  114. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  115. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  116. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  117. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  118. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  119. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  120. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  121. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  122. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  123. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  124. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  125. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  126. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  127. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  128. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  129. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  130. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  131. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  132. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  133. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  134. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  135. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  136. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  137. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  138. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  139. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  140. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  141. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  142. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  143. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  144. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  145. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  146. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  147. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  148. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  149. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  150. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  151. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  152. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  153. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  154. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  155. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  156. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  157. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  158. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  159. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  160. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  161. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  162. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  163. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  164. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  165. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  166. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  167. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  168. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  169. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  170. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  171. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  172. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  173. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  174. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  175. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  176. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  177. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  178. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  179. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  180. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  181. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  182. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  183. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  184. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  185. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  186. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  187. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  188. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  189. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  190. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  191. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  192. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  193. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  194. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  195. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  196. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  197. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  198. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  199. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  200. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  201. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  202. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  203. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  204. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  205. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  206. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  207. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  208. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  209. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  210. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  211. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  212. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  213. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  214. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  215. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  216. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  217. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  218. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  219. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  220. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  221. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  222. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  223. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  224. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  225. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  226. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  227. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  228. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  229. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  230. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  231. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  232. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  233. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  234. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  235. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  236. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  237. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  238. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  239. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  240. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  241. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  242. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  243. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  244. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  245. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  246. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  247. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  248. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  249. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  250. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  251. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  252. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  253. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  254. 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
- }