@bitefoundry/retrieviewer 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/PLUGIN.md +13 -10
  7. package/PREREQUISITES.md +95 -0
  8. package/PUPPY-MEMORY.md +102 -1
  9. package/README.ko.md +110 -0
  10. package/README.md +117 -59
  11. package/RELEASING.md +23 -12
  12. package/THIRD-PARTY-NOTICES.md +5 -3
  13. package/braille.mjs +35 -9
  14. package/examples/live.mjs +2 -2
  15. package/integrations/linux-overlay/overlay.py +2 -2
  16. package/integrations/macos-overlay/overlay.jxa +8 -6
  17. package/integrations/opencode/runtime.mjs +4 -4
  18. package/integrations/opencode/tui.tsx +1 -1
  19. package/integrations/windows-overlay/Overlay.cs +4 -4
  20. package/integrations/windows-overlay/protocol.mjs +11 -7
  21. package/package.json +34 -13
  22. package/puppy-comfort-sprites.mjs +25 -0
  23. package/puppy-companion-comfort-q5.bin.gz +0 -0
  24. package/puppy-companion-comfort.json +77 -0
  25. package/puppy-companion-life.mjs +49 -16
  26. package/puppy-companion-objects.mjs +12 -0
  27. package/puppy-frame-predictive.mjs +15 -9
  28. package/puppy-frame-store.mjs +2 -2
  29. package/puppy-paper-world.mjs +14 -0
  30. package/puppy-paper.mjs +65 -14
  31. package/retrieviewer-plugin.d.mts +1 -1
  32. package/retrieviewer-plugin.mjs +1 -1
  33. package/retrieviewer.d.mts +10 -4
  34. package/retrieviewer.mjs +20 -14
  35. package/locomotion-core.mjs +0 -21
  36. package/locomotion-puppy.animal +0 -0
  37. package/vendor/procedural-animals/package.json +0 -40
  38. package/vendor/procedural-animals/src/animal.js +0 -146
  39. package/vendor/procedural-animals/src/bake.js +0 -51
  40. package/vendor/procedural-animals/src/build.js +0 -81
  41. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  42. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  43. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  44. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  45. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  46. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  47. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  48. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  49. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  50. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  51. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  52. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  53. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  54. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  55. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  56. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  57. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  58. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  59. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  60. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  61. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  62. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  63. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  64. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  65. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  66. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  67. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  68. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  69. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  70. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  71. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  72. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  73. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  74. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  75. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  76. package/vendor/procedural-animals/src/index.d.ts +0 -98
  77. package/vendor/procedural-animals/src/index.js +0 -29
  78. package/vendor/procedural-animals/src/registry.js +0 -48
  79. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  80. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  81. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  82. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  83. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  84. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  85. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  86. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  87. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  88. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  89. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  90. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  91. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  92. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  93. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  94. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  95. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  96. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  97. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  98. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  99. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  100. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  101. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  102. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  103. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  104. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  105. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  106. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  107. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  108. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  109. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  110. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  111. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  112. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  113. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  114. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  115. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  116. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  117. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  118. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  119. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  120. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  121. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  122. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  123. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  124. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  125. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  126. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  127. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  128. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  129. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  130. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  131. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  132. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  133. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  134. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  135. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  136. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  137. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  138. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  139. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  140. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  141. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  142. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  143. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  144. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  145. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  146. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  147. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  148. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  149. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  150. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  151. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  152. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  153. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  154. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  155. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  156. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  157. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  158. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  159. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  160. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  161. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  162. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  163. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  164. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  165. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  166. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  167. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  168. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  169. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  170. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  171. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  172. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  173. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  174. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  175. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  176. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  177. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  178. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  179. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  180. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  181. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  182. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  183. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  184. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  185. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  186. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  187. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  188. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  189. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  190. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  191. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  192. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  193. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  194. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  195. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  196. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  197. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  198. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  199. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  200. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  201. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  202. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  203. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  204. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  205. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  206. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  207. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  208. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  209. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  210. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  211. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  212. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  213. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  214. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  215. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  216. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  217. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  218. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  219. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  220. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  221. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  222. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  223. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  224. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  225. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  226. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  227. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  228. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  229. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  230. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  231. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  232. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  233. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  234. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  235. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  236. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  237. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  238. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  239. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  240. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  241. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  242. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  243. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  244. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  245. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  246. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  247. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  248. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  249. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  250. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  251. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  252. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  253. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  254. 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 };