@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,361 +0,0 @@
|
|
|
1
|
-
// The horse's coat: base colours (bay, dark bay, chestnut, black, grey, palomino, dun, buckskin),
|
|
2
|
-
// black points, dorsal stripe and leg bars (dun), dapples (grey), white face and leg markings with
|
|
3
|
-
// pink skin and pale hooves under them, long mane / forelock / tail hair, hair flow following the
|
|
4
|
-
// equine hair tracts (forehead whorl, down the face, backward and down the barrel, down the legs),
|
|
5
|
-
// bare muzzle skin with whiskers, keratin hooves.
|
|
6
|
-
// Works in the reference space (see rig.js); seeded variation picks the colour and the markings.
|
|
7
|
-
import { HEAD_O, HZ, HY, DOCK_SEGS } from './rig.js';
|
|
8
|
-
import { neckS } from './regions.js';
|
|
9
|
-
import { unifyNeckWeights, spreadThroatWeights } from './seamWeights.js';
|
|
10
|
-
import { blendHeadNeckCoat } from './coatSeam.js';
|
|
11
|
-
import { SDFModel } from '../../core/sdf/sdf.js';
|
|
12
|
-
import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
|
|
13
|
-
import { srgb, mix3, MAT, poissonFeatures, featureDistance, smoothField } from '../../core/build/coatKit.js';
|
|
14
|
-
|
|
15
|
-
// sRGB swatches sampled from the reference photos
|
|
16
|
-
export const COATS = {
|
|
17
|
-
bay: { body: 0x6b3a1f, dorsal: 0x57301a, belly: 0x7a4527, head: 0x62351d, points: 0x17120f, mane: 0x121010, blackPoints: 1 },
|
|
18
|
-
darkbay: { body: 0x3e2418, dorsal: 0x2e1b13, belly: 0x5a3421, head: 0x3a2217, points: 0x141010, mane: 0x100d0c, blackPoints: 1 },
|
|
19
|
-
// (chestnut: #8A4A22 desaturated ~25 % and lifted a little: rendered flanks read as brick red otherwise)
|
|
20
|
-
chestnut: { body: 0x845231, dorsal: 0x77492b, belly: 0x93613a, head: 0x7d4d2e, points: 0x74452a, mane: 0x70432a, blackPoints: 0 },
|
|
21
|
-
black: { body: 0x1f1b1a, dorsal: 0x1a1716, belly: 0x2a221e, head: 0x1f1b1a, points: 0x131010, mane: 0x100e0d, blackPoints: 0 },
|
|
22
|
-
grey: { body: 0x9a9893, dorsal: 0x8e8c87, belly: 0xb4b2ac, head: 0xa8a6a0, points: 0x5a5856, mane: 0xcfcdc7, blackPoints: 0, dapple: 0xc9c7c1 },
|
|
23
|
-
palomino: { body: 0xd4a95a, dorsal: 0xc99d50, belly: 0xdcb46a, head: 0xcfa355, points: 0xc09450, mane: 0xefe3c2, blackPoints: 0 },
|
|
24
|
-
dun: { body: 0xb89a6a, dorsal: 0xa98c5e, belly: 0xc6aa7c, head: 0x7a6040, points: 0x1c1712, mane: 0x16120f, blackPoints: 1, dunFactor: 1 },
|
|
25
|
-
buckskin: { body: 0xb98a4f, dorsal: 0xac7e46, belly: 0xc49a60, head: 0xa87c45, points: 0x1a1410, mane: 0x141110, blackPoints: 1 },
|
|
26
|
-
};
|
|
27
|
-
const WHITE = 0xf0ece4, PINK = 0xc9a09a, MUZZLE = 0x2c2a2a, HOOF = 0x3a3029, HOOF_PALE = 0xb8a88a;
|
|
28
|
-
|
|
29
|
-
function palette(p) {
|
|
30
|
-
const C = COATS[p.coat] || COATS.bay;
|
|
31
|
-
const k = p.coatShade || 0, l = p.coatLightness || 0;
|
|
32
|
-
const tw = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.08 * k + l), c[1] * (1 + l), c[2] * (1 - 0.08 * k + l)]; };
|
|
33
|
-
const out = { body: tw(C.body), dorsal: tw(C.dorsal), belly: tw(C.belly), head: tw(C.head), points: srgb(C.points), mane: srgb(C.mane), blackPoints: C.blackPoints, dunFactor: C.dunFactor || 0 };
|
|
34
|
-
if (p.coat === 'chestnut' && p.flaxen) out.mane = srgb(0xcfb37e);
|
|
35
|
-
if (p.coat === 'grey') {
|
|
36
|
-
// greys whiten with age: 0 = dark dapple grey, 1 = almost white
|
|
37
|
-
const g = p.greyLevel ?? 0.4;
|
|
38
|
-
const lift = (c, t) => mix3(c, srgb(0xe4e2dc), t);
|
|
39
|
-
out.body = lift(out.body, g * 0.8); out.dorsal = lift(out.dorsal, g * 0.75); out.belly = lift(out.belly, g * 0.8); out.head = lift(out.head, g * 0.85);
|
|
40
|
-
out.points = lift(out.points, g * 0.6); out.mane = lift(out.mane, g * 0.5);
|
|
41
|
-
out.dapple = lift(srgb(C.dapple), g * 0.6);
|
|
42
|
-
}
|
|
43
|
-
out.white = srgb(WHITE); out.pink = srgb(PINK); out.muzzle = srgb(MUZZLE); out.hoof = srgb(HOOF); out.hoofPale = srgb(HOOF_PALE);
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const HAIR_TAGS = new Set(['mane', 'manecrest', 'forelock']);
|
|
48
|
-
const HOOF_TAGS = new Set(['hoof', 'heelbulb']);
|
|
49
|
-
|
|
50
|
-
export function horseCoat(ctx) {
|
|
51
|
-
const { model, rig, pos, nrm, regionOf: partOf, weights, nV, params, lists } = ctx;
|
|
52
|
-
const { BONES } = rig;
|
|
53
|
-
const J = rig.J;
|
|
54
|
-
const R = rng(4242 + (params.coatSeed || 0));
|
|
55
|
-
const COL = palette(params);
|
|
56
|
-
const foal = params.age === 'juvenile';
|
|
57
|
-
const { dominant, axialS, skinIndex, skinWeight, limbMember } = weights;
|
|
58
|
-
const AX_LEN = weights.axialLengths;
|
|
59
|
-
const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
|
|
60
|
-
const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
|
|
61
|
-
const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8];
|
|
62
|
-
const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
|
|
63
|
-
const jawPrims = model.forPart('jaw');
|
|
64
|
-
const headPrims = model.forPart('body').filter((p) => p.bone === 'head' && !p.carve);
|
|
65
|
-
const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
|
|
66
|
-
const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof|femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name));
|
|
67
|
-
const legOf = BONES.map((b) => (/^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(b.name) ? 'F' : /^(femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name) ? 'H' : '') + (b.side || ''));
|
|
68
|
-
const tailIdx = (name) => (name.startsWith('tail') ? +name.slice(4) : -1);
|
|
69
|
-
// head-local coordinates (x lateral, y dorsal, z along the nasal line)
|
|
70
|
-
const HL = (p) => { const d = sub(p, HEAD_O); return [d[0], dot(d, HY), dot(d, HZ)]; };
|
|
71
|
-
|
|
72
|
-
// --- nearest primitive tag per vertex
|
|
73
|
-
const tagOf = new Array(nV);
|
|
74
|
-
for (let v = 0; v < nV; v++) {
|
|
75
|
-
const L = lists[v], p = P(v);
|
|
76
|
-
let best = 1e9, t = '';
|
|
77
|
-
for (const pr of L) {
|
|
78
|
-
if (pr.carve) continue;
|
|
79
|
-
const d = SDFModel.dist(pr, p[0], p[1], p[2]);
|
|
80
|
-
if (d < best) { best = d; t = pr.tag; }
|
|
81
|
-
}
|
|
82
|
-
tagOf[v] = t;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- regions: 0 torso, 1 neck, 2 head, 3 mane / forelock, 4 dock, 5 ear, 6 jaw, 7 tail hair
|
|
86
|
-
const region = new Uint8Array(nV);
|
|
87
|
-
const legness = new Float32Array(nV);
|
|
88
|
-
const legBone = new Int16Array(nV).fill(-1);
|
|
89
|
-
for (let v = 0; v < nV; v++) {
|
|
90
|
-
const bn = BONES[dominant[v]].name, p = P(v), tag = tagOf[v];
|
|
91
|
-
let lb = -1, lw = 0;
|
|
92
|
-
for (let k = 0; k < 4; k++) {
|
|
93
|
-
const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
|
|
94
|
-
if (isLimb[b] && w > lw) { lw = w; lb = b; }
|
|
95
|
-
}
|
|
96
|
-
legBone[v] = lb;
|
|
97
|
-
legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
|
|
98
|
-
const ti = tailIdx(bn);
|
|
99
|
-
if (partOf[v] === 2) region[v] = 6;
|
|
100
|
-
else if (bn.startsWith('ear')) region[v] = 5;
|
|
101
|
-
else if (HAIR_TAGS.has(tag)) region[v] = 3;
|
|
102
|
-
else if (tag === 'tailhair') region[v] = 7;
|
|
103
|
-
else if (ti >= 0 && axialS[v] > sTailBase + 0.02) region[v] = ti >= DOCK_SEGS ? 7 : 4;
|
|
104
|
-
else if (axialS[v] < sOcc + 0.02 || (partOf[v] === 1 && neckS(p[0], p[1], p[2]) > 0.03)) region[v] = 2;
|
|
105
|
-
else if (axialS[v] < sNeckBase) region[v] = 1;
|
|
106
|
-
else region[v] = 0;
|
|
107
|
-
if (region[v] >= 2) legness[v] = 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// --- white markings (face + legs), jagged edges
|
|
111
|
-
const face = params.face || 'none';
|
|
112
|
-
const socks = params.socks || [0, 0, 0, 0]; // FL FR HL HR marking height above ground (m), 0 = none
|
|
113
|
-
const LEGI = { FL: 0, FR: 1, HL: 2, HR: 3 };
|
|
114
|
-
const whiteSDF = (v) => {
|
|
115
|
-
const p = P(v), r = region[v];
|
|
116
|
-
const jag = (fbm3(p[0] * 60, p[1] * 60, p[2] * 60, 2) - 0.5) * 0.02;
|
|
117
|
-
let d = 1;
|
|
118
|
-
if (r === 2 || r === 6) {
|
|
119
|
-
const h = HL(p), ax = Math.abs(h[0]);
|
|
120
|
-
const front = (dot(N(v), HY) > -0.2 && h[1] > -0.07 + 0.12 * smoothstep(0.1, -0.1, h[2])) || h[2] > 0.3;
|
|
121
|
-
if (!front) return 1;
|
|
122
|
-
// (star: on the forehead at the eyes' level, just below the forelock's tip, which ends at z ~0)
|
|
123
|
-
if (face === 'star' || face === 'starsnip' || face === 'stripe') d = Math.min(d, Math.hypot(ax / 0.028, (h[2] - 0.028) / 0.034) * 0.028 - 0.028);
|
|
124
|
-
if (face === 'stripe') d = Math.min(d, Math.max(ax - 0.014 - 0.006 * smoothstep(0, 0.3, h[2]), -0.02 - h[2], h[2] - 0.3));
|
|
125
|
-
if (face === 'blaze') d = Math.min(d, Math.max(ax - (0.03 + 0.03 * smoothstep(0.15, 0.4, h[2])), -0.07 - h[2]));
|
|
126
|
-
if (face === 'snip' || face === 'starsnip') d = Math.min(d, Math.hypot(ax / 0.022, (h[2] - 0.36) / 0.035) * 0.022 - 0.022);
|
|
127
|
-
return d + jag;
|
|
128
|
-
}
|
|
129
|
-
const lb = legBone[v];
|
|
130
|
-
if (lb >= 0 && legness[v] > 0.3) {
|
|
131
|
-
const hgt = socks[LEGI[legOf[lb]]] || 0;
|
|
132
|
-
if (hgt > 0) d = p[1] - hgt + jag * 1.5;
|
|
133
|
-
}
|
|
134
|
-
return d;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// --- ventral factor (lighter belly, inner legs, flank)
|
|
138
|
-
const ventral = new Float32Array(nV);
|
|
139
|
-
for (let v = 0; v < nV; v++) {
|
|
140
|
-
const n = N(v), r = region[v], p = P(v);
|
|
141
|
-
let w = 0;
|
|
142
|
-
if (r === 0) w = smoothstep(-0.2, -0.8, n[1]) * smoothstep(1.25, 1.0, p[1]);
|
|
143
|
-
else if (r === 1) w = smoothstep(0.0, -0.7, n[1]) * 0.5;
|
|
144
|
-
const L = legness[v];
|
|
145
|
-
if (L > 0) { const side = p[0] >= 0 ? 1 : -1; w = mix(w, smoothstep(0.1, -0.7, n[0] * side) * 0.6 * smoothstep(0.5, 0.9, p[1]), L); }
|
|
146
|
-
ventral[v] = w;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// --- comb (hair flow)
|
|
150
|
-
const comb = new Float32Array(nV * 3);
|
|
151
|
-
const whorl = [0, 0.055, -0.02]; // forehead whorl, head-local
|
|
152
|
-
for (let v = 0; v < nV; v++) {
|
|
153
|
-
const p = P(v), n = N(v), r = region[v];
|
|
154
|
-
const bone = BONES[dominant[v]];
|
|
155
|
-
let d;
|
|
156
|
-
if (r === 5) d = norm(sub(bone.tail, bone.head));
|
|
157
|
-
else if (r === 2 || r === 6) {
|
|
158
|
-
const h = HL(p);
|
|
159
|
-
// from the whorl: down the face toward the muzzle, back and down on the cheeks and jowl
|
|
160
|
-
const rel = [h[0] - whorl[0], h[1] - whorl[1], h[2] - whorl[2]];
|
|
161
|
-
const lh = [rel[0], rel[1] * 0.4, rel[2] + 0.08];
|
|
162
|
-
const w = [lh[0], lh[1] - 0.05, lh[2]];
|
|
163
|
-
d = norm(add(mul(HY, w[1]), add(mul(HZ, w[2]), [w[0], 0, 0])));
|
|
164
|
-
if (h[2] < -0.1) d = norm(add(d, [0, -0.6, -0.8]));
|
|
165
|
-
} else if (r === 3) {
|
|
166
|
-
// mane falls down the side of the neck; the forelock down the face; foal mane stands up
|
|
167
|
-
if (tagOf[v] === 'forelock') d = HZ;
|
|
168
|
-
else if (foal) d = [0, 1, 0.2];
|
|
169
|
-
else {
|
|
170
|
-
// straight down the mane side; on the far side of the crest up and over to the mane side
|
|
171
|
-
const ms = params.maneSide ?? -1;
|
|
172
|
-
d = p[0] * ms >= 0 ? norm([ms * 0.35, -1, -0.2]) : norm([ms * 0.6, 0.8, -0.2]);
|
|
173
|
-
}
|
|
174
|
-
} else if (r === 7 || r === 4) {
|
|
175
|
-
d = norm(sub(bone.tail, bone.head));
|
|
176
|
-
if (r === 4) d = norm(add(d, [0, 0, -0.2]));
|
|
177
|
-
} else {
|
|
178
|
-
const seg = weights.segT[v * 2] | 0;
|
|
179
|
-
const a = rig.AXIAL.points[seg], b = rig.AXIAL.points[Math.min(seg + 1, rig.AXIAL.points.length - 1)];
|
|
180
|
-
d = norm(sub(b, a));
|
|
181
|
-
if (r === 0) d = norm(add(d, [0, -0.45 * Math.abs(n[0]), 0]));
|
|
182
|
-
if (r === 1) d = norm(add(d, [0, -0.5, 0.1]));
|
|
183
|
-
const L = legness[v];
|
|
184
|
-
if (L > 0 && legBone[v] >= 0) {
|
|
185
|
-
const lbn = BONES[legBone[v]];
|
|
186
|
-
let dl = norm(sub(lbn.tail, lbn.head));
|
|
187
|
-
if (/hoof/.test(lbn.name)) dl = [0, -1, 0.1];
|
|
188
|
-
else dl = norm(add(dl, [0, -0.3, 0]));
|
|
189
|
-
d = norm(add(mul(d, 1 - L), mul(dl, L)));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
let t = sub(d, mul(n, dot(d, n)));
|
|
193
|
-
if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
|
|
194
|
-
t = norm(t);
|
|
195
|
-
comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// --- dapples (grey): light rounded spots with a darker network between them
|
|
199
|
-
let dapples = null;
|
|
200
|
-
if (params.coat === 'grey') {
|
|
201
|
-
dapples = poissonFeatures({
|
|
202
|
-
pos, nrm, nV, R, cell: 0.08, spacing: 1.25, gap: 0.004,
|
|
203
|
-
radius: (v) => (region[v] <= 1 && legness[v] < 0.6 && ventral[v] < 0.7 ? 0.028 : 0),
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// --- per-vertex fields
|
|
208
|
-
const pattern = new Float32Array(nV).fill(1);
|
|
209
|
-
const patInt = new Float32Array(nV);
|
|
210
|
-
const patCol = new Float32Array(nV * 3);
|
|
211
|
-
const markSDF = new Float32Array(nV).fill(1);
|
|
212
|
-
const furLen = new Float32Array(nV);
|
|
213
|
-
const tint = new Float32Array(nV * 4);
|
|
214
|
-
const surf = new Float32Array(nV * 4);
|
|
215
|
-
const lipLocal = (h) => h[1] + 0.078 - 0.02 * smoothstep(0.26, 0.4, h[2]);
|
|
216
|
-
|
|
217
|
-
for (let v = 0; v < nV; v++) {
|
|
218
|
-
const p = P(v), n = N(v), r = region[v], tag = tagOf[v];
|
|
219
|
-
let col, mat = MAT.FUR, fl = 0.008, mark = 1, gloss = 0.6;
|
|
220
|
-
const wsdf = whiteSDF(v);
|
|
221
|
-
const lb = legBone[v];
|
|
222
|
-
const lbName = lb >= 0 ? BONES[lb].name : '';
|
|
223
|
-
if (r === 0 || r === 1) {
|
|
224
|
-
const up = clamp(n[1], -1, 1);
|
|
225
|
-
col = mix3(COL.body, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.8);
|
|
226
|
-
col = mix3(col, COL.belly, ventral[v]);
|
|
227
|
-
fl = mix(0.008, 0.011, ventral[v]);
|
|
228
|
-
if (r === 1) fl = 0.008;
|
|
229
|
-
// dun: dorsal stripe from the mane to the tail
|
|
230
|
-
if (COL.dunFactor && r === 0) mark = Math.min(mark, Math.abs(p[0]) - 0.022 - 0.01 * smoothstep(0.3, -0.6, p[2]) + (1 - smoothstep(0.85, 0.97, up)) * 0.1);
|
|
231
|
-
const L = legness[v];
|
|
232
|
-
if (L > 0) {
|
|
233
|
-
// legs: coat colour above, black points below the knees / hocks (bay, dun, buckskin)
|
|
234
|
-
let lc = col;
|
|
235
|
-
const knee = /^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(lbName) ? 0.52 : 0.6;
|
|
236
|
-
const pts = COL.blackPoints ? smoothstep(knee + 0.18, knee - 0.02, p[1] + (fbm3(p[0] * 20, p[1] * 8, p[2] * 20, 2) - 0.5) * 0.12) : 0;
|
|
237
|
-
lc = mix3(lc, COL.points, pts);
|
|
238
|
-
if (!COL.blackPoints && params.coat === 'grey') lc = mix3(lc, COL.points, smoothstep(0.6, 0.2, p[1]) * 0.8);
|
|
239
|
-
// dun: faint zebra bars on the forearms and gaskins
|
|
240
|
-
if (COL.dunFactor && p[1] > 0.5 && p[1] < 0.95) { const bar = Math.sin(p[1] * 70); lc = mix3(lc, COL.points, 0.22 * smoothstep(0.82, 0.95, bar) * smoothstep(0.95, 0.75, p[1])); }
|
|
241
|
-
col = mix3(col, lc, L);
|
|
242
|
-
fl = mix(fl, mix(0.004, 0.008, smoothstep(0.3, 0.9, p[1])), L);
|
|
243
|
-
// fetlock feathers
|
|
244
|
-
if (/metacarpus|metatarsus|fpaw|hpaw/.test(lbName) && p[1] < 0.24 && p[1] > 0.1) fl = Math.max(fl, 0.018 * smoothstep(0.0, -0.8, n[2]));
|
|
245
|
-
// chestnuts (horny callosities) inside the forearm above the knee and below the hock
|
|
246
|
-
const side = p[0] >= 0 ? 1 : -1;
|
|
247
|
-
const chestnutY = /radius/.test(lbName) ? 0.62 : /metatarsus/.test(lbName) ? 0.46 : -1;
|
|
248
|
-
if (chestnutY > 0 && n[0] * side < -0.6) { const J0 = lbName.startsWith("radius") ? J["wrist" + BONES[lb].side] : J["hock" + BONES[lb].side]; if (Math.hypot((p[1] - chestnutY) / 0.024, (p[2] - J0[2]) / 0.014) < 1) { mat = MAT.KERATIN; col = srgb(0x3a302a); fl = 0; } }
|
|
249
|
-
}
|
|
250
|
-
// hoof wall below the coronet line (higher at the toe than at the heel)
|
|
251
|
-
const Cj = /hoof|paw/.test(lbName) ? J[(lbName[0] === 'f' ? 'fcoffin' : 'hcoffin') + BONES[lb].side] : null;
|
|
252
|
-
if (Cj && HOOF_TAGS.has(tag) && p[1] < Cj[1] + 0.012 + 0.45 * (p[2] - Cj[2])) {
|
|
253
|
-
mat = MAT.KERATIN; fl = 0; gloss = 0.55;
|
|
254
|
-
col = wsdf < 0 ? COL.hoofPale : COL.hoof;
|
|
255
|
-
if (tag === 'heelbulb') col = mix3(col, COL.muzzle, 0.5);
|
|
256
|
-
}
|
|
257
|
-
} else if (r === 3) {
|
|
258
|
-
col = COL.mane;
|
|
259
|
-
// (the forelock lies flat: its lock is geometry, the shells only add short hair along it)
|
|
260
|
-
// (mane: the sheet is geometry; its hair stays under the 25 mm long-hair bevel, so every edge of
|
|
261
|
-
// it - the poll, the forelock - tapers at the fur slope limit instead of standing as a wall)
|
|
262
|
-
fl = tag === 'manecrest' ? (foal ? 0.03 : 0.022) : tag === 'forelock' ? 0.012 : 0.018;
|
|
263
|
-
if (foal) fl = 0.03;
|
|
264
|
-
} else if (r === 4) {
|
|
265
|
-
col = mix3(COL.body, COL.mane, 0.6 + 0.4 * smoothstep(-0.2, 0.6, n[1]));
|
|
266
|
-
fl = 0.035;
|
|
267
|
-
} else if (r === 7) {
|
|
268
|
-
col = COL.mane;
|
|
269
|
-
fl = foal ? 0.04 : 0.055;
|
|
270
|
-
} else if (r === 5) {
|
|
271
|
-
// ears: coat colour, dark rims / tips on bays, hairy inside
|
|
272
|
-
const earP = model.prims.find((q) => q.tag === 'ear' && (q.bone === (p[0] > 0 ? 'earL' : 'earR')));
|
|
273
|
-
const faceDir = [earP.P[9], earP.P[10], earP.P[11]];
|
|
274
|
-
const front = dot(n, faceDir);
|
|
275
|
-
col = COL.head;
|
|
276
|
-
if (COL.blackPoints) col = mix3(col, COL.points, 0.5);
|
|
277
|
-
fl = 0.005;
|
|
278
|
-
if (front > 0.3) { fl = 0.016; col = mix3(col, srgb(0xb0a090), 0.25); }
|
|
279
|
-
} else {
|
|
280
|
-
// head & jaw
|
|
281
|
-
col = COL.head;
|
|
282
|
-
fl = 0.005;
|
|
283
|
-
const h = HL(p);
|
|
284
|
-
// dark soft muzzle skin with whiskers (pink under a white snip / blaze)
|
|
285
|
-
// muzzle: fine short hair over dark skin (pink under a white snip)
|
|
286
|
-
const muz = smoothstep(0.25, 0.36, h[2]);
|
|
287
|
-
if (muz > 0) { col = mix3(col, wsdf < 0 ? COL.pink : COL.muzzle, muz * 0.75); fl = mix(fl, 0.0025, muz); }
|
|
288
|
-
// bays and duns have a black-edged muzzle; duns a dark face
|
|
289
|
-
if (COL.blackPoints && h[2] > 0.25) col = mix3(col, COL.points, smoothstep(0.25, 0.32, h[2]) * 0.6);
|
|
290
|
-
if (h[2] < -0.13) col = mix3(col, COL.body, smoothstep(-0.13, -0.2, h[2]));
|
|
291
|
-
// eye rims (dark skin)
|
|
292
|
-
for (const e of eyePrims) {
|
|
293
|
-
const de = SDFModel.dist(e, p[0], p[1], p[2]);
|
|
294
|
-
if (Math.abs(de) < 0.006) mark = Math.min(mark, Math.abs(de) - 0.0035);
|
|
295
|
-
if (Math.abs(de) < 0.0024) { mat = MAT.DARK_SKIN; fl = 0; }
|
|
296
|
-
else if (Math.abs(de) < 0.006) fl = Math.min(fl, 0.002);
|
|
297
|
-
}
|
|
298
|
-
// nostrils: dark rims
|
|
299
|
-
// (dark, moist lining; SKIN's subsurface scattering read as a pink rim)
|
|
300
|
-
for (const e of nostrilPrims) {
|
|
301
|
-
const dn = SDFModel.dist(e, p[0], p[1], p[2]);
|
|
302
|
-
if (dn < 0.004) { mat = MAT.DARK_SKIN; col = COL.muzzle; fl = 0; mark = Math.min(mark, dn - 0.003); }
|
|
303
|
-
}
|
|
304
|
-
// lips: head vertices near the jaw surface, jaw vertices near the head surface
|
|
305
|
-
// (the closed mouth shows a dark crease where the lips meet; the pink mucosa only on the surfaces
|
|
306
|
-
// the other lip hides, which show when the mouth opens)
|
|
307
|
-
if (partOf[v] !== 2) {
|
|
308
|
-
const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
|
|
309
|
-
if (dj < -0.002 && h[2] > 0.2) { mat = MAT.MOUTH; fl = 0; }
|
|
310
|
-
else if (dj < 0.008 && h[2] > 0.2) { mark = Math.min(mark, dj - 0.0045); fl = Math.min(fl, 0.0015); }
|
|
311
|
-
} else {
|
|
312
|
-
const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
|
|
313
|
-
if (dh < -0.002) { mat = MAT.MOUTH; fl = 0; }
|
|
314
|
-
else if (dh < 0.008) { mark = Math.min(mark, dh - 0.0045); fl = Math.min(fl, 0.0015); }
|
|
315
|
-
}
|
|
316
|
-
void lipLocal;
|
|
317
|
-
}
|
|
318
|
-
// white markings (not on keratin / mouth / eye skin)
|
|
319
|
-
// (the pattern field stays continuous: capped just above zero where there is nothing, so a
|
|
320
|
-
// triangle never interpolates across a jump and grows a stray edge line)
|
|
321
|
-
const CAP = 0.02;
|
|
322
|
-
let pc = COL.white, pi = 1, pd = CAP;
|
|
323
|
-
if ((mat === MAT.FUR || mat === MAT.SKIN) && params.coat !== 'grey') pd = Math.min(CAP, wsdf);
|
|
324
|
-
if (dapples && (r === 0 || r === 1)) {
|
|
325
|
-
const [sd] = featureDistance(dapples, p, n, 1 + 0.25 * (vnoise3(p[0] * 40, p[1] * 40, p[2] * 40) - 0.5));
|
|
326
|
-
pd = Math.min(CAP, sd); pi = 0.75 * (1 - legness[v]) * (1 - ventral[v]); pc = COL.dapple;
|
|
327
|
-
}
|
|
328
|
-
// low-frequency colour variation (and sheen dapples on solid coats)
|
|
329
|
-
const cv = fbm3(p[0] * 5, p[1] * 5, p[2] * 5, 3) - 0.5;
|
|
330
|
-
if (mat === MAT.FUR) col = [col[0] * (1 + 0.14 * cv), col[1] * (1 + 0.12 * cv), col[2] * (1 + 0.1 * cv)];
|
|
331
|
-
// long hair: lock-to-lock shade streaks along the hair
|
|
332
|
-
if (r === 3 || r === 7) {
|
|
333
|
-
const c = [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]];
|
|
334
|
-
const along = dot(p, c), q = sub(p, mul(c, along));
|
|
335
|
-
const st = vnoise3(q[0] * 90, q[1] * 90, q[2] * 90 + along * 3), lk = vnoise3(q[0] * 24 + 7.1, q[1] * 24, q[2] * 24 + along * 1.5);
|
|
336
|
-
col = mul(col, (0.75 + 0.5 * st) * (0.82 + 0.36 * lk));
|
|
337
|
-
}
|
|
338
|
-
pattern[v] = pd; patInt[v] = pi;
|
|
339
|
-
patCol[v * 3] = pc[0]; patCol[v * 3 + 1] = pc[1]; patCol[v * 3 + 2] = pc[2];
|
|
340
|
-
markSDF[v] = mark;
|
|
341
|
-
furLen[v] = fl;
|
|
342
|
-
tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
|
|
343
|
-
surf[v * 4] = gloss;
|
|
344
|
-
}
|
|
345
|
-
// foals: fluffy body coat
|
|
346
|
-
if (foal) for (let v = 0; v < nV; v++) if (tint[v * 4 + 3] === MAT.FUR && region[v] <= 1) furLen[v] = Math.max(furLen[v], 0.016);
|
|
347
|
-
// smooth fur length a little so shells do not step (hair, skin and keratin keep their own)
|
|
348
|
-
smoothField(furLen, weights.neighbors, 2, (v) => tint[v * 4 + 3] !== MAT.FUR);
|
|
349
|
-
// one coat across the head / neck boundary: no parting in the hair flow along the jowl margin and
|
|
350
|
-
// behind the poll (coatSeam.js)
|
|
351
|
-
const coatSeam = blendHeadNeckCoat(ctx, { comb, furLen, tint, region });
|
|
352
|
-
|
|
353
|
-
const patternColor = new Float32Array(nV * 4);
|
|
354
|
-
for (let v = 0; v < nV; v++) { patternColor[v * 4] = patCol[v * 3]; patternColor[v * 4 + 1] = patCol[v * 3 + 1]; patternColor[v * 4 + 2] = patCol[v * 3 + 2]; patternColor[v * 4 + 3] = patInt[v]; }
|
|
355
|
-
// the head -> neck hand-over is spread across the throat latch (no fold when the poll flexes), and
|
|
356
|
-
// the head and body copies of the throat / upper neck take one set of skin weights across the
|
|
357
|
-
// cross-fade band (seamWeights.js; after the coat, which reads each copy's own weights)
|
|
358
|
-
const throat = spreadThroatWeights(ctx);
|
|
359
|
-
const seam = unifyNeckWeights(ctx);
|
|
360
|
-
return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, stats: { dapples: dapples ? dapples.spots.length : 0, neckWeightsShared: seam.changed, coatSeamBlended: coatSeam.changed, throatWeightsSpread: throat.changed }, region, ventral };
|
|
361
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
// One coat across the head / neck boundary: hair flow, hair length and colour run smoothly from the
|
|
2
|
-
// face onto the throat and the upper neck.
|
|
3
|
-
//
|
|
4
|
-
// The coat regions (coat.js) give the head (region 2) its own hair flow, combed from the forehead whorl
|
|
5
|
-
// down the face toward the muzzle, and the neck (region 1) the flow down the neck from the axial chain;
|
|
6
|
-
// the head's short face hair (5 mm) and the neck's 8 mm coat meet on one line, the boundary of region 2:
|
|
7
|
-
// across the throat latch and the back of the jowl (where the axial coordinate passes the occiput) and
|
|
8
|
-
// up behind the poll (the neck cut, neckS = 0.03). On that line the hair flow turned by up to ~160 deg
|
|
9
|
-
// from one vertex to the next: the jowl's hair points to the muzzle, the throat's down the neck, so the
|
|
10
|
-
// two coats parted along the line and the dark roots and the shaded base under the shells showed
|
|
11
|
-
// through as a thin dark line. It reads most when the jowl margin turns edge-on (the poll flexed in a
|
|
12
|
-
// rear, a head looking down to the side): a dark crease across the throat and up behind the eye.
|
|
13
|
-
//
|
|
14
|
-
// Here the hair direction, length and colour of both coats are blurred over the surface (both mesh
|
|
15
|
-
// copies together, splatted into a grid by vertex area and fade, Gaussian sigma SIGMA) and mixed in
|
|
16
|
-
// along the boundary only (by the blurred region indicator: full within ~3 cm of the line, nothing
|
|
17
|
-
// past ~9 cm), so the face, the poll and the neck keep their own coat and the hair turns from the
|
|
18
|
-
// face's flow to the neck's over ~10 cm of skin instead of between two vertices.
|
|
19
|
-
import { smoothstep } from '../../core/math/vec.js';
|
|
20
|
-
import { MAT } from '../../core/build/coatKit.js';
|
|
21
|
-
|
|
22
|
-
const SIGMA = 0.035;
|
|
23
|
-
|
|
24
|
-
export function blendHeadNeckCoat(ctx, { comb, furLen, tint, region }, sigma = SIGMA) {
|
|
25
|
-
const { pos, nrm, nV, fade } = ctx;
|
|
26
|
-
// fur of the head and the neck, both mesh copies (the jaw, the ears, the mane keep their own)
|
|
27
|
-
const usable = (v) => (region[v] === 1 || region[v] === 2) && tint[v * 4 + 3] === MAT.FUR;
|
|
28
|
-
let mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9], any2 = false;
|
|
29
|
-
for (let v = 0; v < nV; v++) {
|
|
30
|
-
if (!usable(v) || region[v] !== 2) continue;
|
|
31
|
-
any2 = true;
|
|
32
|
-
for (let k = 0; k < 3; k++) { mn[k] = Math.min(mn[k], pos[v * 3 + k]); mx[k] = Math.max(mx[k], pos[v * 3 + k]); }
|
|
33
|
-
}
|
|
34
|
-
if (!any2) return { changed: 0 };
|
|
35
|
-
const c = sigma / 2, pad = 4 * sigma;
|
|
36
|
-
mn = mn.map((x) => x - pad); mx = mx.map((x) => x + pad);
|
|
37
|
-
const G = mn.map((x, k) => Math.ceil((mx[k] - x) / c) + 2);
|
|
38
|
-
// channels: comb xyz, hair length, colour rgb, region-2 indicator, weight
|
|
39
|
-
const NC = 9, cells = G[0] * G[1] * G[2];
|
|
40
|
-
const grid = new Float32Array(cells * NC);
|
|
41
|
-
const at = (i, j, k) => (i + G[0] * (j + G[1] * k)) * NC;
|
|
42
|
-
const cellOf = (v) => [0, 1, 2].map((k) => (pos[v * 3 + k] - mn[k]) / c);
|
|
43
|
-
const inside = (g) => g.every((x, k) => x >= 0 && x <= G[k] - 2);
|
|
44
|
-
// vertex areas
|
|
45
|
-
const area = new Float32Array(nV);
|
|
46
|
-
const { index } = ctx;
|
|
47
|
-
for (let i = 0; i < index.length; i += 3) {
|
|
48
|
-
const a = index[i], b = index[i + 1], d = index[i + 2];
|
|
49
|
-
const ux = pos[b * 3] - pos[a * 3], uy = pos[b * 3 + 1] - pos[a * 3 + 1], uz = pos[b * 3 + 2] - pos[a * 3 + 2];
|
|
50
|
-
const wx = pos[d * 3] - pos[a * 3], wy = pos[d * 3 + 1] - pos[a * 3 + 1], wz = pos[d * 3 + 2] - pos[a * 3 + 2];
|
|
51
|
-
const A = (0.5 * Math.hypot(uy * wz - uz * wy, uz * wx - ux * wz, ux * wy - uy * wx)) / 3;
|
|
52
|
-
area[a] += A; area[b] += A; area[d] += A;
|
|
53
|
-
}
|
|
54
|
-
const tmp = new Float32Array(NC);
|
|
55
|
-
for (let v = 0; v < nV; v++) {
|
|
56
|
-
if (!usable(v)) continue;
|
|
57
|
-
const g = cellOf(v);
|
|
58
|
-
if (!inside(g)) continue;
|
|
59
|
-
const a = area[v] * Math.max(0, fade ? fade[v] : 1);
|
|
60
|
-
if (!(a > 0)) continue;
|
|
61
|
-
tmp[0] = comb[v * 3]; tmp[1] = comb[v * 3 + 1]; tmp[2] = comb[v * 3 + 2];
|
|
62
|
-
tmp[3] = furLen[v];
|
|
63
|
-
tmp[4] = tint[v * 4]; tmp[5] = tint[v * 4 + 1]; tmp[6] = tint[v * 4 + 2];
|
|
64
|
-
tmp[7] = region[v] === 2 ? 1 : 0; tmp[8] = 1;
|
|
65
|
-
const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
|
|
66
|
-
const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
|
|
67
|
-
for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
|
|
68
|
-
const t = a * (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
|
|
69
|
-
const o = at(i0 + di, j0 + dj, k0 + dk);
|
|
70
|
-
for (let q = 0; q < NC; q++) grid[o + q] += t * tmp[q];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// separable Gaussian blur
|
|
74
|
-
const R = Math.ceil((3 * sigma) / c), ker = [];
|
|
75
|
-
for (let d = -R; d <= R; d++) ker.push(Math.exp(-0.5 * ((d * c) / sigma) ** 2));
|
|
76
|
-
const buf = new Float32Array(grid.length);
|
|
77
|
-
for (let ax = 0; ax < 3; ax++) {
|
|
78
|
-
buf.fill(0);
|
|
79
|
-
for (let k = 0; k < G[2]; k++) for (let j = 0; j < G[1]; j++) for (let i = 0; i < G[0]; i++) {
|
|
80
|
-
const o = at(i, j, k);
|
|
81
|
-
if (grid[o + NC - 1] === 0) continue;
|
|
82
|
-
for (let d = -R; d <= R; d++) {
|
|
83
|
-
const ii = i + (ax === 0 ? d : 0), jj = j + (ax === 1 ? d : 0), kk = k + (ax === 2 ? d : 0);
|
|
84
|
-
if (ii < 0 || jj < 0 || kk < 0 || ii >= G[0] || jj >= G[1] || kk >= G[2]) continue;
|
|
85
|
-
const o2 = at(ii, jj, kk), w = ker[d + R];
|
|
86
|
-
for (let q = 0; q < NC; q++) buf[o2 + q] += w * grid[o + q];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
grid.set(buf);
|
|
90
|
-
}
|
|
91
|
-
// sample back; mix in along the boundary
|
|
92
|
-
let changed = 0;
|
|
93
|
-
for (let v = 0; v < nV; v++) {
|
|
94
|
-
if (!usable(v)) continue;
|
|
95
|
-
const g = cellOf(v);
|
|
96
|
-
if (!inside(g)) continue;
|
|
97
|
-
const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
|
|
98
|
-
const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
|
|
99
|
-
tmp.fill(0);
|
|
100
|
-
for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
|
|
101
|
-
const t = (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
|
|
102
|
-
const o = at(i0 + di, j0 + dj, k0 + dk);
|
|
103
|
-
for (let q = 0; q < NC; q++) tmp[q] += t * grid[o + q];
|
|
104
|
-
}
|
|
105
|
-
const W = tmp[NC - 1];
|
|
106
|
-
if (!(W > 1e-9)) continue;
|
|
107
|
-
const gb = tmp[7] / W;
|
|
108
|
-
const m = smoothstep(0.02, 0.25, Math.min(gb, 1 - gb));
|
|
109
|
-
if (m <= 0) continue;
|
|
110
|
-
// hair direction: the blurred flow, mixed in and laid back into the skin's tangent plane
|
|
111
|
-
const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
|
|
112
|
-
let d = [0, 1, 2].map((k) => comb[v * 3 + k] * (1 - m) + (tmp[k] / W) * m);
|
|
113
|
-
const dn = d[0] * n[0] + d[1] * n[1] + d[2] * n[2];
|
|
114
|
-
d = d.map((x, k) => x - dn * n[k]);
|
|
115
|
-
const l = Math.hypot(d[0], d[1], d[2]);
|
|
116
|
-
if (l > 1e-4) for (let k = 0; k < 3; k++) comb[v * 3 + k] = d[k] / l;
|
|
117
|
-
furLen[v] = furLen[v] * (1 - m) + (tmp[3] / W) * m;
|
|
118
|
-
for (let k = 0; k < 3; k++) tint[v * 4 + k] = tint[v * 4 + k] * (1 - m) + (tmp[4 + k] / W) * m;
|
|
119
|
-
changed++;
|
|
120
|
-
}
|
|
121
|
-
return { changed };
|
|
122
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// Horse (Equus caballus). The first hoofed animal: cannon / pastern / hoof anatomy with a separate
|
|
2
|
-
// hoof bone (fetlock sink under load, hoof flip in swing), long mane and tail hair, a rideable back
|
|
3
|
-
// with tack attachment points, the four natural gaits and the horse's signature behaviours.
|
|
4
|
-
import { horseRig, HEAD_O } from './rig.js';
|
|
5
|
-
import { sculptHorse, EYE } from './sculpt.js';
|
|
6
|
-
import { horseRegions } from './regions.js';
|
|
7
|
-
import { horseCoat, COATS } from './coat.js';
|
|
8
|
-
import { srgb } from '../../core/build/coatKit.js';
|
|
9
|
-
import { motion } from './motion.js';
|
|
10
|
-
|
|
11
|
-
const pick = (R, table) => {
|
|
12
|
-
let x = R() * table.reduce((s, [, w]) => s + w, 0);
|
|
13
|
-
for (const [k, w] of table) { x -= w; if (x <= 0) return k; }
|
|
14
|
-
return table[table.length - 1][0];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default {
|
|
18
|
-
id: 'horse',
|
|
19
|
-
name: 'Horse',
|
|
20
|
-
latin: 'Equus caballus',
|
|
21
|
-
group: 'hooves',
|
|
22
|
-
plan: 'quadruped',
|
|
23
|
-
covering: 'fur',
|
|
24
|
-
|
|
25
|
-
// Seeded individual. Sex: mare, gelding or stallion (crest and sheath); age: adult or foal
|
|
26
|
-
// (juvenile: long legs, short body, big head, bristly mane, short fluffy tail); size, proportions
|
|
27
|
-
// (sport horse <-> lighter thoroughbred type <-> stockier type); coat colour and white markings.
|
|
28
|
-
// o.variant may force a coat colour ('bay', 'chestnut', 'grey', ...).
|
|
29
|
-
variation(R, o) {
|
|
30
|
-
const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
|
|
31
|
-
const age = o.age || 'adult';
|
|
32
|
-
const foal = age === 'juvenile';
|
|
33
|
-
const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
|
|
34
|
-
const gelding = sex === 'male' && R() < 0.65;
|
|
35
|
-
const type = g(); // - lighter / leggier, + stockier
|
|
36
|
-
const size = (sex === 'male' ? 1.02 : 0.98) * (1 + 0.045 * g()) * (foal ? 0.5 : 1);
|
|
37
|
-
const coat = COATS[o.variant] ? o.variant : pick(R, [['bay', 30], ['darkbay', 10], ['chestnut', 22], ['black', 8], ['grey', 12], ['palomino', 7], ['dun', 6], ['buckskin', 5]]);
|
|
38
|
-
const face = pick(R, [['none', 35], ['star', 20], ['stripe', 10], ['blaze', 20], ['snip', 5], ['starsnip', 10]]);
|
|
39
|
-
const sockH = () => pick(R, [[0, 60], [0.085, 8], [0.13, 10], [0.24, 14], [0.45, 8]]);
|
|
40
|
-
const socks = [sockH(), sockH(), sockH(), sockH()];
|
|
41
|
-
if (R() < 0.3) socks[2] = socks[3] = Math.max(socks[2], socks[3]);
|
|
42
|
-
return {
|
|
43
|
-
sex, age, size, gelding,
|
|
44
|
-
crest: sex === 'male' ? (gelding ? 0.45 : 1) + 0.15 * g() : 0.15 + 0.1 * g(),
|
|
45
|
-
coat, face, socks,
|
|
46
|
-
flaxen: coat === 'chestnut' && R() < 0.3,
|
|
47
|
-
greyLevel: coat === 'grey' ? (foal ? 0.05 : 0.15 + 0.75 * R()) : 0,
|
|
48
|
-
maneLen: 0.14 + 0.12 * R(),
|
|
49
|
-
maneSide: R() < 0.8 ? -1 : 1,
|
|
50
|
-
coatShade: g(), coatLightness: 0.06 * g(),
|
|
51
|
-
coatSeed: Math.floor(R() * 1e6),
|
|
52
|
-
minThick: 0.006,
|
|
53
|
-
warps: [
|
|
54
|
-
{ type: 'legs', k: (1 + 0.035 * g() - 0.025 * type) * (foal ? 1.55 : 1), top: 0.95 },
|
|
55
|
-
{ type: 'length', k: (1 + 0.03 * g() - 0.015 * type) * (foal ? 0.86 : 1), z0: -0.62, z1: 0.62 },
|
|
56
|
-
{ type: 'girth', k: (1 + 0.05 * type) * (sex === 'male' && !gelding ? 1.02 : 1), cy: 1.25, z0: -0.7, z1: 0.75 },
|
|
57
|
-
{ type: 'scaleAbout', c: HEAD_O, k: (1 + 0.03 * g()) * (foal ? 1.22 : 1), r0: 0.2, r1: 0.4 },
|
|
58
|
-
],
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
rig: () => horseRig(),
|
|
62
|
-
sculpt: (m, rig, params) => sculptHorse(m, rig, params),
|
|
63
|
-
regions: horseRegions,
|
|
64
|
-
coat: horseCoat,
|
|
65
|
-
eyeSpecs: () => [1, -1].map((side) => ({ side, spec: EYE, headOrigin: HEAD_O, bone: 'head', look: EYE_LOOK })),
|
|
66
|
-
motion,
|
|
67
|
-
render: { markColor: srgb(0x241f1d), strandDensity: 900, clumpDensity: 1, shellScale: 1, finWidth: 0.45 },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Horizontal bar pupil with corpora nigra, dark brown iris, sclera hidden, dark lids.
|
|
71
|
-
export const EYE_LOOK = {
|
|
72
|
-
pupil: 'bar',
|
|
73
|
-
lids: 'mammal',
|
|
74
|
-
// (dark brown, light enough that the iris and the bar pupil read under the catchlight: near-black
|
|
75
|
-
// read as a black button, and as blue-grey glass under a sky reflection)
|
|
76
|
-
iris: { inner: srgb(0x2a170c), mid: srgb(0x5c3a20), hi: srgb(0x8a5a2e), outer: srgb(0x1e1109) },
|
|
77
|
-
sclera: { color: [0.55, 0.5, 0.45], visible: false },
|
|
78
|
-
lidColor: [0.025, 0.02, 0.02],
|
|
79
|
-
pupilSize: [0.12, 0.4],
|
|
80
|
-
};
|