@bitefoundry/retrieviewer 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) 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 +75 -0
  6. package/INSTALLING.md +8 -5
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +110 -0
  11. package/README.md +119 -58
  12. package/RELEASING.md +27 -14
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/desktop/platform.mjs +27 -0
  17. package/integrations/linux-overlay/README.md +83 -0
  18. package/integrations/linux-overlay/overlay.py +1094 -0
  19. package/integrations/macos-overlay/README.md +83 -0
  20. package/integrations/macos-overlay/overlay.jxa +639 -0
  21. package/integrations/opencode/runtime.mjs +4 -4
  22. package/integrations/opencode/tui.tsx +1 -1
  23. package/integrations/windows-overlay/Overlay.cs +4 -4
  24. package/integrations/windows-overlay/desktop.mjs +40 -8
  25. package/integrations/windows-overlay/protocol.mjs +11 -7
  26. package/integrations/windows-overlay/worker.mjs +13 -7
  27. package/package.json +41 -14
  28. package/puppy-comfort-sprites.mjs +25 -0
  29. package/puppy-companion-comfort-q5.bin.gz +0 -0
  30. package/puppy-companion-comfort.json +77 -0
  31. package/puppy-companion-life.mjs +49 -16
  32. package/puppy-companion-objects.mjs +12 -0
  33. package/puppy-frame-predictive.mjs +15 -9
  34. package/puppy-frame-store.mjs +2 -2
  35. package/puppy-paper-world.mjs +14 -0
  36. package/puppy-paper.mjs +65 -14
  37. package/retrieviewer-cli.mjs +10 -3
  38. package/retrieviewer-plugin.d.mts +1 -1
  39. package/retrieviewer-plugin.mjs +1 -1
  40. package/retrieviewer.d.mts +10 -4
  41. package/retrieviewer.mjs +20 -14
  42. package/locomotion-core.mjs +0 -21
  43. package/locomotion-puppy.animal +0 -0
  44. package/vendor/procedural-animals/package.json +0 -40
  45. package/vendor/procedural-animals/src/animal.js +0 -146
  46. package/vendor/procedural-animals/src/bake.js +0 -51
  47. package/vendor/procedural-animals/src/build.js +0 -81
  48. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  49. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  50. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  51. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  52. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  53. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  54. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  55. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  56. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  57. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  58. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  59. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  60. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  61. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  62. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  63. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  64. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  65. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  66. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  67. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  68. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  69. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  70. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  71. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  72. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  73. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  74. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  75. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  76. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  77. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  79. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  80. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  81. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  82. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  83. package/vendor/procedural-animals/src/index.d.ts +0 -98
  84. package/vendor/procedural-animals/src/index.js +0 -29
  85. package/vendor/procedural-animals/src/registry.js +0 -48
  86. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  87. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  88. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  89. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  90. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  91. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  92. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  94. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  95. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  96. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  97. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  98. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  99. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  100. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  101. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  102. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  103. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  104. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  105. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  106. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  107. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  108. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  109. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  110. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  111. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  112. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  113. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  114. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  115. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  116. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  117. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  118. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  119. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  120. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  121. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  122. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  123. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  124. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  125. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  126. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  127. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  128. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  129. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  130. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  131. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  132. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  133. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  134. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  135. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  136. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  137. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  138. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  139. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  140. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  141. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  142. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  143. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  144. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  145. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  146. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  147. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  148. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  149. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  150. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  151. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  152. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  153. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  154. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  155. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  156. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  157. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  158. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  159. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  160. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  161. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  162. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  163. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  164. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  165. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  166. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  167. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  168. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  169. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  170. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  171. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  172. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  173. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  174. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  175. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  176. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  177. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  178. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  179. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  180. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  181. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  182. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  183. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  184. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  185. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  186. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  187. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  188. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  189. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  190. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  191. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  192. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  193. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  194. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  195. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  196. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  197. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  198. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  199. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  200. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  201. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  202. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  203. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  204. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  205. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  206. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  207. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  208. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  209. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  210. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  211. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  212. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  213. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  214. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  215. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  216. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  217. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  218. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  219. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  220. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  221. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  222. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  223. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  224. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  225. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  226. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  227. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  228. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  229. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  230. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  231. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  232. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  233. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  234. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  235. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  236. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  237. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  238. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  239. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  240. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  241. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  242. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  243. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  244. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  245. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  246. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  247. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  248. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  249. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  250. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  251. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  252. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  253. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  254. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  255. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  256. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  257. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  258. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  259. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  260. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  261. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,521 +0,0 @@
1
- // Dog behaviour layer for the quadruped engine (species.motion.hooks, see core/motion/actions.js):
2
- //
3
- // update(P, dt, layer) every frame over the action layer: mood (happy / alert / angry / afraid) from
4
- // what the dog is doing, which sets the tail wag (rate and amplitude), the tail
5
- // carriage (raised stiff in an attack, tucked when hit), the ears, hackles and
6
- // the snarl; panting after running (jaw, tongue); the head tilt; the curled
7
- // sleep (tail over the nose); automatic idle variants (head tilt, sniffing,
8
- // pawing the ground, play bow, a happy wag burst)
9
- // pose(engine, dt) after the engine's solve: the wag itself (any rate: 2-3 Hz relaxed, 4-6 Hz
10
- // excited, whip-like from the root, biased to the dog's right when happy,
11
- // Quaranta et al. 2007), the tongue sliding out to pant and lapping when
12
- // drinking, and the soft drop ears of retrievers (and pups / button ears)
13
- // swinging as pendulums driven by the head's acceleration and gravity
14
- // actions greet (wiggling, fast-wagging welcome), playbow, sniff, dig (pawing the
15
- // ground), hit (yelp and cower, wraps the built-in flinch)
16
- //
17
- // Distances are dog metres at the reference size x cfg.s; posture offsets that the engine multiplies by
18
- // cfg.k (fwd, headRaise, neckReach) are divided by cfg.unit.
19
- import * as THREE from 'three';
20
- import { clamp, lerp, smooth, TAU, tv, tq, tqa } from '../../core/motion/common.js';
21
- import { mx, mxLeg, ONESHOTS, POSTURES, mouthGroundTarget } from '../../core/motion/actions.js';
22
-
23
- const sm = (w) => w * w * (3 - 2 * w);
24
- const _q = new THREE.Quaternion(), _q2 = new THREE.Quaternion(), _s = new THREE.Vector3(1, 1, 1);
25
- const _p = new THREE.Vector3(), _d = new THREE.Vector3(), _a = new THREE.Vector3(), _lat = new THREE.Vector3();
26
- const _hx = new THREE.Vector3(), _hz = new THREE.Vector3(), _g = new THREE.Vector3(), _qi = new THREE.Quaternion();
27
- const TAIL_W = [0.5, 0.45, 0.4, 0.36, 0.32, 0.29, 0.27, 0.25]; // share of the wag per tail segment, root -> tip
28
- const busy = (L) => L.postures.length > 0 || L.oneshots.some((a) => !a.stopping);
29
-
30
- // tail carriage offsets per variant (rad): raised stiff in an attack, tucked when afraid
31
- const TAIL_MOOD = { shepherd: { up: 1.25, down: -1.0 }, retriever: { up: 1.0, down: -1.3 }, terrier: { up: 0.3, down: -2.2 } };
32
- // roll of the curled sleep onto the outer hip (rad; the core's is 0.35). The terrier lies further over:
33
- // its chest rests on the ground with the shoulders so low that the elbow on the inside of the curl
34
- // sank 6 mm below its skin's clearance at 0.35 and had to be swung out of the ground (measured:
35
- // at 0.45 both elbows stay clear unswung on seeds 1-4, the lowest 1.4 mm above it)
36
- const CURL_ROLL = { terrier: 0.45 };
37
-
38
- function B(e) {
39
- if (!e._dog) {
40
- const v = e.params.variant || 'shepherd';
41
- const earType = e.params.earType || 'prick';
42
- e._dog = {
43
- variant: v, mood: TAIL_MOOD[v] || TAIL_MOOD.shepherd,
44
- happy: 0.4 + 0.3 * e.rand(), wagPh: 0, wagAmp: 0, wagAmpV: 0, wagFreq: 2.4, wagBias: 0, wagOn: 0,
45
- tongue: 0, tongueV: 0, lap: 0, lapW: 0, pant: 0,
46
- tilt: null, idleT: 4 + e.rand() * 6,
47
- swing: earType === 'drop' ? 1 : earType === 'button' ? 0.45 : 0,
48
- ear: [{ f: 0, vf: 0, s: 0, vs: 0 }, { f: 0, vf: 0, s: 0, vs: 0 }],
49
- hp: new THREE.Vector3(), hv: new THREE.Vector3(), ha: new THREE.Vector3(), hInit: 0,
50
- };
51
- }
52
- return e._dog;
53
- }
54
-
55
- // ------------------------------------------------------------------------------------------------
56
- // update: mood, tail, ears, panting, head tilt, idle variants
57
- function update(P, dt, L) {
58
- const e = L.engine, b = B(e), cfg = e.cfg;
59
- const vn = e.speed / cfg.sq;
60
- const moving = smooth(0.05, 0.8, vn) * P.gaitW;
61
- let attack = 0, hit = 0, greet = 0, bow = 0, eat = 0, drink = 0, sniff = 0;
62
- for (const a of L.oneshots) {
63
- const w = sm(a.w);
64
- if (w <= 0) continue;
65
- if (a.name === 'attack') {
66
- attack = Math.max(attack, w);
67
- const t = a.t;
68
- // threat -> lunge: hackles up, tail raised stiff, lips drawn back (the jaw opens a little on the
69
- // crouch so the canines show), a growl
70
- const snarl = smooth(0.0, 0.12, t) * (1 - smooth(0.24, 0.32, t));
71
- P.jaw = Math.max(P.jaw, 0.16 * snarl * w);
72
- mx(P, 'headRaise', -0.03 / cfg.unit, snarl * w);
73
- if (!a._voc) { a._voc = true; e.emit('vocalize', { kind: 'snarl' }); }
74
- } else if (a.name === 'hit') hit = Math.max(hit, w * (1 - smooth(0.9, 1.5, a.t)));
75
- else if (a.name === 'greet') greet = Math.max(greet, w);
76
- else if (a.name === 'playbow') bow = Math.max(bow, w);
77
- else if (a.name === 'eat') eat = Math.max(eat, w);
78
- else if (a.name === 'drink') { drink = Math.max(drink, w); b.lapT = a.t; }
79
- else if (a.name === 'sniff' || a.name === 'dig') sniff = Math.max(sniff, w);
80
- }
81
- let asleep = 0, dead = 0, lying = 0, sitting = 0;
82
- for (const p of L.postures) {
83
- const w = sm(clamp(p.w, 0, 1));
84
- if (p.name === 'sleep') asleep = Math.max(asleep, w * smooth(1.0, 3.0, p.t));
85
- if (p.name === 'death') dead = Math.max(dead, w);
86
- if (p.name === 'lie' || p.name === 'sleep') lying = Math.max(lying, w);
87
- if (p.name === 'sit') sitting = Math.max(sitting, w);
88
- // curled sleep: the tail is drawn round over the nose and forepaws
89
- if (p.name === 'sleep' && p.tune.style !== 'lateral') {
90
- const c = w * smooth(1.2, 3.2, p.t);
91
- mx(P, 'tailSide', p.side * 3.0, c); mx(P, 'tailStiff', 0.7, c); mx(P, 'bend', p.side * 1.35, c); mx(P, 'headYaw', p.side * 0.9, c); mx(P, 'headRoll', p.side * 0.3, c);
92
- if (CURL_ROLL[b.variant]) mx(P, 'roll', p.side * CURL_ROLL[b.variant], c);
93
- }
94
- // (the head lies on its side on the cheek and the lower ear: the occiput rests higher than the core's)
95
- if (p.name === 'death') { const c = w * smooth(0.3, 1.0, p.t); mx(P, 'earFlat', 1.1, c); mx(P, 'earTwitch', 0, c); P.headPos.y += (0.035 + 0.1 * Math.max(0, (e.params.headScale || 1) * (e.params.headW || 1) - 1)) * cfg.s * c; }
96
- if (p.name === 'sleep') { const c = w * smooth(1.0, 3.0, p.t); P.headPos.y += (0.03 + 0.22 * Math.max(0, (e.params.headScale || 1) * (e.params.headW || 1) - 1)) * cfg.s * c; mx(P, 'headPitch', 0.55, c); }
97
- }
98
-
99
- // eating: the mouth works just above the food (the jaw opens below the closed-mouth target)
100
- if (eat > 0) { P.headPos.y += 0.02 * cfg.s * eat; P.jaw = Math.min(P.jaw, 0.22); }
101
-
102
- // ---- mood: happiness rises when greeting / playing, fades slowly; fear and anger override it
103
- b.happy = clamp(b.happy + dt * (0.5 * Math.max(greet, bow) - 0.012) - dt * 0.6 * (attack + hit), 0.15, 1);
104
-
105
- // ---- tail carriage by mood
106
- mx(P, 'tailLift', b.mood.up, attack); mx(P, 'tailStiff', 2.0, attack); mx(P, 'tailIdle', 0.2, attack);
107
- mx(P, 'tailLift', b.mood.down, hit); mx(P, 'tailStiff', 1.6, hit); mx(P, 'tailIdle', 0, hit);
108
- // happy: carried a little higher (never above the back in a shepherd)
109
- const up = Math.max(greet, bow) * (b.variant === 'terrier' ? 0.1 : 0.55);
110
- if (up > 0) P.tailLift += up;
111
- P.furRaise = Math.max(P.furRaise || 0, attack * 0.8);
112
-
113
- // ---- wag: target amplitude (rad at the root segment) and rate
114
- let amp = 0, freq = 2.4;
115
- const idleW = L.idle.w;
116
- // relaxed: a loose wag while standing about, faster and wider the happier the dog is
117
- amp = idleW * (0.1 + 0.35 * b.happy) * (b.wagOn > 0 ? 1 : 0.35);
118
- freq = lerp(2.0, 3.0, b.happy);
119
- // on the move: a small wag at walk and trot, none at the gallop
120
- amp = Math.max(amp, moving * (1 - smooth(1.5, 4, vn)) * 0.12 * b.happy);
121
- // sitting: the tail sweeps the ground
122
- amp = Math.max(amp, sitting * 0.25 * b.happy);
123
- // greeting / play bow: fast, wide, whole-rear wag
124
- if (greet + bow > 0) { amp = lerp(amp, 0.62, Math.max(greet, bow)); freq = lerp(freq, 5.2, Math.max(greet, bow)); }
125
- if (sniff > 0) { amp = lerp(amp, 0.18, sniff); freq = lerp(freq, 3.2, sniff); }
126
- amp *= (1 - attack) * (1 - hit) * (1 - asleep) * (1 - dead) * (1 - 0.8 * lying) * (1 - 0.6 * eat);
127
- // attack: the raised tail quivers ("flagging")
128
- amp += attack * 0.05; if (attack > 0.3) freq = lerp(freq, 7, attack);
129
- b.wagAmpT = amp; b.wagFreqT = freq;
130
- // occasional bursts of wagging while idle
131
- b.wagOn -= dt;
132
- if (b.wagOn < -3 - 6 * (1 - b.happy) && idleW > 0.5) b.wagOn = 1.5 + 3 * e.rand();
133
-
134
- // ---- panting after running (the engine's heat), jaw wider, tongue out
135
- const hot = smooth(0.12, 0.45, e.heat) * (1 - smooth(5, 8, vn));
136
- b.pant = hot * (1 - attack) * (1 - asleep) * (1 - dead) * (1 - eat) * (1 - drink) * (1 - sniff);
137
- if (b.pant > 0.01) { P.jaw += 0.13 * b.pant; mx(P, 'breathAmp', 1.5, b.pant); }
138
- // tongue: panting, a happy greeting, lapping
139
- b.tongueT = Math.max(b.pant, 0.35 * greet * b.happy, 0.25 * bow);
140
- b.lapW = drink;
141
-
142
- // ---- ears
143
- // alert (a look target or sniffing): pricked forward; greeting: laid softly back; attack: core flattens
144
- if (e.input.look && attack < 0.01) mx(P, 'earFlat', -0.2, clamp(P.lookW, 0, 1));
145
- mx(P, 'earFlat', 0.45, greet); mx(P, 'earFlat', -0.15, bow + sniff);
146
- mx(P, 'earFlat', 1.1, hit);
147
-
148
- // ---- head tilt (idle variant): the head rolls 20-25 deg to one side, ears pricked, sometimes twice
149
- const tl = b.tilt;
150
- if (tl) {
151
- tl.t += dt;
152
- const k = smooth(0, 0.35, tl.t) * (1 - smooth(tl.dur, tl.dur + 0.4, tl.t)) * idleW;
153
- mx(P, 'headRoll', tl.side * 0.42, k); mx(P, 'headYaw', -tl.side * 0.12, k); mx(P, 'headPitch', -0.08, k);
154
- mx(P, 'earFlat', -0.25, k);
155
- if (tl.t > tl.dur + 0.4) b.tilt = tl.again ? { t: -0.2, dur: 0.8 + 0.6 * e.rand(), side: -tl.side, again: false } : null;
156
- if (idleW < 0.3) b.tilt = null;
157
- }
158
-
159
- // ---- automatic idle variants (standing still, nothing else running, nobody steering the gaze)
160
- if (e.idleVariants !== false && idleW > 0.98 && !busy(L) && !e.input.look && !e.input.follow && !b.tilt) {
161
- b.idleT -= dt;
162
- if (b.idleT <= 0) {
163
- b.idleT = 5 + e.rand() * 9;
164
- const r = e.rand();
165
- if (r < 0.34) b.tilt = { t: 0, dur: 1.0 + 1.2 * e.rand(), side: e.rand() < 0.5 ? -1 : 1, again: e.rand() < 0.4 };
166
- else if (r < 0.54) L.play('sniff', { duration: 2.5 + 2 * e.rand() });
167
- else if (r < 0.66) L.play('playbow');
168
- else if (r < 0.76) L.play('dig', { duration: 1.8 + e.rand() });
169
- else b.wagOn = 2 + 2 * e.rand(), b.happy = Math.min(1, b.happy + 0.2);
170
- }
171
- } else if (busy(L)) b.idleT = Math.max(b.idleT, 3);
172
- }
173
-
174
- // ------------------------------------------------------------------------------------------------
175
- // pose: wag, tongue, swinging ears (after the engine wrote every bone)
176
- function pose(e, dt) {
177
- const b = B(e), cfg = e.cfg;
178
- // wag state (critically damped amplitude, soft rate changes)
179
- const om = 7;
180
- b.wagAmpV += (om * om * ((b.wagAmpT || 0) - b.wagAmp) - 2 * om * b.wagAmpV) * dt;
181
- b.wagAmp += b.wagAmpV * dt;
182
- b.wagFreq += ((b.wagFreqT || 2.4) - b.wagFreq) * (1 - Math.exp(-dt * 3));
183
- b.wagPh = (b.wagPh + dt * TAU * b.wagFreq) % (TAU * 1000);
184
- // right-biased when positive (the dog's right = -x: negative yaw), smoothed (no steps)
185
- const biasT = -0.18 * b.happy * Math.max(0, b.wagAmp);
186
- b.biasV = (b.biasV || 0) + (om * om * (biasT - b.wagBias) - 2 * om * (b.biasV || 0)) * dt;
187
- b.wagBias += b.biasV * dt;
188
- const A = b.wagAmp;
189
- const lod = e.lod;
190
- // (crowds: only a real wag, not the relaxed idle sway)
191
- const minA = lod >= 2 ? 0.12 : 1e-4;
192
- if ((Math.abs(A) > minA || Math.abs(b.wagBias) > minA) && e.tail && e.tail.n) wagTail(e, b, A);
193
- // tongue (not in crowds)
194
- if (lod < 2) { clearJaw(e, b, dt); tongue(e, b, dt); }
195
- if (b.swing > 0 && lod < 2) swingEars(e, b, dt);
196
- void cfg;
197
- }
198
-
199
- // rotate the tail chain from the root: each segment turns about its own dorsal axis (lateral bending)
200
- function wagTail(e, b, A) {
201
- const n = e.tail.n, T = e.cfg.tail;
202
- const rad0 = T.radius[0] * e.cfg.s, rad1 = T.radius[1] * e.cfg.s;
203
- _lat.set(1, 0, 0).applyQuaternion(e.fr.qPel);
204
- _q2.identity(); // cumulative rotation
205
- let px = 0, py = 0, pz = 0; // accumulated new head position
206
- for (let i = 0; i < n; i++) {
207
- const bone = e.bone['tail' + i];
208
- if (!bone) return;
209
- const m = bone.matrixWorld;
210
- _p.setFromMatrixPosition(m);
211
- _q.setFromRotationMatrix(m);
212
- if (i === 0) { px = _p.x; py = _p.y; pz = _p.z; }
213
- _d.set(0, 1, 0).applyQuaternion(_q); // original direction
214
- // the segment's dorsal axis: lat x dir, plus the pelvis' up axis orthogonal to the segment (never
215
- // zero together, and both point the same way: continuous for a hanging, level, erect or curled tail)
216
- const ax = tv().crossVectors(_lat, _d);
217
- const upx = tv(0, 1, 0).applyQuaternion(e.fr.qPel);
218
- ax.add(upx.addScaledVector(_d, -upx.dot(_d))).normalize();
219
- const a = (A * Math.sin(b.wagPh - 0.38 * i) + b.wagBias) * TAIL_W[Math.min(i, TAIL_W.length - 1)];
220
- _q2.multiply(tqa(ax, a));
221
- const len = e.tail.len[i];
222
- const newQ = tqa(ax, 0).copy(_q2).multiply(_q);
223
- const dn = tv(0, 1, 0).applyQuaternion(newQ);
224
- // the wag never swings a segment into the ground (a tail lying on it when sitting or lying):
225
- // raise the segment's slope just enough, keeping its heading; the rest of the tail follows
226
- const floor = e.terrainH(px + dn.x * len, pz + dn.z * len) + lerp(rad0, rad1, (i + 1) / n);
227
- if (py + dn.y * len < floor) {
228
- const dy = clamp((floor - py) / len, -1, 1), hl = Math.hypot(dn.x, dn.z) || 1, c = Math.sqrt(1 - dy * dy);
229
- const d2 = tv(dn.x / hl * c, dy, dn.z / hl * c);
230
- const fix = tq().setFromUnitVectors(dn, d2);
231
- newQ.premultiply(fix); _q2.premultiply(fix);
232
- dn.copy(d2);
233
- }
234
- m.compose(tv(px, py, pz), newQ, _s);
235
- px += dn.x * len; py += dn.y * len; pz += dn.z * len;
236
- }
237
- }
238
-
239
- // the lower jaw never enters the ground (the core clears the closed mouth; an open jaw reaches lower
240
- // when tearing food or panting with the head down): close it about the hinge just enough, springy
241
- function clearJaw(e, b, dt) {
242
- const jaw = e.bone.jaw;
243
- if (!jaw) return;
244
- const s = e.cfg.s, m = jaw.matrixWorld;
245
- _p.setFromMatrixPosition(m);
246
- _q.setFromRotationMatrix(m);
247
- _d.set(0, 1, 0).applyQuaternion(_q);
248
- const len = b.jawLen || (b.jawLen = e.J.jawHinge.distanceTo(e.J.jawTip));
249
- let need = 0;
250
- // chin (under the jaw tip) and the middle of the mandible's lower edge
251
- for (const [t, r] of [[1, 0.012], [0.55, 0.02]]) {
252
- const pt = tv().copy(_p).addScaledVector(_d, len * t);
253
- const lack = e.terrainH(pt.x, pt.z) + r * s - pt.y;
254
- if (lack > 0) need = Math.max(need, Math.asin(clamp(lack / (len * t), 0, 1)));
255
- }
256
- // rate-limited release (no snap back when the head lifts)
257
- b.jawClr = Math.max(need, (b.jawClr || 0) - dt * 2);
258
- if (b.jawClr < 1e-4) return;
259
- _hx.set(1, 0, 0).applyQuaternion(_q);
260
- // (closing = rotating the jaw's +Y up about its lateral axis: toward the skull)
261
- _q.premultiply(tqa(_hx, -b.jawClr));
262
- m.compose(_p, _q, _s);
263
- // the tongue rides on the jaw: re-derive it from the corrected jaw
264
- const tb = e.bone.tongue;
265
- if (tb) for (const x of e.extraBones) if (x.bone === tb) tb.matrixWorld.multiplyMatrices(m, x.rel);
266
- }
267
-
268
- // tongue: slides out over the lower incisors and hangs (panting), or ladles in and out (lapping)
269
- function tongue(e, b, dt) {
270
- const bone = e.bone.tongue;
271
- if (!bone) return;
272
- // lapping: ~3 laps / s in phase with the built-in drink's jaw (tongue curled back into a ladle)
273
- let tgt = b.tongueT || 0, droop = 0.55;
274
- if (b.lapW > 0.01) {
275
- const lap = Math.max(0, Math.sin((b.lapT || 0) * 3 * TAU));
276
- tgt = lerp(tgt, 0.3 + 0.45 * lap, b.lapW);
277
- droop = lerp(droop, 0.15 + 0.35 * lap, b.lapW);
278
- }
279
- const om = b.lapW > 0.01 ? 30 : 9;
280
- b.tongueV += (om * om * (tgt - b.tongue) - 2 * om * b.tongueV) * dt;
281
- b.tongue += b.tongueV * dt;
282
- const out = clamp(b.tongue, 0, 1.2);
283
- if (out < 1e-3) return;
284
- const s = e.cfg.s;
285
- const m = bone.matrixWorld;
286
- _p.setFromMatrixPosition(m);
287
- _q.setFromRotationMatrix(m);
288
- _d.set(0, 1, 0).applyQuaternion(_q);
289
- _hx.set(1, 0, 0).applyQuaternion(_q);
290
- // slide forward, then droop about the lower incisors; the hanging part bobs with the breath
291
- const slide = 0.058 * s * out;
292
- const pivotAlong = 0.11 * s;
293
- const ang = droop * smooth(0, 0.6, out) * (1 + 0.12 * Math.sin(e.breath));
294
- const base0 = tv().copy(_p), pivot = tv().copy(_p).addScaledVector(_d, pivotAlong);
295
- // (the tip never enters the ground: the tongue first slides back in just enough (the tip's height is
296
- // linear in the slide, well conditioned while the tongue points down, as when lapping with the head
297
- // low), then the droop is reduced, down to curling the tongue up; both by bisection, continuous in
298
- // the pose. Reducing only the droop near the bottom of its arc swung the tongue ~0.6 rad in 10 ms.)
299
- const len = e.J.tongueBase.distanceTo(e.J.tongueTip), p0 = tv();
300
- const tipY = (a, sl) => {
301
- const R = tqa(_hx, a);
302
- p0.copy(base0).addScaledVector(_d, sl);
303
- const base = tv().subVectors(p0, pivot).applyQuaternion(R).add(pivot);
304
- const tip = tv().copy(_d).applyQuaternion(R).multiplyScalar(len).add(base);
305
- return tip.y - (e.terrainH(tip.x, tip.z) + 0.004 * s); // (it may dip into water: lapping)
306
- };
307
- let A = ang, sl = slide;
308
- if (tipY(A, sl) < 0) {
309
- if (tipY(A, 0) >= 0) {
310
- // the longest feasible slide
311
- let lo = 0, hi = sl;
312
- for (let i = 0; i < 14; i++) { const mid = (lo + hi) / 2; if (tipY(A, mid) >= 0) lo = mid; else hi = mid; }
313
- sl = lo;
314
- } else {
315
- // fully in, and the largest feasible droop below A: scan down, then refine between the last two samples
316
- sl = 0;
317
- let hi = A, lo = A;
318
- const step = (A + 0.9) / 24;
319
- for (let i = 1; i <= 24; i++) { lo = A - step * i; if (tipY(lo, 0) >= 0) break; hi = lo; }
320
- for (let i = 0; i < 12; i++) { const mid = (lo + hi) / 2; if (tipY(mid, 0) >= 0) lo = mid; else hi = mid; }
321
- A = lo;
322
- }
323
- }
324
- p0.copy(base0).addScaledVector(_d, sl);
325
- A *= smooth(0, 0.25, out); // (retracted: rigid on the jaw, whatever the clamp says)
326
- const R = tqa(_hx, A);
327
- const rel = tv().subVectors(p0, pivot).applyQuaternion(R);
328
- _p.copy(pivot).add(rel);
329
- _q.premultiply(R);
330
- m.compose(_p, _q, _s);
331
- }
332
-
333
- // soft drop ears: two damped pendulum angles per ear in the head frame (fore-aft about the head's
334
- // lateral axis, outward about its long axis), driven by the head's acceleration and by gravity
335
- function swingEars(e, b, dt) {
336
- if (!(dt > 1e-5)) return;
337
- const hp = e.headPose.position, hq = e.headPose.quaternion;
338
- if (b.hInit < 2) {
339
- if (b.hInit === 1) b.hv.subVectors(hp, b.hp).multiplyScalar(1 / dt);
340
- b.hp.copy(hp); b.hInit++;
341
- return;
342
- }
343
- const v = tv().subVectors(hp, b.hp).multiplyScalar(1 / dt);
344
- const acc = tv().subVectors(v, b.hv).multiplyScalar(1 / dt);
345
- b.hp.copy(hp); b.hv.copy(v);
346
- // low-pass the acceleration (finite differences of a stepped body are noisy)
347
- b.ha.lerp(acc.clampLength(0, 60), 1 - Math.exp(-dt * 30));
348
- _qi.copy(hq).invert();
349
- _a.copy(b.ha).applyQuaternion(_qi); // head-local acceleration
350
- _g.set(0, -1, 0).applyQuaternion(_qi); // gravity direction in the head frame
351
- const s = e.cfg.s;
352
- const lenE = 0.09 * s;
353
- const om = 13 / Math.sqrt(s), z = 0.28;
354
- const K = b.swing;
355
- _hx.set(1, 0, 0).applyQuaternion(hq); _hz.set(0, 0, 1).applyQuaternion(hq);
356
- const sub = Math.max(1, Math.ceil(dt / (1 / 120))), h = dt / sub;
357
- for (let k = 0; k < 2; k++) {
358
- const E = b.ear[k], side = k === 0 ? 1 : -1;
359
- // equilibrium: hang along gravity (a lowered or tilted head swings the flaps), outward flop when the
360
- // head drops fast (trot bounce)
361
- const tf = -0.8 * Math.asin(clamp(_g.z, -1, 1));
362
- let ts = 0.8 * Math.asin(clamp(_g.x, -1, 1));
363
- for (let i = 0; i < sub; i++) {
364
- const af = om * om * (tf - E.f) - 2 * z * om * E.vf + (_a.z / lenE) * 0.9;
365
- const as = om * om * (ts - E.s) - 2 * z * om * E.vs - (_a.x / lenE) * 0.9 - side * Math.min(0, _a.y) / lenE * 0.35;
366
- E.vf += af * h; E.f += E.vf * h;
367
- E.vs += as * h; E.s += E.vs * h;
368
- // the flap cannot swing into the cheek; limits
369
- if (side * E.s < -0.04) { E.s = -0.04 * side; if (side * E.vs < 0) E.vs = 0; }
370
- E.f = clamp(E.f, -0.55, 0.55); E.s = clamp(E.s, -0.65, 0.65);
371
- }
372
- const bone = e.bone[k === 0 ? 'earL' : 'earR'];
373
- if (!bone) continue;
374
- const m = bone.matrixWorld;
375
- _p.setFromMatrixPosition(m);
376
- _q.setFromRotationMatrix(m);
377
- // the flap never swings into the ground (a head lowered or lying on its side): keep the largest
378
- // share of the swing that leaves its tip and its broad middle clear (the core cleared the unswung ear)
379
- const L = b.earLen || (b.earLen = e.J.earBaseL.distanceTo(e.J.earTipL));
380
- const clear = (f) => {
381
- const q = tqa(_hz, E.s * K * f).multiply(tqa(_hx, E.f * K * f)).multiply(_q);
382
- const dy = tv(0, 1, 0).applyQuaternion(q);
383
- let worst = 1;
384
- for (const t of [1, 0.6]) {
385
- const pt = tv().copy(_p).addScaledVector(dy, L * t);
386
- worst = Math.min(worst, pt.y - (e.terrainH(pt.x, pt.z) + 0.012 * s));
387
- }
388
- return worst;
389
- };
390
- let f = 1;
391
- if (clear(1) < 0) {
392
- let lo = 0, hi = 1;
393
- for (let i = 0; i < 10; i++) { const mid = (lo + hi) / 2; if (clear(mid) >= 0) lo = mid; else hi = mid; }
394
- f = lo;
395
- E.vf *= 0.5; E.vs *= 0.5;
396
- }
397
- _q.premultiply(tqa(_hx, E.f * K * f)).premultiply(tqa(_hz, E.s * K * f));
398
- m.compose(_p, _q, _s);
399
- }
400
- }
401
-
402
- // ------------------------------------------------------------------------------------------------
403
- // actions
404
-
405
- // greeting: fast wide wag that swings the whole rear, body wiggles, ears back, head a little low
406
- const greet = {
407
- kind: 'oneshot', fade: 0.3,
408
- start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 3); L.engine.emit('vocalize', { kind: 'whine' }); },
409
- update(inst) { return inst.t >= inst.dur; },
410
- apply(P, w, inst, L) {
411
- const e = L.engine, t = inst.t, b = B(e);
412
- const k = w * smooth(0, 0.4, t);
413
- // the rear swings with the wag ("wiggle"): a lateral spine bend in phase with the tail
414
- mx(P, 'bend', 0.14 * Math.sin(b.wagPh + 0.6), k);
415
- mx(P, 'roll', 0.03 * Math.sin(b.wagPh), k);
416
- mx(P, 'headRaise', -0.015 / e.cfg.unit, k); mx(P, 'headPitch', -0.1, k);
417
- // small bounces on the forelegs
418
- mx(P, 'dropF', 0.06 + 0.05 * Math.sin(t * TAU * 2.2), k);
419
- mx(P, 'eyelid', 0.2, k);
420
- },
421
- };
422
-
423
- // play bow: forequarters down on the elbows, forelegs stretched forward, rear up, tail up and wagging
424
- const playbow = {
425
- kind: 'oneshot', fade: 0.3, needsStand: true,
426
- start(inst, L) {
427
- inst.dur = inst.opts.duration ?? 2.4;
428
- const e = L.engine;
429
- if (e.rand() < 0.5) e.emit('vocalize', { kind: 'bark' });
430
- },
431
- update(inst) { return inst.t >= inst.dur; },
432
- apply(P, w, inst, L) {
433
- const e = L.engine, t = inst.t;
434
- const down = w * smooth(0.0, 0.5, t) * (1 - smooth(inst.dur - 0.5, inst.dur, t));
435
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
436
- // the forelegs fold forward along the ground as in the sphinx (the core's lying fold), the chest
437
- // drops to the elbows while the hind legs stay straight, rear up
438
- mx(P, 'dropF', 0.88, down); mx(P, 'dropH', -0.06, down);
439
- for (let i = 0; i < 2; i++) mxLeg(P, i, 'fold', 1, smooth(0.1, 0.8, down));
440
- mx(P, 'headRaise', 0.03 / e.cfg.unit, down); mx(P, 'headPitch', -0.3, down); mx(P, 'neckReach', 0.02 / e.cfg.unit, down);
441
- mx(P, 'lookW', 0, down);
442
- },
443
- };
444
-
445
- // sniffing: nose to the ground ahead, sweeping side to side
446
- const sniff = {
447
- kind: 'oneshot', fade: 0.5,
448
- start(inst, L) { inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 4); inst.ph = L.engine.rand() * 6; },
449
- update(inst) { return inst.t >= inst.dur; },
450
- apply(P, w, inst, L) {
451
- const e = L.engine, t = inst.t, cfg = e.cfg;
452
- const pitchA = 0.82;
453
- const hp = mouthGroundTarget(e, tv(), pitchA, (x, z) => e.terrainH(x, z), 0.95);
454
- // side to side sweeps, the nose just above the ground; quick sniff bobs
455
- const sweep = Math.sin(t * 1.3 + inst.ph) * 0.8 + Math.sin(t * 0.47 + 2 * inst.ph) * 0.2;
456
- hp.addScaledVector(e.left(e.heading, tv()), 0.07 * cfg.s * sweep);
457
- hp.y += (0.012 + 0.006 * Math.sin(t * TAU * 5)) * cfg.s;
458
- P.headPos.copy(hp);
459
- mx(P, 'headW', 1, w); mx(P, 'headReach', 1, w); mx(P, 'headPitch', pitchA, w); mx(P, 'headYaw', 0.3 * sweep, w);
460
- mx(P, 'lookW', 0, w); mx(P, 'headOmega', 1.6, w);
461
- mx(P, 'gaitW', 0, w); mx(P, 'dropF', 0.18, w);
462
- mx(P, 'speedScale', 0, w);
463
- },
464
- };
465
-
466
- // pawing the ground: head down over the spot, the forepaws scrape it alternately, back under the chest
467
- const dig = {
468
- kind: 'oneshot', fade: 0.3, needsStand: true,
469
- start(inst, L) {
470
- inst.dur = inst.opts.loop ? Infinity : (inst.opts.duration ?? 2.5);
471
- const e = L.engine;
472
- inst.base = [0, 1].map((i) => new THREE.Vector3().copy(e.legs[i].contact));
473
- inst.fwd = e.forward(e.heading, new THREE.Vector3());
474
- },
475
- update(inst) { return inst.t >= inst.dur; },
476
- apply(P, w, inst, L) {
477
- const e = L.engine, t = inst.t, cfg = e.cfg;
478
- const k = w * smooth(0, 0.3, t);
479
- mx(P, 'gaitW', 0, w); mx(P, 'legGait', 0, w);
480
- mx(P, 'dropF', 0.3, k); mx(P, 'dropH', 0.05, k);
481
- mx(P, 'headPitch', 0.75, k); mx(P, 'headRaise', -0.06 / cfg.unit, k); mx(P, 'lookW', 0, k);
482
- mx(P, 'earFlat', -0.1, k);
483
- const env = smooth(0.15, 0.4, t) * (1 - smooth(inst.dur - 0.35, inst.dur, t));
484
- for (let i = 0; i < 2; i++) {
485
- const leg = e.legs[i], len = leg.def.len;
486
- // one stroke: reach forward high, press down, drag back under the chest, lift (3 Hz, legs alternate)
487
- const ph = (t * 3.0 + i * 0.5) % 1;
488
- const u = ph < 0.55 ? 0.28 - 0.4 * smooth(0, 0.55, ph) : -0.12 + 0.4 * smooth(0.55, 1, ph);
489
- const hgt = ph < 0.55 ? 0.03 : 0.03 + 0.08 * Math.sin(Math.PI * (ph - 0.55) / 0.45);
490
- const tp = tv().copy(inst.base[i]).addScaledVector(inst.fwd, u * len);
491
- tp.y = e.terrainH(tp.x, tp.z) + hgt * len;
492
- // the planted foot stays where it stood outside the strokes
493
- tp.lerp(inst.base[i], 1 - env);
494
- const pl = P.legs[i];
495
- pl.target.copy(tp);
496
- mxLeg(P, i, 'reach', 1, w);
497
- }
498
- if (Math.floor(t * 3) !== inst.lastStroke) { inst.lastStroke = Math.floor(t * 3); }
499
- },
500
- };
501
-
502
- // hit: the built-in flinch, then a yelp and a cower (body low, tail tucked, ears flat, head down)
503
- const hit = {
504
- kind: 'oneshot', fade: 0.12,
505
- start(inst, L) { ONESHOTS.hit.start(inst, L); inst.dur = 1.6; L.engine.emit('vocalize', { kind: 'yelp' }); },
506
- update(inst) { return inst.t >= inst.dur; },
507
- apply(P, w, inst, L) {
508
- ONESHOTS.hit.apply(P, w, inst, L);
509
- const t = inst.t, e = L.engine;
510
- const c = w * smooth(0.12, 0.4, t) * (1 - smooth(1.0, 1.6, t));
511
- mx(P, 'dropF', 0.3, c); mx(P, 'dropH', 0.42, c);
512
- mx(P, 'headRaise', -0.035 / e.cfg.unit, c); mx(P, 'headPitch', 0.25, c);
513
- mx(P, 'eyelid', 0.35, c); mx(P, 'lookW', 0.3, c);
514
- },
515
- };
516
-
517
- // the curled sleep: the core's, unrolling a little more slowly when the dog gets up (the forelegs
518
- // come out from under the curled body)
519
- const sleep = { ...POSTURES.sleep, kind: 'posture', fadeOut: 1.6 };
520
-
521
- export const dogHooks = { update, pose, actions: { greet, playbow, sniff, dig, hit, sleep } };