@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,1016 +0,0 @@
1
- // The lion's coat, painted per vertex in reference space.
2
- //
3
- // An essentially uniform tawny coat: a
4
- // slightly darker, greyer back, warm flanks grading into the cream belly, inner legs and throat,
5
- // white chin, lips and muzzle sides, pale crescents above and below the eyes, a black lip line with
6
- // a black spot at the corner of the mouth, rows of black whisker-base dots, black ear backs, the
7
- // black tail tuft and pink-to-black freckled nose leather (darker with age). Cubs are woolly and
8
- // greyer with faint dark rosettes and spots, strongest on the legs and belly; adults keep a trace
9
- // of them on the lower legs. The male's mane is long, clumped hair over the sculpted mane volume:
10
- // blond at the face ruff, darkening toward its rear and lower edge (params.maneDark 0 blond .. 1
11
- // black), with a chest bib and elbow tufts.
12
- import { HEAD_O, HS, FACE, toFace, lionJoints } from './rig.js';
13
- import { neckS, splitFor } from './regions.js';
14
- import { whiskerFollicles, follicleRoot } from './whiskers.js';
15
- import { EYE } from './sculpt.js';
16
- import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
17
- import { SDFModel } from '../../core/sdf/sdf.js';
18
- import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
19
- import { srgb, mix3, MAT, smoothField, poissonFeatures, featureDistance } from '../../core/build/coatKit.js';
20
-
21
- const PAL = {
22
- // neutral-light photo swatches, hue turned ~4 deg toward red (the photos' tawny is hue 25-30 deg)
23
- dorsal: 0x9c8066, flank: 0xae9276, lowFlank: 0xbfa58a, belly: 0xd8cab8, legOuter: 0xb09478, legInner: 0xd2c1aa,
24
- face: 0xb39b7e, crown: 0xa68d71, muzzle: 0xf0e8da, chin: 0xefe6d6, eyePatch: 0xeee2ce, earBack: 0x1c1410, earInner: 0xd6c6ae, earCup: 0x7a6452,
25
- earBackCentre: 0x9a7b5a, tuft: 0x140e0a, tailTip: 0x5a3e28, paw: 0xb89c80,
26
- maneBlond: 0xc6a070, maneMid: 0x6b4523, maneDark: 0x2a1b10,
27
- nosePink: 0x9a7a72, noseDark: 0x2e2220, lipDark: 0x16100e, nostril: 0x140e0d, mouth: 0x4a2426, tongue: 0xa8585c, tooth: 0xe8e0cc, pad: 0x2e2220, tear: 0x4a3526, browMark: 0x7a5a40,
28
- cubBody: 0xbfa27f, cubSpot: 0x8c6a48, whiskerDot: 0x2a1c14,
29
- whiteBody: 0xede4d3,
30
- };
31
-
32
- function palette(p) {
33
- const k = p.coatWarmth || 0, l = p.coatLightness || 0, juv = p.juv || 0, white = p.variant === 'white';
34
- const out = {};
35
- for (const [key, hex] of Object.entries(PAL)) {
36
- let c = srgb(hex);
37
- if (!/nose|nostril|mouth|tongue|tooth|pad|tuft|earBack$|maneDark|tear|lipDark|whiskerDot/.test(key)) {
38
- c = [c[0] * (1 + 0.18 * k + l), c[1] * (1 + 0.04 * k + l), c[2] * (1 - 0.2 * k + l)];
39
- // (+7 % saturation: the neutral-light swatches read grey beside the sunlit photos)
40
- const Y = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
41
- c = c.map((x) => Math.max(0, Y + (x - Y) * 1.07));
42
- }
43
- // cubs: greyer, woollier natal coat; the body colours fade toward it
44
- if (juv > 0 && /dorsal|flank|legOuter|face|crown/.test(key)) c = mix3(c, srgb(PAL.cubBody), 0.6 * juv);
45
- // leucistic "white lion": cream coat, pale tuft and ear backs, normal dark lips and eye rims
46
- // (the face's own tones kept faintly, a warm cream on the brow, the bridge and round the eyes, and the tear
47
- // lines and brow spots a light brown (ffront_African_Lion_*_Female, white_Guylaine2007_*, white_Flickr_*): an even white face read
48
- // as a plush toy with no structure at all)
49
- if (white && !/nose|nostril|mouth|tongue|tooth|pad|cubSpot|lipDark|whiskerDot/.test(key)) c = mix3(c, srgb(PAL.whiteBody), /tuft|earBack$|maneDark|maneMid/.test(key) ? 0.72 : /^(tear|browMark)$/.test(key) ? 0.3 : /^(face|crown)$/.test(key) ? 0.5 : 0.85); // (0.45 / 0.64 still cream on cream, ffront_African_Lion_*_Female / white_Flickr_*: warm tan brows and tear lines)
50
- out[key] = c;
51
- }
52
- // white lions keep a pink nose (research 6: #D2968A), mottling only faintly
53
- // the leather darkens with age: pink-brown in young adults, dusky in old ones (plus the black mottling)
54
- out.nosePink = mix3(srgb(0xb0877c), srgb(0x7c5e58), clamp(p.noseDark ?? 0.3, 0, 1));
55
- // (a duller, greyer pink: #d2968a read as a saturated red-pink on the pale face)
56
- if (white) { out.nosePink = srgb(0xb3928a); out.noseDark = srgb(0x5e4844); } // (darker toward the rim, ffront_African_Lion_*_Female: a dark-rimmed leather)
57
- return out;
58
- }
59
-
60
- // the mane's ruff radiates from the face centre; the ears' bases (reference space) keep the mane short
61
- const MANE_FC = [HEAD_O[0], HEAD_O[1] - 0.035 * HS, HEAD_O[2] + 0.02 * HS];
62
- const EAR_BASES = (() => { const J = lionJoints(); return [J.earBaseL, [-J.earBaseL[0], J.earBaseL[1], J.earBaseL[2]]]; })();
63
- // the lower half of each pinna (base -> half way to the tip): the mane round it rises from the ear's own short fur
64
- const EAR_SEGS = (() => { const J = lionJoints(); return [1, -1].map((s) => { const a = [J.earBaseL[0] * s, J.earBaseL[1], J.earBaseL[2]]; const t = [J.earTipL[0] * s, J.earTipL[1], J.earTipL[2]]; return { a, d: [(t[0] - a[0]) * 0.55, (t[1] - a[1]) * 0.55, (t[2] - a[2]) * 0.55] }; }); })();
65
-
66
- // distance from the ear bases (the lower pinna's axis; behind them it counts 0.55 x)
67
- const earDist = (p) => {
68
- let dEar = 1;
69
- for (const { a, d } of EAR_SEGS) {
70
- const t = clamp(((p[0] - a[0]) * d[0] + (p[1] - a[1]) * d[1] + (p[2] - a[2]) * d[2]) / (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]), 0, 1);
71
- const q = [p[0] - a[0] - d[0] * t, p[1] - a[1] - d[1] * t, p[2] - a[2] - d[2] * t];
72
- dEar = Math.min(dEar, Math.hypot(q[0], q[1], Math.min(0, q[2]) * 0.55 + Math.max(0, q[2])));
73
- }
74
- return dEar;
75
- };
76
- // the longest hair at a distance d from the ear base (m): rising from the ear's own short fur at ~0.9 mm per mm to 25 mm within ~2.5 cm,
77
- // then the mane's full length (the fur-slope limit lets hair over 25 mm rise faster: a mane's bevel)
78
- // (the ears sit in the mane; capped over 5-10 cm round them, a maned male's crown hair stood apart from the cheek
79
- // ruff as a cap with the ears on its rim, mfront_*, m34_Lion_head_turned: one ruff from the cheeks over the crown, the pinnae in it)
80
- const earCap = (d) => (d < 0.026 ? 0.011 + 0.9 * Math.max(0, d - 0.008) : 1);
81
-
82
- export function lionCoat(ctx) {
83
- const { model, rig, pos, nrm, regionOf: partOf, regionNames, weights, nV, params } = ctx;
84
- const canineRegion = regionNames ? regionNames.indexOf('canine') : -1;
85
- const { BONES, AXIAL } = rig;
86
- const AX_LEN = weights.axialLengths;
87
- const R = rng(9173 + (params.coatSeed || 0));
88
- const C = palette(params);
89
- const juv = params.juv || 0;
90
- const male = params.sex === 'male' && !juv ? 1 : 0;
91
- const whiteV = params.variant === 'white';
92
- const maneDark = params.maneDark ?? 0.5;
93
- const maneK = params.mane || 0;
94
- const noseAge = params.noseDark ?? 0.3;
95
- const spotsK = juv ? 1 : (params.legSpots ?? 0.2); // faint rosettes (cub) / their trace on adult legs
96
- const hw = 1.12 + 0.02 * male - 0.04 * juv;
97
- const nOff = (params.coatSeed || 0) % 997;
98
- const { dominant, axialS, skinIndex, skinWeight, limbMember, limbWhich } = weights;
99
- const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
100
- const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
101
- const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8], sTip = AX_LEN[AX_LEN.length - 1];
102
- const tailLen = sTip - sTailBase;
103
- const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
104
- const nosePrims = model.prims.filter((p) => p.tag === 'nose');
105
- const caninePrims = model.prims.filter((p) => p.tag === 'canine');
106
- const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
107
- const manePrims = model.prims.filter((p) => p.tag === 'mane');
108
- const tuftPrims = model.prims.filter((p) => p.tag === 'tuft');
109
- const elbowPrims = model.prims.filter((p) => p.tag === 'elbowtuft');
110
- const jawPrims = model.forPart('jaw').filter((p) => p.tag !== 'canine');
111
- // the closed mouth's envelope (the jaw without its trough, and the trough filled): the head skin inside it is
112
- // the palate and the inside of the lips; the trough (the jaw's mouth carve) and the tongue
113
- const jawSolid = jawPrims.filter((p) => !p.carve && p.tag !== 'tongue');
114
- const mouthPrim = jawPrims.find((p) => p.tag === 'mouth' && p.carve);
115
- const tonguePrim = jawPrims.find((p) => p.tag === 'tongue');
116
- const envelope = (x, y, z) => Math.min(SDFModel.evalList(jawSolid, x, y, z), mouthPrim ? SDFModel.dist(mouthPrim, x, y, z) : 1);
117
- const bodyPrims = model.forPart('body');
118
- const headPrims = bodyPrims.filter((p) => p.bone === 'head' && !p.carve && p.tag !== 'canine' && p.tag !== 'mane');
119
- const earPrims = model.prims.filter((p) => p.tag === 'ear');
120
- const earInnerPrims = model.prims.filter((p) => p.tag === 'earinner');
121
- const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|femur|tibia|metatarsus|hpaw)/.test(b.name));
122
- const isPaw = BONES.map((b) => /^(fpaw|hpaw)/.test(b.name));
123
- const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw)/.test(b.name));
124
- const headLocal = (p) => [(p[0] - HEAD_O[0]) / hw / HS, (p[1] - HEAD_O[1]) / HS, (p[2] - HEAD_O[2]) / HS];
125
- // the pinna's frame: 0 at the base joint .. 1 at the tip joint, the bowl's facing direction, the distance to
126
- // the bowl (the ear's carve)
127
- const earFrame = (p) => {
128
- const S = p[0] > 0 ? 'L' : 'R';
129
- const eb = rig.J['earBase' + S], et = rig.J['earTip' + S];
130
- const eu = norm(sub(et, eb)), eL = len(sub(et, eb));
131
- const q = earPrims.find((e) => (e.P[0] > 0) === (p[0] > 0)) || earPrims[0];
132
- const inner = earInnerPrims.find((e) => (e.P[0] > 0) === (p[0] > 0));
133
- return { up: eu, ht: dot(sub(p, eb), eu) / eL, facing: [q.P[9], q.P[10], q.P[11]], dIn: inner ? SDFModel.dist(inner, p[0], p[1], p[2]) : 1 };
134
- };
135
- // the left eye's corners on the skin (face units): the liner's wing runs back from the outer one, the tear
136
- // line down from the inner one
137
- const EF = eyeFrameOf(EYE, HEAD_O, 1), halfW = Math.sqrt(EYE.R * EYE.R - EYE.d * EYE.d);
138
- const corners = [1, -1].map((g) => toFace(headLocal(add(add(EF.c, mul(EF.x, g * halfW)), mul(EF.z, EYE.r * 1.02)))));
139
- const eyeOut = Math.abs(corners[0][0]) > Math.abs(corners[1][0]) ? corners[0] : corners[1];
140
- const EM = FACE.E * HS; // metres per face unit
141
- // distance (face units) from f to the segment a -> a + d, and the position along it
142
- const seg = (f, a, d) => {
143
- const t = clamp(((f[0] - a[0]) * d[0] + (f[1] - a[1]) * d[1] + (f[2] - a[2]) * d[2]) / (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]), 0, 1);
144
- return [Math.hypot(f[0] - a[0] - d[0] * t, f[1] - a[1] - d[1] * t, f[2] - a[2] - d[2] * t), t];
145
- };
146
-
147
- // a rounded box's signed distance in the face plane (centre (0, cy), half sizes hx, hy, corner radius r) and a smooth union
148
- const sdRBox = (x, y, cy, hx, hy, r) => { const qx = Math.abs(x) - hx + r, qy = Math.abs(y - cy) - hy + r; return Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - r; };
149
- const sminP = (a, b, k) => { const h = clamp(0.5 + (0.5 * (b - a)) / k, 0, 1); return b + (a - b) * h - k * h * (1 - h); };
150
-
151
- // the lowest point of the head's surface in the column at (|X|, Z) (face units): the rim of the upper lip, which the
152
- // lip line follows (cached on a 0.01 E grid)
153
- const lipPrims = headPrims.filter((q) => { const c = toFace(headLocal([q.P[0], q.P[1], q.P[2]])); return c[1] < -0.3 && c[2] > -1.4; });
154
- const lipCache = new Map();
155
- const lipBottom = (ax, z) => {
156
- const i = Math.round(ax * 100), j = Math.round(z * 100), key = i * 1000 + j;
157
- if (lipCache.has(key)) return lipCache.get(key);
158
- let yb = null;
159
- const X = (i / 100) * FACE.X * hw * HS, Z = HEAD_O[2] + (FACE.EZ + (j / 100) * FACE.E) * HS;
160
- for (let Y = -1.75; Y <= -0.95; Y += 0.005) {
161
- if (SDFModel.evalList(lipPrims, HEAD_O[0] + X, HEAD_O[1] + (FACE.EY + Y * FACE.E) * HS, Z) < 0) { yb = Y; break; }
162
- }
163
- lipCache.set(key, yb);
164
- return yb;
165
- };
166
-
167
- // --- regions per vertex (0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw) and limb blend
168
- const region = new Uint8Array(nV);
169
- const legness = new Float32Array(nV);
170
- const legBone = new Int16Array(nV).fill(-1);
171
- const junction = new Float32Array(nV);
172
- const tailT = new Float32Array(nV);
173
- const maneW = new Float32Array(nV);
174
- const maneD = new Float32Array(nV).fill(1); // distance to the sculpted mane volume (m)
175
- for (let v = 0; v < nV; v++) {
176
- const bn = BONES[dominant[v]].name;
177
- const p = P(v);
178
- let lb = -1, lw = 0;
179
- for (let k = 0; k < 4; k++) {
180
- const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
181
- if (isLimb[b] && w > lw) { lw = w; lb = b; }
182
- }
183
- legBone[v] = lb;
184
- const m = limbMember[v];
185
- legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, m) : 0;
186
- junction[v] = lb >= 0 ? 4 * m * (1 - m) : 0;
187
- if (partOf[v] === 2) region[v] = 6;
188
- else if (bn.startsWith('ear')) region[v] = 5;
189
- else if (axialS[v] > sTailBase + 0.03 && bn.startsWith('tail')) { region[v] = 4; tailT[v] = clamp((axialS[v] - sTailBase) / tailLen, 0, 1); }
190
- else if (axialS[v] < sOcc + 0.01 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.03)) region[v] = 2;
191
- else if (axialS[v] < sNeckBase) region[v] = 1;
192
- else region[v] = 0;
193
- if (region[v] >= 2) { legness[v] = 0; junction[v] = 0; }
194
- // mane: skin on (or just outside) the sculpted mane volume
195
- if (manePrims.length && region[v] !== 4 && region[v] !== 5 && region[v] !== 6) {
196
- const dm = SDFModel.evalList(manePrims, p[0], p[1], p[2]);
197
- maneD[v] = dm;
198
- // (the edge broken by noise over the shoulders and the chest: a clean offset of the mane volume read
199
- // as a helmet with a hard rim)
200
- // (round the face the hair lengthens over ~7 cm: over 3.5 cm it stood as a hood edge showing its dark roots)
201
- // (over the forehead the short face fur runs up to the ear line, face_male1 / threequarter_youngmale1: a 7 cm
202
- // margin there drew the crest forward over the brow as a hood edge and hid the ears)
203
- const edge = p[2] < 0.66 ? 0.025 + 0.06 * fbm3(p[0] * 9 + nOff, p[1] * 9, p[2] * 9, 2) + 0.05 * vnoise3(p[0] * 22 + nOff, p[1] * 22, p[2] * 22) : mix(0.07, 0.012, smoothstep(0.15, 0.55, nrm[v * 3 + 1]));
204
- maneW[v] = smoothstep(edge, -0.004, dm) * (1 - legness[v] * 0.8);
205
- }
206
- }
207
- smoothField(maneW, weights.neighbors, 3);
208
- // the face's relief in its paint (a smooth swollen dome, a plush toy, worst in the showcase's soft light and
209
- // on the pale white lion): an occlusion term from the head's own field (how far the skin a few mm to 2.5 cm out along the normal lies
210
- // inside the head), so the creases round the eyes, under the brows, beside the bridge, between the pads and the cheeks and under
211
- // the jaw shade darker as the hair does in the photos (ffront_*, white_Flickr_*: the structure reads in the coat's shading)
212
- const cav = new Float32Array(nV), curv = new Float32Array(nV);
213
- {
214
- const near = (q) => Math.hypot(q.P[0] - HEAD_O[0], q.P[1] - HEAD_O[1], q.P[2] - HEAD_O[2]) < 0.42;
215
- const cavBody = model.prims.filter((q) => q.part === 'body' && near(q) && q.tag !== 'canine' && q.tag !== 'lipedge' && q.tag !== 'mane'); // (not the mane's volume: hair, not a surface that shades the face)
216
- const cavJaw = model.forPart('jaw').filter((q) => q.tag !== 'tongue' && q.tag !== 'canine' && !(q.carve && q.tag === 'mouth'));
217
- const fld = (x, y, z) => Math.min(SDFModel.evalList(cavBody, x, y, z), SDFModel.evalList(cavJaw, x, y, z));
218
- const H = [0.005, 0.011, 0.02, 0.032, 0.046], Wt = [0.3, 0.26, 0.2, 0.14, 0.1];
219
- for (let v = 0; v < nV; v++) {
220
- if (region[v] !== 2 && region[v] !== 6) continue;
221
- if (partOf[v] === canineRegion) continue;
222
- const p = P(v), n = N(v);
223
- let o = 0;
224
- for (let i = 0; i < H.length; i++) { const h = H[i]; o += Wt[i] * Math.max(0, h - fld(p[0] + n[0] * h, p[1] + n[1] * h, p[2] + n[2] * h)) / h; }
225
- cav[v] = clamp(o, 0, 1);
226
- // the field's Laplacian over ~1.6 cm (twice the mean curvature, 1/m: > 0 on ridges and bumps, < 0 in creases and hollows)
227
- const hL = 0.016, f0 = fld(p[0], p[1], p[2]);
228
- curv[v] = (fld(p[0] + hL, p[1], p[2]) + fld(p[0] - hL, p[1], p[2]) + fld(p[0], p[1] + hL, p[2]) + fld(p[0], p[1] - hL, p[2]) + fld(p[0], p[1], p[2] + hL) + fld(p[0], p[1], p[2] - hL) - 6 * f0) / (hL * hL);
229
- }
230
- smoothField(cav, weights.neighbors, 2);
231
- smoothField(curv, weights.neighbors, 2);
232
- }
233
- if (globalThis.process?.env?.LION_CAV) { const acc = {}; for (let v = 0; v < nV; v++) { if (region[v] !== 2) continue; const f = toFace(headLocal(P(v))); if (f[1] < -1.0 && N(v)[1] < -0.3) continue; const k = `${(Math.round(Math.abs(f[0]) * 4) / 4).toFixed(2)},${(Math.round(f[1] * 4) / 4).toFixed(2)}`; if (f[2] < Math.abs(f[0]) * 0 - 0.6) continue; (acc[k] ||= []).push(globalThis.process.env.LION_CAV === 'curv' ? curv[v] : cav[v]); } const rows = {}; for (const [k, a] of Object.entries(acc)) { const [x, y] = k.split(','); a.sort((p, q) => p - q); (rows[y] ||= {})[x] = a[Math.floor(a.length * 0.5)].toFixed(globalThis.process.env.LION_CAV === 'curv' ? 0 : 2); } for (const y of Object.keys(rows).sort((a, b) => b - a)) console.log(y.padStart(6), Object.keys(rows[y]).sort((a, b) => a - b).map((x) => x + ':' + rows[y][x]).join(' ')); }
234
- // head / neck paint blend: across the boundary of the head's paint region (3 cm past the neck cut, or the
235
- // occiput's axial station) the head's colour, hair length and flow hand over to the neck's over ~12 cm (a
236
- // hard switch drew a seam line down each side of a lioness's neck in the shells)
237
- const hbw = new Float32Array(nV);
238
- for (let v = 0; v < nV; v++) {
239
- const r = region[v];
240
- if (r === 6) { hbw[v] = 1; continue; }
241
- if (r !== 1 && r !== 2) continue;
242
- const p = P(v);
243
- hbw[v] = Math.max(smoothstep(-0.03, 0.09, neckS(p[0], p[1], p[2])), smoothstep(sOcc + 0.06, sOcc - 0.04, axialS[v]));
244
- }
245
- // chest fringe (dark-maned males): 20-25 cm of hair on the brisket below the mane's bib, falling between
246
- // the forelegs (side_walk_male4, front_walk_male2, threequarter_male2)
247
- const fringe = new Float32Array(nV);
248
- if (male && maneK > 0) {
249
- const fk = smoothstep(0.35, 0.85, maneDark) * smoothstep(0.4, 0.8, maneK);
250
- for (let v = 0; v < nV && fk > 0; v++) {
251
- if (region[v] > 1) continue;
252
- const p = P(v), n = N(v);
253
- fringe[v] = fk * smoothstep(0.18, 0.3, p[2]) * smoothstep(0.78, 0.66, p[1]) * smoothstep(0.43, 0.52, p[1]) * smoothstep(0.13, 0.04, Math.abs(p[0])) * (1 - legness[v]) * smoothstep(0.2, -0.3, n[1]);
254
- }
255
- smoothField(fringe, weights.neighbors, 2);
256
- }
257
- const underFactor = (v) => {
258
- const n = N(v), p = P(v), li = limbWhich[v];
259
- if (li < 0 || junction[v] <= 0) return 0;
260
- if (li <= 1) return junction[v] * Math.max(smoothstep(0.15, -0.55, n[2]) * smoothstep(0.66, 0.52, p[1]), smoothstep(0.2, -0.6, n[1]));
261
- return junction[v] * smoothstep(0.35, -0.45, n[1] + 0.35 * Math.abs(n[2]));
262
- };
263
-
264
- // --- ventral (pale underside) factor
265
- const ventral = new Float32Array(nV);
266
- const ventralNeck = new Float32Array(nV); // (the neck's own formula, for the head / neck blend)
267
- for (let v = 0; v < nV; v++) {
268
- const p = P(v), n = N(v), r = region[v];
269
- let w = 0;
270
- if (r === 0) {
271
- // (the underside only: from the lower flank on it read as a cream lower third of the body)
272
- w = smoothstep(-0.25, -0.9, n[1]); // (a soft transition: seen edge-on from the side a narrow one read as a light seam)
273
- if (p[2] > 0.3) w = Math.max(w, smoothstep(0.7, 0.52, p[1]) * smoothstep(0.1, 0.045, Math.abs(p[0])) * smoothstep(-0.3, 0.4, n[2]));
274
- w *= smoothstep(0.8, 0.62, p[1]) * 0.55 + 0.45;
275
- } else if (r === 1 || r === 2) {
276
- const wN = smoothstep(0.1, -0.5, n[1]) * smoothstep(-0.4, 0.3, n[2]) + smoothstep(0.78, 0.66, p[1]) * 0.5;
277
- const f = toFace(headLocal(p));
278
- const wH = smoothstep(-1.13, -1.49, f[1]) * smoothstep(-0.2, -0.7, n[1]);
279
- ventralNeck[v] = clamp(wN, 0, 1);
280
- w = mix(wN, wH, hbw[v]);
281
- } else if (r === 4) {
282
- w = 0;
283
- } else if (r === 6) {
284
- const f = toFace(headLocal(p));
285
- w = smoothstep(-1.13, -1.49, f[1]) * smoothstep(-0.2, -0.7, n[1]) + 0.6;
286
- }
287
- const L = legness[v];
288
- if (L > 0) {
289
- const side = p[0] >= 0 ? 1 : -1;
290
- const inner = smoothstep(0.1, -0.6, n[0] * side);
291
- const wl = inner * 0.75 * smoothstep(0.12, 0.4, p[1]) + smoothstep(-0.3, -0.85, n[1]) * 0.5;
292
- w = mix(w, wl, L);
293
- }
294
- w = Math.max(w, underFactor(v) * 0.95);
295
- // (maned males: no pale bib just below the mane: the lower chest there is mane-coloured in the photos)
296
- if (male && r <= 1) w *= 1 - smoothstep(0.15, 0.02, maneD[v]) * smoothstep(0.25, 0.6, p[2]);
297
- ventral[v] = clamp(w, 0, 1);
298
- }
299
-
300
- // the face mask (face units: the front view's half width at height Y): short fur inside it; outside it, on the sides of the
301
- // head, the cheek ruff: the long hair of the "sideburns" that makes a lion's head broad at the eyes (face_female1: +-1.16 E
302
- // at the eye line with the fur, the skull +-1.05) and, on maned males, the pale front of the mane framing the face
303
- // (face_male1: long cream hair from +-0.95 E at the eyes and +-0.75 E beside the whisker pads; a short-furred face out to
304
- // +-1.35 E read as a suede plate set in the mane)
305
- // (a male's face is framed tighter: the pale ruff starts just outside the whisker pads, face_male1)
306
- const MASK = male ? [[0.7, 0.74], [0.3, 0.8], [0.0, 0.78], [-0.3, 0.71], [-0.6, 0.64], [-0.9, 0.6], [-1.2, 0.57], [-1.5, 0.5], [-1.8, 0.44]] // (0.06 E further in, face_male1: the ruff from +-0.95 E at the eyes with its hair)
307
- : [[0.7, 0.88], [0.3, 0.92], [0.0, 0.92], [-0.5, 0.84], [-0.9, 0.72], [-1.2, 0.64], [-1.5, 0.56], [-1.8, 0.5]]; // (the lioness's sideburns from closer in: her outline was 9 % narrow at the jowls)
308
- const maskHalf = (Y) => {
309
- if (Y >= MASK[0][0]) return MASK[0][1];
310
- for (let i = 1; i < MASK.length; i++) if (Y >= MASK[i][0]) { const [y0, w0] = MASK[i - 1], [y1, w1] = MASK[i]; return w1 + ((w0 - w1) * (Y - y1)) / (y0 - y1); }
311
- return MASK[MASK.length - 1][1];
312
- };
313
- // 0 on the face .. 1 on the ruff, over ~0.25 E (~3 cm); behind the eyes the side of the head is ruff whatever its X
314
- const ruffOf = (f) => Math.max(male ? smoothstep(-0.14, 0.3, Math.abs(f[0]) - maskHalf(f[1])) : smoothstep(-0.1, 0.32, Math.abs(f[0]) - maskHalf(f[1])), smoothstep(-0.75, -1.05, f[2]) * smoothstep(0.35, 0.6, Math.abs(f[0])) * smoothstep(0.75, 0.45, f[1]));
315
- const ruffLen = male ? 0.035 + 0.07 * maneK : 0.023; // (a lioness: under the 24 mm at which fins start, render finMinLen)
316
- // the ruff's hair length at a head point: up to 25 mm no faster than ~1 mm per mm of skin (over ~0.3 E), the long part
317
- // of a male's ruff after that (the fur-slope limit lets hair over 25 mm rise faster; rising 9 -> 75 mm over 4 cm in one
318
- // ramp left walls 2.4 mm/mm steep where the limit cut it below 25 mm)
319
- const ruffFl = (f, lock, jit = 0) => {
320
- const t = Math.abs(f[0]) - maskHalf(f[1]) + jit;
321
- const behind = smoothstep(-0.75, -1.05, f[2]) * smoothstep(0.35, 0.6, Math.abs(f[0])) * smoothstep(0.75, 0.45, f[1]);
322
- // (a male's long stage over ~0.5 E with a ragged inner edge (jit): over 0.32 E along a smooth line the ruff's
323
- // foot stood as the rim of a mask round the face)
324
- const u1 = Math.max(male ? smoothstep(-0.2, 0.32, t) : smoothstep(-0.1, 0.22, t), behind), u2 = Math.max(male ? smoothstep(0.02, 0.42, t) : smoothstep(0.08, 0.4, t), behind); // (a male's long stage from just outside the face (from 0.12-0.7 E out the ruff started behind the cheek with an edge, a mask set in a hood)
325
- // (a lioness's ruff stays one 2.3 cm stage: 3.4 cm at the jowls widened her outline to face_female1's 1.26-1.31 E but stood
326
- // as two puffs beside the mouth, a hamster's cheeks again)
327
- return Math.max(Math.min(ruffLen, 0.025) * u1, male ? ruffLen * lock * u2 : 0);
328
- };
329
- // (a male's pale only in a band round the face, ~0.45 E: beyond it the mane's own colour, dark on a dark mane; the whole
330
- // side of the head pale turned a black-fringed mane blond)
331
- const ruffCol = male ? mix3(C.chin, C.maneBlond, 0.68 + 0.22 * maneDark) : mix3(C.face, C.eyePatch, 0.2);
332
- const ruffPale = (f) => ruffOf(f) * (male ? 1 - smoothstep(0.3, 0.65, Math.abs(f[0]) - maskHalf(f[1])) : 1);
333
- // hair flows out from the nose over the face, back over the crown and down the cheeks
334
- const headFlow = (p) => {
335
- const f = toFace(headLocal(p));
336
- const fromNose = norm(sub(f, [0, -0.9, 1.15]));
337
- let d = norm(add(fromNose, [0, -0.2, -0.55]));
338
- if (f[2] < -0.27 && Math.abs(f[0]) > 0.6) d = norm(add(d, [Math.sign(f[0]) * 0.5, -0.5, -0.7]));
339
- return d;
340
- };
341
- // --- comb (hair flow), bind space
342
- const comb = new Float32Array(nV * 3);
343
- for (let v = 0; v < nV; v++) {
344
- const p = P(v), n = N(v), r = region[v];
345
- let d;
346
- const bone = BONES[dominant[v]];
347
- if (r === 5) {
348
- const E5 = earFrame(p);
349
- d = norm(add(E5.up, mul(E5.facing, 0.6 * smoothstep(0.01, -0.003, E5.dIn))));
350
- } else if (r === 6 || (r === 2 && hbw[v] >= 1)) {
351
- d = headFlow(p);
352
- // the chin's tuft and the throat under it: hanging down (the chin's white hair falls over the crease under the jaw)
353
- const fc = toFace(headLocal(p));
354
- const hang = partOf[v] === 2 ? smoothstep(-1.36, -1.46, fc[1]) : smoothstep(-1.3, -1.5, fc[1]) * smoothstep(0.2, -0.5, n[1]);
355
- if (hang > 0) d = norm(add(mul(d, 1 - hang), mul(norm([0, -1, -0.35]), hang)));
356
- // (under the jaw the hair runs straight back to the throat: hanging "down" projected on the rounded underside fanned
357
- // out into a herringbone at the midline)
358
- if (partOf[v] === 2) { const ub = smoothstep(-0.45, -0.8, n[1]); if (ub > 0) d = norm(add(mul(d, 1 - ub), mul(norm([0, -0.25, -1]), ub))); }
359
- // the cheek ruff flares out and falls back (the sideburns; a male's pale ruff framing the face)
360
- const rwc = partOf[v] === 2 ? 0 : ruffOf(fc);
361
- if (rwc > 0) d = norm(add(mul(d, 1 - 0.75 * rwc), mul(norm([Math.sign(fc[0]) * 0.55, -0.5, -0.7]), 0.75 * rwc)));
362
- // (a maned male's crown hair: back and a little out over the crown, standing up from the hairline)
363
- if (male && maneK > 0 && partOf[v] !== 2) { const cwc = smoothstep(0.45, 0.85, fc[1]) * smoothstep(0.35, -0.15, fc[2]); if (cwc > 0) { const lk = fbm3(p[0] * 16 + 3.1 + nOff, p[1] * 16, p[2] * 16, 2) - 0.5, lk2 = fbm3(p[0] * 16 + 9.7 + nOff, p[1] * 16, p[2] * 16, 2) - 0.5; d = norm(add(mul(d, 1 - cwc), mul(norm([Math.sign(fc[0]) * 0.35 * Math.min(1, Math.abs(fc[0])) + 1.0 * lk, 0.1 + 0.5 * lk2, -1]), cwc))); } } // (laid back from a lower hairline, not standing up at it: a roll over the forehead)
364
- } else {
365
- const seg = weights.segT[v * 2] | 0;
366
- const a = AXIAL.points[seg], b = AXIAL.points[Math.min(seg + 1, AXIAL.points.length - 1)];
367
- d = norm(sub(b, a));
368
- if (r === 0) d = norm(add(d, [0, -0.4 * Math.abs(n[0]), 0]));
369
- if (r === 1 || r === 2) d = norm(add(d, [0, -0.45, 0]));
370
- if ((r === 1 || r === 2) && hbw[v] > 0) d = norm(add(mul(d, 1 - hbw[v]), mul(headFlow(p), hbw[v])));
371
- const L = legness[v];
372
- if (L > 0 && legBone[v] >= 0) {
373
- const lbn = BONES[legBone[v]];
374
- let dl = norm(sub(lbn.tail, lbn.head));
375
- if (isPaw[legBone[v]]) dl = norm(add(dl, [0, 0, 0.4]));
376
- else dl = norm(add(dl, [0, -0.2, -0.3]));
377
- d = norm(add(mul(d, 1 - L), mul(dl, L)));
378
- }
379
- }
380
- // the mane falls back and down from the face ruff, and hangs down the chest
381
- const mw = maneW[v];
382
- if (mw > 0) {
383
- // the ruff radiates out from the face and then falls back and down; the crest flows back over the
384
- // crown; over the neck and shoulders it hangs back and down (one comb everywhere read as a helmet)
385
- const down = norm(add([0, -0.9, -0.55], mul(n, 0.35)));
386
- // (swept back as well as out: hair laid radially outward over the ruff's front read as a sunflower, a hood
387
- // round the face; the photos' ruff flows back from the cheeks)
388
- const radial = norm(add(norm(sub(p, MANE_FC)), [0, -0.3, -0.95]));
389
- const crest = norm([0, -0.25, -1]);
390
- const ruffW = smoothstep(0.56, 0.7, p[2]) * (1 - smoothstep(0.35, 0.85, n[1]));
391
- const crestW = smoothstep(0.3, 0.8, n[1]) * smoothstep(0.5, 0.66, p[2]);
392
- let dmn = norm(add(add(mul(down, 1 - ruffW - crestW * (1 - ruffW)), mul(radial, ruffW)), mul(crest, crestW * (1 - ruffW))));
393
- // locks: the direction wanders a little from lock to lock
394
- // (+-25 deg between clumps a few cm across: +-19 deg at 9 cm left the outline smooth)
395
- const lk = fbm3(p[0] * 16 + 3.1 + nOff, p[1] * 16, p[2] * 16, 2) - 0.5, lk2 = fbm3(p[0] * 16 + 9.7 + nOff, p[1] * 16, p[2] * 16, 2) - 0.5;
396
- dmn = norm(add(dmn, [1.0 * lk, 0.55 * lk2, 0.45 * (lk + lk2)]));
397
- d = norm(add(mul(d, 1 - mw), mul(dmn, mw)));
398
- }
399
- if (fringe[v] > 0) d = norm(add(mul(d, 1 - fringe[v]), mul([0, -1, 0.15], fringe[v])));
400
- let t = sub(d, mul(n, dot(d, n)));
401
- if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
402
- t = norm(t);
403
- comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
404
- }
405
-
406
- // --- faint rosettes and spots: cubs (legs and belly strongest), a trace on adult lower legs
407
- const spotR = (v) => {
408
- if (spotsK <= 0.01) return 0;
409
- const r = region[v], p = P(v);
410
- if (maneW[v] > 0.3) return 0;
411
- if (r === 0 || r === 1) {
412
- const L = legness[v];
413
- if (!juv && L < 0.5) return 0;
414
- // (adults: only on the forearm and shin, clear of the joints, where the skin barely stretches)
415
- if (!juv && (p[1] > 0.34 || p[1] < 0.17 || legBone[v] < 0 || !/^(radius|tibia)/.test(BONES[legBone[v]].name))) return 0;
416
- return mix(0.024, 0.018, L) * (juv ? 1.6 : 1);
417
- }
418
- if (r === 2 && juv) {
419
- const h = headLocal(p);
420
- if (h[2] > 0.04 || h[1] < -0.05) return 0;
421
- return 0.01;
422
- }
423
- return 0;
424
- };
425
- const splitS = splitFor(params);
426
- const skipOverlap = (v) => {
427
- const p = P(v);
428
- return (partOf[v] === 1 && splitS(p[0], p[1], p[2]) < 0) || (partOf[v] === 0 && splitS(p[0], p[1], p[2]) > 0);
429
- };
430
- const SP = poissonFeatures({ pos, nrm, nV, radius: spotR, R, spacing: 1.7, gap: 0.006, cell: 0.1, skip: skipOverlap, extra: (v, RR) => ({ c: [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]], el: 1.1 + 0.3 * RR(), ring: RR() < 0.55 && juv ? 1 : 0, pale: smoothstep(0.4, 0.9, ventral[v]) }) });
431
- // whisker-base dots: 4 slightly curved rows of small black spots on the upper half of each pad
432
- // (face_female1: rows 0.93-1.19 E below the eye line, from beside the nose out to ~0.45 E), jittered,
433
- // on the pad's surface; the whiskers grow from the upper three rows (whiskers.js); [x, y, z, radius]
434
- const whiskerDots = whiskerFollicles(params.coatSeed || 0).map((f) => [...follicleRoot(headPrims, f, hw), f.r]);
435
-
436
- // --- per-vertex paint
437
- const furLen = new Float32Array(nV);
438
- const tint = new Float32Array(nV * 4);
439
- const markSDF = new Float32Array(nV).fill(0.03);
440
- const pattern = new Float32Array(nV).fill(0.05);
441
- const patternColor = new Float32Array(nV * 4);
442
- const surf = new Float32Array(nV * 4);
443
- 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));
444
- // (the tail's dorsal side from its bones' directions blended by the skin weights: from the dominant bone alone it
445
- // jumped at every joint, rings from behind)
446
- const tailDorsal = (v) => {
447
- let d = [0, 0, 0];
448
- for (let k = 0; k < 4; k++) {
449
- const w = skinWeight[v * 4 + k];
450
- if (w <= 0) continue;
451
- const bone = BONES[skinIndex[v * 4 + k]];
452
- d = add(d, mul(norm(sub(bone.tail, bone.head)), w));
453
- }
454
- if (len(d) < 1e-6) { const bone = BONES[dominant[v]]; d = sub(bone.tail, bone.head); }
455
- d = norm(d);
456
- return norm([0, -d[2], d[1]]);
457
- };
458
-
459
- // the neck's and the head's base paint (colour, hair length, agouti) for the blend across their boundary
460
- const neckBase = (v, p, n, nz) => {
461
- const up = n[1], vn = ventralNeck[v];
462
- let c = mix3(C.lowFlank, C.flank, smoothstep(-0.35, 0.3, up + 0.15 * nz));
463
- c = mix3(c, C.dorsal, smoothstep(0.35, 0.95, up + 0.2 * nz) * 0.85);
464
- c = mix3(c, C.belly, vn);
465
- c = mix3(c, C.chin, vn * smoothstep(sOcc + 0.22, sOcc + 0.04, axialS[v]) * 0.8);
466
- let f = mix(0.016, 0.022, smoothstep(0.3, 0.9, up));
467
- f = mix(f, 0.03, smoothstep(0.35, 0.9, vn) * 0.5);
468
- return [c, f, 0.15 * smoothstep(0.2, 0.9, up)];
469
- };
470
- // cubs: how far a head point lies out of the face (0 on the face .. 1 behind the ears, on the crown and the throat), in
471
- // face units: the woolly coat grows in over ~0.8 E (~6 cm on a cub)
472
- const cubWool = (p) => {
473
- const f = toFace(headLocal(p));
474
- const q = Math.hypot(f[0], (f[1] + 0.4) / 1.1, Math.min(0, f[2] - 0.2) / 1.1);
475
- return smoothstep(0.72, 1.5, q);
476
- };
477
- const headBase = (p, n) => {
478
- const f = toFace(headLocal(p)), ax = Math.abs(f[0]);
479
- let c = mix3(C.face, C.crown, smoothstep(-0.15, 0.56, f[1]) * 0.6);
480
- c = mix3(c, C.muzzle, clamp(smoothstep(-1.3, -1.55, f[1]) * smoothstep(0.2, -0.45, n[1]) * 0.9, 0, 1));
481
- let fl = mix(0.012, 0.0075, smoothstep(-0.6, 0.02, f[2]));
482
- fl = mix(fl, 0.016, smoothstep(0.6, 1.0, ax) * smoothstep(0.09, -0.45, f[2]));
483
- const rw = ruffOf(f);
484
- fl = Math.max(fl, ruffFl(f, 1));
485
- c = mix3(c, ruffCol, ruffPale(f) * 0.7);
486
- return [c, fl];
487
- };
488
-
489
- for (let v = 0; v < nV; v++) {
490
- if (partOf[v] === canineRegion) {
491
- // the upper canines: ivory, a little yellower at the root
492
- const f = toFace(headLocal(P(v)));
493
- const col = mix3(C.tooth, mix3(C.tooth, C.face, 0.35), smoothstep(-1.32, -1.1, f[1]));
494
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = MAT.KERATIN;
495
- furLen[v] = 0; markSDF[v] = 0.03; surf[v * 4] = 0.7;
496
- continue;
497
- }
498
- const p = P(v), n = N(v), r = region[v];
499
- let col, fl = 0.02, ag = 0, mat = MAT.FUR, mark = 1, under = 0.5;
500
- const nz = fbm3(p[0] * 5 + nOff, p[1] * 5, p[2] * 5, 3) - 0.5;
501
- const nz2 = vnoise3(p[0] * 30 + nOff, p[1] * 30, p[2] * 30) - 0.5;
502
- let spotI = 0, dotI = 0, lidSkin = 0;
503
- if (r === 0 || r === 1) {
504
- const up = n[1];
505
- col = mix3(C.lowFlank, C.flank, smoothstep(-0.35, 0.3, up + 0.15 * nz));
506
- col = mix3(col, C.dorsal, smoothstep(0.35, 0.95, up + 0.2 * nz) * 0.85);
507
- // a faint darker line along the spine from the withers to the tail root (it marks the back of a lion lying
508
- // on its side; a smooth sausage from behind)
509
- col = mix3(col, C.crown, smoothstep(0.05, 0.012, Math.abs(p[0])) * smoothstep(0.88, 0.97, up) * smoothstep(0.35, 0.2, p[2]) * smoothstep(-0.8, -0.68, p[2]) * 0.55);
510
- ag = 0.15 * smoothstep(0.2, 0.9, up);
511
- col = mix3(col, C.belly, ventral[v]);
512
- // the white of the chin carries on down the upper throat
513
- if (r === 1) col = mix3(col, C.chin, ventral[v] * smoothstep(sOcc + 0.22, sOcc + 0.04, axialS[v]) * 0.8);
514
- // sleek short body coat; slightly longer belly fringe and along the spine
515
- fl = mix(0.016, 0.022, smoothstep(0.3, 0.9, up));
516
- fl = mix(fl, 0.03, smoothstep(0.35, 0.9, ventral[v]) * (r === 0 ? 1 : 0.5));
517
- const L = legness[v];
518
- if (L > 0) {
519
- const lb = legBone[v];
520
- let lc = mix3(C.legOuter, C.legInner, smoothstep(0.1, 0.7, ventral[v]));
521
- lc = mix3(lc, col, smoothstep(0.45, 0.65, p[1]) * 0.6);
522
- let lf = mix(0.008, 0.016, smoothstep(0.1, 0.5, p[1]));
523
- // "breeches": slightly longer hair on the back of the thighs
524
- if (lb >= 0 && !isFront[lb]) lf = Math.max(lf, 0.024 * smoothstep(-0.1, -0.7, n[2]) * smoothstep(0.35, 0.6, p[1]));
525
- if (lb >= 0 && isPaw[lb]) { lf = 0.006; lc = C.paw; }
526
- col = mix3(col, lc, L);
527
- fl = mix(fl, lf, L);
528
- }
529
- } else if (r === 4) {
530
- const t = tailT[v];
531
- const dors = dot(n, tailDorsal(v));
532
- // (only a little paler underneath, the photos; a cream underside read as a sharp stripe)
533
- col = mix3(mix3(C.flank, C.belly, 0.4), C.flank, smoothstep(-0.7, 0.3, dors + 0.3 * nz));
534
- col = mix3(col, C.dorsal, smoothstep(0.3, 0.9, dors) * 0.5);
535
- // the tail darkens toward the tuft, which is black
536
- col = mix3(col, C.tailTip, smoothstep(0.6, 0.85, t) * 0.6);
537
- const tuftD = tuftPrims.length ? SDFModel.evalList(tuftPrims, p[0], p[1], p[2]) : 1;
538
- const tuft = Math.max(smoothstep(0.012, -0.004, tuftD), smoothstep(0.86, 0.93, t + 0.02 * nz));
539
- col = mix3(col, C.tuft, tuft);
540
- fl = mix(0.014, 0.018, smoothstep(0.1, 0.6, t));
541
- fl = mix(fl, 0.065 * (1 - 0.5 * juv), tuft);
542
- under = 0.25;
543
- } else if (r === 5) {
544
- const E5 = earFrame(p);
545
- const front = dot(n, E5.facing), ht = E5.ht;
546
- // the front: a tawny rim round a shallow bowl filled with long pale hair rising from its lower part,
547
- // the skin darker only deep in the bottom of the bowl (face_female1; a dark hollow cup with a hard
548
- // edge read as a mouse's ear, and drew jagged dark polygons at the medium tier)
549
- const bowl = smoothstep(0.012, -0.003, E5.dIn) * smoothstep(-0.1, 0.45, front);
550
- let fc = mix3(C.face, C.earInner, bowl * 0.9);
551
- // (the bowl shaded: pale hair at its top and outer side, the skin darker deep in its lower middle (face_female1 /
552
- // face_male1); a uniformly pale bowl read as a mouse's ear; a hard-edged dark cup at 0.45 drew dark
553
- // crack lines at the medium tier, so the shading is a wide soft gradient of the bowl's own weight)
554
- // (a male's bowl filled with pale hair, face_male1: the grey cup read as a flat grey plate)
555
- fc = mix3(fc, C.earCup, bowl * Math.exp(-(((ht - 0.3) / 0.16) ** 2)) * smoothstep(0.35, 0.85, front) * 0.42 * (1 - 0.7 * male));
556
- // (a male's bowl: deep and shaded, its long hair tawny over dark roots, face_male1; the grey cup read as a flat grey plate)
557
- if (male) fc = mix3(fc, mix3(C.earCup, C.maneBlond, 0.5), bowl * 0.75);
558
- // (16-19 mm: 22 mm of hair rising from the 7 mm rim stood as a wall round the bowl)
559
- const ff = mix(0.007, 0.019 + 0.003 * male, bowl * smoothstep(0.78, 0.3, ht));
560
- // the back and the rim: black (a tawny patch low in the centre of the back), tawny at the base
561
- let bc = mix3(C.earBackCentre, C.earBack, 0.55 + 0.45 * (params.earDark ?? 1));
562
- bc = mix3(bc, C.earBackCentre, Math.exp(-(((ht - 0.3) / 0.2) ** 2)) * smoothstep(-0.2, -0.75, front) * 0.55);
563
- // (the black starts behind the rim: the rim itself pale like the front (face_female1); from front 0.3 the coarse
564
- // medium mesh's rim vertices alternated front / back and drew the rim as black dashes)
565
- const backW = smoothstep(-0.12, -0.48, front) * smoothstep(0.08, 0.22, ht); // (from -0.12: from 0.02 the rim drew a dotted dark line at medium)
566
- col = mix3(fc, bc, backW);
567
- // a maned male's ear rim: the dark of the back wraps over the edge of the upper pinna (face_male1: dark-rimmed ears)
568
- if (male) col = mix3(col, bc, smoothstep(0.32, 0.04, front) * (1 - bowl) * smoothstep(0.3, 0.55, ht) * (0.35 + 0.45 * (params.earDark ?? 1)) * (1 - backW));
569
- col = mix3(col, C.crown, smoothstep(0.16, 0.04, ht));
570
- fl = mix(ff, 0.006 + 0.004 * male, backW); // (a male's ear backs furred, face_male1)
571
- under = 0.3;
572
- if (male) {
573
- // a male's pinna: its whole front furred in tawny hair over dark roots, darker deep in the middle (face_male1: a
574
- // furry ear with a shadowed bowl; short pale suede read as a flat grey plate)
575
- const fw = smoothstep(-0.05, 0.35, front) * (1 - backW);
576
- const deep = Math.exp(-(((ht - 0.36) / 0.22) ** 2)) * smoothstep(0.016, -0.003, E5.dIn) * smoothstep(0.3, 0.8, front);
577
- col = mix3(col, mix3(mix3(C.maneBlond, C.face, 0.2), mix3(C.earCup, C.earBack, 0.5), 0.15 + 0.75 * deep), fw);
578
- fl = mix(fl, 0.017 * smoothstep(0.92, 0.55, ht) + 0.004, fw);
579
- under = mix(under, 0.12, fw * (0.4 + 0.6 * deep));
580
- }
581
- fl = mix(fl, 0.009 + 0.003 * maneK, smoothstep(0.14, 0.02, ht));
582
- } else {
583
- // ---------------- head & jaw (face units: rig.js FACE, E = the eye separation from the eye midpoint)
584
- const h = headLocal(p);
585
- const f = toFace(h), ax = Math.abs(f[0]);
586
- col = C.face;
587
- col = mix3(col, C.crown, smoothstep(-0.15, 0.56, f[1]) * 0.6);
588
- // white whisker pads (flanking the nose, below the level of the leather), upper lip and chin, a
589
- // narrow pale crescent under each eye and a pale brow; the bridge and the upper muzzle stay tawny
590
- // (face_female1, face_male1: the whole lower face read pale grey-white)
591
- const padLow = smoothstep(-0.84, -1.02, f[1]);
592
- // (the pads white over their lower two thirds, face_female1 / face_male1: they read tan)
593
- // (white right down to the lip line: fading out below the pads' middle, their lower edge was tan over the white chin, which
594
- // then read as a white lump of a lower lip)
595
- const whisker = partOf[v] !== 2 ? g3(f, [0.29, Math.max(f[1], -1.16), 0.78], [0.34, 0.26, 0.5]) * padLow : 0; // (the white pads reach back along the lip, f34_*, ffront_*)
596
- const lip = g3(f, [0.0, -1.3, 1.0], [0.3, 0.14, 0.24]);
597
- // (the pale under the eye is a thin crescent along the lower lid, added with the lid margins below; a soft
598
- // blob 0.14 E tall read as a white smudge)
599
- const underEye = smoothstep(0.12, 0.5, g3(f, [0.47, -0.21, 0.1], [0.25, 0.105, 0.45])) * 0.95; // (bigger: the eye's frame of pale fur makes the eye read as big as the photos' (ffront_*, f34_*)) // (bigger and whiter, the face read as one flat colour in the showcase's shade) // (the white patches below the eyes)
600
- const aboveEye = smoothstep(0.15, 0.6, g3(f, [0.49, 0.15, 0.1], [0.17, 0.055, 0.42])) * 0.6; // (a pale band over the upper lid, under the brow; 0.38 left the eye small and dark)
601
- // (the chin's white only on its front and underside, +-0.25 E, no brighter than the pads; the jaw's sides the
602
- // jowl's cream-tawny: the whole jaw painted white read as a pale pacifier under the mouth)
603
- const chinG = Math.exp(-((ax / 0.3) ** 2));
604
- const chinW = partOf[v] === 2 ? 0.95 * chinG * Math.max(smoothstep(0.05, 0.5, f[2]), smoothstep(-0.35, -0.8, n[1]) * smoothstep(-0.45, 0.25, f[2])) : 0;
605
- // (the white chin carries on down the throat: the jaw's white ended at a hard edge on a tawny throat)
606
- // (the throat and the jaw's underside pale cream (the pale runs into the throat over a few cm), the white itself
607
- // only under the chin's front)
608
- const throatW = smoothstep(-1.3, -1.6, f[1]) * smoothstep(0.1, -0.55, n[1]) * (partOf[v] === 2 ? 0.55 : 0.7 + 0.15 * smoothstep(-0.9, 0.1, f[2]) * Math.exp(-((ax / 0.4) ** 2)));
609
- if (partOf[v] === 2) col = mix3(col, C.muzzle, 0.28 + 0.4 * smoothstep(-0.2, 0.3, f[2])); // (the lower lip's fur cream-white along the jaw's side under the lip line, f34_*: a tawny-grey jaw read as a heavy dark jowl from below)
610
- // (the white pads with a defined upper-outer edge: a soft fade read as a plush toy's pale muzzle)
611
- const w = clamp(Math.max(smoothstep(0.06, 0.5, whisker) * mix(0.72, 1, smoothstep(-0.98, -1.14, f[1])), lip, underEye, aboveEye, chinW, throatW), 0, 1);
612
- col = mix3(col, mix3(C.muzzle, C.eyePatch, underEye + aboveEye > whisker ? 0.5 : 0), w);
613
- // (grizzled tips on the face's tawny hair, ffront_* / f34_*: a uniform suede read as a plush toy)
614
- ag = (whiteV ? 0.1 : 0.26 - 0.1 * juv) * (1 - w); // (0.4 drew the face front a uniform dark brown in the showcase's shade)
615
- // dark tear streaks: from the inner corner of each eye down along the side of the bridge, fading
616
- // by the top of the leather (face_male1); the upper muzzle beside them a warmer, darker tawny
617
- // (a short streak fading ~0.4 E below the eye line, curving a little outward and thinning (face_male1,
618
- // face_female1): two parallel stripes down to the leather read as the edges of a plank)
619
- const tq = clamp(-f[1] - 0.08, 0, 0.45);
620
- const tx = 0.325 + 0.03 * tq + 0.3 * tq * tq;
621
- const tear = Math.exp(-(((ax - tx) / (0.075 - 0.055 * tq)) ** 2)) * smoothstep(-0.02, -0.12, f[1]) * smoothstep(-0.62, -0.32, f[1]) * smoothstep(-0.15, 0.1, f[2]); // (wider and longer, it did not read at showcase distance)
622
- col = mix3(col, C.tear, tear * (whiteV ? 1 : 0.95));
623
- col = mix3(col, C.crown, g3(f, [0.45, -0.6, 0.45], [0.22, 0.25, 0.4]) * (1 - padLow) * 0.55);
624
- // the lower cheek and the jowl behind the pads paler, cream (f34_*, ffront_*: the face pales from the brow down to the jaw; one
625
- // flat tawny side read as a plush toy's)
626
- if (partOf[v] !== 2) col = mix3(col, mix3(C.eyePatch, C.face, 0.3), smoothstep(-0.28, -0.8, f[1]) * smoothstep(0.36, 0.6, ax) * smoothstep(-1.2, -0.6, f[2]) * (1 - whisker) * (whiteV ? 0.3 : 0.35)); // (0.8 painted the whole lower face a pale mask that flattened it, f34_*: tawny cheeks, the white on the pads, the chin and the throat)
627
- // the dark tan marks above the inner corners of the eyes, and a darker, warmer bridge (face_female1,
628
- // face_male1, threequarter_youngmale1)
629
- // (a small dark spot over each eye (the supraorbital whisker spot, mfront_*, ffront_*); the tall streak
630
- // over the inner corner rose toward the midline like a worried brow)
631
- col = mix3(col, mix3(C.browMark, C.tear, 0.35), smoothstep(0.08, 0.55, g3(f, [0.36, 0.29, 0.1], [0.085, 0.06, 0.5])) * (whiteV ? 0.9 : 0.9));
632
- // a male's face darker and more strongly marked than its pale ruff: the forehead and the bridge a deeper tawny (face_male1,
633
- // threequarter_youngmale1; at low contrast the face and its ruff read as one flat pale shield)
634
- if (male) col = mix3(col, mix3(C.crown, C.browMark, 0.35), Math.max(g3(f, [0, 0.35, -0.15], [0.42, 0.42, 0.7]), g3(f, [0, -0.35, 0.55], [0.2, 0.36, 0.45]) * (1 - padLow)) * 0.25); // (0.7: a flat brown mask against the mane)
635
- // a thin dark furrow up the middle of the forehead (face_male1, face_female1)
636
- col = mix3(col, C.browMark, Math.exp(-((ax / 0.032) ** 2)) * smoothstep(0.12, 0.3, f[1]) * smoothstep(0.9, 0.62, f[1]) * smoothstep(-0.7, -0.4, f[2]) * (whiteV ? 0.8 : 0.65));
637
- col = mix3(col, C.crown, g3(f, [0.0, -0.3, 0.62], [0.2, 0.34, 0.4]) * (1 - padLow) * 0.6);
638
- // the liner's wing: a black line from the outer corner of the eye back and a little down (the lid margins
639
- // alone read as a thin rim; a line down from the inner corner crossed the socket at a grazing angle and
640
- // drew as a crack: the tear line's tint carries on from there)
641
- // (measured in the face plane, x-y: the corners lie on the eyeball, under the skin)
642
- if (f[2] > eyeOut[2] - 0.3) {
643
- const fL = [ax, f[1], 0];
644
- const [dw, tw] = seg(fL, [eyeOut[0], eyeOut[1], 0], [0.19, -0.06, 0]);
645
- mark = Math.min(mark, (dw - 0.03 * (1 - tw)) * EM); // (longer and bolder, f34_*: the eye's liner wing)
646
- }
647
- // whisker-base dots: rows of small black spots on the upper half of each pad
648
- // (small dark-brown dots in the pattern channel, their edges broken by the hair: crisp black marks 4-5 mm across
649
- // read as stitching)
650
- if (whisker > 0.15) {
651
- let dd = 1;
652
- for (const q of whiskerDots) dd = Math.min(dd, Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]) - 1.15 * q[3]); // (bigger, the rows read in the showcase)
653
- if (dd < pattern[v]) { pattern[v] = dd; dotI = 1; }
654
- }
655
- // black spot at the corner of the mouth
656
- // (at the end of the lip line, small: a 9 mm spot above it read as a dot beside the mouth)
657
- mark = Math.min(mark, Math.hypot(ax - 0.55, f[1] + 1.37, f[2] - 0.1) * FACE.E * HS - 0.0055);
658
- // fur length (research 6: face 5-10 mm): 5 mm over the front of the face (the muzzle, the bridge, round
659
- // the eyes: under the 5.8 mm the silhouette fins start at, which drew the muzzle's sides as a dotted
660
- // dark outline), longer on the cheeks and toward the crown
661
- // (7.5 mm over the front (no fins on the face since finMinLen 24 mm): at 5 mm the shells drew the face as
662
- // smooth suede, a plush toy's, with no hair texture in the showcase)
663
- // (16 mm on the cheeks (was 13): the cheek hair's locks and flow read, f34_* / ffront_*; at 13 mm a velvet plush)
664
- fl = mix(0.012, 0.0075, smoothstep(-0.6, 0.02, f[2]));
665
- fl = mix(fl, 0.016, smoothstep(0.6, 1.0, ax) * smoothstep(0.09, -0.45, f[2]));
666
- // the cheek ruff: long hair on the jowls and the cheeks behind the mouth, flaring out and back (every
667
- // lion: face_female1 is widest there, 1.24-1.3 E at the level of the mouth, against 1.12 at the eyes)
668
- // (17 mm on a maneless head over wide ramps: 32 mm from |x| 0.68 E stood as a dark coarse patch pasted on a
669
- // lioness's cheek, and its inner edge drew a V on the throat; the jowl's sculpt gives the width)
670
- const rw = r === 6 ? 0 : ruffOf(f);
671
- // (in locks: +-35 % at ~5 cm, the ruff's outline and surface break up like the mane's)
672
- // (round the ear bases no faster than the mane's rise there: a male's ruff of 2.4 cm stood as a ring of clumps round them)
673
- if (r !== 6) fl = Math.max(fl, Math.min(ruffFl(f, 1 + 0.7 * (vnoise3(p[0] * 20 + 4.1 + nOff, p[1] * 20, p[2] * 20) - 0.5), male ? 0.24 * (vnoise3(p[0] * 28 + 6.7 + nOff, p[1] * 28, p[2] * 28) - 0.5) + 0.14 * (fbm3(p[0] * 9 + 2.2 + nOff, p[1] * 9, p[2] * 9, 2) - 0.5) : 0), male ? earCap(earDist(p)) : 1));
674
- // (a male's ruff only a little paler than the mane: a band of pale colour round the face read as a pale shield set in
675
- // the mane; the long hair itself frames the face)
676
- col = mix3(col, ruffCol, (r === 6 ? 0 : ruffPale(f)) * (0.7 - 0.25 * male));
677
- // (a fluffy white chin whose underside hair hangs long into the throat, face_male1: short, it read as a white
678
- // bar under the mouth with a crease below it)
679
- // (up to 25 mm, rising over the chin's curve: longer, it stood as a wall under the chin; a maned lion's
680
- // long pale hair below it is the mane's)
681
- // (13-16 mm, combed back toward the throat: up to 25 mm on maned males it outlined the chin as a pad)
682
- if (r === 6) {
683
- // (the chin's underside a tuft of longer hair hanging down and back into the throat's, which hides the crease under
684
- // the chin: the depth of the white chin in face_male1 is hair)
685
- // (the chin a white tuft of 2 cm hair hanging down over its own lower edge, face_male1 / threequarter_youngmale1;
686
- // 12 mm on its front read as a smooth pale lens)
687
- fl = mix(0.013, 0.022, Math.max(smoothstep(0.25, -0.6, n[1]), smoothstep(0.2, 0.6, n[2]) * smoothstep(-1.36, -1.46, f[1])));
688
- // maned males: behind the chin the jaw's underside and sides carry the ruff's pale front, its hair lengthening toward
689
- // the mane (a dead male's jaw, seen from below, lay in the mane as a white plate with a crease round it)
690
- if (maneK > 0) {
691
- // (the underside and the lower sides only, rising away from the rim: on the jaw's rear end 4 cm of hair stood
692
- // beside the black rim, a wall)
693
- const dMr = mouthPrim ? SDFModel.dist(mouthPrim, p[0], p[1], p[2]) : 1;
694
- // (by height, not by the normal: the normal turns fast round the jaw's side, a 2 -> 4 cm step in 3 mm)
695
- const rear = maneK * smoothstep(0.35, -0.45, f[2]) * smoothstep(-1.28, -1.52, f[1]) * smoothstep(0.012, 0.04, dMr);
696
- fl = mix(fl, 0.02, rear);
697
- col = mix3(col, mix3(C.chin, C.maneBlond, 0.45), rear * 0.8);
698
- // the chin a fluffy white tuft as long as the pale ruff round it (face_male1: 13-22 mm of chin hair in 5 cm of ruff
699
- // read as a white disc set in the mane, a pacifier)
700
- const tuft = smoothstep(-1.34, -1.55, f[1]) * smoothstep(0.004, 0.014, dMr);
701
- fl = Math.max(fl, 0.02 * tuft);
702
- col = mix3(col, C.chin, tuft * 0.6);
703
- }
704
- }
705
- // the throat skin under and behind the jaw as long as the jaw's underside (the two surfaces meet there, and 7-9 mm
706
- // beside the jaw's 16 mm drew the chin's rim)
707
- else fl = mix(fl, 0.022, smoothstep(-1.15, -1.55, f[1]) * smoothstep(0.35, -0.65, n[1]) * smoothstep(-1.5, -0.5, f[2]));
708
- // cubs: woolly (before the lids, the leather and the lips shorten it: a woolly ring round the eye read as a crater)
709
- if (juv > 0) fl = mix(fl, Math.max(fl, 0.012), juv);
710
- // eye rims: black lid margins; the fur shortens smoothly toward the lids (no ring of long fur)
711
- for (const e of eyePrims) {
712
- const de = Math.abs(SDFModel.dist(e, p[0], p[1], p[2]));
713
- // a thin pale crescent along the lower lid, just outside its black margin (face_male1 / face_female1)
714
- if (de < 0.014 && f[1] < -0.02 && (e.P[0] > HEAD_O[0]) === (p[0] > HEAD_O[0])) col = mix3(col, C.eyePatch, 0.8 * smoothstep(0.003, 0.0048, de) * smoothstep(0.0125, 0.0075, de) * smoothstep(-0.02, -0.07, f[1]));
715
- // (cubs: pale fur all round the eye, cub_Alert_cub_* / cub_Krueger_lion_cub; dark rings read as make-up)
716
- if (juv > 0 && de < 0.02 && (e.P[0] > HEAD_O[0]) === (p[0] > HEAD_O[0])) col = mix3(col, C.eyePatch, 0.7 * juv * smoothstep(0.0025, 0.004, de) * smoothstep(0.02, 0.008, de));
717
- // (a cub's margins thinner: the 15 % eye warp widened them into a doll's thick black rims; cub_lying1's
718
- // are thin)
719
- const lm = 1 - 0.6 * juv; // (thinner still, the cub's margins read as dark eye make-up rings)
720
- if (de < 0.007) mark = Math.min(mark, de - 0.0034 * lm); // (bolder margins, the eye read small)
721
- // (the lid margins and the lip band are leather (1) in a near-black tint: bare dark skin (2) next to
722
- // fur (0) swept the rounded material id through 1, which takes the tint (leatherTint): a light line)
723
- if (de < 0.0024 * lm) { mat = MAT.NOSE; col = C.lipDark; fl = 0; lidSkin = 1; }
724
- else fl = Math.min(fl, mix(0.0015, fl, smoothstep(0.0024, 0.016 - 0.007 * juv, de))); // (a cub's woolly fur shortened over 16 mm left a bare dark ring round the eye)
725
- }
726
- // nose leather: a broad T (a wide top bar carrying the nostril wings, 0.8 E across at 0.74 E below the eyes, narrowing
727
- // in concave sides to the stem and its rounded point at 1.06 E; mfront_Lion_Head, face_male1, ffront_Lioness_*) in one
728
- // smooth dull leather (render hint leatherTint): pink-brown in young lions darkening to near black with age, darker
729
- // toward its rim and round the nostrils, and a crisp black outline drawn as a mark (an interpolated distance, so it is
730
- // smooth at every tier)
731
- // (the per-vertex freckle noise (230 / m) and the furred upper part with a per-vertex fur edge
732
- // rendered as ragged broken dark-red patches with a jagged rim)
733
- const dn = Math.min(...nosePrims.map((q) => SDFModel.dist(q, p[0], p[1], p[2])));
734
- // (the leather still read as a broad rounded triangular blob blended into the bridge, no T and no
735
- // nostrils (mfront_Lion_Face, ffront_Lioness_*_33161755071: a broad bar whose outer parts are the two big dark nostrils, a narrow
736
- // stem down to the philtrum between concave fillets, the leather paler pink-brown on the bar's middle above them))
737
- // the outline in the face plane (ax, f[1]; E units, > 0 inside): a bar with rounded lower corners under a top edge falling a
738
- // little to the sides, and the stem, joined by a smooth union (the fillets)
739
- const yTop = -0.79 - 0.12 * ax * ax;
740
- const bar = Math.max(sdRBox(ax, f[1], -0.89, 0.34, 0.105, 0.08), f[1] - yTop); // (+-0.34 E: face_female1, face_male1, ffront_* +-0.33)
741
- const stem = sdRBox(ax, f[1], -1.05, 0.058, 0.085, 0.05);
742
- const dIn = -sminP(bar, stem, 0.075);
743
- // the nostrils: tilted ellipses on the bar's outer lower parts, the outer end higher (a comma curving up and out)
744
- const nu = (ax - 0.19) * 0.95 + (f[1] + 0.93) * 0.31, nw = -(ax - 0.19) * 0.31 + (f[1] + 0.93) * 0.95;
745
- const dNo = Math.hypot(nu / 0.11, nw / 0.058) - 1;
746
- const nearNose = partOf[v] !== 2 && f[2] > 0.72 && ax < 0.62 && f[1] > -1.3 && f[1] < -0.45;
747
- if (nearNose && dIn > 0 && dn < 0.014) {
748
- fl = 0; mat = MAT.NOSE;
749
- let dns = 1;
750
- for (const q of nostrilPrims) dns = Math.min(dns, SDFModel.dist(q, p[0], p[1], p[2]));
751
- col = mix3(C.nosePink, C.noseDark, clamp(0.02 + 0.7 * noseAge, 0, 1));
752
- // darker toward the rim and down the stem, the bar's middle above the nostrils palest
753
- col = mix3(col, C.noseDark, clamp(0.7 * smoothstep(0.07, 0.0, dIn) + 0.3 * smoothstep(-0.95, -1.08, f[1]), 0, 1));
754
- col = mix3(col, mix3(C.nosePink, C.face, 0.15), 0.35 * smoothstep(0.03, 0.09, dIn) * smoothstep(-0.95, -0.84, f[1]) * smoothstep(0.3, 0.1, ax) * (1 - noseAge));
755
- // the nostrils and a dark rim round their lower outer side
756
- col = mix3(col, C.nostril, Math.max(smoothstep(0.25, -0.15, dNo), smoothstep(0.009, 0.0015, dns)));
757
- // the groove down the middle of the stem
758
- col = mix3(col, C.noseDark, smoothstep(0.03, 0.008, ax) * smoothstep(-0.96, -1.02, f[1]) * 0.8);
759
- }
760
- if (nearNose) {
761
- // the outline: ~3 mm of black round the sides, the nostril wings and the point, thinner (1.5 mm) along the top edge
762
- const wR = 0.0015 + 0.0022 * smoothstep(0.03, 0.12, yTop - f[1]);
763
- const dM = dIn * EM;
764
- mark = Math.min(mark, dM > 0 ? Math.abs(dM - wR / 2) - wR / 2 : -dM);
765
- // the fur round the leather very short right up to it (no shell edge over the outline)
766
- // (above the leather over 0.3 E: the bridge's hair, combed down over the nose, hid its upper half under a furry ledge)
767
- if (dIn <= 0) fl = Math.min(fl, mix(0.0012, fl, smoothstep(0.0, f[1] > yTop - 0.02 ? 0.3 : 0.12, -dIn)));
768
- }
769
- // the philtrum: a black line from the leather's lower point down to the lip (face_male1)
770
- if (partOf[v] !== 2 && ax < 0.08 && f[1] < -1.1 && f[2] > 0.85) mark = Math.min(mark, ax * EM - 0.0016 + 0.004 * smoothstep(-1.33, -1.41, f[1]));
771
- // lips: head vertices near the jaw surface, jaw vertices near the head surface: a black lip band
772
- // along the line where they meet (bare dark lip skin where the surfaces overlap, the mouth lining
773
- // only deep inside), so the line reads as one smooth black band at every tier
774
- // (one lip line on the closed mouth: the head's upper lip edge is black where it meets the jaw, and the
775
- // chin's fur runs up to it; the jaw's rim is the lower lip, black, hidden under the upper lip until the
776
- // mouth opens, and inside it the trough and the tongue; a black band on both surfaces
777
- // with the chin's pale front between them read as bared teeth)
778
- if (partOf[v] !== 2) {
779
- // (only the upper lip's edge, above the jaw's middle: where the jaw's underside meets the throat skin the line ran
780
- // round it, and a dead lion's head seen from below showed a white disc in a black ring)
781
- const lipSide = smoothstep(-1.5, -1.4, f[1]);
782
- const dj = envelope(p[0], p[1], p[2]);
783
- // (the palate and the inside of the lips matte and dark, bare skin in its tint: the mouth material, a glossy
784
- // pink-red whatever its tint, read as a second tongue hanging from the upper jaw in the roar)
785
- // (not the outward-facing skin behind the mouth corner, where the jowl meets the jaw's side: it shows, a dark slit)
786
- // (and the head's skin at the back of the mouth, facing forward into the gape, below the palate: hidden by the closed
787
- // jaw; as fur or black lip skin it drew a band across the open mouth)
788
- const backMouth = f[2] < 0.1 && ax < 0.3 && f[1] > -1.5 && n[2] > 0.2 && n[1] > -0.7 && (f[1] > -1.42 || ax < 0.12) ? 1 : 0;
789
- // the six upper incisors: the palate's front edge between the canines (inside the closed jaw's envelope)
790
- if (dj < -0.003 && lipSide > 0.5 && f[2] > 0.5 && ax < 0.22 && n[1] < -0.2) { const g = ax / 0.07 - Math.round(ax / 0.07); mat = MAT.KERATIN; col = mix3(C.tooth, C.mouth, smoothstep(0.2, 0.08, Math.abs(g)) * 0.85); fl = 0; mark = 1; }
791
- else if (dj < -0.004 && ((lipSide > 0.5 && (ax < 0.36 || f[2] > 0.15 || n[0] * Math.sign(f[0]) < 0.3)) || backMouth)) { mat = MAT.SKIN; col = mix3(mix3(C.mouth, C.tongue, 0.25), C.lipDark, smoothstep(-0.004, -0.012, dj) * 0.35 * smoothstep(-1.45, -1.25, f[1])); fl = 0; }
792
- else if (dj < 0.0 && backMouth && f[2] < -0.1) { mat = MAT.SKIN; col = mix3(C.mouth, C.lipDark, 0.3); fl = 0; }
793
- else if (dj < 0.0 && lipSide > 0.5 && (ax < 0.36 || f[2] > 0.15 || n[0] * Math.sign(f[0]) < 0.3)) { mat = MAT.NOSE; col = C.lipDark; fl = 0; }
794
- else if (dj < 0.012 && f[2] > -0.18) { mark = Math.min(mark, dj - 0.004 + 0.02 * (1 - lipSide)); fl = Math.min(fl, mix(0.001, fl, Math.max(1 - lipSide, smoothstep(0.004, 0.012, dj)))); }
795
- } else {
796
- const dT = tonguePrim ? SDFModel.dist(tonguePrim, p[0], p[1], p[2]) : 1;
797
- const dM = mouthPrim ? SDFModel.dist(mouthPrim, p[0], p[1], p[2]) : 1;
798
- // (the jaw's top, facing up inside the closed mouth: the rim black where it turns down to the outside,
799
- // the mouth colour inside it; the chin's top at the front is the lower lip, not fur)
800
- // (only where the head's skin covers it in the closed mouth: the ramus's upper side behind the mouth corner shows below
801
- // the jowl, and painted as the mouth's lining it drew a dark slit there)
802
- const inside = f[1] > -1.4 && n[1] > 0.1 && (f[2] > 0.0 || SDFModel.evalList(headPrims, p[0], p[1], p[2]) < -0.002);
803
- // the six lower incisors: the rim's top between the canines, hidden in the closed mouth (a smooth band)
804
- const incis = f[2] > 0.56 && ax < 0.165 && n[1] > 0.5 && dT > 0.003 && SDFModel.evalList(headPrims, p[0], p[1], p[2]) < -0.002;
805
- if (incis) { const g = ax / 0.066 - Math.round(ax / 0.066); mat = MAT.KERATIN; col = mix3(C.tooth, C.mouth, smoothstep(0.2, 0.08, Math.abs(g)) * 0.85); fl = 0; mark = 1; }
806
- else if (dT < 0.0025 && dT < dM + 0.002) { mat = MAT.MOUTH; col = C.tongue; fl = 0; }
807
- else if (dM < 0.0025 || (inside && n[1] > 0.62)) { mat = MAT.SKIN; col = C.mouth; fl = 0; }
808
- // (a thin rim, 5 mm, its outer edge a smooth mark on the fur beside it: a 9 mm band switched per vertex read as a
809
- // thick jagged black rim round a pale tray)
810
- else if ((dM < 0.005 && n[1] > -0.2) || inside) { mat = MAT.NOSE; col = C.lipDark; fl = 0; }
811
- else {
812
- // (the rim's mark field continuous over the jaw's outside: cut off by the normal it jumped from ~1 cm to the 3 cm clamp
813
- // between neighbouring vertices, and the shader's antialiasing smeared a grey band under the lip line)
814
- mark = Math.min(mark, dM - 0.0045);
815
- // (short only along the rim: shortened down to 1.46 E below the eyes, the chin's whole front was bare suede, a
816
- // smooth pale lens)
817
- // (only right at the rim: shortened over 2 cm the chin's front below the lip line was a smooth bare plate)
818
- if (dM < 0.008) fl = Math.min(fl, mix(0.002, fl, smoothstep(0.002, 0.008, dM)));
819
- }
820
- }
821
- // the lip line: the black skin of the upper lip's margin, on its underside and the lower part of its rounded rim, from
822
- // the philtrum back to the corner of the mouth (face_male1 / face_female1: the closed mouth reads by it; drawn by the
823
- // jaw's distance it vanished wherever the upper lip hung clear of the jaw)
824
- if (partOf[v] !== 2 && f[1] < -1.12 && f[2] > -0.3 && ax < 0.66 && n[1] < -0.1) {
825
- const yb = lipBottom(ax, f[2]);
826
- // (the underside only, n.y below -0.86: from -0.74 the lip's whole rounded lower edge was a black band 12 mm tall
827
- // under the pads' hair, a grey smear over the chin)
828
- // (a band on the rounded rim only, n.y -0.96 .. -0.7: the whole underside black drew a black ring round the chin of a
829
- // dead lion whose head lay on its side, seen from below, a "porthole")
830
- // (the outer edge of the underside only: n.y below -0.86 and the normal tilted out from the muzzle's axis, so from the
831
- // front it is a thin line at the lip's lower edge and from below a thin outline, not a black ring)
832
- const oz = f[2] - 0.35, ol = Math.hypot(f[0], oz) || 1;
833
- const outw = (n[0] * f[0] + n[2] * oz) / ol;
834
- // (with the deeper whisker pads, the band (n.y below -0.83 and tilted out) was cut off along the
835
- // side of the lip where the jaw lies over 5 mm from the lip, and broke into dashes; the threshold eases from -0.78 to -0.65 behind the
836
- // pads, where the rim's underside is narrow and tilted (only the lowest ~2 mm of skin: wider, it drew a thick bar from below), and the jaw's distance only fades it behind the canines)
837
- const nT = mix(-0.6, -0.72, smoothstep(0.55, 0.85, f[2])); // (wider, the line did not read at showcase distance)
838
- if (yb !== null && f[1] - yb < 0.1) mark = Math.min(mark, Math.max(n[1] - nT, 0.43 - outw) * 0.03 + 0.004 * smoothstep(0.0, -0.3, f[2]) + Math.max(0, f[1] - yb - 0.015) * EM * 1.5 + 0.02 * smoothstep(0.008, 0.02, envelope(p[0], p[1], p[2])) * smoothstep(0.3, 0.1, f[2]));
839
- // (only near the jaw: the rim's whole outward underside, seen from below (a dead lion's head lying rolled), was a thick black C
840
- // round the white chin)
841
- // short hair on the lip's lower edge, so the pads' hair does not hang over the black line (the photos show it crisp)
842
- if (yb !== null) fl = Math.min(fl, mix(0.0015, fl, smoothstep(0.015, 0.07, f[1] - yb)));
843
- if (mark < 0) fl = Math.min(fl, 0.0006); // (no black hair standing out of the line: from below it drew a thick bar)
844
- }
845
- // teeth only where the canine stands clear of the skin it grows from (its base blends into the jaw's
846
- // front at the lip line: painted there, the coarse tiers drew a white zigzag like bared teeth)
847
- for (const c of caninePrims) {
848
- if (SDFModel.dist(c, p[0], p[1], p[2]) >= 0.0018) continue;
849
- const skin = SDFModel.evalList(partOf[v] === 2 ? jawPrims : headPrims, p[0], p[1], p[2]);
850
- if (skin > 0.0015) { mat = MAT.KERATIN; col = C.tooth; fl = 0; mark = 1; }
851
- }
852
- under = (whiteV ? 0.5 : 0.42) + 0.3 * w; // (pale greyish roots, more under the white: at 0.35 the face front, seen into the coat, read a uniform dark brown in the showcase's shade)
853
- // the relief shading (cav): the creases darker and a little warmer, like the shadowed hair in them (more on the white lion, whose
854
- // pale face otherwise carries no structure at all)
855
- // (the form's curvature in the paint as well, at ~1.6 cm: the hair in the hollows and creases darker (beside the
856
- // bridge, under the brow and the cheekbone, between the pads and the cheek), on the ridges and bumps lighter (the brow, the
857
- // cheekbone, the bridge's edges, the pads), as the photos' diffuse light draws the bony face: ffront_*, f34_*, white_*)
858
- if (mat === MAT.FUR) {
859
- let dEyeK = 1;
860
- for (const e of eyePrims) dEyeK = Math.min(dEyeK, Math.abs(SDFModel.dist(e, p[0], p[1], p[2])));
861
- const away = smoothstep(0.01, 0.026, dEyeK);
862
- const dk = smoothstep(6, -28, curv[v]) * (whiteV ? 0.5 : 0.42) * away, lt = smoothstep(12, 36, curv[v]) * 0.22 * away;
863
- col = mix3(col, [col[0] * 0.55, col[1] * 0.48, col[2] * 0.42], dk);
864
- col = mix3(col, mix3(col, C.eyePatch, 0.6), lt);
865
- }
866
- if (mat === MAT.FUR && cav[v] > 0.1) {
867
- // (not in the crease between the brow and the upper lid: a dark arc there read as a cartoon eyebrow; the lids have their own margins)
868
- let dEyeC = 1;
869
- for (const e of eyePrims) dEyeC = Math.min(dEyeC, Math.abs(SDFModel.dist(e, p[0], p[1], p[2])));
870
- const ck = smoothstep(0.1, 0.6, cav[v]) * (whiteV ? 0.95 : 0.7) * smoothstep(0.012, 0.03, dEyeC);
871
- col = mix3(col, whiteV ? mix3([col[0] * 0.5, col[1] * 0.44, col[2] * 0.4], C.tear, 0.15) : [col[0] * 0.5, col[1] * 0.43, col[2] * 0.36], ck);
872
- }
873
- }
874
- // the head's paint hands over to the neck's across their boundary
875
- if ((r === 1 || r === 2) && hbw[v] > 0 && hbw[v] < 1 && mat === MAT.FUR) {
876
- if (r === 2) { const [c2, f2, a2] = neckBase(v, p, n, nz), k = 1 - hbw[v]; col = mix3(col, c2, k); fl = mix(fl, f2, k); ag = mix(ag, a2, k); under = mix(under, 0.5, k); }
877
- else { const [c2, f2] = headBase(p, n), k = hbw[v]; col = mix3(col, c2, k); fl = mix(fl, f2, k); ag = mix(ag, 0, k); under = mix(under, 0.35, k); }
878
- }
879
- // mane: long clumped hair; blond ruff at the face darkening to the rear and lower fringe
880
- const mw = maneW[v];
881
- if (mw > 0 && mat === MAT.FUR) {
882
- const face = r === 2 ? 1 : 0;
883
- const h = headLocal(p);
884
- // 0 at the face ruff .. 1 at the back of the mane (over the shoulders) and its lower edge
885
- const back = clamp(smoothstep(0.62, 0.3, p[2]) * 0.8 + smoothstep(0.8, 0.55, p[1]) * 0.7 + 0.35 * nz, 0, 1);
886
- const ruff = face * smoothstep(-0.02, -0.12, h[2]) * 0.5;
887
- let mc = mix3(C.maneBlond, C.maneMid, clamp((0.3 + 1.1 * maneDark) * back + 0.35 * maneDark - ruff * 0.3, 0, 1));
888
- mc = mix3(mc, C.maneDark, clamp((maneDark - 0.25) * 1.8 * back + 0.25 * nz2, 0, 1));
889
- // (pale under the chin: the white of the chin runs into the front of the mane, face_male1)
890
- const dJw = SDFModel.evalList(jawSolid, p[0], p[1], p[2]);
891
- // (and round the jaw's sides, where the ruff meets it: orange mane skin beside the jaw's pale sides drew its outline)
892
- mc = mix3(mc, mix3(C.chin, C.maneBlond, 0.45), (1 - smoothstep(0.015, 0.06, dJw)) * smoothstep(0.35, -0.2, n[1]) * 0.7);
893
- mc = mix3(mc, mix3(C.chin, C.maneBlond, 0.35), (1 - smoothstep(0.02, 0.11, dJw)) * smoothstep(-0.2, -0.6, n[1]) * 0.85);
894
- // the pale ruff framing the face carries on into the mane (face_male1: cream at the face, darker behind)
895
- const rwm = face ? ruffOf(toFace(h)) * smoothstep(-0.35, -0.02, h[2]) : 0;
896
- mc = mix3(mc, ruffCol, (face ? ruffPale(toFace(h)) * smoothstep(-0.35, -0.02, h[2]) : 0) * 0.5);
897
- col = mix3(col, mc, mw);
898
- // long layered locks: +-30 % in 10-20 cm patches; short (<= 2-3 cm) within ~5 cm of each ear base so
899
- // the ears stand clear of the crown hair (they read as holes in the mane)
900
- // (+-45 % from lock to lock at 5-8 cm on top of the 10-20 cm patches: the outline breaks into strands,
901
- // +-30 % in 10-20 cm patches left a smooth ball at the medium tier, which has no fins)
902
- const lock = 1 + 0.5 * (fbm3(p[0] * 7 + 5.3 + nOff, p[1] * 7, p[2] * 7, 3) - 0.5) + 0.85 * (vnoise3(p[0] * 17 + 2.1 + nOff, p[1] * 17, p[2] * 17) - 0.5) + 0.15 * nz2;
903
- let ml = (0.07 + 0.12 * maneK) * Math.max(0.45, lock) * mix(0.7, 1.1, back) * (face ? mix(0.5, 1.15, smoothstep(-0.02, -0.11, h[2])) : 1);
904
- // the crown in front of the ears: short, the crest rising behind the ear line
905
- if (face) ml *= mix(1, 0.3, smoothstep(0.02, 0.1, h[1]) * smoothstep(-0.1, -0.03, h[2]));
906
- // short round the ear bases and on the crest behind them, so the pinnae stand clear of the crown hair
907
- const dEar = earDist(p);
908
- // (from the ear's own short fur at its base: a step from 9 to 20 mm there stood as a wall round the ear base)
909
- // (rising from 5 cm at 0.5 mm per mm: from 3 cm at 0.9 mm per mm the foot of the mane's bevel stood as a ring of clumps
910
- // round the ear base, 72-94 wall edges)
911
- ml = Math.min(ml, earCap(dEar));
912
- // (short beside the jaw, rising over ~8 cm: 8 cm of mane hair standing round the jaw's short fur drew it as a plate
913
- // sunk in the mane)
914
- if (face) ml = Math.min(ml, mix(0.026, ml, smoothstep(0.0, 0.1, dJw)));
915
- // (the face ruff keeps its length beside the eyes up to the ears, clear of the ear bases: capped by the ear rule there,
916
- // the temples read as the corners of a short-furred plate)
917
- if (rwm > 0) ml = Math.max(ml, Math.min(ruffFl(toFace(h), 1), earCap(dEar)) * smoothstep(-0.35, -0.02, h[2]));
918
- fl = mix(fl, ml, mw * mw * (1.5 - 0.5 * mw));
919
- ag = mix(ag, (0.25 + 0.3 * maneDark) * (1 - 0.65 * rwm), mw);
920
- under = mix(under, mix(0.35, 0.65, rwm), mw); // (the face ruff: pale roots, few dark tips)
921
- // (the pale hair under and beside the chin light to the roots: with the mane's dark undercoat it drew a grey fringe
922
- // round the white chin, which then read as a plate)
923
- under = mix(under, 0.7, (1 - smoothstep(0.02, 0.09, dJw)) * mw);
924
- ag = mix(ag, 0, (1 - smoothstep(0.02, 0.09, dJw)) * mw);
925
- }
926
- // a maned male's crown: the mane grows from a hairline across the top of the forehead (~0.65 E above the eye line) back over
927
- // the crown and round the ear bases, so it frames the face from above and the ears stand half in it (mfront_*, m34_*,
928
- // threequarter_male2; the crown and forehead bare and the ears fully clear, the mane only a ruff at the
929
- // cheeks and the neck). Its length rises no faster than ~1 mm per mm up to 25 mm (the fur-slope limit), then to the mane's.
930
- if (male && maneK > 0 && r === 2 && mat === MAT.FUR) {
931
- const fC = toFace(headLocal(p)), axC = Math.abs(fC[0]);
932
- const hair = 0.56 + 0.1 * axC * axC + 0.26 * (fbm3(p[0] * 12 + 3.3 + nOff, p[1] * 12, p[2] * 12, 2) - 0.5) + 0.22 * (vnoise3(p[0] * 30 + 1.9 + nOff, p[1] * 30, p[2] * 30) - 0.5); // the hairline, higher toward the temples, ragged (lower, the mane's front edge over the brow)
933
- // (how far behind the hairline over the skin: up the forehead, then back over the crown)
934
- const tC = fC[1] - hair + 0.8 * Math.max(0, -0.3 - fC[2]);
935
- const cw = smoothstep(-0.2, 0.3, tC) * smoothstep(-0.45, 0.05, n[1] + 0.25 * fC[2] / 1.5) * smoothstep(0.35, -0.15, fC[2]);
936
- if (cw > 0) {
937
- const lockC = Math.max(0.35, 1 + 1.0 * (vnoise3(p[0] * 20 + 7.3 + nOff, p[1] * 20, p[2] * 20) - 0.5) + 0.6 * (fbm3(p[0] * 7 + 1.7 + nOff, p[1] * 7, p[2] * 7, 2) - 0.5));
938
- const back = smoothstep(-0.1, -1.1, fC[2]);
939
- // (the first 25 mm over ~0.3 E of skin, then the long part over the back of the crown and round the ears)
940
- let cl = 0.024 * smoothstep(-0.25, 0.3, tC) * (0.6 + 0.4 * lockC) + (0.04 + 0.1 * maneK) * lockC * smoothstep(0.15, 0.75, tC) * (0.7 + 0.3 * back); // (the long hair over the whole crown between the ears, measured back over it: by height alone it never reached the mane's length on the crown, which read bare) // (the long hair from just behind the hairline: a short-haired band 0.35-0.95 E deep over the forehead read as a cap's brim)
941
- // (round the ear bases: long enough to bury the lower half of the pinna, rising from the ear's own short fur)
942
- cl = Math.min(cl, earCap(earDist(p)));
943
- if (cl * cw > fl) {
944
- fl = mix(fl, cl, cw);
945
- const mcC = mix3(mix3(C.maneBlond, ruffCol, 0.35), mix3(C.maneMid, C.maneDark, clamp(maneDark - 0.3, 0, 1)), clamp(0.15 + 0.55 * maneDark * back + 0.2 * nz, 0, 1));
946
- col = mix3(col, mcC, cw * smoothstep(0.004, 0.02, cl));
947
- // (pale roots and few dark-banded tips over the crown and round the face: seen side-on along the head's
948
- // outline the dark roots and the agouti band drew a dark arc round the face, the rim of a cap)
949
- ag = mix(ag, (0.08 + 0.2 * maneDark) * smoothstep(0.03, 0.08, cl), cw);
950
- under = mix(under, 0.65, cw);
951
- }
952
- }
953
- }
954
- // below the mane's lower edge on the chest the coat takes the mane's fringe colour (no pale bib)
955
- if (male && maneK > 0 && mat === MAT.FUR && maneW[v] < 0.5 && (r === 0 || r === 1) && p[2] > 0.25) {
956
- const bw = smoothstep(0.12, 0.0, maneD[v]) * (1 - legness[v]) * 0.65;
957
- if (bw > 0) col = mix3(col, mix3(C.maneMid, C.maneDark, clamp(maneDark * 0.8, 0, 1)), bw * clamp(0.35 + maneDark, 0, 1));
958
- }
959
- // chest fringe
960
- if (fringe[v] > 0.01 && mat === MAT.FUR) {
961
- const fw = fringe[v];
962
- {
963
- fl = mix(fl, 0.22 * (0.8 + 0.4 * vnoise3(p[0] * 17 + nOff, p[1] * 17, p[2] * 17)), fw);
964
- col = mix3(col, mix3(C.maneMid, C.maneDark, clamp(maneDark * 0.9, 0, 1)), fw * 0.8);
965
- ag = mix(ag, 0.4, fw);
966
- }
967
- }
968
- // elbow tufts (males)
969
- if (elbowPrims.length && mat === MAT.FUR) {
970
- const de = SDFModel.evalList(elbowPrims, p[0], p[1], p[2]);
971
- const ew = smoothstep(0.02, -0.004, de);
972
- if (ew > 0) { fl = mix(fl, 0.05, ew); col = mix3(col, mix3(C.maneMid, C.maneBlond, 0.4), ew * 0.5 * maneDark); }
973
- }
974
- // spots / rosettes
975
- if (SP.spots.length && mat === MAT.FUR) {
976
- const [sd, s] = featureDistance(SP, p, n, 1 + 0.25 * nz2);
977
- if (s) {
978
- // rosettes: a dark ring round a slightly darker centre
979
- pattern[v] = Math.min(pattern[v], s.ring ? Math.max(sd, -(sd + s.r * 0.45)) : sd);
980
- // cubs: faint rosettes, strongest on the legs and belly and fading out on the back (cub_lying1,
981
- // research 1: bold dark rosettes all over read as a leopard cub)
982
- spotI = (juv ? (r === 2 ? 0.55 : 0.36 * (1 - 0.85 * smoothstep(0.1, 0.65, n[1]))) : 0.22 * spotsK / 0.2) * (1 - 0.4 * s.pale); // (a cub's forehead spots stronger: cub_* photos, the rows of dark spots on the brow and crown)
983
- }
984
- }
985
- // pups: woolly
986
- // (not the ears: 26 mm on the rims stood as a dark spiky fringe)
987
- // (the head and the jaw too, rising smoothly from the face outward: 12 mm on the face beside 26 mm across the head
988
- // region's edge read as a smooth mask in a hood of wool with a dark rim)
989
- // (and shorter: 26 mm round the face stood as a hood whose side-on shells drew a dark rim round the mask; cub_lying1's
990
- // coat is soft but fairly sleek, the head short-haired with fluffy cheeks)
991
- if (juv > 0 && mat === MAT.FUR && r !== 5) {
992
- const wool = r === 2 || r === 6 ? cubWool(p) : 1;
993
- const woolLen = r === 1 || r === 2 ? mix(0.02, 0.016, hbw[v]) : 0.02;
994
- fl = mix(fl, Math.max(fl, woolLen), juv * (r === 1 ? mix(1, cubWool(p), hbw[v]) : wool));
995
- }
996
- // low-frequency colour variation
997
- const cvh = fbm3(p[0] * 3 + 5 + nOff, p[1] * 3, p[2] * 3, 2) - 0.5;
998
- if (mat === MAT.FUR) col = [col[0] * (1 + 0.12 * nz + 0.08 * cvh + 0.06 * nz2), col[1] * (1 + 0.1 * nz + 0.06 * nz2), col[2] * (1 + 0.08 * nz - 0.06 * cvh + 0.06 * nz2)];
999
- // (clamped: a mark or pattern field jumping from millimetres to metres at a region's edge blew up the
1000
- // shader's antialiasing width there, a dashed outline round the whisker pads)
1001
- markSDF[v] = Math.min(mark, 0.03);
1002
- furLen[v] = fl;
1003
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
1004
- const sc = dotI > 0 ? C.whiskerDot : C.cubSpot;
1005
- patternColor[v * 4] = sc[0]; patternColor[v * 4 + 1] = sc[1]; patternColor[v * 4 + 2] = sc[2]; patternColor[v * 4 + 3] = Math.max(spotI, dotI);
1006
- surf[v * 4 + 2] = mat === MAT.FUR ? clamp(ag * (0.85 + 0.3 * nz2), 0, 1) : 0;
1007
- surf[v * 4 + 3] = mat === MAT.FUR ? under : 0;
1008
- if (mat === MAT.KERATIN) surf[v * 4] = 0.7;
1009
- if (mat === MAT.NOSE) surf[v * 4] = lidSkin ? 0.12 : 0.3; // (dull leather: 0.5 read as a wet plastic nose; the lid margins duller still: glossy, a cub's reflected the sky as bluish rings)
1010
- }
1011
-
1012
- // smooth fur length so the shells do not step (bare skin stays locked)
1013
- smoothField(furLen, weights.neighbors, 3, (v) => tint[v * 4 + 3] > 0);
1014
-
1015
- return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, region, ventral, tailT, stats: { spots: SP.spots.length, cav: (() => { const a = Array.from(cav).filter((x, v) => region[v] === 2 || region[v] === 6).sort((x, y) => x - y); return [0.5, 0.75, 0.9, 0.97, 0.995].map((q) => a[Math.floor(q * (a.length - 1))]?.toFixed(3)); })() } };
1016
- }