@bitefoundry/retrieviewer 0.0.3 → 0.0.5

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