@bitefoundry/retrieviewer 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +107 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +61 -2
  12. package/RELEASING.md +61 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/braille.mjs +45 -0
  16. package/examples/bridge.py +62 -0
  17. package/examples/embed.mjs +15 -0
  18. package/examples/live.mjs +40 -0
  19. package/examples/plugin.mjs +21 -0
  20. package/integrations/opencode/runtime.mjs +195 -0
  21. package/integrations/opencode/tui.tsx +59 -0
  22. package/locomotion-core.mjs +21 -0
  23. package/locomotion-puppy.animal +0 -0
  24. package/package.json +125 -4
  25. package/pet-q5-manifest.json +118 -0
  26. package/puppy-clean.mjs +51 -0
  27. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  28. package/puppy-companion-interactions-v3.json +1 -0
  29. package/puppy-companion-life.mjs +182 -0
  30. package/puppy-companion-objects.mjs +30 -0
  31. package/puppy-companion-sprites.mjs +14 -0
  32. package/puppy-continuous.mjs +57 -0
  33. package/puppy-face.mjs +46 -0
  34. package/puppy-frame-predictive.mjs +147 -0
  35. package/puppy-frame-store.mjs +108 -0
  36. package/puppy-mouth-anchor.json +1 -0
  37. package/puppy-mouth-anchor.mjs +33 -0
  38. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  39. package/puppy-paper-actions-v9.json +1 -0
  40. package/puppy-paper-actions.mjs +48 -0
  41. package/puppy-paper-approach-q5.bin.gz +0 -0
  42. package/puppy-paper-approach.json +1 -0
  43. package/puppy-paper-area.mjs +15 -0
  44. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  45. package/puppy-paper-arrival-seat.json +1 -0
  46. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  47. package/puppy-paper-behaviors.json +1 -0
  48. package/puppy-paper-behaviors.mjs +8 -0
  49. package/puppy-paper-contact.mjs +103 -0
  50. package/puppy-paper-director.mjs +21 -0
  51. package/puppy-paper-paw-transport.mjs +76 -0
  52. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  53. package/puppy-paper-rear-gallop.json +1 -0
  54. package/puppy-paper-seat-q5.bin.gz +0 -0
  55. package/puppy-paper-seat.json +14 -0
  56. package/puppy-paper-seat.mjs +13 -0
  57. package/puppy-paper-trick-timing.mjs +58 -0
  58. package/puppy-paper-world.mjs +108 -0
  59. package/puppy-paper.mjs +271 -0
  60. package/puppy-stable-meta.json +1606 -0
  61. package/puppy-stable.mjs +72 -0
  62. package/puppy-stride-meta.json +343 -0
  63. package/puppy-stride-tweens-q5.bin.gz +0 -0
  64. package/puppy-zoomies.mjs +27 -0
  65. package/puppy-zoomies30.mjs +12 -0
  66. package/retrieviewer-cli.mjs +81 -0
  67. package/retrieviewer-config.mjs +49 -0
  68. package/retrieviewer-install.mjs +157 -0
  69. package/retrieviewer-plugin.d.mts +44 -0
  70. package/retrieviewer-plugin.mjs +97 -0
  71. package/retrieviewer-stdio.mjs +38 -0
  72. package/retrieviewer.d.mts +85 -0
  73. package/retrieviewer.mjs +142 -0
  74. package/vendor/procedural-animals/LICENSE +21 -0
  75. package/vendor/procedural-animals/package.json +40 -0
  76. package/vendor/procedural-animals/src/animal.js +146 -0
  77. package/vendor/procedural-animals/src/bake.js +51 -0
  78. package/vendor/procedural-animals/src/build.js +81 -0
  79. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  80. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  81. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  82. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  83. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  84. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  85. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  86. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  87. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  88. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  89. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  90. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  91. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  92. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  93. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  94. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  95. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  96. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  97. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  98. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  99. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  100. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  101. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  102. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  103. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  104. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  105. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  106. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  107. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  108. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  109. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  110. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  111. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  112. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  113. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  114. package/vendor/procedural-animals/src/index.d.ts +98 -0
  115. package/vendor/procedural-animals/src/index.js +29 -0
  116. package/vendor/procedural-animals/src/registry.js +48 -0
  117. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  118. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  119. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  120. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  121. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  122. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  123. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  124. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  125. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  126. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  127. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  128. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  129. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  130. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  131. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  132. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  133. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  134. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  135. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  136. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  137. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  138. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  139. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  140. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  141. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  142. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  143. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  144. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  145. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  146. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  147. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  148. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  149. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  150. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  151. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  152. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  153. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  154. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  155. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  156. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  157. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  158. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  159. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  160. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  161. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  162. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  163. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  164. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  165. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  166. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  167. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  168. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  169. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  170. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  171. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  172. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  173. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  174. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  175. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  176. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  177. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  178. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  179. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  180. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  181. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  182. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  183. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  184. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  185. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  186. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  187. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  188. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  189. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  190. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  191. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  192. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  193. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  194. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  195. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  196. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  197. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  198. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  199. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  200. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  201. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  202. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  203. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  204. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  205. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  206. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  207. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  208. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  209. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  210. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  211. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  212. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  213. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  214. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  215. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  216. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  217. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  218. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  219. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  220. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  221. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  222. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  223. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  224. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  225. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  226. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  227. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  228. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  229. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  230. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  231. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  232. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  233. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  234. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  235. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  236. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  237. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  238. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  239. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  240. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  241. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  242. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  243. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  244. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  245. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  246. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  247. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  248. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  249. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  250. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  251. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  252. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  253. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  254. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  255. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  256. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  257. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  258. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  259. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  260. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  261. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  262. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  263. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  264. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  265. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  266. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  267. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  268. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  269. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  270. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  271. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  272. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  273. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  274. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  275. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  276. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  277. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  278. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  279. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  280. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  281. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  282. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  283. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  284. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  285. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  286. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  287. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  288. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  289. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  290. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  291. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  292. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,361 @@
1
+ // The horse's coat: base colours (bay, dark bay, chestnut, black, grey, palomino, dun, buckskin),
2
+ // black points, dorsal stripe and leg bars (dun), dapples (grey), white face and leg markings with
3
+ // pink skin and pale hooves under them, long mane / forelock / tail hair, hair flow following the
4
+ // equine hair tracts (forehead whorl, down the face, backward and down the barrel, down the legs),
5
+ // bare muzzle skin with whiskers, keratin hooves.
6
+ // Works in the reference space (see rig.js); seeded variation picks the colour and the markings.
7
+ import { HEAD_O, HZ, HY, DOCK_SEGS } from './rig.js';
8
+ import { neckS } from './regions.js';
9
+ import { unifyNeckWeights, spreadThroatWeights } from './seamWeights.js';
10
+ import { blendHeadNeckCoat } from './coatSeam.js';
11
+ import { SDFModel } from '../../core/sdf/sdf.js';
12
+ import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
13
+ import { srgb, mix3, MAT, poissonFeatures, featureDistance, smoothField } from '../../core/build/coatKit.js';
14
+
15
+ // sRGB swatches sampled from the reference photos
16
+ export const COATS = {
17
+ bay: { body: 0x6b3a1f, dorsal: 0x57301a, belly: 0x7a4527, head: 0x62351d, points: 0x17120f, mane: 0x121010, blackPoints: 1 },
18
+ darkbay: { body: 0x3e2418, dorsal: 0x2e1b13, belly: 0x5a3421, head: 0x3a2217, points: 0x141010, mane: 0x100d0c, blackPoints: 1 },
19
+ // (chestnut: #8A4A22 desaturated ~25 % and lifted a little: rendered flanks read as brick red otherwise)
20
+ chestnut: { body: 0x845231, dorsal: 0x77492b, belly: 0x93613a, head: 0x7d4d2e, points: 0x74452a, mane: 0x70432a, blackPoints: 0 },
21
+ black: { body: 0x1f1b1a, dorsal: 0x1a1716, belly: 0x2a221e, head: 0x1f1b1a, points: 0x131010, mane: 0x100e0d, blackPoints: 0 },
22
+ grey: { body: 0x9a9893, dorsal: 0x8e8c87, belly: 0xb4b2ac, head: 0xa8a6a0, points: 0x5a5856, mane: 0xcfcdc7, blackPoints: 0, dapple: 0xc9c7c1 },
23
+ palomino: { body: 0xd4a95a, dorsal: 0xc99d50, belly: 0xdcb46a, head: 0xcfa355, points: 0xc09450, mane: 0xefe3c2, blackPoints: 0 },
24
+ dun: { body: 0xb89a6a, dorsal: 0xa98c5e, belly: 0xc6aa7c, head: 0x7a6040, points: 0x1c1712, mane: 0x16120f, blackPoints: 1, dunFactor: 1 },
25
+ buckskin: { body: 0xb98a4f, dorsal: 0xac7e46, belly: 0xc49a60, head: 0xa87c45, points: 0x1a1410, mane: 0x141110, blackPoints: 1 },
26
+ };
27
+ const WHITE = 0xf0ece4, PINK = 0xc9a09a, MUZZLE = 0x2c2a2a, HOOF = 0x3a3029, HOOF_PALE = 0xb8a88a;
28
+
29
+ function palette(p) {
30
+ const C = COATS[p.coat] || COATS.bay;
31
+ const k = p.coatShade || 0, l = p.coatLightness || 0;
32
+ const tw = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.08 * k + l), c[1] * (1 + l), c[2] * (1 - 0.08 * k + l)]; };
33
+ const out = { body: tw(C.body), dorsal: tw(C.dorsal), belly: tw(C.belly), head: tw(C.head), points: srgb(C.points), mane: srgb(C.mane), blackPoints: C.blackPoints, dunFactor: C.dunFactor || 0 };
34
+ if (p.coat === 'chestnut' && p.flaxen) out.mane = srgb(0xcfb37e);
35
+ if (p.coat === 'grey') {
36
+ // greys whiten with age: 0 = dark dapple grey, 1 = almost white
37
+ const g = p.greyLevel ?? 0.4;
38
+ const lift = (c, t) => mix3(c, srgb(0xe4e2dc), t);
39
+ out.body = lift(out.body, g * 0.8); out.dorsal = lift(out.dorsal, g * 0.75); out.belly = lift(out.belly, g * 0.8); out.head = lift(out.head, g * 0.85);
40
+ out.points = lift(out.points, g * 0.6); out.mane = lift(out.mane, g * 0.5);
41
+ out.dapple = lift(srgb(C.dapple), g * 0.6);
42
+ }
43
+ out.white = srgb(WHITE); out.pink = srgb(PINK); out.muzzle = srgb(MUZZLE); out.hoof = srgb(HOOF); out.hoofPale = srgb(HOOF_PALE);
44
+ return out;
45
+ }
46
+
47
+ const HAIR_TAGS = new Set(['mane', 'manecrest', 'forelock']);
48
+ const HOOF_TAGS = new Set(['hoof', 'heelbulb']);
49
+
50
+ export function horseCoat(ctx) {
51
+ const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params, lists } = ctx;
52
+ const { BONES } = rig;
53
+ const J = rig.J;
54
+ const R = rng(4242 + (params.coatSeed || 0));
55
+ const COL = palette(params);
56
+ const foal = params.age === 'juvenile';
57
+ const { dominant, axialS, skinIndex, skinWeight, limbMember } = weights;
58
+ const AX_LEN = weights.axialLengths;
59
+ const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
60
+ const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
61
+ const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8];
62
+ const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
63
+ const jawPrims = model.forPart('jaw');
64
+ const headPrims = model.forPart('body').filter((p) => p.bone === 'head' && !p.carve);
65
+ const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
66
+ const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof|femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name));
67
+ const legOf = BONES.map((b) => (/^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(b.name) ? 'F' : /^(femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name) ? 'H' : '') + (b.side || ''));
68
+ const tailIdx = (name) => (name.startsWith('tail') ? +name.slice(4) : -1);
69
+ // head-local coordinates (x lateral, y dorsal, z along the nasal line)
70
+ const HL = (p) => { const d = sub(p, HEAD_O); return [d[0], dot(d, HY), dot(d, HZ)]; };
71
+
72
+ // --- nearest primitive tag per vertex
73
+ const tagOf = new Array(nV);
74
+ for (let v = 0; v < nV; v++) {
75
+ const L = lists[v], p = P(v);
76
+ let best = 1e9, t = '';
77
+ for (const pr of L) {
78
+ if (pr.carve) continue;
79
+ const d = SDFModel.dist(pr, p[0], p[1], p[2]);
80
+ if (d < best) { best = d; t = pr.tag; }
81
+ }
82
+ tagOf[v] = t;
83
+ }
84
+
85
+ // --- regions: 0 torso, 1 neck, 2 head, 3 mane / forelock, 4 dock, 5 ear, 6 jaw, 7 tail hair
86
+ const region = new Uint8Array(nV);
87
+ const legness = new Float32Array(nV);
88
+ const legBone = new Int16Array(nV).fill(-1);
89
+ for (let v = 0; v < nV; v++) {
90
+ const bn = BONES[dominant[v]].name, p = P(v), tag = tagOf[v];
91
+ let lb = -1, lw = 0;
92
+ for (let k = 0; k < 4; k++) {
93
+ const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
94
+ if (isLimb[b] && w > lw) { lw = w; lb = b; }
95
+ }
96
+ legBone[v] = lb;
97
+ legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
98
+ const ti = tailIdx(bn);
99
+ if (partOf[v] === 2) region[v] = 6;
100
+ else if (bn.startsWith('ear')) region[v] = 5;
101
+ else if (HAIR_TAGS.has(tag)) region[v] = 3;
102
+ else if (tag === 'tailhair') region[v] = 7;
103
+ else if (ti >= 0 && axialS[v] > sTailBase + 0.02) region[v] = ti >= DOCK_SEGS ? 7 : 4;
104
+ else if (axialS[v] < sOcc + 0.02 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.03)) region[v] = 2;
105
+ else if (axialS[v] < sNeckBase) region[v] = 1;
106
+ else region[v] = 0;
107
+ if (region[v] >= 2) legness[v] = 0;
108
+ }
109
+
110
+ // --- white markings (face + legs), jagged edges
111
+ const face = params.face || 'none';
112
+ const socks = params.socks || [0, 0, 0, 0]; // FL FR HL HR marking height above ground (m), 0 = none
113
+ const LEGI = { FL: 0, FR: 1, HL: 2, HR: 3 };
114
+ const whiteSDF = (v) => {
115
+ const p = P(v), r = region[v];
116
+ const jag = (fbm3(p[0] * 60, p[1] * 60, p[2] * 60, 2) - 0.5) * 0.02;
117
+ let d = 1;
118
+ if (r === 2 || r === 6) {
119
+ const h = HL(p), ax = Math.abs(h[0]);
120
+ const front = (dot(N(v), HY) > -0.2 && h[1] > -0.07 + 0.12 * smoothstep(0.1, -0.1, h[2])) || h[2] > 0.3;
121
+ if (!front) return 1;
122
+ // (star: on the forehead at the eyes' level, just below the forelock's tip, which ends at z ~0)
123
+ if (face === 'star' || face === 'starsnip' || face === 'stripe') d = Math.min(d, Math.hypot(ax / 0.028, (h[2] - 0.028) / 0.034) * 0.028 - 0.028);
124
+ if (face === 'stripe') d = Math.min(d, Math.max(ax - 0.014 - 0.006 * smoothstep(0, 0.3, h[2]), -0.02 - h[2], h[2] - 0.3));
125
+ if (face === 'blaze') d = Math.min(d, Math.max(ax - (0.03 + 0.03 * smoothstep(0.15, 0.4, h[2])), -0.07 - h[2]));
126
+ if (face === 'snip' || face === 'starsnip') d = Math.min(d, Math.hypot(ax / 0.022, (h[2] - 0.36) / 0.035) * 0.022 - 0.022);
127
+ return d + jag;
128
+ }
129
+ const lb = legBone[v];
130
+ if (lb >= 0 && legness[v] > 0.3) {
131
+ const hgt = socks[LEGI[legOf[lb]]] || 0;
132
+ if (hgt > 0) d = p[1] - hgt + jag * 1.5;
133
+ }
134
+ return d;
135
+ };
136
+
137
+ // --- ventral factor (lighter belly, inner legs, flank)
138
+ const ventral = new Float32Array(nV);
139
+ for (let v = 0; v < nV; v++) {
140
+ const n = N(v), r = region[v], p = P(v);
141
+ let w = 0;
142
+ if (r === 0) w = smoothstep(-0.2, -0.8, n[1]) * smoothstep(1.25, 1.0, p[1]);
143
+ else if (r === 1) w = smoothstep(0.0, -0.7, n[1]) * 0.5;
144
+ const L = legness[v];
145
+ if (L > 0) { const side = p[0] >= 0 ? 1 : -1; w = mix(w, smoothstep(0.1, -0.7, n[0] * side) * 0.6 * smoothstep(0.5, 0.9, p[1]), L); }
146
+ ventral[v] = w;
147
+ }
148
+
149
+ // --- comb (hair flow)
150
+ const comb = new Float32Array(nV * 3);
151
+ const whorl = [0, 0.055, -0.02]; // forehead whorl, head-local
152
+ for (let v = 0; v < nV; v++) {
153
+ const p = P(v), n = N(v), r = region[v];
154
+ const bone = BONES[dominant[v]];
155
+ let d;
156
+ if (r === 5) d = norm(sub(bone.tail, bone.head));
157
+ else if (r === 2 || r === 6) {
158
+ const h = HL(p);
159
+ // from the whorl: down the face toward the muzzle, back and down on the cheeks and jowl
160
+ const rel = [h[0] - whorl[0], h[1] - whorl[1], h[2] - whorl[2]];
161
+ const lh = [rel[0], rel[1] * 0.4, rel[2] + 0.08];
162
+ const w = [lh[0], lh[1] - 0.05, lh[2]];
163
+ d = norm(add(mul(HY, w[1]), add(mul(HZ, w[2]), [w[0], 0, 0])));
164
+ if (h[2] < -0.1) d = norm(add(d, [0, -0.6, -0.8]));
165
+ } else if (r === 3) {
166
+ // mane falls down the side of the neck; the forelock down the face; foal mane stands up
167
+ if (tagOf[v] === 'forelock') d = HZ;
168
+ else if (foal) d = [0, 1, 0.2];
169
+ else {
170
+ // straight down the mane side; on the far side of the crest up and over to the mane side
171
+ const ms = params.maneSide ?? -1;
172
+ d = p[0] * ms >= 0 ? norm([ms * 0.35, -1, -0.2]) : norm([ms * 0.6, 0.8, -0.2]);
173
+ }
174
+ } else if (r === 7 || r === 4) {
175
+ d = norm(sub(bone.tail, bone.head));
176
+ if (r === 4) d = norm(add(d, [0, 0, -0.2]));
177
+ } else {
178
+ const seg = weights.segT[v * 2] | 0;
179
+ const a = rig.AXIAL.points[seg], b = rig.AXIAL.points[Math.min(seg + 1, rig.AXIAL.points.length - 1)];
180
+ d = norm(sub(b, a));
181
+ if (r === 0) d = norm(add(d, [0, -0.45 * Math.abs(n[0]), 0]));
182
+ if (r === 1) d = norm(add(d, [0, -0.5, 0.1]));
183
+ const L = legness[v];
184
+ if (L > 0 && legBone[v] >= 0) {
185
+ const lbn = BONES[legBone[v]];
186
+ let dl = norm(sub(lbn.tail, lbn.head));
187
+ if (/hoof/.test(lbn.name)) dl = [0, -1, 0.1];
188
+ else dl = norm(add(dl, [0, -0.3, 0]));
189
+ d = norm(add(mul(d, 1 - L), mul(dl, L)));
190
+ }
191
+ }
192
+ let t = sub(d, mul(n, dot(d, n)));
193
+ if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
194
+ t = norm(t);
195
+ comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
196
+ }
197
+
198
+ // --- dapples (grey): light rounded spots with a darker network between them
199
+ let dapples = null;
200
+ if (params.coat === 'grey') {
201
+ dapples = poissonFeatures({
202
+ pos, nrm, nV, R, cell: 0.08, spacing: 1.25, gap: 0.004,
203
+ radius: (v) => (region[v] <= 1 && legness[v] < 0.6 && ventral[v] < 0.7 ? 0.028 : 0),
204
+ });
205
+ }
206
+
207
+ // --- per-vertex fields
208
+ const pattern = new Float32Array(nV).fill(1);
209
+ const patInt = new Float32Array(nV);
210
+ const patCol = new Float32Array(nV * 3);
211
+ const markSDF = new Float32Array(nV).fill(1);
212
+ const furLen = new Float32Array(nV);
213
+ const tint = new Float32Array(nV * 4);
214
+ const surf = new Float32Array(nV * 4);
215
+ const lipLocal = (h) => h[1] + 0.078 - 0.02 * smoothstep(0.26, 0.4, h[2]);
216
+
217
+ for (let v = 0; v < nV; v++) {
218
+ const p = P(v), n = N(v), r = region[v], tag = tagOf[v];
219
+ let col, mat = MAT.FUR, fl = 0.008, mark = 1, gloss = 0.6;
220
+ const wsdf = whiteSDF(v);
221
+ const lb = legBone[v];
222
+ const lbName = lb >= 0 ? BONES[lb].name : '';
223
+ if (r === 0 || r === 1) {
224
+ const up = clamp(n[1], -1, 1);
225
+ col = mix3(COL.body, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.8);
226
+ col = mix3(col, COL.belly, ventral[v]);
227
+ fl = mix(0.008, 0.011, ventral[v]);
228
+ if (r === 1) fl = 0.008;
229
+ // dun: dorsal stripe from the mane to the tail
230
+ if (COL.dunFactor && r === 0) mark = Math.min(mark, Math.abs(p[0]) - 0.022 - 0.01 * smoothstep(0.3, -0.6, p[2]) + (1 - smoothstep(0.85, 0.97, up)) * 0.1);
231
+ const L = legness[v];
232
+ if (L > 0) {
233
+ // legs: coat colour above, black points below the knees / hocks (bay, dun, buckskin)
234
+ let lc = col;
235
+ const knee = /^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(lbName) ? 0.52 : 0.6;
236
+ const pts = COL.blackPoints ? smoothstep(knee + 0.18, knee - 0.02, p[1] + (fbm3(p[0] * 20, p[1] * 8, p[2] * 20, 2) - 0.5) * 0.12) : 0;
237
+ lc = mix3(lc, COL.points, pts);
238
+ if (!COL.blackPoints && params.coat === 'grey') lc = mix3(lc, COL.points, smoothstep(0.6, 0.2, p[1]) * 0.8);
239
+ // dun: faint zebra bars on the forearms and gaskins
240
+ if (COL.dunFactor && p[1] > 0.5 && p[1] < 0.95) { const bar = Math.sin(p[1] * 70); lc = mix3(lc, COL.points, 0.22 * smoothstep(0.82, 0.95, bar) * smoothstep(0.95, 0.75, p[1])); }
241
+ col = mix3(col, lc, L);
242
+ fl = mix(fl, mix(0.004, 0.008, smoothstep(0.3, 0.9, p[1])), L);
243
+ // fetlock feathers
244
+ if (/metacarpus|metatarsus|fpaw|hpaw/.test(lbName) && p[1] < 0.24 && p[1] > 0.1) fl = Math.max(fl, 0.018 * smoothstep(0.0, -0.8, n[2]));
245
+ // chestnuts (horny callosities) inside the forearm above the knee and below the hock
246
+ const side = p[0] >= 0 ? 1 : -1;
247
+ const chestnutY = /radius/.test(lbName) ? 0.62 : /metatarsus/.test(lbName) ? 0.46 : -1;
248
+ if (chestnutY > 0 && n[0] * side < -0.6) { const J0 = lbName.startsWith("radius") ? J["wrist" + BONES[lb].side] : J["hock" + BONES[lb].side]; if (Math.hypot((p[1] - chestnutY) / 0.024, (p[2] - J0[2]) / 0.014) < 1) { mat = MAT.KERATIN; col = srgb(0x3a302a); fl = 0; } }
249
+ }
250
+ // hoof wall below the coronet line (higher at the toe than at the heel)
251
+ const Cj = /hoof|paw/.test(lbName) ? J[(lbName[0] === 'f' ? 'fcoffin' : 'hcoffin') + BONES[lb].side] : null;
252
+ if (Cj && HOOF_TAGS.has(tag) && p[1] < Cj[1] + 0.012 + 0.45 * (p[2] - Cj[2])) {
253
+ mat = MAT.KERATIN; fl = 0; gloss = 0.55;
254
+ col = wsdf < 0 ? COL.hoofPale : COL.hoof;
255
+ if (tag === 'heelbulb') col = mix3(col, COL.muzzle, 0.5);
256
+ }
257
+ } else if (r === 3) {
258
+ col = COL.mane;
259
+ // (the forelock lies flat: its lock is geometry, the shells only add short hair along it)
260
+ // (mane: the sheet is geometry; its hair stays under the 25 mm long-hair bevel, so every edge of
261
+ // it - the poll, the forelock - tapers at the fur slope limit instead of standing as a wall)
262
+ fl = tag === 'manecrest' ? (foal ? 0.03 : 0.022) : tag === 'forelock' ? 0.012 : 0.018;
263
+ if (foal) fl = 0.03;
264
+ } else if (r === 4) {
265
+ col = mix3(COL.body, COL.mane, 0.6 + 0.4 * smoothstep(-0.2, 0.6, n[1]));
266
+ fl = 0.035;
267
+ } else if (r === 7) {
268
+ col = COL.mane;
269
+ fl = foal ? 0.04 : 0.055;
270
+ } else if (r === 5) {
271
+ // ears: coat colour, dark rims / tips on bays, hairy inside
272
+ const earP = model.prims.find((q) => q.tag === 'ear' && (q.bone === (p[0] > 0 ? 'earL' : 'earR')));
273
+ const faceDir = [earP.P[9], earP.P[10], earP.P[11]];
274
+ const front = dot(n, faceDir);
275
+ col = COL.head;
276
+ if (COL.blackPoints) col = mix3(col, COL.points, 0.5);
277
+ fl = 0.005;
278
+ if (front > 0.3) { fl = 0.016; col = mix3(col, srgb(0xb0a090), 0.25); }
279
+ } else {
280
+ // head & jaw
281
+ col = COL.head;
282
+ fl = 0.005;
283
+ const h = HL(p);
284
+ // dark soft muzzle skin with whiskers (pink under a white snip / blaze)
285
+ // muzzle: fine short hair over dark skin (pink under a white snip)
286
+ const muz = smoothstep(0.25, 0.36, h[2]);
287
+ if (muz > 0) { col = mix3(col, wsdf < 0 ? COL.pink : COL.muzzle, muz * 0.75); fl = mix(fl, 0.0025, muz); }
288
+ // bays and duns have a black-edged muzzle; duns a dark face
289
+ if (COL.blackPoints && h[2] > 0.25) col = mix3(col, COL.points, smoothstep(0.25, 0.32, h[2]) * 0.6);
290
+ if (h[2] < -0.13) col = mix3(col, COL.body, smoothstep(-0.13, -0.2, h[2]));
291
+ // eye rims (dark skin)
292
+ for (const e of eyePrims) {
293
+ const de = SDFModel.dist(e, p[0], p[1], p[2]);
294
+ if (Math.abs(de) < 0.006) mark = Math.min(mark, Math.abs(de) - 0.0035);
295
+ if (Math.abs(de) < 0.0024) { mat = MAT.DARK_SKIN; fl = 0; }
296
+ else if (Math.abs(de) < 0.006) fl = Math.min(fl, 0.002);
297
+ }
298
+ // nostrils: dark rims
299
+ // (dark, moist lining; SKIN's subsurface scattering read as a pink rim)
300
+ for (const e of nostrilPrims) {
301
+ const dn = SDFModel.dist(e, p[0], p[1], p[2]);
302
+ if (dn < 0.004) { mat = MAT.DARK_SKIN; col = COL.muzzle; fl = 0; mark = Math.min(mark, dn - 0.003); }
303
+ }
304
+ // lips: head vertices near the jaw surface, jaw vertices near the head surface
305
+ // (the closed mouth shows a dark crease where the lips meet; the pink mucosa only on the surfaces
306
+ // the other lip hides, which show when the mouth opens)
307
+ if (partOf[v] !== 2) {
308
+ const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
309
+ if (dj < -0.002 && h[2] > 0.2) { mat = MAT.MOUTH; fl = 0; }
310
+ else if (dj < 0.008 && h[2] > 0.2) { mark = Math.min(mark, dj - 0.0045); fl = Math.min(fl, 0.0015); }
311
+ } else {
312
+ const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
313
+ if (dh < -0.002) { mat = MAT.MOUTH; fl = 0; }
314
+ else if (dh < 0.008) { mark = Math.min(mark, dh - 0.0045); fl = Math.min(fl, 0.0015); }
315
+ }
316
+ void lipLocal;
317
+ }
318
+ // white markings (not on keratin / mouth / eye skin)
319
+ // (the pattern field stays continuous: capped just above zero where there is nothing, so a
320
+ // triangle never interpolates across a jump and grows a stray edge line)
321
+ const CAP = 0.02;
322
+ let pc = COL.white, pi = 1, pd = CAP;
323
+ if ((mat === MAT.FUR || mat === MAT.SKIN) && params.coat !== 'grey') pd = Math.min(CAP, wsdf);
324
+ if (dapples && (r === 0 || r === 1)) {
325
+ const [sd] = featureDistance(dapples, p, n, 1 + 0.25 * (vnoise3(p[0] * 40, p[1] * 40, p[2] * 40) - 0.5));
326
+ pd = Math.min(CAP, sd); pi = 0.75 * (1 - legness[v]) * (1 - ventral[v]); pc = COL.dapple;
327
+ }
328
+ // low-frequency colour variation (and sheen dapples on solid coats)
329
+ const cv = fbm3(p[0] * 5, p[1] * 5, p[2] * 5, 3) - 0.5;
330
+ if (mat === MAT.FUR) col = [col[0] * (1 + 0.14 * cv), col[1] * (1 + 0.12 * cv), col[2] * (1 + 0.1 * cv)];
331
+ // long hair: lock-to-lock shade streaks along the hair
332
+ if (r === 3 || r === 7) {
333
+ const c = [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]];
334
+ const along = dot(p, c), q = sub(p, mul(c, along));
335
+ const st = vnoise3(q[0] * 90, q[1] * 90, q[2] * 90 + along * 3), lk = vnoise3(q[0] * 24 + 7.1, q[1] * 24, q[2] * 24 + along * 1.5);
336
+ col = mul(col, (0.75 + 0.5 * st) * (0.82 + 0.36 * lk));
337
+ }
338
+ pattern[v] = pd; patInt[v] = pi;
339
+ patCol[v * 3] = pc[0]; patCol[v * 3 + 1] = pc[1]; patCol[v * 3 + 2] = pc[2];
340
+ markSDF[v] = mark;
341
+ furLen[v] = fl;
342
+ tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
343
+ surf[v * 4] = gloss;
344
+ }
345
+ // foals: fluffy body coat
346
+ if (foal) for (let v = 0; v < nV; v++) if (tint[v * 4 + 3] === MAT.FUR && region[v] <= 1) furLen[v] = Math.max(furLen[v], 0.016);
347
+ // smooth fur length a little so shells do not step (hair, skin and keratin keep their own)
348
+ smoothField(furLen, weights.neighbors, 2, (v) => tint[v * 4 + 3] !== MAT.FUR);
349
+ // one coat across the head / neck boundary: no parting in the hair flow along the jowl margin and
350
+ // behind the poll (coatSeam.js)
351
+ const coatSeam = blendHeadNeckCoat(ctx, { comb, furLen, tint, region });
352
+
353
+ const patternColor = new Float32Array(nV * 4);
354
+ for (let v = 0; v < nV; v++) { patternColor[v * 4] = patCol[v * 3]; patternColor[v * 4 + 1] = patCol[v * 3 + 1]; patternColor[v * 4 + 2] = patCol[v * 3 + 2]; patternColor[v * 4 + 3] = patInt[v]; }
355
+ // the head -> neck hand-over is spread across the throat latch (no fold when the poll flexes), and
356
+ // the head and body copies of the throat / upper neck take one set of skin weights across the
357
+ // cross-fade band (seamWeights.js; after the coat, which reads each copy's own weights)
358
+ const throat = spreadThroatWeights(ctx);
359
+ const seam = unifyNeckWeights(ctx);
360
+ return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, stats: { dapples: dapples ? dapples.spots.length : 0, neckWeightsShared: seam.changed, coatSeamBlended: coatSeam.changed, throatWeightsSpread: throat.changed }, region, ventral };
361
+ }
@@ -0,0 +1,122 @@
1
+ // One coat across the head / neck boundary: hair flow, hair length and colour run smoothly from the
2
+ // face onto the throat and the upper neck.
3
+ //
4
+ // The coat regions (coat.js) give the head (region 2) its own hair flow, combed from the forehead whorl
5
+ // down the face toward the muzzle, and the neck (region 1) the flow down the neck from the axial chain;
6
+ // the head's short face hair (5 mm) and the neck's 8 mm coat meet on one line, the boundary of region 2:
7
+ // across the throat latch and the back of the jowl (where the axial coordinate passes the occiput) and
8
+ // up behind the poll (the neck cut, neckS = 0.03). On that line the hair flow turned by up to ~160 deg
9
+ // from one vertex to the next: the jowl's hair points to the muzzle, the throat's down the neck, so the
10
+ // two coats parted along the line and the dark roots and the shaded base under the shells showed
11
+ // through as a thin dark line. It reads most when the jowl margin turns edge-on (the poll flexed in a
12
+ // rear, a head looking down to the side): a dark crease across the throat and up behind the eye.
13
+ //
14
+ // Here the hair direction, length and colour of both coats are blurred over the surface (both mesh
15
+ // copies together, splatted into a grid by vertex area and fade, Gaussian sigma SIGMA) and mixed in
16
+ // along the boundary only (by the blurred region indicator: full within ~3 cm of the line, nothing
17
+ // past ~9 cm), so the face, the poll and the neck keep their own coat and the hair turns from the
18
+ // face's flow to the neck's over ~10 cm of skin instead of between two vertices.
19
+ import { smoothstep } from '../../core/math/vec.js';
20
+ import { MAT } from '../../core/build/coatKit.js';
21
+
22
+ const SIGMA = 0.035;
23
+
24
+ export function blendHeadNeckCoat(ctx, { comb, furLen, tint, region }, sigma = SIGMA) {
25
+ const { pos, nrm, nV, fade } = ctx;
26
+ // fur of the head and the neck, both mesh copies (the jaw, the ears, the mane keep their own)
27
+ const usable = (v) => (region[v] === 1 || region[v] === 2) && tint[v * 4 + 3] === MAT.FUR;
28
+ let mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9], any2 = false;
29
+ for (let v = 0; v < nV; v++) {
30
+ if (!usable(v) || region[v] !== 2) continue;
31
+ any2 = true;
32
+ 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]); }
33
+ }
34
+ if (!any2) return { changed: 0 };
35
+ const c = sigma / 2, pad = 4 * sigma;
36
+ mn = mn.map((x) => x - pad); mx = mx.map((x) => x + pad);
37
+ const G = mn.map((x, k) => Math.ceil((mx[k] - x) / c) + 2);
38
+ // channels: comb xyz, hair length, colour rgb, region-2 indicator, weight
39
+ const NC = 9, cells = G[0] * G[1] * G[2];
40
+ const grid = new Float32Array(cells * NC);
41
+ const at = (i, j, k) => (i + G[0] * (j + G[1] * k)) * NC;
42
+ const cellOf = (v) => [0, 1, 2].map((k) => (pos[v * 3 + k] - mn[k]) / c);
43
+ const inside = (g) => g.every((x, k) => x >= 0 && x <= G[k] - 2);
44
+ // vertex areas
45
+ const area = new Float32Array(nV);
46
+ const { index } = ctx;
47
+ for (let i = 0; i < index.length; i += 3) {
48
+ const a = index[i], b = index[i + 1], d = index[i + 2];
49
+ 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];
50
+ 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];
51
+ const A = (0.5 * Math.hypot(uy * wz - uz * wy, uz * wx - ux * wz, ux * wy - uy * wx)) / 3;
52
+ area[a] += A; area[b] += A; area[d] += A;
53
+ }
54
+ const tmp = new Float32Array(NC);
55
+ for (let v = 0; v < nV; v++) {
56
+ if (!usable(v)) continue;
57
+ const g = cellOf(v);
58
+ if (!inside(g)) continue;
59
+ const a = area[v] * Math.max(0, fade ? fade[v] : 1);
60
+ if (!(a > 0)) continue;
61
+ tmp[0] = comb[v * 3]; tmp[1] = comb[v * 3 + 1]; tmp[2] = comb[v * 3 + 2];
62
+ tmp[3] = furLen[v];
63
+ tmp[4] = tint[v * 4]; tmp[5] = tint[v * 4 + 1]; tmp[6] = tint[v * 4 + 2];
64
+ tmp[7] = region[v] === 2 ? 1 : 0; tmp[8] = 1;
65
+ const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
66
+ const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
67
+ for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
68
+ const t = a * (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
69
+ const o = at(i0 + di, j0 + dj, k0 + dk);
70
+ for (let q = 0; q < NC; q++) grid[o + q] += t * tmp[q];
71
+ }
72
+ }
73
+ // separable Gaussian blur
74
+ const R = Math.ceil((3 * sigma) / c), ker = [];
75
+ for (let d = -R; d <= R; d++) ker.push(Math.exp(-0.5 * ((d * c) / sigma) ** 2));
76
+ const buf = new Float32Array(grid.length);
77
+ for (let ax = 0; ax < 3; ax++) {
78
+ buf.fill(0);
79
+ for (let k = 0; k < G[2]; k++) for (let j = 0; j < G[1]; j++) for (let i = 0; i < G[0]; i++) {
80
+ const o = at(i, j, k);
81
+ if (grid[o + NC - 1] === 0) continue;
82
+ for (let d = -R; d <= R; d++) {
83
+ const ii = i + (ax === 0 ? d : 0), jj = j + (ax === 1 ? d : 0), kk = k + (ax === 2 ? d : 0);
84
+ if (ii < 0 || jj < 0 || kk < 0 || ii >= G[0] || jj >= G[1] || kk >= G[2]) continue;
85
+ const o2 = at(ii, jj, kk), w = ker[d + R];
86
+ for (let q = 0; q < NC; q++) buf[o2 + q] += w * grid[o + q];
87
+ }
88
+ }
89
+ grid.set(buf);
90
+ }
91
+ // sample back; mix in along the boundary
92
+ let changed = 0;
93
+ for (let v = 0; v < nV; v++) {
94
+ if (!usable(v)) continue;
95
+ const g = cellOf(v);
96
+ if (!inside(g)) continue;
97
+ const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
98
+ const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
99
+ tmp.fill(0);
100
+ for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
101
+ const t = (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
102
+ const o = at(i0 + di, j0 + dj, k0 + dk);
103
+ for (let q = 0; q < NC; q++) tmp[q] += t * grid[o + q];
104
+ }
105
+ const W = tmp[NC - 1];
106
+ if (!(W > 1e-9)) continue;
107
+ const gb = tmp[7] / W;
108
+ const m = smoothstep(0.02, 0.25, Math.min(gb, 1 - gb));
109
+ if (m <= 0) continue;
110
+ // hair direction: the blurred flow, mixed in and laid back into the skin's tangent plane
111
+ const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
112
+ let d = [0, 1, 2].map((k) => comb[v * 3 + k] * (1 - m) + (tmp[k] / W) * m);
113
+ const dn = d[0] * n[0] + d[1] * n[1] + d[2] * n[2];
114
+ d = d.map((x, k) => x - dn * n[k]);
115
+ const l = Math.hypot(d[0], d[1], d[2]);
116
+ if (l > 1e-4) for (let k = 0; k < 3; k++) comb[v * 3 + k] = d[k] / l;
117
+ furLen[v] = furLen[v] * (1 - m) + (tmp[3] / W) * m;
118
+ for (let k = 0; k < 3; k++) tint[v * 4 + k] = tint[v * 4 + k] * (1 - m) + (tmp[4 + k] / W) * m;
119
+ changed++;
120
+ }
121
+ return { changed };
122
+ }
@@ -0,0 +1,80 @@
1
+ // Horse (Equus caballus). The first hoofed animal: cannon / pastern / hoof anatomy with a separate
2
+ // hoof bone (fetlock sink under load, hoof flip in swing), long mane and tail hair, a rideable back
3
+ // with tack attachment points, the four natural gaits and the horse's signature behaviours.
4
+ import { horseRig, HEAD_O } from './rig.js';
5
+ import { sculptHorse, EYE } from './sculpt.js';
6
+ import { horseRegions } from './regions.js';
7
+ import { horseCoat, COATS } from './coat.js';
8
+ import { srgb } from '../../core/build/coatKit.js';
9
+ import { motion } from './motion.js';
10
+
11
+ const pick = (R, table) => {
12
+ let x = R() * table.reduce((s, [, w]) => s + w, 0);
13
+ for (const [k, w] of table) { x -= w; if (x <= 0) return k; }
14
+ return table[table.length - 1][0];
15
+ };
16
+
17
+ export default {
18
+ id: 'horse',
19
+ name: 'Horse',
20
+ latin: 'Equus caballus',
21
+ group: 'hooves',
22
+ plan: 'quadruped',
23
+ covering: 'fur',
24
+
25
+ // Seeded individual. Sex: mare, gelding or stallion (crest and sheath); age: adult or foal
26
+ // (juvenile: long legs, short body, big head, bristly mane, short fluffy tail); size, proportions
27
+ // (sport horse <-> lighter thoroughbred type <-> stockier type); coat colour and white markings.
28
+ // o.variant may force a coat colour ('bay', 'chestnut', 'grey', ...).
29
+ variation(R, o) {
30
+ const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
31
+ const age = o.age || 'adult';
32
+ const foal = age === 'juvenile';
33
+ const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
34
+ const gelding = sex === 'male' && R() < 0.65;
35
+ const type = g(); // - lighter / leggier, + stockier
36
+ const size = (sex === 'male' ? 1.02 : 0.98) * (1 + 0.045 * g()) * (foal ? 0.5 : 1);
37
+ const coat = COATS[o.variant] ? o.variant : pick(R, [['bay', 30], ['darkbay', 10], ['chestnut', 22], ['black', 8], ['grey', 12], ['palomino', 7], ['dun', 6], ['buckskin', 5]]);
38
+ const face = pick(R, [['none', 35], ['star', 20], ['stripe', 10], ['blaze', 20], ['snip', 5], ['starsnip', 10]]);
39
+ const sockH = () => pick(R, [[0, 60], [0.085, 8], [0.13, 10], [0.24, 14], [0.45, 8]]);
40
+ const socks = [sockH(), sockH(), sockH(), sockH()];
41
+ if (R() < 0.3) socks[2] = socks[3] = Math.max(socks[2], socks[3]);
42
+ return {
43
+ sex, age, size, gelding,
44
+ crest: sex === 'male' ? (gelding ? 0.45 : 1) + 0.15 * g() : 0.15 + 0.1 * g(),
45
+ coat, face, socks,
46
+ flaxen: coat === 'chestnut' && R() < 0.3,
47
+ greyLevel: coat === 'grey' ? (foal ? 0.05 : 0.15 + 0.75 * R()) : 0,
48
+ maneLen: 0.14 + 0.12 * R(),
49
+ maneSide: R() < 0.8 ? -1 : 1,
50
+ coatShade: g(), coatLightness: 0.06 * g(),
51
+ coatSeed: Math.floor(R() * 1e6),
52
+ minThick: 0.006,
53
+ warps: [
54
+ { type: 'legs', k: (1 + 0.035 * g() - 0.025 * type) * (foal ? 1.55 : 1), top: 0.95 },
55
+ { type: 'length', k: (1 + 0.03 * g() - 0.015 * type) * (foal ? 0.86 : 1), z0: -0.62, z1: 0.62 },
56
+ { type: 'girth', k: (1 + 0.05 * type) * (sex === 'male' && !gelding ? 1.02 : 1), cy: 1.25, z0: -0.7, z1: 0.75 },
57
+ { type: 'scaleAbout', c: HEAD_O, k: (1 + 0.03 * g()) * (foal ? 1.22 : 1), r0: 0.2, r1: 0.4 },
58
+ ],
59
+ };
60
+ },
61
+ rig: () => horseRig(),
62
+ sculpt: (m, rig, params) => sculptHorse(m, rig, params),
63
+ regions: horseRegions,
64
+ coat: horseCoat,
65
+ eyeSpecs: () => [1, -1].map((side) => ({ side, spec: EYE, headOrigin: HEAD_O, bone: 'head', look: EYE_LOOK })),
66
+ motion,
67
+ render: { markColor: srgb(0x241f1d), strandDensity: 900, clumpDensity: 1, shellScale: 1, finWidth: 0.45 },
68
+ };
69
+
70
+ // Horizontal bar pupil with corpora nigra, dark brown iris, sclera hidden, dark lids.
71
+ export const EYE_LOOK = {
72
+ pupil: 'bar',
73
+ lids: 'mammal',
74
+ // (dark brown, light enough that the iris and the bar pupil read under the catchlight: near-black
75
+ // read as a black button, and as blue-grey glass under a sky reflection)
76
+ iris: { inner: srgb(0x2a170c), mid: srgb(0x5c3a20), hi: srgb(0x8a5a2e), outer: srgb(0x1e1109) },
77
+ sclera: { color: [0.55, 0.5, 0.45], visible: false },
78
+ lidColor: [0.025, 0.02, 0.02],
79
+ pupilSize: [0.12, 0.4],
80
+ };