@bitefoundry/retrieviewer 0.0.0-stage → 0.0.2
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 +43 -0
- package/ASSETS-LICENSE.md +58 -0
- package/ATTRIBUTION.md +54 -0
- package/EMBEDDING.md +96 -0
- package/INSTALLING.md +126 -0
- package/LICENSE +16 -0
- package/LICENSE-CODE +21 -0
- package/PLUGIN.md +110 -0
- package/PUPPY-MEMORY.md +57 -0
- package/PUPPY-Q5-COMPARISON.md +73 -0
- package/README.md +86 -2
- package/RELEASING.md +70 -0
- package/THIRD-PARTY-NOTICES.md +17 -0
- package/TRADEMARKS.md +21 -0
- package/WINDOWS-COMPANION.md +113 -0
- package/braille.mjs +45 -0
- package/examples/bridge.py +62 -0
- package/examples/embed.mjs +15 -0
- package/examples/live.mjs +40 -0
- package/examples/plugin.mjs +21 -0
- package/integrations/opencode/runtime.mjs +195 -0
- package/integrations/opencode/tui.tsx +59 -0
- package/integrations/windows-overlay/Overlay.cs +259 -0
- package/integrations/windows-overlay/desktop.mjs +87 -0
- package/integrations/windows-overlay/protocol.mjs +39 -0
- package/integrations/windows-overlay/worker.mjs +60 -0
- package/locomotion-core.mjs +21 -0
- package/locomotion-puppy.animal +0 -0
- package/package.json +131 -4
- package/pet-q5-manifest.json +118 -0
- package/puppy-clean.mjs +51 -0
- package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
- package/puppy-companion-interactions-v3.json +1 -0
- package/puppy-companion-life.mjs +182 -0
- package/puppy-companion-objects.mjs +30 -0
- package/puppy-companion-sprites.mjs +14 -0
- package/puppy-continuous.mjs +57 -0
- package/puppy-face.mjs +46 -0
- package/puppy-frame-predictive.mjs +147 -0
- package/puppy-frame-store.mjs +108 -0
- package/puppy-mouth-anchor.json +1 -0
- package/puppy-mouth-anchor.mjs +33 -0
- package/puppy-paper-actions-v9-q5.bin.gz +0 -0
- package/puppy-paper-actions-v9.json +1 -0
- package/puppy-paper-actions.mjs +48 -0
- package/puppy-paper-approach-q5.bin.gz +0 -0
- package/puppy-paper-approach.json +1 -0
- package/puppy-paper-area.mjs +15 -0
- package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
- package/puppy-paper-arrival-seat.json +1 -0
- package/puppy-paper-behaviors-q5.bin.gz +0 -0
- package/puppy-paper-behaviors.json +1 -0
- package/puppy-paper-behaviors.mjs +8 -0
- package/puppy-paper-contact.mjs +103 -0
- package/puppy-paper-director.mjs +21 -0
- package/puppy-paper-paw-transport.mjs +76 -0
- package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
- package/puppy-paper-rear-gallop.json +1 -0
- package/puppy-paper-seat-q5.bin.gz +0 -0
- package/puppy-paper-seat.json +14 -0
- package/puppy-paper-seat.mjs +13 -0
- package/puppy-paper-trick-timing.mjs +58 -0
- package/puppy-paper-world.mjs +108 -0
- package/puppy-paper.mjs +271 -0
- package/puppy-stable-meta.json +1606 -0
- package/puppy-stable.mjs +72 -0
- package/puppy-stride-meta.json +343 -0
- package/puppy-stride-tweens-q5.bin.gz +0 -0
- package/puppy-zoomies.mjs +27 -0
- package/puppy-zoomies30.mjs +12 -0
- package/retrieviewer-cli.mjs +94 -0
- package/retrieviewer-config.mjs +49 -0
- package/retrieviewer-install.mjs +157 -0
- package/retrieviewer-plugin.d.mts +44 -0
- package/retrieviewer-plugin.mjs +97 -0
- package/retrieviewer-stdio.mjs +38 -0
- package/retrieviewer.d.mts +85 -0
- package/retrieviewer.mjs +142 -0
- package/vendor/procedural-animals/LICENSE +21 -0
- package/vendor/procedural-animals/package.json +40 -0
- package/vendor/procedural-animals/src/animal.js +146 -0
- package/vendor/procedural-animals/src/bake.js +51 -0
- package/vendor/procedural-animals/src/build.js +81 -0
- package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
- package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
- package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
- package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
- package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
- package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
- package/vendor/procedural-animals/src/core/build/seams.js +183 -0
- package/vendor/procedural-animals/src/core/build/warp.js +112 -0
- package/vendor/procedural-animals/src/core/build/weights.js +393 -0
- package/vendor/procedural-animals/src/core/math/vec.js +81 -0
- package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
- package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
- package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
- package/vendor/procedural-animals/src/core/motion/common.js +158 -0
- package/vendor/procedural-animals/src/core/motion/index.js +22 -0
- package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
- package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
- package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
- package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
- package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
- package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
- package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
- package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
- package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
- package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
- package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
- package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
- package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
- package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
- package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
- package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
- package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
- package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
- package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
- package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
- package/vendor/procedural-animals/src/index.d.ts +98 -0
- package/vendor/procedural-animals/src/index.js +29 -0
- package/vendor/procedural-animals/src/registry.js +48 -0
- package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
- package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
- package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
- package/vendor/procedural-animals/src/species/bear/index.js +139 -0
- package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
- package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
- package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
- package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
- package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
- package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
- package/vendor/procedural-animals/src/species/boar/index.js +110 -0
- package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
- package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
- package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
- package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
- package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
- package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
- package/vendor/procedural-animals/src/species/cat/index.js +211 -0
- package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
- package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
- package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
- package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
- package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
- package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
- package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
- package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
- package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
- package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
- package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
- package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
- package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
- package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
- package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
- package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
- package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
- package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
- package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
- package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
- package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
- package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
- package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
- package/vendor/procedural-animals/src/species/cow/index.js +118 -0
- package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
- package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
- package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
- package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
- package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
- package/vendor/procedural-animals/src/species/crow/index.js +70 -0
- package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
- package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
- package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
- package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
- package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
- package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
- package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
- package/vendor/procedural-animals/src/species/deer/index.js +106 -0
- package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
- package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
- package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
- package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
- package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
- package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
- package/vendor/procedural-animals/src/species/dog/index.js +144 -0
- package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
- package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
- package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
- package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
- package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
- package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
- package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
- package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
- package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
- package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
- package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
- package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
- package/vendor/procedural-animals/src/species/fish/index.js +88 -0
- package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
- package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
- package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
- package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
- package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
- package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
- package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
- package/vendor/procedural-animals/src/species/fox/index.js +120 -0
- package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
- package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
- package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
- package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
- package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
- package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
- package/vendor/procedural-animals/src/species/frog/index.js +97 -0
- package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
- package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
- package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
- package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
- package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
- package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
- package/vendor/procedural-animals/src/species/goat/index.js +149 -0
- package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
- package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
- package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
- package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
- package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
- package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
- package/vendor/procedural-animals/src/species/horse/index.js +80 -0
- package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
- package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
- package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
- package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
- package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
- package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
- package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
- package/vendor/procedural-animals/src/species/lion/index.js +111 -0
- package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
- package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
- package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
- package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
- package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
- package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
- package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
- package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
- package/vendor/procedural-animals/src/species/pig/index.js +161 -0
- package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
- package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
- package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
- package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
- package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
- package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
- package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
- package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
- package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
- package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
- package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
- package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
- package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
- package/vendor/procedural-animals/src/species/rat/index.js +111 -0
- package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
- package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
- package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
- package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
- package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
- package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
- package/vendor/procedural-animals/src/species/shark/index.js +104 -0
- package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
- package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
- package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
- package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
- package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
- package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
- package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
- package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
- package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
- package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
- package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
- package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
- package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
- package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
- package/vendor/procedural-animals/src/species/snake/index.js +73 -0
- package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
- package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
- package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
- package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
- package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
- package/vendor/procedural-animals/src/species/spider/index.js +78 -0
- package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
- package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
- package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
- package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
- package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
- package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
- package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
- package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
- package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
- package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
- package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
- package/vendor/procedural-animals/src/worker.js +29 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
// Action layer for the quadruped engine.
|
|
2
|
+
//
|
|
3
|
+
// Every frame the layer fills a posture parameter set P (createPostureParams) that the engine reads
|
|
4
|
+
// on top of the locomotion. Actions are time-parameterised and blend in / out with weights:
|
|
5
|
+
//
|
|
6
|
+
// postures sit, lie, sleep, death replace the locomotion until 'stand' (or until the animal is
|
|
7
|
+
// asked to move: it stands up first). Cross-fade between them.
|
|
8
|
+
// one-shots jump, attack, hit, eat, blend over whatever runs underneath (an attack can be
|
|
9
|
+
// drink played while galloping), fade in and out.
|
|
10
|
+
// idle automatic when standing with nothing else running: weight shifts with occasional
|
|
11
|
+
// re-steps, looking around, ear twitches, tail flicks (blinks: the eye rig).
|
|
12
|
+
//
|
|
13
|
+
// play(name, opts) returns a Promise resolved when the action ends (postures: when the transition
|
|
14
|
+
// into the pose is complete; looping actions: when stopped). opts: { target (Vector3), direction
|
|
15
|
+
// (Vector3, hit: direction the blow travels), loop, speed (time scale) }. The promise resolves with
|
|
16
|
+
// how the action ended: 'done' | 'interrupted' | 'stopped' | 'refused'.
|
|
17
|
+
//
|
|
18
|
+
// Arbitration (the same rule in every body plan):
|
|
19
|
+
// - a new one-shot interrupts the running one-shots: they fade out over their own fade and resolve
|
|
20
|
+
// 'interrupted' (the new one fades in over the same time: a cross-fade);
|
|
21
|
+
// - a one-shot committed to the air (a jump or pounce between push-off and touchdown) cannot be cut:
|
|
22
|
+
// while it runs a new one-shot is refused, except `hit`, which layers over it;
|
|
23
|
+
// - a posture cross-fades from the current posture and ends the running one-shots (except a hit
|
|
24
|
+
// flinch); jump / attack (and hook actions with needsStand) stand the animal up first;
|
|
25
|
+
// - stop(name) ends that action (a posture: the animal stands up); stop() ends every one-shot and
|
|
26
|
+
// posture except death (only play('stand') or stop('death') revives), and never cuts a jump in the
|
|
27
|
+
// air (it lands first);
|
|
28
|
+
// - dead: every one-shot except hit is refused; moving does not stand a dead animal up.
|
|
29
|
+
// Hook one-shots may set overlay: true (a tail swish, a vocalisation): they neither interrupt nor
|
|
30
|
+
// are interrupted by other one-shots.
|
|
31
|
+
//
|
|
32
|
+
// Species tuning: species.motion.actions = {
|
|
33
|
+
// attack: { style: 'bite-lunge' | 'pounce' | 'charge' | 'headbutt' | 'kick', kick: 'double' | 'rear',
|
|
34
|
+
// kickReach: [back, up] leg lengths ([1, 0.45]) } (play('attack', { kick: 'rear' }) picks
|
|
35
|
+
// the kick per call),
|
|
36
|
+
// eat: { style: 'tear' | 'graze', dropF, dropH, ahead: mouth ahead of the forefeet (leg lengths, 0.5) },
|
|
37
|
+
// drink: { dropF, dropH, ahead },
|
|
38
|
+
// jump: { height (m), distance (m), land: [front, hind] landing crouch, give: landing sink
|
|
39
|
+
// speed handed to the girdle springs (x the body's, 1) }, sleep: { style: 'curl' | 'lateral' },
|
|
40
|
+
// sit / lie / death: { dropF, dropH, ... } overrides of the pose values below,
|
|
41
|
+
// lie / sleep: { down, up: 'front' | 'hind' } which end kneels first / gets up first,
|
|
42
|
+
// idle: { shift: weight-shift amount (1), restep: false disables idle re-steps },
|
|
43
|
+
// fold: { front: { z, k, beta } } forelegs folded under the chest when lying (hoofed animals kneel) }
|
|
44
|
+
//
|
|
45
|
+
// ------------------------------------------------------------------------------------------------
|
|
46
|
+
// Species hooks: species.motion.hooks = { actions, update, pose } (all optional). The one place a
|
|
47
|
+
// species adds behaviour to the quadruped engine; everything else is data.
|
|
48
|
+
//
|
|
49
|
+
// actions: { name: def } extra actions, or replacements of built-ins of the same name. def =
|
|
50
|
+
// { kind: 'oneshot' | 'posture',
|
|
51
|
+
// start(inst, layer), update(inst, dt, layer) -> true when done, apply(P, w, inst, layer),
|
|
52
|
+
// onLand?(inst, layer) (one-shots; apply is called with the faded weight)
|
|
53
|
+
// fadeIn, fadeOut, apply(P, w, inst, layer) (postures: held until 'stand')
|
|
54
|
+
// fade?: s (one-shot fade in / out, 0.12), airborne?: true (it calls engine.takeOff: no second
|
|
55
|
+
// jump while in the air, fast fade-out), needsStand?: true (a sitting / lying animal stands up
|
|
56
|
+
// first, like jump and attack) }
|
|
57
|
+
// inst: { name, t, w, dur, opts (play options), tune (species.motion.actions[name]), side, ... }
|
|
58
|
+
// The built-in definitions are exported (ONESHOTS, POSTURES) so a species can wrap them.
|
|
59
|
+
// update(P, dt, layer) every frame after the built-in postures and one-shots have filled P and before
|
|
60
|
+
// finished actions are resolved: body language and style on top of everything (tail by mood,
|
|
61
|
+
// ears, hackles P.furRaise, a walk's head nod, dozing, idle variants that layer.play() actions).
|
|
62
|
+
// It may also steer (engine.wantSpeed / wantHeading). layer: { engine, idle: { w, stepT },
|
|
63
|
+
// postures: [inst], oneshots: [inst], play(name, opts) }.
|
|
64
|
+
// pose(engine, dt) after the engine has written every bone (legs, head, ears, tail and the species'
|
|
65
|
+
// extra bones): direct bone edits (a twitching snout, independent ear swivels). engine.bone[name]
|
|
66
|
+
// .matrixWorld is world space; keep the change continuous from frame to frame.
|
|
67
|
+
//
|
|
68
|
+
// Units in P: angles in radians; dropF / dropH are fractions of the girdle height; posture lengths
|
|
69
|
+
// (fwd, side, lift, headRaise, neckReach, headLocal) are in engine units, multiplied by cfg.k = size x
|
|
70
|
+
// unit (the individual's size relative to the cheetah): a hook that means species metres divides by
|
|
71
|
+
// cfg.unit. World points (headPos, legs[i].target) are metres. Helpers: mx(P, key, value, w) blends
|
|
72
|
+
// P[key] toward value with weight w; mxLeg(P, i, key, value, w), mxLegs(P, key, front, hind, w);
|
|
73
|
+
// mouthGroundTarget(engine, out, pitch, levelFn). Engine state a hook may read: speed, heading, pos,
|
|
74
|
+
// phase, gait, legs[i] ({ state, plant, contact, def }), fr (body frames), cfg, params (the
|
|
75
|
+
// individual's build params), rand(), input, air.
|
|
76
|
+
import * as THREE from 'three';
|
|
77
|
+
import { clamp, lerp, smooth, envelope, DEG, tv, angDiff } from './common.js';
|
|
78
|
+
|
|
79
|
+
const LEG_FIELDS = ['fold', 'sit', 'side', 'tuck', 'reach'];
|
|
80
|
+
const KICK_REACH = [1.0, 0.45];
|
|
81
|
+
|
|
82
|
+
export function createPostureParams() {
|
|
83
|
+
return {
|
|
84
|
+
gaitW: 1, legGait: 1, holdFeet: 0, bodyOmega: 1, speedScale: 1,
|
|
85
|
+
frontH: 0, frontHW: 0, // absolute shoulder-joint height above the ground (m) and its weight
|
|
86
|
+
dropF: 0, dropH: 0, pitch: 0, roll: 0, flex: 0, bend: 0, lift: 0, fwd: 0, side: 0, groundW: 0,
|
|
87
|
+
headW: 0, headPos: new THREE.Vector3(), headLocalW: 0, headLocal: new THREE.Vector3(), headReach: 0,
|
|
88
|
+
headRaise: 0, neckReach: 0, headPitch: 0, headYaw: 0, headRoll: 0, headLimp: 0, headOmega: 1,
|
|
89
|
+
look: null, lookW: 1, jaw: 0, jawOmega: 12,
|
|
90
|
+
legs: [0, 1, 2, 3].map(() => ({ fold: 0, sit: 0, side: 0, spread: 0, tuck: 0, extend: 0, reach: 0, target: new THREE.Vector3() })),
|
|
91
|
+
tailLift: 0, tailGround: 0, tailSide: 0, tailCurl: 0, tailWag: 0, tailIdle: 1, tailStiff: 1,
|
|
92
|
+
earFlat: 0, earTwitch: 1, earSwivel: 0, eyelid: 0, breathRate: 1, breathAmp: 1, furRaise: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resetParams(P) {
|
|
97
|
+
P.gaitW = 1; P.legGait = 1; P.holdFeet = 0; P.bodyOmega = 1; P.speedScale = 1; P.frontH = 0; P.frontHW = 0;
|
|
98
|
+
P.dropF = P.dropH = P.pitch = P.roll = P.flex = P.bend = P.lift = P.fwd = P.side = P.groundW = 0;
|
|
99
|
+
P.headW = P.headLocalW = P.headReach = P.headRaise = P.neckReach = P.headPitch = P.headYaw = P.headRoll = P.headLimp = 0;
|
|
100
|
+
P.headOmega = 1; P.look = null; P.lookW = 1; P.jaw = 0; P.jawOmega = 12;
|
|
101
|
+
for (const l of P.legs) { l.fold = l.sit = l.side = l.spread = l.tuck = l.extend = l.reach = 0; }
|
|
102
|
+
P.tailLift = P.tailGround = P.tailSide = P.tailCurl = P.tailWag = 0; P.tailIdle = 1; P.tailStiff = 1;
|
|
103
|
+
P.earFlat = 0; P.earTwitch = 1; P.earSwivel = 0; P.eyelid = 0; P.breathRate = 1; P.breathAmp = 1; P.furRaise = 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// P.key = lerp(P.key, value, w)
|
|
107
|
+
const mx = (P, k, v, w) => { P[k] += (v - P[k]) * w; };
|
|
108
|
+
const mxLeg = (P, i, k, v, w) => { const l = P.legs[i]; l[k] += (v - l[k]) * w; };
|
|
109
|
+
const mxLegs = (P, k, vF, vH, w) => { for (let i = 0; i < 4; i++) mxLeg(P, i, k, i < 2 ? vF : vH, w); };
|
|
110
|
+
|
|
111
|
+
// ------------------------------------------------------------------------------------------------
|
|
112
|
+
// Posture poses: value sets applied with the posture weight (a function of time for sequences)
|
|
113
|
+
// ------------------------------------------------------------------------------------------------
|
|
114
|
+
const POSTURES = {
|
|
115
|
+
// haunches down, forelegs straight, tail on the ground curled to one side
|
|
116
|
+
sit: {
|
|
117
|
+
fadeIn: 1.0, fadeOut: 0.8,
|
|
118
|
+
apply(P, w, inst, L) {
|
|
119
|
+
const o = inst.tune;
|
|
120
|
+
const wH = smooth(0, 0.7, w), wF = smooth(0.25, 1, w);
|
|
121
|
+
mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
|
|
122
|
+
mx(P, 'dropH', o.dropH ?? 1.0, wH); mx(P, 'dropF', o.dropF ?? -0.04, wF);
|
|
123
|
+
// forelegs straight: the shoulders sit at the straight foreleg's height (metacarpus near
|
|
124
|
+
// vertical, radius + humerus at 96 % of their length: just inside the IK's soft reach limit)
|
|
125
|
+
const Lf = L.engine.cfg.legs[0];
|
|
126
|
+
mx(P, 'frontH', o.frontH ?? Lf.M.y + Lf.Lmc * Math.cos(0.6 * Lf.a0) + 0.955 * (Lf.Lh + Lf.Lr), wF); mx(P, 'frontHW', 1, wF);
|
|
127
|
+
mxLegs(P, 'sit', 1, 1, w);
|
|
128
|
+
mx(P, 'tailGround', 1, wH); mx(P, 'tailSide', inst.side * 1.3, wH);
|
|
129
|
+
mx(P, 'headRaise', 0.02, w); mx(P, 'groundW', 1, w); // (full ground fit: the rump rests on the ground)
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
// sternal: legs folded, belly down, head up
|
|
133
|
+
lie: {
|
|
134
|
+
fadeIn: 1.3, fadeOut: 1.0,
|
|
135
|
+
apply(P, w, inst) {
|
|
136
|
+
const o = inst.tune;
|
|
137
|
+
let wH = smooth(0.1, 0.9, w), wF = smooth(0, 0.8, w), fF = smooth(0, 0.6, w), fH = fF;
|
|
138
|
+
if (o.down || o.up) {
|
|
139
|
+
// which end goes down / gets up first (tune.down, tune.up: 'front' | 'hind'); horses kneel
|
|
140
|
+
// and rise forelegs first, cattle kneel first but rise hind first. Followed by a spring so a
|
|
141
|
+
// reversal half way through does not jump. (The spring lags the posture weight: the folded
|
|
142
|
+
// legs release behind the rising girdle, which a staging without the lag outran - the foreleg
|
|
143
|
+
// folded up into a Z and its elbow flipped over. The posture therefore ends, and is reported
|
|
144
|
+
// reached, only once these springs have settled: see stageSettled.)
|
|
145
|
+
const lead = inst.target > 0 ? o.down : o.up;
|
|
146
|
+
const a = inst.target > 0 ? smooth(0, 0.6, w) : smooth(0.35, 1, w), b = inst.target > 0 ? smooth(0.35, 1, w) : smooth(0, 0.65, w);
|
|
147
|
+
const tF = lead === 'front' ? a : lead === 'hind' ? b : wF, tH = lead === 'front' ? b : lead === 'hind' ? a : wH;
|
|
148
|
+
const dt = clamp(inst.t - (inst.tk ?? inst.t), 0, 0.1);
|
|
149
|
+
inst.tk = inst.t;
|
|
150
|
+
if (inst.kF === undefined) { inst.kF = tF; inst.kH = tH; inst.vF = 0; inst.vH = 0; }
|
|
151
|
+
const om = 9, st = (x, v, g) => { const a2 = om * om * (g - x) - 2 * om * v; return v + a2 * dt; };
|
|
152
|
+
inst.vF = st(inst.kF, inst.vF, tF); inst.kF += inst.vF * dt;
|
|
153
|
+
inst.vH = st(inst.kH, inst.vH, tH); inst.kH += inst.vH * dt;
|
|
154
|
+
// (held at the ends: a clamped weight keeps no speed, or it stuck there and then kicked)
|
|
155
|
+
if (inst.kF < 0 || inst.kF > 1) { inst.kF = clamp(inst.kF, 0, 1); inst.vF = 0; }
|
|
156
|
+
if (inst.kH < 0 || inst.kH > 1) { inst.kH = clamp(inst.kH, 0, 1); inst.vH = 0; }
|
|
157
|
+
wF = inst.kF; wH = inst.kH; fF = smooth(0, 0.8, wF); fH = smooth(0, 0.8, wH);
|
|
158
|
+
}
|
|
159
|
+
mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
|
|
160
|
+
mx(P, 'dropF', o.dropF ?? 1, wF); mx(P, 'dropH', o.dropH ?? 1, wH);
|
|
161
|
+
for (let i = 0; i < 4; i++) mxLeg(P, i, 'fold', 1, i < 2 ? fF : fH);
|
|
162
|
+
mx(P, 'groundW', 1, w);
|
|
163
|
+
mx(P, 'tailGround', 1, wH); mx(P, 'tailSide', inst.side * 1.0, wH);
|
|
164
|
+
mx(P, 'headRaise', -0.03, w); mx(P, 'breathRate', 0.8, w);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
// lie, then curl (or roll onto the side), head down, eyes closed, slow breathing
|
|
168
|
+
sleep: {
|
|
169
|
+
fadeIn: 1.3, fadeOut: 1.2,
|
|
170
|
+
apply(P, w, inst, L) {
|
|
171
|
+
POSTURES.lie.apply(P, w, inst, L);
|
|
172
|
+
const c = smooth(1.0, 3.0, inst.t) * w;
|
|
173
|
+
const side = inst.side;
|
|
174
|
+
const e = L.engine, cfg = e.cfg;
|
|
175
|
+
if (inst.tune.style === 'lateral') {
|
|
176
|
+
mx(P, 'roll', side * 1.35, c);
|
|
177
|
+
mxLegs(P, 'side', 1, 1, c); mxLegs(P, 'spread', 0.3, 0.3, c);
|
|
178
|
+
mxLegs(P, 'fold', 0, 0, c);
|
|
179
|
+
mx(P, 'bend', side * 0.15, c);
|
|
180
|
+
} else {
|
|
181
|
+
mx(P, 'roll', side * 0.35, c);
|
|
182
|
+
mx(P, 'bend', side * 0.9, c);
|
|
183
|
+
// the tail wraps tightly round the rump and runs forward along the flank on the inside of
|
|
184
|
+
// the curl (tailSide swept it out sideways from the rump instead)
|
|
185
|
+
mx(P, 'tailSide', 0, c); mx(P, 'tailCurl', side * 3.0, c);
|
|
186
|
+
}
|
|
187
|
+
// head resting on the ground beside the forepaws (turned toward the curl)
|
|
188
|
+
const fr = e.fr, s = cfg.k;
|
|
189
|
+
const lat = inst.tune.style === 'lateral';
|
|
190
|
+
const hp = tv().copy(fr.neckBase).addScaledVector(fr.F, cfg.neckLen * (lat ? 0.75 : 0.45)).addScaledVector(fr.Lf, side * cfg.neckLen * (lat ? 0.1 : 0.9));
|
|
191
|
+
// (curled: the head rests on the forepaws, a little above the ground: the neck slopes instead of
|
|
192
|
+
// hanging straight down from the withers with the skull levered 100 deg against it)
|
|
193
|
+
hp.y = e.terrainH(hp.x, hp.z) + (lat ? 0.085 : 0.12) * s;
|
|
194
|
+
P.headPos.lerp(hp, c / Math.max(1e-3, P.headW + c)); mx(P, 'headW', 1, c);
|
|
195
|
+
mx(P, 'headYaw', side * (lat ? 0.1 : 1.1), c); mx(P, 'headPitch', lat ? 0.2 : 0.35, c);
|
|
196
|
+
mx(P, 'headRoll', side * (inst.tune.style === 'lateral' ? 1.2 : 0.5), c); mx(P, 'headLimp', 0.3, c);
|
|
197
|
+
mx(P, 'eyelid', 1, smooth(1.5, 3.2, inst.t) * w);
|
|
198
|
+
mx(P, 'breathRate', 0.45, c); mx(P, 'breathAmp', 1.4, c);
|
|
199
|
+
mx(P, 'earFlat', 0.25, c); mx(P, 'earTwitch', 0.35, c); mx(P, 'tailIdle', 0.1, c);
|
|
200
|
+
mx(P, 'lookW', 0, c);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
// legs buckle, falls on its side, settles on the terrain; stays down until 'stand'
|
|
204
|
+
death: {
|
|
205
|
+
fadeIn: 0.25, fadeOut: 1.6,
|
|
206
|
+
apply(P, w, inst, L) {
|
|
207
|
+
const t = inst.t, side = inst.side, e = L.engine, cfg = e.cfg, s = cfg.k;
|
|
208
|
+
const buckle = smooth(0, 0.35, t);
|
|
209
|
+
const fall = clamp((t - 0.3) / 0.55, 0, 1);
|
|
210
|
+
const fallE = fall * fall; // accelerating topple
|
|
211
|
+
const settle = fall >= 1 ? Math.exp(-(t - 0.85) * 6) * Math.sin((t - 0.85) * 18) * 0.06 : 0;
|
|
212
|
+
// killed on the move: the body carries its momentum (it brakes like the living animal) and the
|
|
213
|
+
// legs keep striding under it while it buckles, folding once it is down (no dragged feet)
|
|
214
|
+
const mv = e.air.active ? 0 : smooth(0.08, 0.45, e.speed / cfg.sk) * (1 - smooth(0.3, 0.7, t));
|
|
215
|
+
mx(P, 'gaitW', 0, w * (1 - mv)); mx(P, 'legGait', 0, w * (1 - mv)); mx(P, 'bodyOmega', 1.6, w);
|
|
216
|
+
const kb = 1 - mv; // (the body does not buckle while the legs still stride under it)
|
|
217
|
+
mx(P, 'dropF', lerp(0.55 * buckle * kb, 1, fallE), w); mx(P, 'dropH', lerp(0.45 * buckle * kb, 1, fallE), w);
|
|
218
|
+
mx(P, 'roll', side * (1.42 * fallE - settle), w);
|
|
219
|
+
mx(P, 'groundW', fallE, w);
|
|
220
|
+
mxLegs(P, 'fold', 0.6 * buckle * (1 - fallE), 0.6 * buckle * (1 - fallE), w);
|
|
221
|
+
mxLegs(P, 'side', fallE, fallE, w); mxLegs(P, 'spread', smooth(0.8, 1.6, t), smooth(0.8, 1.6, t), w);
|
|
222
|
+
// head and neck lie on the terrain
|
|
223
|
+
const fr = e.fr;
|
|
224
|
+
const hp = tv().copy(fr.neckBase).addScaledVector(fr.F, cfg.neckLen * 0.8);
|
|
225
|
+
hp.y = e.terrainH(hp.x, hp.z) + 0.092 * s; // occiput height with the skull on its side (half-width ~7 cm + cheek)
|
|
226
|
+
const hw = smooth(0.35, 1.0, t);
|
|
227
|
+
P.headPos.lerp(hp, hw / Math.max(1e-3, P.headW + hw)); mx(P, 'headW', 1, hw * w);
|
|
228
|
+
mx(P, 'headRoll', side * 1.3, hw * w); mx(P, 'headPitch', 0.15, hw * w); mx(P, 'headLimp', 0.5, hw * w);
|
|
229
|
+
mx(P, 'headOmega', 1.8, w);
|
|
230
|
+
mx(P, 'jaw', 0.1, hw * w); mx(P, 'eyelid', 0.55, smooth(0.2, 1.2, t) * w);
|
|
231
|
+
mx(P, 'tailGround', 1, w); mx(P, 'tailIdle', 0, w); mx(P, 'tailSide', side * 0.6, w); mx(P, 'tailStiff', 0.5, w);
|
|
232
|
+
mx(P, 'breathAmp', 0, w); mx(P, 'breathRate', 0, w);
|
|
233
|
+
mx(P, 'earFlat', 0.4, w); mx(P, 'earTwitch', 0, w); mx(P, 'lookW', 0, w);
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// ------------------------------------------------------------------------------------------------
|
|
239
|
+
// One-shot actions
|
|
240
|
+
// ------------------------------------------------------------------------------------------------
|
|
241
|
+
function mouthGroundTarget(e, out, pitchA, levelFn, ahead) {
|
|
242
|
+
// occiput position that puts the mouth on the ground (or water) ahead of the forefeet
|
|
243
|
+
const cfg = e.cfg, fr = e.fr;
|
|
244
|
+
const g = tv();
|
|
245
|
+
const FL = e.legs[0], FR = e.legs[1];
|
|
246
|
+
// head frame: yaw = heading, pitch = pitchA (nose down); the lower of mouth and nose touches
|
|
247
|
+
const cp = Math.cos(pitchA), sp = Math.sin(pitchA);
|
|
248
|
+
const qm = cfg.mouthLocal, qn = cfg.noseLocal;
|
|
249
|
+
const ym = qm.y * cp - qm.z * sp, yn = qn.y * cp - qn.z * sp;
|
|
250
|
+
const zm = qm.y * sp + qm.z * cp, zn = qn.y * sp + qn.z * cp;
|
|
251
|
+
// the lower of mouth and nose touches; blended over 1 cm of height difference so the target does
|
|
252
|
+
// not jump from one point to the other as the head pitches (tearing jerks)
|
|
253
|
+
const k = smooth(-0.01 * cfg.k, 0.01 * cfg.k, ym - yn); // 0: mouth lower, 1: nose lower
|
|
254
|
+
const x = lerp(qm.x, qn.x, k), y = lerp(ym, yn, k), z = lerp(zm, zn, k);
|
|
255
|
+
// the mouth `ahead` leg lengths ahead of the forefeet (0.5; nearer, the neck had to fold back under
|
|
256
|
+
// itself to reach it and the head bent 80-140 deg against the neck; a short neck on long legs
|
|
257
|
+
// (wolf) needs more, or the neck hangs vertically with the head levered against it)
|
|
258
|
+
g.copy(FL.plant).add(FR.plant).multiplyScalar(0.5).addScaledVector(fr.F, (ahead ?? 0.5) * cfg.legLen);
|
|
259
|
+
g.y = levelFn(g.x, g.z);
|
|
260
|
+
const sh = Math.sin(e.heading), ch = Math.cos(e.heading);
|
|
261
|
+
out.set(g.x - (x * ch + z * sh), g.y - y + 0.015 * cfg.k, g.z - (-x * sh + z * ch));
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const ONESHOTS = {
|
|
266
|
+
jump: {
|
|
267
|
+
start(inst, L) {
|
|
268
|
+
const e = L.engine, cfg = e.cfg, o = inst.tune;
|
|
269
|
+
inst.run = smooth(1.5, 5, e.speed / cfg.sk);
|
|
270
|
+
inst.tc = lerp(0.3, 0.1, inst.run) * cfg.sk;
|
|
271
|
+
const h = (o.height ?? 0.6 * cfg.unit) * cfg.s * lerp(1, 0.7, inst.run);
|
|
272
|
+
inst.vy = Math.sqrt(2 * 9.81 * h);
|
|
273
|
+
let T = (2 * inst.vy) / 9.81;
|
|
274
|
+
let dist = (o.distance ?? 2.5 * cfg.unit) * cfg.s;
|
|
275
|
+
const target = inst.opts.target;
|
|
276
|
+
if (target) dist = clamp(Math.hypot(target.x - e.pos.x, target.z - e.pos.z), 0.3 * cfg.k, dist * 2);
|
|
277
|
+
// (a target the run-up would overshoot: a lower, shorter flight, down to 0.35x the height)
|
|
278
|
+
if (target && e.speed * T > dist) { T = Math.max(Math.sqrt(0.35) * T, dist / e.speed); inst.vy = 4.905 * T; }
|
|
279
|
+
// the flight covers at least the species' jump distance (or the target's): a run-up slower than
|
|
280
|
+
// that is sped up in the push-off, a faster one carries its speed (a longer, flatter running
|
|
281
|
+
// jump). (It was lerp(distance / T, speed, run): from a trot the jump fell short of a standing
|
|
282
|
+
// jump, and from a canter on the distance was ignored.)
|
|
283
|
+
inst.vh = Math.max(e.speed, dist / T);
|
|
284
|
+
// push-off: the crouched body extends and gains its launch speed over tp before it leaves the
|
|
285
|
+
// ground (an instant launch from the crouch yanked every leg in one frame)
|
|
286
|
+
inst.tp = lerp(0.14, 0.08, inst.run) * cfg.sk;
|
|
287
|
+
inst.v0 = e.speed;
|
|
288
|
+
inst.phase = 'crouch';
|
|
289
|
+
inst.dur = Infinity;
|
|
290
|
+
},
|
|
291
|
+
update(inst, dt, L) {
|
|
292
|
+
const e = L.engine;
|
|
293
|
+
if (inst.phase === 'crouch' && inst.t >= inst.tc) inst.phase = 'push';
|
|
294
|
+
if (inst.phase === 'push') {
|
|
295
|
+
const x = clamp((inst.t - inst.tc) / inst.tp, 0, 1);
|
|
296
|
+
e.speedOverride = lerp(inst.v0, inst.vh, x * x * (3 - 2 * x));
|
|
297
|
+
if (x >= 1) { e.speedOverride = null; inst.phase = 'air'; inst.tAir = inst.t; e.takeOff(inst.vy, inst.vh); }
|
|
298
|
+
}
|
|
299
|
+
if (inst.phase === 'land' && inst.t - inst.tLand > 0.45) return true;
|
|
300
|
+
if (inst.phase === 'air' && inst.t - inst.tAir > 4) { e.touchDown(); }
|
|
301
|
+
return false;
|
|
302
|
+
},
|
|
303
|
+
onLand(inst) { inst.phase = 'land'; inst.tLand = inst.t; },
|
|
304
|
+
apply(P, w, inst, L) {
|
|
305
|
+
const e = L.engine;
|
|
306
|
+
if (inst.phase === 'crouch' || inst.phase === 'push') {
|
|
307
|
+
const c = smooth(0, inst.tc, inst.t) * (1 - inst.run * 0.6);
|
|
308
|
+
// push: from the crouch to an extended body (dropF / dropH < 0 raise the girdles)
|
|
309
|
+
const x = inst.phase === 'push' ? smooth(0, 1, (inst.t - inst.tc) / inst.tp) : 0;
|
|
310
|
+
mx(P, 'dropF', lerp(0.35, -0.1, x), c); mx(P, 'dropH', lerp(0.45, -0.12, x), c); mx(P, 'pitch', lerp(-0.05, 0.12, x), c);
|
|
311
|
+
mx(P, 'bodyOmega', lerp(1, 2.2, x), c);
|
|
312
|
+
// standing: the planted feet stay planted while it pushes off (holdFeet: no catch-up steps
|
|
313
|
+
// either, the legs extend over them); from a run the gait steps on until take-off (held
|
|
314
|
+
// at c (1 - run), a canter's push-off dragged a bear's planted forefoot 45 cm behind)
|
|
315
|
+
if (inst.phase === 'push') { const hold = c * (1 - smooth(0, 0.3, inst.run)); mx(P, 'legGait', 0, hold); mx(P, 'holdFeet', 1, hold); }
|
|
316
|
+
mx(P, 'earFlat', 0.3, c);
|
|
317
|
+
} else if (inst.phase === 'air') {
|
|
318
|
+
const T = (2 * inst.vy) / 9.81, x = clamp((inst.t - inst.tAir) / T, 0, 1);
|
|
319
|
+
const k0 = smooth(0, 0.12, inst.t - inst.tAir);
|
|
320
|
+
// (legGait continues from the push-off, where it was already held down)
|
|
321
|
+
mx(P, 'gaitW', 0, k0); mx(P, 'legGait', 0, Math.max(k0, (1 - inst.run * 0.6) * (1 - smooth(0, 0.3, inst.run))));
|
|
322
|
+
const ext = x < 0.25 ? 1 - smooth(0, 0.25, x) * 0.9 : x < 0.65 ? 0.1 : 0.1 + smooth(0.65, 1, x) * 0.7;
|
|
323
|
+
mxLegs(P, 'tuck', 1, 1, k0);
|
|
324
|
+
for (const l of P.legs) l.extend = ext;
|
|
325
|
+
// body pitch in flight, continuous with the push-off's (0.12 at take-off) and held into the
|
|
326
|
+
// landing (inst.pitchAir): a step in P.pitch moves the girdle-height correction of the body
|
|
327
|
+
// frame, which dropped a horse 3 cm in the touchdown frame (and lifted it at take-off)
|
|
328
|
+
const pf = lerp(0.22, -0.18, smooth(0.1, 0.95, x)) * (1 - inst.run * 0.5);
|
|
329
|
+
inst.pitchAir = lerp(0.12 * (1 - inst.run * 0.6), pf, smooth(0, 0.2, x));
|
|
330
|
+
mx(P, 'pitch', inst.pitchAir, 1);
|
|
331
|
+
mx(P, 'tailLift', 0.25, 1); mx(P, 'headPitch', lerp(-0.1, 0.15, x), 1);
|
|
332
|
+
} else {
|
|
333
|
+
const x = (inst.t - inst.tLand) / 0.45;
|
|
334
|
+
const a = Math.sin(Math.PI * Math.min(1, x * 1.6)) * (1 - x);
|
|
335
|
+
const ld = inst.tune.land || [0.3, 0.25]; // landing crouch [front, hind] (jump tuning: long-legged runners land straighter)
|
|
336
|
+
mx(P, 'dropF', ld[0], a); mx(P, 'dropH', ld[1], a);
|
|
337
|
+
const k = 1 - smooth(0, 0.35, x);
|
|
338
|
+
mxLegs(P, 'tuck', 1, 1, k);
|
|
339
|
+
for (const l of P.legs) l.extend = lerp(l.extend, 0.8, k);
|
|
340
|
+
mx(P, 'legGait', 0, k); mx(P, 'gaitW', 0, k);
|
|
341
|
+
// (the flight's pitch eases out over the landing)
|
|
342
|
+
mx(P, 'pitch', (inst.pitchAir || 0) * (1 - smooth(0, 0.6, x)), 1);
|
|
343
|
+
}
|
|
344
|
+
void e;
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
attack: {
|
|
349
|
+
start(inst, L) {
|
|
350
|
+
const e = L.engine, o = inst.tune;
|
|
351
|
+
inst.style = o.style || 'bite-lunge';
|
|
352
|
+
inst.kick = inst.opts.kick || o.kick || 'double'; // play('attack', { kick: 'rear' }) picks per call
|
|
353
|
+
inst.run = smooth(1.5, 4, e.speed / e.cfg.sk);
|
|
354
|
+
if (inst.style === 'pounce' && inst.run > 0.5) inst.style = 'bite-lunge';
|
|
355
|
+
inst.dur = { 'bite-lunge': 0.95, pounce: 1.8, charge: 1.1, headbutt: 1.1, kick: inst.kick === 'rear' ? 1.8 : 1.2 }[inst.style] || 1;
|
|
356
|
+
inst.hitAt = { 'bite-lunge': 0.5, pounce: 0, charge: 0.55, headbutt: 0.55, kick: inst.kick === 'rear' ? 1.05 : 0.5 }[inst.style];
|
|
357
|
+
inst.hit = false;
|
|
358
|
+
inst.target = inst.opts.target ? inst.opts.target.clone() : null;
|
|
359
|
+
if (inst.style === 'pounce') inst.jumped = false;
|
|
360
|
+
},
|
|
361
|
+
update(inst, dt, L) {
|
|
362
|
+
const e = L.engine, cfg = e.cfg;
|
|
363
|
+
if (inst.style === 'pounce' && !inst.jumped && inst.t >= 0.6) {
|
|
364
|
+
inst.jumped = true;
|
|
365
|
+
const h = 0.3 * cfg.k, vy = Math.sqrt(2 * 9.81 * h), T = 2 * vy / 9.81;
|
|
366
|
+
let dist = 1.6 * cfg.k;
|
|
367
|
+
if (inst.target) dist = clamp(Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z) - 0.35 * cfg.legLen, 0.4 * cfg.k, 3.5 * cfg.k);
|
|
368
|
+
inst.T = T;
|
|
369
|
+
e.takeOff(vy, Math.max(e.speed, dist / T));
|
|
370
|
+
}
|
|
371
|
+
if (inst.style === 'pounce' && inst.landT !== undefined && inst.t - inst.landT > 0.7) return true;
|
|
372
|
+
if (inst.style === 'pounce' && inst.jumped && inst.landT === undefined && inst.t > 4) e.touchDown();
|
|
373
|
+
if (!inst.hit && inst.hitAt && inst.t >= inst.hitAt) this.strike(inst, L);
|
|
374
|
+
return inst.style !== 'pounce' && inst.t >= inst.dur;
|
|
375
|
+
},
|
|
376
|
+
onLand(inst, L) {
|
|
377
|
+
if (inst.style !== 'pounce') return;
|
|
378
|
+
inst.landT = inst.t;
|
|
379
|
+
L.engine.speed *= 0.25; // pins the prey: no run-out
|
|
380
|
+
this.strike(inst, L);
|
|
381
|
+
},
|
|
382
|
+
strike(inst, L) {
|
|
383
|
+
const e = L.engine;
|
|
384
|
+
inst.hit = true;
|
|
385
|
+
let p;
|
|
386
|
+
if (inst.style === 'kick') p = inst.kick === 'rear' ? e.legs[0].contact.clone() : e.legs[2].contact.clone().lerp(e.legs[3].contact, 0.5);
|
|
387
|
+
else p = new THREE.Vector3().copy(e.cfg.mouthLocal).applyQuaternion(e.headPose.quaternion).add(e.headPose.position);
|
|
388
|
+
const dir = inst.target ? inst.target.clone().sub(e.headPose.position).setY(0).normalize() : e.forward(e.heading, new THREE.Vector3());
|
|
389
|
+
if (inst.style === 'kick' && inst.kick !== 'rear') dir.negate();
|
|
390
|
+
e.emit('attackHit', { position: p, direction: dir, style: inst.style });
|
|
391
|
+
},
|
|
392
|
+
apply(P, w, inst, L) {
|
|
393
|
+
const e = L.engine, cfg = e.cfg, fr = e.fr, s = cfg.k, t = inst.t, run = inst.run, still = 1 - run;
|
|
394
|
+
const lookAt = inst.target;
|
|
395
|
+
if (lookAt) { P.look = lookAt; mx(P, 'lookW', 1, w); }
|
|
396
|
+
mx(P, 'earFlat', 1, w * smooth(0, 0.15, t)); mx(P, 'earTwitch', 0, w);
|
|
397
|
+
const reachF = (x, amount, fwd, up) => {
|
|
398
|
+
// forepaws strike ahead (toward the target when given)
|
|
399
|
+
for (let i = 0; i < 2; i++) {
|
|
400
|
+
const leg = e.legs[i], Ld = leg.def, pl = P.legs[i];
|
|
401
|
+
const tp = tv();
|
|
402
|
+
if (lookAt) tp.copy(lookAt).addScaledVector(fr.Lf, Ld.s * Ld.cx * 1.2);
|
|
403
|
+
else e.bodyToWorld(Ld.cx * Ld.s * 1.2, 0, cfg.zS + fwd * Ld.len, e.heading, e.pos, tp);
|
|
404
|
+
tp.y = Math.max(tp.y, e.terrainH(tp.x, tp.z)) + up * Ld.len;
|
|
405
|
+
const k = amount * x;
|
|
406
|
+
pl.target.lerp(tp, k / Math.max(1e-3, pl.reach + k));
|
|
407
|
+
mxLeg(P, i, 'reach', 1, k);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
if (inst.style === 'bite-lunge') {
|
|
411
|
+
const crouch = smooth(0, 0.3, t) * (1 - smooth(0.32, 0.45, t)) * still;
|
|
412
|
+
const lunge = smooth(0.3, 0.48, t) * (1 - smooth(0.6, 0.95, t));
|
|
413
|
+
mx(P, 'dropF', 0.35, crouch * w); mx(P, 'dropH', 0.3, crouch * w); mx(P, 'fwd', -0.06, crouch * w);
|
|
414
|
+
mx(P, 'fwd', 0.22 * (1 - run * 0.5), lunge * w); mx(P, 'neckReach', 0.1, lunge * w); mx(P, 'headRaise', -0.03, lunge * w);
|
|
415
|
+
mx(P, 'jaw', 0.65, w * smooth(0.25, 0.42, t) * (1 - smooth(0.46, 0.52, t)));
|
|
416
|
+
mx(P, 'jawOmega', 30, w);
|
|
417
|
+
mx(P, 'gaitW', 1 - 0.7 * still, w * (crouch + lunge > 0 ? 1 : 0));
|
|
418
|
+
// (tune.paws: false for canids, whose forefeet stay planted while the head and neck drive the
|
|
419
|
+
// bite; felids and rodents reach and grab with the forepaws)
|
|
420
|
+
if (inst.tune.paws === false) {
|
|
421
|
+
const snap = smooth(0.55, 0.7, t) * (1 - smooth(0.75, 0.95, t));
|
|
422
|
+
mx(P, 'neckReach', 0.17, lunge * w); mx(P, 'headRaise', -0.07, lunge * w); mx(P, 'headPitch', 0.18, lunge * w);
|
|
423
|
+
mx(P, 'dropF', 0.18, lunge * w * still); mx(P, 'fwd', -0.05, snap * w * still);
|
|
424
|
+
} else if (still > 0.2) reachF(lunge * w, 0.8 * still, 0.55, 0.35);
|
|
425
|
+
} else if (inst.style === 'pounce') {
|
|
426
|
+
const crouch = smooth(0, 0.35, t) * (inst.jumped ? 0 : 1);
|
|
427
|
+
const wig = inst.jumped ? 0 : Math.sin(t * 28) * smooth(0.25, 0.4, t) * 0.02;
|
|
428
|
+
mx(P, 'dropF', 0.55, crouch * w); mx(P, 'dropH', 0.4, crouch * w); mx(P, 'side', wig, w);
|
|
429
|
+
mx(P, 'headRaise', -0.04, crouch * w); mx(P, 'tailLift', -0.2, crouch * w);
|
|
430
|
+
if (inst.jumped && inst.landT === undefined) {
|
|
431
|
+
const x = clamp((t - 0.6) / (inst.T || 0.5), 0, 1);
|
|
432
|
+
const k0 = smooth(0, 0.12, t - 0.6) * w;
|
|
433
|
+
mx(P, 'gaitW', 0, k0); mx(P, 'legGait', 0, k0);
|
|
434
|
+
mxLeg(P, 2, 'tuck', 1, k0); mxLeg(P, 3, 'tuck', 1, k0);
|
|
435
|
+
P.legs[2].extend = P.legs[3].extend = 1 - smooth(0, 0.4, x);
|
|
436
|
+
reachF(k0, 1, 0.6, lerp(0.35, 0.05, x));
|
|
437
|
+
// (flight pitch, held into the landing: see the jump)
|
|
438
|
+
inst.pitchAir = lerp(0.2, -0.15, x);
|
|
439
|
+
mx(P, 'pitch', inst.pitchAir, w); mx(P, 'jaw', 0.5, w * smooth(0.3, 0.8, x));
|
|
440
|
+
} else if (inst.landT !== undefined) {
|
|
441
|
+
const x = (t - inst.landT) / 0.7;
|
|
442
|
+
const k = 1 - smooth(0.3, 1, x);
|
|
443
|
+
mx(P, 'pitch', (inst.pitchAir || 0) * (1 - smooth(0, 0.4, x)), w);
|
|
444
|
+
reachF(k * w, 1, 0.55, 0);
|
|
445
|
+
mx(P, 'dropF', 0.5, k * w); mx(P, 'dropH', 0.2, k * w);
|
|
446
|
+
mx(P, 'jaw', 0.55 * (1 - smooth(0, 0.15, x)), w); mx(P, 'jawOmega', 30, w);
|
|
447
|
+
mx(P, 'headRaise', -0.06, k * w); mx(P, 'neckReach', 0.06, k * w);
|
|
448
|
+
}
|
|
449
|
+
} else if (inst.style === 'charge' || inst.style === 'headbutt') {
|
|
450
|
+
const low = smooth(0, 0.35, t) * (1 - smooth(0.7, 1.1, t));
|
|
451
|
+
const lunge = smooth(0.35, 0.55, t) * (1 - smooth(0.6, 1.0, t));
|
|
452
|
+
mx(P, 'headPitch', 0.7, low * w); mx(P, 'headRaise', -0.12, low * w); mx(P, 'dropF', 0.2, low * w * still);
|
|
453
|
+
mx(P, 'fwd', 0.3, lunge * w); mx(P, 'neckReach', 0.08, lunge * w);
|
|
454
|
+
mx(P, 'fwd', -0.08, low * (1 - lunge) * w * still);
|
|
455
|
+
} else if (inst.style === 'kick') {
|
|
456
|
+
if (inst.kick === 'rear') {
|
|
457
|
+
const up = smooth(0.1, 0.7, t) * (1 - smooth(1.2, 1.75, t));
|
|
458
|
+
const strike = smooth(0.7, 1.05, t) * (1 - smooth(1.15, 1.65, t));
|
|
459
|
+
mx(P, 'dropF', -1.4, up * w); mx(P, 'dropH', 0.25, up * w); mx(P, 'legGait', 0, up * w); mx(P, 'gaitW', 0, up * w);
|
|
460
|
+
mxLegs(P, 'tuck', 1, 0, up * w);
|
|
461
|
+
for (let i = 0; i < 2; i++) P.legs[i].extend = lerp(0.1, 1, strike);
|
|
462
|
+
mx(P, 'headRaise', 0.1, up * w);
|
|
463
|
+
} else {
|
|
464
|
+
const buck = smooth(0.15, 0.4, t) * (1 - smooth(0.6, 1.1, t));
|
|
465
|
+
const kick = smooth(0.35, 0.5, t) * (1 - smooth(0.6, 0.9, t));
|
|
466
|
+
mx(P, 'dropF', 0.15, buck * w); mx(P, 'dropH', -0.25, buck * w); mx(P, 'headPitch', 0.4, buck * w);
|
|
467
|
+
for (let i = 2; i < 4; i++) {
|
|
468
|
+
const leg = e.legs[i], Ld = leg.def, pl = P.legs[i];
|
|
469
|
+
// (tune.kickReach: [back, up] in leg lengths behind the hip / above the ground)
|
|
470
|
+
const kr = inst.tune.kickReach || KICK_REACH;
|
|
471
|
+
const tp = e.bodyToWorld(Ld.cx * Ld.s, 0, cfg.zH - Ld.len * kr[0], e.heading, e.pos, tv());
|
|
472
|
+
tp.y = e.terrainH(tp.x, tp.z) + Ld.len * kr[1];
|
|
473
|
+
pl.target.copy(tp);
|
|
474
|
+
mxLeg(P, i, 'reach', 1, kick * w);
|
|
475
|
+
}
|
|
476
|
+
mx(P, 'legGait', 0.4, buck * w);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
void s;
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
hit: {
|
|
484
|
+
start(inst, L) {
|
|
485
|
+
const e = L.engine;
|
|
486
|
+
const d = inst.opts.direction ? inst.opts.direction.clone().setY(0) : e.forward(e.heading, new THREE.Vector3()).negate();
|
|
487
|
+
if (d.lengthSq() < 1e-6) d.set(0, 0, -1);
|
|
488
|
+
d.normalize();
|
|
489
|
+
inst.dir = d;
|
|
490
|
+
inst.dur = 0.75;
|
|
491
|
+
// body-local components: + left, + forward
|
|
492
|
+
inst.lx = d.dot(e.left(e.heading, new THREE.Vector3()));
|
|
493
|
+
inst.lz = d.dot(e.forward(e.heading, new THREE.Vector3()));
|
|
494
|
+
e.push.addScaledVector(d, 0.9 * e.cfg.sk * (inst.opts.strength ?? 1));
|
|
495
|
+
},
|
|
496
|
+
update(inst) { return inst.t >= inst.dur; },
|
|
497
|
+
apply(P, w, inst) {
|
|
498
|
+
const t = inst.t;
|
|
499
|
+
// recoil: peak at ~0.12 s, a held 35 % until 0.3 s, then decays (continuous: the old
|
|
500
|
+
// sin-then-exp form jumped from 0 to 0.35 at t = 0.3 s)
|
|
501
|
+
const f = smooth(0, 0.12, t) * (t < 0.12 ? 1 : 0.35 + 0.65 * Math.exp(-(t - 0.12) / 0.06)) * Math.exp(-Math.max(0, t - 0.3) * 6);
|
|
502
|
+
const k = f * w;
|
|
503
|
+
mx(P, 'roll', inst.lx * 0.16, k); mx(P, 'bend', -inst.lx * 0.25, k);
|
|
504
|
+
mx(P, 'pitch', inst.lz * 0.08, k); mx(P, 'dropF', 0.18, k); mx(P, 'dropH', 0.12, k);
|
|
505
|
+
P.headLocal.set(inst.lx * 0.06, 0.02, inst.lz * 0.06 - 0.03); mx(P, 'headLocalW', 1, k);
|
|
506
|
+
mx(P, 'headYaw', -inst.lx * 0.35, k); mx(P, 'headPitch', -0.15, k);
|
|
507
|
+
mx(P, 'earFlat', 1, w * (1 - smooth(0.4, 0.75, t))); mx(P, 'eyelid', 0.7, k);
|
|
508
|
+
mx(P, 'jaw', 0.18, k); mx(P, 'tailLift', -0.15, k);
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
eat: {
|
|
513
|
+
start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 6); inst.fade = 0.8; },
|
|
514
|
+
update(inst) { return inst.t >= inst.dur; },
|
|
515
|
+
apply(P, w, inst, L) { applyGround(P, w, inst, L, 'eat'); },
|
|
516
|
+
},
|
|
517
|
+
drink: {
|
|
518
|
+
start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 5); inst.fade = 0.8; },
|
|
519
|
+
update(inst) { return inst.t >= inst.dur; },
|
|
520
|
+
apply(P, w, inst, L) { applyGround(P, w, inst, L, 'drink'); },
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
function applyGround(P, w, inst, L, kind) {
|
|
525
|
+
const e = L.engine, t = inst.t, o = inst.tune;
|
|
526
|
+
const water = e.water;
|
|
527
|
+
const level = kind === 'drink' && water ? (x, z) => { const wl = water(x, z); const g = e.terrainH(x, z); return Number.isFinite(wl) ? Math.max(g, wl) : g; } : (x, z) => e.terrainH(x, z);
|
|
528
|
+
let pitchA = o.pitch ?? 1.3; // nose down, roughly in line with the lowered neck (tune.pitch: rad from the head's bind pose)
|
|
529
|
+
let bob = 0, jaw = 0, yaw = 0;
|
|
530
|
+
if (kind === 'eat') {
|
|
531
|
+
if ((o.style || 'tear') === 'graze') {
|
|
532
|
+
// grazing: bite, a small pull, chew with the head down
|
|
533
|
+
const c = (t * 0.9) % 1;
|
|
534
|
+
bob = -0.02 * Math.sin(Math.PI * smooth(0, 0.25, c));
|
|
535
|
+
jaw = 0.12 + 0.1 * Math.sin(t * 9.5);
|
|
536
|
+
yaw = 0.12 * Math.sin(t * 0.7);
|
|
537
|
+
} else {
|
|
538
|
+
// predators: tearing jerks (pull up and back, twist), jaw clamps, then reset
|
|
539
|
+
const c = (t * 0.75) % 1;
|
|
540
|
+
const jerk = smooth(0.35, 0.5, c) * (1 - smooth(0.6, 0.95, c));
|
|
541
|
+
bob = 0.07 * jerk;
|
|
542
|
+
pitchA -= 0.35 * jerk;
|
|
543
|
+
jaw = 0.05 + 0.3 * smooth(0, 0.15, c) * (1 - smooth(0.25, 0.4, c));
|
|
544
|
+
yaw = 0.25 * jerk * Math.sin(Math.floor(t * 0.75) * 2.3);
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
// lapping
|
|
548
|
+
jaw = 0.1 + 0.08 * Math.max(0, Math.sin(t * 3 * 6.283));
|
|
549
|
+
bob = 0.006 * Math.sin(t * 3 * 6.283);
|
|
550
|
+
pitchA = o.pitch ?? 1.3;
|
|
551
|
+
}
|
|
552
|
+
const hp = mouthGroundTarget(e, tv(), pitchA, level, o.ahead);
|
|
553
|
+
hp.y += bob * e.cfg.k;
|
|
554
|
+
P.headPos.copy(hp);
|
|
555
|
+
const k = w;
|
|
556
|
+
mx(P, 'headW', 1, k); mx(P, 'headReach', 1, k); mx(P, 'headPitch', pitchA, k); mx(P, 'headYaw', yaw, k);
|
|
557
|
+
mx(P, 'lookW', 0, k); mx(P, 'headOmega', 2.2, k);
|
|
558
|
+
mx(P, 'jaw', jaw, k); mx(P, 'jawOmega', 25, k);
|
|
559
|
+
mx(P, 'gaitW', 0, k);
|
|
560
|
+
mx(P, 'dropF', o.dropF ?? 0.1, k); mx(P, 'dropH', o.dropH ?? 0, k);
|
|
561
|
+
mx(P, 'earTwitch', 1.5, k);
|
|
562
|
+
mx(P, 'speedScale', 0, k);
|
|
563
|
+
void L;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// in-place filter (no per-frame allocation)
|
|
567
|
+
function compact(arr, keep) {
|
|
568
|
+
let j = 0;
|
|
569
|
+
for (let i = 0; i < arr.length; i++) if (keep(arr[i])) arr[j++] = arr[i];
|
|
570
|
+
arr.length = j;
|
|
571
|
+
}
|
|
572
|
+
// a staged kneel (POSTURES.lie with tune.down / up) has settled when its fore / hind springs have
|
|
573
|
+
// reached the posture's end (1 lying, 0 standing) and stopped: the posture ends, and is reported
|
|
574
|
+
// reached, only then (released earlier, the springs' last few per cent of the lying drop and of the
|
|
575
|
+
// folded legs vanished in one frame: a pop of the forelegs as a cow finished getting up)
|
|
576
|
+
const stageSettled = (p) => p.kF === undefined || ((p.target > 0 ? 2 - p.kF - p.kH : p.kF + p.kH) < 2e-3 && Math.abs(p.vF) + Math.abs(p.vH) < 2e-2);
|
|
577
|
+
const keepPosture = (p) => p.target > 0 || p.w > 0 || !stageSettled(p);
|
|
578
|
+
const keepOneshot = (a) => !(a.stopping && a.w <= 0);
|
|
579
|
+
const isHolding = (p) => p.target > 0 || p.w > 0.02;
|
|
580
|
+
const isRunning = (a) => !a.stopping;
|
|
581
|
+
const isTargeted = (p) => p.target > 0;
|
|
582
|
+
const isBusy = (a) => !a.stopping && a.name !== 'hit';
|
|
583
|
+
const isAirborneAction = (a) => a.name === 'jump' || a.name === 'attack' || !!a.airborne;
|
|
584
|
+
const isDead = (p) => p.name === 'death' && p.target > 0;
|
|
585
|
+
|
|
586
|
+
// ------------------------------------------------------------------------------------------------
|
|
587
|
+
export class ActionLayer {
|
|
588
|
+
constructor(engine) {
|
|
589
|
+
this.engine = engine;
|
|
590
|
+
this.tuning = engine.cfg.actions || {};
|
|
591
|
+
this.names = ['idle', 'jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand'];
|
|
592
|
+
// species actions (species.motion.hooks.actions): { name: { kind: 'oneshot' | 'posture', airborne?,
|
|
593
|
+
// needsStand?, fade?, start, update, apply, onLand } }; they may replace built-ins of the same name
|
|
594
|
+
this.hooks = engine.hooks || {};
|
|
595
|
+
this.oneshotDefs = { ...ONESHOTS };
|
|
596
|
+
this.postureDefs = { ...POSTURES };
|
|
597
|
+
for (const [n, d] of Object.entries(this.hooks.actions || {})) {
|
|
598
|
+
if (d.kind === 'posture') this.postureDefs[n] = d; else this.oneshotDefs[n] = d;
|
|
599
|
+
if (!this.names.includes(n)) this.names.push(n);
|
|
600
|
+
}
|
|
601
|
+
this.postures = []; // { name, t, w, target, fadeIn, fadeOut, resolve, side }
|
|
602
|
+
this.oneshots = []; // { name, t, dur, w, stopping, resolve }
|
|
603
|
+
this.posture = 'stand';
|
|
604
|
+
this.standReq = null;
|
|
605
|
+
this.idle = { t: 0, lookT: 0, look: new THREE.Vector3(), hasLook: false, stepT: 4, w: 0, shiftPh: 0 };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
holdsStill() {
|
|
609
|
+
return this.postures.some(isHolding);
|
|
610
|
+
}
|
|
611
|
+
dead() { return this.postures.some(isDead); }
|
|
612
|
+
// automatic stand-up (the animal is asked to move): never revives a dead animal
|
|
613
|
+
requestStand() { if (!this.standReq && !this.dead()) this.play('stand'); }
|
|
614
|
+
// a one-shot committed to the air (between push-off and touchdown): not interrupted, not stopped
|
|
615
|
+
committed(a) {
|
|
616
|
+
const e = this.engine;
|
|
617
|
+
return !a.stopping && ((e.air.active && isAirborneAction(a)) || (a.name === 'jump' && a.phase === 'push'));
|
|
618
|
+
}
|
|
619
|
+
// end a one-shot: it fades out, then its promise resolves with the reason
|
|
620
|
+
end(a, reason) {
|
|
621
|
+
if (a.stopping) return;
|
|
622
|
+
a.stopping = true; a.reason = reason;
|
|
623
|
+
this.oneshotDefs[a.name]?.end?.(a, this);
|
|
624
|
+
}
|
|
625
|
+
current() {
|
|
626
|
+
const o = this.oneshots.find(isRunning);
|
|
627
|
+
if (o) return o.name;
|
|
628
|
+
const p = this.postures.find(isTargeted);
|
|
629
|
+
return p ? p.name : this.standReq ? 'stand' : null;
|
|
630
|
+
}
|
|
631
|
+
clear() {
|
|
632
|
+
for (const a of [...this.postures, ...this.oneshots]) { a.resolve?.('stopped'); a.resolveIn?.('stopped'); }
|
|
633
|
+
this.postures.length = 0; this.oneshots.length = 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
play(name, opts = {}) {
|
|
637
|
+
const e = this.engine;
|
|
638
|
+
if (name === 'idle') name = 'stand';
|
|
639
|
+
if (name === 'stand') {
|
|
640
|
+
if (!this.postures.length) return Promise.resolve('done');
|
|
641
|
+
for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
|
|
642
|
+
e.emit('actionStart', { name: 'stand' });
|
|
643
|
+
if (this.standReq) return this.standReq.promise;
|
|
644
|
+
let resolve;
|
|
645
|
+
const promise = new Promise((r) => { resolve = r; });
|
|
646
|
+
this.standReq = { promise, resolve };
|
|
647
|
+
return promise;
|
|
648
|
+
}
|
|
649
|
+
if (this.postureDefs[name]) {
|
|
650
|
+
const cur = this.postures.find((p) => p.target > 0);
|
|
651
|
+
if (cur && cur.name === 'death' && name !== 'death') return Promise.resolve('refused');
|
|
652
|
+
if (cur && cur.name === name) return cur.promiseIn;
|
|
653
|
+
// (in the air only death is taken: the jump lands first, the body collapses as it comes down)
|
|
654
|
+
if (name !== 'death' && this.oneshots.some((a) => this.committed(a))) return Promise.resolve('refused');
|
|
655
|
+
for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
|
|
656
|
+
if (this.standReq) { this.standReq.resolve('interrupted'); this.standReq = null; }
|
|
657
|
+
// one-shots other than hit end when the animal lies down
|
|
658
|
+
for (const a of this.oneshots) if (a.name !== 'hit' && !this.committed(a) && !this.oneshotDefs[a.name]?.overlay) this.end(a, 'interrupted');
|
|
659
|
+
const def = this.postureDefs[name];
|
|
660
|
+
let side = opts.side ?? (e.rand() < 0.5 ? -1 : 1);
|
|
661
|
+
if (opts.direction && name === 'death') {
|
|
662
|
+
// fall away from the blow
|
|
663
|
+
const d = opts.direction;
|
|
664
|
+
side = (d.x * Math.cos(e.heading) - d.z * Math.sin(e.heading)) > 0 ? -1 : 1;
|
|
665
|
+
}
|
|
666
|
+
// continue a lying curl side when going lie -> sleep
|
|
667
|
+
const prev = this.postures.find((p) => p.name === 'lie' || p.name === 'sleep');
|
|
668
|
+
if (prev && opts.side === undefined) side = prev.side;
|
|
669
|
+
const inst = { name, t: 0, w: 0, target: 1, fadeIn: def.fadeIn / (opts.speed || 1), fadeOut: def.fadeOut, side, opts, tune: this.tuning[name] || {}, reached: false };
|
|
670
|
+
// a posture of the same family keeps its time (lie -> sleep keeps lying)
|
|
671
|
+
if (prev && name === 'sleep') {
|
|
672
|
+
inst.w = prev.w; prev.w = 0; prev.target = 0; this.postures = this.postures.filter((p) => p !== prev);
|
|
673
|
+
// (the staged kneel carries on: its springs' state, see POSTURES.lie)
|
|
674
|
+
if (prev.kF !== undefined) { inst.kF = prev.kF; inst.kH = prev.kH; inst.vF = prev.vF; inst.vH = prev.vH; }
|
|
675
|
+
}
|
|
676
|
+
inst.promiseIn = new Promise((r) => { inst.resolveIn = r; });
|
|
677
|
+
this.postures.push(inst);
|
|
678
|
+
e.emit('actionStart', { name });
|
|
679
|
+
if (name === 'death') e.emit('death', { position: e.pos.clone() });
|
|
680
|
+
return inst.promiseIn;
|
|
681
|
+
}
|
|
682
|
+
const def = this.oneshotDefs[name];
|
|
683
|
+
if (!def) return Promise.reject(new Error(`procedural-animals: unknown action "${name}"`));
|
|
684
|
+
if (this.dead() && name !== 'hit') return Promise.resolve('refused');
|
|
685
|
+
const leaves = name === 'jump' || name === 'attack' || !!def.airborne || !!def.needsStand;
|
|
686
|
+
if (this.holdsStill() && leaves) return this.play('stand').then((r) => (r === 'refused' ? r : this.play(name, opts)));
|
|
687
|
+
// arbitration: a new one-shot interrupts the running ones (overlays excepted); nothing but a hit
|
|
688
|
+
// is taken while a one-shot is committed to the air
|
|
689
|
+
if (this.oneshots.some((a) => this.committed(a))) { if (name !== 'hit') return Promise.resolve('refused'); }
|
|
690
|
+
else if (!def.overlay) for (const a of this.oneshots) if (!this.oneshotDefs[a.name]?.overlay || a.name === name) this.end(a, 'interrupted');
|
|
691
|
+
for (const a of this.oneshots) if (a.name === name) this.end(a, 'interrupted');
|
|
692
|
+
const inst = { name, t: 0, w: 0, dur: 1, stopping: false, opts, tune: this.tuning[name] || {}, fade: def.fade ?? 0.12, speed: opts.speed || 1, airborne: !!def.airborne };
|
|
693
|
+
def.start(inst, this);
|
|
694
|
+
inst.promise = new Promise((r) => { inst.resolve = r; });
|
|
695
|
+
this.oneshots.push(inst);
|
|
696
|
+
e.emit('actionStart', { name });
|
|
697
|
+
return inst.promise;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
stop(name) {
|
|
701
|
+
for (const a of this.oneshots) if ((!name || a.name === name) && !this.committed(a)) this.end(a, 'stopped');
|
|
702
|
+
if (!name || this.postureDefs[name]) {
|
|
703
|
+
if (this.postures.some((p) => p.target > 0 && (name ? p.name === name : p.name !== 'death'))) this.play('stand');
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
onLand() {
|
|
708
|
+
for (const a of this.oneshots) if (this.oneshotDefs[a.name].onLand) this.oneshotDefs[a.name].onLand(a, this);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
update(dt) {
|
|
712
|
+
const e = this.engine, P = e.P;
|
|
713
|
+
resetParams(P);
|
|
714
|
+
// ---- postures
|
|
715
|
+
for (const p of this.postures) {
|
|
716
|
+
p.t += dt;
|
|
717
|
+
const rate = p.target > p.w ? 1 / p.fadeIn : 1 / p.fadeOut;
|
|
718
|
+
p.w = clamp(p.w + clamp(p.target - p.w, -rate * dt, rate * dt), 0, 1);
|
|
719
|
+
if (!p.reached && p.target > 0 && p.w >= 1 && (p.name !== 'death' || p.t > 1.6) && (p.name !== 'sleep' || p.t > 3.2) && stageSettled(p)) { p.reached = true; p.resolveIn('done'); e.emit('actionEnd', { name: p.name }); }
|
|
720
|
+
}
|
|
721
|
+
compact(this.postures, keepPosture);
|
|
722
|
+
const active = this.postures.find(isTargeted);
|
|
723
|
+
this.posture = active ? (active.name === 'death' ? 'dead' : active.name) : this.postures.length ? 'standing-up' : 'stand';
|
|
724
|
+
if (this.standReq && !this.postures.length) { this.standReq.resolve('done'); this.standReq = null; e.emit('actionEnd', { name: 'stand' }); }
|
|
725
|
+
|
|
726
|
+
// ---- idle (automatic when standing still with nothing else running)
|
|
727
|
+
const still = e.speed < 0.05 * e.cfg.sk && !e.input.follow;
|
|
728
|
+
const idleTarget = still && !this.postures.length && !this.oneshots.some(isBusy) ? 1 : 0;
|
|
729
|
+
const id = this.idle;
|
|
730
|
+
id.w += clamp(idleTarget - id.w, -dt * 1.5, dt * 0.7);
|
|
731
|
+
this.applyIdle(P, id.w, dt);
|
|
732
|
+
|
|
733
|
+
// ---- postures (smoothstepped weights)
|
|
734
|
+
for (const p of this.postures) {
|
|
735
|
+
const w = p.w * p.w * (3 - 2 * p.w);
|
|
736
|
+
this.postureDefs[p.name].apply(P, w, p, this);
|
|
737
|
+
}
|
|
738
|
+
// ---- one-shots
|
|
739
|
+
const wantsToMove = e.wantSpeed > 0.05 || !!e.input.follow;
|
|
740
|
+
for (const a of this.oneshots) {
|
|
741
|
+
if (wantsToMove && (a.name === 'eat' || a.name === 'drink')) this.end(a, 'interrupted');
|
|
742
|
+
a.t += dt * a.speed;
|
|
743
|
+
const def = this.oneshotDefs[a.name];
|
|
744
|
+
if (!a.stopping && def.update(a, dt * a.speed, this)) this.end(a, 'done');
|
|
745
|
+
// (a jump that has landed ends fast; one interrupted early fades out like any other)
|
|
746
|
+
const fadeIn = a.fade, fadeOut = (a.name === 'jump' || a.airborne) && a.reason === 'done' ? 0.05 : a.fade;
|
|
747
|
+
if (a.stopping) a.w = Math.max(0, a.w - dt / fadeOut);
|
|
748
|
+
else a.w = Math.min(1, a.w + dt / fadeIn);
|
|
749
|
+
const w = a.w * a.w * (3 - 2 * a.w);
|
|
750
|
+
if (w > 0) def.apply(P, w, a, this);
|
|
751
|
+
}
|
|
752
|
+
// species hook: style / behaviour layer on top of everything (may also steer e.wantSpeed / wantHeading)
|
|
753
|
+
if (this.hooks.update) this.hooks.update(P, dt, this);
|
|
754
|
+
for (const a of this.oneshots) {
|
|
755
|
+
if (a.stopping && a.w <= 0) { a.resolve(a.reason || 'done'); e.emit('actionEnd', { name: a.name, reason: a.reason || 'done' }); }
|
|
756
|
+
}
|
|
757
|
+
compact(this.oneshots, keepOneshot);
|
|
758
|
+
if (e.air.active && !this.oneshots.some(isAirborneAction)) e.touchDown();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
applyIdle(P, w, dt) {
|
|
762
|
+
const e = this.engine, id = this.idle, cfg = e.cfg;
|
|
763
|
+
// offsets from idle re-steps relax back once the animal moves
|
|
764
|
+
if (w < 0.5) { for (const leg of e.legs) leg.idleOff.multiplyScalar(Math.exp(-dt * 4)); }
|
|
765
|
+
if (w <= 0.001) { id.hasLook = false; return; }
|
|
766
|
+
id.t += dt;
|
|
767
|
+
// weight shift: slow sway of the body over the feet
|
|
768
|
+
id.shiftPh += dt * 0.23;
|
|
769
|
+
const sh = Math.sin(id.shiftPh * 6.283) * 0.6 + Math.sin(id.shiftPh * 6.283 * 0.37 + 1.1) * 0.4;
|
|
770
|
+
const it = this.tuning.idle || {}, ka = it.shift ?? 1;
|
|
771
|
+
mx(P, 'side', 0.012 * sh * ka, w); mx(P, 'roll', -0.02 * sh * ka, w); mx(P, 'dropH', (0.02 + 0.015 * sh) * ka, w);
|
|
772
|
+
// occasional re-step: nudge one foot's neutral position, the gait engine steps it
|
|
773
|
+
id.stepT -= dt;
|
|
774
|
+
if (id.stepT <= 0 && w > 0.9 && it.restep !== false) {
|
|
775
|
+
id.stepT = 5 + e.rand() * 9;
|
|
776
|
+
const leg = e.legs[Math.floor(e.rand() * 4)];
|
|
777
|
+
const s = cfg.k;
|
|
778
|
+
if (leg.idleOff.lengthSq() > 1e-6) leg.idleOff.set(0, 0, 0);
|
|
779
|
+
else leg.idleOff.set((e.rand() - 0.5) * 0.05 * s, 0, (e.rand() < 0.5 ? -1 : 1) * (0.09 + e.rand() * 0.03) * s);
|
|
780
|
+
}
|
|
781
|
+
// look around at random points (unless the game asked the animal to look at something)
|
|
782
|
+
if (!e.input.look) {
|
|
783
|
+
id.lookT -= dt;
|
|
784
|
+
if (id.lookT <= 0) {
|
|
785
|
+
id.lookT = 1.4 + e.rand() * 2.8;
|
|
786
|
+
if (e.rand() < 0.3) id.hasLook = false;
|
|
787
|
+
else {
|
|
788
|
+
const a = e.heading + (e.rand() - 0.5) * 2.4;
|
|
789
|
+
const d = (6 + e.rand() * 10) * cfg.k;
|
|
790
|
+
const px = e.pos.x + Math.sin(a) * d, pz = e.pos.z + Math.cos(a) * d;
|
|
791
|
+
id.look.set(px, e.terrainH(px, pz) + (0.1 + e.rand() * 0.9) * cfg.k, pz);
|
|
792
|
+
id.hasLook = true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (id.hasLook) { P.look = id.look; P.lookW = w; }
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export { angDiff, DEG, lerp, envelope, LEG_FIELDS, mx, mxLeg, mxLegs, mouthGroundTarget, ONESHOTS, POSTURES };
|