@bitefoundry/retrieviewer 0.0.3 → 0.0.5

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 (263) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +95 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/INSTALLING.md +4 -0
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +116 -0
  11. package/README.md +121 -59
  12. package/RELEASING.md +33 -13
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/linux-overlay/overlay.py +8 -2
  17. package/integrations/macos-overlay/overlay.jxa +9 -7
  18. package/integrations/opencode/runtime.mjs +4 -4
  19. package/integrations/opencode/tui.tsx +1 -1
  20. package/integrations/windows-overlay/Overlay.cs +7 -6
  21. package/integrations/windows-overlay/desktop.mjs +2 -1
  22. package/integrations/windows-overlay/protocol.mjs +13 -7
  23. package/integrations/windows-overlay/worker.mjs +1 -1
  24. package/package.json +34 -13
  25. package/puppy-comfort-sprites.mjs +33 -0
  26. package/puppy-companion-comfort-q5.bin.gz +0 -0
  27. package/puppy-companion-comfort.json +140 -0
  28. package/puppy-companion-life.mjs +58 -18
  29. package/puppy-companion-objects.mjs +12 -0
  30. package/puppy-companion-sprites.mjs +4 -2
  31. package/puppy-frame-predictive.mjs +15 -9
  32. package/puppy-frame-store.mjs +2 -2
  33. package/puppy-paper-actions.mjs +4 -2
  34. package/puppy-paper-behaviors.mjs +1 -1
  35. package/puppy-paper-contact.mjs +4 -2
  36. package/puppy-paper-seat.mjs +4 -2
  37. package/puppy-paper-world.mjs +14 -0
  38. package/puppy-paper.mjs +79 -21
  39. package/retrieviewer-cli.mjs +8 -6
  40. package/retrieviewer-plugin.d.mts +1 -1
  41. package/retrieviewer-plugin.mjs +1 -1
  42. package/retrieviewer.d.mts +12 -4
  43. package/retrieviewer.mjs +20 -14
  44. package/locomotion-core.mjs +0 -21
  45. package/locomotion-puppy.animal +0 -0
  46. package/vendor/procedural-animals/package.json +0 -40
  47. package/vendor/procedural-animals/src/animal.js +0 -146
  48. package/vendor/procedural-animals/src/bake.js +0 -51
  49. package/vendor/procedural-animals/src/build.js +0 -81
  50. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  51. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  52. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  53. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  54. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  55. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  56. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  57. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  58. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  59. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  60. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  61. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  62. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  63. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  64. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  65. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  66. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  67. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  68. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  69. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  70. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  71. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  72. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  73. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  74. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  75. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  76. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  77. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  79. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  80. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  81. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  82. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  83. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  84. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  85. package/vendor/procedural-animals/src/index.d.ts +0 -98
  86. package/vendor/procedural-animals/src/index.js +0 -29
  87. package/vendor/procedural-animals/src/registry.js +0 -48
  88. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  89. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  90. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  91. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  92. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  94. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  95. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  96. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  97. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  98. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  99. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  100. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  101. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  102. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  103. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  104. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  105. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  106. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  107. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  108. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  109. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  110. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  111. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  112. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  113. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  114. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  115. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  116. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  117. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  118. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  119. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  120. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  121. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  122. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  123. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  124. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  125. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  126. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  127. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  128. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  129. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  130. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  131. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  132. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  133. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  134. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  135. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  136. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  137. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  138. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  139. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  140. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  141. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  142. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  143. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  144. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  145. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  146. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  147. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  148. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  149. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  150. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  151. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  152. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  153. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  154. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  155. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  156. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  157. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  158. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  159. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  160. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  161. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  162. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  163. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  164. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  165. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  166. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  167. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  168. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  169. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  170. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  171. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  172. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  173. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  174. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  175. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  176. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  177. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  178. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  179. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  180. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  181. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  182. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  183. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  184. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  185. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  186. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  187. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  188. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  189. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  190. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  191. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  192. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  193. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  194. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  195. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  196. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  197. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  198. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  199. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  200. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  201. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  202. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  203. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  204. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  205. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  206. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  207. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  208. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  209. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  210. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  211. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  212. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  213. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  214. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  215. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  216. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  217. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  218. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  219. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  220. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  221. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  222. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  223. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  224. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  225. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  226. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  227. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  228. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  229. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  230. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  231. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  232. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  233. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  234. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  235. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  236. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  237. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  238. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  239. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  240. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  241. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  242. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  243. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  244. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  245. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  246. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  247. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  248. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  249. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  250. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  251. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  252. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  253. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  254. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  255. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  256. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  257. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  258. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  259. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  260. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  261. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  262. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  263. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,841 +0,0 @@
1
- // The domestic cat's coat, baked per vertex in reference space.
2
- //
3
- // One groom (fur length and flow) serves every coat; the variant picks the colours and the pattern:
4
- // mackerel brown mackerel tabby: narrow wavy stripes down the flanks from a dorsal stripe, barred
5
- // legs, ringed tail with a dark tip, necklaces, the forehead "M", cheek swirls
6
- // classic blotched tabby: a bull's-eye whorl on each flank, the "butterfly" over the shoulders,
7
- // three spine lines; same head, legs and tail
8
- // ginger red tabby: cream-orange ground, low-contrast rust stripes (mackerel or classic by seed),
9
- // pink nose leather and pads
10
- // black solid black with a rusty undertone and faint ghost stripes, black leather
11
- // tuxedo black and white bicolour: white bib, belly, paws and muzzle (layout seeded)
12
- // calico white with patches of orange and black (params.white = 0: tortoiseshell, a brindle of
13
- // black and orange without white)
14
- // grey solid blue-grey ("blue"), plush, blue-grey leather, faint ghost markings
15
- // params.longhair gives the long coat (ruff, britches, plumed tail, toe tufts); kittens are fluffier.
16
- //
17
- // Channels: tint (base colour + material), pattern (crisp SDF in patternColor: tabby stripes, the
18
- // white of a tuxedo, the orange patches of a calico), mark (crisp SDF in the species mark colour: eye
19
- // rims, lips, nose outline, the black patches of a calico). Stripes are a few millimetres wide, so
20
- // their distances are stored x PS (the coat shader's edge jitter and antialiasing widths are metres
21
- // tuned on the cheetah's 15 mm spots).
22
- import { HEAD_O } from './rig.js';
23
- import { neckS, harmonizeSeamWeights } from './regions.js';
24
- import { earFrame, eyeOf } from './sculpt.js';
25
- import { SDFModel } from '../../core/sdf/sdf.js';
26
- import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
27
- import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
28
- import { srgb, mix3, projectToSurface, distPolyline, MAT } from '../../core/build/coatKit.js';
29
- import { limitFurSlope, FUR_SLOPE_DEFAULTS } from '../../core/build/furSlope.js';
30
-
31
- export const PS = 2.5; // stored pattern / mark distance per metre of skin
32
-
33
- // ------------------------------------------------------------------------------------------------
34
- // palettes (sRGB hex sampled from reference photos in neutral light)
35
- export function catPalette(p) {
36
- const v = p.variant || 'mackerel';
37
- const k = p.coatWarmth || 0, l = p.coatLightness || 0;
38
- const tw = (c) => [c[0] * (1 + 0.08 * k + l), c[1] * (1 + 0.02 * k + l), c[2] * (1 - 0.08 * k + l)];
39
- const C = (h) => srgb(h);
40
- const base = {
41
- stripeInt: 1, agouti: 0, gloss: 0.35, under: 0.3, ghost: 0,
42
- pad: C(0x2e2322), padMat: MAT.NOSE, nose: C(0xa0645a), noseMat: MAT.NOSE, noseRim: true,
43
- earInner: C(0xbfa196), nict: C(0x9c7f7a), white: C(0xebe8e2),
44
- };
45
- if (v === 'mackerel' || v === 'classic') {
46
- // (a cool, grey-brown agouti ground as in side1 / threequarter1 / face1, not a yellow khaki; a darker back)
47
- return { ...base, ground: tw(C(0x857a6d)), dorsal: tw(C(0x655a4e)), belly: tw(C(0xc4b7a6)), chin: C(0xe4ded4), stripe: C(0x221b17), agouti: 0.8, under: 0.4, legGround: tw(C(0x908475)), nose: C(0xb47268) };
48
- }
49
- if (v === 'ginger') {
50
- return { ...base, ground: tw(C(0xcf8b4c)), dorsal: tw(C(0xc07a3e)), belly: tw(C(0xecc79a)), chin: C(0xf2dfc4), stripe: tw(C(0x9c5028)), stripeInt: 0.85, agouti: 0.15, under: 0.35, legGround: tw(C(0xd79a5e)),
51
- pad: C(0xc8908a), nose: C(0xc98e88), noseRim: false, earInner: C(0xe6b0a4) };
52
- }
53
- if (v === 'black') {
54
- // (a black lifted a little and glossier than coal: in studio light the face read as a flat black silhouette; then
55
- // a little more again, warmer, with a greyer undercoat, as black fur reads in diffuse light)
56
- return { ...base, ground: tw(C(0x28231f)), dorsal: tw(C(0x1f1b19)), belly: tw(C(0x2d2825)), chin: tw(C(0x2a2522)), stripe: C(0x100d0c), stripeInt: 0.35, ghost: 1, gloss: 0.62, under: 0.22, legGround: tw(C(0x28231f)),
57
- rust: C(0x3a2a20), pad: C(0x221d1d), padMat: MAT.NOSE, nose: C(0x262021), noseMat: MAT.NOSE, noseRim: false, earInner: C(0x5a4a4a), nict: C(0x8a7470) };
58
- }
59
- if (v === 'grey') {
60
- return { ...base, ground: tw(C(0x7c7e83)), dorsal: tw(C(0x707277)), belly: tw(C(0x8a8c91)), chin: tw(C(0x8c8e93)), stripe: C(0x5c5d62), stripeInt: 0.3, ghost: 1, gloss: 0.3, under: 0.55, legGround: tw(C(0x7f8186)),
61
- pad: C(0x6a6166), nose: C(0x6a6068), noseRim: false, earInner: C(0x9a8a8e), nict: C(0xb8a8a8) };
62
- }
63
- if (v === 'tuxedo') {
64
- return { ...base, ground: tw(C(0x1d1a19)), dorsal: tw(C(0x181615)), belly: tw(C(0x1f1c1b)), chin: tw(C(0x1f1c1b)), stripe: C(0x100d0c), stripeInt: 0, gloss: 0.5, under: 0.12, legGround: tw(C(0x1d1a19)),
65
- rust: C(0x33251c), white: C(0xece9e3), pad: p.pinkNose ? C(0xc8928c) : C(0x2a2323), nose: p.pinkNose ? C(0xca918b) : C(0x262021), noseMat: MAT.NOSE, noseRim: false, earInner: C(0x6a5656) };
66
- }
67
- // calico / tortoiseshell
68
- return { ...base, ground: C(0xebe8e2), dorsal: C(0xe6e2da), belly: C(0xefece6), chin: C(0xefece6), stripe: C(0x1c1918), orange: tw(C(0xd08a46)), orangeDark: tw(C(0xb46e32)), black: C(0x1c1918), gloss: 0.4, under: 0.3, legGround: C(0xebe8e2),
69
- pad: C(0xc8928c), nose: C(0xca918b), noseRim: false, earInner: C(0xdcaaa0) };
70
- }
71
-
72
- // ------------------------------------------------------------------------------------------------
73
- // The colour of the fur on the upper lids (linear rgb): the eye shader paints a closing lid in it (blinks,
74
- // the slow blink, sleep, death), so it must be the face's own colour, not black; the dark lid margin is
75
- // drawn by the shader. A calico takes the colour of the patch over its lids (white, orange or black: the
76
- // same fields as catCoat's, sampled on both upper lids; one colour serves both eyes).
77
- export function catLidColor(params = {}) {
78
- const v = params.variant || 'mackerel';
79
- const COL = catPalette(params);
80
- // The shader draws the closing lid as a smooth shell over the eyeball, lit like the fur round it (eyeLook lidFur,
81
- // lidFill: the scene's lights with the coat's wrapped diffuse, camera-side fill and back-scatter, no environment
82
- // map). The shells render a coat darker-than-tint for pale coats and brighter for dark ones, and more saturated:
83
- // fitted on shut eyes in the showcase against the fur ring round each eye (sun in front, behind, to the side), the
84
- // lid is L' = 0.56 L^0.8 in luminance with the chroma x1.4 (lid / fur ring 0.7-1.2 for ginger, blue, the tabbies
85
- // in the three lights). A tabby's lids carry the pale fur of its eye ring.
86
- const lum = (c) => 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
87
- const dim = (c) => { const L = Math.max(1e-5, lum(c)), L2 = 0.56 * Math.pow(L, 0.8); return c.map((x) => Math.max(0, L2 * (1 + 1.4 * (x / L - 1)))); };
88
- const tabbyLid = v === 'mackerel' || v === 'classic' || v === 'ginger';
89
- if (v !== 'calico') return dim(tabbyLid ? mix3(COL.ground, COL.chin, 0.22) : COL.ground);
90
- const seedOff = rng(1337 + (params.coatSeed || 0))() * 100;
91
- const f3 = (p, k, o = 0) => fbm3(p[0] * k + seedOff + o, p[1] * k - o, p[2] * k + seedOff * 0.5, 3);
92
- const wAmt = params.white ?? 0.5;
93
- const E = eyeOf(params);
94
- const votes = { white: 0, orange: 0, black: 0 };
95
- for (const side of [1, -1]) {
96
- const ef = eyeFrameOf(E, HEAD_O, side);
97
- for (const u of [-0.004, 0, 0.004]) for (const vv of [0.0075, 0.009]) {
98
- const p = [0, 1, 2].map((k) => ef.c[k] + ef.x[k] * u + ef.y[k] * vv + ef.z[k] * 0.0085);
99
- const h = sub(p, HEAD_O);
100
- const nz = (f3(p, 18, 3.1) - 0.5) * 0.9 + (f3(p, 42, 7.7) - 0.5) * 0.2; // (low-frequency: solid patches, no flecks)
101
- const mz = Math.hypot(h[0] / (0.02 + 0.012 * wAmt), (h[1] + 0.03) / (0.016 + 0.01 * wAmt), (h[2] - 0.03) / 0.03);
102
- const white = wAmt < 0.05 ? 0 : smoothstep(1.2, 0.9, mz) + nz * 0.3 * (0.3 + wAmt);
103
- const nO = ((f3(p, 16, 11.3) - 0.5) + 0.25 * (f3(p, 55, 5.1) - 0.5)) * 0.03;
104
- votes[white > 0.5 ? 'white' : nO > 0 ? 'orange' : 'black']++;
105
- }
106
- }
107
- const best = Object.keys(votes).reduce((a, b) => (votes[b] > votes[a] ? b : a));
108
- return dim(best === 'white' ? COL.white : best === 'orange' ? COL.orange : COL.black);
109
- }
110
-
111
- // ------------------------------------------------------------------------------------------------
112
- export function catCoat(ctx) {
113
- const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params } = ctx;
114
- const { BONES, AXIAL, J } = rig;
115
- const AX = weights.axialLengths;
116
- const variant = params.variant || 'mackerel';
117
- const R = rng(1337 + (params.coatSeed || 0));
118
- const seedOff = R() * 100;
119
- const COL = catPalette(params);
120
- const tabby = variant === 'mackerel' || variant === 'classic' || variant === 'ginger';
121
- const classic = variant === 'classic' || (variant === 'ginger' && params.classic);
122
- const lh = params.longhair ? 1 : 0;
123
- const juv = params.age === 'juvenile' ? 1 : 0;
124
- const { dominant, axialS, skinIndex, skinWeight, limbMember, limbWhich } = weights;
125
- const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
126
- const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
127
- const sOcc = AX[1], sNeckBase = AX[3], sChest = AX[4], sTailBase = AX[8], sTip = AX[AX.length - 1];
128
- const tailLen = sTip - sTailBase;
129
- const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
130
- const nosePrim = model.prims.find((p) => p.tag === 'nose');
131
- const nictPrims = model.prims.filter((p) => p.tag === 'nictitans');
132
- const padPrims = model.prims.filter((p) => p.tag === 'pad' || p.tag === 'toe' || p.tag === 'carpalpad');
133
- const jawPrims = model.forPart('jaw');
134
- const bodyPrims = model.forPart('body');
135
- const headPrims = bodyPrims.filter((p) => p.bone === 'head' && !p.carve && p.tag !== 'nictitans');
136
- const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|femur|tibia|metatarsus|hpaw)/.test(b.name));
137
- const isPaw = BONES.map((b) => /^(fpaw|hpaw)/.test(b.name));
138
- const ears = { L: earFrame(J, 'L'), R: earFrame(J, 'R') };
139
- const n2 = (a, b) => vnoise3(a + seedOff, b - seedOff * 0.7, seedOff * 1.3);
140
- const f3 = (p, k, o = 0) => fbm3(p[0] * k + seedOff + o, p[1] * k - o, p[2] * k + seedOff * 0.5, 3);
141
-
142
- // ---- regions per vertex; legs are a continuous blend (legness) following the limb skin field
143
- const region = new Uint8Array(nV); // 0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw
144
- const legness = new Float32Array(nV);
145
- const legBone = new Int16Array(nV).fill(-1);
146
- const tailT = new Float32Array(nV);
147
- for (let v = 0; v < nV; v++) {
148
- const bn = BONES[dominant[v]].name;
149
- const p = P(v);
150
- let lb = -1, lw = 0;
151
- for (let k = 0; k < 4; k++) {
152
- const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
153
- if (isLimb[b] && w > lw) { lw = w; lb = b; }
154
- }
155
- legBone[v] = lb;
156
- legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
157
- if (partOf[v] === 2) region[v] = 6;
158
- else if (bn.startsWith('ear')) region[v] = 5;
159
- else if (axialS[v] > sTailBase + 0.01 && bn.startsWith('tail')) { region[v] = 4; tailT[v] = clamp((axialS[v] - sTailBase) / tailLen, 0, 1); }
160
- else if (axialS[v] < sOcc + 0.004 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.006)) region[v] = 2;
161
- else if (axialS[v] < sNeckBase) region[v] = 1;
162
- else region[v] = 0;
163
- if (region[v] >= 2) legness[v] = 0;
164
- }
165
-
166
- // ---- body coordinates: arc along the axis (s) and around it (angle from the dorsal midline)
167
- const bodyTheta = new Float32Array(nV);
168
- for (let v = 0; v < nV; v++) {
169
- const seg = weights.segT[v * 2] | 0, t = weights.segT[v * 2 + 1];
170
- const a = AXIAL.points[seg], b = AXIAL.points[Math.min(seg + 1, AXIAL.points.length - 1)];
171
- const c = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
172
- const p = P(v);
173
- bodyTheta[v] = Math.atan2(Math.abs(p[0] - c[0]), p[1] - c[1]); // 0 top, pi/2 side, pi belly
174
- }
175
-
176
- // ---- the head / neck hand-over: the head's look (short face fur combed back from the nose, its colours
177
- // and markings) and the neck's (the ruff combed down toward the chest, the body's stripes and
178
- // countershading) are both evaluated across a wide band round the neck cut and blended by position, so
179
- // hair length, flow, colour and pattern change gradually from the cheek into the neck. (Switching at the
180
- // region boundary drew a crisp line from under the ear round the jaw: the cheek read as a smooth
181
- // dome with a rim against the streaky neck fur.)
182
- const headW = new Float32Array(nV);
183
- for (let v = 0; v < nV; v++) {
184
- const r = region[v];
185
- if (r === 6) headW[v] = 1;
186
- else if (r === 1 || r === 2) { const p = P(v); headW[v] = smoothstep(-0.012, 0.014, neckS(p[0], p[1], p[2])); }
187
- }
188
- const inBand = (v) => (region[v] === 1 || region[v] === 2) && headW[v] > 0.002 && headW[v] < 0.998;
189
- // the look (colour, hair, flow) a vertex takes outside the band: the skin under the jaw that lies in front of the occiput
190
- // along the axis (region 2) but behind the band (headW 0: the throat 1.5-3 cm below the chin, on the body mesh) is neck,
191
- // not face: as face it took the short face hair and the chin's colour, while its neighbours a cell further up, inside
192
- // the band, were nearly all neck: a pale, short-haired crescent with a crisp edge under the chin, seen from the front
193
- // and below (the throat's hair rose 4.5 -> 16 mm over 2.5 cm; blue tom seed 12)
194
- const lookR = Uint8Array.from(region, (r, v) => (r === 2 && headW[v] <= 0.002 ? 1 : r));
195
-
196
- // the chin's pale colour keeps to the midline: ~2 cm wide under the chin and the lower lip, widening to ~3 cm under
197
- // the throat as it runs back into the neck's pale throat strip (head-local h)
198
- // the front of the chin (the jaw's own pale patch, seen under the lip from the front), not its sides and underside behind
199
- const jawFront = (h) => smoothstep(0.008, 0.017, h[2]);
200
- const chinMid = (h) => { const e = mix(0.017, 0.022, smoothstep(-0.004, -0.03, h[2])); return smoothstep(e, e - 0.009, Math.abs(h[0])); };
201
-
202
- // ---- ventral / countershading factor
203
- const ventral = new Float32Array(nV);
204
- for (let v = 0; v < nV; v++) {
205
- ventral[v] = inBand(v) ? mix(ventralAt(v, 1), ventralAt(v, 2), headW[v]) : ventralAt(v, lookR[v]);
206
- }
207
- function ventralAt(v, r) {
208
- const p = P(v), n = N(v);
209
- let w = 0;
210
- if (r === 0) {
211
- w = smoothstep(1.75, 2.45, bodyTheta[v]);
212
- // chest front between the forelegs
213
- if (p[2] > 0.07) w = Math.max(w, smoothstep(0.16, 0.12, p[1]) * smoothstep(0.03, 0.012, Math.abs(p[0])) * smoothstep(-0.2, 0.4, n[2]));
214
- } else if (r === 1) {
215
- // (the pale throat narrows toward the jaw into the chin's ~2 cm strip: at full width, 2/3 of the way up the side
216
- // of the neck, it showed under the jowl from below and behind the mouth corner as a pale crescent in a low front view)
217
- const nk = smoothstep(-0.05, -0.008, neckS(p[0], p[1], p[2]));
218
- w = smoothstep(mix(1.6, 2.25, nk), mix(2.4, 2.85, nk), bodyTheta[v]);
219
- } else if (r === 4) {
220
- w = smoothstep(0.0, -0.7, n[1]) * 0.8;
221
- } else if (r === 2 || r === 6) {
222
- const h = sub(p, HEAD_O);
223
- // (only along the midline: the chin and the throat under it, ~2 cm wide, widening back into the neck's throat;
224
- // every downward-facing head vertex took it, and the throat fill turned the jowl's side down: a pale crescent
225
- // from the mouth corner along the jowl)
226
- w = Math.min(1, smoothstep(-0.022, -0.034, h[1]) * smoothstep(-0.1, -0.6, n[1]) + (r === 6 ? 0.8 * jawFront(h) : 0)) * chinMid(h);
227
- }
228
- const L = legness[v];
229
- if (L > 0) {
230
- const side = p[0] >= 0 ? 1 : -1;
231
- const inner = smoothstep(0.1, -0.6, n[0] * side);
232
- const wl = inner * 0.75 * smoothstep(0.04, 0.12, p[1]) + smoothstep(-0.3, -0.85, n[1]) * 0.4;
233
- w = mix(w, wl, L);
234
- }
235
- return clamp(w, 0, 1);
236
- }
237
-
238
- // ---- comb (hair flow) direction, in bind space
239
- const comb = new Float32Array(nV * 3);
240
- // the axial chain's direction, continuous along it: each joint's tangent is the mean of its two segments',
241
- // interpolated along the segment (the segment's own direction kinked the flow by up to 60 deg where the
242
- // chain bends: at the occiput the neck's hair turned from 'back' to 'down' across one line)
243
- const nAx = AXIAL.points.length;
244
- const segDir = Array.from({ length: nAx - 1 }, (_, i) => norm(sub(AXIAL.points[i + 1], AXIAL.points[i])));
245
- const jointTan = Array.from({ length: nAx }, (_, j) => (j === 0 ? segDir[0] : j === nAx - 1 ? segDir[nAx - 2] : norm(add(segDir[j - 1], segDir[j]))));
246
- const axialDir = (v) => {
247
- const seg = Math.min(weights.segT[v * 2] | 0, nAx - 2), t = clamp(weights.segT[v * 2 + 1], 0, 1);
248
- return norm(add(mul(jointTan[seg], 1 - t), mul(jointTan[seg + 1], t)));
249
- };
250
- const combAt = (v, r) => {
251
- const p = P(v), n = N(v);
252
- let d;
253
- const bone = BONES[dominant[v]];
254
- if (r === 5) {
255
- d = norm(sub(bone.tail, bone.head));
256
- } else if (r === 2 || r === 6) {
257
- const h = sub(p, HEAD_O);
258
- // outward from the nose, back over the crown, down and back on the cheeks
259
- const fromNose = norm(sub(h, [0, -0.016, 0.042]));
260
- // (the lower cheek and jowl comb back and down into the ruff, and out: the cheek fur flares, its tips standing out
261
- // of the contour)
262
- const jowl = smoothstep(0.0, -0.02, h[1]) * smoothstep(0.02, 0.035, Math.abs(h[0]));
263
- const flare = smoothstep(0.022, 0.036, Math.abs(h[0])) * smoothstep(0.004, -0.012, h[1]);
264
- d = norm(add(fromNose, [0.35 * flare * (h[0] >= 0 ? 1 : -1), -0.15 - 0.25 * jowl, -0.55]));
265
- if (h[1] > 0.004 && h[2] < 0.015) d = norm(add(d, [0, 0, -0.8]));
266
- } else {
267
- d = axialDir(v);
268
- if (r === 0) d = norm(add(d, [0, -0.45 * Math.abs(n[0]), 0]));
269
- // the neck's hair flows back and down toward the shoulders and chest (~35-45 deg below the horizontal
270
- // on its sides), not straight down the steep neck axis: combed down, it lay along a light from above
271
- // and the side of the neck behind the cheek went dark, a rim round the face
272
- if (r === 1) d = norm(add(d, [0, -0.12, -0.45]));
273
- const L = legness[v];
274
- if (L > 0 && legBone[v] >= 0) {
275
- const lbn = BONES[legBone[v]];
276
- let dl = norm(sub(lbn.tail, lbn.head));
277
- if (isPaw[legBone[v]]) dl = norm(add(dl, [0, 0, 0.4]));
278
- else dl = norm(add(dl, [0, -0.2, -0.2]));
279
- d = norm(add(mul(d, 1 - L), mul(dl, L)));
280
- }
281
- }
282
- return d;
283
- };
284
- for (let v = 0; v < nV; v++) {
285
- const n = N(v);
286
- let d = combAt(v, lookR[v]);
287
- // (across the head / neck band the flow turns gradually from the face's (back from the nose) to the
288
- // neck's (down toward the chest): a 55-60 deg switch at the cut drew a line in the fur)
289
- if (inBand(v)) d = norm(add(mul(combAt(v, 1), 1 - headW[v]), mul(combAt(v, 2), headW[v])));
290
- let t = sub(d, mul(n, dot(d, n)));
291
- if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
292
- t = norm(t);
293
- comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
294
- }
295
-
296
- // ---- head marking polylines (head-local), projected onto the skin
297
- const proj = (q) => projectToSurface(headPrims, [HEAD_O[0] + q[0], HEAD_O[1] + q[1], HEAD_O[2] + q[2]]);
298
- const mk = (pts, w) => ({ pts: pts.map(proj), w });
299
- const headLines = [];
300
- const L3 = (pts, w) => headLines.push(mk(pts.map((q) => q.map((x) => x / 1000)), w.map((x) => x / 1000))); // mm
301
- if (tabby || COL.ghost) {
302
- // the "M" on the forehead: bold bars (3-4 mm) rising from the brows and the stop over the crown, where
303
- // they run on as parallel lines to the nape; the outer pair hooks down over the inner brows, and a
304
- // short stroke over each eye makes the M's outer legs
305
- for (const s of [1, -1]) {
306
- L3([[2.6 * s, 9, 17.5], [3.2 * s, 17, 12], [3.6 * s, 23, 3], [4.2 * s, 27, -10], [4.8 * s, 27, -25], [5.2 * s, 22, -40]], [1.2, 1.6, 1.8, 1.9, 2.0, 1.8]);
307
- L3([[7.5 * s, 6, 18], [9 * s, 12, 15.5], [10 * s, 19, 9], [11 * s, 25, -4], [12 * s, 26, -20], [12.5 * s, 21, -37]], [1.1, 1.5, 1.7, 1.8, 1.8, 1.6]);
308
- L3([[12.5 * s, 12, 14.5], [18 * s, 14, 11], [23 * s, 12, 6]], [0.9, 1.1, 0.7]);
309
- // cheek lines (bold, face1 / face2): from the outer corner of the eye back across the cheek and curving
310
- // down behind it, a second one from below the eye back along the jowl
311
- L3([[26 * s, 1, 9], [32 * s, 0, 1], [37 * s, -4, -10], [38 * s, -11, -21], [35 * s, -17, -29]], [1.3, 1.9, 2.1, 1.9, 1.4]);
312
- L3([[22 * s, -11, 12], [29 * s, -15, 4], [35 * s, -19, -7], [36 * s, -24, -17]], [1.1, 1.6, 1.7, 1.3]);
313
- // the line down from the inner corner of the eye (the "mascara" line)
314
- L3([[11.5 * s, -4, 19.5], [10.8 * s, -9, 22.5], [10 * s, -13, 24.5]], [1.0, 0.9, 0.6]);
315
- }
316
- L3([[0, 22, 6], [0, 27, -10], [0, 26, -26], [0, 21, -42]], [0.9, 1.5, 1.7, 1.6]);
317
- }
318
-
319
- // the philtrum cleft (mark channel), from the point of the nose leather down to the upper lip
320
- const philtrum = mk([[0, -0.0226, 0.0345], [0, -0.0258, 0.033], [0, -0.0285, 0.031]], [0.00032, 0.0003, 0.00026]);
321
- // the tear line: a fine dark crease from the inner corner of each eye down toward the nose (face1)
322
- const tearLines = [1, -1].map((s) => mk([[11.6 * s, -1.4, 18.6], [11.0 * s, -4.2, 20.4], [10.3 * s, -7.0, 22.0]].map((q) => q.map((x) => x / 1000)), [0.00034, 0.00028, 0.00012]));
323
-
324
- // ---- white spotting (tuxedo / calico) and calico patch fields
325
- const white = new Float32Array(nV); // 0..1 field, > 0.5 white
326
- const spotting = variant === 'tuxedo' || variant === 'calico';
327
- const wAmt = params.white ?? (variant === 'tuxedo' ? 0.45 : variant === 'calico' ? 0.5 : 0);
328
- const sockF = params.sockF ?? 0.035, sockH = params.sockH ?? 0.05, blaze = params.blaze ?? 0;
329
- const whiteAt = (v, r) => {
330
- const p = P(v), n = N(v);
331
- const h = sub(p, HEAD_O);
332
- let w = 0;
333
- const nz = (f3(p, 18, 3.1) - 0.5) * 0.9 + (f3(p, 42, 7.7) - 0.5) * 0.2; // (low-frequency: solid patches, no flecks)
334
- if (variant === 'tuxedo' && (r === 0 || r === 1)) {
335
- // the tuxedo's shirt front: a white bib on the front of the chest between the forelegs and below the
336
- // throat, wider with more white. (No belly strip or throat strip: their edges ran through the armpits,
337
- // the flank folds, the groin and the underside of the neck, where the skin stretches most, 60-100 % of
338
- // the triangles there > 1.5x in the metrics' poses; the front of the chest stretches 0-16 %.)
339
- const bx = Math.abs(p[0]) + 0.004 * nz;
340
- w = smoothstep(0.086, 0.102, p[2]) * smoothstep(0.128, 0.142, p[1]) * smoothstep(0.2 + 0.006 * wAmt, 0.188 + 0.006 * wAmt, p[1])
341
- * smoothstep(0.03 + 0.016 * wAmt, 0.018 + 0.016 * wAmt, bx) * smoothstep(-0.35, 0.1, n[2]);
342
- } else if (r === 0 || r === 1) {
343
- // bib on the throat and chest, the belly strip, widening with the amount of white
344
- const vt = bodyTheta[v];
345
- const front = smoothstep(sChest + 0.04, sChest - 0.03, axialS[v]);
346
- w = smoothstep(2.55 - 0.9 * wAmt - 0.35 * front, 2.95 - 0.9 * wAmt - 0.35 * front, vt);
347
- // the bib: wide on the front of the chest; up the neck only a strip on the throat (its edges along the
348
- // sides of the neck lay in the skin that stretches most in sleep and the sit)
349
- const neckK = smoothstep(sNeckBase + 0.01, sNeckBase - 0.025, axialS[v]);
350
- w = Math.max(w, front * smoothstep(1.7 - 0.6 * wAmt + 0.55 * neckK, 2.2 - 0.6 * wAmt + 0.35 * neckK, vt));
351
- } else if (r === 2 || r === 6) {
352
- // muzzle, chin and lower cheeks; an optional blaze up the nose bridge
353
- const mz = Math.hypot(h[0] / (0.02 + 0.012 * wAmt), (h[1] + 0.03) / (0.016 + 0.01 * wAmt), (h[2] - 0.03) / 0.03);
354
- w = smoothstep(1.2, 0.9, mz);
355
- if (r === 6) w = Math.max(w, 0.85);
356
- // (the tuxedo's white under the jaw stops at the chin: back toward the throat it ran into the head / neck
357
- // junction, the most stretched skin)
358
- // (not limited to the midline like the chin colour: a white chin's side edges would run into the skin under the
359
- // jaw that stretches when the head curls in sleep)
360
- w = Math.max(w, smoothstep(-0.028, -0.036, h[1]) * smoothstep(-0.1, -0.5, n[1]) * (variant === 'tuxedo' ? smoothstep(0.004, 0.016, h[2]) : 1));
361
- if (blaze > 0) w = Math.max(w, smoothstep(0.004 + 0.004 * blaze, 0.0015, Math.abs(h[0]) - 0.15 * Math.max(0, -h[1])) * smoothstep(0.012 + 0.012 * blaze, 0.0, h[1]) * smoothstep(0.0, 0.02, h[2]));
362
- } else if (r === 4) {
363
- w = variant === 'tuxedo' && params.tailTip ? smoothstep(0.9, 0.94, tailT[v]) : 0;
364
- }
365
- const L = legness[v];
366
- if (L > 0) {
367
- const front = legBone[v] >= 0 && /^(scapula|humerus|radius|metacarpus|fpaw)/.test(BONES[legBone[v]].name);
368
- const top = front ? sockF : sockH;
369
- // (a tuxedo's legs: white socks only; the white inside of the legs ran an edge down every leg)
370
- const wl = Math.max(smoothstep(top + 0.006, top - 0.006, p[1] + 0.006 * nz), variant === 'tuxedo' ? 0 : ventral[v] * smoothstep(0.5, 0.9, wAmt + 0.3));
371
- w = mix(w, wl, L);
372
- }
373
- return clamp(w + nz * 0.3 * (0.3 + wAmt), 0, 1.5);
374
- };
375
- // (not blended across the head / neck band: a tuxedo's white edges stay out of the throat's junction skin, which
376
- // stretches most: blended, the chin's white faded out across it, +16 near-edge vertices, 31 % > 1.5x)
377
- if (spotting) for (let v = 0; v < nV; v++) white[v] = whiteAt(v, region[v]);
378
-
379
- // ---- per-vertex fields
380
- const pattern = new Float32Array(nV).fill(1);
381
- const patternColor = new Float32Array(nV * 4);
382
- const markSDF = new Float32Array(nV).fill(1);
383
- // mouth lining measure (m, > 0 inside the lining) and the width of its ramp (~2 head cells at this tier)
384
- const lin = new Float32Array(nV).fill(-1);
385
- const linW = 0.002 * ((ctx.Q && ctx.Q.res) || 1);
386
- const LINING = [0.42, 0.135, 0.13], LIP = variant === 'ginger' ? [0.085, 0.04, 0.03] : [0.035, 0.025, 0.022];
387
- const furLen = new Float32Array(nV);
388
- const capLen = new Float32Array(nV).fill(Infinity);
389
- const tint = new Float32Array(nV * 4);
390
- const surf = new Float32Array(nV * 4);
391
-
392
- // tabby stripe geometry (varies by individual)
393
- // (stripes 8.5 mm wide every 19 mm: ~45 % coverage, gaps 1-1.3 x the stripe width and
394
- // the photos' 24-34 % dark flank; 7.2 every 21 read pale and sparse)
395
- const P0 = (params.stripeP || 1) * 0.019, wD = 0.0085 * (params.stripeW || 1), tilt = 0.28;
396
- const ringP = 0.024, legP = 0.019;
397
- const sBull = AX[6] + 0.01; // classic bull's-eye centre along the axis (mid flank)
398
- const phOff = (seedOff * 0.617) % 1; // the stripes' phase along the body, per individual
399
- const sFly = AX[4] - 0.005; // butterfly centre (over the shoulder blades)
400
-
401
- // the look of a vertex as region r (the head / neck band evaluates both and blends them): base colour and
402
- // fur length, locket, tabby pattern, white spotting, and the longhair / kitten groom (applied at the end)
403
- const look = (v, r) => {
404
- const p = P(v), n = N(v);
405
- const h = sub(p, HEAD_O), ax = Math.abs(h[0]);
406
- const L = legness[v];
407
- const vt = bodyTheta[v];
408
- let col, fl = 0.012, mark = 1, pat = 1, patC = COL.stripe, patI = COL.stripeInt, patK = PS;
409
- let agouti = COL.agouti, gloss = COL.gloss, under = COL.under;
410
-
411
- // ================= base colour and fur length by region
412
- if (r === 0 || r === 1) {
413
- col = mix3(COL.ground, COL.dorsal, smoothstep(0.9, 0.2, vt));
414
- col = mix3(col, COL.belly, ventral[v]);
415
- // coat: back 12.5 mm, flanks 10.5, belly fringe 12.5, chest / neck ruff 13 (shorthair, as laid)
416
- fl = mix(mix(0.0105, 0.0125, smoothstep(1.0, 0.3, vt)), 0.0125, smoothstep(0.3, 0.9, ventral[v]));
417
- if (r === 1) fl = Math.max(fl, 0.013);
418
- if (L > 0) {
419
- let lc = mix3(COL.legGround, COL.belly, ventral[v] * 0.8);
420
- // legs: 9 mm on the upper leg to 5 mm at the wrist, 3 mm on the paws; britches on the hind thighs
421
- let lf = mix(0.0075, 0.0135, smoothstep(0.03, 0.14, p[1]));
422
- if (p[2] < -0.12) lf = Math.max(lf, 0.013 * smoothstep(-0.2, -0.8, n[2]) * smoothstep(0.08, 0.14, p[1]));
423
- if (legBone[v] >= 0 && isPaw[legBone[v]]) lf = 0.0035;
424
- col = mix3(col, lc, L);
425
- fl = mix(fl, lf, L);
426
- }
427
- agouti *= 1 - ventral[v] * 0.7;
428
- } else if (r === 4) {
429
- const t = tailT[v];
430
- col = mix3(COL.ground, COL.belly, ventral[v] * 0.6);
431
- col = mix3(col, COL.dorsal, smoothstep(0.3, 0.9, n[1]) * 0.5);
432
- fl = mix(0.016, 0.018, t);
433
- } else if (r === 5) {
434
- // the pinna: the back (convex) in the coat colour, darker toward the tip; the front a cup of pink skin
435
- // through short sparse hair, rosier deep in the concha; long pale furnishings grow only from the inner
436
- // (medial) margin and the base of the concha (on the whole front, the thin ear's silhouette hair cards
437
- // painted its back pale from behind); the back's fur wraps over the rim (a 2-3 mm band of the back colour)
438
- const S = p[0] >= 0 ? 'L' : 'R', E = ears[S];
439
- const front = smoothstep(0.05, 0.4, dot(n, E.f));
440
- const ht = dot(sub(p, E.base), E.up) / E.h; // 0 base .. 1 tip
441
- const u = dot(sub(p, E.base), E.across);
442
- const hw = 0.022 * (1 - 0.8 * clamp(ht, 0, 1)) + 1e-4;
443
- const edge = Math.abs(u) / hw;
444
- const medial = smoothstep(0.35, 0.95, (-u * (dot(E.across, [E.s, 0, 0]) >= 0 ? 1 : -1)) / hw);
445
- const back = mix3(mix3(COL.dorsal, COL.ground, 0.3), COL.dorsal, smoothstep(0.5, 1.0, ht) * 0.5);
446
- let inner = mix3(COL.earInner, mix3(COL.earInner, COL.nose, 0.3), smoothstep(0.65, 0.15, ht) * smoothstep(0.9, 0.3, edge) * 0.7);
447
- // (the furnishings: wisps from the inner margin, thinning toward the tip; at the base of the concha only a few
448
- // near that margin: a dense band all along the bottom of the opening read as pale fleece)
449
- const medialSoft = smoothstep(0.0, 0.9, (-u * (dot(E.across, [E.s, 0, 0]) >= 0 ? 1 : -1)) / hw);
450
- const furnish = Math.max(medial * smoothstep(0.8, 0.35, ht), smoothstep(0.22, 0.0, ht) * 0.4 * medialSoft);
451
- inner = mix3(inner, COL.chin, 0.12 + 0.45 * furnish);
452
- const rim = smoothstep(0.8, 1.0, edge) * 0.85;
453
- col = mix3(back, mix3(inner, back, rim), front);
454
- fl = mix(0.003, mix(0.0016, 0.0095, furnish), front * (1 - rim));
455
- if (front > 0.5) { agouti = 0; under = 0; }
456
- if (ht > 0.88 && lh) fl = Math.max(fl, 0.008); // lynx tips on longhairs
457
- } else {
458
- // head & jaw: short on the face (3 mm on the nose bridge and the whisker pads, shorter still round
459
- // the eyes), 5 mm on the crown, lengthening smoothly into the cheek ruff below and behind the eyes
460
- // (10.5 mm at the angle of the jaw) and into the neck fur behind the skull. Every change runs over
461
- // 1-2 cm of skin (and the head's hair may not rise faster than 0.4 mm per mm, furSlope below): no
462
- // pad with a rim, no ring of long fur round a sunken eye.
463
- col = mix3(COL.ground, COL.dorsal, smoothstep(0.3, 0.9, n[1]) * smoothstep(0.0, -0.03, h[2]) * 0.5);
464
- const g3 = (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));
465
- const muzzle = Math.max(g3([0.009, -0.024, 0.027], [0.011, 0.008, 0.012]), g3([0, -0.032, 0.021], [0.012, 0.006, 0.012]));
466
- // (the lower jaw takes the head's rule, pale on the underside along the midline, and its own pale only at the front of
467
- // the chin: all pale, the open jaw of a dead cat seen from below was a pale oval with a crisp rim on the darker throat)
468
- const chinW = Math.max(smoothstep(-0.026, -0.036, h[1]) * smoothstep(-0.1, -0.6, n[1]), r === 6 ? 0.9 * jawFront(h) : 0) * chinMid(h);
469
- // the pale fur round the eye hugs the lid margin, a band ~1-4 mm outside the aperture and a little wider
470
- // below the eye (a round blob of it outlined a goggle round every eye)
471
- let dEye = 1;
472
- for (const e of eyePrims) dEye = Math.min(dEye, Math.abs(SDFModel.dist(e, p[0], p[1], p[2])));
473
- const eyeRing = Math.exp(-(((dEye - 0.0015) / (0.0022 + 0.0014 * smoothstep(0.002, -0.006, h[1]))) ** 2)) * 0.26;
474
- col = mix3(col, COL.chin, clamp(Math.max(muzzle * 0.9, chinW, eyeRing * (tabby ? 1 : 0.3)), 0, 1));
475
- fl = mix(0.0052, 0.003, smoothstep(0.004, 0.024, h[2]));
476
- // the cheek fur flares: 11 mm on a queen's widest cheek and jowl, ~13 on a tom's, 14.5 on a heavy jowled tom
477
- // (research 6: 15-20 mm, more in toms), rising from the face's 4-5 mm over ~18 mm of skin below and behind the
478
- // outer corner of the eye, and running on into the ruff, a little longer still (no local maximum of the fur
479
- // surface at the cheek). (6 mm there drew the face as a smooth short-haired dome with a clean contour;
480
- // the fur-inclusive face 2.2 x the inter-pupil distance at the cheeks, photos 2.45-2.6)
481
- const tomK = clamp(((params.jowl || 1) - 0.97) / 0.21, 0, 1);
482
- // (a kitten's round face has no flaring jowls yet: 7.5 mm, which its fluff multiplier makes ~10.5; its adult's
483
- // 11-14.5 framed the kitten's face in a ruff like a lion's mane)
484
- const cheekL = juv ? 0.0075 : mix(0.011, 0.0145, tomK);
485
- // (the whole side of the head behind the eye, not only the jowl: the temple at 90 % of the cheek's length. With 6-10
486
- // mm there the shells drew a sleek, untufted dome whose edge, where the tufted 12-16 mm ruff began, was the crisp
487
- // outline)
488
- // (not up to the eye: the long cheek hair starts 5 mm from the eye's aperture and is full 1.6 cm away; it reached the
489
- // lid margin's short hair at the outer corner and below the eye, and the slope limit made a 0.4-0.65 mm-per-mm bevel
490
- // ring round every eye: raised goggles with their own outline in the death pose)
491
- const sideW = smoothstep(0.02, 0.034, ax) * smoothstep(0.016, 0.0, h[1]) * smoothstep(0.016, 0.0, h[2]) * smoothstep(0.005, 0.016, dEye);
492
- fl = mix(fl, cheekL * (0.9 + 0.1 * smoothstep(0.004, -0.012, h[1])), sideW);
493
- // (the cheek just below and behind the eye a little paler, as in face1 / face2: the longer cheek fur renders darker
494
- // than the short face fur, its deep shells in shadow, which ringed the face like a frame; not along the jowl, where
495
- // a paler band drew a pale crescent from the mouth corner in a backlit close-up)
496
- col = mix3(col, COL.chin, sideW * 0.1 * smoothstep(0.004, -0.006, h[1]) * smoothstep(-0.026, -0.016, h[1]) * smoothstep(-0.008, 0.004, h[2]) * (tabby ? 1 : 0.4));
497
- const lateral = smoothstep(0.018, 0.03, ax) * smoothstep(0.012, -0.004, h[1]);
498
- // (a longhair's ruff climbs from the crown over 5 cm: multiplied up to 3.6x, the adult's 2.4 cm ramp rose 1.5 mm per mm)
499
- // (the ruff stays behind the mouth corner: the neck's cut plane is tilted, and its ramp put 8-10 mm of hair (longhair
500
- // 15-17) on the lower lip and the mouth corner below the short-haired face, a pale groove between them, a sunken pocket)
501
- const ruff = mix(smoothstep(lh ? 0.05 : 0.028, 0.004, neckS(p[0], p[1], p[2])) * smoothstep(0.016, -0.006, h[2]), smoothstep(-0.012, lh ? -0.055 : -0.04, h[2]), lateral);
502
- fl = mix(fl, Math.max(0.012, cheekL + 0.001), ruff);
503
- // the lower jaw (its own mesh) takes the head's fur lengths at the same place, short only at the front
504
- // of the chin: its own shorter hair (3-6.5 mm among the throat's 5-12) sank it into a pale pocket
505
- // between the jowls, seen from below and at the corner of the mouth
506
- if (r === 6) fl = Math.min(fl, mix(0.0034, 0.012, smoothstep(0.014, -0.016, h[2])));
507
- agouti *= 0.7;
508
- }
509
-
510
- // ================= a small white locket on the chest (some black and blue cats)
511
- if (params.locket && (r === 0 || r === 1)) {
512
- const ls = params.locketSize ?? 1;
513
- const lk = Math.hypot(p[0] / (0.015 * ls), (p[1] - 0.16) / (0.022 * ls), (p[2] - 0.13) / 0.03) + 0.35 * (f3(p, 60, 4.4) - 0.5);
514
- const lw = smoothstep(1.0, 0.75, lk) * smoothstep(-0.2, 0.3, n[2]);
515
- if (lw > 0) { col = mix3(col, COL.white, lw); agouti *= 1 - lw; }
516
- }
517
-
518
- // ================= tabby pattern
519
- if (tabby || COL.ghost) {
520
- let d = 1;
521
- if (r === 0 || r === 1) {
522
- const s = axialS[v];
523
- const varc = vt * 0.048; // arc down from the dorsal midline (m)
524
- if (!classic) {
525
- // mackerel: wavy stripes leaning back as they run down; they break into dashes low on the flank
526
- const wob = 0.9 * (f3([s * 1.2, varc, 0], 26, 1.7) - 0.5) + 0.25 * (n2(s * 90, varc * 40) - 0.5);
527
- const ph = (s - AX[3]) / P0 + tilt * varc / P0 + wob + phOff;
528
- const fi = ph - Math.round(ph);
529
- const ww = wD * (0.8 + 0.5 * n2(Math.round(ph) * 3.1, varc * 30));
530
- d = Math.abs(fi) * P0 - ww / 2;
531
- const brk = smoothstep(0.045, 0.09, varc) * smoothstep(0.4, 0.8, n2(s * 160 + Math.round(ph) * 7, varc * 70));
532
- d += brk * 0.01;
533
- // dorsal stripe
534
- d = Math.min(d, varc - (0.0065 + 0.0025 * n2(s * 60, 1.3)));
535
- } else {
536
- // classic: three spine lines, the bull's-eye on each flank, the butterfly on the shoulders
537
- const spine = Math.min(Math.abs(varc) - 0.0028, Math.abs(varc - 0.0105) - 0.0026);
538
- // (the bull's-eye centred mid-flank and bold: a dark centre and two broad rings, warped; a small crisp
539
- // target high on the flank read as a stamped logo)
540
- // (an irregular 'oyster': the ring radius swings with the angle and winds outward a little, a spiral,
541
- // not a target)
542
- const ds = s - sBull, dv = varc - 0.07, th = Math.atan2(dv, ds);
543
- const rr = Math.hypot(ds * 0.95, dv * 1.1) * (1 + 0.16 * Math.sin(th + seedOff) + 0.1 * Math.sin(2 * th + 1.3 * seedOff) + 0.45 * (f3(p, 32, 2.2) - 0.5))
544
- + 0.0045 * (th / Math.PI) + 0.008 * (f3(p, 80, 6.1) - 0.5);
545
- const bull = Math.min(rr - 0.02, Math.abs(rr - 0.045) - 0.0058, Math.abs(rr - 0.07) - 0.0062);
546
- const fs = s - sFly, fv = varc - 0.028;
547
- const rf = Math.hypot(fs * 1.4, fv * 1.1);
548
- const fly = Math.min(Math.abs(rf - 0.014) - 0.0035, rf - 0.004);
549
- // stripes on the neck, the lower flanks and toward the rump continue as mackerel-like bars
550
- const ph = (s - AX[3]) / P0 + 0.6 * (f3([s, varc, 0], 30, 4.4) - 0.5) + phOff;
551
- const bars = (Math.abs(ph - Math.round(ph)) * P0 - wD * 0.6) + smoothstep(0.07, 0.04, varc) * smoothstep(sNeckBase + 0.03, sNeckBase + 0.06, s) * 0.02;
552
- // (the bars stop short of the whorl: a ground gap round its outer ring)
553
- d = Math.min(spine, bull, fly, Math.max(bars, 0.0035 * smoothstep(0.092, 0.078, rr)));
554
- }
555
- // behind the hips the skin faces back and the axial arc barely changes over it, so one stripe filled
556
- // each buttock (a dark oval): there the phase runs by height, the bars curling down the thigh
557
- const rumpW = smoothstep(-0.25, -0.65, n[2]) * smoothstep(AX[7] - 0.012, AX[8] - 0.002, s);
558
- if (rumpW > 0.001) {
559
- const phR = p[1] / (P0 * 0.95) + 0.35 * (n2(p[0] * 60, p[1] * 60) - 0.5);
560
- d = mix(d, Math.abs(phR - Math.round(phR)) * P0 * 0.95 - wD * 0.42, rumpW);
561
- }
562
- // underside: the belly is plain with a few spots; the throat and the front of the chest carry two or
563
- // three thin "necklace" rings instead of the flank stripes (the axial arc is compressed round the
564
- // neck), fading out toward the sides and back along the chest (a ring on a plane ran as a straight
565
- // line along the side of the chest and stopped abruptly)
566
- const vent = smoothstep(1.9, 2.5, vt);
567
- if (s > sChest + 0.02) {
568
- d += vent * 0.02;
569
- if (vent > 0.5) d = Math.min(d, (n2(p[0] * 300, p[2] * 300) > 0.82 ? -0.001 : 1));
570
- } else {
571
- const ny = p[1] + 0.25 * (p[2] - 0.12);
572
- const neck = Math.min(Math.abs(ny - 0.177) - 0.0022, Math.abs(ny - 0.152) - 0.0025, Math.abs(ny - 0.129) - 0.002 + 0.004 * n2(p[0] * 200, 3.3));
573
- const nw = smoothstep(1.2, 2.0, vt) * smoothstep(-0.15, 0.4, n[2]) * smoothstep(sChest + 0.02, sChest - 0.02, s);
574
- d += vent * 0.02 * (1 - nw);
575
- d = mix(d, neck, nw);
576
- }
577
- } else if (r === 4) {
578
- const st = tailT[v] * tailLen;
579
- const ph = st / ringP + 0.25 * (n2(st * 40, 2.2) - 0.5);
580
- d = Math.abs(ph - Math.round(ph)) * ringP - 0.0045 * (0.9 + 0.3 * tailT[v]);
581
- d += ventral[v] * 0.004;
582
- if (st < 0.02) d = Math.max(d, 0.02 - st); // no ring right at the root
583
- d = Math.min(d, (tailLen - 0.038) - st); // dark tip
584
- } else if (r === 2) {
585
- for (const ln of headLines) d = Math.min(d, distPolyline(p, ln.pts, ln.w));
586
- patK = PS * 2; // (the face's short hair: half the strand raggedness of the body stripes on these bars)
587
- }
588
- if (L > 0) {
589
- // leg bars on the front and outside of the legs, the inside plain; plain paws
590
- const side = p[0] >= 0 ? 1 : -1;
591
- const outer = smoothstep(-0.5, 0.2, n[0] * side + 0.6 * n[2]);
592
- const ph = p[1] / legP + 0.3 * (n2(p[1] * 80, p[2] * 80) - 0.5) + 0.25 * (p[2] > 0 ? 1 : -1);
593
- let dl = Math.abs(ph - Math.round(ph)) * legP - 0.0038;
594
- dl += (1 - outer) * 0.012 + smoothstep(0.03, 0.015, p[1]) * 0.02;
595
- d = mix(d, dl, L);
596
- }
597
- pat = d;
598
- if (COL.ghost) patI = Math.min(1, COL.stripeInt * (params.ghost ?? 1)) * (0.6 + 0.4 * smoothstep(0.4, 0.9, n[1]));
599
- else patI = Math.min(1, COL.stripeInt * (params.stripeC ?? 1));
600
- }
601
-
602
- // ================= spotting / calico
603
- if (spotting) {
604
- const w = white[v];
605
- const dW = (0.5 - w) * 0.018; // < 0 inside the white
606
- if (variant === 'tuxedo') {
607
- col = w > 0.5 ? COL.white : col;
608
- pat = dW; patC = COL.white; patI = 1;
609
- if (w > 0.3) { agouti = 0; under = 0.1; }
610
- } else {
611
- // calico: white base; orange patches in the pattern channel, black patches in the mark channel
612
- const big = f3(p, 16, 11.3) - 0.5, fine = f3(p, 55, 5.1) - 0.5;
613
- const nO = (big + 0.25 * fine) * 0.03;
614
- if (wAmt < 0.05) {
615
- // tortoiseshell: black and orange brindle all over
616
- col = mix3(COL.black, COL.orangeDark, smoothstep(0.62, 0.8, f3(p, 140, 2.9)) * 0.7);
617
- pat = -nO - 0.003 + 0.004 * (f3(p, 90, 8.8) - 0.5) * 3; patC = COL.orange; patI = 1;
618
- agouti = 0.1;
619
- } else {
620
- col = COL.white;
621
- // (the orange runs on under the black by ~2 mm (nO changes ~0.33 per metre at the patch edges): both switched
622
- // at nO = 0 over the white base, and their edge jitter let the white show as a spiky fringe along every
623
- // orange / black border; the black, in the mark channel, is drawn over the orange)
624
- pat = Math.max(-dW, -nO - 0.0008); patC = mix3(COL.orange, COL.orangeDark, smoothstep(0.4, 0.8, f3(p, 60, 1.1)) * 0.6); patI = 1;
625
- mark = Math.min(mark, Math.max(-dW, nO));
626
- agouti = 0.08;
627
- }
628
- }
629
- }
630
-
631
- // ================= longhair / kitten groom (a multiplier and an addition on the final length)
632
- let kMul = 1, kAdd = 0;
633
- if (lh) {
634
- let k = 3.2;
635
- // (the short face and the long ruff framing it meet gradually: the multiplier grows with the adult's own
636
- // face-to-ruff length ramp, 1.4x on the face to 3.6x where the head meets the neck; a hard gate stepped
637
- // the hair from 1.4x to 2.4x across one line, and 2.4x -> 3.8x at the neck, a flat mask set in the ruff)
638
- // (up to 2.6x on the head, whose cheeks now carry 11-15 mm themselves: 3.6x made them 4-5 cm, a fur wall
639
- // round the face; the neck's ruff stays 3.8x)
640
- if (r === 2) k = mix(1.4, 2.6, smoothstep(0.0045, 0.012, fl));
641
- if (r === 6) k = 2.2;
642
- if (r === 5) k = 1.3;
643
- if (L > 0) k = mix(k, p[1] < 0.03 ? 2.2 : 2.8, L); // toe tufts, feathered legs
644
- if (r === 4) k = 3.8; // plume
645
- if (r === 1) k = 3.8; // ruff
646
- kMul = k;
647
- }
648
- // the cobby blue's dense plush coat stands off the body (British type): up to 30 % deeper on the trunk, neck and
649
- // the ruff behind the cheeks, not on the face or the lower legs (nor on a longhair's head: on top of its own 2.6x)
650
- if (params.cob) {
651
- const plush = 1 + 0.3 * params.cob;
652
- const where = r === 0 || r === 1 ? 1 - smoothstep(0.3, 0.9, L) : r === 2 ? smoothstep(0.006, 0.011, fl) * (lh ? 0 : 1) : r === 4 ? 0.6 : 0;
653
- kMul *= mix(1, plush, where);
654
- }
655
- if (juv) {
656
- // a kitten's face (muzzle, nose bridge, round the eyes, forehead: the adult's short face fur) stays
657
- // short; its cheeks, throat and the back of the head take the body's fluff, ramping with the adult's
658
- // own face-to-ruff length (a short-haired disc in a fluffy coat read as a mask)
659
- const face = r === 2 || r === 6 ? smoothstep(0.0072, 0.0038, fl) : 0;
660
- kMul *= mix(1.15, 1.1, face); kAdd = mix(0.002, 0.0013, face); // (the face a little fluffier too: 0.8 mm left a short-haired disc in the fluff)
661
- }
662
- return { col, fl, mark, pat: pat * patK, patC, patI, agouti, gloss, under, kMul, kAdd };
663
- };
664
-
665
- for (let v = 0; v < nV; v++) {
666
- const p = P(v), n = N(v), r = region[v];
667
- const h = sub(p, HEAD_O), ax = Math.abs(h[0]);
668
- const L = legness[v];
669
- let o = look(v, lookR[v]);
670
- if (inBand(v)) {
671
- const a = look(v, 2), b = look(v, 1), w = headW[v];
672
- o = {
673
- col: mix3(b.col, a.col, w), fl: mix(b.fl, a.fl, w), mark: mix(b.mark, a.mark, w), pat: mix(b.pat, a.pat, w),
674
- patC: mix3(b.patC, a.patC, w), patI: mix(b.patI, a.patI, w), agouti: mix(b.agouti, a.agouti, w),
675
- gloss: mix(b.gloss, a.gloss, w), under: mix(b.under, a.under, w), kMul: mix(b.kMul, a.kMul, w), kAdd: mix(b.kAdd, a.kAdd, w),
676
- };
677
- }
678
- let { col, fl, mark, pat, patC, patI, agouti, gloss, under } = o;
679
- let mat = MAT.FUR, zone = false; // (zone: the lid margin's or the mouth's short hair, kept through the rounding below)
680
-
681
- // ================= skin: eye rims, nose leather, lips, third eyelid, pads
682
- if (r === 2 || r === 6) {
683
- for (const e of eyePrims) {
684
- const de = SDFModel.dist(e, p[0], p[1], p[2]);
685
- // the lids' hair very short up to the margin (the dark lid margin itself is the eye shader's, on the
686
- // moving lids: see sculpt.js); (no bare band at the rim: furred skin without shells renders as the dark
687
- // skin under the coat and ringed the eye, a shut one like a coin)
688
- if (Math.abs(de) < 0.004) { fl = Math.min(fl, mix(0.0008, 0.005, smoothstep(0.0, 0.004, Math.abs(de)))); zone = true; }
689
- }
690
- for (const e of nictPrims) if (SDFModel.dist(e, p[0], p[1], p[2]) < 0.0003) { mat = MAT.NOSE; col = COL.nict; fl = 0; mark = 1; pat = 1; }
691
- // nose leather: an inverted triangle on the front of the nose (its top edge ~13 mm wide where the
692
- // bridge fur ends, the sides running down to a point in the philtrum), nostrils at the lower corners
693
- const dn = SDFModel.dist(nosePrim, p[0], p[1], p[2]);
694
- // (10.8 mm wide, 8.2 tall: 0.29 x 0.22 of the inter-pupil distance in face1 / face2; 13 mm drew a big
695
- // pink nose)
696
- const nyTop = -0.015, nyBot = -0.0232, nW = 0.0054, nH = nyTop - nyBot;
697
- const dSide = (ax * nH - (h[1] - nyBot) * nW) / Math.hypot(nH, nW);
698
- const dTri = Math.max(dSide, h[1] - nyTop + 0.0015 * (ax / nW) ** 2);
699
- const onNose = dn < 0.0016 && h[2] > 0.029 && n[2] > -0.2;
700
- if (onNose && dTri < 0) { mat = COL.noseMat; col = COL.nose; fl = 0; pat = 1; mark = 1; }
701
- else if (onNose && dTri < 0.0008 && COL.noseRim) mark = Math.min(mark, dTri - 0.0004);
702
- // the philtrum: a fine dark cleft from the point of the leather down to the lip
703
- if (r === 2 && ax < 0.002 && h[2] > 0.025) mark = Math.min(mark, distPolyline(p, philtrum.pts, philtrum.w));
704
- if (r === 2 && ax > 0.007 && ax < 0.015 && h[1] < 0.002 && h[1] > -0.01 && h[2] > 0.014) mark = Math.min(mark, distPolyline(p, tearLines[h[0] > 0 ? 0 : 1].pts, tearLines[h[0] > 0 ? 0 : 1].w));
705
- // the mouth: skin the closed jaw hides (the palate over the lower jaw, the jaw's top inside the lips:
706
- // the part in front of the skin, 1.5 mm out along its normal, is inside the other surface) is mouth
707
- // lining; a fine dark lip line runs only along the mouth slit (above the chin's lower edge), not round
708
- // the chin where the lower jaw meets the throat and the jowls
709
- // (only skin well inside the other surface, or facing it across the mouth slit, is lining: every vertex whose
710
- // normal-offset point lay inside the other part took it, which drew a jagged dark-red ring round the chin wherever
711
- // the jaw met the head's underside, seen from below)
712
- // (the lining's edge is a smooth signed measure, lin > 0 inside: as a per-vertex material switch its rim followed the
713
- // mesh triangles, and an open mouth (death, hiss) showed a jagged black and white saw edge along the lips: see below)
714
- const q = [p[0] + n[0] * 0.0015, p[1] + n[1] * 0.0015, p[2] + n[2] * 0.0015];
715
- const slit = h[1] > -0.0305;
716
- if (partOf[v] !== 2) {
717
- const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
718
- if (dj < 0.0025) {
719
- const lv = Math.min(Math.max(-0.0006 - dj, Math.min(-SDFModel.evalList(jawPrims, q[0], q[1], q[2]), h[1] + 0.0305)), h[2] + 0.004, h[1] + 0.04);
720
- lin[v] = lv;
721
- if (lv <= 0 && dj < 0.0012 && h[2] > 0.0 && slit) { mark = Math.min(mark, dj - 0.0006); fl = Math.min(fl, 0.0012); zone = true; }
722
- }
723
- } else {
724
- const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
725
- const lv = Math.min(Math.max(-0.0006 - dh, Math.min(-SDFModel.evalList(headPrims, q[0], q[1], q[2]), h[1] + 0.0305)), h[2] + 0.004);
726
- lin[v] = lv;
727
- if (lv <= 0 && dh < 0.001 && slit) { mark = Math.min(mark, dh - 0.0005); fl = Math.min(fl, 0.0012); zone = true; }
728
- }
729
- }
730
- if (L > 0.5 && legBone[v] >= 0 && isPaw[legBone[v]]) {
731
- // paw pads: bare leather on the soles (the metacarpal / metatarsal pad and the four toe beans)
732
- let dp = 1;
733
- for (const pp of padPrims) dp = Math.min(dp, SDFModel.dist(pp, p[0], p[1], p[2]));
734
- if (dp < 0.0007 && n[1] < -0.25 && p[1] < 0.012) { mat = COL.padMat; col = COL.pad; fl = 0; pat = 1; mark = 1; }
735
- }
736
-
737
- // ================= low-frequency colour variation (and the rusty cast of a black coat in the sun)
738
- const cv = fbm3(p[0] * 30, p[1] * 30, p[2] * 30, 3) - 0.5;
739
- const cvh = fbm3(p[0] * 12 + 5, p[1] * 12, p[2] * 12, 2) - 0.5;
740
- if (mat === MAT.FUR) {
741
- col = [col[0] * (1 + 0.14 * cv + 0.08 * cvh), col[1] * (1 + 0.12 * cv), col[2] * (1 + 0.1 * cv - 0.06 * cvh)];
742
- if (COL.rust && (variant === 'black' || white[v] < 0.5)) col = mix3(col, COL.rust, clamp(0.25 + 0.5 * cvh, 0, 0.5) * smoothstep(0.2, 0.9, n[1]));
743
- }
744
-
745
- // ================= longhair / kitten groom
746
- if (mat === MAT.FUR && fl > 0) fl = fl * o.kMul + o.kAdd;
747
-
748
- // ================= mouth lining: wet pink skin inside the lips, a dark lip margin at its edge
749
- // The material id is interpolated across each triangle and rounded in the shader, so a lining that switched per vertex
750
- // drew its rim along the mesh triangles; and as MOUTH (3) next to FUR (0) the rim passed through ids 1 and 2 (a black
751
- // band). An open mouth (the death pose, the hiss) showed it as a jagged black and white saw edge along the lips (the
752
- // white teeth were the chin fur's shells, cut off at the same zigzag). The lining is now tinted leather (1: no id
753
- // between it and fur) with a fractional id that ramps with the smooth measure lin across ~2 cells, so the 0.5 crossing
754
- // (the lip margin) runs smoothly through the triangles; the fur shortens into it, and its colour darkens toward it.
755
- let matOut = mat;
756
- if (mat === MAT.FUR && lin[v] > -0.5 * linW) {
757
- const a = clamp(0.5 + lin[v] / linW, 0, 1);
758
- matOut = a;
759
- fl = Math.min(fl, 0.0007 + 1.2 * Math.max(0, -lin[v])); zone = true;
760
- col = lin[v] > 0 ? mix3(LIP, LINING, smoothstep(0.0, 0.0012, lin[v])) : mix3(col, LIP, 0.35 * smoothstep(-0.5 * linW, 0, lin[v])); pat = 1;
761
- }
762
-
763
- markSDF[v] = mark * PS;
764
- pattern[v] = pat; // (look() stores it x patK)
765
- furLen[v] = fl;
766
- if (zone) capLen[v] = fl;
767
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = matOut;
768
- patternColor[v * 4] = patC[0]; patternColor[v * 4 + 1] = patC[1]; patternColor[v * 4 + 2] = patC[2]; patternColor[v * 4 + 3] = patI;
769
- // no silhouette fins on the head, the jaw and the neck, fading in over the front of the chest behind the neck base (render
770
- // hint finMask, surf.y = 1 = none); the legs, the body and the tail keep theirs. A fin is drawn after every shell and is
771
- // depth-tested only against the skin, so a fin on fur that lies BEHIND a nearer furry contour is painted over that
772
- // contour's soft fringe of outer shells, and the fringe ends in a crisp line at the nearer skin's silhouette: the neck's
773
- // fins behind the cheek drew the face's outline from below the ear round the jaw (the blue's round disc with its own
774
- // outline in a low front view; a render without them has a soft fringe where the cheek's hair lies over the neck), and the
775
- // head's own fins (the head's ring of 1-2 cm dark-rooted cards: the kitten's hood, the longhair's mask and crest, the
776
- // blue's cheek plate; 70 % on the lower back of the cheek: a dark band along the jowl) drew a rim of their own
777
- const isFur = mat === MAT.FUR && matOut < 0.5;
778
- const finOff = !isFur ? 0 : r === 6 || r === 2 || r === 1 ? 1 : r === 0 ? (1 - smoothstep(sNeckBase, sChest + 0.01, axialS[v])) * (1 - legness[v]) : 0;
779
- surf[v * 4] = gloss; surf[v * 4 + 1] = finOff; surf[v * 4 + 2] = isFur ? agouti : 0; surf[v * 4 + 3] = isFur ? under : 0;
780
- }
781
-
782
- // clamp far-away distances (a huge value blows up the shader's antialiasing width)
783
- for (let v = 0; v < nV; v++) {
784
- pattern[v] = clamp(pattern[v], -0.05, 0.05);
785
- markSDF[v] = clamp(markSDF[v], -0.05, 0.05);
786
- }
787
-
788
- // smooth fur length a little so shells do not step
789
- const nb = weights.neighbors;
790
- const tmp = new Float32Array(nV);
791
- for (let it = 0; it < 2; it++) {
792
- for (let v = 0; v < nV; v++) {
793
- const ns = nb[v];
794
- if (!ns.length || tint[v * 4 + 3] > 0) { tmp[v] = furLen[v]; continue; }
795
- let a = 0;
796
- for (const u of ns) a += furLen[u];
797
- tmp[v] = 0.5 * furLen[v] + (0.5 * a) / ns.length;
798
- }
799
- furLen.set(tmp);
800
- }
801
- // the pipeline's fur-slope limit (core/build/furSlope.js): on the face hair may rise at most 0.4 mm per
802
- // mm of skin above the hair around it (lids, nose, whisker pads -> cheek ruff and crown), so the coat
803
- // surface round the eyes and cheeks stays smooth; 1 (the default) elsewhere
804
- // (the side of the head behind and below the outer corner of the eye, 0.65: at 0.4 the flaring cheek fur could not
805
- // rise from the face's 4-5 mm to its 11-15 mm within the cheek, and stayed a sleek 6-10 mm dome)
806
- const furSlope = new Float32Array(nV).fill(1);
807
- for (let v = 0; v < nV; v++) {
808
- if (lookR[v] !== 2 && lookR[v] !== 6) continue;
809
- const h = sub(P(v), HEAD_O);
810
- furSlope[v] = mix(0.4, 0.65, smoothstep(0.02, 0.03, Math.abs(h[0])) * smoothstep(0.016, 0.004, h[1]) * smoothstep(0.016, 0.004, h[2]));
811
- }
812
- // The face's hair rounded after the slope limit. Where the painted length rises faster than the limit allows (the lid
813
- // margin -> the cheek, the whisker pad and the mouth corner -> the jowl, the chin -> the throat) the limit leaves a bevel
814
- // of straight slope with a crease where it meets the longer hair, and the shells draw that crease as a line on the coat:
815
- // a ring round each eye (goggles), a straight edge across the lower cheek of the dead blue tom (seed 12, the cheek's long
816
- // hair ending at 0.65 mm per mm toward the face), a rim round the chin from below. The limit is applied here (as the
817
- // pipeline does after the coat) and the head's lengths are then averaged over ~4 mm of skin (the lid margins and the
818
- // mouth keep their short hair), so every change of length runs into its neighbours without a crease; the pipeline's
819
- // limit then finds (almost) nothing left to cut.
820
- limitFurSlope(furLen, ctx.pos, ctx.index, nV, tint, { slope: FUR_SLOPE_DEFAULTS.slope, free: FUR_SLOPE_DEFAULTS.free, perVertex: furSlope });
821
- {
822
- const hres = (ctx.Q && ctx.Q.res) || 1;
823
- const its = Math.max(3, Math.round(32 / (hres * hres)));
824
- const face = new Uint8Array(nV);
825
- for (let v = 0; v < nV; v++) face[v] = (lookR[v] === 2 || lookR[v] === 6 || headW[v] > 0.002) && tint[v * 4 + 3] === 0 && furLen[v] > 0 ? 1 : 0;
826
- for (let it = 0; it < its; it++) {
827
- for (let v = 0; v < nV; v++) {
828
- if (!face[v]) { tmp[v] = furLen[v]; continue; }
829
- let a = 0, c = 0;
830
- for (const u of nb[v]) if (tint[u * 4 + 3] === 0 && furLen[u] > 0) { a += furLen[u]; c++; }
831
- tmp[v] = c ? Math.min(capLen[v], 0.5 * furLen[v] + (0.5 * a) / c) : furLen[v];
832
- }
833
- furLen.set(tmp);
834
- }
835
- }
836
- // the head and body surfaces' skin weights made equal across the neck cut (regions.js): posed, they parted at the
837
- // hand-over (the core passes the weights to the coat and uses them after it)
838
- harmonizeSeamWeights(ctx);
839
- void limbWhich;
840
- return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, furSlope, stats: { variant }, region, ventral, tailT, headW };
841
- }