@bitefoundry/retrieviewer 0.0.3 → 0.0.5

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 (263) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +95 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/INSTALLING.md +4 -0
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +116 -0
  11. package/README.md +121 -59
  12. package/RELEASING.md +33 -13
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/linux-overlay/overlay.py +8 -2
  17. package/integrations/macos-overlay/overlay.jxa +9 -7
  18. package/integrations/opencode/runtime.mjs +4 -4
  19. package/integrations/opencode/tui.tsx +1 -1
  20. package/integrations/windows-overlay/Overlay.cs +7 -6
  21. package/integrations/windows-overlay/desktop.mjs +2 -1
  22. package/integrations/windows-overlay/protocol.mjs +13 -7
  23. package/integrations/windows-overlay/worker.mjs +1 -1
  24. package/package.json +34 -13
  25. package/puppy-comfort-sprites.mjs +33 -0
  26. package/puppy-companion-comfort-q5.bin.gz +0 -0
  27. package/puppy-companion-comfort.json +140 -0
  28. package/puppy-companion-life.mjs +58 -18
  29. package/puppy-companion-objects.mjs +12 -0
  30. package/puppy-companion-sprites.mjs +4 -2
  31. package/puppy-frame-predictive.mjs +15 -9
  32. package/puppy-frame-store.mjs +2 -2
  33. package/puppy-paper-actions.mjs +4 -2
  34. package/puppy-paper-behaviors.mjs +1 -1
  35. package/puppy-paper-contact.mjs +4 -2
  36. package/puppy-paper-seat.mjs +4 -2
  37. package/puppy-paper-world.mjs +14 -0
  38. package/puppy-paper.mjs +79 -21
  39. package/retrieviewer-cli.mjs +8 -6
  40. package/retrieviewer-plugin.d.mts +1 -1
  41. package/retrieviewer-plugin.mjs +1 -1
  42. package/retrieviewer.d.mts +12 -4
  43. package/retrieviewer.mjs +20 -14
  44. package/locomotion-core.mjs +0 -21
  45. package/locomotion-puppy.animal +0 -0
  46. package/vendor/procedural-animals/package.json +0 -40
  47. package/vendor/procedural-animals/src/animal.js +0 -146
  48. package/vendor/procedural-animals/src/bake.js +0 -51
  49. package/vendor/procedural-animals/src/build.js +0 -81
  50. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  51. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  52. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  53. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  54. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  55. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  56. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  57. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  58. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  59. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  60. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  61. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  62. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  63. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  64. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  65. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  66. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  67. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  68. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  69. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  70. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  71. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  72. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  73. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  74. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  75. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  76. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  77. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  79. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  80. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  81. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  82. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  83. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  84. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  85. package/vendor/procedural-animals/src/index.d.ts +0 -98
  86. package/vendor/procedural-animals/src/index.js +0 -29
  87. package/vendor/procedural-animals/src/registry.js +0 -48
  88. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  89. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  90. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  91. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  92. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  94. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  95. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  96. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  97. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  98. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  99. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  100. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  101. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  102. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  103. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  104. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  105. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  106. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  107. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  108. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  109. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  110. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  111. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  112. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  113. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  114. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  115. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  116. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  117. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  118. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  119. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  120. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  121. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  122. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  123. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  124. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  125. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  126. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  127. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  128. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  129. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  130. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  131. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  132. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  133. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  134. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  135. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  136. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  137. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  138. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  139. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  140. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  141. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  142. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  143. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  144. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  145. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  146. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  147. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  148. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  149. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  150. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  151. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  152. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  153. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  154. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  155. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  156. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  157. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  158. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  159. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  160. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  161. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  162. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  163. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  164. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  165. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  166. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  167. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  168. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  169. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  170. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  171. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  172. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  173. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  174. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  175. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  176. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  177. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  178. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  179. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  180. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  181. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  182. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  183. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  184. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  185. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  186. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  187. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  188. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  189. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  190. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  191. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  192. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  193. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  194. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  195. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  196. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  197. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  198. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  199. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  200. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  201. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  202. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  203. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  204. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  205. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  206. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  207. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  208. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  209. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  210. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  211. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  212. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  213. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  214. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  215. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  216. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  217. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  218. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  219. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  220. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  221. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  222. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  223. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  224. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  225. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  226. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  227. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  228. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  229. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  230. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  231. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  232. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  233. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  234. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  235. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  236. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  237. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  238. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  239. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  240. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  241. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  242. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  243. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  244. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  245. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  246. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  247. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  248. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  249. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  250. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  251. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  252. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  253. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  254. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  255. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  256. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  257. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  258. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  259. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  260. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  261. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  262. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  263. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,547 +0,0 @@
1
- // The goat, 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, face1, face3); the body relative to the joints so the sculpt follows the skeleton.
5
- // Separate surfaces ("parts"): 'jaw' (lower lip, chin and the beard: the mouth opens and the beard
6
- // swings with it) and 'horn' (keratin horns, rigid on the skull).
7
- import { HEAD_O, HZ, HY, hl, hdir, TAIL_SEGS, hqOf, faceZ, KID_FACE } from './rig.js';
8
- import { add, sub, mul, lerp, norm, cross, rng, clamp, smoothstep } from '../../core/math/vec.js';
9
- import { apertureTiltAlong, sculptEyeSocket } from '../../core/sdf/eyeSocket.js';
10
- import { SDFModel } from '../../core/sdf/sdf.js';
11
-
12
- // Eye: globe ~29 mm across, visible opening ~24 x 16 mm, set high on the side
13
- // of the head, prominent, looking out and a little forward.
14
- const EYE_LOCAL = [0.057, -0.004, 0.0];
15
- const eyeDir = (() => {
16
- const a = (24 * Math.PI) / 180;
17
- return norm(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))));
18
- })();
19
- const EYE_BASE = {
20
- 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]),
21
- lid: 0.0017, R: 0.0132, d: 0.0052, off: -0.0006, tilt: 0, irisZ: 0.0081, irisR: 0.0112,
22
- };
23
- // roll the almond: its long axis ~20 deg from the horizontal with the inner corner low (side2, face4,
24
- // walk1), i.e. rolled back ~22 deg from the head axis (a search over a limited range stood it on end)
25
- EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) - 0.38;
26
- export const EYE = EYE_BASE;
27
-
28
- const X = [1, 0, 0];
29
-
30
- // The horn's mesh cell at this individual's quality tier (params.tierRes): its girth sets it (regions.js)
31
- export const hornCell = (params, res = params.tierRes ?? 1.3) => clamp((params.horns?.base || 0.012) * 0.13, 0.0025, 0.0039) * Math.min(res, 3);
32
-
33
- // Growth ridges round the horn's base half (face2, face4: transverse corrugations, the keratin added ring by ring
34
- // at the base), shared by the sculpt (raised collars where the tier's cells resolve them) and the coat (a dark
35
- // groove before each ridge at every tier). Spacing S along the horn (m), height amp x the horn radius, fading out
36
- // toward tEnd (fraction of the length). null for horn buds.
37
- export function hornRidges(params) {
38
- const H = params.horns;
39
- if (!H || H.len < 0.06) return null;
40
- const buck = H.base > 0.02;
41
- const S = clamp(0.45 * H.base, 0.0075, 0.0135);
42
- // (resolved: at least ~2.6 cells per ridge; at the medium tier and coarser the ridges are painted only)
43
- const geo = smoothstep(2.4, 3.0, S / hornCell(params));
44
- // (s0: the first crest, clear of the skin collar round the base: buried in it, a ridge lifted the hair)
45
- return { S, s0: 0.012 + 0.5 * S, amp: buck ? 0.075 : 0.06, tEnd: buck ? 0.62 : 0.5, geo };
46
- }
47
-
48
- // At the low and crowd tiers the horn is meshed as two closed solids: the base ('horn', the tier's horn cells)
49
- // and the tapering outer part ('horntip', at the medium tier's horn cells), the tip's start buried in the base
50
- // and a hair thinner, so the base's rounded end meets it as a fine ring. Returns the first path index of the
51
- // base's end (the tip runs from two segments before it), or -1 when the horn is one solid.
52
- // (one solid at the low tier's ~1 cm cells: the last fifth of a buck's horn, 3-9 mm thick, meshed as a pinched
53
- // neck and a twisted, ragged flake at the tip, the tip read as snapped off; kept ~0.45 of a cell thick, it was a
54
- // sub-cell tube the grid could not resolve)
55
- export function hornSplit(params, pth) {
56
- if ((params.tierRes ?? 1.3) < 2.5 || !pth || pth.length < 6 || params.horns.len < 0.06) return -1;
57
- const hc = hornCell(params);
58
- let j = 1;
59
- while (j < pth.length - 3 && (pth[j].t < 0.3 || pth[j].r > 1.3 * hc) && pth[j].t < 0.75) j++;
60
- return j;
61
- }
62
- export const hornTipCell = (params) => hornCell(params, (params.tierRes ?? 1.3) > 3.5 ? 2.25 : 2);
63
-
64
- // Horn centre line (reference space), shared by the sculpt and the coat: a scimitar arc from the
65
- // poll, up and back, sweeping further back and out along its length. Returns [{ p, r, t }].
66
- // (at the medium tier and coarser the horn is never thinner than ~0.8 of its cell (the tip solid's cell at the low
67
- // and crowd tiers, hornSplit): at the crowd tier's 16 mm cells the tapering half of a buck's horn, 2-8 mm thick,
68
- // broke into ragged fragments along its length, and with ~0.45 of a cell (and at medium with none) the last
69
- // centimetre ended in a black chisel facet or a needle-thin twisted flake past a pinch, a tip that read as snapped
70
- // off; at ~0.8 the tip is a blunt rounded end, as at the high tier)
71
- export function hornPath(params, s) {
72
- const H = params.horns;
73
- if (!H || H.len <= 0) return [];
74
- const rMin = (params.tierRes ?? 1.3) >= 1.9 ? 0.8 * hornTipCell(params) : 0;
75
- const base = hl([0.021 * s, 0.035, -0.052]);
76
- // sagittal start direction (head-local y, z) and the backward-rotated perpendicular
77
- const d0 = norm([0, 0.15 + 0.35 * (H.upright || 0), -1]); // up and back from the poll
78
- const v0 = [0, d0[2], -d0[1]]; // d0 rotated 90 degrees backward in the sagittal plane (back and down)
79
- // (~1 cm segments: at 2.5-3 cm the cones made a faceted curve that caught the light segment by segment)
80
- const n = Math.max(8, Math.round(20 * Math.min(1.6, H.len / 0.2)));
81
- const out = [];
82
- let p = base.slice();
83
- const ds = H.len / n;
84
- for (let i = 0; i <= n; i++) {
85
- const t = i / n;
86
- const th = H.curve * Math.pow(t, 1.15);
87
- const dl = [
88
- s * (H.spread * (0.35 + 1.1 * t)),
89
- d0[1] * Math.cos(th) + v0[1] * Math.sin(th),
90
- d0[2] * Math.cos(th) + v0[2] * Math.sin(th),
91
- ];
92
- const r = Math.max(rMin, H.base * Math.pow(1 - 0.92 * t, 0.85) + 0.0015);
93
- out.push({ p: p.slice(), r, t });
94
- p = add(p, mul(norm(hdir(dl)), ds));
95
- }
96
- return out;
97
- }
98
-
99
- // the direction the inner (cupped) side of an ear faces: forward, a little out and down (erect), toward the
100
- // face and a little forward (lop); shared with the coat (inner-ear skin, the rim of a lop ear)
101
- export function earFacing(params, s) {
102
- return params.ear === 'lop' ? norm(add([-0.9 * s, 0, 0], mul(hdir([0, 0, 1]), 0.4))) : norm([0.25 * s, -0.3, 1]);
103
- }
104
-
105
- export function sculptGoat(m, rig, params = {}) {
106
- const J = rig.J;
107
- const kid = params.age === 'juvenile';
108
- const HQ = hqOf(params);
109
- const male = params.sex === 'male';
110
- const buck = male && !kid;
111
- const heavy = params.heavy ?? (buck ? 1 : 0); // 0 doe .. 1 heavy buck
112
- const R = rng(5311 + (params.coatSeed || 0));
113
- const roman = params.roman || 0; // Nubian / Boer convex nose
114
- void R;
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
- // (head-local points in adult coordinates; a kid's face in front of the eyes is shorter, rig.js faceZ)
124
- const hk = (v) => hl([v[0], v[1], faceZ(v[2], kid)]);
125
- const shortR = (o) => (kid && o.c[2] > 0.02 ? [o.r[0], o.r[1], o.r[2] * KID_FACE] : o.r);
126
- const hell = (o) => m.ell({ ...H, ...o, c: hk(o.c), r: shortR(o), axis: o.axisL ? norm(hdir(o.axisL)) : HZ, up: o.upL ? norm(hdir(o.upL)) : HY });
127
-
128
- // ---------------------------------------------------------------- TORSO
129
- // dairy type: deep barrel, sharp withers, level back, prominent hip bones and pin bones, sloping rump
130
- const gw = 1 + 0.12 * heavy; // bucks are broader
131
- m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 0.585, 0.105], r: [0.122 * gw, 0.18, 0.25], axis: norm([0, 0.08, 1]), k: 0 });
132
- m.ell({ tag: 'girth', bone: 'chest', c: [0, 0.575, 0.28], r: [0.1 * gw, 0.15, 0.12], k: 0.08 });
133
- // (a doe is deep through the belly, deeper in milk: side2, side3; the barrel hung 2-5 cm higher than theirs;
134
- // the Boer's barrel is already deep through its girth, and deeper still its short forelegs could not fold
135
- // under the chest when it lay down)
136
- const milk = !male && !kid ? (params.udder ?? 0.5) * (params.variant === 'boer' ? 0.7 : 1) : 0;
137
- const doe = !male && !kid && params.variant !== 'boer' ? 1 : 0;
138
- // (the trunk pieces blend over 8-10 cm: one smooth barrel with a straight top line, threequarter1 / side2;
139
- // at 5-7 cm the withers, back, loin and flank showed as separate bumps, and the coat parted over the
140
- // junctions)
141
- m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 0.57 - 0.015 * milk, -0.085], r: [0.14 * gw, 0.19 - 0.02 * heavy + 0.02 * milk, 0.165], axis: norm([0, 0.1, -1]), k: 0.09 });
142
- // (the deeper belly of a doe hangs between the elbows and the stifles, clear of the stifles: lowered as a
143
- // whole, the belly's side took the stifle's place and the swinging stifle poked through it at every step)
144
- if (doe) m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 0.43 - 0.008 * milk, -0.02], r: [0.1, 0.075 + 0.012 * milk, 0.125], k: 0.08 });
145
- m.ell({ tag: 'flank', bone: 'spine1', c: [0, 0.62, -0.19], r: [0.12 * gw, 0.12, 0.11], k: 0.09 });
146
- m.ell({ tag: 'withers', bone: 'chest', c: [0, 0.735, 0.22], r: [0.045, 0.05, 0.15], axis: norm([0, -0.12, 1]), k: 0.09 });
147
- m.ell({ tag: 'back', bone: 'spine3', c: [0, 0.72, 0.05], r: [0.09 * gw, 0.05, 0.2], k: 0.1 });
148
- m.ell({ tag: 'loin', bone: 'spine1', c: [0, 0.725, -0.13], r: [0.1 * gw, 0.045, 0.12], k: 0.1 });
149
- // hindquarters: croup sloping to the tail head, hooks (hip points) and pins
150
- m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 0.645, -0.3], r: [0.115 * gw, 0.115, 0.105], k: 0.05 });
151
- m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 0.715, -0.29], r: [0.085 * gw, 0.045, 0.11], axis: norm([0, -0.25, 1]), k: 0.05 });
152
- for (const s of [1, -1]) {
153
- m.sphere({ tag: 'hippoint', bone: 'pelvis', c: [0.095 * s * gw, 0.715, -0.195], rad: 0.021, k: 0.05 });
154
- m.sphere({ tag: 'pinbone', bone: 'pelvis', c: [0.045 * s, 0.69, -0.362], rad: 0.022, k: 0.04 });
155
- m.ell({ tag: 'rump', bone: 'pelvis', c: [0.05 * s, 0.6, -0.335], r: [0.055, 0.1, 0.055], k: 0.06 });
156
- }
157
- // chest front: the breast between the forelegs, running smoothly up into the throat (side2: the point
158
- // of the shoulder shows, the breast does not bulge; it stood 6 cm ahead of the shoulder joints as a knob)
159
- // (front1, front2, threequarter1: one rounded front, the neck flaring into it, the points of the shoulder
160
- // at most a gentle angle at its sides. A breast as wide as the shoulders with the humerus heads at its
161
- // corners read as a box with two round lumps in every front and 3/4 view: the shoulder cones stood
162
- // 1.2 cm proud of a groove, and 2 cm outside every primitive there, where five of them blended. Now the
163
- // breast is narrower and centred, the shoulders 2.5-3 cm behind it; the chest front stands 0.06 / 0.09 /
164
- // 0.11 SH ahead of the forearm at 0.54 / 0.56 / 0.59 SH, side3 0.09 / 0.13 / 0.14 with its near leg)
165
- m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.482, 0.272], r: [0.075, 0.07, 0.07], k: 0.05 });
166
- for (const s of [1, -1]) m.ell({ tag: 'pectoral', bone: 'chest', c: [0.042 * s, 0.475, 0.3], r: [0.04, 0.055, 0.026], k: 0.05 });
167
- // the breast muscles over the sternum
168
- // (bias: its skin counts as the chest's for the weights, so a goat lying on its side rests on it: skinned
169
- // half to the neck and legs, the fuller chest front went 13 mm into the ground in death)
170
- m.ell({ tag: 'pectoral', bone: 'chest', c: [0, 0.525, 0.325], r: [0.075, 0.065, 0.065], k: 0.05, bias: 0.03 });
171
- // udder (adult does): two halves and teats, pink skin (size: dry doe .. in milk); a buck's scrotum.
172
- // Their own closed surface ('udder' part), rigid on the udder bone, its top sunk into the belly: meshed
173
- // with the body, the udder hangs between the gaskins and took 74 % tibia weights (the core gives the limbs
174
- // their share before an appendage), so its skin crumpled and the hair shells over it tore into dark shards
175
- // at every step. Narrow enough to hang between the thighs (their inner skin is ~5 cm off the midline).
176
- const UD = { bone: 'udder', group: 'udder', part: 'udder' };
177
- if (!male && !kid && (params.udder ?? 0.5) > 0.05) {
178
- const u = params.udder ?? 0.5;
179
- for (const s of [1, -1]) {
180
- m.ell({ ...UD, tag: 'udder', c: [0.025 * s, 0.35 - 0.03 * u, -0.338 + HQ], r: [0.032 + 0.013 * u, 0.047 + 0.03 * u, 0.052 + 0.022 * u], k: 0.03 });
181
- const a = [0.033 * s, 0.315 - 0.055 * u, -0.322 + HQ], b = add(a, [0.01 * s, -0.035 - 0.01 * u, 0.013]);
182
- m.cone({ ...UD, tag: 'teat', a, b, ra: 0.0105, rb: 0.0078, k: 0.012 });
183
- }
184
- // the attachment: from the udder up into the belly (hidden inside the body above the groin)
185
- m.ell({ ...UD, tag: 'udder', c: [0, 0.43, -0.345 + HQ], r: [0.045, 0.085, 0.072], k: 0.035 });
186
- }
187
- if (male && !kid) {
188
- // scrotum between the hind legs, under the groin (walk1: a short-haired pendulous bag hanging behind the stifles to
189
- // about the middle of the gaskin): a neck of skin from deep in the groin, narrowing to 4.2 cm below the underline,
190
- // and a pear-shaped bag of two testes side by side (7 cm across, 8 cm deep, 26 cm round once scaled to a buck:
191
- // dairy bucks 26-34 cm), its bottom 18 cm below the groin. The neck is anchored 7 cm inside the body (the old
192
- // sausage's top was 1 cm inside: as the groin skin moved with the thighs its end showed, a separate object). Inside
193
- // the thighs' inner skin (x 4.8-6.4 cm off the midline beside it) with 1.3 cm to spare. Sheath under the belly.
194
- const zc = -0.385 + HQ;
195
- // (widening up into the groin; a flare filling the top of the arch between the thighs read as a second bulb from behind)
196
- m.cone({ ...UD, tag: 'scrotum', a: [0, 0.575, zc + 0.012], b: [0, 0.448, zc - 0.002], ra: 0.034, rb: 0.021, k: 0.02 });
197
- m.ell({ ...UD, tag: 'scrotum', c: [0, 0.392, zc - 0.004], r: [0.03, 0.05, 0.036], k: 0.03 });
198
- for (const s of [1, -1]) m.ell({ ...UD, tag: 'scrotum', c: [0.013 * s, 0.375, zc - 0.006], r: [0.022, 0.05, 0.04], axis: norm([0, 0.12, 1]), k: 0.02 });
199
- m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.375, -0.02], r: [0.017, 0.02, 0.045], axis: norm([0, 0.3, 1]), k: 0.03 });
200
- }
201
-
202
- // ---------------------------------------------------------------- NECK
203
- // long and slender, flattened sideways, deep at the base; a buck's neck is thick with a crest
204
- const nb = J.neckBase, nm = J.neckMid, occ = J.occiput;
205
- const nd1 = norm(sub(nm, nb)), nd2 = norm(sub(occ, nm));
206
- // (perpendiculars in the sagittal plane: fwd = toward the throat, back = toward the crest)
207
- const fwdN = (d) => [0, -d[2], d[1]], backN = (d) => [0, d[2], -d[1]];
208
- const ndM = norm(add(nd1, nd2));
209
- const nk = 1 + 0.3 * heavy;
210
- // (the pieces blend over 7 cm: over a sharper junction the 9 mm coat fanned open along a ragged dark line)
211
- m.ell({ tag: 'neck', bone: 'neck1', c: add(nb, [0, 0.008, 0.006]), r: [0.072 * nk, 0.1, 0.095], axis: nd1, up: [0, 1, -0.6], k: 0.07 });
212
- m.ell({ tag: 'neck', bone: 'neck1', c: add(lerp(nb, nm, 0.72), [0, 0, 0.012]), r: [0.052 * nk, 0.075 * (1 + 0.15 * heavy), 0.1], axis: nd1, up: [0, 1, -0.6], k: 0.07 });
213
- // (a bridge at the mid neck: the neck tapers continuously instead of stepping from one piece to the next)
214
- m.ell({ tag: 'neck', bone: 'neck2', c: add(nm, [0, 0, 0.018]), r: [0.047 * nk, 0.064 * (1 + 0.15 * heavy), 0.075], axis: ndM, up: [0, 1, -0.6], k: 0.07 });
215
- m.ell({ tag: 'neck', bone: 'neck2', c: add(lerp(nm, occ, 0.39), [0, 0, 0.028]), r: [0.042 * nk, 0.052 * (1 + 0.15 * heavy), 0.08], axis: nd2, up: [0, 1, -0.6], k: 0.06 });
216
- // a buck's crest: the thick upper neck behind the axis (a narrow crest stood out as a ridge along the side
217
- // of a doe's neck, where the coat parted)
218
- if (heavy > 0.05) m.ell({ tag: 'crest', bone: 'neck1', c: add(lerp(nb, nm, 0.9), mul(backN(nd1), 0.045)), r: [0.05 + 0.02 * heavy, 0.035 + 0.025 * heavy, 0.13], axis: norm(add(nd1, [0, 0.1, 0])), k: 0.07 });
219
- // underline of the neck (trachea) to the throat
220
- const thr = add(nm, mul(fwdN(ndM), 0.07));
221
- m.cone({ tag: 'throat', bone: 'neck1', a: add(add(nb, mul(nd1, -0.02)), mul(fwdN(nd1), 0.07)), b: thr, ra: 0.038, rb: 0.031, k: 0.045 });
222
- m.cone({ tag: 'throat', bone: 'neck2', a: add(thr, mul(ndM, -0.01)), b: hl([0, -0.105 + (kid ? 0.015 : 0), -0.06]), ra: 0.03, rb: 0.027, k: 0.04 });
223
- // the throatlatch: the larynx fills the corner between the underside of the jaw and the throat (a sharp
224
- // notch there folded over as the head moved, back faces showing at the throat outline); not in kids (their
225
- // throats stretched 3x on 4.2 % of the mesh when grazing)
226
- if (!kid) m.ell({ tag: 'throat', bone: 'neck2', c: hl([0, -0.13, 0.036]), r: [0.02, 0.015, 0.022], k: 0.03 });
227
- // wattles (toggles): two hair-covered tassels hanging from the throat, behind and below the jaw angle
228
- // (at head-local y -0.082 they hung from the cheek beside the eye once the head was carried higher)
229
- if (params.wattles) {
230
- for (const s of [1, -1]) {
231
- const a = hl([0.018 * s, -0.128, -0.068]);
232
- const b = add(a, [0.006 * s, -0.052, -0.006]);
233
- m.cone({ tag: 'wattle', bone: 'neck2', part: 'wattle', a, b, ra: 0.0065, rb: 0.0095, k: 0.008 });
234
- }
235
- }
236
-
237
- // ---------------------------------------------------------------- HEAD (head-local, see rig.js)
238
- // A goat's head from the side is a wedge (side2, walk1, face2, face4; skull anatomy): the eye sits high
239
- // on the side of a deep head, the flat, broad forehead rises above it to the horn bases, the straight
240
- // nasal line runs steeply down (~60 deg below the horizontal, 20 deg below the eye -> nose line) to a
241
- // compact nose, and the lower border of the jaw runs straight from the chin back and down to a deep jaw
242
- // angle behind and 13 cm below the eye (the ramus); the cheek is a flat masseter plate, not a jowl. The
243
- // lips and chin sit well below the nose. From the front the head is widest at the eyes and tapers to
244
- // the muzzle (face1, face3).
245
- // landmarks: poll (0, 0, -0.07), eyes (+-0.057, -0.004, 0), forehead top y 0.052 at z 0.02, nose top
246
- // (0, -0.004, 0.17), nose tip (0, -0.022, 0.205), mouth line y -0.066, chin bottom (0, -0.097, 0.165),
247
- // jaw angle (+-0.031, -0.138, -0.045)
248
- // (kd: a kid's head: a domed braincase, a shallow jaw without the adult's masseter plate)
249
- const kd = kid ? 1 : 0;
250
- hell({ tag: 'cranium', c: [0, 0.012 + 0.004 * kd, -0.042], r: [0.055, 0.041 + 0.01 * kd, 0.058], k: 0.03 });
251
- hell({ tag: 'poll', c: [0, -0.014, -0.068], r: [0.035, 0.037, 0.03], k: 0.03 });
252
- // the dorsal line: one straight line from the forehead between the horn bases to the nose, 16 deg below
253
- // the head axis (58 deg below the horizontal in the bind carriage; side2 55, walk1 60): the forehead
254
- // plate and the nasal bridge are laid along it (a level forehead dropping onto a lower nasal bridge
255
- // made a dog's stop)
256
- const NL = [0, -0.29, 1];
257
- hell({ tag: 'forehead', c: [0, 0.026, 0.012], r: [0.062, 0.018, 0.07], axisL: NL, k: 0.025 });
258
- // nasal bridge: straight / slightly dished in Swiss breeds, convex (Roman) in Nubians and Boers
259
- // (the arch: a separate ridge at mid-face, up to 26 mm for roman = 1)
260
- hell({ tag: 'face', c: [0, -0.004, 0.105], r: [0.029, 0.023, 0.095], axisL: NL, k: 0.025 });
261
- if (roman > 0.05) hell({ tag: 'face', c: [0, 0.0 + 0.013 * roman, 0.105], r: [0.022 + 0.004 * roman, 0.016 + 0.012 * roman, 0.07], axisL: NL, k: 0.03 });
262
- // (the muzzle is broad: face1 / face3, the nose nearly as wide as the face at the mouth; at 5 cm across it
263
- // made a horse's narrow nose)
264
- hell({ tag: 'lowerface', c: [0, -0.042 + 0.004 * kd, 0.082], r: [0.035, 0.043 - 0.006 * kd, 0.088], k: 0.03 });
265
- hell({ tag: 'muzzle', c: [0, -0.032, 0.17], r: [0.031 + 0.003 * roman, 0.027, 0.03], k: 0.024 });
266
- hell({ tag: 'upperlip', c: [0, -0.053, 0.181], r: [0.025, 0.014, 0.021], k: 0.016 });
267
- for (const s of [1, -1]) {
268
- // the face below the eye (maxilla, facial crest): broad under the eyes, tapering to the muzzle
269
- // (face1, face3: the face narrowed straight from the eyes into a horse's nose)
270
- hell({ tag: 'maxilla', c: [0.026 * s, -0.03, 0.045], r: [0.018, 0.033, 0.065], axisL: [0.18 * s, 0, 1], k: 0.03 });
271
- // masseter: a flat swell over the ramus, from below the eye down to the jaw angle
272
- // (its rear edge ended 7 cm behind the eye as a 16 mm step against the jaw angle and throat, blended
273
- // over only 3 cm: a dark strap from under the eye down to the throat on every adult; face1 / face3 /
274
- // side2 show a flat cheek running into the throat, the masseter only a gentle swell)
275
- // (a kid has no plate: its small one ended 6 cm behind the eye in an earlier strap from the eye to the
276
- // throat, and its outline stood as a knob on the throat behind the jaw; the adults' plate moved forward
277
- // stretched a kid's throat 3x on 4.15 % of the mesh when grazing, the skin of the lower head riding on
278
- // the neck. Without it a kid's > 3x is lower, 3.49-3.54 %)
279
- // ... only a fill inside the jaw's hollow below the eye (between the braincase, the maxilla and the jaw,
280
- // raised on a kid: without anything there the kid's head had a pit down to the midline behind the eye)
281
- if (kd) hell({ tag: 'cheek', c: [0.012 * s, -0.055, -0.02], r: [0.02, 0.028, 0.03], k: 0.03 });
282
- else {
283
- hell({ tag: 'cheek', c: [0.035 * s, -0.072, 0.0], r: [0.012, 0.056, 0.045], k: 0.05 });
284
- // behind the ramus (parotid): the side of the head slopes from the cheek into the neck over 9 cm (the
285
- // back of the jaw fell 3-4 cm in as many, a crease from below the eye down to the throat in every
286
- // 3/4 view)
287
- hell({ tag: 'parotid', c: [0.022 * s, -0.072, -0.085], r: [0.024, 0.05, 0.05], k: 0.035 });
288
- }
289
- // the lower border of the jaw, from the jaw angle forward (the front of the mandible is the jaw part)
290
- // (it runs forward over the sides of the chin: the jaw part shows only its lower lip at the front and
291
- // the chin underneath, so the seam between the two surfaces lies along the mouth line and under the
292
- // chin, not on the side of the jaw)
293
- // (a kid's jaw angle 2.2 cm higher, its end 4 mm further in and blended over 3.7 cm: at the side of the
294
- // throat it poked out as a small bare knob, a white fleck at the jaw angle; lower, the throat stretched)
295
- m.cone({ ...H, tag: 'mandible', a: hk([0.029 * s - 0.004 * s * kd, -0.124 + 0.022 * kd, -0.045 + 0.01 * kd]), b: hk([0.013 * s, -0.083, 0.17]), ra: 0.014 - 0.002 * kd, rb: 0.0085, k: 0.025 + 0.012 * kd });
296
- // the cheek between the upper and lower jaws (buccinator), flush from the eye to the mouth corner: the
297
- // face showed a 5-7 mm groove from below the eye forward to the chin between the maxilla and the
298
- // mandible, a long crease in every front and 3/4 view
299
- // (a kid's shallow jaw: the fill stays above its jaw line)
300
- hell({ tag: 'buccal', c: [0.022 * s, -0.073 + 0.005 * kd, 0.085], r: [0.012, 0.019 - 0.004 * kd, 0.07], axisL: [-0.14 * s, 0.14, 1], k: 0.025 });
301
- hell({ tag: 'brow', c: [0.048 * s, 0.017, -0.005], r: [0.012, 0.008, 0.016], k: 0.015 });
302
- hell({ tag: 'nostrilwing', c: [0.02 * s, -0.026, 0.177], r: [0.012, 0.014, 0.014], k: 0.013 });
303
- sculptEyeSocket(m, EYE, HEAD_O, s, { orbit: { r: [0.016, 0.012, 0.01], at: [0.0, 0.001, 0.017], k: 0.007 } });
304
- // nostril: a comma-shaped slit on the front of the nose, the lower ends toward the philtrum and the
305
- // upper ends turned out (face3)
306
- // (its middle, not its tip, breaks the surface: a slit 2 cm long, not a round hole)
307
- hell({ tag: 'nostril', c: [0.018 * s, -0.026, 0.199], r: [0.0026, 0.0105, 0.0065], axisL: [-0.4 * s, 0.15, 1], upL: [0.5 * s, 1, 0], k: 0.0025, carve: true });
308
- }
309
- // the soft floor between the two mandibles (a midline notch under the jaw between the mandible cones)
310
- hell({ tag: 'intermandible', c: [0, -0.094 + 0.008 * kd, 0.068 + 0.012 * kd], r: [0.018, 0.012 - 0.003 * kd, 0.062 - 0.012 * kd], axisL: [0, 0.18, 1], k: 0.02 });
311
- // (no philtrum groove: at mesh resolution its notch at the lip margin showed the lower lip behind it as a
312
- // white fang)
313
-
314
- // ---------------------------------------------------------------- JAW (lower lip, chin, beard)
315
- const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
316
- // a small lower lip tucked under the upper lip, and a rounded chin below it (walk1, face2)
317
- // (the lower lip reaches 6 mm up inside the upper lip, so the mouth stays shut while the jaw grinds)
318
- m.ell({ ...JW, tag: 'chin', c: hk([0, -0.086, 0.168]), r: [0.017, 0.013, 0.02], axis: HZ, up: HY, k: 0 });
319
- m.ell({ ...JW, tag: 'lowerlip', c: hk([0, -0.071, 0.182]), r: [0.02, 0.0105, 0.015], axis: HZ, up: HY, k: 0.012 });
320
- // (the front of the mandible, inside the head's jaw: it carries the chin when the mouth opens; its
321
- // ends reached out between the lips as two white incisors)
322
- for (const s of [1, -1]) m.cone({ ...JW, tag: 'mandible', a: hk([0.009 * s, -0.085, 0.115]), b: hk([0.008 * s, -0.084, 0.152]), ra: 0.006, rb: 0.0065, k: 0.015 });
323
- const beard = params.beard || 0; // length (m)
324
- if (beard > 0.01) {
325
- // hangs from under the chin in three loose locks (face2, face4: a beard of separate locks, the middle one longest),
326
- // each a thin round core with long hair: one 3 cm wide core read as a flat, smooth ribbon inside the hair
327
- const a = hk([0, -0.094, 0.12]);
328
- const down = norm([0, -1, -0.05]);
329
- const nB = 4;
330
- // (each lock a chain of round cones: ellipsoid segments ended in points thinner than a medium-tier cell, whose
331
- // tips folded over)
332
- for (const j of [-1, 0, 1]) {
333
- const L = beard * (j === 0 ? 1 : 0.78);
334
- const at = (t) => add(add(a, mul(down, L * t)), [j * (0.006 + 0.008 * t), 0, -0.024 * t - 0.004 * Math.abs(j)]);
335
- const rad = (t) => (0.0045 + 0.0015 * Math.min(1, beard / 0.15)) * (1 - 0.5 * t);
336
- for (let i = 0; i < nB; i++) {
337
- const t0 = i / nB, t1 = (i + 1) / nB;
338
- m.cone({ ...JW, bone: 'beard', part: 'beard', tag: 'beard', thin: true, a: at(t0), b: at(t1), ra: rad(t0), rb: rad(t1), k: 0.008 });
339
- }
340
- }
341
- // the root of the beard under the chin (a 9 cm lock along the underside of the jaw hung below the
342
- // mandibles as a pale strap)
343
- m.ell({ ...JW, tag: 'beard', c: hk([0, -0.093, 0.13]), r: [0.019, 0.013, 0.026 * (kid ? KID_FACE : 1)], axis: HZ, up: HY, k: 0.016 });
344
- }
345
-
346
- // ---------------------------------------------------------------- EARS
347
- const lop = params.ear === 'lop';
348
- for (const S of ['L', 'R']) {
349
- const s = S === 'L' ? 1 : -1;
350
- const base = J['earBase' + S], tip = J['earTip' + S];
351
- const up = norm(sub(tip, base));
352
- const len = Math.hypot(...sub(tip, base));
353
- // the cup opens forward and down (erect), inward toward the face (lop)
354
- // (erect: the broad inner side faces forward, a little out and down, as in face1 / face3 / front1;
355
- // it faced straight down, so the ears showed their edge from the front)
356
- const facing = earFacing(params, s);
357
- const lat = norm(cross(up, facing));
358
- const fc = norm(cross(lat, up));
359
- const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
360
- // a leaf: a rolled, narrow base, the blade widest at ~40 % of the length, a pointed tip (Swiss:
361
- // ~6 cm wide on 14-16 cm, face1 / face3 / front2); the lop ear a broad drape with a rounded tip
362
- // (it was an 8.4 cm wide disc: mouse ears)
363
- // (a kid's lop ear: its thickness, root roll and rim scaled with the ear's length, x0.82, and a little
364
- // narrower: at the adult's 7.5 mm blades and 5 cm width the blades and the rolled root read through the
365
- // outer face of the short ear as lumps and a raised pad, round the kid's face like a bonnet)
366
- const ek = lop && kid ? 0.82 : 1;
367
- const wd = lop ? 0.05 * (kid ? 0.88 : 1) : 0.029;
368
- if (lop) {
369
- // Nubian / Boer: a long, broad, pendulous ear (the Nubian standard: close to the head at the temple,
370
- // flaring out and forward to the rounded tip, a bell round the face; side_boer). It was one flat oval
371
- // blade pasted on the cheek: short, no roll at the root, no rim.
372
- const out = [s, 0, 0];
373
- const fwd = lat[2] >= 0 ? lat : mul(lat, -1); // the blade's front edge direction
374
- const fwdF = norm(add(fwd, mul(out, 0.5))); // ... turned out in the lower, flaring half
375
- // the root: rolled and narrow, leaving the skull sideways before it drops
376
- // (a 36 mm thick end on the 15 mm blade stood out as a lump with a shadowed step below it)
377
- m.cone({ ...ear, tag: 'ear', a: add(base, mul(up, 0.004)), b: add(lerp(base, tip, 0.28), mul(out, 0.003 * ek)), ra: 0.0115 * ek, rb: 0.0062 * ek, k: 0.018 * ek });
378
- // the upper blade, along the side of the head behind the eye
379
- // (the blades follow the ear bone, which already hangs out 24 deg: set further out on their own they
380
- // stepped out one after the other, an oval lump on the outside of the ear)
381
- ellY({ ...ear, tag: 'ear', c: add(lerp(base, tip, 0.44), mul(fwd, 0.002)), ydir: up, lateral: fwd, r: [wd * 0.84, len * 0.32, 0.0075 * ek], k: 0.016 });
382
- // the lower blade and the rounded tip, turned a little out
383
- ellY({ ...ear, tag: 'ear', c: add(lerp(base, tip, 0.69), mul(out, 0.0015 * ek)), ydir: up, lateral: fwdF, r: [wd, len * 0.28, 0.0072 * ek], k: 0.018 });
384
- ellY({ ...ear, tag: 'eartip', c: add(lerp(base, tip, 0.86), mul(out, 0.002 * ek)), ydir: up, lateral: fwdF, r: [wd * 0.8, len * 0.15, 0.0065 * ek], k: 0.016 });
385
- // the front edge rolled out into a thick rim
386
- m.cone({ ...ear, tag: 'ear', a: add(lerp(base, tip, 0.14), mul(fwd, wd * 0.4)), b: add(lerp(base, tip, 0.64), add(mul(fwdF, wd * 0.86), mul(out, 0.003 * ek))), ra: 0.003 * ek, rb: 0.0055 * ek, k: 0.01 * ek });
387
- // the cup: the inner face hollowed down the middle of the blade (short of the root: medium-tier hole)
388
- // (a kid's cup as deep as an adult's: the coarse tiers inflate thin blades to 1 cm, not carvers, and a cup
389
- // scaled with the kid's blade only grazed the inflated face there, a ragged speckle over the inner face)
390
- ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.55), mul(fc, 0.0068)), ydir: up, lateral: fwd, r: [wd * 0.6, len * 0.28, 0.0052], k: 0.004, carve: true });
391
- } else {
392
- // (the rolled base no thicker than the blade where it ends: a 32 mm round end on the 17 mm blade folded
393
- // the skin over at the medium tier, back faces showing through the ear)
394
- m.cone({ ...ear, tag: 'ear', a: add(base, mul(up, 0.004)), b: lerp(base, tip, 0.27), ra: 0.011, rb: 0.0105, k: 0.014 });
395
- ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.44), ydir: up, lateral: lat, r: [wd, len * 0.42, 0.0085], k: 0.014 });
396
- ellY({ ...ear, tag: 'eartip', c: lerp(base, tip, 0.78), ydir: up, lateral: lat, r: [wd * 0.5, len * 0.24, 0.0065], k: 0.012 });
397
- // (the cup stops short of the narrow root: reaching it, it broke through the ear's floor at the medium
398
- // tier, a ragged hole beside the root; it leaves a 12.5 mm floor at hero and 14 mm at medium: 9.5-12 mm,
399
- // about two medium cells, still showed pinholes through the ear)
400
- // (its root end soft, k 8 mm, and 1 cm further from the root: with a 4 mm blend the end of the carve was a
401
- // corner the medium tier's 5.6 mm cells folded into a white notch)
402
- ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.6), mul(fc, 0.0085)), ydir: up, lateral: lat, r: [wd * 0.66, len * 0.28, 0.0045], k: 0.008, carve: true });
403
- }
404
- m.sphere({ ...H, tag: 'earbase', c: add(base, mul(up, -0.006)), rad: 0.016, k: 0.02 });
405
- }
406
-
407
- // ---------------------------------------------------------------- HORNS (own rigid surface)
408
- for (const s of [1, -1]) {
409
- const pth = hornPath(params, s);
410
- const J = hornSplit(params, pth);
411
- for (let i = 0; i + 1 < pth.length; i++) {
412
- if (J < 0 || i < J) 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 });
413
- if (J >= 0 && i >= J - 2) m.cone({ bone: 'head', part: 'horntip', group: 'axial', tag: 'horn', a: pth[i].p, b: pth[i + 1].p, ra: pth[i].r * (i <= J ? 0.96 : 1), rb: pth[i + 1].r * (i + 1 <= J ? 0.96 : 1), k: 0.004 });
414
- }
415
- // a horn is flattened sideways at its base (a keel along the front)
416
- if (pth.length) {
417
- // (over the first ~2.5 cm of the horn)
418
- let j = 1;
419
- while (j + 1 < pth.length && Math.hypot(...sub(pth[j].p, pth[0].p)) < 0.025) j++;
420
- m.ell({ bone: 'head', part: 'horn', group: 'axial', tag: 'horn', c: lerp(pth[0].p, pth[j].p, 0.5), r: [pth[0].r * 0.75, pth[0].r * 1.15, Math.hypot(...sub(pth[j].p, pth[0].p)) * 0.8], axis: norm(sub(pth[j].p, pth[0].p)), up: HZ, k: 0.004 });
421
- }
422
- // growth ridges: a raised collar every S along the base half, rising over a third of the spacing to its crest and
423
- // falling back to the horn over the rest toward the tip (two round cones about the crest), lower toward tEnd
424
- const RG = hornRidges(params);
425
- if (pth.length && RG && RG.geo > 0.01) {
426
- const L = params.horns.len, n = pth.length - 1;
427
- const at = (sArc) => {
428
- const u = clamp(sArc / L, 0, 1) * n, i = Math.min(n - 1, Math.floor(u)), f = u - i;
429
- return { p: lerp(pth[i].p, pth[i + 1].p, f), r: pth[i].r + (pth[i + 1].r - pth[i].r) * f, t: u / n };
430
- };
431
- for (let sA = RG.s0; sA < RG.tEnd * L; sA += RG.S) {
432
- const c = at(sA), a = at(sA - 0.35 * RG.S), b = at(sA + 0.6 * RG.S);
433
- const h = RG.amp * RG.geo * (1 - smoothstep(0.55 * RG.tEnd, RG.tEnd, c.t));
434
- if (h < 0.01) continue;
435
- const R = { bone: 'head', part: 'horn', group: 'axial', tag: 'horn', k: 0.0015 };
436
- m.cone({ ...R, a: a.p, b: c.p, ra: a.r * (1 + 0.15 * h), rb: c.r * (1 + h) });
437
- m.cone({ ...R, a: c.p, b: b.p, ra: c.r * (1 + h), rb: b.r * (1 + 0.1 * h) });
438
- }
439
- }
440
- // horn base boss on the skull (also the polled goat's small knobs)
441
- if (params.horns && params.horns.len > 0) m.sphere({ ...H, tag: 'hornboss', c: hl([0.021 * s, 0.03, -0.05]), rad: Math.min(0.016, params.horns.base * 0.9), k: 0.015 });
442
- }
443
-
444
- // ---------------------------------------------------------------- MANE (bucks: long hair along the crest and back)
445
- if (buck && (params.mane || 0) > 0.05) {
446
- const mn = params.mane;
447
- // the crest line: the skin of the neck behind its axis (marched out from the axis), then the back
448
- const body = m.prims.filter((q) => (q.part || 'body') === 'body' && !q.carve);
449
- const skin = (p, d) => { let t = 0; while (t < 0.25 && SDFModel.evalList(body, p[0] + d[0] * t, p[1] + d[1] * t, p[2] + d[2] * t) < 0) t += 0.002; return add(p, mul(d, t - 0.012)); };
450
- const line = [
451
- skin(lerp(nm, occ, 0.75), backN(nd2)), skin(lerp(nm, occ, 0.35), backN(nd2)), skin(lerp(nb, nm, 0.85), backN(nd1)),
452
- skin(lerp(nb, nm, 0.4), backN(nd1)), [0, 0.765, 0.2], [0, 0.745, 0.08],
453
- ];
454
- const bones = ['neck2', 'neck2', 'neck1', 'neck1', 'spine3', 'spine3'];
455
- for (let i = 0; i < line.length; i++) {
456
- m.ell({ tag: 'mane', bone: bones[i], c: line[i], r: [0.028 * mn + 0.01, 0.022 * mn + 0.006, 0.055], axis: i + 1 < line.length ? norm(sub(line[i], line[i + 1])) : [0, 0, 1], k: 0.03 });
457
- }
458
- }
459
-
460
- // ---------------------------------------------------------------- LEGS (slender, long cannons)
461
- const lk = 1 + 0.12 * heavy;
462
- for (const S of ['L', 'R']) {
463
- const s = S === 'L' ? 1 : -1;
464
- const lat = [s, 0, 0];
465
- const sx = (v) => [v[0] * s, v[1], v[2]];
466
- const g = 'F' + S;
467
- 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];
468
- 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.022 * lk, 0.1, 0.062], k: 0.06 });
469
- // (the point of the shoulder only a gentle angle: no ball on the humerus head, and the upper arm's cone
470
- // starts a quarter of the way down: seen end-on from the front, a cone from the joint drew a round disc)
471
- m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: lerp(Sh, E, 0.25), b: E, ra: 0.036 * lk, rb: 0.032 * lk, k: 0.04 });
472
- 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.03 * lk, 0.07, 0.048], k: 0.05 });
473
- m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.012, -0.034]), rad: 0.024, k: 0.025 });
474
- m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.005]), b: W, ra: 0.034 * lk, rb: 0.019, k: 0.025 });
475
- 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.032 * lk, 0.075, 0.035 * lk], k: 0.025 });
476
- 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 });
477
- // knee (carpus, with its callus in front)
478
- ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.002]), ydir: [0, 1, 0], lateral: lat, r: [0.021, 0.026, 0.021], k: 0.012 });
479
- 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.0145, rb: 0.0135, k: 0.01 });
480
- m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.015, -0.012]), b: add(M, [0, 0.015, -0.013]), ra: 0.009, rb: 0.011, k: 0.01 });
481
- 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.019, 0.02, 0.021], k: 0.01 });
482
- sculptDewclaws(m, 'fpaw' + S, g, M, s);
483
- m.cone({ tag: 'pastern', bone: 'fpaw' + S, group: g, a: M, b: C, ra: 0.0155, rb: 0.0165, k: 0.01 });
484
- sculptHoof(m, 'fhoof' + S, g, C, T, 1.0, s);
485
-
486
- const h = 'H' + S;
487
- 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];
488
- ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.02, 0, -0.025])), ydir: sub(K, Hp), lateral: lat, r: [0.032 * lk, 0.13, 0.095], k: 0.06 });
489
- m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.075, 0.66, -0.3 + HQ]), b: add(K, sx([0.0, 0.03, 0.02])), ra: 0.048, rb: 0.03, k: 0.05 });
490
- m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.045, 0.64, -0.345]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.04]), ra: 0.045, rb: 0.026, k: 0.04 });
491
- ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.09, 0.47, -0.2 + HQ]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.024, 0.07, 0.038], k: 0.05 });
492
- 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 });
493
- // (a broad gaskin, side2: the lower thigh read thin next to the barrel)
494
- ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.3), sx([0.004, 0, -0.024])), ydir: sub(Hk, K), lateral: lat, r: [0.037 * lk, 0.085, 0.048], k: 0.03 });
495
- m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.026, rb: 0.018, k: 0.025 });
496
- 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 });
497
- m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.028, -0.028]), rad: 0.014, k: 0.012 });
498
- 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.02, 0.03, 0.022], k: 0.015 });
499
- 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.015, rb: 0.0135, k: 0.01 });
500
- m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.016, -0.013]), b: add(Mt, [0, 0.015, -0.013]), ra: 0.009, rb: 0.011, k: 0.01 });
501
- 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.019, 0.02, 0.021], k: 0.01 });
502
- sculptDewclaws(m, 'hpaw' + S, h, Mt, s);
503
- m.cone({ tag: 'pastern', bone: 'hpaw' + S, group: h, a: Mt, b: Ch, ra: 0.015, rb: 0.016, k: 0.01 });
504
- sculptHoof(m, 'hhoof' + S, h, Ch, Tt, 0.95, s);
505
- }
506
-
507
- // ---------------------------------------------------------------- TAIL: short, flat, triangular, carried up
508
- // a thin tail with a flat brush of hair that widens to the tip (side2, walk1)
509
- // (the hair pieces overlap along the tail and blend over 2 cm: five short separate ones left knobs between
510
- // them, a caterpillar)
511
- for (let i = 0; i < TAIL_SEGS; i++) {
512
- const t0 = i / TAIL_SEGS, t1 = (i + 1) / TAIL_SEGS;
513
- const a = J['tail' + i], b = J['tail' + (i + 1)];
514
- const w0 = 0.024 - 0.016 * t0, w1 = 0.024 - 0.016 * t1;
515
- m.cone({ tag: i === 0 ? 'tailhead' : 'tail', bone: 'tail' + i, a, b, ra: w0 * 0.72, rb: w1 * 0.72, k: i === 0 ? 0.03 : 0.012 });
516
- const d = norm(sub(b, a)), tm = (t0 + t1) * 0.5, L = Math.hypot(...sub(b, a));
517
- m.ell({ tag: 'tailhair', bone: 'tail' + i, c: lerp(a, b, 0.55), r: [0.015 + 0.01 * tm, 0.0095 + 0.002 * tm, L * 0.95], axis: d, up: norm(cross(d, X)), k: 0.02 });
518
- }
519
- void kid;
520
- return m;
521
- }
522
-
523
- // Dewclaws: two small horny knobs behind the fetlock
524
- function sculptDewclaws(m, bone, group, M, s) {
525
- for (const k of [1, -1]) m.sphere({ tag: 'dewclaw', bone, group, c: add(M, [0.009 * k * s, -0.006, -0.021]), rad: 0.0055, k: 0.004 });
526
- }
527
-
528
- // Cloven hoof: two claws (digits III and IV) with a cleft between them, the front wall parallel to
529
- // the pastern, heel bulbs behind, flat on the ground.
530
- function sculptHoof(m, bone, group, C, T, w, s) {
531
- const zc = (C[2] + T[2]) * 0.5;
532
- for (const k of [1, -1]) {
533
- const x = C[0] + 0.0105 * k * w;
534
- const top = [x, C[1] + 0.008, C[2] - 0.004];
535
- const toe = [x - 0.002 * k, 0.006, T[2] - 0.004];
536
- const heel = [x, 0.008, C[2] - 0.016];
537
- m.cone({ tag: 'hoof', bone, group, a: top, b: toe, ra: 0.0115 * w, rb: 0.0055 * w, k: 0.006 });
538
- m.cone({ tag: 'hoof', bone, group, a: heel, b: [toe[0], 0.006, zc + 0.004], ra: 0.011 * w, rb: 0.009 * w, k: 0.008 });
539
- m.sphere({ tag: 'heelbulb', bone, group, c: [x, 0.013, C[2] - 0.02], rad: 0.0105 * w, k: 0.008 });
540
- }
541
- m.cone({ tag: 'coronet', bone, group, a: add(C, [0, 0.012, -0.01]), b: add(C, [0, 0.006, 0.008]), ra: 0.017 * w, rb: 0.017 * w, k: 0.008 });
542
- // the cleft between the claws, open at the toe
543
- m.ell({ tag: 'cleft', bone, group, c: [C[0], 0.006, zc + 0.012], r: [0.0022, 0.02, 0.03], k: 0.002, carve: true });
544
- // flat sole
545
- 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 });
546
- void s;
547
- }