@bitefoundry/retrieviewer 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ASSET-PROVENANCE.md +43 -0
- package/ASSETS-LICENSE.md +58 -0
- package/ATTRIBUTION.md +54 -0
- package/EMBEDDING.md +96 -0
- package/INSTALLING.md +126 -0
- package/LICENSE +16 -0
- package/LICENSE-CODE +21 -0
- package/PLUGIN.md +110 -0
- package/PUPPY-MEMORY.md +57 -0
- package/PUPPY-Q5-COMPARISON.md +73 -0
- package/README.md +86 -2
- package/RELEASING.md +70 -0
- package/THIRD-PARTY-NOTICES.md +17 -0
- package/TRADEMARKS.md +21 -0
- package/WINDOWS-COMPANION.md +113 -0
- package/braille.mjs +45 -0
- package/examples/bridge.py +62 -0
- package/examples/embed.mjs +15 -0
- package/examples/live.mjs +40 -0
- package/examples/plugin.mjs +21 -0
- package/integrations/opencode/runtime.mjs +195 -0
- package/integrations/opencode/tui.tsx +59 -0
- package/integrations/windows-overlay/Overlay.cs +259 -0
- package/integrations/windows-overlay/desktop.mjs +87 -0
- package/integrations/windows-overlay/protocol.mjs +39 -0
- package/integrations/windows-overlay/worker.mjs +60 -0
- package/locomotion-core.mjs +21 -0
- package/locomotion-puppy.animal +0 -0
- package/package.json +131 -4
- package/pet-q5-manifest.json +118 -0
- package/puppy-clean.mjs +51 -0
- package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
- package/puppy-companion-interactions-v3.json +1 -0
- package/puppy-companion-life.mjs +182 -0
- package/puppy-companion-objects.mjs +30 -0
- package/puppy-companion-sprites.mjs +14 -0
- package/puppy-continuous.mjs +57 -0
- package/puppy-face.mjs +46 -0
- package/puppy-frame-predictive.mjs +147 -0
- package/puppy-frame-store.mjs +108 -0
- package/puppy-mouth-anchor.json +1 -0
- package/puppy-mouth-anchor.mjs +33 -0
- package/puppy-paper-actions-v9-q5.bin.gz +0 -0
- package/puppy-paper-actions-v9.json +1 -0
- package/puppy-paper-actions.mjs +48 -0
- package/puppy-paper-approach-q5.bin.gz +0 -0
- package/puppy-paper-approach.json +1 -0
- package/puppy-paper-area.mjs +15 -0
- package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
- package/puppy-paper-arrival-seat.json +1 -0
- package/puppy-paper-behaviors-q5.bin.gz +0 -0
- package/puppy-paper-behaviors.json +1 -0
- package/puppy-paper-behaviors.mjs +8 -0
- package/puppy-paper-contact.mjs +103 -0
- package/puppy-paper-director.mjs +21 -0
- package/puppy-paper-paw-transport.mjs +76 -0
- package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
- package/puppy-paper-rear-gallop.json +1 -0
- package/puppy-paper-seat-q5.bin.gz +0 -0
- package/puppy-paper-seat.json +14 -0
- package/puppy-paper-seat.mjs +13 -0
- package/puppy-paper-trick-timing.mjs +58 -0
- package/puppy-paper-world.mjs +108 -0
- package/puppy-paper.mjs +271 -0
- package/puppy-stable-meta.json +1606 -0
- package/puppy-stable.mjs +72 -0
- package/puppy-stride-meta.json +343 -0
- package/puppy-stride-tweens-q5.bin.gz +0 -0
- package/puppy-zoomies.mjs +27 -0
- package/puppy-zoomies30.mjs +12 -0
- package/retrieviewer-cli.mjs +94 -0
- package/retrieviewer-config.mjs +49 -0
- package/retrieviewer-install.mjs +157 -0
- package/retrieviewer-plugin.d.mts +44 -0
- package/retrieviewer-plugin.mjs +97 -0
- package/retrieviewer-stdio.mjs +38 -0
- package/retrieviewer.d.mts +85 -0
- package/retrieviewer.mjs +142 -0
- package/vendor/procedural-animals/LICENSE +21 -0
- package/vendor/procedural-animals/package.json +40 -0
- package/vendor/procedural-animals/src/animal.js +146 -0
- package/vendor/procedural-animals/src/bake.js +51 -0
- package/vendor/procedural-animals/src/build.js +81 -0
- package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
- package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
- package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
- package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
- package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
- package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
- package/vendor/procedural-animals/src/core/build/seams.js +183 -0
- package/vendor/procedural-animals/src/core/build/warp.js +112 -0
- package/vendor/procedural-animals/src/core/build/weights.js +393 -0
- package/vendor/procedural-animals/src/core/math/vec.js +81 -0
- package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
- package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
- package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
- package/vendor/procedural-animals/src/core/motion/common.js +158 -0
- package/vendor/procedural-animals/src/core/motion/index.js +22 -0
- package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
- package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
- package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
- package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
- package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
- package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
- package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
- package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
- package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
- package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
- package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
- package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
- package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
- package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
- package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
- package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
- package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
- package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
- package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
- package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
- package/vendor/procedural-animals/src/index.d.ts +98 -0
- package/vendor/procedural-animals/src/index.js +29 -0
- package/vendor/procedural-animals/src/registry.js +48 -0
- package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
- package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
- package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
- package/vendor/procedural-animals/src/species/bear/index.js +139 -0
- package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
- package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
- package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
- package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
- package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
- package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
- package/vendor/procedural-animals/src/species/boar/index.js +110 -0
- package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
- package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
- package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
- package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
- package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
- package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
- package/vendor/procedural-animals/src/species/cat/index.js +211 -0
- package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
- package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
- package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
- package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
- package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
- package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
- package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
- package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
- package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
- package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
- package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
- package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
- package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
- package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
- package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
- package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
- package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
- package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
- package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
- package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
- package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
- package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
- package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
- package/vendor/procedural-animals/src/species/cow/index.js +118 -0
- package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
- package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
- package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
- package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
- package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
- package/vendor/procedural-animals/src/species/crow/index.js +70 -0
- package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
- package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
- package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
- package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
- package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
- package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
- package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
- package/vendor/procedural-animals/src/species/deer/index.js +106 -0
- package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
- package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
- package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
- package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
- package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
- package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
- package/vendor/procedural-animals/src/species/dog/index.js +144 -0
- package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
- package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
- package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
- package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
- package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
- package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
- package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
- package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
- package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
- package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
- package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
- package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
- package/vendor/procedural-animals/src/species/fish/index.js +88 -0
- package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
- package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
- package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
- package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
- package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
- package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
- package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
- package/vendor/procedural-animals/src/species/fox/index.js +120 -0
- package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
- package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
- package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
- package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
- package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
- package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
- package/vendor/procedural-animals/src/species/frog/index.js +97 -0
- package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
- package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
- package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
- package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
- package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
- package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
- package/vendor/procedural-animals/src/species/goat/index.js +149 -0
- package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
- package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
- package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
- package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
- package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
- package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
- package/vendor/procedural-animals/src/species/horse/index.js +80 -0
- package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
- package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
- package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
- package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
- package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
- package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
- package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
- package/vendor/procedural-animals/src/species/lion/index.js +111 -0
- package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
- package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
- package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
- package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
- package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
- package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
- package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
- package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
- package/vendor/procedural-animals/src/species/pig/index.js +161 -0
- package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
- package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
- package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
- package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
- package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
- package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
- package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
- package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
- package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
- package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
- package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
- package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
- package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
- package/vendor/procedural-animals/src/species/rat/index.js +111 -0
- package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
- package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
- package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
- package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
- package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
- package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
- package/vendor/procedural-animals/src/species/shark/index.js +104 -0
- package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
- package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
- package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
- package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
- package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
- package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
- package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
- package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
- package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
- package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
- package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
- package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
- package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
- package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
- package/vendor/procedural-animals/src/species/snake/index.js +73 -0
- package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
- package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
- package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
- package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
- package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
- package/vendor/procedural-animals/src/species/spider/index.js +78 -0
- package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
- package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
- package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
- package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
- package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
- package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
- package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
- package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
- package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
- package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
- package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
- package/vendor/procedural-animals/src/worker.js +29 -0
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
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
|
+
};
|