@bitefoundry/retrieviewer 0.0.0-stage → 0.0.1
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 +107 -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 +61 -2
- package/RELEASING.md +61 -0
- package/THIRD-PARTY-NOTICES.md +17 -0
- package/TRADEMARKS.md +21 -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/locomotion-core.mjs +21 -0
- package/locomotion-puppy.animal +0 -0
- package/package.json +125 -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 +81 -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,81 @@
|
|
|
1
|
+
// Cow motion data for the quadruped engine (schema: src/core/motion/quadruped.js).
|
|
2
|
+
// Numbers are for the reference individual (params.size = 1, a 1.45 m Holstein cow); the engine
|
|
3
|
+
// scales them by dynamic similarity for calves, Jerseys and bulls.
|
|
4
|
+
//
|
|
5
|
+
// Gaits: lateral-sequence 4-beat walk (preferred ~1.2 m/s, stride
|
|
6
|
+
// ~0.8 Hz, duty 0.66), a short 2-beat trot (to feed, 2-3.5 m/s), and a clumsy canter / transverse
|
|
7
|
+
// gallop (right lead) up to ~11 m/s that cattle rarely use. Walk -> trot at Froude ~0.4 (~1.8 m/s).
|
|
8
|
+
import { behaviour } from './behaviour.js';
|
|
9
|
+
|
|
10
|
+
const G = (name, v, f, D, off, o) => ({ name, v, f, D, off, ...o });
|
|
11
|
+
|
|
12
|
+
export const motion = {
|
|
13
|
+
maxSpeed: 11,
|
|
14
|
+
accel: [2.2, 4],
|
|
15
|
+
decel: 5,
|
|
16
|
+
turnRate: 1.3,
|
|
17
|
+
latAccelMax: 5,
|
|
18
|
+
gears: { walk: 1.2, trot: 2.6, canter: 4.6, gallop: 8 },
|
|
19
|
+
gallopBlend: [4.2, 8],
|
|
20
|
+
// name v f D [FL, FR, HL, HR]
|
|
21
|
+
gaits: [
|
|
22
|
+
G('walk', 0.0, 0.72, 0.7, [0.25, 0.75, 0.0, 0.5], { liftF: 0.07, liftH: 0.06, bobF: 0.025, bobH: 0.03, flex: 0, lat: 0.03, fold: 0.6, heel: 28, tail: 0, drop: 0 }),
|
|
23
|
+
G('walk', 1.5, 0.9, 0.64, [0.25, 0.75, 0.0, 0.5], { liftF: 0.09, liftH: 0.08, bobF: 0.045, bobH: 0.05, flex: 0, lat: 0.04, fold: 0.72, heel: 36, tail: 0.1, drop: 0 }),
|
|
24
|
+
G('trot', 2.0, 1.35, 0.5, [0.52, 0.02, 0.0, 0.5], { liftF: 0.12, liftH: 0.1, bobF: 0.06, bobH: 0.06, flex: 0.01, lat: 0.0, fold: 0.95, heel: 45, tail: 0.3, drop: 0.015 }),
|
|
25
|
+
G('trot', 3.3, 1.55, 0.43, [0.52, 0.02, 0.0, 0.5], { liftF: 0.15, liftH: 0.12, bobF: 0.07, bobH: 0.07, flex: 0.015, lat: 0.0, fold: 1.05, heel: 50, tail: 0.4, drop: 0.02 }),
|
|
26
|
+
G('canter', 4.0, 1.7, 0.42, [0.3, 0.55, 0.0, 0.27], { liftF: 0.17, liftH: 0.14, bobF: 0.04, bobH: 0.055, flex: 0.05, lat: 0, fold: 1.1, heel: 50, tail: 0.55, drop: 0.03 }),
|
|
27
|
+
G('canter', 5.6, 1.8, 0.37, [0.3, 0.55, 0.0, 0.25], { liftF: 0.19, liftH: 0.16, bobF: 0.045, bobH: 0.06, flex: 0.065, lat: 0, fold: 1.15, heel: 45, tail: 0.65, drop: 0.035 }),
|
|
28
|
+
G('gallop', 7.0, 1.95, 0.31, [0.38, 0.52, 0.0, 0.12], { liftF: 0.21, liftH: 0.18, bobF: 0.05, bobH: 0.065, flex: 0.09, lat: 0, fold: 1.25, heel: 40, tail: 0.8, drop: 0.05 }),
|
|
29
|
+
G('gallop', 11, 2.15, 0.25, [0.4, 0.52, 0.0, 0.13], { liftF: 0.23, liftH: 0.2, bobF: 0.05, bobH: 0.07, flex: 0.11, lat: 0, fold: 1.3, heel: 40, tail: 1.0, drop: 0.07 }),
|
|
30
|
+
],
|
|
31
|
+
feet: {
|
|
32
|
+
// cloven hooves on short pasterns: the fetlock sinks a little under load, the claws flip in swing
|
|
33
|
+
front: { type: 'unguligrade', contact: 0.9, curl: 65, flex: 4, sink: 14, hoofFlex: 20, swingBack: 0.16 },
|
|
34
|
+
hind: { type: 'unguligrade', contact: 0.9, curl: 65, flex: 4, sink: 12, hoofFlex: 20, swingBack: 0.04 },
|
|
35
|
+
},
|
|
36
|
+
stance: { width: 1, gallopWidth: 0.85, sprawl: 0, crouch: 0, scuff: [14, 24, 0] },
|
|
37
|
+
// torso cross-sections (bind pose, reference size): centre height, half height, half width
|
|
38
|
+
body: {
|
|
39
|
+
chest: { y: 1.03, hh: 0.42, hw: 0.42 },
|
|
40
|
+
mid: { y: 1.0, hh: 0.4, hw: 0.37 },
|
|
41
|
+
pelvis: { y: 1.14, hh: 0.5, hw: 0.4 },
|
|
42
|
+
back: 0.12,
|
|
43
|
+
},
|
|
44
|
+
head: {
|
|
45
|
+
// [speed, raise (m), reach (m), pitch (rad, + = nose down)]: low and forward at the walk
|
|
46
|
+
// (nodding, see the hook), a little higher at the trot, up and stretched in the gallop
|
|
47
|
+
carriage: [[0, 0, 0, 0], [1.2, -0.05, 0.06, 0.04], [2.6, -0.02, 0.05, 0.0], [4.6, 0.02, 0.08, -0.05], [9, -0.04, 0.14, 0.0]],
|
|
48
|
+
stab: [[0, 0], [1.2, 0.1], [2.6, 0.4], [4.6, 0.3], [9, 0.3]],
|
|
49
|
+
neckBend: 34, neckPivot: 0.55, yaw: 0.7, pitchUp: 0.55, pitchDown: 0.9, mouth: 'jawTip',
|
|
50
|
+
},
|
|
51
|
+
tail: {
|
|
52
|
+
// [speed, pitch per segment from horizontal (deg), tail head -> switch]: hangs straight down at
|
|
53
|
+
// rest, lifts and streams out behind when running
|
|
54
|
+
carriage: [
|
|
55
|
+
[0.0, [-38, -68, -80, -85, -87, -88, -89, -89, -90, -90, -90, -90]],
|
|
56
|
+
[1.2, [-34, -64, -78, -84, -86, -87, -88, -89, -89, -90, -90, -90]],
|
|
57
|
+
[2.6, [-32, -48, -62, -71, -76, -80, -83, -85, -86, -87, -88, -88]],
|
|
58
|
+
[4.6, [-30, -30, -42, -52, -58, -62, -66, -69, -72, -74, -76, -78]],
|
|
59
|
+
[9, [-24, -10, -16, -24, -30, -34, -37, -40, -42, -44, -46, -48]],
|
|
60
|
+
],
|
|
61
|
+
stiffness: 0.75, sway: 6, flick: 0, wag: 0, swish: 1, curl: 0, balance: 0,
|
|
62
|
+
radius: [0.05, 0.05], // (bony tail, then the switch)
|
|
63
|
+
},
|
|
64
|
+
ears: { rest: [0, 0], mobility: 1.1, speedFlatten: 0.2, twitch: 1.3, prick: 0.4 },
|
|
65
|
+
breath: { rate: 0.3, amp: 0.009, pant: false, pantRate: 1.4, pantAmp: 0.02, heatSpeed: 9 },
|
|
66
|
+
actions: {
|
|
67
|
+
// grazing / drinking: the long head hangs near vertical, the tongue wraps the grass (hook)
|
|
68
|
+
eat: { style: 'graze', dropF: 0.05, dropH: 0, pitch: 1.0, ahead: 0.62 },
|
|
69
|
+
drink: { dropF: 0.06, dropH: 0, pitch: 0.95, ahead: 0.62 },
|
|
70
|
+
// cattle jump reluctantly: a low, heavy hop, landing on the forelegs
|
|
71
|
+
jump: { height: 0.55, distance: 2.2, land: [0.22, 0.18] },
|
|
72
|
+
sit: { dropH: 0.8 },
|
|
73
|
+
// kneels on both front knees first, then lowers the hindquarters; rises hind end first
|
|
74
|
+
lie: { down: 'front', up: 'hind' },
|
|
75
|
+
sleep: { style: 'curl', down: 'front', up: 'hind' },
|
|
76
|
+
// lying sternal: forelegs folded under the chest (carpus flexed, cannons back along the ground)
|
|
77
|
+
fold: { front: { z: -0.24, k: 1.5, beta: 0.9 } },
|
|
78
|
+
idle: { shift: 0.8 },
|
|
79
|
+
},
|
|
80
|
+
hooks: behaviour,
|
|
81
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Meshing plan: body at 12.2 mm (0.84 % of the withers height), head at 5.2 mm overlapping the body
|
|
2
|
+
// across the upper neck and cross-faded, the four feet below the fetlocks at 5.5 mm (so the cleft
|
|
3
|
+
// between the two claws and the dew claws read) cross-faded with the body, eyelid patches at 1.5 mm
|
|
4
|
+
// (3 mm at medium: Safari and phones show medium), and rigid surfaces: the lower lip / chin (the mouth
|
|
5
|
+
// opens and chews), the tongue, the horns, the ear tags and the nose ring.
|
|
6
|
+
// Budgets (vertices, hero / high / medium / low / crowd: 130k / 80k / 32k / 15k / 8k): the cells of an
|
|
7
|
+
// individual with more surface than the reference cow (a bull's crest, dewlap and thick legs, a beef
|
|
8
|
+
// breed's width) grow with it, the small rigid parts (ear tags, nose ring) are dropped at low and crowd,
|
|
9
|
+
// and the horns coarsen with the tier.
|
|
10
|
+
import { EYE, hornPath } from './sculpt.js';
|
|
11
|
+
import { HEAD_O, cowJoints } from './rig.js';
|
|
12
|
+
import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
|
|
13
|
+
import { norm, smoothstep } from '../../core/math/vec.js';
|
|
14
|
+
|
|
15
|
+
const J = cowJoints();
|
|
16
|
+
// the head hangs from the neck: cut just behind the poll - throat line
|
|
17
|
+
export const NECK_CUT = { c: [0, 1.35, J.occiput[2] - 0.05], d: norm([0, 0.3, 1]), band: 0.024 };
|
|
18
|
+
export const neckS = (x, y, z) => (x - NECK_CUT.c[0]) * NECK_CUT.d[0] + (y - NECK_CUT.c[1]) * NECK_CUT.d[1] + (z - NECK_CUT.c[2]) * NECK_CUT.d[2];
|
|
19
|
+
// feet: below the fetlocks
|
|
20
|
+
export const FOOT_CUT = { y: 0.115, band: 0.012 };
|
|
21
|
+
|
|
22
|
+
export const EYE_PATCH = { R: 0.033, B: 0.006, h: 0.0015, centres: [1, -1].map((s) => eyeFrameOf(EYE, HEAD_O, s).c) };
|
|
23
|
+
export function eyePatchWeight(x, y, z) {
|
|
24
|
+
let dmin = 1e9;
|
|
25
|
+
for (const c of EYE_PATCH.centres) dmin = Math.min(dmin, Math.hypot(x - c[0], y - c[1], z - c[2]));
|
|
26
|
+
return 1 - smoothstep(EYE_PATCH.R - EYE_PATCH.B, EYE_PATCH.R + EYE_PATCH.B, dmin);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function boxOf(pts, pad) {
|
|
30
|
+
const mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9];
|
|
31
|
+
for (const p of pts) for (let i = 0; i < 3; i++) { mn[i] = Math.min(mn[i], p[i] - pad); mx[i] = Math.max(mx[i], p[i] + pad); }
|
|
32
|
+
return [mn, mx];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function cowRegions(rig, params, Q) {
|
|
36
|
+
const B = NECK_CUT.band, r = Q.res, FB = FOOT_CUT.band, FY = FOOT_CUT.y;
|
|
37
|
+
const crowd = r >= 4, far = r >= 2.5; // (low / crowd: no tongue, ear-tag or ring surfaces at crowd, none of the small ones at low)
|
|
38
|
+
// eyelid patches at hero / high and at medium (coarser there)
|
|
39
|
+
const patches = Q.eyePatches || r <= 2 ? EYE_PATCH.centres.map((c) => ({ c, R: EYE_PATCH.R, B: EYE_PATCH.B, h: EYE_PATCH.h * Math.max(1, r) })) : [];
|
|
40
|
+
// an individual's extra surface over the reference cow: bulls (crest, dewlap, sheath, thick legs and
|
|
41
|
+
// neck: +8-10 % vertices), beef breeds (a wider barrel: +3 %)
|
|
42
|
+
const kS = 1 + 0.045 * (params.bull || 0) + 0.02 * (1 - (params.dairy ?? 1));
|
|
43
|
+
const rigid = [];
|
|
44
|
+
if (params.horns) {
|
|
45
|
+
const pts = [];
|
|
46
|
+
for (const s of [1, -1]) { const hp = hornPath(params, s); pts.push(...hp.pts); }
|
|
47
|
+
const [bmin, bmax] = boxOf(pts, params.horns.r * 1.3 + 0.01);
|
|
48
|
+
rigid.push({ name: 'horn', part: 'horn', bmin, bmax, h: Math.max(0.0035, params.horns.r * 0.15) * (crowd ? 2.6 : Math.min(r, 2.2)), F: 6, rigidBone: 'head' });
|
|
49
|
+
}
|
|
50
|
+
if (params.earTags && !far) {
|
|
51
|
+
for (const S of ['L', 'R']) {
|
|
52
|
+
const a = rig.J['earBase' + S], b = rig.J['earTip' + S];
|
|
53
|
+
const [bmin, bmax] = boxOf([a, b], 0.08);
|
|
54
|
+
rigid.push({ name: 'tag' + S, part: 'tag' + S, bmin, bmax, h: r > 1 ? 0.0032 : 0.003, F: 6, rigidBone: 'ear' + S });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (params.noseRing && !far) {
|
|
58
|
+
const c = rig.J.nose;
|
|
59
|
+
rigid.push({ name: 'ring', part: 'ring', bmin: [c[0] - 0.05, c[1] - 0.12, c[2] - 0.08], bmax: [c[0] + 0.05, c[1] + 0.03, c[2] + 0.08], h: 0.002 * Math.min(r, 1.6), F: 6, rigidBone: 'head' });
|
|
60
|
+
}
|
|
61
|
+
const wide = params.shaggy ? 0.1 : 0;
|
|
62
|
+
// feet: the clip keeps cells by their centre, so the mesh can end up to 1.5 cells below it; the
|
|
63
|
+
// feet surface must reach past the middle of the band (FY, fade 0.5) at every tier, or its open
|
|
64
|
+
// top ring shows (crowd: 18 mm cells, 65-87 exposed edges at the fetlocks); the box ends two cells
|
|
65
|
+
// above the clip so it never cuts the surface first
|
|
66
|
+
const hF = crowd ? 0.02 : 0.0055 * Math.min(r, 3);
|
|
67
|
+
const feetTop = Math.max(FY + FB, FY + 1.5 * hF + 0.004);
|
|
68
|
+
// the neck band likewise: each surface is kept to past the middle of the band (fade 0.5 at the cut)
|
|
69
|
+
// by 1.5 of its own cells (low / crowd: the body's 37-51 mm cells ended in its visible half along
|
|
70
|
+
// the upper neck, 7 exposed edges)
|
|
71
|
+
const hB = 0.0122 * r * kS, hH = 0.0052 * r * kS;
|
|
72
|
+
const bodyTo = Math.max(B, 1.5 * hB + 0.004), headFrom = -Math.max(B, 1.5 * hH + 0.004);
|
|
73
|
+
const bodyFrom = Math.min(FY - FB, FY - 1.5 * hB - 0.004); // (and at the fetlocks)
|
|
74
|
+
return {
|
|
75
|
+
jobs: [
|
|
76
|
+
[{ name: 'body', part: 'body', bmin: [-0.5 - wide, bodyFrom - 2 * hB, -1.14], bmax: [0.5 + wide, 1.72, 1.4], h: hB, F: 4, clip: (x, y, z) => neckS(x, y, z) < bodyTo && y > bodyFrom }],
|
|
77
|
+
[
|
|
78
|
+
{ name: 'head', part: 'body', bmin: [-0.36 - wide, 0.84, 0.86], bmax: [0.36 + wide, 1.66, 1.62], h: hH, F: 6, clip: (x, y, z) => neckS(x, y, z) > headFrom, patches },
|
|
79
|
+
{ name: 'jaw', part: 'jaw', bmin: [-0.1, 0.84, 1.1], bmax: [0.1, 1.36, 1.62], h: 0.0046 * r, F: 6, rigidBone: 'jaw' },
|
|
80
|
+
...(crowd ? [] : [{ name: 'tongue', part: 'tongue', bmin: [-0.06, 0.84, 1.1], bmax: [0.06, 1.36, 1.62], h: 0.0055 * Math.min(r, 2), F: 6, rigidBone: 'tongue' }]),
|
|
81
|
+
],
|
|
82
|
+
[{ name: 'feet', part: 'body', bmin: [-0.3, -0.01, -0.75], bmax: [0.3, feetTop + 2 * hF, 0.86], h: hF, F: 6, clip: (x, y, z) => y < feetTop }, ...rigid],
|
|
83
|
+
],
|
|
84
|
+
fade(x, y, z, name, patch) {
|
|
85
|
+
if (name === 'body') {
|
|
86
|
+
const sN = neckS(x, y, z);
|
|
87
|
+
return (1 - smoothstep(-B, B, sN)) * smoothstep(FY - FB, FY + FB, y);
|
|
88
|
+
}
|
|
89
|
+
if (name === 'feet') return 1 - smoothstep(FY - FB, FY + FB, y);
|
|
90
|
+
if (name === 'head') {
|
|
91
|
+
let f = smoothstep(-B, B, neckS(x, y, z));
|
|
92
|
+
if (patches.length) { const ew = eyePatchWeight(x, y, z); f *= patch ? ew : 1 - ew; }
|
|
93
|
+
return f;
|
|
94
|
+
}
|
|
95
|
+
return 1;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Cow skeleton in bind pose (metres). Forward = +Z, up = +Y, the animal's left = +X.
|
|
2
|
+
// Reference individual: a Holstein-Friesian cow, 1.45 m at the withers, ~650 kg.
|
|
3
|
+
// Landmarks measured on the side references (side1_holstein.jpg: 1 px ~ 2.2 mm; side_natural.jpg for
|
|
4
|
+
// the relaxed head carriage) and bovine osteology: point of shoulder 1.00 m, elbow
|
|
5
|
+
// 0.72, carpus 0.40, fore fetlock 0.13, stifle 0.80, hock 0.53 (point of hock 0.60), body length
|
|
6
|
+
// (point of shoulder -> pin bone) 1.72 m, hooks (tuber coxae) 1.47 m and 0.58 m apart.
|
|
7
|
+
//
|
|
8
|
+
// Hooves: like the horse, a hoof bone per leg below the pastern (fpaw / hpaw = pastern: fetlock ->
|
|
9
|
+
// coffin joint; fhoof / hhoof = the claw pair: coffin joint -> toe), so the engine keeps the claws
|
|
10
|
+
// flat while the fetlock sinks under load. Cattle are cloven: the sculpt splits each hoof into two
|
|
11
|
+
// claws (digits III and IV) with two dew claws behind the fetlock; they share one hoof bone.
|
|
12
|
+
//
|
|
13
|
+
// Extra bones: a tongue (on the jaw, meshed as its own rigid surface, hidden in the mouth until it
|
|
14
|
+
// wraps grass or licks the nose).
|
|
15
|
+
import { buildRig } from '../../core/rig/rig.js';
|
|
16
|
+
import { quadrupedBones, quadrupedLimbs, tailChain } from '../../core/rig/quadruped.js';
|
|
17
|
+
|
|
18
|
+
export const TAIL_SEGS = 12; // 10 caudal segments (the bony tail to the hocks) + 2 of switch hair
|
|
19
|
+
export const BONE_SEGS = 10;
|
|
20
|
+
export const TAIL_PITCH = [-38, -68, -80, -85, -87, -88, -89, -89, -90, -90, -90, -90];
|
|
21
|
+
export const TAIL_LENS = [0.075, 0.072, 0.07, 0.068, 0.066, 0.064, 0.062, 0.06, 0.058, 0.056, 0.16, 0.16];
|
|
22
|
+
|
|
23
|
+
// Head frame: the head is modelled along its own axis (poll -> muzzle), pitched 50 degrees nose down
|
|
24
|
+
// in the bind pose (relaxed standing carriage, research 3: nasal line 50-70 deg below horizontal).
|
|
25
|
+
// Head-local coordinates: x lateral, y dorsal (forehead side), z along the nasal line toward the
|
|
26
|
+
// muzzle; origin on the axis 0.17 m in front of the poll (level with the eyes).
|
|
27
|
+
export const HEAD_PITCH = (50 * Math.PI) / 180;
|
|
28
|
+
export const HZ = [0, -Math.sin(HEAD_PITCH), Math.cos(HEAD_PITCH)];
|
|
29
|
+
export const HY = [0, Math.cos(HEAD_PITCH), Math.sin(HEAD_PITCH)];
|
|
30
|
+
const POLL = [0, 1.46, 1.13]; // top of the poll (skin), bind
|
|
31
|
+
export const HEAD_O = [0, POLL[1] + 0.17 * HZ[1], POLL[2] + 0.17 * HZ[2]];
|
|
32
|
+
export const hl = (v) => [HEAD_O[0] + v[0], HEAD_O[1] + v[1] * HY[1] + v[2] * HZ[1], HEAD_O[2] + v[1] * HY[2] + v[2] * HZ[2]];
|
|
33
|
+
|
|
34
|
+
export function cowJoints() {
|
|
35
|
+
const J = {
|
|
36
|
+
nose: hl([0, -0.01, 0.37]),
|
|
37
|
+
occiput: hl([0, -0.035, -0.14]),
|
|
38
|
+
neckMid: [0, 1.3, 0.9],
|
|
39
|
+
neckBase: [0, 1.2, 0.64],
|
|
40
|
+
chestMid: [0, 1.3, 0.34],
|
|
41
|
+
thoraxRear: [0, 1.33, 0.0],
|
|
42
|
+
lumbarMid: [0, 1.355, -0.27],
|
|
43
|
+
lumbosacral: [0, 1.37, -0.5],
|
|
44
|
+
tailBase: [0, 1.37, -0.88],
|
|
45
|
+
// fore: scapula slopes ~55 deg, humerus down and back, radius and cannon near vertical, short
|
|
46
|
+
// pastern ~50 deg to the ground
|
|
47
|
+
scapTopL: [0.12, 1.36, 0.42],
|
|
48
|
+
shoulderL: [0.19, 1.0, 0.74],
|
|
49
|
+
elbowL: [0.18, 0.72, 0.58],
|
|
50
|
+
wristL: [0.155, 0.4, 0.63],
|
|
51
|
+
mcpL: [0.15, 0.13, 0.65],
|
|
52
|
+
fcoffinL: [0.15, 0.052, 0.705],
|
|
53
|
+
ftoeL: [0.15, 0.004, 0.785],
|
|
54
|
+
// hind: hip joint, stifle, hock, fetlock, coffin joint, toe (the stifle well forward under the
|
|
55
|
+
// flank, the straight-ish dairy hock)
|
|
56
|
+
hipL: [0.2, 1.2, -0.6],
|
|
57
|
+
kneeL: [0.235, 0.8, -0.4],
|
|
58
|
+
hockL: [0.17, 0.53, -0.71],
|
|
59
|
+
mtpL: [0.17, 0.13, -0.665],
|
|
60
|
+
hcoffinL: [0.17, 0.052, -0.61],
|
|
61
|
+
htoeL: [0.17, 0.004, -0.53],
|
|
62
|
+
jawHinge: hl([0, -0.065, -0.075]),
|
|
63
|
+
jawTip: hl([0, -0.118, 0.335]),
|
|
64
|
+
tongueBase: hl([0, -0.085, 0.12]),
|
|
65
|
+
tongueTip: hl([0, -0.09, 0.3]),
|
|
66
|
+
// ears stick out sideways below the poll, nearly horizontal, tipped a little down and back
|
|
67
|
+
earBaseL: hl([0.085, 0.01, -0.125]),
|
|
68
|
+
};
|
|
69
|
+
J.earTipL = [J.earBaseL[0] + 0.2, J.earBaseL[1] - 0.025, J.earBaseL[2] - 0.045];
|
|
70
|
+
// the tail head is set high between the pin bones; the tail hangs straight down to the hocks,
|
|
71
|
+
// the switch below it
|
|
72
|
+
tailChain(J, 'tailBase', TAIL_PITCH, TAIL_LENS);
|
|
73
|
+
return J;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function cowBones() {
|
|
77
|
+
const b = quadrupedBones({ tailSegs: TAIL_SEGS, ears: true, jaw: true });
|
|
78
|
+
for (const d of b) {
|
|
79
|
+
if (d[0] === 'fpaw{S}') { d[2] = 'fcoffin{S}'; d[4] = { region: 'limb', group: 'F{S}' }; }
|
|
80
|
+
if (d[0] === 'hpaw{S}') { d[2] = 'hcoffin{S}'; d[4] = { region: 'limb', group: 'H{S}' }; }
|
|
81
|
+
}
|
|
82
|
+
b.push(
|
|
83
|
+
['fhoof{S}', 'fcoffin{S}', 'ftoe{S}', 'fpaw{S}', { region: 'foot', group: 'F{S}' }],
|
|
84
|
+
['hhoof{S}', 'hcoffin{S}', 'htoe{S}', 'hpaw{S}', { region: 'foot', group: 'H{S}' }],
|
|
85
|
+
['tongue', 'tongueBase', 'tongueTip', 'jaw', { region: 'jaw', group: 'jaw' }],
|
|
86
|
+
);
|
|
87
|
+
return b;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function cowRig(J = cowJoints()) {
|
|
91
|
+
const limbs = quadrupedLimbs({
|
|
92
|
+
front: { tCore: 0.55, aCore: 0.055, aBody: -0.1, midX: 0.0 },
|
|
93
|
+
hind: { tCore: 0.5, aCore: 0.022, aBody: -0.08, midX: 0.02 },
|
|
94
|
+
});
|
|
95
|
+
for (const L of Object.values(limbs)) { L.bones.push((L.front ? 'fhoof' : 'hhoof') + L.S); L.distal.push((L.front ? 'fhoof' : 'hhoof') + L.S); }
|
|
96
|
+
return buildRig({
|
|
97
|
+
joints: J,
|
|
98
|
+
unit: 1.45 / 0.76,
|
|
99
|
+
headOrigin: HEAD_O,
|
|
100
|
+
bones: cowBones(),
|
|
101
|
+
axial: {
|
|
102
|
+
points: ['nose', 'occiput', 'neckMid', 'neckBase', 'chestMid', 'thoraxRear', 'lumbarMid', 'lumbosacral', 'tailBase', ...Array.from({ length: TAIL_SEGS }, (_, i) => 'tail' + (i + 1))],
|
|
103
|
+
bones: ['head', 'neck2', 'neck1', 'chest', 'spine3', 'spine2', 'spine1', 'pelvis', ...Array.from({ length: TAIL_SEGS }, (_, i) => 'tail' + i)],
|
|
104
|
+
// the skull's influence reaches back into the throat (the deep jowl and throat skin), the
|
|
105
|
+
// neck base blends broadly into the chest and the dewlap
|
|
106
|
+
blend: { 1: [-0.06, 0.12, 5], 3: [-0.12, 0.12, 2] },
|
|
107
|
+
bodyTail: 0, // the tail hangs between the pin bones: body skin blends into it only at the tail head
|
|
108
|
+
},
|
|
109
|
+
limbs,
|
|
110
|
+
appendages: [
|
|
111
|
+
{ group: 'earL', bone: 'earL', blend: [-0.003, 0.006] },
|
|
112
|
+
{ group: 'earR', bone: 'earR', blend: [-0.003, 0.006] },
|
|
113
|
+
],
|
|
114
|
+
webTags: ['flankfold', 'thighfront', 'scapmuscle', 'pectoral', 'forearmweb'],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// The cow, sculpted as a signed distance field.
|
|
2
|
+
// Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw). The head
|
|
3
|
+
// is modelled in its own frame (rig.js: hl()), measured from the profile and frontal references; the
|
|
4
|
+
// body relative to the joints so the sculpt follows the skeleton.
|
|
5
|
+
//
|
|
6
|
+
// Individual knobs (params, see index.js): dairy (0 beef .. 1 dairy: angular frame, prominent hooks
|
|
7
|
+
// and pins, visible ribs, big udder), bull (0 cow .. 1 bull: crest over the neck and shoulders, deep
|
|
8
|
+
// forehead, sheath and scrotum instead of an udder), udder (size), dewlap, horns ({ style, len, r }),
|
|
9
|
+
// polled peak, ear tags, nose ring, shaggy (Highland coat volume).
|
|
10
|
+
import { HEAD_O, HZ, HY, hl, TAIL_SEGS, BONE_SEGS } from './rig.js';
|
|
11
|
+
import { add, sub, mul, lerp, norm, cross, rng } from '../../core/math/vec.js';
|
|
12
|
+
import { sculptEyeSocket, apertureTiltAlong } from '../../core/sdf/eyeSocket.js';
|
|
13
|
+
|
|
14
|
+
// Eye (head-local centre -> offset from HEAD_O). Globe ~34 mm axial, visible opening ~35 x 22 mm,
|
|
15
|
+
// set at the edge of the broad flat forehead and bulging out of the head's outline in front view.
|
|
16
|
+
const EYE_LOCAL = [0.111, 0.004, 0.0];
|
|
17
|
+
const eyeDir = (() => {
|
|
18
|
+
// lateral, 18 degrees forward along the nasal line, a little up
|
|
19
|
+
const a = (18 * Math.PI) / 180;
|
|
20
|
+
return norm(add(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))), mul(HY, 0.12)));
|
|
21
|
+
})();
|
|
22
|
+
const EYE_BASE = {
|
|
23
|
+
c: sub(hl(EYE_LOCAL), HEAD_O), r: 0.02, back: 0.004, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
|
|
24
|
+
lid: 0.0024, R: 0.0215, d: 0.0094, off: -0.0008, tilt: 0, irisZ: 0.011, irisR: 0.0146,
|
|
25
|
+
};
|
|
26
|
+
// the almond's roll: in the relaxed carriage (nasal line 50 deg below horizontal) the slit is about
|
|
27
|
+
// level, the inner corner a little lower (side1_holstein.jpg, head raised to ~30 deg: the slit rises
|
|
28
|
+
// ~20 deg toward the nose); i.e. 0.85 rad up from the nasal line, the upper lid up
|
|
29
|
+
EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) - 0.85;
|
|
30
|
+
export const EYE = EYE_BASE;
|
|
31
|
+
|
|
32
|
+
const X = [1, 0, 0];
|
|
33
|
+
const UP = [0, 1, 0];
|
|
34
|
+
const FWD = [0, 0, 1];
|
|
35
|
+
|
|
36
|
+
// horn base on the poll (head-local) and the horn's centre line (world, bind), per side
|
|
37
|
+
export function hornPath(params, s) {
|
|
38
|
+
const H = params.horns;
|
|
39
|
+
if (!H) return null;
|
|
40
|
+
const base = hl([0.082 * s, 0.035, -0.158]);
|
|
41
|
+
const out = [s, 0, 0];
|
|
42
|
+
// the second direction the horn curls toward: lyre (cow: up and forward), 'down' (Hereford:
|
|
43
|
+
// forward and down), 'bull' (short, forward), 'wide' (Highland: out, then up)
|
|
44
|
+
const tw = {
|
|
45
|
+
lyre: norm([0, 0.8, 0.55]), down: norm([0, -0.55, 0.85]), bull: norm([0, 0.35, 0.95]), wide: norm([0, 0.95, 0.3]),
|
|
46
|
+
}[H.style] || norm([0, 0.8, 0.55]);
|
|
47
|
+
const a0 = (H.a0 ?? 12) * Math.PI / 180, a1 = (H.a1 ?? 95) * Math.PI / 180;
|
|
48
|
+
const n = 7, pts = [], rads = [];
|
|
49
|
+
let p = add(base, mul(out, -0.03)); // rooted inside the skull
|
|
50
|
+
pts.push(p); rads.push(H.r * 1.05);
|
|
51
|
+
for (let i = 1; i <= n; i++) {
|
|
52
|
+
const t = (i - 0.5) / n;
|
|
53
|
+
const a = a0 + (a1 - a0) * Math.pow(t, H.curlPow ?? 1.3);
|
|
54
|
+
const d = norm(add(mul(out, Math.cos(a)), mul(tw, Math.sin(a))));
|
|
55
|
+
const seg = (H.len + 0.03) / n;
|
|
56
|
+
p = add(p, mul(d, seg));
|
|
57
|
+
pts.push(p);
|
|
58
|
+
const u = i / n;
|
|
59
|
+
rads.push(Math.max(0.004, H.r * (1 - 0.86 * Math.pow(u, 1.25))));
|
|
60
|
+
}
|
|
61
|
+
return { pts, rads, base };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function sculptCow(m, rig, params = {}) {
|
|
65
|
+
const J = rig.J;
|
|
66
|
+
const calf = params.age === 'juvenile';
|
|
67
|
+
const dairy = params.dairy ?? 1;
|
|
68
|
+
const bull = params.bull ?? 0;
|
|
69
|
+
const beef = 1 - dairy;
|
|
70
|
+
const R = rng(5113 + (params.coatSeed || 0));
|
|
71
|
+
|
|
72
|
+
const ellY = (o) => {
|
|
73
|
+
const y = norm(o.ydir);
|
|
74
|
+
const lat = o.lateral || X;
|
|
75
|
+
const z = norm(cross(lat, y));
|
|
76
|
+
return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
|
|
77
|
+
};
|
|
78
|
+
// ellipsoid in head-local coordinates: r = [lateral, dorsal, along the head]
|
|
79
|
+
const H = { bone: 'head' };
|
|
80
|
+
const hdir = (v) => [v[0], v[1] * HY[1] + v[2] * HZ[1], v[1] * HY[2] + v[2] * HZ[2]];
|
|
81
|
+
const hell = (o) => m.ell({ ...H, ...o, c: hl(o.c), axis: o.axisL ? norm(hdir(o.axisL)) : HZ, up: o.upL ? norm(hdir(o.upL)) : HY });
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------- TORSO
|
|
84
|
+
// the barrel: deep (chest depth ~0.8 m, belly ~0.63 m above the ground), wide (0.62 m), deepest
|
|
85
|
+
// behind the ribs; the dairy wedge: the body gets deeper and wider toward the rear
|
|
86
|
+
const wB = 1 + 0.06 * beef + 0.04 * bull;
|
|
87
|
+
m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 1.04, 0.12], r: [0.3 * wB, 0.4, 0.56], axis: norm([0, 0.04, 1]), k: 0 });
|
|
88
|
+
m.ell({ tag: 'girth', bone: 'chest', c: [0, 1.02, 0.44], r: [0.25 * wB, 0.33, 0.24], k: 0.1 });
|
|
89
|
+
m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 1.0, -0.22], r: [0.33 * wB, 0.37, 0.36], axis: norm([0, 0.1, -1]), k: 0.12 });
|
|
90
|
+
m.ell({ tag: 'belly', bone: 'spine2', c: [0, 0.84, -0.08], r: [0.27 * wB, 0.2, 0.42], k: 0.16 });
|
|
91
|
+
m.ell({ tag: 'flank', bone: 'spine1', c: [0, 1.17, -0.45], r: [0.28 * wB, 0.24, 0.24], k: 0.12 });
|
|
92
|
+
// withers: the dorsal spines of T2-T6 between the scapulae, sharper in dairy cows
|
|
93
|
+
m.ell({ tag: 'withers', bone: 'chest', c: [0, 1.36, 0.4], r: [0.085 + 0.03 * beef, 0.1, 0.24], axis: norm([0, -0.12, 1]), k: 0.1 });
|
|
94
|
+
m.ell({ tag: 'back', bone: 'spine3', c: [0, 1.35, 0.04], r: [0.17 + 0.05 * beef, 0.08, 0.42], k: 0.12 });
|
|
95
|
+
m.ell({ tag: 'loin', bone: 'spine1', c: [0, 1.38, -0.28], r: [0.21 + 0.04 * beef, 0.07, 0.24], k: 0.12 });
|
|
96
|
+
// hindquarters: the rump runs level from the hooks (tuber coxae) to the pins (tuber ischii); the
|
|
97
|
+
// tail head sits between the pins
|
|
98
|
+
m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 1.25, -0.68], r: [0.25 * wB, 0.2, 0.28], k: 0.1 });
|
|
99
|
+
m.ell({ tag: 'rump', bone: 'pelvis', c: [0, 1.38, -0.67], r: [0.2 + 0.05 * beef, 0.08, 0.28], axis: norm([0, -0.06, 1]), k: 0.1 });
|
|
100
|
+
for (const s of [1, -1]) {
|
|
101
|
+
m.sphere({ tag: 'hook', bone: 'pelvis', c: [0.27 * s, 1.37 + 0.012 * dairy, -0.46], rad: 0.055, k: 0.07 + 0.04 * beef });
|
|
102
|
+
m.sphere({ tag: 'pin', bone: 'pelvis', c: [0.095 * s, 1.34, -0.915], rad: 0.05, k: 0.07 + 0.03 * beef });
|
|
103
|
+
// quarters: the muscle between hook, pin and stifle (full and round in beef breeds)
|
|
104
|
+
m.ell({ tag: 'quarter', bone: 'pelvis', c: [0.14 * s, 1.2, -0.78], r: [0.13 + 0.03 * beef, 0.2, 0.14 + 0.02 * beef], k: 0.1 });
|
|
105
|
+
}
|
|
106
|
+
m.ell({ tag: 'tailhead', bone: 'pelvis', c: [0, 1.39, -0.86], r: [0.06, 0.055, 0.08], k: 0.06 });
|
|
107
|
+
// chest front: the brisket (sternum and its fat pad) between and in front of the forelegs
|
|
108
|
+
m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.83, 0.6], r: [0.17 * wB, 0.17, 0.17], k: 0.1 });
|
|
109
|
+
for (const s of [1, -1]) {
|
|
110
|
+
m.ell({ tag: 'pectoral', bone: 'chest', c: [0.1 * s, 0.86, 0.7], r: [0.1, 0.15, 0.1], k: 0.08 });
|
|
111
|
+
}
|
|
112
|
+
if (!calf && bull < 0.5 && (params.udder ?? 1) > 0.05) sculptUdder(m, params.udder ?? 1);
|
|
113
|
+
if (bull >= 0.5) {
|
|
114
|
+
// sheath (prepuce) under the belly, scrotum between the hind legs
|
|
115
|
+
m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.66, -0.05], r: [0.045, 0.06, 0.14], axis: norm([0, 0.35, 1]), k: 0.06 });
|
|
116
|
+
m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.6, 0.08], r: [0.03, 0.04, 0.05], k: 0.04 });
|
|
117
|
+
if (!calf) m.ell({ tag: 'scrotum', bone: 'pelvis', c: [0, 0.66, -0.66], r: [0.075, 0.13, 0.08], axis: norm([0, 0.1, 1]), k: 0.05 });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------- NECK
|
|
121
|
+
// thin and long in dairy cows, short and thick in beef breeds, massive with a crest in bulls
|
|
122
|
+
const nb = J.neckBase, nm = J.neckMid, occ = J.occiput;
|
|
123
|
+
const nd1 = norm(sub(nm, nb)), nd2 = norm(sub(occ, nm));
|
|
124
|
+
const nk = 1 + 0.1 * beef + 0.45 * bull;
|
|
125
|
+
m.ell({ tag: 'neck', bone: 'neck1', c: [0, 1.16 + 0.03 * bull, 0.64], r: [0.2 * nk, 0.27 * (1 + 0.15 * bull), 0.24], axis: nd1, up: [0, 1, -0.3], k: 0.12 });
|
|
126
|
+
m.ell({ tag: 'neck', bone: 'neck1', c: [0, 1.25, 0.84], r: [0.14 * nk, 0.21 * (1 + 0.1 * bull), 0.2], axis: nd1, up: [0, 1, -0.3], k: 0.1 });
|
|
127
|
+
m.ell({ tag: 'neck', bone: 'neck2', c: [0, 1.31, 1.0], r: [0.11 * (1 + 0.15 * bull), 0.16, 0.14], axis: nd2, up: [0, 1, -0.2], k: 0.09 });
|
|
128
|
+
// top of the neck from the withers to the poll; the bull's crest rises over the shoulders
|
|
129
|
+
m.ell({ tag: 'crest', bone: 'neck1', c: [0, 1.37 + 0.07 * bull, 0.72], r: [0.08 + 0.12 * bull, 0.07 + 0.11 * bull, 0.3 + 0.04 * bull], axis: norm([0, 0.02, 1]), k: 0.1 + 0.04 * bull });
|
|
130
|
+
m.ell({ tag: 'crest', bone: 'neck2', c: [0, 1.41 + 0.02 * bull, 0.98], r: [0.06 + 0.04 * bull, 0.05 + 0.03 * bull, 0.14], axis: FWD, k: 0.08 });
|
|
131
|
+
if (bull > 0) m.ell({ tag: 'crest', bone: 'chest', c: [0, 1.37 + 0.03 * bull, 0.5], r: [0.2 * bull + 0.02, 0.13 * bull + 0.01, 0.28], k: 0.14 });
|
|
132
|
+
// underline: trachea, then the dewlap, a hanging fold of skin from the throat to the brisket
|
|
133
|
+
m.cone({ tag: 'throat', bone: 'neck1', a: [0, 1.02, 0.76], b: [0, 1.16, 0.98], ra: 0.09, rb: 0.075, k: 0.1 });
|
|
134
|
+
m.cone({ tag: 'throat', bone: 'neck2', a: [0, 1.15, 0.98], b: hl([0, -0.16, -0.06]), ra: 0.07, rb: 0.06, k: 0.08 });
|
|
135
|
+
const dw = params.dewlap ?? 0.5;
|
|
136
|
+
if (dw > 0.02) {
|
|
137
|
+
const a = [0, 0.99, 0.95], b = [0, 0.76, 0.66], mid = lerp(a, b, 0.55);
|
|
138
|
+
const dd = norm(sub(b, a));
|
|
139
|
+
m.ell({ tag: 'dewlap', bone: 'neck1', c: add(mid, [0, -0.04 * dw, 0.02 * dw]), r: [0.035 + 0.015 * dw, 0.07 + 0.08 * dw, 0.2], axis: dd, up: norm([0, 1, 0.9]), k: 0.07 });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------- HEAD (head-local, see rig.js)
|
|
143
|
+
// landmarks (head_profile of face.jpg / side photos): poll ridge top y 0.06 at z -0.16, flat
|
|
144
|
+
// forehead y 0.075 at z 0, nasal bridge 0.055 at 0.18, muzzle top 0.03 at 0.33, nose tip z 0.39,
|
|
145
|
+
// chin y -0.14 at 0.3, jaw angle y -0.2 at z -0.03, eyes (+-0.098, 0.004, 0)
|
|
146
|
+
const hb = 1 + 0.08 * bull; // broader, deeper bull head
|
|
147
|
+
hell({ tag: 'poll', c: [0, 0.015, -0.14], r: [0.095 * hb, 0.055, 0.055], k: 0.05 });
|
|
148
|
+
hell({ tag: 'cranium', c: [0, 0.0, -0.06], r: [0.1 * hb, 0.075, 0.11], k: 0.05 });
|
|
149
|
+
hell({ tag: 'forehead', c: [0, 0.035, 0.0], r: [0.104 * hb, 0.04, 0.12], k: 0.04 });
|
|
150
|
+
hell({ tag: 'face', c: [0, 0.01, 0.2], r: [0.08 * hb, 0.045, 0.19], k: 0.05 });
|
|
151
|
+
hell({ tag: 'lowerface', c: [0, -0.07, 0.18], r: [0.078 * hb, 0.08, 0.17], k: 0.05 });
|
|
152
|
+
hell({ tag: 'muzzle', c: [0, -0.04, 0.33], r: [0.086 * hb, 0.066, 0.064], k: 0.05 });
|
|
153
|
+
hell({ tag: 'upperlip', c: [0, -0.1, 0.345], r: [0.068 * hb, 0.032, 0.046], k: 0.03 });
|
|
154
|
+
if (params.polledPeak) hell({ tag: 'polltop', c: [0, 0.05, -0.15], r: [0.04, 0.035 + 0.01 * params.polledPeak, 0.04], k: 0.04 });
|
|
155
|
+
if (bull > 0) hell({ tag: 'forehead', c: [0, 0.045, -0.06], r: [0.09, 0.03 * bull + 0.01, 0.1], k: 0.05 }); // curly bull forehead mat
|
|
156
|
+
if (params.dished) hell({ tag: 'face', c: [0, 0.035, 0.1], r: [0.05, 0.02, 0.05], k: 0.03, carve: true });
|
|
157
|
+
for (const s of [1, -1]) {
|
|
158
|
+
// masseter: the big flat cheek; the mandible's lower border from the jaw angle to the chin
|
|
159
|
+
hell({ tag: 'cheek', c: [0.06 * s * hb, -0.105, -0.0], r: [0.048, 0.1, 0.11], k: 0.05 });
|
|
160
|
+
m.cone({ ...H, tag: 'mandible', a: hl([0.052 * s, -0.19, -0.04]), b: hl([0.036 * s, -0.135, 0.27]), ra: 0.036, rb: 0.024, k: 0.05 });
|
|
161
|
+
// bony eye arch (the orbit rim stands out of the forehead's outline) and the temporal fossa
|
|
162
|
+
hell({ tag: 'brow', c: [0.1 * s, 0.03, -0.006], r: [0.03, 0.022, 0.04], k: 0.03 });
|
|
163
|
+
hell({ tag: 'nostrilwing', c: [0.055 * s, -0.03, 0.35], r: [0.03, 0.035, 0.038], k: 0.03 });
|
|
164
|
+
// eye: orbit hollow, lids, almond aperture
|
|
165
|
+
sculptEyeSocket(m, EYE, HEAD_O, s, { orbit: { r: [0.022, 0.016, 0.012], at: [0.0, 0.002, 0.022], k: 0.01 } });
|
|
166
|
+
// nostrils: comma-shaped openings on the front corners of the moist muzzle plate
|
|
167
|
+
hell({ tag: 'nostril', c: [0.043 * s, -0.035, 0.392], r: [0.011, 0.022, 0.02], axisL: [-0.35 * s, 0.1, 1], upL: [0.35 * s, 1, 0], k: 0.006, carve: true });
|
|
168
|
+
// ear base muscles on the side of the poll
|
|
169
|
+
m.sphere({ ...H, tag: 'earbase', c: add(J['earBase' + (s > 0 ? 'L' : 'R')], [-0.012 * s, 0, 0]), rad: 0.032, k: 0.03 });
|
|
170
|
+
}
|
|
171
|
+
// philtrum groove down the middle of the muzzle plate
|
|
172
|
+
hell({ tag: 'philtrum', c: [0, -0.07, 0.405], r: [0.004, 0.03, 0.012], k: 0.006, carve: true });
|
|
173
|
+
// mouth line: a shallow slit under the upper lip (the jaw part fills it)
|
|
174
|
+
hell({ tag: 'mouthcut', c: [0, -0.13, 0.31], r: [0.06, 0.012, 0.07], k: 0.008, carve: true });
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------- JAW (lower lip and chin: the mouth opens, chews sideways)
|
|
177
|
+
const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
|
|
178
|
+
m.ell({ ...JW, tag: 'chin', c: hl([0, -0.138, 0.3]), r: [0.05, 0.038, 0.06], axis: HZ, up: HY, k: 0 });
|
|
179
|
+
m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.125, 0.345]), r: [0.056, 0.026, 0.035], axis: HZ, up: HY, k: 0.02 });
|
|
180
|
+
for (const s of [1, -1]) {
|
|
181
|
+
m.cone({ ...JW, tag: 'mandible', a: hl([0.036 * s, -0.14, 0.2]), b: hl([0.03 * s, -0.135, 0.3]), ra: 0.022, rb: 0.028, k: 0.03 });
|
|
182
|
+
}
|
|
183
|
+
// tongue: its own rigid surface in the mouth (see regions.js); it wraps grass and licks the nose
|
|
184
|
+
const TG = { bone: 'tongue', group: 'jaw', part: 'tongue' };
|
|
185
|
+
m.cone({ ...TG, tag: 'tongue', a: J.tongueBase, b: J.tongueTip, ra: 0.03, rb: 0.024, k: 0.02 });
|
|
186
|
+
m.ell({ ...TG, tag: 'tongue', c: lerp(J.tongueBase, J.tongueTip, 0.7), r: [0.036, 0.016, 0.07], axis: norm(sub(J.tongueTip, J.tongueBase)), up: HY, k: 0.02 });
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------- EARS (broad leaves, carried sideways)
|
|
189
|
+
for (const S of ['L', 'R']) {
|
|
190
|
+
const s = S === 'L' ? 1 : -1;
|
|
191
|
+
const base = J['earBase' + S], tip = J['earTip' + S];
|
|
192
|
+
const along = norm(sub(tip, base));
|
|
193
|
+
// the pinna opens forward and a little down
|
|
194
|
+
const facing = norm([0, -0.25, 1]);
|
|
195
|
+
const lat = norm(cross(along, facing));
|
|
196
|
+
const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
|
|
197
|
+
const eK = calf ? 1.1 : 1;
|
|
198
|
+
ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.5), ydir: along, lateral: lat, r: [0.056 * eK, 0.108 * eK, 0.018], k: 0.02 });
|
|
199
|
+
ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.15), ydir: along, lateral: lat, r: [0.03, 0.05, 0.026], k: 0.02 });
|
|
200
|
+
ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.55), mul(facing, 0.016)), ydir: along, lateral: lat, r: [0.042 * eK, 0.09 * eK, 0.013], k: 0.006, carve: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------- LEGS
|
|
204
|
+
const legK = 1 + 0.08 * beef + 0.1 * bull; // bone and muscle thickness
|
|
205
|
+
for (const S of ['L', 'R']) {
|
|
206
|
+
const s = S === 'L' ? 1 : -1;
|
|
207
|
+
const lat = [s, 0, 0];
|
|
208
|
+
const sx = (v) => [v[0] * s, v[1], v[2]];
|
|
209
|
+
// front
|
|
210
|
+
const g = 'F' + S;
|
|
211
|
+
const Sc = J['scapTop' + S], Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S], M = J['mcp' + S], C = J['fcoffin' + S], T = J['ftoe' + S];
|
|
212
|
+
ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.06, 0, -0.02])), ydir: sub(Sh, Sc), lateral: lat, r: [0.08 + 0.03 * bull, 0.25, 0.15], k: 0.12, bias: 0.006 });
|
|
213
|
+
m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.015, 0.0, 0.03])), rad: 0.075 * legK, k: 0.09 });
|
|
214
|
+
m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.1 * legK, rb: 0.085 * legK, k: 0.09 });
|
|
215
|
+
ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.01, 0.02, -0.09])), ydir: sub(E, Sh), lateral: lat, r: [0.1 * legK, 0.17, 0.12], k: 0.09 });
|
|
216
|
+
m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.02, -0.07]), rad: 0.055, k: 0.05 });
|
|
217
|
+
m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.01]), b: W, ra: 0.08 * legK, rb: 0.048 * legK, k: 0.05 });
|
|
218
|
+
ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.25), sx([0.008, 0, 0.012])), ydir: sub(W, E), lateral: lat, r: [0.074 * legK, 0.14, 0.08 * legK], k: 0.05 });
|
|
219
|
+
m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.04, 0.06, -0.02])), b: add(lerp(E, W, 0.3), sx([-0.02, 0, 0])), ra: 0.06, rb: 0.045, k: 0.06 });
|
|
220
|
+
// knee (carpus): broad, the accessory carpal bone behind
|
|
221
|
+
ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.004]), ydir: UP, lateral: lat, r: [0.053 * legK, 0.058, 0.048 * legK], k: 0.025 });
|
|
222
|
+
m.sphere({ tag: 'accessory', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.03, -0.042]), rad: 0.024, k: 0.02 });
|
|
223
|
+
// cannon (fused metacarpals III+IV): short, broad, flat in front
|
|
224
|
+
m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.02, 0.0]), b: add(M, [0, 0.02, 0.0]), ra: 0.036 * legK, rb: 0.034 * legK, k: 0.02 });
|
|
225
|
+
m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.03, -0.028]), b: add(M, [0, 0.03, -0.03]), ra: 0.022 * legK, rb: 0.026 * legK, k: 0.02 });
|
|
226
|
+
ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.01]), ydir: [0, 1, 0.3], lateral: lat, r: [0.048 * legK, 0.048, 0.05], k: 0.02 });
|
|
227
|
+
sculptDigits(m, 'fpaw' + S, 'fhoof' + S, g, M, C, T, s, 1.0 * legK);
|
|
228
|
+
|
|
229
|
+
// hind
|
|
230
|
+
const h = 'H' + S;
|
|
231
|
+
const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S], Mt = J['mtp' + S], Ch = J['hcoffin' + S], Tt = J['htoe' + S];
|
|
232
|
+
// thigh: flat and lean in dairy cows (the "incurving thigh"), full and round in beef breeds
|
|
233
|
+
ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.02 + 0.02 * beef, 0, -0.05])), ydir: sub(K, Hp), lateral: lat, r: [0.08 + 0.04 * beef, 0.3, 0.2 + 0.03 * beef], k: 0.1, bias: 0.006 });
|
|
234
|
+
m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.22, 1.25, -0.46]), b: add(K, sx([0.0, 0.06, 0.04])), ra: 0.11, rb: 0.07, k: 0.1 });
|
|
235
|
+
m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.11, 1.26, -0.84]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.08]), ra: 0.1 + 0.02 * beef, rb: 0.06, k: 0.08 });
|
|
236
|
+
ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.22, 0.92, -0.3]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.05, 0.15, 0.08], k: 0.1 });
|
|
237
|
+
m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.01, 0.01, 0.03])), rad: 0.06, k: 0.07 });
|
|
238
|
+
ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.32), sx([0.006, 0, -0.045])), ydir: sub(Hk, K), lateral: lat, r: [0.072 * legK, 0.16, 0.09 * legK], k: 0.06 });
|
|
239
|
+
m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.06 * legK, rb: 0.046 * legK, k: 0.05 });
|
|
240
|
+
m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.075]), b: add(Hk, [0, 0.07, -0.07]), ra: 0.03, rb: 0.024, k: 0.03 });
|
|
241
|
+
m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.07, -0.066]), rad: 0.034, k: 0.025 });
|
|
242
|
+
ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.01, -0.005]), ydir: [0, 1, 0.25], lateral: lat, r: [0.052 * legK, 0.075, 0.055], k: 0.03 });
|
|
243
|
+
m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, 0.005]), b: add(Mt, [0, 0.02, 0.0]), ra: 0.036 * legK, rb: 0.033 * legK, k: 0.02 });
|
|
244
|
+
m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.04, -0.034]), b: add(Mt, [0, 0.03, -0.032]), ra: 0.022 * legK, rb: 0.026 * legK, k: 0.02 });
|
|
245
|
+
ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.01]), ydir: [0, 1, 0.3], lateral: lat, r: [0.047 * legK, 0.048, 0.05], k: 0.02 });
|
|
246
|
+
sculptDigits(m, 'hpaw' + S, 'hhoof' + S, h, Mt, Ch, Tt, s, 0.97 * legK);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------- TAIL: tail head, bony tail, switch
|
|
250
|
+
const tailK = calf ? 0.9 : 1;
|
|
251
|
+
for (let i = 0; i < BONE_SEGS; i++) {
|
|
252
|
+
const t0 = i / BONE_SEGS, t1 = (i + 1) / BONE_SEGS;
|
|
253
|
+
const r = (t) => (0.05 - 0.032 * Math.pow(t, 0.7)) * tailK;
|
|
254
|
+
m.cone({ tag: 'tail', bone: 'tail' + i, a: J['tail' + i], b: J['tail' + (i + 1)], ra: r(t0), rb: r(t1), k: i === 0 ? 0.06 : 0.01 });
|
|
255
|
+
}
|
|
256
|
+
// switch: a long tassel of hair from the end of the bony tail
|
|
257
|
+
const sw = (params.switchLen ?? 1) * (calf ? 0.4 : 1);
|
|
258
|
+
const b10 = J['tail' + BONE_SEGS], b11 = J['tail' + (BONE_SEGS + 1)], b12 = J['tail' + TAIL_SEGS];
|
|
259
|
+
const sw1 = lerp(b10, b11, Math.min(1, sw)), sw2 = lerp(b11, b12, Math.max(0, Math.min(1, sw * 1.0)));
|
|
260
|
+
m.cone({ tag: 'switch', bone: 'tail' + (BONE_SEGS - 1), a: lerp(J['tail' + (BONE_SEGS - 1)], b10, 0.3), b: b10, ra: 0.022, rb: 0.04, k: 0.015 });
|
|
261
|
+
m.cone({ tag: 'switch', bone: 'tail' + BONE_SEGS, a: b10, b: sw1, ra: 0.042, rb: 0.048 * Math.max(0.6, sw), k: 0.015 });
|
|
262
|
+
if (sw > 0.5) m.cone({ tag: 'switch', bone: 'tail' + (BONE_SEGS + 1), a: sw1, b: sw2, ra: 0.048, rb: 0.012, k: 0.015 });
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------- HORNS (own rigid surface on the head)
|
|
265
|
+
if (params.horns) {
|
|
266
|
+
for (const s of [1, -1]) {
|
|
267
|
+
const hp = hornPath(params, s);
|
|
268
|
+
for (let i = 0; i < hp.pts.length - 1; i++) {
|
|
269
|
+
m.cone({ bone: 'head', group: 'axial', part: 'horn', tag: 'horn', a: hp.pts[i], b: hp.pts[i + 1], ra: hp.rads[i], rb: hp.rads[i + 1], k: 0.01 });
|
|
270
|
+
}
|
|
271
|
+
// horn base boss on the poll (skin)
|
|
272
|
+
m.sphere({ ...H, tag: 'hornbase', c: add(hp.base, [0.006 * s, 0, 0]), rad: params.horns.r * 1.05, k: 0.03 });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------- EAR TAGS, NOSE RING (own rigid surfaces)
|
|
277
|
+
if (params.earTags) {
|
|
278
|
+
for (const S of ['L', 'R']) {
|
|
279
|
+
const s = S === 'L' ? 1 : -1;
|
|
280
|
+
const base = J['earBase' + S], tip = J['earTip' + S];
|
|
281
|
+
const q = add(lerp(base, tip, 0.42), [0, -0.01, 0.02]);
|
|
282
|
+
m.cone({ bone: 'ear' + S, group: 'ear' + S, part: 'tag' + S, tag: 'eartag', a: add(q, [0, 0.012, 0]), b: add(q, [0, 0.012, 0.012]), ra: 0.007, rb: 0.007, k: 0.002 });
|
|
283
|
+
m.ell({ bone: 'ear' + S, group: 'ear' + S, part: 'tag' + S, tag: 'eartag', c: add(q, [0, -0.028, 0.017]), r: [0.028, 0.034, 0.0028], axis: norm([0, 0.1, 1]), k: 0.004 });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (params.noseRing) {
|
|
287
|
+
const c = hl([0, -0.078, 0.402]), rr = 0.03, n = 14;
|
|
288
|
+
const pt = (i) => { const a = (i / n) * Math.PI * 2; return add(c, add(mul(HY, Math.cos(a) * rr), mul(HZ, Math.sin(a) * rr * 0.85))); };
|
|
289
|
+
for (let i = 0; i < n; i++) m.cone({ bone: 'head', part: 'ring', tag: 'ring', a: pt(i), b: pt(i + 1), ra: 0.0042, rb: 0.0042, k: 0.001 });
|
|
290
|
+
}
|
|
291
|
+
void R;
|
|
292
|
+
return m;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Udder: four quarters under the pelvis between the thighs, rear attachment high up between the
|
|
296
|
+
// thighs, fore udder blending forward into the belly, four teats.
|
|
297
|
+
function sculptUdder(m, k) {
|
|
298
|
+
const U = { bone: 'spine1', tag: 'udder' };
|
|
299
|
+
const sz = 0.75 + 0.35 * k;
|
|
300
|
+
m.ell({ ...U, c: [0, 0.73 + 0.04 * (1 - k), -0.55], r: [0.15 * sz, 0.15 * sz, 0.22 * sz], k: 0.06 });
|
|
301
|
+
m.ell({ ...U, bone: 'pelvis', c: [0, 0.92, -0.74], r: [0.12 * sz, 0.2, 0.1], k: 0.08 });
|
|
302
|
+
m.ell({ ...U, c: [0, 0.76, -0.36], r: [0.13 * sz, 0.1 * sz, 0.18 * sz], k: 0.1 });
|
|
303
|
+
const bottom = 0.73 + 0.04 * (1 - k) - 0.15 * sz;
|
|
304
|
+
for (const s of [1, -1]) {
|
|
305
|
+
// quarters bulge a little on each side (the median suspensory ligament makes a groove)
|
|
306
|
+
m.ell({ ...U, c: [0.075 * s, bottom + 0.1, -0.62], r: [0.085 * sz, 0.1 * sz, 0.12 * sz], k: 0.05 });
|
|
307
|
+
m.ell({ ...U, c: [0.07 * s, bottom + 0.1, -0.45], r: [0.075 * sz, 0.09 * sz, 0.1 * sz], k: 0.05 });
|
|
308
|
+
for (const z of [-0.64, -0.44]) {
|
|
309
|
+
m.cone({ tag: 'teat', bone: 'spine1', c: undefined, a: [0.06 * s, bottom + 0.03, z], b: [0.062 * s, bottom - 0.05, z + 0.005], ra: 0.017, rb: 0.013, k: 0.02 });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Cloven hoof: pastern with two dew claws behind the fetlock, then two claws (digits III and IV),
|
|
315
|
+
// each a hoof-wall half-cone with its front wall parallel to the pastern, heel bulbs behind, flat on
|
|
316
|
+
// the ground (carved at y = 0); the interdigital cleft is carved between them.
|
|
317
|
+
function sculptDigits(m, paw, hoof, group, M, C, T, s, w) {
|
|
318
|
+
m.cone({ tag: 'pastern', bone: paw, group, a: M, b: C, ra: 0.038 * w, rb: 0.04 * w, k: 0.02 });
|
|
319
|
+
// dew claws: two small horny nubs behind and above the fetlock... (cattle: just above the heels)
|
|
320
|
+
for (const d of [1, -1]) {
|
|
321
|
+
m.ell({ tag: 'dewclaw', bone: paw, group, c: add(M, [0.022 * d, -0.035, -0.05]), r: [0.013, 0.017, 0.016], axis: norm([0, -0.5, -1]), k: 0.012 });
|
|
322
|
+
}
|
|
323
|
+
const zMid = (C[2] + T[2]) * 0.5;
|
|
324
|
+
const dir = Math.sign(T[2] - C[2]);
|
|
325
|
+
for (const d of [1, -1]) {
|
|
326
|
+
const off = 0.026 * d * w;
|
|
327
|
+
const top = add(C, [off, 0.018, -0.012 * dir]);
|
|
328
|
+
const base = [C[0] + off * 1.1, 0.0, zMid - 0.004 * dir];
|
|
329
|
+
m.cone({ tag: 'hoof', bone: hoof, group, a: top, b: base, ra: 0.026 * w, rb: 0.036 * w, k: 0.012 });
|
|
330
|
+
// the toe: a pointed front of each claw
|
|
331
|
+
m.ell({ tag: 'hoof', bone: hoof, group, c: [C[0] + off * 0.9, 0.022, T[2] - 0.028 * dir], r: [0.024 * w, 0.022, 0.034], axis: norm([0, -0.25, dir]), k: 0.012 });
|
|
332
|
+
m.sphere({ tag: 'heelbulb', bone: hoof, group, c: add(C, [off * 0.9, -0.026, -0.042 * dir]), rad: 0.026 * w, k: 0.02 });
|
|
333
|
+
}
|
|
334
|
+
m.cone({ tag: 'coronet', bone: hoof, group, a: add(C, [0, 0.024, -0.02 * dir]), b: add(C, [0, 0.012, 0.02 * dir]), ra: 0.043 * w, rb: 0.043 * w, k: 0.015 });
|
|
335
|
+
// interdigital cleft: a V opening toward the front and the ground
|
|
336
|
+
m.ell({ tag: 'cleft', bone: hoof, group, c: [C[0], 0.0, T[2] - 0.004 * dir], r: [0.005, 0.045, 0.075], k: 0.004, carve: true });
|
|
337
|
+
m.ell({ tag: 'cleft', bone: hoof, group, c: [C[0], -0.005, zMid], r: [0.004, 0.03, 0.08], k: 0.003, carve: true });
|
|
338
|
+
m.ell({ tag: 'sole', bone: hoof, group, c: [C[0], -0.2 + 0.002, zMid], r: [0.2, 0.2, 0.2], k: 0.004, carve: true });
|
|
339
|
+
}
|