@bitefoundry/retrieviewer 0.0.3 → 0.0.5
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 +11 -0
- package/ASSETS-LICENSE.md +2 -1
- package/ATTRIBUTION.md +6 -1
- package/COMFORT-ART.md +95 -0
- package/DESKTOP-PLATFORMS.md +6 -1
- package/INSTALLING.md +4 -0
- package/PLUGIN.md +13 -10
- package/PREREQUISITES.md +95 -0
- package/PUPPY-MEMORY.md +102 -1
- package/README.ko.md +116 -0
- package/README.md +121 -59
- package/RELEASING.md +33 -13
- package/THIRD-PARTY-NOTICES.md +5 -3
- package/braille.mjs +35 -9
- package/examples/live.mjs +2 -2
- package/integrations/linux-overlay/overlay.py +8 -2
- package/integrations/macos-overlay/overlay.jxa +9 -7
- package/integrations/opencode/runtime.mjs +4 -4
- package/integrations/opencode/tui.tsx +1 -1
- package/integrations/windows-overlay/Overlay.cs +7 -6
- package/integrations/windows-overlay/desktop.mjs +2 -1
- package/integrations/windows-overlay/protocol.mjs +13 -7
- package/integrations/windows-overlay/worker.mjs +1 -1
- package/package.json +34 -13
- package/puppy-comfort-sprites.mjs +33 -0
- package/puppy-companion-comfort-q5.bin.gz +0 -0
- package/puppy-companion-comfort.json +140 -0
- package/puppy-companion-life.mjs +58 -18
- package/puppy-companion-objects.mjs +12 -0
- package/puppy-companion-sprites.mjs +4 -2
- package/puppy-frame-predictive.mjs +15 -9
- package/puppy-frame-store.mjs +2 -2
- package/puppy-paper-actions.mjs +4 -2
- package/puppy-paper-behaviors.mjs +1 -1
- package/puppy-paper-contact.mjs +4 -2
- package/puppy-paper-seat.mjs +4 -2
- package/puppy-paper-world.mjs +14 -0
- package/puppy-paper.mjs +79 -21
- package/retrieviewer-cli.mjs +8 -6
- package/retrieviewer-plugin.d.mts +1 -1
- package/retrieviewer-plugin.mjs +1 -1
- package/retrieviewer.d.mts +12 -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,1059 +0,0 @@
|
|
|
1
|
-
// Action layer for the bird engine (core/motion/bird.js).
|
|
2
|
-
//
|
|
3
|
-
// Every frame the layer fills a parameter set P (createBirdParams) that the engine reads on top of
|
|
4
|
-
// locomotion and flight. Actions are time-parameterised and blend in / out with weights:
|
|
5
|
-
//
|
|
6
|
-
// postures sit (crouch / brood on the tarsi), lie (belly down), sleep (fluffed, head turned back
|
|
7
|
-
// and tucked into the scapulars, or sunk into the shoulders), death (collapse onto the
|
|
8
|
-
// side, wings splayed, toes clenched), perch (fly to a perch and grip it)
|
|
9
|
-
// replace locomotion until 'stand' (or until the bird is asked to move: it stands up first)
|
|
10
|
-
// one-shots jump (hop up with wing beats), attack (peck lunge / wing-beat lunge / talon strike),
|
|
11
|
-
// hit, eat (pecking), drink (dip, then tilt the head back), takeoff, land, glide, flap
|
|
12
|
-
// idle automatic when standing: saccadic looking around, head cocking, preening, wing and tail
|
|
13
|
-
// flicks, feather shake (rouse), wing-and-leg stretch, weight shifts
|
|
14
|
-
//
|
|
15
|
-
// play(name, opts) returns a Promise resolved when the action ends (postures: once the pose is
|
|
16
|
-
// reached; takeoff: when airborne; land / perch: on touchdown). opts: { target (Vector3), direction
|
|
17
|
-
// (Vector3, hit), duration, loop, side, strength, radius / axis (perch branch) }. The promise resolves
|
|
18
|
-
// with how the action ended: 'done' | 'interrupted' | 'stopped' | 'refused'.
|
|
19
|
-
//
|
|
20
|
-
// Arbitration (the rule of every body plan): a new body
|
|
21
|
-
// one-shot (jump, attack, hit, eat, drink) interrupts the running ones (fade out, 'interrupted');
|
|
22
|
-
// a hop in the air (jump, lunge, spur kick) is committed: only a hit layers over it, anything else is
|
|
23
|
-
// refused; eat / drink / jump need the ground and are refused in the air. Flight commands (takeoff,
|
|
24
|
-
// land, glide, flap) steer the locomotion: they neither interrupt nor are interrupted by body
|
|
25
|
-
// one-shots (glide and flap replace each other). A posture cross-fades from the current one and ends
|
|
26
|
-
// the body one-shots (not a hit); stop(name) ends that action, stop() every one-shot and posture but
|
|
27
|
-
// death. Dead: only hit is taken; moving does not stand a dead bird up. Death in the air or on a
|
|
28
|
-
// perch: the bird falls limp and collapses where it hits the ground.
|
|
29
|
-
//
|
|
30
|
-
// Species tuning: species.motion.actions = { attack: { style: 'peck' | 'lunge' | 'talons' | 'spur', mantle (0..1:
|
|
31
|
-
// talon strike on the ground throws the wings open and mantles them over the prey) },
|
|
32
|
-
// lie: { wings (0..1, default 1: the sunning droop and part spread; 0 keeps them folded) },
|
|
33
|
-
// death: { wings (0..1, default 1: the splay of the wings of a bird dead on the ground; 0 folded) },
|
|
34
|
-
// eat: { style: 'peck' | 'tear' | 'scratch', rate, reach (tear: food distance ahead of the feet, leg
|
|
35
|
-
// lengths), mantle }, jump: { height, distance }, sleep: { style: 'tuck' | 'sink', fluff (0..1, default 1:
|
|
36
|
-
// how far the plumage fluffs up; a bird whose folded wing lies in a bed on the flank fluffs less) },
|
|
37
|
-
// preen: { spot: [x, y, z] (m at the reference size, body frame from the hip pivot), pitch (rad) },
|
|
38
|
-
// idle: { crow: weight } (extra idle behaviours) }
|
|
39
|
-
// 'spur': a flapping jump-kick, both feet thrown forward at the top of the jump (roosters);
|
|
40
|
-
// 'tear': raptor: grip and pull with the bill between the feet, wings mantled;
|
|
41
|
-
// 'scratch': rakes the ground with one foot and then the other, then pecks at the cleared spot
|
|
42
|
-
// (ground-feeding fowl). Idle extra 'crow': wing clap, neck stretched up, bill open (event
|
|
43
|
-
// 'vocalize' { kind: 'crow' }). play('idle', { behaviour: 'crow' }) starts an idle behaviour now.
|
|
44
|
-
import * as THREE from 'three';
|
|
45
|
-
import { clamp, lerp, smooth, DEG, tv, angDiff } from './common.js';
|
|
46
|
-
|
|
47
|
-
export function createBirdParams() {
|
|
48
|
-
return {
|
|
49
|
-
gaitW: 1, legGait: 1, speedScale: 1, turnScale: 1, restep: 1, bodyOmega: 1, headOmega: 1, wingOmega: 1,
|
|
50
|
-
drop: 0, lift: 0, pitch: 0, roll: 0, rollAxisK: 0, side: 0, fwd: 0, chestPitch: 0, groundW: 0, sit: 0, fluff: 0, toeCurl: 0,
|
|
51
|
-
legFree: 0, legReach: 0, legUp: [0, 0], scratchW: [0, 0], scratchU: [0, 0],
|
|
52
|
-
wingOpen: 0, wingElev: 0, wingElevS: [0, 0], wingAlpha: 0, wingPitch: 0, wingExtend: 0, wingFoldExtra: 0, wingDroop: 0,
|
|
53
|
-
fanExtra: 0, flap: 0, flapAmp: 1,
|
|
54
|
-
tailPitch: 0, tailYaw: 0, tailFan: 0,
|
|
55
|
-
headW: 0, headPos: new THREE.Vector3(), headFwd: 0, headUp: 0, headSide: 0, neckReach: 0, neckExtend: 0,
|
|
56
|
-
headYaw: 0, headPitch: 0, headRoll: 0, headLimp: 0, look: null, lookW: 1, cock: 0, jaw: 0,
|
|
57
|
-
eyelid: 0, breathRate: 1, breathAmp: 1, busyGround: false,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function resetParams(P) {
|
|
62
|
-
P.gaitW = 1; P.legGait = 1; P.speedScale = 1; P.turnScale = 1; P.restep = 1; P.bodyOmega = 1; P.headOmega = 1; P.wingOmega = 1;
|
|
63
|
-
P.drop = P.lift = P.pitch = P.roll = P.rollAxisK = P.side = P.fwd = P.chestPitch = P.groundW = P.sit = P.fluff = P.toeCurl = 0;
|
|
64
|
-
P.legFree = P.legReach = 0; P.legUp[0] = P.legUp[1] = 0;
|
|
65
|
-
P.scratchW[0] = P.scratchW[1] = 0; P.scratchU[0] = P.scratchU[1] = 0;
|
|
66
|
-
P.wingOpen = P.wingElev = P.wingAlpha = P.wingPitch = P.wingExtend = P.wingFoldExtra = P.wingDroop = 0;
|
|
67
|
-
P.wingElevS[0] = P.wingElevS[1] = 0;
|
|
68
|
-
P.fanExtra = P.flap = 0; P.flapAmp = 1;
|
|
69
|
-
P.tailPitch = P.tailYaw = P.tailFan = 0;
|
|
70
|
-
P.headW = P.headFwd = P.headUp = P.headSide = P.neckReach = P.neckExtend = 0;
|
|
71
|
-
P.headYaw = P.headPitch = P.headRoll = P.headLimp = 0; P.look = null; P.lookW = 1; P.cock = 0; P.jaw = 0;
|
|
72
|
-
P.eyelid = 0; P.breathRate = 1; P.breathAmp = 1; P.busyGround = false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const mx = (P, k, v, w) => { P[k] += (v - P[k]) * w; };
|
|
76
|
-
const pulse = (t, a, b, c) => smooth(a, b, t) * (1 - smooth(b, c, t));
|
|
77
|
-
|
|
78
|
-
// occiput position that puts the bill tip on a point with the head pitched by `pitch` (+ = bill down)
|
|
79
|
-
function billTo(e, point, pitch, yaw, out) {
|
|
80
|
-
const q = tv(); void q;
|
|
81
|
-
const bl = e.cfg.billLocal;
|
|
82
|
-
const cp = Math.cos(pitch), sp = Math.sin(pitch);
|
|
83
|
-
// bill vector in the heading frame, pitched about the lateral axis
|
|
84
|
-
const y = bl.y * cp - bl.z * sp, z = bl.y * sp + bl.z * cp;
|
|
85
|
-
const h = e.heading + yaw;
|
|
86
|
-
return out.set(point.x - Math.sin(h) * z - Math.cos(h) * bl.x, point.y - y, point.z - Math.cos(h) * z + Math.sin(h) * bl.x);
|
|
87
|
-
}
|
|
88
|
-
// a ground point ahead of the feet (for pecking / drinking)
|
|
89
|
-
function groundAhead(e, k, out, water) {
|
|
90
|
-
const cfg = e.cfg;
|
|
91
|
-
out.copy(e.legs[0].plant).add(e.legs[1].plant).multiplyScalar(0.5).addScaledVector(e.F, k * cfg.legLen);
|
|
92
|
-
let y = e.terrainH(out.x, out.z);
|
|
93
|
-
if (water && e.water) { const w = e.water(out.x, out.z); if (Number.isFinite(w)) y = Math.max(y, w); }
|
|
94
|
-
if (e.mode === 'perch' && e.perch) y = Math.min(e.perch.top + 0.02 * cfg.s, y + 1e9);
|
|
95
|
-
out.y = y;
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Peck fit: the carriage that lets the neck reach a peck (eat, drink, bill wipe, the pecks between
|
|
100
|
-
// scratches) at a comfortable stretch. Species tune a peck (reach ahead of the feet, bill pitch, body
|
|
101
|
-
// pitch, crouch) at their reference proportions; a neck short for its legs (a chick: 0.4 of the hen's)
|
|
102
|
-
// or a target out of reach (a hen raking 1.25 leg lengths ahead with a neck a quarter of that) pulled
|
|
103
|
-
// the neck straight - its bones stretched - with the bill still short of the ground. In order: the body
|
|
104
|
-
// tips forward (to 50 deg), crouches, the peck comes closer to the feet (to half the reach), and only
|
|
105
|
-
// then tips further (to 63 deg), until the occiput target (bill tip on the ground `reach` leg lengths
|
|
106
|
-
// ahead of the feet, head pitched `billPitch`, `hover` above it) lies within the neck's reach.
|
|
107
|
-
const _fit = { pitch: 0, drop: 0, reach: 0 };
|
|
108
|
-
function fitPeck(e, reach0, billPitch, pitch0, drop0, hover = 0, water = false) {
|
|
109
|
-
const cfg = e.cfg;
|
|
110
|
-
const maxD = 0.95 * Math.min(cfg.neckArc, cfg.neckChord0 * 1.08);
|
|
111
|
-
const g0 = e.terrainH(e.pos.x, e.pos.z), sitH = e.sitHeight();
|
|
112
|
-
const h = e.heading, sh = Math.sin(h), ch = Math.cos(h), nb = cfg.neckBaseLocal;
|
|
113
|
-
const pt = tv(), hp = tv();
|
|
114
|
-
const dist = (phi, drop, reach) => {
|
|
115
|
-
groundAhead(e, reach, pt, water);
|
|
116
|
-
billTo(e, pt, billPitch, 0, hp);
|
|
117
|
-
const c = Math.cos(phi), sn = -Math.sin(phi); // (the body frame turns by -pitch about its lateral axis)
|
|
118
|
-
const ly = nb.y * c - nb.z * sn, lz = nb.y * sn + nb.z * c;
|
|
119
|
-
const y = g0 + cfg.pivotY - drop * (cfg.pivotY - sitH) + ly;
|
|
120
|
-
return Math.hypot(hp.x - (e.pos.x + ch * nb.x + sh * lz), hp.y + hover - y, hp.z - (e.pos.z - sh * nb.x + ch * lz));
|
|
121
|
-
};
|
|
122
|
-
let phi = Math.min(pitch0, 0), drop = drop0, reach = reach0;
|
|
123
|
-
for (let i = 0; i < 48 && dist(phi, drop, reach) > maxD; i++) {
|
|
124
|
-
if (phi > -0.87) phi = Math.max(-0.87, phi - 0.06);
|
|
125
|
-
else if (drop < 0.55) drop = Math.min(0.55, drop + 0.05);
|
|
126
|
-
else if (reach > 0.5 * reach0) reach = Math.max(0.5 * reach0, reach - 0.05 * reach0);
|
|
127
|
-
else if (phi > -1.1) phi = Math.max(-1.1, phi - 0.05);
|
|
128
|
-
else break;
|
|
129
|
-
}
|
|
130
|
-
_fit.pitch = phi; _fit.drop = drop; _fit.reach = reach;
|
|
131
|
-
return _fit;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ------------------------------------------------------------------------------------------------
|
|
135
|
-
// Postures (value sets applied with the posture weight)
|
|
136
|
-
// ------------------------------------------------------------------------------------------------
|
|
137
|
-
const POSTURES = {
|
|
138
|
-
// settled on the tarsi (resting, brooding): the body as upright as it stands, belly on the ground,
|
|
139
|
-
// lightly fluffed, the head up and alert (it keeps looking about) - the three resting postures read
|
|
140
|
-
// apart at a glance: sit upright and alert, lie flat and spread, sleep a fluffed ball with no neck
|
|
141
|
-
sit: {
|
|
142
|
-
fadeIn: 0.8, fadeOut: 0.6,
|
|
143
|
-
apply(P, w, inst) {
|
|
144
|
-
mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w); mx(P, 'restep', 0, w);
|
|
145
|
-
mx(P, 'drop', 1, w); mx(P, 'sit', 1, smooth(0.1, 0.8, w)); mx(P, 'groundW', 1, w);
|
|
146
|
-
mx(P, 'pitch', -0.08, w); mx(P, 'fluff', 0.4, w); mx(P, 'neckReach', -0.004, w); mx(P, 'headUp', 0.004, w);
|
|
147
|
-
mx(P, 'tailPitch', 0.12, w); mx(P, 'breathRate', 0.8, w);
|
|
148
|
-
void inst;
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
// lying at rest, sunning: flat on the belly and level, leaning a little onto one side, both wings
|
|
152
|
-
// drooped onto the ground and part spread, the tail fanned, feathers raised, the neck relaxed forward
|
|
153
|
-
// and low, the head tilted, eyes half closed
|
|
154
|
-
lie: {
|
|
155
|
-
fadeIn: 1.1, fadeOut: 0.9,
|
|
156
|
-
apply(P, w, inst, L) {
|
|
157
|
-
const e = L.engine, cfg = e.cfg, s = cfg.s, side = inst.side;
|
|
158
|
-
POSTURES.sit.apply(P, w, inst);
|
|
159
|
-
const c = smooth(0.25, 1.5, inst.t) * w;
|
|
160
|
-
mx(P, 'drop', 1.12, w); mx(P, 'pitch', -Math.max(0.3, cfg.tilt - 0.15), w); mx(P, 'roll', side * 0.2, c);
|
|
161
|
-
mx(P, 'fluff', 0.75, c);
|
|
162
|
-
// (actions.lie.wings: 0..1 scales the drooped, part-spread wings; 0 keeps them folded on the body)
|
|
163
|
-
const ww = c * (inst.tune.wings ?? 1);
|
|
164
|
-
mx(P, 'wingOpen', 0.35, ww); mx(P, 'wingElev', -20 * DEG, ww); mx(P, 'wingAlpha', 10 * DEG, ww);
|
|
165
|
-
mx(P, 'tailFan', 0.55, c); mx(P, 'tailPitch', -0.05, c);
|
|
166
|
-
mx(P, 'headUp', -0.14 * cfg.neckLen / s, c); mx(P, 'neckReach', 0.1 * cfg.neckLen / s, c); mx(P, 'headPitch', 0.15, c);
|
|
167
|
-
mx(P, 'headRoll', side * 0.35, c); mx(P, 'eyelid', 0.45, c); mx(P, 'breathRate', 0.7, c);
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
// asleep: fluffed into a ball, eyes closed, the head turned back with the bill in the scapulars
|
|
171
|
-
// ('tuck') or drawn down into the shoulders with the bill in the breast feathers ('sink'). Ground
|
|
172
|
-
// birds sit; upright birds (raptors: standing carriage >= 40 deg) sleep standing on one leg, the other
|
|
173
|
-
// drawn up into the belly feathers, the body over the standing foot (actions.sleep.stance: 'sit' |
|
|
174
|
-
// 'oneLeg')
|
|
175
|
-
sleep: {
|
|
176
|
-
fadeIn: 1.2, fadeOut: 1.0,
|
|
177
|
-
apply(P, w, inst, L) {
|
|
178
|
-
const e = L.engine, cfg = e.cfg, s = cfg.s, side = inst.side;
|
|
179
|
-
const perched = e.mode === 'perch';
|
|
180
|
-
const oneLeg = !perched && (inst.tune.stance ? inst.tune.stance === 'oneLeg' : cfg.tilt >= 40 * DEG);
|
|
181
|
-
if (perched) { mx(P, 'drop', 0.45, w); mx(P, 'fluff', 0.6, w); mx(P, 'gaitW', 0, w); mx(P, 'restep', 0, w); }
|
|
182
|
-
else if (!oneLeg) POSTURES.sit.apply(P, w, inst);
|
|
183
|
-
else {
|
|
184
|
-
mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w); mx(P, 'restep', 0, w); mx(P, 'drop', 0.16, w);
|
|
185
|
-
// (the leg comes up once the body is over the other foot)
|
|
186
|
-
const k = smooth(0.4, 1.6, inst.t) * w, i = side > 0 ? 0 : 1;
|
|
187
|
-
P.legUp[i] += (1 - P.legUp[i]) * smooth(0.5, 1, k);
|
|
188
|
-
mx(P, 'side', -side * 0.8 * cfg.legs[0].cx / s, k);
|
|
189
|
-
}
|
|
190
|
-
const c = smooth(0.6, 2.2, inst.t) * w;
|
|
191
|
-
mx(P, 'fluff', inst.tune.fluff ?? 1, c);
|
|
192
|
-
if ((inst.tune.style || 'tuck') === 'tuck') {
|
|
193
|
-
// head over the shoulder, bill into the back feathers
|
|
194
|
-
const back = tv(side * 0.012 * cfg.s, cfg.headH - cfg.pivotY - 0.035 * cfg.s, -0.01 * cfg.s);
|
|
195
|
-
const hp = e.B(back, tv());
|
|
196
|
-
P.headPos.lerp(hp, c / Math.max(1e-3, P.headW + c)); mx(P, 'headW', 1, c);
|
|
197
|
-
mx(P, 'headYaw', side * 2.3, c); mx(P, 'headPitch', 0.35, c); mx(P, 'headRoll', side * 0.3, c);
|
|
198
|
-
} else {
|
|
199
|
-
// head drawn down into the shoulders (the neck folded away in the fluffed plumage), the bill
|
|
200
|
-
// resting in the breast feathers
|
|
201
|
-
mx(P, 'headUp', -0.3 * cfg.neckLen / s, c); mx(P, 'neckReach', -0.2 * cfg.neckLen / s, c); mx(P, 'headPitch', 0.6, c);
|
|
202
|
-
mx(P, 'headYaw', side * 0.2, c);
|
|
203
|
-
}
|
|
204
|
-
mx(P, 'lookW', 0, c); mx(P, 'eyelid', 1, smooth(1.2, 2.6, inst.t) * w);
|
|
205
|
-
mx(P, 'breathRate', 0.5, c); mx(P, 'breathAmp', 1.6, c); mx(P, 'headOmega', 0.5, c);
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
// collapse: legs buckle, falls on its side, wings splayed, toes clenched, head limp on the ground.
|
|
209
|
-
// Killed in the air (or on a perch): a limp fall - wings half open, lifted and swept back by the
|
|
210
|
-
// airflow, fluttering; head limp; legs trailing; the body tumbling nose-down and rolling from its
|
|
211
|
-
// flight attitude - then the collapse on its own clock from the impact, eased over from the fall
|
|
212
|
-
death: {
|
|
213
|
-
fadeIn: 0.2, fadeOut: 1.4,
|
|
214
|
-
apply(P, w, inst, L) {
|
|
215
|
-
const side = inst.side, e = L.engine, cfg = e.cfg, s = cfg.s;
|
|
216
|
-
const falling = !!(e.to && e.to.dead);
|
|
217
|
-
// kA: weight of the falling pose (1 in the air, easing out over 0.35 s after the impact)
|
|
218
|
-
let t = inst.t, kA = 0, kR = 0;
|
|
219
|
-
if (inst.fell) {
|
|
220
|
-
if (falling) { inst.tLand = undefined; kA = 1; kR = 1; t = 0; } else {
|
|
221
|
-
if (inst.tLand === undefined) inst.tLand = inst.t;
|
|
222
|
-
const tg = inst.t - inst.tLand;
|
|
223
|
-
kA = 1 - smooth(0, 0.35, tg);
|
|
224
|
-
// (the roll axis turns back to the body's own axis slowly: within the collapse's 0.35 s the
|
|
225
|
-
// rolling body swung a wing lying on the ground up and back, the primaries jittered)
|
|
226
|
-
kR = 1 - smooth(0, 1.2, tg);
|
|
227
|
-
t = 0.22 + tg; // (the legs are already limp: the collapse starts at the fall onto the side)
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// the legs give and the body topples onto its side at once: the roll leads (from 0.1 s), the head
|
|
231
|
-
// follows it down (it used to drop first, the body still upright: a nose-dive with the rump up);
|
|
232
|
-
// the body ends level along the ground whatever its standing carriage (an eagle's upright body
|
|
233
|
-
// pitched by a fixed 20 deg lay tail-up)
|
|
234
|
-
// (killed on the move the topple waits up to 0.2 s longer, while the legs still stride: taken from
|
|
235
|
-
// the speed at the death, so the timing cannot shift as the body brakes)
|
|
236
|
-
if (inst.mv0 === undefined) inst.mv0 = e.mode === 'ground' && !inst.fell ? smooth(0.1, 0.5, e.speed / cfg.sq) : 0;
|
|
237
|
-
const tD = 0.2 * inst.mv0;
|
|
238
|
-
const buckle = smooth(0, 0.28, t);
|
|
239
|
-
const fall = smooth(0.1 + tD, 0.7 + tD, t), fallE = fall;
|
|
240
|
-
const settle = t > 0.7 + tD ? Math.exp(-(t - 0.7 - tD) * 6) * Math.sin((t - 0.7 - tD) * 16) * 0.05 : 0;
|
|
241
|
-
const pitchD = -Math.max(0.3, cfg.tilt - 0.12);
|
|
242
|
-
// killed on the move (walking, hopping, running): the legs keep stepping under the body while it
|
|
243
|
-
// brakes and only then go limp; no crouch onto the tarsi over a swinging foot
|
|
244
|
-
// (not a bird that fell dead from the air: it hits the ground limp and slides, legs free)
|
|
245
|
-
const mv = e.mode === 'ground' && !inst.fell ? smooth(0.1, 0.5, e.speed / cfg.sq) * (1 - smooth(0.25, 0.6, t)) : 0;
|
|
246
|
-
// (actions.death.wings 0..1 scales the splay of the wings on the ground, default 1; 0 leaves them
|
|
247
|
-
// folded on the body)
|
|
248
|
-
const wo = smooth(0.1, 0.7, t) * (inst.tune.wings ?? 1);
|
|
249
|
-
// the ground pose, blended from the falling one (tf: time since the death)
|
|
250
|
-
const tf = inst.t, A = kA;
|
|
251
|
-
const aRoll = side * 0.5 * smooth(0, 1.2, tf), aPitch = -0.55 * smooth(0, 1.0, tf);
|
|
252
|
-
// (in the air the body pose is relative to the attitude at the death, see bird.js fall())
|
|
253
|
-
const pitch0 = e._fallPitch0 ?? 0, bank0 = e._fallBank0 ?? 0;
|
|
254
|
-
const flutter = Math.sin(tf * 11 + side) * 0.5 + Math.sin(tf * 17.3) * 0.5;
|
|
255
|
-
mx(P, 'gaitW', 0, w * (1 - mv)); mx(P, 'legGait', 0, w * (1 - mv)); mx(P, 'restep', 0, w); mx(P, 'bodyOmega', 1.4, w);
|
|
256
|
-
mx(P, 'drop', lerp(lerp(0.6 * buckle * (1 - mv), 1.25, fallE), 0.6, A), w);
|
|
257
|
-
mx(P, 'sit', buckle * (1 - fallE) * (1 - mv) * (1 - A), w);
|
|
258
|
-
mx(P, 'roll', lerp(side * (1.42 * fallE - settle), falling ? aRoll : bank0 + aRoll, A), w);
|
|
259
|
-
mx(P, 'pitch', lerp(pitchD * fallE, falling ? aPitch : pitch0 + aPitch, A), w);
|
|
260
|
-
mx(P, 'rollAxisK', kR, w);
|
|
261
|
-
mx(P, 'groundW', 1, w);
|
|
262
|
-
mx(P, 'legFree', lerp(smooth(0.2, 0.85, t), smooth(0, 0.25, tf), A), w); mx(P, 'legReach', 0.15 * (1 - A), w);
|
|
263
|
-
mx(P, 'toeCurl', lerp(85 * DEG * smooth(0.3, 1, t), 40 * DEG * smooth(0.2, 0.8, tf), A), w); // feet clenched
|
|
264
|
-
// wings: the upper one half open, drooping over / behind the body; the lower one folded under
|
|
265
|
-
// (falling: both half open, raised and swept back by the air, fluttering)
|
|
266
|
-
mx(P, 'wingOpen', lerp(0.5 * wo, 0.62, A), w);
|
|
267
|
-
// (fallen from the air the wings settle splayed on the ground - the lower one is not driven down
|
|
268
|
-
// under the body as it lands on it: jammed against the ground at impact speed it was flung back up,
|
|
269
|
-
// the primaries jittered)
|
|
270
|
-
const eL0 = inst.fell ? -28 : -70;
|
|
271
|
-
const eU = lerp(-35 * DEG * wo, (22 + 5 * flutter) * DEG, A), eL = lerp(eL0 * DEG * wo, (22 - 5 * flutter) * DEG, A);
|
|
272
|
-
P.wingElevS[side > 0 ? 1 : 0] += (eU - P.wingElevS[side > 0 ? 1 : 0]) * w;
|
|
273
|
-
P.wingElevS[side > 0 ? 0 : 1] += (eL - P.wingElevS[side > 0 ? 0 : 1]) * w;
|
|
274
|
-
mx(P, 'wingAlpha', lerp(35 * DEG * wo, 24 * DEG, A), w);
|
|
275
|
-
mx(P, 'fanExtra', 0.12 * A, w);
|
|
276
|
-
mx(P, 'tailFan', lerp(0.35 * wo, 0.45, A), w); mx(P, 'tailPitch', 0.15 * A, w);
|
|
277
|
-
// head and neck lie on the terrain, limp: where the resting head falls with the rolled body, well
|
|
278
|
-
// forward of the breast (the neck lies along the ground instead of folding the head under the chest)
|
|
279
|
-
const hp = e.B(cfg.occLocal, tv()).addScaledVector(e.F, cfg.neckLen * 0.42);
|
|
280
|
-
const hg = e.terrainH(hp.x, hp.z) + Math.max(0.02 * s, 1.3 * cfg.headR);
|
|
281
|
-
hp.y = Math.max(hg, lerp(hp.y, hg, 0.75));
|
|
282
|
-
// (falling dead from the air the head is limp on the neck - the ground pose's head target, pulled
|
|
283
|
-
// toward terrain far below, stretched the neck base - and goes to the ground after the impact)
|
|
284
|
-
const dtt = Math.max(0, inst.t - (inst.tPrev ?? inst.t)); inst.tPrev = inst.t;
|
|
285
|
-
if (inst.gw === undefined) inst.gw = falling || e.airborne ? 0 : 1;
|
|
286
|
-
inst.gw += ((falling ? 0 : 1) - inst.gw) * (1 - Math.exp(-dtt / 0.15));
|
|
287
|
-
const hw = smooth(0.3, 0.85, t) * inst.gw;
|
|
288
|
-
P.headPos.lerp(hp, hw / Math.max(1e-3, P.headW + hw)); mx(P, 'headW', 1, hw * w);
|
|
289
|
-
mx(P, 'headLimp', lerp(0.85 * hw, 0.9 * smooth(0, 0.3, tf), A), w); mx(P, 'headUp', -0.02 * A, w);
|
|
290
|
-
mx(P, 'eyelid', 0.6, lerp(smooth(0.2, 1.2, t), smooth(0.05, 0.5, tf), A) * w);
|
|
291
|
-
mx(P, 'breathAmp', 0, w); mx(P, 'breathRate', 0, w); mx(P, 'lookW', 0, w); mx(P, 'headOmega', 1.6, w);
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
// gripping a perch (or the ground): a slight crouch, toes closed round the branch
|
|
295
|
-
perch: {
|
|
296
|
-
fadeIn: 0.5, fadeOut: 0.4,
|
|
297
|
-
apply(P, w, inst, L) {
|
|
298
|
-
const e = L.engine;
|
|
299
|
-
if (inst.phase === 'fly') return;
|
|
300
|
-
const k = e.mode === 'perch' ? 1 : 0.5;
|
|
301
|
-
mx(P, 'drop', 0.18 * k, w); mx(P, 'restep', 0, w); mx(P, 'gaitW', 0.5, w);
|
|
302
|
-
mx(P, 'toeCurl', e.mode === 'perch' ? 0 : 12 * DEG, w);
|
|
303
|
-
mx(P, 'pitch', 0.06, w);
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
// ------------------------------------------------------------------------------------------------
|
|
309
|
-
// One-shot actions
|
|
310
|
-
// ------------------------------------------------------------------------------------------------
|
|
311
|
-
const ONESHOTS = {
|
|
312
|
-
jump: {
|
|
313
|
-
start(inst, L) {
|
|
314
|
-
const e = L.engine, cfg = e.cfg, o = inst.tune;
|
|
315
|
-
if (e.mode !== 'ground' && e.mode !== 'perch') { inst.dur = 0; return; }
|
|
316
|
-
const h = (inst.opts.height ?? o.height ?? 0.35) * cfg.s;
|
|
317
|
-
const vy = Math.sqrt(2 * 9.81 * 0.75 * h);
|
|
318
|
-
const T = (2 * vy) / (9.81 * 0.75);
|
|
319
|
-
let dist = (o.distance ?? 0.6) * cfg.s;
|
|
320
|
-
if (inst.opts.target) { const t = inst.opts.target; dist = clamp(Math.hypot(t.x - e.pos.x, t.z - e.pos.z), 0.05, dist * 3); }
|
|
321
|
-
e.startTakeoff({ hopOnly: true, vy, vh: Math.max(e.speed, dist / T), quick: true });
|
|
322
|
-
inst.dur = Infinity; inst.phase = 'go';
|
|
323
|
-
},
|
|
324
|
-
update(inst, dt, L) {
|
|
325
|
-
const e = L.engine;
|
|
326
|
-
if (inst.phase === 'land' && inst.t - inst.tLand > 0.4) return true;
|
|
327
|
-
if (inst.dur === 0) return true;
|
|
328
|
-
if (inst.t > 5 && inst.phase !== 'land') return true;
|
|
329
|
-
void e;
|
|
330
|
-
return false;
|
|
331
|
-
},
|
|
332
|
-
onLand(inst) { if (inst.phase !== 'land') { inst.phase = 'land'; inst.tLand = inst.t; } },
|
|
333
|
-
apply(P, w, inst, L) {
|
|
334
|
-
const e = L.engine;
|
|
335
|
-
const airUp = e.to && e.to.hopOnly && e.to.air;
|
|
336
|
-
if (e.to && e.to.hopOnly && !e.to.air) {
|
|
337
|
-
// crouch with wings lifting
|
|
338
|
-
mx(P, 'wingOpen', 0.4, w); mx(P, 'wingElev', 25 * DEG, w); mx(P, 'tailFan', 0.3, w);
|
|
339
|
-
} else if (airUp) {
|
|
340
|
-
// beat on the way up, then hold the wings raised and open to brake the descent
|
|
341
|
-
const up = smooth(-0.6, 0.4, e.vy);
|
|
342
|
-
mx(P, 'wingOpen', 1, w); mx(P, 'flap', 1, w * up); mx(P, 'flapAmp', 0.9, w); mx(P, 'tailFan', 0.6, w);
|
|
343
|
-
mx(P, 'wingElev', 35 * DEG, w * (1 - up));
|
|
344
|
-
} else if (inst.phase === 'land') {
|
|
345
|
-
// wings (raised and open at touchdown) fold over ~0.35 s
|
|
346
|
-
const k = 1 - smooth(0, 1, (inst.t - inst.tLand) / 0.35);
|
|
347
|
-
mx(P, 'wingOpen', 1, w * k); mx(P, 'wingElev', 35 * DEG, w * k); mx(P, 'tailFan', 0.6, w * k);
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
|
|
352
|
-
attack: {
|
|
353
|
-
start(inst, L) {
|
|
354
|
-
const e = L.engine, cfg = e.cfg;
|
|
355
|
-
inst.style = inst.opts.style || inst.tune.style || 'peck';
|
|
356
|
-
inst.target = inst.opts.target ? new THREE.Vector3(inst.opts.target.x, inst.opts.target.y, inst.opts.target.z) : null;
|
|
357
|
-
inst.hit = false;
|
|
358
|
-
if (e.airborne) inst.style = 'talons';
|
|
359
|
-
else if (inst.target) {
|
|
360
|
-
const d = Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z);
|
|
361
|
-
if (inst.style === 'peck' && d > 1.4 * cfg.legLen) inst.style = 'lunge';
|
|
362
|
-
}
|
|
363
|
-
if (inst.style === 'spur') {
|
|
364
|
-
// face off (hackles up, head low) for a moment, then a flapping jump-kick
|
|
365
|
-
inst.dur = 4; inst.hitAt = -1; inst.windup = 0.3 * cfg.sq;
|
|
366
|
-
if (inst.target) e.input.heading = Math.atan2(inst.target.x - e.pos.x, inst.target.z - e.pos.z);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
inst.dur = inst.style === 'peck' ? 0.75 : inst.style === 'talons' ? 0.9 : 1.4;
|
|
370
|
-
inst.hitAt = inst.style === 'peck' ? 0.34 : inst.style === 'talons' ? 0.45 : -1;
|
|
371
|
-
if (inst.style === 'lunge') {
|
|
372
|
-
let dist = 0.8 * cfg.legLen * 2;
|
|
373
|
-
if (inst.target) dist = clamp(Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z) - 0.8 * cfg.legLen, 0.05, 4 * cfg.legLen);
|
|
374
|
-
const vy = Math.sqrt(2 * 9.81 * 0.75 * 0.12 * cfg.s), T = 2 * vy / (9.81 * 0.75);
|
|
375
|
-
if (inst.target) e.input.heading = Math.atan2(inst.target.x - e.pos.x, inst.target.z - e.pos.z);
|
|
376
|
-
e.startTakeoff({ hopOnly: true, vy, vh: Math.max(e.speed, dist / T), quick: true });
|
|
377
|
-
inst.jumped = true;
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
update(inst, dt, L) {
|
|
381
|
-
if (!inst.hit && inst.hitAt > 0 && inst.t >= inst.hitAt) this.strike(inst, L);
|
|
382
|
-
if (inst.style === 'spur') {
|
|
383
|
-
const e = L.engine, cfg = e.cfg;
|
|
384
|
-
if (!inst.jumped && inst.t >= inst.windup) {
|
|
385
|
-
inst.jumped = true;
|
|
386
|
-
if (e.mode !== 'ground') return true;
|
|
387
|
-
let dist = 0.9 * cfg.legLen;
|
|
388
|
-
if (inst.target) dist = clamp(Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z) - 0.9 * cfg.legLen, 0.03, 3 * cfg.legLen);
|
|
389
|
-
const h = (inst.tune.height ?? 0.3) * cfg.s, vy = Math.sqrt(2 * 9.81 * 0.8 * h), T = 2 * vy / (9.81 * 0.7);
|
|
390
|
-
e.startTakeoff({ hopOnly: true, vy, vh: Math.max(e.speed, dist / T), quick: true });
|
|
391
|
-
}
|
|
392
|
-
// the kick lands at the top of the jump
|
|
393
|
-
if (inst.jumped && !inst.hit && e.to && e.to.hopOnly && e.to.air && e.vy <= 0.15) this.strike(inst, L);
|
|
394
|
-
if (inst.landT !== undefined && inst.t - inst.landT > 0.5) return true;
|
|
395
|
-
return inst.t > inst.dur;
|
|
396
|
-
}
|
|
397
|
-
if (inst.style === 'lunge') {
|
|
398
|
-
if (inst.landT !== undefined && inst.t - inst.landT > 0.45) return true;
|
|
399
|
-
return inst.t > 4;
|
|
400
|
-
}
|
|
401
|
-
return inst.t >= inst.dur;
|
|
402
|
-
},
|
|
403
|
-
onLand(inst, L) {
|
|
404
|
-
if (inst.style === 'spur' && inst.jumped && inst.landT === undefined) { inst.landT = inst.t; return; }
|
|
405
|
-
if (inst.style !== 'lunge' || inst.landT !== undefined) return;
|
|
406
|
-
inst.landT = inst.t;
|
|
407
|
-
inst.hitAt = inst.t + 0.12;
|
|
408
|
-
void L;
|
|
409
|
-
},
|
|
410
|
-
strike(inst, L) {
|
|
411
|
-
const e = L.engine;
|
|
412
|
-
inst.hit = true;
|
|
413
|
-
let p;
|
|
414
|
-
if (inst.style === 'talons' || inst.style === 'spur') p = e.legs[0].mtp.clone().lerp(e.legs[1].mtp, 0.5);
|
|
415
|
-
else p = new THREE.Vector3().copy(e.cfg.billLocal).applyQuaternion(e.headPose.quaternion).add(e.headPose.position);
|
|
416
|
-
const dir = inst.target ? inst.target.clone().sub(e.headPose.position).setY(0).normalize() : e.forward(e.heading, new THREE.Vector3());
|
|
417
|
-
e.emit('attackHit', { position: p, direction: dir, style: inst.style });
|
|
418
|
-
},
|
|
419
|
-
apply(P, w, inst, L) {
|
|
420
|
-
const e = L.engine, cfg = e.cfg, t = inst.t, s = cfg.s;
|
|
421
|
-
if (inst.target) { P.look = inst.target; mx(P, 'lookW', 1, w); }
|
|
422
|
-
if (inst.style === 'peck') {
|
|
423
|
-
const draw = pulse(t, 0.0, 0.2, 0.3), strike = pulse(t, 0.24, 0.34, 0.6);
|
|
424
|
-
mx(P, 'headUp', 0.02, draw * w); mx(P, 'neckReach', -0.02, draw * w);
|
|
425
|
-
mx(P, 'fwd', 0.035, strike * w); mx(P, 'pitch', -0.35, strike * w);
|
|
426
|
-
mx(P, 'neckReach', 0.05, strike * w); mx(P, 'headUp', -0.035, strike * w); mx(P, 'headPitch', 0.55, strike * w);
|
|
427
|
-
mx(P, 'jaw', 0.35, w * pulse(t, 0.18, 0.28, 0.36));
|
|
428
|
-
mx(P, 'wingOpen', 0.35, w * pulse(t, 0.05, 0.3, 0.7)); mx(P, 'wingElev', 25 * DEG, w * pulse(t, 0.05, 0.3, 0.7));
|
|
429
|
-
mx(P, 'tailFan', 0.6, w * pulse(t, 0.05, 0.3, 0.75)); mx(P, 'fluff', 0.5, w * pulse(t, 0, 0.2, 0.7));
|
|
430
|
-
mx(P, 'headOmega', 1.6, w);
|
|
431
|
-
void s;
|
|
432
|
-
} else if (inst.style === 'lunge') {
|
|
433
|
-
const air = e.to && e.to.hopOnly;
|
|
434
|
-
const k = air ? 1 : inst.landT !== undefined ? 1 - smooth(0, 0.45, t - inst.landT) : smooth(0, 0.1, t);
|
|
435
|
-
// (wings raised: the open wing tips stay clear of the ground through the landing crouch)
|
|
436
|
-
mx(P, 'wingOpen', 1, k * w); mx(P, 'wingElev', 30 * DEG, k * w); mx(P, 'flap', air ? 1 : 0, w); mx(P, 'tailFan', 0.8, k * w);
|
|
437
|
-
mx(P, 'neckReach', 0.04, k * w); mx(P, 'headPitch', 0.35, k * w);
|
|
438
|
-
if (inst.landT !== undefined) mx(P, 'headPitch', 0.6, pulse(t - inst.landT, 0, 0.12, 0.3) * w);
|
|
439
|
-
mx(P, 'jaw', 0.3, w * (inst.landT !== undefined ? pulse(t - inst.landT, 0, 0.08, 0.2) : 0));
|
|
440
|
-
} else if (inst.style === 'spur') {
|
|
441
|
-
const air = e.to && e.to.hopOnly;
|
|
442
|
-
const wind = inst.jumped ? 1 : smooth(0, inst.windup, t);
|
|
443
|
-
const fold = inst.landT !== undefined ? 1 - smooth(0, 0.45, t - inst.landT) : 1;
|
|
444
|
-
// face-off: hackles and body feathers raised, head low and forward, wings slightly out
|
|
445
|
-
mx(P, 'fluff', 1, w * fold); mx(P, 'headUp', -0.03, w * wind * (air ? 0.3 : 1) * fold); mx(P, 'neckReach', 0.02, w * wind * fold);
|
|
446
|
-
mx(P, 'headPitch', 0.25, w * fold); mx(P, 'tailFan', 0.7, w * fold);
|
|
447
|
-
mx(P, 'wingOpen', air ? 1 : 0.25 + 0.75 * (inst.landT !== undefined ? fold : 0), w);
|
|
448
|
-
mx(P, 'wingElev', 30 * DEG, w * (air ? 1 : fold * (inst.landT !== undefined ? 1 : 0)));
|
|
449
|
-
mx(P, 'flap', air ? 1 : 0, w); mx(P, 'flapAmp', 0.95, w);
|
|
450
|
-
if (air) {
|
|
451
|
-
// both feet thrown forward and up, toes open, chest back, until just after the top
|
|
452
|
-
const kick = smooth(0.02, 0.12, e.to.tAir || 0) * (1 - smooth(-0.2, -0.9, e.vy));
|
|
453
|
-
mx(P, 'legFree', 1, w * kick); mx(P, 'legReach', 1, w * kick); mx(P, 'toeCurl', -30 * DEG, w * kick);
|
|
454
|
-
mx(P, 'pitch', 0.45, w * kick);
|
|
455
|
-
}
|
|
456
|
-
void s;
|
|
457
|
-
} else {
|
|
458
|
-
// talons: feet thrown forward and down, head up
|
|
459
|
-
const k = pulse(t, 0.05, 0.4, 0.85);
|
|
460
|
-
mx(P, 'legFree', 1, k * w); mx(P, 'legReach', 1, k * w); mx(P, 'toeCurl', -25 * DEG, k * w);
|
|
461
|
-
mx(P, 'tailFan', 0.8, k * w); mx(P, 'pitch', 0.5, k * w);
|
|
462
|
-
if (inst.tune.mantle && !e.airborne) {
|
|
463
|
-
// raptor strike on the ground: wings thrown open and forward (balance), then mantled over
|
|
464
|
-
// the prey (drooped and spread forward), head low over the feet
|
|
465
|
-
const m = inst.tune.mantle, open = smooth(0.0, 0.25, t) * w, hold = smooth(0.45, 0.8, t) * w;
|
|
466
|
-
mx(P, 'wingOpen', 0.95 * m, open); mx(P, 'wingElev', 22 * DEG, open * (1 - hold)); mx(P, 'wingAlpha', -18 * DEG * m, open);
|
|
467
|
-
mx(P, 'wingDroop', 0.45 * m, hold); mx(P, 'headUp', -0.03, hold); mx(P, 'neckReach', 0.02, hold); mx(P, 'headPitch', 0.5, hold);
|
|
468
|
-
mx(P, 'fluff', 0.6, open); mx(P, 'jaw', 0.25, open * (1 - hold));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
},
|
|
472
|
-
},
|
|
473
|
-
|
|
474
|
-
hit: {
|
|
475
|
-
start(inst, L) {
|
|
476
|
-
const e = L.engine;
|
|
477
|
-
const d = inst.opts.direction ? new THREE.Vector3(inst.opts.direction.x, 0, inst.opts.direction.z) : e.forward(e.heading, new THREE.Vector3()).negate();
|
|
478
|
-
if (d.lengthSq() < 1e-6) d.set(0, 0, -1);
|
|
479
|
-
d.normalize();
|
|
480
|
-
inst.dur = 0.8;
|
|
481
|
-
inst.lx = d.dot(e.left(e.heading, new THREE.Vector3()));
|
|
482
|
-
inst.lz = d.dot(e.forward(e.heading, new THREE.Vector3()));
|
|
483
|
-
e.push.addScaledVector(d, 0.9 * e.cfg.sq * (inst.opts.strength ?? 1));
|
|
484
|
-
},
|
|
485
|
-
update(inst) { return inst.t >= inst.dur; },
|
|
486
|
-
apply(P, w, inst) {
|
|
487
|
-
const t = inst.t;
|
|
488
|
-
const k = pulse(t, 0, 0.08, 0.6) * w;
|
|
489
|
-
mx(P, 'roll', -inst.lx * 0.3, k); mx(P, 'pitch', inst.lz * 0.2, k);
|
|
490
|
-
mx(P, 'fluff', 1, k); mx(P, 'wingOpen', 0.55, k); mx(P, 'wingElev', 35 * DEG, k); mx(P, 'flap', 0.6, pulse(t, 0.05, 0.15, 0.45) * w);
|
|
491
|
-
mx(P, 'tailFan', 0.8, k); mx(P, 'headSide', inst.lx * 0.02, k); mx(P, 'headUp', 0.015, k);
|
|
492
|
-
mx(P, 'headYaw', -inst.lx * 0.5, k); mx(P, 'eyelid', 0.5, pulse(t, 0, 0.05, 0.25) * w); mx(P, 'jaw', 0.25, k);
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
|
|
496
|
-
eat: {
|
|
497
|
-
tear(P, w, inst, L) {
|
|
498
|
-
// (eat style 'tear'): raptors pin the food under the feet and tear it with the bill: the bill
|
|
499
|
-
// grips between the feet, the head pulls up and back, the wings mantle (spread and drooped
|
|
500
|
-
// forward over the food), the tail fans; short looks around between bites
|
|
501
|
-
const e = L.engine, s = e.cfg.s;
|
|
502
|
-
const rate = inst.tune.rate ?? 0.8;
|
|
503
|
-
const burst = Math.sin(inst.t * 0.45) > -0.7 ? 1 : 0;
|
|
504
|
-
const c = (inst.t * rate) % 1;
|
|
505
|
-
// grip (0 .. 0.35), pull up and back (0.35 .. 0.7), swallow / hold (0.7 .. 1)
|
|
506
|
-
const down = burst * smooth(0.0, 0.3, c) * (1 - smooth(0.4, 0.75, c));
|
|
507
|
-
const pull = burst * smooth(0.35, 0.6, c) * (1 - smooth(0.75, 1.0, c));
|
|
508
|
-
// (the food lies just ahead of the breast: an upright raptor leans well forward over it)
|
|
509
|
-
const pt = groundAhead(e, inst.tune.reach ?? 0.95, tv(), false);
|
|
510
|
-
const hp = billTo(e, pt, 1.2, 0, tv());
|
|
511
|
-
hp.y += (1 - down) * 0.07 * s + pull * 0.03 * s;
|
|
512
|
-
hp.addScaledVector(e.F, -pull * 0.03 * s);
|
|
513
|
-
P.headPos.copy(hp);
|
|
514
|
-
mx(P, 'headW', 1, w); mx(P, 'headPitch', 1.25 - 0.45 * pull, w); mx(P, 'lookW', 0, w); mx(P, 'headOmega', 1.2, w);
|
|
515
|
-
mx(P, 'pitch', -0.72, w); mx(P, 'drop', 0.1, w); mx(P, 'gaitW', 0.3, w); mx(P, 'speedScale', 0, w); mx(P, 'restep', 0, w);
|
|
516
|
-
mx(P, 'jaw', 0.22, w * smooth(0.1, 0.3, c) * (1 - smooth(0.33, 0.4, c)) * burst);
|
|
517
|
-
// mantle
|
|
518
|
-
const mw = w * (inst.tune.mantle ?? 1);
|
|
519
|
-
mx(P, 'wingOpen', 0.9, mw); mx(P, 'wingDroop', 1.4, mw); mx(P, 'wingAlpha', -20 * DEG, mw); mx(P, 'wingElev', -4 * DEG, mw);
|
|
520
|
-
mx(P, 'tailFan', 0.5, mw); mx(P, 'tailPitch', 0.1, w); mx(P, 'fluff', 0.35, w);
|
|
521
|
-
P.busyGround = true;
|
|
522
|
-
},
|
|
523
|
-
start(inst) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 6); inst.fade = 0.35; },
|
|
524
|
-
update(inst) { return inst.t >= inst.dur; },
|
|
525
|
-
apply(P, w, inst, L) {
|
|
526
|
-
if (inst.tune.style === 'scratch') { scratchFeed(P, w, inst, L); return; }
|
|
527
|
-
const e = L.engine, cfg = e.cfg, s = cfg.s;
|
|
528
|
-
if (inst.tune.style === 'tear') { this.tear(P, w, inst, L); return; }
|
|
529
|
-
const rate = inst.tune.rate ?? 2.4;
|
|
530
|
-
// bursts of pecks separated by short looks around
|
|
531
|
-
const burst = Math.sin(inst.t * 0.9) > -0.55 ? 1 : 0;
|
|
532
|
-
const c = (inst.t * rate) % 1;
|
|
533
|
-
const strike = burst * (smooth(0.3, 0.5, c) * (1 - smooth(0.55, 0.85, c)));
|
|
534
|
-
// (a peck strikes with a steep bill: at least 0.95 rad down - a flatter one put the head on the
|
|
535
|
-
// ground with the neck folded over it)
|
|
536
|
-
const pitch = Math.max(0.95, inst.tune.billPitch ?? 1.05);
|
|
537
|
-
const fit = fitPeck(e, inst.tune.reach ?? 1.12, pitch, inst.tune.bodyPitch ?? -0.45, inst.tune.drop ?? 0.32);
|
|
538
|
-
const pt = groundAhead(e, fit.reach * (1 + 0.05 * Math.sin(Math.floor(inst.t * rate) * 2.3)), tv(), false);
|
|
539
|
-
pt.addScaledVector(e.Lf, 0.02 * s * Math.sin(Math.floor(inst.t * rate) * 1.7));
|
|
540
|
-
const hp = billTo(e, pt, pitch, 0, tv());
|
|
541
|
-
hp.y += (1 - strike) * 0.035 * s;
|
|
542
|
-
P.headPos.copy(hp);
|
|
543
|
-
mx(P, 'headW', 1, w); mx(P, 'headPitch', pitch, w); mx(P, 'lookW', 0, w); mx(P, 'headOmega', 2.6, w);
|
|
544
|
-
// body tipped forward moderately (detail_feeding: near horizontal), the neck reaches down
|
|
545
|
-
mx(P, 'pitch', fit.pitch, w); mx(P, 'drop', fit.drop, w); mx(P, 'gaitW', 0.3, w); mx(P, 'speedScale', 0, w); mx(P, 'restep', 0, w);
|
|
546
|
-
mx(P, 'jaw', 0.18, w * smooth(0.25, 0.42, c) * (1 - smooth(0.45, 0.6, c)) * burst);
|
|
547
|
-
mx(P, 'tailPitch', -0.35, w); // counter-pitched: the tail stays near level
|
|
548
|
-
P.busyGround = true;
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
|
|
552
|
-
drink: {
|
|
553
|
-
start(inst) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 5.4); inst.fade = 0.4; },
|
|
554
|
-
update(inst) { return inst.t >= inst.dur; },
|
|
555
|
-
apply(P, w, inst, L) {
|
|
556
|
-
const e = L.engine, s = e.cfg.s;
|
|
557
|
-
// dip the bill (0 .. 0.45 of a cycle), then lift the head and tilt it back to swallow
|
|
558
|
-
const T = 1.8, c = (inst.t % T) / T;
|
|
559
|
-
const dip = smooth(0.0, 0.18, c) * (1 - smooth(0.42, 0.58, c));
|
|
560
|
-
const tilt = smooth(0.52, 0.66, c) * (1 - smooth(0.9, 1.0, c));
|
|
561
|
-
const et = L.tuning.eat || {};
|
|
562
|
-
const bp = et.billPitch !== undefined ? et.billPitch + 0.15 : 1.15;
|
|
563
|
-
const fit = fitPeck(e, et.reach ?? 1.12, bp, et.bodyPitch ?? -0.45, et.drop ?? 0.3, 0, true);
|
|
564
|
-
const pt = groundAhead(e, fit.reach, tv(), true);
|
|
565
|
-
const hp = billTo(e, pt, bp, 0, tv());
|
|
566
|
-
hp.y += 0.004 * s;
|
|
567
|
-
P.headPos.copy(hp);
|
|
568
|
-
mx(P, 'headW', 1, w * dip); mx(P, 'headPitch', bp, w * dip);
|
|
569
|
-
mx(P, 'pitch', fit.pitch, w * Math.max(dip, 0.3)); mx(P, 'drop', fit.drop, w); mx(P, 'tailPitch', -0.3, w * Math.max(dip, 0.3));
|
|
570
|
-
mx(P, 'headPitch', -0.75, w * tilt); mx(P, 'headUp', 0.02, w * tilt); mx(P, 'neckReach', -0.01, w * tilt);
|
|
571
|
-
mx(P, 'jaw', 0.08, w * tilt);
|
|
572
|
-
mx(P, 'lookW', 0, w); mx(P, 'headOmega', 1.8, w); mx(P, 'gaitW', 0.3, w); mx(P, 'speedScale', 0, w); mx(P, 'restep', 0, w);
|
|
573
|
-
P.busyGround = true;
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
|
|
577
|
-
takeoff: {
|
|
578
|
-
start(inst, L) {
|
|
579
|
-
const e = L.engine;
|
|
580
|
-
inst.dur = Infinity;
|
|
581
|
-
if (e.airborne) { inst.done = true; return; }
|
|
582
|
-
e.startTakeoff({});
|
|
583
|
-
L.loiter = true;
|
|
584
|
-
},
|
|
585
|
-
update(inst, dt, L) { return inst.done || L.engine.mode === 'air' || inst.t > 6; },
|
|
586
|
-
apply(P, w) { void P; void w; },
|
|
587
|
-
},
|
|
588
|
-
|
|
589
|
-
land: {
|
|
590
|
-
start(inst, L) {
|
|
591
|
-
const e = L.engine;
|
|
592
|
-
inst.dur = Infinity;
|
|
593
|
-
L.loiter = false;
|
|
594
|
-
if (e.mode === 'takeoff' && !e.to.air) { e.mode = 'ground'; e.to = null; inst.done = true; return; }
|
|
595
|
-
if (!e.airborne && e.mode !== 'takeoff') { inst.done = true; return; }
|
|
596
|
-
if (e.mode === 'takeoff' && e.to && e.to.hopOnly) { inst.done = false; return; }
|
|
597
|
-
e.startLanding(inst.opts.target || null, 'land');
|
|
598
|
-
},
|
|
599
|
-
update(inst, dt, L) { return inst.done || (!L.engine.airborne && L.engine.mode !== 'takeoff') || inst.t > 30; },
|
|
600
|
-
onLand(inst) { inst.done = true; },
|
|
601
|
-
apply() {},
|
|
602
|
-
},
|
|
603
|
-
|
|
604
|
-
glide: {
|
|
605
|
-
start(inst, L) {
|
|
606
|
-
const e = L.engine;
|
|
607
|
-
inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 3);
|
|
608
|
-
if (e.mode === 'ground' || e.mode === 'perch') { e.startTakeoff({}); L.loiter = true; inst.wait = true; }
|
|
609
|
-
},
|
|
610
|
-
update(inst, dt, L) {
|
|
611
|
-
const e = L.engine;
|
|
612
|
-
if (inst.wait) { if (e.mode === 'air') { inst.wait = false; inst.t0 = inst.t; } else if (inst.t > 6) return true; return false; }
|
|
613
|
-
e.fl.forceGlide = 1;
|
|
614
|
-
if (inst.t - (inst.t0 || 0) >= inst.dur || !e.airborne) { e.fl.forceGlide = 0; return true; }
|
|
615
|
-
return false;
|
|
616
|
-
},
|
|
617
|
-
end(inst, L) { L.engine.fl.forceGlide = 0; },
|
|
618
|
-
apply() {},
|
|
619
|
-
},
|
|
620
|
-
|
|
621
|
-
flap: {
|
|
622
|
-
start(inst, L) {
|
|
623
|
-
const e = L.engine;
|
|
624
|
-
inst.air = e.airborne;
|
|
625
|
-
inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? (inst.air ? 1.6 : 1.3));
|
|
626
|
-
},
|
|
627
|
-
update(inst, dt, L) {
|
|
628
|
-
const e = L.engine;
|
|
629
|
-
if (inst.air) { e.fl.forceFlap = 1; if (inst.t >= inst.dur || !e.airborne) { e.fl.forceFlap = 0; return true; } return false; }
|
|
630
|
-
return inst.t >= inst.dur;
|
|
631
|
-
},
|
|
632
|
-
end(inst, L) { L.engine.fl.forceFlap = 0; },
|
|
633
|
-
apply(P, w, inst, L) {
|
|
634
|
-
if (inst.air) return;
|
|
635
|
-
// wing display on the ground: open, raise and beat (drying, threat, stretching the wings)
|
|
636
|
-
const k = smooth(0, 0.2, inst.t) * (1 - smooth(inst.dur - 0.3, inst.dur, inst.t)) * w;
|
|
637
|
-
mx(P, 'wingOpen', 1, k); mx(P, 'flap', 1, k); mx(P, 'flapAmp', 0.8, k); mx(P, 'wingElev', 15 * DEG, k);
|
|
638
|
-
mx(P, 'tailFan', 0.5, k); mx(P, 'pitch', 0.15, k); mx(P, 'fluff', 0.3, k); P.busyGround = true;
|
|
639
|
-
void L;
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
// Scratch feeding (fowl): per cycle two rakes of one foot and one or two of the other (each foot
|
|
645
|
-
// reaches forward, rakes back along the ground and returns to its print), the head low watching the
|
|
646
|
-
// spot; then the bird leans back a little and pecks 1-3 times at the cleared ground and looks up.
|
|
647
|
-
// Everything is a smooth function of the cycle time, so the feet and head move without steps.
|
|
648
|
-
const RAKE = 0.34;
|
|
649
|
-
function scratchFeed(P, w, inst, L) {
|
|
650
|
-
const e = L.engine, cfg = e.cfg, s = cfg.s, sq = cfg.sq;
|
|
651
|
-
const C = 3.8 * sq; // cycle length
|
|
652
|
-
const n = Math.floor(inst.t / C), tc = inst.t - n * C;
|
|
653
|
-
const h = (k) => { const x = Math.sin((n + 1) * 12.9898 + k * 78.233) * 43758.5453; return x - Math.floor(x); };
|
|
654
|
-
const lead = h(1) < 0.5 ? 0 : 1; // which foot scratches first this cycle
|
|
655
|
-
const rakes = [[0.3, lead], [0.3 + RAKE, lead], [0.3 + 2 * RAKE + 0.06, 1 - lead]];
|
|
656
|
-
if (h(2) < 0.6) rakes.push([0.3 + 3 * RAKE + 0.06, 1 - lead]);
|
|
657
|
-
const rs = sq; // durations scale with sqrt(size)
|
|
658
|
-
const on = smooth(0, 0.15, inst.t); // (the first cycle eases in)
|
|
659
|
-
for (const [t0, leg] of rakes) {
|
|
660
|
-
const u = (tc - t0 * rs) / (RAKE * rs);
|
|
661
|
-
if (u <= 0 || u >= 1) continue;
|
|
662
|
-
P.scratchW[leg] = w * on; P.scratchU[leg] = u;
|
|
663
|
-
}
|
|
664
|
-
// weight over the standing foot, shifting across as the other foot takes over (continuous)
|
|
665
|
-
const shiftEnv = smooth(0.15 * rs, 0.3 * rs, tc) * (1 - smooth((0.3 + rakes.length * RAKE) * rs, (0.5 + rakes.length * RAKE) * rs, tc));
|
|
666
|
-
const swap = smooth((0.3 + 2 * RAKE - 0.05) * rs, (0.3 + 2 * RAKE + 0.11) * rs, tc);
|
|
667
|
-
const sideT = lerp(lead === 0 ? -1 : 1, lead === 0 ? 1 : -1, swap);
|
|
668
|
-
mx(P, 'side', 0.01 * sideT * shiftEnv, w * on);
|
|
669
|
-
// pecking phase after the rakes
|
|
670
|
-
const tp0 = (0.5 + rakes.length * RAKE) * rs, nPeck = 1 + Math.floor(h(3) * 3), pr = 0.36 * rs;
|
|
671
|
-
const peckW = smooth(tp0 - 0.2 * rs, tp0 + 0.05 * rs, tc) * (1 - smooth(tp0 + nPeck * pr, tp0 + nPeck * pr + 0.35 * rs, tc));
|
|
672
|
-
const pc = clamp((tc - tp0) / pr, 0, nPeck);
|
|
673
|
-
const cph = pc - Math.floor(pc);
|
|
674
|
-
const strike = pc < nPeck ? smooth(0.25, 0.5, cph) * (1 - smooth(0.55, 0.9, cph)) : 0;
|
|
675
|
-
// head: low over the scratched spot, striking during the pecks
|
|
676
|
-
// (the pecks reach the ground: the body tips and crouches and the pecks come in as far as the neck
|
|
677
|
-
// needs, see fitPeck; the head hovers over the same spot while the feet rake)
|
|
678
|
-
const fit = fitPeck(e, 1.25, 1.05, -0.8, 0.22);
|
|
679
|
-
const pP = fit.pitch, dP = fit.drop, rP = fit.reach;
|
|
680
|
-
const fitR = fitPeck(e, rP, 0.8, -0.3, 0.14, 0.075 * s); // (raking: the head hovers over the spot)
|
|
681
|
-
const pR = fitR.pitch, dR = fitR.drop, rR = fitR.reach;
|
|
682
|
-
const pt = groundAhead(e, lerp(rR, rP, peckW) * (1 + 0.04 * Math.sin(n * 2.1)), tv(), false);
|
|
683
|
-
pt.addScaledVector(e.Lf, 0.012 * s * Math.sin(n * 1.7 + Math.floor(pc) * 2.3));
|
|
684
|
-
const look = smooth(0, 0.3 * rs, tc) * (1 - smooth(C - 0.45 * rs, C, tc)); // looks up between cycles
|
|
685
|
-
const pitch = lerp(0.8, 1.05, peckW);
|
|
686
|
-
const hp = billTo(e, pt, pitch, 0, tv());
|
|
687
|
-
hp.y += (lerp(0.075, 0.034, peckW) * (1 - strike * peckW)) * s;
|
|
688
|
-
P.headPos.copy(hp);
|
|
689
|
-
const hw = w * lerp(0.35, 1, look) * on;
|
|
690
|
-
mx(P, 'headW', 1, hw); mx(P, 'headPitch', pitch, hw); mx(P, 'lookW', 0, w * look); mx(P, 'headOmega', lerp(1.6, 2.6, peckW), w);
|
|
691
|
-
mx(P, 'pitch', lerp(pR, pP, peckW) * lerp(0.4, 1, look), w * on); mx(P, 'drop', lerp(dR, dP, peckW), w * on);
|
|
692
|
-
mx(P, 'fwd', -0.012 * peckW, w * on);
|
|
693
|
-
mx(P, 'gaitW', 0.3, w); mx(P, 'speedScale', 0, w); mx(P, 'restep', 0, w);
|
|
694
|
-
mx(P, 'jaw', 0.18, w * strike * peckW);
|
|
695
|
-
mx(P, 'tailPitch', 0.12, w);
|
|
696
|
-
P.busyGround = true;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function compact(arr, keep) {
|
|
700
|
-
let j = 0;
|
|
701
|
-
for (let i = 0; i < arr.length; i++) if (keep(arr[i])) arr[j++] = arr[i];
|
|
702
|
-
arr.length = j;
|
|
703
|
-
}
|
|
704
|
-
const keepPosture = (p) => p.target > 0 || p.w > 0;
|
|
705
|
-
const keepOneshot = (a) => !(a.stopping && a.w <= 0);
|
|
706
|
-
const isHolding = (p) => (p.target > 0 || p.w > 0.02) && p.name !== 'perch';
|
|
707
|
-
const isRunning = (a) => !a.stopping;
|
|
708
|
-
const isTargeted = (p) => p.target > 0;
|
|
709
|
-
const isBusy = (a) => !a.stopping && a.name !== 'hit';
|
|
710
|
-
const isDead = (p) => p.name === 'death' && p.target > 0;
|
|
711
|
-
// flight commands: locomotion, not body actions (see the arbitration rule above)
|
|
712
|
-
const FLIGHT = new Set(['takeoff', 'land', 'glide', 'flap']);
|
|
713
|
-
const GROUND_ONLY = new Set(['eat', 'drink', 'jump']);
|
|
714
|
-
|
|
715
|
-
// idle behaviours: [name, weight, duration]
|
|
716
|
-
const IDLE = [['preen', 0.2, 2.2], ['wingFlick', 0.18, 0.35], ['tailFlick', 0.14, 0.4], ['shake', 0.1, 0.8], ['cock', 0.25, 1.6], ['stretch', 0.06, 1.8], ['bill', 0.07, 1.2]];
|
|
717
|
-
// species extras (species.motion.actions.idle = { name: weight }): durations
|
|
718
|
-
const IDLE_EXTRA = { crow: 3.4 };
|
|
719
|
-
|
|
720
|
-
// ------------------------------------------------------------------------------------------------
|
|
721
|
-
export class BirdActions {
|
|
722
|
-
constructor(engine) {
|
|
723
|
-
this.engine = engine;
|
|
724
|
-
this.P = createBirdParams();
|
|
725
|
-
this.tuning = engine.cfg.actions || {};
|
|
726
|
-
this.names = ['idle', 'jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand', 'takeoff', 'land', 'perch', 'glide', 'flap'];
|
|
727
|
-
this.postures = [];
|
|
728
|
-
this.oneshots = [];
|
|
729
|
-
this.posture = 'stand';
|
|
730
|
-
this.standReq = null;
|
|
731
|
-
this.loiter = false;
|
|
732
|
-
this.idle = { t: 0, lookT: 0, look: new THREE.Vector3(), hasLook: false, beh: null, behT: 0, behDur: 0, nextT: 3, side: 1, cock: 0, shiftPh: 0, want: null, flag: 0 };
|
|
733
|
-
this.idleList = IDLE.slice();
|
|
734
|
-
for (const [name, wgt] of Object.entries(this.tuning.idle || {})) if (IDLE_EXTRA[name] && wgt > 0) this.idleList.push([name, wgt, IDLE_EXTRA[name]]);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
holdsStill() { return this.postures.some(isHolding); }
|
|
738
|
-
keepsFlying() {
|
|
739
|
-
const e = this.engine;
|
|
740
|
-
if (this.loiter && (e.input.speed || 0) < 0.05) return true;
|
|
741
|
-
return this.oneshots.some((a) => !a.stopping && (a.name === 'glide' || a.name === 'flap' || a.name === 'land' || a.name === 'attack')) || this.postures.some((p) => p.name === 'perch' && p.phase === 'fly');
|
|
742
|
-
}
|
|
743
|
-
dead() { return this.postures.some(isDead); }
|
|
744
|
-
// automatic stand-up (the bird is asked to move): never revives a dead bird
|
|
745
|
-
requestStand() { if (!this.standReq && !this.dead()) this.play('stand'); }
|
|
746
|
-
// a hop under way (jump, lunge, spur kick: the engine is in a hop take-off): cannot be cut
|
|
747
|
-
committed(a) {
|
|
748
|
-
const e = this.engine;
|
|
749
|
-
return !a.stopping && (a.name === 'jump' || a.name === 'attack') && e.mode === 'takeoff' && !!e.to && !!e.to.hopOnly;
|
|
750
|
-
}
|
|
751
|
-
end(a, reason) {
|
|
752
|
-
if (a.stopping) return;
|
|
753
|
-
a.stopping = true; a.reason = reason;
|
|
754
|
-
ONESHOTS[a.name].end?.(a, this);
|
|
755
|
-
}
|
|
756
|
-
current() {
|
|
757
|
-
const o = this.oneshots.find(isRunning);
|
|
758
|
-
if (o) return o.name;
|
|
759
|
-
const p = this.postures.find(isTargeted);
|
|
760
|
-
return p ? p.name : this.standReq ? 'stand' : null;
|
|
761
|
-
}
|
|
762
|
-
clear() {
|
|
763
|
-
for (const a of [...this.postures, ...this.oneshots]) { a.resolve?.('stopped'); a.resolveIn?.('stopped'); }
|
|
764
|
-
this.postures.length = 0; this.oneshots.length = 0;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
play(name, opts = {}) {
|
|
768
|
-
const e = this.engine;
|
|
769
|
-
if (name === 'idle') {
|
|
770
|
-
// an idle behaviour on request (e.g. a rooster's crow), played as soon as the bird stands idle
|
|
771
|
-
const b = opts.behaviour && this.idleList.find((x) => x[0] === opts.behaviour);
|
|
772
|
-
if (b) { this.idle.want = b; if (!this.postures.length) return Promise.resolve(); }
|
|
773
|
-
name = 'stand';
|
|
774
|
-
}
|
|
775
|
-
if (name === 'stand') {
|
|
776
|
-
if (!this.postures.length) return Promise.resolve('done');
|
|
777
|
-
for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
|
|
778
|
-
e.emit('actionStart', { name: 'stand' });
|
|
779
|
-
if (this.standReq) return this.standReq.promise;
|
|
780
|
-
let resolve;
|
|
781
|
-
const promise = new Promise((r) => { resolve = r; });
|
|
782
|
-
this.standReq = { promise, resolve };
|
|
783
|
-
return promise;
|
|
784
|
-
}
|
|
785
|
-
if (POSTURES[name]) return this.playPosture(name, opts);
|
|
786
|
-
const def = ONESHOTS[name];
|
|
787
|
-
if (!def) return Promise.reject(new Error(`procedural-animals: unknown action "${name}"`));
|
|
788
|
-
if (this.dead() && name !== 'hit') return Promise.resolve('refused');
|
|
789
|
-
if (this.holdsStill() && (name === 'jump' || name === 'attack' || name === 'takeoff' || name === 'glide')) {
|
|
790
|
-
return this.play('stand').then((r) => (r === 'refused' ? r : this.play(name, opts)));
|
|
791
|
-
}
|
|
792
|
-
if (GROUND_ONLY.has(name) && (e.airborne || e.mode === 'takeoff')) return Promise.resolve('refused');
|
|
793
|
-
if (FLIGHT.has(name)) {
|
|
794
|
-
// flight commands replace their own kind only (glide <-> flap)
|
|
795
|
-
for (const a of this.oneshots) if (a.name === name || ((name === 'glide' || name === 'flap') && (a.name === 'glide' || a.name === 'flap'))) this.end(a, 'interrupted');
|
|
796
|
-
} else if (this.oneshots.some((a) => this.committed(a))) {
|
|
797
|
-
if (name !== 'hit') return Promise.resolve('refused');
|
|
798
|
-
for (const a of this.oneshots) if (a.name === 'hit') this.end(a, 'interrupted');
|
|
799
|
-
} else {
|
|
800
|
-
for (const a of this.oneshots) if (!FLIGHT.has(a.name)) this.end(a, 'interrupted');
|
|
801
|
-
}
|
|
802
|
-
const inst = { name, t: 0, w: 0, dur: 1, stopping: false, opts, tune: this.tuning[name] || {}, fade: 0.14, speed: opts.speed || 1 };
|
|
803
|
-
def.start(inst, this);
|
|
804
|
-
inst.promise = new Promise((r) => { inst.resolve = r; });
|
|
805
|
-
this.oneshots.push(inst);
|
|
806
|
-
e.emit('actionStart', { name });
|
|
807
|
-
return inst.promise;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
playPosture(name, opts) {
|
|
811
|
-
const e = this.engine;
|
|
812
|
-
const cur = this.postures.find((p) => p.target > 0);
|
|
813
|
-
if (cur && cur.name === 'death' && name !== 'death') return Promise.resolve('refused');
|
|
814
|
-
if (cur && cur.name === name && name !== 'perch') return cur.promiseIn;
|
|
815
|
-
if (name !== 'death' && this.oneshots.some((a) => this.committed(a))) return Promise.resolve('refused');
|
|
816
|
-
for (const p of this.postures) { if (p.target > 0) { p.target = 0; p.resolveIn?.('interrupted'); } }
|
|
817
|
-
if (this.standReq) { this.standReq.resolve('interrupted'); this.standReq = null; }
|
|
818
|
-
for (const a of this.oneshots) if (a.name !== 'hit' && (name === 'death' || !FLIGHT.has(a.name))) this.end(a, 'interrupted');
|
|
819
|
-
const def = POSTURES[name];
|
|
820
|
-
let side = opts.side ?? (e.rand() < 0.5 ? -1 : 1);
|
|
821
|
-
if (opts.direction && name === 'death') side = (opts.direction.x * Math.cos(e.heading) - opts.direction.z * Math.sin(e.heading)) > 0 ? -1 : 1;
|
|
822
|
-
const prev = this.postures.find((p) => p.name === 'lie' || p.name === 'sit' || p.name === 'sleep');
|
|
823
|
-
if (prev && opts.side === undefined) side = prev.side;
|
|
824
|
-
const inst = { name, t: 0, w: 0, target: 1, fadeIn: def.fadeIn, fadeOut: def.fadeOut, side, opts, tune: this.tuning[name] || {}, reached: false, phase: 'hold' };
|
|
825
|
-
if (prev && name === 'sleep') { inst.w = prev.w; prev.w = 0; prev.target = 0; this.postures = this.postures.filter((p) => p !== prev); }
|
|
826
|
-
inst.promiseIn = new Promise((r) => { inst.resolveIn = r; });
|
|
827
|
-
this.postures.push(inst);
|
|
828
|
-
e.emit('actionStart', { name });
|
|
829
|
-
if (name === 'death') {
|
|
830
|
-
e.emit('death', { position: e.pos.clone() });
|
|
831
|
-
// dead in the air or on a perch: falls limp (no lift) and collapses where it hits the ground
|
|
832
|
-
if (e.airborne || e.mode === 'perch' || (e.mode === 'takeoff' && e.to && e.to.air)) { e.fall(); inst.fell = true; }
|
|
833
|
-
}
|
|
834
|
-
if (name === 'perch') this.startPerch(inst);
|
|
835
|
-
return inst.promiseIn;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// perch: fly to the target (given, or the nearest known perch) and land gripping it
|
|
839
|
-
startPerch(inst) {
|
|
840
|
-
const e = this.engine, cfg = e.cfg;
|
|
841
|
-
let target = inst.opts.target || inst.opts.position || null;
|
|
842
|
-
if (!target && e.perches.length) {
|
|
843
|
-
let bd = Infinity;
|
|
844
|
-
for (const p of e.perches) { const d = p.distanceToSquared(e.pos); if (d < bd) { bd = d; target = p; } }
|
|
845
|
-
}
|
|
846
|
-
if (!target) { inst.phase = 'hold'; return; }
|
|
847
|
-
const perch = e.makePerch(target, inst.opts);
|
|
848
|
-
if (e.mode === 'perch' && e.perch && e.perch.point.distanceTo(perch.point) < 0.05 * cfg.s + 0.02) { inst.phase = 'hold'; return; }
|
|
849
|
-
inst.phase = 'fly';
|
|
850
|
-
inst.perch = perch;
|
|
851
|
-
if (e.airborne) e.startLanding(perch.point, 'perch', perch);
|
|
852
|
-
else e.startTakeoff({ then: { perch } });
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
stop(name) {
|
|
856
|
-
for (const a of this.oneshots) if ((!name || a.name === name) && !this.committed(a)) this.end(a, 'stopped');
|
|
857
|
-
if (!name || POSTURES[name]) {
|
|
858
|
-
if (this.postures.some((p) => p.target > 0 && (name ? p.name === name : p.name !== 'death'))) this.play('stand');
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
onLand(perch) {
|
|
863
|
-
for (const a of this.oneshots) if (ONESHOTS[a.name].onLand) ONESHOTS[a.name].onLand(a, this);
|
|
864
|
-
for (const p of this.postures) if (p.name === 'perch' && p.phase === 'fly') { p.phase = 'hold'; p.t = 0; p.w = Math.min(p.w, 0.2); }
|
|
865
|
-
if (perch) this.loiter = false;
|
|
866
|
-
}
|
|
867
|
-
onAirborne() {
|
|
868
|
-
const e = this.engine;
|
|
869
|
-
// a pending perch flight continues as a landing on the perch
|
|
870
|
-
for (const p of this.postures) if (p.name === 'perch' && p.phase === 'fly' && p.perch) { e.startLanding(p.perch.point, 'perch', p.perch); return; }
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
update(dt) {
|
|
874
|
-
const e = this.engine, P = this.P;
|
|
875
|
-
resetParams(P);
|
|
876
|
-
for (const p of this.postures) {
|
|
877
|
-
p.t += dt;
|
|
878
|
-
const rate = p.target > p.w ? 1 / p.fadeIn : 1 / p.fadeOut;
|
|
879
|
-
const tgt = p.phase === 'fly' ? 0 : p.target;
|
|
880
|
-
p.w = clamp(p.w + clamp(tgt - p.w, -rate * dt, rate * dt), 0, 1);
|
|
881
|
-
if (!p.reached && p.target > 0 && p.phase !== 'fly' && p.w >= 1 && (p.name !== 'death' || (p.t > 1.4 && !e.airborne && e.mode !== 'takeoff')) && (p.name !== 'sleep' || p.t > 2.6)) { p.reached = true; p.resolveIn('done'); e.emit('actionEnd', { name: p.name }); }
|
|
882
|
-
// a perch flight that never lands (e.g. interrupted) gives up
|
|
883
|
-
if (p.name === 'perch' && p.phase === 'fly' && p.t > 40) { p.phase = 'hold'; }
|
|
884
|
-
}
|
|
885
|
-
compact(this.postures, keepPosture);
|
|
886
|
-
const active = this.postures.find(isTargeted);
|
|
887
|
-
this.posture = active ? (active.name === 'death' ? 'dead' : active.name) : this.postures.some((p) => p.w > 0.02) ? 'standing-up' : 'stand';
|
|
888
|
-
if (this.standReq && !this.postures.some((p) => p.w > 0.02 && p.name !== 'perch')) { this.standReq.resolve('done'); this.standReq = null; e.emit('actionEnd', { name: 'stand' }); }
|
|
889
|
-
if (this.standReq) for (const p of this.postures) if (p.name === 'perch' && p.target <= 0) p.w = 0;
|
|
890
|
-
// loiter ends once the game steers the bird itself
|
|
891
|
-
if (this.loiter && (e.input.speed || 0) > 0.05) this.loiter = false;
|
|
892
|
-
|
|
893
|
-
// idle
|
|
894
|
-
const still = (e.mode === 'ground' || e.mode === 'perch') && e.speed < 0.05 * e.cfg.sq && !e.input.follow;
|
|
895
|
-
const idleTarget = still && !this.postures.some((p) => p.w > 0.02 && p.name !== 'perch') && !this.oneshots.some(isBusy) ? 1 : 0;
|
|
896
|
-
const id = this.idle;
|
|
897
|
-
id.w = clamp((id.w ?? 0) + clamp(idleTarget - (id.w ?? 0), -dt * 2, dt * 0.8), 0, 1);
|
|
898
|
-
this.applyIdle(P, id.w, dt);
|
|
899
|
-
|
|
900
|
-
for (const p of this.postures) {
|
|
901
|
-
const w = p.w * p.w * (3 - 2 * p.w);
|
|
902
|
-
POSTURES[p.name].apply(P, w, p, this);
|
|
903
|
-
}
|
|
904
|
-
const wantsToMove = e.wantSpeed > 0.05 || !!e.input.follow;
|
|
905
|
-
for (const a of this.oneshots) {
|
|
906
|
-
if (wantsToMove && (a.name === 'eat' || a.name === 'drink')) this.end(a, 'interrupted');
|
|
907
|
-
a.t += dt * a.speed;
|
|
908
|
-
const def = ONESHOTS[a.name];
|
|
909
|
-
if (!a.stopping && def.update(a, dt * a.speed, this)) this.end(a, 'done');
|
|
910
|
-
if (a.stopping) a.w = Math.max(0, a.w - dt / (a.fade || 0.14));
|
|
911
|
-
else a.w = Math.min(1, a.w + dt / (a.fade || 0.14));
|
|
912
|
-
const w = a.w * a.w * (3 - 2 * a.w);
|
|
913
|
-
if (w > 0) def.apply(P, w, a, this);
|
|
914
|
-
}
|
|
915
|
-
for (const a of this.oneshots) if (a.stopping && a.w <= 0) { a.resolve(a.reason || 'done'); e.emit('actionEnd', { name: a.name, reason: a.reason || 'done' }); }
|
|
916
|
-
compact(this.oneshots, keepOneshot);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
applyIdle(P, w, dt) {
|
|
920
|
-
const e = this.engine, id = this.idle, cfg = e.cfg, s = cfg.s;
|
|
921
|
-
if (w <= 0.001) { id.hasLook = false; id.beh = null; return; }
|
|
922
|
-
id.t += dt;
|
|
923
|
-
// weight shift
|
|
924
|
-
id.shiftPh += dt * 0.21;
|
|
925
|
-
const sh = Math.sin(id.shiftPh * 6.283) * 0.6 + Math.sin(id.shiftPh * 6.283 * 0.41 + 1.3) * 0.4;
|
|
926
|
-
mx(P, 'side', 0.004 * sh, w); mx(P, 'roll', -0.03 * sh, w);
|
|
927
|
-
// look around: saccades to random points, sometimes cocking the head to look with one eye
|
|
928
|
-
if (!e.input.look) {
|
|
929
|
-
id.lookT -= dt;
|
|
930
|
-
if (id.lookT <= 0) {
|
|
931
|
-
id.lookT = 0.5 + e.rand() * 1.8;
|
|
932
|
-
if (e.rand() < 0.2) id.hasLook = false;
|
|
933
|
-
else {
|
|
934
|
-
const a = e.heading + (e.rand() - 0.5) * 3.4;
|
|
935
|
-
const d = (1.5 + e.rand() * 8) * cfg.legLen * 4;
|
|
936
|
-
const up = e.rand() < 0.2 ? (1 + e.rand() * 6) : (e.rand() - 0.3) * 0.4;
|
|
937
|
-
id.look.set(e.pos.x + Math.sin(a) * d, e.pos.y + up * cfg.legLen * 4, e.pos.z + Math.cos(a) * d);
|
|
938
|
-
id.hasLook = true;
|
|
939
|
-
id.cock = e.rand() < 0.35 ? (e.rand() < 0.5 ? -1 : 1) : 0;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
if (id.hasLook) { P.look = id.look; P.lookW = w; P.cock = id.cock * w; }
|
|
943
|
-
}
|
|
944
|
-
// occasional behaviours (none while it turns on the spot: a wing stretched and the tail fanned
|
|
945
|
-
// through a stepping turn read as a stumble; a running one fades out, tw)
|
|
946
|
-
const turning = !!e.pivot || Math.abs(angDiff(e.heading, e.wantHeading ?? e.heading)) > 0.3;
|
|
947
|
-
id.tw = clamp((id.tw ?? 1) + (turning ? -4 : 2) * dt, 0, 1);
|
|
948
|
-
id.nextT -= dt;
|
|
949
|
-
if (!id.beh && id.want && w > 0.6) {
|
|
950
|
-
id.beh = id.want[0]; id.behDur = id.want[2]; id.want = null;
|
|
951
|
-
id.behT = 0; id.side = e.rand() < 0.5 ? -1 : 1; id.flag = e.rand(); id.nextT = 2.5 + e.rand() * 5;
|
|
952
|
-
}
|
|
953
|
-
if (!id.beh && id.nextT <= 0 && w > 0.95 && !turning) {
|
|
954
|
-
const list = this.idleList;
|
|
955
|
-
let r = e.rand(), tot = 0;
|
|
956
|
-
for (const b of list) tot += b[1];
|
|
957
|
-
r *= tot;
|
|
958
|
-
for (const b of list) { r -= b[1]; if (r <= 0) { id.beh = b[0]; id.behDur = b[2] * (b[0] === 'crow' ? 1 : 0.8 + e.rand() * 0.5); break; } }
|
|
959
|
-
id.behT = 0; id.side = e.rand() < 0.5 ? -1 : 1; id.flag = e.rand();
|
|
960
|
-
id.nextT = 2.5 + e.rand() * 5;
|
|
961
|
-
}
|
|
962
|
-
if (id.beh) {
|
|
963
|
-
id.behT += dt;
|
|
964
|
-
const t = id.behT, T = id.behDur, k = smooth(0, Math.min(0.25, T * 0.3), t) * (1 - smooth(T - Math.min(0.3, T * 0.35), T, t)) * w * smooth(0, 1, id.tw);
|
|
965
|
-
const sd = id.side;
|
|
966
|
-
switch (id.beh) {
|
|
967
|
-
case 'preen': {
|
|
968
|
-
// bill to the shoulder / wing coverts / breast, nibbling
|
|
969
|
-
// (the breast and the front of the shoulder: the neck arches forward and down, the bill turned
|
|
970
|
-
// back into the feathers)
|
|
971
|
-
// (species may place the spot: actions.preen = { spot: [x, y, z] body frame from the hip pivot,
|
|
972
|
-
// m at the reference size, x toward the preened side; pitch: head pitch, rad })
|
|
973
|
-
const pr = this.tuning.preen;
|
|
974
|
-
const ps = e._preen && !(pr && pr.spot) ? e._preen[sd > 0 ? 0 : 1] : null;
|
|
975
|
-
let hp, yaw = sd * 0.5, pitch = pr?.pitch ?? 1.25;
|
|
976
|
-
if (ps) {
|
|
977
|
-
// (default) the bill tip into the feathers at the side of the breast (measured on the skin),
|
|
978
|
-
// nibbling up and down along it; the head turned to that side, bill down. A neck short for its
|
|
979
|
-
// head (a chick's is half the head's length) cannot bend the head down into the breast's side
|
|
980
|
-
// without wrenching the skin between them: it preens the shoulder above instead, the head
|
|
981
|
-
// carried by the neck to a body-frame spot (nr: 0 for a neck under half the head's length, 1
|
|
982
|
-
// from 0.85)
|
|
983
|
-
const nr = clamp((cfg.neckArc / Math.max(1e-4, cfg.billLocal.length()) - 0.45) / 0.4, 0, 1);
|
|
984
|
-
yaw = sd * lerp(1.0, 0.75, nr); pitch = pr?.pitch ?? lerp(0.6, 1.0, nr);
|
|
985
|
-
const w = e.B(tv().copy(ps.p).addScaledVector(ps.n, 0.004 * s).add(tv(0, 0.012 * s * Math.sin(t * 1.3), 0.008 * s * Math.sin(t * 0.9))), tv());
|
|
986
|
-
hp = billTo(e, w, pitch, yaw, tv());
|
|
987
|
-
// (a short neck: the head turned toward the shoulder and lowered a little - 30 % of the way from
|
|
988
|
-
// its rest to a spot on the shoulder - the bill down 34 deg: on a chick, the full reach down
|
|
989
|
-
// into the breast put 2.4 % of its skin over 3x, this 0.2 %)
|
|
990
|
-
if (nr < 1) {
|
|
991
|
-
hp.lerp(e.B(tv(sd * 0.012 * s, cfg.headH - cfg.pivotY - 0.055 * s, 0.075 * s + 0.01 * s * Math.sin(t * 1.3)), tv()), 1 - nr);
|
|
992
|
-
hp.lerp(e.B(tv().copy(cfg.occLocal), tv()), (1 - nr) * 0.7);
|
|
993
|
-
}
|
|
994
|
-
} else {
|
|
995
|
-
const spot = pr && pr.spot ? tv(sd * pr.spot[0] * s, pr.spot[1] * s, pr.spot[2] * s + 0.01 * s * Math.sin(t * 1.3))
|
|
996
|
-
: tv(sd * 0.012 * s, cfg.headH - cfg.pivotY - 0.055 * s, 0.075 * s + 0.01 * s * Math.sin(t * 1.3));
|
|
997
|
-
hp = e.B(spot, tv());
|
|
998
|
-
}
|
|
999
|
-
P.headPos.lerp(hp, k / Math.max(1e-3, P.headW + k)); mx(P, 'headW', 1, k);
|
|
1000
|
-
mx(P, 'headYaw', yaw, k); mx(P, 'headPitch', pitch, k); mx(P, 'headRoll', sd * 0.2, k);
|
|
1001
|
-
mx(P, 'jaw', 0.12 * (0.5 + 0.5 * Math.sin(t * 22)), k); mx(P, 'lookW', 0, k);
|
|
1002
|
-
mx(P, 'fluff', 0.4, k); P.wingElevS[sd > 0 ? 0 : 1] += 8 * DEG * k;
|
|
1003
|
-
break;
|
|
1004
|
-
}
|
|
1005
|
-
case 'wingFlick': mx(P, 'wingOpen', 0.22, k); mx(P, 'wingElev', 12 * DEG, k); mx(P, 'wingOmega', 3, k); break;
|
|
1006
|
-
case 'tailFlick': mx(P, 'tailPitch', 0.25 * Math.sin(Math.min(1, t / T) * Math.PI * 2), w * smooth(0, 1, id.tw)); mx(P, 'tailFan', 0.45, k); break;
|
|
1007
|
-
case 'shake': {
|
|
1008
|
-
const sh2 = Math.sin(t * 38) * k;
|
|
1009
|
-
mx(P, 'fluff', 1, k); mx(P, 'roll', 0.12 * sh2, 1); mx(P, 'headRoll', 0.5 * sh2, 1); mx(P, 'wingOpen', 0.12, k); mx(P, 'tailFan', 0.3, k);
|
|
1010
|
-
break;
|
|
1011
|
-
}
|
|
1012
|
-
case 'cock': mx(P, 'headRoll', sd * 0.55, k); mx(P, 'headPitch', -0.15, k); break;
|
|
1013
|
-
case 'stretch': {
|
|
1014
|
-
// wing-and-leg stretch: one wing down and back
|
|
1015
|
-
P.wingElevS[sd > 0 ? 0 : 1] += (-10 * DEG - P.wingElevS[sd > 0 ? 0 : 1]) * k;
|
|
1016
|
-
mx(P, 'wingOpen', 0.45, k); mx(P, 'wingAlpha', 50 * DEG, k); mx(P, 'tailFan', 0.5, k); mx(P, 'wingOmega', 0.6, k);
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
case 'crow': {
|
|
1020
|
-
// rooster crowing: often a wing clap first, then the neck stretched up and forward, chest
|
|
1021
|
-
// out, bill wide open for the call (three syllables), and back
|
|
1022
|
-
const wt = w * smooth(0, 1, id.tw);
|
|
1023
|
-
const clap = id.flag < 0.6 ? pulse(t, 0.02, 0.18, 0.75) * wt : 0;
|
|
1024
|
-
mx(P, 'wingOpen', 0.9, clap); mx(P, 'flap', 1, clap); mx(P, 'flapAmp', 0.7, clap); mx(P, 'wingElev', 10 * DEG, clap);
|
|
1025
|
-
const st = smooth(0.55, 1.05, t) * (1 - smooth(T - 0.55, T, t)) * wt;
|
|
1026
|
-
mx(P, 'headUp', 0.07, st); mx(P, 'neckReach', 0.028, st); mx(P, 'headPitch', -0.8, st); mx(P, 'headW', 0, st);
|
|
1027
|
-
mx(P, 'pitch', 0.28, st); mx(P, 'chestPitch', 0.1, st); mx(P, 'tailPitch', 0.12, st); mx(P, 'fluff', 0.35, st);
|
|
1028
|
-
mx(P, 'lookW', 0, st); mx(P, 'cock', 0, st); mx(P, 'headOmega', 0.8, st);
|
|
1029
|
-
const call = smooth(1.0, 1.15, t) * (1 - smooth(2.55, 2.75, t));
|
|
1030
|
-
const syl = 0.75 + 0.25 * Math.sin(Math.max(0, t - 1.0) * 7.5);
|
|
1031
|
-
mx(P, 'jaw', 0.55 * syl, call * wt);
|
|
1032
|
-
if (!id.called && t > 1.0) { id.called = true; e.emit('vocalize', { kind: 'crow' }); }
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
case 'bill': {
|
|
1036
|
-
// wipe the bill on the ground / perch
|
|
1037
|
-
// (reach / bill pitch as the species pecks: actions.eat { reach, billPitch, bodyPitch, drop })
|
|
1038
|
-
// (raptors that tear their food wipe the bill just ahead of the breast, as they eat)
|
|
1039
|
-
// (the bill steep as in a peck, at least 0.95 rad down: a flatter bill on the ground put the
|
|
1040
|
-
// throat - a crow's chin feathers, a hen's wattles - 5-10 mm into it)
|
|
1041
|
-
const et = this.tuning.eat || {}, tear = et.style === 'tear';
|
|
1042
|
-
const bp = tear ? (et.billPitch ?? 1.2) : Math.max(0.95, et.billPitch ?? 1.05);
|
|
1043
|
-
// (a raptor wipes a little further ahead than it tears: the bill down at its feet swung the
|
|
1044
|
-
// neck base - and the breast skin blended to it - far forward)
|
|
1045
|
-
const fit = fitPeck(e, et.reach ?? (tear ? 1.3 : 1.12), bp, et.bodyPitch ?? (tear ? -0.72 : -0.45), et.drop ?? (tear ? 0.1 : 0.25));
|
|
1046
|
-
const pt = groundAhead(e, fit.reach, tv(), false);
|
|
1047
|
-
const hp = billTo(e, pt, bp, sd * 0.3 * Math.sin(t * 7), tv());
|
|
1048
|
-
P.headPos.lerp(hp, k / Math.max(1e-3, P.headW + k)); mx(P, 'headW', 1, k);
|
|
1049
|
-
mx(P, 'headPitch', bp, k); mx(P, 'pitch', fit.pitch, k); mx(P, 'lookW', 0, k);
|
|
1050
|
-
mx(P, 'drop', fit.drop, k); mx(P, 'tailPitch', -0.3, k);
|
|
1051
|
-
break;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
if (t >= T) { id.beh = null; id.called = false; }
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
export { angDiff };
|