@bitefoundry/retrieviewer 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +107 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +61 -2
  12. package/RELEASING.md +61 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/braille.mjs +45 -0
  16. package/examples/bridge.py +62 -0
  17. package/examples/embed.mjs +15 -0
  18. package/examples/live.mjs +40 -0
  19. package/examples/plugin.mjs +21 -0
  20. package/integrations/opencode/runtime.mjs +195 -0
  21. package/integrations/opencode/tui.tsx +59 -0
  22. package/locomotion-core.mjs +21 -0
  23. package/locomotion-puppy.animal +0 -0
  24. package/package.json +125 -4
  25. package/pet-q5-manifest.json +118 -0
  26. package/puppy-clean.mjs +51 -0
  27. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  28. package/puppy-companion-interactions-v3.json +1 -0
  29. package/puppy-companion-life.mjs +182 -0
  30. package/puppy-companion-objects.mjs +30 -0
  31. package/puppy-companion-sprites.mjs +14 -0
  32. package/puppy-continuous.mjs +57 -0
  33. package/puppy-face.mjs +46 -0
  34. package/puppy-frame-predictive.mjs +147 -0
  35. package/puppy-frame-store.mjs +108 -0
  36. package/puppy-mouth-anchor.json +1 -0
  37. package/puppy-mouth-anchor.mjs +33 -0
  38. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  39. package/puppy-paper-actions-v9.json +1 -0
  40. package/puppy-paper-actions.mjs +48 -0
  41. package/puppy-paper-approach-q5.bin.gz +0 -0
  42. package/puppy-paper-approach.json +1 -0
  43. package/puppy-paper-area.mjs +15 -0
  44. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  45. package/puppy-paper-arrival-seat.json +1 -0
  46. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  47. package/puppy-paper-behaviors.json +1 -0
  48. package/puppy-paper-behaviors.mjs +8 -0
  49. package/puppy-paper-contact.mjs +103 -0
  50. package/puppy-paper-director.mjs +21 -0
  51. package/puppy-paper-paw-transport.mjs +76 -0
  52. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  53. package/puppy-paper-rear-gallop.json +1 -0
  54. package/puppy-paper-seat-q5.bin.gz +0 -0
  55. package/puppy-paper-seat.json +14 -0
  56. package/puppy-paper-seat.mjs +13 -0
  57. package/puppy-paper-trick-timing.mjs +58 -0
  58. package/puppy-paper-world.mjs +108 -0
  59. package/puppy-paper.mjs +271 -0
  60. package/puppy-stable-meta.json +1606 -0
  61. package/puppy-stable.mjs +72 -0
  62. package/puppy-stride-meta.json +343 -0
  63. package/puppy-stride-tweens-q5.bin.gz +0 -0
  64. package/puppy-zoomies.mjs +27 -0
  65. package/puppy-zoomies30.mjs +12 -0
  66. package/retrieviewer-cli.mjs +81 -0
  67. package/retrieviewer-config.mjs +49 -0
  68. package/retrieviewer-install.mjs +157 -0
  69. package/retrieviewer-plugin.d.mts +44 -0
  70. package/retrieviewer-plugin.mjs +97 -0
  71. package/retrieviewer-stdio.mjs +38 -0
  72. package/retrieviewer.d.mts +85 -0
  73. package/retrieviewer.mjs +142 -0
  74. package/vendor/procedural-animals/LICENSE +21 -0
  75. package/vendor/procedural-animals/package.json +40 -0
  76. package/vendor/procedural-animals/src/animal.js +146 -0
  77. package/vendor/procedural-animals/src/bake.js +51 -0
  78. package/vendor/procedural-animals/src/build.js +81 -0
  79. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  80. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  81. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  82. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  83. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  84. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  85. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  86. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  87. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  88. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  89. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  90. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  91. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  92. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  93. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  94. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  95. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  96. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  97. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  98. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  99. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  100. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  101. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  102. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  103. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  104. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  105. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  106. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  107. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  108. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  109. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  110. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  111. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  112. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  113. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  114. package/vendor/procedural-animals/src/index.d.ts +98 -0
  115. package/vendor/procedural-animals/src/index.js +29 -0
  116. package/vendor/procedural-animals/src/registry.js +48 -0
  117. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  118. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  119. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  120. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  121. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  122. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  123. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  124. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  125. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  126. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  127. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  128. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  129. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  130. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  131. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  132. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  133. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  134. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  135. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  136. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  137. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  138. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  139. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  140. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  141. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  142. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  143. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  144. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  145. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  146. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  147. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  148. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  149. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  150. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  151. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  152. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  153. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  154. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  155. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  156. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  157. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  158. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  159. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  160. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  161. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  162. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  163. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  164. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  165. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  166. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  167. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  168. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  169. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  170. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  171. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  172. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  173. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  174. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  175. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  176. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  177. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  178. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  179. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  180. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  181. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  182. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  183. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  184. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  185. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  186. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  187. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  188. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  189. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  190. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  191. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  192. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  193. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  194. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  195. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  196. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  197. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  198. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  199. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  200. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  201. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  202. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  203. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  204. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  205. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  206. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  207. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  208. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  209. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  210. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  211. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  212. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  213. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  214. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  215. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  216. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  217. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  218. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  219. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  220. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  221. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  222. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  223. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  224. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  225. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  226. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  227. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  228. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  229. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  230. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  231. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  232. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  233. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  234. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  235. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  236. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  237. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  238. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  239. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  240. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  241. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  242. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  243. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  244. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  245. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  246. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  247. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  248. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  249. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  250. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  251. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  252. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  253. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  254. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  255. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  256. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  257. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  258. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  259. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  260. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  261. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  262. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  263. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  264. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  265. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  266. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  267. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  268. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  269. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  270. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  271. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  272. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  273. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  274. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  275. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  276. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  277. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  278. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  279. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  280. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  281. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  282. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  283. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  284. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  285. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  286. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  287. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  288. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  289. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  290. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  291. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  292. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,730 @@
1
+ // The pig's covering: bare skin (material 4, subsurface-lit) under sparse bristles (short thin hair
2
+ // shells: 10-30 mm on the body, longer along the dorsal midline, the jowls and the tail tip, fine on
3
+ // the ears), per breed:
4
+ // largewhite / landrace pink skin, white bristles, flushed ears, snout and belly
5
+ // duroc red (golden to mahogany) skin and bristles
6
+ // hampshire black with a white belt over the shoulders and both forelegs
7
+ // berkshire black with six white points (feet, face blaze and snout, tail tip)
8
+ // spotted pink with seeded black spots (Gloucester Old Spot / Pietrain)
9
+ // the rostral disc (moist pink or slate skin, dark nostrils), long lashes on the upper lids, pale or
10
+ // dark horn claws, ivory tusks, the teats, skin creases on the neck and behind the joints (darkened
11
+ // tint), seeded mud on outdoor pigs, and bristle flow nose -> tail, down the legs, back from the
12
+ // snout on the face.
13
+ import { HEAD_O, HZ, HY, TAIL_SEGS, SNOUT_K } from './rig.js';
14
+ import { neckS } from './regions.js';
15
+ import { EYE, tuskCones } from './sculpt.js';
16
+ import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
17
+ import { SDFModel } from '../../core/sdf/sdf.js';
18
+ import { norm, sub, dot, cross, len, add, mul, smoothstep, clamp, mix, rng, fbm3, vnoise3 } from '../../core/math/vec.js';
19
+ import { srgb, mix3, MAT, smoothField, projectToSurface } from '../../core/build/coatKit.js';
20
+
21
+ // sRGB swatches sampled from the reference photos. Pink skin is
22
+ // given as albedo (a little deeper than the lit photo values).
23
+ // (the white breeds paler and less saturated, s 0.15 (was 0.20, #ECC4BC): lw_belagro3 in sun reads
24
+ // s 0.11-0.14 lit and a mauve grey in shade; the showcase's tone mapping saturates a dim pink, and the old albedo
25
+ // turned terracotta to maroon wherever the sun did not reach)
26
+ export const COATS = {
27
+ pink: { body: 0xf2d6ce, dorsal: 0xf4ddd6, belly: 0xf0cdc5, flush: 0xeab2aa, ear: 0xeec2ba, disc: 0xe4aca6, hoof: 0xc8ab98, bristle: 0xf2ece2, dark: false },
28
+ // (the Duroc less saturated, s 0.49 (was 0.58), a touch lighter: lit it still matches du_boar7 /
29
+ // du_belagro1 / head_duroc_sau (133-153, 89-116, 73-108; s 0.29-0.45), and in shade, where the showcase's tone
30
+ // mapping crushes a dim red, the face under the ear read a near-black maroon, s 0.8, v 0.13, the eye lost in it)
31
+ red: { body: 0x9e6a58, dorsal: 0x94604f, belly: 0xa87462, flush: 0x9e6250, ear: 0x98604f, disc: 0xa87a6a, hoof: 0x4a3a32, bristle: 0xb87a48, dark: false },
32
+ // (the black breeds' charcoal skin, bk_porco / bk_boar: (30-80) sRGB over the body in daylight, median
33
+ // ~(50, 45, 50); #201D1D rendered (3-8) lit in the showcase, #3A3434 (18), a black hole of a face with the eye lost in it)
34
+ black: { body: 0x504847, dorsal: 0x413a3a, belly: 0x524948, flush: 0x524544, ear: 0x4a3f3f, disc: 0x5a4e4e, hoof: 0x3a3330, bristle: 0x2c2727, dark: true },
35
+ };
36
+ const WHITE = 0xe8d6cc; // white points / belt: white bristles over pink skin
37
+ // (tusks: ivory, the tip whitest (khaki in studio light at #F2EADB / #D9C9A8 under the keratin striations);
38
+ // mud: a warm grey-brown, wet #857060 to dry #A89C8C (threequarter2, face3; #8F8068 read olive))
39
+ const NOSTRIL = 0x5a3230, TEAT = 0xd49a8e, TUSK = 0xfbf6ec, TUSK_ROOT = 0xece2cc, MUD = 0x857060, MUD_DRY = 0xa89c8c;
40
+ // the mouth line (head-local): its half width on each surface, and where it starts to fade toward the corner
41
+ const LIP_W = 0.0008, LIP_Z0 = 0.045;
42
+ // inside the mouth (palate, gums, the inside of the lips): moist pink-red mucosa
43
+ // (a little less saturated: the inside of the lower lip read as a lipstick-red ring round the open mouth)
44
+ const MUCOSA = srgb(0x9a5552), TONGUE = 0xc47a72;
45
+
46
+ const BASE_OF = { largewhite: 'pink', landrace: 'pink', duroc: 'red', hampshire: 'black', berkshire: 'black', spotted: 'pink' };
47
+ // meshing cell per surface at the hero tier (regions.js)
48
+ const CELL = { body: 0.0078, head: 0.0041, feet: 0.0035, jaw: 0.003, tail: 0.002 };
49
+
50
+ function palette(p) {
51
+ const C = COATS[BASE_OF[p.variant] || 'pink'];
52
+ const k = p.coatShade || 0, l = p.coatLightness || 0;
53
+ const tw = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.06 * k + l), c[1] * (1 + l), c[2] * (1 - 0.05 * k + l)]; };
54
+ const out = {};
55
+ for (const [key, v] of Object.entries(C)) out[key] = typeof v === 'number' ? tw(v) : v;
56
+ out.white = srgb(WHITE);
57
+ out.spot = srgb(0x3a3436); // (Gloucester Old Spot: slate-black skin under black hair)
58
+ return out;
59
+ }
60
+
61
+ const mul3 = (a, b) => [a[0] * b[0], a[1] * b[1], a[2] * b[2]];
62
+ const segSD2 = (px, py, ax, ay, bx, by) => {
63
+ const dx = bx - ax, dy = by - ay, t = clamp(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0, 1);
64
+ return [Math.hypot(px - ax - dx * t, py - ay - dy * t), t];
65
+ };
66
+ // The veins on the back of an ear leaf, in leaf units (u 0 root .. 1 tip along the ear bone, w across, out from the
67
+ // midrib, both / the bone's length): signed distance to the nearest vein's edge (< 0 inside). A central vein and two
68
+ // marginal ones fanning out from the root, each with one or two branches toward the rim; widths 1.8 -> 0.5 % of the
69
+ // length (3.6 -> 1 mm wide on a 0.2 m ear), tapering; a little seeded variation per pig.
70
+ const VEINS = [
71
+ // [u0, w0, u1, w1, width0, width1]
72
+ [0.04, 0.0, 0.5, 0.02, 0.009, 0.006], [0.5, 0.02, 0.88, 0.0, 0.006, 0.0025],
73
+ [0.06, 0.05, 0.42, 0.17, 0.008, 0.0055], [0.42, 0.17, 0.8, 0.22, 0.0055, 0.0025],
74
+ [0.06, -0.05, 0.4, -0.15, 0.008, 0.0055], [0.4, -0.15, 0.78, -0.2, 0.0055, 0.0025],
75
+ [0.28, 0.12, 0.5, 0.29, 0.004, 0.002], [0.55, 0.19, 0.7, 0.31, 0.0035, 0.0018],
76
+ [0.3, -0.12, 0.52, -0.27, 0.004, 0.002], [0.6, -0.18, 0.72, -0.29, 0.0035, 0.0018],
77
+ [0.62, 0.015, 0.8, 0.11, 0.003, 0.0016], [0.66, 0.01, 0.82, -0.1, 0.003, 0.0016],
78
+ ];
79
+ function earVeinSD(u, w, seed) {
80
+ const j = ((seed % 13) / 13 - 0.5) * 0.04;
81
+ let d = 1;
82
+ for (const [u0, w0, u1, w1, r0, r1] of VEINS) {
83
+ const [dd, t] = segSD2(u, w, u0, w0 * (1 + j * 3), u1, w1 * (1 + j * 3) + j);
84
+ d = Math.min(d, dd - 0.8 * (r0 + (r1 - r0) * t));
85
+ }
86
+ return d;
87
+ }
88
+
89
+ export function pigCoat(ctx) {
90
+ const { model, rig, pos, nrm, regionOf: partOf, regionNames, weights, nV, params, lists } = ctx;
91
+ // lashes only where the shells are fine enough to draw them (hero, high): at medium and low 8-16
92
+ // shells draw a pale halo over the eye
93
+ const lashes = !ctx.Q || ctx.Q.res <= 1.35;
94
+ const { BONES } = rig;
95
+ const J = rig.J;
96
+ const COL = palette(params);
97
+ const piglet = params.age === 'juvenile';
98
+ const breed = params.variant || 'largewhite';
99
+ const { dominant, axialS, skinIndex, skinWeight, limbMember } = weights;
100
+ const AX_LEN = weights.axialLengths;
101
+ const P = (v) => [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
102
+ const N = (v) => [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
103
+ const sOcc = AX_LEN[1], sNeckBase = AX_LEN[3], sTailBase = AX_LEN[8];
104
+ const eyePrims = model.prims.filter((p) => p.tag === 'eyesocket');
105
+ const eyeFrames = [1, -1].map((s) => eyeFrameOf(EYE, HEAD_O, s));
106
+ const jawPrims = model.forPart('jaw');
107
+ const headPrims = model.forPart('body').filter((p) => (p.bone === 'head' || p.bone === 'snout') && !p.carve);
108
+ const nostrilPrims = model.prims.filter((p) => p.tag === 'nostril');
109
+ // tusks: root and tip per side (the colour runs from a creamy base to the white point along the tusk)
110
+ const tuskSegs = (params.tusks || 0) > 0.05 ? [1, -1].map((s) => tuskCones(params, s)) : [];
111
+ const tuskEnds = (params.tusks || 0) > 0.05 ? [1, -1].map((s) => { const C = tuskCones(params, s); return { root: C[0].a, tip: C[C.length - 1].b }; }) : [];
112
+ const JAW_BONE = rig.BONE.jaw ? rig.BONE.jaw.index : -1;
113
+ const isLimb = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof|femur|tibia|metatarsus|hpaw|hhoof)/.test(b.name));
114
+ const isFront = BONES.map((b) => /^(scapula|humerus|radius|metacarpus|fpaw|fhoof)/.test(b.name));
115
+ const HL = (p) => { const d = sub(p, HEAD_O); return [d[0], dot(d, HY), dot(d, HZ)]; };
116
+ const rname = regionNames;
117
+
118
+ // --- nearest primitive tag per vertex
119
+ const tagOf = new Array(nV);
120
+ for (let v = 0; v < nV; v++) {
121
+ const L = lists[v], p = P(v);
122
+ let best = 1e9, t = '';
123
+ for (const pr of L) {
124
+ if (pr.carve) continue;
125
+ const d = SDFModel.dist(pr, p[0], p[1], p[2]);
126
+ if (d < best) { best = d; t = pr.tag; }
127
+ }
128
+ tagOf[v] = t;
129
+ }
130
+
131
+ // --- regions: 0 torso / legs, 1 neck, 2 head, 4 tail, 5 ear, 6 jaw, 7 snout disc
132
+ const region = new Uint8Array(nV);
133
+ const legness = new Float32Array(nV);
134
+ const legBone = new Int16Array(nV).fill(-1);
135
+ const tailT = new Float32Array(nV); // 0 root .. 1 tip
136
+ for (let v = 0; v < nV; v++) {
137
+ const bn = BONES[dominant[v]].name, p = P(v), rn = rname[partOf[v]];
138
+ let lb = -1, lw = 0;
139
+ for (let k = 0; k < 4; k++) {
140
+ const b = skinIndex[v * 4 + k], w = skinWeight[v * 4 + k];
141
+ if (isLimb[b] && w > lw) { lw = w; lb = b; }
142
+ }
143
+ legBone[v] = lb;
144
+ legness[v] = lb >= 0 ? smoothstep(0.15, 0.85, limbMember[v]) : 0;
145
+ if (rn === 'jaw' || rn === 'tuskL' || rn === 'tuskR') region[v] = 6;
146
+ else if (bn.startsWith('ear')) region[v] = 5;
147
+ // (and the floor of the nostrils, which the snout cone's vertices form under the head bone: painted as head skin
148
+ // it showed as pink patches deep in the Berkshire's nostrils)
149
+ else if (bn === 'snout' || tagOf[v] === 'disc' || tagOf[v] === 'snoutend' || nostrilPrims.some((e) => SDFModel.dist(e, p[0], p[1], p[2]) < 0.003)) region[v] = 7;
150
+ else if (rn === 'tail' || (bn.startsWith('tail') && axialS[v] > sTailBase + 0.015)) region[v] = 4;
151
+ else if (rn === 'feet' || (lb >= 0 && limbMember[v] > 0.5 && p[1] < 0.5)) region[v] = 0;
152
+ else if (axialS[v] < sOcc + 0.02 || (rn === 'head' && neckS(p[0], p[1], p[2]) > 0.03)) region[v] = 2;
153
+ else if (axialS[v] < sNeckBase) region[v] = 1;
154
+ else region[v] = 0;
155
+ if (region[v] >= 2) legness[v] = 0;
156
+ if (rn === 'feet') legness[v] = 1;
157
+ if (region[v] === 4) tailT[v] = clamp((axialS[v] - sTailBase) / Math.max(1e-3, AX_LEN[AX_LEN.length - 1] - sTailBase), 0, 1);
158
+ }
159
+
160
+ // --- pattern: signed distance (m) to the second colour (< 0 inside), per breed
161
+ const R = rng(7717 + (params.coatSeed || 0));
162
+ const off = [R() * 100, R() * 100, R() * 100];
163
+ const toSD = (fn, p) => {
164
+ const e = 0.01, f0 = fn(p);
165
+ const gx = (fn([p[0] + e, p[1], p[2]]) - f0) / e, gy = (fn([p[0], p[1] + e, p[2]]) - f0) / e, gz = (fn([p[0], p[1], p[2] + e]) - f0) / e;
166
+ const g = Math.max(0.5, Math.hypot(gx, gy, gz));
167
+ return -f0 / g;
168
+ };
169
+ const rag = (p, f, a) => (fbm3(p[0] * f + off[0], p[1] * f + off[1], p[2] * f + off[2], 2) - 0.5) * a;
170
+ // spotted: seeded black blotches on the body, neck and head (Gloucester Old Spot: 4-12 spots, 5-15 cm;
171
+ // Pietrain: more, smaller): each one 2-3 overlapping lobes with a ragged, hairy outline, none on the snout.
172
+ // The centres are drawn in space and projected onto the sculpt, with a fixed number of random draws per
173
+ // candidate, so every quality tier gets the same spots (drawn from the mesh's vertex list, they moved
174
+ // when the tier changed); the nearest vertex only says whether a place may carry one.
175
+ const spots = [];
176
+ const offSnout = (v) => { if (region[v] === 7 || region[v] === 6) return false; if (region[v] !== 2) return true; return HL(P(v))[2] < 0.06; };
177
+ if (breed === 'spotted') {
178
+ const nSpots = params.spotCount ?? 8;
179
+ const body = model.forPart('body').filter((q) => !q.carve);
180
+ const cell = 0.025, grid = new Map(), gk = (x, y, z) => (Math.floor(x / cell) + 64) * 16384 + (Math.floor(y / cell) + 64) * 128 + (Math.floor(z / cell) + 64);
181
+ for (let v = 0; v < nV; v++) { const k = gk(pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]); let a = grid.get(k); if (!a) grid.set(k, (a = [])); a.push(v); }
182
+ const nearest = (p) => {
183
+ let best = -1, bd = 1e9;
184
+ for (let dx = -1; dx <= 1; dx++) for (let dy = -1; dy <= 1; dy++) for (let dz = -1; dz <= 1; dz++) {
185
+ const a = grid.get(gk(p[0] + dx * cell, p[1] + dy * cell, p[2] + dz * cell));
186
+ if (a) for (const v of a) { const d = (pos[v * 3] - p[0]) ** 2 + (pos[v * 3 + 1] - p[1]) ** 2 + (pos[v * 3 + 2] - p[2]) ** 2; if (d < bd) { bd = d; best = v; } }
187
+ }
188
+ return best;
189
+ };
190
+ // (off the neck and the limb junctions, whose skin stretches most when the legs swing and the head
191
+ // moves: a spot's edge there smeared; keep-outs from the joints, the same on every tier)
192
+ const allowed = (v) => v >= 0 && (region[v] === 0 || region[v] === 2) && legness[v] < 0.08 && tagOf[v] !== 'teat' && offSnout(v);
193
+ const segD = (p, a, b) => { const ab = sub(b, a), t = clamp(dot(sub(p, a), ab) / dot(ab, ab), 0, 1); return len(sub(p, add(a, mul(ab, t)))); };
194
+ const clearOf = (p, r) => {
195
+ if (p[2] > J.neckBase[2] - 0.03 && HL(p)[2] < -0.1) return false;
196
+ for (const S of ['L', 'R']) {
197
+ if (segD(p, J['scapTop' + S], J['elbow' + S]) < r + 0.1) return false;
198
+ if (segD(p, J['elbow' + S], add(J['elbow' + S], [0, 0.05, -0.16])) < r + 0.07) return false;
199
+ if (segD(p, J['hip' + S], J['knee' + S]) < r + 0.07) return false;
200
+ if (Math.hypot(...sub(p, J['knee' + S])) < r + 0.12) return false;
201
+ }
202
+ return true;
203
+ };
204
+ for (let i = 0; i < 400 && spots.length < nSpots; i++) {
205
+ const q = [(R() - 0.5) * 0.5, 0.3 + 0.55 * R(), -0.62 + 1.3 * R()];
206
+ const rr = R(), nl = 1 + Math.floor(R() * 2.2), L = Array.from({ length: 12 }, () => R());
207
+ const p = projectToSurface(body, q);
208
+ const v = nearest(p);
209
+ if (!allowed(v)) continue;
210
+ const r = (0.045 + 0.07 * rr) * (params.spotSize ?? 1) * (region[v] === 2 ? 0.55 : 1);
211
+ if (region[v] !== 2 && !clearOf(p, 1.3 * r)) continue;
212
+ if (spots.some((s) => Math.hypot(p[0] - s.p[0], p[1] - s.p[1], p[2] - s.p[2]) < (r + s.r) * 1.15)) continue;
213
+ const lobes = [{ c: p, r }];
214
+ for (let j = 0; j < nl; j++) {
215
+ const d = [L[j * 4] - 0.5, L[j * 4 + 1] - 0.5, L[j * 4 + 2] - 0.5], dl = Math.hypot(...d) || 1;
216
+ lobes.push({ c: add(p, mul(d, (0.55 * r) / dl)), r: r * (0.45 + 0.3 * L[j * 4 + 3]) });
217
+ }
218
+ spots.push({ p, r, lobes });
219
+ }
220
+ }
221
+ const beltF = params.belt ? params.belt[0] : -0.055, beltB = params.belt ? params.belt[1] : 0.02;
222
+ // patternSD(v) < 0: the second colour (spots: black; belt / points: white)
223
+ const patternSD = (v) => {
224
+ const p = P(v), r = region[v], lb = legBone[v];
225
+ if (breed === 'spotted') {
226
+ // (none on the ears either: a spot running onto the pink leaf's root read as a dark smudge above the eye)
227
+ if (!offSnout(v) || tagOf[v] === 'teat' || r === 5) return 1;
228
+ let d = 1;
229
+ for (const s of spots) for (const l of s.lobes) d = Math.min(d, Math.hypot(p[0] - l.c[0], p[1] - l.c[1], p[2] - l.c[2]) - l.r);
230
+ // (a coarse wander of the outline and a fine hairy fray)
231
+ return d + rag(p, 12, 0.06) + rag(p, 45, 0.012);
232
+ }
233
+ if (breed === 'hampshire') {
234
+ if (r === 7 || r === 6 || r === 5 || r === 4) return 1;
235
+ // white belt: over the shoulders, down both forelegs and the chest, forward up the neck to the head
236
+ // (the head and ears black): the front edge is a plane across the head behind the eyes (head-local
237
+ // z = beltF), where the skin rides on the skull. On the neck, any edge sat in the skin that
238
+ // stretches most (a lying pig's neck bends sideways to lay its head down: 65-78 % of the edge
239
+ // triangles stretched > 1.5x, against 15 % here); the back edge at mid-ribcage (none stretched)
240
+ if (lb >= 0 && isFront[lb] && legness[v] > 0.4) return -0.03;
241
+ const zb = beltB + 0.05 * (p[1] - 0.45);
242
+ const h = HL(p);
243
+ // (and a black cap over the poll to behind the ears, so the ears stand in black skin)
244
+ const cap = Math.min(h[1] - 0.025, h[2] + 0.115);
245
+ const d = Math.max(Math.max(h[2] - beltF, cap), zb - p[2]);
246
+ return d + rag(p, 18, 0.03);
247
+ }
248
+ if (breed === 'berkshire') {
249
+ if (r === 7) return -0.02;
250
+ const h = r === 2 || r === 6 ? HL(p) : null;
251
+ if (h) {
252
+ // blaze: a white stripe down the face widening onto the snout
253
+ const w = 0.012 + 0.03 * smoothstep(0.05, 0.16, h[2]) * (params.blaze ?? 1);
254
+ // (the white snout's rear edge slants forward toward the chin: the jaw behind the mouth corner stays black)
255
+ const snoutEdge = 0.08 + 0.5 * Math.max(0, -0.03 - h[1]);
256
+ // (the blaze on the face only: on the jaw its midline drew a white bar down the chin)
257
+ return Math.min(r === 6 ? 1 : Math.abs(h[0]) - w, snoutEdge - h[2]) + rag(p, 30, 0.012);
258
+ }
259
+ if (r === 4) return (0.55 - tailT[v]) * 0.05 + rag(p, 40, 0.004);
260
+ if (lb >= 0 && legness[v] > 0.2) {
261
+ const top = isFront[lb] ? 0.13 : 0.14;
262
+ return p[1] - top * (params.socks ?? 1) + rag(p, 25, 0.03);
263
+ }
264
+ return 1;
265
+ }
266
+ return 1;
267
+ };
268
+
269
+ // --- ventral factor (lighter belly, inner legs)
270
+ const ventral = new Float32Array(nV);
271
+ for (let v = 0; v < nV; v++) {
272
+ const n = N(v), r = region[v], p = P(v);
273
+ let w = 0;
274
+ if (r <= 2 || r === 6) w = smoothstep(-0.2, -0.8, n[1]) * smoothstep(0.55, 0.35, p[1]);
275
+ ventral[v] = w;
276
+ }
277
+
278
+ // --- comb (bristle flow)
279
+ const comb = new Float32Array(nV * 3);
280
+ for (let v = 0; v < nV; v++) {
281
+ const p = P(v), n = N(v), r = region[v];
282
+ const bone = BONES[dominant[v]];
283
+ let d;
284
+ if (r === 5) d = norm(sub(bone.tail, bone.head));
285
+ else if (r === 2 || r === 6 || r === 7) {
286
+ // face: back from the snout, then down and back over the jowls
287
+ const h = HL(p);
288
+ d = norm(add(mul(HZ, -1), add(mul(HY, -0.35 * Math.sign(h[1] + 0.02)), [Math.sign(h[0]) * 0.25, 0, 0])));
289
+ if (h[2] < -0.04) d = norm(add(d, [0, -0.4, -0.5]));
290
+ } else if (r === 4) {
291
+ d = norm(sub(bone.tail, bone.head));
292
+ } else {
293
+ const seg = weights.segT[v * 2] | 0;
294
+ const a = rig.AXIAL.points[seg], b = rig.AXIAL.points[Math.min(seg + 1, rig.AXIAL.points.length - 1)];
295
+ d = norm(sub(b, a));
296
+ if (r === 0) d = norm(add(d, [0, -0.5 * Math.abs(n[0]), 0]));
297
+ if (r === 1) d = norm(add(d, [0, -0.6, 0.1]));
298
+ const L = legness[v];
299
+ if (L > 0 && legBone[v] >= 0) {
300
+ const lbn = BONES[legBone[v]];
301
+ let dl = norm(sub(lbn.tail, lbn.head));
302
+ if (/hoof/.test(lbn.name)) dl = [0, -1, 0.1];
303
+ else dl = norm(add(dl, [0, -0.3, 0]));
304
+ d = norm(add(mul(d, 1 - L), mul(dl, L)));
305
+ }
306
+ }
307
+ // tusks: combed along the tusk (the keratin shader's striations run along the comb: combed like the face they
308
+ // ran across the tusk as crinkled bark rings)
309
+ const rnv = rname[partOf[v]];
310
+ if ((rnv === 'tuskL' || rnv === 'tuskR') && tuskSegs.length) {
311
+ let best = Infinity;
312
+ for (const sg of tuskSegs[rnv === 'tuskL' ? 0 : 1] || tuskSegs[0]) {
313
+ const ab = sub(sg.b, sg.a), u = clamp(dot(sub(p, sg.a), ab) / Math.max(1e-9, dot(ab, ab)), 0, 1);
314
+ const dd = len(sub(p, add(sg.a, mul(ab, u))));
315
+ if (dd < best) { best = dd; d = norm(ab); }
316
+ }
317
+ }
318
+ let t = sub(d, mul(n, dot(d, n)));
319
+ if (len(t) < 1e-4) t = cross(n, [1, 0, 0]);
320
+ t = norm(t);
321
+ comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2];
322
+ }
323
+
324
+ // --- per-vertex fields
325
+ const pattern = new Float32Array(nV).fill(1);
326
+ const patInt = new Float32Array(nV);
327
+ const patCol = new Float32Array(nV * 3);
328
+ const markSDF = new Float32Array(nV).fill(1);
329
+ const furLen = new Float32Array(nV);
330
+ const tint = new Float32Array(nV * 4);
331
+ const surf = new Float32Array(nV * 4);
332
+ const mud = params.mud || 0;
333
+ // (bristles: at medium and low 8-16 shells draw each one as a dot, a pepper over the skin: shorter there)
334
+ // (a piglet's fine hair about as long as an adult's bristles: at 0.45x, 1.3-1.5 mm, every shell drew it at the
335
+ // same place and it piled up into a crisp white speck: 9.5 % bright specks on a piglet's cheek; it
336
+ // is also finer and denser, index.js render())
337
+ const bristleK = (params.bristle ?? 1) * (piglet ? 0.9 : 1) * (breed === 'duroc' ? 1.15 : 1) * (ctx.Q && ctx.Q.res > 1.5 ? 0.5 : 1);
338
+ const dark = COL.dark;
339
+ const pink = !dark && BASE_OF[breed] === 'pink';
340
+ // satin-matte skin under the bristles (0.3 read as vinyl on pink and latex on black skin; black 0.08 still
341
+ // made bright specular sheets in the sun)
342
+ // (black skin below 0: the shader reads a bare skin's gloss only for its roughness, mix(0.62, 0.3, gloss), so
343
+ // -0.4 is roughness 0.75, a dull matte black; at 0.03 (0.61) a Berkshire in the sun showed two broad grey sheen
344
+ // patches on shoulder and rump)
345
+ // (pink and red skin smooth and slightly glossy)
346
+ const SKIN_GLOSS = dark ? -0.4 : pink ? 0.24 : 0.2;
347
+ const SPOT_GLOSS = -0.3; // (the Old Spot's dark spots: the black breeds' matte, roughness 0.71) // (the Duroc's red skin under its denser red hair: satin, 0.24 read as leather)
348
+ // the rostral disc: its colour blends in along the head axis over the last 2 cm before the plate's face
349
+ // (a per-vertex switch at the snout bone's region drew a jagged edge)
350
+ const snoutK = (params.snoutLen ?? 1) * SNOUT_K;
351
+ const DZ = 0.06 + (0.178 - 0.06) * snoutK;
352
+ const discOf = (h) => smoothstep(DZ - 0.02, DZ - 0.002, h[2]);
353
+
354
+ for (let v = 0; v < nV; v++) {
355
+ const p = P(v), n = N(v), r = region[v], tag = tagOf[v];
356
+ // (sy: surf.y, for bare skin the share of the light that shines through when lit from behind: index.js
357
+ // render.skinTrans; 0 on the thick body)
358
+ let col, mat = MAT.SKIN, fl = 0.012, gloss = SKIN_GLOSS, sy = 0, lashW = 0, nostrilK = 0, nostrilCol = null, veinSD = 1, veinCol = null, earRimK = 0;
359
+ const lb = legBone[v];
360
+ const lbName = lb >= 0 ? BONES[lb].name : '';
361
+ const up = clamp(n[1], -1, 1);
362
+ if (r === 0 || r === 1) {
363
+ col = mix3(COL.body, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.7);
364
+ col = mix3(col, COL.belly, ventral[v]);
365
+ // bristles: longer along the dorsal midline (the "mane" of the back), shorter low down
366
+ // (fine bristles lying along the skin, long enough for the silhouette fins (>= 5.8 mm):
367
+ // fine sparse pale bristles visible mostly in silhouette and up close)
368
+ fl = mix(0.0065, 0.01, smoothstep(0.75, 0.97, up) * smoothstep(0.05, 0.015, Math.abs(p[0])));
369
+ fl = mix(fl, 0.004, ventral[v]);
370
+ const L = legness[v];
371
+ if (L > 0) {
372
+ fl = mix(fl, mix(0.0025, 0.004, smoothstep(0.12, 0.4, p[1])), L);
373
+ // lower legs a touch more flushed (thin skin over the tendons)
374
+ if (pink) col = mix3(col, COL.flush, 0.25 * smoothstep(0.3, 0.1, p[1]) * L);
375
+ }
376
+ // hooves, heel bulbs and dew claws below the coronet line
377
+ const Cj = /hoof|paw/.test(lbName) ? J[(lbName[0] === 'f' ? 'fcoffin' : 'hcoffin') + BONES[lb].side] : null;
378
+ if (Cj && (tag === 'hoof' || tag === 'heelbulb' || tag === 'dewclaw') && (tag === 'dewclaw' || p[1] < Cj[1] + 0.009 + 0.3 * (p[2] - Cj[2]) * (lbName[0] === 'f' ? 1 : 1))) {
379
+ mat = MAT.KERATIN; fl = 0; gloss = -0.3;
380
+ const streak = smoothstep(0.45, 0.65, fbm3(p[0] * 140 + (params.coatSeed % 97), p[1] * 9, p[2] * 140, 2));
381
+ col = pink ? mix3(COL.hoof, mul(COL.hoof, 0.55), 0.35 * streak) : mix3(COL.hoof, mul(COL.hoof, 1.5), 0.3 * streak);
382
+ if (tag === 'heelbulb') col = mix3(col, pink ? srgb(0xc89c8a) : mul(col, 0.8), 0.5);
383
+ }
384
+ if (tag === 'teat') { col = pink ? srgb(TEAT) : mix3(COL.body, srgb(0x6a5050), 0.3); fl = 0; gloss = 0.4; }
385
+ if (tag === 'scrotum' || tag === 'sheath') { col = mix3(col, COL.flush, pink ? 0.35 : 0.1); fl = 0.005; }
386
+ } else if (r === 4) {
387
+ col = mix3(COL.body, COL.dorsal, 0.4);
388
+ fl = 0.004 + 0.02 * smoothstep(0.8, 1, tailT[v]); // the terminal tuft
389
+ sy = 0.3 + 0.3 * tailT[v]; // (thin: the light shines through it, render.skinTrans)
390
+ if (pink) col = mix3(col, COL.flush, 0.15);
391
+ } else if (r === 5) {
392
+ // ears: thin skin, flushed and translucent in pink pigs (inner pinna redder), fine short hair
393
+ // (the hollow side: the nearest leaf tile's normal, which earShape points into the hollow)
394
+ let dn = 1e9, tile = null;
395
+ for (const q of lists[v]) if (q.tag === 'ear') { const d = Math.abs(SDFModel.dist(q, p[0], p[1], p[2])); if (d < dn) { dn = d; tile = q; } }
396
+ const faceDir = tile ? [tile.P[9], tile.P[10], tile.P[11]] : [0, 0, 1];
397
+ const inner = dot(n, faceDir) > 0.2;
398
+ // how far out along the ear (0 root .. 1 tip)
399
+ const eb = BONES[dominant[v]], ev = sub(eb.tail, eb.head);
400
+ const sE = clamp(dot(sub(p, eb.head), ev) / Math.max(1e-6, dot(ev, ev)), 0, 1);
401
+ // (pink ears, face1 / front1 / face2: the inner pinna a light, bright pink, redder deep in the hollow at
402
+ // the base (the research's backlit #E8998C); the renderer's red subsurface darkens it in shade, so the
403
+ // albedo is kept light: sampled #E8998C everywhere rendered brick red)
404
+ // (the colour switched at a fixed angle on the rim, which drew the rim as a cut edge round a
405
+ // slab; now graded across the rounded rim, so the two faces turn into each other)
406
+ const inCol = pink ? mix3(srgb(0xe8a098), srgb(0xf2bcb2), smoothstep(0.08, 0.55, sE)) : mul(mix3(COL.ear, COL.flush, 0.1), dark ? 1 : 1.06);
407
+ const outCol = mix3(COL.ear, COL.body, 0.5);
408
+ const kin = smoothstep(-0.45, 0.6, dot(n, faceDir));
409
+ col = mix3(outCol, inCol, kin);
410
+ // (short, skin-coloured hair in the hollow: longer pale hair there frosted the pink hollow with white
411
+ // specks in the studio light, hero)
412
+ // (and almost none in the hollow itself: at hero the key light's strand glints frosted the pink hollow white, and the
413
+ // Duroc's sparser, coarser hair drew a grey speckle over it; the rim fringe below stays)
414
+ fl = mix(0.003, 0.0022, kin) * (1 - 0.85 * kin);
415
+ // (a soft fringe of longer hair along the rim, where the leaf's surface turns edge-on: a thin hard outline
416
+ // read as cut paper; face3's Duroc ears are hairy at the edge)
417
+ const rimK = 1 - smoothstep(0.3, 0.7, Math.abs(dot(n, faceDir)));
418
+ // (a shorter fringe; the 5.5 mm rim hair drew the edge as a frayed slab)
419
+ // (shorter again, 3 / 4.5 mm: the Duroc's 6.5 mm red-gold fringe and hollow hair drew a frosted, torn edge)
420
+ // (the Duroc's 4.5 mm fringe, in strands darker than the skin, drew the rim as a torn edge with a
421
+ // dark speckle in the showcase; 3.2 mm in the body's red-gold)
422
+ // (almost none on the pink ears: their fine pale fringe, seen against a shaded head or the sky, drew a
423
+ // speckled frayed rim on the Large White and Landrace in the showcase; lw_show / head_gifu show a clean rim)
424
+ fl = mix(fl, breed === 'duroc' ? 0.0032 : pink ? 0.0012 : 0.003, rimK);
425
+ earRimK = rimK;
426
+ // veins on the back of the leaf (pink ears: head_gifu, head_hausschwein5, lw_show: a few dark-pink veins from
427
+ // the root toward the tip, branching outward; they show most against the light, render.skinTrans): drawn in
428
+ // the pattern channel (per pixel: per-vertex paint on the 3 mm ear cells came out as blobs)
429
+ if (pink && kin < 0.2 && rimK < 0.3) {
430
+ const evn = norm(ev), Le = len(ev), lat = norm(cross(evn, faceDir));
431
+ const q = sub(p, eb.head), u = dot(q, evn) / Le;
432
+ const w = dot(q, lat) / Le + 0.008 * (fbm3(p[0] * 35 + off[0], p[1] * 35, p[2] * 35, 2) - 0.5);
433
+ veinSD = earVeinSD(u, w * (p[0] > 0 ? 1 : -1), params.coatSeed || 0) * Le;
434
+ veinCol = mul3(col, [0.9, 0.74, 0.78]);
435
+ // (the back of the leaf nearly hairless: the tint here is the vein's colour, and the shells draw hair in it)
436
+ fl = 0;
437
+ }
438
+ gloss = dark ? 0.08 : 0.12;
439
+ // thin skin: the light shines through the leaf, most toward the tip (render.skinTrans; research 6: "ears
440
+ // are thin and translucent, strong red backlight")
441
+ // (above 0.9 the skin shader also passes the light that falls on the other face through the leaf,
442
+ // tinted by render.skinScatter: an ear turned away from the sun glows pink instead of the dark maroon of the
443
+ // shade; at 0.35-0.8 only the view-dependent back-light term applied)
444
+ // (only the pink ears: the Duroc's red and the black breeds' pigmented leaves passed the sun through
445
+ // as a pale grey-pink hollow, a pale speckled strip along the rim seen edge-on; they keep the back-light glow only)
446
+ sy = pink ? mix(0.92, 1.0, smoothstep(0.1, 0.8, sE)) : mix(dark ? 0.3 : 0.5, dark ? 0.5 : 0.75, smoothstep(0.1, 0.8, sE));
447
+ } else if (r === 7) {
448
+ // rostral disc: moist, glossy, finely pitted; the snout skin behind it blends in along the head axis.
449
+ // Skin material throughout (nose leather next to skin interpolated through the lid and mouth ids:
450
+ // a red ring round a dark disc)
451
+ const h = HL(p);
452
+ const dw = discOf(h);
453
+ const face = dw * smoothstep(0.2, 0.6, dot(n, HZ));
454
+ const snoutCol = mix3(mix3(COL.body, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.7), COL.belly, ventral[v]);
455
+ col = mix3(pink ? mix3(snoutCol, COL.flush, 0.25) : snoutCol, mix3(COL.disc, pink ? COL.flush : COL.disc, 0.2), dw);
456
+ fl = mix(0.003, 0, dw);
457
+ gloss = mix(SKIN_GLOSS, 0.6, face);
458
+ let nk = 0;
459
+ for (const e of nostrilPrims) {
460
+ const dn = SDFModel.dist(e, p[0], p[1], p[2]);
461
+ if (dn < 0.002) nk = Math.max(nk, smoothstep(0.002, 0.0002, dn));
462
+ // (and whatever lies recessed beside it: the carve's fillet at the inner upper end left a patch of disc colour
463
+ // inside the opening)
464
+ if (dn < 0.006) nk = Math.max(nk, smoothstep(0.001, 0.004, DZ + 0.012 - h[2]) * smoothstep(0.006, 0.002, dn));
465
+ }
466
+ if (nk > 0) {
467
+ // (darker with depth: the floor of a nostril, lit straight on, read as the pale centre of a dark ring; the
468
+ // nostril's colour also goes into the marking tint below, the Berkshire's white snout covered them as
469
+ // pale lilac dimples)
470
+ const depth = DZ + 0.012 - h[2];
471
+ const deep = smoothstep(0.002, 0.014, depth);
472
+ nostrilCol = mix3(dark ? mul(COL.disc, 0.4) : srgb(NOSTRIL), dark ? srgb(0x140a0a) : srgb(0x2a1214), deep);
473
+ // (matt in the depth: a glossy floor caught the light as a pale glint in the middle of the pit)
474
+ col = mix3(col, nostrilCol, nk); gloss = mix(gloss, dark ? -0.4 : mix(0.1, -0.2, deep), nk); fl *= 1 - nk; nostrilK = nk;
475
+ }
476
+ } else {
477
+ // head & jaw
478
+ const h = HL(p);
479
+ col = mix3(mix3(COL.body, COL.dorsal, smoothstep(0.3, 0.95, up) * 0.7), COL.belly, ventral[v]);
480
+ fl = 0.003; // (fine short hair on the face)
481
+ // bristly jowls and cheeks; short hair on the snout
482
+ if (h[2] < 0.02 && h[1] < -0.02) fl = mix(fl, 0.007, smoothstep(-0.02, -0.07, h[1]));
483
+ fl = mix(fl, 0.003, smoothstep(0.08, 0.16, h[2]));
484
+ if (pink) col = mix3(col, COL.flush, 0.25 * smoothstep(0.06, 0.17, h[2]));
485
+ if (r === 2) col = mix3(col, mix3(COL.disc, pink ? COL.flush : COL.disc, 0.2), discOf(h));
486
+ // eye rims (lid skin, no hair: a pink or dark margin, face1) and the pale lashes of the upper lid
487
+ for (let e = 0; e < eyePrims.length; e++) {
488
+ const pr = eyePrims[e], ec = eyeFrames[e].c;
489
+ if (Math.hypot(p[0] - ec[0], p[1] - ec[1], p[2] - ec[2]) > 0.03) continue;
490
+ const de = Math.abs(SDFModel.dist(pr, p[0], p[1], p[2]));
491
+ // (the lid margin a pink rim and the skin round the eye a little flushed on the pink breeds: front1, face2)
492
+ if (pink && de < 0.014) col = mix3(col, COL.flush, 0.42 * (1 - smoothstep(0.003, 0.014, de)));
493
+ if (de < 0.003) { fl = 0; col = dark ? mul(COL.body, 0.6) : mix3(col, COL.flush, 0.55 * (1 - smoothstep(0.002, 0.003, de)) + 0.25); gloss = 0.5; }
494
+ else if (de < 0.0115) {
495
+ const ef = eyeFrames[p[0] > 0 ? 0 : 1];
496
+ const rel = sub(p, ef.c);
497
+ const upAmt = dot(rel, ef.y) / 0.011;
498
+ // short fine hair on the lids, growing from the margin (no step at the rim)
499
+ fl = Math.min(fl, mix(0.0012, 0.004, smoothstep(0.002, 0.0095, de)));
500
+ if (lashes && upAmt > 0.15) {
501
+ // lashes: a pale fringe standing up and out from the upper lid (face1), longest mid-lid and
502
+ // tapered to both corners; they rise from the margin no faster than the skin runs (shells
503
+ // cannot draw single lashes: a long band combed over the eye read as a fuzzy blob). They
504
+ // stay bristles on the skin material (a fur-material band drew a black stepped outline: the
505
+ // interpolated material id passes 1-3 between fur 0 and skin 4), their colour from the tint
506
+ // (also tapered toward the corners across the eye: past the corners the lid patch rises
507
+ // above upAmt 0.15 too, and a tuft of long pale lashes there caught the back light as a white fleck)
508
+ const xc = Math.abs(dot(rel, ef.x));
509
+ const along = smoothstep(0.15, 0.55, upAmt) * (1 - smoothstep(0.004, 0.0095, xc));
510
+ // (up to 7.5 mm mid-lid, a clear pale fringe; the band ends 2 mm inside the eyelid patch and
511
+ // fades back to the lid hair over 3 mm, no faster than the skin runs)
512
+ const L = Math.min(0.0075, 0.95 * (de - 0.0018)) * along;
513
+ const k = smoothstep(0.0085, 0.0115, de);
514
+ const Lf = mix(L, fl, k);
515
+ if (Lf > fl) {
516
+ fl = Lf; lashW = along * (1 - k);
517
+ const lash = norm(add(add(mul(ef.y, 0.7), mul(norm(rel), 0.5)), mul(ef.z, 0.3)));
518
+ let t = sub(lash, mul(n, dot(lash, n)));
519
+ if (len(t) > 1e-4) { t = norm(t); comb[v * 3] = t[0]; comb[v * 3 + 1] = t[1]; comb[v * 3 + 2] = t[2]; }
520
+ }
521
+ }
522
+ // the lid folds (eye_lwshow_R, eye_mochyn: a soft crease above the upper lid's roll and a
523
+ // fainter one under the lower lid, running out past the corners): darker skin in thin bands, painted on the
524
+ // fine eyelid patch
525
+ const xr = Math.abs(dot(rel, ef.x)), along = smoothstep(0.0125, 0.006, xr);
526
+ const fold = upAmt > 0 ? Math.exp(-(((de - 0.0045) / 0.0011) ** 2)) * 0.2 : Math.exp(-(((de - 0.0036) / 0.0009) ** 2)) * 0.1;
527
+ col = mul(col, 1 - fold * along);
528
+ // (at medium and low, where no lashes are drawn, a pale lash line along the upper lid's margin)
529
+ if (!lashes && !dark && upAmt > 0.2) col = mix3(col, srgb(0xf6efe6), 0.45 * smoothstep(0.2, 0.5, upAmt) * (1 - smoothstep(0.0032, 0.0048, de)));
530
+ }
531
+ }
532
+ // lips: head vertices near the jaw surface, jaw vertices near the head surface. Only the mouth line is
533
+ // drawn: where the upper lip overhangs the lower one (the other surface lies above / below this vertex),
534
+ // in front of the mouth corner and fading out over the last 2 cm to it (face1: a short crease rising a
535
+ // little to the corner). The line is the mark channel (interpolated per pixel: a crease painted per
536
+ // vertex in a band narrower than a face cell drew a sawtooth), with a soft flush of lip skin beside it.
537
+ // Where the chin's sides and underside meet the throat nothing is painted (a dark crease all round the
538
+ // jaw outlined it as a separate slab). The mucosa only on the underside of the upper lip, facing the jaw.
539
+ if (r !== 6) {
540
+ const dj = SDFModel.evalList(jawPrims, p[0], p[1], p[2]);
541
+ if (h[2] > 0.03 && h[1] < -0.04 && dj < 0.008) {
542
+ const q = sub(p, mul(HY, 0.006));
543
+ const lipK = (SDFModel.evalList(jawPrims, q[0], q[1], q[2]) < dj ? 1 : 0) * smoothstep(-0.086, -0.078, h[1]) * smoothstep(LIP_Z0, LIP_Z0 + 0.02, h[2]);
544
+ fl = mix(fl, Math.min(fl, 0.002 * smoothstep(0.001, 0.005, dj)), lipK);
545
+ if (pink) col = mix3(col, COL.flush, 0.3 * lipK * smoothstep(0.008, 0.002, dj));
546
+ // (a line along the lip margin, dj = 0, not the whole overlap under the lip: a filled mark showed as dark
547
+ // scribbles at the mouth corners when the mouth opened)
548
+ if (lipK > 0) markSDF[v] = mix(0.003, Math.abs(dj) - LIP_W, lipK);
549
+ // the roof of the mouth (hidden by the lower lip until the mouth opens): mucosa, graded in by the depth
550
+ // inside the lower lip, in the skin material (a switch to the mouth material drew its boundary in steps,
551
+ // and its wet coat mirrored the sky: a white plate in an open mouth)
552
+ // (deeper in, the roof a darker, matte red: it faces down into the mouth, which no light reaches;
553
+ // a glossy roof mirrored the sky blue-white, the mouth material's clear coat included). The roof also
554
+ // where the jaw lies just below the skin (not inside it), in front of the mouth corner: it shows when
555
+ // the mouth opens (painted skin there read as a pale plate)
556
+ let jb = 1;
557
+ if (-dot(n, HY) > 0.3 && h[2] > LIP_Z0) for (const dd of [0.004, 0.009, 0.015, 0.022]) { const q2 = sub(p, mul(HY, dd)); if (SDFModel.evalList(jawPrims, q2[0], q2[1], q2[2]) < 0) { jb = dd; break; } }
558
+ const roof = jb < 1 ? smoothstep(0.024, 0.012, jb) * smoothstep(LIP_Z0, LIP_Z0 + 0.015, h[2]) * smoothstep(0.3, 0.6, -dot(n, HY)) : 0;
559
+ // (and the upper face of the skin sheet under the chin that rides on the jaw, sculpt.js 'underjaw': it is
560
+ // the floor of the open mouth behind the lower lip)
561
+ let wj = 0;
562
+ for (let k = 0; k < 4; k++) if (skinIndex[v * 4 + k] === JAW_BONE) wj += skinWeight[v * 4 + k];
563
+ const floor = smoothstep(0.3, 0.7, wj) * smoothstep(0.1, 0.4, dot(n, HY));
564
+ const mu = Math.max(smoothstep(-0.001, -0.007, dj), roof, floor), deep = Math.max(smoothstep(-0.006, -0.016, dj), 0.6 * roof, 0.8 * floor);
565
+ // (the palate deeper in a ridged dusky pink rather than near black: it faces down into the mouth, which little
566
+ // light reaches, and read as a flat black slot when the mouth opened)
567
+ if (mu > 0) { col = mix3(col, mix3(MUCOSA, mul(MUCOSA, 0.6), deep), mu); fl *= 1 - mu; gloss = mix(gloss, mix(0.3, 0.05, deep), mu); }
568
+ }
569
+ } else {
570
+ const dh = SDFModel.evalList(headPrims, p[0], p[1], p[2]);
571
+ if (tag === 'tongue') { mat = MAT.SKIN; fl = 0; gloss = 0.45; col = mix3(srgb(TONGUE), mul(srgb(TONGUE), 0.75), smoothstep(0.03, 0.1, -h[2] + 0.12)); }
572
+ else if (tag === 'tusk') {
573
+ mat = MAT.SKIN; fl = 0; gloss = 0.85; // (smooth enamel: skin material with no hair; the keratin shader's growth rings and striations, made for horn and hoof, crinkled the thin tusk like bark)
574
+ const E = tuskEnds[p[0] > 0 ? 0 : 1] || tuskEnds[0];
575
+ const t = E ? clamp(1 - len(sub(p, E.tip)) / Math.max(1e-4, len(sub(E.root, E.tip))), 0, 1) : 1;
576
+ col = mix3(srgb(TUSK_ROOT), srgb(TUSK), smoothstep(0.25, 0.85, t));
577
+ }
578
+ // the floor of the mouth and the inside of the lower lip: mucosa on the jaw's upper face, graded in by the
579
+ // depth inside the upper lip (skin material, as above); where the chin's sides and underside run into the
580
+ // head it stays skin (red there bled along the whole intersection and outlined the jaw as a slab)
581
+ else if (dh < 0.0) {
582
+ const mu = smoothstep(-0.001, -0.007, dh) * smoothstep(0.1, 0.4, dot(n, HY));
583
+ fl = Math.min(fl, 0.002) * (1 - mu);
584
+ if (mu > 0) { col = mix3(col, MUCOSA, mu); gloss = mix(gloss, 0.3, mu); }
585
+ } else if (dh < 0.008) {
586
+ const q = add(p, mul(HY, 0.006));
587
+ const lipK = (SDFModel.evalList(headPrims, q[0], q[1], q[2]) < dh ? 1 : 0) * smoothstep(-0.086, -0.078, h[1]) * smoothstep(LIP_Z0, LIP_Z0 + 0.02, h[2]);
588
+ fl = mix(fl, Math.min(fl, 0.002), lipK * smoothstep(0.008, 0.004, dh));
589
+ if (pink) col = mix3(col, COL.flush, 0.3 * lipK * smoothstep(0.008, 0.002, dh));
590
+ }
591
+ }
592
+ }
593
+ // skin creases: folds across the neck behind the jowls, over the knees and hocks (darkened tint
594
+ // in thin lines, deeper in older / fatter pigs)
595
+ if (mat === MAT.SKIN && (r === 1 || r === 0 || r === 2 || r === 7) && !piglet) {
596
+ let crease = 0;
597
+ // (wrinkles kept only where real (snout base, neck); drawn only where the cells are fine enough for a
598
+ // fold a few mm wide: hero and high)
599
+ const fineK = !ctx.Q || ctx.Q.res <= 1.35 ? 1 : 0;
600
+ const wr = 0.6 + 0.6 * (params.wrinkles ?? 0.5);
601
+ if (fineK && r === 1 && mat === MAT.SKIN) {
602
+ // neck folds: two or three soft folds round the side and underside of the neck behind the jowls (lw_show,
603
+ // head_pietrain), ~3 cm apart, fading out over the top of the neck
604
+ const sN = axialS[v] - sOcc;
605
+ const ph = (sN + 0.012 * (fbm3(p[0] * 25 + off[1], p[1] * 25, p[2] * 25, 2) - 0.5)) / 0.03;
606
+ const fr = Math.abs(ph - Math.round(ph));
607
+ crease = Math.exp(-(((fr * 0.03) / 0.0045) ** 2)) * smoothstep(0.015, 0.035, sN) * smoothstep(0.13, 0.08, sN) * smoothstep(0.5, -0.1, n[1]) * 0.55 * wr;
608
+ }
609
+ if (fineK && (r === 2 || r === 7) && mat === MAT.SKIN && nostrilK === 0) {
610
+ // the snout base: three or four transverse folds over the top of the snout a few cm behind the disc (face1,
611
+ // lw_show, head_hausschwein5), slightly arched, fading out down the sides
612
+ const h = HL(p), dz = DZ - h[2] + 6 * h[0] * h[0];
613
+ const ph = (dz + 0.003 * (fbm3(p[0] * 40 + off[0], p[1] * 40, p[2] * 40, 2) - 0.5)) / 0.0095;
614
+ const fr = Math.abs(ph - Math.round(ph));
615
+ crease = Math.exp(-(((fr * 0.0095) / 0.0018) ** 2)) * smoothstep(0.015, 0.025, dz) * smoothstep(0.065, 0.04, dz) * smoothstep(0.15, 0.55, dot(n, HY)) * 0.5 * wr;
616
+ }
617
+ if (legness[v] > 0.5 && lb >= 0) {
618
+ const jn = isFront[lb] ? J['wrist' + BONES[lb].side] : J['hock' + BONES[lb].side];
619
+ const dy = p[1] - jn[1];
620
+ const ph = Math.abs((dy / 0.012) % 1);
621
+ crease = Math.max(crease, smoothstep(0.2, 0.0, Math.min(ph, 1 - ph)) * smoothstep(0.03, 0.01, Math.abs(dy)) * 0.7 * (params.wrinkles ?? 0.5));
622
+ }
623
+ if (crease > 0) col = mul(col, 1 - 0.28 * Math.min(1, crease));
624
+ }
625
+ // Colours. The shells draw a bristle in 70 % of the vertex tint and 30 % of the albedo under it, so the
626
+ // skin itself is carried by the pattern channel and the tint is the BRISTLE colour: pale bristles on pink
627
+ // skin (face1: a soft white haze, not a dark pepper), golden on the Duroc, black on black skin, white on
628
+ // white markings. pattern < 0 (inside) shows patternColor = the skin; outside a marking's edge (belt,
629
+ // points, spots: pdRaw < 0) the tint shows, so near a marking the tint is the marking's colour (its
630
+ // bristles fray over the edge: a hairy boundary) and the crisp edge still comes from the distance field.
631
+ const CAP = 0.02;
632
+ let pdRaw = 1;
633
+ let second = breed === 'spotted' ? COL.spot : COL.white;
634
+ if ((mat === MAT.SKIN || mat === MAT.FUR) && breed !== 'largewhite' && breed !== 'landrace' && breed !== 'duroc') pdRaw = patternSD(v);
635
+ if (breed === 'berkshire' && (r === 7 || r === 2)) second = mix3(second, srgb(0xe2b4aa), 0.7 * discOf(HL(p)));
636
+ if (nostrilK > 0) second = mix3(second, nostrilCol, nostrilK);
637
+ // (the Old Spot's spots and the Hampshire's belt are painted per vertex in the skin's own colour channel,
638
+ // graded over 2-3 cm (gos_1: the spots' edges are soft, the pale hair over the dark skin), with the bristles following
639
+ // the skin under them. Drawn as a crisp per-pixel pattern edge, with the marking's colour in the tint that also colours
640
+ // the bristles, the spots read as holes cut in the skin, ringed by a halo of grey and white bristles: the tint changes
641
+ // over a whole cell beside an edge 1 mm wide. The spots also matte (the pink skin's satin over a dark albedo mirrored
642
+ // the sky as a grey sheen))
643
+ const col0 = col;
644
+ let softK = -1;
645
+ if (pdRaw < 1 && (breed === 'spotted' || breed === 'hampshire')) {
646
+ const cellM = (CELL[rname[partOf[v]]] || 0.0078) * (ctx.Q ? ctx.Q.res : 1);
647
+ const Wv = Math.max(r === 2 || r === 6 ? 0.007 : 0.011, Math.min(1.1 * cellM, 0.016)); // (capped: at low and crowd a ramp of a whole cell blurred the spots into grey blobs)
648
+ softK = smoothstep(Wv, -Wv, pdRaw);
649
+ col = mix3(col, second, softK);
650
+ if (breed === 'spotted') gloss = mix(gloss, SPOT_GLOSS, softK);
651
+ }
652
+ // (the bristle a little paler than the skin it grows from, not a white dot: at 1-3 m they blend into a
653
+ // faint pale haze over pink skin, face1; 1.6 mm white dots on a 5.5 mm grid read as frost)
654
+ // (a piglet's fine hair the same: on its finer strand grid, index.js render(), it is a pale velvet, front2)
655
+ // (the Duroc's red-gold hair, face3: redder than the skin is dark: a fine pale speckle like sandpaper)
656
+ // (the pale tint, mix 0.4 toward #F4EFE6, covered the face in white speckle; the shells draw a
657
+ // bristle in 0.7 x (tint x 1.1 + 0.03) + 0.3 x the skin, so this tint makes it the skin's colour a few per cent
658
+ // paler: the bristles show in the silhouette fins and against the light, not as dots over the lit skin)
659
+ const skinMatch = (c, k) => mix3([Math.max(0, 0.93 * c[0] - 0.027), Math.max(0, 0.93 * c[1] - 0.027), Math.max(0, 0.93 * c[2] - 0.027)], srgb(0xf4efe6), k);
660
+ // (the hair shader draws a bristle at ~0.85 of its albedo (strand shade 0.8-0.98, root 0.9) and lights it
661
+ // as hair, so a tint at the skin's own colour drew dark dashes over the lit face; 1.22 x the skin puts the lit bristle a
662
+ // touch paler than the skin under it: pale hairs up close, no pepper)
663
+ const paleMatch = (c) => mix3([Math.min(0.97, 1.22 * c[0] - 0.027), Math.min(0.97, 1.22 * c[1] - 0.027), Math.min(0.97, 1.22 * c[2] - 0.027)], srgb(0xf8f4ee), 0.12);
664
+ // (the Duroc's ear hair the body's red-gold too: the darker skin-matched strands specked the rim)
665
+ // (the pink ears' rim fringe in the pale body tint: skin-matched strands, drawn a little darker than the skin by
666
+ // the hair shader, outlined the Large White's and Landrace's rims with a speckled reddish fringe in the showcase)
667
+ let bristle = pink ? (r === 5 ? mix3(skinMatch(col0, 0.05), paleMatch(col0), earRimK) : paleMatch(col0)) : dark ? COL.bristle : mix3(skinMatch(col0, 0), srgb(0xc89064), 0.3);
668
+ // (in a spot, black hair a little darker than its skin, as on the black breeds; in the belt, white hair)
669
+ if (softK > 0) bristle = mix3(bristle, breed === 'spotted' ? mul(second, 0.6) : second, softK);
670
+ // (the lashes whiter than the body bristles: a pale fringe on the upper lid, face1)
671
+ if (lashW > 0 && !dark) bristle = mix3(bristle, srgb(pink ? 0xf2eae2 : 0xd8b890), lashW);
672
+ // mud (threequarter2, face3): only caked on the hooves and pasterns, dried pale grey or still wet and dark
673
+ // (the blotches and splashes on the belly, flanks and snout read as a dirty, blotchy skin; a
674
+ // clean pig's skin is an even pink)
675
+ if (mud > 0 && mat !== MAT.MOUTH && tag !== 'tusk' && r !== 5 && r !== 4 && r !== 7 && r !== 2 && r !== 6) {
676
+ const nz = (f, o) => fbm3(p[0] * f + off[0] + o, p[1] * f + off[1], p[2] * f + off[2] - o, 3);
677
+ const caked = smoothstep(0.075, 0.035, p[1] + 0.02 * (nz(9, 3) - 0.5));
678
+ const mk = clamp(caked * Math.min(1, 1.6 * mud), 0, 0.85);
679
+ if (mk > 0) {
680
+ const mc = mix3(srgb(MUD), srgb(MUD_DRY), smoothstep(0.4, 0.65, nz(5, 9)));
681
+ // (the bristles caked faster than the skin and flattened: black ones drew a black pepper over pale mud)
682
+ col = mix3(col, mc, mk); second = mix3(second, mc, mk); bristle = mix3(bristle, mul(mc, 0.9), Math.min(1, 1.6 * mk));
683
+ gloss = mix(gloss, 0.05, mk); fl *= 1 - 0.65 * mk;
684
+ }
685
+ }
686
+ // (black bristles on black skin read as a glitter of strand highlights: shorter)
687
+ if (dark && mat === MAT.SKIN && softK >= 0) fl *= mix(0.26, 1, softK);
688
+ else if (dark && mat === MAT.SKIN && pdRaw > 0) fl *= 0.26;
689
+ // (and the spots' black hair shorter, sparse on the dark skin: a glitter of strand highlights otherwise)
690
+ if (breed === 'spotted' && softK > 0) fl *= mix(1, 0.5, softK); // (0.3 on the 6000 /m grid: a grey glitter of strand highlights over the black skin; 0.18 left it bare like latex)
691
+ // faint, broad warm variation of the skin (a +-10 % red fbm at 17 cm read as dirty blotches)
692
+ const cv = fbm3(p[0] * 3 + off[2], p[1] * 3, p[2] * 3, 2) - 0.5;
693
+ if (mat === MAT.SKIN && r !== 7) col = [col[0] * (1 + 0.035 * cv), col[1] * (1 + 0.012 * cv), col[2] * (1 + 0.012 * cv)];
694
+ if (mat === MAT.SKIN && fl > 0) fl *= bristleK;
695
+ // (the tint turns to the marking's colour a body cell before its edge: the triangles across the edge
696
+ // show it whole on the marking's side)
697
+ // (one cell of the vertex's own surface: the face's 4 mm, not the body's 8: a white fringe 8-11 mm wide round the
698
+ // Berkshire's blaze read as overspray at hero; outside the marking, where that tint lies over
699
+ // the other skin, the bristles are almost gone: the skin's own pattern edge stays crisp and the per-vertex tint
700
+ // cannot be narrower than a cell)
701
+ // (Berkshire only: the spots and the belt are painted per vertex above, softK)
702
+ if (softK >= 0) pdRaw = 1;
703
+ const margin = (CELL[rname[partOf[v]]] || 0.0078) * 1.05 * (ctx.Q ? ctx.Q.res : 1);
704
+ const mw = smoothstep(1.4 * margin, margin, pdRaw);
705
+ // (none at all there: at 15 % length the white bristles of that band still sprinkled the black round the
706
+ // Berkshire's blaze and the Hampshire's belt with white specks at hero)
707
+ // (and fading in over the next cell out: the triangles from those vertices to the next ring blended the white tint
708
+ // into the black skin's bristles, a grey halo)
709
+ if (pdRaw < 1) fl *= 1 - smoothstep(2.6 * margin, 1.4 * margin, pdRaw) * smoothstep(-0.002, 0.002, pdRaw);
710
+ // (and the marking's own bristles short in its last cell: the white ones there are drawn over the black pixels
711
+ // just past the crisp edge, a sparkling halo)
712
+ if (pdRaw < 0) fl *= mix(0.25, 1, smoothstep(-0.2 * margin, -1.2 * margin, pdRaw));
713
+ const tc = mat === MAT.SKIN || mat === MAT.FUR ? mix3(bristle, second, mw) : col;
714
+ pattern[v] = clamp(-pdRaw, -CAP, CAP); patInt[v] = 1;
715
+ patCol[v * 3] = col[0]; patCol[v * 3 + 1] = col[1]; patCol[v * 3 + 2] = col[2];
716
+ furLen[v] = fl;
717
+ tint[v * 4] = tc[0]; tint[v * 4 + 1] = tc[1]; tint[v * 4 + 2] = tc[2]; tint[v * 4 + 3] = mat;
718
+ // ear veins: the vein is the 'marking' (pattern > 0 shows the tint), the skin round it the pattern colour
719
+ if (veinCol && pdRaw > 0.02 && !(softK > 0.05)) { pattern[v] = clamp(-veinSD, -CAP, CAP); tint[v * 4] = veinCol[0]; tint[v * 4 + 1] = veinCol[1]; tint[v * 4 + 2] = veinCol[2]; }
720
+ surf[v * 4] = gloss; surf[v * 4 + 1] = sy;
721
+ }
722
+ smoothField(furLen, weights.neighbors, 2, (v) => tint[v * 4 + 3] !== MAT.SKIN);
723
+
724
+ const patternColor = new Float32Array(nV * 4);
725
+ 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]; }
726
+ void vnoise3; void TAIL_SEGS; void add;
727
+ const perRegion = {};
728
+ for (let v = 0; v < nV; v++) perRegion[rname[partOf[v]]] = (perRegion[rname[partOf[v]]] || 0) + 1;
729
+ return { comb, tint, pattern, mark: markSDF, furLen, patternColor, surf, stats: { spots: spots.length, perRegion }, region, ventral };
730
+ }