@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,122 @@
|
|
|
1
|
+
// The snake sculpted as a signed distance field: a tube of round cones along the spine with a flat,
|
|
2
|
+
// carved belly (loaf cross-section), a head modelled in head-local coordinates (origin at v0,
|
|
3
|
+
// +Z forward), a separately meshed lower jaw, a forked tongue in two rigid pieces, hinged fangs
|
|
4
|
+
// (rattlesnake) and a rattle.
|
|
5
|
+
import { SPINE, bodyPlan } from './rig.js';
|
|
6
|
+
import { add, sub, mul, norm, lerp } from '../../core/math/vec.js';
|
|
7
|
+
import { sculptEyeSocket } from '../../core/sdf/eyeSocket.js';
|
|
8
|
+
|
|
9
|
+
// Eye spec (core/sdf/eyeSocket.js) for this individual: round aperture (no lids: a spectacle),
|
|
10
|
+
// iris filling the whole visible eye.
|
|
11
|
+
export function eyeSpec(params) {
|
|
12
|
+
const B = bodyPlan(params);
|
|
13
|
+
const e = B.P.eye, k = B.kH * (params.age === 'juvenile' ? 1.15 : 1);
|
|
14
|
+
const r = e.r * k;
|
|
15
|
+
return {
|
|
16
|
+
c: [e.at[0] * B.kH, e.at[1] * B.kH, e.at[2] * B.kH], r, back: 0.0002 * B.kH, yaw: e.yaw, pitch: e.pitch,
|
|
17
|
+
lid: 0.00015 * B.kH, R: r * 0.96, d: 0, off: 0, tilt: 0, irisZ: r * 0.62, irisR: r * 0.9,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function sculptSnake(m, rig, params) {
|
|
22
|
+
const J = rig.J, B = rig.plan, P = B.P, viper = params.variant === 'rattlesnake';
|
|
23
|
+
const HO = J.v0;
|
|
24
|
+
const hL = B.headLen, W = P.headW * B.kH, H = P.headH * B.kH;
|
|
25
|
+
const yB = -HO[1]; // head-local height of the belly / jaw underside (y = 0 in the world)
|
|
26
|
+
const hl = (v) => [HO[0] + v[0], HO[1] + v[1], HO[2] + v[2]];
|
|
27
|
+
const HB = { bone: 'head' };
|
|
28
|
+
const sym = (f) => { f(1); f(-1); };
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------- BODY
|
|
31
|
+
// round cones of radius = half width; the axis sits at flat * hw, so the circle's lower part is
|
|
32
|
+
// carved off by the belly plane (flat belly with ventrolateral angles, rounded back)
|
|
33
|
+
const hwAt = [];
|
|
34
|
+
let total = 0;
|
|
35
|
+
const sig = [0];
|
|
36
|
+
for (let i = 0; i < SPINE; i++) { total += Math.hypot(...sub(J['v' + (i + 1)], J['v' + i])); sig.push(total); }
|
|
37
|
+
for (let i = 0; i <= SPINE; i++) hwAt.push(B.hw(sig[i] / total));
|
|
38
|
+
for (let i = 0; i < SPINE; i++) {
|
|
39
|
+
m.cone({ tag: i >= SPINE * (1 - B.tailFrac) ? 'tail' : i < 3 ? 'neck' : 'body', bone: 'spine' + i, a: J['v' + i], b: J['v' + (i + 1)], ra: hwAt[i], rb: hwAt[i + 1], k: 0 });
|
|
40
|
+
}
|
|
41
|
+
// thin tail tip: overlapping ellipsoids that the pipeline inflates at coarse tiers (minThick)
|
|
42
|
+
for (let i = SPINE - 12; i < SPINE; i++) {
|
|
43
|
+
const a = J['v' + i], b = J['v' + (i + 1)], rr = (hwAt[i] + hwAt[i + 1]) * 0.5;
|
|
44
|
+
m.ell({ tag: 'tailtip', bone: 'spine' + i, c: lerp(a, b, 0.5), axis: norm(sub(a, b)), r: [rr * 0.98, rr * 0.98, Math.hypot(...sub(a, b)) * 0.62], k: 0, thin: true });
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------- HEAD (upper jaw + braincase)
|
|
47
|
+
if (viper) {
|
|
48
|
+
// broad, triangular: venom-gland bulges at the back, flat crown, blunt canthus, narrow neck
|
|
49
|
+
m.ell({ ...HB, tag: 'cranium', c: hl([0, yB + 0.62 * H, 0.36 * hL]), r: [0.33 * W, 0.34 * H, 0.36 * hL], k: 0.004 });
|
|
50
|
+
sym((s) => m.ell({ ...HB, tag: 'gland', c: hl([0.31 * W * s, yB + 0.5 * H, 0.22 * hL]), r: [0.28 * W, 0.34 * H, 0.27 * hL], axis: norm([0.35 * s, 0, 1]), k: 0.005 }));
|
|
51
|
+
sym((s) => m.ell({ ...HB, tag: 'cheek', c: hl([0.2 * W * s, yB + 0.5 * H, 0.52 * hL]), r: [0.22 * W, 0.3 * H, 0.3 * hL], axis: norm([-0.25 * s, 0, 1]), k: 0.005 }));
|
|
52
|
+
m.ell({ ...HB, tag: 'snout', c: hl([0, yB + 0.58 * H, 0.7 * hL]), r: [0.29 * W, 0.28 * H, 0.25 * hL], k: 0.005 });
|
|
53
|
+
sym((s) => m.ell({ ...HB, tag: 'lip', c: hl([0.26 * W * s, yB + 0.38 * H, 0.5 * hL]), r: [0.16 * W, 0.16 * H, 0.44 * hL], axis: norm([-0.28 * s, 0, 1]), k: 0.004 }));
|
|
54
|
+
m.sphere({ ...HB, tag: 'rostral', c: hl([0, yB + 0.5 * H, 0.94 * hL]), rad: 0.13 * W, k: 0.004 });
|
|
55
|
+
// supraocular scales: a brow shelf over each eye
|
|
56
|
+
const E = eyeSpec(params);
|
|
57
|
+
sym((s) => m.ell({ ...HB, tag: 'brow', c: hl([E.c[0] * s * 0.95, E.c[1] + E.r * 0.95, E.c[2]]), r: [E.r * 1.25, E.r * 0.5, E.r * 1.5], k: 0.0015 }));
|
|
58
|
+
} else {
|
|
59
|
+
// corn snake: barely wider than the neck, long rounded snout, domed crown
|
|
60
|
+
m.ell({ ...HB, tag: 'cranium', c: hl([0, yB + 0.6 * H, 0.34 * hL]), r: [0.42 * W, 0.4 * H, 0.4 * hL], k: 0.004 });
|
|
61
|
+
sym((s) => m.ell({ ...HB, tag: 'temporal', c: hl([0.2 * W * s, yB + 0.5 * H, 0.2 * hL]), r: [0.28 * W, 0.36 * H, 0.3 * hL], k: 0.004 }));
|
|
62
|
+
m.ell({ ...HB, tag: 'snout', c: hl([0, yB + 0.56 * H, 0.72 * hL]), r: [0.29 * W, 0.34 * H, 0.3 * hL], axis: norm([0, -0.08, 1]), k: 0.005 });
|
|
63
|
+
sym((s) => m.ell({ ...HB, tag: 'lip', c: hl([0.24 * W * s, yB + 0.4 * H, 0.52 * hL]), r: [0.19 * W, 0.2 * H, 0.42 * hL], axis: norm([-0.22 * s, 0, 1]), k: 0.004 }));
|
|
64
|
+
m.sphere({ ...HB, tag: 'rostral', c: hl([0, yB + 0.5 * H, 0.92 * hL]), rad: 0.17 * W, k: 0.004 });
|
|
65
|
+
}
|
|
66
|
+
// neck: blend the cranium into the first body cone
|
|
67
|
+
m.cone({ ...HB, tag: 'nape', a: hl([0, 0, 0.18 * hL]), b: hl([0, 0, -0.25 * hL]), ra: 0.3 * W, rb: hwAt[0] * 1.02, k: 0.01 });
|
|
68
|
+
// eyes in round sockets (the spectacle sits flush), nostrils, rostral notch for the tongue
|
|
69
|
+
const E = eyeSpec(params);
|
|
70
|
+
sym((s) => sculptEyeSocket(m, E, HO, s, { bone: 'head', orbit: { r: [E.r * 0.95, E.r * 0.8, E.r * 0.45], at: [0, 0, E.r * 1.0], k: E.r * 0.35 } }));
|
|
71
|
+
sym((s) => m.sphere({ ...HB, tag: 'nostril', c: hl([0.17 * W * s, yB + 0.62 * H, 0.95 * hL]), rad: 0.04 * W, k: 0.0006, carve: true }));
|
|
72
|
+
if (viper) sym((s) => m.sphere({ ...HB, tag: 'pit', c: hl([0.33 * W * s, yB + 0.48 * H, 0.8 * hL]), rad: 0.05 * W, k: 0.0006, carve: true }));
|
|
73
|
+
m.ell({ ...HB, tag: 'notch', c: hl([0, yB + 0.36 * H, 0.99 * hL]), r: [0.07 * W, 0.07 * H, 0.05 * hL], k: 0.0006, carve: true });
|
|
74
|
+
|
|
75
|
+
// palate: the upper jaw ends at the lip line (the lower jaw is its own surface)
|
|
76
|
+
m.ell({ ...HB, tag: 'palate', c: hl([0, yB + 0.1 * H, 0.62 * hL]), r: [0.33 * W, 0.26 * H, 0.5 * hL], k: 0.0012, carve: true });
|
|
77
|
+
// belly plane: a very flat carving ellipsoid whose top is y = 0 (sag < 0.2 mm over the body)
|
|
78
|
+
const zMid = (J.v0[2] + J['v' + SPINE][2]) * 0.5;
|
|
79
|
+
m.ell({ tag: 'belly', bone: 'spine' + (SPINE >> 1), c: [0, -0.012, zMid], r: [0.4, 0.012, 3.2], k: 0.18 * hwAt[SPINE >> 1], carve: true });
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------- LOWER JAW (own surface)
|
|
82
|
+
const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
|
|
83
|
+
sym((s) => m.cone({ ...JW, tag: 'mandible', a: hl([(viper ? 0.34 : 0.3) * W * s, yB + 0.22 * H, 0.08 * hL]), b: hl([0.07 * W * s, yB + 0.17 * H, 0.88 * hL]), ra: viper ? 0.14 * H + 0.05 * W : 0.11 * H + 0.03 * W, rb: viper ? 0.1 * H : 0.085 * H, k: 0.003 }));
|
|
84
|
+
m.ell({ ...JW, tag: 'jawfloor', c: hl([0, yB + 0.19 * H, 0.46 * hL]), r: [0.3 * W, 0.18 * H, 0.42 * hL], k: 0.003 });
|
|
85
|
+
m.sphere({ ...JW, tag: 'chin', c: hl([0, yB + 0.2 * H, 0.9 * hL]), rad: 0.14 * H, k: 0.003 });
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------- TONGUE (two rigid pieces)
|
|
88
|
+
const tb = J.tongueBase, tf = J.tongueFork, tt = J.tongueTip;
|
|
89
|
+
const tr = 0.0007 * B.kH * (viper ? 1.25 : 1);
|
|
90
|
+
m.cone({ bone: 'tongue', group: 'tongue', part: 'tongue', tag: 'tongue', a: tb, b: add(tf, [0, 0, tr]), ra: tr * 1.1, rb: tr * 0.85, k: 0 });
|
|
91
|
+
const forkLen = Math.hypot(...sub(tt, tf));
|
|
92
|
+
sym((s) => m.cone({ bone: 'tongueTip', group: 'tongue', part: 'fork', tag: 'fork', a: add(tf, [0, 0, -tr * 0.5]), b: add(tt, [s * forkLen * 0.28, 0, 0]), ra: tr * 0.8, rb: tr * 0.32, k: tr * 0.6 }));
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------- FANGS (rattlesnake)
|
|
95
|
+
if (viper) {
|
|
96
|
+
const fb = J.fangBase, ft = J.fangTip;
|
|
97
|
+
sym((s) => m.cone({ bone: 'fang', group: 'fang', part: 'fang', tag: 'fang', a: add(fb, [0.2 * W * s, 0, 0]), b: add(ft, [0.2 * W * s, 0, 0]), ra: 0.0009 * B.kH, rb: 0.00015, k: 0 }));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------- RATTLE
|
|
101
|
+
if (P.rattle) {
|
|
102
|
+
const last = 'spine' + (SPINE - 1);
|
|
103
|
+
const a = J['v' + (SPINE - 1)], b = J['v' + SPINE];
|
|
104
|
+
const d = norm(sub(b, a));
|
|
105
|
+
const nSeg = params.rattleSegs ?? P.rattle.segs;
|
|
106
|
+
const tip = hwAt[SPINE];
|
|
107
|
+
const segL = (P.rattle.len / P.rattle.segs) * (params.shape?.girth ?? 1);
|
|
108
|
+
for (let k = 0; k < nSeg; k++) {
|
|
109
|
+
const t = k / Math.max(1, P.rattle.segs - 1);
|
|
110
|
+
const w = tip * (1.35 - 0.3 * t);
|
|
111
|
+
const c0 = add(b, mul(d, segL * (k + 0.55)));
|
|
112
|
+
// the rattle rests on (not in) the ground: lift a segment whose half height exceeds the tip's axis height
|
|
113
|
+
const c = [c0[0], Math.max(c0[1], w * 0.78 + 0.0003), c0[2]];
|
|
114
|
+
// each segment: a flattened, bi-lobed bell overlapping the next
|
|
115
|
+
m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'rattle', c, axis: d, r: [w, w * 0.78, segL * 0.62], k: segL * 0.25 });
|
|
116
|
+
m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'rattle', c: add(c, mul(d, segL * 0.35)), axis: d, r: [w * 0.82, w * 0.62, segL * 0.38], k: segL * 0.2 });
|
|
117
|
+
}
|
|
118
|
+
// the tail tip plugs into the button
|
|
119
|
+
m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'button', c: ((q) => [q[0], Math.max(q[1], tip * 0.9 + 0.0003), q[2]])(add(b, mul(d, -segL * 0.2))), axis: d, r: [tip * 1.05, tip * 0.9, segL * 0.7], k: 0 });
|
|
120
|
+
}
|
|
121
|
+
return m;
|
|
122
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// The spider's coat: chitin (material 8) under a covering of setae (shell hairs), and glossy bare
|
|
2
|
+
// cuticle around the eyes and on the fangs (keratin-like, material 5).
|
|
3
|
+
//
|
|
4
|
+
// Wolf spider (Hogna / Lycosa type): dark brown carapace with a pale median band and pale
|
|
5
|
+
// submarginal bands, dark striae radiating from the fovea, black ocular area; abdomen with a dark
|
|
6
|
+
// lanceolate heart mark in front and pale chevrons behind; banded legs; black venter.
|
|
7
|
+
// Tarantula morphs: red-knee (Brachypelma: velvet black, orange knee bands, pale rings, tan carapace
|
|
8
|
+
// margin, reddish abdominal setae), rosy (Grammostola: pinkish brown) and pinktoe (Avicularia: black
|
|
9
|
+
// with pink tarsi). Colours sampled from the reference photos.
|
|
10
|
+
//
|
|
11
|
+
// Pattern and mark distances are stored in "cheetah-equivalent" metres (real distance x PK) so the
|
|
12
|
+
// covering shader's anti-aliasing widths and hair-level edge jitter, which are tuned for a cheetah,
|
|
13
|
+
// stay proportionate on a 25 mm animal.
|
|
14
|
+
import { spiderDims } from './rig.js';
|
|
15
|
+
import { EYES, eyeSpec } from './sculpt.js';
|
|
16
|
+
import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
|
|
17
|
+
import { srgb, mix3 } from '../../core/build/coatKit.js';
|
|
18
|
+
import { norm, sub, dot, cross, add, mul, smoothstep, clamp, mix, fbm3, len } from '../../core/math/vec.js';
|
|
19
|
+
|
|
20
|
+
const MAT = { CHITIN: 8, KERATIN: 5 };
|
|
21
|
+
|
|
22
|
+
function palette(p) {
|
|
23
|
+
const w = p.coatWarmth || 0, l = p.coatLightness || 0;
|
|
24
|
+
// warmth (rufous .. cool), lightness, and greyness (Pardosa-like grey individuals .. brown Hogna)
|
|
25
|
+
const gr = p.coatGrey || 0;
|
|
26
|
+
const tw = (c) => {
|
|
27
|
+
const r = c[0] * (1 + 0.12 * w + l), g = c[1] * (1 + 0.03 * w + l), b = c[2] * (1 - 0.12 * w + l);
|
|
28
|
+
const y = 0.3 * r + 0.55 * g + 0.15 * b;
|
|
29
|
+
return [mix(r, y, gr), mix(g, y, gr), mix(b, y * 1.04, gr)];
|
|
30
|
+
};
|
|
31
|
+
if (p.variant !== 'tarantula') {
|
|
32
|
+
return {
|
|
33
|
+
carapace: tw(srgb(0x3a2b1f)), stripe: tw(srgb(0xa08a6a)), margin: tw(srgb(0x8c7657)),
|
|
34
|
+
abdomen: tw(srgb(0x4e3b2a)), heart: tw(srgb(0x261b13)), chevron: tw(srgb(0x8e7a5c)), abdSide: tw(srgb(0x5d4935)),
|
|
35
|
+
leg: tw(srgb(0x5f4a35)), legBand: tw(srgb(0x9a8465)), legDark: tw(srgb(0x33261b)),
|
|
36
|
+
chel: srgb(0x1f1812), chelHair: tw(srgb(0x6a4a2c)), venter: srgb(0x1a1512), ocular: srgb(0x0d0b09),
|
|
37
|
+
fang: srgb(0x160f0b), palp: tw(srgb(0x5a4633)), spinneret: tw(srgb(0x6e5a44)),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const morph = p.morph || 'redknee';
|
|
41
|
+
const base = {
|
|
42
|
+
carapace: srgb(0x17120f), stripe: srgb(0x17120f), margin: srgb(0xc3884b),
|
|
43
|
+
abdomen: srgb(0x1a1411), heart: srgb(0x120d0b), chevron: srgb(0x6b3a22), abdSide: srgb(0x1c1512),
|
|
44
|
+
leg: srgb(0x15110e), legBand: srgb(0xd9651e), legDark: srgb(0x0f0c0a), legRing: srgb(0xe0a068),
|
|
45
|
+
chel: srgb(0x120e0c), chelHair: srgb(0x2a1d16), venter: srgb(0x0e0b09), ocular: srgb(0x0a0908),
|
|
46
|
+
fang: srgb(0x0e0a08), palp: srgb(0x16120f), spinneret: srgb(0x201813), tarsusTip: null,
|
|
47
|
+
};
|
|
48
|
+
if (morph === 'rosy') {
|
|
49
|
+
Object.assign(base, {
|
|
50
|
+
carapace: srgb(0x6e4c40), margin: srgb(0x9a7462), abdomen: srgb(0x5e4036), chevron: srgb(0x8a5a4a), abdSide: srgb(0x5a3e34),
|
|
51
|
+
leg: srgb(0x4a3530), legBand: srgb(0x86645a), legRing: srgb(0x9a7a6c), legDark: srgb(0x35261f), chelHair: srgb(0x6a4a3e), palp: srgb(0x4a3530),
|
|
52
|
+
});
|
|
53
|
+
} else if (morph === 'pinktoe') {
|
|
54
|
+
Object.assign(base, {
|
|
55
|
+
carapace: srgb(0x1b191d), margin: srgb(0x2a2630), abdomen: srgb(0x1c1a1e), chevron: srgb(0x3a3036), abdSide: srgb(0x1c1a1e),
|
|
56
|
+
leg: srgb(0x1a181c), legBand: srgb(0x2c2830), legRing: srgb(0x2c2830), legDark: srgb(0x121014), tarsusTip: srgb(0xe07a7a), palp: srgb(0x1a181c),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const tw2 = (c) => (c ? [c[0] * (1 + l), c[1] * (1 + l), c[2] * (1 + l)] : c);
|
|
60
|
+
for (const k of Object.keys(base)) base[k] = tw2(base[k]);
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function spiderCoat(ctx) {
|
|
65
|
+
const { rig, pos, nrm, regionOf, regionNames, weights, nV, params } = ctx;
|
|
66
|
+
const d = spiderDims(params);
|
|
67
|
+
const J = rig.J, BONES = rig.BONES;
|
|
68
|
+
const U = d.unit, F = U / 0.013, PK = 0.33 / U;
|
|
69
|
+
const wolf = d.variant === 'wolf';
|
|
70
|
+
const COL = palette(params);
|
|
71
|
+
const Mm = 0.001 * F; // one "wolf millimetre" at this variant's scale
|
|
72
|
+
const comb = new Float32Array(nV * 3), tint = new Float32Array(nV * 4), pattern = new Float32Array(nV).fill(1);
|
|
73
|
+
const mark = new Float32Array(nV).fill(1), furLen = new Float32Array(nV), patternColor = new Float32Array(nV * 4), surf = new Float32Array(nV * 4);
|
|
74
|
+
const dom = weights.dominant;
|
|
75
|
+
const contrast = params.patternContrast ?? 1;
|
|
76
|
+
|
|
77
|
+
// carapace landmarks
|
|
78
|
+
const z0 = J.pedA[2], z1 = J.front[2], Lc = z1 - z0;
|
|
79
|
+
const fovea = [0, 0, z0 + 0.36 * Lc];
|
|
80
|
+
const eyeC = add(J.ceph, [0, 0, 0.0]);
|
|
81
|
+
const halfW = (z) => {
|
|
82
|
+
// carapace half width along z (pear shape: widest behind the middle, narrow head)
|
|
83
|
+
const u = (z - z0) / Lc;
|
|
84
|
+
const wMax = d.carapace.wid * 0.0005, wHead = d.carapace.headW * 0.0005;
|
|
85
|
+
return u < 0.45 ? wMax * Math.sqrt(Math.max(0.05, 1 - ((0.45 - u) / 0.47) ** 2)) : mix(wMax, wHead, smoothstep(0.45, 0.85, u));
|
|
86
|
+
};
|
|
87
|
+
const eyes = [];
|
|
88
|
+
for (const e of EYES[d.variant]) { const E = eyeSpec(e); for (const s of [1, -1]) eyes.push({ c: eyeFrameOf(E, J.ceph, s).c, r: E.r }); }
|
|
89
|
+
const boneKind = BONES.map((b) => {
|
|
90
|
+
const m = /^(coxa|troch|femur|patella|tibia|meta|tarsus)(\d)([LR])$/.exec(b.name);
|
|
91
|
+
if (m) return { kind: 'leg', seg: m[1], leg: +m[2], S: m[3] };
|
|
92
|
+
if (/^palp/.test(b.name)) return { kind: 'palp', seg: b.name.replace(/^palp|[LR]$/g, '').toLowerCase() };
|
|
93
|
+
return { kind: b.name.replace(/[LR]$/, '') };
|
|
94
|
+
});
|
|
95
|
+
const segIdx = { coxa: 0, troch: 1, femur: 2, patella: 3, tibia: 4, meta: 5, tarsus: 6 };
|
|
96
|
+
|
|
97
|
+
for (let v = 0; v < nV; v++) {
|
|
98
|
+
const p = [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
|
|
99
|
+
const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
|
|
100
|
+
const rn = regionNames[regionOf[v]];
|
|
101
|
+
const b = BONES[dom[v]], bk = boneKind[dom[v]];
|
|
102
|
+
let col = COL.carapace, mat = MAT.CHITIN, fl = 0, gloss = 0.05, pat = 1, pcol = COL.stripe, pint = 0, mk = 1;
|
|
103
|
+
let cdir = [0, 0, -1];
|
|
104
|
+
const noise = fbm3(p[0] / (1.6 * Mm), p[1] / (1.6 * Mm), p[2] / (1.6 * Mm), 3) - 0.5;
|
|
105
|
+
const noiseF = fbm3(p[0] / (0.45 * Mm) + 7, p[1] / (0.45 * Mm), p[2] / (0.45 * Mm), 2) - 0.5;
|
|
106
|
+
|
|
107
|
+
if (rn.startsWith('fang')) {
|
|
108
|
+
mat = MAT.KERATIN; col = COL.fang; gloss = 0.9; fl = 0;
|
|
109
|
+
cdir = sub(J['fangTip' + rn.slice(-1)], J['cheTip' + rn.slice(-1)]);
|
|
110
|
+
} else if (rn.startsWith('chel')) {
|
|
111
|
+
const S = rn.slice(-1);
|
|
112
|
+
col = COL.chel; fl = (wolf ? 0.66 : 1.6) * Mm; gloss = 0.05;
|
|
113
|
+
cdir = sub(J['cheTip' + S], J['cheBase' + S]);
|
|
114
|
+
// hair on the front face, bare glossy cuticle near the tip (the fang base)
|
|
115
|
+
const t = dot(sub(p, J['cheBase' + S]), norm(cdir)) / len(cdir);
|
|
116
|
+
fl *= 1 - smoothstep(0.7, 0.95, t);
|
|
117
|
+
col = mix3(col, COL.chelHair, clamp(0.5 + noise, 0, 1) * (wolf ? 0.35 : 0.5) * (1 - smoothstep(0.6, 0.9, t)));
|
|
118
|
+
} else if (bk.kind === 'leg' || bk.kind === 'palp') {
|
|
119
|
+
// ---- legs and palps: distal hair flow, pale joint bands
|
|
120
|
+
const a = b.head, e = b.tail, ax = sub(e, a), L = len(ax);
|
|
121
|
+
cdir = ax;
|
|
122
|
+
const t = clamp(dot(sub(p, a), ax) / (L * L), 0, 1);
|
|
123
|
+
const seg = bk.kind === 'leg' ? bk.seg : bk.seg === 'coxa' ? 'coxa' : bk.seg === 'troch' ? 'troch' : bk.seg === 'femur' ? 'femur' : bk.seg === 'patella' ? 'patella' : bk.seg === 'tibia' ? 'tibia' : 'tarsus';
|
|
124
|
+
const si = segIdx[seg] ?? 6;
|
|
125
|
+
const up = n[1];
|
|
126
|
+
col = bk.kind === 'palp' ? COL.palp : COL.leg;
|
|
127
|
+
if (si <= 1) col = mix3(COL.venter, col, smoothstep(-0.2, 0.6, up));
|
|
128
|
+
// bands: pale rings near the distal end of femur, patella and tibia (wolf); red-knee: the
|
|
129
|
+
// orange band covers the patella top and the distal femur, pale rings at tibia / metatarsus ends
|
|
130
|
+
let bandD = 1e9, bandCol = COL.legBand;
|
|
131
|
+
const along = t * L; // metres from the segment start
|
|
132
|
+
if (wolf) {
|
|
133
|
+
if (si === 2) bandD = Math.min(Math.abs(along - L * 0.18) - 0.35 * Mm, Math.abs(along - L * 0.62) - 0.4 * Mm);
|
|
134
|
+
else if (si === 3) bandD = Math.abs(along - L * 0.5) - L * 0.22;
|
|
135
|
+
else if (si === 4) bandD = Math.min(Math.abs(along - L * 0.2) - 0.45 * Mm, Math.abs(along - L * 0.72) - 0.4 * Mm);
|
|
136
|
+
else if (si === 5) bandD = Math.abs(along - L * 0.12) - 0.3 * Mm;
|
|
137
|
+
// diffuse, mottled banding of the setae (not crisp rings)
|
|
138
|
+
bandD += 0.35 * Mm * noiseF + 0.25 * Mm * noise;
|
|
139
|
+
} else if ((params.morph || 'redknee') === 'redknee' || params.morph === 'rosy') {
|
|
140
|
+
if (si === 3) bandD = -0.2 * Mm + (0.25 - up) * 1.4 * Mm; // top of the patella
|
|
141
|
+
else if (si === 2) bandD = (L - along) - 0.12 * L + (0.2 - up) * 1.2 * Mm;
|
|
142
|
+
else if (si === 4) { bandD = Math.min(L - along - 0.07 * L, along - 0.04 * L) + (0.1 - up) * 0.8 * Mm; bandCol = COL.legRing; }
|
|
143
|
+
else if (si === 5) { bandD = (L - along) - 0.06 * L + (0.1 - up) * 0.8 * Mm; bandCol = COL.legRing; }
|
|
144
|
+
// "knee" stripes run lengthwise along the patella and tibia top in B. hamorii
|
|
145
|
+
if (si === 3 || si === 4) bandD = Math.min(bandD, Math.abs(dot(norm(cross(ax, [0, 1, 0])), sub(p, a))) - 0.4 * Mm + (0.5 - up) * 2 * Mm);
|
|
146
|
+
} else if (params.morph === 'pinktoe' && si === 6 && bk.kind === 'leg') {
|
|
147
|
+
col = COL.tarsusTip;
|
|
148
|
+
}
|
|
149
|
+
// (wolf: soft edges, about 3x the anti-aliasing width, and a lower contrast)
|
|
150
|
+
if (bandD < 1e8) { pat = bandD * PK * (wolf ? 0.33 : 1); pcol = bandCol; pint = contrast * (wolf ? 0.6 : 1); }
|
|
151
|
+
// darker ventral side and joint membranes
|
|
152
|
+
col = mix3(col, COL.legDark, smoothstep(0.1, -0.7, up) * 0.4);
|
|
153
|
+
const lens = wolf ? [0.62, 0.64, 0.85, 0.8, 0.8, 0.72, 0.66] : [1.8, 1.8, 3.4, 3.2, 3.2, 2.8, 2.4];
|
|
154
|
+
fl = lens[si] * Mm * (bk.kind === 'palp' ? 0.9 : 1) * (1 + 0.2 * noiseF);
|
|
155
|
+
if (!wolf && si === 6) fl *= 0.8;
|
|
156
|
+
gloss = 0.03;
|
|
157
|
+
} else if (bk.kind === 'abdomen' || bk.kind === 'pedicel' || bk.kind === 'spinnerets') {
|
|
158
|
+
// ---- abdomen: hair lying backwards, dorsal heart mark and chevrons, dark venter
|
|
159
|
+
const c = d.abdomen.c.map((x) => x * 0.001), r = d.abdomen.r.map((x) => x * 0.001);
|
|
160
|
+
const u = (p[2] - c[2]) / r[2]; // +1 front .. -1 rear
|
|
161
|
+
const up = n[1];
|
|
162
|
+
const dors = smoothstep(-0.1, 0.55, up);
|
|
163
|
+
col = mix3(COL.venter, mix3(COL.abdSide, COL.abdomen, dors), smoothstep(-0.75, -0.2, up));
|
|
164
|
+
cdir = [0, -0.15 * u, -1];
|
|
165
|
+
if (wolf) {
|
|
166
|
+
// lanceolate heart mark: a dark spear along the front half of the midline (tint), pale
|
|
167
|
+
// chevrons over the rear half (pattern)
|
|
168
|
+
const hw = mix(0.95, 0.15, clamp((0.95 - u) / 1.0, 0, 1)) * Mm;
|
|
169
|
+
const heart = (Math.abs(p[0]) - hw * (u > -0.1 ? 1 : 0)) / Mm;
|
|
170
|
+
col = mix3(col, COL.heart, smoothstep(0.3, -0.2, heart) * dors * contrast * smoothstep(-0.2, 0.2, u));
|
|
171
|
+
mk = Math.min(mk, u > -0.05 && u < 0.95 ? (Math.abs(Math.abs(p[0]) - hw) - 0.08 * Mm) * PK / (dors > 0.3 ? 1 : 1e3) : 1);
|
|
172
|
+
// rear half: pairs of pale spots flanking the midline, joined by faint, broken chevrons
|
|
173
|
+
let spots = 1e9, ch = 1e9;
|
|
174
|
+
for (let k = 0; k < 4; k++) {
|
|
175
|
+
const uz = -0.12 - k * 0.2;
|
|
176
|
+
spots = Math.min(spots, Math.hypot(Math.abs(p[0]) - r[0] * (0.3 - 0.03 * k), (u - uz) * r[2]) - 0.32 * Mm * (1 - 0.15 * k));
|
|
177
|
+
const vz = uz + Math.abs(p[0]) / r[0] * 0.25;
|
|
178
|
+
ch = Math.min(ch, Math.abs(u - vz) * r[2] - 0.1 * Mm * (1 - 0.2 * k));
|
|
179
|
+
}
|
|
180
|
+
ch = Math.max(ch, Math.abs(p[0]) - r[0] * 0.3, 0.25 * Mm * (noiseF + 0.2)); // broken up
|
|
181
|
+
// pale flanks: lateral pale bands (Hogna) framing the dorsum
|
|
182
|
+
const flank = Math.abs(Math.abs(p[0]) / r[0] - 0.72) * r[0] - 0.35 * Mm;
|
|
183
|
+
pat = (Math.min(spots, ch, flank + 0.3 * Mm * noise) + 0.2 * Mm * noiseF) * PK * 0.6;
|
|
184
|
+
pcol = COL.chevron;
|
|
185
|
+
pint = dors * contrast * 0.8 * smoothstep(-0.35, 0.1, up);
|
|
186
|
+
// mottling
|
|
187
|
+
col = [col[0] * (1 + 0.3 * noiseF), col[1] * (1 + 0.28 * noiseF), col[2] * (1 + 0.24 * noiseF)];
|
|
188
|
+
fl = 0.9 * Mm * (1 + 0.25 * noiseF);
|
|
189
|
+
} else {
|
|
190
|
+
// tarantula: long reddish setae on black; a bald urticating patch sometimes
|
|
191
|
+
// black under the hair, the long setae reddish (strand-mottled, stronger on the dorsum)
|
|
192
|
+
pat = (-0.5 * Mm + 0.2 * Mm * noise) * PK;
|
|
193
|
+
pcol = COL.chevron;
|
|
194
|
+
pint = smoothstep(-0.4, 0.3, up) * (0.4 + 0.3 * noiseF);
|
|
195
|
+
fl = (3.8 + 1.2 * noiseF) * Mm * smoothstep(-0.9, -0.3, up) + 1.2 * Mm;
|
|
196
|
+
if (params.bald) {
|
|
197
|
+
const bd = len([p[0] / r[0], (p[1] - c[1] - r[1] * 0.7) / r[1], (p[2] - c[2] + r[2] * 0.35) / r[2]]);
|
|
198
|
+
fl *= smoothstep(0.28, 0.42, bd + 0.05 * noise);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (bk.kind === 'spinnerets') { col = COL.spinneret; fl *= 0.3; }
|
|
202
|
+
if (bk.kind === 'pedicel') fl *= 0.4;
|
|
203
|
+
gloss = 0.03;
|
|
204
|
+
} else {
|
|
205
|
+
// ---- prosoma: carapace, head, sternum
|
|
206
|
+
const up = n[1];
|
|
207
|
+
const x = p[0], z = p[2];
|
|
208
|
+
const W = halfW(z);
|
|
209
|
+
const dors = smoothstep(-0.15, 0.35, up);
|
|
210
|
+
col = mix3(COL.venter, COL.carapace, smoothstep(-0.55, -0.1, up));
|
|
211
|
+
// hair radiates back and out from the eye region
|
|
212
|
+
cdir = [x, 0, z - eyeC[2] - 0.001 * F];
|
|
213
|
+
const u = (z - z0) / Lc;
|
|
214
|
+
if (wolf) {
|
|
215
|
+
// median band: narrow between the eyes, widest over the thorax, narrowing to the rear
|
|
216
|
+
// (a narrow pale stripe between the eyes and down the face, widening behind the eyes)
|
|
217
|
+
const mw = (u > 0.72 ? mix(0.5, 0.35, smoothstep(0.72, 0.95, u)) : mix(0.4, 0.85, smoothstep(0.1, 0.55, u))) * Mm;
|
|
218
|
+
const median = Math.abs(x) - mw + 0.12 * Mm * noiseF;
|
|
219
|
+
// submarginal bands along the sides of the carapace
|
|
220
|
+
const rel = Math.abs(x) / Math.max(W, 1e-5);
|
|
221
|
+
const sub1 = Math.abs(rel - 0.8) * W - 0.13 * W + 0.1 * Mm * noise;
|
|
222
|
+
const subm = Math.max(sub1, (0.15 - up) * 3 * Mm, (u - 0.8) * 8 * Mm);
|
|
223
|
+
pat = Math.min(median, subm) * PK;
|
|
224
|
+
pint = dors * contrast;
|
|
225
|
+
pcol = COL.stripe;
|
|
226
|
+
// dark striae radiating from the fovea
|
|
227
|
+
let st = 1e9;
|
|
228
|
+
for (let k = 0; k < 4; k++) {
|
|
229
|
+
const ang = (-0.95 + k * 0.62);
|
|
230
|
+
const dir = [Math.sin(ang + Math.PI / 2), 0, Math.cos(ang + Math.PI / 2)];
|
|
231
|
+
const q = sub([Math.abs(x), 0, z], fovea);
|
|
232
|
+
const along = dot(q, dir);
|
|
233
|
+
if (along > 0.4 * Mm && along < W * 0.95) st = Math.min(st, Math.abs(q[0] * dir[2] - q[2] * dir[0]) - 0.08 * Mm * (1 - along / W));
|
|
234
|
+
}
|
|
235
|
+
mk = Math.min(mk, dors > 0.2 ? st * PK : 1);
|
|
236
|
+
} else {
|
|
237
|
+
// velvet black centre with a tan margin and faint radiating lines
|
|
238
|
+
const rel = Math.abs(x) / Math.max(W, 1e-5);
|
|
239
|
+
pat = (Math.max(0.78 - rel, 0) * W * 1.0 - 0.06 * Mm + (0.25 - up) * 0.5 * Mm) * PK;
|
|
240
|
+
if (up < -0.2) pat = 1;
|
|
241
|
+
pcol = COL.margin; pint = (params.morph === 'pinktoe' ? 0.3 : 1) * smoothstep(-0.2, 0.2, up);
|
|
242
|
+
}
|
|
243
|
+
// ocular area (and the tarantula's tubercle): bare, black, glossy
|
|
244
|
+
let eyeD = 1e9;
|
|
245
|
+
for (const e of eyes) eyeD = Math.min(eyeD, len(sub(p, e.c)) - e.r * 1.35);
|
|
246
|
+
if (eyeD < 0.5 * Mm * (wolf ? 1 : 0.6)) col = mix3(col, COL.ocular, smoothstep(0.5 * Mm, 0.05 * Mm, eyeD));
|
|
247
|
+
fl = (wolf ? 0.72 : 1.1) * Mm * smoothstep(0.05 * Mm, 0.6 * Mm, eyeD);
|
|
248
|
+
if (up < -0.5) fl *= 0.5; // sternum
|
|
249
|
+
gloss = eyeD < 0.3 * Mm ? 0.4 : 0.05;
|
|
250
|
+
if (b.name === 'head' && up > 0.2 && z > J.front[2] - 0.0015 * F) fl *= 0.6;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// low frequency colour noise
|
|
254
|
+
col = [col[0] * (1 + 0.22 * noise), col[1] * (1 + 0.2 * noise), col[2] * (1 + 0.16 * noise)];
|
|
255
|
+
// comb: tangent to the surface
|
|
256
|
+
let cd = sub(cdir, mul(n, dot(cdir, n)));
|
|
257
|
+
if (len(cd) < 1e-9) cd = norm(cross(n, [1, 0, 0])); else cd = norm(cd);
|
|
258
|
+
comb.set(cd, v * 3);
|
|
259
|
+
tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
|
|
260
|
+
pattern[v] = pat; mark[v] = mk; furLen[v] = fl;
|
|
261
|
+
patternColor[v * 4] = pcol[0]; patternColor[v * 4 + 1] = pcol[1]; patternColor[v * 4 + 2] = pcol[2]; patternColor[v * 4 + 3] = pint;
|
|
262
|
+
// gloss, feature scale (fine cuticle striation), agouti (banded setae), undercoat
|
|
263
|
+
surf[v * 4] = gloss; surf[v * 4 + 1] = -0.00012 * F; surf[v * 4 + 2] = wolf ? 0.35 : 0.1; surf[v * 4 + 3] = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// smooth hair length so shells do not step
|
|
267
|
+
const nb = weights.neighbors, tmp = new Float32Array(nV);
|
|
268
|
+
for (let it = 0; it < 2; it++) {
|
|
269
|
+
for (let v = 0; v < nV; v++) {
|
|
270
|
+
const ns = nb[v];
|
|
271
|
+
if (!ns.length || tint[v * 4 + 3] !== MAT.CHITIN) { tmp[v] = furLen[v]; continue; }
|
|
272
|
+
let a = 0;
|
|
273
|
+
for (const u of ns) a += furLen[u];
|
|
274
|
+
tmp[v] = 0.5 * furLen[v] + (0.5 * a) / ns.length;
|
|
275
|
+
}
|
|
276
|
+
furLen.set(tmp);
|
|
277
|
+
}
|
|
278
|
+
return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { patternScale: PK } };
|
|
279
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Spider: wolf spider (Lycosidae, default) and tarantula (Theraphosidae, opts.variant = 'tarantula').
|
|
2
|
+
// Body plan 'spider' (core/motion/spider.js): 8 legs x 7 segments, pedipalps, chelicerae with
|
|
3
|
+
// fangs, eight simple eyes.
|
|
4
|
+
import { spiderRig } from './rig.js';
|
|
5
|
+
import { sculptSpider, EYES, eyeSpec } from './sculpt.js';
|
|
6
|
+
import { spiderRegions } from './regions.js';
|
|
7
|
+
import { spiderCoat } from './coat.js';
|
|
8
|
+
import { motion } from './motion.js';
|
|
9
|
+
|
|
10
|
+
// glossy black domes; the look.iris.outer colour tints the dome (dark amber-brown lens)
|
|
11
|
+
const EYE_LOOK = {
|
|
12
|
+
pupil: 'round',
|
|
13
|
+
lids: 'simple',
|
|
14
|
+
// the dome colour comes from iris.outer: near black with a faint amber tapetum glint
|
|
15
|
+
iris: { inner: [0.02, 0.02, 0.02], mid: [0.03, 0.03, 0.025], hi: [0.05, 0.05, 0.04], outer: [0.3, 0.17, 0.05] },
|
|
16
|
+
sclera: { color: [0.02, 0.02, 0.02], visible: false },
|
|
17
|
+
lidColor: [0.02, 0.018, 0.015],
|
|
18
|
+
pupilSize: [0.4, 0.5],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
id: 'spider',
|
|
23
|
+
name: 'Spider',
|
|
24
|
+
latin: 'Lycosidae / Theraphosidae',
|
|
25
|
+
group: 'others',
|
|
26
|
+
plan: 'spider',
|
|
27
|
+
covering: 'chitin',
|
|
28
|
+
variants: [
|
|
29
|
+
{ id: 'wolf', name: 'Wolf spider', latin: 'Lycosidae' },
|
|
30
|
+
{ id: 'tarantula', name: 'Tarantula', latin: 'Theraphosidae' },
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
// Seeded individual. Females are larger with a heavy abdomen (gravid ones more so); males are
|
|
34
|
+
// smaller and leggier with swollen palpal bulbs; spiderlings are small and short-legged.
|
|
35
|
+
variation(R, o) {
|
|
36
|
+
const variant = o.variant === 'tarantula' ? 'tarantula' : 'wolf';
|
|
37
|
+
const sex = o.sex || (R() < 0.6 ? 'female' : 'male');
|
|
38
|
+
const age = o.age || 'adult';
|
|
39
|
+
const juv = age === 'juvenile';
|
|
40
|
+
const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
|
|
41
|
+
const male = sex === 'male' && !juv;
|
|
42
|
+
const size = (male ? (variant === 'wolf' ? 0.82 : 0.88) : 1) * (1 + 0.08 * g()) * (juv ? 0.42 : 1);
|
|
43
|
+
const p = {
|
|
44
|
+
variant, sex, age, size,
|
|
45
|
+
quality: o.quality,
|
|
46
|
+
legK: (male ? 1.1 : 1) * (1 + 0.04 * g()) * (juv ? 0.9 : 1),
|
|
47
|
+
abdK: (male ? 0.78 : 1 + 0.08 * Math.abs(g()) + 0.05 * g()) * (juv ? 0.9 : 1),
|
|
48
|
+
palpBulb: male ? 1 : 0,
|
|
49
|
+
coatWarmth: 0.9 * g(),
|
|
50
|
+
coatLightness: 0.18 * g() + (male && variant === 'wolf' ? -0.08 : 0),
|
|
51
|
+
patternContrast: variant === 'wolf' ? 0.6 + 0.55 * R() : 1,
|
|
52
|
+
coatGrey: variant === 'wolf' ? 0.75 * R() ** 1.5 : 0,
|
|
53
|
+
coatSeed: Math.floor(R() * 1e6),
|
|
54
|
+
warps: [],
|
|
55
|
+
};
|
|
56
|
+
if (variant === 'tarantula') {
|
|
57
|
+
const m = R();
|
|
58
|
+
p.morph = m < 0.6 ? 'redknee' : m < 0.8 ? 'rosy' : 'pinktoe';
|
|
59
|
+
p.bald = R() < 0.3; // kicked off its urticating hairs
|
|
60
|
+
}
|
|
61
|
+
return p;
|
|
62
|
+
},
|
|
63
|
+
rig: (params) => spiderRig(params),
|
|
64
|
+
sculpt: (m, rig, params) => sculptSpider(m, rig, params),
|
|
65
|
+
regions: spiderRegions,
|
|
66
|
+
coat: spiderCoat,
|
|
67
|
+
eyeSpecs: (params, rig) => {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const e of EYES[params.variant === 'tarantula' ? 'tarantula' : 'wolf']) for (const side of [1, -1]) out.push({ side, spec: eyeSpec(e), headOrigin: rig.J.ceph, bone: 'head', look: EYE_LOOK });
|
|
70
|
+
return out;
|
|
71
|
+
},
|
|
72
|
+
motion,
|
|
73
|
+
// setae: fine but still resolvable strands (a few per millimetre), loosely tufted; the tarantula's
|
|
74
|
+
// long hair is coarser and fuzzier
|
|
75
|
+
render: (params) => (params?.variant === 'tarantula'
|
|
76
|
+
? { markColor: [0.012, 0.009, 0.007], strandDensity: 3000, clumpDensity: 3, shellScale: 1, finWidth: 0.6 }
|
|
77
|
+
: { markColor: [0.012, 0.009, 0.007], strandDensity: 5000, clumpDensity: 5, shellScale: 1, finWidth: 0.45 }),
|
|
78
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Spider motion data for the spider engine (schema: src/core/motion/spider.js).
|
|
2
|
+
// Numbers are for the reference individual of each variant (params.size = 1); the engine scales
|
|
3
|
+
// speeds by dynamic similarity (sqrt(size)) and reads every length from the joints.
|
|
4
|
+
//
|
|
5
|
+
// Gait: alternating tetrapod (L1 R2 L3 R4 against R1 L2 R3 L4) with a back-to-front metachronal lag
|
|
6
|
+
// inside each tetrapod (Biancardi et al. 2011 on Grammostola: quadruped lag 16 % slow, 8 % fast;
|
|
7
|
+
// Weihmann 2013 on Cupiennius: contralateral pairs in antiphase, body oscillation tiny). Offsets are
|
|
8
|
+
// touchdown phases relative to L4, in the order L1 L2 L3 L4 R1 R2 R3 R4.
|
|
9
|
+
// Duty factor falls with speed (d = 0.666 - 0.531 v for Grammostola; Cupiennius < 0.5 when fast);
|
|
10
|
+
// stride frequency rises with speed (f = 14.4 v + 0.14 Hz for an 18 g tarantula, faster for the
|
|
11
|
+
// smaller wolf spider by dynamic similarity).
|
|
12
|
+
const slow = [0.74, 0.16, 0.58, 0.0, 0.24, 0.66, 0.08, 0.5];
|
|
13
|
+
const fast = [0.62, 0.08, 0.54, 0.0, 0.12, 0.58, 0.04, 0.5];
|
|
14
|
+
const G = (name, v, f, D, off, o = {}) => ({ name, v, f, D, off, ...o });
|
|
15
|
+
|
|
16
|
+
export const motion = {
|
|
17
|
+
// ---------------------------------------------------------------- wolf spider (default)
|
|
18
|
+
maxSpeed: 0.32, // m/s: a dash (wolf spiders sprint at 0.3-0.6 m/s over short distances)
|
|
19
|
+
accel: 1.6, // m/s^2: wolf spiders reach full speed within a stride or two
|
|
20
|
+
decel: 2.4, // and freeze abruptly
|
|
21
|
+
turnRate: 4.0, // rad/s (on the spot and at a walk)
|
|
22
|
+
latAccelMax: 0.4, // m/s^2: at speed it turns in arcs of a few body lengths
|
|
23
|
+
gears: { walk: 0.05, run: 0.14, sprint: 0.3 },
|
|
24
|
+
gaits: [
|
|
25
|
+
G('walk', 0.0, 1.6, 0.72, slow, { lift: 0.0022, bob: 0.00008, sway: 0.015 }),
|
|
26
|
+
G('walk', 0.05, 2.4, 0.66, slow, { lift: 0.0026, bob: 0.00012, sway: 0.015 }),
|
|
27
|
+
G('run', 0.1, 3.3, 0.58, fast, { lift: 0.003, bob: 0.00016, sway: 0.01 }),
|
|
28
|
+
G('run', 0.18, 4.9, 0.5, fast, { lift: 0.0034, bob: 0.0002, sway: 0.008 }),
|
|
29
|
+
G('run', 0.3, 6.0, 0.48, fast, { lift: 0.0038, bob: 0.00022, sway: 0.006 }),
|
|
30
|
+
],
|
|
31
|
+
// neutral footholds: bind claw positions pulled toward the coxa (legs are not fully spread in
|
|
32
|
+
// stance) and the stance height relative to the bind pose
|
|
33
|
+
stance: { reach: [0.8, 0.9, 0.9, 0.78], height: 1.0, width: 1.0 },
|
|
34
|
+
feet: { contact: 'claw', heelLift: 12 },
|
|
35
|
+
abdomen: { stiffness: 16, lag: 0.12, droop: 0.05 },
|
|
36
|
+
breath: { rate: 0.55, amp: 0.018 },
|
|
37
|
+
chelicerae: { fang: 'labidognath', fangOpen: 1.25, spread: 0.35, pitch: 0.45 },
|
|
38
|
+
palps: { twitch: 1, tap: 0.6 },
|
|
39
|
+
idle: { twitch: 1, raiseFront: 0.12, look: 0.35 },
|
|
40
|
+
look: { yaw: 0.45, pitch: 0.32 },
|
|
41
|
+
actions: {
|
|
42
|
+
jump: { height: 0.016, distance: 0.035, crouch: 0.25 },
|
|
43
|
+
attack: { style: 'threat-lunge', rear: 0.3, lunge: 0.005, threat: 0.7 },
|
|
44
|
+
sleep: { tuck: 0.85 },
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------- tarantula
|
|
48
|
+
variants: {
|
|
49
|
+
tarantula: {
|
|
50
|
+
maxSpeed: 0.18,
|
|
51
|
+
accel: 1.2,
|
|
52
|
+
decel: 2.4,
|
|
53
|
+
turnRate: 3.2,
|
|
54
|
+
gears: { walk: 0.035, run: 0.1, sprint: 0.2 },
|
|
55
|
+
gaits: [
|
|
56
|
+
G('walk', 0.0, 0.8, 0.7, slow, { lift: 0.005, bob: 0.00015, sway: 0.012 }),
|
|
57
|
+
G('walk', 0.04, 1.4, 0.65, slow, { lift: 0.0055, bob: 0.0002, sway: 0.012 }),
|
|
58
|
+
G('run', 0.11, 2.4, 0.6, fast, { lift: 0.0065, bob: 0.0003, sway: 0.008 }),
|
|
59
|
+
G('run', 0.236, 3.6, 0.54, fast, { lift: 0.0075, bob: 0.0004, sway: 0.006 }),
|
|
60
|
+
],
|
|
61
|
+
stance: { reach: [0.88, 0.93, 0.93, 0.88], height: 1.0, width: 1.0 },
|
|
62
|
+
breath: { rate: 0.35, amp: 0.012 },
|
|
63
|
+
chelicerae: { fang: 'orthognath', fangOpen: 1.2, spread: 0.12, pitch: 0.55 },
|
|
64
|
+
idle: { twitch: 0.5, raiseFront: 0.05, look: 0.2 },
|
|
65
|
+
actions: {
|
|
66
|
+
jump: { height: 0.012, distance: 0.05, crouch: 0.2 },
|
|
67
|
+
attack: { style: 'threat-lunge', rear: 0.7, raise2: 0.65, lunge: 0.008, threat: 0.9 },
|
|
68
|
+
sleep: { tuck: 0.8 },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Meshing plan. A spider is mostly thin legs spread over a wide box, so one dense grid would be
|
|
2
|
+
// wasteful: the body (prosoma, abdomen, palps, coxae, trochanters) is one region, every leg its
|
|
3
|
+
// own region from the middle of the trochanter outward, and the chelicerae and fangs are rigid
|
|
4
|
+
// regions of their own (they move on their own bones). Legs and body overlap across a cut plane
|
|
5
|
+
// through the trochanter and cross-fade there (both surfaces see the same primitives near the cut).
|
|
6
|
+
import { spiderDims } from './rig.js';
|
|
7
|
+
import { legJoints, LEG_KEYS } from '../../core/rig/spider.js';
|
|
8
|
+
import { lerp, norm, sub, smoothstep } from '../../core/math/vec.js';
|
|
9
|
+
|
|
10
|
+
// cut planes through the trochanters (reference space)
|
|
11
|
+
export function legCuts(J, d) {
|
|
12
|
+
const F = d.unit / 0.013;
|
|
13
|
+
return LEG_KEYS.map((key) => {
|
|
14
|
+
const S = key[0], i = +key[1];
|
|
15
|
+
const lj = legJoints(i, S);
|
|
16
|
+
const t = J[lj[1]], f = J[lj[2]];
|
|
17
|
+
return { key, p: lerp(t, f, 0.5), n: norm(sub(f, t)), e: J[lj[3]], r: d.legR[2][0] * 0.001 * 2.2 * (i === 4 ? d.hindR : 1) };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function box(points, pad) {
|
|
22
|
+
const mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9];
|
|
23
|
+
for (const p of points) for (let k = 0; k < 3; k++) { mn[k] = Math.min(mn[k], p[k] - pad); mx[k] = Math.max(mx[k], p[k] + pad); }
|
|
24
|
+
return [mn, mx];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function spiderRegions(rig, params, Q) {
|
|
28
|
+
const J = rig.J, d = spiderDims(params);
|
|
29
|
+
// the tarantula is stockier (thicker legs, bigger abdomen for its unit): slightly coarser cells
|
|
30
|
+
const F = (d.unit / 0.013) * (d.variant === 'tarantula' ? 1.12 : 1), r = Q.res;
|
|
31
|
+
const hB = 0.156e-3 * F * r, hL = 0.148e-3 * F * r, hC = 0.115e-3 * F * r;
|
|
32
|
+
const B = 2.2 * Math.max(hB, hL);
|
|
33
|
+
const cuts = legCuts(J, d);
|
|
34
|
+
// s: signed distance along the cut normal; near: within reach of the leg (distance to the segment
|
|
35
|
+
// from the cut centre to the knee)
|
|
36
|
+
for (const c of cuts) { c.ab = sub(c.e, c.p); c.ab2 = c.ab[0] ** 2 + c.ab[1] ** 2 + c.ab[2] ** 2; }
|
|
37
|
+
const inCut = (c, x, y, z) => {
|
|
38
|
+
const dx = x - c.p[0], dy = y - c.p[1], dz = z - c.p[2];
|
|
39
|
+
const s = dx * c.n[0] + dy * c.n[1] + dz * c.n[2];
|
|
40
|
+
const t = Math.max(0, Math.min(1, (dx * c.ab[0] + dy * c.ab[1] + dz * c.ab[2]) / c.ab2));
|
|
41
|
+
const ex = dx - c.ab[0] * t, ey = dy - c.ab[1] * t, ez = dz - c.ab[2] * t;
|
|
42
|
+
return { s, near: ex * ex + ey * ey + ez * ez < c.r * c.r };
|
|
43
|
+
};
|
|
44
|
+
const bodyClip = (x, y, z) => {
|
|
45
|
+
for (const c of cuts) { const q = inCut(c, x, y, z); if (q.near && q.s > B) return false; }
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
const maxR = Math.max(...d.legR.map((q) => q[0])) * 0.001 * d.hindR;
|
|
49
|
+
const minPad = 0.4e-3 * F;
|
|
50
|
+
// body box: axial joints, palps, coxae, trochanters, the start of the femora
|
|
51
|
+
const bodyPts = [];
|
|
52
|
+
for (const k of Object.keys(J)) if (!/^(femur|patella|tibia|meta|tarsus|claw)/.test(k)) bodyPts.push(J[k]);
|
|
53
|
+
for (const c of cuts) bodyPts.push(c.p);
|
|
54
|
+
const abd = d.abdomen.r.map((x) => x * 0.001);
|
|
55
|
+
const [bmin, bmax] = box(bodyPts, Math.max(abd[0], abd[1]) + minPad);
|
|
56
|
+
bmin[1] = Math.max(bmin[1], -0.2e-3 * F);
|
|
57
|
+
const regions = [{ name: 'body', part: 'body', bmin, bmax, h: hB, F: 5, clip: bodyClip }];
|
|
58
|
+
const rigid = [];
|
|
59
|
+
for (const S of ['L', 'R']) {
|
|
60
|
+
const [cmin, cmax] = box([J['cheBase' + S], J['cheTip' + S]], d.chel.r[0] * 0.0013 + minPad);
|
|
61
|
+
rigid.push({ name: 'chel' + S, part: 'chel' + S, bmin: cmin, bmax: cmax, h: hC, F: 5, rigidBone: 'chelicera' + S });
|
|
62
|
+
const [fmin, fmax] = box([J['cheTip' + S], J['fangTip' + S]], d.chel.fangR * 0.0015 + minPad * 0.5);
|
|
63
|
+
rigid.push({ name: 'fang' + S, part: 'fang' + S, bmin: fmin, bmax: fmax, h: hC * 0.8, F: 5, rigidBone: 'fang' + S });
|
|
64
|
+
}
|
|
65
|
+
const legs = { L: [], R: [] };
|
|
66
|
+
for (const c of cuts) {
|
|
67
|
+
const S = c.key[0], i = +c.key[1];
|
|
68
|
+
const lj = legJoints(i, S).slice(1).map((n) => J[n]);
|
|
69
|
+
const [lmin, lmax] = box(lj, maxR * 1.3 + minPad);
|
|
70
|
+
legs[S].push({ name: 'leg' + c.key, part: 'leg' + c.key, bmin: lmin, bmax: lmax, h: hL, F: 5, clip: (x, y, z) => inCut(c, x, y, z).s > -B });
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
jobs: [[...regions, ...rigid], legs.L, legs.R],
|
|
74
|
+
fade(x, y, z, name) {
|
|
75
|
+
if (name === 'body') {
|
|
76
|
+
let f = 1;
|
|
77
|
+
for (const c of cuts) { const q = inCut(c, x, y, z); if (q.near) f *= 1 - smoothstep(-B, B, q.s); }
|
|
78
|
+
return f;
|
|
79
|
+
}
|
|
80
|
+
if (name.startsWith('leg')) {
|
|
81
|
+
const c = cuts.find((q) => 'leg' + q.key === name);
|
|
82
|
+
return smoothstep(-B, B, inCut(c, x, y, z).s);
|
|
83
|
+
}
|
|
84
|
+
return 1;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|