@bitefoundry/retrieviewer 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/ASSET-PROVENANCE.md +11 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +95 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/INSTALLING.md +4 -0
  7. package/PLUGIN.md +13 -10
  8. package/PREREQUISITES.md +95 -0
  9. package/PUPPY-MEMORY.md +102 -1
  10. package/README.ko.md +116 -0
  11. package/README.md +121 -59
  12. package/RELEASING.md +33 -13
  13. package/THIRD-PARTY-NOTICES.md +5 -3
  14. package/braille.mjs +35 -9
  15. package/examples/live.mjs +2 -2
  16. package/integrations/linux-overlay/overlay.py +8 -2
  17. package/integrations/macos-overlay/overlay.jxa +9 -7
  18. package/integrations/opencode/runtime.mjs +4 -4
  19. package/integrations/opencode/tui.tsx +1 -1
  20. package/integrations/windows-overlay/Overlay.cs +7 -6
  21. package/integrations/windows-overlay/desktop.mjs +2 -1
  22. package/integrations/windows-overlay/protocol.mjs +13 -7
  23. package/integrations/windows-overlay/worker.mjs +1 -1
  24. package/package.json +34 -13
  25. package/puppy-comfort-sprites.mjs +33 -0
  26. package/puppy-companion-comfort-q5.bin.gz +0 -0
  27. package/puppy-companion-comfort.json +140 -0
  28. package/puppy-companion-life.mjs +58 -18
  29. package/puppy-companion-objects.mjs +12 -0
  30. package/puppy-companion-sprites.mjs +4 -2
  31. package/puppy-frame-predictive.mjs +15 -9
  32. package/puppy-frame-store.mjs +2 -2
  33. package/puppy-paper-actions.mjs +4 -2
  34. package/puppy-paper-behaviors.mjs +1 -1
  35. package/puppy-paper-contact.mjs +4 -2
  36. package/puppy-paper-seat.mjs +4 -2
  37. package/puppy-paper-world.mjs +14 -0
  38. package/puppy-paper.mjs +79 -21
  39. package/retrieviewer-cli.mjs +8 -6
  40. package/retrieviewer-plugin.d.mts +1 -1
  41. package/retrieviewer-plugin.mjs +1 -1
  42. package/retrieviewer.d.mts +12 -4
  43. package/retrieviewer.mjs +20 -14
  44. package/locomotion-core.mjs +0 -21
  45. package/locomotion-puppy.animal +0 -0
  46. package/vendor/procedural-animals/package.json +0 -40
  47. package/vendor/procedural-animals/src/animal.js +0 -146
  48. package/vendor/procedural-animals/src/bake.js +0 -51
  49. package/vendor/procedural-animals/src/build.js +0 -81
  50. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  51. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  52. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  53. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  54. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  55. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  56. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  57. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  58. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  59. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  60. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  61. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  62. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  63. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  64. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  65. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  66. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  67. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  68. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  69. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  70. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  71. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  72. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  73. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  74. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  75. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  76. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  77. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  78. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  79. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  80. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  81. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  82. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  83. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  84. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  85. package/vendor/procedural-animals/src/index.d.ts +0 -98
  86. package/vendor/procedural-animals/src/index.js +0 -29
  87. package/vendor/procedural-animals/src/registry.js +0 -48
  88. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  89. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  90. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  91. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  92. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  93. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  94. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  95. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  96. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  97. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  98. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  99. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  100. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  101. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  102. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  103. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  104. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  105. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  106. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  107. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  108. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  109. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  110. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  111. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  112. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  113. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  114. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  115. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  116. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  117. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  118. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  119. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  120. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  121. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  122. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  123. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  124. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  125. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  126. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  127. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  128. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  129. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  130. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  131. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  132. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  133. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  134. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  135. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  136. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  137. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  138. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  139. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  140. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  141. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  142. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  143. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  144. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  145. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  146. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  147. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  148. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  149. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  150. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  151. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  152. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  153. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  154. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  155. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  156. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  157. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  158. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  159. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  160. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  161. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  162. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  163. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  164. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  165. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  166. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  167. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  168. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  169. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  170. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  171. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  172. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  173. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  174. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  175. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  176. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  177. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  178. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  179. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  180. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  181. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  182. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  183. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  184. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  185. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  186. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  187. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  188. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  189. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  190. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  191. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  192. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  193. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  194. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  195. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  196. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  197. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  198. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  199. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  200. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  201. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  202. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  203. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  204. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  205. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  206. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  207. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  208. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  209. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  210. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  211. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  212. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  213. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  214. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  215. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  216. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  217. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  218. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  219. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  220. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  221. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  222. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  223. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  224. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  225. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  226. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  227. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  228. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  229. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  230. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  231. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  232. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  233. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  234. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  235. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  236. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  237. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  238. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  239. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  240. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  241. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  242. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  243. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  244. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  245. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  246. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  247. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  248. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  249. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  250. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  251. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  252. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  253. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  254. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  255. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  256. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  257. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  258. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  259. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  260. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  261. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  262. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  263. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -1,315 +0,0 @@
1
- // Snake coat: overlapping dorsal scales (keeled on the rattlesnake), wide ventral scutes, head plates;
2
- // corn snake saddles with dark borders, lateral blotches and the checkerboard belly; rattlesnake
3
- // diamonds with pale edges, flank blotches, face stripes and the black-and-white "coon tail".
4
- // Everything is laid out in body coordinates: arc length sigma along the spine from the occiput and
5
- // the arc u around the body from the dorsal midline, so the pattern wraps the tube like the real one.
6
- import { SPINE } from './rig.js';
7
- import { eyeSpec } from './sculpt.js';
8
- import { eyeFrameOf } from '../../core/sdf/eyeSocket.js';
9
- import { srgb, mix3, MAT, distPolyline } from '../../core/build/coatKit.js';
10
- import { clamp, smoothstep, mix, fbm3, rng } from '../../core/math/vec.js';
11
-
12
- // Colour sets (sRGB hex sampled from reference photos in neutral light).
13
- export const MORPHS = {
14
- normal: { ground: 0xc9763f, flank: 0xd99a5c, saddle: 0xa8321f, lateral: 0x983a26, border: 0x1b1310, belly: 0xe9e2d2, check: 0x171412, mark: [0.012, 0.009, 0.008] },
15
- okeetee: { ground: 0xd6843f, flank: 0xe0a45e, saddle: 0xb52f1c, lateral: 0xa23421, border: 0x100b09, belly: 0xece4d2, check: 0x141110, mark: [0.006, 0.004, 0.004], borderW: 2.6 },
16
- wild: { ground: 0xa68a6c, flank: 0xb89c7c, saddle: 0x8a3a24, lateral: 0x7c3a28, border: 0x1d1512, belly: 0xe4ddcc, check: 0x1a1715, mark: [0.013, 0.01, 0.009] },
17
- amel: { ground: 0xeb9656, flank: 0xf0b070, saddle: 0xd9482a, lateral: 0xd65a36, border: 0xf2ece0, belly: 0xf3eee4, check: 0xe8a070, mark: [0.86, 0.82, 0.74], borderW: 1.2 },
18
- anery: { ground: 0xa9a6a2, flank: 0xbdbab4, saddle: 0x4a4847, lateral: 0x55524f, border: 0x151414, belly: 0xe6e4e0, check: 0x161515, mark: [0.01, 0.01, 0.01] },
19
- juvenile: { ground: 0x9c8672, flank: 0xae9a86, saddle: 0x6e2a1c, lateral: 0x5e2a20, border: 0x140e0c, belly: 0xe6e0d2, check: 0x151211, mark: [0.008, 0.006, 0.006] },
20
- };
21
- export const DIAMONDBACK = {
22
- ground: 0x8a7560, flank: 0x9a846a, diamond: 0x56422f, centre: 0x76604a, edge: 0xcdbd9c, blotch: 0x5e4a38,
23
- belly: 0xe2d6bc, ringW: 0xe6e0d4, ringB: 0x1a1715, rattle: 0xbfae8c, mark: [0.69, 0.6, 0.45],
24
- };
25
-
26
- export function markColorOf(params) {
27
- if (params.variant === 'rattlesnake') return DIAMONDBACK.mark;
28
- return (MORPHS[params.coat?.morph] || MORPHS.normal).mark;
29
- }
30
-
31
- export function snakeCoat(ctx) {
32
- const { rig, params, pos, nrm, nV, regionOf, regionNames, weights } = ctx;
33
- const J = rig.J, B = rig.plan, viper = params.variant === 'rattlesnake';
34
- const cp = params.coat || {};
35
- const R = rng(9173 + (cp.seed || 0));
36
- const AXL = weights.axialLengths;
37
- const s0 = AXL[1]; // arc of v0 from the nose
38
- const total = AXL[AXL.length - 1] - s0;
39
- const sVent = total * (1 - B.tailFrac);
40
- const hL = B.headLen, W = B.P.headW * B.kH, H = B.P.headH * B.kH;
41
- const yB = -J.v0[1];
42
- // axis samples: y and half width at arc sigma
43
- const axY = (sg) => {
44
- let i = 1;
45
- while (i < AXL.length - 2 && sg + s0 > AXL[i + 1]) i++;
46
- const t = clamp((sg + s0 - AXL[i]) / (AXL[i + 1] - AXL[i]), 0, 1);
47
- const a = J['v' + (i - 1)], b = J['v' + i];
48
- return a[1] + (b[1] - a[1]) * t;
49
- };
50
- const hwAt = (sg) => B.hw(clamp(sg / total, 0, 1));
51
-
52
- // ---- colours
53
- const juv = params.age === 'juvenile';
54
- const morph = viper ? null : juv && (cp.morph === 'normal' || cp.morph === 'wild') ? MORPHS.juvenile : MORPHS[cp.morph] || MORPHS.normal;
55
- const warm = cp.warm || 0, light = cp.light || 0;
56
- const tweak = (hex) => { const c = srgb(hex); return [c[0] * (1 + 0.1 * warm + light), c[1] * (1 + 0.02 * warm + light), c[2] * (1 - 0.1 * warm + light)]; };
57
- const C0 = viper
58
- ? Object.fromEntries(Object.entries(DIAMONDBACK).filter(([k]) => k !== 'mark').map(([k, v]) => [k, tweak(v)]))
59
- : Object.fromEntries(Object.entries(morph).filter(([k]) => typeof morph[k] === 'number' && k !== 'borderW').map(([k, v]) => [k, tweak(v)]));
60
- // individuals range from bright orange to duller, browner / greyer grounds
61
- const C = C0;
62
- if (!viper && (cp.morph === 'normal' || cp.morph === 'okeetee')) {
63
- const d = clamp(cp.dull || 0, 0, 0.7);
64
- C.ground = mix3(C.ground, srgb(MORPHS.wild.ground), d); C.flank = mix3(C.flank, srgb(MORPHS.wild.flank), d);
65
- C.saddle = mix3(C.saddle, srgb(0x8e2c1c), d * 0.6);
66
- }
67
- if (viper) {
68
- // wild C. atrox range from pale grey through grey-brown to reddish-brown, with bolder or fainter
69
- // diamonds (seeded tone: < 0 greyer, > 0 redder; light: paler / darker)
70
- const tone = clamp(cp.tone || 0, -1, 1), lt = cp.light || 0;
71
- const tgt = tone < 0 ? srgb(0x8f8c86) : srgb(0x9a6a4e);
72
- const k = Math.abs(tone) * 0.75;
73
- for (const key of ['ground', 'flank', 'centre']) C[key] = mix3(C[key], [tgt[0] * (key === 'flank' ? 1.08 : 1), tgt[1] * (key === 'flank' ? 1.08 : 1), tgt[2] * (key === 'flank' ? 1.08 : 1)], k);
74
- C.diamond = mix3(C.diamond, tone < 0 ? srgb(0x4a4744) : srgb(0x5a3526), k * 0.7);
75
- for (const key of ['ground', 'flank', 'centre', 'edge']) C[key] = C[key].map((c) => c * (1 + 1.6 * lt));
76
- C.diamond = mix3(C.diamond, C.ground, clamp(0.3 - 0.3 * ((cp.blotch || 1) - 1) / 0.18, 0, 0.55));
77
- }
78
- const bk = cp.blotch || 1;
79
-
80
- // ---- pattern features (body coordinates)
81
- const sStart = viper ? 0.045 * total : Math.max(0, 1.5 * hL - (0.5 * (total * (1 - B.tailFrac) - 1.5 * hL)) / 36);
82
- const feats = []; // { s, a, b, u (centre across), kind, col }
83
- if (viper) {
84
- const n = Math.round((cp.count || 1) * 29);
85
- const sp = (sVent - sStart) / n;
86
- for (let k = 0; k < n; k++) {
87
- const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.08 * sp;
88
- feats.push({ s, a: 0.56 * sp, b: 0.9, u: 0, kind: 'diamond' });
89
- // flank blotches below the lateral corners of the diamonds
90
- for (const side of [1, -1]) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.1 * sp, a: 0.2 * sp, b: 0.28, u: side * 1.55, kind: 'blotch' });
91
- }
92
- } else {
93
- const n = Math.round((cp.count || 1) * 36);
94
- const sp = (sVent - sStart) / n;
95
- for (let k = 0; k < n; k++) {
96
- const s = sStart + (k + 0.5) * sp + (R() - 0.5) * 0.14 * sp;
97
- feats.push({ s, a: (0.25 + 0.06 * R()) * sp * bk, b: (1.0 + 0.14 * R()) * bk, u: (R() - 0.5) * 0.12, kind: 'saddle' });
98
- for (const side of [1, -1]) if (R() < 0.85) feats.push({ s: s + 0.5 * sp + (R() - 0.5) * 0.2 * sp, a: (0.15 + 0.05 * R()) * sp * bk, b: 0.3 + 0.08 * R(), u: side * (1.72 + 0.1 * R()), kind: 'lateral' });
99
- }
100
- // tail: saddles continue, closer
101
- const nt = Math.round(11 * (cp.count || 1));
102
- const spt = (total * 0.985 - sVent) / nt;
103
- for (let k = 0; k < nt; k++) feats.push({ s: sVent + (k + 0.5) * spt, a: 0.27 * spt * bk, b: 1.2, u: 0, kind: 'saddle' });
104
- }
105
- feats.sort((p, q) => p.s - q.s);
106
- // belly checks (corn): blocks on alternating sides, scute by scute
107
- const checks = [];
108
- const scute = viper ? total / 182 : total / 215;
109
- if (!viper) {
110
- for (let s = 0.09 * total; s < sVent; s += scute) {
111
- if (R() < 0.5) checks.push({ s: s + scute * (0.4 + 0.2 * R()), a: scute * (0.45 + 0.5 * R()), side: R() < 0.5 ? 1 : -1, w: 0.3 + 0.25 * R() });
112
- }
113
- }
114
-
115
- // head and neck features, as 2-D polylines in a projection of the (straight) bind pose:
116
- // top view (x, z) for the crown and nape, side view (z, y) for the flanks of the head
117
- const E = eyeSpec(params);
118
- const eyeC = eyeFrameOf(E, J.v0, 1).c; // left eye centre (x > 0)
119
- const z0 = J.v0[2];
120
- const ez = eyeC[2] - z0, ey = eyeC[1]; // eye position along the head, height (world)
121
- const seg2 = (px, py, P, Wd) => {
122
- let best = 1e9;
123
- for (let i = 0; i < P.length - 1; i++) {
124
- const ax = P[i][0], ay = P[i][1], bx = P[i + 1][0] - ax, by = P[i + 1][1] - ay;
125
- const t = clamp(((px - ax) * bx + (py - ay) * by) / (bx * bx + by * by || 1e-12), 0, 1);
126
- const d = Math.hypot(px - ax - bx * t, py - ay - by * t) - (Wd[i] + (Wd[i + 1] - Wd[i]) * t);
127
- if (d < best) best = d;
128
- }
129
- return best;
130
- };
131
- const kH = B.kH;
132
- // corn: spear point (two blotches meeting in a point between the eyes, running back onto the neck),
133
- // the interocular bar, and the post-ocular stripe through the eye to the jaw angle
134
- const spearL = [[0.0, z0 + ez + 0.003 * kH], [0.06 * W, z0 + 0.4 * hL], [0.11 * W, z0 + 0.0 * hL], [0.14 * W, z0 - 0.35 * hL], [0.1 * W, z0 - 0.75 * hL]];
135
- const spearW = [0.0006, 0.0011, 0.0013, 0.0014, 0.001].map((w) => w * kH);
136
- const barP = [[0, z0 + ez + 0.1 * E.r], [eyeC[0] * 0.85, z0 + ez + 0.4 * E.r]];
137
- const postP = [[z0 + ez - 1.0 * E.r, ey - 0.3 * E.r], [z0 + 0.2 * hL, 0.45 * H], [z0 + 0.02 * hL, 0.36 * H]];
138
- // rattlesnake: pale pre-ocular stripe (front of the eye down to the lip) and post-ocular stripe
139
- // (behind the eye diagonally to the mouth corner), with the dark mask between them
140
- const preV = [[z0 + ez + 1.7 * E.r, ey + 1.1 * E.r], [z0 + 0.78 * hL, 0.62 * H], [z0 + 0.66 * hL, 0.4 * H]];
141
- const postV = [[z0 + ez - 0.5 * E.r, ey - 1.5 * E.r], [z0 + 0.3 * hL, 0.34 * H], [z0 + 0.02 * hL, 0.26 * H]];
142
- const maskV = [[z0 + ez + 0.4 * E.r, ey], [z0 + 0.3 * hL, 0.5 * H], [z0 + 0.05 * hL, 0.42 * H]];
143
- // fine vertical sutures between the labial scales (corn)
144
- const labialMark = (x, z, k) => {
145
- const u = ((z - J.v0[2]) / hL - 0.12) / 0.11; // ~8 labials from the jaw angle to the snout
146
- if (u < 0 || u > 8) return 1;
147
- const f = Math.abs(u - Math.round(u));
148
- return f * 0.11 * hL - 0.00008 * B.kH + (1 - k) * 0;
149
- };
150
- // ---- per-vertex outputs
151
- const comb = new Float32Array(nV * 3), tint = new Float32Array(nV * 4), pattern = new Float32Array(nV).fill(1);
152
- const mark = new Float32Array(nV).fill(1), furLen = new Float32Array(nV), patternColor = new Float32Array(nV * 4), surf = new Float32Array(nV * 4);
153
- const bw = ((morph && morph.borderW) || 1) * 0.0007;
154
- const idx = { body: 0, head: 1, jaw: 2, tongue: 3, fork: 4, fang: 5, rattle: 6 };
155
- for (let v = 0; v < nV; v++) {
156
- const x = pos[v * 3], y = pos[v * 3 + 1], z = pos[v * 3 + 2];
157
- const nx = nrm[v * 3], ny = nrm[v * 3 + 1], nz = nrm[v * 3 + 2];
158
- const reg = idx[regionNames[regionOf[v]]] ?? 0;
159
- // comb: scales overlap tail-ward
160
- let cx = -nx * -nz, cy = -ny * -nz, cz = -1 - nz * -nz;
161
- const cl = Math.hypot(cx, cy, cz) || 1;
162
- comb[v * 3] = cx / cl; comb[v * 3 + 1] = cy / cl; comb[v * 3 + 2] = cz / cl;
163
- const sg = weights.axialS[v] - s0;
164
- const hw = hwAt(Math.max(0, sg));
165
- const ya = axY(Math.max(0, sg));
166
- const th = Math.atan2(x, y - ya); // 0 dorsal, +-pi ventral
167
- const u = th; // angular coordinate (radians around the body)
168
- const nzF = fbm3(x * 60, y * 60, z * 60, 3) - 0.5;
169
- const nLo = fbm3(x * 9, y * 9, z * 9, 3) - 0.5;
170
- let col, mat = MAT.SCALES, gloss = viper ? 0.28 : 0.55, size = (viper ? 0.0042 : 0.0024) * (hw / (viper ? 0.0265 : 0.0135)) ** 0.35, keel = viper ? 0.85 : 0.05, irid = viper ? -0.15 : -0.4;
171
- let pat = 1, pcol = null, mk = 1;
172
- const belly = reg <= 1 && y < 0.1 * hw && ny < -0.75 && sg > -0.2 * hL;
173
- // ---------------- body (and neck)
174
- if (reg === 0 || reg === 1) {
175
- const lat = smoothstep(0.9, 2.2, Math.abs(u)); // 0 dorsal .. 1 low flank
176
- const t = clamp(sg / total, 0, 1);
177
- if (viper) {
178
- col = mix3(C.ground, C.flank, lat);
179
- if (sg > sVent) {
180
- // coon tail: alternating rings from the vent to the rattle
181
- const rl = (total - sVent) / 9.5;
182
- const q = (sg - sVent) / rl;
183
- col = C.ringW;
184
- const f = q - Math.floor(q);
185
- if (Math.floor(q) % 2 === 1) { pat = -Math.min(f, 1 - f) * rl; pcol = C.ringB; }
186
- else pat = Math.min(f, 1 - f) * rl;
187
- pat += nzF * 0.0015;
188
- }
189
- } else {
190
- col = mix3(C.ground, C.flank, lat);
191
- }
192
- // dorsal features near sigma
193
- let lo = 0, hi = feats.length;
194
- while (lo < hi) { const md = (lo + hi) >> 1; if (feats[md].s < sg - 0.06) lo = md + 1; else hi = md; }
195
- for (let k = lo; k < feats.length && feats[k].s < sg + 0.06; k++) {
196
- const F = feats[k];
197
- if (sg > sVent && viper) break;
198
- const ds = sg - F.s, du = (u - F.u) * hw; // metres across the surface
199
- const bb = F.b * hw;
200
- let d;
201
- if (F.kind === 'diamond') {
202
- const L1 = Math.abs(ds) / F.a + Math.abs(du) / bb;
203
- d = (L1 - 1) * (F.a * bb) / Math.hypot(F.a, bb);
204
- } else {
205
- const rr = Math.min(F.a, bb) * 0.55;
206
- const qx = Math.abs(ds) - (F.a - rr), qy = Math.abs(du) - (bb - rr);
207
- d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - rr;
208
- }
209
- d += nzF * (viper ? 0.0012 : 0.0016);
210
- if (d < pat) {
211
- pat = d;
212
- if (F.kind === 'diamond') pcol = mix3(C.diamond, C.centre, smoothstep(-0.003, -0.009, d) * 0.8);
213
- else pcol = F.kind === 'saddle' ? C.saddle : F.kind === 'lateral' ? C.lateral : C.blotch;
214
- }
215
- }
216
- if (viper && sg <= sVent) {
217
- // pale diamond edges (one scale wide), fading into the ground colour
218
- col = mix3(C.edge, col, smoothstep(0.0022, 0.0034, pat + 0.0006 * nzF));
219
- mk = 1;
220
- } else if (!viper) {
221
- mk = Math.abs(pat) - bw * (0.8 + 0.4 * (0.5 + nzF));
222
- }
223
- // belly
224
- if (belly || Math.abs(u) > 2.55) {
225
- const bcol = viper ? C.belly : C.belly;
226
- const e = belly ? 1 : smoothstep(2.55, 2.75, Math.abs(u));
227
- col = mix3(col, bcol, e);
228
- if (e > 0.5) { pat = 1; pcol = null; mk = 1; }
229
- if (!viper && belly) {
230
- // checkerboard blocks, and two dark stripes under the tail
231
- let best = 1;
232
- for (const c of checks) {
233
- if (Math.abs(c.s - sg) > scute * 3) continue;
234
- const qx = Math.abs(sg - c.s) - c.a, xc = c.side * hw * (0.2 + c.w * 0.5), qy = Math.abs(x - xc) - hw * c.w * 0.5;
235
- const d = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0);
236
- if (d < best) best = d;
237
- }
238
- if (sg > sVent) best = Math.min(best, Math.abs(Math.abs(x) - hw * 0.35) - hw * 0.14);
239
- if (best < 1) { pat = best + nzF * 0.0006; pcol = C.check; }
240
- }
241
- if (belly) { mat = MAT.SCALES; gloss = 0.7; size = scute; keel = -1; irid = -0.3; }
242
- }
243
- // head and nape: the scales grow into large head plates over ~1 head length, and their relief
244
- // fades in the same span, so head and neck are one continuous covering (no seam)
245
- // (measured straight back from the occiput in the bind pose, identical on the overlapping head
246
- // and body meshes, so their cross-fade shows no seam)
247
- const sgG = J.v0[2] - z;
248
- const hq = smoothstep(-0.1 * hL, 1.2 * hL, sgG); // 0 on the head .. 1 on the body
249
- if (!belly && sgG < 1.2 * hL) {
250
- const plate = viper ? 0.0027 * kH : 0.009 * kH;
251
- size = size + (plate - size) * (1 - hq);
252
- keel = keel * (viper ? 0.5 + 0.5 * hq : 1);
253
- irid = mix(viper ? -0.5 : -0.75, irid, hq);
254
- gloss = mix(viper ? 0.35 : 0.75, gloss, hq);
255
- }
256
- const upper = ny > -0.15 && y > 0.35 * hw;
257
- if (!viper && sgG < 1.3 * hL) {
258
- // spear point on the crown and nape (top view), with black borders
259
- let d = 1;
260
- if (upper) {
261
- const ax = Math.abs(x);
262
- d = Math.min(seg2(ax, z, spearL, spearW), seg2(ax, z, barP, [0.0012 * kH, 0.0009 * kH])) + nzF * 0.0005;
263
- }
264
- // post-ocular stripe on the side of the head (side view)
265
- if (Math.abs(x) > 0.2 * W && sgG < 0.1 * hL) d = Math.min(d, seg2(z, y, postP, [0.0009 * kH, 0.0012 * kH, 0.0009 * kH]) + nzF * 0.0004);
266
- if (d < pat) { pat = d; pcol = C.saddle; mk = Math.abs(d) - bw * 0.9; }
267
- }
268
- if (viper && sgG < 0.3 * hL && Math.abs(x) > 0.15 * W) {
269
- const dm = seg2(z, y, maskV, [0.0022 * kH, 0.0022 * kH, 0.0016 * kH]);
270
- if (dm < 0) col = mix3(col, C.diamond, 0.75 * smoothstep(0, -0.0015, dm));
271
- mk = Math.min(mk, seg2(z, y, preV, [0.0011 * kH, 0.0018 * kH, 0.0015 * kH]) + nzF * 0.0004, seg2(z, y, postV, [0.0011 * kH, 0.002 * kH, 0.0016 * kH]) + nzF * 0.0004);
272
- }
273
- if (reg === 1 && sgG < 0.02 * hL) {
274
- const hy = y - J.v0[1];
275
- if (viper) {
276
- col = mix3(col, C.edge, 0.8 * smoothstep(yB + 0.46 * H, yB + 0.34 * H, hy));
277
- } else {
278
- // labials: belly cream with thin, faint sutures
279
- if (hy < yB + 0.48 * H) col = mix3(col, C.belly, 0.7 * smoothstep(yB + 0.48 * H, yB + 0.38 * H, hy));
280
- if (hy < yB + 0.48 * H && Math.abs(x) > 0.3 * W) mk = Math.min(mk, labialMark(x, z, 0.1));
281
- }
282
- // mouth: the palate, well inside the lips (no lining shows while the mouth is shut)
283
- if (ny < -0.6 && hy < yB + 0.4 * H && Math.abs(x) < 0.15 * W && z > J.v0[2] + 0.12 * hL && !belly) { mat = MAT.MOUTH; col = srgb(0x8a5a58); pat = 1; mk = 1; }
284
- }
285
- // low-frequency mottling
286
- col = [col[0] * (1 + 0.1 * nLo), col[1] * (1 + 0.1 * nLo), col[2] * (1 + 0.08 * nLo)];
287
- void t;
288
- } else if (reg === 2) {
289
- // lower jaw: pale labials outside (plates, faint relief), mouth lining well inside
290
- const hy = y - J.v0[1];
291
- col = viper ? mix3(C.edge, C.belly, 0.5) : mix3(C.belly, C.flank, 0.3);
292
- size = viper ? 0.003 * kH : 0.008 * kH;
293
- keel = 0; gloss = viper ? 0.5 : 0.75; irid = viper ? -0.5 : -0.75;
294
- if (ny > 0.6 && hy > yB + 0.32 * H && Math.abs(x) < 0.12 * W) { mat = MAT.MOUTH; col = srgb(0x8a5a58); }
295
- } else if (reg === 3 || reg === 4) {
296
- mat = MAT.SKIN;
297
- col = viper ? srgb(0x141212) : reg === 4 ? srgb(0x1a0f0f) : srgb(0x5a1414);
298
- gloss = 0.85;
299
- } else if (reg === 5) {
300
- mat = MAT.KERATIN; col = srgb(0xece4d0); gloss = 0.8;
301
- } else if (reg === 6) {
302
- mat = MAT.KERATIN;
303
- col = mix3(C.rattle, [C.rattle[0] * 0.55, C.rattle[1] * 0.5, C.rattle[2] * 0.45], 0.5 + 0.5 * Math.sin(-z * 900));
304
- gloss = 0.55;
305
- }
306
- tint[v * 4] = col[0]; tint[v * 4 + 1] = col[1]; tint[v * 4 + 2] = col[2]; tint[v * 4 + 3] = mat;
307
- pattern[v] = pat;
308
- mark[v] = mk;
309
- const pc = pcol || col;
310
- patternColor[v * 4] = pc[0]; patternColor[v * 4 + 1] = pc[1]; patternColor[v * 4 + 2] = pc[2]; patternColor[v * 4 + 3] = pcol ? 1 : 0;
311
- surf[v * 4] = gloss; surf[v * 4 + 1] = size; surf[v * 4 + 2] = keel; surf[v * 4 + 3] = irid;
312
- }
313
- void SPINE; void mix;
314
- return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { features: feats.length, checks: checks.length } };
315
- }
@@ -1,73 +0,0 @@
1
- // Snake: corn snake (Pantherophis guttatus) by default; western diamondback rattlesnake
2
- // (Crotalus atrox) with `variant: 'rattlesnake'`. Proving species for the snake body plan
3
- // (core/motion/snake.js, core/rig/snake.js).
4
- import { snakeRig } from './rig.js';
5
- import { sculptSnake, eyeSpec } from './sculpt.js';
6
- import { snakeRegions } from './regions.js';
7
- import { snakeCoat, markColorOf } from './coat.js';
8
- import { motion } from './motion.js';
9
-
10
- const VARIANTS = { corn: 'corn', cornsnake: 'corn', rattlesnake: 'rattlesnake', rattler: 'rattlesnake', diamondback: 'rattlesnake', viper: 'rattlesnake' };
11
-
12
- export default {
13
- id: 'snake',
14
- name: 'Snake',
15
- latin: 'Pantherophis guttatus',
16
- group: 'others',
17
- plan: 'snake',
18
- covering: 'scales',
19
- variants: [
20
- { id: 'corn', name: 'Corn snake', latin: 'Pantherophis guttatus' },
21
- { id: 'rattlesnake', name: 'Western diamondback rattlesnake', latin: 'Crotalus atrox' },
22
- ],
23
-
24
- // Seeded individual. Size: corn adults 0.9-1.5 m (reference 1.2 m), rattlesnake 0.9-1.6 m, males
25
- // larger with longer tails; hatchlings ~0.3 m with relatively bigger heads and eyes.
26
- variation(R, o) {
27
- const variant = VARIANTS[String(o.variant || 'corn').toLowerCase()] || 'corn';
28
- const viper = variant === 'rattlesnake';
29
- const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
30
- const age = o.age || 'adult';
31
- const juv = age === 'juvenile';
32
- const g = () => (R() + R() + R() - 1.5) / 1.5; // ~[-1, 1], peaked
33
- const male = sex === 'male';
34
- const size = (male ? 1.04 : 0.96) * (1 + 0.1 * g()) * (juv ? 0.27 : 1) * (viper && male ? 1.05 : 1);
35
- const shape = {
36
- length: 1,
37
- girth: (1 + 0.07 * g()) * (juv ? 0.92 : 1) * (viper && !male ? 1.04 : 1),
38
- head: (1 + 0.04 * g()) * (juv ? 1.4 : 1),
39
- tail: (male ? 1.08 : 0.94) * (1 + 0.04 * g()),
40
- };
41
- const r = R();
42
- const morph = viper ? 'diamondback' : r < 0.52 ? 'normal' : r < 0.68 ? 'okeetee' : r < 0.84 ? 'wild' : r < 0.94 ? 'amel' : 'anery';
43
- return {
44
- variant, sex, age, size, shape,
45
- warps: [],
46
- minThick: 0.0005,
47
- rattleSegs: viper ? (juv ? 1 : 5 + Math.floor(R() * 4)) : 0,
48
- coat: { morph, seed: Math.floor(R() * 1e6), warm: 1.1 * g(), light: 0.1 * g(), count: 1 + 0.1 * g(), dull: Math.max(0, 0.45 * g() + 0.1), blotch: 1 + 0.18 * g(), tone: viper ? 2 * R() - 1 : 0 },
49
- };
50
- },
51
- rig: (params) => snakeRig(params),
52
- sculpt: (m, rig, params) => sculptSnake(m, rig, params),
53
- regions: snakeRegions,
54
- coat: snakeCoat,
55
- eyeSpecs: (params, rig) => [1, -1].map((side) => ({ side, spec: eyeSpec(params), headOrigin: rig.J.v0, bone: 'head', look: lookOf(params) })),
56
- motion,
57
- // render hints may depend on the individual (the mark colour follows the morph)
58
- render: (params) => ({ markColor: markColorOf(params || {}) }),
59
- };
60
-
61
- function lookOf(params) {
62
- if (params.variant === 'rattlesnake') {
63
- return { pupil: 'slit', lids: 'none', pupilSize: [0.1, 0.55], iris: { inner: [0.12, 0.1, 0.07], mid: [0.33, 0.27, 0.19], hi: [0.5, 0.42, 0.3], outer: [0.16, 0.13, 0.09] }, sclera: { color: [0.2, 0.17, 0.12], visible: false }, lidColor: [0.2, 0.17, 0.13] };
64
- }
65
- const amel = params.coat?.morph === 'amel';
66
- return {
67
- pupil: 'round', lids: 'none', pupilSize: [0.34, 0.5],
68
- iris: amel
69
- ? { inner: [0.35, 0.02, 0.03], mid: [0.62, 0.06, 0.07], hi: [0.8, 0.2, 0.16], outer: [0.4, 0.03, 0.04] }
70
- : { inner: [0.2, 0.06, 0.02], mid: [0.62, 0.22, 0.06], hi: [0.82, 0.42, 0.14], outer: [0.16, 0.05, 0.02] },
71
- sclera: { color: [0.3, 0.12, 0.05], visible: false }, lidColor: [0.3, 0.12, 0.05],
72
- };
73
- }
@@ -1,51 +0,0 @@
1
- // Snake motion data for the snake engine (schema: src/core/motion/snake.js). Numbers are for the
2
- // reference individual (params.size = 1, total length 1.2 m); the engine scales lengths by size and
3
- // speeds by sqrt(size).
4
- //
5
- // Lateral undulation (Jayne 1986, Hu et al. 2009, Fu et al. 2022): every point follows the
6
- // head's path; 1.5-2.5 waves on the body; wavelength 0.35-0.45 of the total length, amplitude
7
- // 0.06-0.10 of it, shrinking at a slow crawl; cruise 0.1-0.3 m/s, sprint ~0.6-0.8 m/s.
8
- // Strike (Penning et al. 2016): peak head speed ~2.7-3 m/s, ~190 m/s^2, 50-90 ms to the target.
9
- export const motion = {
10
- maxSpeed: 0.7,
11
- accel: 0.9,
12
- decel: 1.6,
13
- turnRate: 2.2,
14
- minTurnRadius: 0.1, // x total length
15
- gears: { crawl: 0.07, slither: 0.24, dash: 0.6 },
16
- // [name, speed (m/s), wavelength (x TL), amplitude (x TL), head lift (m), head yaw stabilisation 0..1]
17
- gaits: [
18
- { name: 'crawl', v: 0, wave: 0.5, amp: 0.028, headLift: 0.012, stab: 0.75 },
19
- { name: 'crawl', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.012, stab: 0.7 },
20
- { name: 'slither', v: 0.16, wave: 0.43, amp: 0.066, headLift: 0.009, stab: 0.65 },
21
- { name: 'slither', v: 0.35, wave: 0.4, amp: 0.078, headLift: 0.006, stab: 0.6 },
22
- { name: 'dash', v: 0.45, wave: 0.37, amp: 0.085, headLift: 0.004, stab: 0.55 },
23
- { name: 'dash', v: 0.8, wave: 0.35, amp: 0.09, headLift: 0.003, stab: 0.5 },
24
- ],
25
- body: { neck: 0.12, forebody: 0.3 }, // x TL: neck (raised when looking), strike forebody
26
- tongue: { rate: 0.7, length: 0.6, flicks: [3, 6], freq: 7.5 },
27
- breath: { rate: 0.28, roll: 0.03 },
28
- actions: {
29
- strike: { reach: 0.33, speed: 2.8, gape: 75, coilHeight: 0.07, rise: 0.12 },
30
- coil: { pitch: 0.88, speed: 0.34 },
31
- sit: { raise: 0.13 },
32
- eat: { swallow: 3.2 },
33
- drink: { pumps: 4 },
34
- },
35
- variants: {
36
- // heavy-bodied viper: slower, stiffer undulation, big gape, fangs, rattle
37
- // C. atrox: heavy and slow; slow travel is nearly straight (rectilinear-looking, 1-5 cm/s),
38
- // a fast escape tops out ~0.35 m/s; the rattle is carried just off the ground while moving
39
- rattlesnake: {
40
- maxSpeed: 0.38, gears: { crawl: 0.04, slither: 0.16, dash: 0.34 }, tailCarry: 0.012,
41
- gaits: [
42
- { name: 'crawl', v: 0, wave: 0.6, amp: 0.012, headLift: 0.006, stab: 0.85 },
43
- { name: 'crawl', v: 0.06, wave: 0.55, amp: 0.022, headLift: 0.008, stab: 0.8 },
44
- { name: 'slither', v: 0.1, wave: 0.46, amp: 0.05, headLift: 0.008, stab: 0.7 },
45
- { name: 'slither', v: 0.22, wave: 0.42, amp: 0.062, headLift: 0.006, stab: 0.65 },
46
- { name: 'dash', v: 0.28, wave: 0.4, amp: 0.07, headLift: 0.004, stab: 0.6 },
47
- { name: 'dash', v: 0.38, wave: 0.38, amp: 0.075, headLift: 0.004, stab: 0.55 },
48
- ],
49
- tongue: { length: 0.85, amp: 0.55 }, strike: { gape: 115, reach: 0.36, coilHeight: 0.1 }, rattle: true },
50
- },
51
- };
@@ -1,51 +0,0 @@
1
- // Meshing plan: the long body at ~7 % of the max half width, the head at half that (overlapping the
2
- // body across the neck and cross-faded), the lower jaw, tongue, fangs and rattle as rigid regions.
3
- // Thin parts (tongue, fangs) are dropped at the low and crowd tiers.
4
- import { SPINE } from './rig.js';
5
- import { smoothstep } from '../../core/math/vec.js';
6
-
7
- export function snakeRegions(rig, params, Q) {
8
- const J = rig.J, B = rig.plan, P = B.P, r = Q.res;
9
- let hwMax = 0;
10
- for (let i = 0; i <= 40; i++) hwMax = Math.max(hwMax, B.hw(i / 40));
11
- const hL = B.headLen, W = P.headW * B.kH, H = P.headH * B.kH;
12
- const hBody = 0.078 * hwMax * r;
13
- const hHead = Math.min(hBody * 0.5, 0.012 * hL * r);
14
- const zCut = -0.35 * hL, band = Math.max(0.0025, 1.5 * hBody);
15
- const zTail = J['v' + SPINE][2];
16
- const pad = 3 * hBody;
17
- const y0 = J.v0[1];
18
- const jobs = [
19
- [{ name: 'body', part: 'body', bmin: [-hwMax - pad, -pad, zTail - pad - 0.01], bmax: [hwMax + pad, 2.2 * hwMax + pad, zCut + band + pad], h: hBody, F: 4, clip: (x, y, z) => z < zCut + band }],
20
- ];
21
- const head = [
22
- { name: 'head', part: 'body', bmin: [-0.7 * W, -pad, zCut - band - 0.004], bmax: [0.7 * W, y0 + 1.2 * H, hL * 1.12], h: hHead, F: 5, clip: (x, y, z) => z > zCut - band },
23
- { name: 'jaw', part: 'jaw', bmin: [-0.6 * W, -0.003, -0.1 * hL], bmax: [0.6 * W, y0 + 0.2 * H, hL * 1.05], h: hHead, F: 5, rigidBone: 'jaw' },
24
- ];
25
- if (r < 2.5) {
26
- const tr = 0.0012 * B.kH;
27
- const hT = Math.min(0.00022 * B.kH * r, hHead);
28
- head.push(
29
- { name: 'tongue', part: 'tongue', bmin: [-tr * 2, J.tongueBase[1] - tr * 2, J.tongueBase[2] - tr * 2], bmax: [tr * 2, J.tongueBase[1] + tr * 2, J.tongueFork[2] + tr * 3], h: hT, F: 4, rigidBone: 'tongue' },
30
- { name: 'fork', part: 'fork', bmin: [-0.3 * hL, J.tongueFork[1] - tr * 2, J.tongueFork[2] - tr * 2], bmax: [0.3 * hL, J.tongueFork[1] + tr * 2, J.tongueTip[2] + tr * 2], h: hT, F: 4, rigidBone: 'tongueTip' },
31
- );
32
- if (params.variant === 'rattlesnake') {
33
- head.push({ name: 'fang', part: 'fang', bmin: [-0.4 * W, J.fangTip[1] - 0.004, J.fangTip[2] - 0.004], bmax: [0.4 * W, J.fangBase[1] + 0.003, J.fangBase[2] + 0.003], h: hT, F: 4, rigidBone: 'fang' });
34
- }
35
- }
36
- jobs.push(head);
37
- if (P.rattle) {
38
- const b = J['v' + SPINE];
39
- const L = P.rattle.len * 1.3 * (params.shape?.girth ?? 1);
40
- const w = B.hw(1) * 2;
41
- jobs[0].push({ name: 'rattle', part: 'rattle', bmin: [-w, b[1] - w, b[2] - L - 0.01], bmax: [w, b[1] + w, b[2] + 0.01], h: Math.min(hBody * 0.5, 0.00035 * r), F: 4, rigidBone: 'spine' + (SPINE - 1) });
42
- }
43
- return {
44
- jobs,
45
- fade(x, y, z, name) {
46
- if (name === 'body') return 1 - smoothstep(zCut - band, zCut + band, z);
47
- if (name === 'head') return smoothstep(zCut - band, zCut + band, z);
48
- return 1;
49
- },
50
- };
51
- }
@@ -1,108 +0,0 @@
1
- // Snake skeleton in bind pose (metres): lying straight along -Z, belly on y = 0, head at +Z.
2
- // Two builds share the code: the corn snake (default) and the western diamondback rattlesnake
3
- // (variant). Numbers: total length 1.2 m (reference adults), corn head 3.4 cm and
4
- // max body width 2.7 cm, rattlesnake head 5.2 cm (3.8 cm wide) and max body width 5.2 cm;
5
- // tail 17 % (corn) / 7.5 % (rattlesnake) of the total length.
6
- import { buildRig } from '../../core/rig/rig.js';
7
- import { snakeSpine, snakeBones, snakeAxial, snakeSegments } from '../../core/rig/snake.js';
8
-
9
- export const SPINE = 50; // spine bones (each ~4-5 vertebrae)
10
-
11
- // Per-variant proportions (reference individual). hw = half width of the body (m) at arc fraction t
12
- // (0 = occiput, 1 = tail tip); flat = axis height / half width (loaf cross-section: flat belly).
13
- export const PROFILES = {
14
- corn: {
15
- TL: 1.2, headLen: 0.034, headW: 0.0185, headH: 0.0112, tail: 0.17, flat: 0.74,
16
- hw: [[0, 0.0071], [0.05, 0.0076], [0.22, 0.0112], [0.45, 0.0135], [0.66, 0.0128], [0.8, 0.0098], [0.88, 0.0068], [0.95, 0.0036], [1, 0.0011]],
17
- eye: { r: 0.0026, at: [0.006, 0.0027, 0.0205], yaw: 1.05, pitch: 0.2 },
18
- },
19
- rattlesnake: {
20
- TL: 1.2, headLen: 0.052, headW: 0.038, headH: 0.02, tail: 0.075, flat: 0.64,
21
- hw: [[0, 0.0092], [0.04, 0.0105], [0.2, 0.019], [0.46, 0.0265], [0.7, 0.0238], [0.86, 0.016], [0.92, 0.0112], [0.97, 0.0078], [1, 0.0062]],
22
- eye: { r: 0.0029, at: [0.0133, 0.0052, 0.032], yaw: 1.0, pitch: 0.22 },
23
- rattle: { len: 0.05, segs: 7 },
24
- },
25
- };
26
-
27
- // monotone cubic (Fritsch-Carlson) through [[t, v]...]
28
- export function pchip(keys) {
29
- const n = keys.length, x = keys.map((k) => k[0]), y = keys.map((k) => k[1]);
30
- const d = [], m = new Array(n).fill(0);
31
- for (let i = 0; i < n - 1; i++) d.push((y[i + 1] - y[i]) / (x[i + 1] - x[i]));
32
- m[0] = d[0]; m[n - 1] = d[n - 2];
33
- for (let i = 1; i < n - 1; i++) m[i] = d[i - 1] * d[i] <= 0 ? 0 : (d[i - 1] + d[i]) / 2;
34
- for (let i = 0; i < n - 1; i++) {
35
- if (d[i] === 0) { m[i] = m[i + 1] = 0; continue; }
36
- const a = m[i] / d[i], b = m[i + 1] / d[i], s = a * a + b * b;
37
- if (s > 9) { const t = 3 / Math.sqrt(s); m[i] = t * a * d[i]; m[i + 1] = t * b * d[i]; }
38
- }
39
- return (t) => {
40
- if (t <= x[0]) return y[0];
41
- if (t >= x[n - 1]) return y[n - 1];
42
- let i = 0;
43
- while (i < n - 2 && t > x[i + 1]) i++;
44
- const h = x[i + 1] - x[i], u = (t - x[i]) / h;
45
- const h00 = 2 * u * u * u - 3 * u * u + 1, h10 = u * u * u - 2 * u * u + u, h01 = -2 * u * u * u + 3 * u * u, h11 = u * u * u - u * u;
46
- return h00 * y[i] + h10 * h * m[i] + h01 * y[i + 1] + h11 * h * m[i + 1];
47
- };
48
- }
49
-
50
- // Individual body plan from the variant profile and the seeded proportions in params:
51
- // params.shape = { length, girth, head, tail } multipliers (1 = reference).
52
- export function bodyPlan(params) {
53
- const P = PROFILES[params.variant] || PROFILES.corn;
54
- const sh = params.shape || {};
55
- const kL = sh.length ?? 1, kG = sh.girth ?? 1, kH = sh.head ?? 1, kT = sh.tail ?? 1;
56
- const headLen = P.headLen * kH;
57
- const bodyLen = P.TL * kL - headLen;
58
- const tailFrac = Math.min(0.3, (P.tail * kT * P.TL * kL) / bodyLen);
59
- // re-map the profile's tail portion to this individual's tail fraction
60
- const refTail = (P.tail * P.TL) / (P.TL - P.headLen);
61
- const remap = (t) => (t <= 1 - tailFrac ? (t / (1 - tailFrac)) * (1 - refTail) : 1 - refTail + ((t - (1 - tailFrac)) / tailFrac) * refTail);
62
- const base = pchip(P.hw);
63
- const neckK = kH; // a bigger head comes with a slightly thicker neck
64
- const hw = (t) => {
65
- const r = base(remap(t));
66
- const neck = 1 - Math.min(1, t / 0.1);
67
- return r * (kG * (1 - neck) + (0.5 * kG + 0.5 * neckK) * neck);
68
- };
69
- return { P, headLen, bodyLen, tailFrac, hw, flat: P.flat, kH };
70
- }
71
-
72
- export function snakeRig(params) {
73
- const B = bodyPlan(params);
74
- const P = B.P;
75
- const J = {};
76
- const lens = snakeSegments(SPINE, B.bodyLen, { tipShrink: P.rattle ? 0.8 : 0.5, tipBones: 10 });
77
- snakeSpine(J, 0, lens, (s, t) => B.hw(t) * B.flat);
78
- const y0 = J.v0[1];
79
- const kH = B.kH;
80
- const hL = B.headLen, hH = P.headH * kH;
81
- // head landmarks (head-local: origin v0, +Z forward). The jaw's underside rests on y = 0.
82
- // heights are from the belly plane (the jaw's underside rests on y = 0)
83
- J.nose = [0, 0.56 * hH, hL];
84
- J.jawHinge = [0, 0.34 * hH, 0.06 * hL];
85
- J.jawTip = [0, 0.16 * hH, 0.9 * hL];
86
- // tongue lies in its sheath in the floor of the mouth, tips just behind the chin
87
- J.tongueBase = [0, 0.2 * hH, 0.18 * hL];
88
- J.tongueFork = [0, 0.2 * hH, 0.62 * hL];
89
- J.tongueTip = [0, 0.2 * hH, 0.84 * hL];
90
- // fangs hinge at the front of the upper jaw, folded back along the palate
91
- J.fangBase = [0, 0.38 * hH, 0.72 * hL];
92
- J.fangTip = [0, 0.32 * hH, 0.55 * hL];
93
- const rig = buildRig({
94
- joints: J,
95
- unit: (B.headLen + B.bodyLen) / 1.3,
96
- headOrigin: J.v0,
97
- bones: snakeBones(SPINE, { neck: 4, tail: Math.round(B.tailFrac * SPINE) }),
98
- axial: {
99
- ...snakeAxial(SPINE),
100
- // the skull is rigid right up to the occiput; the neck blends in just behind it
101
- blend: { 1: [-0.004 * kH, 0.006 * kH] },
102
- },
103
- limbs: {},
104
- appendages: [],
105
- });
106
- rig.plan = B;
107
- return rig;
108
- }