@bitefoundry/retrieviewer 0.0.2 → 0.0.4

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