@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,726 +0,0 @@
1
- // The brown bear's coat: long shaggy guard hair over a dense underfur, painted per vertex in
2
- // reference space.
3
- //
4
- // Colour fields: the back, hump and head are the
5
- // palest parts (sun-bleached guard-hair tips), the legs darkest (near black on the lower legs and
6
- // feet), the belly dark; the face is a little paler and greyer than the crown, the muzzle paler
7
- // again, the ears dark with darker rims, the nose pad, lips and soles black, the claws horn coloured
8
- // (dark in Eurasian bears, pale cream in grizzlies). Grizzled coats use the shader's agouti band
9
- // (surf.z): dark roots and a dark band below a pale (silver-cream) tip. Coats (params.coat): 'dark'
10
- // (Eurasian chocolate), 'red' (reddish brown), 'medium' (coastal brown), 'blonde' (straw, dark
11
- // legs), 'grizzled' (silver-tipped grizzly) and 'black' (American black bear: black with a tan
12
- // muzzle). Cubs are fluffy and uniform, Eurasian cubs often with a pale neck collar.
13
- // Fur length: longest on the hump, nape and neck ruff (winter guard hair 9-12 cm), long on the
14
- // flanks, belly fringe, forearm "sleeves" and thigh "trousers", short on the face, lower legs and
15
- // feet (research 6.1).
16
- import { HEAD_O, MUZZLE_Z0 } from './rig.js';
17
- import { EYE_BASE } from './sculpt.js';
18
- import { neckS, eyePatchesAt } from './regions.js';
19
- import { SDFModel } from '../../core/sdf/sdf.js';
20
- import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, fbm3, vnoise3 } from '../../core/math/vec.js';
21
- import { srgb, mix3, MAT, smoothField } from '../../core/build/coatKit.js';
22
-
23
- // sRGB swatches (neutral-light albedo). `tip`: hump / back guard-hair tips, `ag`: agouti
24
- // amounts (dark band below the tip) per region.
25
- const COATS = {
26
- dark: {
27
- back: 0x5c4029, flank: 0x4c3422, tip: 0x76573a, head: 0x634833, face: 0x6e5540, muzzle: 0x86684c,
28
- leg: 0x35251a, belly: 0x33241a, ear: 0x3a291d, earRim: 0x1f1610, earInner: 0x2a1e16, claw: 0x3a3530,
29
- ag: { back: 0.12, head: 0.08, leg: 0.1 },
30
- },
31
- // (side3: a deep red-brown with paler tips on the back, the shoulders darker; less orange than before)
32
- red: {
33
- back: 0x7a4c30, flank: 0x5e3924, tip: 0x9a6a48, head: 0x7a5236, face: 0x846044, muzzle: 0x9a7658,
34
- leg: 0x38241a, belly: 0x3a2518, ear: 0x4a2e1c, earRim: 0x241810, earInner: 0x2e1f16, claw: 0x3c3530,
35
- ag: { back: 0.12, head: 0.06, leg: 0.1 },
36
- },
37
- medium: {
38
- back: 0x80593a, flank: 0x6e4b30, tip: 0xa57b4c, head: 0x8a653e, face: 0x92704e, muzzle: 0x9e7c58,
39
- leg: 0x45301f, belly: 0x352315, ear: 0x4e3622, earRim: 0x251a12, earInner: 0x2e2118, claw: 0x8e8272,
40
- ag: { back: 0.1, head: 0.06, leg: 0.1 },
41
- },
42
- // (front3, tq3: golden-blonde tips on the hump and shoulders, a darker face, near-black legs and chest)
43
- // (front3's medians, forehead #735644, cheeks #755c34, hump #89553d with its locks ranging #5b2d00 to
44
- // #c69665 and a pale muzzle #c8a68b: the old head / face 0x8e6e50 / 0x967656 with little agouti rendered as an
45
- // even pale cream, a plush toy (the default showcase bear, grizzly s1, is blonde))
46
- blonde: {
47
- back: 0x96683e, flank: 0x74522f, tip: 0xc8965c, head: 0x7c5a3c, face: 0x846240, muzzle: 0xc0a486,
48
- leg: 0x3a2c22, belly: 0x3e2e22, ear: 0x5e4632, earRim: 0x2e2218, earInner: 0x3a2c20, claw: 0xb8ab94,
49
- ag: { back: 0.3, head: 0.32, leg: 0.1 },
50
- },
51
- // (tq4, tq1, front1: a dark chocolate-grey coat, silver-tipped on the back and shoulders, a dark face and
52
- // legs; the pale grey-beige of before read as a sheep's fleece)
53
- grizzled: {
54
- back: 0x7c7064, flank: 0x5a4e46, tip: 0x9e9282, head: 0x6e5c4e, face: 0x7a6858, muzzle: 0x8a7866,
55
- leg: 0x302826, belly: 0x383028, ear: 0x4a3e34, earRim: 0x241e1a, earInner: 0x2a2420, claw: 0xcfc2a8,
56
- ag: { back: 0.6, head: 0.4, leg: 0.12 },
57
- },
58
- black: {
59
- back: 0x1b1816, flank: 0x171412, tip: 0x24201d, head: 0x1c1917, face: 0x2a2420, muzzle: 0x8a6a4a,
60
- leg: 0x121010, belly: 0x151311, ear: 0x1a1715, earRim: 0x0f0d0c, earInner: 0x1c1816, claw: 0x2a2622,
61
- ag: { back: 0.0, head: 0.0, leg: 0 },
62
- },
63
- };
64
- // desaturation of the head, face and muzzle swatches toward their luminance, per coat (palette())
65
- // (0.7 for the brown coats drew a grey hood on the Eurasian bear's rust body)
66
- const FACE_DESAT = { dark: 0.55, red: 0.55, medium: 0.55, blonde: 0.45, grizzled: 0.65, black: 0.35 };
67
- const TOUSLE = 0.8;
68
- const CUB = 0x6a4c34; // cubs: a uniform soft brown natal coat
69
- const COLLAR = 0xe8e0cc; // the pale neck collar of many Eurasian cubs
70
-
71
- // Value noise stretched along a flow direction `t` (unit, reference space): `k` cycles per metre across the flow, `s` times
72
- // longer along it, so the pattern runs in streaks with the hair (locks). The along-flow coordinate is measured from
73
- // anchors snapped to 4 cm cells and two lattices offset by half a cell are cross-faded, so curving flow does not shear
74
- // it (as the coat shader's own streak() for the base without shells).
75
- function streak(p, t, k, s, o) {
76
- const fr = (x) => x - Math.floor(x);
77
- const edge = (a, b, c) => Math.min(a, 1 - a, b, 1 - b, c, 1 - c);
78
- const g = [p[0] * 25, p[1] * 25, p[2] * 25];
79
- const ea = edge(fr(g[0]), fr(g[1]), fr(g[2])), eb = edge(fr(g[0] + 0.5), fr(g[1] + 0.5), fr(g[2] + 0.5));
80
- const st = k * (1 - 1 / s);
81
- const at = (a, oo) => {
82
- const d = ((p[0] - a[0]) * t[0] + (p[1] - a[1]) * t[1] + (p[2] - a[2]) * t[2]) * st;
83
- return vnoise3(p[0] * k - t[0] * d + oo, p[1] * k - t[1] * d, p[2] * k - t[2] * d);
84
- };
85
- const nA = at(g.map((x) => Math.floor(x) * 0.04), o), nB = at(g.map((x) => (Math.floor(x + 0.5) - 0.5) * 0.04), o + 17.3);
86
- const w = (ea * ea) / (ea * ea + eb * eb + 1e-6);
87
- return (nB + (nA - nB) * w - 0.5) / Math.sqrt(w * w + (1 - w) * (1 - w)) + 0.5;
88
- }
89
-
90
- const desat = (c, k) => { const y = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; return c.map((x) => x + (y - x) * k); };
91
-
92
- // claw horn (claws.js): the palette's claw colour at the base, paler worn horn toward the tip (side1, tq2, detail_bipedal)
93
- export function clawColour(params) {
94
- const c = palette(params).claw;
95
- // dark horn at the base (a near-black grey-brown, darker than the swatch for the pale grizzly claws), worn paler
96
- // toward the tip. (Mixed toward 0x6a5e52 / 0xd2c4a8 the bare horn, lit by the sun without the fur's self-shadowing,
97
- // rendered pale ivory along its whole length, ~(148, 139, 126) median, where photos show dark horn with
98
- // lighter tips. The dark base still reads against the ground the claws lie on.)
99
- return { base: mix3(c, srgb(0x2a2420), 0.6), tip: mix3(c, srgb(0xcdbfa6), 0.7) };
100
- }
101
-
102
- function palette(p) {
103
- const base = COATS[p.coat] || COATS.dark;
104
- const k = p.coatWarmth || 0, l = p.coatLightness || 0, juv = p.juv || 0;
105
- const cub = srgb(CUB).map((c) => c * (p.coat === 'black' ? 0.2 : p.coat === 'blonde' ? 1.5 : p.coat === 'grizzled' ? 1.2 : 1));
106
- const out = { ag: base.ag };
107
- for (const [key, hex] of Object.entries(base)) {
108
- if (key === 'ag') continue;
109
- let c = srgb(hex);
110
- if (key !== 'claw') c = [c[0] * (1 + 0.1 * k + l), c[1] * (1 + 0.02 * k + l), c[2] * (1 - 0.12 * k + l)];
111
- // (photos of brown bears in neutral light read greyer than the swatches: desaturate a fifth)
112
- // (the head more, per coat (FACE_DESAT): face1 / face2 / tq1 medians, forehead (156, 138, 116) / (198, 164, 149) /
113
- // (177, 156, 133), R/B 1.33-1.34, saturation 0.25; the render's red / dark faces at a third were an even orange-brown,
114
- // R/B 1.8-1.9. The blonde's head stays golden like its hump (front3: forehead R/B 2.1, saturation 0.5):
115
- // greyed, it was a grey mask framed by the golden ruff)
116
- if (key !== 'claw') { const y = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; c = c.map((x) => x + (y - x) * (/^(head|face|muzzle)$/.test(key) && !(p.coat === 'black' && key === 'muzzle') ? FACE_DESAT[p.coat] ?? 0.6 : 0.22)); }
117
- // cubs: the pattern is mostly washed into the uniform natal coat (legs stay darker)
118
- const keep = /leg|earRim|claw/.test(key) ? 0.55 : /muzzle/.test(key) ? 0.4 : 0.15;
119
- if (juv > 0 && key !== 'claw') c = mix3(c, cub, juv * (1 - keep));
120
- out[key] = c;
121
- }
122
- return out;
123
- }
124
-
125
- export function bearCoat(ctx) {
126
- const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params } = ctx;
127
- // the head's mesh cell (regions.js): the mouth interior is painted only deeper than a cell or so inside
128
- // the other part, so the facets of the lip line never show it with the mouth shut
129
- const cellH = 0.0036 * (ctx.Q?.res || 1), mouthIn = Math.max(0.004, 1.3 * cellH);
130
- const { BONES, AXIAL } = rig;
131
- const AX_LEN = weights.axialLengths;
132
- const C = palette(params);
133
- const AG = C.ag;
134
- const juv = params.juv || 0;
135
- const hw = params.headW || 1, mz = params.muzzle || 1;
136
- const shag = params.shag ?? 1; // coat length (summer 0.7 .. winter 1.2)
137
- const bleach = params.bleach ?? 0; // sun-bleached back and head (0..1)
138
- const collar = params.collar ?? 0; // cub neck collar (0..1)
139
- const nOff = (params.coatSeed || 0) % 997;
140
- const { dominant, axialS, skinIndex, skinWeight, limbMember, limbWhich } = weights;
141
- const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
142
- const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
143
- const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8];
144
- const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
145
- const nosePrims = model.prims.filter((p) => p.tag === 'nose');
146
- const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
147
- const caninePrims = model.prims.filter((p) => p.tag === 'canine');
148
- const carpalPrims = model.prims.filter((p) => p.tag === 'carpalpad');
149
- const jawPrims = model.forPart('jaw').filter((p) => p.tag !== 'canine');
150
- const bodyPrims = model.forPart('body');
151
- const headPrims = bodyPrims.filter((p) => p.bone === 'head' && !p.carve && p.tag !== 'canine');
152
- const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|femur|tibia|metatarsus|hpaw)/.test(b.name));
153
- const isPaw = BONES.map((b) => /^(fpaw|hpaw|metacarpus|metatarsus)/.test(b.name));
154
- const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw)/.test(b.name));
155
- // head-local coordinates of a reference-space point (undo head width and muzzle stretch)
156
- const headLocal = (p) => {
157
- const x = (p[0] - HEAD_O[0]) / hw, y = p[1] - HEAD_O[1];
158
- let z = p[2] - HEAD_O[2];
159
- if (z > MUZZLE_Z0) z = MUZZLE_Z0 + (z - MUZZLE_Z0) / mz;
160
- return [x, y, z];
161
- };
162
-
163
- // --- regions per vertex (0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw) and limb blend
164
- const region = new Uint8Array(nV);
165
- const legness = new Float32Array(nV);
166
- const legBone = new Int16Array(nV).fill(-1);
167
- for (let v = 0; v < nV; v++) {
168
- const bn = BONES[dominant[v]].name;
169
- const p = P(v);
170
- let lb = -1, lw = 0;
171
- for (let k = 0; k < 4; k++) {
172
- const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
173
- if (isLimb[b] && w > lw) { lw = w; lb = b; }
174
- }
175
- legBone[v] = lb;
176
- legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
177
- if (partOf[v] === 2) region[v] = 6;
178
- else if (bn.startsWith('ear')) region[v] = 5;
179
- else if (axialS[v] > sTailBase - 0.01 && bn.startsWith('tail')) region[v] = 4;
180
- else if (axialS[v] < sOcc + 0.01 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.03)) region[v] = 2;
181
- else if (axialS[v] < sNeckBase) region[v] = 1;
182
- else region[v] = 0;
183
- if (region[v] >= 2) legness[v] = 0;
184
- }
185
- void limbWhich;
186
-
187
- // --- comb (hair flow), bind space. Every field blends continuously (a hard switch in the flow
188
- // direction parts the shells along a line: it drew dark rings round the face and the neck)
189
- const segDir = (k) => {
190
- const K = Math.max(0, Math.min(k, AXIAL.points.length - 2));
191
- return norm(sub(AXIAL.points[K + 1], AXIAL.points[K]));
192
- };
193
- const comb = new Float32Array(nV * 3);
194
- const earBone = BONES.map((b) => /^ear/.test(b.name));
195
- // the pinna's frame: its centre, the cup's facing and up (base -> tip)
196
- const earFrame = (s) => {
197
- const b = BONES.find((bb) => bb.name === (s > 0 ? 'earL' : 'earR'));
198
- const up = norm(sub(b.tail, b.head));
199
- return { b, up, facing: norm([0.6 * s, 0.05, 1]), c: add(b.head, mul(sub(b.tail, b.head), 0.4)) };
200
- };
201
- const EARF = { 1: earFrame(1), [-1]: earFrame(-1) };
202
- const earW = (v) => { let ew = 0; for (let k = 0; k < 4; k++) if (earBone[skinIndex[v * 4 + k]]) ew += skinWeight[v * 4 + k]; return ew; };
203
- // ear hair lies out from the middle of the pinna: the rim fringe sticks out past the edge (a ragged outline, not a
204
- // clean circle), the cup's hair grows out of the cup, the back's up and out
205
- // (the cup's hair grows up across the hollow from its lower margin (face1: the cup filled with paler hair; lying in
206
- // from the whole margin it swirled into a vortex), the fringe out past the rim, the back's up and out: the flow
207
- // parts along the rim, as on a real ear)
208
- const earComb = (p, n) => {
209
- const E = EARF[p[0] > 0 ? 1 : -1];
210
- let rv = sub(p, E.c);
211
- rv = sub(rv, mul(E.facing, dot(rv, E.facing)));
212
- const radial = len(rv) > 1e-5 ? norm(rv) : E.up;
213
- const front = dot(n, E.facing);
214
- const wf = smoothstep(0.15, 0.55, front), wb = smoothstep(-0.1, -0.5, front);
215
- const out = norm(add(radial, mul(E.up, 0.5 * wb)));
216
- return norm(add(mul(out, 1 - wf), mul(norm(add(E.up, mul(radial, 0.25))), wf)));
217
- };
218
- for (let v = 0; v < nV; v++) {
219
- const p = P(v), n = N(v), r = region[v];
220
- let d;
221
- {
222
- // body flow: along the axial chain (blended across the joints), hanging down the sides of the
223
- // body and, on the neck, the ruff hanging down and back
224
- const seg = weights.segT[v * 2] | 0, tt = weights.segT[v * 2 + 1];
225
- let db = add(add(segDir(seg), mul(segDir(seg - 1), 1 - smoothstep(0, 0.35, tt))), mul(segDir(seg + 1), smoothstep(0.65, 1, tt)));
226
- db = norm(db);
227
- const nk = 1 - smoothstep(sNeckBase - 0.06, sNeckBase + 0.12, axialS[v]);
228
- // (the chest front and the rump face along the body axis: the axial flow projects to nothing there
229
- // and the hair parted in a line across the brisket; it hangs down instead)
230
- const endOn = smoothstep(0.3, 0.8, Math.abs(n[2]));
231
- db = norm(add(db, [0, -(0.6 * Math.abs(n[0]) + 0.25 * smoothstep(0.1, -0.6, n[1]) + 1.5 * endOn) * (1 - nk) - 0.7 * nk, -0.1 * nk]));
232
- // head flow: out from the nose over the face and crown; the cheek ruff sweeps back, out and down
233
- const h = headLocal(p);
234
- let dh = norm(add(norm(sub(h, [0, -0.01, 0.22])), [0, -0.15, -0.4]));
235
- const cheekW = smoothstep(0.035, -0.03, h[2]) * smoothstep(0.035, 0.09, Math.abs(h[0]));
236
- dh = norm(add(dh, mul([Math.sign(h[0] || 1) * 0.5, -0.7, -0.6], cheekW)));
237
- // round the eye the hair lies away from it, radially (as the lashes and the hair of the lids do)
238
- {
239
- const ex = Math.sign(h[0] || 1) * EYE_BASE.c[0];
240
- const rv = [h[0] - ex, h[1] - EYE_BASE.c[1], h[2] - EYE_BASE.c[2]];
241
- const dE = len(rv);
242
- // (close to the lids only, and half way: a full radial flow out to 3 cm parted the hair in a crinkled line in
243
- // front of the eye, where it met the face's flow back from the nose)
244
- const wE = 0.6 * (1 - smoothstep(0.013, 0.024, dE));
245
- if (wE > 0 && dE > 1e-4) dh = norm(add(mul(dh, 1 - wE), mul(norm([rv[0] * hw, rv[1], rv[2]]), wE)));
246
- }
247
- const toBody = r === 6 ? 0 : smoothstep(sOcc - 0.05, sOcc + 0.1, axialS[v]);
248
- d = norm(add(mul(dh, 1 - toBody), mul(db, toBody)));
249
- const L = legness[v];
250
- if (L > 0 && legBone[v] >= 0) {
251
- const lbn = BONES[legBone[v]];
252
- let dl = norm(sub(lbn.tail, lbn.head));
253
- if (/^(fpaw|hpaw|metacarpus|metatarsus)/.test(lbn.name)) dl = norm(add(dl, [0, -0.3, 0.6]));
254
- else dl = norm(add(dl, [0, -0.3, -0.25]));
255
- d = norm(add(mul(d, 1 - L), mul(dl, L)));
256
- }
257
- if (r === 5 || r === 2) {
258
- const ew = earW(v);
259
- if (ew > 0.02) d = norm(add(mul(d, 1 - smoothstep(0.15, 0.7, ew)), mul(earComb(p, n), smoothstep(0.15, 0.7, ew))));
260
- }
261
- }
262
- // tousled: the long coat's locks lean a little this way and that (+-~20 deg, ~3 cm locks), so its tufts do not stand
263
- // in regular rows; at medium the evenly combed neck, shoulders and cheek ruff drew rows of round scale-like tufts,
264
- // a pine cone (the face, ears and feet keep their flow)
265
- {
266
- const hh = headLocal(p);
267
- const tw = TOUSLE * (r === 6 || r === 5 ? 0 : r === 2 ? smoothstep(0.035, -0.03, hh[2]) * smoothstep(0.03, 0.09, Math.abs(hh[0])) : smoothstep(0.08, 0.18, p[1]));
268
- if (tw > 0) {
269
- const q = [p[0] * 32 + nOff, p[1] * 32, p[2] * 32];
270
- d = norm(add(d, mul([vnoise3(q[0], q[1], q[2]) - 0.5, vnoise3(q[0] + 41.7, q[1], q[2]) - 0.5, vnoise3(q[0], q[1] + 23.1, q[2]) - 0.5], 2 * tw)));
271
- }
272
- }
273
- let t = sub(d, mul(n, dot(d, n)));
274
- if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
275
- t = norm(t);
276
- comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
277
- }
278
-
279
- // --- per-vertex paint
280
- const furLen = new Float32Array(nV);
281
- const tint = new Float32Array(nV * 4);
282
- // marks are signed distances capped at MARK_FAR everywhere (the shader antialiases with fwidth: a value
283
- // that jumped from a few mm to 1 at the edge of a band drew a faint line or dashes there)
284
- const MARK_FAR = 0.02;
285
- const markSDF = new Float32Array(nV).fill(MARK_FAR);
286
- const surf = new Float32Array(nV * 4);
287
- // per-vertex fur-slope limit (furSlope.js: 1 = the default, Infinity = as painted) and hairless fur-id skin
288
- const slopeV = new Float32Array(nV).fill(1);
289
- const bareFur = new Uint8Array(nV);
290
- 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));
291
-
292
- // Head, neck and ear paints are computed separately and blended across their junctions by position
293
- // (the head and body meshes overlap across the neck cut and cross-fade: a paint that switched by mesh
294
- // or by a region test drew a hard edge, a dark collar round the neck and a notched patch at the ear
295
- // bases whenever the nape stretched)
296
- const bodyPaint = (v, p, n, rb, nz, nz2) => {
297
- let col, fl, ag, mat = MAT.FUR, mark = MARK_FAR;
298
- const up = n[1];
299
- // dorsal: paler guard-hair tips on the back, hump and nape; darker flanks; dark belly
300
- const dors = smoothstep(-0.15, 0.7, up + 0.3 * nz);
301
- col = mix3(C.flank, C.back, dors);
302
- const humpW = smoothstep(0.02, 0.3, p[2]) * smoothstep(0.62, 0.5, p[2]) * smoothstep(0.2, 0.8, up) + (rb === 1 ? 0.7 * smoothstep(0.0, 0.7, up) : 0);
303
- // (the pale tips vary lock to lock, +-30 %, so the guard-hair locks of the hump, nape and back separate, front3 /
304
- // tq4; an even tip colour read as plush)
305
- const tipK = clamp(humpW * 0.75 + 0.35 * bleach * dors, 0, 1);
306
- col = mix3(col, C.tip, clamp(tipK * (1 + 0.6 * nz2) + 0.15 * nz2 * dors, 0, 1));
307
- // (a dark band under the pale tips of the hump and nape guard hair, so its locks separate, tq4 / front3)
308
- ag = mix(AG.back * 0.6, AG.back, dors) + 0.15 * clamp(humpW, 0, 1);
309
- const vent = smoothstep(-0.1, -0.7, up) * smoothstep(0.62, 0.45, p[1]);
310
- col = mix3(col, C.belly, vent * 0.85);
311
- ag *= 1 - 0.8 * vent;
312
- // hair length: longest on the hump and nape, long flanks, a hanging belly fringe
313
- // (the belly fringe hangs 12 cm: side1 / side2 / side3 show the belly line at 0.23-0.30 of the hump
314
- // height; at 9.5 cm the render's was 0.34-0.36)
315
- fl = mix(0.06, 0.075, smoothstep(-0.2, 0.6, up));
316
- // (8.5 cm on the hump: at 9.5 cm over the hump's sculpted swell its outline stood up as a mound)
317
- fl = Math.max(fl, 0.085 * clamp(humpW, 0, 1));
318
- // (on the brisket and the chest front between the forelegs, skin facing forward, 6.5 cm: seen from the front, a
319
- // 12 cm fringe there showed the bright ground through its shells as pale horizontal slices; the
320
- // flanks keep the 12 cm that makes the side silhouette's belly line)
321
- // (and on the chest's underside between the forelegs, whose fringe hangs into the gap between them)
322
- const chestFront = Math.max(smoothstep(0.15, 0.5, n[2]) * smoothstep(0.08, 0.22, p[2]), smoothstep(0.04, 0.14, p[2]) * smoothstep(0.14, 0.07, Math.abs(p[0])));
323
- // (and along the midline of the belly's front half, 8 cm: seen from the front the gap between the forelegs looks along
324
- // the belly, and its 12 cm fringe hung there edge-on as pale-gapped slices over the floor; the side
325
- // silhouette's belly line is the flanks' fringe)
326
- const midFront = smoothstep(0.12, 0.05, Math.abs(p[0])) * smoothstep(-0.2, 0.05, p[2]);
327
- fl = Math.max(fl, 0.12 * vent * (1 - 0.46 * chestFront) * (1 - 0.35 * midFront));
328
- if (rb === 1) {
329
- // neck ruff: long, hanging; darker toward the throat. It starts at the back of the head as long
330
- // as the crown hair there (4.5 cm) and grows to the ruff (a short ring between them read as a collar)
331
- // (below the ears and behind the jaw angles 4 cm too, where the cheek ruff ends: in a look turn the neck's side
332
- // behind the far jaw corner is seen edge-on, and longer hair there stood off as torn dark sheets)
333
- const sideN = 1 - smoothstep(0.2, 0.6, up);
334
- // (5.2-5.6 cm at the head, where the crown's 4.5 cm and the cheek ruff's tapered 5-6 cm end: at 2-4 cm the neck behind
335
- // the head was a trench of short hair between the cheek ruff and the long neck hair, a dark ring round the neck, a
336
- // collar, in the live page; torn dark sheets there are the shell shadows,
337
- // which look up at the hair's root, render.shellShadowRoot)
338
- fl = Math.max(fl, 0.08) * mix(mix(0.7, 0.65, sideN), 1, smoothstep(sOcc + 0.02, sOcc + 0.26, axialS[v]));
339
- col = mix3(col, C.flank, smoothstep(0.0, -0.6, up) * 0.6);
340
- // (little agouti on the neck's first 20 cm behind the head: the dark band under the tips is a dark shell layer at 3/4 of
341
- // the hair's height, and where the neck's long hair is seen edge-on behind the jaw in a look turn that layer showed
342
- // as torn dark sheets)
343
- ag *= mix(0.3, 1, smoothstep(sOcc + 0.08, sOcc + 0.3, axialS[v]));
344
- // (greyed toward the head like the cheek ruff over the neck's first 25 cm, so the face's grey runs into the neck;
345
- // over 15 cm the grey face ended in a ring, a hood)
346
- col = desat(col, 0.5 * (FACE_DESAT[params.coat] ?? 0.6) * (1 - smoothstep(sOcc, sOcc + 0.25, axialS[v])));
347
- }
348
- if (rb === 4) { fl = 0.05; col = mix3(C.flank, C.back, 0.5); }
349
- // (lock to lock: +-30 % in length, so the long coat breaks into locks instead of an even pile; +-15 % on the neck's
350
- // first 15 cm behind the head, whose long locks stood off as sheets behind the jaw corners in look turns)
351
- // (and by lock along the flow, ~2.5 cm across and ~8 cm long, +-45 %: at medium (16 shells) the evenly long neck and
352
- // shoulder hair drew rows of round scale-like tufts; locks of unequal length break the rows)
353
- const lkB = streak(p, [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]], 40, 3.2, 57 + nOff) - 0.5;
354
- fl *= 1 + (0.3 * nz2 + 0.9 * lkB * smoothstep(0.03, 0.06, fl)) * (rb === 1 ? mix(0.5, 1, smoothstep(sOcc + 0.05, sOcc + 0.2, axialS[v])) : 1);
355
- // (between the forelegs, under the chest, 4.5 cm and without the lock length noise: seen from the front the 6-9 cm
356
- // fringe hung into the gap between the forelegs, and the floor and its strands' sheen showed through its shells as a
357
- // pale banded patch below the chin; tq2 / front2 / front3 show a dark, dense chest there)
358
- const gapW = smoothstep(0.13, 0.07, Math.abs(p[0])) * smoothstep(0.06, 0.16, p[2]) * smoothstep(0.1, -0.4, n[1]) * smoothstep(0.62, 0.5, p[1]);
359
- if (gapW > 0) fl = mix(fl, Math.min(fl, 0.045), gapW);
360
- const L = legness[v];
361
- if (L > 0) {
362
- const lb = legBone[v];
363
- const front = lb >= 0 && isFront[lb];
364
- // legs darken toward the feet (near black below the elbow / stifle)
365
- const dark = smoothstep(0.62, 0.3, p[1] + 0.06 * nz);
366
- let lc = mix3(col, C.leg, dark);
367
- // (heavy hairy columns, tq2 / front3: the lower legs 5 cm, "sleeves" 9 cm behind the forearm, "trousers"
368
- // 10 cm behind the thighs; thinner, they read as long bare-looking legs)
369
- let lf = mix(0.05, 0.07, smoothstep(0.12, 0.45, p[1]));
370
- if (!front) lf = Math.max(lf, 0.1 * smoothstep(-0.1, -0.7, n[2]) * smoothstep(0.25, 0.5, p[1]));
371
- else lf = Math.max(lf, 0.09 * smoothstep(-0.2, -0.8, n[2]) * smoothstep(0.12, 0.3, p[1]));
372
- let la = AG.leg;
373
- // (short hair over the toes: the claws show their whole length)
374
- if (lb >= 0 && isPaw[lb]) { lf = mix(0.008, 0.035, smoothstep(0.025, 0.12, p[1])); lc = C.leg; la = 0; }
375
- col = mix3(col, lc, L);
376
- ag = mix(ag, la + (AG.back - la) * (1 - dark) * 0.6, L);
377
- fl = mix(fl, lf, L);
378
- // pads: bare, matte black leather where the paw meets the ground (the forefoot's palm and toe pads,
379
- // the whole long sole of the hind foot) and on the carpal pad; the rest of the underside is hairy
380
- // (the whole bare underside of the wrist flashed as a glossy black ball in every swing)
381
- if (lb >= 0 && isPaw[lb]) {
382
- let dc = 1;
383
- if (front) for (const c of carpalPrims) dc = Math.min(dc, SDFModel.dist(c, p[0], p[1], p[2]));
384
- const pad = front ? (p[1] < 0.016 && n[1] < -0.55) || (dc < 0.004 && n[1] < -0.2) : p[1] < 0.03 && n[1] < -0.35;
385
- // (nose leather: pebbled, satin; its id sits next to fur's, so the interpolated material id never
386
- // passes the mouth's pink at the rim, as skin (4) did)
387
- if (pad) { mat = MAT.NOSE; col = srgb(0x1c1814); fl = 0; }
388
- else if (p[1] < 0.035 && n[1] < -0.3) fl = Math.min(fl, 0.008);
389
- }
390
- }
391
- // cub collar: a pale band around the neck base
392
- if (collar > 0 && (rb === 1 || (rb === 0 && p[2] > 0.3))) {
393
- const band = Math.exp(-(((p[2] - 0.47 + 0.04 * nz) / 0.06) ** 2)) * smoothstep(0.2, -0.3, up + 0.4 * smoothstep(0.3, 0.9, Math.abs(n[0])));
394
- col = mix3(col, srgb(COLLAR), clamp(band * collar * 1.3, 0, 0.9));
395
- }
396
- if (mat === MAT.FUR) fl *= shag;
397
- return { col, fl, ag, mat, mark, under: 0.35 };
398
- };
399
- const EAR_PALE = srgb(0xd9c3a2);
400
- const earPaint = (v, p, n) => {
401
- const E = EARF[p[0] > 0 ? 1 : -1];
402
- const front = dot(n, E.facing);
403
- const ht = clamp(dot(sub(p, E.b.head), E.up) / 0.1, 0, 1);
404
- const nzF = vnoise3(p[0] * 110 + nOff, p[1] * 110, p[2] * 110) - 0.5;
405
- // distance from the middle of the cup in the ear's plane
406
- let rv = sub(p, E.c);
407
- rv = sub(rv, mul(E.facing, dot(rv, E.facing)));
408
- // the edge of the pinna (where the cup turns into the back), above its buried lower part
409
- const rim = (1 - smoothstep(0.25, 0.7, Math.abs(front))) * smoothstep(0.2, 0.45, ht);
410
- const cup = smoothstep(0.2, 0.6, front) * (1 - rim);
411
- // ears are furred all over in the head colour (a darker ear read as a teddy's cup on a pale head); the fringe
412
- // round the rim pale-tipped (tq2, tq1, front2), the cup's hollow dark only in its deep middle (face2) and
413
- // filled with paler hair growing in from its margin (face1, tq2). (A dark cup under a fringe ~15 % paler than
414
- // the head read as a Mickey Mouse disc)
415
- const pale = mix3(C.tip, EAR_PALE, 0.15);
416
- let col = mix3(C.head, C.ear, 0.15);
417
- col = mix3(col, pale, rim * 0.55);
418
- // (the dark hollow is the lower middle of the cup, running down into the crown hair, face1 / face2; a round dark
419
- // middle read as a hole)
420
- const lat = Math.abs(dot(rv, norm(cross(E.up, E.facing))));
421
- // (kept off the ear's base: running down to the crown it drew a dark rim / notch round the base)
422
- const hollow = cup * (1 - smoothstep(0.012, 0.026, lat)) * (1 - smoothstep(0.42, 0.7, ht)) * smoothstep(0.1, 0.28, ht);
423
- col = mix3(col, mix3(C.head, pale, 0.45), cup * (1 - hollow));
424
- col = mix3(col, C.earInner, 0.7 * hollow);
425
- // hair: the back 20 mm, the rim fringe ~33 mm and ragged (+-20 % lock to lock), the cup's margin 22 mm lying in
426
- // over the hollow, 12 mm in its middle (at 23 mm round a thin pinna the fringe drew the ear as a clean circle, a
427
- // cup with a smooth thick rim)
428
- let fl = mix(0.02, 0.033, rim);
429
- fl = mix(fl, mix(0.022, 0.013, hollow), cup);
430
- fl *= 1 + 0.4 * nzF;
431
- // (the rim's fringe and the back's outer band ragged by lock, +-40 % at ~1.5 cm: from behind the evenly long fringe drew
432
- // the ear as a round disc with a smooth outline; tq2 / side3 show tufted, uneven ear outlines)
433
- const back = smoothstep(-0.2, -0.6, front) * smoothstep(0.35, 0.7, ht);
434
- const nzR = vnoise3(p[0] * 65 + 13 + nOff, p[1] * 65, p[2] * 65) - 0.5;
435
- fl *= 1 + 0.8 * nzR * Math.max(rim, back);
436
- fl = mix(fl, fl * 1.2, back);
437
- col = mix3(col, pale, 0.3 * back * smoothstep(-0.1, 0.25, nzR));
438
- return { col, fl, ag: AG.head * 0.5, mat: MAT.FUR, mark: MARK_FAR, under: 0.35, rim };
439
- };
440
- const headPaint = (v, p, n, jaw, nz, nz2) => {
441
- const h = headLocal(p);
442
- const nzF = vnoise3(p[0] * 90 + nOff, p[1] * 90, p[2] * 90) - 0.5;
443
- let col = C.head, ag = AG.head, mat = MAT.FUR, mark = MARK_FAR, fl, nostril = 0, nrim = 0, bareLid = 0;
444
- // the face grades from the crown into a paler, greyer muzzle; the pale muzzle is the bridge and the
445
- // front of the snout (face1 / face2): the sides of the face below and behind the eyes stay darker
446
- // (the paler face colour on the front of the face, fading out over the cheeks: across the whole front half of the head,
447
- // the cheeks included, the grey face was a round disc framed by the ruff, a balloon round the muzzle in the live page's
448
- // low sun; face1 / face2 / tq1: the cheeks are the crown's colour)
449
- const face = smoothstep(-0.02, 0.07, h[2]) * (1 - 0.6 * smoothstep(0.04, 0.085, Math.abs(h[0])));
450
- col = mix3(col, C.face, face);
451
- // (graded over 10 cm from between the eyes to the nose, palest on the bridge and round the nose and
452
- // mouth; the sides of the snout under the eyes stay the face colour: a pale snout all round, sharply
453
- // bounded under the eyes, read as a teddy's muzzle patch)
454
- const lat = smoothstep(0.02, 0.05, Math.abs(h[0]));
455
- const muzz = smoothstep(0.06, 0.16, h[2]) * (1 - 0.7 * lat * smoothstep(0.2, 0.12, h[2]));
456
- col = mix3(col, C.muzzle, 0.7 * muzz);
457
- // (mottled lock to lock, lighter and darker locks: face1 / face2 / tq1 show a salt-and-pepper face with the hair's flow
458
- // in it; the body's 3 cm shade noise left the face an even plush, round 1 cm blotches read as spots on felt: locks are
459
- // streaks along the comb, ~7 mm across and ~3 cm long)
460
- const cv = [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]];
461
- const lock = streak(p, cv, 140, 4.5, 31 + nOff) - 0.5;
462
- const mot = 0.7 * lock + 0.3 * (vnoise3(p[0] * 110 + 31 + nOff, p[1] * 110, p[2] * 110) - 0.5);
463
- col = mul(col, 1 + 0.65 * mot * (1 - 0.4 * muzz));
464
- // (grizzled: pale-tipped guard hairs over a dark band on the forehead and cheeks, face1 / face2 / tq1; stronger in the
465
- // paler locks)
466
- ag = clamp(Math.max(ag, 0.7) * (1 - 0.6 * muzz) * (1 + 0.5 * lock), 0, 1);
467
- // (the long cheek ruff with a third of it: its dark band layer, seen edge-on at the jaw corner, drew torn dark sheets)
468
- ag *= mix(1, 0.35, smoothstep(0.25, 0.75, smoothstep(0.13, -0.05, h[2]) * smoothstep(-0.02, 0.14, Math.abs(h[0])) * smoothstep(0.125, 0.02, h[1]) * smoothstep(-0.14, -0.06, h[1])));
469
- // cheeks below and behind the eyes darker (the face is framed by darker hair)
470
- const cheek = smoothstep(0.045, 0.09, Math.abs(h[0])) * smoothstep(0.11, 0.0, h[2]) * smoothstep(0.045, -0.03, h[1]);
471
- col = mix3(col, mix3(C.head, C.flank, 0.5), cheek * 0.35);
472
- // the cheek ruff: long hair from beside and below the eyes back to the jaw angle and the neck
473
- // (the ruff starts beside and below the eyes: face1 / face2 are a disk of hair 3.1-3.4 x the eye
474
- // separation wide at eye level)
475
- // (down to the jaw line: the cheek hair hangs to below the mouth, so the face is widest at and below the
476
- // eyes, face1 / face2; a ruff ending at the eyes left a round head narrowing to the snout, a teddy's)
477
- // (it rises from ~2 cm under the eye to 9.5 cm at the jaw angle; over ~5 cm across the cheek it drew a hard edge
478
- // where the face's short hair met it, from the front a seam down the side of the face: the lateral ramp
479
- // runs over ~8 cm)
480
- const ruffW = smoothstep(0.13, -0.05, h[2]) * smoothstep(-0.02, 0.14, Math.abs(h[0])) * smoothstep(0.125, 0.02, h[1]) * smoothstep(-0.14, -0.06, h[1]);
481
- // (and it takes the neck ruff's colour as it lengthens: the face's grey-brown ending in a ring of the neck's paler,
482
- // golden-tipped hair drew the face as a disk; front3 / tq1: the hair round the face is the neck's)
483
- // (greyed half as much as the face: face1 / face2's cheek ruff, saturation 0.13-0.42, against the neck's 0.6-0.8 in the
484
- // render; the face's grey ending in the neck's saturated red-gold drew a grey mask framed by a ring)
485
- if (!jaw && ruffW > 0.02) col = mix3(col, desat(bodyPaint(v, p, n, 1, nz, nz2).col, 0.5 * (FACE_DESAT[params.coat] ?? 0.6)), 0.7 * smoothstep(0.1, 0.7, ruffW));
486
- // dark patches round the eyes, running a little down and forward
487
- // (a ring of dark hair right round the eye, 2.5 cm, and a darker streak from its front corner down toward
488
- // the side of the muzzle, face2 / front3: the eyes sit in shadow; a patch centred behind the eye left a pale
489
- // ring of short hair round a bead)
490
- const dEye = Math.hypot(Math.abs(h[0]) - EYE_BASE.c[0], h[1] - EYE_BASE.c[1], h[2] - EYE_BASE.c[2]);
491
- // (darker and wider below the eye: face1 / face2 show the eyes in patches of dark hair as dark as the cheeks or darker,
492
- // (78, 60, 63) under face2's eye against (113, 86, 77) on its cheek; the render's were as pale as the forehead)
493
- const eyeDark = Math.max(Math.exp(-((dEye / 0.03) ** 2)), 0.8 * g3(h, [0.036, 0.012, 0.085], [0.014, 0.02, 0.026]), 0.7 * g3(h, [0.048, 0.006, 0.066], [0.02, 0.016, 0.02]));
494
- col = mix3(col, mix3(C.head, C.leg, 0.8), eyeDark * 0.9);
495
- // lower jaw and throat a little darker
496
- // (the lower jaw small, dark and in shadow under the upper lip: face1 / tq1 / front1 show a small dark hairy
497
- // chin; a paler front read as a pale rounded lower lip under a smile)
498
- if (jaw) col = mix3(col, mix3(C.face, C.belly, 0.55), 0.75);
499
- // crown sun-bleached on some individuals
500
- col = mix3(col, C.tip, bleach * 0.4 * smoothstep(0.2, 0.8, n[1]) * (1 - muzz));
501
- // fur length (face1 / face2 / face3_profile): tousled strands 2-4 cm on the forehead and the cheeks, growing
502
- // gradually from the short muzzle out to the cheek ruff and back to the long crown, with the ears sunk in the
503
- // crown hair. Muzzle 9 mm at the nose, the bridge 13-14 mm between the eyes, the forehead and the face beside
504
- // and below the eyes 20 mm (they get a little clumping: texture, not curls, render.clumpLen). At 9-12 mm with
505
- // no clumping the face rendered as a suede mask framed by the long ruff
506
- // (the snout sleek from just in front of the eyes, the bridge 11 mm: face3_profile / side2 show a smooth snout line;
507
- // 14-18 mm on its top and sides stood up in 3/4 views as a pale sheet of shells over the far eye)
508
- fl = mix(0.02, 0.009, smoothstep(0.06, 0.15, h[2]));
509
- fl = mix(fl, Math.min(fl, 0.011), smoothstep(0.04, 0.012, Math.abs(h[0])) * smoothstep(0.05, 0.09, h[2]) * smoothstep(-0.01, 0.02, h[1]));
510
- // the forehead grades into the crown over its whole length (42 mm between the ears), ~50 mm round the ear
511
- // bases (face2, tq2: the lower third of each ear is buried in the crown hair)
512
- if (!jaw) {
513
- // (30 mm on the crown: at 42 mm, fully clumped, it drew rows of round tufts between the ears seen from the front,
514
- // lamb's wool; at 34 mm still rows)
515
- fl = Math.max(fl, mix(0.02, 0.03, smoothstep(0.045, -0.07, h[2])) * smoothstep(0.015, 0.06, h[1]));
516
- const dEarB = Math.hypot(Math.abs(h[0]) - 0.122, h[1] - 0.082, h[2] + 0.05);
517
- fl = Math.max(fl, 0.05 * Math.exp(-((dEarB / 0.045) ** 2)));
518
- }
519
- // (the cheek ruff, ruffW above: 9.5 cm at the jaw angle)
520
- // (tapering to ~5 cm toward the neck cut, where the neck's hair starts at that length: the cheek ruff's 9.5 cm hair
521
- // ended in a shelf over it, torn dark sheets at the far jaw corner in a look turn)
522
- // (by lock along the flow, +-45 %: the evenly long ruff drew a grid of round scale-like tufts behind the eye at medium)
523
- const lkR = streak(p, cv, 55, 3.2, 77 + nOff) - 0.5;
524
- fl = Math.max(fl, 0.095 * ruffW * mix(1, 0.65, smoothstep(-0.05, -0.14, h[2])) * (1 + 0.9 * lkR * smoothstep(0.2, 0.6, ruffW)));
525
- if (!jaw) fl = mix(fl, Math.max(fl, 0.045), smoothstep(-0.04, -0.15, h[2]));
526
- // (the throat skin under the jaw carries the jaw's long hair: 12 mm there beside a 4 cm jaw was a wall)
527
- if (!jaw) fl = Math.max(fl, mix(0.012, 0.04, smoothstep(0.1, -0.02, h[2])) * smoothstep(-0.03, -0.075, h[1]));
528
- // (the jaw: the lower lip 12 mm, hanging down over its rim, long under the cheek ruff and toward the throat,
529
- // 4 cm: a 2 cm jaw under the 7-9 cm ruff stood up as a wall along the jaw line)
530
- if (jaw) fl = mix(0.012, 0.04, smoothstep(0.1, -0.02, h[2]));
531
- // round the eyes the face hair is shorter, 12 mm out to ~3 cm from the eye (face3_profile), so the short hair
532
- // at the lids grows back within ~1 cm (a 2 cm ring of short hair round the eye drew a pale crater)
533
- const dEyeC = Math.hypot(Math.abs(h[0]) - EYE_BASE.c[0], h[1] - EYE_BASE.c[1], h[2] - EYE_BASE.c[2]);
534
- fl = Math.min(fl, mix(0.012, 0.1, smoothstep(0.028, 0.048, dEyeC)));
535
- // (in front of the eye, toward the bridge, shorter still, 5.5 mm at 1.6 cm: the far eye in a 3/4 view is seen past
536
- // the bridge, and the 12 mm hair there, combed out from the eye, stood off it as pale flaps over the eye)
537
- const eyeFront = smoothstep(-0.004, 0.008, h[2] - EYE_BASE.c[2]) * (1 - smoothstep(0.024, 0.034, dEyeC));
538
- fl = Math.min(fl, mix(fl, mix(0.0055, 0.012, smoothstep(0.016, 0.03, dEyeC)), eyeFront));
539
- // (the upper side of the bridge from the eyes forward ~4 cm, between the bridge's top and the eye: 5 mm, darker, and
540
- // without the lock-to-lock length noise. In a 3/4 view the snout's silhouette runs along it across the far eye, and its
541
- // 11-14 mm hair stood off there as a stepped edge of shells over the eye)
542
- const bSide = smoothstep(0.007, 0.016, Math.abs(h[0])) * (1 - smoothstep(0.04, 0.055, Math.abs(h[0])))
543
- * smoothstep(0.05, 0.064, h[2]) * (1 - smoothstep(0.105, 0.13, h[2]))
544
- * smoothstep(-0.012, 0.004, h[1]) * (1 - smoothstep(0.044, 0.058, h[1]));
545
- fl = Math.min(fl, mix(fl, 0.005, bSide));
546
- col = mix3(col, mix3(C.head, C.leg, 0.6), 0.4 * bSide * (1 - eyeDark));
547
- // lock to lock +-25 % at ~1 cm (the body's coat locks; an even length read as a pile)
548
- fl *= 1 + 0.5 * nzF * (1 - 0.8 * bSide);
549
- // (the crown and the top of the forehead also by lock, the locks running with the flow: its fully clumped 34 mm hair drew
550
- // rows of round shingle-like tufts between the ears from the front; with +-45 % about half the locks
551
- // fall under the 22 mm at which the tufts form fully)
552
- const crownW = smoothstep(0.015, 0.05, h[1]) * smoothstep(0.07, 0.02, h[2]) * (jaw ? 0 : 1);
553
- fl *= 1 + 0.55 * lock * crownW;
554
- // eye rims (dark lid margins)
555
- // (without eyelid patches, medium and below, the aperture is meshed at the head cell: a lid band one
556
- // cell wide, dark, so the stepped cut reads as the lid margin instead of lit grey facets)
557
- const lidW = ctx.Q && !eyePatchesAt(ctx.Q) ? Math.max(0.0018, 0.5 * cellH) : 0.0018;
558
- for (const e of eyePrims) {
559
- const de = SDFModel.dist(e, p[0], p[1], p[2]);
560
- mark = Math.min(mark, Math.abs(de) - Math.max(0.002, lidW));
561
- // (the lid margin: hairless skin painted black with the fur id, matte like the deep coat: as moist bare dark skin
562
- // (id 2) it mirrored the sky outdoors, a light blue-grey ring that made the eyes read as glassy marbles in the
563
- // showcase; the eyeball's cornea keeps the wet catchlight)
564
- if (Math.abs(de) < lidW) { mat = MAT.FUR; col = srgb(0x0b0908); fl = 0; ag = 0; bareLid = 1; }
565
- else if (Math.abs(de) < lidW + 0.0015) fl = Math.min(fl, 0.003);
566
- // (round the eye the hair lies away from it, like the hair of the lids (the comb): 5 mm at the margin, the
567
- // 12 mm eye-surround hair within ~7 mm; a 2-3 cm ring of 2-3 mm hair rendered as a pale smooth felt, a crater)
568
- else if (de < lidW + 0.0085) fl = Math.min(fl, mix(0.005, fl, smoothstep(lidW + 0.0015, lidW + 0.0085, de)));
569
- }
570
- // nose leather
571
- let dn = 1;
572
- for (const q of nosePrims) dn = Math.min(dn, SDFModel.dist(q, p[0], p[1], p[2]));
573
- // (the crisp rim is a mark band round the pad; a mark over the whole pad painted it and the nostrils one flat
574
- // mark colour)
575
- mark = Math.min(mark, Math.max(dn - 0.0045 + Math.max(0, 0.17 - h[2]), 0.0012 - dn));
576
- // (the leather wraps round the nostrils: the muzzle's pale hair reaching into a nostril's outer corner lit up as a pale
577
- // glint)
578
- let dq0 = 1;
579
- if (h[2] > 0.185 && !jaw) for (const q of nostrilPrims) dq0 = Math.min(dq0, SDFModel.dist(q, p[0], p[1], p[2]));
580
- if ((dn < 0.0035 || (dq0 < 0.0028 && dn < 0.007)) && h[2] > 0.19) {
581
- // (face2: a cool dark grey in shade, #282a37, the nostrils near black)
582
- mat = MAT.NOSE; col = srgb(0x25242a); fl = 0;
583
- // (the nostrils are openings: black inside, darkest deepest, so they read from the front and below as in
584
- // face2 even where the carve is shallow)
585
- let dq = 1;
586
- for (const q of nostrilPrims) dq = Math.min(dq, SDFModel.dist(q, p[0], p[1], p[2]));
587
- // (black from ~2 mm outside the carve: the leather's fixed grey on the inner walls of the rims, lit from above, drew
588
- // a pale lining round the lower half of each opening)
589
- nostril = smoothstep(0.0026, 0.0006, dq) * smoothstep(0.007, 0.0, dn);
590
- // (the leather round each nostril matte: its rim, a tight convex edge, caught the light as pale glints)
591
- nrim = smoothstep(0.0045, 0.001, dq);
592
- col = mix3(col, srgb(0x040303), nostril);
593
- // (the shader draws nose leather in its own fixed grey whatever the tint, and bare dark skin (lids, lips) moist
594
- // with a sharp highlight: two glossy beads. The nostril walls are hairless fur-id skin painted black, which the
595
- // base pass shades as the deep coat: dark and matte; the id lies next to the leather's)
596
- if (nostril > 0.5) { mat = MAT.FUR; col = srgb(0x030202); ag = 0; }
597
- // the groove between the nostrils: a crisp dark line down the lower half of the pad
598
- mark = Math.min(mark, Math.abs(h[0]) - 0.0011 + 2 * Math.max(0, h[1] + 0.012));
599
- } else if (dn < 0.008 && h[2] > 0.18) fl = Math.min(fl, 0.003);
600
- // (short dark hair round the nostrils' outer corners)
601
- if (mat === MAT.FUR && dq0 < 0.008) { const kq = smoothstep(0.008, 0.003, dq0); fl = Math.min(fl, mix(fl, 0.002, kq)); col = mix3(col, srgb(0x1a1512), 0.8 * kq); }
602
- if (h[2] > 0.185 && !jaw && mat !== MAT.NOSE) {
603
- // (a nostril wall reaching past the pad: bare and black like the rest of the nostril)
604
- let dq = 1;
605
- for (const q of nostrilPrims) dq = Math.min(dq, SDFModel.dist(q, p[0], p[1], p[2]));
606
- // (within 2.5 mm of the carve: its smooth subtraction leaves the deep walls 1-2 mm off the carver's own surface, and
607
- // there they took the muzzle's colour, a brown lining seen through the openings)
608
- if (dq < 0.0025) { mat = MAT.FUR; fl = 0; nostril = 1; col = srgb(0x030202); ag = 0; }
609
- }
610
- // below the pad the groove runs on as a dark philtrum, ~2 cm, fading toward the mouth (face2, face3_profile):
611
- // a soft dark cleft with the lip hair short along it (a crisp black line to the lip read as a teddy's stitching)
612
- // (short, soft and dim, the hair left as long as the lip's: a 3 cm black stripe of 3 mm hair down the pale lip read as
613
- // a drip under the nose; shorter hair there showed as a pale notch)
614
- // (not on the nostril walls, which are fur-id skin too: the lip's colour mixed into them drew a brown lining)
615
- if (h[2] > 0.17 && !jaw && mat === MAT.FUR && nostril < 0.5) {
616
- // (darker and a little wider right under the pad, fading over 1.5 cm: the hair's flow fans out from the pad's lower
617
- // edge there and a lit strand at the fan's root showed as a pale tick under the pad)
618
- const along = smoothstep(-0.046, -0.034, h[1]) * smoothstep(-0.016, -0.026, h[1]);
619
- const cleft = (1 - smoothstep(0.0015, 0.0045, Math.abs(h[0]))) * along;
620
- col = mix3(col, srgb(0x2a201a), 0.7 * cleft);
621
- // (the upper lip right under the pad darker, fading down and out: tq1 / face1; the pale lip hair under the
622
- // septum's point lit up as a pale tab)
623
- const under = smoothstep(-0.052, -0.034, h[1]) * (1 - smoothstep(0.006, 0.02, Math.abs(h[0])));
624
- col = mix3(col, mix3(C.face, C.belly, 0.5), 0.6 * under);
625
- }
626
- // lips: head vertices near the jaw surface, jaw vertices near the head surface (black lip line)
627
- // (the bare black lip shows only round the front of the mouth, under the nose: behind the canines the
628
- // lip hair covers the mouth line back to the corner below the eye; a black line drawn all the way back
629
- // read as a puppet's mouth, a frown in the low 3/4)
630
- const lipF = smoothstep(0.08, 0.12, h[2]);
631
- if (!jaw) {
632
- const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
633
- if (dj < -mouthIn) { mat = MAT.MOUTH; col = srgb(0x5e2e30); fl = 0; }
634
- else if (dj < 0.0015 * lipF) { mat = MAT.DARK_SKIN; col = srgb(0x1a1614); fl = 0; }
635
- // (toward the mouth line the hair shortens at under 1 mm per mm of skin, however long it is: the lip
636
- // hair lies over the line without standing up as a wall along it)
637
- else fl = Math.min(fl, mix(0.006, 0.003, lipF) + 0.85 * Math.max(0, dj - 0.0015 * lipF));
638
- mark = Math.min(mark, dj - 0.003 * lipF + 0.004 * (1 - lipF));
639
- } else {
640
- const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
641
- if (dh < -mouthIn) { mat = MAT.MOUTH; col = srgb(0x5e2e30); fl = 0; }
642
- else if (dh < 0.0015 * lipF) { mat = MAT.DARK_SKIN; col = srgb(0x1a1614); fl = 0; }
643
- else fl = Math.min(fl, mix(0.005, 0.003, lipF) + 0.85 * Math.max(0, dh - 0.0015 * lipF));
644
- mark = Math.min(mark, dh - 0.003 * lipF + 0.004 * (1 - lipF));
645
- }
646
- // (the shells of short, unclumped hair render as a pale smooth felt, much lighter than longer hair of the same
647
- // colour, whose strands show the darker coat between them: the lid margins and the muzzle a little darker)
648
- if (mat === MAT.FUR) col = mul(col, mix(0.72, 1, smoothstep(0.003, 0.012, fl)));
649
- // teeth
650
- for (const c of caninePrims) if (SDFModel.dist(c, p[0], p[1], p[2]) < 0.0015) { mat = MAT.KERATIN; col = srgb(0xe4dac4); fl = 0; mark = MARK_FAR; }
651
- void nz;
652
- return { col, fl, ag, mat, mark, under: 0.3, nostril, nrim, bare: nostril > 0.5 || bareLid > 0 };
653
- };
654
- const mixPaint = (a, b, t) => ({
655
- col: mix3(a.col, b.col, t), fl: mix(a.fl, b.fl, t), ag: mix(a.ag, b.ag, t), mark: Math.min(a.mark, b.mark),
656
- mat: a.mat === MAT.FUR ? b.mat : a.mat, under: mix(a.under, b.under, t), nostril: Math.max(a.nostril || 0, b.nostril || 0), nrim: Math.max(a.nrim || 0, b.nrim || 0), bare: !!(a.bare || b.bare),
657
- });
658
-
659
- for (let v = 0; v < nV; v++) {
660
- const p = P(v), n = N(v), r = region[v];
661
- const nz = fbm3(p[0] * 5 + nOff, p[1] * 5, p[2] * 5, 3) - 0.5;
662
- const nz2 = vnoise3(p[0] * 30 + nOff, p[1] * 30, p[2] * 30) - 0.5;
663
- let o;
664
- if (r === 6) o = headPaint(v, p, n, true, nz, nz2);
665
- else {
666
- // head vs neck by position along the neck (the same for both meshes of the overlap), 10 cm wide
667
- const hn = r === 4 ? 0 : smoothstep(-0.05, 0.05, neckS(p[0], p[1], p[2]));
668
- const rb = r === 4 ? 4 : axialS[v] < sNeckBase ? 1 : 0;
669
- if (hn >= 0.999) o = headPaint(v, p, n, false, nz, nz2);
670
- else if (hn <= 0.001) o = bodyPaint(v, p, n, rb, nz, nz2);
671
- else o = mixPaint(bodyPaint(v, p, n, 1, nz, nz2), headPaint(v, p, n, false, nz, nz2), hn);
672
- // ears: blended into the head by their skin weight
673
- let ew = 0;
674
- for (let k = 0; k < 4; k++) if (earBone[skinIndex[v * 4 + k]]) ew += skinWeight[v * 4 + k];
675
- // (the hair length over a wider band than the colour: the 8-9 cm cheek ruff below the ear falls to the
676
- // ear's 2 cm without a wall at the ear base)
677
- if (ew > 0.02) {
678
- const fl0 = o.fl, ep = earPaint(v, p, n); o = mixPaint(o, ep, smoothstep(0.1, 0.7, ew)); o.fl = mix(fl0, ep.fl, smoothstep(0.02, 0.95, ew));
679
- // (the rim fringe stays as painted: the fur-slope limit bevelled it down to the back's 20 mm)
680
- if (ep.rim > 0.4 && ew > 0.6) slopeV[v] = Infinity;
681
- }
682
- }
683
- let { col, fl, ag, mat, mark, under } = o;
684
- const nostril = o.nostril || 0, nrim = o.nrim || 0;
685
- if (o.bare && mat === MAT.FUR) bareFur[v] = 1;
686
- // cubs: fluffy and uniform
687
- if (juv > 0 && mat === MAT.FUR) {
688
- fl = mix(fl, Math.max(fl * 0.7, r === 2 || r === 6 ? 0.02 : 0.04), juv);
689
- ag *= 1 - 0.8 * juv;
690
- }
691
- // low-frequency colour variation (patchy moult, sun-bleach) and a finer lock-to-lock shade
692
- const cvh = fbm3(p[0] * 3 + 5 + nOff, p[1] * 3, p[2] * 3, 2) - 0.5;
693
- // (grizzled coats: a stronger lock-to-lock speckle of silver-tipped and dark locks)
694
- const sp = params.coat === 'grizzled' ? 0.2 : 0.15;
695
- if (mat === MAT.FUR) col = [col[0] * (1 + 0.18 * nz + 0.12 * cvh + sp * nz2), col[1] * (1 + 0.16 * nz + 0.1 * cvh + sp * nz2), col[2] * (1 + 0.12 * nz + 0.04 * cvh + sp * nz2)];
696
- markSDF[v] = Math.min(MARK_FAR, mark);
697
- furLen[v] = fl;
698
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
699
- surf[v * 4 + 2] = mat === MAT.FUR ? clamp(ag * (0.85 + 0.3 * nz2), 0, 1) : 0;
700
- surf[v * 4 + 3] = mat === MAT.FUR ? under : 0;
701
- if (mat === MAT.KERATIN) surf[v * 4] = 0.6;
702
- // (nose leather: pebbled and matte, roughness 0.76: at the shader's default 0.42 it drew a white sky highlight in the
703
- // showcase, at 0.62 a broad highlight across the top of a glossy dome; the paw pads dry and matte, 0.75)
704
- // (inside the nostrils and round their rims rough: a satin sheen there drew each nostril as a glossy bowl)
705
- if (mat === MAT.NOSE) surf[v * 4 + 1] = legBone[v] >= 0 && region[v] < 2 ? -0.75 : -mix(0.76, 0.97, Math.max(nostril, nrim));
706
- }
707
-
708
- // smooth fur length so the shells do not step (bare skin and the hairless nostril walls stay locked)
709
- smoothField(furLen, weights.neighbors, 3, (v) => tint[v * 4 + 3] > 0 || bareFur[v] === 1);
710
- // (the fur-slope limit lets hair painted over 25 mm rise proportionally faster; head hair painted just over it and
711
- // cut back under it kept that allowance and stood 1.1-1.2 mm per mm above the forehead hair at the ear bases, fur
712
- // walls: on the head, hair of 25-46 mm (the crown, the ear bases) rises at 1 mm per mm like shorter hair, and the ear's rim fringe is left as
713
- // painted only where it is over 25 mm)
714
- // (likewise on the feet and wrists, where the 3.5-5 cm hair of the lower leg meets the short hair of the toes)
715
- for (let v = 0; v < nV; v++) {
716
- const r = region[v];
717
- if (r !== 2 && r !== 5 && r !== 6 && !(legBone[v] >= 0 && pos[v * 3 + 1] < 0.15)) continue;
718
- const L = furLen[v];
719
- if (slopeV[v] === Infinity) { if (L < 0.025) slopeV[v] = 1; continue; }
720
- if (L > 0.025 && L < 0.046) slopeV[v] = Math.min(slopeV[v], 0.025 / L);
721
- }
722
-
723
- const pattern = new Float32Array(nV).fill(1);
724
- const patternColor = new Float32Array(nV * 4);
725
- return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, region, furSlope: slopeV };
726
- }