@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,590 @@
|
|
|
1
|
+
// The goat's coat: breed colours (white Saanen, Alpine chamoisee / cou blanc / Toggenburg / British
|
|
2
|
+
// Alpine with the Swiss face stripes, pied, Nubian, Boer), countershading, the buck's long hair (mane,
|
|
3
|
+
// "pants" on the thighs), the beard and wattles, pink skin under white (nose, ears, udder), keratin
|
|
4
|
+
// horns with growth rings and cloven hooves, hair flow following the hair tracts.
|
|
5
|
+
// Works in the reference space (see rig.js); seeded variation picks the breed pattern and colours.
|
|
6
|
+
import { HEAD_O, HZ, HY, unFaceZ } from './rig.js';
|
|
7
|
+
import { hornPath, hornRidges, earFacing } from './sculpt.js';
|
|
8
|
+
import { SDFModel } from '../../core/sdf/sdf.js';
|
|
9
|
+
import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
|
|
10
|
+
import { srgb, mix3, MAT, smoothField } from '../../core/build/coatKit.js';
|
|
11
|
+
|
|
12
|
+
// sRGB swatches sampled from the reference photos.
|
|
13
|
+
// base = body colour, dorsal / belly = countershading, head, mark = the breed's second colour
|
|
14
|
+
// (face stripes, points, patches), markRegions = which masks carry it.
|
|
15
|
+
export const COATS = {
|
|
16
|
+
saanen: { base: 0xeeebdd, dorsal: 0xe8e4d4, belly: 0xf3f0e6, head: 0xefece0, mark: 0xeeebdd, masks: [], pink: 1 },
|
|
17
|
+
chamoisee: { base: 0x906f4e, dorsal: 0x7e5f40, belly: 0x2a2320, head: 0x8a6848, mark: 0x221d1b, masks: ['stripes', 'facecentre', 'dorsal', 'legs', 'belly', 'ears', 'muzzle'] },
|
|
18
|
+
coublanc: { base: 0xe2d9c6, dorsal: 0xd8ceb8, belly: 0xe6dfcf, head: 0x9a8c7c, mark: 0x1e1c1c, masks: ['rear', 'stripes', 'legsHind'] },
|
|
19
|
+
toggenburg: { base: 0x8a5a2b, dorsal: 0x7a4f26, belly: 0x9a6a3a, head: 0x7e5327, mark: 0xe9e3d6, masks: ['stripes', 'legs', 'ears', 'muzzle', 'tailpatch'] },
|
|
20
|
+
britishalpine: { base: 0x2d3035, dorsal: 0x282b2f, belly: 0x323336, head: 0x2d3035, mark: 0xe9e4d8, masks: ['stripes', 'legs', 'ears', 'muzzle', 'tailpatch'] },
|
|
21
|
+
pied: { base: 0xefebe0, dorsal: 0xe9e5d8, belly: 0xf3f0e7, head: 0xefebe0, mark: 0x1f1c1b, masks: ['patches'], pink: 0.7 },
|
|
22
|
+
piedbrown: { base: 0xefebe0, dorsal: 0xe9e5d8, belly: 0xf3f0e7, head: 0xefebe0, mark: 0x7a4a26, masks: ['patches'], pink: 0.7 },
|
|
23
|
+
// (Nubian red: a deep mahogany, not orange: 0x8a4f2a rendered a bright orange; saturation ~20 % lower, a little darker)
|
|
24
|
+
nubianred: { base: 0x7c4a30, dorsal: 0x6e4129, belly: 0x93613f, head: 0x7c4a30, mark: 0xe6ddcc, masks: ['spots'] },
|
|
25
|
+
nubiantan: { base: 0xb08a5c, dorsal: 0x9e7b50, belly: 0xc3a174, head: 0xa98458, mark: 0x2b2420, masks: ['dorsal'] },
|
|
26
|
+
nubianblack: { base: 0x2a2624, dorsal: 0x24201f, belly: 0x322d2a, head: 0x2a2624, mark: 0xd8cbb4, masks: ['spots'] },
|
|
27
|
+
boer: { base: 0xf0ede6, dorsal: 0xebe7de, belly: 0xf3f1ea, head: 0xf0ede6, mark: 0x74391f, masks: ['boerhead'], pink: 0.4 },
|
|
28
|
+
};
|
|
29
|
+
const PINK = 0xd9a08a, NOSE_DARK = 0x3a302c, HOOF = 0x3b3632, HOOF_PALE = 0xc8b394;
|
|
30
|
+
|
|
31
|
+
function palette(p) {
|
|
32
|
+
const C = COATS[p.coat] || COATS.saanen;
|
|
33
|
+
const k = p.coatShade || 0, l = p.coatLightness || 0;
|
|
34
|
+
const tw = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.06 * k + l), c[1] * (1 + l), c[2] * (1 - 0.06 * k + l)]; };
|
|
35
|
+
return {
|
|
36
|
+
base: tw(C.base), dorsal: tw(C.dorsal), belly: tw(C.belly), head: tw(C.head), mark: srgb(C.mark),
|
|
37
|
+
masks: new Set(C.masks), pink: C.pink || 0,
|
|
38
|
+
skinPink: srgb(PINK), noseDark: srgb(NOSE_DARK), hoof: srgb(HOOF), hoofPale: srgb(HOOF_PALE),
|
|
39
|
+
horn: srgb(p.hornColor || 0x9e8058), hornDark: srgb(0x5a4a36),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const HOOF_TAGS = new Set(['hoof', 'heelbulb', 'dewclaw']);
|
|
44
|
+
|
|
45
|
+
export function goatCoat(ctx) {
|
|
46
|
+
const { model, rig, pos, nrm, regionOf, regionNames, weights, nV, params, lists } = ctx;
|
|
47
|
+
const { BONES } = rig;
|
|
48
|
+
const J = rig.J;
|
|
49
|
+
const R = rng(7331 + (params.coatSeed || 0));
|
|
50
|
+
const COL = palette(params);
|
|
51
|
+
// 0 for pale horn (Saanen) .. 1 for the dark Nubian / Boer horn (luminance of the linear horn colour)
|
|
52
|
+
const hornDark = 1 - smoothstep(0.12, 0.3, 0.2126 * COL.horn[0] + 0.7152 * COL.horn[1] + 0.0722 * COL.horn[2]);
|
|
53
|
+
const kid = params.age === 'juvenile';
|
|
54
|
+
const buck = params.sex === 'male' && !kid;
|
|
55
|
+
const shag = params.shaggy || 0; // long-haired landrace coat 0..1
|
|
56
|
+
const { dominant, axialS, skinIndex, skinWeight, limbMember } = weights;
|
|
57
|
+
const AX_LEN = weights.axialLengths;
|
|
58
|
+
const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
|
|
59
|
+
const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
|
|
60
|
+
const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8];
|
|
61
|
+
// the crest of the neck faces up and back (square to the neck axis, neck base -> occiput)
|
|
62
|
+
const ND = norm(sub(J.occiput, J.neckBase)), CREST_N = norm([0, ND[2], -ND[1]]);
|
|
63
|
+
const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
|
|
64
|
+
const jawPrims = model.forPart('jaw').filter((p) => p.tag !== 'beard');
|
|
65
|
+
const headPrims = model.forPart('body').filter((p) => p.bone === 'head' && !p.carve);
|
|
66
|
+
const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
|
|
67
|
+
const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof|femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name));
|
|
68
|
+
const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(b.name));
|
|
69
|
+
// (head-local, in adult coordinates: a kid's short face mapped back, rig.js faceZ)
|
|
70
|
+
const HL = (p) => { const d = sub(p, HEAD_O); return [d[0], dot(d, HY), unFaceZ(dot(d, HZ), kid)]; };
|
|
71
|
+
const partName = (v) => regionNames[regionOf[v]];
|
|
72
|
+
// a buck's scrotum (its own surface): the hair length where its neck enters the groin, shared with the body round it
|
|
73
|
+
const SCR = model.forPart('udder').filter((q) => q.tag === 'scrotum' && !q.carve), SCR_JOIN = 0.0065;
|
|
74
|
+
// position along the beard, 0 at its root under the chin, 1 at its tip (< 0: the chin in front of it)
|
|
75
|
+
const bTop = J.beardTop, bDir = sub(J.beardTip, J.beardTop), bLen2 = Math.max(1e-6, dot(bDir, bDir));
|
|
76
|
+
const beardT = (p) => dot(sub(p, bTop), bDir) / bLen2;
|
|
77
|
+
const horns = [hornPath(params, 1), hornPath(params, -1)];
|
|
78
|
+
const RIDGES = hornRidges(params);
|
|
79
|
+
// nearest point of a horn's centre line: t (0 base .. 1 tip) and the line's direction there, blended
|
|
80
|
+
// between the path points (per segment, the keratin's anisotropic sheen lit whole segments as bands)
|
|
81
|
+
const hornTan = horns.map((H) => H.map((q, i) => norm(sub(H[Math.min(i + 1, H.length - 1)].p, H[Math.max(i - 1, 0)].p))));
|
|
82
|
+
const hornAt = (p) => {
|
|
83
|
+
const S = p[0] >= 0 ? 0 : 1, H = horns[S], T = hornTan[S];
|
|
84
|
+
let bt = 0, bd = 1e9, dir = [0, 1, 0];
|
|
85
|
+
for (let i = 0; i + 1 < H.length; i++) {
|
|
86
|
+
const a = H[i].p, b = H[i + 1].p, ab = sub(b, a);
|
|
87
|
+
const u = clamp(dot(sub(p, a), ab) / dot(ab, ab), 0, 1);
|
|
88
|
+
const dd = len(sub(p, add(a, mul(ab, u))));
|
|
89
|
+
if (dd < bd) { bd = dd; bt = H[i].t + (H[i + 1].t - H[i].t) * u; dir = add(mul(T[i], 1 - u), mul(T[i + 1], u)); }
|
|
90
|
+
}
|
|
91
|
+
return { t: bt, dir: norm(dir) };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// the inner face of an ear: its facing (sculpt.js) square to the ear bone
|
|
95
|
+
const earInner = (v) => {
|
|
96
|
+
const b = BONES[dominant[v]], ax = norm(sub(b.tail, b.head)), f = earFacing(params, pos[v * 3] >= 0 ? 1 : -1);
|
|
97
|
+
return norm(sub(f, mul(ax, dot(f, ax))));
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// --- nearest primitive tag per vertex
|
|
101
|
+
const tagOf = new Array(nV);
|
|
102
|
+
for (let v = 0; v < nV; v++) {
|
|
103
|
+
const L = lists[v], p = P(v);
|
|
104
|
+
let best = 1e9, t = '';
|
|
105
|
+
for (const pr of L) {
|
|
106
|
+
if (pr.carve) continue;
|
|
107
|
+
const d = SDFModel.dist(pr, p[0], p[1], p[2]);
|
|
108
|
+
if (d < best) { best = d; t = pr.tag; }
|
|
109
|
+
}
|
|
110
|
+
tagOf[v] = t;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- regions: 0 torso, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw, 7 horn
|
|
114
|
+
const region = new Uint8Array(nV);
|
|
115
|
+
const legness = new Float32Array(nV);
|
|
116
|
+
const legBone = new Int16Array(nV).fill(-1);
|
|
117
|
+
for (let v = 0; v < nV; v++) {
|
|
118
|
+
const bn = BONES[dominant[v]].name, p = P(v), pn = partName(v);
|
|
119
|
+
let lb = -1, lw = 0;
|
|
120
|
+
for (let k = 0; k < 4; k++) {
|
|
121
|
+
const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
|
|
122
|
+
if (isLimb[b] && w > lw) { lw = w; lb = b; }
|
|
123
|
+
}
|
|
124
|
+
legBone[v] = lb;
|
|
125
|
+
legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
|
|
126
|
+
if (pn === 'horn' || pn === 'horntip') region[v] = 7;
|
|
127
|
+
else if (pn === 'jaw' || pn === 'beard') region[v] = 6;
|
|
128
|
+
else if (bn.startsWith('ear')) region[v] = 5;
|
|
129
|
+
else if (bn.startsWith('tail') && axialS[v] > sTailBase + 0.01) region[v] = 4;
|
|
130
|
+
else if (axialS[v] < sOcc + 0.01) region[v] = 2;
|
|
131
|
+
else if (axialS[v] < sNeckBase) region[v] = 1;
|
|
132
|
+
else region[v] = 0;
|
|
133
|
+
if (region[v] >= 2) legness[v] = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- pied patches: a handful of big irregular blotches (seeded), plus a chance of a dark head
|
|
137
|
+
const patches = [];
|
|
138
|
+
if (COL.masks.has('patches') || COL.masks.has('spots')) {
|
|
139
|
+
const spots = COL.masks.has('spots');
|
|
140
|
+
const n = spots ? 10 + Math.floor(R() * 10) : 4 + Math.floor(R() * 4);
|
|
141
|
+
for (let i = 0; i < n; i++) {
|
|
142
|
+
// (on the barrel, the back and the rump: a border across the upper neck or the throat smeared whenever
|
|
143
|
+
// the head went down to graze, one across an elbow or a stifle with every stride)
|
|
144
|
+
if (spots) { patches.push({ c: [(R() * 2 - 1) * 0.18, 0.55 + R() * 0.25, -0.35 + R() * 0.55], r: 0.02 + 0.03 * R() }); continue; }
|
|
145
|
+
// pied: big irregular blotches, a third of them saddles over the back (both sides), the rest on one side,
|
|
146
|
+
// centred on the skin (centres anywhere in the body's box mostly lay inside it, and showed as one small round
|
|
147
|
+
// spot per side or none: the random herd's pied does were nearly all white)
|
|
148
|
+
// (each blotch on the barrel and rump, z -0.44 .. 0.14, above the elbows and stifles: over the shoulder blade a
|
|
149
|
+
// kid's border stretched 2x on 17 % of the triangles near pattern edges, low on the flank 1.5x on 28 %)
|
|
150
|
+
const saddle = R() < 0.35, r = saddle ? 0.1 + 0.05 * R() : 0.07 + 0.06 * R(), ez = 0.75 + 0.25 * R(), ext = r / ez;
|
|
151
|
+
const z = mix(-0.44 + ext, 0.14 - ext, R());
|
|
152
|
+
if (saddle) patches.push({ c: [0, 0.72 + 0.05 * R(), z], r, ez });
|
|
153
|
+
else { const sd = R() < 0.5 ? 1 : -1; patches.push({ c: [sd * (0.11 + 0.04 * R()), 0.56 + 0.2 * R(), z], r, ez }); }
|
|
154
|
+
}
|
|
155
|
+
// a dark head and neck on most (a hood down to the neck base; a round patch on the head ran its border round
|
|
156
|
+
// the eye and across the cheek, which stretches with the throat when the head goes down)
|
|
157
|
+
if (!spots && R() < 0.7) patches.push({ c: hl0([0, 0.02, 0.05]), r: 0.1, head: true, t: 0.3 + 0.35 * R() });
|
|
158
|
+
}
|
|
159
|
+
function hl0(v) { return [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]]; }
|
|
160
|
+
|
|
161
|
+
// --- the breed's second colour as a signed distance field (< 0 inside), jagged edges
|
|
162
|
+
const markSDFof = (v) => {
|
|
163
|
+
const p = P(v), n = N(v), r = region[v], M = COL.masks;
|
|
164
|
+
if (r === 7) return 1;
|
|
165
|
+
const jag = (fbm3(p[0] * 45, p[1] * 45, p[2] * 45, 2) - 0.5) * (r === 2 || r === 5 || r === 6 ? 0.005 : 0.02);
|
|
166
|
+
let d = 1;
|
|
167
|
+
const lb = legBone[v];
|
|
168
|
+
const L = legness[v];
|
|
169
|
+
if (r === 2 || r === 6 || r === 5) {
|
|
170
|
+
const h = HL(p), ax = Math.abs(h[0]);
|
|
171
|
+
// Swiss markings: a stripe from above each eye down the side of the face to the muzzle
|
|
172
|
+
if (M.has('stripes') && (r === 2 || r === 6)) {
|
|
173
|
+
// on the face's cross-section (head-local x, y about the face's centre line), a band at an
|
|
174
|
+
// angle from the dorsal midline: from the inner corner of the eye down the side of the nasal
|
|
175
|
+
// bone to the corner of the muzzle; capped at both ends
|
|
176
|
+
const z0 = -0.015, z1 = 0.165;
|
|
177
|
+
const zc = clamp(h[2], z0, z1), tt = (zc - z0) / (z1 - z0);
|
|
178
|
+
const yC = mix(-0.018, -0.03, tt), rad = Math.hypot(ax, h[1] - yC);
|
|
179
|
+
const ang = Math.atan2(ax, h[1] - yC), angC = mix(0.95, 0.72, tt), wA = mix(0.2, 0.26, tt);
|
|
180
|
+
const dz = Math.max(z0 - h[2], h[2] - z1, 0);
|
|
181
|
+
const across = (Math.abs(ang - angC) - wA) * rad;
|
|
182
|
+
d = Math.min(d, dz > 0 ? Math.hypot(Math.max(across, 0), dz) : across);
|
|
183
|
+
}
|
|
184
|
+
// dark face centre (chamoisee / Oberhasli: a black band from the forehead down the nose)
|
|
185
|
+
if (M.has('facecentre') && r === 2 && h[1] > -0.02) d = Math.min(d, Math.max(ax - 0.016 * (1 - 0.35 * clamp(h[2] / 0.16, 0, 1)), -0.03 - h[2], h[2] - 0.17));
|
|
186
|
+
if (M.has('muzzle') && (r === 2 || r === 6)) d = Math.min(d, 0.155 - h[2] + (h[1] > -0.02 ? 0.01 : 0));
|
|
187
|
+
// ear edges and tip in the second colour (erect ears; a hanging lop ear's outer 38 % was a black disc on
|
|
188
|
+
// the side of a tan Nubian's head, and a black Nubian's light ears a pale oval on a black head: lop ears
|
|
189
|
+
// are the colour of the head)
|
|
190
|
+
if (M.has('ears') && r === 5 && params.ear !== 'lop') {
|
|
191
|
+
const eb = BONES[dominant[v]], ab = sub(eb.tail, eb.head);
|
|
192
|
+
const t = dot(sub(p, eb.head), ab) / dot(ab, ab);
|
|
193
|
+
d = Math.min(d, (0.62 - t) * 0.06);
|
|
194
|
+
}
|
|
195
|
+
if (M.has('boerhead')) d = Math.min(d, -0.02);
|
|
196
|
+
}
|
|
197
|
+
if (M.has('boerhead') && r !== 5) {
|
|
198
|
+
// red-brown head and neck, white body: the border runs round the middle of the lower neck (side_boer:
|
|
199
|
+
// the lower neck and the shoulders are white; at the base of the neck it also lay where the skin
|
|
200
|
+
// stretches most as the head goes down to graze)
|
|
201
|
+
// (the legs stay white below the elbows: a leg's skin sits at chest level along the axis; above the
|
|
202
|
+
// elbows the leg term left white islands in the red of the chest front)
|
|
203
|
+
const s = axialS[v];
|
|
204
|
+
d = Math.min(d, (s - 0.5 * (AX_LEN[2] + sNeckBase)) + (fbm3(p[0] * 20, p[1] * 20, p[2] * 20, 2) - 0.5) * 0.05 + 0.1 * smoothstep(0.1, 0.5, legness[v]) * smoothstep(0.44, 0.38, p[1]));
|
|
205
|
+
}
|
|
206
|
+
if (r === 0 || r === 1) {
|
|
207
|
+
if (M.has('dorsal')) d = Math.min(d, Math.abs(p[0]) - 0.012 - 0.006 * smoothstep(0.3, -0.4, p[2]) + (1 - smoothstep(0.7, 0.95, n[1])) * 0.05);
|
|
208
|
+
// chamoisee: the black belly; where the legs join the body it hands over to the legs' rule (below)
|
|
209
|
+
// over a band of leg membership (a switch at 0.2 stepped the border from 0.43 to 0.5 m: black
|
|
210
|
+
// blocks at the elbows and stifles)
|
|
211
|
+
if (M.has('belly')) {
|
|
212
|
+
const torso = p[1] - 0.43;
|
|
213
|
+
let leg = 1;
|
|
214
|
+
if (lb >= 0) { const front = isFront[lb]; leg = p[1] - 0.5 + 0.12 * smoothstep(-0.2, 0.7, n[2] * (front ? 1 : -1)); }
|
|
215
|
+
d = Math.min(d, mix(torso, leg, smoothstep(0.1, 0.6, L)));
|
|
216
|
+
}
|
|
217
|
+
// cou blanc: the black hindquarters, an irregular slanting border from behind the withers to the
|
|
218
|
+
// flank, grizzled (it was a vertical plane with 5 cm of noise: a straight cut round the barrel)
|
|
219
|
+
if (M.has('rear')) d = Math.min(d, (p[2] + 0.02 + 0.22 * (p[1] - 0.6)) * 0.9 + (fbm3(p[0] * 6, p[1] * 6, p[2] * 6, 3) - 0.5) * 0.12 + (vnoise3(p[0] * 90, p[1] * 90, p[2] * 90) - 0.5) * 0.012);
|
|
220
|
+
// Toggenburg / British Alpine: the light patch either side of the tail, over the pin bones (it was
|
|
221
|
+
// inside only in front of z -0.47: a white bib on the throat and brisket)
|
|
222
|
+
if (M.has('tailpatch')) { const zT = J.tailBase[2]; d = Math.min(d, Math.max(p[1] - 0.72, Math.hypot(p[0] * 1.1, (p[1] - 0.63) * 0.9) - 0.075, p[2] - (zT + 0.07))); }
|
|
223
|
+
}
|
|
224
|
+
if (r === 4) {
|
|
225
|
+
if (M.has('rear') || M.has('dorsal')) d = -0.01;
|
|
226
|
+
if (M.has('tailpatch')) d = Math.min(d, dot(n, [0, 1, 0]) < 0 ? -0.01 : 0.01);
|
|
227
|
+
}
|
|
228
|
+
if (L > 0.2 && lb >= 0) {
|
|
229
|
+
const front = isFront[lb];
|
|
230
|
+
const kneeY = front ? 0.3 : 0.34;
|
|
231
|
+
// points below the knees / hocks (chamoisee: black; Toggenburg / Brit. Alpine: white)
|
|
232
|
+
if (M.has('legs')) d = Math.min(d, p[1] - kneeY);
|
|
233
|
+
// (chamoisee: the black of the belly running down the backs of the legs is blended in above)
|
|
234
|
+
// cou blanc: the black hindquarters run down the hind legs
|
|
235
|
+
if (M.has('rear') && !front) d = Math.min(d, -0.01);
|
|
236
|
+
}
|
|
237
|
+
if (r !== 7 && patches.length) {
|
|
238
|
+
const wob = (fbm3(p[0] * 9, p[1] * 9, p[2] * 9, 3) - 0.5) * 0.08;
|
|
239
|
+
// (lobed, ragged blotches: a low-frequency wobble of +-6 cm and a ragged rim)
|
|
240
|
+
const lobe = (fbm3(p[0] * 4.5 + 3.1, p[1] * 4.5, p[2] * 4.5, 3) - 0.5) * 0.12 + (fbm3(p[0] * 28, p[1] * 28, p[2] * 28, 2) - 0.5) * 0.02;
|
|
241
|
+
// (the hood's border crosses the lower neck, where the skin rides rigidly on neck1)
|
|
242
|
+
for (const q of patches) d = Math.min(d, q.head ? (axialS[v] - mix(AX_LEN[2], sNeckBase, q.t)) * 0.8 + wob * 0.6 : Math.hypot(p[0] - q.c[0], (p[1] - q.c[1]) * 1.1, (p[2] - q.c[2]) * (q.ez || 0.85)) - q.r + (q.ez ? lobe : wob));
|
|
243
|
+
}
|
|
244
|
+
return d + jag;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// --- ventral factor (lighter belly, inner legs)
|
|
248
|
+
const ventral = new Float32Array(nV);
|
|
249
|
+
for (let v = 0; v < nV; v++) {
|
|
250
|
+
const n = N(v), r = region[v], p = P(v);
|
|
251
|
+
let w = 0;
|
|
252
|
+
if (r === 0) w = smoothstep(-0.2, -0.8, n[1]) * smoothstep(0.55, 0.42, p[1]);
|
|
253
|
+
else if (r === 1) w = smoothstep(0.0, -0.7, n[1]) * 0.5;
|
|
254
|
+
const L = legness[v];
|
|
255
|
+
if (L > 0) { const side = p[0] >= 0 ? 1 : -1; w = mix(w, smoothstep(0.1, -0.7, n[0] * side) * 0.6 * smoothstep(0.25, 0.45, p[1]), L); }
|
|
256
|
+
ventral[v] = w;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- a buck's mane: the crest of the neck and the withers (coarse long hair on the neck crest
|
|
260
|
+
// and the dorsal line), fading out along the back over ~20 cm behind the withers and down the sides of the neck
|
|
261
|
+
// (it ended behind the withers over the span of one axial joint, in a wall of hair)
|
|
262
|
+
const maneW = new Float32Array(nV);
|
|
263
|
+
if (buck && (params.mane || 0) > 0.05) {
|
|
264
|
+
for (let v = 0; v < nV; v++) {
|
|
265
|
+
// (the neck and body skin only: a wattle behind the jaw faces up the crest's way and grew a 27 mm mane)
|
|
266
|
+
if ((region[v] !== 0 && region[v] !== 1) || (partName(v) !== 'body' && partName(v) !== 'head')) continue;
|
|
267
|
+
const n = N(v), s = axialS[v], up = clamp(n[1], -1, 1);
|
|
268
|
+
// across: on the neck down the sides from the crest, behind the neck base a strip ~9 cm either side of the spine
|
|
269
|
+
// (the normal's turn over the withers ended it within 3 cm there: a bevel of 5 cm hair on both sides)
|
|
270
|
+
const wN = smoothstep(sNeckBase + 0.06, sNeckBase - 0.02, s);
|
|
271
|
+
const onNeck = smoothstep(0.12, 0.95, Math.max(up, dot(n, CREST_N)));
|
|
272
|
+
const onBack = (1 - smoothstep(0.02, 0.1, Math.abs(pos[v * 3]))) * smoothstep(-0.1, 0.4, up);
|
|
273
|
+
// (above the shoulders only: a leg's skin sits at the chest's axial position, and the upward faces of the
|
|
274
|
+
// coronets grew a mane)
|
|
275
|
+
maneW[v] = mix(onBack, onNeck, wN) * (1 - smoothstep(AX_LEN[4] - 0.06, AX_LEN[5] + 0.06, s)) * smoothstep(sOcc, sOcc + 0.08, s) * smoothstep(0.58, 0.66, pos[v * 3 + 1]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- comb (hair flow)
|
|
280
|
+
const comb = new Float32Array(nV * 3);
|
|
281
|
+
const whorl = [0, 0.03, -0.02]; // forehead whorl, head-local
|
|
282
|
+
for (let v = 0; v < nV; v++) {
|
|
283
|
+
const p = P(v), n = N(v), r = region[v], tag = tagOf[v];
|
|
284
|
+
const bone = BONES[dominant[v]];
|
|
285
|
+
let d;
|
|
286
|
+
if (r === 5) d = norm(sub(bone.tail, bone.head));
|
|
287
|
+
// (the horn's comb runs along the horn, base to tip: the keratin shader lays its striations and
|
|
288
|
+
// highlight along the comb and its growth rings across it; world-up ran 48-52 deg off the axis of a
|
|
289
|
+
// horn sweeping back and out, and the streaks swirled into a white net)
|
|
290
|
+
else if (r === 7) d = hornAt(p).dir;
|
|
291
|
+
else if (tag === 'beard' || tag === 'wattle') d = [0, -1, -0.1];
|
|
292
|
+
else if (r === 2 || r === 6) {
|
|
293
|
+
const h = HL(p);
|
|
294
|
+
const rel = [h[0] - whorl[0], (h[1] - whorl[1]) * 0.4, h[2] - whorl[2] + 0.05];
|
|
295
|
+
d = norm(add(mul(HY, rel[1] - 0.03), add(mul(HZ, rel[2]), [rel[0], 0, 0])));
|
|
296
|
+
if (h[2] < -0.05) d = norm(add(d, [0, -0.6, -0.8]));
|
|
297
|
+
} else if (r === 4) {
|
|
298
|
+
d = norm(sub(bone.tail, bone.head));
|
|
299
|
+
} else {
|
|
300
|
+
const seg = weights.segT[v * 2] | 0;
|
|
301
|
+
const a = rig.AXIAL.points[seg], b = rig.AXIAL.points[Math.min(seg + 1, rig.AXIAL.points.length - 1)];
|
|
302
|
+
d = norm(sub(b, a));
|
|
303
|
+
if (r === 0) d = norm(add(d, [0, -0.5 * Math.abs(n[0]), 0]));
|
|
304
|
+
if (r === 1) d = norm(add(d, [0, -0.6, 0.1]));
|
|
305
|
+
// (a buck's mane falls to both sides from the crest; by the mane's weight, not its sculpt tag: where the last
|
|
306
|
+
// mane primitive ended behind the withers the comb flipped from sideways to backward)
|
|
307
|
+
if (maneW[v] > 0) d = norm(add(d, [(p[0] >= 0 ? 0.45 : -0.45) * maneW[v], 0.1 * maneW[v], 0]));
|
|
308
|
+
const L = legness[v];
|
|
309
|
+
if (L > 0 && legBone[v] >= 0) {
|
|
310
|
+
const lbn = BONES[legBone[v]];
|
|
311
|
+
let dl = norm(sub(lbn.tail, lbn.head));
|
|
312
|
+
if (/hoof/.test(lbn.name)) dl = [0, -1, 0.1];
|
|
313
|
+
else dl = norm(add(dl, [0, -0.3, 0]));
|
|
314
|
+
d = norm(add(mul(d, 1 - L), mul(dl, L)));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
let t = sub(d, mul(n, dot(d, n)));
|
|
318
|
+
if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
|
|
319
|
+
t = norm(t);
|
|
320
|
+
comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// --- per-vertex fields
|
|
324
|
+
const pattern = new Float32Array(nV).fill(1);
|
|
325
|
+
const patInt = new Float32Array(nV);
|
|
326
|
+
const patCol = new Float32Array(nV * 3);
|
|
327
|
+
const markSDF = new Float32Array(nV).fill(1);
|
|
328
|
+
const furLen = new Float32Array(nV);
|
|
329
|
+
const tint = new Float32Array(nV * 4);
|
|
330
|
+
const surf = new Float32Array(nV * 4);
|
|
331
|
+
|
|
332
|
+
for (let v = 0; v < nV; v++) {
|
|
333
|
+
const p = P(v), n = N(v), r = region[v], tag = tagOf[v];
|
|
334
|
+
let col, mat = MAT.FUR, fl = 0.012, mark = 1, gloss = 0.5, pint = 1;
|
|
335
|
+
const msd = markSDFof(v);
|
|
336
|
+
const lb = legBone[v];
|
|
337
|
+
const lbName = lb >= 0 ? BONES[lb].name : '';
|
|
338
|
+
const white = COL.pink > 0 && msd > 0; // white-coated areas have pink skin underneath
|
|
339
|
+
if (r === 0 || r === 1) {
|
|
340
|
+
const up = clamp(n[1], -1, 1);
|
|
341
|
+
col = mix3(COL.base, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.8);
|
|
342
|
+
col = mix3(col, COL.belly, ventral[v] * (COL.masks.has('belly') ? 0.5 : 1));
|
|
343
|
+
fl = mix(0.009, 0.011, ventral[v]);
|
|
344
|
+
// (the neck's coat grows in behind the head over a few centimetres; a buck's long hair and mane and a
|
|
345
|
+
// kid's fluffy coat over 8 cm: against the head's 5 mm they stood up as a collar behind the poll)
|
|
346
|
+
// (the upper neck behind the poll, crest and sides: the skin there folds to 0.65 of its length as the poll opens to
|
|
347
|
+
// graze or drink, and its 9 mm hair stood apart in a band of disordered, light fur across the nape; its coat grows
|
|
348
|
+
// in over the next 10 cm instead)
|
|
349
|
+
const nape = r === 1 ? smoothstep(-0.1, 0.5, dot(n, CREST_N)) : 0;
|
|
350
|
+
const collar = smoothstep(sOcc + 0.06 * nape, sOcc + 0.08 + 0.06 * nape, axialS[v]);
|
|
351
|
+
if (r === 1) fl = mix(0.0035 + 0.0015 * (1 - nape), 0.009, smoothstep(sOcc + 0.06 * nape, sOcc + 0.04 + 0.08 * nape, axialS[v]));
|
|
352
|
+
// a buck's body coat is short and sleek like a doe's (15-40 mm; side2, walk1): only the
|
|
353
|
+
// dorsal line carries a ridge of coarser hair (2 cm over the whole side and a 25 % shaggy roll for every buck
|
|
354
|
+
// made every buck's barrel a sheep's fleece); the long-haired landrace (shag) keeps its skirt
|
|
355
|
+
const dorsal = smoothstep(0.7, 0.97, up);
|
|
356
|
+
fl += ((buck ? 0.007 : 0) * dorsal + shag * 0.035 * (0.4 + 0.6 * smoothstep(-0.3, 0.6, -up + 0.8))) * collar;
|
|
357
|
+
const L = legness[v];
|
|
358
|
+
if (L > 0) {
|
|
359
|
+
// (the pants grow in over the gaskin: cut at 0.4 m they ended in a wall round the thigh)
|
|
360
|
+
const pants = !isFront[lb] ? smoothstep(0.1, -0.8, n[2]) * smoothstep(0.33, 0.47, p[1]) : 0;
|
|
361
|
+
const lfl = mix(0.003, 0.008, smoothstep(0.1, 0.45, p[1])) + (buck ? 0.03 : 0.006) * pants + shag * 0.04 * smoothstep(0.15, 0.5, p[1]);
|
|
362
|
+
fl = mix(fl, lfl, L);
|
|
363
|
+
// carpal callus: bare dark patch on the front of the knee in adults
|
|
364
|
+
if (!kid && isFront[lb] && Math.abs(p[1] - 0.21) < 0.022 && n[2] > 0.5) { col = mix3(col, [0.08, 0.065, 0.055], 0.5); fl = Math.min(fl, 0.003); }
|
|
365
|
+
}
|
|
366
|
+
// the mane after the legs' blend (the withers' skin is partly the scapulae's, and their short hair cut the mane
|
|
367
|
+
// off behind the withers: a triangular fin of hair standing on the back)
|
|
368
|
+
if (maneW[v] > 0) fl = mix(fl, Math.max(fl, 0.022 + 0.028 * params.mane), maneW[v]);
|
|
369
|
+
if (tag === 'udder' || tag === 'teat') {
|
|
370
|
+
// the udder: its upper half covered in fine hair of the coat's colour, the lower part and the teats
|
|
371
|
+
// bare pink (or grey) skin (side2, threequarter1; it was a saturated salmon bag)
|
|
372
|
+
const ut = clamp((J.udderTop[1] - p[1]) / (J.udderTop[1] - J.udderBot[1]), 0, 1.3);
|
|
373
|
+
const skinCol = mix3(mix3(COL.skinPink, [0.03, 0.025, 0.022], COL.pink > 0 ? 0.1 : 0.45), [0.8, 0.78, 0.75], COL.pink > 0 ? 0.2 : 0);
|
|
374
|
+
// (the hair line is irregular and the hair thins out onto the skin over ~2 cm, the skin showing through: a level
|
|
375
|
+
// line at 62 % of the bag's height, where the fur met the bare skin, drew a straight, stitched ring round the udder)
|
|
376
|
+
const border = 0.56 + (fbm3(p[0] * 26 + 7.7, p[1] * 26, p[2] * 26, 2) - 0.5) * 0.24;
|
|
377
|
+
const bare = smoothstep(border - 0.04, border + 0.14, ut);
|
|
378
|
+
// (all of the bag is skin, the upper part under fine hair of the coat's colour: a fur / skin material border
|
|
379
|
+
// round the bag was drawn in the stair steps of the mesh)
|
|
380
|
+
// (the breed's second colour baked in: under a dark belly (chamoisee, cou blanc) the hair is dark and so is the
|
|
381
|
+
// skin; as skin the bag took no pattern colour, and black hair stood speckled on pink skin)
|
|
382
|
+
const inPat = smoothstep(0.004, -0.004, msd);
|
|
383
|
+
const hairCol = mix3(col, COL.mark, inPat);
|
|
384
|
+
const skinC = mix3(skinCol, mul(COL.mark, 1.6), inPat * (COL.mark[0] + COL.mark[1] < 0.2 ? 0.75 : 0));
|
|
385
|
+
mat = MAT.SKIN; gloss = mix(0.2, 0.35, bare); pint = 0;
|
|
386
|
+
if (tag === 'teat') { fl = 0; col = skinC; }
|
|
387
|
+
else { col = mix3(hairCol, skinC, 0.3 * smoothstep(0.1, border, ut) + 0.7 * bare); fl = mix(mix(0.006, 0.0025, smoothstep(0.15, border, ut)), 0, bare); }
|
|
388
|
+
}
|
|
389
|
+
// (the scrotum is covered in the coat: the groin's hair on its neck, where it enters the body, thinning to short
|
|
390
|
+
// fine hair over the bag; a fixed 4 mm from the groin down drew the neck's way into the body as a hard line)
|
|
391
|
+
if (tag === 'scrotum') fl = mix(0.005, SCR_JOIN, smoothstep(0.42, 0.49, p[1]));
|
|
392
|
+
if (/hoof|paw/.test(lbName) && HOOF_TAGS.has(tag)) {
|
|
393
|
+
const Cj = J[(lbName[0] === 'f' ? 'fcoffin' : 'hcoffin') + BONES[lb].side];
|
|
394
|
+
if (tag === 'dewclaw' || p[1] < Cj[1] + 0.006 + 0.35 * (p[2] - Cj[2])) {
|
|
395
|
+
mat = MAT.KERATIN; fl = 0; gloss = 0.5;
|
|
396
|
+
const pale = COL.pink > 0 && msd > 0 && params.coat !== 'boer' ? 1 : COL.masks.has('legs') && COL.mark[0] > 0.5 ? 0.7 : 0;
|
|
397
|
+
col = mix3(COL.hoof, COL.hoofPale, pale);
|
|
398
|
+
if (tag === 'heelbulb') col = mix3(col, [0.1, 0.08, 0.07], 0.4);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} else if (r === 4) {
|
|
402
|
+
col = mix3(COL.base, COL.dorsal, 0.5);
|
|
403
|
+
// the hair lengthens along the tail to a brush at the tip (a 28 mm coat from the root stood up as a
|
|
404
|
+
// ring round it against the rump's 10 mm)
|
|
405
|
+
const tt = clamp((axialS[v] - sTailBase) / Math.max(0.05, AX_LEN[AX_LEN.length - 1] - sTailBase), 0, 1);
|
|
406
|
+
fl = mix(0.012, 0.045, smoothstep(0.05, 0.9, tt)) + 0.02 * shag * tt;
|
|
407
|
+
} else if (r === 5) {
|
|
408
|
+
// ears: short hair outside, sparse inside with pink / dark skin showing
|
|
409
|
+
// (the inner side from the ear's own facing: the first 'ear' primitive's parameters, read as a
|
|
410
|
+
// direction, pointed along +x for both ears, so the right ear was pink on its back)
|
|
411
|
+
const front = dot(n, earInner(v));
|
|
412
|
+
col = COL.head;
|
|
413
|
+
fl = 0.004;
|
|
414
|
+
if (front > 0.3) {
|
|
415
|
+
if (params.ear !== 'lop') { fl = 0.0025; col = white ? mix3(col, COL.skinPink, 0.35) : mix3(col, [0.1, 0.08, 0.07], 0.3); }
|
|
416
|
+
else { fl = 0.003; col = white ? mix3(col, COL.skinPink, 0.2) : col; }
|
|
417
|
+
}
|
|
418
|
+
} else if (r === 7) {
|
|
419
|
+
// horn: dark base, lighter towards the tip, transverse growth ridges
|
|
420
|
+
const ha = hornAt(p), bt = ha.t;
|
|
421
|
+
// (the ridged base is dull, the worn tip polished: a uniform 0.4 lit the broad base as a metallic band)
|
|
422
|
+
// (dark horn is matte: the keratin's highlight and grazing sheen are the same on any albedo, so on the dark
|
|
423
|
+
// Nubian and Boer horns a 0.4 tip read as chrome and the bases as silver; face2 / face4 show a dull sheen.
|
|
424
|
+
// A negative keratin gloss is duller than 0 (core coatMaterial: -1 matte); never 0 itself, the shader's default)
|
|
425
|
+
mat = MAT.KERATIN; fl = 0; gloss = hornDark > 0.5 ? -mix(0.55, 0.25, smoothstep(0.15, 0.85, bt)) : mix(0.08, 0.4, smoothstep(0.15, 0.85, bt)) * (1 - 0.6 * hornDark);
|
|
426
|
+
col = mix3(COL.hornDark, COL.horn, smoothstep(0.0, 0.5, bt));
|
|
427
|
+
col = mix3(col, mul(COL.horn, 1.15), smoothstep(0.7, 1, bt) * 0.5);
|
|
428
|
+
// (narrow dark grooves between the growth ridges, most marked toward the base: side2, face4)
|
|
429
|
+
const hl = (params.horns?.len || 0.2) * bt;
|
|
430
|
+
const ring = Math.pow(0.5 + 0.5 * Math.sin(hl * 2 * Math.PI / 0.009), 3);
|
|
431
|
+
col = mul(col, 1 - 0.1 * ring * (1 - smoothstep(0.5, 0.9, bt)));
|
|
432
|
+
// (the growth ridges' grooves, in phase with the sculpted collars, sculpt.js: dark just before each ridge rises,
|
|
433
|
+
// the crest a little lighter; painted at every tier, the medium and coarser tiers have no sculpted ridges)
|
|
434
|
+
if (RIDGES) {
|
|
435
|
+
const u = (hl - RIDGES.s0) / RIDGES.S, fr = u - Math.floor(u);
|
|
436
|
+
const fade = (u > -0.6 ? 1 : 0) * (1 - smoothstep(0.55 * RIDGES.tEnd, RIDGES.tEnd + 0.08, bt));
|
|
437
|
+
const groove = Math.exp(-(((fr - 0.66) / 0.11) ** 2)), crest = Math.exp(-((Math.min(fr, 1 - fr) / 0.12) ** 2));
|
|
438
|
+
// (softer on pale horn: 0.3 drew a doe's pale horn in regular dark stripes)
|
|
439
|
+
col = mul(col, 1 + fade * (0.06 * crest - mix(0.16, 0.3, hornDark) * groove));
|
|
440
|
+
}
|
|
441
|
+
// (streaky shade along the horn: the noise is stretched along the horn's axis, not world-up)
|
|
442
|
+
const along = dot(p, ha.dir), q = sub(p, mul(ha.dir, along));
|
|
443
|
+
col = mul(col, 0.9 + 0.2 * vnoise3(q[0] * 300, q[1] * 300, q[2] * 300 + along * 60));
|
|
444
|
+
} else {
|
|
445
|
+
// head & jaw
|
|
446
|
+
col = COL.head;
|
|
447
|
+
fl = 0.005;
|
|
448
|
+
const h = HL(p);
|
|
449
|
+
// beard: long coarse hair that grows in from a short root under the chin (a 30 mm coat from the root
|
|
450
|
+
// stood up as a rim against the chin's 5 mm)
|
|
451
|
+
if (tag === 'beard') {
|
|
452
|
+
const bt = beardT(p);
|
|
453
|
+
fl = mix(0.008, 0.028 + 0.1 * Math.min(params.beard || 0, 0.2), smoothstep(-0.15, 0.4, bt));
|
|
454
|
+
col = mix3(COL.head, COL.base, 0.3);
|
|
455
|
+
}
|
|
456
|
+
// a buck's shaggy crown and forelock (face2, face4)
|
|
457
|
+
if (buck && tag !== 'beard') fl = Math.max(fl, 0.005 + 0.012 * smoothstep(0.06, -0.05, h[2]) * smoothstep(-0.005, 0.03, h[1]));
|
|
458
|
+
if (tag === 'wattle') fl = 0.006;
|
|
459
|
+
// nose leather and lips: pink skin under white, dark grey otherwise
|
|
460
|
+
const muz = smoothstep(0.155, 0.185, h[2]) * (tag === 'beard' ? 0 : 1);
|
|
461
|
+
if (muz > 0) { col = mix3(col, white || (COL.pink && msd > 0) ? COL.skinPink : COL.noseDark, muz * 0.45); fl = mix(fl, 0.0025, muz); }
|
|
462
|
+
if (h[2] < -0.08) col = mix3(col, COL.base, smoothstep(-0.08, -0.14, h[2]));
|
|
463
|
+
// eyelids: a thin margin of lid skin (pink-grey under white hair, dark otherwise) and a short lash
|
|
464
|
+
// line; the hair shortens toward the lid over ~10 mm (a 4 mm switch sank the eye in a fur ring)
|
|
465
|
+
for (const e of eyePrims) {
|
|
466
|
+
const de = Math.abs(SDFModel.dist(e, p[0], p[1], p[2]));
|
|
467
|
+
if (de < 0.0025) mark = Math.min(mark, de - (white ? 0.0008 : 0.0016));
|
|
468
|
+
if (de < 0.0014) { if (white) { mat = MAT.SKIN; col = mix3(COL.skinPink, COL.noseDark, 0.45); } else mat = MAT.DARK_SKIN; fl = 0; }
|
|
469
|
+
else if (de < 0.012) fl = Math.min(fl, mix(0.0012, fl, smoothstep(0.0014, 0.012, de)));
|
|
470
|
+
}
|
|
471
|
+
// nose: the bare pad (rhinarium) round the nostrils and down to the lip, pink-grey under white hair,
|
|
472
|
+
// dark grey otherwise; the nostrils are slits whose inside is darker, with no outline
|
|
473
|
+
// (a dark nose is satin skin, not the pebbled leather, ending in a 4 mm ramp of very short dark hair: the
|
|
474
|
+
// leather's fixed black against the red Boer head drew the pad's outline in stair steps of mesh cells, and its
|
|
475
|
+
// nostrils were black holes in black; the nostrils are dark slits on a dark grey-brown pad, as on the Saanen)
|
|
476
|
+
const noseCol = white ? srgb(0xbca59f) : COL.noseDark;
|
|
477
|
+
const noseMat = MAT.SKIN;
|
|
478
|
+
const padIn = white ? 1 : 0.84;
|
|
479
|
+
if (tag !== 'beard' && regionNames[regionOf[v]] !== 'jaw') {
|
|
480
|
+
// (a goat's bare nose is small: round the nostrils and between them; the rest of the muzzle has
|
|
481
|
+
// short fine hair with the skin showing through)
|
|
482
|
+
const pad = Math.hypot(h[0] / 0.025, (h[1] + 0.028) / 0.019);
|
|
483
|
+
const front = h[2] > 0.175 && dot(n, HZ) > 0.2;
|
|
484
|
+
if (front && pad < padIn) { mat = noseMat; fl = 0; col = noseCol; if (!white) { gloss = mix(0.6, 0.3, smoothstep(0.5, padIn, pad)); pint = 0; } }
|
|
485
|
+
else if (!white && h[2] > 0.165 && pad < 1.3) { const t = smoothstep(padIn, 1.3, pad); col = mix3(noseCol, msd < 0 ? COL.mark : col, t); pint = t; fl = Math.min(fl, 0.0008 + 0.0025 * t); }
|
|
486
|
+
else if (h[2] > 0.15 && pad < 1.8) fl = Math.min(fl, 0.0015 + 0.004 * smoothstep(1, 1.8, pad));
|
|
487
|
+
}
|
|
488
|
+
for (const e of nostrilPrims) {
|
|
489
|
+
const dn = SDFModel.dist(e, p[0], p[1], p[2]);
|
|
490
|
+
if (dn < 0.0012) { mat = noseMat; col = white ? srgb(0x7a5e58) : mul(noseCol, 0.35); fl = 0; if (!white) { gloss = 0.3; pint = 0; } }
|
|
491
|
+
}
|
|
492
|
+
// lips: where the head's surface meets the lower jaw (and the jaw's meets the head), a thin band of
|
|
493
|
+
// lip skin; the mouth (red) only where one surface lies inside the other, hidden while the mouth is
|
|
494
|
+
// closed (a red band 1.5 mm into the contact, wide and facing sideways at the corners, and black
|
|
495
|
+
// mark lines either side of it made a torn zigzag of the mouth line)
|
|
496
|
+
// (the mouth colour only 4 mm and more inside the other surface: more than a mesh cell, so no
|
|
497
|
+
// triangle that shows while the mouth is shut reaches it; the lip skin covers the band either side
|
|
498
|
+
// of the contact line)
|
|
499
|
+
const lip = () => { if (white) { mat = MAT.SKIN; col = srgb(0xa89390); } else mat = MAT.DARK_SKIN; fl = 0; };
|
|
500
|
+
// (lip skin only along the mouth line, above the chin: where the chin meets the head's jaw under
|
|
501
|
+
// the lower lip both surfaces keep their hair)
|
|
502
|
+
const mouthLine = h[1] > -0.08;
|
|
503
|
+
if (tag === 'beard') { /* long hair, no lips */ } else if (regionNames[regionOf[v]] !== 'jaw') {
|
|
504
|
+
const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
|
|
505
|
+
if (h[2] > 0.1) {
|
|
506
|
+
if (dj < -0.004) { mat = MAT.MOUTH; fl = 0; }
|
|
507
|
+
else if (!mouthLine) { /* chin seam */ }
|
|
508
|
+
else if (dj < 0.0015) lip();
|
|
509
|
+
else if (dj < 0.006) fl = Math.min(fl, mix(0.0012, fl, (dj - 0.0015) / 0.0045));
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
|
|
513
|
+
if (dh < -0.004) { mat = MAT.MOUTH; fl = 0; }
|
|
514
|
+
else if (!mouthLine) { /* chin seam */ }
|
|
515
|
+
// (the rim of the lower lip that shows below the upper lip is bare lip too, not a white tuft)
|
|
516
|
+
else if (dh < 0.0015 || (tag === 'lowerlip' && dh < 0.004)) lip();
|
|
517
|
+
else if (dh < 0.006) fl = Math.min(fl, mix(0.0012, fl, (dh - 0.0015) / 0.0045));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// the breed's second colour: its distance runs on through the bare features (lids, nostrils, lips,
|
|
521
|
+
// mouth, udder), so their borders are no pattern edges (reset to "outside" inside a coloured head, they
|
|
522
|
+
// were 61 % of the Boer's pattern-edge mesh edges, and stretched with the lips); only fur and
|
|
523
|
+
// hair-covered skin take its colour (intensity), keratin (hooves) stays out of it
|
|
524
|
+
const CAP = 0.02;
|
|
525
|
+
// (clamped on both sides: deep inside a pattern its distance jumped between regions, -0.3 on the head
|
|
526
|
+
// and -0.02 on an ear by the rules, and the shader's antialiasing width (fwidth of the pattern) opened a
|
|
527
|
+
// thin light line of the base colour along the ear root and down the neck behind a Boer's lop ear)
|
|
528
|
+
const pd = mat === MAT.KERATIN ? CAP : clamp(msd, -CAP, CAP);
|
|
529
|
+
const takes = mat === MAT.FUR || (mat === MAT.SKIN && tag !== 'teat');
|
|
530
|
+
// short dark coats shine (a black goat's flanks show a sheen in sun; they read as a flat silhouette)
|
|
531
|
+
// (long dark hair too: a British Alpine buck's back and neck read as a flat black shape in the showcase)
|
|
532
|
+
if (mat === MAT.FUR && col[0] + col[1] + col[2] < 0.12) gloss = fl < 0.02 ? 0.64 : 0.52;
|
|
533
|
+
// low-frequency colour variation
|
|
534
|
+
const cv = fbm3(p[0] * 7, p[1] * 7, p[2] * 7, 3) - 0.5;
|
|
535
|
+
if (mat === MAT.FUR) col = [col[0] * (1 + 0.12 * cv), col[1] * (1 + 0.1 * cv), col[2] * (1 + 0.08 * cv)];
|
|
536
|
+
// long hair: lock-to-lock shade streaks
|
|
537
|
+
if (tag === 'beard' || tag === 'mane' || fl > 0.03) {
|
|
538
|
+
const c = [comb[v * 3], comb[v * 3 + 1], comb[v * 3 + 2]];
|
|
539
|
+
const along = dot(p, c), q = sub(p, mul(c, along));
|
|
540
|
+
const st = vnoise3(q[0] * 160, q[1] * 160, q[2] * 160 + along * 4);
|
|
541
|
+
col = mul(col, 0.82 + 0.36 * st);
|
|
542
|
+
}
|
|
543
|
+
pattern[v] = pd; patInt[v] = takes ? pint : 0;
|
|
544
|
+
patCol[v * 3] = COL.mark[0]; patCol[v * 3 + 1] = COL.mark[1]; patCol[v * 3 + 2] = COL.mark[2];
|
|
545
|
+
markSDF[v] = mark;
|
|
546
|
+
furLen[v] = fl;
|
|
547
|
+
tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
|
|
548
|
+
surf[v * 4] = gloss;
|
|
549
|
+
}
|
|
550
|
+
// cavity shading: the groin, the inner thighs and the top of the udder lie in the shadow of the thighs and the belly
|
|
551
|
+
// (lit as open skin, the inner skin of the groin showed as a light, torn-looking wedge through the crease in front of
|
|
552
|
+
// the stifle when the leg swung forward). Occlusion from the sculpt (SDF samples 1.5-6 cm out along the normal) over
|
|
553
|
+
// the hindquarters' lower half: down to 0.35 between the thighs, 1 on open skin
|
|
554
|
+
{
|
|
555
|
+
const occl = [...model.forPart('body'), ...model.forPart('udder')].filter((q) => !q.carve && (q.group === 'axial' || q.group === 'udder' || q.group[0] === 'H'));
|
|
556
|
+
const near = occl.filter((q) => { const c = q.P; return c[1] < 0.7 && c[2] < 0.1 && c[2] > -0.55; });
|
|
557
|
+
for (let v = 0; v < nV; v++) {
|
|
558
|
+
const p = P(v);
|
|
559
|
+
if (p[1] > 0.56 || p[1] < 0.18 || p[2] > 0.06 || p[2] < -0.5 || Math.abs(p[0]) > 0.16) continue;
|
|
560
|
+
const n = N(v);
|
|
561
|
+
let occ = 0;
|
|
562
|
+
for (let i = 1; i <= 4; i++) {
|
|
563
|
+
const d = 0.015 * i;
|
|
564
|
+
const sd = SDFModel.evalList(near, p[0] + n[0] * d, p[1] + n[1] * d, p[2] + n[2] * d);
|
|
565
|
+
occ += Math.max(0, d - sd) / d / i;
|
|
566
|
+
}
|
|
567
|
+
const ao = clamp(1 - 0.55 * occ, 0.35, 1);
|
|
568
|
+
for (let k = 0; k < 3; k++) { tint[v * 4 + k] *= ao; patCol[v * 3 + k] *= ao; }
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// the scrotum's neck enters the groin under one coat: the body's hair round it shortens to the neck's own over ~4 cm
|
|
572
|
+
// (the buck's long inner pants and perineum hair stood round the neck as a fringe of cut shell edges, a collar that
|
|
573
|
+
// drew the bag as a separate object pushed into a slot)
|
|
574
|
+
if (SCR.length) {
|
|
575
|
+
for (let v = 0; v < nV; v++) {
|
|
576
|
+
if (partName(v) !== 'body' || tint[v * 4 + 3] !== MAT.FUR || furLen[v] <= SCR_JOIN) continue;
|
|
577
|
+
const p = P(v);
|
|
578
|
+
if (p[1] < 0.3 || p[1] > 0.62 || p[2] > -0.1 || p[2] < -0.5 || Math.abs(p[0]) > 0.12) continue;
|
|
579
|
+
const d = SDFModel.evalList(SCR, p[0], p[1], p[2]);
|
|
580
|
+
furLen[v] = mix(SCR_JOIN, furLen[v], smoothstep(0.006, 0.045, d));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// kids: a soft, fluffy coat (straight: 16 mm clumped fully and read as a lamb's wool)
|
|
584
|
+
if (kid) for (let v = 0; v < nV; v++) if (tint[v * 4 + 3] === MAT.FUR && region[v] <= 1) furLen[v] = Math.max(furLen[v], mix(0.005, 0.012, smoothstep(sOcc, sOcc + 0.08, axialS[v])));
|
|
585
|
+
smoothField(furLen, weights.neighbors, 2, (v) => tint[v * 4 + 3] !== MAT.FUR);
|
|
586
|
+
|
|
587
|
+
const patternColor = new Float32Array(nV * 4);
|
|
588
|
+
for (let v = 0; v < nV; v++) { patternColor[v * 4] = patCol[v * 3]; patternColor[v * 4 + 1] = patCol[v * 3 + 1]; patternColor[v * 4 + 2] = patCol[v * 3 + 2]; patternColor[v * 4 + 3] = patInt[v]; }
|
|
589
|
+
return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, stats: { patches: patches.length }, region, ventral };
|
|
590
|
+
}
|