@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,315 @@
|
|
|
1
|
+
// Snake coat: overlapping dorsal scales (keeled on the rattlesnake), wide ventral scutes, head plates;
|
|
2
|
+
// corn snake saddles with dark borders, lateral blotches and the checkerboard belly; rattlesnake
|
|
3
|
+
// diamonds with pale edges, flank blotches, face stripes and the black-and-white "coon tail".
|
|
4
|
+
// Everything is laid out in body coordinates: arc length sigma along the spine from the occiput and
|
|
5
|
+
// the arc u around the body from the dorsal midline, so the pattern wraps the tube like the real one.
|
|
6
|
+
import { SPINE } from './rig.js';
|
|
7
|
+
import { eyeSpec } from './sculpt.js';
|
|
8
|
+
import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
|
|
9
|
+
import { srgb, mix3, MAT, distPolyline } from '../../core/build/coatKit.js';
|
|
10
|
+
import { clamp, smoothstep, mix, fbm3, rng } from '../../core/math/vec.js';
|
|
11
|
+
|
|
12
|
+
// Colour sets (sRGB hex sampled from reference photos in neutral light).
|
|
13
|
+
export const MORPHS = {
|
|
14
|
+
normal: { ground: 0xc9763f, flank: 0xd99a5c, saddle: 0xa8321f, lateral: 0x983a26, border: 0x1b1310, belly: 0xe9e2d2, check: 0x171412, mark: [0.012, 0.009, 0.008] },
|
|
15
|
+
okeetee: { ground: 0xd6843f, flank: 0xe0a45e, saddle: 0xb52f1c, lateral: 0xa23421, border: 0x100b09, belly: 0xece4d2, check: 0x141110, mark: [0.006, 0.004, 0.004], borderW: 2.6 },
|
|
16
|
+
wild: { ground: 0xa68a6c, flank: 0xb89c7c, saddle: 0x8a3a24, lateral: 0x7c3a28, border: 0x1d1512, belly: 0xe4ddcc, check: 0x1a1715, mark: [0.013, 0.01, 0.009] },
|
|
17
|
+
amel: { ground: 0xeb9656, flank: 0xf0b070, saddle: 0xd9482a, lateral: 0xd65a36, border: 0xf2ece0, belly: 0xf3eee4, check: 0xe8a070, mark: [0.86, 0.82, 0.74], borderW: 1.2 },
|
|
18
|
+
anery: { ground: 0xa9a6a2, flank: 0xbdbab4, saddle: 0x4a4847, lateral: 0x55524f, border: 0x151414, belly: 0xe6e4e0, check: 0x161515, mark: [0.01, 0.01, 0.01] },
|
|
19
|
+
juvenile: { ground: 0x9c8672, flank: 0xae9a86, saddle: 0x6e2a1c, lateral: 0x5e2a20, border: 0x140e0c, belly: 0xe6e0d2, check: 0x151211, mark: [0.008, 0.006, 0.006] },
|
|
20
|
+
};
|
|
21
|
+
export const DIAMONDBACK = {
|
|
22
|
+
ground: 0x8a7560, flank: 0x9a846a, diamond: 0x56422f, centre: 0x76604a, edge: 0xcdbd9c, blotch: 0x5e4a38,
|
|
23
|
+
belly: 0xe2d6bc, ringW: 0xe6e0d4, ringB: 0x1a1715, rattle: 0xbfae8c, mark: [0.69, 0.6, 0.45],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function markColorOf(params) {
|
|
27
|
+
if (params.variant === 'rattlesnake') return DIAMONDBACK.mark;
|
|
28
|
+
return (MORPHS[params.coat?.morph] || MORPHS.normal).mark;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function snakeCoat(ctx) {
|
|
32
|
+
const { rig, params, pos, nrm, nV, regionOf, regionNames, weights } = ctx;
|
|
33
|
+
const J = rig.J, B = rig.plan, viper = params.variant === 'rattlesnake';
|
|
34
|
+
const cp = params.coat || {};
|
|
35
|
+
const R = rng(9173 + (cp.seed || 0));
|
|
36
|
+
const AXL = weights.axialLengths;
|
|
37
|
+
const s0 = AXL[1]; // arc of v0 from the nose
|
|
38
|
+
const total = AXL[AXL.length - 1] - s0;
|
|
39
|
+
const sVent = total * (1 - B.tailFrac);
|
|
40
|
+
const hL = B.headLen, W = B.P.headW * B.kH, H = B.P.headH * B.kH;
|
|
41
|
+
const yB = -J.v0[1];
|
|
42
|
+
// axis samples: y and half width at arc sigma
|
|
43
|
+
const axY = (sg) => {
|
|
44
|
+
let i = 1;
|
|
45
|
+
while (i < AXL.length - 2 && sg + s0 > AXL[i + 1]) i++;
|
|
46
|
+
const t = clamp((sg + s0 - AXL[i]) / (AXL[i + 1] - AXL[i]), 0, 1);
|
|
47
|
+
const a = J['v' + (i - 1)], b = J['v' + i];
|
|
48
|
+
return a[1] + (b[1] - a[1]) * t;
|
|
49
|
+
};
|
|
50
|
+
const hwAt = (sg) => B.hw(clamp(sg / total, 0, 1));
|
|
51
|
+
|
|
52
|
+
// ---- colours
|
|
53
|
+
const juv = params.age === 'juvenile';
|
|
54
|
+
const morph = viper ? null : juv && (cp.morph === 'normal' || cp.morph === 'wild') ? MORPHS.juvenile : MORPHS[cp.morph] || MORPHS.normal;
|
|
55
|
+
const warm = cp.warm || 0, light = cp.light || 0;
|
|
56
|
+
const tweak = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.1 * warm + light), c[1] * (1 + 0.02 * warm + light), c[2] * (1 - 0.1 * warm + light)]; };
|
|
57
|
+
const C0 = viper
|
|
58
|
+
? Object.fromEntries(Object.entries(DIAMONDBACK).filter(([k]) => k !== 'mark').map(([k, v]) => [k, tweak(v)]))
|
|
59
|
+
: Object.fromEntries(Object.entries(morph).filter(([k]) => typeof morph[k] === 'number' && k !== 'borderW').map(([k, v]) => [k, tweak(v)]));
|
|
60
|
+
// individuals range from bright orange to duller, browner / greyer grounds
|
|
61
|
+
const C = C0;
|
|
62
|
+
if (!viper && (cp.morph === 'normal' || cp.morph === 'okeetee')) {
|
|
63
|
+
const d = clamp(cp.dull || 0, 0, 0.7);
|
|
64
|
+
C.ground = mix3(C.ground, srgb(MORPHS.wild.ground), d); C.flank = mix3(C.flank, srgb(MORPHS.wild.flank), d);
|
|
65
|
+
C.saddle = mix3(C.saddle, srgb(0x8e2c1c), d * 0.6);
|
|
66
|
+
}
|
|
67
|
+
if (viper) {
|
|
68
|
+
// wild C. atrox range from pale grey through grey-brown to reddish-brown, with bolder or fainter
|
|
69
|
+
// diamonds (seeded tone: < 0 greyer, > 0 redder; light: paler / darker)
|
|
70
|
+
const tone = clamp(cp.tone || 0, -1, 1), lt = cp.light || 0;
|
|
71
|
+
const tgt = tone < 0 ? srgb(0x8f8c86) : srgb(0x9a6a4e);
|
|
72
|
+
const k = Math.abs(tone) * 0.75;
|
|
73
|
+
for (const key of ['ground', 'flank', 'centre']) C[key] = mix3(C[key], [tgt[0] * (key === 'flank' ? 1.08 : 1), tgt[1] * (key === 'flank' ? 1.08 : 1), tgt[2] * (key === 'flank' ? 1.08 : 1)], k);
|
|
74
|
+
C.diamond = mix3(C.diamond, tone < 0 ? srgb(0x4a4744) : srgb(0x5a3526), k * 0.7);
|
|
75
|
+
for (const key of ['ground', 'flank', 'centre', 'edge']) C[key] = C[key].map((c) => c * (1 + 1.6 * lt));
|
|
76
|
+
C.diamond = mix3(C.diamond, C.ground, clamp(0.3 - 0.3 * ((cp.blotch || 1) - 1) / 0.18, 0, 0.55));
|
|
77
|
+
}
|
|
78
|
+
const bk = cp.blotch || 1;
|
|
79
|
+
|
|
80
|
+
// ---- pattern features (body coordinates)
|
|
81
|
+
const sStart = viper ? 0.045 * total : Math.max(0, 1.5 * hL - (0.5 * (total * (1 - B.tailFrac) - 1.5 * hL)) / 36);
|
|
82
|
+
const feats = []; // { s, a, b, u (centre across), kind, col }
|
|
83
|
+
if (viper) {
|
|
84
|
+
const n = Math.round((cp.count || 1) * 29);
|
|
85
|
+
const sp = (sVent - sStart) / n;
|
|
86
|
+
for (let k = 0; k < n; k++) {
|
|
87
|
+
const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.08 * sp;
|
|
88
|
+
feats.push({ s, a: 0.56 * sp, b: 0.9, u: 0, kind: 'diamond' });
|
|
89
|
+
// flank blotches below the lateral corners of the diamonds
|
|
90
|
+
for (const side of [1, -1]) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.1 * sp, a: 0.2 * sp, b: 0.28, u: side * 1.55, kind: 'blotch' });
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
const n = Math.round((cp.count || 1) * 36);
|
|
94
|
+
const sp = (sVent - sStart) / n;
|
|
95
|
+
for (let k = 0; k < n; k++) {
|
|
96
|
+
const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.14 * sp;
|
|
97
|
+
feats.push({ s, a: (0.25 + 0.06 * R()) * sp * bk, b: (1.0 + 0.14 * R()) * bk, u: (R() - 0.5) * 0.12, kind: 'saddle' });
|
|
98
|
+
for (const side of [1, -1]) if (R() < 0.85) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.2 * sp, a: (0.15 + 0.05 * R()) * sp * bk, b: 0.3 + 0.08 * R(), u: side * (1.72 + 0.1 * R()), kind: 'lateral' });
|
|
99
|
+
}
|
|
100
|
+
// tail: saddles continue, closer
|
|
101
|
+
const nt = Math.round(11 * (cp.count || 1));
|
|
102
|
+
const spt = (total * 0.985 - sVent) / nt;
|
|
103
|
+
for (let k = 0; k < nt; k++) feats.push({ s: sVent + (k + 0.5) * spt, a: 0.27 * spt * bk, b: 1.2, u: 0, kind: 'saddle' });
|
|
104
|
+
}
|
|
105
|
+
feats.sort((p, q) => p.s - q.s);
|
|
106
|
+
// belly checks (corn): blocks on alternating sides, scute by scute
|
|
107
|
+
const checks = [];
|
|
108
|
+
const scute = viper ? total / 182 : total / 215;
|
|
109
|
+
if (!viper) {
|
|
110
|
+
for (let s = 0.09 * total; s < sVent; s += scute) {
|
|
111
|
+
if (R() < 0.5) checks.push({ s: s + scute * (0.4 + 0.2 * R()), a: scute * (0.45 + 0.5 * R()), side: R() < 0.5 ? 1 : -1, w: 0.3 + 0.25 * R() });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// head and neck features, as 2-D polylines in a projection of the (straight) bind pose:
|
|
116
|
+
// top view (x, z) for the crown and nape, side view (z, y) for the flanks of the head
|
|
117
|
+
const E = eyeSpec(params);
|
|
118
|
+
const eyeC = eyeFrameOf(E, J.v0, 1).c; // left eye centre (x > 0)
|
|
119
|
+
const z0 = J.v0[2];
|
|
120
|
+
const ez = eyeC[2] - z0, ey = eyeC[1]; // eye position along the head, height (world)
|
|
121
|
+
const seg2 = (px, py, P, Wd) => {
|
|
122
|
+
let best = 1e9;
|
|
123
|
+
for (let i = 0; i < P.length - 1; i++) {
|
|
124
|
+
const ax = P[i][0], ay = P[i][1], bx = P[i + 1][0] - ax, by = P[i + 1][1] - ay;
|
|
125
|
+
const t = clamp(((px - ax) * bx + (py - ay) * by) / (bx * bx + by * by || 1e-12), 0, 1);
|
|
126
|
+
const d = Math.hypot(px - ax - bx * t, py - ay - by * t) - (Wd[i] + (Wd[i + 1] - Wd[i]) * t);
|
|
127
|
+
if (d < best) best = d;
|
|
128
|
+
}
|
|
129
|
+
return best;
|
|
130
|
+
};
|
|
131
|
+
const kH = B.kH;
|
|
132
|
+
// corn: spear point (two blotches meeting in a point between the eyes, running back onto the neck),
|
|
133
|
+
// the interocular bar, and the post-ocular stripe through the eye to the jaw angle
|
|
134
|
+
const spearL = [[0.0, z0 + ez + 0.003 * kH], [0.06 * W, z0 + 0.4 * hL], [0.11 * W, z0 + 0.0 * hL], [0.14 * W, z0 - 0.35 * hL], [0.1 * W, z0 - 0.75 * hL]];
|
|
135
|
+
const spearW = [0.0006, 0.0011, 0.0013, 0.0014, 0.001].map((w) => w * kH);
|
|
136
|
+
const barP = [[0, z0 + ez + 0.1 * E.r], [eyeC[0] * 0.85, z0 + ez + 0.4 * E.r]];
|
|
137
|
+
const postP = [[z0 + ez - 1.0 * E.r, ey - 0.3 * E.r], [z0 + 0.2 * hL, 0.45 * H], [z0 + 0.02 * hL, 0.36 * H]];
|
|
138
|
+
// rattlesnake: pale pre-ocular stripe (front of the eye down to the lip) and post-ocular stripe
|
|
139
|
+
// (behind the eye diagonally to the mouth corner), with the dark mask between them
|
|
140
|
+
const preV = [[z0 + ez + 1.7 * E.r, ey + 1.1 * E.r], [z0 + 0.78 * hL, 0.62 * H], [z0 + 0.66 * hL, 0.4 * H]];
|
|
141
|
+
const postV = [[z0 + ez - 0.5 * E.r, ey - 1.5 * E.r], [z0 + 0.3 * hL, 0.34 * H], [z0 + 0.02 * hL, 0.26 * H]];
|
|
142
|
+
const maskV = [[z0 + ez + 0.4 * E.r, ey], [z0 + 0.3 * hL, 0.5 * H], [z0 + 0.05 * hL, 0.42 * H]];
|
|
143
|
+
// fine vertical sutures between the labial scales (corn)
|
|
144
|
+
const labialMark = (x, z, k) => {
|
|
145
|
+
const u = ((z - J.v0[2]) / hL - 0.12) / 0.11; // ~8 labials from the jaw angle to the snout
|
|
146
|
+
if (u < 0 || u > 8) return 1;
|
|
147
|
+
const f = Math.abs(u - Math.round(u));
|
|
148
|
+
return f * 0.11 * hL - 0.00008 * B.kH + (1 - k) * 0;
|
|
149
|
+
};
|
|
150
|
+
// ---- per-vertex outputs
|
|
151
|
+
const comb = new Float32Array(nV * 3), tint = new Float32Array(nV * 4), pattern = new Float32Array(nV).fill(1);
|
|
152
|
+
const mark = new Float32Array(nV).fill(1), furLen = new Float32Array(nV), patternColor = new Float32Array(nV * 4), surf = new Float32Array(nV * 4);
|
|
153
|
+
const bw = ((morph && morph.borderW) || 1) * 0.0007;
|
|
154
|
+
const idx = { body: 0, head: 1, jaw: 2, tongue: 3, fork: 4, fang: 5, rattle: 6 };
|
|
155
|
+
for (let v = 0; v < nV; v++) {
|
|
156
|
+
const x = pos[v * 3], y = pos[v * 3 + 1], z = pos[v * 3 + 2];
|
|
157
|
+
const nx = nrm[v * 3], ny = nrm[v * 3 + 1], nz = nrm[v * 3 + 2];
|
|
158
|
+
const reg = idx[regionNames[regionOf[v]]] ?? 0;
|
|
159
|
+
// comb: scales overlap tail-ward
|
|
160
|
+
let cx = -nx * -nz, cy = -ny * -nz, cz = -1 - nz * -nz;
|
|
161
|
+
const cl = Math.hypot(cx, cy, cz) || 1;
|
|
162
|
+
comb[v * 3] = cx / cl; comb[v * 3 + 1] = cy / cl; comb[v * 3 + 2] = cz / cl;
|
|
163
|
+
const sg = weights.axialS[v] - s0;
|
|
164
|
+
const hw = hwAt(Math.max(0, sg));
|
|
165
|
+
const ya = axY(Math.max(0, sg));
|
|
166
|
+
const th = Math.atan2(x, y - ya); // 0 dorsal, +-pi ventral
|
|
167
|
+
const u = th; // angular coordinate (radians around the body)
|
|
168
|
+
const nzF = fbm3(x * 60, y * 60, z * 60, 3) - 0.5;
|
|
169
|
+
const nLo = fbm3(x * 9, y * 9, z * 9, 3) - 0.5;
|
|
170
|
+
let col, mat = MAT.SCALES, gloss = viper ? 0.28 : 0.55, size = (viper ? 0.0042 : 0.0024) * (hw / (viper ? 0.0265 : 0.0135)) ** 0.35, keel = viper ? 0.85 : 0.05, irid = viper ? -0.15 : -0.4;
|
|
171
|
+
let pat = 1, pcol = null, mk = 1;
|
|
172
|
+
const belly = reg <= 1 && y < 0.1 * hw && ny < -0.75 && sg > -0.2 * hL;
|
|
173
|
+
// ---------------- body (and neck)
|
|
174
|
+
if (reg === 0 || reg === 1) {
|
|
175
|
+
const lat = smoothstep(0.9, 2.2, Math.abs(u)); // 0 dorsal .. 1 low flank
|
|
176
|
+
const t = clamp(sg / total, 0, 1);
|
|
177
|
+
if (viper) {
|
|
178
|
+
col = mix3(C.ground, C.flank, lat);
|
|
179
|
+
if (sg > sVent) {
|
|
180
|
+
// coon tail: alternating rings from the vent to the rattle
|
|
181
|
+
const rl = (total - sVent) / 9.5;
|
|
182
|
+
const q = (sg - sVent) / rl;
|
|
183
|
+
col = C.ringW;
|
|
184
|
+
const f = q - Math.floor(q);
|
|
185
|
+
if (Math.floor(q) % 2 === 1) { pat = -Math.min(f, 1 - f) * rl; pcol = C.ringB; }
|
|
186
|
+
else pat = Math.min(f, 1 - f) * rl;
|
|
187
|
+
pat += nzF * 0.0015;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
col = mix3(C.ground, C.flank, lat);
|
|
191
|
+
}
|
|
192
|
+
// dorsal features near sigma
|
|
193
|
+
let lo = 0, hi = feats.length;
|
|
194
|
+
while (lo < hi) { const md = (lo + hi) >> 1; if (feats[md].s < sg - 0.06) lo = md + 1; else hi = md; }
|
|
195
|
+
for (let k = lo; k < feats.length && feats[k].s < sg + 0.06; k++) {
|
|
196
|
+
const F = feats[k];
|
|
197
|
+
if (sg > sVent && viper) break;
|
|
198
|
+
const ds = sg - F.s, du = (u - F.u) * hw; // metres across the surface
|
|
199
|
+
const bb = F.b * hw;
|
|
200
|
+
let d;
|
|
201
|
+
if (F.kind === 'diamond') {
|
|
202
|
+
const L1 = Math.abs(ds) / F.a + Math.abs(du) / bb;
|
|
203
|
+
d = (L1 - 1) * (F.a * bb) / Math.hypot(F.a, bb);
|
|
204
|
+
} else {
|
|
205
|
+
const rr = Math.min(F.a, bb) * 0.55;
|
|
206
|
+
const qx = Math.abs(ds) - (F.a - rr), qy = Math.abs(du) - (bb - rr);
|
|
207
|
+
d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - rr;
|
|
208
|
+
}
|
|
209
|
+
d += nzF * (viper ? 0.0012 : 0.0016);
|
|
210
|
+
if (d < pat) {
|
|
211
|
+
pat = d;
|
|
212
|
+
if (F.kind === 'diamond') pcol = mix3(C.diamond, C.centre, smoothstep(-0.003, -0.009, d) * 0.8);
|
|
213
|
+
else pcol = F.kind === 'saddle' ? C.saddle : F.kind === 'lateral' ? C.lateral : C.blotch;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (viper && sg <= sVent) {
|
|
217
|
+
// pale diamond edges (one scale wide), fading into the ground colour
|
|
218
|
+
col = mix3(C.edge, col, smoothstep(0.0022, 0.0034, pat + 0.0006 * nzF));
|
|
219
|
+
mk = 1;
|
|
220
|
+
} else if (!viper) {
|
|
221
|
+
mk = Math.abs(pat) - bw * (0.8 + 0.4 * (0.5 + nzF));
|
|
222
|
+
}
|
|
223
|
+
// belly
|
|
224
|
+
if (belly || Math.abs(u) > 2.55) {
|
|
225
|
+
const bcol = viper ? C.belly : C.belly;
|
|
226
|
+
const e = belly ? 1 : smoothstep(2.55, 2.75, Math.abs(u));
|
|
227
|
+
col = mix3(col, bcol, e);
|
|
228
|
+
if (e > 0.5) { pat = 1; pcol = null; mk = 1; }
|
|
229
|
+
if (!viper && belly) {
|
|
230
|
+
// checkerboard blocks, and two dark stripes under the tail
|
|
231
|
+
let best = 1;
|
|
232
|
+
for (const c of checks) {
|
|
233
|
+
if (Math.abs(c.s - sg) > scute * 3) continue;
|
|
234
|
+
const qx = Math.abs(sg - c.s) - c.a, xc = c.side * hw * (0.2 + c.w * 0.5), qy = Math.abs(x - xc) - hw * c.w * 0.5;
|
|
235
|
+
const d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0);
|
|
236
|
+
if (d < best) best = d;
|
|
237
|
+
}
|
|
238
|
+
if (sg > sVent) best = Math.min(best, Math.abs(Math.abs(x) - hw * 0.35) - hw * 0.14);
|
|
239
|
+
if (best < 1) { pat = best + nzF * 0.0006; pcol = C.check; }
|
|
240
|
+
}
|
|
241
|
+
if (belly) { mat = MAT.SCALES; gloss = 0.7; size = scute; keel = -1; irid = -0.3; }
|
|
242
|
+
}
|
|
243
|
+
// head and nape: the scales grow into large head plates over ~1 head length, and their relief
|
|
244
|
+
// fades in the same span, so head and neck are one continuous covering (no seam)
|
|
245
|
+
// (measured straight back from the occiput in the bind pose, identical on the overlapping head
|
|
246
|
+
// and body meshes, so their cross-fade shows no seam)
|
|
247
|
+
const sgG = J.v0[2] - z;
|
|
248
|
+
const hq = smoothstep(-0.1 * hL, 1.2 * hL, sgG); // 0 on the head .. 1 on the body
|
|
249
|
+
if (!belly && sgG < 1.2 * hL) {
|
|
250
|
+
const plate = viper ? 0.0027 * kH : 0.009 * kH;
|
|
251
|
+
size = size + (plate - size) * (1 - hq);
|
|
252
|
+
keel = keel * (viper ? 0.5 + 0.5 * hq : 1);
|
|
253
|
+
irid = mix(viper ? -0.5 : -0.75, irid, hq);
|
|
254
|
+
gloss = mix(viper ? 0.35 : 0.75, gloss, hq);
|
|
255
|
+
}
|
|
256
|
+
const upper = ny > -0.15 && y > 0.35 * hw;
|
|
257
|
+
if (!viper && sgG < 1.3 * hL) {
|
|
258
|
+
// spear point on the crown and nape (top view), with black borders
|
|
259
|
+
let d = 1;
|
|
260
|
+
if (upper) {
|
|
261
|
+
const ax = Math.abs(x);
|
|
262
|
+
d = Math.min(seg2(ax, z, spearL, spearW), seg2(ax, z, barP, [0.0012 * kH, 0.0009 * kH])) + nzF * 0.0005;
|
|
263
|
+
}
|
|
264
|
+
// post-ocular stripe on the side of the head (side view)
|
|
265
|
+
if (Math.abs(x) > 0.2 * W && sgG < 0.1 * hL) d = Math.min(d, seg2(z, y, postP, [0.0009 * kH, 0.0012 * kH, 0.0009 * kH]) + nzF * 0.0004);
|
|
266
|
+
if (d < pat) { pat = d; pcol = C.saddle; mk = Math.abs(d) - bw * 0.9; }
|
|
267
|
+
}
|
|
268
|
+
if (viper && sgG < 0.3 * hL && Math.abs(x) > 0.15 * W) {
|
|
269
|
+
const dm = seg2(z, y, maskV, [0.0022 * kH, 0.0022 * kH, 0.0016 * kH]);
|
|
270
|
+
if (dm < 0) col = mix3(col, C.diamond, 0.75 * smoothstep(0, -0.0015, dm));
|
|
271
|
+
mk = Math.min(mk, seg2(z, y, preV, [0.0011 * kH, 0.0018 * kH, 0.0015 * kH]) + nzF * 0.0004, seg2(z, y, postV, [0.0011 * kH, 0.002 * kH, 0.0016 * kH]) + nzF * 0.0004);
|
|
272
|
+
}
|
|
273
|
+
if (reg === 1 && sgG < 0.02 * hL) {
|
|
274
|
+
const hy = y - J.v0[1];
|
|
275
|
+
if (viper) {
|
|
276
|
+
col = mix3(col, C.edge, 0.8 * smoothstep(yB + 0.46 * H, yB + 0.34 * H, hy));
|
|
277
|
+
} else {
|
|
278
|
+
// labials: belly cream with thin, faint sutures
|
|
279
|
+
if (hy < yB + 0.48 * H) col = mix3(col, C.belly, 0.7 * smoothstep(yB + 0.48 * H, yB + 0.38 * H, hy));
|
|
280
|
+
if (hy < yB + 0.48 * H && Math.abs(x) > 0.3 * W) mk = Math.min(mk, labialMark(x, z, 0.1));
|
|
281
|
+
}
|
|
282
|
+
// mouth: the palate, well inside the lips (no lining shows while the mouth is shut)
|
|
283
|
+
if (ny < -0.6 && hy < yB + 0.4 * H && Math.abs(x) < 0.15 * W && z > J.v0[2] + 0.12 * hL && !belly) { mat = MAT.MOUTH; col = srgb(0x8a5a58); pat = 1; mk = 1; }
|
|
284
|
+
}
|
|
285
|
+
// low-frequency mottling
|
|
286
|
+
col = [col[0] * (1 + 0.1 * nLo), col[1] * (1 + 0.1 * nLo), col[2] * (1 + 0.08 * nLo)];
|
|
287
|
+
void t;
|
|
288
|
+
} else if (reg === 2) {
|
|
289
|
+
// lower jaw: pale labials outside (plates, faint relief), mouth lining well inside
|
|
290
|
+
const hy = y - J.v0[1];
|
|
291
|
+
col = viper ? mix3(C.edge, C.belly, 0.5) : mix3(C.belly, C.flank, 0.3);
|
|
292
|
+
size = viper ? 0.003 * kH : 0.008 * kH;
|
|
293
|
+
keel = 0; gloss = viper ? 0.5 : 0.75; irid = viper ? -0.5 : -0.75;
|
|
294
|
+
if (ny > 0.6 && hy > yB + 0.32 * H && Math.abs(x) < 0.12 * W) { mat = MAT.MOUTH; col = srgb(0x8a5a58); }
|
|
295
|
+
} else if (reg === 3 || reg === 4) {
|
|
296
|
+
mat = MAT.SKIN;
|
|
297
|
+
col = viper ? srgb(0x141212) : reg === 4 ? srgb(0x1a0f0f) : srgb(0x5a1414);
|
|
298
|
+
gloss = 0.85;
|
|
299
|
+
} else if (reg === 5) {
|
|
300
|
+
mat = MAT.KERATIN; col = srgb(0xece4d0); gloss = 0.8;
|
|
301
|
+
} else if (reg === 6) {
|
|
302
|
+
mat = MAT.KERATIN;
|
|
303
|
+
col = mix3(C.rattle, [C.rattle[0] * 0.55, C.rattle[1] * 0.5, C.rattle[2] * 0.45], 0.5 + 0.5 * Math.sin(-z * 900));
|
|
304
|
+
gloss = 0.55;
|
|
305
|
+
}
|
|
306
|
+
tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
|
|
307
|
+
pattern[v] = pat;
|
|
308
|
+
mark[v] = mk;
|
|
309
|
+
const pc = pcol || col;
|
|
310
|
+
patternColor[v * 4] = pc[0]; patternColor[v * 4 + 1] = pc[1]; patternColor[v * 4 + 2] = pc[2]; patternColor[v * 4 + 3] = pcol ? 1 : 0;
|
|
311
|
+
surf[v * 4] = gloss; surf[v * 4 + 1] = size; surf[v * 4 + 2] = keel; surf[v * 4 + 3] = irid;
|
|
312
|
+
}
|
|
313
|
+
void SPINE; void mix;
|
|
314
|
+
return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { features: feats.length, checks: checks.length } };
|
|
315
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Snake: corn snake (Pantherophis guttatus) by default; western diamondback rattlesnake
|
|
2
|
+
// (Crotalus atrox) with `variant: 'rattlesnake'`. Proving species for the snake body plan
|
|
3
|
+
// (core/motion/snake.js, core/rig/snake.js).
|
|
4
|
+
import { snakeRig } from './rig.js';
|
|
5
|
+
import { sculptSnake, eyeSpec } from './sculpt.js';
|
|
6
|
+
import { snakeRegions } from './regions.js';
|
|
7
|
+
import { snakeCoat, markColorOf } from './coat.js';
|
|
8
|
+
import { motion } from './motion.js';
|
|
9
|
+
|
|
10
|
+
const VARIANTS = { corn: 'corn', cornsnake: 'corn', rattlesnake: 'rattlesnake', rattler: 'rattlesnake', diamondback: 'rattlesnake', viper: 'rattlesnake' };
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
id: 'snake',
|
|
14
|
+
name: 'Snake',
|
|
15
|
+
latin: 'Pantherophis guttatus',
|
|
16
|
+
group: 'others',
|
|
17
|
+
plan: 'snake',
|
|
18
|
+
covering: 'scales',
|
|
19
|
+
variants: [
|
|
20
|
+
{ id: 'corn', name: 'Corn snake', latin: 'Pantherophis guttatus' },
|
|
21
|
+
{ id: 'rattlesnake', name: 'Western diamondback rattlesnake', latin: 'Crotalus atrox' },
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
// Seeded individual. Size: corn adults 0.9-1.5 m (reference 1.2 m), rattlesnake 0.9-1.6 m, males
|
|
25
|
+
// larger with longer tails; hatchlings ~0.3 m with relatively bigger heads and eyes.
|
|
26
|
+
variation(R, o) {
|
|
27
|
+
const variant = VARIANTS[String(o.variant || 'corn').toLowerCase()] || 'corn';
|
|
28
|
+
const viper = variant === 'rattlesnake';
|
|
29
|
+
const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
|
|
30
|
+
const age = o.age || 'adult';
|
|
31
|
+
const juv = age === 'juvenile';
|
|
32
|
+
const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
|
|
33
|
+
const male = sex === 'male';
|
|
34
|
+
const size = (male ? 1.04 : 0.96) * (1 + 0.1 * g()) * (juv ? 0.27 : 1) * (viper && male ? 1.05 : 1);
|
|
35
|
+
const shape = {
|
|
36
|
+
length: 1,
|
|
37
|
+
girth: (1 + 0.07 * g()) * (juv ? 0.92 : 1) * (viper && !male ? 1.04 : 1),
|
|
38
|
+
head: (1 + 0.04 * g()) * (juv ? 1.4 : 1),
|
|
39
|
+
tail: (male ? 1.08 : 0.94) * (1 + 0.04 * g()),
|
|
40
|
+
};
|
|
41
|
+
const r = R();
|
|
42
|
+
const morph = viper ? 'diamondback' : r < 0.52 ? 'normal' : r < 0.68 ? 'okeetee' : r < 0.84 ? 'wild' : r < 0.94 ? 'amel' : 'anery';
|
|
43
|
+
return {
|
|
44
|
+
variant, sex, age, size, shape,
|
|
45
|
+
warps: [],
|
|
46
|
+
minThick: 0.0005,
|
|
47
|
+
rattleSegs: viper ? (juv ? 1 : 5 + Math.floor(R() * 4)) : 0,
|
|
48
|
+
coat: { morph, seed: Math.floor(R() * 1e6), warm: 1.1 * g(), light: 0.1 * g(), count: 1 + 0.1 * g(), dull: Math.max(0, 0.45 * g() + 0.1), blotch: 1 + 0.18 * g(), tone: viper ? 2 * R() - 1 : 0 },
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
rig: (params) => snakeRig(params),
|
|
52
|
+
sculpt: (m, rig, params) => sculptSnake(m, rig, params),
|
|
53
|
+
regions: snakeRegions,
|
|
54
|
+
coat: snakeCoat,
|
|
55
|
+
eyeSpecs: (params, rig) => [1, -1].map((side) => ({ side, spec: eyeSpec(params), headOrigin: rig.J.v0, bone: 'head', look: lookOf(params) })),
|
|
56
|
+
motion,
|
|
57
|
+
// render hints may depend on the individual (the mark colour follows the morph)
|
|
58
|
+
render: (params) => ({ markColor: markColorOf(params || {}) }),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function lookOf(params) {
|
|
62
|
+
if (params.variant === 'rattlesnake') {
|
|
63
|
+
return { pupil: 'slit', lids: 'none', pupilSize: [0.1, 0.55], iris: { inner: [0.12, 0.1, 0.07], mid: [0.33, 0.27, 0.19], hi: [0.5, 0.42, 0.3], outer: [0.16, 0.13, 0.09] }, sclera: { color: [0.2, 0.17, 0.12], visible: false }, lidColor: [0.2, 0.17, 0.13] };
|
|
64
|
+
}
|
|
65
|
+
const amel = params.coat?.morph === 'amel';
|
|
66
|
+
return {
|
|
67
|
+
pupil: 'round', lids: 'none', pupilSize: [0.34, 0.5],
|
|
68
|
+
iris: amel
|
|
69
|
+
? { inner: [0.35, 0.02, 0.03], mid: [0.62, 0.06, 0.07], hi: [0.8, 0.2, 0.16], outer: [0.4, 0.03, 0.04] }
|
|
70
|
+
: { inner: [0.2, 0.06, 0.02], mid: [0.62, 0.22, 0.06], hi: [0.82, 0.42, 0.14], outer: [0.16, 0.05, 0.02] },
|
|
71
|
+
sclera: { color: [0.3, 0.12, 0.05], visible: false }, lidColor: [0.3, 0.12, 0.05],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Snake motion data for the snake engine (schema: src/core/motion/snake.js). Numbers are for the
|
|
2
|
+
// reference individual (params.size = 1, total length 1.2 m); the engine scales lengths by size and
|
|
3
|
+
// speeds by sqrt(size).
|
|
4
|
+
//
|
|
5
|
+
// Lateral undulation (Jayne 1986, Hu et al. 2009, Fu et al. 2022): every point follows the
|
|
6
|
+
// head's path; 1.5-2.5 waves on the body; wavelength 0.35-0.45 of the total length, amplitude
|
|
7
|
+
// 0.06-0.10 of it, shrinking at a slow crawl; cruise 0.1-0.3 m/s, sprint ~0.6-0.8 m/s.
|
|
8
|
+
// Strike (Penning et al. 2016): peak head speed ~2.7-3 m/s, ~190 m/s^2, 50-90 ms to the target.
|
|
9
|
+
export const motion = {
|
|
10
|
+
maxSpeed: 0.7,
|
|
11
|
+
accel: 0.9,
|
|
12
|
+
decel: 1.6,
|
|
13
|
+
turnRate: 2.2,
|
|
14
|
+
minTurnRadius: 0.1, // x total length
|
|
15
|
+
gears: { crawl: 0.07, slither: 0.24, dash: 0.6 },
|
|
16
|
+
// [name, speed (m/s), wavelength (x TL), amplitude (x TL), head lift (m), head yaw stabilisation 0..1]
|
|
17
|
+
gaits: [
|
|
18
|
+
{ name: 'crawl', v: 0, wave: 0.5, amp: 0.028, headLift: 0.012, stab: 0.75 },
|
|
19
|
+
{ name: 'crawl', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.012, stab: 0.7 },
|
|
20
|
+
{ name: 'slither', v: 0.16, wave: 0.43, amp: 0.066, headLift: 0.009, stab: 0.65 },
|
|
21
|
+
{ name: 'slither', v: 0.35, wave: 0.4, amp: 0.078, headLift: 0.006, stab: 0.6 },
|
|
22
|
+
{ name: 'dash', v: 0.45, wave: 0.37, amp: 0.085, headLift: 0.004, stab: 0.55 },
|
|
23
|
+
{ name: 'dash', v: 0.8, wave: 0.35, amp: 0.09, headLift: 0.003, stab: 0.5 },
|
|
24
|
+
],
|
|
25
|
+
body: { neck: 0.12, forebody: 0.3 }, // x TL: neck (raised when looking), strike forebody
|
|
26
|
+
tongue: { rate: 0.7, length: 0.6, flicks: [3, 6], freq: 7.5 },
|
|
27
|
+
breath: { rate: 0.28, roll: 0.03 },
|
|
28
|
+
actions: {
|
|
29
|
+
strike: { reach: 0.33, speed: 2.8, gape: 75, coilHeight: 0.07, rise: 0.12 },
|
|
30
|
+
coil: { pitch: 0.88, speed: 0.34 },
|
|
31
|
+
sit: { raise: 0.13 },
|
|
32
|
+
eat: { swallow: 3.2 },
|
|
33
|
+
drink: { pumps: 4 },
|
|
34
|
+
},
|
|
35
|
+
variants: {
|
|
36
|
+
// heavy-bodied viper: slower, stiffer undulation, big gape, fangs, rattle
|
|
37
|
+
// C. atrox: heavy and slow; slow travel is nearly straight (rectilinear-looking, 1-5 cm/s),
|
|
38
|
+
// a fast escape tops out ~0.35 m/s; the rattle is carried just off the ground while moving
|
|
39
|
+
rattlesnake: {
|
|
40
|
+
maxSpeed: 0.38, gears: { crawl: 0.04, slither: 0.16, dash: 0.34 }, tailCarry: 0.012,
|
|
41
|
+
gaits: [
|
|
42
|
+
{ name: 'crawl', v: 0, wave: 0.6, amp: 0.012, headLift: 0.006, stab: 0.85 },
|
|
43
|
+
{ name: 'crawl', v: 0.06, wave: 0.55, amp: 0.022, headLift: 0.008, stab: 0.8 },
|
|
44
|
+
{ name: 'slither', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.008, stab: 0.7 },
|
|
45
|
+
{ name: 'slither', v: 0.22, wave: 0.42, amp: 0.062, headLift: 0.006, stab: 0.65 },
|
|
46
|
+
{ name: 'dash', v: 0.28, wave: 0.4, amp: 0.07, headLift: 0.004, stab: 0.6 },
|
|
47
|
+
{ name: 'dash', v: 0.38, wave: 0.38, amp: 0.075, headLift: 0.004, stab: 0.55 },
|
|
48
|
+
],
|
|
49
|
+
tongue: { length: 0.85, amp: 0.55 }, strike: { gape: 115, reach: 0.36, coilHeight: 0.1 }, rattle: true },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Meshing plan: the long body at ~7 % of the max half width, the head at half that (overlapping the
|
|
2
|
+
// body across the neck and cross-faded), the lower jaw, tongue, fangs and rattle as rigid regions.
|
|
3
|
+
// Thin parts (tongue, fangs) are dropped at the low and crowd tiers.
|
|
4
|
+
import { SPINE } from './rig.js';
|
|
5
|
+
import { smoothstep } from '../../core/math/vec.js';
|
|
6
|
+
|
|
7
|
+
export function snakeRegions(rig, params, Q) {
|
|
8
|
+
const J = rig.J, B = rig.plan, P = B.P, r = Q.res;
|
|
9
|
+
let hwMax = 0;
|
|
10
|
+
for (let i = 0; i <= 40; i++) hwMax = Math.max(hwMax, B.hw(i / 40));
|
|
11
|
+
const hL = B.headLen, W = P.headW * B.kH, H = P.headH * B.kH;
|
|
12
|
+
const hBody = 0.078 * hwMax * r;
|
|
13
|
+
const hHead = Math.min(hBody * 0.5, 0.012 * hL * r);
|
|
14
|
+
const zCut = -0.35 * hL, band = Math.max(0.0025, 1.5 * hBody);
|
|
15
|
+
const zTail = J['v' + SPINE][2];
|
|
16
|
+
const pad = 3 * hBody;
|
|
17
|
+
const y0 = J.v0[1];
|
|
18
|
+
const jobs = [
|
|
19
|
+
[{ name: 'body', part: 'body', bmin: [-hwMax - pad, -pad, zTail - pad - 0.01], bmax: [hwMax + pad, 2.2 * hwMax + pad, zCut + band + pad], h: hBody, F: 4, clip: (x, y, z) => z < zCut + band }],
|
|
20
|
+
];
|
|
21
|
+
const head = [
|
|
22
|
+
{ name: 'head', part: 'body', bmin: [-0.7 * W, -pad, zCut - band - 0.004], bmax: [0.7 * W, y0 + 1.2 * H, hL * 1.12], h: hHead, F: 5, clip: (x, y, z) => z > zCut - band },
|
|
23
|
+
{ name: 'jaw', part: 'jaw', bmin: [-0.6 * W, -0.003, -0.1 * hL], bmax: [0.6 * W, y0 + 0.2 * H, hL * 1.05], h: hHead, F: 5, rigidBone: 'jaw' },
|
|
24
|
+
];
|
|
25
|
+
if (r < 2.5) {
|
|
26
|
+
const tr = 0.0012 * B.kH;
|
|
27
|
+
const hT = Math.min(0.00022 * B.kH * r, hHead);
|
|
28
|
+
head.push(
|
|
29
|
+
{ name: 'tongue', part: 'tongue', bmin: [-tr * 2, J.tongueBase[1] - tr * 2, J.tongueBase[2] - tr * 2], bmax: [tr * 2, J.tongueBase[1] + tr * 2, J.tongueFork[2] + tr * 3], h: hT, F: 4, rigidBone: 'tongue' },
|
|
30
|
+
{ name: 'fork', part: 'fork', bmin: [-0.3 * hL, J.tongueFork[1] - tr * 2, J.tongueFork[2] - tr * 2], bmax: [0.3 * hL, J.tongueFork[1] + tr * 2, J.tongueTip[2] + tr * 2], h: hT, F: 4, rigidBone: 'tongueTip' },
|
|
31
|
+
);
|
|
32
|
+
if (params.variant === 'rattlesnake') {
|
|
33
|
+
head.push({ name: 'fang', part: 'fang', bmin: [-0.4 * W, J.fangTip[1] - 0.004, J.fangTip[2] - 0.004], bmax: [0.4 * W, J.fangBase[1] + 0.003, J.fangBase[2] + 0.003], h: hT, F: 4, rigidBone: 'fang' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
jobs.push(head);
|
|
37
|
+
if (P.rattle) {
|
|
38
|
+
const b = J['v' + SPINE];
|
|
39
|
+
const L = P.rattle.len * 1.3 * (params.shape?.girth ?? 1);
|
|
40
|
+
const w = B.hw(1) * 2;
|
|
41
|
+
jobs[0].push({ name: 'rattle', part: 'rattle', bmin: [-w, b[1] - w, b[2] - L - 0.01], bmax: [w, b[1] + w, b[2] + 0.01], h: Math.min(hBody * 0.5, 0.00035 * r), F: 4, rigidBone: 'spine' + (SPINE - 1) });
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
jobs,
|
|
45
|
+
fade(x, y, z, name) {
|
|
46
|
+
if (name === 'body') return 1 - smoothstep(zCut - band, zCut + band, z);
|
|
47
|
+
if (name === 'head') return smoothstep(zCut - band, zCut + band, z);
|
|
48
|
+
return 1;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Snake skeleton in bind pose (metres): lying straight along -Z, belly on y = 0, head at +Z.
|
|
2
|
+
// Two builds share the code: the corn snake (default) and the western diamondback rattlesnake
|
|
3
|
+
// (variant). Numbers: total length 1.2 m (reference adults), corn head 3.4 cm and
|
|
4
|
+
// max body width 2.7 cm, rattlesnake head 5.2 cm (3.8 cm wide) and max body width 5.2 cm;
|
|
5
|
+
// tail 17 % (corn) / 7.5 % (rattlesnake) of the total length.
|
|
6
|
+
import { buildRig } from '../../core/rig/rig.js';
|
|
7
|
+
import { snakeSpine, snakeBones, snakeAxial, snakeSegments } from '../../core/rig/snake.js';
|
|
8
|
+
|
|
9
|
+
export const SPINE = 50; // spine bones (each ~4-5 vertebrae)
|
|
10
|
+
|
|
11
|
+
// Per-variant proportions (reference individual). hw = half width of the body (m) at arc fraction t
|
|
12
|
+
// (0 = occiput, 1 = tail tip); flat = axis height / half width (loaf cross-section: flat belly).
|
|
13
|
+
export const PROFILES = {
|
|
14
|
+
corn: {
|
|
15
|
+
TL: 1.2, headLen: 0.034, headW: 0.0185, headH: 0.0112, tail: 0.17, flat: 0.74,
|
|
16
|
+
hw: [[0, 0.0071], [0.05, 0.0076], [0.22, 0.0112], [0.45, 0.0135], [0.66, 0.0128], [0.8, 0.0098], [0.88, 0.0068], [0.95, 0.0036], [1, 0.0011]],
|
|
17
|
+
eye: { r: 0.0026, at: [0.006, 0.0027, 0.0205], yaw: 1.05, pitch: 0.2 },
|
|
18
|
+
},
|
|
19
|
+
rattlesnake: {
|
|
20
|
+
TL: 1.2, headLen: 0.052, headW: 0.038, headH: 0.02, tail: 0.075, flat: 0.64,
|
|
21
|
+
hw: [[0, 0.0092], [0.04, 0.0105], [0.2, 0.019], [0.46, 0.0265], [0.7, 0.0238], [0.86, 0.016], [0.92, 0.0112], [0.97, 0.0078], [1, 0.0062]],
|
|
22
|
+
eye: { r: 0.0029, at: [0.0133, 0.0052, 0.032], yaw: 1.0, pitch: 0.22 },
|
|
23
|
+
rattle: { len: 0.05, segs: 7 },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// monotone cubic (Fritsch-Carlson) through [[t, v]...]
|
|
28
|
+
export function pchip(keys) {
|
|
29
|
+
const n = keys.length, x = keys.map((k) => k[0]), y = keys.map((k) => k[1]);
|
|
30
|
+
const d = [], m = new Array(n).fill(0);
|
|
31
|
+
for (let i = 0; i < n - 1; i++) d.push((y[i + 1] - y[i]) / (x[i + 1] - x[i]));
|
|
32
|
+
m[0] = d[0]; m[n - 1] = d[n - 2];
|
|
33
|
+
for (let i = 1; i < n - 1; i++) m[i] = d[i - 1] * d[i] <= 0 ? 0 : (d[i - 1] + d[i]) / 2;
|
|
34
|
+
for (let i = 0; i < n - 1; i++) {
|
|
35
|
+
if (d[i] === 0) { m[i] = m[i + 1] = 0; continue; }
|
|
36
|
+
const a = m[i] / d[i], b = m[i + 1] / d[i], s = a * a + b * b;
|
|
37
|
+
if (s > 9) { const t = 3 / Math.sqrt(s); m[i] = t * a * d[i]; m[i + 1] = t * b * d[i]; }
|
|
38
|
+
}
|
|
39
|
+
return (t) => {
|
|
40
|
+
if (t <= x[0]) return y[0];
|
|
41
|
+
if (t >= x[n - 1]) return y[n - 1];
|
|
42
|
+
let i = 0;
|
|
43
|
+
while (i < n - 2 && t > x[i + 1]) i++;
|
|
44
|
+
const h = x[i + 1] - x[i], u = (t - x[i]) / h;
|
|
45
|
+
const h00 = 2 * u * u * u - 3 * u * u + 1, h10 = u * u * u - 2 * u * u + u, h01 = -2 * u * u * u + 3 * u * u, h11 = u * u * u - u * u;
|
|
46
|
+
return h00 * y[i] + h10 * h * m[i] + h01 * y[i + 1] + h11 * h * m[i + 1];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Individual body plan from the variant profile and the seeded proportions in params:
|
|
51
|
+
// params.shape = { length, girth, head, tail } multipliers (1 = reference).
|
|
52
|
+
export function bodyPlan(params) {
|
|
53
|
+
const P = PROFILES[params.variant] || PROFILES.corn;
|
|
54
|
+
const sh = params.shape || {};
|
|
55
|
+
const kL = sh.length ?? 1, kG = sh.girth ?? 1, kH = sh.head ?? 1, kT = sh.tail ?? 1;
|
|
56
|
+
const headLen = P.headLen * kH;
|
|
57
|
+
const bodyLen = P.TL * kL - headLen;
|
|
58
|
+
const tailFrac = Math.min(0.3, (P.tail * kT * P.TL * kL) / bodyLen);
|
|
59
|
+
// re-map the profile's tail portion to this individual's tail fraction
|
|
60
|
+
const refTail = (P.tail * P.TL) / (P.TL - P.headLen);
|
|
61
|
+
const remap = (t) => (t <= 1 - tailFrac ? (t / (1 - tailFrac)) * (1 - refTail) : 1 - refTail + ((t - (1 - tailFrac)) / tailFrac) * refTail);
|
|
62
|
+
const base = pchip(P.hw);
|
|
63
|
+
const neckK = kH; // a bigger head comes with a slightly thicker neck
|
|
64
|
+
const hw = (t) => {
|
|
65
|
+
const r = base(remap(t));
|
|
66
|
+
const neck = 1 - Math.min(1, t / 0.1);
|
|
67
|
+
return r * (kG * (1 - neck) + (0.5 * kG + 0.5 * neckK) * neck);
|
|
68
|
+
};
|
|
69
|
+
return { P, headLen, bodyLen, tailFrac, hw, flat: P.flat, kH };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function snakeRig(params) {
|
|
73
|
+
const B = bodyPlan(params);
|
|
74
|
+
const P = B.P;
|
|
75
|
+
const J = {};
|
|
76
|
+
const lens = snakeSegments(SPINE, B.bodyLen, { tipShrink: P.rattle ? 0.8 : 0.5, tipBones: 10 });
|
|
77
|
+
snakeSpine(J, 0, lens, (s, t) => B.hw(t) * B.flat);
|
|
78
|
+
const y0 = J.v0[1];
|
|
79
|
+
const kH = B.kH;
|
|
80
|
+
const hL = B.headLen, hH = P.headH * kH;
|
|
81
|
+
// head landmarks (head-local: origin v0, +Z forward). The jaw's underside rests on y = 0.
|
|
82
|
+
// heights are from the belly plane (the jaw's underside rests on y = 0)
|
|
83
|
+
J.nose = [0, 0.56 * hH, hL];
|
|
84
|
+
J.jawHinge = [0, 0.34 * hH, 0.06 * hL];
|
|
85
|
+
J.jawTip = [0, 0.16 * hH, 0.9 * hL];
|
|
86
|
+
// tongue lies in its sheath in the floor of the mouth, tips just behind the chin
|
|
87
|
+
J.tongueBase = [0, 0.2 * hH, 0.18 * hL];
|
|
88
|
+
J.tongueFork = [0, 0.2 * hH, 0.62 * hL];
|
|
89
|
+
J.tongueTip = [0, 0.2 * hH, 0.84 * hL];
|
|
90
|
+
// fangs hinge at the front of the upper jaw, folded back along the palate
|
|
91
|
+
J.fangBase = [0, 0.38 * hH, 0.72 * hL];
|
|
92
|
+
J.fangTip = [0, 0.32 * hH, 0.55 * hL];
|
|
93
|
+
const rig = buildRig({
|
|
94
|
+
joints: J,
|
|
95
|
+
unit: (B.headLen + B.bodyLen) / 1.3,
|
|
96
|
+
headOrigin: J.v0,
|
|
97
|
+
bones: snakeBones(SPINE, { neck: 4, tail: Math.round(B.tailFrac * SPINE) }),
|
|
98
|
+
axial: {
|
|
99
|
+
...snakeAxial(SPINE),
|
|
100
|
+
// the skull is rigid right up to the occiput; the neck blends in just behind it
|
|
101
|
+
blend: { 1: [-0.004 * kH, 0.006 * kH] },
|
|
102
|
+
},
|
|
103
|
+
limbs: {},
|
|
104
|
+
appendages: [],
|
|
105
|
+
});
|
|
106
|
+
rig.plan = B;
|
|
107
|
+
return rig;
|
|
108
|
+
}
|