@bitefoundry/retrieviewer 0.0.0-stage → 0.0.1

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 (292) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +107 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +61 -2
  12. package/RELEASING.md +61 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/braille.mjs +45 -0
  16. package/examples/bridge.py +62 -0
  17. package/examples/embed.mjs +15 -0
  18. package/examples/live.mjs +40 -0
  19. package/examples/plugin.mjs +21 -0
  20. package/integrations/opencode/runtime.mjs +195 -0
  21. package/integrations/opencode/tui.tsx +59 -0
  22. package/locomotion-core.mjs +21 -0
  23. package/locomotion-puppy.animal +0 -0
  24. package/package.json +125 -4
  25. package/pet-q5-manifest.json +118 -0
  26. package/puppy-clean.mjs +51 -0
  27. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  28. package/puppy-companion-interactions-v3.json +1 -0
  29. package/puppy-companion-life.mjs +182 -0
  30. package/puppy-companion-objects.mjs +30 -0
  31. package/puppy-companion-sprites.mjs +14 -0
  32. package/puppy-continuous.mjs +57 -0
  33. package/puppy-face.mjs +46 -0
  34. package/puppy-frame-predictive.mjs +147 -0
  35. package/puppy-frame-store.mjs +108 -0
  36. package/puppy-mouth-anchor.json +1 -0
  37. package/puppy-mouth-anchor.mjs +33 -0
  38. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  39. package/puppy-paper-actions-v9.json +1 -0
  40. package/puppy-paper-actions.mjs +48 -0
  41. package/puppy-paper-approach-q5.bin.gz +0 -0
  42. package/puppy-paper-approach.json +1 -0
  43. package/puppy-paper-area.mjs +15 -0
  44. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  45. package/puppy-paper-arrival-seat.json +1 -0
  46. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  47. package/puppy-paper-behaviors.json +1 -0
  48. package/puppy-paper-behaviors.mjs +8 -0
  49. package/puppy-paper-contact.mjs +103 -0
  50. package/puppy-paper-director.mjs +21 -0
  51. package/puppy-paper-paw-transport.mjs +76 -0
  52. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  53. package/puppy-paper-rear-gallop.json +1 -0
  54. package/puppy-paper-seat-q5.bin.gz +0 -0
  55. package/puppy-paper-seat.json +14 -0
  56. package/puppy-paper-seat.mjs +13 -0
  57. package/puppy-paper-trick-timing.mjs +58 -0
  58. package/puppy-paper-world.mjs +108 -0
  59. package/puppy-paper.mjs +271 -0
  60. package/puppy-stable-meta.json +1606 -0
  61. package/puppy-stable.mjs +72 -0
  62. package/puppy-stride-meta.json +343 -0
  63. package/puppy-stride-tweens-q5.bin.gz +0 -0
  64. package/puppy-zoomies.mjs +27 -0
  65. package/puppy-zoomies30.mjs +12 -0
  66. package/retrieviewer-cli.mjs +81 -0
  67. package/retrieviewer-config.mjs +49 -0
  68. package/retrieviewer-install.mjs +157 -0
  69. package/retrieviewer-plugin.d.mts +44 -0
  70. package/retrieviewer-plugin.mjs +97 -0
  71. package/retrieviewer-stdio.mjs +38 -0
  72. package/retrieviewer.d.mts +85 -0
  73. package/retrieviewer.mjs +142 -0
  74. package/vendor/procedural-animals/LICENSE +21 -0
  75. package/vendor/procedural-animals/package.json +40 -0
  76. package/vendor/procedural-animals/src/animal.js +146 -0
  77. package/vendor/procedural-animals/src/bake.js +51 -0
  78. package/vendor/procedural-animals/src/build.js +81 -0
  79. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  80. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  81. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  82. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  83. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  84. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  85. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  86. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  87. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  88. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  89. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  90. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  91. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  92. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  93. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  94. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  95. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  96. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  97. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  98. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  99. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  100. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  101. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  102. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  103. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  104. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  105. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  106. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  107. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  108. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  109. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  110. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  111. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  112. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  113. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  114. package/vendor/procedural-animals/src/index.d.ts +98 -0
  115. package/vendor/procedural-animals/src/index.js +29 -0
  116. package/vendor/procedural-animals/src/registry.js +48 -0
  117. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  118. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  119. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  120. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  121. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  122. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  123. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  124. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  125. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  126. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  127. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  128. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  129. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  130. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  131. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  132. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  133. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  134. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  135. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  136. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  137. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  138. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  139. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  140. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  141. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  142. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  143. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  144. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  145. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  146. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  147. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  148. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  149. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  150. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  151. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  152. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  153. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  154. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  155. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  156. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  157. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  158. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  159. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  160. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  161. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  162. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  163. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  164. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  165. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  166. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  167. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  168. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  169. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  170. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  171. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  172. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  173. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  174. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  175. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  176. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  177. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  178. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  179. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  180. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  181. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  182. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  183. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  184. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  185. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  186. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  187. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  188. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  189. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  190. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  191. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  192. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  193. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  194. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  195. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  196. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  197. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  198. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  199. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  200. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  201. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  202. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  203. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  204. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  205. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  206. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  207. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  208. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  209. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  210. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  211. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  212. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  213. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  214. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  215. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  216. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  217. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  218. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  219. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  220. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  221. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  222. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  223. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  224. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  225. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  226. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  227. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  228. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  229. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  230. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  231. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  232. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  233. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  234. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  235. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  236. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  237. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  238. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  239. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  240. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  241. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  242. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  243. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  244. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  245. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  246. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  247. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  248. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  249. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  250. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  251. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  252. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  253. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  254. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  255. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  256. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  257. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  258. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  259. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  260. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  261. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  262. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  263. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  264. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  265. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  266. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  267. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  268. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  269. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  270. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  271. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  272. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  273. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  274. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  275. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  276. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  277. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  278. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  279. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  280. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  281. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  282. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  283. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  284. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  285. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  286. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  287. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  288. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  289. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  290. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  291. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  292. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,542 @@
1
+ // The sheep, sculpted as a signed distance field.
2
+ // Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw). The head is
3
+ // modelled in its own frame (rig.js: hl()), measured from the profile and frontal references (side2,
4
+ // side3_texel, face1, face2, front1); the body relative to the joints so the sculpt follows the
5
+ // skeleton.
6
+ //
7
+ // The fleece is a sculpted volume: the skin body is wrapped in offset "fleece" primitives (params.
8
+ // wool = depth of the wool volume, m) that give the boxy silhouette of a sheep in wool (the neck
9
+ // disappears, the head emerges from the wool, the belly wool hangs, the britch covers the thighs to
10
+ // the hocks), and its surface is broken into locks: a Poisson-disc scatter of small ellipsoids
11
+ // elongated down the fall of the staples, blended into the fleece. The coat grows short, tightly
12
+ // clumped shells on top (the crimped staple tips) and darkens the crevices between locks.
13
+ // Separate surfaces ("parts"): 'jaw' (lower lip and chin: the mouth opens) and 'horn' (keratin
14
+ // spiral horns of rams, rigid on the skull).
15
+ import { HEAD_O, HZ, HY, hl, hdir, TAIL_SEGS } from './rig.js';
16
+ import { add, sub, mul, lerp, norm, cross, dot, rng, smoothstep } from '../../core/math/vec.js';
17
+ import { apertureTiltAlong, eyeFrameOf } from '../../core/sdf/eyeSocket.js';
18
+ import { SDFModel } from '../../core/sdf/sdf.js';
19
+ import { neckS } from './neck.js';
20
+
21
+ // Eye: globe ~29 mm across, visible opening ~23 x 15 mm (literature), set on the side of the
22
+ // head below the brow, looking out and a little forward (lateral eyes, visual field ~300 deg).
23
+ const EYE_LOCAL = [0.058, -0.004, 0.013]; // (set in the orbit: the brow and the orbit's rim stand round it)
24
+ const eyeDir = (() => {
25
+ const a = (38 * Math.PI) / 180;
26
+ return norm(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))));
27
+ })();
28
+ const EYE_BASE = {
29
+ c: sub(hl(EYE_LOCAL), HEAD_O), r: 0.0145, back: 0.003, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
30
+ lid: 0.0017, R: 0.017, d: 0.0095, off: -0.0006, tilt: 0, irisZ: 0.0081, irisR: 0.0112,
31
+ };
32
+ // roll the almond so its long axis follows the nasal line, the inner corner a little lower (toward the
33
+ // muzzle). (A search over the roll stood the almond on end: a vertical slit, the "bead" eye.)
34
+ EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) + 0.16;
35
+ export const EYE = EYE_BASE;
36
+
37
+ const X = [1, 0, 0];
38
+
39
+ // Horn centre line (reference space), shared by the sculpt and the coat: the ram's spiral. From the
40
+ // poll it sweeps up and back, down behind the ear, forward under it and up again beside the eye
41
+ // (a logarithmic spiral in the side plane of the head, drifting outward so the turns clear the face
42
+ // and each other). params.horns = { turns, base (radius, m), r0 (curl radius), flare }.
43
+ // Returns [{ p, r, t }].
44
+ export function hornPath(params, s) {
45
+ const H = params.horns;
46
+ if (!H || !(H.turns > 0)) return [];
47
+ // (in the world frame of the bind pose: the coil turns in a near-vertical plane beside the head,
48
+ // whatever the head's pitch)
49
+ const B = hl([0.04 * s, 0.03, -0.04]); // horn base on the poll
50
+ const C = [B[0], B[1] - 0.048 * (H.r0 ?? 1), B[2] - 0.05 * (H.r0 ?? 1)]; // curl centre, behind the ear
51
+ const r0 = Math.hypot(B[1] - C[1], B[2] - C[2]);
52
+ const th0 = Math.atan2(B[1] - C[1], B[2] - C[2]);
53
+ const b = Math.log(1.45) / (2 * Math.PI);
54
+ const turns = H.turns;
55
+ const n = Math.max(6, Math.round(22 * turns));
56
+ const out = [];
57
+ for (let i = 0; i <= n; i++) {
58
+ const t = i / n;
59
+ const th = th0 + t * turns * 2 * Math.PI;
60
+ const r = r0 * Math.exp(b * (th - th0));
61
+ const lat = B[0] + s * (0.095 * (H.flare ?? 1) * turns * t + 0.03 * Math.sin(Math.PI * Math.min(1, t * 2)));
62
+ const v = [lat, C[1] + r * Math.sin(th), C[2] + r * Math.cos(th)];
63
+ const rad = H.base * Math.pow(1 - 0.84 * t, 0.9) + 0.0025;
64
+ out.push({ p: v, r: rad, t });
65
+ }
66
+ return out;
67
+ }
68
+
69
+ // The fall of the staples on the fleece surface (normal n, point p): away from the head along the body
70
+ // (the axial chain's direction, nose -> tail, blended softly between its segments) and down. The tangent
71
+ // field is the projection of that 3D direction onto the surface, so it only vanishes where the surface
72
+ // faces straight along it: under the rear of the belly (down and back), never on the visible fleece.
73
+ // (An earlier field added a "back over the top" term (n x X) that pointed UP on the front of the
74
+ // neck and the breast: the fall turned through 180 degrees across a line there, and the fur shells, which
75
+ // lean along the flow, parted along it: the dark crack across the neck and shoulder.)
76
+ const FALL_SEGS = new WeakMap();
77
+ const FALL_CHAIN = ['occiput', 'neckMid', 'neckBase', 'chestMid', 'thoraxRear', 'lumbarMid', 'lumbosacral', 'tailBase'];
78
+ export function stapleFall(n, p, J) {
79
+ let cd = [0, 0, -1];
80
+ if (p && J) {
81
+ let segs = FALL_SEGS.get(J);
82
+ if (!segs) FALL_SEGS.set(J, segs = FALL_CHAIN.slice(1).map((k, i) => { const a = J[FALL_CHAIN[i]], b = J[k]; const d = sub(b, a); return { a, d, l2: dot(d, d), u: norm(d) }; }));
83
+ let dmin = 1e9; const ds = [];
84
+ for (const S of segs) {
85
+ const t = Math.max(0, Math.min(1, dot(sub(p, S.a), S.d) / S.l2));
86
+ const dd = Math.hypot(p[0] - S.a[0] - S.d[0] * t, p[1] - S.a[1] - S.d[1] * t, p[2] - S.a[2] - S.d[2] * t);
87
+ ds.push(dd); if (dd < dmin) dmin = dd;
88
+ }
89
+ cd = [0, 0, 0];
90
+ segs.forEach((S, i) => { const w = Math.exp(-(ds[i] - dmin) / 0.05); cd = add(cd, mul(S.u, w)); });
91
+ cd = norm(cd);
92
+ }
93
+ const v = [cd[0], cd[1] - 1.2, cd[2]], k = dot(n, v);
94
+ const t = [v[0] - n[0] * k, v[1] - n[1] * k, v[2] - n[2] * k];
95
+ const l = Math.hypot(t[0], t[1], t[2]);
96
+ return l > 1e-6 ? [t[0] / l, t[1] / l, t[2] / l] : [0, 0, -1];
97
+ }
98
+
99
+ // lock radius (m) for a sculpted wool depth: staple blocks of 2-3 cm (finer in the Merino)
100
+ export const lockRadius = (F, merino) => Math.min(0.032, 0.01 + 0.2 * F) * (merino ? 0.75 : 1);
101
+
102
+ // all wool tags (the coat grows fleece on them)
103
+ export const WOOL_TAGS = new Set(['fleece', 'woolneck', 'woolarm', 'woolthigh', 'woolweb', 'woolbelly', 'wooltail', 'woolpoll', 'woolcheek', 'lock', 'lockleg', 'woolleg', 'fold']);
104
+
105
+ export function sculptSheep(m, rig, params = {}) {
106
+ const J = rig.J;
107
+ const lamb = params.age === 'juvenile';
108
+ const male = params.sex === 'male';
109
+ const ram = male && !lamb;
110
+ const heavy = params.heavy ?? (ram ? 1 : 0); // 0 ewe .. 1 heavy ram
111
+ const roman = params.roman || 0; // Suffolk / Merino convex nose
112
+ const F = params.wool ?? 0.07; // depth of the sculpted wool volume (m)
113
+ const merino = params.variant === 'merino';
114
+ const R = rng(9127 + (params.coatSeed || 0));
115
+
116
+ const ellY = (o) => {
117
+ const y = norm(o.ydir);
118
+ const lat = o.lateral || X;
119
+ const z = norm(cross(lat, y));
120
+ return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
121
+ };
122
+ const H = { bone: 'head' };
123
+ 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 });
124
+
125
+ // ---------------------------------------------------------------- TORSO (skin)
126
+ // deep, broad barrel, level back, well-sprung ribs, broad rounded rump (meat breeds: Texel, Suffolk)
127
+ const gw = 1 + 0.1 * heavy + (params.muscle || 0) * 0.08;
128
+ m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 0.5, 0.07], r: [0.152 * gw, 0.19, 0.27], axis: norm([0, 0.06, 1]), k: 0 });
129
+ m.ell({ tag: 'girth', bone: 'chest', c: [0, 0.49, 0.23], r: [0.122 * gw, 0.17, 0.12], k: 0.05 });
130
+ m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 0.485, -0.13], r: [0.16 * gw, 0.18, 0.2], axis: norm([0, 0.06, -1]), k: 0.06 });
131
+ m.ell({ tag: 'flank', bone: 'spine1', c: [0, 0.53, -0.26], r: [0.14 * gw, 0.13, 0.13], k: 0.06 });
132
+ m.ell({ tag: 'withers', bone: 'chest', c: [0, 0.645, 0.2], r: [0.05, 0.05, 0.13], axis: norm([0, -0.1, 1]), k: 0.07 });
133
+ m.ell({ tag: 'back', bone: 'spine3', c: [0, 0.64, 0.0], r: [0.11 * gw, 0.05, 0.22], k: 0.07 });
134
+ m.ell({ tag: 'loin', bone: 'spine1', c: [0, 0.645, -0.2], r: [0.12 * gw, 0.045, 0.14], k: 0.07 });
135
+ // hindquarters: broad croup, rounded buttocks (the leg of lamb)
136
+ m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 0.56, -0.39], r: [0.13 * gw, 0.12, 0.13], k: 0.05 });
137
+ m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 0.63, -0.38], r: [0.105 * gw, 0.045, 0.14], axis: norm([0, -0.1, 1]), k: 0.05 });
138
+ for (const s of [1, -1]) {
139
+ m.sphere({ tag: 'hippoint', bone: 'pelvis', c: [0.1 * s * gw, 0.635, -0.29], rad: 0.022, k: 0.05 });
140
+ m.sphere({ tag: 'pinbone', bone: 'pelvis', c: [0.05 * s, 0.61, -0.49], rad: 0.022, k: 0.04 });
141
+ m.ell({ tag: 'rump', bone: 'pelvis', c: [0.06 * s, 0.51, -0.46], r: [0.065 * gw, 0.11, 0.065], k: 0.06 });
142
+ }
143
+ // chest front: brisket between the forelegs
144
+ m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.395, 0.3], r: [0.075, 0.075, 0.08], k: 0.05 });
145
+ for (const s of [1, -1]) m.ell({ tag: 'pectoral', bone: 'chest', c: [0.046 * s, 0.4, 0.33], r: [0.042, 0.06, 0.04], k: 0.04 });
146
+ // udder (ewes, small, mostly hidden in the wool) / scrotum (rams: large, hanging between the thighs)
147
+ if (!male && !lamb && (params.udder ?? 0.3) > 0.05) {
148
+ const u = params.udder ?? 0.3;
149
+ for (const s of [1, -1]) {
150
+ m.ell({ tag: 'udder', bone: 'udder', group: 'udder', c: [0.026 * s, 0.315 - 0.02 * u, -0.3], r: [0.034 + 0.014 * u, 0.04 + 0.02 * u, 0.045 + 0.015 * u], k: 0.03 });
151
+ const a = [0.032 * s, 0.285 - 0.035 * u, -0.29], b = add(a, [0.01 * s, -0.022, 0.01]);
152
+ m.cone({ tag: 'teat', bone: 'udder', group: 'udder', a, b, ra: 0.008, rb: 0.006, k: 0.01 });
153
+ }
154
+ }
155
+ if (male && !lamb) {
156
+ m.ell({ tag: 'scrotum', bone: 'udder', group: 'udder', c: [0, 0.3, -0.34], r: [0.045, 0.075, 0.045], axis: norm([0, 0.2, 1]), k: 0.035 });
157
+ m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.3, -0.05], r: [0.016, 0.02, 0.045], axis: norm([0, 0.3, 1]), k: 0.03 });
158
+ }
159
+
160
+ // ---------------------------------------------------------------- NECK (skin)
161
+ // short and thick; a ram's is heavier with a crest
162
+ const nb = J.neckBase, nm = J.neckMid, occ = J.occiput;
163
+ const nd1 = norm(sub(nm, nb)), nd2 = norm(sub(occ, nm));
164
+ const nk = 1 + 0.25 * heavy;
165
+ m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.575, 0.32], r: [0.11 * nk, 0.13, 0.11], axis: nd1, up: [0, 1, -0.6], k: 0.05 });
166
+ m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.67, 0.4], r: [0.1 * nk, 0.105 * (1 + 0.15 * heavy), 0.095], axis: nd1, up: [0, 1, -0.6], k: 0.05 });
167
+ m.ell({ tag: 'neck', bone: 'neck2', c: [0, 0.76, 0.47], r: [0.078 * nk, 0.085 * (1 + 0.15 * heavy), 0.08], axis: nd2, up: [0, 1, -0.6], k: 0.04 });
168
+ // the neck meets the withers and shoulders in one broad mass (no notch in the shorn topline)
169
+ m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.6, 0.265], r: [0.12 * nk, 0.12, 0.12], k: 0.06 });
170
+ m.ell({ tag: 'crest', bone: 'neck1', c: [0, 0.71, 0.37], r: [0.055 + 0.02 * heavy, 0.05 + 0.02 * heavy, 0.15], axis: norm([0, 0.65, 0.75]), k: 0.07 });
171
+ m.cone({ tag: 'throat', bone: 'neck1', a: [0, 0.51, 0.38], b: [0, 0.65, 0.455], ra: 0.05, rb: 0.038, k: 0.05 });
172
+ // (the throat runs in behind the angle of the jaw: the jaw's outline stands clear of the neck)
173
+ m.cone({ tag: 'throat', bone: 'neck2', a: [0, 0.63, 0.455], b: hl([0, -0.07, -0.1]), ra: 0.03, rb: 0.022, k: 0.03 });
174
+
175
+ // ---------------------------------------------------------------- HEAD (head-local, see rig.js)
176
+ // A defined skull rather than one smooth mass (side4_suffolk, face1, face2, extra/: Suffolk_Sheep,
177
+ // Tete_de_brebie_Suffolk, Lundy head): a narrow braincase behind a flat forehead; the orbits stand out
178
+ // to the sides under a brow; a long nasal bridge, straight in the white-faced breeds and arched
179
+ // (Roman) in the Suffolk and in rams; the face narrows from the facial crest below the eye to a
180
+ // blunt, deep muzzle with comma nostrils and a split upper lip; the cheek (masseter) and the
181
+ // rounded angle of the jaw behind it, the lower edge of the mandible running forward to the chin.
182
+ // Landmarks: poll (0, 0.04, -0.075), eyes (+-0.059, -0.006, 0.004), nose tip (0, -0.024, 0.2), chin
183
+ // y -0.09 at 0.15, forehead width 0.105 between the brows.
184
+ // dorsal line: straight from the forehead (y 0.046 at z 0) to the top of the nose (0.012 at 0.175),
185
+ // plus the Roman arch
186
+ const topY = (z) => 0.046 - 0.194 * z + (0.004 + 0.011 * roman) * Math.sin(Math.PI * Math.max(0, Math.min(1, (z + 0.02) / 0.21)));
187
+ // (broad and flat across the top: from the front the head is wide at the eyes and ears and narrows to
188
+ // the muzzle, not a dome over a long nose)
189
+ hell({ tag: 'cranium', c: [0, -0.002, -0.042], r: [0.05, 0.04, 0.05], k: 0.03 });
190
+ hell({ tag: 'poll', c: [0, 0.0, -0.068], r: [0.038, 0.032, 0.03], k: 0.03 });
191
+ hell({ tag: 'forehead', c: [0, 0.028, 0.0], r: [0.056, 0.018, 0.05], k: 0.025 });
192
+ // nasal bridge: a narrow, flat-topped ridge following the dorsal line
193
+ for (const [z, w] of [[0.035, 0.032], [0.08, 0.03], [0.122, 0.028], [0.158, 0.027]]) {
194
+ const ry = 0.017;
195
+ const dz = 0.01, slope = (topY(z + dz) - topY(z - dz)) / (2 * dz);
196
+ hell({ tag: 'face', c: [0, topY(z) - ry, z], r: [w, ry, 0.038], axisL: [0, slope, 1], k: 0.022 });
197
+ }
198
+ // the face below the bridge, narrowing to the muzzle
199
+ hell({ tag: 'lowerface', c: [0, -0.043, 0.066], r: [0.043, 0.045, 0.08], k: 0.03 });
200
+ hell({ tag: 'muzzle', c: [0, -0.024, 0.155], r: [0.035 + 0.002 * roman, 0.034, 0.043], k: 0.024 });
201
+ // (the nasal line runs on to a blunt, nearly upright nose front: the tip is not a rounded bulb)
202
+ hell({ tag: 'face', c: [0, topY(0.186) - 0.019, 0.184], r: [0.025, 0.019, 0.016], axisL: [0, -0.3, 1], k: 0.018 });
203
+ // (the throat between the jaws, a little hollow under the lower edges of the mandibles)
204
+ hell({ tag: 'intermandible', c: [0, -0.092, 0.025], r: [0.02, 0.014, 0.06], k: 0.018 });
205
+ for (const s of [1, -1]) {
206
+ // the sides of the face (maxilla), sloping out from the bridge to the cheek
207
+ hell({ tag: 'maxilla', c: [0.031 * s, -0.03, 0.078], r: [0.022, 0.036, 0.074], axisL: [-0.18 * s, 0, 1], k: 0.028 });
208
+ // the brow: the orbit's rim stands out over the eye
209
+ // (a capsule: a flat ellipsoid's approximate distance, blended at k 22-35 mm, drew a crisp groove along its
210
+ // lower edge, a frown line over each eye)
211
+ m.cone({ ...H, tag: 'brow', a: hl([0.043 * s, 0.013, 0.03]), b: hl([0.053 * s, 0.011, -0.012]), ra: 0.006, rb: 0.005, k: 0.02 });
212
+ // the orbit's back and lower rim (behind and below the eye)
213
+ hell({ tag: 'orbitrim', c: [0.048 * s, -0.012, -0.011], r: [0.012, 0.018, 0.014], k: 0.016 });
214
+ // the facial crest: a ridge from below the eye forward along the cheek
215
+ m.cone({ ...H, tag: 'facialcrest', a: hl([0.05 * s, -0.026, 0.001]), b: hl([0.038 * s, -0.033, 0.075]), ra: 0.01, rb: 0.007, k: 0.016 });
216
+ // the side of the face below the facial crest is full down to the lips (a hollow there left the muzzle
217
+ // a thin tube in front of the cheeks: the 'anteater' look in 3/4)
218
+ hell({ tag: 'buccal', c: [0.032 * s, -0.058, 0.075], r: [0.019, 0.028, 0.062], k: 0.03 });
219
+ // cheek (masseter) and the rounded angle of the jaw behind it
220
+ hell({ tag: 'cheek', c: [0.043 * s, -0.074, -0.01], r: [0.021, 0.052, 0.046], k: 0.028 });
221
+ // (the angle a rounded corner standing out of the throat: the jaw's outline breaks into the neck)
222
+ hell({ tag: 'jawangle', c: [0.035 * s, -0.116, -0.03], r: [0.019, 0.032, 0.031], k: 0.024 });
223
+ // lower edge of the mandible
224
+ m.cone({ ...H, tag: 'mandible', a: hl([0.034 * s, -0.128, -0.026]), b: hl([0.024 * s, -0.075, 0.13]), ra: 0.011, rb: 0.0095, k: 0.022 });
225
+ hell({ tag: 'nostrilwing', c: [0.019 * s, -0.019, 0.177], r: [0.012, 0.014, 0.014], k: 0.013 });
226
+ // the sides of the upper lip hang over the lower jaw back to the corner of the mouth (closed lips)
227
+ hell({ tag: 'lipside', c: [0.021 * s, -0.045, 0.16], r: [0.011, 0.011, 0.025], axisL: [-0.14 * s, 0.05, 1], k: 0.015 });
228
+ // the upper lip in two halves (the philtrum splits it)
229
+ hell({ tag: 'upperlip', c: [0.013 * s, -0.04, 0.182], r: [0.016, 0.0125, 0.018], k: 0.012 });
230
+ // the eye socket (as core sculptEyeSocket, without its orbit carve and with a softer lid blend, 0.8 r): the
231
+ // lid's bulge over the globe runs smoothly into the brow and the orbit's rim; the almond's cut stops 1.4 r out
232
+ // (just outside the lids) instead of 2 r
233
+ {
234
+ const ef = eyeFrameOf(EYE, HEAD_O, s);
235
+ m.sphere({ bone: 'head', tag: 'eyelid', c: ef.c, rad: EYE.r + EYE.lid, k: EYE.r * 0.8 });
236
+ m.lens({ bone: 'head', tag: 'eyesocket', c: add(ef.c, mul(ef.y, EYE.off)), x: ef.x, y: ef.y, z: ef.z, R: EYE.R, d: EYE.d, zMin: -EYE.r * 0.16, zMax: EYE.r * 1.4, k: EYE.r * 0.18, carve: true });
237
+ }
238
+ // preorbital (infraorbital) gland: a small pit in front of and below the eye
239
+ hell({ tag: 'preorbital', c: [0.047 * s, -0.021, 0.047], r: [0.004, 0.003, 0.008], k: 0.004, carve: true });
240
+ // nostril: a narrow oblique slit on the front of the nose, the upper ends turned toward each other (an
241
+ // inverted V, front1, face2); its centre 1 mm under the skin, ~15 x 2.5 mm where it opens
242
+ // (a 7 x 20 mm carve 9 mm inside the nose opened on its underside, a dark smear from below)
243
+ hell({ tag: 'nostril', c: [0.0105 * s, -0.019, 0.2015], r: [0.0014, 0.0078, 0.004], axisL: [0.3 * s, -0.1, 1], upL: [-0.64 * s, 0.77, 0], k: 0.0015, carve: true });
244
+ }
245
+ // philtrum: the groove that splits the upper lip (sheep graze close with the split lip); a shallow notch in the
246
+ // lip's edge, the line down from the nose is painted (coat.js; a carved groove on the fine nose mesh shaded
247
+ // into a dark fang under the nose)
248
+ hell({ tag: 'philtrum', c: [0, -0.046, 0.2], r: [0.0018, 0.006, 0.006], k: 0.002, carve: true });
249
+
250
+ // ---------------------------------------------------------------- JAW (lower lip, chin)
251
+ const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
252
+ m.ell({ ...JW, tag: 'chin', c: hl([0, -0.063, 0.158]), r: [0.023, 0.019, 0.028], axis: HZ, up: HY, k: 0 });
253
+ m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.053, 0.176]), r: [0.021, 0.013, 0.017], axis: HZ, up: HY, k: 0.012 });
254
+ // (the rear of the lower jaw thins into the head: a full round end stood out of the cheek below the corner of
255
+ // the mouth, its crease turning the mouth line down)
256
+ for (const s of [1, -1]) m.cone({ ...JW, tag: 'mandible', a: hl([0.02 * s, -0.06, 0.124]), b: hl([0.018 * s, -0.058, 0.158]), ra: 0.0085, rb: 0.012, k: 0.015 });
257
+
258
+ // ---------------------------------------------------------------- EARS
259
+ // leaf-shaped, held level out to the sides (Suffolk: longer, a little below level); a rolled, thick
260
+ // base (the ear's stalk) widening into the blade, the inside a shallow cup with a rim
261
+ const earW = params.ear === 'droop' ? 0.031 : params.ear === 'small' ? 0.024 : 0.028;
262
+ for (const S of ['L', 'R']) {
263
+ const s = S === 'L' ? 1 : -1;
264
+ const base = J['earBase' + S], tip = J['earTip' + S];
265
+ const up = norm(sub(tip, base));
266
+ const len = Math.hypot(...sub(tip, base));
267
+ // the blade turned 55 deg about its own axis from level, so the cup opens forward, out and down: the
268
+ // inside shows from the front and from below (face1, front1, threequarter1), the back of the leaf from
269
+ // above, and from the side (ears swept back 46 deg, rig.js) the near ear is a level leaf pointing back
270
+ // showing its inside (side2), not a round cup standing upright (at 32 deg back the cup faced
271
+ // the side camera, a pink cup over the poll; at 28-40 deg of turn the blade was edge-on, a rod). The
272
+ // Suffolk's long hanging ears: the blade near upright, its face forward (Tete_de_brebie_Suffolk; at 55-62
273
+ // deg the leaf seen from the side was a thin black rod)
274
+ const fwd = norm([-s * up[2], 0, Math.abs(up[0])]);
275
+ const tiltB = ((params.ear === 'droop' ? 80 : 55) * Math.PI) / 180;
276
+ const facing = norm(add(mul([0, -1, 0], Math.cos(tiltB)), mul(fwd, Math.sin(tiltB))));
277
+ const lat = norm(cross(up, facing));
278
+ const fc = norm(cross(lat, up));
279
+ const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
280
+ m.cone({ ...ear, tag: 'earstalk', a: add(base, mul(up, 0.004)), b: lerp(base, tip, 0.3), ra: 0.0115, rb: 0.0095, k: 0.01 });
281
+ ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.53), ydir: up, lateral: lat, r: [earW, len * 0.4, 0.0062], k: 0.016 });
282
+ // (a leaf narrowing to a point, not a round paddle)
283
+ ellY({ ...ear, tag: 'eartip', c: lerp(base, tip, 0.8), ydir: up, lateral: lat, r: [earW * 0.5, len * 0.24, 0.0045], k: 0.014 });
284
+ // (the cup starts clear of the stalk: where it cut into the stalk's junction it left dark pits)
285
+ ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.58), mul(fc, 0.0054)), ydir: up, lateral: lat, r: [earW * 0.7, len * 0.32, 0.0042], k: 0.005, carve: true });
286
+ m.sphere({ ...H, tag: 'earbase', c: add(base, mul(up, -0.006)), rad: 0.016, k: 0.02 });
287
+ }
288
+
289
+ // ---------------------------------------------------------------- HORNS (rams of horned breeds)
290
+ for (const s of [1, -1]) {
291
+ const pth = hornPath(params, s);
292
+ for (let i = 0; i + 1 < pth.length; i++) {
293
+ m.cone({ bone: 'head', part: 'horn', group: 'axial', tag: 'horn', a: pth[i].p, b: pth[i + 1].p, ra: pth[i].r, rb: pth[i + 1].r, k: 0.004 });
294
+ }
295
+ if (pth.length) m.sphere({ ...H, tag: 'hornboss', c: hl([0.036 * s, 0.028, -0.035]), rad: Math.min(0.018, params.horns.base * 0.6), k: 0.015 });
296
+ }
297
+
298
+ // ---------------------------------------------------------------- LEGS (skin): fine-boned, long cannons
299
+ const lk = 1 + 0.1 * heavy;
300
+ for (const S of ['L', 'R']) {
301
+ const s = S === 'L' ? 1 : -1;
302
+ const lat = [s, 0, 0];
303
+ const sx = (v) => [v[0] * s, v[1], v[2]];
304
+ const g = 'F' + S;
305
+ 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];
306
+ ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.03, 0, -0.01])), ydir: sub(Sh, Sc), lateral: lat, r: [0.024 * lk, 0.095, 0.062], k: 0.06 });
307
+ m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.008, 0.0, 0.012])), rad: 0.027, k: 0.04 });
308
+ m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.038 * lk, rb: 0.033 * lk, k: 0.04 });
309
+ ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.005, 0.01, -0.045])), ydir: sub(E, Sh), lateral: lat, r: [0.032 * lk, 0.068, 0.05], k: 0.05 });
310
+ m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.012, -0.032]), rad: 0.023, k: 0.025 });
311
+ m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.005]), b: W, ra: 0.033 * lk, rb: 0.018, k: 0.025 });
312
+ ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.25), sx([0.004, 0, 0.006])), ydir: sub(W, E), lateral: lat, r: [0.031 * lk, 0.065, 0.034 * lk], k: 0.025 });
313
+ m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.02, 0.03, -0.01])), b: add(lerp(E, W, 0.3), sx([-0.01, 0, 0])), ra: 0.028, rb: 0.02, k: 0.03 });
314
+ ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.002]), ydir: [0, 1, 0], lateral: lat, r: [0.02, 0.025, 0.02], k: 0.012 });
315
+ m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.01, 0.0]), b: add(M, [0, 0.01, 0.0]), ra: 0.014, rb: 0.013, k: 0.01 });
316
+ m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.015, -0.011]), b: add(M, [0, 0.015, -0.012]), ra: 0.0085, rb: 0.0105, k: 0.01 });
317
+ ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.004]), ydir: [0, 1, 0.3], lateral: lat, r: [0.018, 0.019, 0.02], k: 0.01 });
318
+ sculptDewclaws(m, 'fpaw' + S, g, M, s);
319
+ m.cone({ tag: 'pastern', bone: 'fpaw' + S, group: g, a: M, b: C, ra: 0.0148, rb: 0.0158, k: 0.01 });
320
+ sculptHoof(m, 'fhoof' + S, g, C, T, 0.97, s);
321
+
322
+ const h = 'H' + S;
323
+ 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];
324
+ ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.022, 0, -0.03])), ydir: sub(K, Hp), lateral: lat, r: [0.036 * lk, 0.125, 0.095], k: 0.06 });
325
+ m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.075, 0.585, -0.28]), b: add(K, sx([0.0, 0.03, 0.02])), ra: 0.05, rb: 0.03, k: 0.05 });
326
+ m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.05, 0.58, -0.47]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.04]), ra: 0.05, rb: 0.027, k: 0.04 });
327
+ ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.095, 0.41, -0.19]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.025, 0.065, 0.038], k: 0.05 });
328
+ m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.004, 0.005, 0.014])), rad: 0.027, k: 0.035 });
329
+ ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.3), sx([0.004, 0, -0.022])), ydir: sub(Hk, K), lateral: lat, r: [0.034 * lk, 0.075, 0.043], k: 0.03 });
330
+ m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.026, rb: 0.017, k: 0.025 });
331
+ m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.035]), b: add(Hk, [0, 0.03, -0.03]), ra: 0.012, rb: 0.01, k: 0.015 });
332
+ m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.027, -0.027]), rad: 0.014, k: 0.012 });
333
+ ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.004, -0.002]), ydir: [0, 1, 0.25], lateral: lat, r: [0.019, 0.029, 0.021], k: 0.015 });
334
+ m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.016, 0.002]), b: add(Mt, [0, 0.01, 0.0]), ra: 0.0145, rb: 0.013, k: 0.01 });
335
+ m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.016, -0.012]), b: add(Mt, [0, 0.015, -0.012]), ra: 0.0085, rb: 0.0105, k: 0.01 });
336
+ ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.004]), ydir: [0, 1, 0.3], lateral: lat, r: [0.018, 0.019, 0.02], k: 0.01 });
337
+ sculptDewclaws(m, 'hpaw' + S, h, Mt, s);
338
+ m.cone({ tag: 'pastern', bone: 'hpaw' + S, group: h, a: Mt, b: Ch, ra: 0.0145, rb: 0.0155, k: 0.01 });
339
+ sculptHoof(m, 'hhoof' + S, h, Ch, Tt, 0.93, s);
340
+ }
341
+
342
+ // ---------------------------------------------------------------- TAIL (skin): thin, hanging
343
+ for (let i = 0; i < TAIL_SEGS; i++) {
344
+ const t0 = i / TAIL_SEGS, t1 = (i + 1) / TAIL_SEGS;
345
+ const a = J['tail' + i], b = J['tail' + (i + 1)];
346
+ const w0 = 0.026 - 0.014 * t0, w1 = 0.026 - 0.014 * t1;
347
+ m.cone({ tag: i === 0 ? 'tailhead' : 'tail', bone: 'tail' + i, a, b, ra: w0, rb: w1, k: i === 0 ? 0.03 : 0.008, thin: i >= TAIL_SEGS - 2 });
348
+ }
349
+
350
+ // ---------------------------------------------------------------- FLEECE
351
+ if (F > 0.02) sculptFleece(m, J, params, F, { gw, nk, lk, heavy, merino, R, lamb, ram });
352
+ return m;
353
+ }
354
+
355
+ // The wool volume: offsets of the body masses, boxed out into the sheep's silhouette, then broken into
356
+ // locks on its surface.
357
+ function sculptFleece(m, J, params, F, o) {
358
+ const { gw, nk, heavy, merino, R } = o;
359
+ const fleece = [];
360
+ const add1 = (p) => { fleece.push(p); return p; };
361
+ const kF = 0.035 + 0.25 * F;
362
+ // torso: an offset rounded box (top line level, sides nearly vertical, bottom hanging)
363
+ const side = 0.155 * gw + F, top = 0.685 + F * 0.72, bot = 0.305 - F * 0.3;
364
+ const cy = (top + bot) / 2, hh = (top - bot) / 2;
365
+ // three stacked slabs per section give the flat sides and the squared-off back of a sheep in wool
366
+ const secs = [
367
+ { bone: 'chest', z: 0.2, w: 0.9, t: 0, b: 0.02, rz: 0.16 + F * 0.7 },
368
+ { bone: 'spine3', z: 0.03, w: 1, t: 0, b: -0.01, rz: 0.17 + F * 0.45 },
369
+ { bone: 'spine2', z: -0.14, w: 1.02, t: 0, b: 0, rz: 0.15 + F * 0.4 },
370
+ { bone: 'spine1', z: -0.28, w: 1.02, t: 0.005, b: 0.03, rz: 0.13 + F * 0.4 },
371
+ { bone: 'pelvis', z: -0.4, w: 0.98, t: 0.0, b: 0.06, rz: 0.13 + F * 0.8 },
372
+ ];
373
+ for (const S of secs) {
374
+ const hw = side * S.w, t = top - S.t, b = bot + S.b;
375
+ const c = (t + b) / 2, h = (t - b) / 2;
376
+ add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, c + h * 0.25, S.z], r: [hw * 0.98, h * 0.78, S.rz], k: kF }));
377
+ add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, c - h * 0.28, S.z], r: [hw, h * 0.74, S.rz * 0.96], k: kF }));
378
+ add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, t - hw * 0.45, S.z], r: [hw * 0.92, hw * 0.46, S.rz * 0.98], k: kF }));
379
+ }
380
+ // breast wool (between the forelegs, running up into the neck) and the britch (the back of the thighs)
381
+ add1(m.ell({ tag: 'fleece', bone: 'chest', c: [0, 0.43, 0.3], r: [0.1 + F * 0.8, 0.13 + F * 0.6, 0.09 + F], k: kF }));
382
+ add1(m.ell({ tag: 'woolbelly', bone: 'spine2', c: [0, cy - hh * 0.55, -0.06], r: [side * 0.82, hh * 0.45, 0.3 + F * 0.3], k: kF }));
383
+ for (const s of [1, -1]) add1(m.ell({ tag: 'fleece', bone: 'pelvis', c: [0.065 * s, 0.5, -0.47], r: [0.075 + F * 0.8, 0.13 + F * 0.4, 0.075 + F * 0.95], k: kF }));
384
+ // neck: a thick collar of wool; the head emerges from it behind the ears
385
+ const nf = F * (merino ? 1 : 0.9);
386
+ add1(m.ell({ tag: 'woolneck', bone: 'neck1', c: [0, 0.58, 0.33], r: [0.09 * nk + nf, 0.11 + nf, 0.1 + nf * 0.6], axis: norm(sub(J.neckMid, J.neckBase)), up: [0, 1, -0.6], k: kF }));
387
+ add1(m.ell({ tag: 'woolneck', bone: 'neck1', c: [0, 0.66, 0.39], r: [0.07 * nk + nf, 0.085 + nf, 0.078 + nf * 0.32], axis: norm(sub(J.neckMid, J.neckBase)), up: [0, 1, -0.6], k: kF }));
388
+ const collar = params.horns ? 0.035 : 0; // (a horned ram's wool starts further back: the horns curl there)
389
+ const cc = lerp(J.neckMid, J.occiput, 0.44 - collar * 4);
390
+ add1(m.ell({ tag: 'woolneck', bone: 'neck2', c: add(cc, [0, -0.012, -0.016]), r: [0.05 * nk + nf * 0.62, 0.05 + nf * 0.36, 0.048 + nf * 0.26], axis: norm(sub(J.occiput, J.neckMid)), up: [0, 1, -0.4], k: kF * 0.8 }));
391
+ // the hood: the wool thins toward the head (a tapered sleeve from behind the ears back into the
392
+ // collar), so the face comes out of a short, even edge of wool instead of a wall standing off the cheek
393
+ // (its top stays level with the poll: the head stands out of the fleece, not inside a cowl)
394
+ // (and a short ruff where it starts, standing 1.5-3 cm off the skin behind the jaw and the ear,
395
+ // so the face has an edge: with the hood flush with the head the white face ran on into the neck)
396
+ const hoodA = add(lerp(J.occiput, J.neckMid, 0.12 + collar * 3), [0, -0.058, -0.012]), hoodB = add(lerp(J.occiput, J.neckMid, 0.75), [0, -0.02, 0]);
397
+ add1(m.cone({ tag: 'woolneck', bone: 'neck2', a: hoodA, b: hoodB, ra: 0.048 + nf * 0.3, rb: 0.058 * nk + nf * 0.6, k: kF * 0.6 }));
398
+ // poll topknot (white-faced / Merino: a tuft of wool on the forehead)
399
+ if ((params.topknot || 0) > 0.05) {
400
+ const tk = params.topknot;
401
+ add1(m.ell({ tag: 'woolpoll', bone: 'head', c: hl([0, 0.03, -0.055 + 0.015 * tk]), r: [0.032 + 0.018 * tk, 0.01 + F * 0.15 * tk, 0.028 + 0.022 * tk], axis: HZ, up: HY, k: 0.02 }));
402
+ }
403
+ // Merino: wool over the face to the eyes, the cheeks and down the legs
404
+ if (merino) {
405
+ for (const s of [1, -1]) add1(m.ell({ tag: 'woolcheek', bone: 'head', c: hl([0.036 * s, -0.035, -0.05]), r: [0.03 + F * 0.25, 0.045, 0.045], axis: HZ, up: HY, k: 0.025 }));
406
+ }
407
+ // legs: the wool covers the upper arm to the elbow, the thigh and the gaskin to above the hock
408
+ for (const S of ['L', 'R']) {
409
+ const s = S === 'L' ? 1 : -1;
410
+ const sx = (v) => [v[0] * s, v[1], v[2]];
411
+ const Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S];
412
+ const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S];
413
+ const fl = F * (merino ? 0.85 : 0.7);
414
+ add1(m.ell({ tag: 'woolarm', bone: 'humerus' + S, group: 'F' + S, c: add(lerp(Sh, E, 0.55), sx([0.008, 0, -0.01])), r: [0.045 + fl * 0.8, 0.1 + fl * 0.5, 0.06 + fl * 0.7], axis: norm(sub(E, Sh)), up: [0, 0, 1], k: kF }));
415
+ add1(m.cone({ tag: 'woolleg', bone: 'radius' + S, group: 'F' + S, a: add(E, [0, 0.01, -0.01]), b: lerp(E, W, merino ? 0.75 : 0.28), ra: 0.035 + fl * 0.55, rb: 0.024 + fl * (merino ? 0.25 : 0.35), k: 0.03 }));
416
+ add1(m.ell({ tag: 'woolthigh', bone: 'femur' + S, group: 'H' + S, c: add(lerp(Hp, K, 0.5), sx([0.02, -0.01, -0.04])), r: [0.05 + fl * 0.8, 0.14 + fl * 0.4, 0.1 + fl * 0.8], axis: norm(sub(K, Hp)), up: [0, 0, 1], k: kF }));
417
+ add1(m.cone({ tag: 'woolleg', bone: 'tibia' + S, group: 'H' + S, a: add(K, sx([0.0, 0.0, -0.03])), b: add(lerp(K, Hk, merino ? 0.85 : 0.62), [0, 0, -0.012]), ra: 0.045 + fl * 0.6, rb: 0.026 + fl * (merino ? 0.3 : 0.35), k: 0.03 }));
418
+ }
419
+ // tail wool
420
+ const tailLen = params.tailLen ?? 0.34;
421
+ for (let i = 0; i < TAIL_SEGS; i++) {
422
+ const t0 = i / TAIL_SEGS, t1 = (i + 1) / TAIL_SEGS;
423
+ const a = J['tail' + i], b = J['tail' + (i + 1)];
424
+ const wf = Math.min(F, 0.04) * (tailLen > 0.15 ? 0.6 : 1.2);
425
+ add1(m.cone({ tag: 'wooltail', bone: 'tail' + i, a, b, ra: 0.03 + wf * (1 - 0.3 * t0), rb: 0.03 * (1 - 0.35 * t1) + wf * (1 - 0.3 * t1), k: i === 0 ? 0.022 : 0.012, thin: i >= TAIL_SEGS - 2 }));
426
+ }
427
+ // Merino skin folds: rings of wool around the neck ("necklace") and over the breast
428
+ const folds = merino ? (params.folds ?? 0.6) : 0;
429
+ if (folds > 0.05) {
430
+ for (let i = 0; i < 4; i++) {
431
+ const t = 0.15 + i * 0.22;
432
+ const c = lerp(J.neckBase, J.occiput, t * 0.8);
433
+ const rr = (0.075 * nk + nf) * (1.02 - 0.2 * t);
434
+ add1(m.ell({ tag: 'fold', bone: t < 0.45 ? 'neck1' : 'neck2', c: add(c, [0, -0.01, 0.01]), r: [rr + 0.012 * folds, rr * 1.1 + 0.012 * folds, 0.018 + 0.008 * folds], axis: norm(sub(J.occiput, J.neckBase)), up: [0, 1, -0.5], k: 0.03 }));
435
+ }
436
+ }
437
+
438
+ // ---- locks: a Poisson-disc scatter of staple bundles over the fleece surface
439
+ if (F < 0.016) return;
440
+ const rl = lockRadius(F, merino); // lock radius
441
+ const spacing = rl * 1.55;
442
+ const list = fleece.filter((p) => !p.carve);
443
+ const f = (p) => SDFModel.evalList(list, p[0], p[1], p[2]);
444
+ const grad = (p) => {
445
+ const e = 0.001;
446
+ return norm([f([p[0] + e, p[1], p[2]]) - f([p[0] - e, p[1], p[2]]), f([p[0], p[1] + e, p[2]]) - f([p[0], p[1] - e, p[2]]), f([p[0], p[1], p[2] + e]) - f([p[0], p[1], p[2] - e])]);
447
+ };
448
+ const cell = spacing;
449
+ const hash = new Map();
450
+ const key = (p) => `${Math.floor(p[0] / cell)},${Math.floor(p[1] / cell)},${Math.floor(p[2] / cell)}`;
451
+ const near = (p) => {
452
+ const i0 = Math.floor(p[0] / cell), j0 = Math.floor(p[1] / cell), k0 = Math.floor(p[2] / cell);
453
+ for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) for (let k = -1; k <= 1; k++) {
454
+ const L = hash.get(`${i0 + i},${j0 + j},${k0 + k}`);
455
+ if (L) for (const q of L) if (Math.hypot(q[0] - p[0], q[1] - p[1], q[2] - p[2]) < spacing) return true;
456
+ }
457
+ return false;
458
+ };
459
+ // area weights: the big torso slabs get most of the samples
460
+ const wts = list.map((p) => (p.type === 0 ? p.P[12] * p.P[13] + p.P[13] * p.P[14] + p.P[12] * p.P[14] : 0.02));
461
+ const wsum = wts.reduce((a, b) => a + b, 0);
462
+ const tries = Math.round(Math.min(16000, 10 * wsum * 4 / (spacing * spacing)));
463
+ const locks = [];
464
+ for (let n = 0; n < tries; n++) {
465
+ let x = R() * wsum, pi = 0;
466
+ while (pi < list.length - 1 && x > wts[pi]) { x -= wts[pi]; pi++; }
467
+ const pr = list[pi];
468
+ let p;
469
+ if (pr.type === 0) {
470
+ // a random point on the ellipsoid's surface
471
+ const u = norm([R() * 2 - 1, R() * 2 - 1, R() * 2 - 1]);
472
+ const P = pr.P;
473
+ p = [0, 1, 2].map((a) => P[a] + P[3 + a] * u[0] * P[12] + P[6 + a] * u[1] * P[13] + P[9 + a] * u[2] * P[14]);
474
+ } else {
475
+ const P = pr.P, t = R();
476
+ p = [P[0] + P[3] * t + (R() - 0.5) * 0.05, P[1] + P[4] * t + (R() - 0.5) * 0.05, P[2] + P[5] * t + (R() - 0.5) * 0.05];
477
+ }
478
+ // project onto the fleece surface
479
+ let ok = true;
480
+ for (let it = 0; it < 6; it++) {
481
+ const d = f(p);
482
+ if (Math.abs(d) < 3e-4) break;
483
+ const g = grad(p);
484
+ p = sub(p, mul(g, d));
485
+ if (it === 5 && Math.abs(f(p)) > 0.003) ok = false;
486
+ }
487
+ if (!ok) continue;
488
+ // not on the face / in front of the collar, not under the hooves
489
+ // (and none at the hood's edge: lumps there stood off the cheek like a wall)
490
+ // (and clear of the head / body hand-over band)
491
+ if (neckS(p[0], p[1], p[2]) > -0.03) continue;
492
+ if (p[1] < 0.1) continue;
493
+ if (near(p)) continue;
494
+ const kk = key(p);
495
+ if (!hash.has(kk)) hash.set(kk, []);
496
+ hash.get(kk).push(p);
497
+ // owning primitive (bone and group) = the nearest fleece primitive
498
+ let best = null, bd = 1e9;
499
+ for (const q of list) { const d = SDFModel.dist(q, p[0], p[1], p[2]); if (d < bd) { bd = d; best = q; } }
500
+ locks.push({ p, n: grad(p), owner: best });
501
+ }
502
+ for (const L of locks) {
503
+ const n = L.n;
504
+ // staples fall down the sides, run back along the top; each lock a little different
505
+ const fall = stapleFall(n, L.p, J);
506
+ // (smaller toward the head, where the scatter stops: full-size locks ending on a line drew a long crevice
507
+ // and a step in the staple length across the collar)
508
+ const r = rl * (0.8 + 0.4 * R()) * (0.35 + 0.65 * smoothstep(-0.03, -0.1, neckS(L.p[0], L.p[1], L.p[2])));
509
+ const onLeg = L.owner.group !== 'axial';
510
+ // (low, staple-shaped blocks blended well in: an even fleece with soft creases between the staples,
511
+ // not deep cauliflower lumps whose crevices shadow into dark blotches under a low sun)
512
+ const c = sub(L.p, mul(n, r * (0.82 + 0.08 * R())));
513
+ m.ell({
514
+ tag: onLeg ? 'lockleg' : 'lock', bone: L.owner.bone, group: L.owner.group,
515
+ c, r: [r, r * 0.9, r * (1.3 + 0.35 * R())], axis: fall, up: n, k: 0.01 + 0.3 * r,
516
+ });
517
+ }
518
+ }
519
+
520
+ // Dewclaws: two small horny knobs behind the fetlock
521
+ function sculptDewclaws(m, bone, group, M, s) {
522
+ for (const k of [1, -1]) m.sphere({ tag: 'dewclaw', bone, group, c: add(M, [0.009 * k * s, -0.006, -0.02]), rad: 0.0052, k: 0.004 });
523
+ }
524
+
525
+ // Cloven hoof: two claws (digits III and IV) with a cleft between them, the front wall parallel to
526
+ // the pastern, heel bulbs behind, flat on the ground.
527
+ function sculptHoof(m, bone, group, C, T, w, s) {
528
+ const zc = (C[2] + T[2]) * 0.5;
529
+ for (const k of [1, -1]) {
530
+ const x = C[0] + 0.0102 * k * w;
531
+ const top = [x, C[1] + 0.008, C[2] - 0.004];
532
+ const toe = [x - 0.002 * k, 0.006, T[2] - 0.004];
533
+ const heel = [x, 0.008, C[2] - 0.015];
534
+ m.cone({ tag: 'hoof', bone, group, a: top, b: toe, ra: 0.0112 * w, rb: 0.0052 * w, k: 0.006 });
535
+ m.cone({ tag: 'hoof', bone, group, a: heel, b: [toe[0], 0.006, zc + 0.004], ra: 0.0108 * w, rb: 0.0088 * w, k: 0.008 });
536
+ m.sphere({ tag: 'heelbulb', bone, group, c: [x, 0.013, C[2] - 0.019], rad: 0.0102 * w, k: 0.008 });
537
+ }
538
+ m.cone({ tag: 'coronet', bone, group, a: add(C, [0, 0.012, -0.01]), b: add(C, [0, 0.006, 0.008]), ra: 0.0165 * w, rb: 0.0165 * w, k: 0.008 });
539
+ m.ell({ tag: 'cleft', bone, group, c: [C[0], 0.006, zc + 0.012], r: [0.0022, 0.02, 0.028], k: 0.002, carve: true });
540
+ m.ell({ tag: 'sole', bone, group, c: [C[0], -0.1 + 0.001, zc], r: [0.1, 0.1, 0.1], k: 0.002, carve: true });
541
+ void s;
542
+ }