@bitefoundry/retrieviewer 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +75 -0
  6. package/INSTALLING.md +8 -5
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +110 -0
  11. package/README.md +119 -58
  12. package/RELEASING.md +27 -14
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/desktop/platform.mjs +27 -0
  17. package/integrations/linux-overlay/README.md +83 -0
  18. package/integrations/linux-overlay/overlay.py +1094 -0
  19. package/integrations/macos-overlay/README.md +83 -0
  20. package/integrations/macos-overlay/overlay.jxa +639 -0
  21. package/integrations/opencode/runtime.mjs +4 -4
  22. package/integrations/opencode/tui.tsx +1 -1
  23. package/integrations/windows-overlay/Overlay.cs +4 -4
  24. package/integrations/windows-overlay/desktop.mjs +40 -8
  25. package/integrations/windows-overlay/protocol.mjs +11 -7
  26. package/integrations/windows-overlay/worker.mjs +13 -7
  27. package/package.json +41 -14
  28. package/puppy-comfort-sprites.mjs +25 -0
  29. package/puppy-companion-comfort-q5.bin.gz +0 -0
  30. package/puppy-companion-comfort.json +77 -0
  31. package/puppy-companion-life.mjs +49 -16
  32. package/puppy-companion-objects.mjs +12 -0
  33. package/puppy-frame-predictive.mjs +15 -9
  34. package/puppy-frame-store.mjs +2 -2
  35. package/puppy-paper-world.mjs +14 -0
  36. package/puppy-paper.mjs +65 -14
  37. package/retrieviewer-cli.mjs +10 -3
  38. package/retrieviewer-plugin.d.mts +1 -1
  39. package/retrieviewer-plugin.mjs +1 -1
  40. package/retrieviewer.d.mts +10 -4
  41. package/retrieviewer.mjs +20 -14
  42. package/locomotion-core.mjs +0 -21
  43. package/locomotion-puppy.animal +0 -0
  44. package/vendor/procedural-animals/package.json +0 -40
  45. package/vendor/procedural-animals/src/animal.js +0 -146
  46. package/vendor/procedural-animals/src/bake.js +0 -51
  47. package/vendor/procedural-animals/src/build.js +0 -81
  48. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  49. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  50. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  51. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  52. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  53. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  54. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  55. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  56. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  57. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  58. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  59. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  60. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  61. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  62. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  63. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  64. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  65. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  66. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  67. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  68. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  69. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  70. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  71. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  72. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  73. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  74. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  75. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  76. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  77. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  79. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  80. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  81. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  82. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  83. package/vendor/procedural-animals/src/index.d.ts +0 -98
  84. package/vendor/procedural-animals/src/index.js +0 -29
  85. package/vendor/procedural-animals/src/registry.js +0 -48
  86. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  87. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  88. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  89. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  90. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  91. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  92. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  94. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  95. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  96. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  97. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  98. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  99. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  100. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  101. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  102. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  103. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  104. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  105. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  106. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  107. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  108. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  109. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  110. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  111. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  112. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  113. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  114. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  115. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  116. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  117. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  118. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  119. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  120. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  121. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  122. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  123. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  124. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  125. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  126. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  127. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  128. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  129. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  130. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  131. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  132. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  133. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  134. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  135. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  136. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  137. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  138. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  139. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  140. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  141. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  142. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  143. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  144. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  145. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  146. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  147. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  148. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  149. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  150. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  151. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  152. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  153. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  154. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  155. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  156. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  157. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  158. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  159. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  160. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  161. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  162. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  163. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  164. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  165. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  166. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  167. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  168. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  169. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  170. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  171. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  172. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  173. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  174. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  175. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  176. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  177. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  178. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  179. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  180. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  181. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  182. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  183. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  184. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  185. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  186. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  187. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  188. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  189. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  190. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  191. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  192. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  193. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  194. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  195. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  196. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  197. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  198. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  199. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  200. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  201. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  202. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  203. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  204. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  205. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  206. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  207. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  208. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  209. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  210. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  211. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  212. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  213. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  214. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  215. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  216. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  217. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  218. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  219. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  220. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  221. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  222. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  223. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  224. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  225. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  226. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  227. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  228. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  229. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  230. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  231. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  232. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  233. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  234. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  235. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  236. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  237. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  238. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  239. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  240. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  241. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  242. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  243. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  244. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  245. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  246. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  247. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  248. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  249. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  250. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  251. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  252. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  253. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  254. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  255. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  256. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  257. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  258. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  259. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  260. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  261. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,749 +0,0 @@
1
- // The wolf's coat: a dense double coat painted per vertex in reference space.
2
- //
3
- // Colour fields follow the grey wolf's pattern (Mech 1974): a dark
4
- // saddle of black-tipped guard hair from the withers into the tail top, a dark cape / "V" across the
5
- // shoulders, a pale patch in front of it, grizzled mane, cinnamon brow, tawny legs and ear backs,
6
- // pale cream mask on the muzzle sides, cheeks and throat, cream belly and inner legs, a dark tail
7
- // gland spot a third of the way down the tail and a black tip, black lip line and nose. The grizzle
8
- // is the shader's agouti band (surf.z): light roots, a dark band below a light tip.
9
- // Morphs: grey (agouti), black (melanistic, grey frosting on muzzle and chest), pale (arctic-type
10
- // cream) and tawny (brown Eurasian type); pups are a sooty uniform grey-brown and fluffy.
11
- // Fur length: long ruff / hackles / cape, long breeches and brush, short sleek face and lower legs.
12
- import { HEAD_O, MUZZLE_Z0, HS } from './rig.js';
13
- import { neckS, eyePatchOf } from './regions.js';
14
- import { noseAt, noseFrame, nostrilSDF, NOSTRIL } from './sculpt.js';
15
- import { SDFModel } from '../../core/sdf/sdf.js';
16
- import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
17
- import { srgb, mix3, MAT, smoothField } from '../../core/build/coatKit.js';
18
-
19
- const P3 = {
20
- // sRGB swatches, neutral-light albedo
21
- grey: {
22
- saddle: 0x3a3330, flank: 0xae9a86, lowFlank: 0xc0ad97, cape: 0x4e4744, capePale: 0xd6cbbb, mane: 0x8a7f76, maneDark: 0x514842,
23
- legOuter: 0xc4a888, legInner: 0xe2d8c8, belly: 0xe2d8c8, throat: 0xd9cfbe, cheek: 0xdbd2c3, brow: 0x6a635b, crown: 0x6c6660,
24
- muzzleTop: 0x837262, earBack: 0x9a8068, earRim: 0x2e2724, earInner: 0xc2b8aa, tailTop: 0x6e6152, tailTip: 0x141110, tailUnder: 0xcdbca4,
25
- rump: 0xcfc0aa, paw: 0xcfb99c, gland: 0x1a1614, legLine: 0x5a4a3c,
26
- ag: { saddle: 0.85, flank: 0.45, cape: 0.7, mane: 0.6, head: 0.35, leg: 0.1, tail: 0.45 },
27
- },
28
- // (side2: charcoal shoulders, neck and face #20-2f, the brown-grey undercoat showing on the flanks and
29
- // thighs #45-6c, black legs. The render's tone curve crushes dark albedos: the old palette (#221e1b
30
- // saddle) rendered as a featureless #02-0b silhouette; these land ~2x under the photo in sRGB, the
31
- // same ratio as the grey morph against side1, whose light is brighter than the studio's)
32
- black: {
33
- saddle: 0x3e3632, flank: 0x6e6053, lowFlank: 0x766757, cape: 0x3a3330, capePale: 0x564b42, mane: 0x4a413a, maneDark: 0x3a3330,
34
- legOuter: 0x3a3330, legInner: 0x443c36, belly: 0x62574e, throat: 0x544a42, cheek: 0x4a423c, brow: 0x3a332e, crown: 0x403833,
35
- muzzleTop: 0x3e3632, earBack: 0x3a3330, earRim: 0x1e1a18, earInner: 0x4a423c, tailTop: 0x3a3330, tailTip: 0x1c1816, tailUnder: 0x4e453e,
36
- rump: 0x6e6053, paw: 0x3a3330, gland: 0x1c1816, legLine: 0x2a2522,
37
- ag: { saddle: 0.45, flank: 0.35, cape: 0.4, mane: 0.45, head: 0.2, leg: 0.1, tail: 0.35 },
38
- },
39
- pale: {
40
- saddle: 0xc6b797, flank: 0xd9d0bb, lowFlank: 0xe2dac7, cape: 0xcdbf9f, capePale: 0xebe5d6, mane: 0xddd4c0, maneDark: 0xc9bc9f,
41
- legOuter: 0xe2d9c6, legInner: 0xece6d8, belly: 0xece6d8, throat: 0xefe9dd, cheek: 0xefe9dd, brow: 0xcab897, crown: 0xd1c5ab,
42
- muzzleTop: 0xd2c19f, earBack: 0xcdbb9a, earRim: 0x9d8c70, earInner: 0xe8e1d2, tailTop: 0xc0af8d, tailTip: 0x8c7b62, tailUnder: 0xe6dfcd,
43
- rump: 0xe6dfcd, paw: 0xe2d9c6, gland: 0x9a886c, legLine: 0xd2c5a8,
44
- ag: { saddle: 0.3, flank: 0.12, cape: 0.2, mane: 0.15, head: 0.08, leg: 0, tail: 0.3 },
45
- },
46
- tawny: {
47
- saddle: 0x4d3d30, flank: 0xa88a68, lowFlank: 0xb89a78, cape: 0x62503f, capePale: 0xd2bf9f, mane: 0x8a7258, maneDark: 0x5c4a3a,
48
- legOuter: 0xc09a70, legInner: 0xd8c4a8, belly: 0xd8c4a8, throat: 0xdecbb0, cheek: 0xd9c7ad, brow: 0x8c6444, crown: 0x7e6650,
49
- muzzleTop: 0x927052, earBack: 0xa67448, earRim: 0x3a2c22, earInner: 0xc6b69c, tailTop: 0x6a5440, tailTip: 0x1c1612, tailUnder: 0xc6ad8a,
50
- rump: 0xcdb592, paw: 0xc9ab86, gland: 0x221a14, legLine: 0x5e4834,
51
- ag: { saddle: 0.65, flank: 0.35, cape: 0.5, mane: 0.45, head: 0.25, leg: 0.08, tail: 0.4 },
52
- },
53
- };
54
- const PUP = 0x5b534c; // sooty grey-brown natal coat
55
-
56
- function palette(p) {
57
- const base = P3[p.variant] || P3.grey;
58
- const k = p.coatWarmth || 0, l = p.coatLightness || 0, juv = p.juv || 0;
59
- const pup = srgb(PUP).map((c) => c * (p.variant === 'black' ? 0.45 : p.variant === 'pale' ? 1.9 : 1));
60
- // grey individuals: a seeded tone from cream-grey (toward the pale palette, up to 35 %, less grizzle:
61
- // threequarter1, front1) to dark grizzled (the dorsal colours up to 25 % toward the black palette and
62
- // more black-tipped guard hair, the cream mask, throat and belly kept: face1, threequarter2)
63
- const tone = p.variant === 'grey' || !P3[p.variant] ? p.greyTone || 0 : 0;
64
- const KEEP_PALE = /cheek|throat|belly|legInner|earInner|capePale|rump|tailUnder/;
65
- const ag = {};
66
- for (const [key, a] of Object.entries(base.ag)) ag[key] = tone > 0 ? mix(a, P3.pale.ag[key], 0.35 * tone) : Math.min(1, a * (1 - 0.3 * tone));
67
- const out = { ag };
68
- for (const [key, hex] of Object.entries(base)) {
69
- if (key === 'ag') continue;
70
- let c = srgb(hex);
71
- if (tone > 0 && P3.pale[key] !== undefined) c = mix3(c, srgb(P3.pale[key]), 0.35 * tone);
72
- else if (tone < 0 && P3.black[key] !== undefined && !KEEP_PALE.test(key)) c = mix3(c, srgb(P3.black[key]), -0.25 * tone);
73
- c = [c[0] * (1 + 0.1 * k + l), c[1] * (1 + 0.02 * k + l), c[2] * (1 - 0.1 * k + l)];
74
- // pups: most of the pattern is washed into the uniform natal coat (face mask stays a little paler)
75
- const keep = /cheek|throat|belly|legInner|earInner/.test(key) ? 0.35 : /tailTip|earRim|gland/.test(key) ? 0.5 : 0.12;
76
- if (juv > 0) c = mix3(c, pup.map((v, i) => v * (0.9 + 0.2 * (c[i] > v ? 1 : 0))), juv * (1 - keep));
77
- out[key] = c;
78
- }
79
- return out;
80
- }
81
-
82
- // Blend fur length and flow across the head (region 2) / neck (region 1) and neck / torso (0) lines: a
83
- // Dijkstra distance from the lines over the mesh graph, then Laplacian smoothing restricted to the band
84
- // within W of them (the band's outer ring and bare skin stay locked), iterated enough to diffuse ~2 cm on
85
- // the finest cells.
86
- function blendCollar(furLen, comb, region, tint, pos, nrm, nb, nV, headCell, W = 0.035) {
87
- const dist = new Float64Array(nV).fill(Infinity); // (double: the heap keys are doubles)
88
- const heap = [];
89
- const push = (v, d) => {
90
- heap.push([d, v]);
91
- for (let i = heap.length - 1; i > 0;) { const p = (i - 1) >> 1; if (heap[p][0] <= heap[i][0]) break; [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; }
92
- };
93
- const pop = () => {
94
- const top = heap[0], last = heap.pop();
95
- if (heap.length) {
96
- heap[0] = last;
97
- for (let i = 0; ;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < heap.length && heap[l][0] < heap[m][0]) m = l; if (r < heap.length && heap[r][0] < heap[m][0]) m = r; if (m === i) break; [heap[m], heap[i]] = [heap[i], heap[m]]; i = m; }
98
- }
99
- return top;
100
- };
101
- const hn = (r) => r <= 2;
102
- for (let v = 0; v < nV; v++) {
103
- if (!hn(region[v])) continue;
104
- for (const u of nb[v]) if (hn(region[u]) && region[u] !== region[v]) { dist[v] = 0; push(v, 0); break; }
105
- }
106
- const band = [];
107
- while (heap.length) {
108
- const [d, v] = pop();
109
- if (d > dist[v]) continue;
110
- band.push(v);
111
- for (const u of nb[v]) {
112
- if (!hn(region[u])) continue;
113
- const e = Math.hypot(pos[u * 3] - pos[v * 3], pos[u * 3 + 1] - pos[v * 3 + 1], pos[u * 3 + 2] - pos[v * 3 + 2]);
114
- const du = d + e;
115
- if (du < W && du < dist[u]) { dist[u] = du; push(u, du); }
116
- }
117
- }
118
- if (!band.length) return;
119
- const free = new Uint8Array(nV);
120
- for (const v of band) if (tint[v * 4 + 3] === MAT.FUR && dist[v] < W * 0.9) free[v] = 1;
121
- const iters = Math.min(160, Math.ceil(2 * (0.02 / headCell) ** 2));
122
- const tmp = new Float32Array(4 * band.length);
123
- for (let it = 0; it < iters; it++) {
124
- for (let k = 0; k < band.length; k++) {
125
- const v = band[k];
126
- if (!free[v]) { tmp[k * 4] = furLen[v]; tmp[k * 4 + 1] = comb[v * 3]; tmp[k * 4 + 2] = comb[v * 3 + 1]; tmp[k * 4 + 3] = comb[v * 3 + 2]; continue; }
127
- // (neighbours weighted by 1 / edge length: the ramp comes out even per millimetre of skin, not per
128
- // hop, so the short edges of the marching-cubes mesh do not end up steeper)
129
- let a = 0, cx = 0, cy = 0, cz = 0, n = 0;
130
- for (const u of nb[v]) {
131
- if (tint[u * 4 + 3] !== MAT.FUR) continue;
132
- const w = 1 / Math.max(0.0005, Math.hypot(pos[u * 3] - pos[v * 3], pos[u * 3 + 1] - pos[v * 3 + 1], pos[u * 3 + 2] - pos[v * 3 + 2]));
133
- a += w * furLen[u]; cx += w * comb[u * 3]; cy += w * comb[u * 3 + 1]; cz += w * comb[u * 3 + 2]; n += w;
134
- }
135
- if (!n) { tmp[k * 4] = furLen[v]; tmp[k * 4 + 1] = comb[v * 3]; tmp[k * 4 + 2] = comb[v * 3 + 1]; tmp[k * 4 + 3] = comb[v * 3 + 2]; continue; }
136
- tmp[k * 4] = 0.5 * furLen[v] + (0.5 * a) / n;
137
- tmp[k * 4 + 1] = 0.5 * comb[v * 3] + (0.5 * cx) / n;
138
- tmp[k * 4 + 2] = 0.5 * comb[v * 3 + 1] + (0.5 * cy) / n;
139
- tmp[k * 4 + 3] = 0.5 * comb[v * 3 + 2] + (0.5 * cz) / n;
140
- }
141
- for (let k = 0; k < band.length; k++) {
142
- const v = band[k];
143
- furLen[v] = tmp[k * 4]; comb[v * 3] = tmp[k * 4 + 1]; comb[v * 3 + 1] = tmp[k * 4 + 2]; comb[v * 3 + 2] = tmp[k * 4 + 3];
144
- }
145
- }
146
- // hair flow back into each vertex's tangent plane, unit length
147
- for (const v of band) {
148
- if (!free[v]) continue;
149
- const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
150
- let t = [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]];
151
- t = sub(t, mul(n, dot(t, n)));
152
- if (len(t) < 1e-6) continue;
153
- t = norm(t);
154
- comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
155
- }
156
- }
157
-
158
- // The lip line as a distance over the skin: per vertex of the muzzle, how far (m, along the mesh) the curve lies
159
- // where this surface enters the other part (the head's skin entering the jaw, or the jaw's entering the head), from
160
- // sd = the other part's signed distance at each vertex. The sources are the crossing points on the mesh edges (sd
161
- // interpolated linearly), so the field is linear across the crossing and a band of any width ends where it should,
162
- // even on cells wider than the band. adj: the vertex has a neighbour on the other side of the crossing.
163
- function lipLineDistance(sd, zone, nb, pos, nV, max) {
164
- const geo = new Float64Array(nV).fill(Infinity);
165
- const adj = new Uint8Array(nV);
166
- const heap = [];
167
- const push = (v, d) => {
168
- heap.push([d, v]);
169
- for (let i = heap.length - 1; i > 0;) { const q = (i - 1) >> 1; if (heap[q][0] <= heap[i][0]) break; [heap[q], heap[i]] = [heap[i], heap[q]]; i = q; }
170
- };
171
- const pop = () => {
172
- const top = heap[0], last = heap.pop();
173
- if (heap.length) {
174
- heap[0] = last;
175
- for (let i = 0; ;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < heap.length && heap[l][0] < heap[m][0]) m = l; if (r < heap.length && heap[r][0] < heap[m][0]) m = r; if (m === i) break; [heap[m], heap[i]] = [heap[i], heap[m]]; i = m; }
176
- }
177
- return top;
178
- };
179
- const el = (u, v) => Math.hypot(pos[u * 3] - pos[v * 3], pos[u * 3 + 1] - pos[v * 3 + 1], pos[u * 3 + 2] - pos[v * 3 + 2]);
180
- for (let v = 0; v < nV; v++) {
181
- if (!zone[v]) continue;
182
- for (const u of nb[v]) {
183
- if (!zone[u] || (sd[u] >= 0) === (sd[v] >= 0)) continue;
184
- adj[v] = 1;
185
- const d = (el(u, v) * Math.abs(sd[v])) / (Math.abs(sd[v]) + Math.abs(sd[u]) || 1);
186
- if (d < geo[v]) { geo[v] = d; push(v, d); }
187
- }
188
- }
189
- while (heap.length) {
190
- const [d, v] = pop();
191
- if (d > geo[v]) continue;
192
- for (const u of nb[v]) {
193
- if (!zone[u] || (sd[u] >= 0) !== (sd[v] >= 0)) continue;
194
- const du = d + el(u, v);
195
- if (du < max && du < geo[u]) { geo[u] = du; push(u, du); }
196
- }
197
- }
198
- return { geo, adj };
199
- }
200
-
201
- export function wolfCoat(ctx) {
202
- const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params } = ctx;
203
- const { BONES, AXIAL } = rig;
204
- const AX_LEN = weights.axialLengths;
205
- const R = rng(4241 + (params.coatSeed || 0));
206
- // whisker follicles (head-local, per side): 4 rows of 5 along the upper lip, each individual's own
207
- // (offsets of ~1 mm, sizes, a few missing: a regular grid read as a printed pattern)
208
- const RF = rng(9127 + (params.coatSeed || 0));
209
- const FOLLICLES = [0, 1].map(() => {
210
- const out = [];
211
- for (let i = 0; i < 4; i++) for (let j = 0; j < 5; j++) {
212
- const zc = 0.1375 - j * 0.0046 - (i & 1) * 0.0023 - i * 0.0012 + (RF() - 0.5) * 0.0018;
213
- const yc = -0.0045 - i * 0.0047 - 0.12 * (0.1375 - zc) + (RF() - 0.5) * 0.0014;
214
- const sg = 0.0014 + 0.0006 * RF(), a = RF() < 0.12 ? 0 : 0.75 + 0.25 * RF();
215
- out.push({ z: zc, y: yc, s2: sg * sg, a });
216
- }
217
- return out;
218
- });
219
- const C = palette(params);
220
- const AG = C.ag;
221
- const juv = params.juv || 0;
222
- const hw = params.headW || 1, mz = params.muzzle || 1;
223
- const ruffK = params.ruff ?? 1;
224
- const saddleExt = params.saddle ?? 0; // how far the saddle reaches down the flanks (-1..1)
225
- const maskK = params.mask ?? 1; // contrast of the pale facial mask
226
- const legLine = params.legLine ?? 0; // dark line on the front of the forelegs (0..1)
227
- const frost = params.frost ?? 0; // black morph: grey frosting on the muzzle and chest (age)
228
- const nOff = (params.coatSeed || 0) % 997;
229
- // mark bands at least half a head cell wide (the head is meshed at 2.5 mm x the tier's res)
230
- const Qt = ctx.Q || { res: 1, eyePatches: true };
231
- const headCell = 0.0025 * Qt.res;
232
- const band = (w) => Math.max(w, 0.5 * headCell);
233
- // (the lid margins are meshed by the eyelid patches where the tier has them)
234
- const EP = eyePatchOf(Qt);
235
- const eyeBand = (w) => Math.max(w, 0.5 * (EP ? EP.h : headCell));
236
- const { dominant, axialS, skinIndex, skinWeight, limbMember, limbWhich } = weights;
237
- const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
238
- const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
239
- const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8], sTip = AX_LEN[AX_LEN.length - 1];
240
- const tailLen = sTip - sTailBase;
241
- const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
242
- // the leather: the front lobe and the dorsal plate (sculpt.js)
243
- const leatherPrims = model.prims.filter((p) => p.tag === 'nose' || p.tag === 'nosetop');
244
- const GROOVE = noseAt(0, -0.0122, 0.0005); // head-local: the philtrum groove under the pad's front lobe
245
- const caninePrims = model.prims.filter((p) => p.tag === 'canine' || p.tag === 'tooth');
246
- const jawPrims = model.forPart('jaw').filter((p) => p.tag !== 'canine' && p.tag !== 'tooth');
247
- const bodyPrims = model.forPart('body');
248
- const headPrims = bodyPrims.filter((p) => p.bone === 'head' && !p.carve && p.tag !== 'canine' && p.tag !== 'tooth');
249
- const earPrims = model.prims.filter((p) => p.tag === 'ear');
250
- const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|femur|tibia|metatarsus|hpaw)/.test(b.name));
251
- const isPaw = BONES.map((b) => /^(fpaw|hpaw)/.test(b.name));
252
- const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw)/.test(b.name));
253
- // head-local coordinates of a reference-space point (undo head width and muzzle stretch)
254
- const headLocal = (p) => {
255
- const x = (p[0] - HEAD_O[0]) / hw / HS, y = (p[1] - HEAD_O[1]) / HS;
256
- let z = (p[2] - HEAD_O[2]) / HS;
257
- if (z > MUZZLE_Z0) z = MUZZLE_Z0 + (z - MUZZLE_Z0) / mz;
258
- return [x, y, z];
259
- };
260
-
261
- // --- regions per vertex (0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw) and limb blend
262
- const region = new Uint8Array(nV);
263
- const legness = new Float32Array(nV);
264
- const legBone = new Int16Array(nV).fill(-1);
265
- const junction = new Float32Array(nV);
266
- const tailT = new Float32Array(nV);
267
- for (let v = 0; v < nV; v++) {
268
- const bn = BONES[dominant[v]].name;
269
- const p = P(v);
270
- let lb = -1, lw = 0;
271
- for (let k = 0; k < 4; k++) {
272
- const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
273
- if (isLimb[b] && w > lw) { lw = w; lb = b; }
274
- }
275
- legBone[v] = lb;
276
- const m = limbMember[v];
277
- legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, m) : 0;
278
- junction[v] = lb >= 0 ? 4 * m * (1 - m) : 0;
279
- if (partOf[v] === 2) region[v] = 6;
280
- else if (bn.startsWith('ear')) region[v] = 5;
281
- else if (axialS[v] > sTailBase + 0.02 && bn.startsWith('tail')) { region[v] = 4; tailT[v] = clamp((axialS[v] - sTailBase) / tailLen, 0, 1); }
282
- else if (axialS[v] < sOcc + 0.01 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.02)) region[v] = 2;
283
- else if (axialS[v] < sNeckBase) region[v] = 1;
284
- else region[v] = 0;
285
- if (region[v] >= 2) { legness[v] = 0; junction[v] = 0; }
286
- }
287
- const underFactor = (v) => {
288
- const n = N(v), p = P(v), li = limbWhich[v];
289
- if (li < 0 || junction[v] <= 0) return 0;
290
- if (li <= 1) return junction[v] * Math.max(smoothstep(0.15, -0.55, n[2]) * smoothstep(0.5, 0.4, p[1]), smoothstep(0.2, -0.6, n[1]));
291
- return junction[v] * smoothstep(0.35, -0.45, n[1] + 0.35 * Math.abs(n[2]));
292
- };
293
-
294
- // --- ventral (pale underside) factor
295
- const ventral = new Float32Array(nV);
296
- for (let v = 0; v < nV; v++) {
297
- const p = P(v), n = N(v), r = region[v];
298
- let w = 0;
299
- if (r === 0) {
300
- w = smoothstep(-0.05, -0.65, n[1]);
301
- // chest front between the forelegs: pale bib
302
- if (p[2] > 0.25) w = Math.max(w, smoothstep(0.56, 0.44, p[1]) * smoothstep(0.08, 0.035, Math.abs(p[0])) * smoothstep(-0.3, 0.4, n[2]));
303
- w *= smoothstep(0.6, 0.5, p[1]) * 0.55 + 0.45;
304
- } else if (r === 1) {
305
- // throat and the front of the ruff
306
- w = smoothstep(0.1, -0.5, n[1]) * smoothstep(-0.4, 0.3, n[2]) + smoothstep(0.62, 0.52, p[1]) * 0.6;
307
- } else if (r === 4) {
308
- w = 0;
309
- } else if (r === 2 || r === 6) {
310
- const h = headLocal(p);
311
- w = smoothstep(-0.03, -0.06, h[1]) * smoothstep(-0.2, -0.7, n[1]) + (r === 6 ? 0.7 : 0);
312
- }
313
- const L = legness[v];
314
- if (L > 0) {
315
- const side = p[0] >= 0 ? 1 : -1;
316
- const inner = smoothstep(0.1, -0.6, n[0] * side);
317
- const wl = inner * 0.8 * smoothstep(0.08, 0.3, p[1]) + smoothstep(-0.3, -0.85, n[1]) * 0.5;
318
- w = mix(w, wl, L);
319
- }
320
- w = Math.max(w, underFactor(v) * 0.95);
321
- ventral[v] = clamp(w, 0, 1);
322
- }
323
-
324
- // --- comb (hair flow), bind space
325
- const comb = new Float32Array(nV * 3);
326
- for (let v = 0; v < nV; v++) {
327
- const p = P(v), n = N(v), r = region[v];
328
- let d;
329
- const bone = BONES[dominant[v]];
330
- if (r === 5) {
331
- d = norm(sub(bone.tail, bone.head));
332
- } else if (r === 2 || r === 6) {
333
- const h = headLocal(p);
334
- // out from the nose over the face; the cheek ruff sweeps back and out
335
- const fromNose = norm(sub(h, [0, -0.01, 0.16]));
336
- d = norm(add(fromNose, [0, -0.2, -0.5]));
337
- if (h[2] < 0.0 && Math.abs(h[0]) > 0.035) d = norm(add(d, [Math.sign(h[0]) * 0.6, -0.5, -0.8]));
338
- } else {
339
- const seg = weights.segT[v * 2] | 0;
340
- const a = AXIAL.points[seg], b = AXIAL.points[Math.min(seg + 1, AXIAL.points.length - 1)];
341
- d = norm(sub(b, a));
342
- if (r === 0) d = norm(add(d, [0, -0.45 * Math.abs(n[0]), 0]));
343
- if (r === 1) d = norm(add(d, [0, -0.5, 0])); // the ruff hangs down and back
344
- const L = legness[v];
345
- if (L > 0 && legBone[v] >= 0) {
346
- const lbn = BONES[legBone[v]];
347
- let dl = norm(sub(lbn.tail, lbn.head));
348
- if (isPaw[legBone[v]]) dl = norm(add(dl, [0, 0, 0.4]));
349
- else dl = norm(add(dl, [0, -0.2, -0.3]));
350
- d = norm(add(mul(d, 1 - L), mul(dl, L)));
351
- }
352
- }
353
- let t = sub(d, mul(n, dot(d, n)));
354
- if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
355
- t = norm(t);
356
- comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
357
- }
358
-
359
- // --- per-vertex paint
360
- // --- the lip line: the distance over the skin from where the head's skin enters the jaw (and the jaw's enters the
361
- // head), for the black lip margins below (a band of a set width along that curve, see lipLineDistance)
362
- const lipSd = new Float32Array(nV), lipZone = new Uint8Array(nV);
363
- for (let v = 0; v < nV; v++) {
364
- if (partOf[v] !== 1 && partOf[v] !== 2) continue;
365
- const p = P(v), h = headLocal(p);
366
- if (h[2] < -0.05 || h[1] > 0.005) continue;
367
- lipZone[v] = 1;
368
- lipSd[v] = partOf[v] === 2 ? SDFModel.evalList(headPrims, p[0], p[1], p[2]) : SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
369
- }
370
- const LIP = lipLineDistance(lipSd, lipZone, weights.neighbors, pos, nV, 0.02);
371
- // the band's value at a vertex: its signed distance from the lip line minus the band width w (< 0 = black); next
372
- // to the crossing the hidden side carries the field on linearly, further inside a dark inner margin 3 mm deep (it
373
- // shows when the mouth opens), then the mouth's own colour
374
- const LIP_T = (z) => smoothstep(0.092, 0.108, z); // the share of the band along the crossing (the front of the mouth)
375
- // (inside, also everything within 1.5 mm of the other surface: where the skin runs just inside the other part, its
376
- // mouth colours showed through that part's mesh as red specks along the lip)
377
- const lipBand = (v, w) => {
378
- const g = LIP.geo[v];
379
- if (!lipZone[v] || g === Infinity) return 1;
380
- if (lipSd[v] < 0) return Math.min(LIP.adj[v] ? -g - w : g - 0.003, -lipSd[v] - 0.0015);
381
- return g - w;
382
- };
383
-
384
- const furLen = new Float32Array(nV);
385
- const tint = new Float32Array(nV * 4);
386
- const markSDF = new Float32Array(nV).fill(1);
387
- const surf = new Float32Array(nV * 4);
388
- 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));
389
- const X = [1, 0, 0];
390
- const tailSegDorsal = (v) => {
391
- const bone = BONES[dominant[v]];
392
- const d = norm(sub(bone.tail, bone.head));
393
- return norm([0, -d[2], d[1]]);
394
- };
395
-
396
- for (let v = 0; v < nV; v++) {
397
- const p = P(v), n = N(v), r = region[v];
398
- let col, fl = 0.02, ag = 0, mat = MAT.FUR, mark = 1, under = 0.4;
399
- // low-frequency noise (ragged pattern edges) and a finer grizzle field
400
- const nz = fbm3(p[0] * 7 + nOff, p[1] * 7, p[2] * 7, 3) - 0.5;
401
- const nz2 = vnoise3(p[0] * 38 + nOff, p[1] * 38, p[2] * 38) - 0.5;
402
- // (hair-length variation of the long coat at a coarser scale: at 38 / m it stepped 4-7 mm between
403
- // neighbouring 2-3 mm head cells in the ruff, a field of small fur walls)
404
- const nzL = vnoise3(p[0] * 16 + nOff, p[1] * 16, p[2] * 16) - 0.5;
405
- if (r === 0 || r === 1) {
406
- const up = n[1];
407
- const vent = ventral[v];
408
- // flanks -> saddle by the surface normal, ragged
409
- const dors = smoothstep(-0.2, 0.55, up + 0.35 * nz + 0.3 * saddleExt + 0.15 * smoothstep(0.62, 0.72, p[1]));
410
- col = mix3(C.lowFlank, C.flank, smoothstep(-0.3, 0.3, up + 0.2 * nz));
411
- ag = mix(AG.flank * 0.7, AG.flank, smoothstep(-0.3, 0.3, up));
412
- const alongSaddle = smoothstep(0.34, 0.24, p[2]) * smoothstep(-0.42, -0.3, p[2]);
413
- if (r === 0) {
414
- col = mix3(col, C.saddle, dors * 0.92 * alongSaddle);
415
- ag = mix(ag, AG.saddle, dors * alongSaddle);
416
- // cape: a dark band from the withers slanting down in front of the shoulder, and a pale
417
- // patch in front of it (between the mane and the cape)
418
- const capeLine = p[2] - (0.37 + (0.66 - p[1]) * 0.55);
419
- const cape = Math.exp(-(((capeLine + 0.04 * nz) / 0.045) ** 2)) * smoothstep(0.45, 0.58, p[1]);
420
- const pale = Math.exp(-(((capeLine - 0.075) / 0.04) ** 2)) * smoothstep(0.5, 0.6, p[1]) * smoothstep(0.66, 0.6, p[1]);
421
- col = mix3(col, C.capePale, pale * 0.55 * (1 - dors));
422
- col = mix3(col, C.cape, cape * 0.75);
423
- ag = mix(ag, AG.cape, cape);
424
- // pale buttocks behind the thighs
425
- const rump = smoothstep(-0.33, -0.4, p[2]) * smoothstep(-0.2, -0.7, n[2]) * smoothstep(0.66, 0.55, p[1]);
426
- col = mix3(col, C.rump, rump * 0.85);
427
- ag *= 1 - rump * 0.7;
428
- col = mix3(col, C.belly, vent);
429
- ag *= 1 - vent;
430
- // guard-hair lengths: flanks, long saddle, longest over the cape / hackles, belly fringe
431
- const withers = smoothstep(0.1, 0.3, p[2]) * smoothstep(0.45, 0.9, up);
432
- fl = mix(0.03, 0.042, smoothstep(0.2, 0.85, up)) * (1 + 0.14 * nzL);
433
- fl = Math.max(fl, 0.058 * ruffK * withers);
434
- fl = mix(fl, 0.022, smoothstep(0.3, 0.9, vent));
435
- if (p[2] < -0.3) fl = Math.max(fl, 0.04 * rump);
436
- } else {
437
- // mane: grizzled, dark-tipped along the top, cream throat
438
- const top = smoothstep(0.1, 0.85, up + 0.25 * nz);
439
- col = mix3(C.mane, C.maneDark, top * 0.8);
440
- ag = mix(AG.mane * 0.7, AG.mane, top);
441
- // dark collar line down the side of the neck, edging the pale throat
442
- const collar = Math.exp(-(((vent - 0.35 + 0.2 * nz) / 0.18) ** 2)) * (1 - top);
443
- col = mix3(col, C.maneDark, collar * 0.45);
444
- col = mix3(col, C.throat, smoothstep(0.35, 0.8, vent));
445
- ag *= 1 - smoothstep(0.3, 0.7, vent);
446
- fl = mix(0.064, 0.05, vent) * ruffK * (1 + 0.16 * nzL);
447
- }
448
- const L = legness[v];
449
- if (L > 0) {
450
- const lb = legBone[v];
451
- const front = lb >= 0 && isFront[lb];
452
- let lc = mix3(C.legOuter, C.legInner, smoothstep(0.1, 0.7, ventral[v]));
453
- // the upper leg keeps some of the body colour
454
- lc = mix3(lc, col, smoothstep(0.32, 0.5, p[1]) * 0.6);
455
- // dark line down the front of the foreleg (many grey wolves)
456
- if (front && legLine > 0) lc = mix3(lc, C.legLine, legLine * smoothstep(0.35, 0.8, n[2]) * smoothstep(0.08, 0.14, p[1]) * smoothstep(0.34, 0.26, p[1]) * smoothstep(0.3, -0.2, Math.abs(n[0])));
457
- let lf = mix(0.008, 0.024, smoothstep(0.1, 0.42, p[1]));
458
- // breeches on the back of the thighs, feathering behind the forearm
459
- if (!front) lf = Math.max(lf, 0.048 * smoothstep(-0.1, -0.7, n[2]) * smoothstep(0.24, 0.45, p[1]));
460
- else lf = Math.max(lf, 0.02 * smoothstep(-0.2, -0.8, n[2]) * smoothstep(0.14, 0.3, p[1]));
461
- if (lb >= 0 && isPaw[lb]) { lf = 0.004; lc = C.paw; }
462
- col = mix3(col, lc, L);
463
- ag = mix(ag, AG.leg * smoothstep(0.15, 0.45, p[1]), L);
464
- fl = mix(fl, lf, L);
465
- }
466
- under = 0.45;
467
- } else if (r === 4) {
468
- const t = tailT[v];
469
- const dors = dot(n, tailSegDorsal(v));
470
- const top = smoothstep(-0.15, 0.35, dors + 0.25 * nz);
471
- col = mix3(C.tailUnder, C.tailTop, top);
472
- // the saddle runs into the tail top
473
- ag = AG.tail * top;
474
- // tail gland: a dark oval on the top, a third of the way down
475
- const gl = Math.exp(-(((t - 0.3) / 0.075) ** 2)) * smoothstep(0.2, 0.75, dors);
476
- col = mix3(col, C.gland, gl * 0.7);
477
- // black tip
478
- const tip = smoothstep(0.76, 0.84, t + 0.03 * nz);
479
- col = mix3(col, C.tailTip, tip);
480
- ag *= 1 - tip;
481
- // the longest hair at the tip: a pointed brush beyond the last vertebra
482
- fl = mix(0.04, 0.05, smoothstep(0.1, 0.5, t)) + 0.016 * smoothstep(0.7, 1, t);
483
- under = 0.45;
484
- } else if (r === 5) {
485
- const earP = earPrims[p[0] > 0 ? 0 : 3];
486
- const faceDir = [earP.P[9], earP.P[10], earP.P[11]];
487
- const upE = [earP.P[6], earP.P[7], earP.P[8]];
488
- const lat = [earP.P[3], earP.P[4], earP.P[5]];
489
- const front = dot(n, faceDir);
490
- const rel = sub(p, [earP.P[0], earP.P[1], earP.P[2]]);
491
- const ht = dot(rel, upE) / 0.075; // ~0 base .. 1 tip
492
- const edge = Math.abs(dot(rel, lat)) / (0.03 * Math.max(0.3, 1 - ht));
493
- // (the ear's root is as furry as the head around it: no step in hair length at the ear base)
494
- const earRoot = 1 - smoothstep(0.0, 0.4, ht);
495
- if (front > 0.3) {
496
- col = mix3(C.earBack, C.earInner, smoothstep(0.3, 0.75, front));
497
- // the dark edge of the pinna frames the pale cup (face1, face2)
498
- col = mix3(col, C.earRim, smoothstep(0.78, 1.0, edge) * 0.6);
499
- fl = mix(0.012 * (1 - smoothstep(0.5, 1.0, ht)), 0.02, earRoot);
500
- } else {
501
- col = C.earBack;
502
- // dark rims and tip on the back of the ear
503
- col = mix3(col, C.earRim, Math.max(smoothstep(0.7, 1.0, edge), smoothstep(0.7, 0.95, ht)) * 0.85);
504
- fl = mix(0.007, 0.02, earRoot);
505
- }
506
- ag = AG.head * 0.5;
507
- } else {
508
- // ---------------- head & jaw
509
- // The mask (face1, face2, threequarter2): a grizzled grey-brown crown, darker along the midline
510
- // between the eyes; a tawny bridge over the top and upper half of the muzzle; cream on the upper
511
- // lips and lower muzzle sides that runs back, without a break, into large cream cheek patches under
512
- // and behind the eyes and down to the throat (the tawny / cream line rises from the nose's lower
513
- // third to just under the eye); a dark crescent under each eye and a tear line from its inner
514
- // corner; a dark grizzled temple band from the outer corner back above the cream cheek; the cheek
515
- // ruff grizzled grey with a dark rim framing the pale cheek; pale spots above the eyes.
516
- const h = headLocal(p);
517
- const ax = Math.abs(h[0]), lat = Math.abs(n[0]);
518
- col = C.crown;
519
- ag = AG.head;
520
- const brow = smoothstep(-0.03, 0.02, h[2]) * smoothstep(0.075, 0.03, h[2]) * smoothstep(0.0, 0.03, h[1]);
521
- col = mix3(col, C.brow, brow * 0.6);
522
- // (the forehead and the upper bridge are grizzled, salt-and-pepper: face1, face2, front1)
523
- ag = mix(ag, AG.mane, brow * 0.5);
524
- // dark midline from the stop up the forehead
525
- const midDark = Math.exp(-((ax / 0.012) ** 2)) * smoothstep(0.012, 0.032, h[1]) * smoothstep(0.062, 0.03, h[2]) * smoothstep(-0.06, -0.015, h[2]);
526
- col = mix3(col, C.maneDark, midDark * 0.45 * (1 - juv));
527
- ag = mix(ag, AG.mane, midDark * 0.6);
528
- // the cream zone lies under the mask line: just under the eye, falling back along the cheek (the
529
- // temple band above it); the bridge (medial, above the lip line) stays tawny down to the nose
530
- const nzl = 0.003 * nz;
531
- const onMuzzle = smoothstep(0.036, 0.052, h[2]);
532
- const maskLine = 0.012 + 0.22 * Math.min(0, h[2] - 0.03) + nzl;
533
- // (the cream blends into the grizzled cheek and the tawny bridge over 1.5-2 cm of skin, through warm tawny and
534
- // grizzle: threequarter2, lying1; a 1 cm edge read as a husky's crisp mask)
535
- const below = smoothstep(maskLine + 0.009, maskLine - 0.012, h[1]);
536
- // (over the last 3-4 cm the cream of the upper lips comes up beside the nose to the middle of the pad,
537
- // the bridge colour above it: face1, face2, threequarter2)
538
- const tipK = smoothstep(0.105, 0.145, h[2]);
539
- const bridgeW = mix(0.036, 0.024, smoothstep(0.05, 0.14, h[2])) + 0.003 * nz;
540
- const lipLine = mix(-0.012, -0.016, smoothstep(0.06, 0.14, h[2])) + 0.016 * tipK + nzl;
541
- const medial = smoothstep(bridgeW + 0.005, bridgeW - 0.003, ax);
542
- const mtop = onMuzzle * Math.max(medial * smoothstep(lipLine - 0.009, lipLine + 0.01, h[1]), 1 - below);
543
- // (tawny toward the nose, the grizzled crown colour carried down the bridge between the eyes;
544
- // face1 / face2: the bridge is grizzled, never a flat orange)
545
- col = mix3(col, C.muzzleTop, mtop * 0.9 * (1 - 0.85 * smoothstep(0.115, 0.07, h[2])));
546
- ag = mix(ag, AG.head, mtop);
547
- // cream mask: under the mask line (cheeks, sides of the muzzle, upper lips), the lips and chin, the
548
- // eyebrow spots
549
- const face = smoothstep(0.1, 0.35, lat + onMuzzle) * smoothstep(0.018, 0.03, ax + 0.03 * onMuzzle) * smoothstep(-0.075, -0.045, h[2]);
550
- const cream = below * (1 - mtop) * face;
551
- const eyebrow = g3(h, [0.03, 0.041, 0.037], [0.013, 0.008, 0.014]) * 0.5; // (soft pale spots, not pips)
552
- const lowFace = smoothstep(-0.03, -0.055, h[1]) * smoothstep(-0.1, -0.6, n[1]) * (r === 6 ? 0.55 : 1);
553
- let mask = clamp(Math.max(cream, eyebrow, lowFace * 0.85, r === 6 ? 0.3 : 0) * maskK * 1.05, 0, 1);
554
- col = mix3(col, C.cheek, mask);
555
- // the middle of the transition warm (tawny, a little grizzle), on the muzzle and the cheeks
556
- const midT = 4 * mask * (1 - mask) * smoothstep(-0.02, 0.01, h[2]) * (r === 2 ? 1 : 0);
557
- col = mix3(col, mix3(C.muzzleTop, C.cheek, 0.35), 0.45 * midT);
558
- ag *= 1 - 0.95 * mask;
559
- // dark accents: a crescent under each eye, the tear line from its inner corner down the edge of the
560
- // bridge, the temple band from the outer corner back along the top of the cheek patch
561
- const edge = Math.exp(-(((ax - bridgeW) / 0.004) ** 2)) * onMuzzle * smoothstep(0.075, 0.05, h[2]) * smoothstep(-0.01, 0.008, h[1]) * 0.6;
562
- const crescent = g3(h, [0.036, 0.008, 0.036], [0.012, 0.005, 0.011]) * 0.65;
563
- const temple = Math.exp(-(((h[1] - maskLine - 0.006) / 0.006) ** 2)) * (1 - onMuzzle) * smoothstep(0.036, 0.05, ax) * smoothstep(-0.06, -0.03, h[2]) * 0.6;
564
- const eyeRing = g3(h, [0.034, 0.021, 0.036], [0.017, 0.013, 0.015]) * 0.45;
565
- col = mix3(col, C.maneDark, clamp(Math.max(edge, crescent, temple, eyeRing), 0, 1) * (1 - juv));
566
- // cheek ruff: grizzled like the mane; its front rim frames the pale cheek
567
- const ruffW = smoothstep(-0.035, -0.075, h[2]) * smoothstep(0.035, 0.055, ax) * smoothstep(0.02, -0.01, h[1]);
568
- const ruffRim = Math.exp(-(((h[2] + 0.06 + 0.004 * nz) / 0.01) ** 2)) * smoothstep(0.035, 0.055, ax) * smoothstep(0.03, 0.0, h[1]) * smoothstep(-0.08, -0.04, h[1]);
569
- col = mix3(col, mix3(C.mane, C.maneDark, 0.35), ruffW * 0.75);
570
- col = mix3(col, C.maneDark, ruffRim * 0.5 * (1 - juv));
571
- ag = mix(ag, AG.mane, Math.max(ruffW, ruffRim));
572
- // whisker follicles: ~4 rows of dark spots along the upper lip, from just behind the leather back
573
- // ~25 mm (threequarter2, lying1, face1): soft (the dark hair round each follicle), on the tiers whose
574
- // face mesh can hold them (hero, high: 2.5-3.3 mm cells)
575
- const wsK = smoothstep(0.0045, 0.003, headCell) * smoothstep(0.25, 0.55, lat) * (1 - juv);
576
- if (wsK > 0 && r === 2 && h[2] > 0.105 && h[2] < 0.145 && h[1] < 0.002 && h[1] > -0.032) {
577
- let sp = 0;
578
- for (const f of FOLLICLES[p[0] >= 0 ? 0 : 1]) sp = Math.max(sp, f.a * Math.exp(-(((h[2] - f.z) ** 2 + (h[1] - f.y) ** 2) / f.s2)));
579
- col = mix3(col, [0.02, 0.017, 0.015], 0.88 * sp * wsK);
580
- }
581
- // black morph: grey frosting on the lips and chin (patchy, with age)
582
- if (frost > 0) col = mix3(col, [0.2, 0.19, 0.18], frost * 0.55 * smoothstep(0.06, 0.1, h[2]) * smoothstep(-0.015, -0.04, h[1]) * smoothstep(-0.1, 0.25, nz + 0.12));
583
- // fur length: a 7 mm muzzle (research 8-15 mm, ~60 % sculpted), 9.5 mm under the eyes growing to 12.5 mm
584
- // behind them, a sleek 8 mm forehead and crown (face1, face2: the hair lies flat, flowing back from the
585
- // bridge; at 12.5-13 mm the shader clumped it into fleece-like tufts, and under the eye into a honeycomb
586
- // of scales), longer hair on the temples in front of the ears (the wide wolf face), the flaring cheek ruff
587
- fl = mix(mix(0.0125, 0.0095, smoothstep(-0.03, 0.01, h[2])), 0.007, smoothstep(0.03, 0.09, h[2]));
588
- const crown = smoothstep(0.006, 0.022, h[1]) * smoothstep(0.05, 0.036, ax) * smoothstep(-0.085, -0.06, h[2]);
589
- fl = mix(fl, Math.min(fl, 0.008), crown);
590
- // (salt-and-pepper: black-banded guard hair tips, as grizzled as the mane)
591
- if (mat === MAT.FUR) ag = mix(ag, Math.max(ag, AG.mane * (1 - mask)), crown * 0.7);
592
- const templeHair = smoothstep(0.04, 0.058, ax) * smoothstep(0.02, -0.02, h[2]) * smoothstep(-0.005, 0.015, h[1]) * smoothstep(0.075, 0.05, h[1]);
593
- fl = Math.max(fl, 0.022 * templeHair);
594
- // (the cheek ruff's hair grows over wider ramps than its colour: ~1 mm of skin per mm of length)
595
- const ruffL = smoothstep(-0.02, -0.085, h[2]) * smoothstep(0.03, 0.075, ax) * smoothstep(0.035, -0.035, h[1]);
596
- fl = Math.max(fl, 0.04 * ruffK * ruffL);
597
- // (the chin's hair a little longer under the front of the mouth: at 6 mm it read as a bare, pale lobe from the
598
- // front and below)
599
- if (r === 6) fl = mix(mix(0.006, 0.0085, smoothstep(0.095, 0.112, h[2]) * smoothstep(-0.03, -0.036, h[1])), 0.014, smoothstep(0.08, 0.0, h[2]));
600
- // back of the head blends into the mane
601
- if (r === 2 && h[2] < -0.055) {
602
- const b = smoothstep(-0.055, -0.12, h[2]);
603
- col = mix3(col, C.mane, b * 0.6);
604
- fl = mix(fl, 0.04 * ruffK, b);
605
- }
606
- // Crisp marks (lid margins, the nose outline, the black lips) as one continuous distance field:
607
- // the shader antialiases a mark by its screen-space gradient, so a field that jumps from a few mm
608
- // to 1 between neighbouring vertices drew a smeared, dashed lip line on the 5 mm cells of the
609
- // medium tier (a row of "teeth"). Every band is at least half a head cell wide.
610
- let md = 1e9;
611
- // eye rims (dark lid margins)
612
- for (const e of eyePrims) {
613
- const de = SDFModel.dist(e, p[0], p[1], p[2]);
614
- md = Math.min(md, Math.abs(de) - eyeBand(0.0019));
615
- if (Math.abs(de) < 0.0016) { mat = MAT.DARK_SKIN; fl = 0; }
616
- else fl = Math.min(fl, 0.0015 + 0.8 * Math.max(0, Math.abs(de) - 0.0045)); // (no crater of long brow hair round the lids)
617
- }
618
- // nose leather (the front lobe and the dorsal plate), with a thin dark fringe of fur behind its edge.
619
- // The nostrils are marks on the leather (render hint noseMarks: drawn as dark openings with no sheen),
620
- // the same comma the sculpt carves: a carved opening alone read as a faint dimple, and its shape is
621
- // lost on the 5 mm face cells of the medium tier.
622
- let dn = 1e9;
623
- for (const q of leatherPrims) dn = Math.min(dn, SDFModel.dist(q, p[0], p[1], p[2]));
624
- if (dn < 0.0022 && h[2] > 0.135) {
625
- mat = MAT.NOSE; col = C.nose || srgb(0x3f3a38); fl = 0; // (render hint noseTint: the shader's own leather grey)
626
- // (dilated by the inward bias of a linearly interpolated distance field on the tier's cells,
627
- // ~h^2 / 8r: on the 5 mm cells of medium the 4 mm openings otherwise all but vanished)
628
- const [lx, ly] = noseFrame(h);
629
- const dN = nostrilSDF(lx, ly) * HS - headCell * headCell / (8 * 0.0038);
630
- md = Math.min(md, dN);
631
- // the nostrils' lit rims: a paler band of leather along the upper and outer edge of each opening, and the
632
- // septum between them (face1, face2: black commas framed by grey leather; dark marks on a uniformly dark
633
- // pad read as dim ovals from above)
634
- const rim = smoothstep(0, 0.0008, dN) * smoothstep(0.0032, 0.0012, dN) * smoothstep(NOSTRIL.c[1] - 0.004, NOSTRIL.c[1] + 0.001, ly);
635
- const septum = smoothstep(0.0045, 0.002, Math.abs(lx)) * smoothstep(NOSTRIL.c[1] - 0.0065, NOSTRIL.c[1] - 0.003, ly) * smoothstep(NOSTRIL.c[1] + 0.006, NOSTRIL.c[1] + 0.002, ly);
636
- col = mix3(col, [0.17, 0.158, 0.15], 0.9 * Math.max(rim, 0.7 * septum));
637
- } else {
638
- if (dn < 0.005 && h[2] > 0.13) fl = Math.min(fl, 0.0035);
639
- md = Math.min(md, dn - band(0.0012) + 4 * Math.max(0, 0.13 - h[2]));
640
- }
641
- // the philtrum: a dark groove splitting the bottom of the pad (face1, threequarter2); only where the
642
- // face mesh can draw a line this thin (hero, high)
643
- // (from between the nostrils down the short rostral lip to the lip margin, the centre of the "w": face1,
644
- // face2; under a 3 cm deep lip, a groove this long drew a black stroke down the lip)
645
- // (a V cleft in the leather, narrow between the nostrils and widest at the pad's lower edge, running on as a soft
646
- // dark groove in the hair of the lip that fades toward the mouth. A straight-sided strip of mark ended in a flat
647
- // edge under the pad: a square tab of leather; the face cells cannot draw a mark narrower than about one cell,
648
- // so on the lip the groove is a colour and the hair over it is short)
649
- const yE = GROOVE[1] - 0.0019, dyE = h[1] - yE; // the leather's lower edge on the midline
650
- if (Qt.res <= 1.3 && h[2] > 0.125 && dyE > 0) md = Math.min(md, ax - 0.0009 * smoothstep(0.0035, 0.0, dyE) - 0.4 * headCell * smoothstep(0.0035, 0.0015, dyE));
651
- const groove = h[2] > 0.125 && dyE <= 0 ? Math.exp(-((ax / 0.0016) ** 2)) * smoothstep(-0.008, -0.0015, dyE) : 0;
652
- if (groove > 0.01 && mat === MAT.FUR) { col = mix3(col, [0.035, 0.03, 0.027], 0.85 * groove); fl = Math.min(fl, mix(fl, 0.0025, groove)); }
653
- // lips: head vertices near the jaw surface, jaw vertices near the head surface (black lip line);
654
- // the inner lip margin is black too, the mouth behind it dark red
655
- if (partOf[v] !== 2) {
656
- const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
657
- // (palate and upper gums: dark pigmented skin; the shader's MOUTH albedo is a fixed pink)
658
- if (dj < -0.0005) { mat = MAT.SKIN; col = srgb(0x3c1c1e); fl = 0; }
659
- // the roof and the back of the mouth between the mandibles, above the jaw's floor (enclosed at rest):
660
- // mucosa, not throat fur (it showed as a pale blob at the back of the open mouth)
661
- else if (n[1] < -0.35 && ax < 0.017 && h[1] < -0.012 && h[2] > -0.05 && h[2] < 0.075 && dj < 0.02 && SDFModel.evalList(jawPrims, p[0], p[1] - 0.005, p[2]) < dj) { mat = MAT.SKIN; col = srgb(0x3c1c1e); fl = 0; }
662
- else if (dj < 0.0045 && h[2] > -0.02) {
663
- // (beside the chin at the front of the mouth, where the lip line runs down its sides, the flews' hair stays
664
- // long enough to fall over the crossing: face1, face2 show the "w" on top of the chin and plain fur beside it)
665
- const side = smoothstep(0.098, 0.108, h[2]) * smoothstep(0.006, 0.009, ax) * smoothstep(-0.03, -0.033, h[1]);
666
- fl = Math.min(fl, mix(0.002, 0.0055, side));
667
- }
668
- // (narrower toward the front of the mouth, where the rostral lip overhangs the lower lip and a wide band
669
- // covered the whole underside of the upper lip: from the front and below it read as an open mouth)
670
- // the black margin of the upper lip: a band of set width along the lip line, measured over the skin (as a
671
- // 3D distance to the jaw it filled every stretch where the lip runs close over the lower lip: from the front
672
- // a dark ring round the chin, at medium a thick zigzag), 3 mm toward the mouth corner, 1.3 mm at the front
673
- // (along the sides, where head and jaw graze, the 3D distance draws the strong line of lying1 and
674
- // threequarter2 that a band along the crossing alone left thin and broken; the two blend over 2 cm)
675
- const lipOld = dj >= 0 ? dj - band(mix(0.0032, 0.0018, smoothstep(0.09, 0.12, h[2]))) : -dj - band(0.0015);
676
- md = Math.min(md, mix(lipOld, lipBand(v, Math.max(0.0016 + 0.0016 * smoothstep(0.016, 0.004, ax) * smoothstep(0.112, 0.12, h[2]), 0.3 * headCell)), LIP_T(h[2])) + 4 * Math.max(0, -0.02 - h[2]));
677
- // (the margin is bare skin: the hair stops short of it, or the lip's hair hung over a band this thin)
678
- // (and the hair of the lip shortens toward the margin over ~1 cm: 7 mm hair hung over the line from the front)
679
- if (lipZone[v] && lipSd[v] >= 0 && LIP.geo[v] < 0.02) fl = Math.min(fl, mix(fl, 0.0006 + 0.5 * Math.max(0, LIP.geo[v] - 0.0012), LIP_T(h[2])));
680
- // (the short rostral lip between the leather and the mouth line keeps short hair, 4 mm: face1, face2 show the
681
- // mouth line plainly under the nose; 7 mm hair from under the leather hung down over it)
682
- fl = Math.min(fl, mix(fl, 0.004, smoothstep(0.026, 0.01, ax) * smoothstep(0.108, 0.122, h[2])));
683
- } else {
684
- const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
685
- // The part of the lower jaw hidden inside the upper lips at rest shows when the mouth opens. Only
686
- // its inside is mouth (rest normal up or toward the tongue): the pink tongue down the middle, dark
687
- // pigmented gums along the tooth rows. Its outer side stays the furred chin, with the black lower
688
- // lip along the rim (painting the whole hidden band as mouth drew the open jaw as a thin glossy
689
- // maroon rod, and its colour surfaced as red specks in the closed lip line at medium).
690
- // (the floor of the mouth between the mandibles at the back lies under the head's underside, not inside
691
- // it, but is enclosed at rest: it showed as a pale fur blob at the back of the open mouth)
692
- const floor = n[1] > 0.45 && ax < 0.02 && h[2] < 0.075;
693
- // the sides of the chin at the front of the mouth, below the mouth line: the lower lip's margin lies under the
694
- // flews there, and the chin's fur runs up to the crossing (a dark margin and bare skin along it outlined the
695
- // chin as a pale lobe from the front and below)
696
- const chinSide = smoothstep(0.1, 0.11, h[2]) * smoothstep(0.005, 0.008, ax) * smoothstep(-0.031, -0.034, h[1]);
697
- if (dh < 0.0 || floor) {
698
- const inward = n[0] * Math.sign(p[0] || 1);
699
- const inside = n[1] > 0.45 || inward < -0.3 || (ax < 0.006 && n[1] > 0.1);
700
- if (inside) { const tongue = ax < 0.0085 && h[2] < 0.108; mat = tongue ? MAT.MOUTH : MAT.SKIN; col = srgb(0x3a1b1d); fl = 0; }
701
- else if (n[1] > 0.15) { mat = MAT.DARK_SKIN; fl = 0; }
702
- else fl = Math.min(fl, 0.003 + 0.5 * Math.max(0, -dh - 0.003));
703
- } else if (dh < 0.0035) fl = Math.min(fl, mix(0.002, 0.004, chinSide));
704
- // (a wider band toward the mouth corner, where head and jaw graze and a narrow one broke into dashes)
705
- // (toward the front, where the lower lip lies under the rostral upper lip, the band narrows: a full band there
706
- // drew the line as a thick black "U" round the chin, at medium a cartoon mouth; the pigmented lower lip beside
707
- // the crease is a soft dark tint instead, continuous on the 5 mm cells of medium)
708
- // (the lower lip's margin along the same curve, narrower toward the front where it lies under the upper lip)
709
- const jf = smoothstep(0.1, 0.125, h[2]);
710
- const lipOld = dh >= 0 ? dh - band(h[2] < 0.06 ? 0.0035 : mix(0.0026, 0.0016, smoothstep(0.09, 0.12, h[2]))) : -dh - band(0.0015);
711
- md = Math.min(md, mix(lipOld, lipBand(v, Math.max(0.0008, 0.25 * headCell)), LIP_T(h[2])) + 0.004 * chinSide);
712
- if (dh >= 0 && mat === MAT.FUR && lipZone[v]) col = mix3(col, [0.06, 0.052, 0.046], 0.7 * jf * smoothstep(0.003, 0.001, LIP.geo[v]) * smoothstep(0.012, 0.006, ax));
713
- }
714
- // (far from every mark the field climbs quickly to 1, the value of unmarked skin)
715
- mark = Math.min(mark, md < 0.03 ? md : Math.min(1, 0.03 + (md - 0.03) * 20));
716
- // teeth
717
- for (const c of caninePrims) if (SDFModel.dist(c, p[0], p[1], p[2]) < 0.0012) { mat = MAT.KERATIN; col = srgb(0xe8e0cc); fl = 0; mark = Math.max(mark, 0.004); }
718
- under = 0.3;
719
- }
720
- // pups: fluffy and uniform
721
- if (juv > 0 && mat === MAT.FUR) {
722
- fl = mix(fl, Math.max(fl * 0.8, r === 2 || r === 6 ? 0.012 : 0.03), juv);
723
- ag *= 1 - 0.8 * juv;
724
- }
725
- // low-frequency colour variation and fine grizzle
726
- const cvh = fbm3(p[0] * 4 + 5 + nOff, p[1] * 4, p[2] * 4, 2) - 0.5;
727
- if (mat === MAT.FUR) col = [col[0] * (1 + 0.16 * nz + 0.1 * cvh + 0.08 * nz2), col[1] * (1 + 0.14 * nz + 0.08 * nz2), col[2] * (1 + 0.1 * nz - 0.08 * cvh + 0.08 * nz2)];
728
- markSDF[v] = mark;
729
- furLen[v] = fl;
730
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
731
- surf[v * 4 + 2] = mat === MAT.FUR ? clamp(ag * (0.85 + 0.3 * nz2), 0, 1) : 0;
732
- surf[v * 4 + 3] = mat === MAT.FUR ? under : 0;
733
- if (mat === MAT.KERATIN) surf[v * 4] = 0.7;
734
- if (mat === MAT.NOSE) surf[v * 4] = 0.55;
735
- }
736
-
737
- // The collar: the short face hair met the 50-64 mm ruff at the head / neck region line, a hard hood
738
- // edge behind the cheeks and a lit rim under the jaw; the ruff met the 30-42 mm
739
- // body coat at the neck / torso line the same way. Fur length and hair flow are blended across both
740
- // lines over ~+-3 cm of skin, so the ruff frames the face without standing off it.
741
- blendCollar(furLen, comb, region, tint, pos, nrm, weights.neighbors, nV, headCell);
742
- // smooth fur length so the shells do not step (bare skin stays locked)
743
- smoothField(furLen, weights.neighbors, 3, (v) => tint[v * 4 + 3] > 0);
744
-
745
- void X; void R; void add;
746
- const pattern = new Float32Array(nV).fill(1);
747
- const patternColor = new Float32Array(nV * 4);
748
- return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, region, ventral, tailT };
749
- }