@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,146 +0,0 @@
|
|
|
1
|
-
// The public Animal: build data + render object + motion engine behind a small game-facing API.
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { createAnimalObject } from './core/render/animalObject.js';
|
|
4
|
-
import { createMotion } from './core/motion/index.js';
|
|
5
|
-
|
|
6
|
-
const _v = new THREE.Vector3();
|
|
7
|
-
|
|
8
|
-
export class Animal {
|
|
9
|
-
constructor(species, data, opts = {}) {
|
|
10
|
-
this.species = species;
|
|
11
|
-
this.id = species.id;
|
|
12
|
-
this.data = data;
|
|
13
|
-
this.seed = data.seed;
|
|
14
|
-
this.params = data.params;
|
|
15
|
-
this.quality = opts.quality || data.quality;
|
|
16
|
-
const ground = opts.ground || (() => 0);
|
|
17
|
-
this.ground = ground;
|
|
18
|
-
this.water = opts.water || null;
|
|
19
|
-
this.render = createAnimalObject(data, species, { quality: this.quality, castShadow: opts.castShadow, receiveShadow: opts.receiveShadow });
|
|
20
|
-
/** Add this to your scene. Keep its own transform at identity: the animal moves by itself. */
|
|
21
|
-
this.object = this.render.group;
|
|
22
|
-
this.object.userData.animal = this;
|
|
23
|
-
this._listeners = new Map();
|
|
24
|
-
this.motion = createMotion(species.plan, {
|
|
25
|
-
species, data, skeleton: this.render.skeleton, ground, water: this.water, perches: opts.perches,
|
|
26
|
-
position: opts.position, heading: opts.heading ?? 0,
|
|
27
|
-
emit: (type, payload) => this._emit(type, payload),
|
|
28
|
-
});
|
|
29
|
-
this.attachments = {};
|
|
30
|
-
this._attachDefs = [];
|
|
31
|
-
for (const [name, def] of Object.entries(this.motion.attachmentPoints || {})) {
|
|
32
|
-
const o = new THREE.Object3D();
|
|
33
|
-
o.name = name;
|
|
34
|
-
o.matrixAutoUpdate = false;
|
|
35
|
-
this.object.add(o);
|
|
36
|
-
this.attachments[name] = o;
|
|
37
|
-
this._attachDefs.push({ o, ...def });
|
|
38
|
-
}
|
|
39
|
-
this.stats = { vertices: data.nV, triangles: data.index.length / 3, bones: data.bones.length, buildMs: data.stats?.buildMs || 0, updateMs: 0 };
|
|
40
|
-
this.time = 0;
|
|
41
|
-
this._lastDt = 1 / 60;
|
|
42
|
-
this.update(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------- control
|
|
46
|
-
/** Locomotion: the animal moves itself. speed in m/s, heading in radians (0 = +Z). */
|
|
47
|
-
move({ speed = 0, heading, climb = 0, gait } = {}) {
|
|
48
|
-
const inp = this.motion.input;
|
|
49
|
-
inp.follow = null;
|
|
50
|
-
inp.target = null;
|
|
51
|
-
inp.speed = Math.max(0, speed);
|
|
52
|
-
if (heading !== undefined) inp.heading = heading;
|
|
53
|
-
inp.climb = climb;
|
|
54
|
-
inp.gait = gait || null;
|
|
55
|
-
return this;
|
|
56
|
-
}
|
|
57
|
-
stop() { return this.move({ speed: 0 }); }
|
|
58
|
-
/** Walk / run to a point and stop there. */
|
|
59
|
-
moveTo(point, { speed = 3 } = {}) {
|
|
60
|
-
const inp = this.motion.input;
|
|
61
|
-
inp.follow = null;
|
|
62
|
-
inp.target = point ? new THREE.Vector3().copy(point) : null;
|
|
63
|
-
inp.targetSpeed = speed;
|
|
64
|
-
return this;
|
|
65
|
-
}
|
|
66
|
-
/** Match your own physics body: the animal is placed at `position` and animates for `velocity`. */
|
|
67
|
-
follow(position, velocity, { heading } = {}) {
|
|
68
|
-
const inp = this.motion.input;
|
|
69
|
-
inp.follow = inp.follow || { position: new THREE.Vector3(), velocity: new THREE.Vector3(), heading: null };
|
|
70
|
-
inp.follow.position.copy(position);
|
|
71
|
-
inp.follow.velocity.copy(velocity || _v.set(0, 0, 0));
|
|
72
|
-
inp.follow.heading = heading ?? null;
|
|
73
|
-
return this;
|
|
74
|
-
}
|
|
75
|
-
/** Look at a world point (Vector3) or stop looking (null). The animal keeps tracking the point you pass. */
|
|
76
|
-
lookAt(target) {
|
|
77
|
-
this.motion.input.look = target ? (target.isVector3 ? target : new THREE.Vector3().copy(target)) : null;
|
|
78
|
-
return this;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Play an action ('jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand', ...).
|
|
82
|
-
* A new one-shot interrupts the running one; postures cross-fade.
|
|
83
|
-
* Resolves with 'done' | 'interrupted' | 'stopped' | 'refused'.
|
|
84
|
-
*/
|
|
85
|
-
play(name, opts = {}) { return this.motion.play(name, opts); }
|
|
86
|
-
/** End an action (a posture: stand up), or every action but death when no name is given. */
|
|
87
|
-
stopAction(name) { this.motion.stop(name); return this; }
|
|
88
|
-
get actions() { return this.motion.actions; }
|
|
89
|
-
get gaits() { return this.motion.gaits; }
|
|
90
|
-
/** Named speeds for UIs ({ walk, trot, ... } in m/s, scaled to this individual's size). */
|
|
91
|
-
get gears() { return this.motion.gears || {}; }
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------- events
|
|
94
|
-
on(type, fn) {
|
|
95
|
-
if (!this._listeners.has(type)) this._listeners.set(type, new Set());
|
|
96
|
-
this._listeners.get(type).add(fn);
|
|
97
|
-
return () => this.off(type, fn);
|
|
98
|
-
}
|
|
99
|
-
off(type, fn) { this._listeners.get(type)?.delete(fn); return this; }
|
|
100
|
-
_emit(type, payload) {
|
|
101
|
-
const set = this._listeners.get(type);
|
|
102
|
-
if (set) for (const fn of set) fn({ type, animal: this, ...payload });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ---------------------------------------------------------------- state
|
|
106
|
-
get position() { return this.motion.state.position; }
|
|
107
|
-
get heading() { return this.motion.state.heading; }
|
|
108
|
-
get speed() { return this.motion.state.speed; }
|
|
109
|
-
get velocity() { return this.motion.state.velocity; }
|
|
110
|
-
get state() { return this.motion.state; }
|
|
111
|
-
|
|
112
|
-
// ---------------------------------------------------------------- frame
|
|
113
|
-
/** Call once per frame with the frame time in seconds. */
|
|
114
|
-
update(dt) {
|
|
115
|
-
const t0 = now();
|
|
116
|
-
if (!(dt >= 0) || !Number.isFinite(dt)) dt = 0;
|
|
117
|
-
dt = Math.min(dt, 0.1);
|
|
118
|
-
this.time += dt;
|
|
119
|
-
this.motion.update(dt);
|
|
120
|
-
this.render.update({ dt, time: this.time, motion: this.motion });
|
|
121
|
-
for (const a of this._attachDefs) {
|
|
122
|
-
const b = this.render.skeleton.byName[a.bone];
|
|
123
|
-
if (!b) continue;
|
|
124
|
-
a.o.matrix.multiplyMatrices(b.matrixWorld, a.local);
|
|
125
|
-
a.o.matrixWorld.multiplyMatrices(this.object.matrixWorld, a.o.matrix);
|
|
126
|
-
a.o.matrixWorldNeedsUpdate = false;
|
|
127
|
-
}
|
|
128
|
-
this.stats.updateMs = this.stats.updateMs * 0.9 + (now() - t0) * 0.1;
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
setQuality(q) { this.render.setQuality?.(q); this.quality = q; return this; }
|
|
133
|
-
/** Motion level of detail: 0 full, 1 reduced secondary motion, 2 crowd (half-rate IK). */
|
|
134
|
-
setLod(level) { this.motion.setLod?.(level); this.lod = level; return this; }
|
|
135
|
-
setDebug(mode, on) { this.render.setDebug?.(mode, on); return this; }
|
|
136
|
-
/** Show or hide the coverings (fur shells and silhouette fins); the base still reads as fur. */
|
|
137
|
-
setCoverings(on) { this.render.setCoverings?.(on); return this; }
|
|
138
|
-
|
|
139
|
-
dispose() {
|
|
140
|
-
this.render.dispose();
|
|
141
|
-
this.motion.dispose?.();
|
|
142
|
-
this._listeners.clear();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// .animal bake files: a built individual, stored so it loads instantly (no SDF meshing, no weights).
|
|
2
|
-
// Layout: "PANM" | u32 version | u32 headerBytes | header JSON (utf-8, padded to 8) | array blobs (8-aligned)
|
|
3
|
-
import { ARRAY_KEYS, FORMAT_VERSION } from './core/build/pipeline.js';
|
|
4
|
-
|
|
5
|
-
const TYPES = { Float32Array, Uint32Array, Uint16Array, Int16Array, Uint8Array, Int8Array, Int32Array };
|
|
6
|
-
|
|
7
|
-
export function writeBake(data) {
|
|
8
|
-
const header = {};
|
|
9
|
-
for (const [k, v] of Object.entries(data)) if (!ARRAY_KEYS.includes(k)) header[k] = v;
|
|
10
|
-
header.arrays = [];
|
|
11
|
-
let offset = 0;
|
|
12
|
-
for (const k of ARRAY_KEYS) {
|
|
13
|
-
const a = data[k];
|
|
14
|
-
header.arrays.push({ key: k, type: a.constructor.name, length: a.length, offset });
|
|
15
|
-
offset += Math.ceil(a.byteLength / 8) * 8;
|
|
16
|
-
}
|
|
17
|
-
let json = new TextEncoder().encode(JSON.stringify(header));
|
|
18
|
-
const hb = Math.ceil((json.length) / 8) * 8;
|
|
19
|
-
const total = 12 + hb + offset;
|
|
20
|
-
const buf = new ArrayBuffer(Math.ceil(total / 8) * 8 + 8);
|
|
21
|
-
const u8 = new Uint8Array(buf);
|
|
22
|
-
u8.set([80, 65, 78, 77], 0); // "PANM"
|
|
23
|
-
const dv = new DataView(buf);
|
|
24
|
-
dv.setUint32(4, FORMAT_VERSION, true);
|
|
25
|
-
dv.setUint32(8, hb, true);
|
|
26
|
-
u8.set(json, 12);
|
|
27
|
-
for (let i = 12 + json.length; i < 12 + hb; i++) u8[i] = 32;
|
|
28
|
-
const base = 16 + hb - ((16 + hb) % 8 === 0 ? 0 : (16 + hb) % 8) ;
|
|
29
|
-
// blobs start at an 8-aligned offset after the header
|
|
30
|
-
const start = align8(12 + hb);
|
|
31
|
-
for (const a of header.arrays) u8.set(new Uint8Array(data[a.key].buffer, data[a.key].byteOffset, data[a.key].byteLength), start + a.offset);
|
|
32
|
-
void base;
|
|
33
|
-
return buf.slice(0, start + offset);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function readBake(buf) {
|
|
37
|
-
const u8 = new Uint8Array(buf);
|
|
38
|
-
if (u8[0] !== 80 || u8[1] !== 65 || u8[2] !== 78 || u8[3] !== 77) throw new Error('not a .animal file');
|
|
39
|
-
const dv = new DataView(buf);
|
|
40
|
-
const version = dv.getUint32(4, true);
|
|
41
|
-
if (version !== FORMAT_VERSION) throw new Error(`.animal version ${version} is not supported (expected ${FORMAT_VERSION})`);
|
|
42
|
-
const hb = dv.getUint32(8, true);
|
|
43
|
-
const header = JSON.parse(new TextDecoder().decode(u8.subarray(12, 12 + hb)));
|
|
44
|
-
const start = align8(12 + hb);
|
|
45
|
-
const data = { ...header };
|
|
46
|
-
for (const a of header.arrays) data[a.key] = new TYPES[a.type](buf, start + a.offset, a.length);
|
|
47
|
-
delete data.arrays;
|
|
48
|
-
return data;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const align8 = (n) => Math.ceil(n / 8) * 8;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// Builds an individual's data: in Web Workers when possible (meshing jobs in parallel, then
|
|
2
|
-
// weights + coat), otherwise on the calling thread. Results are cached per (species, seed, quality,
|
|
3
|
-
// sex, age) so a herd that reuses individuals shares geometry.
|
|
4
|
-
import { buildSync, normaliseOptions } from './core/build/pipeline.js';
|
|
5
|
-
import { loadSpecies } from './registry.js';
|
|
6
|
-
|
|
7
|
-
let defaultWorkerFactory = null;
|
|
8
|
-
export function setWorkerFactory(fn) { defaultWorkerFactory = fn; }
|
|
9
|
-
|
|
10
|
-
function makeWorker(opt) {
|
|
11
|
-
if (opt === false) return null;
|
|
12
|
-
try {
|
|
13
|
-
if (typeof opt === 'function') return opt();
|
|
14
|
-
if (defaultWorkerFactory) return defaultWorkerFactory();
|
|
15
|
-
if (typeof Worker === 'undefined') return null;
|
|
16
|
-
return new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
|
|
17
|
-
} catch (e) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const cache = new Map();
|
|
23
|
-
export function cacheKey(speciesId, opts) {
|
|
24
|
-
const o = normaliseOptions(opts);
|
|
25
|
-
return [speciesId, o.seed, o.quality, o.sex || '', o.age || '', o.variant || '', JSON.stringify(o.overrides)].join('|');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function buildAnimalData(species, opts = {}, onProgress = () => {}) {
|
|
29
|
-
const s = await loadSpecies(species);
|
|
30
|
-
const key = cacheKey(s.id, opts);
|
|
31
|
-
if (cache.has(key)) return cache.get(key);
|
|
32
|
-
const p = buildInWorkers(s, opts, onProgress).catch((e) => { cache.delete(key); throw e; });
|
|
33
|
-
cache.set(key, p);
|
|
34
|
-
return p;
|
|
35
|
-
}
|
|
36
|
-
export function clearBuildCache() { cache.clear(); }
|
|
37
|
-
|
|
38
|
-
async function buildInWorkers(species, opts, onProgress) {
|
|
39
|
-
const o = normaliseOptions(opts);
|
|
40
|
-
const nJobs = species.regions ? null : 1;
|
|
41
|
-
void nJobs;
|
|
42
|
-
const first = makeWorker(opts.worker);
|
|
43
|
-
if (!first) {
|
|
44
|
-
await new Promise((r) => setTimeout(r, 0));
|
|
45
|
-
return buildSync(species, o, onProgress);
|
|
46
|
-
}
|
|
47
|
-
const t0 = performance.now();
|
|
48
|
-
// how many meshing jobs? (cheap: prepare runs the sculpt only)
|
|
49
|
-
const { prepare } = await import('./core/build/pipeline.js');
|
|
50
|
-
const jobsN = prepare(species, o).reg.jobs.length;
|
|
51
|
-
const workers = [first];
|
|
52
|
-
for (let i = 1; i < jobsN; i++) { const w = makeWorker(opts.worker); if (w) workers.push(w); }
|
|
53
|
-
const call = (w, msg) => new Promise((resolve, reject) => {
|
|
54
|
-
w.onmessage = (e) => {
|
|
55
|
-
const m = e.data;
|
|
56
|
-
if (m.type === 'progress') onProgress(m.msg);
|
|
57
|
-
else if (m.type === 'error') reject(new Error(m.message));
|
|
58
|
-
else resolve(m);
|
|
59
|
-
};
|
|
60
|
-
w.onerror = (e) => reject(new Error(e.message || 'procedural-animals worker failed'));
|
|
61
|
-
w.postMessage(msg);
|
|
62
|
-
});
|
|
63
|
-
try {
|
|
64
|
-
const jobs = new Array(jobsN);
|
|
65
|
-
let next = 0;
|
|
66
|
-
await Promise.all(workers.map(async (w) => {
|
|
67
|
-
while (next < jobsN) {
|
|
68
|
-
const j = next++;
|
|
69
|
-
onProgress('mesh ' + j);
|
|
70
|
-
const r = await call(w, { task: 'mesh', species: species.id, opts: o, job: j });
|
|
71
|
-
jobs[j] = r.parts;
|
|
72
|
-
}
|
|
73
|
-
}));
|
|
74
|
-
onProgress('finish');
|
|
75
|
-
const r = await call(first, { task: 'finish', species: species.id, opts: o, jobs });
|
|
76
|
-
r.data.stats.buildMs = Math.round(performance.now() - t0);
|
|
77
|
-
return r.data;
|
|
78
|
-
} finally {
|
|
79
|
-
for (const w of workers) w.terminate();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
// Helpers for species coat painters.
|
|
2
|
-
import { SDFModel } from '../sdf/sdf.js';
|
|
3
|
-
import { sub, add, mul, dot, len, clamp, mix } from '../math/vec.js';
|
|
4
|
-
|
|
5
|
-
// sRGB hex -> linear rgb
|
|
6
|
-
export const srgb = (hex) => {
|
|
7
|
-
const c = [(hex >> 16) & 255, (hex >> 8) & 255, hex & 255].map((v) => v / 255);
|
|
8
|
-
return c.map((v) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)));
|
|
9
|
-
};
|
|
10
|
-
export const mix3 = (a, b, t) => [mix(a[0], b[0], t), mix(a[1], b[1], t), mix(a[2], b[2], t)];
|
|
11
|
-
export const scale3 = (a, k) => [a[0] * k, a[1] * k, a[2] * k];
|
|
12
|
-
|
|
13
|
-
// Material ids stored in tint.w (see render/coatMaterial.js)
|
|
14
|
-
export const MAT = { FUR: 0, NOSE: 1, DARK_SKIN: 2, MOUTH: 3, SKIN: 4, KERATIN: 5, SCALES: 6, WET_SKIN: 7, CHITIN: 8, FEATHER: 9, VANE: 10, FIN: 11 }; // 10: flight-feather vane (featherCards.js), 11: fin membrane (render/membranes.js)
|
|
15
|
-
|
|
16
|
-
// Newton-project a point onto the zero set of a primitive list
|
|
17
|
-
export function projectToSurface(prims, q, iters = 6) {
|
|
18
|
-
let x = q.slice();
|
|
19
|
-
for (let i = 0; i < iters; i++) {
|
|
20
|
-
const d = SDFModel.evalList(prims, x[0], x[1], x[2]);
|
|
21
|
-
const e = 0.0005;
|
|
22
|
-
const g = [
|
|
23
|
-
SDFModel.evalList(prims, x[0] + e, x[1], x[2]) - d,
|
|
24
|
-
SDFModel.evalList(prims, x[0], x[1] + e, x[2]) - d,
|
|
25
|
-
SDFModel.evalList(prims, x[0], x[1], x[2] + e) - d,
|
|
26
|
-
].map((c) => c / e);
|
|
27
|
-
const gl = len(g) || 1;
|
|
28
|
-
x = sub(x, mul(g, d / (gl * gl)));
|
|
29
|
-
}
|
|
30
|
-
return x;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// distance from p to a polyline with per-vertex half widths (negative inside)
|
|
34
|
-
export function distPolyline(p, poly, widths) {
|
|
35
|
-
let best = 1e9;
|
|
36
|
-
for (let i = 0; i < poly.length - 1; i++) {
|
|
37
|
-
const a = poly[i], b = poly[i + 1], ab = sub(b, a);
|
|
38
|
-
const t = clamp(dot(sub(p, a), ab) / dot(ab, ab), 0, 1);
|
|
39
|
-
const d = len(sub(p, add(a, mul(ab, t)))) - mix(widths[i], widths[i + 1], t);
|
|
40
|
-
if (d < best) best = d;
|
|
41
|
-
}
|
|
42
|
-
return best;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Laplacian smoothing of a scalar field over the mesh graph; `lock(v)` keeps a vertex fixed
|
|
46
|
-
export function smoothField(f, neighbors, iters = 2, lock = null) {
|
|
47
|
-
const n = f.length, tmp = new Float32Array(n);
|
|
48
|
-
for (let it = 0; it < iters; it++) {
|
|
49
|
-
for (let v = 0; v < n; v++) {
|
|
50
|
-
const ns = neighbors[v];
|
|
51
|
-
if (!ns.length || (lock && lock(v))) { tmp[v] = f[v]; continue; }
|
|
52
|
-
let a = 0;
|
|
53
|
-
for (const u of ns) a += f[u];
|
|
54
|
-
tmp[v] = 0.5 * f[v] + (0.5 * a) / ns.length;
|
|
55
|
-
}
|
|
56
|
-
f.set(tmp);
|
|
57
|
-
}
|
|
58
|
-
return f;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Poisson-disc features grown on the surface. radius(v) -> r (0 = no feature here);
|
|
62
|
-
// returns { spots, grid, cell, key } for use with featureField.
|
|
63
|
-
export function poissonFeatures({ pos, nrm, nV, radius, R, spacing = 1.52, gap = 0.0025, cell = 0.03, skip = null, extra = null }) {
|
|
64
|
-
const order = new Uint32Array(nV);
|
|
65
|
-
for (let i = 0; i < nV; i++) order[i] = i;
|
|
66
|
-
for (let i = nV - 1; i > 0; i--) { const j = Math.floor(R() * (i + 1)); const t = order[i]; order[i] = order[j]; order[j] = t; }
|
|
67
|
-
const grid = new Map();
|
|
68
|
-
const key = (x, y, z) => ((x + 512) * 1024 + (y + 512)) * 1024 + (z + 512);
|
|
69
|
-
const spots = [];
|
|
70
|
-
for (let k = 0; k < nV; k++) {
|
|
71
|
-
const v = order[k];
|
|
72
|
-
if (skip && skip(v)) continue;
|
|
73
|
-
let r = radius(v);
|
|
74
|
-
if (!(r > 0)) continue;
|
|
75
|
-
r *= 0.78 + 0.44 * R();
|
|
76
|
-
const p = [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
|
|
77
|
-
const gx = Math.floor(p[0] / cell), gy = Math.floor(p[1] / cell), gz = Math.floor(p[2] / cell);
|
|
78
|
-
let ok = true;
|
|
79
|
-
for (let dz = -1; dz <= 1 && ok; dz++)
|
|
80
|
-
for (let dy = -1; dy <= 1 && ok; dy++)
|
|
81
|
-
for (let dx = -1; dx <= 1 && ok; dx++) {
|
|
82
|
-
const arr = grid.get(key(gx + dx, gy + dy, gz + dz));
|
|
83
|
-
if (!arr) continue;
|
|
84
|
-
for (const s of arr) {
|
|
85
|
-
const minD = spacing * (r + s.r) + gap;
|
|
86
|
-
const ddx = p[0] - s.p[0], ddy = p[1] - s.p[1], ddz = p[2] - s.p[2];
|
|
87
|
-
if (ddx * ddx + ddy * ddy + ddz * ddz < minD * minD) { ok = false; break; }
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!ok) continue;
|
|
91
|
-
const s = { v, p, r, n: [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]], ...(extra ? extra(v, R) : {}) };
|
|
92
|
-
spots.push(s);
|
|
93
|
-
const kk = key(gx, gy, gz);
|
|
94
|
-
(grid.get(kk) || grid.set(kk, []).get(kk)).push(s);
|
|
95
|
-
}
|
|
96
|
-
return { spots, grid, cell, key };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Signed distance to the nearest feature of a poissonFeatures() set, elongated along each
|
|
100
|
-
// feature's comb direction `c` by `el`. Returns [sdf, feature].
|
|
101
|
-
export function featureDistance(F, p, n, wob = 1) {
|
|
102
|
-
const { grid, cell, key } = F;
|
|
103
|
-
let best = 1e9, bs = null;
|
|
104
|
-
const gx = Math.floor(p[0] / cell), gy = Math.floor(p[1] / cell), gz = Math.floor(p[2] / cell);
|
|
105
|
-
for (let dz = -1; dz <= 1; dz++)
|
|
106
|
-
for (let dy = -1; dy <= 1; dy++)
|
|
107
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
108
|
-
const arr = grid.get(key(gx + dx, gy + dy, gz + dz));
|
|
109
|
-
if (!arr) continue;
|
|
110
|
-
for (const s of arr) {
|
|
111
|
-
if (dot(s.n, n) < 0.2) continue;
|
|
112
|
-
const d = sub(p, s.p);
|
|
113
|
-
const el = s.el || 1;
|
|
114
|
-
const along = s.c ? dot(d, s.c) : 0;
|
|
115
|
-
const dd = Math.sqrt(Math.max(0, dot(d, d) - along * along * (1 - 1 / (el * el))));
|
|
116
|
-
const sd = dd - s.r * wob;
|
|
117
|
-
if (sd < best) { best = sd; bs = s; }
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return [best, bs];
|
|
121
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// Flight-feather cards: real feather geometry (primaries, secondaries, rectrices and their coverts)
|
|
2
|
-
// appended to the base surface. Each card is a thin two-sided vane (top and bottom layer, rachis
|
|
3
|
-
// down the middle, asymmetric outer / inner vane, rounded or emarginated tip, curved rachis) rigidly
|
|
4
|
-
// skinned to its own feather bone, so the engine can fan, fold, twist and splay every feather while
|
|
5
|
-
// the base draw call renders it (one draw, shadows and the crowd tier included). Material id 10
|
|
6
|
-
// (vane) is shaded in core/render/coatMaterial.js from surf = [gloss, iridescence, across, along]:
|
|
7
|
-
// across -1 (outer vane edge) .. 0 (rachis) .. 1 (inner vane edge), along 0 (root) .. 1 (tip).
|
|
8
|
-
// colour(kind, i, side, t, c, layer) -> { rgb, gloss, irid, bar? }: bar { rgb, period (m) } draws bars
|
|
9
|
-
// across the vane in the shader (patternColor = [rgb, -bar count]; opt-in, e.g. barred poultry).
|
|
10
|
-
//
|
|
11
|
-
// Species usage (surfaces hook, see core/build/pipeline.js):
|
|
12
|
-
// surfaces: (ctx) => featherCards({ ...ctx, wing: motion.wing, feathers: motion.feathers,
|
|
13
|
-
// tail: motion.tail, colour: (kind, i, side, t, c) => ({ rgb, gloss, irid }) })
|
|
14
|
-
import { featherJoints, DEG } from '../rig/bird.js';
|
|
15
|
-
import { MAT } from './coatKit.js';
|
|
16
|
-
|
|
17
|
-
const V = (x, y, z) => [x, y, z];
|
|
18
|
-
const addS = (a, b, k) => [a[0] + b[0] * k, a[1] + b[1] * k, a[2] + b[2] * k];
|
|
19
|
-
const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
20
|
-
const norm = (a) => { const l = Math.hypot(a[0], a[1], a[2]) || 1; return [a[0] / l, a[1] / l, a[2] / l]; };
|
|
21
|
-
const mirror = (p) => [-p[0], p[1], p[2]];
|
|
22
|
-
|
|
23
|
-
// vane half-widths at t (0 root .. 1 tip): [outer, inner] (also used by the bird engine's ground checks)
|
|
24
|
-
// (f.base0: the vane's width at the root as a fraction of its full width, default 0.3)
|
|
25
|
-
export function vaneWidth(f, t, w) {
|
|
26
|
-
const b0 = f.base0 ?? 0.3;
|
|
27
|
-
const base = b0 + (1 - b0) * smooth(0.0, 0.16, t);
|
|
28
|
-
const tr = f.tipRound;
|
|
29
|
-
let tip = 1;
|
|
30
|
-
if (t > 1 - tr) { const x = (t - (1 - tr)) / tr; tip = Math.sqrt(Math.max(0, 1 - x * x)); }
|
|
31
|
-
let wo = w * f.outer * base * tip, wi = w * (1 - f.outer) * base * tip;
|
|
32
|
-
if (f.emarg > 0) {
|
|
33
|
-
// emarginated primaries: both vanes step in beyond the notch, leaving a narrow "finger"
|
|
34
|
-
const k = smooth(0.5, 0.64, t) * f.emarg;
|
|
35
|
-
wi *= 1 - 0.62 * k; wo *= 1 - 0.3 * k;
|
|
36
|
-
}
|
|
37
|
-
return [wo, Math.max(wi, 0.0006)];
|
|
38
|
-
}
|
|
39
|
-
function smooth(a, b, x) { const t = Math.min(1, Math.max(0, (x - a) / (b - a))); return t * t * (3 - 2 * t); }
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param ctx { rig, params, Q, wing, feathers, tail, colour, thickness? }
|
|
43
|
-
* @returns surfaces block { nV, pos, nrm, index, skinIndex, skinWeight, comb, tint, pattern, mark, furLen, patternColor, surf }
|
|
44
|
-
*/
|
|
45
|
-
export function featherCards(ctx) {
|
|
46
|
-
const { rig, Q, wing, feathers, tail } = ctx;
|
|
47
|
-
const colour = ctx.colour || (() => ({ rgb: [0.02, 0.02, 0.02], gloss: 0.5, irid: 0 }));
|
|
48
|
-
// the bind layout (the rig built the joints from the same call)
|
|
49
|
-
const Jl = {};
|
|
50
|
-
for (const k of ['shoulderL', 'tailBase', 'tailTip']) Jl[k] = rig.J[k];
|
|
51
|
-
const lay = featherJoints(Jl, wing, feathers, tail);
|
|
52
|
-
const res = Q?.res ?? 1;
|
|
53
|
-
const nA = res <= 1.35 ? 9 : res <= 2.1 ? 6 : res <= 3.1 ? 4 : 3;
|
|
54
|
-
const th = ctx.thickness ?? 0.0005;
|
|
55
|
-
const coverts = res < 4 || ctx.crowdCoverts;
|
|
56
|
-
const P = [], N = [], C = [], T = [], S = [], SI = [], I = [], X = [];
|
|
57
|
-
const PCL = []; // vane bars: [vertex, rgb, -count]
|
|
58
|
-
const cov = lay.F.coverts;
|
|
59
|
-
|
|
60
|
-
const card = (bone, side, fr, f, opts) => {
|
|
61
|
-
const b = rig.BONE[bone];
|
|
62
|
-
if (!b) throw new Error('featherCards: missing bone ' + bone);
|
|
63
|
-
const s = side;
|
|
64
|
-
const m = (p) => (s > 0 ? p : mirror(p));
|
|
65
|
-
const L = f.len * (opts.lenK || 1), W = f.width * (opts.widthK || 1);
|
|
66
|
-
const A = m(V(fr.axis.x, fr.axis.y, fr.axis.z)), Nn = m(V(fr.normal.x, fr.normal.y, fr.normal.z));
|
|
67
|
-
// inner vane side (+across): s * (N x A)
|
|
68
|
-
const Tv = norm(cross(Nn, A)).map((x) => x * s);
|
|
69
|
-
const root0 = m(V(fr.root.x, fr.root.y, fr.root.z));
|
|
70
|
-
const root = addS(addS(root0, Nn, opts.lift || 0), A, opts.shift || 0);
|
|
71
|
-
const fo = opts.shape ? { ...f, ...opts.shape } : f;
|
|
72
|
-
// vane arch: the edges sit this fraction of the half-width below the rachis (feather spec `arch`,
|
|
73
|
-
// default 0.12; a flatter vane lets overlapping neighbours stack on smaller lift steps)
|
|
74
|
-
const arch = fo.arch ?? 0.12;
|
|
75
|
-
const v0 = P.length / 3;
|
|
76
|
-
const ts = [];
|
|
77
|
-
for (let j = 0; j <= nA; j++) { const u = j / nA; ts.push(0.5 - 0.5 * Math.cos(Math.PI * u)); }
|
|
78
|
-
const pt = (t, c) => {
|
|
79
|
-
const [wo, wi] = vaneWidth(fo, t, W);
|
|
80
|
-
const w = c < 0 ? wo : wi;
|
|
81
|
-
if (fo.arc) {
|
|
82
|
-
// rachis bent along a circular arc of total angle `arc` (rad) toward the inner vane, length
|
|
83
|
-
// kept (long display feathers: a rooster's sickles); the vane width turns with the rachis
|
|
84
|
-
const a = fo.arc * t, R = L / fo.arc, ca = Math.cos(a), sa = Math.sin(a);
|
|
85
|
-
let p = addS(root, A, R * sa);
|
|
86
|
-
p = addS(p, Tv, R * (1 - ca));
|
|
87
|
-
p = addS(p, A, -sa * c * w);
|
|
88
|
-
p = addS(p, Tv, ca * c * w);
|
|
89
|
-
return addS(p, Nn, -fo.droop * L * t * t - Math.abs(c) * w * arch);
|
|
90
|
-
}
|
|
91
|
-
// curved rachis (toward the inner vane, drooping), vanes slightly convex (edges lower)
|
|
92
|
-
const along = t * L, curve = fo.curve * L * t * t, droop = fo.droop * L * t * t;
|
|
93
|
-
let p = addS(root, A, along);
|
|
94
|
-
p = addS(p, Tv, curve + c * w);
|
|
95
|
-
p = addS(p, Nn, -droop - Math.abs(c) * w * arch);
|
|
96
|
-
return p;
|
|
97
|
-
};
|
|
98
|
-
const cols = [-1, 0, 1];
|
|
99
|
-
for (const layer of [1, -1]) {
|
|
100
|
-
for (let j = 0; j <= nA; j++) {
|
|
101
|
-
const t = ts[j];
|
|
102
|
-
for (const c of cols) {
|
|
103
|
-
const p = addS(pt(t, c), Nn, layer * th * 0.5 * (1 - 0.6 * t));
|
|
104
|
-
// surface tangents for the normal
|
|
105
|
-
const e = 1e-3;
|
|
106
|
-
const pa = pt(Math.min(1, t + e), c), pb = pt(Math.max(0, t - e), c);
|
|
107
|
-
const pc = pt(t, Math.min(1, c + 0.05)), pd = pt(t, Math.max(-1, c - 0.05));
|
|
108
|
-
const dt = [pa[0] - pb[0], pa[1] - pb[1], pa[2] - pb[2]], dc = [pc[0] - pd[0], pc[1] - pd[1], pc[2] - pd[2]];
|
|
109
|
-
let n = norm(cross(dt, dc));
|
|
110
|
-
const sgn = (n[0] * Nn[0] + n[1] * Nn[1] + n[2] * Nn[2]) < 0 ? -1 : 1;
|
|
111
|
-
n = n.map((x) => x * sgn * layer);
|
|
112
|
-
if (!Number.isFinite(n[0])) n = Nn.map((x) => x * layer);
|
|
113
|
-
P.push(...p); N.push(...n);
|
|
114
|
-
C.push(...norm(dt));
|
|
115
|
-
const col = colour(opts.kind, f.i, s, t, c, layer);
|
|
116
|
-
T.push(col.rgb[0], col.rgb[1], col.rgb[2], MAT.VANE);
|
|
117
|
-
// opt-in bars across the vane (colour() returns bar: { rgb, period (m) }): shader-drawn
|
|
118
|
-
if (col.bar) PCL.push([P.length / 3 - 1, col.bar.rgb, -L / col.bar.period]);
|
|
119
|
-
S.push(col.gloss ?? 0.5, col.irid ?? 0, c, t);
|
|
120
|
-
const wv = vaneWidth(fo, t, W);
|
|
121
|
-
X.push(c * (c < 0 ? wv[0] : wv[1]) * s);
|
|
122
|
-
SI.push(b.index, 0, 0, 0);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const base = v0 + (layer > 0 ? 0 : (nA + 1) * 3);
|
|
126
|
-
for (let j = 0; j < nA; j++) {
|
|
127
|
-
for (let k = 0; k < 2; k++) {
|
|
128
|
-
const a = base + j * 3 + k, bq = a + 1, c2 = a + 3, d = a + 4;
|
|
129
|
-
// top layer faces +N; the bottom layer is wound the other way
|
|
130
|
-
if ((layer > 0) === (s > 0)) I.push(a, c2, bq, bq, c2, d);
|
|
131
|
-
else I.push(a, bq, c2, bq, d, c2);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// winding sanity: flip this card's triangles if their geometric normal disagrees with the layer normal
|
|
136
|
-
fixWinding(P, N, I, v0);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// per-feather covert overrides (spec field `cov`: { lift, shift, lenK, widthK, shape }, merged over
|
|
140
|
-
// the defaults below; opt-in: a species can restack its coverts, e.g. reverse their overlap order;
|
|
141
|
-
// `cov: { skip: true }` drops the covert card, e.g. where the species sculpts the coverts as skin)
|
|
142
|
-
const covOpts = (f, o) => (f.cov ? { ...o, ...f.cov, shape: { ...o.shape, ...(f.cov.shape || {}) } } : o);
|
|
143
|
-
const wEH = lay.frames;
|
|
144
|
-
for (const side of [1, -1]) {
|
|
145
|
-
const S2 = side > 0 ? 'L' : 'R';
|
|
146
|
-
for (const fr of wEH.pri) {
|
|
147
|
-
const f = fr.f;
|
|
148
|
-
card(`pri${f.i + 1}${S2}`, side, fr, f, { kind: 'primary' });
|
|
149
|
-
if (coverts && !f.cov?.skip) card(`pri${f.i + 1}${S2}`, side, fr, f, covOpts(f, { kind: 'primaryCovert', lenK: cov.primary * (1 - 0.25 * f.i / Math.max(1, f.n - 1)), widthK: 1.05, lift: 0.0022, shift: -0.004, shape: { emarg: 0, tipRound: 0.6, outer: 0.45, curve: 0.02 } }));
|
|
150
|
-
}
|
|
151
|
-
for (const fr of wEH.sec) {
|
|
152
|
-
const f = fr.f;
|
|
153
|
-
card(`sec${f.i + 1}${S2}`, side, fr, f, { kind: 'secondary' });
|
|
154
|
-
if (coverts && !f.cov?.skip) card(`sec${f.i + 1}${S2}`, side, fr, f, covOpts(f, { kind: 'secondaryCovert', lenK: cov.secondary, widthK: 1.15, lift: 0.0028, shift: -0.006, shape: { tipRound: 0.65, outer: 0.45, curve: 0.02 } }));
|
|
155
|
-
}
|
|
156
|
-
for (const fr of wEH.rec) {
|
|
157
|
-
const f = fr.f;
|
|
158
|
-
card(`rec${f.i + 1}${S2}`, side, fr, f, { kind: 'rectrix' });
|
|
159
|
-
if (coverts && f.i < 4 && !f.cov?.skip) card(`rec${f.i + 1}${S2}`, side, fr, f, covOpts(f, { kind: 'tailCovert', lenK: cov.tail * (1 - 0.1 * f.i), widthK: 1.2, lift: 0.0025, shift: -0.012, shape: { tipRound: 0.7, outer: 0.5 } }));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const nV = P.length / 3;
|
|
163
|
-
const out = {
|
|
164
|
-
nV,
|
|
165
|
-
pos: Float32Array.from(P), nrm: Float32Array.from(N), index: Uint32Array.from(I),
|
|
166
|
-
skinIndex: Uint16Array.from(SI), skinWeight: new Float32Array(nV * 4),
|
|
167
|
-
comb: Float32Array.from(C), tint: Float32Array.from(T), surf: Float32Array.from(S),
|
|
168
|
-
pattern: Float32Array.from(X), mark: new Float32Array(nV).fill(1), furLen: new Float32Array(nV),
|
|
169
|
-
patternColor: new Float32Array(nV * 4),
|
|
170
|
-
};
|
|
171
|
-
for (const [v, rgb, n] of PCL) out.patternColor.set([rgb[0], rgb[1], rgb[2], n], v * 4);
|
|
172
|
-
for (let v = 0; v < nV; v++) out.skinWeight[v * 4] = 1;
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function fixWinding(P, N, I, v0) {
|
|
177
|
-
// triangles are appended per card; check the first triangle referencing v0's range
|
|
178
|
-
for (let t = 0; t < I.length; t += 3) {
|
|
179
|
-
const a = I[t], b = I[t + 1], c = I[t + 2];
|
|
180
|
-
if (a < v0) continue;
|
|
181
|
-
const e1 = [P[b * 3] - P[a * 3], P[b * 3 + 1] - P[a * 3 + 1], P[b * 3 + 2] - P[a * 3 + 2]];
|
|
182
|
-
const e2 = [P[c * 3] - P[a * 3], P[c * 3 + 1] - P[a * 3 + 1], P[c * 3 + 2] - P[a * 3 + 2]];
|
|
183
|
-
const g = cross(e1, e2);
|
|
184
|
-
const d = g[0] * N[a * 3] + g[1] * N[a * 3 + 1] + g[2] * N[a * 3 + 2];
|
|
185
|
-
if (d < 0) { I[t + 1] = c; I[t + 2] = b; }
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export { DEG };
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
// Knife-thin fin edges below the mesh cell size.
|
|
2
|
-
//
|
|
3
|
-
// A fin slab (SDFModel.fin) meshes with a rim no thinner than ~2 cells (thinner slabs alias into
|
|
4
|
-
// holes), so at 1 cm cells a shark's fins end in 2 cm rounded, rubbery edges. A fin primitive tagged
|
|
5
|
-
// `sharpen: { edge, band, rim }` has its meshed surface squeezed toward its mid-plane near its free
|
|
6
|
-
// outline: every vertex whose nearest fin slab is that primitive has its distance from the plane
|
|
7
|
-
// scaled by `edge` (e.g. 0.15) at the outline, easing back to 1 at `band` mesh cells (of the vertex's
|
|
8
|
-
// region, so every quality tier) inside it; the rounded rim beyond the outline is drawn in toward it
|
|
9
|
-
// by `rim` (0.4: to 0.4 of its width), so the squeezed edge runs straight instead of following the
|
|
10
|
-
// cells' sampling of the round cap. Where the fin grows out of the body (the field of the non-fin
|
|
11
|
-
// primitives within ~1.5-4 cells of the vertex) nothing moves, so the root fillet keeps its shape.
|
|
12
|
-
// Same vertex count; the field's analytic normals are carried through the squeeze (inverse transpose
|
|
13
|
-
// of its Jacobian), so the shading stays as smooth as the field's. Opt-in: no tagged primitive, no
|
|
14
|
-
// change.
|
|
15
|
-
import { SDFModel, T_FIN, polySD } from '../sdf/sdf.js';
|
|
16
|
-
|
|
17
|
-
const smooth = (a, b, x) => { const t = Math.min(1, Math.max(0, (x - a) / (b - a))); return t * t * (3 - 2 * t); };
|
|
18
|
-
const dsmooth = (a, b, x) => { if (x <= a || x >= b) return 0; const t = (x - a) / (b - a); return (6 * t * (1 - t)) / (b - a); };
|
|
19
|
-
|
|
20
|
-
/** @returns the number of vertices moved */
|
|
21
|
-
export function sharpenFinEdges({ pos, nrm, lists, nV, regionOf }, hOfRegion) {
|
|
22
|
-
let n = 0;
|
|
23
|
-
for (let v = 0; v < nV; v++) {
|
|
24
|
-
const L = lists[v];
|
|
25
|
-
if (!L) continue;
|
|
26
|
-
const h = hOfRegion[regionOf[v]];
|
|
27
|
-
let fin = null, best = Infinity;
|
|
28
|
-
const x = pos[v * 3], y = pos[v * 3 + 1], z = pos[v * 3 + 2];
|
|
29
|
-
for (const pr of L) {
|
|
30
|
-
if (pr.type !== T_FIN || !pr.sharpen || pr.carve) continue;
|
|
31
|
-
const d = Math.abs(SDFModel.dist(pr, x, y, z));
|
|
32
|
-
if (d < best) { best = d; fin = pr; }
|
|
33
|
-
}
|
|
34
|
-
if (!fin) continue;
|
|
35
|
-
const P = fin.P, S = fin.sharpen, e = S.edge, B = S.band * h, c = S.rim ?? 1;
|
|
36
|
-
const dx = x - P[0], dy = y - P[1], dz = z - P[2];
|
|
37
|
-
const pu = dx * P[3] + dy * P[4] + dz * P[5], pv = dx * P[6] + dy * P[7] + dz * P[8];
|
|
38
|
-
const pn = dx * P[9] + dy * P[10] + dz * P[11];
|
|
39
|
-
// depth inside the outline (< 0 on the rounded rim beyond it)
|
|
40
|
-
const din = -polySD(P, pu, pv);
|
|
41
|
-
if (din >= B) continue;
|
|
42
|
-
// the body around the root: the field of every non-fin primitive near the vertex
|
|
43
|
-
const rest = L.filter((pr) => pr.type !== T_FIN && !pr.carve);
|
|
44
|
-
const dBody = rest.length ? SDFModel.evalList(rest, x, y, z) : 1e9;
|
|
45
|
-
const wr = smooth(1.5 * h, 4 * h, dBody);
|
|
46
|
-
if (wr < 1e-3) continue;
|
|
47
|
-
// squeeze factor s(din) = 1 - wr (1 - k(din)), k = e + (1 - e) smooth(0, B, din)
|
|
48
|
-
const s = 1 - wr * (1 - e) * (1 - smooth(0, B, din));
|
|
49
|
-
const ds = wr * (1 - e) * dsmooth(0, B, din);
|
|
50
|
-
// in-plane gradient of din (unit, pointing into the fin)
|
|
51
|
-
const eps = 0.25 * h;
|
|
52
|
-
let gu = -(polySD(P, pu + eps, pv) - polySD(P, pu - eps, pv)) / (2 * eps);
|
|
53
|
-
let gv = -(polySD(P, pu, pv + eps) - polySD(P, pu, pv - eps)) / (2 * eps);
|
|
54
|
-
const gl = Math.hypot(gu, gv) || 1;
|
|
55
|
-
gu /= gl; gv /= gl;
|
|
56
|
-
const gx = P[3] * gu + P[6] * gv, gy = P[4] * gu + P[7] * gv, gz = P[5] * gu + P[8] * gv;
|
|
57
|
-
// the rim beyond the outline drawn in toward it (to c of its width)
|
|
58
|
-
const pull = din < 0 ? (1 - c) * -din * wr : 0;
|
|
59
|
-
const off = pn * (s - 1);
|
|
60
|
-
pos[v * 3] += P[9] * off + gx * pull; pos[v * 3 + 1] += P[10] * off + gy * pull; pos[v * 3 + 2] += P[11] * off + gz * pull;
|
|
61
|
-
// normal: J = I + nf a^T (a = (s - 1) nf + pn ds g) -> J^-T n0 = n0 - a (nf . n0) / s; the rim pull
|
|
62
|
-
// scales along g by c' = 1 - (1 - c) wr -> divide the g component by c'
|
|
63
|
-
let nx = nrm[v * 3], ny = nrm[v * 3 + 1], nz = nrm[v * 3 + 2];
|
|
64
|
-
const fn = (nx * P[9] + ny * P[10] + nz * P[11]) / s;
|
|
65
|
-
const ax = (s - 1) * P[9] + pn * ds * gx, ay = (s - 1) * P[10] + pn * ds * gy, az = (s - 1) * P[11] + pn * ds * gz;
|
|
66
|
-
nx -= ax * fn; ny -= ay * fn; nz -= az * fn;
|
|
67
|
-
if (din < 0 && c < 1) {
|
|
68
|
-
const cc = 1 - (1 - c) * wr, gn = (nx * gx + ny * gy + nz * gz) * (1 / cc - 1);
|
|
69
|
-
nx += gx * gn; ny += gy * gn; nz += gz * gn;
|
|
70
|
-
}
|
|
71
|
-
// at the knife edge itself the faces above and below meet: a vertex there (on the field's rounded
|
|
72
|
-
// cap, close to the mid-plane) takes the edge's in-plane normal; otherwise vertices a hair above and
|
|
73
|
-
// below the plane along the rim would alternate up / down normals (stripes across the edge)
|
|
74
|
-
const q = Math.abs(pn) / Math.max(1e-9, P[12]);
|
|
75
|
-
const fq = smooth(0.15, 0.7, q);
|
|
76
|
-
if (fq < 1) {
|
|
77
|
-
const nfc = (nx * P[9] + ny * P[10] + nz * P[11]) * (1 - fq);
|
|
78
|
-
nx -= P[9] * nfc; ny -= P[10] * nfc; nz -= P[11] * nfc;
|
|
79
|
-
}
|
|
80
|
-
const l = Math.hypot(nx, ny, nz) || 1;
|
|
81
|
-
nrm[v * 3] = nx / l; nrm[v * 3 + 1] = ny / l; nrm[v * 3 + 2] = nz / l;
|
|
82
|
-
n++;
|
|
83
|
-
}
|
|
84
|
-
return n;
|
|
85
|
-
}
|