@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,316 +0,0 @@
1
- // The horse, sculpted as a signed distance field.
2
- // Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw).
3
- // The head is modelled in its own frame (see rig.js: hl()), measured from the profile and frontal
4
- // references; the body relative to the joints so the sculpt follows the skeleton.
5
- import { HEAD_O, HZ, HY, hl, TAIL_SEGS, DOCK_SEGS } from './rig.js';
6
- import { add, sub, mul, lerp, norm, cross, len, rng } from '../../core/math/vec.js';
7
- import { sculptEyeSocket, apertureTiltAlong } from '../../core/sdf/eyeSocket.js';
8
- import { SDFModel } from '../../core/sdf/sdf.js';
9
-
10
- // Eye (head-local centre -> world offset from HEAD_O). The horse eye is among the largest of land
11
- // mammals: globe ~50 mm across, palpebral opening ~45 x 30 mm, set on the side of the head and
12
- // looking out and a little forward and down.
13
- const EYE_LOCAL = [0.092, -0.004, -0.01]; // (large, high on the head; bulging a little under the brow)
14
- const eyeDir = (() => {
15
- // lateral, 22 degrees forward along the nasal line
16
- const a = (22 * 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.026, back: 0.005, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
21
- lid: 0.0026, R: 0.0262, d: 0.0133, off: -0.0015, tilt: 0, irisZ: 0.0137, irisR: 0.0182,
22
- };
23
- // roll the almond so its long axis follows the nasal line with the upper lid up (a search over a
24
- // limited range stood the almond on end: -99 deg, lids and blinks facing the ear), inner corner a
25
- // little lower: the slit runs ~27 deg below horizontal toward the nose, as in head_profile.jpg
26
- EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) - 0.12;
27
- export const EYE = EYE_BASE;
28
-
29
- const X = [1, 0, 0];
30
-
31
- export function sculptHorse(m, rig, params = {}) {
32
- const J = rig.J;
33
- const crest = params.crest ?? 0.3; // 0 mare .. 1 stallion
34
- const foal = params.age === 'juvenile';
35
- const R = rng(9173 + (params.coatSeed || 0));
36
-
37
- const ellY = (o) => {
38
- const y = norm(o.ydir);
39
- const lat = o.lateral || X;
40
- const z = norm(cross(lat, y));
41
- return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
42
- };
43
- // ellipsoid in head-local coordinates: r = [lateral, dorsal, along the head]
44
- const H = { bone: 'head' };
45
- 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 });
46
- const hdir = (v) => [v[0], v[1] * HY[1] + v[2] * HZ[1], v[1] * HY[2] + v[2] * HZ[2]];
47
-
48
- // ---------------------------------------------------------------- TORSO
49
- // barrel: deep chest (girth ~1.95 m), sternum ~0.88 m above the ground, belly slightly lower
50
- m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 1.26, 0.14], r: [0.285, 0.345, 0.56], axis: norm([0, 0.06, 1]), k: 0 });
51
- m.ell({ tag: 'girth', bone: 'chest', c: [0, 1.22, 0.46], r: [0.24, 0.29, 0.2], k: 0.08 });
52
- m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 1.25, -0.16], r: [0.29, 0.29, 0.32], axis: norm([0, 0.16, -1]), k: 0.1 });
53
- m.ell({ tag: 'flank', bone: 'spine1', c: [0, 1.32, -0.38], r: [0.26, 0.22, 0.22], k: 0.1 });
54
- // withers: the dorsal spines of T3-T9 make a sharp ridge between the scapulae
55
- m.ell({ tag: 'withers', bone: 'chest', c: [0, 1.5, 0.44], r: [0.075, 0.12, 0.22], axis: norm([0, -0.2, 1]), k: 0.1 });
56
- m.ell({ tag: 'back', bone: 'spine3', c: [0, 1.46, 0.08], r: [0.17, 0.085, 0.36], k: 0.1 });
57
- m.ell({ tag: 'loin', bone: 'spine1', c: [0, 1.48, -0.3], r: [0.2, 0.08, 0.22], k: 0.1 });
58
- // hindquarters: croup, points of hip, buttocks
59
- m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 1.41, -0.6], r: [0.27, 0.21, 0.26], k: 0.08 });
60
- m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 1.5, -0.58], r: [0.2, 0.09, 0.27], axis: norm([0, -0.12, 1]), k: 0.08 });
61
- for (const s of [1, -1]) {
62
- m.sphere({ tag: 'hippoint', bone: 'pelvis', c: [0.235 * s, 1.44, -0.43], rad: 0.065, k: 0.08 });
63
- m.ell({ tag: 'rump', bone: 'pelvis', c: [0.13 * s, 1.33, -0.72], r: [0.13, 0.19, 0.12], k: 0.08 });
64
- }
65
- // chest front: the pectorals make a broad, rounded breast between the forelegs
66
- m.ell({ tag: 'brisket', bone: 'chest', c: [0, 1.08, 0.64], r: [0.17, 0.16, 0.17], k: 0.08 });
67
- for (const s of [1, -1]) {
68
- m.ell({ tag: 'pectoral', bone: 'chest', c: [0.105 * s, 1.07, 0.75], r: [0.1, 0.14, 0.09], k: 0.07 });
69
- }
70
- if (params.sex === 'male') {
71
- // sheath between the hind legs
72
- m.ell({ tag: 'sheath', bone: 'spine1', c: [0, 0.94, -0.2], r: [0.045, 0.055, 0.1], axis: norm([0, 0.3, 1]), k: 0.05 });
73
- }
74
-
75
- // ---------------------------------------------------------------- NECK
76
- // laterally flattened, deep at the base, rising to the poll; crest heavier in stallions
77
- // The neck runs from its base (withers top 1.63 m / chest front 0.86 m) to the head (poll /
78
- // throat latch), arched along the crest, deep at the base and narrow at the throat latch.
79
- const WITHERS = [0, 1.64, 0.5], CHESTF = [0, 1.15, 0.86], POLLT = hl([0, 0, -0.2]), LATCH = hl([0, -0.17, 0.07]);
80
- const cBase = lerp(WITHERS, CHESTF, 0.5), cTop = lerp(POLLT, LATCH, 0.5);
81
- const nAx = norm(sub(cTop, cBase)), nUp = norm([0, nAx[2], -nAx[1]]); // dorsal side of the neck
82
- const neckAt = (t) => lerp(cBase, cTop, t);
83
- // [t along the neck, half width, half depth, half length, bone]
84
- for (const [t, hw, hd, hlen, bone] of [[0.12, 0.2, 0.27, 0.2, 'neck1'], [0.38, 0.155, 0.215, 0.2, 'neck1'], [0.62, 0.12, 0.17, 0.18, 'neck2'], [0.84, 0.095, 0.13, 0.14, 'neck2']]) {
85
- m.ell({ tag: 'neck', bone, c: neckAt(t), r: [hw, hd, hlen], axis: nAx, up: nUp, k: 0.09 });
86
- }
87
- // crest: an arch along the top of the neck from the withers to the poll (heavier in stallions)
88
- const crestTop = (t) => { const q = lerp(WITHERS, POLLT, t); return add(q, mul(nUp, 0.045 * Math.sin(Math.PI * t))); };
89
- const cd = norm(sub(POLLT, WITHERS));
90
- for (const [t, r0, bone] of [[0.3, 0.075, 'neck1'], [0.62, 0.06, 'neck2'], [0.86, 0.05, 'neck2']]) {
91
- const c = add(crestTop(t), mul(nUp, -(r0 + 0.012)));
92
- m.ell({ tag: 'crest', bone, c, r: [r0 + 0.035 * crest, r0 + 0.025 * crest, 0.2], axis: cd, up: nUp, k: 0.08 });
93
- }
94
- // underline of the neck (trachea, jugular groove) to a narrow throat latch
95
- const ud = norm(sub(LATCH, CHESTF)), inU = mul(norm([0, ud[2], -ud[1]]), 1);
96
- m.cone({ tag: 'throat', bone: 'neck1', a: add(lerp(CHESTF, LATCH, 0.08), mul(inU, 0.08)), b: add(lerp(CHESTF, LATCH, 0.55), mul(inU, 0.06)), ra: 0.08, rb: 0.06, k: 0.08 });
97
- m.cone({ tag: 'throat', bone: 'neck2', a: add(lerp(CHESTF, LATCH, 0.55), mul(inU, 0.06)), b: hl([0, -0.13, -0.03]), ra: 0.06, rb: 0.045, k: 0.07 });
98
-
99
- // ---------------------------------------------------------------- HEAD (head-local, see rig.js)
100
- // landmarks from head_profile.jpg: forehead top y 0.087 at z -0.05, nasal bridge y 0.058 at 0.15,
101
- // muzzle top 0.028 at 0.36, nostril (0.035, -0.019, 0.355), chin y -0.1 at 0.4, jowl bottom
102
- // y -0.176 at z 0.11, throat latch (-0.17, 0.07), eyes (+-0.083, -0.012, 0)
103
- hell({ tag: 'cranium', c: [0, 0.02, -0.1], r: [0.085, 0.075, 0.11], k: 0.05 });
104
- hell({ tag: 'poll', c: [0, -0.01, -0.17], r: [0.065, 0.065, 0.06], k: 0.05 });
105
- hell({ tag: 'forehead', c: [0, 0.045, 0.0], r: [0.095, 0.042, 0.12], k: 0.04 });
106
- hell({ tag: 'face', c: [0, 0.008, 0.2], r: [0.056, 0.042, 0.19], k: 0.04 }); // (a slightly dished nasal line)
107
- hell({ tag: 'lowerface', c: [0, -0.055, 0.21], r: [0.052, 0.062, 0.15], k: 0.05 });
108
- hell({ tag: 'muzzle', c: [0, -0.026, 0.35], r: [0.05, 0.048, 0.06], k: 0.04 });
109
- hell({ tag: 'upperlip', c: [0, -0.066, 0.372], r: [0.044, 0.026, 0.046], k: 0.025 });
110
- for (const s of [1, -1]) {
111
- // masseter: the big round cheek; mandible's lower border from the jowl to the chin
112
- hell({ tag: 'cheek', c: [0.055 * s, -0.075, 0.07], r: [0.048, 0.095, 0.11], k: 0.05 });
113
- m.cone({ ...H, tag: 'mandible', a: hl([0.05 * s, -0.145, 0.07]), b: hl([0.028 * s, -0.108, 0.3]), ra: 0.03, rb: 0.02, k: 0.04 });
114
- hell({ tag: 'facialcrest', c: [0.075 * s, -0.04, 0.08], r: [0.016, 0.018, 0.08], k: 0.03 });
115
- hell({ tag: 'brow', c: [0.076 * s, 0.034, -0.008], r: [0.03, 0.022, 0.04], k: 0.03 }); // (bony orbital ridge over the eye)
116
- hell({ tag: 'nostrilwing', c: [0.036 * s, -0.02, 0.352], r: [0.02, 0.026, 0.032], k: 0.02 });
117
- // eye: orbit hollow, lids, almond aperture
118
- sculptEyeSocket(m, EYE, HEAD_O, s, { orbit: { r: [0.03, 0.022, 0.018], at: [0.0, 0.002, 0.03], k: 0.012 } });
119
- }
120
- // nostrils: a large comma on each side of the muzzle, facing forward and out, the round opening
121
- // low and its tail (the false nostril) rising up and out. The carves sit a measured depth under
122
- // the skin, so they cut the surface at a steep angle (a carve grazing the skin cut a ragged sliver).
123
- {
124
- const muzPrims = m.prims.filter((q) => q.bone === 'head' && q.part !== 'jaw');
125
- for (const s of [1, -1]) {
126
- const nL = norm([0.6 * s, 0.05, 0.8]); // head-local opening direction
127
- const skinAt = (o) => { // head-local point where the ray o + t nL leaves the muzzle
128
- let lo = 0, hi = 0.12;
129
- for (let i = 0; i < 30; i++) { const t = 0.5 * (lo + hi), q = hl(add(o, mul(nL, t))); if (SDFModel.evalList(muzPrims, q[0], q[1], q[2]) < 0) lo = t; else hi = t; }
130
- return add(o, mul(nL, 0.5 * (lo + hi)));
131
- };
132
- const low = skinAt([0.02 * s, -0.03, 0.34]), high = skinAt([0.03 * s, -0.004, 0.33]);
133
- const up = norm(sub(high, low));
134
- // opening: round low part, narrower tail above (merged into one comma)
135
- hell({ tag: 'nostril', c: sub(low, mul(nL, 0.011)), r: [0.013, 0.017, 0.028], axisL: nL, upL: up, k: 0.006, carve: true });
136
- hell({ tag: 'nostril', c: sub(add(low, mul(up, 0.022)), mul(nL, 0.012)), r: [0.0085, 0.014, 0.024], axisL: nL, upL: up, k: 0.008, carve: true });
137
- }
138
- }
139
-
140
- // ---------------------------------------------------------------- JAW (lower lip and chin: the mouth opens)
141
- const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
142
- m.ell({ ...JW, tag: 'chin', c: hl([0, -0.104, 0.345]), r: [0.036, 0.03, 0.052], axis: HZ, up: HY, k: 0 });
143
- m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.09, 0.39]), r: [0.038, 0.022, 0.034], axis: HZ, up: HY, k: 0.02 });
144
- for (const s of [1, -1]) {
145
- m.cone({ ...JW, tag: 'mandible', a: hl([0.026 * s, -0.104, 0.29]), b: hl([0.018 * s, -0.102, 0.35]), ra: 0.018, rb: 0.022, k: 0.03 });
146
- }
147
-
148
- // ---------------------------------------------------------------- EARS (long, cupped, pointed)
149
- for (const S of ['L', 'R']) {
150
- const s = S === 'L' ? 1 : -1;
151
- const base = J['earBase' + S], tip = J['earTip' + S];
152
- const up = norm(sub(tip, base));
153
- const facing = norm(add(mul(hdir([0, 0, 1]), 0.9), [0.55 * s, 0, 0])); // cup opens forward and out
154
- const lat = norm(cross(up, facing));
155
- const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
156
- ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.42), ydir: up, lateral: lat, r: [0.034, 0.083, 0.02], k: 0.02 });
157
- ellY({ ...ear, tag: 'eartip', c: lerp(base, tip, 0.8), ydir: up, lateral: lat, r: [0.019, 0.045, 0.013], k: 0.02 });
158
- ellY({ ...ear, tag: 'earinner', c: add(add(lerp(base, tip, 0.5), mul(facing, 0.016)), mul(up, 0.0)), ydir: up, lateral: lat, r: [0.024, 0.075, 0.014], k: 0.006, carve: true });
159
- // ear base muscle mass on the poll
160
- m.sphere({ ...H, tag: 'earbase', c: add(base, mul(up, -0.01)), rad: 0.035, k: 0.035 });
161
- }
162
-
163
- // ---------------------------------------------------------------- FORELOCK AND MANE
164
- // mane: a ridge of hair on the crest, and the hair falling from it down one side of the neck
165
- // (usually the right) as one sheet, 14-26 cm long with a ragged lower edge. Shells cannot make long
166
- // hair, so the sheet is geometry: at each station along the crest two flat ellipsoids laid on the
167
- // neck's skin (found in the field), overlapping their neighbours so they merge into one sheet; the
168
- // coat gives it short hair combed straight down with lock-to-lock streaks. (22 round lock cones
169
- // under 5 cm shells read as a row of curls, and as a braid without the fur.)
170
- const side = params.maneSide ?? -1;
171
- const maneLen = foal ? 0.05 : params.maneLen ?? 0.2;
172
- const neckPrims = m.prims.filter((q) => q.part !== 'jaw' && q.bone !== 'head' && !q.carve);
173
- const skinX = (q) => { // the neck's skin on the mane side at the height and depth of q (x ignored)
174
- let lo = 0, hi = 0.45;
175
- for (let i = 0; i < 28; i++) { const x = 0.5 * (lo + hi); if (SDFModel.evalList(neckPrims, side * x, q[1], q[2]) < 0) lo = x; else hi = x; }
176
- return [side * 0.5 * (lo + hi), q[1], q[2]];
177
- };
178
- const NL = 22;
179
- for (let i = 0; i < NL; i++) {
180
- const t = 0.03 + 0.94 * (i + 0.5) / NL;
181
- const lift = mul(nUp, 0.028 * crest * Math.sin(Math.PI * Math.min(1, t * 1.15)));
182
- const p = add(crestTop(t), lift), p2 = add(crestTop(Math.min(1, t + 0.04)), lift), dir = norm(sub(p2, p));
183
- const bone = t < 0.45 ? 'neck1' : 'neck2';
184
- // (the ridge thins out over the last 15 % toward the poll: the mane runs into the forelock
185
- // there, a full ridge stood between the ears as a cushion)
186
- const toPoll = 1 - 0.6 * Math.max(0, Math.min(1, (t - 0.82) / 0.15));
187
- m.ell({ tag: 'manecrest', bone, c: add(p, mul(nUp, -0.01 - 0.008 * (1 - toPoll))), r: [(0.03 + 0.03 * crest) * (0.6 + 0.4 * toPoll), (foal ? 0.045 : 0.028) * toPoll, 0.06], axis: dir, up: nUp, k: 0.04 });
188
- }
189
- if (!foal) {
190
- const NS = 16, down = norm(add(mul(nUp, -1), [0, 0, -0.15]));
191
- const tAt = (i) => 0.03 + 0.94 * (i + 0.5) / NS;
192
- for (let i = 0; i < NS; i++) {
193
- const t = tAt(i);
194
- const lift = mul(nUp, 0.028 * crest * Math.sin(Math.PI * Math.min(1, t * 1.15)));
195
- const p = add(crestTop(t), lift);
196
- const spacing = len(sub(crestTop(tAt(i + 1)), crestTop(t)));
197
- const along = norm(sub(crestTop(Math.min(1, t + 0.02)), crestTop(Math.max(0, t - 0.02))));
198
- const L = maneLen * (0.75 + 0.5 * R()) * (0.65 + 0.35 * Math.sin(Math.PI * Math.min(1, t * 1.25)));
199
- const bone = t < 0.45 ? 'neck1' : 'neck2';
200
- const th = 0.0085;
201
- // the hair leaves the crest over its top: the sheet starts on the crest's far edge
202
- const depths = [0.02, 0.02 + (L - 0.02) * 0.5, L];
203
- const sk = depths.map((d) => skinX(add(p, mul(down, d))));
204
- for (let j = 0; j < 2; j++) {
205
- const a0 = sk[j], a1 = sk[j + 1];
206
- const ax = norm(sub(a1, a0));
207
- const nrm = norm(cross(ax, along)); // out of the skin on the mane side
208
- const out = nrm[0] * side >= 0 ? nrm : mul(nrm, -1);
209
- const c = add(lerp(a0, a1, 0.5), mul(out, th * 0.35));
210
- m.ell({ tag: 'mane', bone, c, r: [spacing * 0.85, th, len(sub(a1, a0)) * 0.58 + 0.01], axis: ax, up: out, k: 0.022 });
211
- }
212
- }
213
- }
214
- if (!foal) {
215
- // forelock: a thin lock of long hair lying flat from the poll down the forehead, ending above the
216
- // eyes (a chain of flat ellipsoids laid on the skin: shells cannot make long hair, so the
217
- // geometry carries the lock and the coat keeps its hair short). Two raised pads read as a fur
218
- // cushion with a hard rim that hid the star.
219
- const headPrims = m.prims.filter((q) => q.bone === 'head' && q.part !== 'jaw');
220
- const skinY = (z) => { // forehead skin height (head-local y) on the midline at head-local z
221
- let lo = 0.0, hi = 0.2; // (y 0 lies inside the cranium / poll from the poll to the eyes)
222
- for (let i = 0; i < 30; i++) { const y = 0.5 * (lo + hi), q = hl([0, y, z]); if (SDFModel.evalList(headPrims, q[0], q[1], q[2]) < 0) lo = y; else hi = y; }
223
- return 0.5 * (lo + hi);
224
- };
225
- const n = 7, z0 = -0.2, z1 = -0.035;
226
- for (let i = 0; i < n; i++) {
227
- const t = i / (n - 1), z = z0 + (z1 - z0) * t;
228
- const zb = z + 0.3 * (z1 - z0) / (n - 1);
229
- const y = skinY(z), yb = skinY(zb);
230
- const half = 0.03 - 0.014 * t; // wide at the poll, narrowing to the tip
231
- const thick = 0.0085 - 0.0035 * t;
232
- const sway = 0.006 * Math.sin(3.1 * t + (params.coatSeed || 0)); // not ruler straight
233
- hell({ tag: 'forelock', c: [sway, y - 0.15 * thick, z], r: [half, thick, 0.034], axisL: [0, yb - y, zb - z], upL: cross(X, [0, yb - y, zb - z]).map((v) => -v), k: 0.012 });
234
- }
235
- }
236
-
237
- // ---------------------------------------------------------------- LEGS
238
- for (const S of ['L', 'R']) {
239
- const s = S === 'L' ? 1 : -1;
240
- const lat = [s, 0, 0];
241
- const sx = (v) => [v[0] * s, v[1], v[2]];
242
- // front
243
- const g = 'F' + S;
244
- 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];
245
- ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.07, 0, -0.02])), ydir: sub(Sh, Sc), lateral: lat, r: [0.08, 0.26, 0.15], k: 0.1, bias: 0.006 });
246
- m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.02, 0.0, 0.03])), rad: 0.07, k: 0.08 });
247
- m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.1, rb: 0.085, k: 0.08 });
248
- ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.01, 0.02, -0.1])), ydir: sub(E, Sh), lateral: lat, r: [0.1, 0.17, 0.12], k: 0.08 });
249
- m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.02, -0.075]), rad: 0.055, k: 0.05 });
250
- m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.01]), b: W, ra: 0.085, rb: 0.047, k: 0.05 });
251
- ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.22), sx([0.008, 0, 0.012])), ydir: sub(W, E), lateral: lat, r: [0.078, 0.16, 0.085], k: 0.05 });
252
- m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.04, 0.06, -0.02])), b: add(lerp(E, W, 0.3), sx([-0.02, 0, 0])), ra: 0.06, rb: 0.045, k: 0.06 });
253
- // knee (carpus): broad and flat in front, accessory carpal bone behind
254
- ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.004]), ydir: [0, 1, 0], lateral: lat, r: [0.05, 0.06, 0.047], k: 0.025 });
255
- m.sphere({ tag: 'accessory', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.03, -0.04]), rad: 0.024, k: 0.02 });
256
- // cannon: flat bone in front, flexor tendons behind
257
- m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.02, 0.0]), b: add(M, [0, 0.02, 0.0]), ra: 0.034, rb: 0.032, k: 0.02 });
258
- m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.03, -0.03]), b: add(M, [0, 0.03, -0.034]), ra: 0.022, rb: 0.026, k: 0.02 });
259
- // fetlock, ergot, pastern, coronet, hoof
260
- ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.012]), ydir: [0, 1, 0.3], lateral: lat, r: [0.044, 0.05, 0.05], k: 0.02 });
261
- m.sphere({ tag: 'ergot', bone: 'fpaw' + S, group: g, c: add(M, [0, -0.02, -0.052]), rad: 0.018, k: 0.018 });
262
- m.cone({ tag: 'pastern', bone: 'fpaw' + S, group: g, a: M, b: C, ra: 0.036, rb: 0.037, k: 0.02 });
263
- sculptHoof(m, 'fhoof' + S, g, C, T, 1.0);
264
-
265
- // hind
266
- const h = 'H' + S;
267
- 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];
268
- // the thigh fills the whole quarter from the point of hip to the stifle and the gaskin
269
- ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.04, 0, -0.05])), ydir: sub(K, Hp), lateral: lat, r: [0.1, 0.3, 0.22], k: 0.1, bias: 0.006 });
270
- m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.2, 1.32, -0.45]), b: add(K, sx([0.0, 0.06, 0.04])), ra: 0.12, rb: 0.07, k: 0.1 });
271
- m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.12, 1.3, -0.74]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.09]), ra: 0.12, rb: 0.065, k: 0.08 });
272
- ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.2, 1.03, -0.3]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.05, 0.15, 0.08], k: 0.1 });
273
- m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.01, 0.01, 0.03])), rad: 0.06, k: 0.07 });
274
- ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.32), sx([0.008, 0, -0.05])), ydir: sub(Hk, K), lateral: lat, r: [0.078, 0.17, 0.095], k: 0.06 });
275
- m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.06, rb: 0.045, k: 0.05 });
276
- m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.08]), b: add(Hk, [0, 0.07, -0.07]), ra: 0.03, rb: 0.024, k: 0.03 });
277
- m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.07, -0.068]), rad: 0.033, k: 0.025 });
278
- ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.01, -0.005]), ydir: [0, 1, 0.25], lateral: lat, r: [0.05, 0.075, 0.055], k: 0.03 });
279
- m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, 0.005]), b: add(Mt, [0, 0.02, 0.0]), ra: 0.036, rb: 0.033, k: 0.02 });
280
- m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, -0.035]), b: add(Mt, [0, 0.03, -0.035]), ra: 0.022, rb: 0.026, k: 0.02 });
281
- ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.012]), ydir: [0, 1, 0.3], lateral: lat, r: [0.043, 0.05, 0.05], k: 0.02 });
282
- m.sphere({ tag: 'ergot', bone: 'hpaw' + S, group: h, c: add(Mt, [0, -0.02, -0.052]), rad: 0.018, k: 0.018 });
283
- m.cone({ tag: 'pastern', bone: 'hpaw' + S, group: h, a: Mt, b: Ch, ra: 0.035, rb: 0.036, k: 0.02 });
284
- sculptHoof(m, 'hhoof' + S, h, Ch, Tt, 0.93);
285
- }
286
-
287
- // ---------------------------------------------------------------- TAIL: dock + hair
288
- const tailLen = foal ? 0.45 : 1;
289
- for (let i = 0; i < DOCK_SEGS; i++) {
290
- const t0 = i / DOCK_SEGS, t1 = (i + 1) / DOCK_SEGS;
291
- m.cone({ tag: 'dock', bone: 'tail' + i, a: J['tail' + i], b: J['tail' + (i + 1)], ra: 0.062 - 0.03 * t0, rb: 0.062 - 0.03 * t1, k: i === 0 ? 0.06 : 0.01 });
292
- }
293
- // hair: one continuous tapered volume from the tail head to the hocks (foal: short fluffy brush)
294
- const hairR = (i) => {
295
- if (foal) return [0.06, 0.068, 0.072, 0.07, 0.06, 0.045, 0.03][Math.min(6, i)];
296
- const t = i / TAIL_SEGS;
297
- return t < 0.55 ? 0.066 + 0.018 * (t / 0.55) : 0.084 - 0.05 * ((t - 0.55) / 0.45) ** 1.5;
298
- };
299
- const nHair = foal ? DOCK_SEGS : TAIL_SEGS;
300
- for (let i = 0; i < nHair; i++) {
301
- m.cone({ tag: 'tailhair', bone: 'tail' + i, a: i === 0 ? lerp(J.tail0, J.tail1, 0.4) : J['tail' + i], b: J['tail' + (i + 1)], ra: hairR(i) * (i === 0 ? 0.8 : 1), rb: hairR(i + 1), k: i === 0 ? 0.03 : 0.015 });
302
- }
303
- void tailLen;
304
- return m;
305
- }
306
-
307
- // Hoof: a truncated cone with its front wall parallel to the pastern, a coronet band on top, the
308
- // heel bulbs behind, flat on the ground (carved at y = 0).
309
- function sculptHoof(m, bone, group, C, T, w) {
310
- const base = [C[0], 0.0, (C[2] + T[2]) * 0.5 - 0.012];
311
- const top = add(C, [0, 0.02, -0.012]);
312
- m.cone({ tag: 'hoof', bone, group, a: top, b: base, ra: 0.045 * w, rb: 0.064 * w, k: 0.012 });
313
- m.cone({ tag: 'coronet', bone, group, a: add(C, [0, 0.022, -0.018]), b: add(C, [0, 0.008, 0.02]), ra: 0.042 * w, rb: 0.042 * w, k: 0.015 });
314
- m.sphere({ tag: 'heelbulb', bone, group, c: add(C, [0, -0.028, -0.05]), rad: 0.03 * w, k: 0.02 });
315
- m.ell({ tag: 'sole', bone, group, c: [C[0], -0.2 + 0.002, base[2]], r: [0.2, 0.2, 0.2], k: 0.004, carve: true });
316
- }
@@ -1,272 +0,0 @@
1
- // One set of skin weights for the head and body copies of the throat and upper neck.
2
- //
3
- // The head (fine cells) and the body (coarse cells) both mesh the upper neck and cross-fade across the
4
- // neck cut (regions.js). The core weighs every surface on its own: the axial coordinate is relaxed
5
- // over each mesh separately and the weights are smoothed over each mesh's own neighbours, so at the
6
- // same point of skin the two copies took weights up to 0.17 (medium) apart, inside the occiput blend
7
- // where the head weight changes fastest. Standing still that is invisible, but when the poll flexes
8
- // (grazing, drinking, a head turned, lying flat) the two copies swing apart along the skin's normal
9
- // by up to 10 mm, ~5x the seam skirt's depth: one surface rises out of the other and its edge reads
10
- // as a dark crease across the throat latch.
11
- //
12
- // Here both copies take the same field across the band: at a point p with head fade f(p) (the neck
13
- // cut's cross-fade alone, without the eyelid patches),
14
- // W(p) = (1 - f) * W_body(p) + f * W_head(p),
15
- // where W_body / W_head are each surface's own weights, read off the other surface at its closest
16
- // point (barycentric on the nearest triangle). Each copy keeps its own weights where it takes over
17
- // (f -> 0 on the body side, f -> 1 on the head side), so neither copy steps within itself, and across
18
- // the middle of the band, where both are drawn, the two copies of a point move as one.
19
- import { neckBand, neckS, H_BODY } from './regions.js';
20
- import { smoothstep } from '../../core/math/vec.js';
21
-
22
- const K = 4;
23
-
24
- export function unifyNeckWeights(ctx) {
25
- const { pos, index, regionOf, regionNames, patchOf, weights, nV, Q } = ctx;
26
- const { skinIndex: SI, skinWeight: SW } = weights;
27
- const hR = regionNames.indexOf('head'), bR = regionNames.indexOf('body');
28
- if (hR < 0 || bR < 0) return { changed: 0 };
29
- const r = Q?.res ?? 1;
30
- const { B, OV, clipBody, clipHead } = neckBand(r);
31
- // The head copy takes the body's weights wherever the body copy is drawn (body fade >= 0.1, the
32
- // seam skirt's floor: neck coordinate < OV + 0.6 B) and hands over to its own weights past it,
33
- // where it is the only surface, finishing 1.5 body cells before the body copy's clip edge. The body
34
- // copy keeps its own (smooth at its coarse cell, so a head vertex reads it exactly off the body's
35
- // triangle).
36
- const s0 = OV + 0.6 * B, s1 = clipBody - 1.5 * H_BODY * r;
37
- const share = (g, s) => (g === hR ? 1 - smoothstep(s0, s1, s) : 0); // share of the other copy's weights
38
- const S = (v) => neckS(pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]);
39
- const inBand = (v) => { const s = S(v); return s > -clipHead - 0.01 && s < clipBody + 0.01; };
40
- const own = (v) => (patchOf && patchOf[v] ? -1 : regionOf[v]);
41
-
42
- // triangles of each copy near the band, in a hash grid
43
- const G = 0.02;
44
- const key = (x, y, z) => `${Math.floor(x / G)},${Math.floor(y / G)},${Math.floor(z / G)}`;
45
- const grids = { [hR]: new Map(), [bR]: new Map() };
46
- for (let i = 0; i < index.length; i += 3) {
47
- const a = index[i], b = index[i + 1], c = index[i + 2];
48
- if (a >= nV || b >= nV || c >= nV) continue;
49
- const g = own(a);
50
- if ((g !== hR && g !== bR) || own(b) !== g || own(c) !== g) continue;
51
- if (!inBand(a) && !inBand(b) && !inBand(c)) continue;
52
- const mn = [0, 1, 2].map((k) => Math.min(pos[a * 3 + k], pos[b * 3 + k], pos[c * 3 + k]));
53
- const mx = [0, 1, 2].map((k) => Math.max(pos[a * 3 + k], pos[b * 3 + k], pos[c * 3 + k]));
54
- for (let x = Math.floor(mn[0] / G); x <= Math.floor(mx[0] / G); x++)
55
- for (let y = Math.floor(mn[1] / G); y <= Math.floor(mx[1] / G); y++)
56
- for (let z = Math.floor(mn[2] / G); z <= Math.floor(mx[2] / G); z++) {
57
- const k = `${x},${y},${z}`;
58
- let l = grids[g].get(k);
59
- if (!l) grids[g].set(k, (l = []));
60
- l.push(i);
61
- }
62
- }
63
-
64
- // closest point of the other copy: [a, b, c, u, v, w] or null (within maxD)
65
- const maxD = 0.03;
66
- const closest = (g, px, py, pz) => {
67
- let best = null, bd = maxD * maxD;
68
- const seen = new Set();
69
- const cx = Math.floor(px / G), cy = Math.floor(py / G), cz = Math.floor(pz / G);
70
- for (let x = cx - 1; x <= cx + 1; x++) for (let y = cy - 1; y <= cy + 1; y++) for (let z = cz - 1; z <= cz + 1; z++) {
71
- const l = grids[g].get(`${x},${y},${z}`);
72
- if (!l) continue;
73
- for (const i of l) {
74
- if (seen.has(i)) continue;
75
- seen.add(i);
76
- const r3 = closestOnTri(pos, index[i], index[i + 1], index[i + 2], px, py, pz);
77
- if (r3.d2 < bd) { bd = r3.d2; best = [index[i], index[i + 1], index[i + 2], r3.u, r3.v, r3.w]; }
78
- }
79
- }
80
- return best;
81
- };
82
-
83
- // new weights, computed from the original ones (the two copies read each other's)
84
- const nSI = new Map(), nSW = new Map();
85
- const acc = new Map();
86
- const add = (v, f) => { for (let k = 0; k < K; k++) { const w = SW[v * K + k]; if (w > 0) acc.set(SI[v * K + k], (acc.get(SI[v * K + k]) || 0) + w * f); } };
87
- let changed = 0;
88
- for (let v = 0; v < nV; v++) {
89
- const g = own(v);
90
- if (g !== hR && g !== bR) continue;
91
- // (the hidden run of each copy past the band follows the other copy all the way to its clip edge)
92
- const a = share(g, S(v));
93
- if (a < 1e-3) continue;
94
- const o = closest(g === hR ? bR : hR, pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]);
95
- if (!o) continue;
96
- acc.clear();
97
- add(v, 1 - a);
98
- add(o[0], a * o[3]); add(o[1], a * o[4]); add(o[2], a * o[5]);
99
- const top = [...acc.entries()].filter((e) => e[1] > 1e-5).sort((x, y) => y[1] - x[1]).slice(0, K);
100
- const sum = top.reduce((t, e) => t + e[1], 0) || 1;
101
- nSI.set(v, top.map((e) => e[0]));
102
- nSW.set(v, top.map((e) => e[1] / sum));
103
- changed++;
104
- }
105
- for (const [v, bs] of nSI) {
106
- const ws = nSW.get(v);
107
- for (let k = 0; k < K; k++) { SI[v * K + k] = k < bs.length ? bs[k] : 0; SW[v * K + k] = k < ws.length ? ws[k] : 0; }
108
- }
109
- return { changed };
110
- }
111
-
112
- // closest point on triangle (a, b, c) to p: squared distance and barycentrics (Ericson, RTCD 5.1.5)
113
- function closestOnTri(P, a, b, c, px, py, pz) {
114
- const ax = P[a * 3], ay = P[a * 3 + 1], az = P[a * 3 + 2];
115
- const abx = P[b * 3] - ax, aby = P[b * 3 + 1] - ay, abz = P[b * 3 + 2] - az;
116
- const acx = P[c * 3] - ax, acy = P[c * 3 + 1] - ay, acz = P[c * 3 + 2] - az;
117
- const apx = px - ax, apy = py - ay, apz = pz - az;
118
- const d1 = abx * apx + aby * apy + abz * apz, d2 = acx * apx + acy * apy + acz * apz;
119
- let u, v, w;
120
- if (d1 <= 0 && d2 <= 0) { u = 1; v = 0; w = 0; } else {
121
- const bpx = px - P[b * 3], bpy = py - P[b * 3 + 1], bpz = pz - P[b * 3 + 2];
122
- const d3 = abx * bpx + aby * bpy + abz * bpz, d4 = acx * bpx + acy * bpy + acz * bpz;
123
- const cpx = px - P[c * 3], cpy = py - P[c * 3 + 1], cpz = pz - P[c * 3 + 2];
124
- const d5 = abx * cpx + aby * cpy + abz * cpz, d6 = acx * cpx + acy * cpy + acz * cpz;
125
- const vc = d1 * d4 - d3 * d2, vb = d5 * d2 - d1 * d6, va = d3 * d6 - d5 * d4;
126
- if (d3 >= 0 && d4 <= d3) { u = 0; v = 1; w = 0; }
127
- else if (d6 >= 0 && d5 <= d6) { u = 0; v = 0; w = 1; }
128
- else if (vc <= 0 && d1 >= 0 && d3 <= 0) { const t = d1 / (d1 - d3); u = 1 - t; v = t; w = 0; }
129
- else if (vb <= 0 && d2 >= 0 && d6 <= 0) { const t = d2 / (d2 - d6); u = 1 - t; v = 0; w = t; }
130
- else if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) { const t = (d4 - d3) / (d4 - d3 + (d5 - d6)); u = 0; v = 1 - t; w = t; }
131
- else { const den = 1 / (va + vb + vc); v = vb * den; w = vc * den; u = 1 - v - w; }
132
- }
133
- const qx = ax + abx * v + acx * w, qy = ay + aby * v + acy * w, qz = az + abz * v + acz * w;
134
- return { d2: (qx - px) ** 2 + (qy - py) ** 2 + (qz - pz) ** 2, u, v, w };
135
- }
136
-
137
- // Spread the head -> neck hand-over across the whole throat latch.
138
- //
139
- // The core weighs the throat by its axial coordinate, but the throat lies in the acute wedge between
140
- // the head and the neck (82 deg at the occiput in the bind pose), where the projection onto the chain
141
- // crowds: on the skin from the back of the jowl down to the windpipe the head weight fell from 0.85
142
- // to 0.15 within ~10 cm (12 deg about the poll). The poll flexes up to ~60 deg past the bind pose
143
- // (a head looking down at the forefeet, on the vertical with the neck up; rearing; a head turned to
144
- // the flank), and a point 25 cm from the occiput then moves ~25 cm relative to the neck: across a
145
- // 10 cm hand-over the skin folds over itself, and the folded fur reads as a dark crease from the
146
- // throat latch up behind the jowl.
147
- //
148
- // Here the weights in the throat wedge are blurred over the surface (both copies together, splatted
149
- // into a grid by vertex area and fade, Gaussian sigma SIGMA, sampled back), so the hand-over runs over
150
- // ~3 sigma of skin, from the back of the jowl into the upper third of the neck. The mask keeps the
151
- // face, the poll and ear bases, and the neck beyond ~0.5 m from the occiput as they
152
- // were; only the share on head / neck2 / neck1 is redistributed (ears keep theirs).
153
- const SIGMA = 0.1;
154
- export function spreadThroatWeights(ctx, sigma = SIGMA) {
155
- const { pos, index, regionOf, regionNames, rigidBone, weights, nV, fade, rig } = ctx;
156
- const { skinIndex: SI, skinWeight: SW } = weights;
157
- const hR = regionNames.indexOf('head'), bR = regionNames.indexOf('body');
158
- const O = rig.J.occiput, N = rig.J.nose;
159
- const ha = Math.atan2(N[1] - O[1], N[2] - O[2]);
160
- const DEG = Math.PI / 180;
161
- const bones = ['head', 'neck2', 'neck1'].map((n) => rig.BONE[n].index);
162
- const NBc = bones.length;
163
- const bslot = new Map(bones.map((b, i) => [b, i]));
164
- // mask: phi, the angle about the occiput (sagittal plane) from the head axis toward the throat: the
165
- // whole wedge to the neck axis (82 deg) and on round the sides and top of the upper neck (a head
166
- // turned or rolled, lying flat or grazing, bends the upper neck there too), and the back of the
167
- // jowl (phi 0-12 deg: a rigid jowl margin creased against the throat when the head tucked in to
168
- // rear), but not the face (phi < -5: eyes, forehead, nasal bone; the jowl keeps >= 0.85 of the head)
169
- // or the skin round the occiput itself (r < 9-16 cm: poll, ear bases)
170
- const mask = (v) => {
171
- const y = pos[v * 3 + 1] - O[1], z = pos[v * 3 + 2] - O[2];
172
- const r = Math.hypot(y, z);
173
- let phi = ha - Math.atan2(y, z);
174
- phi = ((phi / DEG + 540) % 360) - 180;
175
- return smoothstep(-5, 12, phi) * (1 - smoothstep(140, 180, phi)) * smoothstep(0.09, 0.16, r) * (1 - smoothstep(0.5, 0.6, r));
176
- };
177
- const usable = (v) => (regionOf[v] === hR || regionOf[v] === bR) && !(rigidBone && rigidBone[v] >= 0);
178
- const M = new Float32Array(nV);
179
- let mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9], any = false;
180
- for (let v = 0; v < nV; v++) {
181
- if (!usable(v)) continue;
182
- const m = mask(v);
183
- if (m <= 0) continue;
184
- M[v] = m; any = true;
185
- for (let k = 0; k < 3; k++) { mn[k] = Math.min(mn[k], pos[v * 3 + k]); mx[k] = Math.max(mx[k], pos[v * 3 + k]); }
186
- }
187
- if (!any) return { changed: 0 };
188
- const pad = 3 * sigma, c = sigma / 2;
189
- mn = mn.map((x) => x - pad); mx = mx.map((x) => x + pad);
190
- const G = mn.map((x, k) => Math.ceil((mx[k] - x) / c) + 2);
191
- const NC = NBc + 1, cells = G[0] * G[1] * G[2];
192
- const grid = new Float32Array(cells * NC);
193
- // vertex areas
194
- const area = new Float32Array(nV);
195
- for (let i = 0; i < index.length; i += 3) {
196
- const a = index[i], b = index[i + 1], d = index[i + 2];
197
- const ux = pos[b * 3] - pos[a * 3], uy = pos[b * 3 + 1] - pos[a * 3 + 1], uz = pos[b * 3 + 2] - pos[a * 3 + 2];
198
- const wx = pos[d * 3] - pos[a * 3], wy = pos[d * 3 + 1] - pos[a * 3 + 1], wz = pos[d * 3 + 2] - pos[a * 3 + 2];
199
- const A = 0.5 * Math.hypot(uy * wz - uz * wy, uz * wx - ux * wz, ux * wy - uy * wx) / 3;
200
- area[a] += A; area[b] += A; area[d] += A;
201
- }
202
- const cellOf = (v) => [0, 1, 2].map((k) => (pos[v * 3 + k] - mn[k]) / c);
203
- const at = (i, j, k) => (i + G[0] * (j + G[1] * k)) * NC;
204
- const tmp = new Float32Array(NC);
205
- for (let v = 0; v < nV; v++) {
206
- if (!usable(v)) continue;
207
- const g = cellOf(v);
208
- if (g.some((x, k) => x < 0 || x > G[k] - 2)) continue;
209
- const a = area[v] * Math.max(0, fade ? fade[v] : 1);
210
- if (!(a > 0)) continue;
211
- // the vertex's weights on head / neck2 / neck1 (other bones: the nearest of these by the chain)
212
- tmp.fill(0);
213
- let s = 0;
214
- for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; const sl = bslot.get(SI[v * 4 + k]); if (w > 0 && sl !== undefined) { tmp[sl] += w; s += w; } }
215
- if (s < 0.5) continue; // skin of a limb / ear: not a source
216
- for (let k = 0; k < NBc; k++) tmp[k] /= s;
217
- tmp[NBc] = 1;
218
- const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
219
- const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
220
- for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
221
- const t = a * (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
222
- const o = at(i0 + di, j0 + dj, k0 + dk);
223
- for (let q = 0; q < NC; q++) grid[o + q] += t * tmp[q];
224
- }
225
- }
226
- // separable Gaussian blur
227
- const R = Math.ceil(3 * sigma / c), ker = [];
228
- for (let d = -R; d <= R; d++) ker.push(Math.exp(-0.5 * ((d * c) / sigma) ** 2));
229
- const buf = new Float32Array(grid.length);
230
- for (let ax = 0; ax < 3; ax++) {
231
- buf.fill(0);
232
- for (let k = 0; k < G[2]; k++) for (let j = 0; j < G[1]; j++) for (let i = 0; i < G[0]; i++) {
233
- const o = at(i, j, k);
234
- if (grid[o + NBc] === 0 && !grid.subarray(o, o + NC).some((x) => x)) continue;
235
- for (let d = -R; d <= R; d++) {
236
- const ii = i + (ax === 0 ? d : 0), jj = j + (ax === 1 ? d : 0), kk = k + (ax === 2 ? d : 0);
237
- if (ii < 0 || jj < 0 || kk < 0 || ii >= G[0] || jj >= G[1] || kk >= G[2]) continue;
238
- const o2 = at(ii, jj, kk), w = ker[d + R];
239
- for (let q = 0; q < NC; q++) buf[o2 + q] += w * grid[o + q];
240
- }
241
- }
242
- grid.set(buf);
243
- }
244
- // sample back and mix in by the mask
245
- let changed = 0;
246
- const acc = new Map();
247
- for (let v = 0; v < nV; v++) {
248
- const m = M[v];
249
- if (!(m > 0)) continue;
250
- const g = cellOf(v);
251
- const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
252
- const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
253
- tmp.fill(0);
254
- for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
255
- const t = (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
256
- const o = at(i0 + di, j0 + dj, k0 + dk);
257
- for (let q = 0; q < NC; q++) tmp[q] += t * grid[o + q];
258
- }
259
- if (!(tmp[NBc] > 1e-9)) continue;
260
- // only the share on the head / neck bones is redistributed: an ear's or a foreleg's share stays
261
- acc.clear();
262
- let ax = 0;
263
- for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; if (w > 0 && bslot.has(SI[v * 4 + k])) ax += w; }
264
- for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; if (w > 0) acc.set(SI[v * 4 + k], (acc.get(SI[v * 4 + k]) || 0) + w * (bslot.has(SI[v * 4 + k]) ? 1 - m : 1)); }
265
- for (let q = 0; q < NBc; q++) { const w = (tmp[q] / tmp[NBc]) * m * ax; if (w > 0) acc.set(bones[q], (acc.get(bones[q]) || 0) + w); }
266
- const top = [...acc.entries()].filter((e) => e[1] > 1e-4).sort((x, y) => y[1] - x[1]).slice(0, K);
267
- const sum = top.reduce((t, e) => t + e[1], 0) || 1;
268
- for (let k = 0; k < K; k++) { SI[v * K + k] = k < top.length ? top[k][0] : 0; SW[v * K + k] = k < top.length ? top[k][1] / sum : 0; }
269
- changed++;
270
- }
271
- return { changed };
272
- }