@bitefoundry/retrieviewer 0.0.3 → 0.0.4

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