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