@bitefoundry/retrieviewer 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +95 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/INSTALLING.md +4 -0
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +116 -0
  11. package/README.md +121 -59
  12. package/RELEASING.md +33 -13
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/linux-overlay/overlay.py +8 -2
  17. package/integrations/macos-overlay/overlay.jxa +9 -7
  18. package/integrations/opencode/runtime.mjs +4 -4
  19. package/integrations/opencode/tui.tsx +1 -1
  20. package/integrations/windows-overlay/Overlay.cs +7 -6
  21. package/integrations/windows-overlay/desktop.mjs +2 -1
  22. package/integrations/windows-overlay/protocol.mjs +13 -7
  23. package/integrations/windows-overlay/worker.mjs +1 -1
  24. package/package.json +34 -13
  25. package/puppy-comfort-sprites.mjs +33 -0
  26. package/puppy-companion-comfort-q5.bin.gz +0 -0
  27. package/puppy-companion-comfort.json +140 -0
  28. package/puppy-companion-life.mjs +58 -18
  29. package/puppy-companion-objects.mjs +12 -0
  30. package/puppy-companion-sprites.mjs +4 -2
  31. package/puppy-frame-predictive.mjs +15 -9
  32. package/puppy-frame-store.mjs +2 -2
  33. package/puppy-paper-actions.mjs +4 -2
  34. package/puppy-paper-behaviors.mjs +1 -1
  35. package/puppy-paper-contact.mjs +4 -2
  36. package/puppy-paper-seat.mjs +4 -2
  37. package/puppy-paper-world.mjs +14 -0
  38. package/puppy-paper.mjs +79 -21
  39. package/retrieviewer-cli.mjs +8 -6
  40. package/retrieviewer-plugin.d.mts +1 -1
  41. package/retrieviewer-plugin.mjs +1 -1
  42. package/retrieviewer.d.mts +12 -4
  43. package/retrieviewer.mjs +20 -14
  44. package/locomotion-core.mjs +0 -21
  45. package/locomotion-puppy.animal +0 -0
  46. package/vendor/procedural-animals/package.json +0 -40
  47. package/vendor/procedural-animals/src/animal.js +0 -146
  48. package/vendor/procedural-animals/src/bake.js +0 -51
  49. package/vendor/procedural-animals/src/build.js +0 -81
  50. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  51. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  52. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  53. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  54. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  55. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  56. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  57. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  58. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  59. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  60. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  61. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  62. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  63. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  64. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  65. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  66. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  67. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  68. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  69. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  70. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  71. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  72. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  73. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  74. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  75. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  76. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  77. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  79. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  80. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  81. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  82. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  83. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  84. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  85. package/vendor/procedural-animals/src/index.d.ts +0 -98
  86. package/vendor/procedural-animals/src/index.js +0 -29
  87. package/vendor/procedural-animals/src/registry.js +0 -48
  88. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  89. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  90. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  91. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  92. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  94. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  95. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  96. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  97. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  98. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  99. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  100. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  101. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  102. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  103. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  104. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  105. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  106. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  107. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  108. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  109. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  110. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  111. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  112. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  113. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  114. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  115. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  116. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  117. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  118. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  119. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  120. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  121. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  122. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  123. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  124. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  125. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  126. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  127. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  128. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  129. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  130. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  131. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  132. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  133. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  134. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  135. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  136. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  137. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  138. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  139. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  140. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  141. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  142. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  143. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  144. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  145. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  146. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  147. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  148. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  149. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  150. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  151. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  152. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  153. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  154. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  155. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  156. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  157. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  158. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  159. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  160. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  161. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  162. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  163. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  164. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  165. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  166. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  167. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  168. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  169. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  170. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  171. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  172. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  173. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  174. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  175. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  176. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  177. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  178. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  179. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  180. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  181. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  182. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  183. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  184. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  185. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  186. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  187. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  188. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  189. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  190. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  191. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  192. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  193. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  194. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  195. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  196. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  197. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  198. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  199. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  200. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  201. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  202. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  203. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  204. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  205. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  206. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  207. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  208. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  209. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  210. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  211. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  212. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  213. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  214. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  215. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  216. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  217. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  218. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  219. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  220. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  221. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  222. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  223. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  224. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  225. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  226. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  227. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  228. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  229. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  230. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  231. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  232. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  233. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  234. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  235. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  236. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  237. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  238. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  239. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  240. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  241. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  242. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  243. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  244. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  245. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  246. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  247. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  248. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  249. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  250. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  251. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  252. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  253. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  254. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  255. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  256. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  257. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  258. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  259. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  260. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  261. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  262. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  263. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,272 +0,0 @@
1
- // The brown bear, sculpted as a signed distance field.
2
- // Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw). A bear's
3
- // silhouette is mostly coat over fat and muscle: the barrel, the shoulder hump (trapezius /
4
- // rhomboid digging muscle plus fat and the long guard hair of the ruff), the heavy "trousers" of the
5
- // forearms and thighs are sculpted as volume, the shells add the hair on top.
6
- // params: hump (0 black bear .. 1.3 big grizzly male), dish (dished facial profile 0..1), headW,
7
- // muzzle, ear, juv (cub: domed head), girth, claw (claw length factor).
8
- import { HEAD_O, TAIL_SEGS, hlOf } from './rig.js';
9
- import { add, sub, mul, lerp, norm, cross } from '../../core/math/vec.js';
10
- import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
11
-
12
- // Eye geometry shared by the sculpt (lids + aperture) and the eyeball shader (head-local, left eye).
13
- // Small eyes (~16 x 11 mm palpebral fissure, eyeball r 12 mm), set forward (diverging ~20 deg),
14
- // ~90 mm apart (x headW; face1 / face2: the nose pad's bottom 0.91-0.98 eye separations under the eye line);
15
- // the eyeball front flush with the skin (1.08 r: at 0.043 / 0.0668 the brow, bridge and cheek stood 5 mm in
16
- // front of it, 1.36 r, and the lids were buried in a tunnel).
17
- export const EYE_BASE = { c: [0.045, 0.03, 0.0698], r: 0.014, back: 0.003, yaw: 0.36, pitch: 0.06, lid: 0.0018, R: 0.0116, d: 0.0049, off: -0.0014, tilt: (10 * Math.PI) / 180, irisZ: 0.0086, irisR: 0.0108 };
18
- export const eyeOf = (params = {}) => ({ ...EYE_BASE, c: [EYE_BASE.c[0] * (params.headW || 1), EYE_BASE.c[1], EYE_BASE.c[2]] });
19
-
20
- const X = [1, 0, 0];
21
- // the toe joints (ftoe / htoe) sit at the claw tips: the paw rolls about them at push-off, so the
22
- // claws never dig into the ground (m ahead of the toe pads)
23
- export const TOE_F = 0.05, TOE_H = 0.035;
24
-
25
- export function sculptBear(m, rig, params = {}) {
26
- const J = rig.J;
27
- const EYE = eyeOf(params);
28
- const mz = params.muzzle || 1, hw = params.headW || 1, juv = params.juv || 0;
29
- const hump = params.hump ?? 1, dish = params.dish ?? 1, girth = params.girth ?? 1;
30
- const hl = hlOf(mz, hw);
31
- const hr = (r) => [r[0] * hw, r[1], r[2]]; // radii in head space
32
- const eyeFrame = (s) => eyeFrameOf(EYE, HEAD_O, s);
33
-
34
- // Ellipsoid whose local Y follows `ydir`; radii = [lateral, along ydir, third axis]
35
- const ellY = (o) => {
36
- const y = norm(o.ydir);
37
- const lat = o.lateral || X;
38
- const z = norm(cross(lat, y));
39
- return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
40
- };
41
- const G = girth;
42
- // the barrel is broad: from the front a brown bear's body with its coat is ~2x its head and ~3x its
43
- // stance wide (tq2, front3); the torso's lateral radii carry this factor (1: the body read narrow,
44
- // 1.3x its head, under an oversized head)
45
- const GW = G * 1.12;
46
-
47
- // ---------------------------------------------------------------- TORSO
48
- // a deep, very broad barrel; the back line dips a little behind the hump and rises to the rump;
49
- // the belly hangs low with its fringe of long hair
50
- m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 0.665, 0.06], r: [0.235 * GW, 0.22, 0.32], axis: norm([0, 0.05, -1]), k: 0 });
51
- m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.58, 0.29], r: [0.175 * GW, 0.17, 0.14], axis: norm([0, -0.3, 1]), k: 0.08 });
52
- m.ell({ tag: 'pectoral', bone: 'chest', c: [0, 0.645, 0.38], r: [0.165 * GW, 0.15, 0.09], k: 0.07 });
53
- m.ell({ tag: 'withers', bone: 'chest', c: [0, 0.84, 0.17], r: [0.15 * GW, 0.09, 0.18], axis: norm([0, -0.1, 1]), k: 0.08 });
54
- // the hump: a mass of muscle and fat over the shoulder blades (absent in the black bear), a broad
55
- // swell from the nape well back over the shoulders that runs into the back line (side1, side2, tq4);
56
- // (a 32 cm long, 14 cm tall mound stood up on big males like a camel's hump)
57
- // (moderate: the back's highest point over the shoulders, ~4-6 % of the height above the back behind it, side1 / side2;
58
- // 12 cm above it, blended with k 0.12, the render's was 10-13 % and read as a big mound)
59
- if (hump > 0.05) {
60
- const ry = 0.03 * hump + 0.035;
61
- m.ell({ tag: 'hump', bone: 'chest', c: [0, 0.905 + 0.03 * hump - ry, 0.225], r: [0.13 * GW * (0.7 + 0.3 * hump), ry, 0.24], axis: norm([0, 0.08, 1]), k: 0.07 });
62
- }
63
- m.ell({ tag: 'back', bone: 'spine3', c: [0, 0.82, -0.04], r: [0.17 * GW, 0.08, 0.18], k: 0.08 });
64
- m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 0.655, -0.14], r: [0.195 * GW, 0.19, 0.22], axis: norm([0, 0.08, -1]), k: 0.08 });
65
- // (4 cm lower and longer than before: the belly line hangs at 0.23-0.30 of the hump height in side1-3, the
66
- // render's was 0.34-0.36)
67
- // (its front end stays 5 cm behind the elbows: reaching them, it took forearm weight and the lying forearm
68
- // pressed 11 mm into the ground as the bear rose from sleep)
69
- m.ell({ tag: 'belly', bone: 'spine2', c: [0, 0.475, -0.05], r: [0.12 * GW, 0.09, 0.2], k: 0.08 });
70
- m.ell({ tag: 'loin', bone: 'spine1', c: [0, 0.81, -0.21], r: [0.17 * GW, 0.08, 0.15], k: 0.07 });
71
- m.ell({ tag: 'flank', bone: 'spine1', c: [0, 0.665, -0.28], r: [0.19 * GW, 0.17, 0.14], k: 0.07 });
72
- m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 0.73, -0.4], r: [0.19 * GW, 0.155, 0.15], k: 0.07 });
73
- m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 0.82, -0.38], r: [0.15 * GW, 0.07, 0.14], k: 0.06 });
74
- // the crotch between the thighs belongs to the body (anchors the midline skin: the thighs fold
75
- // forward when lying and pulled it apart)
76
- m.ell({ tag: 'perineum', bone: 'pelvis', c: [0, 0.6, -0.45], r: [0.07, 0.07, 0.13], k: 0.06 });
77
- for (const s of [1, -1]) {
78
- // round heavy buttocks behind the thighs
79
- m.ell({ tag: 'rump', bone: 'pelvis', c: [0.09 * s * GW, 0.7, -0.5], r: [0.105 * GW, 0.14, 0.1], k: 0.06 });
80
- }
81
-
82
- // ---------------------------------------------------------------- NECK (short, as thick as the head)
83
- m.cone({ tag: 'neck', bone: 'neck1', a: [0, 0.72, 0.32], b: [0, 0.775, 0.49], ra: 0.17 * G, rb: 0.135, k: 0.08 });
84
- m.cone({ tag: 'neck', bone: 'neck2', a: [0, 0.775, 0.49], b: [0, 0.785, 0.58], ra: 0.125, rb: 0.1, k: 0.07 });
85
- m.ell({ tag: 'nape', bone: 'neck1', c: [0, 0.83, 0.37], r: [0.11 * G, 0.06, 0.13], axis: norm([0, 0.25, 1]), k: 0.07 });
86
- m.ell({ tag: 'throat', bone: 'neck2', c: [0, 0.67, 0.51], r: [0.095, 0.09, 0.1], axis: norm([0, 0.6, 0.8]), k: 0.08 });
87
- // ruff: long hair down the sides of the neck and under the throat
88
- m.ell({ tag: 'ruff', bone: 'neck1', c: [0, 0.7, 0.42], r: [0.15 * G, 0.14, 0.12], axis: norm([0, 0.4, 1]), k: 0.07 });
89
-
90
- // ---------------------------------------------------------------- HEAD (head-local coords, see HEAD_O)
91
- // landmarks from profile / frontal photos (adult ~0.36 m occiput -> nose): nose tip
92
- // (0, -0.012, 0.215), eyes (+-0.047, 0.03, 0.058), stop z ~0.085, chin (0, -0.085, 0.16), skull top
93
- // ~0.1 (the forehead rises steeply from the muzzle: the "dished" profile), occiput -0.125,
94
- // cheek width ~0.3 m with the cheek ruff. A cub has a domed skull and a short muzzle (params).
95
- const H = { bone: 'head' };
96
- // Profile (side2, face3_profile, the head levelled): the top line rises ~13 deg along the bridge from
97
- // the nose pad to the eyes, then ~23 deg up a long forehead to a low crown between the ears (y ~0.11):
98
- // a shallow dish at the stop, not a dome (a forehead climbing 8 cm within 4 cm in front of the eyes
99
- // stood up as a cap). Straighter in the black bear (dish 0.15); a cub's skull is domed.
100
- // braincase: long, broad, low-crowned
101
- // (broad: at 8.8 cm wide the braincase with the crest read as a ball between ears on top of it)
102
- m.ell({ ...H, tag: 'cranium', c: hl([0, 0.028 + 0.014 * juv, -0.04]), r: hr([0.094, 0.076 + 0.016 * juv, 0.1]), k: 0.05 });
103
- // forehead: a broad slope from the stop between the eyes back and up to the crown
104
- m.cone({ ...H, tag: 'forehead', a: hl([0, 0.02 + 0.006 * (1 - dish) + 0.008 * juv, 0.045]), b: hl([0, 0.042 + 0.012 * juv, -0.07]), ra: 0.041 * hw, rb: 0.068 * hw, k: 0.05 });
105
- // crown between the ears (temporal muscles over the sagittal crest)
106
- // (broad and flat between the ears: the temporal muscles fill the skull's top out to the ear bases)
107
- m.ell({ ...H, tag: 'crest', c: hl([0, 0.068 + 0.012 * juv, -0.066]), r: hr([0.1, 0.04, 0.075]), k: 0.05 });
108
- for (const s of [1, -1]) {
109
- // (over the upper lid: face2 / face3_profile show hooded eyes under a brow; 5 mm higher and 4 mm further back
110
- // the eyes were round beads with nothing over them)
111
- m.ell({ ...H, tag: 'brow', c: hl([0.042 * s, 0.0475, 0.056]), r: hr([0.021, 0.0095, 0.017]), k: 0.012 });
112
- // the widest part of the skull: the zygomatic arches at and behind the eyes
113
- // (wider: the face is broadest across the cheekbones, face1 / face2)
114
- m.ell({ ...H, tag: 'zygomatic', c: hl([0.079 * s, 0.004, -0.014]), r: hr([0.043, 0.043, 0.066]), axis: norm([-0.25 * s, 0, 1]), k: 0.06 });
115
- // fleshy cheek over the upper jaw below the eye (no jowl: nothing hangs below the jaw line)
116
- m.ell({ ...H, tag: 'cheek', c: hl([0.05 * s, -0.014, 0.035]), r: hr([0.036, 0.03, 0.052]), k: 0.05 });
117
- // skin under the cheek ruff behind the zygoma (the broad face is hair: coat.js)
118
- // (heavy and low, down beside the jaw: the cheeks run on into the neck, face1 / tq1 / front1)
119
- m.ell({ ...H, tag: 'cheekruff', c: hl([0.076 * s, -0.016, -0.078]), r: hr([0.045, 0.056, 0.06]), axis: norm([0.3 * s, 0, 1]), k: 0.07 });
120
- m.ell({ ...H, tag: 'masseter', c: hl([0.055 * s, -0.03, -0.04]), r: hr([0.032, 0.036, 0.048]), k: 0.06 });
121
- // the upper lip hangs a little over the lower jaw
122
- // (reaching back to the mouth corner below the front of the eye: the lip line runs straight back from
123
- // the chin; a gap between the lip and the masseter bowed it up into a smile)
124
- // (its rear end 1 cm lower: the mouth line runs straight back and a little down to the corner, face3_profile /
125
- // tq1 / front1; rising toward the cheek it read as a smile)
126
- m.ell({ ...H, tag: 'lip', c: hl([0.029 * s, -0.058, 0.112]), r: hr([0.018, 0.026, 0.09]), axis: norm([-0.2 * s, 0.06, 1]), k: 0.02 });
127
- // (the upper lip's rear, back to the mouth corner below the front of the eye: the lip above tapered there and the head's
128
- // lower outline rose ~2 cm over its last 4 cm, so the mouth line turned up toward the cheek, a smile in the low 3/4;
129
- // tq1 / front1 / face3_profile: the line runs straight back and a little down to the corner)
130
- m.ell({ ...H, tag: 'lip', c: hl([0.04 * s, -0.07, 0.035]), r: hr([0.017, 0.017, 0.048]), axis: norm([-0.3 * s, 0.14, 1]), k: 0.018 });
131
- m.ell({ ...H, tag: 'whisker', c: hl([0.022 * s, -0.035, 0.172]), r: hr([0.017, 0.019, 0.03]), k: 0.018 });
132
- }
133
- // long, broad muzzle; a shallow concave line from the stop to the nose (straighter in the
134
- // black bear), a big black nose pad standing proud of the muzzle
135
- // (the bridge runs down to the nose pad's top edge: the pad is the tip of the muzzle)
136
- // (the bridge runs straight down onto the top edge of the nose pad, side2: 1.4 cm above it the pad sat on the
137
- // front of a round snout like a teddy's button nose)
138
- m.cone({ ...H, tag: 'nasal', a: hl([0, 0.016 + 0.012 * (1 - dish), 0.07]), b: hl([0, -0.008, 0.19]), ra: 0.038 * hw, rb: 0.024 * hw, k: 0.03 });
139
- // (narrower in front: face2 shows the muzzle 1.4 x the nose pad wide at the pad, the render was 1.8 x)
140
- m.ell({ ...H, tag: 'muzzle', c: hl([0, -0.028, 0.13]), r: hr([0.04, 0.048, 0.08]), axis: norm([0, -0.1, 1]), k: 0.03 });
141
- // (0.57 x the eye separation wide, as face1 / face2; flat in front, its top edge rolling back into the
142
- // bridge; a ball-shaped pad read as a clown's nose)
143
- // The pad (face2, face1, front3, tq1): a rounded trapezoid ~1.3 x as wide as tall (0.44-0.51 eye separations), its
144
- // top edge broad and nearly straight, its front flat, the bottom straight across with the nostrils at its corners;
145
- // two lobes (one smooth oval read as a rubber button)
146
- // (flat-fronted: the old 14 mm deep lobes, the lower one narrowing to a point between the nostrils, made a glossy dome
147
- // over a downward beak)
148
- m.ell({ ...H, tag: 'nose', c: hl([0, 0.0078, 0.2105]), r: hr([0.0238, 0.0135, 0.0118]), axis: norm([0, -0.12, 1]), k: 0.006 });
149
- m.ell({ ...H, tag: 'nose', c: hl([0, -0.009, 0.2105]), r: hr([0.0218, 0.0125, 0.0115]), axis: norm([0, -0.2, 1]), k: 0.008 });
150
- // (the upper lip hangs as low in the middle as at the sides: a notch there showed the chin as a pout)
151
- m.ell({ ...H, tag: 'philtrum', c: hl([0, -0.049, 0.194]), r: hr([0.018, 0.025, 0.013]), k: 0.014 });
152
- for (const s of [1, -1]) {
153
- const ef = eyeFrame(s);
154
- const at = (u, v, w) => add(add(add(ef.c, mul(ef.x, u)), mul(ef.y, v)), mul(ef.z, w));
155
- // orbit: a soft hollow so the brow, cheek and bridge fall away to the lids
156
- ellY({ ...H, tag: 'orbit', c: at(-0.002 * s, 0.0, 0.02), ydir: ef.y, lateral: ef.x, r: [0.02, 0.014, 0.012], k: 0.01, carve: true });
157
- m.sphere({ ...H, tag: 'eyelid', c: ef.c, rad: EYE.r + EYE.lid, k: 0.006 });
158
- m.lens({ ...H, tag: 'eyesocket', c: add(ef.c, mul(ef.y, EYE.off)), x: ef.x, y: ef.y, z: ef.z, R: EYE.R, d: EYE.d, zMin: -0.002, zMax: 0.024, k: 0.0022, carve: true });
159
- // nostrils (face2 / face1 / face3_profile / tq1): comma-shaped openings at the lower corners of the pad, a round
160
- // opening that faces down and out (it is drilled along the pad's normal there) and a slit curving up and back along
161
- // the side of the pad; from the front each reads as a dark crescent at the side, the wide septum between them
162
- // (6.8 x 3.4 mm dimples were invisible from the front; round holes facing forward beside the midline
163
- // read as the holes of a button)
164
- m.ell({ ...H, tag: 'nostril', c: hl([0.0135 * s, -0.0138, 0.2172]), r: [0.0064, 0.0045, 0.009], axis: norm([0.35 * s, -0.5, 0.8]), up: [0, 1, 0], k: 0.002, carve: true });
165
- m.ell({ ...H, tag: 'nostril', c: hl([0.0198 * s, -0.0078, 0.2132]), r: [0.0026, 0.0068, 0.0058], axis: norm([0.8 * s, -0.15, 0.6]), up: norm([0.5 * s, 1, 0]), k: 0.002, carve: true });
166
- // canines, hidden behind the lips until the mouth opens
167
- m.cone({ ...H, tag: 'canine', a: hl([0.019 * s, -0.052, 0.148]), b: hl([0.018 * s, -0.066, 0.146]), ra: 0.0055, rb: 0.002, k: 0.002 });
168
- }
169
- // the groove down the middle of the pad's lower half, between the nostrils (it runs on as the philtrum, coat.js)
170
- // (ending above the pad's lower edge: cut through it, it left a lit cusp under the pad, a pale tick)
171
- m.ell({ ...H, tag: 'nosegroove', c: hl([0, -0.0132, 0.2228]), r: [0.0021, 0.0056, 0.0032], axis: norm([0, -0.3, 1]), k: 0.0015, carve: true });
172
-
173
- // ---------------------------------------------------------------- JAW (separate surface so the mouth can open)
174
- const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
175
- // (shallow in front, its top lower toward the mouth corner: face3_profile / side2 show a small receding chin, the
176
- // nose pad : upper lip : chin ~1 : 0.95 : 0.3 in depth, and a mouth line running straight back and a little down;
177
- // a 3 cm deep jaw whose top rose toward the hinge bulged below a mouth line curving up into a smile)
178
- m.cone({ ...JW, tag: 'mandible', a: hl([0, -0.07, -0.02]), b: hl([0, -0.076, 0.152]), ra: 0.026, rb: 0.012, k: 0 });
179
- for (const s of [1, -1]) {
180
- m.cone({ ...JW, tag: 'mandible', a: hl([0.05 * s, -0.066, -0.035]), b: hl([0.014 * s, -0.073, 0.152]), ra: 0.02, rb: 0.011, k: 0.025 });
181
- }
182
- // a small chin set back under the upper lip
183
- // (small: a chin standing 2 cm below the upper lip, painted pale, read as a pout)
184
- m.ell({ ...JW, tag: 'chin', c: hl([0, -0.079, 0.1]), r: hr([0.016, 0.01, 0.02]), k: 0.02 });
185
- m.cone({ ...JW, tag: 'canine', a: hl([0.017, -0.078, 0.155]), b: hl([0.018, -0.068, 0.157]), ra: 0.005, rb: 0.0018, k: 0.002 });
186
- m.cone({ ...JW, tag: 'canine', a: hl([-0.017, -0.078, 0.155]), b: hl([-0.018, -0.068, 0.157]), ra: 0.005, rb: 0.0018, k: 0.002 });
187
-
188
- // ---------------------------------------------------------------- EARS
189
- // small, round, furred, set wide on the top corners of the head
190
- for (const S of ['L', 'R']) {
191
- const s = S === 'L' ? 1 : -1;
192
- const base = J['earBase' + S], tip = J['earTip' + S];
193
- const up = norm(sub(tip, base));
194
- // (turned a little more outward than before, 0.45: in profile the thin pinna was seen edge-on and its fur drew a
195
- // tall, pointed ear; side1 / face3_profile show a short round ear from the side)
196
- const facing = norm([0.6 * s, 0.05, 1]);
197
- const lat = norm(cross(up, facing));
198
- const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
199
- const at = (t) => lerp(base, tip, t);
200
- const ek = params.ear || 1;
201
- // (a thin pinna, 9 cm wide before the head warp, its bulk the hair: an 11 cm, 4 cm thick paddle with a
202
- // round dish carved in front read as a teddy's cup ear with a thick rim; tq2, front2, face1 / face2 show
203
- // small ears 0.6-0.9 x the eye separation wide with their fur)
204
- ellY({ ...ear, tag: 'ear', c: at(0.3), ydir: up, lateral: lat, r: [0.045 * ek, 0.043 * ek, 0.012], k: 0.016 });
205
- // (the top a little flatter: face2 / tq2 show a broad rounded top, not a disc)
206
- ellY({ ...ear, tag: 'ear', c: at(0.7), ydir: up, lateral: lat, r: [0.045 * ek, 0.029 * ek, 0.0095], k: 0.016 });
207
- ellY({ ...ear, tag: 'earinner', c: add(at(0.45), mul(facing, 0.0105)), ydir: up, lateral: lat, r: [0.026 * ek, 0.03 * ek, 0.0065], k: 0.006, carve: true });
208
- }
209
-
210
- // ---------------------------------------------------------------- LEGS
211
- // massive columns; broad plantigrade feet with five toes (the long, curved, non-retractile claws: claws.js)
212
- for (const S of ['L', 'R']) {
213
- const s = S === 'L' ? 1 : -1;
214
- const lat = [s, 0, 0];
215
- const sx = (v) => [v[0] * s, v[1], v[2]];
216
- const g = 'F' + S;
217
- const Sc = J['scapTop' + S], Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S], M = J['mcp' + S], T = J['ftoe' + S];
218
- ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.5), sx([0.02, 0, 0])), ydir: sub(Sh, Sc), lateral: lat, r: [0.07, 0.17, 0.13], k: 0.08, bias: 0.004 });
219
- m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.11, rb: 0.085, k: 0.08 });
220
- ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.5), [0, 0, -0.06]), ydir: sub(E, Sh), lateral: lat, r: [0.085, 0.15, 0.08], k: 0.07 });
221
- ellY({ tag: 'armpit', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.75), sx([-0.035, 0.02, 0.02])), ydir: sub(E, Sh), lateral: lat, r: [0.06, 0.1, 0.08], k: 0.08 });
222
- m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.02, -0.035]), rad: 0.045, k: 0.04 });
223
- m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: E, b: W, ra: 0.08, rb: 0.056, k: 0.04 });
224
- ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.3), sx([0.008, 0, 0.012])), ydir: sub(W, E), lateral: lat, r: [0.075, 0.13, 0.075], k: 0.05 });
225
- // "sleeves": the long hair behind the forearm
226
- ellY({ tag: 'feather', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.4), [0, 0, -0.045]), ydir: sub(W, E), lateral: lat, r: [0.055, 0.13, 0.045], k: 0.04 });
227
- m.sphere({ tag: 'wrist', bone: 'metacarpus' + S, group: g, c: add(W, [0, -0.002, 0.0]), rad: 0.05, k: 0.03 });
228
- // the forepaw: a broad pad of a hand, the palm flat on the ground
229
- const dp = norm(sub(T, M));
230
- // (flat: a flexed paw shows a flat palm, not a ball)
231
- ellY({ tag: 'palm', bone: 'metacarpus' + S, group: g, c: add(lerp(W, M, 0.55), [0, -0.016, 0]), ydir: sub(M, W), lateral: lat, r: [0.07, 0.07, 0.027], k: 0.03 });
232
- m.sphere({ tag: 'carpalpad', bone: 'metacarpus' + S, group: g, c: add(W, [0, -0.052, -0.02]), rad: 0.017, k: 0.015 });
233
- ellY({ tag: 'paw', bone: 'fpaw' + S, group: g, c: add(add(M, mul(dp, 0.01)), [0, 0.003, 0]), ydir: dp, lateral: lat, r: [0.075, 0.045, 0.03], k: 0.022 });
234
- // five toes in an arc (the claws are their own surfaces, claws.js)
235
- const toeX = [-0.056, -0.028, 0.0, 0.028, 0.056], toeZ = [-0.02, -0.004, 0.002, -0.004, -0.02];
236
- for (let i = 0; i < 5; i++) {
237
- const tp = [T[0] + toeX[i] * s, 0.024, T[2] - TOE_F - 0.012 + toeZ[i]];
238
- m.sphere({ tag: 'toe', bone: 'fpaw' + S, group: g, c: tp, rad: 0.019, k: 0.012 });
239
- }
240
-
241
- const h = 'H' + S;
242
- const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S], Mt = J['mtp' + S], Tt = J['htoe' + S];
243
- // broad heavy thighs ("trousers" of long hair behind), the flank fold running into the belly
244
- ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.02, 0, -0.03])), ydir: sub(K, Hp), lateral: lat, r: [0.1, 0.24, 0.16], k: 0.08, bias: 0.004 });
245
- m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.12, 0.7, -0.22]), b: add(K, sx([-0.01, 0.07, 0.02])), ra: 0.1, rb: 0.07, k: 0.08 });
246
- m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.13, 0.74, -0.46]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.05]), ra: 0.1, rb: 0.07, k: 0.07 });
247
- ellY({ tag: 'breeches', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.65), sx([0.015, -0.02, -0.12])), ydir: sub(K, Hp), lateral: lat, r: [0.075, 0.13, 0.06], k: 0.05 });
248
- ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.13, 0.56, -0.16]), ydir: [-0.05, 0.2, 0.12], lateral: lat, r: [0.05, 0.13, 0.08], k: 0.08 });
249
- m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.005, 0.01, 0.015])), rad: 0.055, k: 0.05 });
250
- m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.05), b: Hk, ra: 0.065, rb: 0.052, k: 0.04 });
251
- ellY({ tag: 'calf', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.3), sx([0.004, 0.01, -0.045])), ydir: sub(Hk, K), lateral: lat, r: [0.065, 0.12, 0.06], k: 0.05 });
252
- m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, -0.02, -0.035]), rad: 0.045, k: 0.03 });
253
- m.sphere({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: Hk, rad: 0.056, k: 0.03 });
254
- // the long flat sole: heel pad on the ground behind the hock
255
- const dh = norm(sub(Tt, Mt));
256
- ellY({ tag: 'sole', bone: 'metatarsus' + S, group: h, c: add(lerp(Hk, Mt, 0.5), [0, -0.006, 0]), ydir: sub(Mt, Hk), lateral: lat, r: [0.062, 0.12, 0.032], k: 0.03 });
257
- ellY({ tag: 'paw', bone: 'hpaw' + S, group: h, c: add(add(Mt, mul(dh, 0.012)), [0, 0.004, 0]), ydir: dh, lateral: lat, r: [0.066, 0.042, 0.03], k: 0.022 });
258
- const hx = [-0.048, -0.024, 0.0, 0.024, 0.048], hz = [-0.018, -0.004, 0.002, -0.004, -0.016];
259
- for (let i = 0; i < 5; i++) {
260
- const tp = [Tt[0] + hx[i] * s, 0.022, Tt[2] - TOE_H - 0.012 + hz[i]];
261
- m.sphere({ tag: 'toe', bone: 'hpaw' + S, group: h, c: tp, rad: 0.017, k: 0.011 });
262
- }
263
- }
264
-
265
- // ---------------------------------------------------------------- TAIL
266
- // a stubby tail (~10 cm with its hair), hidden in the rump fur
267
- m.cone({ tag: 'tailroot', bone: 'tail0', a: [0, 0.77, -0.46], b: J.tail1, ra: 0.045, rb: 0.035, k: 0.04 });
268
- for (let i = 0; i < TAIL_SEGS; i++) {
269
- m.cone({ tag: 'tail', bone: 'tail' + i, a: J['tail' + i], b: J['tail' + (i + 1)], ra: 0.035 - 0.01 * i, rb: 0.025 - 0.01 * i, k: 0.02, thin: i === TAIL_SEGS - 1 });
270
- }
271
- return m;
272
- }
@@ -1,335 +0,0 @@
1
- // Wild boar 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: the mane (P.furRaise: the long dorsal
4
- // bristles rise when the boar is alarmed, charging, hit or galloping away;
5
- // `motion.input.alarm = true` raises it on demand), jaw champing in a threat,
6
- // ear flicks, the lazy tail swing, automatic idle variants (sniffing with the
7
- // snout raised, a bout of rooting, a head shake, grunting), the charge steering
8
- // of the attack and the spin of the hit
9
- // actions eat (rooting: the disc ploughs the ground in forward-upward thrusts, the head
10
- // swinging side to side), sniff (snout raised, the disc testing the air in
11
- // quick small movements, ears forward), grunt, headshake, attack (a threat with
12
- // the head low and champing jaws, then a fast head-low charge ending in an upward
13
- // tusk hook with a head toss: attackHit style 'tusk'), hit (squeal and spin round
14
- // toward the blow), lie (on the belly, forelegs folded), sleep (flat on the side,
15
- // legs out)
16
- //
17
- // Posture lengths (headRaise, neckReach, fwd, side) are engine units (x cfg.k): species metres /
18
- // cfg.unit.
19
- import * as THREE from 'three';
20
- import { clamp, smooth, TAU } from '../../core/motion/common.js';
21
- import { mx, ONESHOTS, POSTURES } from '../../core/motion/actions.js';
22
-
23
- const PI = Math.PI;
24
- const sm = (w) => w * w * (3 - 2 * w);
25
-
26
- function B(e) {
27
- if (!e._boar) {
28
- e._boar = {
29
- ear: [{ age: 9, next: 2 + e.rand() * 4, a: 0 }, { age: 9, next: 3 + e.rand() * 5, a: 0 }],
30
- idleT: 6 + e.rand() * 6,
31
- raise: 0,
32
- charge: null, spin: null,
33
- };
34
- }
35
- return e._boar;
36
- }
37
- const busy = (L) => L.postures.length > 0 || L.oneshots.some((a) => !a.stopping);
38
-
39
- // ------------------------------------------------------------------------------------------------
40
- function update(P, dt, L) {
41
- const e = L.engine, b = B(e), cfg = e.cfg;
42
- const vn = e.speed / cfg.sq;
43
- const still = 1 - smooth(0.1, 0.6, vn);
44
-
45
- // --- mane: raised in alarm (galloping away, charging, hit, on demand)
46
- let want = e.input.alarm ? 1 : 0;
47
- want = Math.max(want, 0.55 * smooth(4.2, 6.5, vn) * P.gaitW);
48
- for (const a of L.oneshots) {
49
- const w = sm(a.w);
50
- if (a.name === 'attack') want = Math.max(want, w);
51
- else if (a.name === 'hit') want = Math.max(want, 0.8 * w);
52
- else if (a.name === 'sniff') want = Math.max(want, 0.25 * w);
53
- }
54
- // (eased here as well as in the renderer: a smooth rise, a slower fall)
55
- b.raise += (want - b.raise) * (1 - Math.exp(-dt * (want > b.raise ? 6 : 1.5)));
56
- P.furRaise = Math.max(P.furRaise || 0, b.raise);
57
- // alarm: head up, ears forward, tail out stiff
58
- if (e.input.alarm) {
59
- const k = still * (busy(L) ? 0.3 : 1);
60
- mx(P, 'headRaise', 0.05 / cfg.unit, k); mx(P, 'headPitch', -0.2, k); mx(P, 'earFlat', -0.2, k);
61
- mx(P, 'tailLift', 0.5, 1); mx(P, 'tailStiff', 1.6, 1);
62
- }
63
-
64
- // --- ear flicks (one ear at a time)
65
- for (const E of b.ear) {
66
- E.age += dt; E.next -= dt * (0.5 + 0.5 * still);
67
- if (E.next <= 0) { E.next = 3 + e.rand() * 7; E.age = 0; E.a = 0.6 + 0.5 * e.rand(); }
68
- if (E.age < 0.5) { const f = Math.sin(Math.min(1, E.age / 0.45) * PI) * E.a; mx(P, 'earTwitch', 2.2, f * (1 - P.eyelid)); }
69
- }
70
-
71
- // --- dead / flat on the side: ears fold back along the neck, out of the ground
72
- const dead = L.postures.reduce((m, p) => (p.name === 'death' ? Math.max(m, p.w) : m), 0);
73
- const flat = Math.max(smooth(0.3, 1.0, Math.abs(P.roll)), dead * smooth(0.3, 1, dead));
74
- if (flat > 0) { mx(P, 'earFlat', 1.1, flat); mx(P, 'earTwitch', 0, flat); }
75
- // ... and the long, deep head (carried 40 deg nose-down in the bind pose) lies level on its cheek:
76
- // nose up to the horizontal, the occiput higher than a cat-sized skull, rolled with the body rather
77
- // than twisted on the atlas (the throat stretched)
78
- let side = 0, lift = 0;
79
- for (const p of L.postures) {
80
- const k = p.name === 'death' ? sm(clamp(p.w, 0, 1)) * smooth(0.35, 1.0, p.t) : p.name === 'sleep' && p.tune.style === 'lateral' ? sm(clamp(p.w, 0, 1)) * smooth(1.0, 3.0, p.t) : 0;
81
- if (k > side) { side = k; lift = p.name === 'death' ? 0.08 * k + 0.12 * Math.sin(Math.PI * k) : 0.16 * k + 0.12 * Math.sin(Math.PI * k); }
82
- // (going down to sleep: the head is held clear of the ground while the body settles and rolls)
83
- if (p.name === 'sleep' && p.tune.style === 'lateral') {
84
- const k2 = sm(clamp(p.w, 0, 1)) * smooth(0.4, 3.0, p.t);
85
- lift = Math.max(lift, 0.16 * k2 + 0.16 * Math.sin(Math.PI * k2));
86
- side = Math.max(side, 1e-4);
87
- }
88
- }
89
- if (side > 0 && P.headW > 0) {
90
- P.headPos.y += lift * cfg.s;
91
- P.headPitch -= 0.3 * side;
92
- P.headRoll *= 1 - 0.5 * side;
93
- }
94
- // the thin tail lies straight along the ground (no curl round the body)
95
- P.tailSide *= 0.35;
96
-
97
- // --- charge steering (attack)
98
- const cg = b.charge;
99
- if (cg) {
100
- if (cg.run) {
101
- e.wantSpeed = Math.max(e.wantSpeed, cg.speed * smooth(0, 0.4, cg.tRun));
102
- e.wantHeading = cg.heading;
103
- } else if (cg.brake) e.wantSpeed = Math.min(e.wantSpeed, 0);
104
- }
105
- // --- spin round toward a blow (hit)
106
- const sp = b.spin;
107
- if (sp) {
108
- sp.t += dt;
109
- if (sp.t < sp.dur && !L.holdsStill()) e.wantHeading = sp.heading;
110
- else {
111
- if (!(e.input.speed > 0.05) && e.input.heading !== undefined && e.input.heading !== null) e.input.heading = sp.heading;
112
- b.spin = null;
113
- }
114
- }
115
-
116
- // --- any other action interrupts rooting
117
- if (L.oneshots.some((a) => !a.stopping && ['attack', 'jump', 'sniff', 'headshake'].includes(a.name))) for (const a of L.oneshots) if (a.name === 'eat' || a.name === 'drink') a.stopping = true;
118
-
119
- // --- idle variants
120
- if (e.idleVariants !== false && L.idle.w > 0.98 && !busy(L) && !e.input.look && !e.input.follow) {
121
- b.idleT -= dt;
122
- if (b.idleT <= 0) {
123
- b.idleT = 8 + e.rand() * 9;
124
- const r = e.rand();
125
- if (r < 0.35) L.play('sniff');
126
- else if (r < 0.6) L.play('eat', { duration: 3.5 + 3 * e.rand() });
127
- else if (r < 0.75) L.play('headshake');
128
- else L.play('grunt');
129
- }
130
- } else if (busy(L)) b.idleT = Math.max(b.idleT, 3);
131
- }
132
-
133
- // ------------------------------------------------------------------------------------------------
134
- // eat: rooting. The disc goes to the ground ahead of the forefeet (the engine's grazing posture),
135
- // then ploughs forward and flicks up and sideways, turning the soil; the head swings side to side,
136
- // the mouth closed but for short chews, a grunt now and then.
137
- const eat = {
138
- kind: 'oneshot',
139
- start(inst, L) { ONESHOTS.eat.start(inst, L); inst.grunt = 1 + L.engine.rand() * 2; inst.side = L.engine.rand() < 0.5 ? -1 : 1; },
140
- update(inst, dt, L) {
141
- inst.grunt -= dt;
142
- if (inst.grunt <= 0) { inst.grunt = 1.5 + L.engine.rand() * 2.5; L.engine.emit('vocalize', { kind: 'grunt' }); }
143
- return ONESHOTS.eat.update(inst, dt, L);
144
- },
145
- apply(P, w, inst, L) {
146
- ONESHOTS.eat.apply(P, w, inst, L);
147
- const e = L.engine, t = inst.t, s = e.cfg.s;
148
- const k = w * smooth(0.6, 1.4, t);
149
- // thrust cycle ~0.9 s: push the disc forward along the ground, then lever it up and aside
150
- const c = (t * 1.1) % 1;
151
- const push = smooth(0.0, 0.45, c) * (1 - smooth(0.55, 0.95, c));
152
- const flick = smooth(0.4, 0.6, c) * (1 - smooth(0.62, 0.95, c));
153
- const n = Math.floor(t * 1.1);
154
- const side = ((n * 7919) % 3) - 1; // -1, 0, 1: flick direction per thrust
155
- const sh = Math.sin(e.heading), ch = Math.cos(e.heading);
156
- const fwd = 0.045 * s * push * k, lat = 0.03 * s * flick * side * k;
157
- P.headPos.x += sh * fwd + ch * lat; P.headPos.z += ch * fwd - sh * lat;
158
- P.headPos.y += 0.035 * s * flick * k;
159
- P.headPitch -= 0.3 * flick * k;
160
- P.headYaw += (0.22 * Math.sin(t * 0.8 + inst.side) + 0.15 * flick * side) * k;
161
- P.headRoll += 0.12 * flick * side * k;
162
- // the mouth stays closed while the disc ploughs (the engine's grazing chew would hang it open, red
163
- // lining showing): only short chews, the lips barely parting
164
- mx(P, 'jaw', 0.035 * Math.pow(Math.max(0, Math.sin(t * 5.5)), 4), w);
165
- mx(P, 'earFlat', 0.15, k);
166
- },
167
- };
168
-
169
- // sniff: stands still with the snout raised to wind, the disc testing the air in small quick moves,
170
- // ears forward; a short snort at the end
171
- const sniff = {
172
- kind: 'oneshot', fade: 0.4,
173
- start(inst, L) { const e = L.engine; inst.dur = inst.opts.duration ?? 3.2 + e.rand() * 1.5; inst.yaw = (e.rand() - 0.5) * 0.7; inst.snort = false; },
174
- update(inst, dt, L) {
175
- if (!inst.snort && inst.t > inst.dur - 0.6) { inst.snort = true; L.engine.emit('vocalize', { kind: 'snort' }); }
176
- return inst.t >= inst.dur;
177
- },
178
- apply(P, w, inst, L) {
179
- const e = L.engine, t = inst.t, u = 1 / e.cfg.unit;
180
- const up = w * smooth(0, 0.6, t);
181
- mx(P, 'lookW', 0, up);
182
- mx(P, 'headRaise', 0.1 * u, up); mx(P, 'neckReach', 0.02 * u, up);
183
- // (quick small movements of the raised snout: two frequencies)
184
- const tw = 0.05 * Math.sin(t * TAU * 2.3) + 0.035 * Math.sin(t * TAU * 3.7 + 1);
185
- mx(P, 'headPitch', -0.5 + tw, up); mx(P, 'headOmega', 2.2, up);
186
- mx(P, 'headYaw', inst.yaw + 0.25 * Math.sin(t * 0.9) + 0.4 * tw, up);
187
- mx(P, 'earFlat', -0.25, up); mx(P, 'earTwitch', 0.3, up);
188
- const snort = smooth(inst.dur - 0.6, inst.dur - 0.5, t) * (1 - smooth(inst.dur - 0.4, inst.dur - 0.2, t));
189
- mx(P, 'breathAmp', 2.5, snort * w); mx(P, 'headPitch', -0.3, snort * w);
190
- mx(P, 'tailLift', 0.15, up);
191
- },
192
- };
193
-
194
- // grunt: low grunts, the jaw barely opening, head a little down
195
- const grunt = {
196
- kind: 'oneshot', fade: 0.2,
197
- start(inst) { inst.dur = 1.4; inst.n = 0; },
198
- update(inst, dt, L) {
199
- const n = Math.floor((inst.t - 0.2) / 0.4);
200
- if (n >= inst.n && n < 3 && inst.t > 0.2) { inst.n = n + 1; L.engine.emit('vocalize', { kind: 'grunt' }); }
201
- return inst.t >= inst.dur;
202
- },
203
- apply(P, w, inst) {
204
- const t = inst.t;
205
- const g = Math.max(0, Math.sin(clamp((t - 0.2) / 0.4, 0, 3) * PI));
206
- mx(P, 'jaw', 0.05 * g, w); mx(P, 'jawOmega', 25, w); mx(P, 'breathAmp', 1 + 1.5 * g, w);
207
- mx(P, 'headPitch', 0.08, w * smooth(0, 0.3, t));
208
- },
209
- };
210
-
211
- // head shake: fast rotations about the snout axis, ears flapping
212
- const headshake = {
213
- kind: 'oneshot', fade: 0.12,
214
- start(inst) { inst.dur = 0.9; },
215
- update(inst) { return inst.t >= inst.dur; },
216
- apply(P, w, inst) {
217
- const t = inst.t;
218
- const env = smooth(0, 0.12, t) * (1 - smooth(0.5, 0.85, t)) * w;
219
- const s = Math.sin(t * TAU * 4.5);
220
- mx(P, 'headOmega', 5, env);
221
- mx(P, 'headRoll', 0.35 * s, env); mx(P, 'headYaw', 0.12 * s, env); mx(P, 'headPitch', -0.05, env);
222
- mx(P, 'earTwitch', 2.5, env); mx(P, 'eyelid', 0.5, env); mx(P, 'lookW', 0, env);
223
- },
224
- };
225
-
226
- // attack: standing, a short threat (head low, mane up, jaws champing) then the charge; already
227
- // running, straight into the charge. Head low and forward in the run, then at the target an upward
228
- // hook of the lower tusks with a toss of the head to one side (attackHit at the mouth, style 'tusk'),
229
- // then it brakes, ready to wheel and charge again.
230
- const attack = {
231
- kind: 'oneshot', fade: 0.18, needsStand: true,
232
- start(inst, L) {
233
- const e = L.engine, b = B(e), cfg = e.cfg;
234
- inst.run = smooth(1.2, 3.5, e.speed / cfg.sq);
235
- inst.target = inst.opts.target ? inst.opts.target.clone() : null;
236
- inst.side = inst.opts.side ?? (e.rand() < 0.5 ? -1 : 1);
237
- inst.tThreat = inst.run > 0.3 ? 0.15 : 0.9;
238
- let h = e.heading;
239
- if (inst.target) h = Math.atan2(inst.target.x - e.pos.x, inst.target.z - e.pos.z);
240
- const dist = inst.target ? Math.hypot(inst.target.x - e.pos.x, inst.target.z - e.pos.z) : 3.2 * cfg.s;
241
- inst.vRun = clamp(dist * 1.4, 4, 7) * cfg.sq;
242
- inst.tRun = clamp(dist / (inst.vRun * 0.75) + 0.4, 1.0, 2.8);
243
- b.charge = { run: false, brake: false, heading: h, speed: inst.vRun, tRun: 0 };
244
- inst.hitAt = inst.tThreat + inst.tRun;
245
- inst.dur = inst.hitAt + 1.3;
246
- inst.hit = false;
247
- },
248
- update(inst, dt, L) {
249
- const e = L.engine, b = B(e);
250
- const cg = b.charge;
251
- if (cg) {
252
- cg.run = inst.t >= inst.tThreat && inst.t < inst.hitAt + 0.1;
253
- cg.brake = inst.t >= inst.hitAt + 0.1;
254
- if (cg.run) cg.tRun += dt;
255
- }
256
- if (!inst.hit && inst.t >= inst.hitAt) {
257
- inst.hit = true;
258
- const p = new THREE.Vector3().copy(e.cfg.mouthLocal).applyQuaternion(e.headPose.quaternion).add(e.headPose.position);
259
- const dir = e.forward(e.heading, new THREE.Vector3()).add(new THREE.Vector3(0, 0.6, 0)).normalize();
260
- e.emit('attackHit', { position: p, direction: dir, style: 'tusk' });
261
- }
262
- if (inst.t >= inst.dur) { b.charge = null; return true; }
263
- return false;
264
- },
265
- apply(P, w, inst, L) {
266
- const e = L.engine, cfg = e.cfg, u = 1 / cfg.unit, t = inst.t, h = inst.hitAt;
267
- if (inst.target) { P.look = inst.target; mx(P, 'lookW', 0.5, w); }
268
- // threat: head low, jaws champing (tooth clacking)
269
- const thr = smooth(0, 0.25, t) * (1 - smooth(inst.tThreat - 0.1, inst.tThreat + 0.1, t)) * (1 - inst.run);
270
- mx(P, 'jaw', 0.12 * Math.max(0, Math.sin(t * TAU * 4.5)), thr * w); mx(P, 'jawOmega', 35, w);
271
- mx(P, 'headPitch', 0.2, thr * w); mx(P, 'headRaise', -0.04 * u, thr * w); mx(P, 'dropF', 0.08, thr * w);
272
- // charge: head low and forward, ears back, tail up
273
- const low = smooth(inst.tThreat - 0.1, inst.tThreat + 0.3, t) * (1 - smooth(h - 0.12, h, t));
274
- mx(P, 'headRaise', -0.07 * u, low * w); mx(P, 'headPitch', 0.3, low * w); mx(P, 'neckReach', 0.03 * u, low * w);
275
- mx(P, 'earFlat', 0.9, w * smooth(0, 0.3, t)); mx(P, 'earTwitch', 0, w);
276
- mx(P, 'tailLift', 0.7, w * smooth(0, 0.5, t)); mx(P, 'tailStiff', 1.6, w);
277
- // the hook: from low and nose-down, the head tosses up and to one side, the jaw a little open
278
- const hook = smooth(h - 0.12, h + 0.08, t) * (1 - smooth(h + 0.25, h + 0.75, t));
279
- mx(P, 'headPitch', -0.5, hook * w); mx(P, 'headRaise', 0.06 * u, hook * w);
280
- mx(P, 'headRoll', 0.45 * inst.side, hook * w); mx(P, 'headYaw', 0.35 * inst.side, hook * w);
281
- mx(P, 'headOmega', 3, w * smooth(h - 0.3, h - 0.1, t) * (1 - smooth(h + 0.3, h + 0.6, t)));
282
- mx(P, 'jaw', 0.12, hook * w);
283
- // the forequarters drive up into the hook
284
- mx(P, 'dropF', -0.06, hook * w); mx(P, 'pitch', -0.04, hook * w);
285
- },
286
- };
287
-
288
- // hit: the engine's stagger with a squeal (mouth open, head up), then it spins round toward the blow
289
- const hit = {
290
- ...ONESHOTS.hit,
291
- kind: 'oneshot',
292
- start(inst, L) {
293
- ONESHOTS.hit.start(inst, L);
294
- const e = L.engine, b = B(e);
295
- inst.dur = 1.0;
296
- inst.said = false;
297
- // face the source of the blow (the blow travels along inst.dir)
298
- const h = Math.atan2(-inst.dir.x, -inst.dir.z);
299
- b.spin = { heading: h, t: 0, dur: 1.6 };
300
- },
301
- update(inst, dt, L) {
302
- if (!inst.said && inst.t > 0.05) { inst.said = true; L.engine.emit('vocalize', { kind: 'squeal' }); }
303
- return inst.t >= inst.dur;
304
- },
305
- apply(P, w, inst, L) {
306
- ONESHOTS.hit.apply(P, w, inst, L);
307
- const t = inst.t;
308
- const sq = smooth(0.03, 0.12, t) * (1 - smooth(0.5, 0.8, t)) * w;
309
- mx(P, 'jaw', 0.32 + 0.04 * Math.sin(t * TAU * 12), sq); mx(P, 'jawOmega', 35, w);
310
- mx(P, 'headPitch', -0.3, sq); mx(P, 'eyelid', 0.4, sq);
311
- mx(P, 'tailLift', 0.5, w); mx(P, 'tailStiff', 1.5, w);
312
- },
313
- };
314
-
315
- // lie: on the belly, forelegs folded (the engine's kneeling lie, down front first, up front first),
316
- // with the rise finished a little before the posture is released so the end-first springs settle
317
- function lieApply(P, w, inst, L) {
318
- const we = inst.target > 0 ? w : smooth(0.3, 1, w);
319
- POSTURES.lie.apply(P, we, inst, L);
320
- }
321
- const lie = { kind: 'posture', fadeIn: 1.4, fadeOut: 1.3, apply: lieApply };
322
-
323
- // sleep: flat on the side, legs extended (lateral recumbency, the usual sleeping posture in the nest)
324
- const sleep = {
325
- kind: 'posture', fadeIn: 1.5, fadeOut: 1.4,
326
- apply(P, w, inst, L) {
327
- const we = inst.target > 0 ? w : smooth(0.3, 1, w);
328
- POSTURES.sleep.apply(P, we, inst, L);
329
- },
330
- };
331
-
332
- export const hooks = {
333
- update,
334
- actions: { eat, sniff, grunt, headshake, attack, hit, lie, sleep },
335
- };