@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,122 @@
1
+ // The snake sculpted as a signed distance field: a tube of round cones along the spine with a flat,
2
+ // carved belly (loaf cross-section), a head modelled in head-local coordinates (origin at v0,
3
+ // +Z forward), a separately meshed lower jaw, a forked tongue in two rigid pieces, hinged fangs
4
+ // (rattlesnake) and a rattle.
5
+ import { SPINE, bodyPlan } from './rig.js';
6
+ import { add, sub, mul, norm, lerp } from '../../core/math/vec.js';
7
+ import { sculptEyeSocket } from '../../core/sdf/eyeSocket.js';
8
+
9
+ // Eye spec (core/sdf/eyeSocket.js) for this individual: round aperture (no lids: a spectacle),
10
+ // iris filling the whole visible eye.
11
+ export function eyeSpec(params) {
12
+ const B = bodyPlan(params);
13
+ const e = B.P.eye, k = B.kH * (params.age === 'juvenile' ? 1.15 : 1);
14
+ const r = e.r * k;
15
+ return {
16
+ c: [e.at[0] * B.kH, e.at[1] * B.kH, e.at[2] * B.kH], r, back: 0.0002 * B.kH, yaw: e.yaw, pitch: e.pitch,
17
+ lid: 0.00015 * B.kH, R: r * 0.96, d: 0, off: 0, tilt: 0, irisZ: r * 0.62, irisR: r * 0.9,
18
+ };
19
+ }
20
+
21
+ export function sculptSnake(m, rig, params) {
22
+ const J = rig.J, B = rig.plan, P = B.P, viper = params.variant === 'rattlesnake';
23
+ const HO = J.v0;
24
+ const hL = B.headLen, W = P.headW * B.kH, H = P.headH * B.kH;
25
+ const yB = -HO[1]; // head-local height of the belly / jaw underside (y = 0 in the world)
26
+ const hl = (v) => [HO[0] + v[0], HO[1] + v[1], HO[2] + v[2]];
27
+ const HB = { bone: 'head' };
28
+ const sym = (f) => { f(1); f(-1); };
29
+
30
+ // ---------------------------------------------------------------- BODY
31
+ // round cones of radius = half width; the axis sits at flat * hw, so the circle's lower part is
32
+ // carved off by the belly plane (flat belly with ventrolateral angles, rounded back)
33
+ const hwAt = [];
34
+ let total = 0;
35
+ const sig = [0];
36
+ for (let i = 0; i < SPINE; i++) { total += Math.hypot(...sub(J['v' + (i + 1)], J['v' + i])); sig.push(total); }
37
+ for (let i = 0; i <= SPINE; i++) hwAt.push(B.hw(sig[i] / total));
38
+ for (let i = 0; i < SPINE; i++) {
39
+ m.cone({ tag: i >= SPINE * (1 - B.tailFrac) ? 'tail' : i < 3 ? 'neck' : 'body', bone: 'spine' + i, a: J['v' + i], b: J['v' + (i + 1)], ra: hwAt[i], rb: hwAt[i + 1], k: 0 });
40
+ }
41
+ // thin tail tip: overlapping ellipsoids that the pipeline inflates at coarse tiers (minThick)
42
+ for (let i = SPINE - 12; i < SPINE; i++) {
43
+ const a = J['v' + i], b = J['v' + (i + 1)], rr = (hwAt[i] + hwAt[i + 1]) * 0.5;
44
+ m.ell({ tag: 'tailtip', bone: 'spine' + i, c: lerp(a, b, 0.5), axis: norm(sub(a, b)), r: [rr * 0.98, rr * 0.98, Math.hypot(...sub(a, b)) * 0.62], k: 0, thin: true });
45
+ }
46
+ // ---------------------------------------------------------------- HEAD (upper jaw + braincase)
47
+ if (viper) {
48
+ // broad, triangular: venom-gland bulges at the back, flat crown, blunt canthus, narrow neck
49
+ m.ell({ ...HB, tag: 'cranium', c: hl([0, yB + 0.62 * H, 0.36 * hL]), r: [0.33 * W, 0.34 * H, 0.36 * hL], k: 0.004 });
50
+ sym((s) => m.ell({ ...HB, tag: 'gland', c: hl([0.31 * W * s, yB + 0.5 * H, 0.22 * hL]), r: [0.28 * W, 0.34 * H, 0.27 * hL], axis: norm([0.35 * s, 0, 1]), k: 0.005 }));
51
+ sym((s) => m.ell({ ...HB, tag: 'cheek', c: hl([0.2 * W * s, yB + 0.5 * H, 0.52 * hL]), r: [0.22 * W, 0.3 * H, 0.3 * hL], axis: norm([-0.25 * s, 0, 1]), k: 0.005 }));
52
+ m.ell({ ...HB, tag: 'snout', c: hl([0, yB + 0.58 * H, 0.7 * hL]), r: [0.29 * W, 0.28 * H, 0.25 * hL], k: 0.005 });
53
+ sym((s) => m.ell({ ...HB, tag: 'lip', c: hl([0.26 * W * s, yB + 0.38 * H, 0.5 * hL]), r: [0.16 * W, 0.16 * H, 0.44 * hL], axis: norm([-0.28 * s, 0, 1]), k: 0.004 }));
54
+ m.sphere({ ...HB, tag: 'rostral', c: hl([0, yB + 0.5 * H, 0.94 * hL]), rad: 0.13 * W, k: 0.004 });
55
+ // supraocular scales: a brow shelf over each eye
56
+ const E = eyeSpec(params);
57
+ sym((s) => m.ell({ ...HB, tag: 'brow', c: hl([E.c[0] * s * 0.95, E.c[1] + E.r * 0.95, E.c[2]]), r: [E.r * 1.25, E.r * 0.5, E.r * 1.5], k: 0.0015 }));
58
+ } else {
59
+ // corn snake: barely wider than the neck, long rounded snout, domed crown
60
+ m.ell({ ...HB, tag: 'cranium', c: hl([0, yB + 0.6 * H, 0.34 * hL]), r: [0.42 * W, 0.4 * H, 0.4 * hL], k: 0.004 });
61
+ sym((s) => m.ell({ ...HB, tag: 'temporal', c: hl([0.2 * W * s, yB + 0.5 * H, 0.2 * hL]), r: [0.28 * W, 0.36 * H, 0.3 * hL], k: 0.004 }));
62
+ m.ell({ ...HB, tag: 'snout', c: hl([0, yB + 0.56 * H, 0.72 * hL]), r: [0.29 * W, 0.34 * H, 0.3 * hL], axis: norm([0, -0.08, 1]), k: 0.005 });
63
+ sym((s) => m.ell({ ...HB, tag: 'lip', c: hl([0.24 * W * s, yB + 0.4 * H, 0.52 * hL]), r: [0.19 * W, 0.2 * H, 0.42 * hL], axis: norm([-0.22 * s, 0, 1]), k: 0.004 }));
64
+ m.sphere({ ...HB, tag: 'rostral', c: hl([0, yB + 0.5 * H, 0.92 * hL]), rad: 0.17 * W, k: 0.004 });
65
+ }
66
+ // neck: blend the cranium into the first body cone
67
+ m.cone({ ...HB, tag: 'nape', a: hl([0, 0, 0.18 * hL]), b: hl([0, 0, -0.25 * hL]), ra: 0.3 * W, rb: hwAt[0] * 1.02, k: 0.01 });
68
+ // eyes in round sockets (the spectacle sits flush), nostrils, rostral notch for the tongue
69
+ const E = eyeSpec(params);
70
+ sym((s) => sculptEyeSocket(m, E, HO, s, { bone: 'head', orbit: { r: [E.r * 0.95, E.r * 0.8, E.r * 0.45], at: [0, 0, E.r * 1.0], k: E.r * 0.35 } }));
71
+ sym((s) => m.sphere({ ...HB, tag: 'nostril', c: hl([0.17 * W * s, yB + 0.62 * H, 0.95 * hL]), rad: 0.04 * W, k: 0.0006, carve: true }));
72
+ if (viper) sym((s) => m.sphere({ ...HB, tag: 'pit', c: hl([0.33 * W * s, yB + 0.48 * H, 0.8 * hL]), rad: 0.05 * W, k: 0.0006, carve: true }));
73
+ m.ell({ ...HB, tag: 'notch', c: hl([0, yB + 0.36 * H, 0.99 * hL]), r: [0.07 * W, 0.07 * H, 0.05 * hL], k: 0.0006, carve: true });
74
+
75
+ // palate: the upper jaw ends at the lip line (the lower jaw is its own surface)
76
+ m.ell({ ...HB, tag: 'palate', c: hl([0, yB + 0.1 * H, 0.62 * hL]), r: [0.33 * W, 0.26 * H, 0.5 * hL], k: 0.0012, carve: true });
77
+ // belly plane: a very flat carving ellipsoid whose top is y = 0 (sag < 0.2 mm over the body)
78
+ const zMid = (J.v0[2] + J['v' + SPINE][2]) * 0.5;
79
+ m.ell({ tag: 'belly', bone: 'spine' + (SPINE >> 1), c: [0, -0.012, zMid], r: [0.4, 0.012, 3.2], k: 0.18 * hwAt[SPINE >> 1], carve: true });
80
+
81
+ // ---------------------------------------------------------------- LOWER JAW (own surface)
82
+ const JW = { bone: 'jaw', group: 'jaw', part: 'jaw' };
83
+ sym((s) => m.cone({ ...JW, tag: 'mandible', a: hl([(viper ? 0.34 : 0.3) * W * s, yB + 0.22 * H, 0.08 * hL]), b: hl([0.07 * W * s, yB + 0.17 * H, 0.88 * hL]), ra: viper ? 0.14 * H + 0.05 * W : 0.11 * H + 0.03 * W, rb: viper ? 0.1 * H : 0.085 * H, k: 0.003 }));
84
+ m.ell({ ...JW, tag: 'jawfloor', c: hl([0, yB + 0.19 * H, 0.46 * hL]), r: [0.3 * W, 0.18 * H, 0.42 * hL], k: 0.003 });
85
+ m.sphere({ ...JW, tag: 'chin', c: hl([0, yB + 0.2 * H, 0.9 * hL]), rad: 0.14 * H, k: 0.003 });
86
+
87
+ // ---------------------------------------------------------------- TONGUE (two rigid pieces)
88
+ const tb = J.tongueBase, tf = J.tongueFork, tt = J.tongueTip;
89
+ const tr = 0.0007 * B.kH * (viper ? 1.25 : 1);
90
+ m.cone({ bone: 'tongue', group: 'tongue', part: 'tongue', tag: 'tongue', a: tb, b: add(tf, [0, 0, tr]), ra: tr * 1.1, rb: tr * 0.85, k: 0 });
91
+ const forkLen = Math.hypot(...sub(tt, tf));
92
+ sym((s) => m.cone({ bone: 'tongueTip', group: 'tongue', part: 'fork', tag: 'fork', a: add(tf, [0, 0, -tr * 0.5]), b: add(tt, [s * forkLen * 0.28, 0, 0]), ra: tr * 0.8, rb: tr * 0.32, k: tr * 0.6 }));
93
+
94
+ // ---------------------------------------------------------------- FANGS (rattlesnake)
95
+ if (viper) {
96
+ const fb = J.fangBase, ft = J.fangTip;
97
+ sym((s) => m.cone({ bone: 'fang', group: 'fang', part: 'fang', tag: 'fang', a: add(fb, [0.2 * W * s, 0, 0]), b: add(ft, [0.2 * W * s, 0, 0]), ra: 0.0009 * B.kH, rb: 0.00015, k: 0 }));
98
+ }
99
+
100
+ // ---------------------------------------------------------------- RATTLE
101
+ if (P.rattle) {
102
+ const last = 'spine' + (SPINE - 1);
103
+ const a = J['v' + (SPINE - 1)], b = J['v' + SPINE];
104
+ const d = norm(sub(b, a));
105
+ const nSeg = params.rattleSegs ?? P.rattle.segs;
106
+ const tip = hwAt[SPINE];
107
+ const segL = (P.rattle.len / P.rattle.segs) * (params.shape?.girth ?? 1);
108
+ for (let k = 0; k < nSeg; k++) {
109
+ const t = k / Math.max(1, P.rattle.segs - 1);
110
+ const w = tip * (1.35 - 0.3 * t);
111
+ const c0 = add(b, mul(d, segL * (k + 0.55)));
112
+ // the rattle rests on (not in) the ground: lift a segment whose half height exceeds the tip's axis height
113
+ const c = [c0[0], Math.max(c0[1], w * 0.78 + 0.0003), c0[2]];
114
+ // each segment: a flattened, bi-lobed bell overlapping the next
115
+ m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'rattle', c, axis: d, r: [w, w * 0.78, segL * 0.62], k: segL * 0.25 });
116
+ m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'rattle', c: add(c, mul(d, segL * 0.35)), axis: d, r: [w * 0.82, w * 0.62, segL * 0.38], k: segL * 0.2 });
117
+ }
118
+ // the tail tip plugs into the button
119
+ m.ell({ bone: last, part: 'rattle', group: 'rattle', tag: 'button', c: ((q) => [q[0], Math.max(q[1], tip * 0.9 + 0.0003), q[2]])(add(b, mul(d, -segL * 0.2))), axis: d, r: [tip * 1.05, tip * 0.9, segL * 0.7], k: 0 });
120
+ }
121
+ return m;
122
+ }
@@ -0,0 +1,279 @@
1
+ // The spider's coat: chitin (material 8) under a covering of setae (shell hairs), and glossy bare
2
+ // cuticle around the eyes and on the fangs (keratin-like, material 5).
3
+ //
4
+ // Wolf spider (Hogna / Lycosa type): dark brown carapace with a pale median band and pale
5
+ // submarginal bands, dark striae radiating from the fovea, black ocular area; abdomen with a dark
6
+ // lanceolate heart mark in front and pale chevrons behind; banded legs; black venter.
7
+ // Tarantula morphs: red-knee (Brachypelma: velvet black, orange knee bands, pale rings, tan carapace
8
+ // margin, reddish abdominal setae), rosy (Grammostola: pinkish brown) and pinktoe (Avicularia: black
9
+ // with pink tarsi). Colours sampled from the reference photos.
10
+ //
11
+ // Pattern and mark distances are stored in "cheetah-equivalent" metres (real distance x PK) so the
12
+ // covering shader's anti-aliasing widths and hair-level edge jitter, which are tuned for a cheetah,
13
+ // stay proportionate on a 25 mm animal.
14
+ import { spiderDims } from './rig.js';
15
+ import { EYES, eyeSpec } from './sculpt.js';
16
+ import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
17
+ import { srgb, mix3 } from '../../core/build/coatKit.js';
18
+ import { norm, sub, dot, cross, add, mul, smoothstep, clamp, mix, fbm3, len } from '../../core/math/vec.js';
19
+
20
+ const MAT = { CHITIN: 8, KERATIN: 5 };
21
+
22
+ function palette(p) {
23
+ const w = p.coatWarmth || 0, l = p.coatLightness || 0;
24
+ // warmth (rufous .. cool), lightness, and greyness (Pardosa-like grey individuals .. brown Hogna)
25
+ const gr = p.coatGrey || 0;
26
+ const tw = (c) => {
27
+ const r = c[0] * (1 + 0.12 * w + l), g = c[1] * (1 + 0.03 * w + l), b = c[2] * (1 - 0.12 * w + l);
28
+ const y = 0.3 * r + 0.55 * g + 0.15 * b;
29
+ return [mix(r, y, gr), mix(g, y, gr), mix(b, y * 1.04, gr)];
30
+ };
31
+ if (p.variant !== 'tarantula') {
32
+ return {
33
+ carapace: tw(srgb(0x3a2b1f)), stripe: tw(srgb(0xa08a6a)), margin: tw(srgb(0x8c7657)),
34
+ abdomen: tw(srgb(0x4e3b2a)), heart: tw(srgb(0x261b13)), chevron: tw(srgb(0x8e7a5c)), abdSide: tw(srgb(0x5d4935)),
35
+ leg: tw(srgb(0x5f4a35)), legBand: tw(srgb(0x9a8465)), legDark: tw(srgb(0x33261b)),
36
+ chel: srgb(0x1f1812), chelHair: tw(srgb(0x6a4a2c)), venter: srgb(0x1a1512), ocular: srgb(0x0d0b09),
37
+ fang: srgb(0x160f0b), palp: tw(srgb(0x5a4633)), spinneret: tw(srgb(0x6e5a44)),
38
+ };
39
+ }
40
+ const morph = p.morph || 'redknee';
41
+ const base = {
42
+ carapace: srgb(0x17120f), stripe: srgb(0x17120f), margin: srgb(0xc3884b),
43
+ abdomen: srgb(0x1a1411), heart: srgb(0x120d0b), chevron: srgb(0x6b3a22), abdSide: srgb(0x1c1512),
44
+ leg: srgb(0x15110e), legBand: srgb(0xd9651e), legDark: srgb(0x0f0c0a), legRing: srgb(0xe0a068),
45
+ chel: srgb(0x120e0c), chelHair: srgb(0x2a1d16), venter: srgb(0x0e0b09), ocular: srgb(0x0a0908),
46
+ fang: srgb(0x0e0a08), palp: srgb(0x16120f), spinneret: srgb(0x201813), tarsusTip: null,
47
+ };
48
+ if (morph === 'rosy') {
49
+ Object.assign(base, {
50
+ carapace: srgb(0x6e4c40), margin: srgb(0x9a7462), abdomen: srgb(0x5e4036), chevron: srgb(0x8a5a4a), abdSide: srgb(0x5a3e34),
51
+ leg: srgb(0x4a3530), legBand: srgb(0x86645a), legRing: srgb(0x9a7a6c), legDark: srgb(0x35261f), chelHair: srgb(0x6a4a3e), palp: srgb(0x4a3530),
52
+ });
53
+ } else if (morph === 'pinktoe') {
54
+ Object.assign(base, {
55
+ carapace: srgb(0x1b191d), margin: srgb(0x2a2630), abdomen: srgb(0x1c1a1e), chevron: srgb(0x3a3036), abdSide: srgb(0x1c1a1e),
56
+ leg: srgb(0x1a181c), legBand: srgb(0x2c2830), legRing: srgb(0x2c2830), legDark: srgb(0x121014), tarsusTip: srgb(0xe07a7a), palp: srgb(0x1a181c),
57
+ });
58
+ }
59
+ const tw2 = (c) => (c ? [c[0] * (1 + l), c[1] * (1 + l), c[2] * (1 + l)] : c);
60
+ for (const k of Object.keys(base)) base[k] = tw2(base[k]);
61
+ return base;
62
+ }
63
+
64
+ export function spiderCoat(ctx) {
65
+ const { rig, pos, nrm, regionOf, regionNames, weights, nV, params } = ctx;
66
+ const d = spiderDims(params);
67
+ const J = rig.J, BONES = rig.BONES;
68
+ const U = d.unit, F = U / 0.013, PK = 0.33 / U;
69
+ const wolf = d.variant === 'wolf';
70
+ const COL = palette(params);
71
+ const Mm = 0.001 * F; // one "wolf millimetre" at this variant's scale
72
+ const comb = new Float32Array(nV * 3), tint = new Float32Array(nV * 4), pattern = new Float32Array(nV).fill(1);
73
+ const mark = new Float32Array(nV).fill(1), furLen = new Float32Array(nV), patternColor = new Float32Array(nV * 4), surf = new Float32Array(nV * 4);
74
+ const dom = weights.dominant;
75
+ const contrast = params.patternContrast ?? 1;
76
+
77
+ // carapace landmarks
78
+ const z0 = J.pedA[2], z1 = J.front[2], Lc = z1 - z0;
79
+ const fovea = [0, 0, z0 + 0.36 * Lc];
80
+ const eyeC = add(J.ceph, [0, 0, 0.0]);
81
+ const halfW = (z) => {
82
+ // carapace half width along z (pear shape: widest behind the middle, narrow head)
83
+ const u = (z - z0) / Lc;
84
+ const wMax = d.carapace.wid * 0.0005, wHead = d.carapace.headW * 0.0005;
85
+ return u < 0.45 ? wMax * Math.sqrt(Math.max(0.05, 1 - ((0.45 - u) / 0.47) ** 2)) : mix(wMax, wHead, smoothstep(0.45, 0.85, u));
86
+ };
87
+ const eyes = [];
88
+ for (const e of EYES[d.variant]) { const E = eyeSpec(e); for (const s of [1, -1]) eyes.push({ c: eyeFrameOf(E, J.ceph, s).c, r: E.r }); }
89
+ const boneKind = BONES.map((b) => {
90
+ const m = /^(coxa|troch|femur|patella|tibia|meta|tarsus)(\d)([LR])$/.exec(b.name);
91
+ if (m) return { kind: 'leg', seg: m[1], leg: +m[2], S: m[3] };
92
+ if (/^palp/.test(b.name)) return { kind: 'palp', seg: b.name.replace(/^palp|[LR]$/g, '').toLowerCase() };
93
+ return { kind: b.name.replace(/[LR]$/, '') };
94
+ });
95
+ const segIdx = { coxa: 0, troch: 1, femur: 2, patella: 3, tibia: 4, meta: 5, tarsus: 6 };
96
+
97
+ for (let v = 0; v < nV; v++) {
98
+ const p = [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
99
+ const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
100
+ const rn = regionNames[regionOf[v]];
101
+ const b = BONES[dom[v]], bk = boneKind[dom[v]];
102
+ let col = COL.carapace, mat = MAT.CHITIN, fl = 0, gloss = 0.05, pat = 1, pcol = COL.stripe, pint = 0, mk = 1;
103
+ let cdir = [0, 0, -1];
104
+ const noise = fbm3(p[0] / (1.6 * Mm), p[1] / (1.6 * Mm), p[2] / (1.6 * Mm), 3) - 0.5;
105
+ const noiseF = fbm3(p[0] / (0.45 * Mm) + 7, p[1] / (0.45 * Mm), p[2] / (0.45 * Mm), 2) - 0.5;
106
+
107
+ if (rn.startsWith('fang')) {
108
+ mat = MAT.KERATIN; col = COL.fang; gloss = 0.9; fl = 0;
109
+ cdir = sub(J['fangTip' + rn.slice(-1)], J['cheTip' + rn.slice(-1)]);
110
+ } else if (rn.startsWith('chel')) {
111
+ const S = rn.slice(-1);
112
+ col = COL.chel; fl = (wolf ? 0.66 : 1.6) * Mm; gloss = 0.05;
113
+ cdir = sub(J['cheTip' + S], J['cheBase' + S]);
114
+ // hair on the front face, bare glossy cuticle near the tip (the fang base)
115
+ const t = dot(sub(p, J['cheBase' + S]), norm(cdir)) / len(cdir);
116
+ fl *= 1 - smoothstep(0.7, 0.95, t);
117
+ col = mix3(col, COL.chelHair, clamp(0.5 + noise, 0, 1) * (wolf ? 0.35 : 0.5) * (1 - smoothstep(0.6, 0.9, t)));
118
+ } else if (bk.kind === 'leg' || bk.kind === 'palp') {
119
+ // ---- legs and palps: distal hair flow, pale joint bands
120
+ const a = b.head, e = b.tail, ax = sub(e, a), L = len(ax);
121
+ cdir = ax;
122
+ const t = clamp(dot(sub(p, a), ax) / (L * L), 0, 1);
123
+ const seg = bk.kind === 'leg' ? bk.seg : bk.seg === 'coxa' ? 'coxa' : bk.seg === 'troch' ? 'troch' : bk.seg === 'femur' ? 'femur' : bk.seg === 'patella' ? 'patella' : bk.seg === 'tibia' ? 'tibia' : 'tarsus';
124
+ const si = segIdx[seg] ?? 6;
125
+ const up = n[1];
126
+ col = bk.kind === 'palp' ? COL.palp : COL.leg;
127
+ if (si <= 1) col = mix3(COL.venter, col, smoothstep(-0.2, 0.6, up));
128
+ // bands: pale rings near the distal end of femur, patella and tibia (wolf); red-knee: the
129
+ // orange band covers the patella top and the distal femur, pale rings at tibia / metatarsus ends
130
+ let bandD = 1e9, bandCol = COL.legBand;
131
+ const along = t * L; // metres from the segment start
132
+ if (wolf) {
133
+ if (si === 2) bandD = Math.min(Math.abs(along - L * 0.18) - 0.35 * Mm, Math.abs(along - L * 0.62) - 0.4 * Mm);
134
+ else if (si === 3) bandD = Math.abs(along - L * 0.5) - L * 0.22;
135
+ else if (si === 4) bandD = Math.min(Math.abs(along - L * 0.2) - 0.45 * Mm, Math.abs(along - L * 0.72) - 0.4 * Mm);
136
+ else if (si === 5) bandD = Math.abs(along - L * 0.12) - 0.3 * Mm;
137
+ // diffuse, mottled banding of the setae (not crisp rings)
138
+ bandD += 0.35 * Mm * noiseF + 0.25 * Mm * noise;
139
+ } else if ((params.morph || 'redknee') === 'redknee' || params.morph === 'rosy') {
140
+ if (si === 3) bandD = -0.2 * Mm + (0.25 - up) * 1.4 * Mm; // top of the patella
141
+ else if (si === 2) bandD = (L - along) - 0.12 * L + (0.2 - up) * 1.2 * Mm;
142
+ else if (si === 4) { bandD = Math.min(L - along - 0.07 * L, along - 0.04 * L) + (0.1 - up) * 0.8 * Mm; bandCol = COL.legRing; }
143
+ else if (si === 5) { bandD = (L - along) - 0.06 * L + (0.1 - up) * 0.8 * Mm; bandCol = COL.legRing; }
144
+ // "knee" stripes run lengthwise along the patella and tibia top in B. hamorii
145
+ if (si === 3 || si === 4) bandD = Math.min(bandD, Math.abs(dot(norm(cross(ax, [0, 1, 0])), sub(p, a))) - 0.4 * Mm + (0.5 - up) * 2 * Mm);
146
+ } else if (params.morph === 'pinktoe' && si === 6 && bk.kind === 'leg') {
147
+ col = COL.tarsusTip;
148
+ }
149
+ // (wolf: soft edges, about 3x the anti-aliasing width, and a lower contrast)
150
+ if (bandD < 1e8) { pat = bandD * PK * (wolf ? 0.33 : 1); pcol = bandCol; pint = contrast * (wolf ? 0.6 : 1); }
151
+ // darker ventral side and joint membranes
152
+ col = mix3(col, COL.legDark, smoothstep(0.1, -0.7, up) * 0.4);
153
+ const lens = wolf ? [0.62, 0.64, 0.85, 0.8, 0.8, 0.72, 0.66] : [1.8, 1.8, 3.4, 3.2, 3.2, 2.8, 2.4];
154
+ fl = lens[si] * Mm * (bk.kind === 'palp' ? 0.9 : 1) * (1 + 0.2 * noiseF);
155
+ if (!wolf && si === 6) fl *= 0.8;
156
+ gloss = 0.03;
157
+ } else if (bk.kind === 'abdomen' || bk.kind === 'pedicel' || bk.kind === 'spinnerets') {
158
+ // ---- abdomen: hair lying backwards, dorsal heart mark and chevrons, dark venter
159
+ const c = d.abdomen.c.map((x) => x * 0.001), r = d.abdomen.r.map((x) => x * 0.001);
160
+ const u = (p[2] - c[2]) / r[2]; // +1 front .. -1 rear
161
+ const up = n[1];
162
+ const dors = smoothstep(-0.1, 0.55, up);
163
+ col = mix3(COL.venter, mix3(COL.abdSide, COL.abdomen, dors), smoothstep(-0.75, -0.2, up));
164
+ cdir = [0, -0.15 * u, -1];
165
+ if (wolf) {
166
+ // lanceolate heart mark: a dark spear along the front half of the midline (tint), pale
167
+ // chevrons over the rear half (pattern)
168
+ const hw = mix(0.95, 0.15, clamp((0.95 - u) / 1.0, 0, 1)) * Mm;
169
+ const heart = (Math.abs(p[0]) - hw * (u > -0.1 ? 1 : 0)) / Mm;
170
+ col = mix3(col, COL.heart, smoothstep(0.3, -0.2, heart) * dors * contrast * smoothstep(-0.2, 0.2, u));
171
+ mk = Math.min(mk, u > -0.05 && u < 0.95 ? (Math.abs(Math.abs(p[0]) - hw) - 0.08 * Mm) * PK / (dors > 0.3 ? 1 : 1e3) : 1);
172
+ // rear half: pairs of pale spots flanking the midline, joined by faint, broken chevrons
173
+ let spots = 1e9, ch = 1e9;
174
+ for (let k = 0; k < 4; k++) {
175
+ const uz = -0.12 - k * 0.2;
176
+ spots = Math.min(spots, Math.hypot(Math.abs(p[0]) - r[0] * (0.3 - 0.03 * k), (u - uz) * r[2]) - 0.32 * Mm * (1 - 0.15 * k));
177
+ const vz = uz + Math.abs(p[0]) / r[0] * 0.25;
178
+ ch = Math.min(ch, Math.abs(u - vz) * r[2] - 0.1 * Mm * (1 - 0.2 * k));
179
+ }
180
+ ch = Math.max(ch, Math.abs(p[0]) - r[0] * 0.3, 0.25 * Mm * (noiseF + 0.2)); // broken up
181
+ // pale flanks: lateral pale bands (Hogna) framing the dorsum
182
+ const flank = Math.abs(Math.abs(p[0]) / r[0] - 0.72) * r[0] - 0.35 * Mm;
183
+ pat = (Math.min(spots, ch, flank + 0.3 * Mm * noise) + 0.2 * Mm * noiseF) * PK * 0.6;
184
+ pcol = COL.chevron;
185
+ pint = dors * contrast * 0.8 * smoothstep(-0.35, 0.1, up);
186
+ // mottling
187
+ col = [col[0] * (1 + 0.3 * noiseF), col[1] * (1 + 0.28 * noiseF), col[2] * (1 + 0.24 * noiseF)];
188
+ fl = 0.9 * Mm * (1 + 0.25 * noiseF);
189
+ } else {
190
+ // tarantula: long reddish setae on black; a bald urticating patch sometimes
191
+ // black under the hair, the long setae reddish (strand-mottled, stronger on the dorsum)
192
+ pat = (-0.5 * Mm + 0.2 * Mm * noise) * PK;
193
+ pcol = COL.chevron;
194
+ pint = smoothstep(-0.4, 0.3, up) * (0.4 + 0.3 * noiseF);
195
+ fl = (3.8 + 1.2 * noiseF) * Mm * smoothstep(-0.9, -0.3, up) + 1.2 * Mm;
196
+ if (params.bald) {
197
+ const bd = len([p[0] / r[0], (p[1] - c[1] - r[1] * 0.7) / r[1], (p[2] - c[2] + r[2] * 0.35) / r[2]]);
198
+ fl *= smoothstep(0.28, 0.42, bd + 0.05 * noise);
199
+ }
200
+ }
201
+ if (bk.kind === 'spinnerets') { col = COL.spinneret; fl *= 0.3; }
202
+ if (bk.kind === 'pedicel') fl *= 0.4;
203
+ gloss = 0.03;
204
+ } else {
205
+ // ---- prosoma: carapace, head, sternum
206
+ const up = n[1];
207
+ const x = p[0], z = p[2];
208
+ const W = halfW(z);
209
+ const dors = smoothstep(-0.15, 0.35, up);
210
+ col = mix3(COL.venter, COL.carapace, smoothstep(-0.55, -0.1, up));
211
+ // hair radiates back and out from the eye region
212
+ cdir = [x, 0, z - eyeC[2] - 0.001 * F];
213
+ const u = (z - z0) / Lc;
214
+ if (wolf) {
215
+ // median band: narrow between the eyes, widest over the thorax, narrowing to the rear
216
+ // (a narrow pale stripe between the eyes and down the face, widening behind the eyes)
217
+ const mw = (u > 0.72 ? mix(0.5, 0.35, smoothstep(0.72, 0.95, u)) : mix(0.4, 0.85, smoothstep(0.1, 0.55, u))) * Mm;
218
+ const median = Math.abs(x) - mw + 0.12 * Mm * noiseF;
219
+ // submarginal bands along the sides of the carapace
220
+ const rel = Math.abs(x) / Math.max(W, 1e-5);
221
+ const sub1 = Math.abs(rel - 0.8) * W - 0.13 * W + 0.1 * Mm * noise;
222
+ const subm = Math.max(sub1, (0.15 - up) * 3 * Mm, (u - 0.8) * 8 * Mm);
223
+ pat = Math.min(median, subm) * PK;
224
+ pint = dors * contrast;
225
+ pcol = COL.stripe;
226
+ // dark striae radiating from the fovea
227
+ let st = 1e9;
228
+ for (let k = 0; k < 4; k++) {
229
+ const ang = (-0.95 + k * 0.62);
230
+ const dir = [Math.sin(ang + Math.PI / 2), 0, Math.cos(ang + Math.PI / 2)];
231
+ const q = sub([Math.abs(x), 0, z], fovea);
232
+ const along = dot(q, dir);
233
+ if (along > 0.4 * Mm && along < W * 0.95) st = Math.min(st, Math.abs(q[0] * dir[2] - q[2] * dir[0]) - 0.08 * Mm * (1 - along / W));
234
+ }
235
+ mk = Math.min(mk, dors > 0.2 ? st * PK : 1);
236
+ } else {
237
+ // velvet black centre with a tan margin and faint radiating lines
238
+ const rel = Math.abs(x) / Math.max(W, 1e-5);
239
+ pat = (Math.max(0.78 - rel, 0) * W * 1.0 - 0.06 * Mm + (0.25 - up) * 0.5 * Mm) * PK;
240
+ if (up < -0.2) pat = 1;
241
+ pcol = COL.margin; pint = (params.morph === 'pinktoe' ? 0.3 : 1) * smoothstep(-0.2, 0.2, up);
242
+ }
243
+ // ocular area (and the tarantula's tubercle): bare, black, glossy
244
+ let eyeD = 1e9;
245
+ for (const e of eyes) eyeD = Math.min(eyeD, len(sub(p, e.c)) - e.r * 1.35);
246
+ if (eyeD < 0.5 * Mm * (wolf ? 1 : 0.6)) col = mix3(col, COL.ocular, smoothstep(0.5 * Mm, 0.05 * Mm, eyeD));
247
+ fl = (wolf ? 0.72 : 1.1) * Mm * smoothstep(0.05 * Mm, 0.6 * Mm, eyeD);
248
+ if (up < -0.5) fl *= 0.5; // sternum
249
+ gloss = eyeD < 0.3 * Mm ? 0.4 : 0.05;
250
+ if (b.name === 'head' && up > 0.2 && z > J.front[2] - 0.0015 * F) fl *= 0.6;
251
+ }
252
+
253
+ // low frequency colour noise
254
+ col = [col[0] * (1 + 0.22 * noise), col[1] * (1 + 0.2 * noise), col[2] * (1 + 0.16 * noise)];
255
+ // comb: tangent to the surface
256
+ let cd = sub(cdir, mul(n, dot(cdir, n)));
257
+ if (len(cd) < 1e-9) cd = norm(cross(n, [1, 0, 0])); else cd = norm(cd);
258
+ comb.set(cd, v * 3);
259
+ tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
260
+ pattern[v] = pat; mark[v] = mk; furLen[v] = fl;
261
+ patternColor[v * 4] = pcol[0]; patternColor[v * 4 + 1] = pcol[1]; patternColor[v * 4 + 2] = pcol[2]; patternColor[v * 4 + 3] = pint;
262
+ // gloss, feature scale (fine cuticle striation), agouti (banded setae), undercoat
263
+ surf[v * 4] = gloss; surf[v * 4 + 1] = -0.00012 * F; surf[v * 4 + 2] = wolf ? 0.35 : 0.1; surf[v * 4 + 3] = 0;
264
+ }
265
+
266
+ // smooth hair length so shells do not step
267
+ const nb = weights.neighbors, tmp = new Float32Array(nV);
268
+ for (let it = 0; it < 2; it++) {
269
+ for (let v = 0; v < nV; v++) {
270
+ const ns = nb[v];
271
+ if (!ns.length || tint[v * 4 + 3] !== MAT.CHITIN) { tmp[v] = furLen[v]; continue; }
272
+ let a = 0;
273
+ for (const u of ns) a += furLen[u];
274
+ tmp[v] = 0.5 * furLen[v] + (0.5 * a) / ns.length;
275
+ }
276
+ furLen.set(tmp);
277
+ }
278
+ return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { patternScale: PK } };
279
+ }
@@ -0,0 +1,78 @@
1
+ // Spider: wolf spider (Lycosidae, default) and tarantula (Theraphosidae, opts.variant = 'tarantula').
2
+ // Body plan 'spider' (core/motion/spider.js): 8 legs x 7 segments, pedipalps, chelicerae with
3
+ // fangs, eight simple eyes.
4
+ import { spiderRig } from './rig.js';
5
+ import { sculptSpider, EYES, eyeSpec } from './sculpt.js';
6
+ import { spiderRegions } from './regions.js';
7
+ import { spiderCoat } from './coat.js';
8
+ import { motion } from './motion.js';
9
+
10
+ // glossy black domes; the look.iris.outer colour tints the dome (dark amber-brown lens)
11
+ const EYE_LOOK = {
12
+ pupil: 'round',
13
+ lids: 'simple',
14
+ // the dome colour comes from iris.outer: near black with a faint amber tapetum glint
15
+ iris: { inner: [0.02, 0.02, 0.02], mid: [0.03, 0.03, 0.025], hi: [0.05, 0.05, 0.04], outer: [0.3, 0.17, 0.05] },
16
+ sclera: { color: [0.02, 0.02, 0.02], visible: false },
17
+ lidColor: [0.02, 0.018, 0.015],
18
+ pupilSize: [0.4, 0.5],
19
+ };
20
+
21
+ export default {
22
+ id: 'spider',
23
+ name: 'Spider',
24
+ latin: 'Lycosidae / Theraphosidae',
25
+ group: 'others',
26
+ plan: 'spider',
27
+ covering: 'chitin',
28
+ variants: [
29
+ { id: 'wolf', name: 'Wolf spider', latin: 'Lycosidae' },
30
+ { id: 'tarantula', name: 'Tarantula', latin: 'Theraphosidae' },
31
+ ],
32
+
33
+ // Seeded individual. Females are larger with a heavy abdomen (gravid ones more so); males are
34
+ // smaller and leggier with swollen palpal bulbs; spiderlings are small and short-legged.
35
+ variation(R, o) {
36
+ const variant = o.variant === 'tarantula' ? 'tarantula' : 'wolf';
37
+ const sex = o.sex || (R() < 0.6 ? 'female' : 'male');
38
+ const age = o.age || 'adult';
39
+ const juv = age === 'juvenile';
40
+ const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
41
+ const male = sex === 'male' && !juv;
42
+ const size = (male ? (variant === 'wolf' ? 0.82 : 0.88) : 1) * (1 + 0.08 * g()) * (juv ? 0.42 : 1);
43
+ const p = {
44
+ variant, sex, age, size,
45
+ quality: o.quality,
46
+ legK: (male ? 1.1 : 1) * (1 + 0.04 * g()) * (juv ? 0.9 : 1),
47
+ abdK: (male ? 0.78 : 1 + 0.08 * Math.abs(g()) + 0.05 * g()) * (juv ? 0.9 : 1),
48
+ palpBulb: male ? 1 : 0,
49
+ coatWarmth: 0.9 * g(),
50
+ coatLightness: 0.18 * g() + (male && variant === 'wolf' ? -0.08 : 0),
51
+ patternContrast: variant === 'wolf' ? 0.6 + 0.55 * R() : 1,
52
+ coatGrey: variant === 'wolf' ? 0.75 * R() ** 1.5 : 0,
53
+ coatSeed: Math.floor(R() * 1e6),
54
+ warps: [],
55
+ };
56
+ if (variant === 'tarantula') {
57
+ const m = R();
58
+ p.morph = m < 0.6 ? 'redknee' : m < 0.8 ? 'rosy' : 'pinktoe';
59
+ p.bald = R() < 0.3; // kicked off its urticating hairs
60
+ }
61
+ return p;
62
+ },
63
+ rig: (params) => spiderRig(params),
64
+ sculpt: (m, rig, params) => sculptSpider(m, rig, params),
65
+ regions: spiderRegions,
66
+ coat: spiderCoat,
67
+ eyeSpecs: (params, rig) => {
68
+ const out = [];
69
+ for (const e of EYES[params.variant === 'tarantula' ? 'tarantula' : 'wolf']) for (const side of [1, -1]) out.push({ side, spec: eyeSpec(e), headOrigin: rig.J.ceph, bone: 'head', look: EYE_LOOK });
70
+ return out;
71
+ },
72
+ motion,
73
+ // setae: fine but still resolvable strands (a few per millimetre), loosely tufted; the tarantula's
74
+ // long hair is coarser and fuzzier
75
+ render: (params) => (params?.variant === 'tarantula'
76
+ ? { markColor: [0.012, 0.009, 0.007], strandDensity: 3000, clumpDensity: 3, shellScale: 1, finWidth: 0.6 }
77
+ : { markColor: [0.012, 0.009, 0.007], strandDensity: 5000, clumpDensity: 5, shellScale: 1, finWidth: 0.45 }),
78
+ };
@@ -0,0 +1,72 @@
1
+ // Spider motion data for the spider engine (schema: src/core/motion/spider.js).
2
+ // Numbers are for the reference individual of each variant (params.size = 1); the engine scales
3
+ // speeds by dynamic similarity (sqrt(size)) and reads every length from the joints.
4
+ //
5
+ // Gait: alternating tetrapod (L1 R2 L3 R4 against R1 L2 R3 L4) with a back-to-front metachronal lag
6
+ // inside each tetrapod (Biancardi et al. 2011 on Grammostola: quadruped lag 16 % slow, 8 % fast;
7
+ // Weihmann 2013 on Cupiennius: contralateral pairs in antiphase, body oscillation tiny). Offsets are
8
+ // touchdown phases relative to L4, in the order L1 L2 L3 L4 R1 R2 R3 R4.
9
+ // Duty factor falls with speed (d = 0.666 - 0.531 v for Grammostola; Cupiennius < 0.5 when fast);
10
+ // stride frequency rises with speed (f = 14.4 v + 0.14 Hz for an 18 g tarantula, faster for the
11
+ // smaller wolf spider by dynamic similarity).
12
+ const slow = [0.74, 0.16, 0.58, 0.0, 0.24, 0.66, 0.08, 0.5];
13
+ const fast = [0.62, 0.08, 0.54, 0.0, 0.12, 0.58, 0.04, 0.5];
14
+ const G = (name, v, f, D, off, o = {}) => ({ name, v, f, D, off, ...o });
15
+
16
+ export const motion = {
17
+ // ---------------------------------------------------------------- wolf spider (default)
18
+ maxSpeed: 0.32, // m/s: a dash (wolf spiders sprint at 0.3-0.6 m/s over short distances)
19
+ accel: 1.6, // m/s^2: wolf spiders reach full speed within a stride or two
20
+ decel: 2.4, // and freeze abruptly
21
+ turnRate: 4.0, // rad/s (on the spot and at a walk)
22
+ latAccelMax: 0.4, // m/s^2: at speed it turns in arcs of a few body lengths
23
+ gears: { walk: 0.05, run: 0.14, sprint: 0.3 },
24
+ gaits: [
25
+ G('walk', 0.0, 1.6, 0.72, slow, { lift: 0.0022, bob: 0.00008, sway: 0.015 }),
26
+ G('walk', 0.05, 2.4, 0.66, slow, { lift: 0.0026, bob: 0.00012, sway: 0.015 }),
27
+ G('run', 0.1, 3.3, 0.58, fast, { lift: 0.003, bob: 0.00016, sway: 0.01 }),
28
+ G('run', 0.18, 4.9, 0.5, fast, { lift: 0.0034, bob: 0.0002, sway: 0.008 }),
29
+ G('run', 0.3, 6.0, 0.48, fast, { lift: 0.0038, bob: 0.00022, sway: 0.006 }),
30
+ ],
31
+ // neutral footholds: bind claw positions pulled toward the coxa (legs are not fully spread in
32
+ // stance) and the stance height relative to the bind pose
33
+ stance: { reach: [0.8, 0.9, 0.9, 0.78], height: 1.0, width: 1.0 },
34
+ feet: { contact: 'claw', heelLift: 12 },
35
+ abdomen: { stiffness: 16, lag: 0.12, droop: 0.05 },
36
+ breath: { rate: 0.55, amp: 0.018 },
37
+ chelicerae: { fang: 'labidognath', fangOpen: 1.25, spread: 0.35, pitch: 0.45 },
38
+ palps: { twitch: 1, tap: 0.6 },
39
+ idle: { twitch: 1, raiseFront: 0.12, look: 0.35 },
40
+ look: { yaw: 0.45, pitch: 0.32 },
41
+ actions: {
42
+ jump: { height: 0.016, distance: 0.035, crouch: 0.25 },
43
+ attack: { style: 'threat-lunge', rear: 0.3, lunge: 0.005, threat: 0.7 },
44
+ sleep: { tuck: 0.85 },
45
+ },
46
+
47
+ // ---------------------------------------------------------------- tarantula
48
+ variants: {
49
+ tarantula: {
50
+ maxSpeed: 0.18,
51
+ accel: 1.2,
52
+ decel: 2.4,
53
+ turnRate: 3.2,
54
+ gears: { walk: 0.035, run: 0.1, sprint: 0.2 },
55
+ gaits: [
56
+ G('walk', 0.0, 0.8, 0.7, slow, { lift: 0.005, bob: 0.00015, sway: 0.012 }),
57
+ G('walk', 0.04, 1.4, 0.65, slow, { lift: 0.0055, bob: 0.0002, sway: 0.012 }),
58
+ G('run', 0.11, 2.4, 0.6, fast, { lift: 0.0065, bob: 0.0003, sway: 0.008 }),
59
+ G('run', 0.236, 3.6, 0.54, fast, { lift: 0.0075, bob: 0.0004, sway: 0.006 }),
60
+ ],
61
+ stance: { reach: [0.88, 0.93, 0.93, 0.88], height: 1.0, width: 1.0 },
62
+ breath: { rate: 0.35, amp: 0.012 },
63
+ chelicerae: { fang: 'orthognath', fangOpen: 1.2, spread: 0.12, pitch: 0.55 },
64
+ idle: { twitch: 0.5, raiseFront: 0.05, look: 0.2 },
65
+ actions: {
66
+ jump: { height: 0.012, distance: 0.05, crouch: 0.2 },
67
+ attack: { style: 'threat-lunge', rear: 0.7, raise2: 0.65, lunge: 0.008, threat: 0.9 },
68
+ sleep: { tuck: 0.8 },
69
+ },
70
+ },
71
+ },
72
+ };
@@ -0,0 +1,87 @@
1
+ // Meshing plan. A spider is mostly thin legs spread over a wide box, so one dense grid would be
2
+ // wasteful: the body (prosoma, abdomen, palps, coxae, trochanters) is one region, every leg its
3
+ // own region from the middle of the trochanter outward, and the chelicerae and fangs are rigid
4
+ // regions of their own (they move on their own bones). Legs and body overlap across a cut plane
5
+ // through the trochanter and cross-fade there (both surfaces see the same primitives near the cut).
6
+ import { spiderDims } from './rig.js';
7
+ import { legJoints, LEG_KEYS } from '../../core/rig/spider.js';
8
+ import { lerp, norm, sub, smoothstep } from '../../core/math/vec.js';
9
+
10
+ // cut planes through the trochanters (reference space)
11
+ export function legCuts(J, d) {
12
+ const F = d.unit / 0.013;
13
+ return LEG_KEYS.map((key) => {
14
+ const S = key[0], i = +key[1];
15
+ const lj = legJoints(i, S);
16
+ const t = J[lj[1]], f = J[lj[2]];
17
+ return { key, p: lerp(t, f, 0.5), n: norm(sub(f, t)), e: J[lj[3]], r: d.legR[2][0] * 0.001 * 2.2 * (i === 4 ? d.hindR : 1) };
18
+ });
19
+ }
20
+
21
+ function box(points, pad) {
22
+ const mn = [1e9, 1e9, 1e9], mx = [-1e9, -1e9, -1e9];
23
+ for (const p of points) for (let k = 0; k < 3; k++) { mn[k] = Math.min(mn[k], p[k] - pad); mx[k] = Math.max(mx[k], p[k] + pad); }
24
+ return [mn, mx];
25
+ }
26
+
27
+ export function spiderRegions(rig, params, Q) {
28
+ const J = rig.J, d = spiderDims(params);
29
+ // the tarantula is stockier (thicker legs, bigger abdomen for its unit): slightly coarser cells
30
+ const F = (d.unit / 0.013) * (d.variant === 'tarantula' ? 1.12 : 1), r = Q.res;
31
+ const hB = 0.156e-3 * F * r, hL = 0.148e-3 * F * r, hC = 0.115e-3 * F * r;
32
+ const B = 2.2 * Math.max(hB, hL);
33
+ const cuts = legCuts(J, d);
34
+ // s: signed distance along the cut normal; near: within reach of the leg (distance to the segment
35
+ // from the cut centre to the knee)
36
+ for (const c of cuts) { c.ab = sub(c.e, c.p); c.ab2 = c.ab[0] ** 2 + c.ab[1] ** 2 + c.ab[2] ** 2; }
37
+ const inCut = (c, x, y, z) => {
38
+ const dx = x - c.p[0], dy = y - c.p[1], dz = z - c.p[2];
39
+ const s = dx * c.n[0] + dy * c.n[1] + dz * c.n[2];
40
+ const t = Math.max(0, Math.min(1, (dx * c.ab[0] + dy * c.ab[1] + dz * c.ab[2]) / c.ab2));
41
+ const ex = dx - c.ab[0] * t, ey = dy - c.ab[1] * t, ez = dz - c.ab[2] * t;
42
+ return { s, near: ex * ex + ey * ey + ez * ez < c.r * c.r };
43
+ };
44
+ const bodyClip = (x, y, z) => {
45
+ for (const c of cuts) { const q = inCut(c, x, y, z); if (q.near && q.s > B) return false; }
46
+ return true;
47
+ };
48
+ const maxR = Math.max(...d.legR.map((q) => q[0])) * 0.001 * d.hindR;
49
+ const minPad = 0.4e-3 * F;
50
+ // body box: axial joints, palps, coxae, trochanters, the start of the femora
51
+ const bodyPts = [];
52
+ for (const k of Object.keys(J)) if (!/^(femur|patella|tibia|meta|tarsus|claw)/.test(k)) bodyPts.push(J[k]);
53
+ for (const c of cuts) bodyPts.push(c.p);
54
+ const abd = d.abdomen.r.map((x) => x * 0.001);
55
+ const [bmin, bmax] = box(bodyPts, Math.max(abd[0], abd[1]) + minPad);
56
+ bmin[1] = Math.max(bmin[1], -0.2e-3 * F);
57
+ const regions = [{ name: 'body', part: 'body', bmin, bmax, h: hB, F: 5, clip: bodyClip }];
58
+ const rigid = [];
59
+ for (const S of ['L', 'R']) {
60
+ const [cmin, cmax] = box([J['cheBase' + S], J['cheTip' + S]], d.chel.r[0] * 0.0013 + minPad);
61
+ rigid.push({ name: 'chel' + S, part: 'chel' + S, bmin: cmin, bmax: cmax, h: hC, F: 5, rigidBone: 'chelicera' + S });
62
+ const [fmin, fmax] = box([J['cheTip' + S], J['fangTip' + S]], d.chel.fangR * 0.0015 + minPad * 0.5);
63
+ rigid.push({ name: 'fang' + S, part: 'fang' + S, bmin: fmin, bmax: fmax, h: hC * 0.8, F: 5, rigidBone: 'fang' + S });
64
+ }
65
+ const legs = { L: [], R: [] };
66
+ for (const c of cuts) {
67
+ const S = c.key[0], i = +c.key[1];
68
+ const lj = legJoints(i, S).slice(1).map((n) => J[n]);
69
+ const [lmin, lmax] = box(lj, maxR * 1.3 + minPad);
70
+ legs[S].push({ name: 'leg' + c.key, part: 'leg' + c.key, bmin: lmin, bmax: lmax, h: hL, F: 5, clip: (x, y, z) => inCut(c, x, y, z).s > -B });
71
+ }
72
+ return {
73
+ jobs: [[...regions, ...rigid], legs.L, legs.R],
74
+ fade(x, y, z, name) {
75
+ if (name === 'body') {
76
+ let f = 1;
77
+ for (const c of cuts) { const q = inCut(c, x, y, z); if (q.near) f *= 1 - smoothstep(-B, B, q.s); }
78
+ return f;
79
+ }
80
+ if (name.startsWith('leg')) {
81
+ const c = cuts.find((q) => 'leg' + q.key === name);
82
+ return smoothstep(-B, B, inCut(c, x, y, z).s);
83
+ }
84
+ return 1;
85
+ },
86
+ };
87
+ }