@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,542 @@
|
|
|
1
|
+
// The sheep, sculpted as a signed distance field.
|
|
2
|
+
// Every primitive is attached to a bone (skinning) and a group (axial / limb / ear / jaw). The head is
|
|
3
|
+
// modelled in its own frame (rig.js: hl()), measured from the profile and frontal references (side2,
|
|
4
|
+
// side3_texel, face1, face2, front1); the body relative to the joints so the sculpt follows the
|
|
5
|
+
// skeleton.
|
|
6
|
+
//
|
|
7
|
+
// The fleece is a sculpted volume: the skin body is wrapped in offset "fleece" primitives (params.
|
|
8
|
+
// wool = depth of the wool volume, m) that give the boxy silhouette of a sheep in wool (the neck
|
|
9
|
+
// disappears, the head emerges from the wool, the belly wool hangs, the britch covers the thighs to
|
|
10
|
+
// the hocks), and its surface is broken into locks: a Poisson-disc scatter of small ellipsoids
|
|
11
|
+
// elongated down the fall of the staples, blended into the fleece. The coat grows short, tightly
|
|
12
|
+
// clumped shells on top (the crimped staple tips) and darkens the crevices between locks.
|
|
13
|
+
// Separate surfaces ("parts"): 'jaw' (lower lip and chin: the mouth opens) and 'horn' (keratin
|
|
14
|
+
// spiral horns of rams, rigid on the skull).
|
|
15
|
+
import { HEAD_O, HZ, HY, hl, hdir, TAIL_SEGS } from './rig.js';
|
|
16
|
+
import { add, sub, mul, lerp, norm, cross, dot, rng, smoothstep } from '../../core/math/vec.js';
|
|
17
|
+
import { apertureTiltAlong, eyeFrameOf } from '../../core/sdf/eyeSocket.js';
|
|
18
|
+
import { SDFModel } from '../../core/sdf/sdf.js';
|
|
19
|
+
import { neckS } from './neck.js';
|
|
20
|
+
|
|
21
|
+
// Eye: globe ~29 mm across, visible opening ~23 x 15 mm (literature), set on the side of the
|
|
22
|
+
// head below the brow, looking out and a little forward (lateral eyes, visual field ~300 deg).
|
|
23
|
+
const EYE_LOCAL = [0.058, -0.004, 0.013]; // (set in the orbit: the brow and the orbit's rim stand round it)
|
|
24
|
+
const eyeDir = (() => {
|
|
25
|
+
const a = (38 * Math.PI) / 180;
|
|
26
|
+
return norm(add([Math.cos(a), 0, 0], mul(HZ, Math.sin(a))));
|
|
27
|
+
})();
|
|
28
|
+
const EYE_BASE = {
|
|
29
|
+
c: sub(hl(EYE_LOCAL), HEAD_O), r: 0.0145, back: 0.003, yaw: Math.atan2(eyeDir[0], eyeDir[2]), pitch: Math.asin(eyeDir[1]),
|
|
30
|
+
lid: 0.0017, R: 0.017, d: 0.0095, off: -0.0006, tilt: 0, irisZ: 0.0081, irisR: 0.0112,
|
|
31
|
+
};
|
|
32
|
+
// roll the almond so its long axis follows the nasal line, the inner corner a little lower (toward the
|
|
33
|
+
// muzzle). (A search over the roll stood the almond on end: a vertical slit, the "bead" eye.)
|
|
34
|
+
EYE_BASE.tilt = apertureTiltAlong(EYE_BASE, HEAD_O, HZ) + 0.16;
|
|
35
|
+
export const EYE = EYE_BASE;
|
|
36
|
+
|
|
37
|
+
const X = [1, 0, 0];
|
|
38
|
+
|
|
39
|
+
// Horn centre line (reference space), shared by the sculpt and the coat: the ram's spiral. From the
|
|
40
|
+
// poll it sweeps up and back, down behind the ear, forward under it and up again beside the eye
|
|
41
|
+
// (a logarithmic spiral in the side plane of the head, drifting outward so the turns clear the face
|
|
42
|
+
// and each other). params.horns = { turns, base (radius, m), r0 (curl radius), flare }.
|
|
43
|
+
// Returns [{ p, r, t }].
|
|
44
|
+
export function hornPath(params, s) {
|
|
45
|
+
const H = params.horns;
|
|
46
|
+
if (!H || !(H.turns > 0)) return [];
|
|
47
|
+
// (in the world frame of the bind pose: the coil turns in a near-vertical plane beside the head,
|
|
48
|
+
// whatever the head's pitch)
|
|
49
|
+
const B = hl([0.04 * s, 0.03, -0.04]); // horn base on the poll
|
|
50
|
+
const C = [B[0], B[1] - 0.048 * (H.r0 ?? 1), B[2] - 0.05 * (H.r0 ?? 1)]; // curl centre, behind the ear
|
|
51
|
+
const r0 = Math.hypot(B[1] - C[1], B[2] - C[2]);
|
|
52
|
+
const th0 = Math.atan2(B[1] - C[1], B[2] - C[2]);
|
|
53
|
+
const b = Math.log(1.45) / (2 * Math.PI);
|
|
54
|
+
const turns = H.turns;
|
|
55
|
+
const n = Math.max(6, Math.round(22 * turns));
|
|
56
|
+
const out = [];
|
|
57
|
+
for (let i = 0; i <= n; i++) {
|
|
58
|
+
const t = i / n;
|
|
59
|
+
const th = th0 + t * turns * 2 * Math.PI;
|
|
60
|
+
const r = r0 * Math.exp(b * (th - th0));
|
|
61
|
+
const lat = B[0] + s * (0.095 * (H.flare ?? 1) * turns * t + 0.03 * Math.sin(Math.PI * Math.min(1, t * 2)));
|
|
62
|
+
const v = [lat, C[1] + r * Math.sin(th), C[2] + r * Math.cos(th)];
|
|
63
|
+
const rad = H.base * Math.pow(1 - 0.84 * t, 0.9) + 0.0025;
|
|
64
|
+
out.push({ p: v, r: rad, t });
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The fall of the staples on the fleece surface (normal n, point p): away from the head along the body
|
|
70
|
+
// (the axial chain's direction, nose -> tail, blended softly between its segments) and down. The tangent
|
|
71
|
+
// field is the projection of that 3D direction onto the surface, so it only vanishes where the surface
|
|
72
|
+
// faces straight along it: under the rear of the belly (down and back), never on the visible fleece.
|
|
73
|
+
// (An earlier field added a "back over the top" term (n x X) that pointed UP on the front of the
|
|
74
|
+
// neck and the breast: the fall turned through 180 degrees across a line there, and the fur shells, which
|
|
75
|
+
// lean along the flow, parted along it: the dark crack across the neck and shoulder.)
|
|
76
|
+
const FALL_SEGS = new WeakMap();
|
|
77
|
+
const FALL_CHAIN = ['occiput', 'neckMid', 'neckBase', 'chestMid', 'thoraxRear', 'lumbarMid', 'lumbosacral', 'tailBase'];
|
|
78
|
+
export function stapleFall(n, p, J) {
|
|
79
|
+
let cd = [0, 0, -1];
|
|
80
|
+
if (p && J) {
|
|
81
|
+
let segs = FALL_SEGS.get(J);
|
|
82
|
+
if (!segs) FALL_SEGS.set(J, segs = FALL_CHAIN.slice(1).map((k, i) => { const a = J[FALL_CHAIN[i]], b = J[k]; const d = sub(b, a); return { a, d, l2: dot(d, d), u: norm(d) }; }));
|
|
83
|
+
let dmin = 1e9; const ds = [];
|
|
84
|
+
for (const S of segs) {
|
|
85
|
+
const t = Math.max(0, Math.min(1, dot(sub(p, S.a), S.d) / S.l2));
|
|
86
|
+
const dd = Math.hypot(p[0] - S.a[0] - S.d[0] * t, p[1] - S.a[1] - S.d[1] * t, p[2] - S.a[2] - S.d[2] * t);
|
|
87
|
+
ds.push(dd); if (dd < dmin) dmin = dd;
|
|
88
|
+
}
|
|
89
|
+
cd = [0, 0, 0];
|
|
90
|
+
segs.forEach((S, i) => { const w = Math.exp(-(ds[i] - dmin) / 0.05); cd = add(cd, mul(S.u, w)); });
|
|
91
|
+
cd = norm(cd);
|
|
92
|
+
}
|
|
93
|
+
const v = [cd[0], cd[1] - 1.2, cd[2]], k = dot(n, v);
|
|
94
|
+
const t = [v[0] - n[0] * k, v[1] - n[1] * k, v[2] - n[2] * k];
|
|
95
|
+
const l = Math.hypot(t[0], t[1], t[2]);
|
|
96
|
+
return l > 1e-6 ? [t[0] / l, t[1] / l, t[2] / l] : [0, 0, -1];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// lock radius (m) for a sculpted wool depth: staple blocks of 2-3 cm (finer in the Merino)
|
|
100
|
+
export const lockRadius = (F, merino) => Math.min(0.032, 0.01 + 0.2 * F) * (merino ? 0.75 : 1);
|
|
101
|
+
|
|
102
|
+
// all wool tags (the coat grows fleece on them)
|
|
103
|
+
export const WOOL_TAGS = new Set(['fleece', 'woolneck', 'woolarm', 'woolthigh', 'woolweb', 'woolbelly', 'wooltail', 'woolpoll', 'woolcheek', 'lock', 'lockleg', 'woolleg', 'fold']);
|
|
104
|
+
|
|
105
|
+
export function sculptSheep(m, rig, params = {}) {
|
|
106
|
+
const J = rig.J;
|
|
107
|
+
const lamb = params.age === 'juvenile';
|
|
108
|
+
const male = params.sex === 'male';
|
|
109
|
+
const ram = male && !lamb;
|
|
110
|
+
const heavy = params.heavy ?? (ram ? 1 : 0); // 0 ewe .. 1 heavy ram
|
|
111
|
+
const roman = params.roman || 0; // Suffolk / Merino convex nose
|
|
112
|
+
const F = params.wool ?? 0.07; // depth of the sculpted wool volume (m)
|
|
113
|
+
const merino = params.variant === 'merino';
|
|
114
|
+
const R = rng(9127 + (params.coatSeed || 0));
|
|
115
|
+
|
|
116
|
+
const ellY = (o) => {
|
|
117
|
+
const y = norm(o.ydir);
|
|
118
|
+
const lat = o.lateral || X;
|
|
119
|
+
const z = norm(cross(lat, y));
|
|
120
|
+
return m.ell({ ...o, axis: z, up: y, r: [o.r[0], o.r[1], o.r[2]] });
|
|
121
|
+
};
|
|
122
|
+
const H = { bone: 'head' };
|
|
123
|
+
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 });
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------- TORSO (skin)
|
|
126
|
+
// deep, broad barrel, level back, well-sprung ribs, broad rounded rump (meat breeds: Texel, Suffolk)
|
|
127
|
+
const gw = 1 + 0.1 * heavy + (params.muscle || 0) * 0.08;
|
|
128
|
+
m.ell({ tag: 'ribcage', bone: 'spine3', c: [0, 0.5, 0.07], r: [0.152 * gw, 0.19, 0.27], axis: norm([0, 0.06, 1]), k: 0 });
|
|
129
|
+
m.ell({ tag: 'girth', bone: 'chest', c: [0, 0.49, 0.23], r: [0.122 * gw, 0.17, 0.12], k: 0.05 });
|
|
130
|
+
m.ell({ tag: 'abdomen', bone: 'spine2', c: [0, 0.485, -0.13], r: [0.16 * gw, 0.18, 0.2], axis: norm([0, 0.06, -1]), k: 0.06 });
|
|
131
|
+
m.ell({ tag: 'flank', bone: 'spine1', c: [0, 0.53, -0.26], r: [0.14 * gw, 0.13, 0.13], k: 0.06 });
|
|
132
|
+
m.ell({ tag: 'withers', bone: 'chest', c: [0, 0.645, 0.2], r: [0.05, 0.05, 0.13], axis: norm([0, -0.1, 1]), k: 0.07 });
|
|
133
|
+
m.ell({ tag: 'back', bone: 'spine3', c: [0, 0.64, 0.0], r: [0.11 * gw, 0.05, 0.22], k: 0.07 });
|
|
134
|
+
m.ell({ tag: 'loin', bone: 'spine1', c: [0, 0.645, -0.2], r: [0.12 * gw, 0.045, 0.14], k: 0.07 });
|
|
135
|
+
// hindquarters: broad croup, rounded buttocks (the leg of lamb)
|
|
136
|
+
m.ell({ tag: 'pelvis', bone: 'pelvis', c: [0, 0.56, -0.39], r: [0.13 * gw, 0.12, 0.13], k: 0.05 });
|
|
137
|
+
m.ell({ tag: 'croup', bone: 'pelvis', c: [0, 0.63, -0.38], r: [0.105 * gw, 0.045, 0.14], axis: norm([0, -0.1, 1]), k: 0.05 });
|
|
138
|
+
for (const s of [1, -1]) {
|
|
139
|
+
m.sphere({ tag: 'hippoint', bone: 'pelvis', c: [0.1 * s * gw, 0.635, -0.29], rad: 0.022, k: 0.05 });
|
|
140
|
+
m.sphere({ tag: 'pinbone', bone: 'pelvis', c: [0.05 * s, 0.61, -0.49], rad: 0.022, k: 0.04 });
|
|
141
|
+
m.ell({ tag: 'rump', bone: 'pelvis', c: [0.06 * s, 0.51, -0.46], r: [0.065 * gw, 0.11, 0.065], k: 0.06 });
|
|
142
|
+
}
|
|
143
|
+
// chest front: brisket between the forelegs
|
|
144
|
+
m.ell({ tag: 'brisket', bone: 'chest', c: [0, 0.395, 0.3], r: [0.075, 0.075, 0.08], k: 0.05 });
|
|
145
|
+
for (const s of [1, -1]) m.ell({ tag: 'pectoral', bone: 'chest', c: [0.046 * s, 0.4, 0.33], r: [0.042, 0.06, 0.04], k: 0.04 });
|
|
146
|
+
// udder (ewes, small, mostly hidden in the wool) / scrotum (rams: large, hanging between the thighs)
|
|
147
|
+
if (!male && !lamb && (params.udder ?? 0.3) > 0.05) {
|
|
148
|
+
const u = params.udder ?? 0.3;
|
|
149
|
+
for (const s of [1, -1]) {
|
|
150
|
+
m.ell({ tag: 'udder', bone: 'udder', group: 'udder', c: [0.026 * s, 0.315 - 0.02 * u, -0.3], r: [0.034 + 0.014 * u, 0.04 + 0.02 * u, 0.045 + 0.015 * u], k: 0.03 });
|
|
151
|
+
const a = [0.032 * s, 0.285 - 0.035 * u, -0.29], b = add(a, [0.01 * s, -0.022, 0.01]);
|
|
152
|
+
m.cone({ tag: 'teat', bone: 'udder', group: 'udder', a, b, ra: 0.008, rb: 0.006, k: 0.01 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (male && !lamb) {
|
|
156
|
+
m.ell({ tag: 'scrotum', bone: 'udder', group: 'udder', c: [0, 0.3, -0.34], r: [0.045, 0.075, 0.045], axis: norm([0, 0.2, 1]), k: 0.035 });
|
|
157
|
+
m.ell({ tag: 'sheath', bone: 'spine2', c: [0, 0.3, -0.05], r: [0.016, 0.02, 0.045], axis: norm([0, 0.3, 1]), k: 0.03 });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------- NECK (skin)
|
|
161
|
+
// short and thick; a ram's is heavier with a crest
|
|
162
|
+
const nb = J.neckBase, nm = J.neckMid, occ = J.occiput;
|
|
163
|
+
const nd1 = norm(sub(nm, nb)), nd2 = norm(sub(occ, nm));
|
|
164
|
+
const nk = 1 + 0.25 * heavy;
|
|
165
|
+
m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.575, 0.32], r: [0.11 * nk, 0.13, 0.11], axis: nd1, up: [0, 1, -0.6], k: 0.05 });
|
|
166
|
+
m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.67, 0.4], r: [0.1 * nk, 0.105 * (1 + 0.15 * heavy), 0.095], axis: nd1, up: [0, 1, -0.6], k: 0.05 });
|
|
167
|
+
m.ell({ tag: 'neck', bone: 'neck2', c: [0, 0.76, 0.47], r: [0.078 * nk, 0.085 * (1 + 0.15 * heavy), 0.08], axis: nd2, up: [0, 1, -0.6], k: 0.04 });
|
|
168
|
+
// the neck meets the withers and shoulders in one broad mass (no notch in the shorn topline)
|
|
169
|
+
m.ell({ tag: 'neck', bone: 'neck1', c: [0, 0.6, 0.265], r: [0.12 * nk, 0.12, 0.12], k: 0.06 });
|
|
170
|
+
m.ell({ tag: 'crest', bone: 'neck1', c: [0, 0.71, 0.37], r: [0.055 + 0.02 * heavy, 0.05 + 0.02 * heavy, 0.15], axis: norm([0, 0.65, 0.75]), k: 0.07 });
|
|
171
|
+
m.cone({ tag: 'throat', bone: 'neck1', a: [0, 0.51, 0.38], b: [0, 0.65, 0.455], ra: 0.05, rb: 0.038, k: 0.05 });
|
|
172
|
+
// (the throat runs in behind the angle of the jaw: the jaw's outline stands clear of the neck)
|
|
173
|
+
m.cone({ tag: 'throat', bone: 'neck2', a: [0, 0.63, 0.455], b: hl([0, -0.07, -0.1]), ra: 0.03, rb: 0.022, k: 0.03 });
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------- HEAD (head-local, see rig.js)
|
|
176
|
+
// A defined skull rather than one smooth mass (side4_suffolk, face1, face2, extra/: Suffolk_Sheep,
|
|
177
|
+
// Tete_de_brebie_Suffolk, Lundy head): a narrow braincase behind a flat forehead; the orbits stand out
|
|
178
|
+
// to the sides under a brow; a long nasal bridge, straight in the white-faced breeds and arched
|
|
179
|
+
// (Roman) in the Suffolk and in rams; the face narrows from the facial crest below the eye to a
|
|
180
|
+
// blunt, deep muzzle with comma nostrils and a split upper lip; the cheek (masseter) and the
|
|
181
|
+
// rounded angle of the jaw behind it, the lower edge of the mandible running forward to the chin.
|
|
182
|
+
// Landmarks: poll (0, 0.04, -0.075), eyes (+-0.059, -0.006, 0.004), nose tip (0, -0.024, 0.2), chin
|
|
183
|
+
// y -0.09 at 0.15, forehead width 0.105 between the brows.
|
|
184
|
+
// dorsal line: straight from the forehead (y 0.046 at z 0) to the top of the nose (0.012 at 0.175),
|
|
185
|
+
// plus the Roman arch
|
|
186
|
+
const topY = (z) => 0.046 - 0.194 * z + (0.004 + 0.011 * roman) * Math.sin(Math.PI * Math.max(0, Math.min(1, (z + 0.02) / 0.21)));
|
|
187
|
+
// (broad and flat across the top: from the front the head is wide at the eyes and ears and narrows to
|
|
188
|
+
// the muzzle, not a dome over a long nose)
|
|
189
|
+
hell({ tag: 'cranium', c: [0, -0.002, -0.042], r: [0.05, 0.04, 0.05], k: 0.03 });
|
|
190
|
+
hell({ tag: 'poll', c: [0, 0.0, -0.068], r: [0.038, 0.032, 0.03], k: 0.03 });
|
|
191
|
+
hell({ tag: 'forehead', c: [0, 0.028, 0.0], r: [0.056, 0.018, 0.05], k: 0.025 });
|
|
192
|
+
// nasal bridge: a narrow, flat-topped ridge following the dorsal line
|
|
193
|
+
for (const [z, w] of [[0.035, 0.032], [0.08, 0.03], [0.122, 0.028], [0.158, 0.027]]) {
|
|
194
|
+
const ry = 0.017;
|
|
195
|
+
const dz = 0.01, slope = (topY(z + dz) - topY(z - dz)) / (2 * dz);
|
|
196
|
+
hell({ tag: 'face', c: [0, topY(z) - ry, z], r: [w, ry, 0.038], axisL: [0, slope, 1], k: 0.022 });
|
|
197
|
+
}
|
|
198
|
+
// the face below the bridge, narrowing to the muzzle
|
|
199
|
+
hell({ tag: 'lowerface', c: [0, -0.043, 0.066], r: [0.043, 0.045, 0.08], k: 0.03 });
|
|
200
|
+
hell({ tag: 'muzzle', c: [0, -0.024, 0.155], r: [0.035 + 0.002 * roman, 0.034, 0.043], k: 0.024 });
|
|
201
|
+
// (the nasal line runs on to a blunt, nearly upright nose front: the tip is not a rounded bulb)
|
|
202
|
+
hell({ tag: 'face', c: [0, topY(0.186) - 0.019, 0.184], r: [0.025, 0.019, 0.016], axisL: [0, -0.3, 1], k: 0.018 });
|
|
203
|
+
// (the throat between the jaws, a little hollow under the lower edges of the mandibles)
|
|
204
|
+
hell({ tag: 'intermandible', c: [0, -0.092, 0.025], r: [0.02, 0.014, 0.06], k: 0.018 });
|
|
205
|
+
for (const s of [1, -1]) {
|
|
206
|
+
// the sides of the face (maxilla), sloping out from the bridge to the cheek
|
|
207
|
+
hell({ tag: 'maxilla', c: [0.031 * s, -0.03, 0.078], r: [0.022, 0.036, 0.074], axisL: [-0.18 * s, 0, 1], k: 0.028 });
|
|
208
|
+
// the brow: the orbit's rim stands out over the eye
|
|
209
|
+
// (a capsule: a flat ellipsoid's approximate distance, blended at k 22-35 mm, drew a crisp groove along its
|
|
210
|
+
// lower edge, a frown line over each eye)
|
|
211
|
+
m.cone({ ...H, tag: 'brow', a: hl([0.043 * s, 0.013, 0.03]), b: hl([0.053 * s, 0.011, -0.012]), ra: 0.006, rb: 0.005, k: 0.02 });
|
|
212
|
+
// the orbit's back and lower rim (behind and below the eye)
|
|
213
|
+
hell({ tag: 'orbitrim', c: [0.048 * s, -0.012, -0.011], r: [0.012, 0.018, 0.014], k: 0.016 });
|
|
214
|
+
// the facial crest: a ridge from below the eye forward along the cheek
|
|
215
|
+
m.cone({ ...H, tag: 'facialcrest', a: hl([0.05 * s, -0.026, 0.001]), b: hl([0.038 * s, -0.033, 0.075]), ra: 0.01, rb: 0.007, k: 0.016 });
|
|
216
|
+
// the side of the face below the facial crest is full down to the lips (a hollow there left the muzzle
|
|
217
|
+
// a thin tube in front of the cheeks: the 'anteater' look in 3/4)
|
|
218
|
+
hell({ tag: 'buccal', c: [0.032 * s, -0.058, 0.075], r: [0.019, 0.028, 0.062], k: 0.03 });
|
|
219
|
+
// cheek (masseter) and the rounded angle of the jaw behind it
|
|
220
|
+
hell({ tag: 'cheek', c: [0.043 * s, -0.074, -0.01], r: [0.021, 0.052, 0.046], k: 0.028 });
|
|
221
|
+
// (the angle a rounded corner standing out of the throat: the jaw's outline breaks into the neck)
|
|
222
|
+
hell({ tag: 'jawangle', c: [0.035 * s, -0.116, -0.03], r: [0.019, 0.032, 0.031], k: 0.024 });
|
|
223
|
+
// lower edge of the mandible
|
|
224
|
+
m.cone({ ...H, tag: 'mandible', a: hl([0.034 * s, -0.128, -0.026]), b: hl([0.024 * s, -0.075, 0.13]), ra: 0.011, rb: 0.0095, k: 0.022 });
|
|
225
|
+
hell({ tag: 'nostrilwing', c: [0.019 * s, -0.019, 0.177], r: [0.012, 0.014, 0.014], k: 0.013 });
|
|
226
|
+
// the sides of the upper lip hang over the lower jaw back to the corner of the mouth (closed lips)
|
|
227
|
+
hell({ tag: 'lipside', c: [0.021 * s, -0.045, 0.16], r: [0.011, 0.011, 0.025], axisL: [-0.14 * s, 0.05, 1], k: 0.015 });
|
|
228
|
+
// the upper lip in two halves (the philtrum splits it)
|
|
229
|
+
hell({ tag: 'upperlip', c: [0.013 * s, -0.04, 0.182], r: [0.016, 0.0125, 0.018], k: 0.012 });
|
|
230
|
+
// the eye socket (as core sculptEyeSocket, without its orbit carve and with a softer lid blend, 0.8 r): the
|
|
231
|
+
// lid's bulge over the globe runs smoothly into the brow and the orbit's rim; the almond's cut stops 1.4 r out
|
|
232
|
+
// (just outside the lids) instead of 2 r
|
|
233
|
+
{
|
|
234
|
+
const ef = eyeFrameOf(EYE, HEAD_O, s);
|
|
235
|
+
m.sphere({ bone: 'head', tag: 'eyelid', c: ef.c, rad: EYE.r + EYE.lid, k: EYE.r * 0.8 });
|
|
236
|
+
m.lens({ bone: 'head', tag: 'eyesocket', c: add(ef.c, mul(ef.y, EYE.off)), x: ef.x, y: ef.y, z: ef.z, R: EYE.R, d: EYE.d, zMin: -EYE.r * 0.16, zMax: EYE.r * 1.4, k: EYE.r * 0.18, carve: true });
|
|
237
|
+
}
|
|
238
|
+
// preorbital (infraorbital) gland: a small pit in front of and below the eye
|
|
239
|
+
hell({ tag: 'preorbital', c: [0.047 * s, -0.021, 0.047], r: [0.004, 0.003, 0.008], k: 0.004, carve: true });
|
|
240
|
+
// nostril: a narrow oblique slit on the front of the nose, the upper ends turned toward each other (an
|
|
241
|
+
// inverted V, front1, face2); its centre 1 mm under the skin, ~15 x 2.5 mm where it opens
|
|
242
|
+
// (a 7 x 20 mm carve 9 mm inside the nose opened on its underside, a dark smear from below)
|
|
243
|
+
hell({ tag: 'nostril', c: [0.0105 * s, -0.019, 0.2015], r: [0.0014, 0.0078, 0.004], axisL: [0.3 * s, -0.1, 1], upL: [-0.64 * s, 0.77, 0], k: 0.0015, carve: true });
|
|
244
|
+
}
|
|
245
|
+
// philtrum: the groove that splits the upper lip (sheep graze close with the split lip); a shallow notch in the
|
|
246
|
+
// lip's edge, the line down from the nose is painted (coat.js; a carved groove on the fine nose mesh shaded
|
|
247
|
+
// into a dark fang under the nose)
|
|
248
|
+
hell({ tag: 'philtrum', c: [0, -0.046, 0.2], r: [0.0018, 0.006, 0.006], k: 0.002, carve: true });
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------- JAW (lower lip, chin)
|
|
251
|
+
const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
|
|
252
|
+
m.ell({ ...JW, tag: 'chin', c: hl([0, -0.063, 0.158]), r: [0.023, 0.019, 0.028], axis: HZ, up: HY, k: 0 });
|
|
253
|
+
m.ell({ ...JW, tag: 'lowerlip', c: hl([0, -0.053, 0.176]), r: [0.021, 0.013, 0.017], axis: HZ, up: HY, k: 0.012 });
|
|
254
|
+
// (the rear of the lower jaw thins into the head: a full round end stood out of the cheek below the corner of
|
|
255
|
+
// the mouth, its crease turning the mouth line down)
|
|
256
|
+
for (const s of [1, -1]) m.cone({ ...JW, tag: 'mandible', a: hl([0.02 * s, -0.06, 0.124]), b: hl([0.018 * s, -0.058, 0.158]), ra: 0.0085, rb: 0.012, k: 0.015 });
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------- EARS
|
|
259
|
+
// leaf-shaped, held level out to the sides (Suffolk: longer, a little below level); a rolled, thick
|
|
260
|
+
// base (the ear's stalk) widening into the blade, the inside a shallow cup with a rim
|
|
261
|
+
const earW = params.ear === 'droop' ? 0.031 : params.ear === 'small' ? 0.024 : 0.028;
|
|
262
|
+
for (const S of ['L', 'R']) {
|
|
263
|
+
const s = S === 'L' ? 1 : -1;
|
|
264
|
+
const base = J['earBase' + S], tip = J['earTip' + S];
|
|
265
|
+
const up = norm(sub(tip, base));
|
|
266
|
+
const len = Math.hypot(...sub(tip, base));
|
|
267
|
+
// the blade turned 55 deg about its own axis from level, so the cup opens forward, out and down: the
|
|
268
|
+
// inside shows from the front and from below (face1, front1, threequarter1), the back of the leaf from
|
|
269
|
+
// above, and from the side (ears swept back 46 deg, rig.js) the near ear is a level leaf pointing back
|
|
270
|
+
// showing its inside (side2), not a round cup standing upright (at 32 deg back the cup faced
|
|
271
|
+
// the side camera, a pink cup over the poll; at 28-40 deg of turn the blade was edge-on, a rod). The
|
|
272
|
+
// Suffolk's long hanging ears: the blade near upright, its face forward (Tete_de_brebie_Suffolk; at 55-62
|
|
273
|
+
// deg the leaf seen from the side was a thin black rod)
|
|
274
|
+
const fwd = norm([-s * up[2], 0, Math.abs(up[0])]);
|
|
275
|
+
const tiltB = ((params.ear === 'droop' ? 80 : 55) * Math.PI) / 180;
|
|
276
|
+
const facing = norm(add(mul([0, -1, 0], Math.cos(tiltB)), mul(fwd, Math.sin(tiltB))));
|
|
277
|
+
const lat = norm(cross(up, facing));
|
|
278
|
+
const fc = norm(cross(lat, up));
|
|
279
|
+
const ear = { bone: 'ear' + S, group: 'ear' + S, thin: true };
|
|
280
|
+
m.cone({ ...ear, tag: 'earstalk', a: add(base, mul(up, 0.004)), b: lerp(base, tip, 0.3), ra: 0.0115, rb: 0.0095, k: 0.01 });
|
|
281
|
+
ellY({ ...ear, tag: 'ear', c: lerp(base, tip, 0.53), ydir: up, lateral: lat, r: [earW, len * 0.4, 0.0062], k: 0.016 });
|
|
282
|
+
// (a leaf narrowing to a point, not a round paddle)
|
|
283
|
+
ellY({ ...ear, tag: 'eartip', c: lerp(base, tip, 0.8), ydir: up, lateral: lat, r: [earW * 0.5, len * 0.24, 0.0045], k: 0.014 });
|
|
284
|
+
// (the cup starts clear of the stalk: where it cut into the stalk's junction it left dark pits)
|
|
285
|
+
ellY({ ...ear, tag: 'earinner', c: add(lerp(base, tip, 0.58), mul(fc, 0.0054)), ydir: up, lateral: lat, r: [earW * 0.7, len * 0.32, 0.0042], k: 0.005, carve: true });
|
|
286
|
+
m.sphere({ ...H, tag: 'earbase', c: add(base, mul(up, -0.006)), rad: 0.016, k: 0.02 });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------- HORNS (rams of horned breeds)
|
|
290
|
+
for (const s of [1, -1]) {
|
|
291
|
+
const pth = hornPath(params, s);
|
|
292
|
+
for (let i = 0; i + 1 < pth.length; i++) {
|
|
293
|
+
m.cone({ bone: 'head', part: 'horn', group: 'axial', tag: 'horn', a: pth[i].p, b: pth[i + 1].p, ra: pth[i].r, rb: pth[i + 1].r, k: 0.004 });
|
|
294
|
+
}
|
|
295
|
+
if (pth.length) m.sphere({ ...H, tag: 'hornboss', c: hl([0.036 * s, 0.028, -0.035]), rad: Math.min(0.018, params.horns.base * 0.6), k: 0.015 });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------- LEGS (skin): fine-boned, long cannons
|
|
299
|
+
const lk = 1 + 0.1 * heavy;
|
|
300
|
+
for (const S of ['L', 'R']) {
|
|
301
|
+
const s = S === 'L' ? 1 : -1;
|
|
302
|
+
const lat = [s, 0, 0];
|
|
303
|
+
const sx = (v) => [v[0] * s, v[1], v[2]];
|
|
304
|
+
const g = 'F' + S;
|
|
305
|
+
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];
|
|
306
|
+
ellY({ tag: 'scapmuscle', bone: 'scapula' + S, group: g, c: add(lerp(Sc, Sh, 0.45), sx([0.03, 0, -0.01])), ydir: sub(Sh, Sc), lateral: lat, r: [0.024 * lk, 0.095, 0.062], k: 0.06 });
|
|
307
|
+
m.sphere({ tag: 'shoulderpoint', bone: 'humerus' + S, group: g, c: add(Sh, sx([0.008, 0.0, 0.012])), rad: 0.027, k: 0.04 });
|
|
308
|
+
m.cone({ tag: 'upperarm', bone: 'humerus' + S, group: g, a: Sh, b: E, ra: 0.038 * lk, rb: 0.033 * lk, k: 0.04 });
|
|
309
|
+
ellY({ tag: 'triceps', bone: 'humerus' + S, group: g, c: add(lerp(Sh, E, 0.55), sx([0.005, 0.01, -0.045])), ydir: sub(E, Sh), lateral: lat, r: [0.032 * lk, 0.068, 0.05], k: 0.05 });
|
|
310
|
+
m.sphere({ tag: 'olecranon', bone: 'radius' + S, group: g, c: add(E, [0, 0.012, -0.032]), rad: 0.023, k: 0.025 });
|
|
311
|
+
m.cone({ tag: 'forearm', bone: 'radius' + S, group: g, a: add(E, [0, 0, -0.005]), b: W, ra: 0.033 * lk, rb: 0.018, k: 0.025 });
|
|
312
|
+
ellY({ tag: 'forearmmuscle', bone: 'radius' + S, group: g, c: add(lerp(E, W, 0.25), sx([0.004, 0, 0.006])), ydir: sub(W, E), lateral: lat, r: [0.031 * lk, 0.065, 0.034 * lk], k: 0.025 });
|
|
313
|
+
m.cone({ tag: 'forearmweb', bone: 'radius' + S, group: g, a: add(E, sx([-0.02, 0.03, -0.01])), b: add(lerp(E, W, 0.3), sx([-0.01, 0, 0])), ra: 0.028, rb: 0.02, k: 0.03 });
|
|
314
|
+
ellY({ tag: 'knee', bone: 'metacarpus' + S, group: g, c: add(W, [0, 0.0, 0.002]), ydir: [0, 1, 0], lateral: lat, r: [0.02, 0.025, 0.02], k: 0.012 });
|
|
315
|
+
m.cone({ tag: 'cannon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.01, 0.0]), b: add(M, [0, 0.01, 0.0]), ra: 0.014, rb: 0.013, k: 0.01 });
|
|
316
|
+
m.cone({ tag: 'tendon', bone: 'metacarpus' + S, group: g, a: add(W, [0, -0.015, -0.011]), b: add(M, [0, 0.015, -0.012]), ra: 0.0085, rb: 0.0105, k: 0.01 });
|
|
317
|
+
ellY({ tag: 'fetlock', bone: 'metacarpus' + S, group: g, c: add(M, [0, 0.0, -0.004]), ydir: [0, 1, 0.3], lateral: lat, r: [0.018, 0.019, 0.02], k: 0.01 });
|
|
318
|
+
sculptDewclaws(m, 'fpaw' + S, g, M, s);
|
|
319
|
+
m.cone({ tag: 'pastern', bone: 'fpaw' + S, group: g, a: M, b: C, ra: 0.0148, rb: 0.0158, k: 0.01 });
|
|
320
|
+
sculptHoof(m, 'fhoof' + S, g, C, T, 0.97, s);
|
|
321
|
+
|
|
322
|
+
const h = 'H' + S;
|
|
323
|
+
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];
|
|
324
|
+
ellY({ tag: 'thigh', bone: 'femur' + S, group: h, c: add(lerp(Hp, K, 0.42), sx([0.022, 0, -0.03])), ydir: sub(K, Hp), lateral: lat, r: [0.036 * lk, 0.125, 0.095], k: 0.06 });
|
|
325
|
+
m.cone({ tag: 'thighfront', bone: 'femur' + S, group: h, a: sx([0.075, 0.585, -0.28]), b: add(K, sx([0.0, 0.03, 0.02])), ra: 0.05, rb: 0.03, k: 0.05 });
|
|
326
|
+
m.cone({ tag: 'hamstring', bone: 'femur' + S, group: h, a: sx([0.05, 0.58, -0.47]), b: add(lerp(K, Hk, 0.3), [0, 0, -0.04]), ra: 0.05, rb: 0.027, k: 0.04 });
|
|
327
|
+
ellY({ tag: 'flankfold', bone: 'femur' + S, group: h, c: sx([0.095, 0.41, -0.19]), ydir: [-0.1, 0.3, 0.12], lateral: lat, r: [0.025, 0.065, 0.038], k: 0.05 });
|
|
328
|
+
m.sphere({ tag: 'stifle', bone: 'tibia' + S, group: h, c: add(K, sx([0.004, 0.005, 0.014])), rad: 0.027, k: 0.035 });
|
|
329
|
+
ellY({ tag: 'gaskin', bone: 'tibia' + S, group: h, c: add(lerp(K, Hk, 0.3), sx([0.004, 0, -0.022])), ydir: sub(Hk, K), lateral: lat, r: [0.034 * lk, 0.075, 0.043], k: 0.03 });
|
|
330
|
+
m.cone({ tag: 'shin', bone: 'tibia' + S, group: h, a: lerp(K, Hk, 0.1), b: Hk, ra: 0.026, rb: 0.017, k: 0.025 });
|
|
331
|
+
m.cone({ tag: 'achilles', bone: 'tibia' + S, group: h, a: add(lerp(K, Hk, 0.45), [0, 0, -0.035]), b: add(Hk, [0, 0.03, -0.03]), ra: 0.012, rb: 0.01, k: 0.015 });
|
|
332
|
+
m.sphere({ tag: 'calcaneus', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.027, -0.027]), rad: 0.014, k: 0.012 });
|
|
333
|
+
ellY({ tag: 'hock', bone: 'metatarsus' + S, group: h, c: add(Hk, [0, 0.004, -0.002]), ydir: [0, 1, 0.25], lateral: lat, r: [0.019, 0.029, 0.021], k: 0.015 });
|
|
334
|
+
m.cone({ tag: 'cannon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.016, 0.002]), b: add(Mt, [0, 0.01, 0.0]), ra: 0.0145, rb: 0.013, k: 0.01 });
|
|
335
|
+
m.cone({ tag: 'tendon', bone: 'metatarsus' + S, group: h, a: add(Hk, [0, -0.016, -0.012]), b: add(Mt, [0, 0.015, -0.012]), ra: 0.0085, rb: 0.0105, k: 0.01 });
|
|
336
|
+
ellY({ tag: 'fetlock', bone: 'metatarsus' + S, group: h, c: add(Mt, [0, 0.0, -0.004]), ydir: [0, 1, 0.3], lateral: lat, r: [0.018, 0.019, 0.02], k: 0.01 });
|
|
337
|
+
sculptDewclaws(m, 'hpaw' + S, h, Mt, s);
|
|
338
|
+
m.cone({ tag: 'pastern', bone: 'hpaw' + S, group: h, a: Mt, b: Ch, ra: 0.0145, rb: 0.0155, k: 0.01 });
|
|
339
|
+
sculptHoof(m, 'hhoof' + S, h, Ch, Tt, 0.93, s);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------- TAIL (skin): thin, hanging
|
|
343
|
+
for (let i = 0; i < TAIL_SEGS; i++) {
|
|
344
|
+
const t0 = i / TAIL_SEGS, t1 = (i + 1) / TAIL_SEGS;
|
|
345
|
+
const a = J['tail' + i], b = J['tail' + (i + 1)];
|
|
346
|
+
const w0 = 0.026 - 0.014 * t0, w1 = 0.026 - 0.014 * t1;
|
|
347
|
+
m.cone({ tag: i === 0 ? 'tailhead' : 'tail', bone: 'tail' + i, a, b, ra: w0, rb: w1, k: i === 0 ? 0.03 : 0.008, thin: i >= TAIL_SEGS - 2 });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ---------------------------------------------------------------- FLEECE
|
|
351
|
+
if (F > 0.02) sculptFleece(m, J, params, F, { gw, nk, lk, heavy, merino, R, lamb, ram });
|
|
352
|
+
return m;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// The wool volume: offsets of the body masses, boxed out into the sheep's silhouette, then broken into
|
|
356
|
+
// locks on its surface.
|
|
357
|
+
function sculptFleece(m, J, params, F, o) {
|
|
358
|
+
const { gw, nk, heavy, merino, R } = o;
|
|
359
|
+
const fleece = [];
|
|
360
|
+
const add1 = (p) => { fleece.push(p); return p; };
|
|
361
|
+
const kF = 0.035 + 0.25 * F;
|
|
362
|
+
// torso: an offset rounded box (top line level, sides nearly vertical, bottom hanging)
|
|
363
|
+
const side = 0.155 * gw + F, top = 0.685 + F * 0.72, bot = 0.305 - F * 0.3;
|
|
364
|
+
const cy = (top + bot) / 2, hh = (top - bot) / 2;
|
|
365
|
+
// three stacked slabs per section give the flat sides and the squared-off back of a sheep in wool
|
|
366
|
+
const secs = [
|
|
367
|
+
{ bone: 'chest', z: 0.2, w: 0.9, t: 0, b: 0.02, rz: 0.16 + F * 0.7 },
|
|
368
|
+
{ bone: 'spine3', z: 0.03, w: 1, t: 0, b: -0.01, rz: 0.17 + F * 0.45 },
|
|
369
|
+
{ bone: 'spine2', z: -0.14, w: 1.02, t: 0, b: 0, rz: 0.15 + F * 0.4 },
|
|
370
|
+
{ bone: 'spine1', z: -0.28, w: 1.02, t: 0.005, b: 0.03, rz: 0.13 + F * 0.4 },
|
|
371
|
+
{ bone: 'pelvis', z: -0.4, w: 0.98, t: 0.0, b: 0.06, rz: 0.13 + F * 0.8 },
|
|
372
|
+
];
|
|
373
|
+
for (const S of secs) {
|
|
374
|
+
const hw = side * S.w, t = top - S.t, b = bot + S.b;
|
|
375
|
+
const c = (t + b) / 2, h = (t - b) / 2;
|
|
376
|
+
add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, c + h * 0.25, S.z], r: [hw * 0.98, h * 0.78, S.rz], k: kF }));
|
|
377
|
+
add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, c - h * 0.28, S.z], r: [hw, h * 0.74, S.rz * 0.96], k: kF }));
|
|
378
|
+
add1(m.ell({ tag: 'fleece', bone: S.bone, c: [0, t - hw * 0.45, S.z], r: [hw * 0.92, hw * 0.46, S.rz * 0.98], k: kF }));
|
|
379
|
+
}
|
|
380
|
+
// breast wool (between the forelegs, running up into the neck) and the britch (the back of the thighs)
|
|
381
|
+
add1(m.ell({ tag: 'fleece', bone: 'chest', c: [0, 0.43, 0.3], r: [0.1 + F * 0.8, 0.13 + F * 0.6, 0.09 + F], k: kF }));
|
|
382
|
+
add1(m.ell({ tag: 'woolbelly', bone: 'spine2', c: [0, cy - hh * 0.55, -0.06], r: [side * 0.82, hh * 0.45, 0.3 + F * 0.3], k: kF }));
|
|
383
|
+
for (const s of [1, -1]) add1(m.ell({ tag: 'fleece', bone: 'pelvis', c: [0.065 * s, 0.5, -0.47], r: [0.075 + F * 0.8, 0.13 + F * 0.4, 0.075 + F * 0.95], k: kF }));
|
|
384
|
+
// neck: a thick collar of wool; the head emerges from it behind the ears
|
|
385
|
+
const nf = F * (merino ? 1 : 0.9);
|
|
386
|
+
add1(m.ell({ tag: 'woolneck', bone: 'neck1', c: [0, 0.58, 0.33], r: [0.09 * nk + nf, 0.11 + nf, 0.1 + nf * 0.6], axis: norm(sub(J.neckMid, J.neckBase)), up: [0, 1, -0.6], k: kF }));
|
|
387
|
+
add1(m.ell({ tag: 'woolneck', bone: 'neck1', c: [0, 0.66, 0.39], r: [0.07 * nk + nf, 0.085 + nf, 0.078 + nf * 0.32], axis: norm(sub(J.neckMid, J.neckBase)), up: [0, 1, -0.6], k: kF }));
|
|
388
|
+
const collar = params.horns ? 0.035 : 0; // (a horned ram's wool starts further back: the horns curl there)
|
|
389
|
+
const cc = lerp(J.neckMid, J.occiput, 0.44 - collar * 4);
|
|
390
|
+
add1(m.ell({ tag: 'woolneck', bone: 'neck2', c: add(cc, [0, -0.012, -0.016]), r: [0.05 * nk + nf * 0.62, 0.05 + nf * 0.36, 0.048 + nf * 0.26], axis: norm(sub(J.occiput, J.neckMid)), up: [0, 1, -0.4], k: kF * 0.8 }));
|
|
391
|
+
// the hood: the wool thins toward the head (a tapered sleeve from behind the ears back into the
|
|
392
|
+
// collar), so the face comes out of a short, even edge of wool instead of a wall standing off the cheek
|
|
393
|
+
// (its top stays level with the poll: the head stands out of the fleece, not inside a cowl)
|
|
394
|
+
// (and a short ruff where it starts, standing 1.5-3 cm off the skin behind the jaw and the ear,
|
|
395
|
+
// so the face has an edge: with the hood flush with the head the white face ran on into the neck)
|
|
396
|
+
const hoodA = add(lerp(J.occiput, J.neckMid, 0.12 + collar * 3), [0, -0.058, -0.012]), hoodB = add(lerp(J.occiput, J.neckMid, 0.75), [0, -0.02, 0]);
|
|
397
|
+
add1(m.cone({ tag: 'woolneck', bone: 'neck2', a: hoodA, b: hoodB, ra: 0.048 + nf * 0.3, rb: 0.058 * nk + nf * 0.6, k: kF * 0.6 }));
|
|
398
|
+
// poll topknot (white-faced / Merino: a tuft of wool on the forehead)
|
|
399
|
+
if ((params.topknot || 0) > 0.05) {
|
|
400
|
+
const tk = params.topknot;
|
|
401
|
+
add1(m.ell({ tag: 'woolpoll', bone: 'head', c: hl([0, 0.03, -0.055 + 0.015 * tk]), r: [0.032 + 0.018 * tk, 0.01 + F * 0.15 * tk, 0.028 + 0.022 * tk], axis: HZ, up: HY, k: 0.02 }));
|
|
402
|
+
}
|
|
403
|
+
// Merino: wool over the face to the eyes, the cheeks and down the legs
|
|
404
|
+
if (merino) {
|
|
405
|
+
for (const s of [1, -1]) add1(m.ell({ tag: 'woolcheek', bone: 'head', c: hl([0.036 * s, -0.035, -0.05]), r: [0.03 + F * 0.25, 0.045, 0.045], axis: HZ, up: HY, k: 0.025 }));
|
|
406
|
+
}
|
|
407
|
+
// legs: the wool covers the upper arm to the elbow, the thigh and the gaskin to above the hock
|
|
408
|
+
for (const S of ['L', 'R']) {
|
|
409
|
+
const s = S === 'L' ? 1 : -1;
|
|
410
|
+
const sx = (v) => [v[0] * s, v[1], v[2]];
|
|
411
|
+
const Sh = J['shoulder' + S], E = J['elbow' + S], W = J['wrist' + S];
|
|
412
|
+
const Hp = J['hip' + S], K = J['knee' + S], Hk = J['hock' + S];
|
|
413
|
+
const fl = F * (merino ? 0.85 : 0.7);
|
|
414
|
+
add1(m.ell({ tag: 'woolarm', bone: 'humerus' + S, group: 'F' + S, c: add(lerp(Sh, E, 0.55), sx([0.008, 0, -0.01])), r: [0.045 + fl * 0.8, 0.1 + fl * 0.5, 0.06 + fl * 0.7], axis: norm(sub(E, Sh)), up: [0, 0, 1], k: kF }));
|
|
415
|
+
add1(m.cone({ tag: 'woolleg', bone: 'radius' + S, group: 'F' + S, a: add(E, [0, 0.01, -0.01]), b: lerp(E, W, merino ? 0.75 : 0.28), ra: 0.035 + fl * 0.55, rb: 0.024 + fl * (merino ? 0.25 : 0.35), k: 0.03 }));
|
|
416
|
+
add1(m.ell({ tag: 'woolthigh', bone: 'femur' + S, group: 'H' + S, c: add(lerp(Hp, K, 0.5), sx([0.02, -0.01, -0.04])), r: [0.05 + fl * 0.8, 0.14 + fl * 0.4, 0.1 + fl * 0.8], axis: norm(sub(K, Hp)), up: [0, 0, 1], k: kF }));
|
|
417
|
+
add1(m.cone({ tag: 'woolleg', bone: 'tibia' + S, group: 'H' + S, a: add(K, sx([0.0, 0.0, -0.03])), b: add(lerp(K, Hk, merino ? 0.85 : 0.62), [0, 0, -0.012]), ra: 0.045 + fl * 0.6, rb: 0.026 + fl * (merino ? 0.3 : 0.35), k: 0.03 }));
|
|
418
|
+
}
|
|
419
|
+
// tail wool
|
|
420
|
+
const tailLen = params.tailLen ?? 0.34;
|
|
421
|
+
for (let i = 0; i < TAIL_SEGS; i++) {
|
|
422
|
+
const t0 = i / TAIL_SEGS, t1 = (i + 1) / TAIL_SEGS;
|
|
423
|
+
const a = J['tail' + i], b = J['tail' + (i + 1)];
|
|
424
|
+
const wf = Math.min(F, 0.04) * (tailLen > 0.15 ? 0.6 : 1.2);
|
|
425
|
+
add1(m.cone({ tag: 'wooltail', bone: 'tail' + i, a, b, ra: 0.03 + wf * (1 - 0.3 * t0), rb: 0.03 * (1 - 0.35 * t1) + wf * (1 - 0.3 * t1), k: i === 0 ? 0.022 : 0.012, thin: i >= TAIL_SEGS - 2 }));
|
|
426
|
+
}
|
|
427
|
+
// Merino skin folds: rings of wool around the neck ("necklace") and over the breast
|
|
428
|
+
const folds = merino ? (params.folds ?? 0.6) : 0;
|
|
429
|
+
if (folds > 0.05) {
|
|
430
|
+
for (let i = 0; i < 4; i++) {
|
|
431
|
+
const t = 0.15 + i * 0.22;
|
|
432
|
+
const c = lerp(J.neckBase, J.occiput, t * 0.8);
|
|
433
|
+
const rr = (0.075 * nk + nf) * (1.02 - 0.2 * t);
|
|
434
|
+
add1(m.ell({ tag: 'fold', bone: t < 0.45 ? 'neck1' : 'neck2', c: add(c, [0, -0.01, 0.01]), r: [rr + 0.012 * folds, rr * 1.1 + 0.012 * folds, 0.018 + 0.008 * folds], axis: norm(sub(J.occiput, J.neckBase)), up: [0, 1, -0.5], k: 0.03 }));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ---- locks: a Poisson-disc scatter of staple bundles over the fleece surface
|
|
439
|
+
if (F < 0.016) return;
|
|
440
|
+
const rl = lockRadius(F, merino); // lock radius
|
|
441
|
+
const spacing = rl * 1.55;
|
|
442
|
+
const list = fleece.filter((p) => !p.carve);
|
|
443
|
+
const f = (p) => SDFModel.evalList(list, p[0], p[1], p[2]);
|
|
444
|
+
const grad = (p) => {
|
|
445
|
+
const e = 0.001;
|
|
446
|
+
return norm([f([p[0] + e, p[1], p[2]]) - f([p[0] - e, p[1], p[2]]), f([p[0], p[1] + e, p[2]]) - f([p[0], p[1] - e, p[2]]), f([p[0], p[1], p[2] + e]) - f([p[0], p[1], p[2] - e])]);
|
|
447
|
+
};
|
|
448
|
+
const cell = spacing;
|
|
449
|
+
const hash = new Map();
|
|
450
|
+
const key = (p) => `${Math.floor(p[0] / cell)},${Math.floor(p[1] / cell)},${Math.floor(p[2] / cell)}`;
|
|
451
|
+
const near = (p) => {
|
|
452
|
+
const i0 = Math.floor(p[0] / cell), j0 = Math.floor(p[1] / cell), k0 = Math.floor(p[2] / cell);
|
|
453
|
+
for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) for (let k = -1; k <= 1; k++) {
|
|
454
|
+
const L = hash.get(`${i0 + i},${j0 + j},${k0 + k}`);
|
|
455
|
+
if (L) for (const q of L) if (Math.hypot(q[0] - p[0], q[1] - p[1], q[2] - p[2]) < spacing) return true;
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
};
|
|
459
|
+
// area weights: the big torso slabs get most of the samples
|
|
460
|
+
const wts = list.map((p) => (p.type === 0 ? p.P[12] * p.P[13] + p.P[13] * p.P[14] + p.P[12] * p.P[14] : 0.02));
|
|
461
|
+
const wsum = wts.reduce((a, b) => a + b, 0);
|
|
462
|
+
const tries = Math.round(Math.min(16000, 10 * wsum * 4 / (spacing * spacing)));
|
|
463
|
+
const locks = [];
|
|
464
|
+
for (let n = 0; n < tries; n++) {
|
|
465
|
+
let x = R() * wsum, pi = 0;
|
|
466
|
+
while (pi < list.length - 1 && x > wts[pi]) { x -= wts[pi]; pi++; }
|
|
467
|
+
const pr = list[pi];
|
|
468
|
+
let p;
|
|
469
|
+
if (pr.type === 0) {
|
|
470
|
+
// a random point on the ellipsoid's surface
|
|
471
|
+
const u = norm([R() * 2 - 1, R() * 2 - 1, R() * 2 - 1]);
|
|
472
|
+
const P = pr.P;
|
|
473
|
+
p = [0, 1, 2].map((a) => P[a] + P[3 + a] * u[0] * P[12] + P[6 + a] * u[1] * P[13] + P[9 + a] * u[2] * P[14]);
|
|
474
|
+
} else {
|
|
475
|
+
const P = pr.P, t = R();
|
|
476
|
+
p = [P[0] + P[3] * t + (R() - 0.5) * 0.05, P[1] + P[4] * t + (R() - 0.5) * 0.05, P[2] + P[5] * t + (R() - 0.5) * 0.05];
|
|
477
|
+
}
|
|
478
|
+
// project onto the fleece surface
|
|
479
|
+
let ok = true;
|
|
480
|
+
for (let it = 0; it < 6; it++) {
|
|
481
|
+
const d = f(p);
|
|
482
|
+
if (Math.abs(d) < 3e-4) break;
|
|
483
|
+
const g = grad(p);
|
|
484
|
+
p = sub(p, mul(g, d));
|
|
485
|
+
if (it === 5 && Math.abs(f(p)) > 0.003) ok = false;
|
|
486
|
+
}
|
|
487
|
+
if (!ok) continue;
|
|
488
|
+
// not on the face / in front of the collar, not under the hooves
|
|
489
|
+
// (and none at the hood's edge: lumps there stood off the cheek like a wall)
|
|
490
|
+
// (and clear of the head / body hand-over band)
|
|
491
|
+
if (neckS(p[0], p[1], p[2]) > -0.03) continue;
|
|
492
|
+
if (p[1] < 0.1) continue;
|
|
493
|
+
if (near(p)) continue;
|
|
494
|
+
const kk = key(p);
|
|
495
|
+
if (!hash.has(kk)) hash.set(kk, []);
|
|
496
|
+
hash.get(kk).push(p);
|
|
497
|
+
// owning primitive (bone and group) = the nearest fleece primitive
|
|
498
|
+
let best = null, bd = 1e9;
|
|
499
|
+
for (const q of list) { const d = SDFModel.dist(q, p[0], p[1], p[2]); if (d < bd) { bd = d; best = q; } }
|
|
500
|
+
locks.push({ p, n: grad(p), owner: best });
|
|
501
|
+
}
|
|
502
|
+
for (const L of locks) {
|
|
503
|
+
const n = L.n;
|
|
504
|
+
// staples fall down the sides, run back along the top; each lock a little different
|
|
505
|
+
const fall = stapleFall(n, L.p, J);
|
|
506
|
+
// (smaller toward the head, where the scatter stops: full-size locks ending on a line drew a long crevice
|
|
507
|
+
// and a step in the staple length across the collar)
|
|
508
|
+
const r = rl * (0.8 + 0.4 * R()) * (0.35 + 0.65 * smoothstep(-0.03, -0.1, neckS(L.p[0], L.p[1], L.p[2])));
|
|
509
|
+
const onLeg = L.owner.group !== 'axial';
|
|
510
|
+
// (low, staple-shaped blocks blended well in: an even fleece with soft creases between the staples,
|
|
511
|
+
// not deep cauliflower lumps whose crevices shadow into dark blotches under a low sun)
|
|
512
|
+
const c = sub(L.p, mul(n, r * (0.82 + 0.08 * R())));
|
|
513
|
+
m.ell({
|
|
514
|
+
tag: onLeg ? 'lockleg' : 'lock', bone: L.owner.bone, group: L.owner.group,
|
|
515
|
+
c, r: [r, r * 0.9, r * (1.3 + 0.35 * R())], axis: fall, up: n, k: 0.01 + 0.3 * r,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Dewclaws: two small horny knobs behind the fetlock
|
|
521
|
+
function sculptDewclaws(m, bone, group, M, s) {
|
|
522
|
+
for (const k of [1, -1]) m.sphere({ tag: 'dewclaw', bone, group, c: add(M, [0.009 * k * s, -0.006, -0.02]), rad: 0.0052, k: 0.004 });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Cloven hoof: two claws (digits III and IV) with a cleft between them, the front wall parallel to
|
|
526
|
+
// the pastern, heel bulbs behind, flat on the ground.
|
|
527
|
+
function sculptHoof(m, bone, group, C, T, w, s) {
|
|
528
|
+
const zc = (C[2] + T[2]) * 0.5;
|
|
529
|
+
for (const k of [1, -1]) {
|
|
530
|
+
const x = C[0] + 0.0102 * k * w;
|
|
531
|
+
const top = [x, C[1] + 0.008, C[2] - 0.004];
|
|
532
|
+
const toe = [x - 0.002 * k, 0.006, T[2] - 0.004];
|
|
533
|
+
const heel = [x, 0.008, C[2] - 0.015];
|
|
534
|
+
m.cone({ tag: 'hoof', bone, group, a: top, b: toe, ra: 0.0112 * w, rb: 0.0052 * w, k: 0.006 });
|
|
535
|
+
m.cone({ tag: 'hoof', bone, group, a: heel, b: [toe[0], 0.006, zc + 0.004], ra: 0.0108 * w, rb: 0.0088 * w, k: 0.008 });
|
|
536
|
+
m.sphere({ tag: 'heelbulb', bone, group, c: [x, 0.013, C[2] - 0.019], rad: 0.0102 * w, k: 0.008 });
|
|
537
|
+
}
|
|
538
|
+
m.cone({ tag: 'coronet', bone, group, a: add(C, [0, 0.012, -0.01]), b: add(C, [0, 0.006, 0.008]), ra: 0.0165 * w, rb: 0.0165 * w, k: 0.008 });
|
|
539
|
+
m.ell({ tag: 'cleft', bone, group, c: [C[0], 0.006, zc + 0.012], r: [0.0022, 0.02, 0.028], k: 0.002, carve: true });
|
|
540
|
+
m.ell({ tag: 'sole', bone, group, c: [C[0], -0.1 + 0.001, zc], r: [0.1, 0.1, 0.1], k: 0.002, carve: true });
|
|
541
|
+
void s;
|
|
542
|
+
}
|