@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,137 @@
1
+ // The eagle sculpted as a signed distance field (the feathered silhouette: contour feathers are part
2
+ // of the body shape; flight feathers are cards, see core/build/featherCards.js). Every primitive is
3
+ // attached to a bone and a group (axial / legL / wingL / jaw). The torso is laid out in the upright
4
+ // body-axis frame (rig.js AX); the head in head-local coordinates (HEAD_O, between the eyes).
5
+ import { HEAD_O, NECK_SEGS, AX, U_AX, TILT } from './rig.js';
6
+ import { add, sub, mul, lerp, norm, cross, dot } from '../../core/math/vec.js';
7
+ import { sculptEyeSocket } from '../../core/sdf/eyeSocket.js';
8
+
9
+ // Eye (head-local, left): large, set under the supraorbital shelf, fairly forward (yaw 52 deg: raptor
10
+ // binocular overlap), visible iris ~15 mm, round aperture slightly flattened from above.
11
+ export const EYE = { c: [0.0242, 0.0, 0.004], r: 0.0118, back: 0.0038, yaw: 0.9, pitch: 0.06, lid: 0.0014, R: 0.0096, d: 0.0012, off: -0.0005, tilt: 0.12, irisZ: 0.0074, irisR: 0.0082 };
12
+
13
+ const hl = (v) => [v[0] + HEAD_O[0], v[1] + HEAD_O[1], v[2] + HEAD_O[2]];
14
+ const rad = (d) => (d * Math.PI) / 180;
15
+ const axisAt = (deg) => [0, Math.sin(rad(deg)), Math.cos(rad(deg))];
16
+
17
+ export function sculptEagle(m, rig, params = {}) {
18
+ const J = rig.J;
19
+ const booted = params.variant === 'golden';
20
+ const ellY = (o) => {
21
+ const y = norm(o.ydir);
22
+ let x = o.xdir || [1, 0, 0];
23
+ x = norm(sub(x, mul(y, dot(x, y))));
24
+ const z = cross(x, y);
25
+ return m.ell({ ...o, axis: z, up: y });
26
+ };
27
+ const A = { bone: 'chest' }, Pv = { bone: 'pelvis' };
28
+ const tilt = U_AX;
29
+
30
+ // ---------------------------------------------------------------- TORSO (feathered)
31
+ // A streamlined spindle in the body-axis frame (AX: a along the axis, b dorsal): deepest at the breast
32
+ // under the wing roots, the belly and flanks tapering into the tail. Depth ~0.19 m and width ~0.16 m
33
+ // at the breast (a 4-6 kg bird: ~6 L torso with its feathers; flight photos:
34
+ // body depth ~0.18-0.2 of the bill-to-tail length, the back flat, the belly line rising to the vent).
35
+ m.ell({ ...A, tag: 'breast', c: AX(0.13, -0.02), r: [0.077, 0.074, 0.135], axis: tilt, k: 0 });
36
+ m.ell({ ...A, tag: 'mantle', c: AX(0.08, 0.032), r: [0.076, 0.058, 0.15], axis: tilt, k: 0.04 });
37
+ m.ell({ ...Pv, tag: 'belly', c: AX(-0.01, -0.03), r: [0.062, 0.058, 0.13], axis: axisAt(TILT - 8), k: 0.04 });
38
+ m.ell({ ...Pv, tag: 'rump', c: AX(-0.07, 0.03), r: [0.052, 0.046, 0.085], axis: axisAt(TILT - 6), k: 0.035 });
39
+ const td = norm(sub(J.tailTip, J.tailBase));
40
+ m.ell({ bone: 'tail', tag: 'undertail', c: add(J.tailTip, [0, -0.018, 0.01]), r: [0.038, 0.026, 0.1], axis: mul(td, -1), k: 0.03 });
41
+ m.ell({ bone: 'tail', tag: 'uppertail', c: add(J.tailTip, [0, 0.012, -0.004]), r: [0.038, 0.018, 0.07], axis: mul(td, -1), k: 0.024 });
42
+ for (const s of [1, -1]) {
43
+ m.ell({ ...Pv, tag: 'flankfold', c: AX(0.01, -0.036, 0.042 * s), r: [0.032, 0.054, 0.1], axis: tilt, k: 0.04 });
44
+ m.ell({ ...A, tag: 'scapular', c: AX(0.13, 0.045, 0.05 * s), r: [0.036, 0.028, 0.11], axis: tilt, k: 0.032 });
45
+ }
46
+
47
+ // ---------------------------------------------------------------- NECK (thick, hackled)
48
+ const nj = ['neckBase', ...Array.from({ length: NECK_SEGS - 1 }, (_, i) => 'neck' + (i + 1)), 'occiput'];
49
+ const nr = nj.map((_, i) => { const u = i / NECK_SEGS; return 0.054 - 0.012 * Math.pow(u, 0.8); });
50
+ for (let i = 0; i < NECK_SEGS; i++) m.cone({ bone: 'neck' + i, tag: 'neck', a: J[nj[i]], b: J[nj[i + 1]], ra: nr[i], rb: nr[i + 1], k: 0.04 });
51
+ // (neck landmarks by fraction of the neck: throat ~40 %, nape ~60 %)
52
+ const nAt = (f) => { const x = f * NECK_SEGS, i = Math.min(NECK_SEGS - 1, Math.floor(x)); return { bone: 'neck' + i, p: lerp(J[nj[i]], J[nj[i + 1]], x - i) }; };
53
+ const th = nAt(0.4), np = nAt(0.6);
54
+ m.ell({ bone: th.bone, tag: 'throat', c: add(th.p, [0, -0.004, 0.024]), r: [0.04, 0.05, 0.034], axis: norm([0, 0.6, 1]), k: 0.04 });
55
+ m.ell({ bone: np.bone, tag: 'nape', c: add(np.p, [0, 0.01, -0.026]), r: [0.04, 0.046, 0.038], k: 0.04 });
56
+
57
+ // ---------------------------------------------------------------- HEAD (head-local, see HEAD_O)
58
+ // landmarks from face1 / face2 (profile): eyes at (+-0.0235, 0, 0.004), crown 0.04 above the eyes,
59
+ // occiput (0, -0.026, -0.058), cere 0.03-0.052 ahead of the eye, hook tip (0, -0.036, 0.104)
60
+ const H = { bone: 'head' };
61
+ m.ell({ ...H, tag: 'cranium', c: hl([0, -0.005, -0.028]), r: [0.035, 0.032, 0.05], k: 0.02 });
62
+ m.ell({ ...H, tag: 'crown', c: hl([0, 0.013, -0.006]), r: [0.026, 0.012, 0.042], axis: norm([0, -0.12, 1]), k: 0.016 });
63
+ m.ell({ ...H, tag: 'hackles', c: hl([0, -0.008, -0.062]), r: [0.034, 0.03, 0.032], k: 0.02 });
64
+ m.ell({ ...H, tag: 'forehead', c: hl([0, 0.014, 0.022]), r: [0.019, 0.015, 0.024], axis: norm([0, -0.35, 1]), k: 0.012 });
65
+ m.ell({ ...H, tag: 'chin', c: hl([0, -0.034, -0.012]), r: [0.026, 0.024, 0.044], axis: norm([0, 0.15, 1]), k: 0.02 });
66
+ for (const s of [1, -1]) {
67
+ m.ell({ ...H, tag: 'cheek', c: hl([0.019 * s, -0.016, -0.008]), r: [0.017, 0.02, 0.032], k: 0.014 });
68
+ m.ell({ ...H, tag: 'lore', c: hl([0.013 * s, -0.008, 0.026]), r: [0.011, 0.013, 0.02], axis: norm([0, -0.1, 1]), k: 0.01 });
69
+ // supraorbital ridge: a hard shelf over the front of the eye (the fierce frown)
70
+ ellY({ ...H, tag: 'brow', c: hl([0.0225 * s, 0.0138, 0.01]), ydir: [0.25 * s, 1, 0.12], xdir: [1, 0, 0], r: [0.0095, 0.0055, 0.023], k: 0.005 });
71
+ sculptEyeSocket(m, EYE, HEAD_O, s, { bone: 'head' });
72
+ }
73
+ // upper mandible: deep at the base, laterally compressed, culmen curving into a strong hook; the
74
+ // cere (bare waxy skin with the nostril) covers its base
75
+ m.ell({ ...H, tag: 'cere', c: hl([0, 0.003, 0.036]), r: [0.0148, 0.0135, 0.016], axis: norm([0, -0.1, 1]), k: 0.008 });
76
+ m.ell({ ...H, tag: 'bill', c: hl([0, -0.0095, 0.055]), r: [0.0128, 0.0205, 0.036], axis: norm([0, -0.1, 1]), k: 0.008 });
77
+ m.ell({ ...H, tag: 'bill', c: hl([0, -0.012, 0.082]), r: [0.0095, 0.018, 0.021], axis: norm([0, -0.45, 1]), k: 0.008 });
78
+ m.ell({ ...H, tag: 'bill', c: hl([0, -0.02, 0.1]), r: [0.0074, 0.016, 0.012], axis: norm([0, -0.95, 0.3]), k: 0.006 });
79
+ m.cone({ ...H, tag: 'bill', a: hl([0, -0.029, 0.1055]), b: hl([0, -0.0465, 0.108]), ra: 0.006, rb: 0.0012, k: 0.004 });
80
+ for (const s of [1, -1]) m.ell({ ...H, tag: 'nostril', c: hl([0.0118 * s, 0.003, 0.044]), r: [0.003, 0.0028, 0.005], axis: norm([0, 0.2, 1]), k: 0.002, carve: true });
81
+ // lower mandible (own surface so the bill can open), tucked inside the hook
82
+ const Jw = { bone: 'jaw', part: 'jaw' };
83
+ m.ell({ ...Jw, tag: 'mandible', c: hl([0, -0.035, 0.045]), r: [0.0125, 0.0072, 0.046], axis: norm([0, -0.08, 1]), k: 0.006 });
84
+ m.cone({ ...Jw, tag: 'mandible', a: hl([0, -0.036, 0.072]), b: hl([0, -0.0395, 0.095]), ra: 0.007, rb: 0.0018, k: 0.005 });
85
+ m.ell({ ...Jw, tag: 'gonys', c: hl([0, -0.039, 0.02]), r: [0.016, 0.008, 0.03], k: 0.008 });
86
+
87
+ // ---------------------------------------------------------------- LEGS
88
+ for (const s of [1, -1]) {
89
+ const S = s > 0 ? 'L' : 'R', g = 'leg' + S;
90
+ const j = (n) => J[n + S];
91
+ const kn = j('knee'), an = j('ankle'), mt = j('mtp');
92
+ // feathered "trousers": long loose tibial feathers hanging over the ankle
93
+ ellY({ bone: 'tibia' + S, group: g, tag: 'thigh', c: lerp(kn, an, 0.25), ydir: sub(an, kn), r: [0.036, 0.058, 0.032], k: 0.04 });
94
+ m.cone({ bone: 'tibia' + S, group: g, tag: 'trousers', a: lerp(kn, an, 0.3), b: lerp(kn, an, 0.96), ra: 0.029, rb: 0.021, k: 0.02 });
95
+ if (booted) {
96
+ // golden eagle: feathered to the toes
97
+ m.cone({ bone: 'tarsus' + S, group: g, tag: 'boot', a: lerp(an, mt, 0.05), b: lerp(an, mt, 0.66), ra: 0.017, rb: 0.0115, k: 0.006 });
98
+ } else {
99
+ m.cone({ bone: 'tarsus' + S, group: g, tag: 'cuff', a: an, b: lerp(an, mt, 0.42), ra: 0.02, rb: 0.0135, k: 0.01 });
100
+ }
101
+ m.cone({ bone: 'tarsus' + S, group: g, tag: 'tarsus', a: an, b: mt, ra: 0.0115, rb: 0.0098, k: 0.006 });
102
+ m.sphere({ bone: 'tarsus' + S, group: g, tag: 'pad', c: add(mt, [0, -0.001, 0.003]), rad: 0.0105, k: 0.005 });
103
+ for (const t of [1, 2, 3, 4]) {
104
+ const a = mt, b = J[`t${t}m${S}`], c = J[`t${t}t${S}`];
105
+ const big = t === 1 || t === 2; // hallux and inner toe carry the largest talons
106
+ const r0 = t === 1 ? 0.0105 : 0.0092;
107
+ m.cone({ bone: `toe${t}a${S}`, group: g, tag: 'toe', a, b, ra: r0, rb: 0.0082, k: 0.005 });
108
+ m.cone({ bone: `toe${t}b${S}`, group: g, tag: 'toe', a: b, b: lerp(b, c, 0.5), ra: 0.0082, rb: 0.0068, k: 0.004 });
109
+ // talon: thick at the base, strongly curved down to a needle point
110
+ const q0 = add(lerp(b, c, 0.42), [0, 0.004, 0]), q1 = add(lerp(b, c, 0.78), [0, 0.0035, 0]), q2 = add(c, [0, -0.0035, 0]);
111
+ const tr = big ? 0.0062 : 0.0054;
112
+ m.cone({ bone: `toe${t}b${S}`, group: g, tag: 'claw', a: q0, b: q1, ra: tr, rb: tr * 0.62, k: 0.002 });
113
+ m.cone({ bone: `toe${t}b${S}`, group: g, tag: 'claw', a: q1, b: q2, ra: tr * 0.62, rb: 0.0008, k: 0.0015 });
114
+ }
115
+ }
116
+
117
+ // ---------------------------------------------------------------- WINGS (arm; flight feathers are cards)
118
+ for (const s of [1, -1]) {
119
+ const S = s > 0 ? 'L' : 'R', g = 'wing' + S;
120
+ const sh = J['shoulder' + S], el = J['elbow' + S], wr = J['wrist' + S], tp = J['handTip' + S];
121
+ let n = norm(cross(sub(el, sh), sub(wr, el)));
122
+ if (n[1] < 0) n = mul(n, -1);
123
+ const seg = (a, b, bone, thick, chord, back, tag, k = 0.016) => {
124
+ const d = norm(sub(b, a));
125
+ const t = mul(norm(cross(n, d)), s); // trailing side
126
+ const c = add(lerp(a, b, 0.5), mul(t, back));
127
+ return ellY({ bone, group: g, tag, c, ydir: d, xdir: n, r: [thick, Math.hypot(...sub(b, a)) * 0.58, chord], k });
128
+ };
129
+ seg(sh, el, 'humerus' + S, 0.019, 0.05, 0.018, 'arm', 0.024);
130
+ seg(el, wr, 'ulna' + S, 0.015, 0.052, 0.022, 'arm');
131
+ seg(wr, tp, 'hand' + S, 0.012, 0.026, 0.008, 'hand', 0.012);
132
+ // leading-edge roll (propatagium) along the forearm: on the ulna only, so it stays on the arm
133
+ // when the elbow opens (a web spanning shoulder -> wrist would swing forward as a loop)
134
+ seg(lerp(el, wr, 0.08), wr, 'ulna' + S, 0.011, 0.016, -0.02, 'patagium', 0.014);
135
+ m.sphere({ bone: 'hand' + S, group: g, tag: 'wrist', c: wr, rad: 0.0135, k: 0.012 });
136
+ }
137
+ }
@@ -0,0 +1,271 @@
1
+ // Fish coat: scales (material 6, with iridescence) on the body, smooth glossy skin on the scaleless
2
+ // head, fins as translucent membranes (material 11: ray phase / position along the ray / opacity in
3
+ // `surf`), variant colour schemes (trout spots and pink band, goldfish metallic orange and sarasa
4
+ // patches, clownfish bands with black edging, bluegill bars, ear flap and breast), mouth and gills.
5
+ import { SDFModel } from '../../core/sdf/sdf.js';
6
+ import { finRayCoords } from '../../core/rig/swimmer.js';
7
+ import { norm, sub, dot, cross, clamp, smoothstep, mix, rng, fbm3 } from '../../core/math/vec.js';
8
+ import { srgb, mix3, MAT, poissonFeatures, featureDistance, smoothField } from '../../core/build/coatKit.js';
9
+ import { fishGeo } from './rig.js';
10
+ import { CELL, opEdgeU } from './sculpt.js';
11
+
12
+ const FIN_MAT = MAT.FIN ?? 11;
13
+
14
+ export function fishCoat(ctx) {
15
+ const { model, pos, nrm, nV, lists, regionOf, regionNames, params, weights } = ctx;
16
+ const G = fishGeo(params);
17
+ const V = G.V, SL = G.SL, H = V.head;
18
+ const key = params.variant;
19
+ const R = rng(4049 + (params.coatSeed || 0));
20
+ const col = {};
21
+ for (const [k, hex] of Object.entries(V.colour)) col[k] = srgb(hex);
22
+ const morph = params.morph || '';
23
+ const shade = params.coatShade || 0;
24
+ const tweak = (c, k = 1) => [c[0] * (1 + shade * 0.12 * k), c[1] * (1 + shade * 0.08 * k), c[2] * (1 + shade * 0.04 * k)];
25
+ const cell = SL * CELL * (V.cellK || 1);
26
+
27
+ const comb = new Float32Array(nV * 3);
28
+ const tint = new Float32Array(nV * 4);
29
+ const pattern = new Float32Array(nV).fill(1);
30
+ const patternColor = new Float32Array(nV * 4);
31
+ const mark = new Float32Array(nV).fill(1);
32
+ const furLen = new Float32Array(nV);
33
+ const surf = new Float32Array(nV * 4);
34
+ const kind = new Uint8Array(nV); // 0 body, 1 head, 2 fin, 5 adipose, 6 jaw, 7 ear flap, 8 buccal cavity, 9 lips' cut faces
35
+ const finInfo = new Array(nV);
36
+ const jawRegion = regionNames.indexOf('jaw');
37
+
38
+ for (let v = 0; v < nV; v++) {
39
+ const p = [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
40
+ const n = [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]];
41
+ let best = null, bd = 1e9, carveHit = null;
42
+ for (const pr of lists[v]) {
43
+ const d = SDFModel.dist(pr, p[0], p[1], p[2]);
44
+ if (pr.carve) { if (Math.abs(d) < cell * 0.9 && (!carveHit || Math.abs(d) < carveHit.d)) carveHit = { pr, d: Math.abs(d) }; continue; }
45
+ if (d < bd) { bd = d; best = pr; }
46
+ }
47
+ const u = G.u(p[2]);
48
+ const u1 = clamp(u, 0, 1), e1 = (p[1] - G.yc(u1)) / Math.max(1e-6, G.hd(u1));
49
+ // head (scaleless) in front of the gill cover's free edge, scaled body behind it
50
+ let k = u < opEdgeU(H.op, clamp(e1, -1.2, 1.2)) ? 1 : 0;
51
+ if (regionOf[v] === jawRegion) k = 6;
52
+ if (best && (best.tag === 'fin')) { k = 2; finInfo[v] = { prim: best, rc: finRayCoords(best.fin, p) }; }
53
+ if (best && best.tag === 'adipose') k = 5;
54
+ if (carveHit) {
55
+ const tg = carveHit.pr.tag;
56
+ if ((tg === 'mouthcut' && n[1] < -0.25) || (tg === 'jawcut' && n[1] > 0.3)) k = 9;
57
+ else if (tg === 'mouth') k = 8;
58
+ }
59
+ if (best && best.tag === 'earflap') k = 7;
60
+ kind[v] = k;
61
+ void n;
62
+ }
63
+
64
+ // ---- spots (trout on the body and on the dorsal / adipose / caudal fins; bluegill dorsal blotch)
65
+ let spotsF = null;
66
+ if (key === 'trout' || morph === 'sarasa') {
67
+ const sarasa = morph === 'sarasa';
68
+ spotsF = poissonFeatures({
69
+ pos, nrm, nV, R,
70
+ radius: (v) => {
71
+ const k = kind[v];
72
+ const y = pos[v * 3 + 1], u = G.u(pos[v * 3 + 2]);
73
+ if (sarasa) {
74
+ if (k !== 0 && k !== 1) return 0;
75
+ return 0.07 * SL;
76
+ }
77
+ if (k === 2) {
78
+ const fi = finInfo[v];
79
+ const nm = fi.prim.fin.name;
80
+ if (!(nm.startsWith('dorsal') || nm === 'caudal')) return 0;
81
+ return 0.0075 * SL;
82
+ }
83
+ if (k !== 0 && k !== 5 && k !== 1) return 0;
84
+ const e = (y - G.yc(u)) / G.hd(u);
85
+ const dens = smoothstep(-0.35, 0.25, e) * (u < 0.1 ? 0 : 1);
86
+ return dens > 0.2 ? (0.0042 + 0.003 * dens) * SL * (params.spotScale || 1) : 0;
87
+ },
88
+ spacing: sarasa ? 1.4 : 1.9, gap: sarasa ? 0.01 * SL : 0.003 * SL, cell: sarasa ? 0.12 * SL : 0.03 * SL,
89
+ });
90
+ }
91
+
92
+ for (let v = 0; v < nV; v++) {
93
+ const o3 = v * 3, o4 = v * 4;
94
+ const p = [pos[o3], pos[o3 + 1], pos[o3 + 2]];
95
+ const n = [nrm[o3], nrm[o3 + 1], nrm[o3 + 2]];
96
+ const k = kind[v];
97
+ const u = clamp(G.u(p[2]), 0, 1.3);
98
+ const uc = Math.min(u, 1);
99
+ const e = (p[1] - G.yc(uc)) / Math.max(1e-6, G.hd(uc));
100
+ const noise = fbm3(p[0] * 9 / SL, p[1] * 9 / SL, p[2] * 9 / SL, 3) - 0.5;
101
+ let c, mat = MAT.SCALES, gl = V.gloss, sz = V.scale * SL, irid = V.irid, sw = 0;
102
+ let cm = norm(sub([0, 0, -1], mul(n, -n[2])));
103
+ if (k === 2) {
104
+ // ---------------- fin membrane
105
+ const { prim, rc } = finInfo[v];
106
+ const nm = prim.fin.name;
107
+ cm = rc.dir;
108
+ mat = FIN_MAT;
109
+ c = mix3(col.fin, col.finEdge, 0);
110
+ let opac = key === 'clownfish' ? 0.86 : key === 'bluegill' ? 0.7 : key === 'goldfish' ? 0.55 : 0.6;
111
+ const along = rc.along;
112
+ c = mix3(c, mix3(c, col.back, 0.4), smoothstep(0.5, 0.0, along) * 0.5);
113
+ if (key === 'trout') {
114
+ // white leading edge on the pelvic and anal fins, olive elsewhere
115
+ if (nm.startsWith('pelvic') || nm === 'anal') {
116
+ const lead = nm === 'anal' ? rc.phase < 1.2 : rc.phase < 1.2;
117
+ if (lead) c = mix3(c, col.finEdge, 0.85);
118
+ if (nm.startsWith('pelvic') || nm === 'anal') c = mix3(c, srgb(0xb58a6a), 0.3);
119
+ }
120
+ } else if (key === 'goldfish') {
121
+ c = mix3(col.fin, col.finEdge, smoothstep(0.3, 1, along));
122
+ } else if (key === 'clownfish') {
123
+ // black margins: the outer band of every fin
124
+ const rayLen = 1;
125
+ void rayLen;
126
+ const margin = nm.startsWith('pectoral') ? 0.0 : 0.13 * (params.edgeK ?? 1);
127
+ if (margin > 0) mark[v] = (1 - along - margin) * 0.2 * SL;
128
+ } else if (key === 'bluegill') {
129
+ if (nm === 'dorsal1') {
130
+ // dark blotch at the rear of the soft dorsal fin
131
+ const ph = rc.phase / Math.max(1, prim.fin.rays.length - 1);
132
+ const d = Math.hypot((ph - 0.88) * 0.55, (along - 0.32) * 0.45) - 0.16;
133
+ pattern[v] = d * 0.1 * SL;
134
+ patternColor.set([...srgb(0x1e1e18), 0.9], o4);
135
+ }
136
+ if (nm.startsWith('pelvic') || nm === 'anal') c = mix3(c, srgb(0x3c3a2c), 0.5);
137
+ }
138
+ if (morph === 'black' && key === 'clownfish') c = mix3(c, srgb(0x2a160c), 0.7);
139
+ c = tweak(c, 0.5);
140
+ sw = opac * (0.92 + 0.16 * noise);
141
+ surf[o4] = 0.75; surf[o4 + 1] = rc.phase; surf[o4 + 2] = along; surf[o4 + 3] = sw;
142
+ if (spotsF && (nm.startsWith('dorsal') || nm === 'caudal')) {
143
+ const [d] = featureDistance(spotsF, p, n);
144
+ pattern[v] = d; patternColor.set([...col.spot, 1], o4);
145
+ }
146
+ tint.set([c[0], c[1], c[2], mat], o4);
147
+ comb.set(cm, o3);
148
+ continue;
149
+ }
150
+ if (k === 8) {
151
+ // buccal cavity (seen only when the mouth opens): dark, moist red-brown, not a black hole
152
+ c = srgb(0x4a2622);
153
+ tint.set([c[0], c[1], c[2], MAT.SKIN], o4);
154
+ comb.set(cm, o3);
155
+ surf.set([0.6, 0, 0, 0], o4);
156
+ continue;
157
+ }
158
+ // ---------------- body, head, jaw, adipose
159
+ const up = smoothstep(-0.2, 0.9, e * 0.8 + n[1] * 0.35);
160
+ const lowK = smoothstep(0.2, -0.85, e * 0.85 + n[1] * 0.25);
161
+ if (key === 'trout') {
162
+ c = mix3(col.lower, col.upper, smoothstep(-0.35, 0.3, e));
163
+ c = mix3(c, col.back, smoothstep(0.35, 0.95, up));
164
+ c = mix3(c, col.belly, smoothstep(0.4, 0.95, lowK));
165
+ // the pink lateral band from the gill cover to the tail (and a flush on the cheek)
166
+ const band = (1 - smoothstep(0.1, 0.34, Math.abs(e - 0.02))) * smoothstep(0.16, 0.3, u) * (params.bandK ?? 1);
167
+ c = mix3(c, col.band, band * 0.55);
168
+ if (k === 1 || k === 6) {
169
+ const cheek = (1 - smoothstep(0.2, 0.7, Math.abs(e + 0.1))) * smoothstep(0.1, 0.2, u);
170
+ c = mix3(mix3(c, col.head, 0.25), col.cheek, cheek * 0.6 * (params.bandK ?? 1));
171
+ }
172
+ } else if (key === 'goldfish') {
173
+ c = mix3(col.lower, col.upper, smoothstep(-0.5, 0.3, e));
174
+ c = mix3(c, col.back, smoothstep(0.45, 0.95, up));
175
+ c = mix3(c, col.belly, smoothstep(0.35, 0.95, lowK));
176
+ if (morph === 'white') c = mix3(srgb(0xf1eee6), srgb(0xf6e7c8), smoothstep(0.3, 0.9, up) * 0.4);
177
+ if (morph === 'red') c = mix3(c, srgb(0xd83414), 0.72);
178
+ // common goldfish range from orange-gold to red-orange
179
+ if (morph === 'common') { const t = (params.hueShift ?? 0.5) - 0.5; c = t < 0 ? mix3(c, srgb(0xf5a834), -t * 1.1) : mix3(c, srgb(0xe85a18), t * 0.8); }
180
+ } else if (key === 'clownfish') {
181
+ c = mix3(col.lower, col.upper, smoothstep(-0.5, 0.2, e));
182
+ c = mix3(c, col.back, smoothstep(0.5, 1.0, up) * 0.6);
183
+ c = mix3(c, col.belly, smoothstep(0.5, 1.0, lowK) * 0.5);
184
+ if (morph === 'black') c = mix3(c, srgb(0x1c120c), 0.85);
185
+ } else {
186
+ // bluegill: olive back, brassy flanks, orange / yellow breast, dusky vertical bars, blue cheek
187
+ c = mix3(col.lower, col.upper, smoothstep(-0.5, 0.25, e));
188
+ c = mix3(c, col.back, smoothstep(0.4, 0.95, up));
189
+ const breast = smoothstep(0.1, 0.85, lowK) * (1 - smoothstep(0.35, 0.6, u));
190
+ c = mix3(c, params.sex === 'male' ? col.breast : col.belly, breast * 0.85);
191
+ c = mix3(c, col.belly, smoothstep(0.55, 1, lowK) * (1 - breast) * 0.5);
192
+ const barPh = (u - 0.3) / 0.105;
193
+ const bar = u > 0.28 && u < 0.95 ? Math.pow(0.5 + 0.5 * Math.cos(barPh * 2 * Math.PI), 3) * smoothstep(-0.7, 0.2, e) : 0;
194
+ c = mix3(c, col.spot, bar * 0.45 * (params.barK ?? 1));
195
+ if (k === 1 || k === 6) {
196
+ const streak = 0.5 + 0.5 * Math.sin((p[1] - G.axisY) / SL * 90 + u * 30);
197
+ // two or three blue-violet streaks across the lower cheek and gill cover
198
+ const zone = (1 - smoothstep(0.15, 0.45, Math.abs(e + 0.35))) * smoothstep(0.1, 0.18, u) * (1 - smoothstep(0.28, 0.33, u));
199
+ c = mix3(c, col.cheek, zone * smoothstep(0.55, 0.85, streak) * 0.5);
200
+ }
201
+ }
202
+ if (k === 7) c = col.ear;
203
+ if (col.ear && (k === 0 || k === 1)) {
204
+ // the bluegill's opercular flap: a solid black lobe (~0.08 x 0.05 SL) at the top rear of the gill cover
205
+ const fz = (u - (H.op + 0.03)) / 0.058, fy = (p[1] - (G.yc(H.op) + 0.2 * G.hd(H.op))) / (0.034 * SL);
206
+ const r = fz * fz + fy * fy;
207
+ if (r < 1.25) c = mix3(c, col.ear, smoothstep(1.25, 0.9, r));
208
+ }
209
+ // the lips' cut faces (the lip line at rest, the inside of the lips when the mouth opens): the
210
+ // skin colour a little darker, toward the pale pink of the mouth lining
211
+ if (k === 9) c = mix3(c.map((x) => x * 0.85), srgb(0xb88a80), 0.3);
212
+ // the gill cover's free edge: a soft shading line (the plate overlaps the body there), the head
213
+ // colour darkened ~15 %, fading out where the edge runs under the throat and the nape
214
+ const eC = clamp(e, -1.2, 1.2);
215
+ const du = u - opEdgeU(H.op, eC);
216
+ const edgeLine = (k === 0 || k === 1) ? smoothstep(-0.005, 0.0, du) * (1 - smoothstep(0.0015, 0.008, du)) * (1 - smoothstep(0.62, 0.9, Math.abs(eC + 0.05))) : 0;
217
+ c = c.map((x) => x * (1 - 0.15 * edgeLine));
218
+ c = mix3(c, c.map((x) => x * 0.8), 0.25 * (noise + 0.5));
219
+ c = tweak(c);
220
+ if (k === 5) { sz = 0.0008 * SL; }
221
+ // the head is scaleless skin (tiny scale size: no scale relief, same sheen); behind the gill
222
+ // cover the scales fade in over V.scaleFade of SL (surf.w < 0 lowers their contrast), so large
223
+ // scales (goldfish) do not start at a hard line
224
+ let sw2 = irid;
225
+ if (k === 1 || k === 6 || k === 9) sz = 0.0006 * SL;
226
+ else if (k === 0 && V.scaleFade && du < V.scaleFade) sw2 = mix(-1, irid, clamp(du / V.scaleFade, 0, 1));
227
+ // spots / patches
228
+ if (spotsF && (k === 0 || k === 1 || k === 5 || (k === 6 && morph === 'sarasa'))) {
229
+ const [d] = featureDistance(spotsF, p, n);
230
+ pattern[v] = d;
231
+ patternColor.set([...col.spot, 1], o4);
232
+ }
233
+ if (key === 'clownfish') {
234
+ const bandsSD = clownBands(G, u, e);
235
+ pattern[v] = bandsSD;
236
+ patternColor.set([...col.spot, 1], o4);
237
+ // black edging around the bands
238
+ mark[v] = Math.abs(bandsSD) - 0.012 * SL * (params.edgeK ?? 1);
239
+ if (morph === 'black') patternColor.set([...col.spot, 1], o4);
240
+ }
241
+ tint.set([c[0], c[1], c[2], mat], o4);
242
+ comb.set(cm, o3);
243
+ surf.set([gl, sz, 0, sw2], o4);
244
+ }
245
+ // far from any feature the SDF is 'infinite': clamp it, or its screen-space derivative (the shader's
246
+ // antialiasing width) explodes across the triangles on that border and paints a stepped line
247
+ for (let v = 0; v < nV; v++) { if (pattern[v] > 0.03 * SL) pattern[v] = 0.03 * SL; if (mark[v] > 0.03 * SL) mark[v] = 0.03 * SL; }
248
+ // soften fin opacity / phase noise across the mesh
249
+ const w = new Float32Array(nV);
250
+ for (let v = 0; v < nV; v++) w[v] = surf[v * 4 + 3];
251
+ smoothField(w, weights.neighbors, 1, (v) => kind[v] !== 2);
252
+ for (let v = 0; v < nV; v++) if (kind[v] === 2) surf[v * 4 + 3] = w[v];
253
+ return { comb, tint, pattern, mark, furLen, patternColor, surf, stats: { spots: spotsF ? spotsF.spots.length : 0 } };
254
+ }
255
+
256
+ const mul = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
257
+
258
+ // clownfish: three white bands (behind the eye curving forward, mid-body with a forward bulge,
259
+ // caudal peduncle), as a signed distance in metres (< 0 inside a band)
260
+ function clownBands(G, u, e) {
261
+ const SL = G.SL;
262
+ // the forehead and chin stand above the outline table (the sections overlap there): without the
263
+ // clamp the head band's forward curve ran onto the snout as a white smear
264
+ e = clamp(e, -1.1, 1.1);
265
+ const band = (c, w) => (Math.abs(u - c) - w) * SL;
266
+ const b1 = band(0.235 - 0.05 * e * e + 0.01 * e, 0.045);
267
+ const b2 = band(0.53 + 0.07 * Math.exp(-e * e * 4) - 0.03, 0.05 + 0.012 * Math.exp(-e * e * 4));
268
+ const b3 = band(0.905, 0.035);
269
+ return Math.min(b1, b2, b3);
270
+ }
271
+ void cross; void dot; void mix;
@@ -0,0 +1,88 @@
1
+ // Fish (teleosts): rainbow trout, goldfish, ocellaris clownfish and bluegill as seeded / explicit
2
+ // variants (createAnimal('fish', { variant: 'trout' })). Swimmer body plan (core/motion/swimmer.js).
3
+ import { fishRig, fishGeo } from './rig.js';
4
+ import { sculptFish, CELL } from './sculpt.js';
5
+ import { fishRegions } from './regions.js';
6
+ import { fishCoat } from './coat.js';
7
+ import { motion } from './motion.js';
8
+ import { VARIANTS, VARIANT_KEYS } from './variants.js';
9
+ import { srgb } from '../../core/build/coatKit.js';
10
+ import { rng } from '../../core/math/vec.js';
11
+
12
+ const pick = (R, table) => {
13
+ let x = R(), acc = 0;
14
+ for (const [k, w] of table) { acc += w; if (x < acc) return k; }
15
+ return table[table.length - 1][0];
16
+ };
17
+
18
+ export default {
19
+ id: 'fish',
20
+ name: 'Fish',
21
+ latin: 'Teleostei',
22
+ group: 'swimmers',
23
+ plan: 'swimmer',
24
+ covering: 'scales',
25
+ variants: VARIANT_KEYS.map((id) => ({ id, name: VARIANTS[id].name, latin: VARIANTS[id].latin })),
26
+
27
+ // Seeded individual. The variant (species) comes from opts.variant or from the seed; within a
28
+ // variant: size, body depth, head size, colour morph, sex (bluegill breeding colours, clownfish
29
+ // females larger), juveniles smaller with bigger heads and eyes.
30
+ variation(R, o) {
31
+ const variant = o.variant && VARIANTS[o.variant] ? o.variant : VARIANT_KEYS[Math.floor(R() * VARIANT_KEYS.length) % VARIANT_KEYS.length];
32
+ const sex = o.sex || (R() < 0.5 ? 'male' : 'female');
33
+ const age = o.age || 'adult';
34
+ const juv = age === 'juvenile';
35
+ const g = () => (R() + R() + R() - 1.5) / 1.5;
36
+ const G = fishGeo({ variant });
37
+ let sexK = 1;
38
+ if (variant === 'clownfish') sexK = sex === 'female' ? 1.1 : 0.86;
39
+ if (variant === 'trout') sexK = sex === 'male' ? 1.04 : 0.97;
40
+ const size = sexK * (1 + 0.1 * g()) * (juv ? 0.45 : 1);
41
+ // colour morphs from their own hashed stream: consecutive seeds must not correlate
42
+ const M = rng(Math.imul((o.seed ?? 1) + 0x632be5ab, 0x9e3779b1) ^ 0x5bd1e995);
43
+ for (let i = 0; i < 4; i++) M();
44
+ let morph = '';
45
+ if (variant === 'trout') morph = pick(M, [['stream', 0.72], ['silver', 0.28]]);
46
+ if (variant === 'goldfish') morph = pick(M, [['common', 0.55], ['red', 0.17], ['sarasa', 0.2], ['white', 0.08]]);
47
+ if (variant === 'clownfish') morph = pick(M, [['orange', 0.9], ['black', 0.1]]);
48
+ const shadeM = M();
49
+ const SL = G.SL;
50
+ const depthK = 1 + 0.05 * g() + (variant === 'trout' && sex === 'female' ? 0.02 : 0);
51
+ return {
52
+ variant, sex, age, size, morph,
53
+ warps: [
54
+ { type: 'girth', k: depthK, cy: G.axisY, z0: G.z(0.95), z1: G.z(0.15), fade: 0.12 * SL },
55
+ { type: 'scaleAbout', c: G.headO, k: (1 + 0.035 * g()) * (juv ? 1.14 : 1), r0: 0.12 * SL, r1: 0.3 * SL },
56
+ ],
57
+ coatShade: 0.6 * g(),
58
+ hueShift: variant === 'goldfish' && morph === 'common' ? shadeM : 0.5,
59
+ coatSeed: Math.floor(R() * 1e6),
60
+ bandK: variant === 'trout' ? (morph === 'silver' ? 0.3 : 0.8 + 0.25 * R()) * (sex === 'male' ? 1.15 : 0.9) : 1,
61
+ barK: variant === 'bluegill' ? (sex === 'male' ? 0.8 : 1.1) * (0.8 + 0.4 * R()) : 1,
62
+ edgeK: variant === 'clownfish' ? 0.8 + 0.5 * R() : 1,
63
+ spotScale: 0.8 + 0.4 * R(),
64
+ tailK: (variant === 'goldfish' && morph !== 'common' && R() < 0.5 ? 1.3 : 1) * (1 + 0.06 * g()),
65
+ finK: 1 + 0.07 * g() + (juv ? 0.08 : 0),
66
+ minThick: SL * CELL * (VARIANTS[variant].cellK || 1) * 1.15,
67
+ };
68
+ },
69
+ rig: (params) => fishRig(params),
70
+ sculpt: (m, rig, params) => sculptFish(m, rig, params),
71
+ regions: fishRegions,
72
+ coat: fishCoat,
73
+ eyeSpecs(params) {
74
+ const G = fishGeo(params);
75
+ const E = G.eye, V = G.V;
76
+ const spec = { c: [E.x, 0, 0], r: E.r, back: 0, yaw: 1.28, pitch: 0.1, lid: 0, R: E.r * 1.0, d: 0, off: 0, tilt: 0, irisZ: E.r * 0.42, irisR: E.r * 0.86 };
77
+ const iris = V.eye.iris.map((h) => srgb(h));
78
+ const look = {
79
+ pupil: 'round', lids: 'none', pupilSize: [0.34, 0.42],
80
+ iris: { inner: iris[0], mid: iris[1], hi: iris[2], outer: iris[3] },
81
+ sclera: { color: [0.05, 0.05, 0.05], visible: false },
82
+ };
83
+ return [1, -1].map((side) => ({ side, spec, headOrigin: G.headO, bone: 'head', look }));
84
+ },
85
+ motion,
86
+ // scale rim contrast per variant: the goldfish's big scales read as a fine lattice, not outlined cells
87
+ render: (params) => ({ markColor: [0.008, 0.008, 0.009], scaleEdge: VARIANTS[params?.variant]?.scaleEdge ?? 0.15 }),
88
+ };
@@ -0,0 +1,92 @@
1
+ // Fish motion data for the swimmer engine (schema: src/core/motion/swimmer.js). Speeds in body
2
+ // lengths per second (BL/s), so one table serves every size. Per-variant overrides in `variants`.
3
+ //
4
+ // Tail-beat frequency vs speed from Bainbridge (1958, dace / trout / goldfish): U/L = 0.75 f - 1 above
5
+ // ~2 BL/s, tail amplitude ~0.2 L peak-to-peak; below that the frequencies and amplitudes are estimates
6
+ // from subcarangiform kinematics (Videler 1993). Bluegill: pectoral (labriform) swimming up to 1.2 BL/s,
7
+ // fin beat 2.8 Hz at the gait transition (Jones et al. 2007). C-start stage 1 ~ 0.24 s per metre of body
8
+ // (Domenici 2023: 16 ms at 5 cm, 60 ms at 25 cm). Wave rows: [v, f Hz, amp p-p / L, lambda / L,
9
+ // pectoral activity, pectoral beat Hz, pectoral fold].
10
+ export const motion = {
11
+ speeds: { scull: 0.4, cruise: 1.5, sprint: 4 },
12
+ maxSpeed: 6, burstSpeed: 10, accel: 12, decel: 4, turnRate: 3.2, turnRadius: 0.35,
13
+ wave: [
14
+ [0, 0.8, 0.03, 1.0, 1.0, 1.5, 0],
15
+ [0.5, 1.9, 0.1, 0.95, 0.6, 1.8, 0.3],
16
+ [1.5, 3.3, 0.17, 0.9, 0.15, 2.0, 0.75],
17
+ [3, 5.3, 0.2, 0.85, 0, 2, 1],
18
+ [6, 9.3, 0.2, 0.8, 0, 2, 1],
19
+ [10, 14.7, 0.21, 0.75, 0, 2, 1],
20
+ ],
21
+ envelope: { exp: 3.5 },
22
+ flex: [0.15, 0.86], bend: 1.4, cBend: 3.1, bank: 0.3, pitchMax: 0.9, hover: 0.35,
23
+ clearance: 0.1, cruiseDepth: 0.6,
24
+ pectoral: { rest: [0, 0, 0], flap: 9, row: 14, feather: 22, fold: 24, flare: 45, alternate: 0.25, stiff: 0 },
25
+ pelvic: { rest: 0, spread: 10, fold: 12 },
26
+ breath: { rate: 1.2, operculum: 9, mouth: 0.07, ram: 2.5 },
27
+ jaw: { gape: 30, protrude: 0.02 },
28
+ actions: {
29
+ burst: { turn: 90 },
30
+ jump: { height: 1.4 },
31
+ eat: { style: 'suction' },
32
+ drink: { reach: 3 },
33
+ sleep: { style: 'bottom' },
34
+ },
35
+ variants: {
36
+ trout: {
37
+ speeds: { scull: 0.4, cruise: 1.5, sprint: 4 },
38
+ jaw: { gape: 34, protrude: 0.012 },
39
+ actions: { jump: { height: 1.8 } },
40
+ },
41
+ goldfish: {
42
+ speeds: { scull: 0.35, cruise: 1.3, sprint: 3.5 },
43
+ maxSpeed: 5, burstSpeed: 9,
44
+ wave: [
45
+ [0, 1.0, 0.03, 1.0, 1.0, 2.2, 0],
46
+ [0.5, 1.8, 0.09, 0.95, 0.8, 2.4, 0.2],
47
+ [1.3, 3.1, 0.17, 0.9, 0.3, 2.4, 0.6],
48
+ [3, 5.3, 0.2, 0.85, 0, 2.4, 1],
49
+ [6, 9.3, 0.2, 0.8, 0, 2.4, 1],
50
+ [10, 14.7, 0.21, 0.75, 0, 2.4, 1],
51
+ ],
52
+ envelope: { exp: 3.0 }, turnRadius: 0.25,
53
+ pectoral: { rest: [0, 0, 0], flap: 11, row: 16, feather: 26, fold: 26, flare: 48, alternate: 0.8 },
54
+ jaw: { gape: 36, protrude: 0.03 },
55
+ breath: { rate: 1.4 },
56
+ actions: { eat: { style: 'bottom' }, jump: { height: 1.0 } },
57
+ },
58
+ clownfish: {
59
+ speeds: { scull: 0.8, cruise: 2.0, sprint: 4 },
60
+ maxSpeed: 6, burstSpeed: 9, turnRadius: 0.2, turnRate: 4,
61
+ wave: [
62
+ [0, 1.0, 0.02, 1.0, 1.0, 3.5, 0],
63
+ [0.6, 1.2, 0.03, 1.0, 1.0, 4.5, 0],
64
+ [1.4, 2.4, 0.08, 0.95, 0.8, 5.0, 0.15],
65
+ [2.2, 4.0, 0.16, 0.9, 0.2, 4, 0.6],
66
+ [4, 6.7, 0.2, 0.85, 0, 4, 1],
67
+ [8, 12, 0.21, 0.8, 0, 4, 1],
68
+ ],
69
+ envelope: { exp: 2.8 },
70
+ pectoral: { rest: [0, 0, 0], flap: 12, row: 20, feather: 28, fold: 28, flare: 45, alternate: 0.08 },
71
+ breath: { rate: 1.8 },
72
+ jaw: { gape: 30, protrude: 0.03 },
73
+ actions: { sleep: { style: 'side' }, jump: { height: 0.8 } },
74
+ },
75
+ bluegill: {
76
+ speeds: { scull: 0.6, cruise: 1.8, sprint: 4 },
77
+ maxSpeed: 6, burstSpeed: 9, turnRadius: 0.2, turnRate: 3.8,
78
+ wave: [
79
+ [0, 0.8, 0.02, 1.0, 1.0, 1.6, 0],
80
+ [0.6, 1.0, 0.03, 1.0, 1.0, 2.2, 0],
81
+ [1.2, 2.2, 0.08, 0.95, 0.7, 2.8, 0.2],
82
+ [1.8, 3.7, 0.17, 0.9, 0.15, 2.8, 0.7],
83
+ [4, 6.7, 0.2, 0.85, 0, 2.8, 1],
84
+ [8, 12, 0.21, 0.8, 0, 2.8, 1],
85
+ ],
86
+ envelope: { exp: 3.0 },
87
+ pectoral: { rest: [0, 0, 0], flap: 12, row: 18, feather: 28, fold: 18, flare: 42, alternate: 0.1 },
88
+ jaw: { gape: 32, protrude: 0.03 },
89
+ actions: { sleep: { style: 'hover' }, jump: { height: 1.0 } },
90
+ },
91
+ },
92
+ };
@@ -0,0 +1,27 @@
1
+ // Meshing plan: the whole fish (body + fins) as one surface at SL * CELL (0.8 mm for a 26 cm trout at
2
+ // the hero tier), and the lower jaw as its own rigid surface so the mouth can open.
3
+ import { fishGeo } from './rig.js';
4
+ import { CELL } from './sculpt.js';
5
+
6
+ export function fishRegions(rig, params, Q) {
7
+ const G = fishGeo(params);
8
+ const SL = G.SL, V = G.V, r = Q.res;
9
+ const h = SL * CELL * (V.cellK || 1) * r;
10
+ // tight box: body outline + the longest dorsal / anal rays + the paired fins
11
+ let top = 0, bot = 0, hw = 0;
12
+ for (const r of V.prof) { top = Math.max(top, r[1]); bot = Math.max(bot, r[2]); hw = Math.max(hw, r[3]); }
13
+ const dl = Math.max(...V.fins.dorsal.map((d) => Math.max(...d.len))), al = Math.max(...V.fins.anal.map((d) => Math.max(...d.len)));
14
+ const J = rig.J;
15
+ const xMax = Math.max(hw * SL, Math.abs(J.pecTipL[0]), Math.abs(J.pelTipL[0])) + 0.03 * SL;
16
+ const topY = G.axisY + (top + dl) * SL + 0.03 * SL;
17
+ const botY = Math.min(G.axisY - (bot + al * 0.9) * SL, J.pelTipL[1], J.pecTipL[1]) - 0.03 * SL;
18
+ const zTail = G.z(1 + G.tailLen * 1.08 + 0.03);
19
+ const g = V.head.mouth.gape;
20
+ return {
21
+ jobs: [
22
+ [{ name: 'body', part: 'body', bmin: [-xMax, botY, zTail], bmax: [xMax, topY, G.z0 + 0.03 * SL], h, F: 6 }],
23
+ [{ name: 'jaw', part: 'jaw', bmin: [-0.2 * SL, -0.02 * SL, G.z(g + 0.14)], bmax: [0.2 * SL, G.mouthY + 0.03 * SL, G.z0 + 0.04 * SL], h: h * 0.8, F: 4, rigidBone: 'jaw' }],
24
+ ],
25
+ fade: () => 1,
26
+ };
27
+ }