@bitefoundry/retrieviewer 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ASSET-PROVENANCE.md +6 -0
- package/ASSETS-LICENSE.md +2 -1
- package/ATTRIBUTION.md +6 -1
- package/COMFORT-ART.md +65 -0
- package/DESKTOP-PLATFORMS.md +6 -1
- package/PLUGIN.md +13 -10
- package/PREREQUISITES.md +95 -0
- package/PUPPY-MEMORY.md +102 -1
- package/README.ko.md +110 -0
- package/README.md +117 -59
- package/RELEASING.md +23 -12
- package/THIRD-PARTY-NOTICES.md +5 -3
- package/braille.mjs +35 -9
- package/examples/live.mjs +2 -2
- package/integrations/linux-overlay/overlay.py +2 -2
- package/integrations/macos-overlay/overlay.jxa +8 -6
- package/integrations/opencode/runtime.mjs +4 -4
- package/integrations/opencode/tui.tsx +1 -1
- package/integrations/windows-overlay/Overlay.cs +4 -4
- package/integrations/windows-overlay/protocol.mjs +11 -7
- package/package.json +34 -13
- package/puppy-comfort-sprites.mjs +25 -0
- package/puppy-companion-comfort-q5.bin.gz +0 -0
- package/puppy-companion-comfort.json +77 -0
- package/puppy-companion-life.mjs +49 -16
- package/puppy-companion-objects.mjs +12 -0
- package/puppy-frame-predictive.mjs +15 -9
- package/puppy-frame-store.mjs +2 -2
- package/puppy-paper-world.mjs +14 -0
- package/puppy-paper.mjs +65 -14
- package/retrieviewer-plugin.d.mts +1 -1
- package/retrieviewer-plugin.mjs +1 -1
- package/retrieviewer.d.mts +10 -4
- package/retrieviewer.mjs +20 -14
- package/locomotion-core.mjs +0 -21
- package/locomotion-puppy.animal +0 -0
- package/vendor/procedural-animals/package.json +0 -40
- package/vendor/procedural-animals/src/animal.js +0 -146
- package/vendor/procedural-animals/src/bake.js +0 -51
- package/vendor/procedural-animals/src/build.js +0 -81
- package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
- package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
- package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
- package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
- package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
- package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
- package/vendor/procedural-animals/src/core/build/seams.js +0 -183
- package/vendor/procedural-animals/src/core/build/warp.js +0 -112
- package/vendor/procedural-animals/src/core/build/weights.js +0 -393
- package/vendor/procedural-animals/src/core/math/vec.js +0 -81
- package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
- package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
- package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
- package/vendor/procedural-animals/src/core/motion/common.js +0 -158
- package/vendor/procedural-animals/src/core/motion/index.js +0 -22
- package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
- package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
- package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
- package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
- package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
- package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
- package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
- package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
- package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
- package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
- package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
- package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
- package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
- package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
- package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
- package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
- package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
- package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
- package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
- package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
- package/vendor/procedural-animals/src/index.d.ts +0 -98
- package/vendor/procedural-animals/src/index.js +0 -29
- package/vendor/procedural-animals/src/registry.js +0 -48
- package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
- package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
- package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
- package/vendor/procedural-animals/src/species/bear/index.js +0 -139
- package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
- package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
- package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
- package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
- package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
- package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
- package/vendor/procedural-animals/src/species/boar/index.js +0 -110
- package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
- package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
- package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
- package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
- package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
- package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
- package/vendor/procedural-animals/src/species/cat/index.js +0 -211
- package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
- package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
- package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
- package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
- package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
- package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
- package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
- package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
- package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
- package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
- package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
- package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
- package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
- package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
- package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
- package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
- package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
- package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
- package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
- package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
- package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
- package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
- package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
- package/vendor/procedural-animals/src/species/cow/index.js +0 -118
- package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
- package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
- package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
- package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
- package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
- package/vendor/procedural-animals/src/species/crow/index.js +0 -70
- package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
- package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
- package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
- package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
- package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
- package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
- package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
- package/vendor/procedural-animals/src/species/deer/index.js +0 -106
- package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
- package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
- package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
- package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
- package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
- package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
- package/vendor/procedural-animals/src/species/dog/index.js +0 -144
- package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
- package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
- package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
- package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
- package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
- package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
- package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
- package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
- package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
- package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
- package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
- package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
- package/vendor/procedural-animals/src/species/fish/index.js +0 -88
- package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
- package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
- package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
- package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
- package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
- package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
- package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
- package/vendor/procedural-animals/src/species/fox/index.js +0 -120
- package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
- package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
- package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
- package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
- package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
- package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
- package/vendor/procedural-animals/src/species/frog/index.js +0 -97
- package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
- package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
- package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
- package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
- package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
- package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
- package/vendor/procedural-animals/src/species/goat/index.js +0 -149
- package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
- package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
- package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
- package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
- package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
- package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
- package/vendor/procedural-animals/src/species/horse/index.js +0 -80
- package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
- package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
- package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
- package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
- package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
- package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
- package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
- package/vendor/procedural-animals/src/species/lion/index.js +0 -111
- package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
- package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
- package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
- package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
- package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
- package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
- package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
- package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
- package/vendor/procedural-animals/src/species/pig/index.js +0 -161
- package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
- package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
- package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
- package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
- package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
- package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
- package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
- package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
- package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
- package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
- package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
- package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
- package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
- package/vendor/procedural-animals/src/species/rat/index.js +0 -111
- package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
- package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
- package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
- package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
- package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
- package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
- package/vendor/procedural-animals/src/species/shark/index.js +0 -104
- package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
- package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
- package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
- package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
- package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
- package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
- package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
- package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
- package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
- package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
- package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
- package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
- package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
- package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
- package/vendor/procedural-animals/src/species/snake/index.js +0 -73
- package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
- package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
- package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
- package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
- package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
- package/vendor/procedural-animals/src/species/spider/index.js +0 -78
- package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
- package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
- package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
- package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
- package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
- package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
- package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
- package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
- package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
- package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
- package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
- package/vendor/procedural-animals/src/worker.js +0 -29
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Standard snake skeleton (the names the snake motion engine, core/motion/snake.js, expects).
|
|
2
|
-
//
|
|
3
|
-
// Axial chain, head to tail tip: nose <- v0 -> v1 -> ... -> vN
|
|
4
|
-
// head v0 -> nose (v0 = occiput / atlas, the head's pivot on the neck)
|
|
5
|
-
// spine0.. v(i) -> v(i+1) (N bones, each pointing tail-ward; spine0 is the root bone)
|
|
6
|
-
// Rigid parts riding on the head (each meshed as its own rigid region):
|
|
7
|
-
// jaw jawHinge -> jawTip (lower jaw, rotates about the quadrate hinge)
|
|
8
|
-
// tongue tongueBase -> tongueFork (tongue shaft; slides forward out of its sheath in the jaw)
|
|
9
|
-
// tongueTip tongueFork -> tongueTip (the forked tips; flick up and down)
|
|
10
|
-
// fang fangBase -> fangTip (hinged maxillary fangs of vipers; folded along the palate at rest)
|
|
11
|
-
// A rattle (or any tail-tip ornament) is meshed as a rigid region on the last spine bone.
|
|
12
|
-
//
|
|
13
|
-
// Bind pose: the snake lies straight along -Z with its belly on y = 0; joint vi sits on the body axis
|
|
14
|
-
// at height = the distance from the axis to the belly (the engine reads it back as the axis height).
|
|
15
|
-
// Joints are on the midline (no left/right pairs).
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Spine joints v0..vN from a list of segment lengths and axis heights.
|
|
19
|
-
* @param J joint table to fill
|
|
20
|
-
* @param z0 z of v0 (the occiput)
|
|
21
|
-
* @param lens N segment lengths (m), head -> tail
|
|
22
|
-
* @param height (sigma, t) -> axis height above the belly at arc distance sigma from v0 (t = sigma / total)
|
|
23
|
-
*/
|
|
24
|
-
export function snakeSpine(J, z0, lens, height) {
|
|
25
|
-
const total = lens.reduce((a, b) => a + b, 0);
|
|
26
|
-
let s = 0;
|
|
27
|
-
J.v0 = [0, height(0, 0), z0];
|
|
28
|
-
// place joints by arc length along the (slightly tilted) axis so bone lengths equal `lens`
|
|
29
|
-
let z = z0;
|
|
30
|
-
for (let i = 0; i < lens.length; i++) {
|
|
31
|
-
s += lens[i];
|
|
32
|
-
const y = height(s, s / total);
|
|
33
|
-
const dy = y - J['v' + i][1];
|
|
34
|
-
z -= Math.sqrt(Math.max(1e-10, lens[i] * lens[i] - dy * dy));
|
|
35
|
-
J['v' + (i + 1)] = [0, y, z];
|
|
36
|
-
}
|
|
37
|
-
return J;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Bone list: head, jaw, tongue, tongueTip, fang, spine0..spine(n-1). */
|
|
41
|
-
export function snakeBones(n, { neck = 4, tail = 0 } = {}) {
|
|
42
|
-
const b = [];
|
|
43
|
-
for (let i = 0; i < n; i++) {
|
|
44
|
-
const region = i < neck ? 'neck' : i >= n - tail ? 'tail' : 'torso';
|
|
45
|
-
b.push(['spine' + i, 'v' + i, 'v' + (i + 1), i === 0 ? null : 'spine' + (i - 1), { region }]);
|
|
46
|
-
}
|
|
47
|
-
b.push(
|
|
48
|
-
['head', 'v0', 'nose', 'spine0', { region: 'head' }],
|
|
49
|
-
['jaw', 'jawHinge', 'jawTip', 'head', { region: 'jaw', group: 'jaw' }],
|
|
50
|
-
['tongue', 'tongueBase', 'tongueFork', 'jaw', { region: 'tongue', group: 'tongue' }],
|
|
51
|
-
['tongueTip', 'tongueFork', 'tongueTip', 'tongue', { region: 'tongue', group: 'tongue' }],
|
|
52
|
-
['fang', 'fangBase', 'fangTip', 'head', { region: 'fang', group: 'fang' }],
|
|
53
|
-
);
|
|
54
|
-
return b;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Axial chain description for buildRig (skin weights along nose -> tail tip). */
|
|
58
|
-
export function snakeAxial(n) {
|
|
59
|
-
return {
|
|
60
|
-
points: ['nose', ...Array.from({ length: n + 1 }, (_, i) => 'v' + i)],
|
|
61
|
-
bones: ['head', ...Array.from({ length: n }, (_, i) => 'spine' + i)],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Segment lengths for n bones over `total` metres: uniform, shortened toward the tail tip so the
|
|
66
|
-
* thin tail can curl tightly (tipShrink = length of the last bone relative to the others). */
|
|
67
|
-
export function snakeSegments(n, total, { tipShrink = 0.55, tipBones = 8, neckShrink = 0.85, neckBones = 3 } = {}) {
|
|
68
|
-
const w = Array.from({ length: n }, (_, i) => {
|
|
69
|
-
let k = 1;
|
|
70
|
-
if (i >= n - tipBones) k = 1 - (1 - tipShrink) * ((i - (n - tipBones) + 1) / tipBones);
|
|
71
|
-
if (i < neckBones) k = neckShrink + (1 - neckShrink) * (i / neckBones);
|
|
72
|
-
return k;
|
|
73
|
-
});
|
|
74
|
-
const sw = w.reduce((a, b) => a + b, 0);
|
|
75
|
-
return w.map((k) => (k * total) / sw);
|
|
76
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
// Standard spider skeleton (the names the spider motion engine expects) and helpers to build it.
|
|
2
|
-
//
|
|
3
|
-
// Axial (nose -> tail): front (clypeus) - ceph (cephalic / thoracic boundary) - pedA (rear of the
|
|
4
|
-
// carapace, pedicel start) - pedB (pedicel end, abdomen front) - abdEnd (above the spinnerets) -
|
|
5
|
-
// spinTip. Bones: head (ceph -> front, rigid on the prosoma, carries the eyes), prosoma (root,
|
|
6
|
-
// pedA -> ceph), pedicel, abdomen, spinnerets.
|
|
7
|
-
// Chelicerae: cheBase{S} -> cheTip{S} (bone chelicera{S}), fang cheTip{S} -> fangTip{S} (fang{S}).
|
|
8
|
-
// Legs i = 1..4 (I front .. IV hind), 7 segments each: coxa, troch, femur, patella, tibia, meta,
|
|
9
|
-
// tarsus. The joint named after a segment is where that segment starts; `claw{i}{S}` is the tip.
|
|
10
|
-
// Bones carry the same names as their start joint (coxa1L = coxa1L -> troch1L ...); groups 'L1' ..
|
|
11
|
-
// 'R4' (the metrics and state.legs keys); the tarsus is the 'foot' region.
|
|
12
|
-
// Pedipalps: palpCoxa, palpTroch, palpFemur, palpPatella, palpTibia, palpTarsus (+ palpTip), group
|
|
13
|
-
// 'P{S}', no foot region (palps are not walking legs).
|
|
14
|
-
import { add, mul } from '../math/vec.js';
|
|
15
|
-
|
|
16
|
-
export const LEG_SEGS = ['coxa', 'troch', 'femur', 'patella', 'tibia', 'meta', 'tarsus'];
|
|
17
|
-
export const PALP_SEGS = ['palpCoxa', 'palpTroch', 'palpFemur', 'palpPatella', 'palpTibia', 'palpTarsus'];
|
|
18
|
-
export const LEG_KEYS = ['L1', 'L2', 'L3', 'L4', 'R1', 'R2', 'R3', 'R4'];
|
|
19
|
-
export const AXIAL_POINTS = ['front', 'ceph', 'pedA', 'pedB', 'abdEnd', 'spinTip'];
|
|
20
|
-
export const AXIAL_BONES = ['head', 'prosoma', 'pedicel', 'abdomen', 'spinnerets'];
|
|
21
|
-
|
|
22
|
-
/** joint names of leg i (1..4) on side S ('L' | 'R'), base -> claw tip */
|
|
23
|
-
export const legJoints = (i, S) => [...LEG_SEGS.map((g) => g + i + S), 'claw' + i + S];
|
|
24
|
-
export const legBones = (i, S) => LEG_SEGS.map((g) => g + i + S);
|
|
25
|
-
export const palpJoints = (S) => [...PALP_SEGS.map((g) => g + S), 'palpTip' + S];
|
|
26
|
-
export const palpBones = (S) => PALP_SEGS.map((g) => g + S);
|
|
27
|
-
|
|
28
|
-
// Direction of a segment in a vertical leg plane: azimuth phi from forward (+Z) toward the leg's
|
|
29
|
-
// side (side = +1 left, -1 right), elevation e above horizontal.
|
|
30
|
-
export function planeDir(phi, e, side) {
|
|
31
|
-
const c = Math.cos(e);
|
|
32
|
-
return [Math.sin(phi) * c * side, Math.sin(e), Math.cos(phi) * c];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Writes a planar chain into J: joints names[0..n] starting at base, segment k along azimuth
|
|
37
|
-
* az[k] (or one number) and elevation el[k] (radians), length lens[k].
|
|
38
|
-
*/
|
|
39
|
-
export function planarChain(J, names, base, az, el, lens, side) {
|
|
40
|
-
let p = base.slice();
|
|
41
|
-
J[names[0]] = p.slice();
|
|
42
|
-
for (let k = 0; k < lens.length; k++) {
|
|
43
|
-
const a = Array.isArray(az) ? az[k] : az;
|
|
44
|
-
p = add(p, mul(planeDir(a, el[k], side), lens[k]));
|
|
45
|
-
J[names[k + 1]] = p;
|
|
46
|
-
}
|
|
47
|
-
return J;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Bind-pose leg: finds the elevation of segment `free` so that the claw tip ends at height yTip.
|
|
52
|
-
* Returns the elevations used (radians). If the target cannot be met the free segment is clamped
|
|
53
|
-
* to straight down / up.
|
|
54
|
-
*/
|
|
55
|
-
export function solveBindElevations(yBase, lens, el, free, yTip) {
|
|
56
|
-
let drop = 0;
|
|
57
|
-
for (let k = 0; k < lens.length; k++) if (k !== free) drop += lens[k] * Math.sin(el[k]);
|
|
58
|
-
const s = (yTip - yBase - drop) / lens[free];
|
|
59
|
-
const out = el.slice();
|
|
60
|
-
out[free] = Math.asin(Math.max(-1, Math.min(1, s)));
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function spiderBones({ chelicerae = true, palps = true, spinnerets = true, extra = [] } = {}) {
|
|
65
|
-
const b = [
|
|
66
|
-
['prosoma', 'pedA', 'ceph', null, { region: 'torso' }],
|
|
67
|
-
['head', 'ceph', 'front', 'prosoma', { region: 'head' }],
|
|
68
|
-
['pedicel', 'pedA', 'pedB', 'prosoma', { region: 'torso' }],
|
|
69
|
-
['abdomen', 'pedB', 'abdEnd', 'pedicel', { region: 'torso' }],
|
|
70
|
-
];
|
|
71
|
-
if (spinnerets) b.push(['spinnerets', 'abdEnd', 'spinTip', 'abdomen', { region: 'tail' }]);
|
|
72
|
-
if (chelicerae) {
|
|
73
|
-
b.push(['chelicera{S}', 'cheBase{S}', 'cheTip{S}', 'head', { region: 'jaw', group: 'chel{S}' }]);
|
|
74
|
-
b.push(['fang{S}', 'cheTip{S}', 'fangTip{S}', 'chelicera{S}', { region: 'jaw', group: 'fang{S}' }]);
|
|
75
|
-
}
|
|
76
|
-
if (palps) {
|
|
77
|
-
const pj = palpJoints('{S}'), pb = palpBones('{S}');
|
|
78
|
-
pb.forEach((name, k) => b.push([name, pj[k], pj[k + 1], k === 0 ? 'prosoma' : pb[k - 1], { region: 'limb', group: 'P{S}' }]));
|
|
79
|
-
}
|
|
80
|
-
for (let i = 1; i <= 4; i++) {
|
|
81
|
-
const lj = legJoints(i, '{S}'), lb = legBones(i, '{S}');
|
|
82
|
-
lb.forEach((name, k) => b.push([name, lj[k], lj[k + 1], k === 0 ? 'prosoma' : lb[k - 1], { region: k === 6 ? 'foot' : 'limb', group: '{S}' + i }]));
|
|
83
|
-
}
|
|
84
|
-
return b.concat(extra);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Limb descriptions for the harmonic limb weights (see core/build/weights.js). The limb core starts
|
|
88
|
-
// part way along the coxa (tCore of the way from the coxa base to the femur base), so the coxa's
|
|
89
|
-
// arthrodial membrane blends into the prosoma.
|
|
90
|
-
export function spiderLimbs({ leg = {}, palp = {} } = {}) {
|
|
91
|
-
const L = {};
|
|
92
|
-
for (const S of ['L', 'R']) {
|
|
93
|
-
const side = S === 'L' ? 1 : -1;
|
|
94
|
-
for (let i = 1; i <= 4; i++) {
|
|
95
|
-
const bones = legBones(i, S);
|
|
96
|
-
L[S + i] = {
|
|
97
|
-
side, S, front: i <= 2, leg: i,
|
|
98
|
-
bones, proximal: [bones[0]], distal: bones.slice(5),
|
|
99
|
-
field: { a: 'coxa' + i + S, b: 'femur' + i + S, top: bones[0], tCore: 0.45, aCore: 0.03, aBody: -0.05, midX: 0.0, ...leg },
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
const pb = palpBones(S);
|
|
103
|
-
L['P' + S] = {
|
|
104
|
-
side, S, front: true, palp: true,
|
|
105
|
-
bones: pb, proximal: [pb[0]], distal: pb.slice(4),
|
|
106
|
-
field: { a: 'palpCoxa' + S, b: 'palpFemur' + S, top: pb[0], tCore: 0.4, aCore: 0.03, aBody: -0.05, midX: 0.0, ...palp },
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
return L;
|
|
110
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
// Standard swimmer skeleton (fish, sharks: the names the swimmer motion engine expects) and helpers
|
|
2
|
-
// for sculpting fins as thin membranes.
|
|
3
|
-
//
|
|
4
|
-
// Axial chain (snout -> caudal fin tip; drives the body wave, and the median fins ride on it):
|
|
5
|
-
// joints snout, spine0 (head-trunk joint, behind the skull), spine1 .. spine{N} (spine{N} = caudal
|
|
6
|
-
// base / hypural plate = caudal0), caudal1 .. caudal{M} (along the caudal fin; for a shark's
|
|
7
|
-
// heterocercal tail they climb into the upper lobe)
|
|
8
|
-
// bones head (spine0 -> snout), spine0 .. spine{N-1}, caudal0 .. caudal{M-1}
|
|
9
|
-
// Head: jaw (jawHinge -> jawTip, lower jaw, rigid part), upperJaw (premaxBase -> premaxTip, optional:
|
|
10
|
-
// protrusion for suction feeding / shark bites), operculum{S} (opHinge -> opEdge, optional;
|
|
11
|
-
// auxiliary joint opLow{S} gives the hinge axis with opHinge)
|
|
12
|
-
// Paired fins (appendages): pectoral{S} (pecBase -> pecTip, auxiliary pecUp{S}: a second point on the
|
|
13
|
-
// fin base line = the fold axis), pelvic{S} (pelBase -> pelTip, auxiliary pelFront{S})
|
|
14
|
-
// Dorsal, anal and adipose fins are skinned to the spine (they follow the body wave); the caudal fin
|
|
15
|
-
// to the caudal bones.
|
|
16
|
-
//
|
|
17
|
-
// Everything is measured from the joints, so any fish (or shark) shape works: the engine reads the
|
|
18
|
-
// rest geometry of the chain (segment lengths and pitch, e.g. an up-turned heterocercal tail).
|
|
19
|
-
import { add, sub, mul, norm, cross, dot, lerp, len } from '../math/vec.js';
|
|
20
|
-
|
|
21
|
-
export const spineName = (i) => 'spine' + i;
|
|
22
|
-
export const caudalName = (i) => 'caudal' + i;
|
|
23
|
-
|
|
24
|
-
/** Joint list of the axial chain, snout -> caudal tip. */
|
|
25
|
-
export function axialJoints(spineSegs, caudalSegs) {
|
|
26
|
-
const pts = ['snout'];
|
|
27
|
-
for (let i = 0; i <= spineSegs; i++) pts.push(spineName(i));
|
|
28
|
-
for (let i = 1; i <= caudalSegs; i++) pts.push(caudalName(i));
|
|
29
|
-
return pts;
|
|
30
|
-
}
|
|
31
|
-
/** Bone list of the axial chain, matching axialJoints (head first). */
|
|
32
|
-
export function axialBones(spineSegs, caudalSegs) {
|
|
33
|
-
const b = ['head'];
|
|
34
|
-
for (let i = 0; i < spineSegs; i++) b.push(spineName(i));
|
|
35
|
-
for (let i = 0; i < caudalSegs; i++) b.push(caudalName(i));
|
|
36
|
-
return b;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Standard swimmer bones for buildRig.
|
|
41
|
-
* @param o { spineSegs, caudalSegs, jaw, upperJaw, opercula, pectoral, pelvic,
|
|
42
|
-
* pectoralParent, pelvicParent (spine bone names), extra }
|
|
43
|
-
* The caudal chain's joint 0 is spine{N}: J.caudal0 must equal J['spine' + N] (swimmerJointsAlias does it).
|
|
44
|
-
*/
|
|
45
|
-
export function swimmerBones({ spineSegs = 12, caudalSegs = 3, jaw = true, upperJaw = false, opercula = true, pectoral = true, pelvic = true, pectoralParent = 'spine0', pelvicParent = 'spine4', extra = [] } = {}) {
|
|
46
|
-
const b = [];
|
|
47
|
-
b.push([spineName(0), spineName(0), spineName(1), null, { region: 'torso' }]);
|
|
48
|
-
b.push(['head', spineName(0), 'snout', spineName(0), { region: 'head' }]);
|
|
49
|
-
for (let i = 1; i < spineSegs; i++) b.push([spineName(i), spineName(i), spineName(i + 1), spineName(i - 1), { region: 'torso' }]);
|
|
50
|
-
for (let i = 0; i < caudalSegs; i++) b.push([caudalName(i), caudalName(i), caudalName(i + 1), i === 0 ? spineName(spineSegs - 1) : caudalName(i - 1), { region: 'tail' }]);
|
|
51
|
-
if (jaw) b.push(['jaw', 'jawHinge', 'jawTip', 'head', { region: 'jaw', group: 'jaw' }]);
|
|
52
|
-
if (upperJaw) b.push(['upperJaw', 'premaxBase', 'premaxTip', 'head', { region: 'head', group: 'upperJaw' }]);
|
|
53
|
-
if (opercula) b.push(['operculum{S}', 'opHinge{S}', 'opEdge{S}', 'head', { region: 'head', group: 'operculum{S}' }]);
|
|
54
|
-
if (pectoral) b.push(['pectoral{S}', 'pecBase{S}', 'pecTip{S}', pectoralParent, { region: 'fin', group: 'pectoral{S}' }]);
|
|
55
|
-
if (pelvic) b.push(['pelvic{S}', 'pelBase{S}', 'pelTip{S}', pelvicParent, { region: 'fin', group: 'pelvic{S}' }]);
|
|
56
|
-
return b.concat(extra);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Appendage list (skin blended by SDF distance) for the optional head and paired-fin bones. */
|
|
60
|
-
export function swimmerAppendages({ upperJaw = false, opercula = true, pectoral = true, pelvic = true, blend = [-0.003, 0.005] } = {}) {
|
|
61
|
-
const a = [];
|
|
62
|
-
const add2 = (g) => { for (const S of ['L', 'R']) a.push({ group: g + S, bone: g + S, blend }); };
|
|
63
|
-
if (opercula) add2('operculum');
|
|
64
|
-
if (pectoral) add2('pectoral');
|
|
65
|
-
if (pelvic) add2('pelvic');
|
|
66
|
-
if (upperJaw) a.push({ group: 'upperJaw', bone: 'upperJaw', blend });
|
|
67
|
-
return a;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** The caudal chain starts at the last spine joint. */
|
|
71
|
-
export function swimmerJointsAlias(J, spineSegs) {
|
|
72
|
-
J.caudal0 = J[spineName(spineSegs)].slice();
|
|
73
|
-
return J;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ------------------------------------------------------------------------------------------------
|
|
77
|
-
// Fins as membranes: an outline polygon in the fin's plane, extruded to a thin slab (SDF T_FIN).
|
|
78
|
-
// A fin is described by its rays: each ray runs from a point on the base line to a tip. The outline
|
|
79
|
-
// is base0 (sunk into the body) -> ray tips (with optional notches between spiny rays) -> baseN (sunk).
|
|
80
|
-
// The prim keeps the ray layout so the coat can paint the rays (phase between rays, fraction along).
|
|
81
|
-
// ------------------------------------------------------------------------------------------------
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @param m SDFModel
|
|
85
|
-
* @param o { origin [x,y,z], u, v (in-plane axes, 3D), rays: [[bu, bv], [tu, tv]] (2D, u/v coords),
|
|
86
|
-
* sink: how far the base is pushed into the body (along -outward2D), notch: 0..1 depth of
|
|
87
|
-
* the membrane notch between ray tips (spiny fins), t: thickness, bone, group, tag, k,
|
|
88
|
-
* thin, round }
|
|
89
|
-
* @returns the primitive (with prim.fin = { rays2D, origin, u, v })
|
|
90
|
-
*/
|
|
91
|
-
export function sculptFin(m, o) {
|
|
92
|
-
const rays = o.rays;
|
|
93
|
-
const n = rays.length;
|
|
94
|
-
const sink = o.sink || 0;
|
|
95
|
-
const poly = [];
|
|
96
|
-
// base line direction (first base -> last base) and the side the tips are on
|
|
97
|
-
const b0 = rays[0][0], bN = rays[n - 1][0];
|
|
98
|
-
const bd = [bN[0] - b0[0], bN[1] - b0[1]];
|
|
99
|
-
const bl = Math.hypot(bd[0], bd[1]) || 1;
|
|
100
|
-
let nx = -bd[1] / bl, ny = bd[0] / bl; // normal to the base line
|
|
101
|
-
const tipMid = rays[Math.floor(n / 2)][1];
|
|
102
|
-
if ((tipMid[0] - b0[0]) * nx + (tipMid[1] - b0[1]) * ny < 0) { nx = -nx; ny = -ny; }
|
|
103
|
-
const sunk = (p) => [p[0] - nx * sink, p[1] - ny * sink];
|
|
104
|
-
poly.push(sunk(b0));
|
|
105
|
-
for (let i = 0; i < n; i++) {
|
|
106
|
-
poly.push(rays[i][1].slice());
|
|
107
|
-
if (o.notch && i < n - 1) {
|
|
108
|
-
const a = rays[i][1], b = rays[i + 1][1], ba = rays[i][0], bb = rays[i + 1][0];
|
|
109
|
-
const tm = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], bm = [(ba[0] + bb[0]) / 2, (ba[1] + bb[1]) / 2];
|
|
110
|
-
const nt = typeof o.notch === 'function' ? o.notch(i) : o.notch;
|
|
111
|
-
poly.push([tm[0] + (bm[0] - tm[0]) * nt, tm[1] + (bm[1] - tm[1]) * nt]);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
poly.push(sunk(bN));
|
|
115
|
-
const prim = m.fin({ o: o.origin, u: o.u, v: o.v, t: o.t, poly, bone: o.bone, group: o.group, tag: o.tag, k: o.k ?? o.t, round: o.round, part: o.part });
|
|
116
|
-
prim.thin = o.thin !== false;
|
|
117
|
-
const u = norm(o.u), nn = norm(cross(o.u, o.v)), v = cross(nn, u);
|
|
118
|
-
prim.fin = { rays: rays.map((r) => [r[0].slice(), r[1].slice()]), origin: o.origin.slice(), u, v, n: nn, name: o.name || o.tag };
|
|
119
|
-
return prim;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** 2D coordinates of a 3D point in a fin's plane. */
|
|
123
|
-
export function finUV(fin, p) {
|
|
124
|
-
const d = sub(p, fin.origin);
|
|
125
|
-
return [dot(d, fin.u), dot(d, fin.v)];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Ray coordinates of a point in a fin: { phase: continuous ray index (0 .. n-1), along: 0 at the base,
|
|
130
|
-
* 1 at the tip, dir: 3D ray direction there }. Points outside the fan are clamped to the nearest ray.
|
|
131
|
-
*/
|
|
132
|
-
export function finRayCoords(fin, p) {
|
|
133
|
-
const q = finUV(fin, p);
|
|
134
|
-
const R = fin.rays, n = R.length;
|
|
135
|
-
// side of q relative to ray i (positive toward ray i+1)
|
|
136
|
-
const side = (i) => {
|
|
137
|
-
const [b, t] = R[i];
|
|
138
|
-
const dx = t[0] - b[0], dy = t[1] - b[1];
|
|
139
|
-
const l = Math.hypot(dx, dy) || 1;
|
|
140
|
-
return ((q[0] - b[0]) * -dy + (q[1] - b[1]) * dx) / l;
|
|
141
|
-
};
|
|
142
|
-
// orientation: does side increase from ray 0 to ray n-1?
|
|
143
|
-
const s0 = side(0), sN = side(n - 1);
|
|
144
|
-
const [b0, t0] = R[0], [bN] = R[n - 1];
|
|
145
|
-
const sgn = ((bN[0] - b0[0]) * -(t0[1] - b0[1]) + (bN[1] - b0[1]) * (t0[0] - b0[0])) >= 0 ? 1 : -1;
|
|
146
|
-
let phase;
|
|
147
|
-
if (s0 * sgn <= 0) phase = 0;
|
|
148
|
-
else if (sN * sgn >= 0) phase = n - 1;
|
|
149
|
-
else {
|
|
150
|
-
phase = 0;
|
|
151
|
-
let prev = s0 * sgn;
|
|
152
|
-
for (let i = 1; i < n; i++) {
|
|
153
|
-
const si = side(i) * sgn;
|
|
154
|
-
if (si <= 0) { phase = i - 1 + prev / (prev - si); break; }
|
|
155
|
-
prev = si;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const i0 = Math.min(n - 2, Math.floor(phase)), f = phase - i0;
|
|
159
|
-
const lp = (a, b) => [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
|
|
160
|
-
const b = lp(R[i0][0], R[i0 + 1][0]), t = lp(R[i0][1], R[i0 + 1][1]);
|
|
161
|
-
const dx = t[0] - b[0], dy = t[1] - b[1];
|
|
162
|
-
const L2 = dx * dx + dy * dy || 1e-12;
|
|
163
|
-
const along = Math.max(0, Math.min(1.2, ((q[0] - b[0]) * dx + (q[1] - b[1]) * dy) / L2));
|
|
164
|
-
const dir3 = norm(add(mul(fin.u, dx), mul(fin.v, dy)));
|
|
165
|
-
return { phase, along, dir: dir3 };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Fan of rays between two base points toward tips given by angle (deg, in the u/v plane) and length. */
|
|
169
|
-
export function fanRays({ base0, base1, n, angle0, angle1, length }) {
|
|
170
|
-
const rays = [];
|
|
171
|
-
for (let i = 0; i < n; i++) {
|
|
172
|
-
const t = n === 1 ? 0 : i / (n - 1);
|
|
173
|
-
const b = [base0[0] + (base1[0] - base0[0]) * t, base0[1] + (base1[1] - base0[1]) * t];
|
|
174
|
-
const a = ((angle0 + (angle1 - angle0) * t) * Math.PI) / 180;
|
|
175
|
-
const L = typeof length === 'function' ? length(t, i) : length;
|
|
176
|
-
rays.push([b, [b[0] + Math.cos(a) * L, b[1] + Math.sin(a) * L]]);
|
|
177
|
-
}
|
|
178
|
-
return rays;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export { lerp, len };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// Eye geometry helpers shared by the sculpt (orbit, lids, almond aperture) and the eyeball shader.
|
|
2
|
-
//
|
|
3
|
-
// An eye spec (per species, head-local, left eye):
|
|
4
|
-
// c: centre [x,y,z] relative to the head origin; r: eyeball radius; back: how far the ball sits
|
|
5
|
-
// behind c; yaw: outward divergence (rad); pitch: upward tilt of the view axis (rad, optional);
|
|
6
|
-
// lid: lid skin thickness; R, d: the almond aperture is the intersection of two circles of radius R
|
|
7
|
-
// whose centres are offset -/+d (width = 2*sqrt(R^2-d^2), height = 2*(R-d)); off: aperture offset
|
|
8
|
-
// along the eye's up axis; tilt: roll of the aperture (inner corner lower for +);
|
|
9
|
-
// irisZ / irisR: iris plane depth and radius (eyeball-local).
|
|
10
|
-
import { add, mul, frameZY } from '../math/vec.js';
|
|
11
|
-
|
|
12
|
-
export function eyeFrameOf(E, headO, s) {
|
|
13
|
-
const c = [headO[0] + E.c[0] * s, headO[1] + E.c[1], headO[2] + E.c[2] - E.back];
|
|
14
|
-
const p = E.pitch || 0;
|
|
15
|
-
const z = [Math.sin(E.yaw) * Math.cos(p) * s, Math.sin(p), Math.cos(E.yaw) * Math.cos(p)];
|
|
16
|
-
const [x0, y0] = frameZY(z, [0, 1, 0]);
|
|
17
|
-
const ph = s * E.tilt;
|
|
18
|
-
const x = add(mul(x0, Math.cos(ph)), mul(y0, Math.sin(ph)));
|
|
19
|
-
const y = add(mul(x0, -Math.sin(ph)), mul(y0, Math.cos(ph)));
|
|
20
|
-
return { c, x, y, z };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Adds an orbit hollow, a lid shell hugging the eyeball and the almond aperture cut into it.
|
|
24
|
-
// `bone` is usually 'head'. `orbit` (optional) = { r:[rx,ry,rz], at:[u,v,w], k } in aperture frame.
|
|
25
|
-
export function sculptEyeSocket(m, E, headO, s, { bone = 'head', orbit = null, zMax = null } = {}) {
|
|
26
|
-
const ef = eyeFrameOf(E, headO, s);
|
|
27
|
-
const at = (u, v, w) => add(add(add(ef.c, mul(ef.x, u)), mul(ef.y, v)), mul(ef.z, w));
|
|
28
|
-
if (orbit) m.ell({ bone, tag: 'orbit', c: at(orbit.at[0] * s, orbit.at[1], orbit.at[2]), axis: ef.z, up: ef.y, r: orbit.r, k: orbit.k ?? E.r * 0.65, carve: true });
|
|
29
|
-
m.sphere({ bone, tag: 'eyelid', c: ef.c, rad: E.r + E.lid, k: E.r * 0.4 });
|
|
30
|
-
m.lens({ bone, tag: 'eyesocket', c: add(ef.c, mul(ef.y, E.off)), x: ef.x, y: ef.y, z: ef.z, R: E.R, d: E.d, zMin: -E.r * 0.16, zMax: zMax ?? E.r * 2, k: E.r * 0.18, carve: true });
|
|
31
|
-
return ef;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// The aperture roll (E.tilt) that lays the almond's long axis along `axis` (e.g. the head axis: the eye
|
|
35
|
-
// slit follows the face line), with the upper lid kept up. The long axis has no direction, so the roll
|
|
36
|
-
// is the one within +-90 degrees of upright (the frame's y, the upper lid, stays on the upper side).
|
|
37
|
-
// (A search for the largest dot(x, axis) over tilt in [-1.6, 1.6] does not do this: the left eye's
|
|
38
|
-
// untilted x points backward, so such a search ends at the edge of its range and stands the almond on
|
|
39
|
-
// end, the upper lid facing the ear.) Add the species' own roll to the result: + lowers the inner corner.
|
|
40
|
-
export function apertureTiltAlong(E, headO, axis) {
|
|
41
|
-
const f = eyeFrameOf({ ...E, tilt: 0 }, headO, 1);
|
|
42
|
-
const k = axis[0] * f.z[0] + axis[1] * f.z[1] + axis[2] * f.z[2];
|
|
43
|
-
const a = [axis[0] - f.z[0] * k, axis[1] - f.z[1] * k, axis[2] - f.z[2] * k];
|
|
44
|
-
let ph = Math.atan2(a[0] * f.y[0] + a[1] * f.y[1] + a[2] * f.y[2], a[0] * f.x[0] + a[1] * f.x[1] + a[2] * f.x[2]);
|
|
45
|
-
if (ph > Math.PI / 2) ph -= Math.PI;
|
|
46
|
-
else if (ph < -Math.PI / 2) ph += Math.PI;
|
|
47
|
-
return ph;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Aperture size in metres (for docs and checks).
|
|
51
|
-
export function apertureSize(E) {
|
|
52
|
-
return { width: 2 * Math.sqrt(Math.max(0, E.R * E.R - E.d * E.d)), height: 2 * (E.R - E.d) };
|
|
53
|
-
}
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// Narrow-band Surface Nets polygoniser for the SDF model.
|
|
2
|
-
// Coarse pass finds cells near the zero set; only those are refined to the fine grid.
|
|
3
|
-
// Vertices are projected onto the true surface (Newton steps along the SDF gradient)
|
|
4
|
-
// and normals come from the analytic field gradient, so the mesh is smooth and accurate.
|
|
5
|
-
import { SDFModel, gradient } from './sdf.js';
|
|
6
|
-
|
|
7
|
-
export function meshSDF(prims, bmin, bmax, h, F = 6, onProgress, clip = null) {
|
|
8
|
-
const kmax = prims.reduce((m, p) => Math.max(m, p.k), 0);
|
|
9
|
-
// absolute steps tuned for cheetah-sized cells (>= 1 mm), scaled down for finer grids
|
|
10
|
-
const eP = Math.min(0.0006, 0.6 * h), eN = Math.min(0.0008, 0.8 * h), tolP = Math.min(2e-5, 0.02 * h);
|
|
11
|
-
const nx = Math.ceil((bmax[0] - bmin[0]) / h) + 3;
|
|
12
|
-
const ny = Math.ceil((bmax[1] - bmin[1]) / h) + 3;
|
|
13
|
-
const nz = Math.ceil((bmax[2] - bmin[2]) / h) + 3;
|
|
14
|
-
const ox = bmin[0] - h, oy = bmin[1] - h, oz = bmin[2] - h;
|
|
15
|
-
const cnx = Math.ceil((nx - 1) / F), cny = Math.ceil((ny - 1) / F), cnz = Math.ceil((nz - 1) / F);
|
|
16
|
-
const H = h * F;
|
|
17
|
-
|
|
18
|
-
// --- coarse samples, evaluated per super-cell with culled primitive lists
|
|
19
|
-
const T0 = performance.now();
|
|
20
|
-
const cv = new Float32Array((cnx + 1) * (cny + 1) * (cnz + 1)).fill(NaN);
|
|
21
|
-
const cidx = (i, j, k) => i + (cnx + 1) * (j + (cny + 1) * k);
|
|
22
|
-
const F2 = 4;
|
|
23
|
-
const snx = Math.ceil(cnx / F2), sny = Math.ceil(cny / F2), snz = Math.ceil(cnz / F2);
|
|
24
|
-
const H2 = H * F2, rho2 = H2 * Math.sqrt(3) * 0.5;
|
|
25
|
-
const superLists = new Map();
|
|
26
|
-
for (let sk = 0; sk < snz; sk++)
|
|
27
|
-
for (let sj = 0; sj < sny; sj++)
|
|
28
|
-
for (let si = 0; si < snx; si++) {
|
|
29
|
-
const list2 = SDFModel.cull(prims, ox + (si + 0.5) * H2, oy + (sj + 0.5) * H2, oz + (sk + 0.5) * H2, rho2, kmax);
|
|
30
|
-
superLists.set(si + snx * (sj + sny * sk), list2);
|
|
31
|
-
for (let k = sk * F2; k <= Math.min(sk * F2 + F2, cnz); k++)
|
|
32
|
-
for (let j = sj * F2; j <= Math.min(sj * F2 + F2, cny); j++)
|
|
33
|
-
for (let i = si * F2; i <= Math.min(si * F2 + F2, cnx); i++) {
|
|
34
|
-
const id = cidx(i, j, k);
|
|
35
|
-
if (cv[id] === cv[id]) continue;
|
|
36
|
-
cv[id] = list2.length ? SDFModel.evalList(list2, ox + i * H, oy + j * H, oz + k * H) : 1;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const superListFor = (i, j, k) => superLists.get(Math.min(snx - 1, Math.floor(i / F2)) + snx * (Math.min(sny - 1, Math.floor(j / F2)) + sny * Math.min(snz - 1, Math.floor(k / F2))));
|
|
40
|
-
const T1 = performance.now();
|
|
41
|
-
|
|
42
|
-
// --- active coarse cells (surface may pass through), dilated by one
|
|
43
|
-
const cellIdx = (i, j, k) => i + cnx * (j + cny * k);
|
|
44
|
-
const active0 = new Uint8Array(cnx * cny * cnz);
|
|
45
|
-
const reach = H * Math.sqrt(3) * 1.15;
|
|
46
|
-
for (let k = 0; k < cnz; k++)
|
|
47
|
-
for (let j = 0; j < cny; j++)
|
|
48
|
-
for (let i = 0; i < cnx; i++) {
|
|
49
|
-
let mn = 1e9, neg = false, pos_ = false;
|
|
50
|
-
for (let c = 0; c < 8; c++) {
|
|
51
|
-
const v = cv[cidx(i + (c & 1), j + ((c >> 1) & 1), k + ((c >> 2) & 1))];
|
|
52
|
-
const a = Math.abs(v);
|
|
53
|
-
if (a < mn) mn = a;
|
|
54
|
-
if (v < 0) neg = true; else pos_ = true;
|
|
55
|
-
}
|
|
56
|
-
// a surface point inside the cube is within half a diagonal of some corner
|
|
57
|
-
if ((neg && pos_) || mn < reach * 0.5) active0[cellIdx(i, j, k)] = 1;
|
|
58
|
-
}
|
|
59
|
-
const active = active0; // every cell crossed by the surface is already active (1-Lipschitz bound)
|
|
60
|
-
|
|
61
|
-
// --- fine samples inside active coarse cells
|
|
62
|
-
const N = nx * ny * nz;
|
|
63
|
-
const fv = new Float32Array(N).fill(NaN);
|
|
64
|
-
const fidx = (x, y, z) => x + nx * (y + ny * z);
|
|
65
|
-
const cellLists = new Map();
|
|
66
|
-
const rho = H * Math.sqrt(3) * 0.5;
|
|
67
|
-
let cellCount = 0;
|
|
68
|
-
for (let k = 0; k < cnz; k++)
|
|
69
|
-
for (let j = 0; j < cny; j++)
|
|
70
|
-
for (let i = 0; i < cnx; i++) {
|
|
71
|
-
const ci = cellIdx(i, j, k);
|
|
72
|
-
if (!active[ci]) continue;
|
|
73
|
-
cellCount++;
|
|
74
|
-
const cx = ox + (i + 0.5) * H, cy = oy + (j + 0.5) * H, cz = oz + (k + 0.5) * H;
|
|
75
|
-
const list = SDFModel.cull(superListFor(i, j, k), cx, cy, cz, rho, kmax);
|
|
76
|
-
cellLists.set(ci, list);
|
|
77
|
-
const x0 = i * F, y0 = j * F, z0 = k * F;
|
|
78
|
-
for (let z = z0; z <= Math.min(z0 + F, nz - 1); z++)
|
|
79
|
-
for (let y = y0; y <= Math.min(y0 + F, ny - 1); y++)
|
|
80
|
-
for (let x = x0; x <= Math.min(x0 + F, nx - 1); x++) {
|
|
81
|
-
const id = fidx(x, y, z);
|
|
82
|
-
if (fv[id] === fv[id]) continue; // already evaluated
|
|
83
|
-
fv[id] = SDFModel.evalList(list, ox + x * h, oy + y * h, oz + z * h);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const T2 = performance.now();
|
|
87
|
-
onProgress && onProgress('field', cellCount);
|
|
88
|
-
|
|
89
|
-
const listFor = (x, y, z) => {
|
|
90
|
-
const i = Math.min(cnx - 1, Math.floor(x / F)), j = Math.min(cny - 1, Math.floor(y / F)), k = Math.min(cnz - 1, Math.floor(z / F));
|
|
91
|
-
return cellLists.get(cellIdx(i, j, k)) || prims;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// --- surface nets vertices
|
|
95
|
-
const vmap = new Map();
|
|
96
|
-
const pos = [];
|
|
97
|
-
const cellOf = [];
|
|
98
|
-
const edges = [
|
|
99
|
-
[0, 1], [2, 3], [4, 5], [6, 7],
|
|
100
|
-
[0, 2], [1, 3], [4, 6], [5, 7],
|
|
101
|
-
[0, 4], [1, 5], [2, 6], [3, 7],
|
|
102
|
-
];
|
|
103
|
-
const corner = new Float64Array(8);
|
|
104
|
-
for (let k = 0; k < cnz; k++)
|
|
105
|
-
for (let j = 0; j < cny; j++)
|
|
106
|
-
for (let i = 0; i < cnx; i++) {
|
|
107
|
-
if (!active[cellIdx(i, j, k)]) continue;
|
|
108
|
-
const list = cellLists.get(cellIdx(i, j, k));
|
|
109
|
-
const x0 = i * F, y0 = j * F, z0 = k * F;
|
|
110
|
-
for (let z = z0; z < Math.min(z0 + F, nz - 1); z++)
|
|
111
|
-
for (let y = y0; y < Math.min(y0 + F, ny - 1); y++)
|
|
112
|
-
for (let x = x0; x < Math.min(x0 + F, nx - 1); x++) {
|
|
113
|
-
let mask = 0, bad = false;
|
|
114
|
-
for (let c = 0; c < 8; c++) {
|
|
115
|
-
const v = fv[fidx(x + (c & 1), y + ((c >> 1) & 1), z + ((c >> 2) & 1))];
|
|
116
|
-
if (v !== v) { bad = true; break; }
|
|
117
|
-
corner[c] = v;
|
|
118
|
-
if (v < 0) mask |= 1 << c;
|
|
119
|
-
}
|
|
120
|
-
if (bad || mask === 0 || mask === 255) continue;
|
|
121
|
-
if (clip && !clip(ox + (x + 0.5) * h, oy + (y + 0.5) * h, oz + (z + 0.5) * h)) continue;
|
|
122
|
-
// mass point of edge crossings
|
|
123
|
-
let sx = 0, sy = 0, sz = 0, cnt = 0;
|
|
124
|
-
for (const [a, b] of edges) {
|
|
125
|
-
const va = corner[a], vb = corner[b];
|
|
126
|
-
if ((va < 0) === (vb < 0)) continue;
|
|
127
|
-
const t = va / (va - vb);
|
|
128
|
-
const ax = a & 1, ay = (a >> 1) & 1, az = (a >> 2) & 1;
|
|
129
|
-
const bx = b & 1, by = (b >> 1) & 1, bz = (b >> 2) & 1;
|
|
130
|
-
sx += ax + (bx - ax) * t;
|
|
131
|
-
sy += ay + (by - ay) * t;
|
|
132
|
-
sz += az + (bz - az) * t;
|
|
133
|
-
cnt++;
|
|
134
|
-
}
|
|
135
|
-
let px = ox + (x + sx / cnt) * h, py = oy + (y + sy / cnt) * h, pz = oz + (z + sz / cnt) * h;
|
|
136
|
-
// project onto the surface (tetrahedral samples give value + gradient)
|
|
137
|
-
for (let it = 0; it < 2; it++) {
|
|
138
|
-
const e = eP; // finite-difference step: 0.6 mm, smaller on very fine grids (tiny animals)
|
|
139
|
-
const a = SDFModel.evalList(list, px + e, py - e, pz - e);
|
|
140
|
-
const b = SDFModel.evalList(list, px - e, py - e, pz + e);
|
|
141
|
-
const c2 = SDFModel.evalList(list, px - e, py + e, pz - e);
|
|
142
|
-
const d2 = SDFModel.evalList(list, px + e, py + e, pz + e);
|
|
143
|
-
const d = (a + b + c2 + d2) * 0.25;
|
|
144
|
-
const gx = (a - b - c2 + d2) / (4 * e), gy = (-a - b + c2 + d2) / (4 * e), gz = (-a + b - c2 + d2) / (4 * e);
|
|
145
|
-
const g2 = gx * gx + gy * gy + gz * gz + 1e-12;
|
|
146
|
-
let stx = (-d * gx) / g2, sty = (-d * gy) / g2, stz = (-d * gz) / g2;
|
|
147
|
-
const sl = Math.hypot(stx, sty, stz);
|
|
148
|
-
if (sl > h) { stx *= h / sl; sty *= h / sl; stz *= h / sl; }
|
|
149
|
-
px += stx; py += sty; pz += stz;
|
|
150
|
-
if (Math.abs(d) < tolP) break;
|
|
151
|
-
}
|
|
152
|
-
// keep inside an expanded cell to avoid folding
|
|
153
|
-
const lo = 0.35 * h;
|
|
154
|
-
px = Math.min(Math.max(px, ox + x * h - lo), ox + (x + 1) * h + lo);
|
|
155
|
-
py = Math.min(Math.max(py, oy + y * h - lo), oy + (y + 1) * h + lo);
|
|
156
|
-
pz = Math.min(Math.max(pz, oz + z * h - lo), oz + (z + 1) * h + lo);
|
|
157
|
-
vmap.set(fidx(x, y, z), pos.length / 3);
|
|
158
|
-
pos.push(px, py, pz);
|
|
159
|
-
cellOf.push(fidx(x, y, z));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const T3 = performance.now();
|
|
163
|
-
onProgress && onProgress('verts', pos.length / 3);
|
|
164
|
-
|
|
165
|
-
// --- faces
|
|
166
|
-
const idx = [];
|
|
167
|
-
const nV = pos.length / 3;
|
|
168
|
-
const P = pos;
|
|
169
|
-
const dist2 = (a, b) => {
|
|
170
|
-
const dx = P[a * 3] - P[b * 3], dy = P[a * 3 + 1] - P[b * 3 + 1], dz = P[a * 3 + 2] - P[b * 3 + 2];
|
|
171
|
-
return dx * dx + dy * dy + dz * dz;
|
|
172
|
-
};
|
|
173
|
-
const strides = [1, nx, nx * ny];
|
|
174
|
-
for (let v = 0; v < nV; v++) {
|
|
175
|
-
const c = cellOf[v];
|
|
176
|
-
const z = Math.floor(c / (nx * ny));
|
|
177
|
-
const y = Math.floor((c - z * nx * ny) / nx);
|
|
178
|
-
const x = c - z * nx * ny - y * nx;
|
|
179
|
-
const xyz = [x, y, z];
|
|
180
|
-
for (let a = 0; a < 3; a++) {
|
|
181
|
-
const b = (a + 1) % 3, cc = (a + 2) % 3;
|
|
182
|
-
// edge from grid point (cell + e_b + e_c) along axis a
|
|
183
|
-
const p0 = [x, y, z];
|
|
184
|
-
p0[b] += 1;
|
|
185
|
-
p0[cc] += 1;
|
|
186
|
-
if (p0[0] >= nx || p0[1] >= ny || p0[2] >= nz) continue;
|
|
187
|
-
const p1 = p0.slice();
|
|
188
|
-
p1[a] += 1;
|
|
189
|
-
if (p1[a] >= [nx, ny, nz][a]) continue;
|
|
190
|
-
const v0 = fv[fidx(p0[0], p0[1], p0[2])], v1 = fv[fidx(p1[0], p1[1], p1[2])];
|
|
191
|
-
if (v0 !== v0 || v1 !== v1) continue;
|
|
192
|
-
if ((v0 < 0) === (v1 < 0)) continue;
|
|
193
|
-
const cA = c, cB = c + strides[b], cC = c + strides[b] + strides[cc], cD = c + strides[cc];
|
|
194
|
-
const A = vmap.get(cA), B = vmap.get(cB), C = vmap.get(cC), D = vmap.get(cD);
|
|
195
|
-
if (A === undefined || B === undefined || C === undefined || D === undefined) continue;
|
|
196
|
-
// orientation: inside->outside along +a means normal points +a
|
|
197
|
-
const flip = v0 < 0;
|
|
198
|
-
let q = flip ? [A, B, C, D] : [A, D, C, B];
|
|
199
|
-
if (dist2(q[0], q[2]) <= dist2(q[1], q[3])) idx.push(q[0], q[1], q[2], q[0], q[2], q[3]);
|
|
200
|
-
else idx.push(q[0], q[1], q[3], q[1], q[2], q[3]);
|
|
201
|
-
void xyz;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// --- normals from field gradient
|
|
206
|
-
const nrm = new Float32Array(nV * 3);
|
|
207
|
-
for (let v = 0; v < nV; v++) {
|
|
208
|
-
const c = cellOf[v];
|
|
209
|
-
const z = Math.floor(c / (nx * ny));
|
|
210
|
-
const y = Math.floor((c - z * nx * ny) / nx);
|
|
211
|
-
const x = c - z * nx * ny - y * nx;
|
|
212
|
-
const list = listFor(x, y, z);
|
|
213
|
-
const g = gradient(list, P[v * 3], P[v * 3 + 1], P[v * 3 + 2], eN);
|
|
214
|
-
const l = Math.hypot(g[0], g[1], g[2]) || 1;
|
|
215
|
-
nrm[v * 3] = g[0] / l;
|
|
216
|
-
nrm[v * 3 + 1] = g[1] / l;
|
|
217
|
-
nrm[v * 3 + 2] = g[2] / l;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const lists = new Array(nV);
|
|
221
|
-
for (let v = 0; v < nV; v++) {
|
|
222
|
-
const c = cellOf[v];
|
|
223
|
-
const z = Math.floor(c / (nx * ny));
|
|
224
|
-
const y = Math.floor((c - z * nx * ny) / nx);
|
|
225
|
-
const x = c - z * nx * ny - y * nx;
|
|
226
|
-
lists[v] = listFor(x, y, z);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const T4 = performance.now();
|
|
230
|
-
if (typeof process !== 'undefined' && process.env && process.env.MESH_TIMING) console.log('coarse', (T1-T0)|0, 'fine', (T2-T1)|0, 'verts', (T3-T2)|0, 'faces+normals', (T4-T3)|0, 'cells', cellCount);
|
|
231
|
-
return {
|
|
232
|
-
positions: new Float32Array(pos),
|
|
233
|
-
normals: nrm,
|
|
234
|
-
indices: nV > 65535 ? new Uint32Array(idx) : new Uint16Array(idx),
|
|
235
|
-
lists,
|
|
236
|
-
count: nV,
|
|
237
|
-
};
|
|
238
|
-
}
|