@bitefoundry/retrieviewer 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/ASSET-PROVENANCE.md +6 -0
  2. package/ASSETS-LICENSE.md +2 -1
  3. package/ATTRIBUTION.md +6 -1
  4. package/COMFORT-ART.md +65 -0
  5. package/DESKTOP-PLATFORMS.md +6 -1
  6. package/PLUGIN.md +13 -10
  7. package/PREREQUISITES.md +95 -0
  8. package/PUPPY-MEMORY.md +102 -1
  9. package/README.ko.md +110 -0
  10. package/README.md +117 -59
  11. package/RELEASING.md +23 -12
  12. package/THIRD-PARTY-NOTICES.md +5 -3
  13. package/braille.mjs +35 -9
  14. package/examples/live.mjs +2 -2
  15. package/integrations/linux-overlay/overlay.py +2 -2
  16. package/integrations/macos-overlay/overlay.jxa +8 -6
  17. package/integrations/opencode/runtime.mjs +4 -4
  18. package/integrations/opencode/tui.tsx +1 -1
  19. package/integrations/windows-overlay/Overlay.cs +4 -4
  20. package/integrations/windows-overlay/protocol.mjs +11 -7
  21. package/package.json +34 -13
  22. package/puppy-comfort-sprites.mjs +25 -0
  23. package/puppy-companion-comfort-q5.bin.gz +0 -0
  24. package/puppy-companion-comfort.json +77 -0
  25. package/puppy-companion-life.mjs +49 -16
  26. package/puppy-companion-objects.mjs +12 -0
  27. package/puppy-frame-predictive.mjs +15 -9
  28. package/puppy-frame-store.mjs +2 -2
  29. package/puppy-paper-world.mjs +14 -0
  30. package/puppy-paper.mjs +65 -14
  31. package/retrieviewer-plugin.d.mts +1 -1
  32. package/retrieviewer-plugin.mjs +1 -1
  33. package/retrieviewer.d.mts +10 -4
  34. package/retrieviewer.mjs +20 -14
  35. package/locomotion-core.mjs +0 -21
  36. package/locomotion-puppy.animal +0 -0
  37. package/vendor/procedural-animals/package.json +0 -40
  38. package/vendor/procedural-animals/src/animal.js +0 -146
  39. package/vendor/procedural-animals/src/bake.js +0 -51
  40. package/vendor/procedural-animals/src/build.js +0 -81
  41. package/vendor/procedural-animals/src/core/build/coatKit.js +0 -121
  42. package/vendor/procedural-animals/src/core/build/featherCards.js +0 -189
  43. package/vendor/procedural-animals/src/core/build/finEdges.js +0 -85
  44. package/vendor/procedural-animals/src/core/build/furSlope.js +0 -115
  45. package/vendor/procedural-animals/src/core/build/mesh.js +0 -67
  46. package/vendor/procedural-animals/src/core/build/pipeline.js +0 -272
  47. package/vendor/procedural-animals/src/core/build/seams.js +0 -183
  48. package/vendor/procedural-animals/src/core/build/warp.js +0 -112
  49. package/vendor/procedural-animals/src/core/build/weights.js +0 -393
  50. package/vendor/procedural-animals/src/core/math/vec.js +0 -81
  51. package/vendor/procedural-animals/src/core/motion/actions.js +0 -800
  52. package/vendor/procedural-animals/src/core/motion/bird.js +0 -3063
  53. package/vendor/procedural-animals/src/core/motion/birdActions.js +0 -1059
  54. package/vendor/procedural-animals/src/core/motion/common.js +0 -158
  55. package/vendor/procedural-animals/src/core/motion/index.js +0 -22
  56. package/vendor/procedural-animals/src/core/motion/quadruped.js +0 -2875
  57. package/vendor/procedural-animals/src/core/motion/snake.js +0 -1594
  58. package/vendor/procedural-animals/src/core/motion/spider.js +0 -1810
  59. package/vendor/procedural-animals/src/core/motion/swimmer.js +0 -1047
  60. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +0 -613
  61. package/vendor/procedural-animals/src/core/render/animalObject.js +0 -262
  62. package/vendor/procedural-animals/src/core/render/coatMaterial.js +0 -1301
  63. package/vendor/procedural-animals/src/core/render/dqs.js +0 -159
  64. package/vendor/procedural-animals/src/core/render/eyes.js +0 -676
  65. package/vendor/procedural-animals/src/core/render/membranes.js +0 -77
  66. package/vendor/procedural-animals/src/core/render/skeleton.js +0 -41
  67. package/vendor/procedural-animals/src/core/rig/bird.js +0 -327
  68. package/vendor/procedural-animals/src/core/rig/quadruped.js +0 -76
  69. package/vendor/procedural-animals/src/core/rig/rig.js +0 -79
  70. package/vendor/procedural-animals/src/core/rig/snake.js +0 -76
  71. package/vendor/procedural-animals/src/core/rig/spider.js +0 -110
  72. package/vendor/procedural-animals/src/core/rig/swimmer.js +0 -181
  73. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +0 -53
  74. package/vendor/procedural-animals/src/core/sdf/mesher.js +0 -238
  75. package/vendor/procedural-animals/src/core/sdf/sdf.js +0 -263
  76. package/vendor/procedural-animals/src/index.d.ts +0 -98
  77. package/vendor/procedural-animals/src/index.js +0 -29
  78. package/vendor/procedural-animals/src/registry.js +0 -48
  79. package/vendor/procedural-animals/src/species/bear/behaviour.js +0 -463
  80. package/vendor/procedural-animals/src/species/bear/claws.js +0 -147
  81. package/vendor/procedural-animals/src/species/bear/coat.js +0 -726
  82. package/vendor/procedural-animals/src/species/bear/index.js +0 -139
  83. package/vendor/procedural-animals/src/species/bear/motion.js +0 -112
  84. package/vendor/procedural-animals/src/species/bear/regions.js +0 -78
  85. package/vendor/procedural-animals/src/species/bear/rig.js +0 -112
  86. package/vendor/procedural-animals/src/species/bear/sculpt.js +0 -272
  87. package/vendor/procedural-animals/src/species/boar/behaviour.js +0 -335
  88. package/vendor/procedural-animals/src/species/boar/coat.js +0 -372
  89. package/vendor/procedural-animals/src/species/boar/index.js +0 -110
  90. package/vendor/procedural-animals/src/species/boar/motion.js +0 -85
  91. package/vendor/procedural-animals/src/species/boar/regions.js +0 -88
  92. package/vendor/procedural-animals/src/species/boar/rig.js +0 -111
  93. package/vendor/procedural-animals/src/species/boar/sculpt.js +0 -285
  94. package/vendor/procedural-animals/src/species/cat/behaviour.js +0 -559
  95. package/vendor/procedural-animals/src/species/cat/coat.js +0 -841
  96. package/vendor/procedural-animals/src/species/cat/index.js +0 -211
  97. package/vendor/procedural-animals/src/species/cat/motion.js +0 -101
  98. package/vendor/procedural-animals/src/species/cat/regions.js +0 -82
  99. package/vendor/procedural-animals/src/species/cat/rig.js +0 -100
  100. package/vendor/procedural-animals/src/species/cat/sculpt.js +0 -266
  101. package/vendor/procedural-animals/src/species/cat/whiskers.js +0 -110
  102. package/vendor/procedural-animals/src/species/cheetah/coat.js +0 -427
  103. package/vendor/procedural-animals/src/species/cheetah/index.js +0 -51
  104. package/vendor/procedural-animals/src/species/cheetah/joints.js +0 -3
  105. package/vendor/procedural-animals/src/species/cheetah/motion.js +0 -77
  106. package/vendor/procedural-animals/src/species/cheetah/regions.js +0 -95
  107. package/vendor/procedural-animals/src/species/cheetah/rig.js +0 -66
  108. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +0 -158
  109. package/vendor/procedural-animals/src/species/chicken/coat.js +0 -313
  110. package/vendor/procedural-animals/src/species/chicken/coverts.js +0 -320
  111. package/vendor/procedural-animals/src/species/chicken/index.js +0 -110
  112. package/vendor/procedural-animals/src/species/chicken/motion.js +0 -177
  113. package/vendor/procedural-animals/src/species/chicken/regions.js +0 -66
  114. package/vendor/procedural-animals/src/species/chicken/rig.js +0 -144
  115. package/vendor/procedural-animals/src/species/chicken/sculpt.js +0 -389
  116. package/vendor/procedural-animals/src/species/chicken/wingLook.js +0 -66
  117. package/vendor/procedural-animals/src/species/chicken/wingStack.js +0 -124
  118. package/vendor/procedural-animals/src/species/cow/behaviour.js +0 -521
  119. package/vendor/procedural-animals/src/species/cow/coat.js +0 -465
  120. package/vendor/procedural-animals/src/species/cow/index.js +0 -118
  121. package/vendor/procedural-animals/src/species/cow/motion.js +0 -81
  122. package/vendor/procedural-animals/src/species/cow/regions.js +0 -98
  123. package/vendor/procedural-animals/src/species/cow/rig.js +0 -116
  124. package/vendor/procedural-animals/src/species/cow/sculpt.js +0 -339
  125. package/vendor/procedural-animals/src/species/crow/coat.js +0 -145
  126. package/vendor/procedural-animals/src/species/crow/index.js +0 -70
  127. package/vendor/procedural-animals/src/species/crow/joints.js +0 -3
  128. package/vendor/procedural-animals/src/species/crow/motion.js +0 -101
  129. package/vendor/procedural-animals/src/species/crow/regions.js +0 -50
  130. package/vendor/procedural-animals/src/species/crow/rig.js +0 -83
  131. package/vendor/procedural-animals/src/species/crow/sculpt.js +0 -118
  132. package/vendor/procedural-animals/src/species/deer/behaviour.js +0 -513
  133. package/vendor/procedural-animals/src/species/deer/coat.js +0 -391
  134. package/vendor/procedural-animals/src/species/deer/index.js +0 -106
  135. package/vendor/procedural-animals/src/species/deer/motion.js +0 -97
  136. package/vendor/procedural-animals/src/species/deer/regions.js +0 -60
  137. package/vendor/procedural-animals/src/species/deer/rig.js +0 -124
  138. package/vendor/procedural-animals/src/species/deer/sculpt.js +0 -297
  139. package/vendor/procedural-animals/src/species/dog/behaviour.js +0 -521
  140. package/vendor/procedural-animals/src/species/dog/coat.js +0 -495
  141. package/vendor/procedural-animals/src/species/dog/index.js +0 -144
  142. package/vendor/procedural-animals/src/species/dog/motion.js +0 -118
  143. package/vendor/procedural-animals/src/species/dog/regions.js +0 -61
  144. package/vendor/procedural-animals/src/species/dog/rig.js +0 -129
  145. package/vendor/procedural-animals/src/species/dog/sculpt.js +0 -246
  146. package/vendor/procedural-animals/src/species/eagle/coat.js +0 -250
  147. package/vendor/procedural-animals/src/species/eagle/index.js +0 -76
  148. package/vendor/procedural-animals/src/species/eagle/joints.js +0 -3
  149. package/vendor/procedural-animals/src/species/eagle/motion.js +0 -137
  150. package/vendor/procedural-animals/src/species/eagle/regions.js +0 -57
  151. package/vendor/procedural-animals/src/species/eagle/rig.js +0 -107
  152. package/vendor/procedural-animals/src/species/eagle/sculpt.js +0 -137
  153. package/vendor/procedural-animals/src/species/fish/coat.js +0 -271
  154. package/vendor/procedural-animals/src/species/fish/index.js +0 -88
  155. package/vendor/procedural-animals/src/species/fish/motion.js +0 -92
  156. package/vendor/procedural-animals/src/species/fish/regions.js +0 -27
  157. package/vendor/procedural-animals/src/species/fish/rig.js +0 -143
  158. package/vendor/procedural-animals/src/species/fish/sculpt.js +0 -214
  159. package/vendor/procedural-animals/src/species/fish/variants.js +0 -126
  160. package/vendor/procedural-animals/src/species/fox/behaviour.js +0 -314
  161. package/vendor/procedural-animals/src/species/fox/coat.js +0 -444
  162. package/vendor/procedural-animals/src/species/fox/index.js +0 -120
  163. package/vendor/procedural-animals/src/species/fox/motion.js +0 -81
  164. package/vendor/procedural-animals/src/species/fox/regions.js +0 -59
  165. package/vendor/procedural-animals/src/species/fox/rig.js +0 -91
  166. package/vendor/procedural-animals/src/species/fox/sculpt.js +0 -202
  167. package/vendor/procedural-animals/src/species/frog/behaviour.js +0 -590
  168. package/vendor/procedural-animals/src/species/frog/coat.js +0 -305
  169. package/vendor/procedural-animals/src/species/frog/index.js +0 -97
  170. package/vendor/procedural-animals/src/species/frog/motion.js +0 -69
  171. package/vendor/procedural-animals/src/species/frog/regions.js +0 -46
  172. package/vendor/procedural-animals/src/species/frog/rig.js +0 -156
  173. package/vendor/procedural-animals/src/species/frog/sculpt.js +0 -240
  174. package/vendor/procedural-animals/src/species/goat/behaviour.js +0 -509
  175. package/vendor/procedural-animals/src/species/goat/coat.js +0 -590
  176. package/vendor/procedural-animals/src/species/goat/index.js +0 -149
  177. package/vendor/procedural-animals/src/species/goat/motion.js +0 -95
  178. package/vendor/procedural-animals/src/species/goat/regions.js +0 -98
  179. package/vendor/procedural-animals/src/species/goat/rig.js +0 -181
  180. package/vendor/procedural-animals/src/species/goat/sculpt.js +0 -547
  181. package/vendor/procedural-animals/src/species/horse/coat.js +0 -361
  182. package/vendor/procedural-animals/src/species/horse/coatSeam.js +0 -122
  183. package/vendor/procedural-animals/src/species/horse/index.js +0 -80
  184. package/vendor/procedural-animals/src/species/horse/motion.js +0 -245
  185. package/vendor/procedural-animals/src/species/horse/regions.js +0 -60
  186. package/vendor/procedural-animals/src/species/horse/rig.js +0 -117
  187. package/vendor/procedural-animals/src/species/horse/sculpt.js +0 -316
  188. package/vendor/procedural-animals/src/species/horse/seamWeights.js +0 -272
  189. package/vendor/procedural-animals/src/species/lion/behaviour.js +0 -509
  190. package/vendor/procedural-animals/src/species/lion/coat.js +0 -1016
  191. package/vendor/procedural-animals/src/species/lion/index.js +0 -111
  192. package/vendor/procedural-animals/src/species/lion/motion.js +0 -87
  193. package/vendor/procedural-animals/src/species/lion/regions.js +0 -106
  194. package/vendor/procedural-animals/src/species/lion/rig.js +0 -110
  195. package/vendor/procedural-animals/src/species/lion/sculpt.js +0 -384
  196. package/vendor/procedural-animals/src/species/lion/whiskers.js +0 -132
  197. package/vendor/procedural-animals/src/species/pig/behaviour.js +0 -575
  198. package/vendor/procedural-animals/src/species/pig/coat.js +0 -730
  199. package/vendor/procedural-animals/src/species/pig/ears.js +0 -242
  200. package/vendor/procedural-animals/src/species/pig/index.js +0 -161
  201. package/vendor/procedural-animals/src/species/pig/motion.js +0 -105
  202. package/vendor/procedural-animals/src/species/pig/regions.js +0 -100
  203. package/vendor/procedural-animals/src/species/pig/rig.js +0 -152
  204. package/vendor/procedural-animals/src/species/pig/sculpt.js +0 -382
  205. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +0 -605
  206. package/vendor/procedural-animals/src/species/rabbit/coat.js +0 -285
  207. package/vendor/procedural-animals/src/species/rabbit/index.js +0 -85
  208. package/vendor/procedural-animals/src/species/rabbit/motion.js +0 -85
  209. package/vendor/procedural-animals/src/species/rabbit/regions.js +0 -48
  210. package/vendor/procedural-animals/src/species/rabbit/rig.js +0 -99
  211. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +0 -148
  212. package/vendor/procedural-animals/src/species/rat/behaviour.js +0 -539
  213. package/vendor/procedural-animals/src/species/rat/coat.js +0 -341
  214. package/vendor/procedural-animals/src/species/rat/index.js +0 -111
  215. package/vendor/procedural-animals/src/species/rat/motion.js +0 -90
  216. package/vendor/procedural-animals/src/species/rat/regions.js +0 -92
  217. package/vendor/procedural-animals/src/species/rat/rig.js +0 -112
  218. package/vendor/procedural-animals/src/species/rat/sculpt.js +0 -163
  219. package/vendor/procedural-animals/src/species/rat/whiskers.js +0 -119
  220. package/vendor/procedural-animals/src/species/shark/coat.js +0 -383
  221. package/vendor/procedural-animals/src/species/shark/index.js +0 -104
  222. package/vendor/procedural-animals/src/species/shark/motion.js +0 -69
  223. package/vendor/procedural-animals/src/species/shark/regions.js +0 -34
  224. package/vendor/procedural-animals/src/species/shark/rig.js +0 -194
  225. package/vendor/procedural-animals/src/species/shark/sculpt.js +0 -343
  226. package/vendor/procedural-animals/src/species/shark/variants.js +0 -105
  227. package/vendor/procedural-animals/src/species/sheep/behaviour.js +0 -522
  228. package/vendor/procedural-animals/src/species/sheep/coat.js +0 -529
  229. package/vendor/procedural-animals/src/species/sheep/index.js +0 -152
  230. package/vendor/procedural-animals/src/species/sheep/motion.js +0 -86
  231. package/vendor/procedural-animals/src/species/sheep/neck.js +0 -41
  232. package/vendor/procedural-animals/src/species/sheep/regions.js +0 -62
  233. package/vendor/procedural-animals/src/species/sheep/rig.js +0 -163
  234. package/vendor/procedural-animals/src/species/sheep/sculpt.js +0 -542
  235. package/vendor/procedural-animals/src/species/snake/coat.js +0 -315
  236. package/vendor/procedural-animals/src/species/snake/index.js +0 -73
  237. package/vendor/procedural-animals/src/species/snake/motion.js +0 -51
  238. package/vendor/procedural-animals/src/species/snake/regions.js +0 -51
  239. package/vendor/procedural-animals/src/species/snake/rig.js +0 -108
  240. package/vendor/procedural-animals/src/species/snake/sculpt.js +0 -122
  241. package/vendor/procedural-animals/src/species/spider/coat.js +0 -279
  242. package/vendor/procedural-animals/src/species/spider/index.js +0 -78
  243. package/vendor/procedural-animals/src/species/spider/motion.js +0 -72
  244. package/vendor/procedural-animals/src/species/spider/regions.js +0 -87
  245. package/vendor/procedural-animals/src/species/spider/rig.js +0 -118
  246. package/vendor/procedural-animals/src/species/spider/sculpt.js +0 -182
  247. package/vendor/procedural-animals/src/species/wolf/behaviour.js +0 -160
  248. package/vendor/procedural-animals/src/species/wolf/coat.js +0 -749
  249. package/vendor/procedural-animals/src/species/wolf/index.js +0 -107
  250. package/vendor/procedural-animals/src/species/wolf/motion.js +0 -80
  251. package/vendor/procedural-animals/src/species/wolf/regions.js +0 -57
  252. package/vendor/procedural-animals/src/species/wolf/rig.js +0 -87
  253. package/vendor/procedural-animals/src/species/wolf/sculpt.js +0 -339
  254. package/vendor/procedural-animals/src/worker.js +0 -29
@@ -35,16 +35,22 @@ export interface PetState {
35
35
  [key: string]: unknown;
36
36
  }
37
37
  export interface LifeReport {
38
+ /** Last selected autonomous episode; null before the first decision. */
39
+ episode: string | null;
38
40
  intent: string;
39
41
  label: string;
40
- interaction: 'ball' | 'treat' | null;
42
+ interaction: 'ball' | 'treat' | 'tug' | null;
41
43
  busy: boolean;
42
44
  energy: number;
43
45
  decisions: number;
44
- cooldowns: {ball: number; treat: number};
46
+ cooldowns: {ball: number; treat: number; tug: number};
45
47
  gesture: {clip: string; progress: number} | null;
46
48
  ball: {x: number; z: number; height: number; carried: boolean} | null;
47
49
  treat: {x: number; z: number; height: number} | null;
50
+ /** Tug-of-war state while a tug interaction is active. */
51
+ tug: {progress: number; grip: number} | null;
52
+ /** Silent rising sleep cue, normalized cycle progress; no audio process. */
53
+ snore: number | null;
48
54
  }
49
55
  export interface PetFrame {
50
56
  artProfile: 'original' | 'q5';
@@ -70,7 +76,7 @@ export interface Pet {
70
76
  step(dt: number): number;
71
77
  frame(options: FrameOptions): PetFrame;
72
78
  /** False when busy or cooling down. */
73
- interact(kind: 'ball' | 'treat'): boolean;
79
+ interact(kind: 'ball' | 'treat' | 'tug'): boolean;
74
80
  readonly disposed: boolean;
75
81
  dispose(): void;
76
82
  }
@@ -78,7 +84,7 @@ export function createPet(options?: PetOptions): Promise<Pet>;
78
84
  export function defaultLoadAsset(name: string): Promise<ArrayBuffer | Uint8Array>;
79
85
  export const STEP_SECONDS: number;
80
86
  export const MAX_CATCH_UP_SECONDS: number;
81
- export const INTERACTIONS: readonly ['ball', 'treat'];
87
+ export const INTERACTIONS: readonly ['ball', 'treat', 'tug'];
82
88
  export const ART_PROFILES: readonly ['original', 'q5'];
83
89
  export const DEFAULT_ART_PROFILE: 'q5';
84
90
  export function getPetAssetFiles(artProfile?: 'original' | 'q5'): readonly string[];
package/retrieviewer.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import {makeLabAnimal} from './locomotion-core.mjs';
2
- import {createPaperActor,createPaperRenderer} from './puppy-paper.mjs';
3
- import {createPaperProjection} from './puppy-paper-world.mjs';
1
+ import {createPaperActor,createPaperRenderer,createPaperComfortTimings} from './puppy-paper.mjs';
2
+ import {createPaperProjection,createPaperBody} from './puppy-paper-world.mjs';
4
3
  import {rgbaToBraille} from './braille.mjs';
5
4
  import {createSnappyTrickTimings} from './puppy-paper-trick-timing.mjs';
6
5
  import {createCompanionLife} from './puppy-companion-life.mjs';
@@ -11,24 +10,25 @@ import {readPackedFrames} from './puppy-frame-store.mjs';
11
10
  // or listener side effects: the host owns the loop and calls step() then frame().
12
11
  export const STEP_SECONDS=1/60;
13
12
  export const MAX_CATCH_UP_SECONDS=.1;
14
- export const INTERACTIONS=Object.freeze(['ball','treat']);
13
+ export const INTERACTIONS=Object.freeze(['ball','treat','tug']);
15
14
  // Approved 32-level art ships by default; preserved original art is optional.
16
15
  export const DEFAULT_ART_PROFILE='q5';
17
16
  export const ART_PROFILES=Object.freeze(['original','q5']);
18
17
  // Current companion runtime: v9 tricks + interactions-v3, same as puppy-paper.html.
19
18
  export const PET_ASSETS=Object.freeze({
19
+ // Kept as a legacy identifier only; createPet/getPetAssetFiles no longer load it.
20
20
  animal:'locomotion-puppy.animal',
21
21
  stride:'puppy-stride-tweens-q5.bin.gz',strideInfo:'puppy-stride-meta.json',stableInfo:'puppy-stable-meta.json',
22
22
  rear:'puppy-paper-rear-gallop',approach:'puppy-paper-approach',seat:'puppy-paper-seat',arrivalSeat:'puppy-paper-arrival-seat',
23
- behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',
23
+ behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',comfort:'puppy-companion-comfort',
24
24
  });
25
- const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions'];
25
+ const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions','comfort'];
26
26
  const frameFile=(base,profile)=>base+(profile==='q5'?'-q5.bin.gz':'-compact.bin.gz');
27
27
  /** Exact files a host/bundler must copy, including metadata. Never loads assets. */
28
28
  export function getPetAssetFiles(artProfile=DEFAULT_ART_PROFILE){
29
29
  if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
30
30
  return Object.freeze([PET_ASSETS.strideInfo,PET_ASSETS.stableInfo,frameFile('puppy-stride-tweens',artProfile),
31
- ...BUNDLES.flatMap(key=>[PET_ASSETS[key]+'.json',frameFile(PET_ASSETS[key],artProfile)]),PET_ASSETS.animal]);
31
+ ...BUNDLES.flatMap(key=>[PET_ASSETS[key]+'.json',frameFile(PET_ASSETS[key],artProfile)])]);
32
32
  }
33
33
 
34
34
  // Relative to this module, never the process cwd: file: URLs in Node, HTTP in browsers.
@@ -62,13 +62,19 @@ const sharedAssets=new Map();
62
62
  async function loadPetAssets(loadAsset,artProfile){
63
63
  const load=async name=>bytesOf(name,await loadAsset(name));
64
64
  const json=async name=>JSON.parse(new TextDecoder().decode(await load(name)));
65
- const frames=async name=>readPackedFrames(await gunzip(await load(name)));
65
+ const frames=async (name,options)=>readPackedFrames(await gunzip(await load(name)),options);
66
66
  // One family at a time: never inflate all eight legacy-sized buffers together.
67
67
  const data={assets:{}};
68
68
  data.info=await json(PET_ASSETS.strideInfo);data.meta=await json(PET_ASSETS.stableInfo);
69
69
  data.cache=await frames(frameFile('puppy-stride-tweens',artProfile));
70
- for(const key of BUNDLES){const base=PET_ASSETS[key];data.assets[key]={info:await json(base+'.json'),bytes:await frames(frameFile(base,artProfile))};}
71
- data.baked=await load(PET_ASSETS.animal);
70
+ for(const key of BUNDLES){
71
+ const base=PET_ASSETS[key],info=await json(base+'.json');
72
+ // Old interaction sheets include preparation frames no runtime clip can reach.
73
+ // Keep exact active samples only, leaving room for small new gestures without
74
+ // keeping another multi-megabyte art family resident. Original RVF1 unchanged.
75
+ const options=key==='interactions'&&artProfile==='q5'?{frameIndices:[...new Set(Object.values(info.clips).flatMap(list=>list.flatMap(p=>Array.from({length:info.samples},(_,s)=>p*info.samples+s))))]}:undefined;
76
+ data.assets[key]={info,bytes:await frames(frameFile(base,artProfile),options)};
77
+ }
72
78
  return data;
73
79
  }
74
80
  async function acquireAssets(loadAsset,artProfile){
@@ -90,12 +96,12 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
90
96
  if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
91
97
  let lease=await acquireAssets(loadAsset,artProfile),data=lease.data;
92
98
  const release=lease.release;lease=null;
93
- let {cache,info,meta,baked}=data;const assets={...data.assets};data=null;
99
+ let {cache,info,meta}=data;const assets={...data.assets};data=null;
94
100
  let animal,actor,life;
95
101
  try{
96
102
  if(!assets.actions.info.clips?.['lie-down']||!assets.actions.info.clips?.['lie-up'])throw Error('v9 action cache lacks lie-down/lie-up clips');
97
- animal=await makeLabAnimal({baked:baked.buffer.slice(baked.byteOffset,baked.byteOffset+baked.byteLength)});baked=null;
98
- actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),walkTransitions:true});
103
+ animal=createPaperBody();
104
+ actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),comfortTimings:createPaperComfortTimings(assets.actions.info),walkTransitions:true});
99
105
  life=createCompanionLife(actor,{seed,...energy===undefined?{}:{energy}});
100
106
  }catch(e){animal?.dispose?.();release();throw e;}
101
107
  let paint=null,paintKey='',time=0,acc=0,disposed=false;
@@ -127,7 +133,7 @@ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTrans
127
133
  const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1});
128
134
  return {text,columns,rows,pixels,state,report,placement,projection,artProfile,mouth:paint.lastMouth,contacts:paint.report()};
129
135
  },
130
- // 'ball' | 'treat'. False when busy or cooling down; unknown kinds throw.
136
+ // 'ball' | 'treat' | 'tug'. False when busy or cooling down; unknown kinds throw.
131
137
  interact(kind){alive();if(!INTERACTIONS.includes(kind))throw TypeError(`Unknown interaction: ${kind}`);return life.interact(kind);},
132
138
  get disposed(){return disposed;},
133
139
  dispose(){
@@ -1,21 +0,0 @@
1
- import * as THREE from 'three';
2
- import {createAnimal} from './vendor/procedural-animals/src/index.js';
3
- export const LAB_OPTIONS={variant:'retriever',age:'juvenile',seed:2,quality:'low',worker:false,overrides:{colour:'yellow',stanceSoftMargin:.005}};
4
- export async function makeLabAnimal(extra={}){
5
- return createAnimal('dog',{...LAB_OPTIONS,...extra,overrides:{...LAB_OPTIONS.overrides,...extra.overrides}});
6
- }
7
- // Never translate the mesh externally. The gait engine owns root motion + IK.
8
- export function makeLabController(animal){
9
- let target=null,speed=animal.gears.walk;
10
- return {get target(){return target;},get speed(){return speed;},setSpeed(kind){
11
- if(!['walk','trot','gallop'].includes(kind))throw Error('Unknown gait');
12
- speed=animal.gears[kind];if(target)animal.moveTo(target,{speed});
13
- },go(x,z){
14
- if(![x,z].every(Number.isFinite))throw Error('Invalid ground target');
15
- target=new THREE.Vector3(Math.max(-.8,Math.min(.8,x)),0,Math.max(-1.7,Math.min(.3,z)));
16
- animal.stopAction();animal.moveTo(target,{speed});
17
- },stop(){target=null;animal.stop();},update(dt){
18
- if(!Number.isFinite(dt)||dt<0||dt>.1)throw Error('Invalid lab step');animal.update(dt);
19
- if(target&&animal.position.distanceTo(target)<.025&&animal.speed<.03){target=null;animal.stop();}
20
- }};
21
- }
Binary file
@@ -1,40 +0,0 @@
1
- {
2
- "name": "procedural-animals",
3
- "version": "0.1.0",
4
- "description": "24 fully procedural animals for three.js: SDF-sculpted bodies, dual-quaternion skinning, GPU fur, feathers and scales, IK locomotion and actions. No models, no textures, no downloads.",
5
- "license": "MIT",
6
- "author": "Majid Manzarpour",
7
- "homepage": "https://majidmanzarpour.github.io/threejs-procedural-animals/",
8
- "repository": { "type": "git", "url": "git+https://github.com/majidmanzarpour/threejs-procedural-animals.git" },
9
- "bugs": { "url": "https://github.com/majidmanzarpour/threejs-procedural-animals/issues" },
10
- "type": "module",
11
- "main": "./src/index.js",
12
- "module": "./src/index.js",
13
- "types": "./src/index.d.ts",
14
- "exports": {
15
- ".": { "types": "./src/index.d.ts", "default": "./src/index.js" },
16
- "./species/*": "./src/species/*/index.js",
17
- "./worker": "./src/worker.js",
18
- "./bake": "./src/bake.js",
19
- "./package.json": "./package.json"
20
- },
21
- "bin": { "procedural-animals-bake": "./tools/bake.mjs" },
22
- "sideEffects": ["./src/worker.js"],
23
- "files": [
24
- "src/**/*.js",
25
- "src/index.d.ts",
26
- "tools/bake.mjs",
27
- "README.md",
28
- "LICENSE",
29
- "llms.txt"
30
- ],
31
- "engines": { "node": ">=18" },
32
- "peerDependencies": { "three": ">=0.160.0" },
33
- "devDependencies": { "esbuild": "^0.28.2", "playwright-core": "1.56", "three": "^0.186.0" },
34
- "scripts": {
35
- "build:showcase": "node showcase/build.mjs",
36
- "build:pages": "node showcase/build.mjs --pages",
37
- "test": "node tools/test.mjs"
38
- },
39
- "keywords": ["three", "threejs", "procedural", "animals", "animation", "ik", "fur", "sdf", "creatures", "gamedev", "webgl"]
40
- }
@@ -1,146 +0,0 @@
1
- // The public Animal: build data + render object + motion engine behind a small game-facing API.
2
- import * as THREE from 'three';
3
- import { createAnimalObject } from './core/render/animalObject.js';
4
- import { createMotion } from './core/motion/index.js';
5
-
6
- const _v = new THREE.Vector3();
7
-
8
- export class Animal {
9
- constructor(species, data, opts = {}) {
10
- this.species = species;
11
- this.id = species.id;
12
- this.data = data;
13
- this.seed = data.seed;
14
- this.params = data.params;
15
- this.quality = opts.quality || data.quality;
16
- const ground = opts.ground || (() => 0);
17
- this.ground = ground;
18
- this.water = opts.water || null;
19
- this.render = createAnimalObject(data, species, { quality: this.quality, castShadow: opts.castShadow, receiveShadow: opts.receiveShadow });
20
- /** Add this to your scene. Keep its own transform at identity: the animal moves by itself. */
21
- this.object = this.render.group;
22
- this.object.userData.animal = this;
23
- this._listeners = new Map();
24
- this.motion = createMotion(species.plan, {
25
- species, data, skeleton: this.render.skeleton, ground, water: this.water, perches: opts.perches,
26
- position: opts.position, heading: opts.heading ?? 0,
27
- emit: (type, payload) => this._emit(type, payload),
28
- });
29
- this.attachments = {};
30
- this._attachDefs = [];
31
- for (const [name, def] of Object.entries(this.motion.attachmentPoints || {})) {
32
- const o = new THREE.Object3D();
33
- o.name = name;
34
- o.matrixAutoUpdate = false;
35
- this.object.add(o);
36
- this.attachments[name] = o;
37
- this._attachDefs.push({ o, ...def });
38
- }
39
- this.stats = { vertices: data.nV, triangles: data.index.length / 3, bones: data.bones.length, buildMs: data.stats?.buildMs || 0, updateMs: 0 };
40
- this.time = 0;
41
- this._lastDt = 1 / 60;
42
- this.update(0);
43
- }
44
-
45
- // ---------------------------------------------------------------- control
46
- /** Locomotion: the animal moves itself. speed in m/s, heading in radians (0 = +Z). */
47
- move({ speed = 0, heading, climb = 0, gait } = {}) {
48
- const inp = this.motion.input;
49
- inp.follow = null;
50
- inp.target = null;
51
- inp.speed = Math.max(0, speed);
52
- if (heading !== undefined) inp.heading = heading;
53
- inp.climb = climb;
54
- inp.gait = gait || null;
55
- return this;
56
- }
57
- stop() { return this.move({ speed: 0 }); }
58
- /** Walk / run to a point and stop there. */
59
- moveTo(point, { speed = 3 } = {}) {
60
- const inp = this.motion.input;
61
- inp.follow = null;
62
- inp.target = point ? new THREE.Vector3().copy(point) : null;
63
- inp.targetSpeed = speed;
64
- return this;
65
- }
66
- /** Match your own physics body: the animal is placed at `position` and animates for `velocity`. */
67
- follow(position, velocity, { heading } = {}) {
68
- const inp = this.motion.input;
69
- inp.follow = inp.follow || { position: new THREE.Vector3(), velocity: new THREE.Vector3(), heading: null };
70
- inp.follow.position.copy(position);
71
- inp.follow.velocity.copy(velocity || _v.set(0, 0, 0));
72
- inp.follow.heading = heading ?? null;
73
- return this;
74
- }
75
- /** Look at a world point (Vector3) or stop looking (null). The animal keeps tracking the point you pass. */
76
- lookAt(target) {
77
- this.motion.input.look = target ? (target.isVector3 ? target : new THREE.Vector3().copy(target)) : null;
78
- return this;
79
- }
80
- /**
81
- * Play an action ('jump', 'sit', 'lie', 'sleep', 'eat', 'drink', 'attack', 'hit', 'death', 'stand', ...).
82
- * A new one-shot interrupts the running one; postures cross-fade.
83
- * Resolves with 'done' | 'interrupted' | 'stopped' | 'refused'.
84
- */
85
- play(name, opts = {}) { return this.motion.play(name, opts); }
86
- /** End an action (a posture: stand up), or every action but death when no name is given. */
87
- stopAction(name) { this.motion.stop(name); return this; }
88
- get actions() { return this.motion.actions; }
89
- get gaits() { return this.motion.gaits; }
90
- /** Named speeds for UIs ({ walk, trot, ... } in m/s, scaled to this individual's size). */
91
- get gears() { return this.motion.gears || {}; }
92
-
93
- // ---------------------------------------------------------------- events
94
- on(type, fn) {
95
- if (!this._listeners.has(type)) this._listeners.set(type, new Set());
96
- this._listeners.get(type).add(fn);
97
- return () => this.off(type, fn);
98
- }
99
- off(type, fn) { this._listeners.get(type)?.delete(fn); return this; }
100
- _emit(type, payload) {
101
- const set = this._listeners.get(type);
102
- if (set) for (const fn of set) fn({ type, animal: this, ...payload });
103
- }
104
-
105
- // ---------------------------------------------------------------- state
106
- get position() { return this.motion.state.position; }
107
- get heading() { return this.motion.state.heading; }
108
- get speed() { return this.motion.state.speed; }
109
- get velocity() { return this.motion.state.velocity; }
110
- get state() { return this.motion.state; }
111
-
112
- // ---------------------------------------------------------------- frame
113
- /** Call once per frame with the frame time in seconds. */
114
- update(dt) {
115
- const t0 = now();
116
- if (!(dt >= 0) || !Number.isFinite(dt)) dt = 0;
117
- dt = Math.min(dt, 0.1);
118
- this.time += dt;
119
- this.motion.update(dt);
120
- this.render.update({ dt, time: this.time, motion: this.motion });
121
- for (const a of this._attachDefs) {
122
- const b = this.render.skeleton.byName[a.bone];
123
- if (!b) continue;
124
- a.o.matrix.multiplyMatrices(b.matrixWorld, a.local);
125
- a.o.matrixWorld.multiplyMatrices(this.object.matrixWorld, a.o.matrix);
126
- a.o.matrixWorldNeedsUpdate = false;
127
- }
128
- this.stats.updateMs = this.stats.updateMs * 0.9 + (now() - t0) * 0.1;
129
- return this;
130
- }
131
-
132
- setQuality(q) { this.render.setQuality?.(q); this.quality = q; return this; }
133
- /** Motion level of detail: 0 full, 1 reduced secondary motion, 2 crowd (half-rate IK). */
134
- setLod(level) { this.motion.setLod?.(level); this.lod = level; return this; }
135
- setDebug(mode, on) { this.render.setDebug?.(mode, on); return this; }
136
- /** Show or hide the coverings (fur shells and silhouette fins); the base still reads as fur. */
137
- setCoverings(on) { this.render.setCoverings?.(on); return this; }
138
-
139
- dispose() {
140
- this.render.dispose();
141
- this.motion.dispose?.();
142
- this._listeners.clear();
143
- }
144
- }
145
-
146
- const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
@@ -1,51 +0,0 @@
1
- // .animal bake files: a built individual, stored so it loads instantly (no SDF meshing, no weights).
2
- // Layout: "PANM" | u32 version | u32 headerBytes | header JSON (utf-8, padded to 8) | array blobs (8-aligned)
3
- import { ARRAY_KEYS, FORMAT_VERSION } from './core/build/pipeline.js';
4
-
5
- const TYPES = { Float32Array, Uint32Array, Uint16Array, Int16Array, Uint8Array, Int8Array, Int32Array };
6
-
7
- export function writeBake(data) {
8
- const header = {};
9
- for (const [k, v] of Object.entries(data)) if (!ARRAY_KEYS.includes(k)) header[k] = v;
10
- header.arrays = [];
11
- let offset = 0;
12
- for (const k of ARRAY_KEYS) {
13
- const a = data[k];
14
- header.arrays.push({ key: k, type: a.constructor.name, length: a.length, offset });
15
- offset += Math.ceil(a.byteLength / 8) * 8;
16
- }
17
- let json = new TextEncoder().encode(JSON.stringify(header));
18
- const hb = Math.ceil((json.length) / 8) * 8;
19
- const total = 12 + hb + offset;
20
- const buf = new ArrayBuffer(Math.ceil(total / 8) * 8 + 8);
21
- const u8 = new Uint8Array(buf);
22
- u8.set([80, 65, 78, 77], 0); // "PANM"
23
- const dv = new DataView(buf);
24
- dv.setUint32(4, FORMAT_VERSION, true);
25
- dv.setUint32(8, hb, true);
26
- u8.set(json, 12);
27
- for (let i = 12 + json.length; i < 12 + hb; i++) u8[i] = 32;
28
- const base = 16 + hb - ((16 + hb) % 8 === 0 ? 0 : (16 + hb) % 8) ;
29
- // blobs start at an 8-aligned offset after the header
30
- const start = align8(12 + hb);
31
- for (const a of header.arrays) u8.set(new Uint8Array(data[a.key].buffer, data[a.key].byteOffset, data[a.key].byteLength), start + a.offset);
32
- void base;
33
- return buf.slice(0, start + offset);
34
- }
35
-
36
- export function readBake(buf) {
37
- const u8 = new Uint8Array(buf);
38
- if (u8[0] !== 80 || u8[1] !== 65 || u8[2] !== 78 || u8[3] !== 77) throw new Error('not a .animal file');
39
- const dv = new DataView(buf);
40
- const version = dv.getUint32(4, true);
41
- if (version !== FORMAT_VERSION) throw new Error(`.animal version ${version} is not supported (expected ${FORMAT_VERSION})`);
42
- const hb = dv.getUint32(8, true);
43
- const header = JSON.parse(new TextDecoder().decode(u8.subarray(12, 12 + hb)));
44
- const start = align8(12 + hb);
45
- const data = { ...header };
46
- for (const a of header.arrays) data[a.key] = new TYPES[a.type](buf, start + a.offset, a.length);
47
- delete data.arrays;
48
- return data;
49
- }
50
-
51
- const align8 = (n) => Math.ceil(n / 8) * 8;
@@ -1,81 +0,0 @@
1
- // Builds an individual's data: in Web Workers when possible (meshing jobs in parallel, then
2
- // weights + coat), otherwise on the calling thread. Results are cached per (species, seed, quality,
3
- // sex, age) so a herd that reuses individuals shares geometry.
4
- import { buildSync, normaliseOptions } from './core/build/pipeline.js';
5
- import { loadSpecies } from './registry.js';
6
-
7
- let defaultWorkerFactory = null;
8
- export function setWorkerFactory(fn) { defaultWorkerFactory = fn; }
9
-
10
- function makeWorker(opt) {
11
- if (opt === false) return null;
12
- try {
13
- if (typeof opt === 'function') return opt();
14
- if (defaultWorkerFactory) return defaultWorkerFactory();
15
- if (typeof Worker === 'undefined') return null;
16
- return new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
17
- } catch (e) {
18
- return null;
19
- }
20
- }
21
-
22
- const cache = new Map();
23
- export function cacheKey(speciesId, opts) {
24
- const o = normaliseOptions(opts);
25
- return [speciesId, o.seed, o.quality, o.sex || '', o.age || '', o.variant || '', JSON.stringify(o.overrides)].join('|');
26
- }
27
-
28
- export async function buildAnimalData(species, opts = {}, onProgress = () => {}) {
29
- const s = await loadSpecies(species);
30
- const key = cacheKey(s.id, opts);
31
- if (cache.has(key)) return cache.get(key);
32
- const p = buildInWorkers(s, opts, onProgress).catch((e) => { cache.delete(key); throw e; });
33
- cache.set(key, p);
34
- return p;
35
- }
36
- export function clearBuildCache() { cache.clear(); }
37
-
38
- async function buildInWorkers(species, opts, onProgress) {
39
- const o = normaliseOptions(opts);
40
- const nJobs = species.regions ? null : 1;
41
- void nJobs;
42
- const first = makeWorker(opts.worker);
43
- if (!first) {
44
- await new Promise((r) => setTimeout(r, 0));
45
- return buildSync(species, o, onProgress);
46
- }
47
- const t0 = performance.now();
48
- // how many meshing jobs? (cheap: prepare runs the sculpt only)
49
- const { prepare } = await import('./core/build/pipeline.js');
50
- const jobsN = prepare(species, o).reg.jobs.length;
51
- const workers = [first];
52
- for (let i = 1; i < jobsN; i++) { const w = makeWorker(opts.worker); if (w) workers.push(w); }
53
- const call = (w, msg) => new Promise((resolve, reject) => {
54
- w.onmessage = (e) => {
55
- const m = e.data;
56
- if (m.type === 'progress') onProgress(m.msg);
57
- else if (m.type === 'error') reject(new Error(m.message));
58
- else resolve(m);
59
- };
60
- w.onerror = (e) => reject(new Error(e.message || 'procedural-animals worker failed'));
61
- w.postMessage(msg);
62
- });
63
- try {
64
- const jobs = new Array(jobsN);
65
- let next = 0;
66
- await Promise.all(workers.map(async (w) => {
67
- while (next < jobsN) {
68
- const j = next++;
69
- onProgress('mesh ' + j);
70
- const r = await call(w, { task: 'mesh', species: species.id, opts: o, job: j });
71
- jobs[j] = r.parts;
72
- }
73
- }));
74
- onProgress('finish');
75
- const r = await call(first, { task: 'finish', species: species.id, opts: o, jobs });
76
- r.data.stats.buildMs = Math.round(performance.now() - t0);
77
- return r.data;
78
- } finally {
79
- for (const w of workers) w.terminate();
80
- }
81
- }
@@ -1,121 +0,0 @@
1
- // Helpers for species coat painters.
2
- import { SDFModel } from '../sdf/sdf.js';
3
- import { sub, add, mul, dot, len, clamp, mix } from '../math/vec.js';
4
-
5
- // sRGB hex -> linear rgb
6
- export const srgb = (hex) => {
7
- const c = [(hex >> 16) & 255, (hex >> 8) & 255, hex & 255].map((v) => v / 255);
8
- return c.map((v) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)));
9
- };
10
- export const mix3 = (a, b, t) => [mix(a[0], b[0], t), mix(a[1], b[1], t), mix(a[2], b[2], t)];
11
- export const scale3 = (a, k) => [a[0] * k, a[1] * k, a[2] * k];
12
-
13
- // Material ids stored in tint.w (see render/coatMaterial.js)
14
- export const MAT = { FUR: 0, NOSE: 1, DARK_SKIN: 2, MOUTH: 3, SKIN: 4, KERATIN: 5, SCALES: 6, WET_SKIN: 7, CHITIN: 8, FEATHER: 9, VANE: 10, FIN: 11 }; // 10: flight-feather vane (featherCards.js), 11: fin membrane (render/membranes.js)
15
-
16
- // Newton-project a point onto the zero set of a primitive list
17
- export function projectToSurface(prims, q, iters = 6) {
18
- let x = q.slice();
19
- for (let i = 0; i < iters; i++) {
20
- const d = SDFModel.evalList(prims, x[0], x[1], x[2]);
21
- const e = 0.0005;
22
- const g = [
23
- SDFModel.evalList(prims, x[0] + e, x[1], x[2]) - d,
24
- SDFModel.evalList(prims, x[0], x[1] + e, x[2]) - d,
25
- SDFModel.evalList(prims, x[0], x[1], x[2] + e) - d,
26
- ].map((c) => c / e);
27
- const gl = len(g) || 1;
28
- x = sub(x, mul(g, d / (gl * gl)));
29
- }
30
- return x;
31
- }
32
-
33
- // distance from p to a polyline with per-vertex half widths (negative inside)
34
- export function distPolyline(p, poly, widths) {
35
- let best = 1e9;
36
- for (let i = 0; i < poly.length - 1; i++) {
37
- const a = poly[i], b = poly[i + 1], ab = sub(b, a);
38
- const t = clamp(dot(sub(p, a), ab) / dot(ab, ab), 0, 1);
39
- const d = len(sub(p, add(a, mul(ab, t)))) - mix(widths[i], widths[i + 1], t);
40
- if (d < best) best = d;
41
- }
42
- return best;
43
- }
44
-
45
- // Laplacian smoothing of a scalar field over the mesh graph; `lock(v)` keeps a vertex fixed
46
- export function smoothField(f, neighbors, iters = 2, lock = null) {
47
- const n = f.length, tmp = new Float32Array(n);
48
- for (let it = 0; it < iters; it++) {
49
- for (let v = 0; v < n; v++) {
50
- const ns = neighbors[v];
51
- if (!ns.length || (lock && lock(v))) { tmp[v] = f[v]; continue; }
52
- let a = 0;
53
- for (const u of ns) a += f[u];
54
- tmp[v] = 0.5 * f[v] + (0.5 * a) / ns.length;
55
- }
56
- f.set(tmp);
57
- }
58
- return f;
59
- }
60
-
61
- // Poisson-disc features grown on the surface. radius(v) -> r (0 = no feature here);
62
- // returns { spots, grid, cell, key } for use with featureField.
63
- export function poissonFeatures({ pos, nrm, nV, radius, R, spacing = 1.52, gap = 0.0025, cell = 0.03, skip = null, extra = null }) {
64
- const order = new Uint32Array(nV);
65
- for (let i = 0; i < nV; i++) order[i] = i;
66
- for (let i = nV - 1; i > 0; i--) { const j = Math.floor(R() * (i + 1)); const t = order[i]; order[i] = order[j]; order[j] = t; }
67
- const grid = new Map();
68
- const key = (x, y, z) => ((x + 512) * 1024 + (y + 512)) * 1024 + (z + 512);
69
- const spots = [];
70
- for (let k = 0; k < nV; k++) {
71
- const v = order[k];
72
- if (skip && skip(v)) continue;
73
- let r = radius(v);
74
- if (!(r > 0)) continue;
75
- r *= 0.78 + 0.44 * R();
76
- const p = [pos[v * 3], pos[v * 3 + 1], pos[v * 3 + 2]];
77
- const gx = Math.floor(p[0] / cell), gy = Math.floor(p[1] / cell), gz = Math.floor(p[2] / cell);
78
- let ok = true;
79
- for (let dz = -1; dz <= 1 && ok; dz++)
80
- for (let dy = -1; dy <= 1 && ok; dy++)
81
- for (let dx = -1; dx <= 1 && ok; dx++) {
82
- const arr = grid.get(key(gx + dx, gy + dy, gz + dz));
83
- if (!arr) continue;
84
- for (const s of arr) {
85
- const minD = spacing * (r + s.r) + gap;
86
- const ddx = p[0] - s.p[0], ddy = p[1] - s.p[1], ddz = p[2] - s.p[2];
87
- if (ddx * ddx + ddy * ddy + ddz * ddz < minD * minD) { ok = false; break; }
88
- }
89
- }
90
- if (!ok) continue;
91
- const s = { v, p, r, n: [nrm[v * 3], nrm[v * 3 + 1], nrm[v * 3 + 2]], ...(extra ? extra(v, R) : {}) };
92
- spots.push(s);
93
- const kk = key(gx, gy, gz);
94
- (grid.get(kk) || grid.set(kk, []).get(kk)).push(s);
95
- }
96
- return { spots, grid, cell, key };
97
- }
98
-
99
- // Signed distance to the nearest feature of a poissonFeatures() set, elongated along each
100
- // feature's comb direction `c` by `el`. Returns [sdf, feature].
101
- export function featureDistance(F, p, n, wob = 1) {
102
- const { grid, cell, key } = F;
103
- let best = 1e9, bs = null;
104
- const gx = Math.floor(p[0] / cell), gy = Math.floor(p[1] / cell), gz = Math.floor(p[2] / cell);
105
- for (let dz = -1; dz <= 1; dz++)
106
- for (let dy = -1; dy <= 1; dy++)
107
- for (let dx = -1; dx <= 1; dx++) {
108
- const arr = grid.get(key(gx + dx, gy + dy, gz + dz));
109
- if (!arr) continue;
110
- for (const s of arr) {
111
- if (dot(s.n, n) < 0.2) continue;
112
- const d = sub(p, s.p);
113
- const el = s.el || 1;
114
- const along = s.c ? dot(d, s.c) : 0;
115
- const dd = Math.sqrt(Math.max(0, dot(d, d) - along * along * (1 - 1 / (el * el))));
116
- const sd = dd - s.r * wob;
117
- if (sd < best) { best = sd; bs = s; }
118
- }
119
- }
120
- return [best, bs];
121
- }