@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,316 +0,0 @@
|
|
|
1
|
-
// The horse, sculpted as a signed distance field.
|
|
2
|
-
// Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw).
|
|
3
|
-
// The head is modelled in its own frame (see rig.js: hl()), measured from the profile and frontal
|
|
4
|
-
// references; the body relative to the joints so the sculpt follows the skeleton.
|
|
5
|
-
import { HEAD_O, HZ, HY, hl, TAIL_SEGS, DOCK_SEGS } from './rig.js';
|
|
6
|
-
import { add, sub, mul, lerp, norm, cross, len, rng } from '../../core/math/vec.js';
|
|
7
|
-
import { sculptEyeSocket, apertureTiltAlong } from '../../core/sdf/eyeSocket.js';
|
|
8
|
-
import { SDFModel } from '../../core/sdf/sdf.js';
|
|
9
|
-
|
|
10
|
-
// Eye (head-local centre -> world offset from HEAD_O). The horse eye is among the largest of land
|
|
11
|
-
// mammals: globe ~50 mm across, palpebral opening ~45 x 30 mm, set on the side of the head and
|
|
12
|
-
// looking out and a little forward and down.
|
|
13
|
-
const EYE_LOCAL = [0.092, -0.004, -0.01]; // (large, high on the head; bulging a little under the brow)
|
|
14
|
-
const eyeDir = (() => {
|
|
15
|
-
// lateral, 22 degrees forward along the nasal line
|
|
16
|
-
const a = (22 * Math.PI) / 180;
|
|
17
|
-
return norm(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))));
|
|
18
|
-
})();
|
|
19
|
-
const EYE_BASE = {
|
|
20
|
-
c: sub(hl(EYE_LOCAL), HEAD_O), r: 0.026, back: 0.005, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
|
|
21
|
-
lid: 0.0026, R: 0.0262, d: 0.0133, off: -0.0015, tilt: 0, irisZ: 0.0137, irisR: 0.0182,
|
|
22
|
-
};
|
|
23
|
-
// roll the almond so its long axis follows the nasal line with the upper lid up (a search over a
|
|
24
|
-
// limited range stood the almond on end: -99 deg, lids and blinks facing the ear), inner corner a
|
|
25
|
-
// little lower: the slit runs ~27 deg below horizontal toward the nose, as in head_profile.jpg
|
|
26
|
-
EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) - 0.12;
|
|
27
|
-
export const EYE = EYE_BASE;
|
|
28
|
-
|
|
29
|
-
const X = [1, 0, 0];
|
|
30
|
-
|
|
31
|
-
export function sculptHorse(m, rig, params = {}) {
|
|
32
|
-
const J = rig.J;
|
|
33
|
-
const crest = params.crest ?? 0.3; // 0 mare .. 1 stallion
|
|
34
|
-
const foal = params.age === 'juvenile';
|
|
35
|
-
const R = rng(9173 + (params.coatSeed || 0));
|
|
36
|
-
|
|
37
|
-
const ellY = (o) => {
|
|
38
|
-
const y = norm(o.ydir);
|
|
39
|
-
const lat = o.lateral || X;
|
|
40
|
-
const z = norm(cross(lat, y));
|
|
41
|
-
return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
|
|
42
|
-
};
|
|
43
|
-
// ellipsoid in head-local coordinates: r = [lateral, dorsal, along the head]
|
|
44
|
-
const H = { bone: 'head' };
|
|
45
|
-
const hell = (o) => m.ell({ ...H, ...o, c: hl(o.c), axis: o.axisL ? norm(hdir(o.axisL)) : HZ, up: o.upL ? norm(hdir(o.upL)) : HY });
|
|
46
|
-
const hdir = (v) => [v[0], v[1] * HY[1] + v[2] * HZ[1], v[1] * HY[2] + v[2] * HZ[2]];
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------- TORSO
|
|
49
|
-
// barrel: deep chest (girth ~1.95 m), sternum ~0.88 m above the ground, belly slightly lower
|
|
50
|
-
m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 1.26, 0.14], r: [0.285, 0.345, 0.56], axis: norm([0, 0.06, 1]), k: 0 });
|
|
51
|
-
m.ell({ tag: 'girth', bone: 'chest', c: [0, 1.22, 0.46], r: [0.24, 0.29, 0.2], k: 0.08 });
|
|
52
|
-
m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 1.25, -0.16], r: [0.29, 0.29, 0.32], axis: norm([0, 0.16, -1]), k: 0.1 });
|
|
53
|
-
m.ell({ tag: 'flank', bone: 'spine1', c: [0, 1.32, -0.38], r: [0.26, 0.22, 0.22], k: 0.1 });
|
|
54
|
-
// withers: the dorsal spines of T3-T9 make a sharp ridge between the scapulae
|
|
55
|
-
m.ell({ tag: 'withers', bone: 'chest', c: [0, 1.5, 0.44], r: [0.075, 0.12, 0.22], axis: norm([0, -0.2, 1]), k: 0.1 });
|
|
56
|
-
m.ell({ tag: 'back', bone: 'spine3', c: [0, 1.46, 0.08], r: [0.17, 0.085, 0.36], k: 0.1 });
|
|
57
|
-
m.ell({ tag: 'loin', bone: 'spine1', c: [0, 1.48, -0.3], r: [0.2, 0.08, 0.22], k: 0.1 });
|
|
58
|
-
// hindquarters: croup, points of hip, buttocks
|
|
59
|
-
m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 1.41, -0.6], r: [0.27, 0.21, 0.26], k: 0.08 });
|
|
60
|
-
m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 1.5, -0.58], r: [0.2, 0.09, 0.27], axis: norm([0, -0.12, 1]), k: 0.08 });
|
|
61
|
-
for (const s of [1, -1]) {
|
|
62
|
-
m.sphere({ tag: 'hippoint', bone: 'pelvis', c: [0.235 * s, 1.44, -0.43], rad: 0.065, k: 0.08 });
|
|
63
|
-
m.ell({ tag: 'rump', bone: 'pelvis', c: [0.13 * s, 1.33, -0.72], r: [0.13, 0.19, 0.12], k: 0.08 });
|
|
64
|
-
}
|
|
65
|
-
// chest front: the pectorals make a broad, rounded breast between the forelegs
|
|
66
|
-
m.ell({ tag: 'brisket', bone: 'chest', c: [0, 1.08, 0.64], r: [0.17, 0.16, 0.17], k: 0.08 });
|
|
67
|
-
for (const s of [1, -1]) {
|
|
68
|
-
m.ell({ tag: 'pectoral', bone: 'chest', c: [0.105 * s, 1.07, 0.75], r: [0.1, 0.14, 0.09], k: 0.07 });
|
|
69
|
-
}
|
|
70
|
-
if (params.sex === 'male') {
|
|
71
|
-
// sheath between the hind legs
|
|
72
|
-
m.ell({ tag: 'sheath', bone: 'spine1', c: [0, 0.94, -0.2], r: [0.045, 0.055, 0.1], axis: norm([0, 0.3, 1]), k: 0.05 });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------- NECK
|
|
76
|
-
// laterally flattened, deep at the base, rising to the poll; crest heavier in stallions
|
|
77
|
-
// The neck runs from its base (withers top 1.63 m / chest front 0.86 m) to the head (poll /
|
|
78
|
-
// throat latch), arched along the crest, deep at the base and narrow at the throat latch.
|
|
79
|
-
const WITHERS = [0, 1.64, 0.5], CHESTF = [0, 1.15, 0.86], POLLT = hl([0, 0, -0.2]), LATCH = hl([0, -0.17, 0.07]);
|
|
80
|
-
const cBase = lerp(WITHERS, CHESTF, 0.5), cTop = lerp(POLLT, LATCH, 0.5);
|
|
81
|
-
const nAx = norm(sub(cTop, cBase)), nUp = norm([0, nAx[2], -nAx[1]]); // dorsal side of the neck
|
|
82
|
-
const neckAt = (t) => lerp(cBase, cTop, t);
|
|
83
|
-
// [t along the neck, half width, half depth, half length, bone]
|
|
84
|
-
for (const [t, hw, hd, hlen, bone] of [[0.12, 0.2, 0.27, 0.2, 'neck1'], [0.38, 0.155, 0.215, 0.2, 'neck1'], [0.62, 0.12, 0.17, 0.18, 'neck2'], [0.84, 0.095, 0.13, 0.14, 'neck2']]) {
|
|
85
|
-
m.ell({ tag: 'neck', bone, c: neckAt(t), r: [hw, hd, hlen], axis: nAx, up: nUp, k: 0.09 });
|
|
86
|
-
}
|
|
87
|
-
// crest: an arch along the top of the neck from the withers to the poll (heavier in stallions)
|
|
88
|
-
const crestTop = (t) => { const q = lerp(WITHERS, POLLT, t); return add(q, mul(nUp, 0.045 * Math.sin(Math.PI * t))); };
|
|
89
|
-
const cd = norm(sub(POLLT, WITHERS));
|
|
90
|
-
for (const [t, r0, bone] of [[0.3, 0.075, 'neck1'], [0.62, 0.06, 'neck2'], [0.86, 0.05, 'neck2']]) {
|
|
91
|
-
const c = add(crestTop(t), mul(nUp, -(r0 + 0.012)));
|
|
92
|
-
m.ell({ tag: 'crest', bone, c, r: [r0 + 0.035 * crest, r0 + 0.025 * crest, 0.2], axis: cd, up: nUp, k: 0.08 });
|
|
93
|
-
}
|
|
94
|
-
// underline of the neck (trachea, jugular groove) to a narrow throat latch
|
|
95
|
-
const ud = norm(sub(LATCH, CHESTF)), inU = mul(norm([0, ud[2], -ud[1]]), 1);
|
|
96
|
-
m.cone({ tag: 'throat', bone: 'neck1', a: add(lerp(CHESTF, LATCH, 0.08), mul(inU, 0.08)), b: add(lerp(CHESTF, LATCH, 0.55), mul(inU, 0.06)), ra: 0.08, rb: 0.06, k: 0.08 });
|
|
97
|
-
m.cone({ tag: 'throat', bone: 'neck2', a: add(lerp(CHESTF, LATCH, 0.55), mul(inU, 0.06)), b: hl([0, -0.13, -0.03]), ra: 0.06, rb: 0.045, k: 0.07 });
|
|
98
|
-
|
|
99
|
-
// ---------------------------------------------------------------- HEAD (head-local, see rig.js)
|
|
100
|
-
// landmarks from head_profile.jpg: forehead top y 0.087 at z -0.05, nasal bridge y 0.058 at 0.15,
|
|
101
|
-
// muzzle top 0.028 at 0.36, nostril (0.035, -0.019, 0.355), chin y -0.1 at 0.4, jowl bottom
|
|
102
|
-
// y -0.176 at z 0.11, throat latch (-0.17, 0.07), eyes (+-0.083, -0.012, 0)
|
|
103
|
-
hell({ tag: 'cranium', c: [0, 0.02, -0.1], r: [0.085, 0.075, 0.11], k: 0.05 });
|
|
104
|
-
hell({ tag: 'poll', c: [0, -0.01, -0.17], r: [0.065, 0.065, 0.06], k: 0.05 });
|
|
105
|
-
hell({ tag: 'forehead', c: [0, 0.045, 0.0], r: [0.095, 0.042, 0.12], k: 0.04 });
|
|
106
|
-
hell({ tag: 'face', c: [0, 0.008, 0.2], r: [0.056, 0.042, 0.19], k: 0.04 }); // (a slightly dished nasal line)
|
|
107
|
-
hell({ tag: 'lowerface', c: [0, -0.055, 0.21], r: [0.052, 0.062, 0.15], k: 0.05 });
|
|
108
|
-
hell({ tag: 'muzzle', c: [0, -0.026, 0.35], r: [0.05, 0.048, 0.06], k: 0.04 });
|
|
109
|
-
hell({ tag: 'upperlip', c: [0, -0.066, 0.372], r: [0.044, 0.026, 0.046], k: 0.025 });
|
|
110
|
-
for (const s of [1, -1]) {
|
|
111
|
-
// masseter: the big round cheek; mandible's lower border from the jowl to the chin
|
|
112
|
-
hell({ tag: 'cheek', c: [0.055 * s, -0.075, 0.07], r: [0.048, 0.095, 0.11], k: 0.05 });
|
|
113
|
-
m.cone({ ...H, tag: 'mandible', a: hl([0.05 * s, -0.145, 0.07]), b: hl([0.028 * s, -0.108, 0.3]), ra: 0.03, rb: 0.02, k: 0.04 });
|
|
114
|
-
hell({ tag: 'facialcrest', c: [0.075 * s, -0.04, 0.08], r: [0.016, 0.018, 0.08], k: 0.03 });
|
|
115
|
-
hell({ tag: 'brow', c: [0.076 * s, 0.034, -0.008], r: [0.03, 0.022, 0.04], k: 0.03 }); // (bony orbital ridge over the eye)
|
|
116
|
-
hell({ tag: 'nostrilwing', c: [0.036 * s, -0.02, 0.352], r: [0.02, 0.026, 0.032], k: 0.02 });
|
|
117
|
-
// eye: orbit hollow, lids, almond aperture
|
|
118
|
-
sculptEyeSocket(m, EYE, HEAD_O, s, { orbit: { r: [0.03, 0.022, 0.018], at: [0.0, 0.002, 0.03], k: 0.012 } });
|
|
119
|
-
}
|
|
120
|
-
// nostrils: a large comma on each side of the muzzle, facing forward and out, the round opening
|
|
121
|
-
// low and its tail (the false nostril) rising up and out. The carves sit a measured depth under
|
|
122
|
-
// the skin, so they cut the surface at a steep angle (a carve grazing the skin cut a ragged sliver).
|
|
123
|
-
{
|
|
124
|
-
const muzPrims = m.prims.filter((q) => q.bone === 'head' && q.part !== 'jaw');
|
|
125
|
-
for (const s of [1, -1]) {
|
|
126
|
-
const nL = norm([0.6 * s, 0.05, 0.8]); // head-local opening direction
|
|
127
|
-
const skinAt = (o) => { // head-local point where the ray o + t nL leaves the muzzle
|
|
128
|
-
let lo = 0, hi = 0.12;
|
|
129
|
-
for (let i = 0; i < 30; i++) { const t = 0.5 * (lo + hi), q = hl(add(o, mul(nL, t))); if (SDFModel.evalList(muzPrims, q[0], q[1], q[2]) < 0) lo = t; else hi = t; }
|
|
130
|
-
return add(o, mul(nL, 0.5 * (lo + hi)));
|
|
131
|
-
};
|
|
132
|
-
const low = skinAt([0.02 * s, -0.03, 0.34]), high = skinAt([0.03 * s, -0.004, 0.33]);
|
|
133
|
-
const up = norm(sub(high, low));
|
|
134
|
-
// opening: round low part, narrower tail above (merged into one comma)
|
|
135
|
-
hell({ tag: 'nostril', c: sub(low, mul(nL, 0.011)), r: [0.013, 0.017, 0.028], axisL: nL, upL: up, k: 0.006, carve: true });
|
|
136
|
-
hell({ tag: 'nostril', c: sub(add(low, mul(up, 0.022)), mul(nL, 0.012)), r: [0.0085, 0.014, 0.024], axisL: nL, upL: up, k: 0.008, carve: true });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------------------------------------------------------------- JAW (lower lip and chin: the mouth opens)
|
|
141
|
-
const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
|
|
142
|
-
m.ell({ ...JW, tag: 'chin', c: hl([0, -0.104, 0.345]), r: [0.036, 0.03, 0.052], axis: HZ, up: HY, k: 0 });
|
|
143
|
-
m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.09, 0.39]), r: [0.038, 0.022, 0.034], axis: HZ, up: HY, k: 0.02 });
|
|
144
|
-
for (const s of [1, -1]) {
|
|
145
|
-
m.cone({ ...JW, tag: 'mandible', a: hl([0.026 * s, -0.104, 0.29]), b: hl([0.018 * s, -0.102, 0.35]), ra: 0.018, rb: 0.022, k: 0.03 });
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ---------------------------------------------------------------- EARS (long, cupped, pointed)
|
|
149
|
-
for (const S of ['L', 'R']) {
|
|
150
|
-
const s = S === 'L' ? 1 : -1;
|
|
151
|
-
const base = J['earBase' + S], tip = J['earTip' + S];
|
|
152
|
-
const up = norm(sub(tip, base));
|
|
153
|
-
const facing = norm(add(mul(hdir([0, 0, 1]), 0.9), [0.55 * s, 0, 0])); // cup opens forward and out
|
|
154
|
-
const lat = norm(cross(up, facing));
|
|
155
|
-
const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
|
|
156
|
-
ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.42), ydir: up, lateral: lat, r: [0.034, 0.083, 0.02], k: 0.02 });
|
|
157
|
-
ellY({ ...ear, tag: 'eartip', c: lerp(base, tip, 0.8), ydir: up, lateral: lat, r: [0.019, 0.045, 0.013], k: 0.02 });
|
|
158
|
-
ellY({ ...ear, tag: 'earinner', c: add(add(lerp(base, tip, 0.5), mul(facing, 0.016)), mul(up, 0.0)), ydir: up, lateral: lat, r: [0.024, 0.075, 0.014], k: 0.006, carve: true });
|
|
159
|
-
// ear base muscle mass on the poll
|
|
160
|
-
m.sphere({ ...H, tag: 'earbase', c: add(base, mul(up, -0.01)), rad: 0.035, k: 0.035 });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------- FORELOCK AND MANE
|
|
164
|
-
// mane: a ridge of hair on the crest, and the hair falling from it down one side of the neck
|
|
165
|
-
// (usually the right) as one sheet, 14-26 cm long with a ragged lower edge. Shells cannot make long
|
|
166
|
-
// hair, so the sheet is geometry: at each station along the crest two flat ellipsoids laid on the
|
|
167
|
-
// neck's skin (found in the field), overlapping their neighbours so they merge into one sheet; the
|
|
168
|
-
// coat gives it short hair combed straight down with lock-to-lock streaks. (22 round lock cones
|
|
169
|
-
// under 5 cm shells read as a row of curls, and as a braid without the fur.)
|
|
170
|
-
const side = params.maneSide ?? -1;
|
|
171
|
-
const maneLen = foal ? 0.05 : params.maneLen ?? 0.2;
|
|
172
|
-
const neckPrims = m.prims.filter((q) => q.part !== 'jaw' && q.bone !== 'head' && !q.carve);
|
|
173
|
-
const skinX = (q) => { // the neck's skin on the mane side at the height and depth of q (x ignored)
|
|
174
|
-
let lo = 0, hi = 0.45;
|
|
175
|
-
for (let i = 0; i < 28; i++) { const x = 0.5 * (lo + hi); if (SDFModel.evalList(neckPrims, side * x, q[1], q[2]) < 0) lo = x; else hi = x; }
|
|
176
|
-
return [side * 0.5 * (lo + hi), q[1], q[2]];
|
|
177
|
-
};
|
|
178
|
-
const NL = 22;
|
|
179
|
-
for (let i = 0; i < NL; i++) {
|
|
180
|
-
const t = 0.03 + 0.94 * (i + 0.5) / NL;
|
|
181
|
-
const lift = mul(nUp, 0.028 * crest * Math.sin(Math.PI * Math.min(1, t * 1.15)));
|
|
182
|
-
const p = add(crestTop(t), lift), p2 = add(crestTop(Math.min(1, t + 0.04)), lift), dir = norm(sub(p2, p));
|
|
183
|
-
const bone = t < 0.45 ? 'neck1' : 'neck2';
|
|
184
|
-
// (the ridge thins out over the last 15 % toward the poll: the mane runs into the forelock
|
|
185
|
-
// there, a full ridge stood between the ears as a cushion)
|
|
186
|
-
const toPoll = 1 - 0.6 * Math.max(0, Math.min(1, (t - 0.82) / 0.15));
|
|
187
|
-
m.ell({ tag: 'manecrest', bone, c: add(p, mul(nUp, -0.01 - 0.008 * (1 - toPoll))), r: [(0.03 + 0.03 * crest) * (0.6 + 0.4 * toPoll), (foal ? 0.045 : 0.028) * toPoll, 0.06], axis: dir, up: nUp, k: 0.04 });
|
|
188
|
-
}
|
|
189
|
-
if (!foal) {
|
|
190
|
-
const NS = 16, down = norm(add(mul(nUp, -1), [0, 0, -0.15]));
|
|
191
|
-
const tAt = (i) => 0.03 + 0.94 * (i + 0.5) / NS;
|
|
192
|
-
for (let i = 0; i < NS; i++) {
|
|
193
|
-
const t = tAt(i);
|
|
194
|
-
const lift = mul(nUp, 0.028 * crest * Math.sin(Math.PI * Math.min(1, t * 1.15)));
|
|
195
|
-
const p = add(crestTop(t), lift);
|
|
196
|
-
const spacing = len(sub(crestTop(tAt(i + 1)), crestTop(t)));
|
|
197
|
-
const along = norm(sub(crestTop(Math.min(1, t + 0.02)), crestTop(Math.max(0, t - 0.02))));
|
|
198
|
-
const L = maneLen * (0.75 + 0.5 * R()) * (0.65 + 0.35 * Math.sin(Math.PI * Math.min(1, t * 1.25)));
|
|
199
|
-
const bone = t < 0.45 ? 'neck1' : 'neck2';
|
|
200
|
-
const th = 0.0085;
|
|
201
|
-
// the hair leaves the crest over its top: the sheet starts on the crest's far edge
|
|
202
|
-
const depths = [0.02, 0.02 + (L - 0.02) * 0.5, L];
|
|
203
|
-
const sk = depths.map((d) => skinX(add(p, mul(down, d))));
|
|
204
|
-
for (let j = 0; j < 2; j++) {
|
|
205
|
-
const a0 = sk[j], a1 = sk[j + 1];
|
|
206
|
-
const ax = norm(sub(a1, a0));
|
|
207
|
-
const nrm = norm(cross(ax, along)); // out of the skin on the mane side
|
|
208
|
-
const out = nrm[0] * side >= 0 ? nrm : mul(nrm, -1);
|
|
209
|
-
const c = add(lerp(a0, a1, 0.5), mul(out, th * 0.35));
|
|
210
|
-
m.ell({ tag: 'mane', bone, c, r: [spacing * 0.85, th, len(sub(a1, a0)) * 0.58 + 0.01], axis: ax, up: out, k: 0.022 });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (!foal) {
|
|
215
|
-
// forelock: a thin lock of long hair lying flat from the poll down the forehead, ending above the
|
|
216
|
-
// eyes (a chain of flat ellipsoids laid on the skin: shells cannot make long hair, so the
|
|
217
|
-
// geometry carries the lock and the coat keeps its hair short). Two raised pads read as a fur
|
|
218
|
-
// cushion with a hard rim that hid the star.
|
|
219
|
-
const headPrims = m.prims.filter((q) => q.bone === 'head' && q.part !== 'jaw');
|
|
220
|
-
const skinY = (z) => { // forehead skin height (head-local y) on the midline at head-local z
|
|
221
|
-
let lo = 0.0, hi = 0.2; // (y 0 lies inside the cranium / poll from the poll to the eyes)
|
|
222
|
-
for (let i = 0; i < 30; i++) { const y = 0.5 * (lo + hi), q = hl([0, y, z]); if (SDFModel.evalList(headPrims, q[0], q[1], q[2]) < 0) lo = y; else hi = y; }
|
|
223
|
-
return 0.5 * (lo + hi);
|
|
224
|
-
};
|
|
225
|
-
const n = 7, z0 = -0.2, z1 = -0.035;
|
|
226
|
-
for (let i = 0; i < n; i++) {
|
|
227
|
-
const t = i / (n - 1), z = z0 + (z1 - z0) * t;
|
|
228
|
-
const zb = z + 0.3 * (z1 - z0) / (n - 1);
|
|
229
|
-
const y = skinY(z), yb = skinY(zb);
|
|
230
|
-
const half = 0.03 - 0.014 * t; // wide at the poll, narrowing to the tip
|
|
231
|
-
const thick = 0.0085 - 0.0035 * t;
|
|
232
|
-
const sway = 0.006 * Math.sin(3.1 * t + (params.coatSeed || 0)); // not ruler straight
|
|
233
|
-
hell({ tag: 'forelock', c: [sway, y - 0.15 * thick, z], r: [half, thick, 0.034], axisL: [0, yb - y, zb - z], upL: cross(X, [0, yb - y, zb - z]).map((v) => -v), k: 0.012 });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ---------------------------------------------------------------- LEGS
|
|
238
|
-
for (const S of ['L', 'R']) {
|
|
239
|
-
const s = S === 'L' ? 1 : -1;
|
|
240
|
-
const lat = [s, 0, 0];
|
|
241
|
-
const sx = (v) => [v[0] * s, v[1], v[2]];
|
|
242
|
-
// front
|
|
243
|
-
const g = 'F' + S;
|
|
244
|
-
const Sc = J['scapTop' + S], Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S], M = J['mcp' + S], C = J['fcoffin' + S], T = J['ftoe' + S];
|
|
245
|
-
ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.07, 0, -0.02])), ydir: sub(Sh, Sc), lateral: lat, r: [0.08, 0.26, 0.15], k: 0.1, bias: 0.006 });
|
|
246
|
-
m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.02, 0.0, 0.03])), rad: 0.07, k: 0.08 });
|
|
247
|
-
m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.1, rb: 0.085, k: 0.08 });
|
|
248
|
-
ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.01, 0.02, -0.1])), ydir: sub(E, Sh), lateral: lat, r: [0.1, 0.17, 0.12], k: 0.08 });
|
|
249
|
-
m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.02, -0.075]), rad: 0.055, k: 0.05 });
|
|
250
|
-
m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.01]), b: W, ra: 0.085, rb: 0.047, k: 0.05 });
|
|
251
|
-
ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.22), sx([0.008, 0, 0.012])), ydir: sub(W, E), lateral: lat, r: [0.078, 0.16, 0.085], k: 0.05 });
|
|
252
|
-
m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.04, 0.06, -0.02])), b: add(lerp(E, W, 0.3), sx([-0.02, 0, 0])), ra: 0.06, rb: 0.045, k: 0.06 });
|
|
253
|
-
// knee (carpus): broad and flat in front, accessory carpal bone behind
|
|
254
|
-
ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.004]), ydir: [0, 1, 0], lateral: lat, r: [0.05, 0.06, 0.047], k: 0.025 });
|
|
255
|
-
m.sphere({ tag: 'accessory', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.03, -0.04]), rad: 0.024, k: 0.02 });
|
|
256
|
-
// cannon: flat bone in front, flexor tendons behind
|
|
257
|
-
m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.02, 0.0]), b: add(M, [0, 0.02, 0.0]), ra: 0.034, rb: 0.032, k: 0.02 });
|
|
258
|
-
m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.03, -0.03]), b: add(M, [0, 0.03, -0.034]), ra: 0.022, rb: 0.026, k: 0.02 });
|
|
259
|
-
// fetlock, ergot, pastern, coronet, hoof
|
|
260
|
-
ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.012]), ydir: [0, 1, 0.3], lateral: lat, r: [0.044, 0.05, 0.05], k: 0.02 });
|
|
261
|
-
m.sphere({ tag: 'ergot', bone: 'fpaw' + S, group: g, c: add(M, [0, -0.02, -0.052]), rad: 0.018, k: 0.018 });
|
|
262
|
-
m.cone({ tag: 'pastern', bone: 'fpaw' + S, group: g, a: M, b: C, ra: 0.036, rb: 0.037, k: 0.02 });
|
|
263
|
-
sculptHoof(m, 'fhoof' + S, g, C, T, 1.0);
|
|
264
|
-
|
|
265
|
-
// hind
|
|
266
|
-
const h = 'H' + S;
|
|
267
|
-
const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S], Mt = J['mtp' + S], Ch = J['hcoffin' + S], Tt = J['htoe' + S];
|
|
268
|
-
// the thigh fills the whole quarter from the point of hip to the stifle and the gaskin
|
|
269
|
-
ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.04, 0, -0.05])), ydir: sub(K, Hp), lateral: lat, r: [0.1, 0.3, 0.22], k: 0.1, bias: 0.006 });
|
|
270
|
-
m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.2, 1.32, -0.45]), b: add(K, sx([0.0, 0.06, 0.04])), ra: 0.12, rb: 0.07, k: 0.1 });
|
|
271
|
-
m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.12, 1.3, -0.74]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.09]), ra: 0.12, rb: 0.065, k: 0.08 });
|
|
272
|
-
ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.2, 1.03, -0.3]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.05, 0.15, 0.08], k: 0.1 });
|
|
273
|
-
m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.01, 0.01, 0.03])), rad: 0.06, k: 0.07 });
|
|
274
|
-
ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.32), sx([0.008, 0, -0.05])), ydir: sub(Hk, K), lateral: lat, r: [0.078, 0.17, 0.095], k: 0.06 });
|
|
275
|
-
m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.06, rb: 0.045, k: 0.05 });
|
|
276
|
-
m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.08]), b: add(Hk, [0, 0.07, -0.07]), ra: 0.03, rb: 0.024, k: 0.03 });
|
|
277
|
-
m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.07, -0.068]), rad: 0.033, k: 0.025 });
|
|
278
|
-
ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.01, -0.005]), ydir: [0, 1, 0.25], lateral: lat, r: [0.05, 0.075, 0.055], k: 0.03 });
|
|
279
|
-
m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, 0.005]), b: add(Mt, [0, 0.02, 0.0]), ra: 0.036, rb: 0.033, k: 0.02 });
|
|
280
|
-
m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, -0.035]), b: add(Mt, [0, 0.03, -0.035]), ra: 0.022, rb: 0.026, k: 0.02 });
|
|
281
|
-
ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.012]), ydir: [0, 1, 0.3], lateral: lat, r: [0.043, 0.05, 0.05], k: 0.02 });
|
|
282
|
-
m.sphere({ tag: 'ergot', bone: 'hpaw' + S, group: h, c: add(Mt, [0, -0.02, -0.052]), rad: 0.018, k: 0.018 });
|
|
283
|
-
m.cone({ tag: 'pastern', bone: 'hpaw' + S, group: h, a: Mt, b: Ch, ra: 0.035, rb: 0.036, k: 0.02 });
|
|
284
|
-
sculptHoof(m, 'hhoof' + S, h, Ch, Tt, 0.93);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------- TAIL: dock + hair
|
|
288
|
-
const tailLen = foal ? 0.45 : 1;
|
|
289
|
-
for (let i = 0; i < DOCK_SEGS; i++) {
|
|
290
|
-
const t0 = i / DOCK_SEGS, t1 = (i + 1) / DOCK_SEGS;
|
|
291
|
-
m.cone({ tag: 'dock', bone: 'tail' + i, a: J['tail' + i], b: J['tail' + (i + 1)], ra: 0.062 - 0.03 * t0, rb: 0.062 - 0.03 * t1, k: i === 0 ? 0.06 : 0.01 });
|
|
292
|
-
}
|
|
293
|
-
// hair: one continuous tapered volume from the tail head to the hocks (foal: short fluffy brush)
|
|
294
|
-
const hairR = (i) => {
|
|
295
|
-
if (foal) return [0.06, 0.068, 0.072, 0.07, 0.06, 0.045, 0.03][Math.min(6, i)];
|
|
296
|
-
const t = i / TAIL_SEGS;
|
|
297
|
-
return t < 0.55 ? 0.066 + 0.018 * (t / 0.55) : 0.084 - 0.05 * ((t - 0.55) / 0.45) ** 1.5;
|
|
298
|
-
};
|
|
299
|
-
const nHair = foal ? DOCK_SEGS : TAIL_SEGS;
|
|
300
|
-
for (let i = 0; i < nHair; i++) {
|
|
301
|
-
m.cone({ tag: 'tailhair', bone: 'tail' + i, a: i === 0 ? lerp(J.tail0, J.tail1, 0.4) : J['tail' + i], b: J['tail' + (i + 1)], ra: hairR(i) * (i === 0 ? 0.8 : 1), rb: hairR(i + 1), k: i === 0 ? 0.03 : 0.015 });
|
|
302
|
-
}
|
|
303
|
-
void tailLen;
|
|
304
|
-
return m;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Hoof: a truncated cone with its front wall parallel to the pastern, a coronet band on top, the
|
|
308
|
-
// heel bulbs behind, flat on the ground (carved at y = 0).
|
|
309
|
-
function sculptHoof(m, bone, group, C, T, w) {
|
|
310
|
-
const base = [C[0], 0.0, (C[2] + T[2]) * 0.5 - 0.012];
|
|
311
|
-
const top = add(C, [0, 0.02, -0.012]);
|
|
312
|
-
m.cone({ tag: 'hoof', bone, group, a: top, b: base, ra: 0.045 * w, rb: 0.064 * w, k: 0.012 });
|
|
313
|
-
m.cone({ tag: 'coronet', bone, group, a: add(C, [0, 0.022, -0.018]), b: add(C, [0, 0.008, 0.02]), ra: 0.042 * w, rb: 0.042 * w, k: 0.015 });
|
|
314
|
-
m.sphere({ tag: 'heelbulb', bone, group, c: add(C, [0, -0.028, -0.05]), rad: 0.03 * w, k: 0.02 });
|
|
315
|
-
m.ell({ tag: 'sole', bone, group, c: [C[0], -0.2 + 0.002, base[2]], r: [0.2, 0.2, 0.2], k: 0.004, carve: true });
|
|
316
|
-
}
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
// One set of skin weights for the head and body copies of the throat and upper neck.
|
|
2
|
-
//
|
|
3
|
-
// The head (fine cells) and the body (coarse cells) both mesh the upper neck and cross-fade across the
|
|
4
|
-
// neck cut (regions.js). The core weighs every surface on its own: the axial coordinate is relaxed
|
|
5
|
-
// over each mesh separately and the weights are smoothed over each mesh's own neighbours, so at the
|
|
6
|
-
// same point of skin the two copies took weights up to 0.17 (medium) apart, inside the occiput blend
|
|
7
|
-
// where the head weight changes fastest. Standing still that is invisible, but when the poll flexes
|
|
8
|
-
// (grazing, drinking, a head turned, lying flat) the two copies swing apart along the skin's normal
|
|
9
|
-
// by up to 10 mm, ~5x the seam skirt's depth: one surface rises out of the other and its edge reads
|
|
10
|
-
// as a dark crease across the throat latch.
|
|
11
|
-
//
|
|
12
|
-
// Here both copies take the same field across the band: at a point p with head fade f(p) (the neck
|
|
13
|
-
// cut's cross-fade alone, without the eyelid patches),
|
|
14
|
-
// W(p) = (1 - f) * W_body(p) + f * W_head(p),
|
|
15
|
-
// where W_body / W_head are each surface's own weights, read off the other surface at its closest
|
|
16
|
-
// point (barycentric on the nearest triangle). Each copy keeps its own weights where it takes over
|
|
17
|
-
// (f -> 0 on the body side, f -> 1 on the head side), so neither copy steps within itself, and across
|
|
18
|
-
// the middle of the band, where both are drawn, the two copies of a point move as one.
|
|
19
|
-
import { neckBand, neckS, H_BODY } from './regions.js';
|
|
20
|
-
import { smoothstep } from '../../core/math/vec.js';
|
|
21
|
-
|
|
22
|
-
const K = 4;
|
|
23
|
-
|
|
24
|
-
export function unifyNeckWeights(ctx) {
|
|
25
|
-
const { pos, index, regionOf, regionNames, patchOf, weights, nV, Q } = ctx;
|
|
26
|
-
const { skinIndex: SI, skinWeight: SW } = weights;
|
|
27
|
-
const hR = regionNames.indexOf('head'), bR = regionNames.indexOf('body');
|
|
28
|
-
if (hR < 0 || bR < 0) return { changed: 0 };
|
|
29
|
-
const r = Q?.res ?? 1;
|
|
30
|
-
const { B, OV, clipBody, clipHead } = neckBand(r);
|
|
31
|
-
// The head copy takes the body's weights wherever the body copy is drawn (body fade >= 0.1, the
|
|
32
|
-
// seam skirt's floor: neck coordinate < OV + 0.6 B) and hands over to its own weights past it,
|
|
33
|
-
// where it is the only surface, finishing 1.5 body cells before the body copy's clip edge. The body
|
|
34
|
-
// copy keeps its own (smooth at its coarse cell, so a head vertex reads it exactly off the body's
|
|
35
|
-
// triangle).
|
|
36
|
-
const s0 = OV + 0.6 * B, s1 = clipBody - 1.5 * H_BODY * r;
|
|
37
|
-
const share = (g, s) => (g === hR ? 1 - smoothstep(s0, s1, s) : 0); // share of the other copy's weights
|
|
38
|
-
const S = (v) => neckS(pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]);
|
|
39
|
-
const inBand = (v) => { const s = S(v); return s > -clipHead - 0.01 && s < clipBody + 0.01; };
|
|
40
|
-
const own = (v) => (patchOf && patchOf[v] ? -1 : regionOf[v]);
|
|
41
|
-
|
|
42
|
-
// triangles of each copy near the band, in a hash grid
|
|
43
|
-
const G = 0.02;
|
|
44
|
-
const key = (x, y, z) => `${Math.floor(x / G)},${Math.floor(y / G)},${Math.floor(z / G)}`;
|
|
45
|
-
const grids = { [hR]: new Map(), [bR]: new Map() };
|
|
46
|
-
for (let i = 0; i < index.length; i += 3) {
|
|
47
|
-
const a = index[i], b = index[i + 1], c = index[i + 2];
|
|
48
|
-
if (a >= nV || b >= nV || c >= nV) continue;
|
|
49
|
-
const g = own(a);
|
|
50
|
-
if ((g !== hR && g !== bR) || own(b) !== g || own(c) !== g) continue;
|
|
51
|
-
if (!inBand(a) && !inBand(b) && !inBand(c)) continue;
|
|
52
|
-
const mn = [0, 1, 2].map((k) => Math.min(pos[a * 3 + k], pos[b * 3 + k], pos[c * 3 + k]));
|
|
53
|
-
const mx = [0, 1, 2].map((k) => Math.max(pos[a * 3 + k], pos[b * 3 + k], pos[c * 3 + k]));
|
|
54
|
-
for (let x = Math.floor(mn[0] / G); x <= Math.floor(mx[0] / G); x++)
|
|
55
|
-
for (let y = Math.floor(mn[1] / G); y <= Math.floor(mx[1] / G); y++)
|
|
56
|
-
for (let z = Math.floor(mn[2] / G); z <= Math.floor(mx[2] / G); z++) {
|
|
57
|
-
const k = `${x},${y},${z}`;
|
|
58
|
-
let l = grids[g].get(k);
|
|
59
|
-
if (!l) grids[g].set(k, (l = []));
|
|
60
|
-
l.push(i);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// closest point of the other copy: [a, b, c, u, v, w] or null (within maxD)
|
|
65
|
-
const maxD = 0.03;
|
|
66
|
-
const closest = (g, px, py, pz) => {
|
|
67
|
-
let best = null, bd = maxD * maxD;
|
|
68
|
-
const seen = new Set();
|
|
69
|
-
const cx = Math.floor(px / G), cy = Math.floor(py / G), cz = Math.floor(pz / G);
|
|
70
|
-
for (let x = cx - 1; x <= cx + 1; x++) for (let y = cy - 1; y <= cy + 1; y++) for (let z = cz - 1; z <= cz + 1; z++) {
|
|
71
|
-
const l = grids[g].get(`${x},${y},${z}`);
|
|
72
|
-
if (!l) continue;
|
|
73
|
-
for (const i of l) {
|
|
74
|
-
if (seen.has(i)) continue;
|
|
75
|
-
seen.add(i);
|
|
76
|
-
const r3 = closestOnTri(pos, index[i], index[i + 1], index[i + 2], px, py, pz);
|
|
77
|
-
if (r3.d2 < bd) { bd = r3.d2; best = [index[i], index[i + 1], index[i + 2], r3.u, r3.v, r3.w]; }
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return best;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// new weights, computed from the original ones (the two copies read each other's)
|
|
84
|
-
const nSI = new Map(), nSW = new Map();
|
|
85
|
-
const acc = new Map();
|
|
86
|
-
const add = (v, f) => { for (let k = 0; k < K; k++) { const w = SW[v * K + k]; if (w > 0) acc.set(SI[v * K + k], (acc.get(SI[v * K + k]) || 0) + w * f); } };
|
|
87
|
-
let changed = 0;
|
|
88
|
-
for (let v = 0; v < nV; v++) {
|
|
89
|
-
const g = own(v);
|
|
90
|
-
if (g !== hR && g !== bR) continue;
|
|
91
|
-
// (the hidden run of each copy past the band follows the other copy all the way to its clip edge)
|
|
92
|
-
const a = share(g, S(v));
|
|
93
|
-
if (a < 1e-3) continue;
|
|
94
|
-
const o = closest(g === hR ? bR : hR, pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]);
|
|
95
|
-
if (!o) continue;
|
|
96
|
-
acc.clear();
|
|
97
|
-
add(v, 1 - a);
|
|
98
|
-
add(o[0], a * o[3]); add(o[1], a * o[4]); add(o[2], a * o[5]);
|
|
99
|
-
const top = [...acc.entries()].filter((e) => e[1] > 1e-5).sort((x, y) => y[1] - x[1]).slice(0, K);
|
|
100
|
-
const sum = top.reduce((t, e) => t + e[1], 0) || 1;
|
|
101
|
-
nSI.set(v, top.map((e) => e[0]));
|
|
102
|
-
nSW.set(v, top.map((e) => e[1] / sum));
|
|
103
|
-
changed++;
|
|
104
|
-
}
|
|
105
|
-
for (const [v, bs] of nSI) {
|
|
106
|
-
const ws = nSW.get(v);
|
|
107
|
-
for (let k = 0; k < K; k++) { SI[v * K + k] = k < bs.length ? bs[k] : 0; SW[v * K + k] = k < ws.length ? ws[k] : 0; }
|
|
108
|
-
}
|
|
109
|
-
return { changed };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// closest point on triangle (a, b, c) to p: squared distance and barycentrics (Ericson, RTCD 5.1.5)
|
|
113
|
-
function closestOnTri(P, a, b, c, px, py, pz) {
|
|
114
|
-
const ax = P[a * 3], ay = P[a * 3 + 1], az = P[a * 3 + 2];
|
|
115
|
-
const abx = P[b * 3] - ax, aby = P[b * 3 + 1] - ay, abz = P[b * 3 + 2] - az;
|
|
116
|
-
const acx = P[c * 3] - ax, acy = P[c * 3 + 1] - ay, acz = P[c * 3 + 2] - az;
|
|
117
|
-
const apx = px - ax, apy = py - ay, apz = pz - az;
|
|
118
|
-
const d1 = abx * apx + aby * apy + abz * apz, d2 = acx * apx + acy * apy + acz * apz;
|
|
119
|
-
let u, v, w;
|
|
120
|
-
if (d1 <= 0 && d2 <= 0) { u = 1; v = 0; w = 0; } else {
|
|
121
|
-
const bpx = px - P[b * 3], bpy = py - P[b * 3 + 1], bpz = pz - P[b * 3 + 2];
|
|
122
|
-
const d3 = abx * bpx + aby * bpy + abz * bpz, d4 = acx * bpx + acy * bpy + acz * bpz;
|
|
123
|
-
const cpx = px - P[c * 3], cpy = py - P[c * 3 + 1], cpz = pz - P[c * 3 + 2];
|
|
124
|
-
const d5 = abx * cpx + aby * cpy + abz * cpz, d6 = acx * cpx + acy * cpy + acz * cpz;
|
|
125
|
-
const vc = d1 * d4 - d3 * d2, vb = d5 * d2 - d1 * d6, va = d3 * d6 - d5 * d4;
|
|
126
|
-
if (d3 >= 0 && d4 <= d3) { u = 0; v = 1; w = 0; }
|
|
127
|
-
else if (d6 >= 0 && d5 <= d6) { u = 0; v = 0; w = 1; }
|
|
128
|
-
else if (vc <= 0 && d1 >= 0 && d3 <= 0) { const t = d1 / (d1 - d3); u = 1 - t; v = t; w = 0; }
|
|
129
|
-
else if (vb <= 0 && d2 >= 0 && d6 <= 0) { const t = d2 / (d2 - d6); u = 1 - t; v = 0; w = t; }
|
|
130
|
-
else if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) { const t = (d4 - d3) / (d4 - d3 + (d5 - d6)); u = 0; v = 1 - t; w = t; }
|
|
131
|
-
else { const den = 1 / (va + vb + vc); v = vb * den; w = vc * den; u = 1 - v - w; }
|
|
132
|
-
}
|
|
133
|
-
const qx = ax + abx * v + acx * w, qy = ay + aby * v + acy * w, qz = az + abz * v + acz * w;
|
|
134
|
-
return { d2: (qx - px) ** 2 + (qy - py) ** 2 + (qz - pz) ** 2, u, v, w };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Spread the head -> neck hand-over across the whole throat latch.
|
|
138
|
-
//
|
|
139
|
-
// The core weighs the throat by its axial coordinate, but the throat lies in the acute wedge between
|
|
140
|
-
// the head and the neck (82 deg at the occiput in the bind pose), where the projection onto the chain
|
|
141
|
-
// crowds: on the skin from the back of the jowl down to the windpipe the head weight fell from 0.85
|
|
142
|
-
// to 0.15 within ~10 cm (12 deg about the poll). The poll flexes up to ~60 deg past the bind pose
|
|
143
|
-
// (a head looking down at the forefeet, on the vertical with the neck up; rearing; a head turned to
|
|
144
|
-
// the flank), and a point 25 cm from the occiput then moves ~25 cm relative to the neck: across a
|
|
145
|
-
// 10 cm hand-over the skin folds over itself, and the folded fur reads as a dark crease from the
|
|
146
|
-
// throat latch up behind the jowl.
|
|
147
|
-
//
|
|
148
|
-
// Here the weights in the throat wedge are blurred over the surface (both copies together, splatted
|
|
149
|
-
// into a grid by vertex area and fade, Gaussian sigma SIGMA, sampled back), so the hand-over runs over
|
|
150
|
-
// ~3 sigma of skin, from the back of the jowl into the upper third of the neck. The mask keeps the
|
|
151
|
-
// face, the poll and ear bases, and the neck beyond ~0.5 m from the occiput as they
|
|
152
|
-
// were; only the share on head / neck2 / neck1 is redistributed (ears keep theirs).
|
|
153
|
-
const SIGMA = 0.1;
|
|
154
|
-
export function spreadThroatWeights(ctx, sigma = SIGMA) {
|
|
155
|
-
const { pos, index, regionOf, regionNames, rigidBone, weights, nV, fade, rig } = ctx;
|
|
156
|
-
const { skinIndex: SI, skinWeight: SW } = weights;
|
|
157
|
-
const hR = regionNames.indexOf('head'), bR = regionNames.indexOf('body');
|
|
158
|
-
const O = rig.J.occiput, N = rig.J.nose;
|
|
159
|
-
const ha = Math.atan2(N[1] - O[1], N[2] - O[2]);
|
|
160
|
-
const DEG = Math.PI / 180;
|
|
161
|
-
const bones = ['head', 'neck2', 'neck1'].map((n) => rig.BONE[n].index);
|
|
162
|
-
const NBc = bones.length;
|
|
163
|
-
const bslot = new Map(bones.map((b, i) => [b, i]));
|
|
164
|
-
// mask: phi, the angle about the occiput (sagittal plane) from the head axis toward the throat: the
|
|
165
|
-
// whole wedge to the neck axis (82 deg) and on round the sides and top of the upper neck (a head
|
|
166
|
-
// turned or rolled, lying flat or grazing, bends the upper neck there too), and the back of the
|
|
167
|
-
// jowl (phi 0-12 deg: a rigid jowl margin creased against the throat when the head tucked in to
|
|
168
|
-
// rear), but not the face (phi < -5: eyes, forehead, nasal bone; the jowl keeps >= 0.85 of the head)
|
|
169
|
-
// or the skin round the occiput itself (r < 9-16 cm: poll, ear bases)
|
|
170
|
-
const mask = (v) => {
|
|
171
|
-
const y = pos[v * 3 + 1] - O[1], z = pos[v * 3 + 2] - O[2];
|
|
172
|
-
const r = Math.hypot(y, z);
|
|
173
|
-
let phi = ha - Math.atan2(y, z);
|
|
174
|
-
phi = ((phi / DEG + 540) % 360) - 180;
|
|
175
|
-
return smoothstep(-5, 12, phi) * (1 - smoothstep(140, 180, phi)) * smoothstep(0.09, 0.16, r) * (1 - smoothstep(0.5, 0.6, r));
|
|
176
|
-
};
|
|
177
|
-
const usable = (v) => (regionOf[v] === hR || regionOf[v] === bR) && !(rigidBone && rigidBone[v] >= 0);
|
|
178
|
-
const M = new Float32Array(nV);
|
|
179
|
-
let mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9], any = false;
|
|
180
|
-
for (let v = 0; v < nV; v++) {
|
|
181
|
-
if (!usable(v)) continue;
|
|
182
|
-
const m = mask(v);
|
|
183
|
-
if (m <= 0) continue;
|
|
184
|
-
M[v] = m; any = true;
|
|
185
|
-
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]); }
|
|
186
|
-
}
|
|
187
|
-
if (!any) return { changed: 0 };
|
|
188
|
-
const pad = 3 * sigma, c = sigma / 2;
|
|
189
|
-
mn = mn.map((x) => x - pad); mx = mx.map((x) => x + pad);
|
|
190
|
-
const G = mn.map((x, k) => Math.ceil((mx[k] - x) / c) + 2);
|
|
191
|
-
const NC = NBc + 1, cells = G[0] * G[1] * G[2];
|
|
192
|
-
const grid = new Float32Array(cells * NC);
|
|
193
|
-
// vertex areas
|
|
194
|
-
const area = new Float32Array(nV);
|
|
195
|
-
for (let i = 0; i < index.length; i += 3) {
|
|
196
|
-
const a = index[i], b = index[i + 1], d = index[i + 2];
|
|
197
|
-
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];
|
|
198
|
-
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];
|
|
199
|
-
const A = 0.5 * Math.hypot(uy * wz - uz * wy, uz * wx - ux * wz, ux * wy - uy * wx) / 3;
|
|
200
|
-
area[a] += A; area[b] += A; area[d] += A;
|
|
201
|
-
}
|
|
202
|
-
const cellOf = (v) => [0, 1, 2].map((k) => (pos[v * 3 + k] - mn[k]) / c);
|
|
203
|
-
const at = (i, j, k) => (i + G[0] * (j + G[1] * k)) * NC;
|
|
204
|
-
const tmp = new Float32Array(NC);
|
|
205
|
-
for (let v = 0; v < nV; v++) {
|
|
206
|
-
if (!usable(v)) continue;
|
|
207
|
-
const g = cellOf(v);
|
|
208
|
-
if (g.some((x, k) => x < 0 || x > G[k] - 2)) continue;
|
|
209
|
-
const a = area[v] * Math.max(0, fade ? fade[v] : 1);
|
|
210
|
-
if (!(a > 0)) continue;
|
|
211
|
-
// the vertex's weights on head / neck2 / neck1 (other bones: the nearest of these by the chain)
|
|
212
|
-
tmp.fill(0);
|
|
213
|
-
let s = 0;
|
|
214
|
-
for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; const sl = bslot.get(SI[v * 4 + k]); if (w > 0 && sl !== undefined) { tmp[sl] += w; s += w; } }
|
|
215
|
-
if (s < 0.5) continue; // skin of a limb / ear: not a source
|
|
216
|
-
for (let k = 0; k < NBc; k++) tmp[k] /= s;
|
|
217
|
-
tmp[NBc] = 1;
|
|
218
|
-
const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
|
|
219
|
-
const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
|
|
220
|
-
for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
|
|
221
|
-
const t = a * (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
|
|
222
|
-
const o = at(i0 + di, j0 + dj, k0 + dk);
|
|
223
|
-
for (let q = 0; q < NC; q++) grid[o + q] += t * tmp[q];
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// separable Gaussian blur
|
|
227
|
-
const R = Math.ceil(3 * sigma / c), ker = [];
|
|
228
|
-
for (let d = -R; d <= R; d++) ker.push(Math.exp(-0.5 * ((d * c) / sigma) ** 2));
|
|
229
|
-
const buf = new Float32Array(grid.length);
|
|
230
|
-
for (let ax = 0; ax < 3; ax++) {
|
|
231
|
-
buf.fill(0);
|
|
232
|
-
for (let k = 0; k < G[2]; k++) for (let j = 0; j < G[1]; j++) for (let i = 0; i < G[0]; i++) {
|
|
233
|
-
const o = at(i, j, k);
|
|
234
|
-
if (grid[o + NBc] === 0 && !grid.subarray(o, o + NC).some((x) => x)) continue;
|
|
235
|
-
for (let d = -R; d <= R; d++) {
|
|
236
|
-
const ii = i + (ax === 0 ? d : 0), jj = j + (ax === 1 ? d : 0), kk = k + (ax === 2 ? d : 0);
|
|
237
|
-
if (ii < 0 || jj < 0 || kk < 0 || ii >= G[0] || jj >= G[1] || kk >= G[2]) continue;
|
|
238
|
-
const o2 = at(ii, jj, kk), w = ker[d + R];
|
|
239
|
-
for (let q = 0; q < NC; q++) buf[o2 + q] += w * grid[o + q];
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
grid.set(buf);
|
|
243
|
-
}
|
|
244
|
-
// sample back and mix in by the mask
|
|
245
|
-
let changed = 0;
|
|
246
|
-
const acc = new Map();
|
|
247
|
-
for (let v = 0; v < nV; v++) {
|
|
248
|
-
const m = M[v];
|
|
249
|
-
if (!(m > 0)) continue;
|
|
250
|
-
const g = cellOf(v);
|
|
251
|
-
const i0 = Math.floor(g[0]), j0 = Math.floor(g[1]), k0 = Math.floor(g[2]);
|
|
252
|
-
const fx = g[0] - i0, fy = g[1] - j0, fz = g[2] - k0;
|
|
253
|
-
tmp.fill(0);
|
|
254
|
-
for (let dk = 0; dk < 2; dk++) for (let dj = 0; dj < 2; dj++) for (let di = 0; di < 2; di++) {
|
|
255
|
-
const t = (di ? fx : 1 - fx) * (dj ? fy : 1 - fy) * (dk ? fz : 1 - fz);
|
|
256
|
-
const o = at(i0 + di, j0 + dj, k0 + dk);
|
|
257
|
-
for (let q = 0; q < NC; q++) tmp[q] += t * grid[o + q];
|
|
258
|
-
}
|
|
259
|
-
if (!(tmp[NBc] > 1e-9)) continue;
|
|
260
|
-
// only the share on the head / neck bones is redistributed: an ear's or a foreleg's share stays
|
|
261
|
-
acc.clear();
|
|
262
|
-
let ax = 0;
|
|
263
|
-
for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; if (w > 0 && bslot.has(SI[v * 4 + k])) ax += w; }
|
|
264
|
-
for (let k = 0; k < 4; k++) { const w = SW[v * 4 + k]; if (w > 0) acc.set(SI[v * 4 + k], (acc.get(SI[v * 4 + k]) || 0) + w * (bslot.has(SI[v * 4 + k]) ? 1 - m : 1)); }
|
|
265
|
-
for (let q = 0; q < NBc; q++) { const w = (tmp[q] / tmp[NBc]) * m * ax; if (w > 0) acc.set(bones[q], (acc.get(bones[q]) || 0) + w); }
|
|
266
|
-
const top = [...acc.entries()].filter((e) => e[1] > 1e-4).sort((x, y) => y[1] - x[1]).slice(0, K);
|
|
267
|
-
const sum = top.reduce((t, e) => t + e[1], 0) || 1;
|
|
268
|
-
for (let k = 0; k < K; k++) { SI[v * K + k] = k < top.length ? top[k][0] : 0; SW[v * K + k] = k < top.length ? top[k][1] / sum : 0; }
|
|
269
|
-
changed++;
|
|
270
|
-
}
|
|
271
|
-
return { changed };
|
|
272
|
-
}
|