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