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