@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,444 @@
1
+ // The red fox's coat, painted per vertex in reference space.
2
+ //
3
+ // Colour fields follow the red fox's pattern (Lariviere & Pasitschniak-Arts 1996):
4
+ // rufous-orange back, flanks, head and brush, brightest on the head, shoulders and flanks;
5
+ // white upper lips, cheeks, chin, throat and chest bib, a pale grey-white belly; black "stockings"
6
+ // from the paws up the front of the forelegs to the elbows and over the hind feet up to the hocks
7
+ // (a dark stripe runs on up the front of the shin); black backs of the ears with a white-furred
8
+ // inside; dark tear lines from the inner eye corners toward the lips; a black lip line and nose; the
9
+ // brush has black-tipped guard hair (the shader's agouti band), a dark tail-gland spot near its
10
+ // base and a white tip. Morphs: red (default), cross (a dark cross over the shoulders and down the
11
+ // spine, dark legs and belly, grizzled flanks), silver (black with silver-tipped guard hair, white
12
+ // tail tip), urban (duller, greyer, grizzled winter coat). Kits: woolly, sandy grey-brown with dark
13
+ // legs and a pale face.
14
+ // Fur length: long ruff and cheek fluff, long flanks and breeches, the huge brush; short sleek face,
15
+ // muzzle and lower legs.
16
+ import { HEAD_O, MUZZLE_Z0, HS } from './rig.js';
17
+ import { neckS } from './regions.js';
18
+ import { SDFModel } from '../../core/sdf/sdf.js';
19
+ import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
20
+ import { srgb, mix3, MAT, smoothField } from '../../core/build/coatKit.js';
21
+
22
+ const P3 = {
23
+ // sRGB swatches, neutral-light albedo
24
+ red: {
25
+ back: 0xb4582a, flank: 0xc46a32, lowFlank: 0xc97a42, head: 0xca7236, muzzleTop: 0xb46a34, shoulder: 0xc56a32,
26
+ white: 0xece8e2, belly: 0xd8d2ca, stocking: 0x19140f, stockingEdge: 0x3a2418, earBack: 0x1e1814, earInner: 0xe6dccb,
27
+ tail: 0xa45a2c, tailUnder: 0xc08450, tailTip: 0xf0ebe2, gland: 0x3a2618, tear: 0x3e2a1e, rump: 0xc98a58, dark: 0x2a1c14,
28
+ ag: { back: 0.35, flank: 0.1, head: 0.05, leg: 0, tail: 0.55 },
29
+ },
30
+ cross: {
31
+ back: 0x8a5634, flank: 0x9c6a44, lowFlank: 0x8a6448, head: 0xa8703e, muzzleTop: 0x7a5234, shoulder: 0x8e5a36,
32
+ white: 0xcfc8bc, belly: 0x3a3430, stocking: 0x151210, stockingEdge: 0x2a2018, earBack: 0x1a1512, earInner: 0xcfc4b2,
33
+ tail: 0x6e4a32, tailUnder: 0x8a6448, tailTip: 0xe8e2d8, gland: 0x2a1c14, tear: 0x2a1c14, rump: 0x8a6448, dark: 0x1e1a18,
34
+ ag: { back: 0.55, flank: 0.45, head: 0.15, leg: 0, tail: 0.7 },
35
+ },
36
+ silver: {
37
+ back: 0x8e8c8a, flank: 0x7a7876, lowFlank: 0x2e2c2a, head: 0x5e5c5a, muzzleTop: 0x201e1c, shoulder: 0x74726f,
38
+ white: 0x5a5856, belly: 0x1e1c1a, stocking: 0x121010, stockingEdge: 0x181614, earBack: 0x141210, earInner: 0x6a6866,
39
+ tail: 0x242220, tailUnder: 0x1c1a18, tailTip: 0xeae6de, gland: 0x141210, tear: 0x141210, rump: 0x3a3836, dark: 0x161412,
40
+ ag: { back: 0.85, flank: 0.8, head: 0.6, leg: 0.1, tail: 0.3 },
41
+ },
42
+ urban: {
43
+ back: 0x7e4a2e, flank: 0x96684a, lowFlank: 0x9a7a62, head: 0x9a6440, muzzleTop: 0x8a5a3a, shoulder: 0x86522f,
44
+ white: 0xd8d2c8, belly: 0xa8a098, stocking: 0x1c1712, stockingEdge: 0x3a2a1e, earBack: 0x1e1814, earInner: 0xd6cab8,
45
+ tail: 0x6e4630, tailUnder: 0x8e6a50, tailTip: 0xe0dad0, gland: 0x2a1c14, tear: 0x3a2618, rump: 0x9a7a62, dark: 0x2a1c14,
46
+ ag: { back: 0.6, flank: 0.55, head: 0.15, leg: 0, tail: 0.65 },
47
+ },
48
+ };
49
+ const KIT = 0x7a6450; // woolly sandy grey-brown natal / juvenile coat
50
+
51
+ export function palette(p) {
52
+ const base = P3[p.variant] || P3.red;
53
+ const k = p.coatWarmth || 0, l = p.coatLightness || 0, juv = p.juv || 0;
54
+ const kit = srgb(KIT).map((c) => c * (p.variant === 'silver' ? 0.45 : 1));
55
+ const out = { ag: base.ag };
56
+ for (const [key, hex] of Object.entries(base)) {
57
+ if (key === 'ag') continue;
58
+ let c = srgb(hex);
59
+ // warmth shifts red against blue; the white parts stay white
60
+ const wk = /white|belly|earInner|tailTip/.test(key) ? 0.25 : 1;
61
+ c = [c[0] * (1 + (0.1 * k + l) * wk), c[1] * (1 + (0.01 * k + l) * wk), c[2] * (1 - (0.12 * k - l) * wk)];
62
+ // kits: most of the pattern is washed into the woolly juvenile coat (face, stockings, ears and the
63
+ // tail tip stay)
64
+ const keep = /white|earInner|tailTip/.test(key) ? 0.6 : /stocking|earBack/.test(key) ? 0.7 : /head|muzzleTop/.test(key) ? 0.45 : 0.15;
65
+ if (juv > 0) c = mix3(c, kit, juv * (1 - keep));
66
+ out[key] = c;
67
+ }
68
+ // eyelids (the eye shader's lidColor): furred in the face colour round the eye, a little darker; the
69
+ // shader darkens their margin itself, so a blink or a closed eye reads as a furred lid with a dark
70
+ // line (it was near black: every blink, sleep and death showed glossy black beads)
71
+ out.lid = mix3(out.head, out.tear, 0.25).map((c) => c * 0.85);
72
+ return out;
73
+ }
74
+
75
+ export function foxCoat(ctx) {
76
+ const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params } = ctx;
77
+ const { BONES, AXIAL } = rig;
78
+ const J = rig.J;
79
+ const AX_LEN = weights.axialLengths;
80
+ const C = palette(params);
81
+ const AG = C.ag;
82
+ const juv = params.juv || 0;
83
+ const hw = params.headW || 1, mz = params.muzzle || 1;
84
+ const ruffK = params.ruff ?? 1;
85
+ const bibK = params.bib ?? 1; // extent of the white bib / belly
86
+ const stockK = params.stockings ?? 1; // how far up the black stockings reach
87
+ const tipK = params.tailTip ?? 1; // length of the white tail tip (0 = none)
88
+ const tearK = params.tear ?? 1;
89
+ const variant = params.variant || 'red';
90
+ const nOff = (params.coatSeed || 0) % 997;
91
+ const { dominant, axialS, skinIndex, skinWeight, limbMember, limbWhich } = weights;
92
+ const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
93
+ const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
94
+ const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8], sTip = AX_LEN[AX_LEN.length - 1];
95
+ const tailLen = sTip - sTailBase;
96
+ const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
97
+ const nosePrim = model.prims.find((p) => p.tag === 'nose');
98
+ const caninePrims = model.prims.filter((p) => p.tag === 'canine');
99
+ const jawPrims = model.forPart('jaw').filter((p) => p.tag !== 'canine');
100
+ const bodyPrims = model.forPart('body');
101
+ const headPrims = bodyPrims.filter((p) => p.bone === 'head' && !p.carve && p.tag !== 'canine');
102
+ const earPrims = model.prims.filter((p) => p.tag === 'ear');
103
+ const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|femur|tibia|metatarsus|hpaw)/.test(b.name));
104
+ const isPaw = BONES.map((b) => /^(fpaw|hpaw)/.test(b.name));
105
+ const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw)/.test(b.name));
106
+ // reference heights
107
+ const yEl = J.elbowL[1], yHock = J.hockL[1], yKnee = J.kneeL[1], yBack = J.thoraxRear[1];
108
+ const zSh = J.shoulderL[2], zHip = J.hipL[2];
109
+ // head-local coordinates of a reference-space point (undo head width and muzzle stretch)
110
+ const headLocal = (p) => {
111
+ const x = (p[0] - HEAD_O[0]) / hw / HS, y = (p[1] - HEAD_O[1]) / HS;
112
+ let z = (p[2] - HEAD_O[2]) / HS;
113
+ if (z > MUZZLE_Z0) z = MUZZLE_Z0 + (z - MUZZLE_Z0) / mz;
114
+ return [x, y, z];
115
+ };
116
+
117
+ // --- regions per vertex (0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw) and limb blend
118
+ const region = new Uint8Array(nV);
119
+ const legness = new Float32Array(nV);
120
+ const legBone = new Int16Array(nV).fill(-1);
121
+ const junction = new Float32Array(nV);
122
+ const tailT = new Float32Array(nV);
123
+ for (let v = 0; v < nV; v++) {
124
+ const bn = BONES[dominant[v]].name;
125
+ const p = P(v);
126
+ let lb = -1, lw = 0;
127
+ for (let k = 0; k < 4; k++) {
128
+ const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
129
+ if (isLimb[b] && w > lw) { lw = w; lb = b; }
130
+ }
131
+ legBone[v] = lb;
132
+ const m = limbMember[v];
133
+ legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, m) : 0;
134
+ junction[v] = lb >= 0 ? 4 * m * (1 - m) : 0;
135
+ if (partOf[v] === 2) region[v] = 6;
136
+ else if (bn.startsWith('ear')) region[v] = 5;
137
+ else if (axialS[v] > sTailBase + 0.012 && bn.startsWith('tail')) { region[v] = 4; tailT[v] = clamp((axialS[v] - sTailBase) / tailLen, 0, 1); }
138
+ else if (axialS[v] < sOcc + 0.006 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.012)) region[v] = 2;
139
+ else if (axialS[v] < sNeckBase) region[v] = 1;
140
+ else region[v] = 0;
141
+ if (region[v] >= 2) { legness[v] = 0; junction[v] = 0; }
142
+ }
143
+ const underFactor = (v) => {
144
+ const n = N(v), p = P(v), li = limbWhich[v];
145
+ if (li < 0 || junction[v] <= 0) return 0;
146
+ if (li <= 1) return junction[v] * Math.max(smoothstep(0.15, -0.55, n[2]) * smoothstep(yEl + 0.1, yEl + 0.05, p[1]), smoothstep(0.2, -0.6, n[1]));
147
+ return junction[v] * smoothstep(0.35, -0.45, n[1] + 0.35 * Math.abs(n[2]));
148
+ };
149
+
150
+ // --- ventral (white underside) factor: throat, bib, belly, inner thighs
151
+ const ventral = new Float32Array(nV);
152
+ const yBib = yEl + 0.13 * bibK; // top of the white bib on the chest front
153
+ for (let v = 0; v < nV; v++) {
154
+ const p = P(v), n = N(v), r = region[v];
155
+ let w = 0;
156
+ if (r === 0) {
157
+ w = smoothstep(-0.45, -0.85, n[1]);
158
+ // chest front between the forelegs: the white bib, continuing the throat
159
+ if (p[2] > zSh - 0.05) w = Math.max(w, smoothstep(yBib, yBib - 0.035, p[1] + 0.012 * (fbm3(p[0] * 40, p[1] * 40, p[2] * 40, 2) - 0.5)) * smoothstep(0.042, 0.018, Math.abs(p[0])) * smoothstep(-0.3, 0.35, n[2]));
160
+ w *= smoothstep(yBack - 0.02, yBack - 0.08, p[1]);
161
+ } else if (r === 1) {
162
+ // throat and the front of the neck ruff
163
+ w = smoothstep(0.4, -0.05, n[1] + 0.65 * Math.abs(n[0]) - 0.45 * Math.max(0, n[2]));
164
+ } else if (r === 2 || r === 6) {
165
+ const h = headLocal(p);
166
+ w = smoothstep(-0.028, -0.05, h[1]) * smoothstep(-0.1, -0.6, n[1]) + (r === 6 ? 0.8 : 0);
167
+ }
168
+ const L = legness[v];
169
+ if (L > 0) {
170
+ const side = p[0] >= 0 ? 1 : -1;
171
+ const inner = smoothstep(0.1, -0.6, n[0] * side);
172
+ const wl = inner * 0.75 * smoothstep(yKnee - 0.01, yKnee + 0.05, p[1]) * (isFront[legBone[v]] ? 0.4 : 1) + smoothstep(-0.3, -0.85, n[1]) * 0.4 * smoothstep(yKnee - 0.02, yKnee + 0.04, p[1]);
173
+ w = mix(w, wl, L);
174
+ }
175
+ w = Math.max(w, underFactor(v) * 0.6 * smoothstep(-0.1, -0.5, n[1]));
176
+ ventral[v] = clamp(w, 0, 1);
177
+ }
178
+
179
+ // --- comb (hair flow), bind space
180
+ const comb = new Float32Array(nV * 3);
181
+ for (let v = 0; v < nV; v++) {
182
+ const p = P(v), n = N(v), r = region[v];
183
+ let d;
184
+ const bone = BONES[dominant[v]];
185
+ if (r === 5) {
186
+ d = norm(sub(bone.tail, bone.head));
187
+ } else if (r === 2 || r === 6) {
188
+ const h = headLocal(p);
189
+ // out from the nose over the face; the cheek ruff sweeps back, out and down
190
+ const fromNose = norm(sub(h, [0, -0.01, 0.165]));
191
+ d = norm(add(fromNose, [0, -0.2, -0.5]));
192
+ if (h[2] < 0.01 && Math.abs(h[0]) > 0.03) d = norm(add(d, [Math.sign(h[0]) * 0.7, -0.6, -0.7]));
193
+ } else {
194
+ const seg = weights.segT[v * 2] | 0;
195
+ const a = AXIAL.points[seg], b = AXIAL.points[Math.min(seg + 1, AXIAL.points.length - 1)];
196
+ d = norm(sub(b, a));
197
+ if (r === 0) d = norm(add(d, [0, -0.45 * Math.abs(n[0]), 0]));
198
+ if (r === 1) d = norm(add(d, [0, -0.55, 0])); // the ruff hangs down and back
199
+ const L = legness[v];
200
+ if (L > 0 && legBone[v] >= 0) {
201
+ const lbn = BONES[legBone[v]];
202
+ let dl = norm(sub(lbn.tail, lbn.head));
203
+ if (isPaw[legBone[v]]) dl = norm(add(dl, [0, 0, 0.4]));
204
+ else dl = norm(add(dl, [0, -0.2, -0.3]));
205
+ d = norm(add(mul(d, 1 - L), mul(dl, L)));
206
+ }
207
+ }
208
+ let t = sub(d, mul(n, dot(d, n)));
209
+ if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
210
+ t = norm(t);
211
+ comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
212
+ }
213
+
214
+ // --- per-vertex paint
215
+ const furLen = new Float32Array(nV);
216
+ const tint = new Float32Array(nV * 4);
217
+ const markSDF = new Float32Array(nV).fill(1);
218
+ const surf = new Float32Array(nV * 4);
219
+ const g3 = (h, c, rr) => Math.exp(-(((Math.abs(h[0]) - c[0]) / rr[0]) ** 2 + ((h[1] - c[1]) / rr[1]) ** 2 + ((h[2] - c[2]) / rr[2]) ** 2));
220
+ // distance (head-local) from a point to the segment a-b, mirrored to the left side
221
+ const segD = (h, a, b) => {
222
+ const q = [Math.abs(h[0]), h[1], h[2]];
223
+ const ab = sub(b, a), t = clamp(dot(sub(q, a), ab) / dot(ab, ab), 0, 1);
224
+ return { d: len(sub(q, add(a, mul(ab, t)))), t };
225
+ };
226
+ const tailSegDorsal = (v) => {
227
+ const bone = BONES[dominant[v]];
228
+ const d = norm(sub(bone.tail, bone.head));
229
+ return norm([0, -d[2], d[1]]);
230
+ };
231
+ const cross_ = variant === 'cross';
232
+
233
+ for (let v = 0; v < nV; v++) {
234
+ const p = P(v), n = N(v), r = region[v];
235
+ let col, fl = 0.02, ag = 0, mat = MAT.FUR, mark = 1, under = 0.4;
236
+ // low-frequency noise (ragged pattern edges) and a finer field
237
+ const nz = fbm3(p[0] * 12 + nOff, p[1] * 12, p[2] * 12, 3) - 0.5;
238
+ const nz2 = vnoise3(p[0] * 70 + nOff, p[1] * 70, p[2] * 70) - 0.5;
239
+ if (r === 0 || r === 1) {
240
+ const up = n[1];
241
+ const vent = ventral[v];
242
+ const dors = smoothstep(-0.1, 0.7, up + 0.3 * nz);
243
+ col = mix3(C.lowFlank, C.flank, smoothstep(-0.4, 0.2, up + 0.25 * nz));
244
+ col = mix3(col, C.back, dors * 0.85);
245
+ ag = mix(AG.flank, AG.back, dors);
246
+ if (r === 0) {
247
+ // brighter shoulders; grizzled hips and rump in the dull coats
248
+ const sh = Math.exp(-(((p[2] - zSh + 0.01) / 0.05) ** 2)) * smoothstep(-0.3, 0.3, n[0] * (p[0] >= 0 ? 1 : -1));
249
+ col = mix3(col, C.shoulder, sh * 0.6);
250
+ const rump = smoothstep(zHip + 0.02, zHip - 0.05, p[2]) * smoothstep(-0.2, -0.8, n[2]) * smoothstep(yBack + 0.02, yBack - 0.06, p[1]);
251
+ col = mix3(col, C.rump, rump * 0.6);
252
+ // cross morph: a dark stripe down the spine crossed by one over the shoulders
253
+ if (cross_) {
254
+ const spine = smoothstep(0.4, 0.8, up + 0.15 * nz) * smoothstep(zHip - 0.06, zHip + 0.02, p[2]);
255
+ const bar = Math.exp(-(((p[2] - zSh - 0.035 + 0.02 * nz) / 0.05) ** 2)) * smoothstep(yEl + 0.02, yBack - 0.04, p[1]);
256
+ const c = clamp(Math.max(spine, bar), 0, 1);
257
+ col = mix3(col, C.dark, c * 0.92);
258
+ ag = mix(ag, 0.3, c);
259
+ }
260
+ col = mix3(col, C.belly, vent);
261
+ // the white bib is whiter than the belly
262
+ const bib = smoothstep(zSh - 0.06, zSh + 0.01, p[2]) * vent;
263
+ col = mix3(col, C.white, bib);
264
+ ag *= 1 - vent;
265
+ // guard-hair lengths: flanks, long over the shoulders and hips, a belly fringe
266
+ fl = mix(0.017, 0.024, smoothstep(0.1, 0.85, up)) * (1 + 0.1 * nz2);
267
+ fl = mix(fl, 0.014, smoothstep(0.3, 0.9, vent));
268
+ fl = Math.max(fl, 0.022 * bib);
269
+ if (p[2] < zHip) fl = Math.max(fl, 0.024 * rump);
270
+ } else {
271
+ // neck: rufous above, the white throat below, the ruff long and soft
272
+ col = mix3(col, C.white, smoothstep(0.2, 0.65, vent + 0.15 * nz));
273
+ ag *= 1 - smoothstep(0.2, 0.6, vent);
274
+ fl = mix(0.026, 0.024, vent) * (0.85 + 0.15 * ruffK) * (1 + 0.1 * nz2);
275
+ if (cross_) col = mix3(col, C.dark, smoothstep(0.6, 0.95, up) * 0.6);
276
+ }
277
+ const L = legness[v];
278
+ if (L > 0) {
279
+ const lb = legBone[v];
280
+ const front = lb >= 0 && isFront[lb];
281
+ const side = p[0] >= 0 ? 1 : -1;
282
+ const outer = smoothstep(-0.2, 0.5, n[0] * side);
283
+ // upper leg: body colour (orange thighs, white inside)
284
+ let lc = mix3(col, C.belly, smoothstep(0.2, 0.7, ventral[v]) * (1 - outer));
285
+ // the black stockings: forelegs from the paw up to the elbow (highest on the front), a thin
286
+ // dark line up the front of the upper arm; hind feet up to the hock and a dark stripe up
287
+ // the front of the shin
288
+ let st;
289
+ const e = 0.012 * nz;
290
+ if (front) {
291
+ const top = yEl + 0.01 * stockK + 0.02 * smoothstep(-0.2, 0.8, n[2]);
292
+ st = smoothstep(top + 0.012, top - 0.012, p[1] + e);
293
+ st = Math.max(st, 0.8 * smoothstep(0.4, 0.85, n[2]) * smoothstep(yEl + 0.075, yEl + 0.03, p[1]) * stockK);
294
+ } else {
295
+ const top = yHock + 0.012 + 0.03 * stockK * smoothstep(0.1, 0.8, n[2]);
296
+ st = smoothstep(top + 0.012, top - 0.012, p[1] + e);
297
+ // dark front of the shin (from the hock up toward the stifle)
298
+ st = Math.max(st, 0.85 * smoothstep(0.35, 0.8, n[2]) * smoothstep(yKnee - 0.01, yHock + 0.03, p[1]) * stockK);
299
+ }
300
+ st = clamp(st * (variant === 'silver' ? 1 : 1), 0, 1);
301
+ lc = mix3(lc, mix3(C.stockingEdge, C.stocking, smoothstep(0.3, 0.9, st)), st);
302
+ let lf = mix(0.007, 0.018, smoothstep(front ? yEl - 0.06 : yHock, front ? yEl + 0.06 : yKnee + 0.04, p[1]));
303
+ // breeches on the back of the thighs, a little feathering behind the forearm
304
+ if (!front) lf = Math.max(lf, 0.024 * smoothstep(-0.1, -0.7, n[2]) * smoothstep(yKnee - 0.04, yKnee + 0.05, p[1]));
305
+ else lf = Math.max(lf, 0.009 * smoothstep(-0.2, -0.8, n[2]) * smoothstep(yEl - 0.1, yEl - 0.02, p[1]));
306
+ if (lb >= 0 && isPaw[lb]) { lf = 0.005; lc = C.stocking; }
307
+ col = mix3(col, lc, L);
308
+ ag = mix(ag, AG.leg * (1 - st), L);
309
+ fl = mix(fl, lf, L);
310
+ }
311
+ under = 0.45;
312
+ } else if (r === 4) {
313
+ // the brush: rufous with black-tipped guard hair (strongest on top), a dark tail-gland spot near
314
+ // the base, paler underside, white tip
315
+ const t = tailT[v];
316
+ const dors = dot(n, tailSegDorsal(v));
317
+ const top = smoothstep(-0.45, 0.55, dors + 0.3 * nz);
318
+ col = mix3(C.tailUnder, C.tail, top);
319
+ ag = AG.tail * (0.55 + 0.45 * top) * smoothstep(0.02, 0.2, t);
320
+ const gl = Math.exp(-(((t - 0.14) / 0.05) ** 2)) * smoothstep(0.2, 0.75, dors);
321
+ col = mix3(col, C.gland, gl * 0.75);
322
+ // a darker band before the white tip (dark-tipped hair crowding at the end)
323
+ const tipStart = 1 - 0.17 * tipK;
324
+ const band = Math.exp(-(((t - tipStart + 0.06) / 0.07) ** 2));
325
+ col = mix3(col, C.dark, band * 0.35);
326
+ const tip = tipK > 0 ? smoothstep(tipStart - 0.02, tipStart + 0.04, t + 0.035 * nz) : 0;
327
+ col = mix3(col, C.tailTip, tip);
328
+ ag *= 1 - tip;
329
+ fl = mix(0.024, 0.04, smoothstep(0.0, 0.35, t)) * (1 - 0.35 * smoothstep(0.88, 1, t));
330
+ under = 0.4;
331
+ } else if (r === 5) {
332
+ // ears: black backs, the black running round the rim; the inside furred white
333
+ const earP = earPrims[p[0] > 0 ? 0 : 3];
334
+ const faceDir = [earP.P[9], earP.P[10], earP.P[11]];
335
+ const upE = [earP.P[6], earP.P[7], earP.P[8]];
336
+ const lat = [earP.P[3], earP.P[4], earP.P[5]];
337
+ const front = dot(n, faceDir);
338
+ const rel = sub(p, [earP.P[0], earP.P[1], earP.P[2]]);
339
+ const ht = dot(rel, upE) / 0.095; // ~0 base .. 1 tip
340
+ const edge = Math.abs(dot(rel, lat)) / (0.022 * Math.max(0.25, 1 - ht));
341
+ if (front > 0.25) {
342
+ col = mix3(C.head, C.earInner, smoothstep(0.25, 0.7, front) * (1 - smoothstep(0.75, 1.05, edge)));
343
+ fl = 0.012 * (1 - smoothstep(0.4, 1.0, ht));
344
+ } else {
345
+ // the back: black, orange at the very base where the head fur runs onto it
346
+ col = mix3(C.earBack, C.head, smoothstep(0.12, -0.05, ht) * 0.9);
347
+ fl = 0.005;
348
+ }
349
+ ag = 0;
350
+ } else {
351
+ // ---------------- head & jaw
352
+ const h = headLocal(p);
353
+ col = C.head;
354
+ ag = AG.head;
355
+ // muzzle top a little darker and duller
356
+ const mtop = smoothstep(0.05, 0.1, h[2]) * smoothstep(-0.1, 0.5, n[1]);
357
+ col = mix3(col, C.muzzleTop, mtop * 0.6);
358
+ // white mask: upper lips, muzzle sides low down, cheeks (the white cheek ruff), chin, throat
359
+ const lipSide = smoothstep(0.035, 0.065, h[2]) * smoothstep(-0.018, -0.032, h[1] - 0.06 * (h[2] - 0.1)) * smoothstep(0.1, 0.5, Math.abs(n[0]));
360
+ const cheek = smoothstep(-0.01, -0.03, h[1] + 0.25 * (h[2] - 0.02) + 0.01 * nz) * smoothstep(0.075, 0.02, h[2]) * smoothstep(0.012, 0.03, Math.abs(h[0]));
361
+ const lowFace = smoothstep(-0.03, -0.05, h[1]) * smoothstep(-0.1, -0.6, n[1]);
362
+ const throat = smoothstep(-0.034, -0.055, h[1]) * smoothstep(0.08, 0.03, h[2]);
363
+ let mask = clamp(Math.max(lipSide, cheek, lowFace, throat, r === 6 ? 0.85 : 0), 0, 1);
364
+ col = mix3(col, C.white, mask);
365
+ ag *= 1 - mask;
366
+ // dark tear line: from the inner eye corner down and forward toward the upper lip
367
+ const tl = segD(h, [0.025, 0.012, 0.05], [0.017, -0.018, 0.085]);
368
+ const tear = Math.exp(-((tl.d / (0.0045 + 0.003 * tl.t)) ** 2)) * tearK * (1 - 0.6 * juv);
369
+ col = mix3(col, C.tear, tear * 0.85);
370
+ // cheek ruff: long white fluff with an orange / dark edge toward the neck
371
+ const ruffW = smoothstep(-0.015, -0.06, h[2]) * smoothstep(0.03, 0.05, Math.abs(h[0])) * smoothstep(0.015, -0.015, h[1]);
372
+ // fur length: sleek muzzle and face, longer crown and cheeks, flaring cheek ruff
373
+ fl = mix(0.009, 0.0035, smoothstep(0.02, 0.1, h[2]));
374
+ if (h[1] > 0.03 && h[2] < 0.02) fl = 0.009;
375
+ fl = Math.max(fl, 0.024 * (0.8 + 0.2 * ruffK) * ruffW);
376
+ if (r === 6) fl = mix(0.004, 0.012, smoothstep(0.08, 0.0, h[2]));
377
+ // the throat under the jaw runs into the long fur of the neck ruff (no step at the region seam)
378
+ if (r === 2) fl = mix(fl, 0.024, smoothstep(-0.05, -0.075, h[1]) * smoothstep(0.06, 0.0, h[2]));
379
+ // back of the head blends into the neck
380
+ if (r === 2 && h[2] < -0.07) {
381
+ const b = smoothstep(-0.07, -0.11, h[2]);
382
+ fl = mix(fl, 0.02, b);
383
+ }
384
+ // eye rims (black lid margins)
385
+ for (const e of eyePrims) {
386
+ const de = SDFModel.dist(e, p[0], p[1], p[2]);
387
+ if (Math.abs(de) < 0.0032) mark = Math.min(mark, Math.abs(de) - 0.0013);
388
+ if (Math.abs(de) < 0.0011) { mat = MAT.DARK_SKIN; fl = 0; }
389
+ else if (Math.abs(de) < 0.0032) fl = Math.min(fl, 0.001);
390
+ }
391
+ // nose leather
392
+ const dn = SDFModel.dist(nosePrim, p[0], p[1], p[2]);
393
+ if (dn < 0.0015 && h[2] > 0.138) { mat = MAT.NOSE; col = srgb(0x1a1616); fl = 0; }
394
+ else if (dn < 0.0035 && h[2] > 0.13) { mark = Math.min(mark, dn - 0.0022); fl = Math.min(fl, 0.0015); }
395
+ // lips: head vertices near the jaw surface, jaw vertices near the head surface (black lip line)
396
+ // (the lip line ends at the mouth corner, z ~0.025: behind it the cheek's jowl wraps the jaw's side and
397
+ // the line along their seam ran back under the cheek fur as a dark crease. Skin facing out (the jowl's
398
+ // side and underside, the jaw's outer side) is never the mouth's lining: it shows when the mouth opens,
399
+ // and painted as the lining it drew a dark pocket behind the mouth corner; buried in the other surface it
400
+ // keeps fur only as long as it stays inside it)
401
+ const lipZ = smoothstep(0.012, 0.03, h[2]);
402
+ const nOut = n[0] * Math.sign(h[0] || 1);
403
+ if (partOf[v] !== 2) {
404
+ const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
405
+ const lining = h[2] > 0.025 ? nOut < 0.55 || n[1] > 0.3 : nOut < 0.2 && n[1] > -0.5;
406
+ if (dj < -0.0004 && lining) { mat = MAT.MOUTH; col = srgb(0x6e3438); fl = 0; }
407
+ else if (dj < -0.0004) fl = Math.min(fl, Math.max(0, -dj - 0.0015));
408
+ else if (dj < 0.006) { mark = Math.min(mark, mix(1, dj - 0.0018, lipZ)); fl = Math.min(fl, mix(fl, mix(0, 0.003, smoothstep(0.002, 0.006, dj)), lipZ)); }
409
+ } else {
410
+ const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
411
+ const lining = nOut < 0.45 || n[1] > 0.35;
412
+ if (dh < 0.0 && lining) { mat = MAT.MOUTH; col = srgb(0x6e3438); fl = 0; }
413
+ else if (dh < 0.0) fl = Math.min(fl, Math.max(0, -dh - 0.0015));
414
+ else if (dh < 0.005) { mark = Math.min(mark, mix(1, dh - 0.0015, lipZ)); fl = Math.min(fl, mix(fl, mix(0, 0.003, smoothstep(0.0015, 0.005, dh)), lipZ)); }
415
+ }
416
+ // teeth
417
+ for (const c of caninePrims) if (SDFModel.dist(c, p[0], p[1], p[2]) < 0.0008) { mat = MAT.KERATIN; col = srgb(0xe8e0cc); fl = 0; mark = 1; }
418
+ under = 0.3;
419
+ }
420
+ // kits: woolly and uniform
421
+ if (juv > 0 && mat === MAT.FUR) {
422
+ fl = mix(fl, Math.max(fl * 0.8, r === 2 || r === 6 ? 0.008 : 0.016), juv);
423
+ ag *= 1 - 0.8 * juv;
424
+ }
425
+ // low-frequency colour variation
426
+ const cvh = fbm3(p[0] * 6 + 5 + nOff, p[1] * 6, p[2] * 6, 2) - 0.5;
427
+ if (mat === MAT.FUR) col = [col[0] * (1 + 0.14 * nz + 0.1 * cvh + 0.06 * nz2), col[1] * (1 + 0.12 * nz + 0.06 * nz2), col[2] * (1 + 0.1 * nz - 0.08 * cvh + 0.06 * nz2)];
428
+ markSDF[v] = mark;
429
+ furLen[v] = fl;
430
+ tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
431
+ surf[v * 4 + 2] = mat === MAT.FUR ? clamp(ag * (0.85 + 0.3 * nz2), 0, 1) : 0;
432
+ surf[v * 4 + 3] = mat === MAT.FUR ? under : 0;
433
+ if (mat === MAT.KERATIN) surf[v * 4] = 0.7;
434
+ if (mat === MAT.NOSE) surf[v * 4] = 0.55;
435
+ }
436
+
437
+ // smooth fur length so the shells do not step (bare skin stays locked)
438
+ smoothField(furLen, weights.neighbors, 3, (v) => tint[v * 4 + 3] > 0);
439
+
440
+ void rng;
441
+ const pattern = new Float32Array(nV).fill(1);
442
+ const patternColor = new Float32Array(nV * 4);
443
+ return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, region, ventral, tailT };
444
+ }
@@ -0,0 +1,120 @@
1
+ // Red fox (Vulpes vulpes). A small, light, long-legged canid with a narrow pointed muzzle, big black-
2
+ // backed ears, vertical-slit pupils, black stockings, a white bib and a huge white-tipped brush; trots
3
+ // in a near single-file line and hunts small prey with a high arcing "mousing" pounce.
4
+ // Colour morphs are variants (createAnimal('fox', { variant: 'silver' })) or come from the seed.
5
+ import { foxRig, HEAD_O } from './rig.js';
6
+ import { sculptFox, eyeOf } from './sculpt.js';
7
+ import { foxRegions } from './regions.js';
8
+ import { foxCoat, palette } from './coat.js';
9
+ import { motion } from './motion.js';
10
+ import { rng } from '../../core/math/vec.js';
11
+
12
+ // [id, name, seeded frequency]
13
+ const VARIANTS = [
14
+ ['red', 'Red fox', 0.7],
15
+ ['cross', 'Cross fox', 0.12],
16
+ ['silver', 'Silver fox', 0.08],
17
+ ['urban', 'Urban fox (dark winter coat)', 0.1],
18
+ ];
19
+
20
+ export default {
21
+ id: 'fox',
22
+ name: 'Red fox',
23
+ latin: 'Vulpes vulpes',
24
+ group: 'paws',
25
+ plan: 'quadruped',
26
+ covering: 'fur',
27
+ variants: VARIANTS.map(([id, name]) => ({ id, name, latin: 'Vulpes vulpes' })),
28
+
29
+ // Seeded individual. Morph (red 70 %, cross 12 %, silver 8 %, urban 10 %, or opts.variant); dog
30
+ // foxes ~15-20 % heavier (+4 % withers height), broader head and muzzle, fuller ruff; vixens finer;
31
+ // kits (age 'juvenile', ~10-12 weeks): about half size, big domed head, short muzzle, very big ears,
32
+ // short legs and brush, woolly sandy coat, blue-grey eyes. Individuals vary in leg length, body
33
+ // length, muzzle, ears, brush, the extent of the white bib, stockings and tail tip, colour warmth.
34
+ variation(R, o) {
35
+ const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
36
+ const age = o.age || 'adult';
37
+ const juv = age === 'juvenile' ? 1 : 0;
38
+ const male = sex === 'male';
39
+ const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
40
+ // morph from its own hashed stream (consecutive seeds must not correlate)
41
+ const M = rng(Math.imul((o.seed ?? 1) + 0x3c6ef372, 0x9e3779b1) ^ 0x5be0cd19);
42
+ for (let i = 0; i < 3; i++) M();
43
+ let variant = o.variant && VARIANTS.some((v) => v[0] === o.variant) ? o.variant : null;
44
+ if (!variant) {
45
+ let x = M();
46
+ variant = 'red';
47
+ for (const [id, , p] of VARIANTS) { if (x < p) { variant = id; break; } x -= p; }
48
+ } else M();
49
+ const size = (male ? 1.04 : 0.96) * (1 + 0.04 * g()) * (juv ? 0.55 : 1);
50
+ const legs = 1 + 0.03 * g() - 0.1 * juv;
51
+ const head = (male ? 1.03 : 0.98) * (1 + 0.02 * g());
52
+ const coatSeed = Math.floor(R() * 1e6);
53
+ // (every draw is made whatever the morph, so that a morph never changes the individual: the silver
54
+ // fox's stockings ignore theirs)
55
+ const morphed = (v, x) => (variant === 'silver' ? v : x);
56
+ return {
57
+ sex, age, size, variant, juv,
58
+ muzzle: (male ? 1.02 : 0.99) * (1 + 0.04 * g()) * (juv ? 0.7 : 1),
59
+ headW: (male ? 1.05 : 0.98) * (1 + 0.025 * g()) * (juv ? 1.06 : 1),
60
+ ear: (1 + 0.05 * g()) * (juv ? 1.12 : 1),
61
+ eyeK: juv ? 1.18 : 1,
62
+ tail: (1 + 0.05 * g()) * (juv ? 0.72 : 1),
63
+ tailBrush: (1 + 0.07 * g()) * (juv ? 0.75 : 1),
64
+ ruff: (male ? 1.1 : 0.9) + 0.12 * g() - 0.2 * juv,
65
+ slim: (male ? 1.04 : 0.97) * (1 + 0.03 * g()) * (juv ? 1.08 : 1),
66
+ boneK: juv ? 1.12 : 1,
67
+ pawK: juv ? 1.15 : 1,
68
+ bib: 1 + 0.25 * g(),
69
+ stockings: morphed(1, Math.max(0.2, 1 + 0.45 * g())),
70
+ tailTip: M() < 0.08 && variant === 'red' ? 0 : 0.75 + 0.5 * M(), // 70-90 % of foxes have a white tip
71
+ tear: 0.7 + 0.5 * M(),
72
+ coatWarmth: 1.3 * g(),
73
+ coatLightness: 0.16 * g(),
74
+ coatSeed,
75
+ minThick: 0.0022,
76
+ warps: [
77
+ { type: 'legs', k: legs, top: 0.25 },
78
+ { type: 'length', k: 1 + 0.03 * g() - 0.05 * juv, z0: -0.19, z1: 0.24 },
79
+ { type: 'scaleAbout', c: HEAD_O, k: head * (juv ? 1.3 : 1), r0: 0.05, r1: 0.12 },
80
+ // big kit paws
81
+ ...(juv ? [[0.028, 0.2], [-0.028, 0.2], [0.033, -0.2], [-0.033, -0.2]].map(([x, z]) => ({ type: 'scaleAbout', c: [x, 0.015, z], k: 1.18, r0: 0.018, r1: 0.04 })) : []),
82
+ ],
83
+ };
84
+ },
85
+ rig: (params) => foxRig(params),
86
+ sculpt: (m, rig, params) => sculptFox(m, rig, params),
87
+ regions: foxRegions,
88
+ coat: foxCoat,
89
+ eyeSpecs: (params) => {
90
+ const look = eyeLookOf(params);
91
+ return [1, -1].map((side) => ({ side, spec: eyeOf(params), headOrigin: HEAD_O, bone: 'head', look }));
92
+ },
93
+ motion,
94
+ render: {
95
+ markColor: [0.012, 0.01, 0.009],
96
+ strandDensity: 1400,
97
+ clumpDensity: 0.9,
98
+ },
99
+ };
100
+
101
+ // Vertical slit pupil (elliptical in bright light, dilating to round in the dark), amber to yellow-
102
+ // orange iris (#c8943c with a darker ring #7a4a1c), no visible sclera, black lid margins.
103
+ export const EYE_LOOK = {
104
+ pupil: 'slit',
105
+ lids: 'mammal',
106
+ iris: { inner: [0.5, 0.26, 0.04], mid: [0.62, 0.32, 0.06], hi: [0.8, 0.5, 0.14], outer: [0.19, 0.065, 0.012] },
107
+ sclera: { color: [0.2, 0.15, 0.11], visible: false },
108
+ lidColor: [0.45, 0.14, 0.035], // (per individual: eyeLookOf)
109
+ pupilSize: [0.12, 0.75],
110
+ };
111
+ // kits: blue-grey eyes (turn amber at ~4-5 months)
112
+ export const KIT_EYE = {
113
+ ...EYE_LOOK,
114
+ iris: { inner: [0.1, 0.13, 0.17], mid: [0.2, 0.25, 0.32], hi: [0.36, 0.42, 0.5], outer: [0.06, 0.07, 0.09] },
115
+ };
116
+ // the lids are furred in the individual's face colour (the shader darkens the margin itself), so a
117
+ // blink, the curled sleep and death show a closed, furred lid with a dark line, not a black bead
118
+ function eyeLookOf(params) {
119
+ return { ...(params.juv ? KIT_EYE : EYE_LOOK), lidColor: palette(params).lid };
120
+ }
@@ -0,0 +1,81 @@
1
+ // Red fox motion data for the quadruped engine (schema: src/core/motion/quadruped.js).
2
+ // Numbers are for the reference individual (params.size = 1, withers 0.40 m, ~6 kg); the engine scales
3
+ // them by dynamic similarity for kits and big dog foxes.
4
+ //
5
+ // Gaits (Elbroch 2003, Heglund & Taylor 1988, Alexander & Jayes 1983): the trot
6
+ // is the fox's travelling gait. Lateral-sequence walk (0.4-1.0 m/s, 1.2-1.8 Hz, duty 0.60-0.70) used
7
+ // when hunting and investigating; trot (1.0-3.0 m/s, typically ~2 m/s, 2.4-3.2 Hz, duty 0.38-0.48,
8
+ // hind of each diagonal ~3 % first) in a near single-file line (trail straddle ~7.5 cm: `width`
9
+ // narrows the stance per row); transverse lope (3-6 m/s, right lead, 3.0-3.5 Hz); rotary gallop
10
+ // (LH-RH-RF-LF, 3.5-4.2 Hz, duty 0.20-0.28) with strong spinal flexion up to ~13 m/s (48 km/h).
11
+ // Trot -> gallop 1.53 M^0.24 = 2.4 m/s by scaling; foxes hold the trot to ~3 m/s.
12
+ import { foxHooks } from './behaviour.js';
13
+
14
+ const G = (name, v, f, D, off, o) => ({ name, v, f, D, off, ...o });
15
+
16
+ export const motion = {
17
+ // fox behaviour: mousing pounce, listening (ear swivels, head tilt), tail as a counterweight,
18
+ // curled sleep with the brush over the nose, the upright sit with the tail round the forefeet
19
+ hooks: foxHooks,
20
+ maxSpeed: 13,
21
+ accel: [5, 9],
22
+ decel: 9,
23
+ turnRate: 2.8,
24
+ latAccelMax: 11,
25
+ gears: { walk: 0.8, trot: 2.0, canter: 3.6, gallop: 8 },
26
+ gallopBlend: [3.4, 6.5],
27
+ gaits: [
28
+ G('walk', 0.0, 1.1, 0.72, [0.25, 0.75, 0.0, 0.5], { liftF: 0.032, liftH: 0.027, bobF: 0.003, bobH: 0.003, flex: 0.0, lat: 0.05, fold: 1.0, heel: 35, tail: 0, drop: 0.0, width: 0.85 }),
29
+ G('walk', 0.85, 1.55, 0.63, [0.25, 0.75, 0.0, 0.5], { liftF: 0.04, liftH: 0.032, bobF: 0.005, bobH: 0.005, flex: 0.0, lat: 0.05, fold: 1.2, heel: 42, tail: 0.12, drop: 0.0, width: 0.7 }),
30
+ G('trot', 1.2, 2.45, 0.47, [0.53, 0.03, 0.0, 0.5], { liftF: 0.053, liftH: 0.045, bobF: 0.009, bobH: 0.009, flex: 0.02, lat: 0.015, fold: 1.45, heel: 50, tail: 0.3, drop: 0.003, width: 0.5 }),
31
+ G('trot', 2.8, 2.95, 0.39, [0.53, 0.03, 0.0, 0.5], { liftF: 0.064, liftH: 0.054, bobF: 0.012, bobH: 0.012, flex: 0.03, lat: 0.01, fold: 1.6, heel: 58, tail: 0.4, drop: 0.006, width: 0.48 }),
32
+ G('canter', 3.6, 3.1, 0.35, [0.3, 0.45, 0.0, 0.3], { liftF: 0.085, liftH: 0.075, bobF: 0.012, bobH: 0.018, flex: 0.2, lat: 0, fold: 1.8, heel: 62, tail: 0.6, drop: 0.01, width: 0.75 }),
33
+ G('gallop', 6, 3.55, 0.27, [0.52, 0.42, 0.0, 0.1], { liftF: 0.105, liftH: 0.095, bobF: 0.016, bobH: 0.024, flex: 0.34, lat: 0, fold: 1.9, heel: 68, tail: 0.8, drop: 0.016, width: 1 }),
34
+ G('gallop', 12, 4.1, 0.21, [0.52, 0.42, 0.0, 0.1], { liftF: 0.125, liftH: 0.115, bobF: 0.016, bobH: 0.026, flex: 0.44, lat: 0, fold: 2.0, heel: 72, tail: 0.9, drop: 0.026, width: 1 }),
35
+ ],
36
+ feet: {
37
+ front: { type: 'digitigrade', curl: 55 },
38
+ hind: { type: 'digitigrade', curl: 65 },
39
+ },
40
+ stance: { width: 1, gallopWidth: 0.6, sprawl: 0, crouch: 0, scuff: [11, 18, 0] },
41
+ // torso cross-sections (bind, reference size): centre height, half height, half width (incl. coat)
42
+ body: {
43
+ chest: { y: 0.305, hh: 0.112, hw: 0.068 },
44
+ pelvis: { y: 0.31, hh: 0.11, hw: 0.076 }, // incl. the folded thighs a lying fox rests on
45
+ back: 0.036,
46
+ },
47
+ head: {
48
+ // [speed, raise (m), reach (m), pitch (rad, + = nose down)]: standing, the head is held up at or
49
+ // above the withers, muzzle near level; the trot lowers it toward the back line with the muzzle
50
+ // pointing down and ahead; the gallop stretches it forward
51
+ carriage: [[0, 0.09, -0.02, -0.02], [0.8, -0.008, 0.01, 0.16], [2.0, -0.045, 0.026, 0.3], [4.2, -0.04, 0.032, 0.25], [8, -0.037, 0.043, 0.18]],
52
+ stab: [[0, 0], [0.7, 0.22], [1.5, 0.3], [4, 0.34], [8, 0.6]],
53
+ neckBend: 42, neckPivot: 0.6, yaw: 1.3, pitchUp: 0.6, pitchDown: 0.75,
54
+ },
55
+ tail: {
56
+ // [speed, pitch per segment from horizontal (deg), base -> tip]: at rest a shallow downward curve
57
+ // with the tip just off the ground; held level with a slight droop in the trot (the brush floats
58
+ // behind, never curled over the back); straight out behind as a counterweight in the gallop
59
+ carriage: [
60
+ [0.0, [-24, -32, -38, -41, -43, -43, -42, -40, -37, -33]],
61
+ [0.8, [-14, -20, -24, -27, -28, -28, -27, -25, -22, -19]],
62
+ [2.0, [-8, -10, -12, -13, -13, -12, -11, -9, -7, -5]],
63
+ [4.2, [-5, -7, -7, -7, -6, -5, -4, -3, -2, -1]],
64
+ [8, [-3, -4, -4, -3, -2, -1, 0, 1, 1, 2]],
65
+ ],
66
+ stiffness: 0.85, sway: 6, flick: 0, wag: 0, swish: 0, curl: 0,
67
+ balance: 0.65, // the brush is a counterweight and rudder in turns and speed changes
68
+ radius: [0.05, 0.052], // (the brush: coat included)
69
+ },
70
+ ears: { rest: [0, 0], mobility: 1.2, speedFlatten: 0.5, twitch: 1.2, prick: 0.5 },
71
+ breath: { rate: 0.35, amp: 0.012, pant: true, pantRate: 4.0, pantAmp: 0.05, heatSpeed: 5 },
72
+ actions: {
73
+ attack: { style: 'pounce' }, // (the mousing pounce is a species action, behaviour.js)
74
+ eat: { style: 'tear', dropF: 0.3, dropH: 0.1, ahead: 0.75 },
75
+ drink: { dropF: 0.34, dropH: 0.12, ahead: 0.75 },
76
+ jump: { height: 0.6, distance: 2.0 },
77
+ sit: { dropH: 0.9 },
78
+ lie: {},
79
+ sleep: { style: 'curl' },
80
+ },
81
+ };