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