@bitefoundry/retrieviewer 0.0.3 → 0.0.4

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.
Files changed (254) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/PLUGIN.md +13 -10
  7. package/PREREQUISITES.md +95 -0
  8. package/PUPPY-MEMORY.md +102 -1
  9. package/README.ko.md +110 -0
  10. package/README.md +117 -59
  11. package/RELEASING.md +23 -12
  12. package/THIRD-PARTY-NOTICES.md +5 -3
  13. package/braille.mjs +35 -9
  14. package/examples/live.mjs +2 -2
  15. package/integrations/linux-overlay/overlay.py +2 -2
  16. package/integrations/macos-overlay/overlay.jxa +8 -6
  17. package/integrations/opencode/runtime.mjs +4 -4
  18. package/integrations/opencode/tui.tsx +1 -1
  19. package/integrations/windows-overlay/Overlay.cs +4 -4
  20. package/integrations/windows-overlay/protocol.mjs +11 -7
  21. package/package.json +34 -13
  22. package/puppy-comfort-sprites.mjs +25 -0
  23. package/puppy-companion-comfort-q5.bin.gz +0 -0
  24. package/puppy-companion-comfort.json +77 -0
  25. package/puppy-companion-life.mjs +49 -16
  26. package/puppy-companion-objects.mjs +12 -0
  27. package/puppy-frame-predictive.mjs +15 -9
  28. package/puppy-frame-store.mjs +2 -2
  29. package/puppy-paper-world.mjs +14 -0
  30. package/puppy-paper.mjs +65 -14
  31. package/retrieviewer-plugin.d.mts +1 -1
  32. package/retrieviewer-plugin.mjs +1 -1
  33. package/retrieviewer.d.mts +10 -4
  34. package/retrieviewer.mjs +20 -14
  35. package/locomotion-core.mjs +0 -21
  36. package/locomotion-puppy.animal +0 -0
  37. package/vendor/procedural-animals/package.json +0 -40
  38. package/vendor/procedural-animals/src/animal.js +0 -146
  39. package/vendor/procedural-animals/src/bake.js +0 -51
  40. package/vendor/procedural-animals/src/build.js +0 -81
  41. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  42. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  43. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  44. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  45. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  46. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  47. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  48. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  49. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  50. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  51. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  52. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  53. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  54. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  55. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  56. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  57. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  58. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  59. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  60. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  61. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  62. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  63. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  64. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  65. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  66. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  67. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  68. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  69. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  70. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  71. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  72. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  73. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  74. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  75. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  76. package/vendor/procedural-animals/src/index.d.ts +0 -98
  77. package/vendor/procedural-animals/src/index.js +0 -29
  78. package/vendor/procedural-animals/src/registry.js +0 -48
  79. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  80. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  81. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  82. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  83. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  84. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  85. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  86. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  87. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  88. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  89. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  90. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  91. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  92. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  93. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  94. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  95. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  96. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  97. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  98. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  99. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  100. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  101. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  102. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  103. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  104. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  105. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  106. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  107. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  108. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  109. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  110. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  111. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  112. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  113. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  114. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  115. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  116. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  117. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  118. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  119. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  120. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  121. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  122. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  123. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  124. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  125. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  126. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  127. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  128. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  129. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  130. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  131. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  132. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  133. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  134. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  135. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  136. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  137. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  138. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  139. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  140. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  141. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  142. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  143. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  144. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  145. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  146. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  147. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  148. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  149. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  150. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  151. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  152. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  153. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  154. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  155. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  156. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  157. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  158. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  159. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  160. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  161. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  162. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  163. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  164. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  165. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  166. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  167. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  168. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  169. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  170. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  171. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  172. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  173. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  174. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  175. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  176. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  177. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  178. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  179. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  180. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  181. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  182. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  183. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  184. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  185. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  186. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  187. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  188. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  189. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  190. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  191. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  192. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  193. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  194. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  195. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  196. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  197. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  198. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  199. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  200. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  201. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  202. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  203. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  204. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  205. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  206. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  207. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  208. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  209. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  210. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  211. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  212. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  213. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  214. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  215. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  216. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  217. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  218. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  219. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  220. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  221. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  222. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  223. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  224. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  225. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  226. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  227. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  228. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  229. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  230. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  231. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  232. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  233. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  234. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  235. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  236. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  237. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  238. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  239. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  240. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  241. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  242. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  243. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  244. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  245. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  246. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  247. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  248. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  249. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  250. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  251. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  252. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  253. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  254. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,590 +0,0 @@
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
- }