@babylonjs/loaders 9.13.0 → 9.15.0

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 (500) hide show
  1. package/BVH/bvhFileLoader.d.ts +4 -67
  2. package/BVH/bvhFileLoader.js +6 -130
  3. package/BVH/bvhFileLoader.js.map +1 -1
  4. package/BVH/bvhFileLoader.pure.d.ts +75 -0
  5. package/BVH/bvhFileLoader.pure.js +154 -0
  6. package/BVH/bvhFileLoader.pure.js.map +1 -0
  7. package/BVH/bvhFileLoader.types.d.ts +11 -0
  8. package/BVH/bvhFileLoader.types.js +2 -0
  9. package/BVH/bvhFileLoader.types.js.map +1 -0
  10. package/BVH/pure.d.ts +3 -0
  11. package/BVH/pure.js +5 -0
  12. package/BVH/pure.js.map +1 -0
  13. package/FBX/fbxFileLoader.d.ts +4 -192
  14. package/FBX/fbxFileLoader.js +6 -2437
  15. package/FBX/fbxFileLoader.js.map +1 -1
  16. package/FBX/fbxFileLoader.pure.d.ts +190 -0
  17. package/FBX/fbxFileLoader.pure.js +2451 -0
  18. package/FBX/fbxFileLoader.pure.js.map +1 -0
  19. package/FBX/fbxFileLoader.types.d.ts +11 -0
  20. package/FBX/fbxFileLoader.types.js +2 -0
  21. package/FBX/fbxFileLoader.types.js.map +1 -0
  22. package/FBX/pure.d.ts +3 -0
  23. package/FBX/pure.js +4 -0
  24. package/FBX/pure.js.map +1 -0
  25. package/OBJ/objFileLoader.d.ts +4 -134
  26. package/OBJ/objFileLoader.js +6 -315
  27. package/OBJ/objFileLoader.js.map +1 -1
  28. package/OBJ/objFileLoader.pure.d.ts +132 -0
  29. package/OBJ/objFileLoader.pure.js +329 -0
  30. package/OBJ/objFileLoader.pure.js.map +1 -0
  31. package/OBJ/objFileLoader.types.d.ts +11 -0
  32. package/OBJ/objFileLoader.types.js +2 -0
  33. package/OBJ/objFileLoader.types.js.map +1 -0
  34. package/OBJ/pure.d.ts +5 -0
  35. package/OBJ/pure.js +6 -0
  36. package/OBJ/pure.js.map +1 -0
  37. package/OBJ/solidParser.js +4 -3
  38. package/OBJ/solidParser.js.map +1 -1
  39. package/SPLAT/gaussianSplattingBlockAllocator.d.ts +137 -0
  40. package/SPLAT/gaussianSplattingBlockAllocator.js +555 -0
  41. package/SPLAT/gaussianSplattingBlockAllocator.js.map +1 -0
  42. package/SPLAT/gaussianSplattingDownloadManager.d.ts +109 -0
  43. package/SPLAT/gaussianSplattingDownloadManager.js +241 -0
  44. package/SPLAT/gaussianSplattingDownloadManager.js.map +1 -0
  45. package/SPLAT/gaussianSplattingResidencyController.d.ts +133 -0
  46. package/SPLAT/gaussianSplattingResidencyController.js +222 -0
  47. package/SPLAT/gaussianSplattingResidencyController.js.map +1 -0
  48. package/SPLAT/gaussianSplattingStream.d.ts +122 -4
  49. package/SPLAT/gaussianSplattingStream.js +413 -42
  50. package/SPLAT/gaussianSplattingStream.js.map +1 -1
  51. package/SPLAT/gaussianSplattingWorkBuffer.d.ts +36 -0
  52. package/SPLAT/gaussianSplattingWorkBuffer.js +125 -13
  53. package/SPLAT/gaussianSplattingWorkBuffer.js.map +1 -1
  54. package/SPLAT/gaussianSplattingWorkBufferShaders.d.ts +15 -0
  55. package/SPLAT/gaussianSplattingWorkBufferShaders.js +82 -0
  56. package/SPLAT/gaussianSplattingWorkBufferShaders.js.map +1 -1
  57. package/SPLAT/pure.d.ts +5 -0
  58. package/SPLAT/pure.js +7 -0
  59. package/SPLAT/pure.js.map +1 -0
  60. package/SPLAT/sog.d.ts +1 -97
  61. package/SPLAT/sog.js +1 -501
  62. package/SPLAT/sog.js.map +1 -1
  63. package/SPLAT/sog.pure.d.ts +102 -0
  64. package/SPLAT/sog.pure.js +514 -0
  65. package/SPLAT/sog.pure.js.map +1 -0
  66. package/SPLAT/splatFileLoader.d.ts +5 -106
  67. package/SPLAT/splatFileLoader.js +9 -636
  68. package/SPLAT/splatFileLoader.js.map +1 -1
  69. package/SPLAT/splatFileLoader.pure.d.ts +104 -0
  70. package/SPLAT/splatFileLoader.pure.js +650 -0
  71. package/SPLAT/splatFileLoader.pure.js.map +1 -0
  72. package/SPLAT/splatFileLoader.types.d.ts +11 -0
  73. package/SPLAT/splatFileLoader.types.js +2 -0
  74. package/SPLAT/splatFileLoader.types.js.map +1 -0
  75. package/STL/pure.d.ts +2 -0
  76. package/STL/pure.js +3 -0
  77. package/STL/pure.js.map +1 -0
  78. package/STL/stlFileLoader.d.ts +5 -76
  79. package/STL/stlFileLoader.js +7 -236
  80. package/STL/stlFileLoader.js.map +1 -1
  81. package/STL/stlFileLoader.pure.d.ts +73 -0
  82. package/STL/stlFileLoader.pure.js +249 -0
  83. package/STL/stlFileLoader.pure.js.map +1 -0
  84. package/STL/stlFileLoader.types.d.ts +10 -0
  85. package/STL/stlFileLoader.types.js +2 -0
  86. package/STL/stlFileLoader.types.js.map +1 -0
  87. package/glTF/1.0/pure.d.ts +3 -0
  88. package/glTF/1.0/pure.js +4 -0
  89. package/glTF/1.0/pure.js.map +1 -0
  90. package/glTF/2.0/Extensions/EXT_lights_area.d.ts +4 -39
  91. package/glTF/2.0/Extensions/EXT_lights_area.js +6 -91
  92. package/glTF/2.0/Extensions/EXT_lights_area.js.map +1 -1
  93. package/glTF/2.0/Extensions/EXT_lights_area.pure.d.ts +38 -0
  94. package/glTF/2.0/Extensions/EXT_lights_area.pure.js +106 -0
  95. package/glTF/2.0/Extensions/EXT_lights_area.pure.js.map +1 -0
  96. package/glTF/2.0/Extensions/EXT_lights_area.types.d.ts +9 -0
  97. package/glTF/2.0/Extensions/EXT_lights_area.types.js +2 -0
  98. package/glTF/2.0/Extensions/EXT_lights_area.types.js.map +1 -0
  99. package/glTF/2.0/Extensions/EXT_lights_ies.d.ts +4 -39
  100. package/glTF/2.0/Extensions/EXT_lights_ies.js +6 -81
  101. package/glTF/2.0/Extensions/EXT_lights_ies.js.map +1 -1
  102. package/glTF/2.0/Extensions/EXT_lights_ies.pure.d.ts +38 -0
  103. package/glTF/2.0/Extensions/EXT_lights_ies.pure.js +96 -0
  104. package/glTF/2.0/Extensions/EXT_lights_ies.pure.js.map +1 -0
  105. package/glTF/2.0/Extensions/EXT_lights_ies.types.d.ts +9 -0
  106. package/glTF/2.0/Extensions/EXT_lights_ies.types.js +2 -0
  107. package/glTF/2.0/Extensions/EXT_lights_ies.types.js.map +1 -0
  108. package/glTF/2.0/Extensions/EXT_lights_image_based.d.ts +4 -46
  109. package/glTF/2.0/Extensions/EXT_lights_image_based.js +6 -113
  110. package/glTF/2.0/Extensions/EXT_lights_image_based.js.map +1 -1
  111. package/glTF/2.0/Extensions/EXT_lights_image_based.pure.d.ts +37 -0
  112. package/glTF/2.0/Extensions/EXT_lights_image_based.pure.js +128 -0
  113. package/glTF/2.0/Extensions/EXT_lights_image_based.pure.js.map +1 -0
  114. package/glTF/2.0/Extensions/EXT_lights_image_based.types.d.ts +17 -0
  115. package/glTF/2.0/Extensions/EXT_lights_image_based.types.js +2 -0
  116. package/glTF/2.0/Extensions/EXT_lights_image_based.types.js.map +1 -0
  117. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.d.ts +5 -37
  118. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.js +7 -92
  119. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.js.map +1 -1
  120. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.d.ts +35 -0
  121. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.js +106 -0
  122. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.pure.js.map +1 -0
  123. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.d.ts +9 -0
  124. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.js +2 -0
  125. package/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.types.js.map +1 -0
  126. package/glTF/2.0/Extensions/EXT_meshopt_compression.d.ts +4 -37
  127. package/glTF/2.0/Extensions/EXT_meshopt_compression.js +6 -48
  128. package/glTF/2.0/Extensions/EXT_meshopt_compression.js.map +1 -1
  129. package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.d.ts +36 -0
  130. package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.js +63 -0
  131. package/glTF/2.0/Extensions/EXT_meshopt_compression.pure.js.map +1 -0
  132. package/glTF/2.0/Extensions/EXT_meshopt_compression.types.d.ts +9 -0
  133. package/glTF/2.0/Extensions/EXT_meshopt_compression.types.js +2 -0
  134. package/glTF/2.0/Extensions/EXT_meshopt_compression.types.js.map +1 -0
  135. package/glTF/2.0/Extensions/EXT_texture_avif.d.ts +4 -32
  136. package/glTF/2.0/Extensions/EXT_texture_avif.js +6 -36
  137. package/glTF/2.0/Extensions/EXT_texture_avif.js.map +1 -1
  138. package/glTF/2.0/Extensions/EXT_texture_avif.pure.d.ts +31 -0
  139. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js +51 -0
  140. package/glTF/2.0/Extensions/EXT_texture_avif.pure.js.map +1 -0
  141. package/glTF/2.0/Extensions/EXT_texture_avif.types.d.ts +9 -0
  142. package/glTF/2.0/Extensions/EXT_texture_avif.types.js +2 -0
  143. package/glTF/2.0/Extensions/EXT_texture_avif.types.js.map +1 -0
  144. package/glTF/2.0/Extensions/EXT_texture_webp.d.ts +4 -31
  145. package/glTF/2.0/Extensions/EXT_texture_webp.js +6 -35
  146. package/glTF/2.0/Extensions/EXT_texture_webp.js.map +1 -1
  147. package/glTF/2.0/Extensions/EXT_texture_webp.pure.d.ts +30 -0
  148. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js +50 -0
  149. package/glTF/2.0/Extensions/EXT_texture_webp.pure.js.map +1 -0
  150. package/glTF/2.0/Extensions/EXT_texture_webp.types.d.ts +9 -0
  151. package/glTF/2.0/Extensions/EXT_texture_webp.types.js +2 -0
  152. package/glTF/2.0/Extensions/EXT_texture_webp.types.js.map +1 -0
  153. package/glTF/2.0/Extensions/ExtrasAsMetadata.d.ts +4 -51
  154. package/glTF/2.0/Extensions/ExtrasAsMetadata.js +6 -71
  155. package/glTF/2.0/Extensions/ExtrasAsMetadata.js.map +1 -1
  156. package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.d.ts +50 -0
  157. package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.js +85 -0
  158. package/glTF/2.0/Extensions/ExtrasAsMetadata.pure.js.map +1 -0
  159. package/glTF/2.0/Extensions/ExtrasAsMetadata.types.d.ts +9 -0
  160. package/glTF/2.0/Extensions/ExtrasAsMetadata.types.js +2 -0
  161. package/glTF/2.0/Extensions/ExtrasAsMetadata.types.js.map +1 -0
  162. package/glTF/2.0/Extensions/KHR_animation_pointer.d.ts +5 -45
  163. package/glTF/2.0/Extensions/KHR_animation_pointer.js +7 -75
  164. package/glTF/2.0/Extensions/KHR_animation_pointer.js.map +1 -1
  165. package/glTF/2.0/Extensions/KHR_animation_pointer.pure.d.ts +43 -0
  166. package/glTF/2.0/Extensions/KHR_animation_pointer.pure.js +89 -0
  167. package/glTF/2.0/Extensions/KHR_animation_pointer.pure.js.map +1 -0
  168. package/glTF/2.0/Extensions/KHR_animation_pointer.types.d.ts +9 -0
  169. package/glTF/2.0/Extensions/KHR_animation_pointer.types.js +2 -0
  170. package/glTF/2.0/Extensions/KHR_animation_pointer.types.js.map +1 -0
  171. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.d.ts +4 -45
  172. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.js +6 -90
  173. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.js.map +1 -1
  174. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.d.ts +44 -0
  175. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.js +105 -0
  176. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.pure.js.map +1 -0
  177. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.d.ts +9 -0
  178. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.js +2 -0
  179. package/glTF/2.0/Extensions/KHR_draco_mesh_compression.types.js.map +1 -0
  180. package/glTF/2.0/Extensions/KHR_interactivity/pure.d.ts +4 -0
  181. package/glTF/2.0/Extensions/KHR_interactivity/pure.js +5 -0
  182. package/glTF/2.0/Extensions/KHR_interactivity/pure.js.map +1 -0
  183. package/glTF/2.0/Extensions/KHR_interactivity.d.ts +4 -36
  184. package/glTF/2.0/Extensions/KHR_interactivity.js +6 -148
  185. package/glTF/2.0/Extensions/KHR_interactivity.js.map +1 -1
  186. package/glTF/2.0/Extensions/KHR_interactivity.pure.d.ts +35 -0
  187. package/glTF/2.0/Extensions/KHR_interactivity.pure.js +163 -0
  188. package/glTF/2.0/Extensions/KHR_interactivity.pure.js.map +1 -0
  189. package/glTF/2.0/Extensions/KHR_interactivity.types.d.ts +9 -0
  190. package/glTF/2.0/Extensions/KHR_interactivity.types.js +2 -0
  191. package/glTF/2.0/Extensions/KHR_interactivity.types.js.map +1 -0
  192. package/glTF/2.0/Extensions/KHR_lights_punctual.d.ts +4 -39
  193. package/glTF/2.0/Extensions/KHR_lights_punctual.js +6 -89
  194. package/glTF/2.0/Extensions/KHR_lights_punctual.js.map +1 -1
  195. package/glTF/2.0/Extensions/KHR_lights_punctual.pure.d.ts +38 -0
  196. package/glTF/2.0/Extensions/KHR_lights_punctual.pure.js +104 -0
  197. package/glTF/2.0/Extensions/KHR_lights_punctual.pure.js.map +1 -0
  198. package/glTF/2.0/Extensions/KHR_lights_punctual.types.d.ts +9 -0
  199. package/glTF/2.0/Extensions/KHR_lights_punctual.types.js +2 -0
  200. package/glTF/2.0/Extensions/KHR_lights_punctual.types.js.map +1 -0
  201. package/glTF/2.0/Extensions/KHR_materials_anisotropy.d.ts +4 -40
  202. package/glTF/2.0/Extensions/KHR_materials_anisotropy.js +6 -63
  203. package/glTF/2.0/Extensions/KHR_materials_anisotropy.js.map +1 -1
  204. package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.d.ts +39 -0
  205. package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js +78 -0
  206. package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js.map +1 -0
  207. package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.d.ts +9 -0
  208. package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.js +2 -0
  209. package/glTF/2.0/Extensions/KHR_materials_anisotropy.types.js.map +1 -0
  210. package/glTF/2.0/Extensions/KHR_materials_clearcoat.d.ts +4 -41
  211. package/glTF/2.0/Extensions/KHR_materials_clearcoat.js +6 -77
  212. package/glTF/2.0/Extensions/KHR_materials_clearcoat.js.map +1 -1
  213. package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.d.ts +40 -0
  214. package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.js +92 -0
  215. package/glTF/2.0/Extensions/KHR_materials_clearcoat.pure.js.map +1 -0
  216. package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.d.ts +9 -0
  217. package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.js +2 -0
  218. package/glTF/2.0/Extensions/KHR_materials_clearcoat.types.js.map +1 -0
  219. package/glTF/2.0/Extensions/KHR_materials_coat.d.ts +4 -47
  220. package/glTF/2.0/Extensions/KHR_materials_coat.js +6 -117
  221. package/glTF/2.0/Extensions/KHR_materials_coat.js.map +1 -1
  222. package/glTF/2.0/Extensions/KHR_materials_coat.pure.d.ts +46 -0
  223. package/glTF/2.0/Extensions/KHR_materials_coat.pure.js +132 -0
  224. package/glTF/2.0/Extensions/KHR_materials_coat.pure.js.map +1 -0
  225. package/glTF/2.0/Extensions/KHR_materials_coat.types.d.ts +9 -0
  226. package/glTF/2.0/Extensions/KHR_materials_coat.types.js +2 -0
  227. package/glTF/2.0/Extensions/KHR_materials_coat.types.js.map +1 -0
  228. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.d.ts +4 -41
  229. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.js +6 -56
  230. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.js.map +1 -1
  231. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.d.ts +40 -0
  232. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.js +71 -0
  233. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.pure.js.map +1 -0
  234. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.d.ts +9 -0
  235. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.js +2 -0
  236. package/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.types.js.map +1 -0
  237. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.d.ts +4 -41
  238. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.js +6 -71
  239. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.js.map +1 -1
  240. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.d.ts +40 -0
  241. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.js +86 -0
  242. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.pure.js.map +1 -0
  243. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.d.ts +9 -0
  244. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.js +2 -0
  245. package/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.types.js.map +1 -0
  246. package/glTF/2.0/Extensions/KHR_materials_dispersion.d.ts +4 -41
  247. package/glTF/2.0/Extensions/KHR_materials_dispersion.js +6 -54
  248. package/glTF/2.0/Extensions/KHR_materials_dispersion.js.map +1 -1
  249. package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.d.ts +40 -0
  250. package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.js +69 -0
  251. package/glTF/2.0/Extensions/KHR_materials_dispersion.pure.js.map +1 -0
  252. package/glTF/2.0/Extensions/KHR_materials_dispersion.types.d.ts +9 -0
  253. package/glTF/2.0/Extensions/KHR_materials_dispersion.types.js +2 -0
  254. package/glTF/2.0/Extensions/KHR_materials_dispersion.types.js.map +1 -0
  255. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.d.ts +4 -40
  256. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.js +6 -45
  257. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.js.map +1 -1
  258. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.d.ts +39 -0
  259. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.js +60 -0
  260. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.pure.js.map +1 -0
  261. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.d.ts +9 -0
  262. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.js +2 -0
  263. package/glTF/2.0/Extensions/KHR_materials_emissive_strength.types.js.map +1 -0
  264. package/glTF/2.0/Extensions/KHR_materials_fuzz.d.ts +4 -41
  265. package/glTF/2.0/Extensions/KHR_materials_fuzz.js +6 -75
  266. package/glTF/2.0/Extensions/KHR_materials_fuzz.js.map +1 -1
  267. package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.d.ts +40 -0
  268. package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.js +90 -0
  269. package/glTF/2.0/Extensions/KHR_materials_fuzz.pure.js.map +1 -0
  270. package/glTF/2.0/Extensions/KHR_materials_fuzz.types.d.ts +9 -0
  271. package/glTF/2.0/Extensions/KHR_materials_fuzz.types.js +2 -0
  272. package/glTF/2.0/Extensions/KHR_materials_fuzz.types.js.map +1 -0
  273. package/glTF/2.0/Extensions/KHR_materials_ior.d.ts +4 -44
  274. package/glTF/2.0/Extensions/KHR_materials_ior.js +6 -52
  275. package/glTF/2.0/Extensions/KHR_materials_ior.js.map +1 -1
  276. package/glTF/2.0/Extensions/KHR_materials_ior.pure.d.ts +43 -0
  277. package/glTF/2.0/Extensions/KHR_materials_ior.pure.js +67 -0
  278. package/glTF/2.0/Extensions/KHR_materials_ior.pure.js.map +1 -0
  279. package/glTF/2.0/Extensions/KHR_materials_ior.types.d.ts +9 -0
  280. package/glTF/2.0/Extensions/KHR_materials_ior.types.js +2 -0
  281. package/glTF/2.0/Extensions/KHR_materials_ior.types.js.map +1 -0
  282. package/glTF/2.0/Extensions/KHR_materials_iridescence.d.ts +4 -40
  283. package/glTF/2.0/Extensions/KHR_materials_iridescence.js +6 -66
  284. package/glTF/2.0/Extensions/KHR_materials_iridescence.js.map +1 -1
  285. package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.d.ts +39 -0
  286. package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.js +81 -0
  287. package/glTF/2.0/Extensions/KHR_materials_iridescence.pure.js.map +1 -0
  288. package/glTF/2.0/Extensions/KHR_materials_iridescence.types.d.ts +9 -0
  289. package/glTF/2.0/Extensions/KHR_materials_iridescence.types.js +2 -0
  290. package/glTF/2.0/Extensions/KHR_materials_iridescence.types.js.map +1 -0
  291. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.d.ts +4 -40
  292. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.js +6 -72
  293. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.js.map +1 -1
  294. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.d.ts +39 -0
  295. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.js +87 -0
  296. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.pure.js.map +1 -0
  297. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.d.ts +9 -0
  298. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.js +2 -0
  299. package/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.types.js.map +1 -0
  300. package/glTF/2.0/Extensions/KHR_materials_sheen.d.ts +4 -41
  301. package/glTF/2.0/Extensions/KHR_materials_sheen.js +6 -71
  302. package/glTF/2.0/Extensions/KHR_materials_sheen.js.map +1 -1
  303. package/glTF/2.0/Extensions/KHR_materials_sheen.pure.d.ts +40 -0
  304. package/glTF/2.0/Extensions/KHR_materials_sheen.pure.js +86 -0
  305. package/glTF/2.0/Extensions/KHR_materials_sheen.pure.js.map +1 -0
  306. package/glTF/2.0/Extensions/KHR_materials_sheen.types.d.ts +9 -0
  307. package/glTF/2.0/Extensions/KHR_materials_sheen.types.js +2 -0
  308. package/glTF/2.0/Extensions/KHR_materials_sheen.types.js.map +1 -0
  309. package/glTF/2.0/Extensions/KHR_materials_specular.d.ts +4 -40
  310. package/glTF/2.0/Extensions/KHR_materials_specular.js +6 -74
  311. package/glTF/2.0/Extensions/KHR_materials_specular.js.map +1 -1
  312. package/glTF/2.0/Extensions/KHR_materials_specular.pure.d.ts +39 -0
  313. package/glTF/2.0/Extensions/KHR_materials_specular.pure.js +89 -0
  314. package/glTF/2.0/Extensions/KHR_materials_specular.pure.js.map +1 -0
  315. package/glTF/2.0/Extensions/KHR_materials_specular.types.d.ts +9 -0
  316. package/glTF/2.0/Extensions/KHR_materials_specular.types.js +2 -0
  317. package/glTF/2.0/Extensions/KHR_materials_specular.types.js.map +1 -0
  318. package/glTF/2.0/Extensions/KHR_materials_transmission.d.ts +4 -40
  319. package/glTF/2.0/Extensions/KHR_materials_transmission.js +6 -70
  320. package/glTF/2.0/Extensions/KHR_materials_transmission.js.map +1 -1
  321. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.d.ts +39 -0
  322. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js +85 -0
  323. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js.map +1 -0
  324. package/glTF/2.0/Extensions/KHR_materials_transmission.types.d.ts +9 -0
  325. package/glTF/2.0/Extensions/KHR_materials_transmission.types.js +2 -0
  326. package/glTF/2.0/Extensions/KHR_materials_transmission.types.js.map +1 -0
  327. package/glTF/2.0/Extensions/KHR_materials_unlit.d.ts +4 -40
  328. package/glTF/2.0/Extensions/KHR_materials_unlit.js +6 -64
  329. package/glTF/2.0/Extensions/KHR_materials_unlit.js.map +1 -1
  330. package/glTF/2.0/Extensions/KHR_materials_unlit.pure.d.ts +39 -0
  331. package/glTF/2.0/Extensions/KHR_materials_unlit.pure.js +79 -0
  332. package/glTF/2.0/Extensions/KHR_materials_unlit.pure.js.map +1 -0
  333. package/glTF/2.0/Extensions/KHR_materials_unlit.types.d.ts +9 -0
  334. package/glTF/2.0/Extensions/KHR_materials_unlit.types.js +2 -0
  335. package/glTF/2.0/Extensions/KHR_materials_unlit.types.js.map +1 -0
  336. package/glTF/2.0/Extensions/KHR_materials_variants.d.ts +4 -110
  337. package/glTF/2.0/Extensions/KHR_materials_variants.js +6 -260
  338. package/glTF/2.0/Extensions/KHR_materials_variants.js.map +1 -1
  339. package/glTF/2.0/Extensions/KHR_materials_variants.pure.d.ts +97 -0
  340. package/glTF/2.0/Extensions/KHR_materials_variants.pure.js +275 -0
  341. package/glTF/2.0/Extensions/KHR_materials_variants.pure.js.map +1 -0
  342. package/glTF/2.0/Extensions/KHR_materials_variants.types.d.ts +29 -0
  343. package/glTF/2.0/Extensions/KHR_materials_variants.types.js +2 -0
  344. package/glTF/2.0/Extensions/KHR_materials_variants.types.js.map +1 -0
  345. package/glTF/2.0/Extensions/KHR_materials_volume.d.ts +4 -41
  346. package/glTF/2.0/Extensions/KHR_materials_volume.js +6 -77
  347. package/glTF/2.0/Extensions/KHR_materials_volume.js.map +1 -1
  348. package/glTF/2.0/Extensions/KHR_materials_volume.pure.d.ts +40 -0
  349. package/glTF/2.0/Extensions/KHR_materials_volume.pure.js +92 -0
  350. package/glTF/2.0/Extensions/KHR_materials_volume.pure.js.map +1 -0
  351. package/glTF/2.0/Extensions/KHR_materials_volume.types.d.ts +9 -0
  352. package/glTF/2.0/Extensions/KHR_materials_volume.types.js +2 -0
  353. package/glTF/2.0/Extensions/KHR_materials_volume.types.js.map +1 -0
  354. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.d.ts +4 -43
  355. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js +6 -113
  356. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js.map +1 -1
  357. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.d.ts +42 -0
  358. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js +128 -0
  359. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js.map +1 -0
  360. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.d.ts +9 -0
  361. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.js +2 -0
  362. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.types.js.map +1 -0
  363. package/glTF/2.0/Extensions/KHR_mesh_quantization.d.ts +4 -27
  364. package/glTF/2.0/Extensions/KHR_mesh_quantization.js +6 -20
  365. package/glTF/2.0/Extensions/KHR_mesh_quantization.js.map +1 -1
  366. package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.d.ts +26 -0
  367. package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.js +35 -0
  368. package/glTF/2.0/Extensions/KHR_mesh_quantization.pure.js.map +1 -0
  369. package/glTF/2.0/Extensions/KHR_mesh_quantization.types.d.ts +9 -0
  370. package/glTF/2.0/Extensions/KHR_mesh_quantization.types.js +2 -0
  371. package/glTF/2.0/Extensions/KHR_mesh_quantization.types.js.map +1 -0
  372. package/glTF/2.0/Extensions/KHR_node_hoverability.d.ts +4 -29
  373. package/glTF/2.0/Extensions/KHR_node_hoverability.js +6 -187
  374. package/glTF/2.0/Extensions/KHR_node_hoverability.js.map +1 -1
  375. package/glTF/2.0/Extensions/KHR_node_hoverability.pure.d.ts +28 -0
  376. package/glTF/2.0/Extensions/KHR_node_hoverability.pure.js +202 -0
  377. package/glTF/2.0/Extensions/KHR_node_hoverability.pure.js.map +1 -0
  378. package/glTF/2.0/Extensions/KHR_node_hoverability.types.d.ts +9 -0
  379. package/glTF/2.0/Extensions/KHR_node_hoverability.types.js +2 -0
  380. package/glTF/2.0/Extensions/KHR_node_hoverability.types.js.map +1 -0
  381. package/glTF/2.0/Extensions/KHR_node_selectability.d.ts +4 -28
  382. package/glTF/2.0/Extensions/KHR_node_selectability.js +6 -121
  383. package/glTF/2.0/Extensions/KHR_node_selectability.js.map +1 -1
  384. package/glTF/2.0/Extensions/KHR_node_selectability.pure.d.ts +27 -0
  385. package/glTF/2.0/Extensions/KHR_node_selectability.pure.js +136 -0
  386. package/glTF/2.0/Extensions/KHR_node_selectability.pure.js.map +1 -0
  387. package/glTF/2.0/Extensions/KHR_node_selectability.types.d.ts +9 -0
  388. package/glTF/2.0/Extensions/KHR_node_selectability.types.js +2 -0
  389. package/glTF/2.0/Extensions/KHR_node_selectability.types.js.map +1 -0
  390. package/glTF/2.0/Extensions/KHR_node_visibility.d.ts +4 -28
  391. package/glTF/2.0/Extensions/KHR_node_visibility.js +6 -64
  392. package/glTF/2.0/Extensions/KHR_node_visibility.js.map +1 -1
  393. package/glTF/2.0/Extensions/KHR_node_visibility.pure.d.ts +27 -0
  394. package/glTF/2.0/Extensions/KHR_node_visibility.pure.js +79 -0
  395. package/glTF/2.0/Extensions/KHR_node_visibility.pure.js.map +1 -0
  396. package/glTF/2.0/Extensions/KHR_node_visibility.types.d.ts +9 -0
  397. package/glTF/2.0/Extensions/KHR_node_visibility.types.js +2 -0
  398. package/glTF/2.0/Extensions/KHR_node_visibility.types.js.map +1 -0
  399. package/glTF/2.0/Extensions/KHR_texture_basisu.d.ts +4 -31
  400. package/glTF/2.0/Extensions/KHR_texture_basisu.js +6 -35
  401. package/glTF/2.0/Extensions/KHR_texture_basisu.js.map +1 -1
  402. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.d.ts +30 -0
  403. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js +50 -0
  404. package/glTF/2.0/Extensions/KHR_texture_basisu.pure.js.map +1 -0
  405. package/glTF/2.0/Extensions/KHR_texture_basisu.types.d.ts +9 -0
  406. package/glTF/2.0/Extensions/KHR_texture_basisu.types.js +2 -0
  407. package/glTF/2.0/Extensions/KHR_texture_basisu.types.js.map +1 -0
  408. package/glTF/2.0/Extensions/KHR_texture_transform.d.ts +4 -35
  409. package/glTF/2.0/Extensions/KHR_texture_transform.js +6 -56
  410. package/glTF/2.0/Extensions/KHR_texture_transform.js.map +1 -1
  411. package/glTF/2.0/Extensions/KHR_texture_transform.pure.d.ts +34 -0
  412. package/glTF/2.0/Extensions/KHR_texture_transform.pure.js +71 -0
  413. package/glTF/2.0/Extensions/KHR_texture_transform.pure.js.map +1 -0
  414. package/glTF/2.0/Extensions/KHR_texture_transform.types.d.ts +9 -0
  415. package/glTF/2.0/Extensions/KHR_texture_transform.types.js +2 -0
  416. package/glTF/2.0/Extensions/KHR_texture_transform.types.js.map +1 -0
  417. package/glTF/2.0/Extensions/KHR_xmp_json_ld.d.ts +4 -37
  418. package/glTF/2.0/Extensions/KHR_xmp_json_ld.js +6 -45
  419. package/glTF/2.0/Extensions/KHR_xmp_json_ld.js.map +1 -1
  420. package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.d.ts +36 -0
  421. package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.js +60 -0
  422. package/glTF/2.0/Extensions/KHR_xmp_json_ld.pure.js.map +1 -0
  423. package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.d.ts +9 -0
  424. package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.js +2 -0
  425. package/glTF/2.0/Extensions/KHR_xmp_json_ld.types.js.map +1 -0
  426. package/glTF/2.0/Extensions/MSFT_audio_emitter.d.ts +5 -54
  427. package/glTF/2.0/Extensions/MSFT_audio_emitter.js +7 -226
  428. package/glTF/2.0/Extensions/MSFT_audio_emitter.js.map +1 -1
  429. package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.d.ts +52 -0
  430. package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.js +240 -0
  431. package/glTF/2.0/Extensions/MSFT_audio_emitter.pure.js.map +1 -0
  432. package/glTF/2.0/Extensions/MSFT_audio_emitter.types.d.ts +9 -0
  433. package/glTF/2.0/Extensions/MSFT_audio_emitter.types.js +2 -0
  434. package/glTF/2.0/Extensions/MSFT_audio_emitter.types.js.map +1 -0
  435. package/glTF/2.0/Extensions/MSFT_lod.d.ts +4 -102
  436. package/glTF/2.0/Extensions/MSFT_lod.js +6 -335
  437. package/glTF/2.0/Extensions/MSFT_lod.js.map +1 -1
  438. package/glTF/2.0/Extensions/MSFT_lod.pure.d.ts +96 -0
  439. package/glTF/2.0/Extensions/MSFT_lod.pure.js +350 -0
  440. package/glTF/2.0/Extensions/MSFT_lod.pure.js.map +1 -0
  441. package/glTF/2.0/Extensions/MSFT_lod.types.d.ts +14 -0
  442. package/glTF/2.0/Extensions/MSFT_lod.types.js +2 -0
  443. package/glTF/2.0/Extensions/MSFT_lod.types.js.map +1 -0
  444. package/glTF/2.0/Extensions/MSFT_minecraftMesh.d.ts +6 -27
  445. package/glTF/2.0/Extensions/MSFT_minecraftMesh.js +8 -40
  446. package/glTF/2.0/Extensions/MSFT_minecraftMesh.js.map +1 -1
  447. package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.d.ts +24 -0
  448. package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.js +53 -0
  449. package/glTF/2.0/Extensions/MSFT_minecraftMesh.pure.js.map +1 -0
  450. package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.d.ts +9 -0
  451. package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.js +2 -0
  452. package/glTF/2.0/Extensions/MSFT_minecraftMesh.types.js.map +1 -0
  453. package/glTF/2.0/Extensions/MSFT_sRGBFactors.d.ts +6 -27
  454. package/glTF/2.0/Extensions/MSFT_sRGBFactors.js +8 -38
  455. package/glTF/2.0/Extensions/MSFT_sRGBFactors.js.map +1 -1
  456. package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.d.ts +24 -0
  457. package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.js +51 -0
  458. package/glTF/2.0/Extensions/MSFT_sRGBFactors.pure.js.map +1 -0
  459. package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.d.ts +9 -0
  460. package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.js +2 -0
  461. package/glTF/2.0/Extensions/MSFT_sRGBFactors.types.js.map +1 -0
  462. package/glTF/2.0/Extensions/objectModelMapping.d.ts +31 -2
  463. package/glTF/2.0/Extensions/objectModelMapping.js +3 -3
  464. package/glTF/2.0/Extensions/objectModelMapping.js.map +1 -1
  465. package/glTF/2.0/Extensions/pure.d.ts +45 -0
  466. package/glTF/2.0/Extensions/pure.js +47 -0
  467. package/glTF/2.0/Extensions/pure.js.map +1 -0
  468. package/glTF/2.0/glTFLoader.d.ts +1 -471
  469. package/glTF/2.0/glTFLoader.js +3 -2539
  470. package/glTF/2.0/glTFLoader.js.map +1 -1
  471. package/glTF/2.0/glTFLoader.pure.d.ts +476 -0
  472. package/glTF/2.0/glTFLoader.pure.js +2575 -0
  473. package/glTF/2.0/glTFLoader.pure.js.map +1 -0
  474. package/glTF/2.0/glTFLoaderAnimation.d.ts +2 -2
  475. package/glTF/2.0/glTFLoaderAnimation.js +2 -2
  476. package/glTF/2.0/glTFLoaderAnimation.js.map +1 -1
  477. package/glTF/2.0/pbrMaterialLoadingAdapter.d.ts +1 -1
  478. package/glTF/2.0/pbrMaterialLoadingAdapter.js +2 -2
  479. package/glTF/2.0/pbrMaterialLoadingAdapter.js.map +1 -1
  480. package/glTF/2.0/pure.d.ts +8 -0
  481. package/glTF/2.0/pure.js +10 -0
  482. package/glTF/2.0/pure.js.map +1 -0
  483. package/glTF/glTFFileLoader.d.ts +5 -551
  484. package/glTF/glTFFileLoader.js +6 -996
  485. package/glTF/glTFFileLoader.js.map +1 -1
  486. package/glTF/glTFFileLoader.pure.d.ts +562 -0
  487. package/glTF/glTFFileLoader.pure.js +1024 -0
  488. package/glTF/glTFFileLoader.pure.js.map +1 -0
  489. package/glTF/glTFFileLoader.types.d.ts +11 -0
  490. package/glTF/glTFFileLoader.types.js +2 -0
  491. package/glTF/glTFFileLoader.types.js.map +1 -0
  492. package/glTF/glTFValidation.js +1 -1
  493. package/glTF/glTFValidation.js.map +1 -1
  494. package/glTF/pure.d.ts +6 -0
  495. package/glTF/pure.js +8 -0
  496. package/glTF/pure.js.map +1 -0
  497. package/package.json +56 -4
  498. package/pure.d.ts +8 -0
  499. package/pure.js +10 -0
  500. package/pure.js.map +1 -0
@@ -9,10 +9,18 @@ import { Camera } from "@babylonjs/core/Cameras/camera.js";
9
9
  import { BoundingInfo } from "@babylonjs/core/Culling/boundingInfo.js";
10
10
  import { CreateLineSystem } from "@babylonjs/core/Meshes/Builders/linesBuilder.js";
11
11
  import { VertexBuffer } from "@babylonjs/core/Buffers/buffer.js";
12
- import { ParseSogMetaAsTextures } from "./sog.js";
12
+ import { ParseSogMetaAsTextures } from "./sog.pure.js";
13
13
  import { GaussianSplattingWorkBuffer } from "./gaussianSplattingWorkBuffer.js";
14
+ import { GaussianSplattingDownloadManager } from "./gaussianSplattingDownloadManager.js";
15
+ import { GaussianSplattingResidencyController } from "./gaussianSplattingResidencyController.js";
14
16
  // tan(22.5deg): reference half-FOV for a 45-degree vertical FOV, used for FOV compensation (matches PlayCanvas).
15
17
  const RefTanHalfFov = Math.tan((22.5 * Math.PI) / 180);
18
+ // Sentinel "file" ids for the residency controller's pinned (never-evicted) allocations.
19
+ const PaddingFileId = -2;
20
+ const EnvironmentFileId = -1;
21
+ // Approximate bytes per resident splat used to convert a memory budget (MB) to a splat budget: the four
22
+ // work-buffer textures cost 16+16+16+4 = 52 bytes on the GPU, plus ~32 bytes of CPU position/sort data.
23
+ const BytesPerResidentSplat = 84;
16
24
  // Scratch objects reused by the per-frame optimal-LOD evaluation (avoids per-call allocations).
17
25
  const TmpInvWorld = new Matrix();
18
26
  const TmpLocalCamera = new Vector3();
@@ -122,18 +130,33 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
122
130
  this._readbackCandidate = false;
123
131
  // Set once the one-time readback validation has run (success or failure).
124
132
  this._readbackProbed = false;
125
- // Global splat offset where each source file begins in the work buffer (fixed for all files up front).
126
- this._fileBaseSplat = new Map();
133
+ // Residency controller: owns the work-buffer slot allocator, per-file blocks, and eviction cooldowns.
134
+ this._residency = null;
127
135
  // Splat count of each source file (learned from its metadata before allocation).
128
136
  this._fileCounts = new Map();
129
137
  // Cached SOG metadata per file so on-demand decodes don't refetch the meta.json.
130
138
  this._fileMeta = new Map();
131
- // Files whose splats have been GPU-decoded into the work buffer.
139
+ // Files whose splats have been fully GPU-decoded into the work buffer (render-safe).
132
140
  this._decodedFiles = new Set();
133
141
  // Files whose decode is currently in flight (dedupes concurrent requests).
134
142
  this._loadingFiles = new Set();
135
143
  // FIFO of file ids waiting to be decoded (drained under a per-frame budget).
136
144
  this._decodeQueue = [];
145
+ // Per-file reference count: number of leaf nodes whose active LOD renders, or whose pending target points
146
+ // at, each file. At zero, a decoded file is scheduled for eviction and a still-downloading file is cancelled.
147
+ this._fileRefs = new Map();
148
+ // Files whose in-flight decode was cancelled; checked at decode checkpoints to bail out cooperatively.
149
+ this._cancelledDecodes = new Set();
150
+ // Eviction streaming config: enabled only when a budget smaller than the full dataset is configured.
151
+ this._evictionEnabled = false;
152
+ this._residentBudget = 0;
153
+ this._evictionCooldownFrames = 100;
154
+ // Serializes the allocate -> decode -> readback critical section so a defrag relayout (which runs inside it)
155
+ // never overlaps another file's decode writing the work buffer, which would corrupt the moved data.
156
+ this._decodeGate = Promise.resolve();
157
+ // Reusable scratch for the (rare) defrag relayout, to avoid per-relayout allocations during streaming.
158
+ this._relayoutOldOffsets = new Map();
159
+ this._relayoutSrcIndex = null;
137
160
  // Global range covered by the environment file (always rendered), or null until it loads.
138
161
  this._environmentRange = null;
139
162
  // Unzipped environment bundle contents, retained between count-gathering and decode.
@@ -201,6 +224,23 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
201
224
  if (options.debugLodSource) {
202
225
  this._debugLodSource = options.debugLodSource;
203
226
  }
227
+ if (options.evictionCooldownFrames !== undefined) {
228
+ this._evictionCooldownFrames = Math.max(0, Math.floor(options.evictionCooldownFrames));
229
+ }
230
+ // Resolve the resident-splat budget from the splat-count and/or memory-size options (smaller wins).
231
+ let budget = 0;
232
+ if (options.maxResidentSplats !== undefined && options.maxResidentSplats > 0) {
233
+ budget = Math.floor(options.maxResidentSplats);
234
+ }
235
+ if (options.memoryBudgetMb !== undefined && options.memoryBudgetMb > 0) {
236
+ const fromMB = Math.floor((options.memoryBudgetMb * 1024 * 1024) / BytesPerResidentSplat);
237
+ budget = budget > 0 ? Math.min(budget, fromMB) : fromMB;
238
+ }
239
+ this._residentBudget = budget;
240
+ this._downloadManager = new GaussianSplattingDownloadManager({
241
+ maxConcurrent: options.maxConcurrentDownloads,
242
+ maxRetries: options.maxDownloadRetries,
243
+ });
204
244
  // PlayCanvas SOG data is authored with a flipped Y; match the standard SOG loader.
205
245
  this.scaling.y *= -1;
206
246
  // PlayCanvas SOG LOD scenes are authored Z-up; rotate into Babylon's Y-up convention.
@@ -218,6 +258,88 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
218
258
  getClassName() {
219
259
  return "GaussianSplattingStream";
220
260
  }
261
+ /**
262
+ * Resolves once the scene is fully streamed and displayed for the current camera: a LOD re-evaluation has
263
+ * run for the current point of view, every reachable LOD file has finished downloading and decoding (no
264
+ * downloads, decodes, or queued work remain), and the depth sort for the resulting splats has been applied
265
+ * and rendered. Intended for deterministic automated testing and screenshot/image comparison.
266
+ *
267
+ * Streaming and settling require rendered frames. If an external render loop is already running, this waits
268
+ * on it passively; otherwise (e.g. when awaited inside an async `createScene` before the host starts its
269
+ * render loop) it drives `scene.render()` itself until settled, so it never deadlocks.
270
+ *
271
+ * Note: the promise only resolves while the camera is still — if the camera keeps moving, the target LODs
272
+ * (and the depth sort) keep changing and the stream never settles. Position the camera, then await this.
273
+ * @param stableFrames number of consecutive settled frames to require before resolving (defaults to 3), so
274
+ * the final sorted frame is actually on screen
275
+ * @returns a promise that resolves when loading and rendering are complete for the current view
276
+ */
277
+ async whenSettledAsync(stableFrames = 3) {
278
+ if (this._disposed) {
279
+ return;
280
+ }
281
+ // Re-evaluate LODs immediately so the target levels reflect the current camera before we wait.
282
+ this._forceLodUpdate = true;
283
+ const required = Math.max(1, stableFrames);
284
+ const scene = this._scene;
285
+ let stable = 0;
286
+ const isSettled = () => {
287
+ if (this._isLoadingIdle() && this._isDepthSortSettled) {
288
+ return ++stable >= required;
289
+ }
290
+ stable = 0;
291
+ return false;
292
+ };
293
+ // An external render loop is already driving frames: observe it passively.
294
+ if (scene.getEngine().activeRenderLoops.length > 0) {
295
+ await new Promise((resolve) => {
296
+ let observer = null;
297
+ observer = scene.onAfterRenderObservable.add(() => {
298
+ if (this._disposed || isSettled()) {
299
+ if (observer) {
300
+ scene.onAfterRenderObservable.remove(observer);
301
+ observer = null;
302
+ }
303
+ resolve();
304
+ }
305
+ });
306
+ });
307
+ return;
308
+ }
309
+ // No render loop yet (e.g. awaited inside createScene): drive rendering ourselves so the streaming
310
+ // decodes and depth sort can progress, yielding between frames so async downloads/readbacks resolve.
311
+ // Wrap each render in beginFrame/endFrame exactly like the engine's own render loop: on WebGPU,
312
+ // endFrame submits the frame's command buffers and presents the swapchain, so a bare scene.render()
313
+ // would leave the acquired swapchain texture to be destroyed at the frame boundary before its command
314
+ // buffer is submitted ("destroyed texture used in a submit").
315
+ const engine = scene.getEngine();
316
+ const requestFrame = globalThis.requestAnimationFrame;
317
+ while (!this._disposed) {
318
+ engine.beginFrame();
319
+ scene.render();
320
+ engine.endFrame();
321
+ if (isSettled()) {
322
+ return;
323
+ }
324
+ // eslint-disable-next-line no-await-in-loop
325
+ await new Promise((resolve) => {
326
+ if (typeof requestFrame === "function") {
327
+ requestFrame(() => resolve());
328
+ }
329
+ else {
330
+ setTimeout(resolve, 16);
331
+ }
332
+ });
333
+ }
334
+ }
335
+ /**
336
+ * Whether the base layer is ready and there is no streaming work in flight (nothing queued for decode, no
337
+ * decode running, and no downloads pending).
338
+ * @returns true when no loading work remains
339
+ */
340
+ _isLoadingIdle() {
341
+ return this._baseLayerReady && this._decodeQueue.length === 0 && this._loadingFiles.size === 0 && this._downloadManager.isIdle;
342
+ }
221
343
  /**
222
344
  * Finest (most detailed) LOD level any node is allowed to render. `0` allows full detail (level 0);
223
345
  * `1` caps detail at the next-coarser level, and so on. Nodes already coarser than this cap (by
@@ -300,6 +422,9 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
300
422
  this._lodObserver = null;
301
423
  }
302
424
  this._clearDebugDisplay();
425
+ this._downloadManager.dispose();
426
+ this._residency?.dispose();
427
+ this._residency = null;
303
428
  this._workBuffer?.dispose();
304
429
  this._workBuffer = null;
305
430
  super.dispose(doNotRecurse);
@@ -576,26 +701,40 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
576
701
  if (this._disposed) {
577
702
  return;
578
703
  }
579
- // Phase 2: assign fixed work-buffer offsets (environment first, then every file) and allocate.
704
+ // Phase 2: learn the full dataset size (padding + environment + every LOD file). The work buffer is
705
+ // sized to this unless a smaller budget enables eviction-based streaming.
580
706
  // Index 0 is reserved as a never-decoded padding splat: the sort worker and index buffer pad unused
581
707
  // slots with index 0, and leaving that slot zeroed (center.w = 0 => zero covariance, alpha 0) makes
582
708
  // the padding invisible instead of ghosting a copy of the first real splat.
583
- let capacity = 1;
709
+ let fullCapacity = 1;
584
710
  if (envCount > 0) {
585
- this._environmentRange = { offset: capacity, count: envCount };
586
- capacity += envCount;
711
+ fullCapacity += envCount;
587
712
  }
588
713
  for (const fileId of fileIds) {
589
714
  const count = this._fileCounts.get(fileId);
590
- if (count === undefined || count <= 0) {
591
- continue;
715
+ if (count !== undefined && count > 0) {
716
+ fullCapacity += count;
592
717
  }
593
- this._fileBaseSplat.set(fileId, capacity);
594
- capacity += count;
595
718
  }
596
- if (capacity <= 1) {
719
+ if (fullCapacity <= 1) {
597
720
  return;
598
721
  }
722
+ // Eviction streams the dataset through a fixed budget; only enabled when that budget is below the full set.
723
+ this._evictionEnabled = this._residentBudget > 0 && this._residentBudget < fullCapacity;
724
+ const capacity = this._evictionEnabled ? Math.max(this._residentBudget, 1) : fullCapacity;
725
+ this._residency = new GaussianSplattingResidencyController(capacity, this._evictionCooldownFrames, (file) => this._onFileEvicted(file));
726
+ // Pin splat 0 as the invisible padding splat, then the environment (always rendered) — neither is evicted.
727
+ this._residency.pin(PaddingFileId, 1);
728
+ if (envCount > 0) {
729
+ const envOffset = this._residency.pin(EnvironmentFileId, envCount);
730
+ if (envOffset !== null) {
731
+ this._environmentRange = { offset: envOffset, count: envCount };
732
+ }
733
+ else {
734
+ Logger.Warn("GaussianSplattingStream: environment does not fit the memory budget; skipping it.");
735
+ this._environmentFiles = null;
736
+ }
737
+ }
599
738
  this._workBuffer = new GaussianSplattingWorkBuffer(this._scene, capacity);
600
739
  // GPU readback is only enabled after it is validated against a CPU decode on the first file (see
601
740
  // _probeReadbackAsync); until then positions are decoded on the CPU so there is always a correct result.
@@ -614,7 +753,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
614
753
  const baseFiles = new Set();
615
754
  for (const node of this._leafNodes) {
616
755
  const entry = node.lods[String(node.baseLod)];
617
- if (entry && this._fileBaseSplat.has(entry.file)) {
756
+ if (entry && this._fileCounts.has(entry.file)) {
618
757
  baseFiles.add(entry.file);
619
758
  }
620
759
  }
@@ -661,7 +800,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
661
800
  if (this._metadata.environment) {
662
801
  try {
663
802
  const url = this._rootUrl + this._metadata.environment;
664
- const buffer = (await Tools.LoadFileAsync(url, true));
803
+ const buffer = await this._downloadManager.loadFileAsync(url);
665
804
  const files = await this._unzipAsync(new Uint8Array(buffer));
666
805
  const metaBytes = files.get("meta.json");
667
806
  if (metaBytes) {
@@ -684,8 +823,8 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
684
823
  try {
685
824
  const metaUrl = this._rootUrl + relativePath;
686
825
  const subRootUrl = metaUrl.substring(0, metaUrl.lastIndexOf("/") + 1);
687
- const metaText = (await Tools.LoadFileAsync(metaUrl, false));
688
- const sogData = JSON.parse(metaText);
826
+ const metaBuffer = await this._downloadManager.loadFileAsync(metaUrl);
827
+ const sogData = JSON.parse(new TextDecoder().decode(new Uint8Array(metaBuffer)));
689
828
  this._fileCounts.set(fileId, GaussianSplattingStream._GetSplatCount(sogData));
690
829
  this._fileMeta.set(fileId, { sogData, subRootUrl });
691
830
  }
@@ -823,7 +962,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
823
962
  }
824
963
  const range = this._environmentRange;
825
964
  try {
826
- const parsed = await ParseSogMetaAsTextures(this._environmentFiles, "", this._scene, !this._useGpuPositionReadback);
965
+ const parsed = await ParseSogMetaAsTextures(this._environmentFiles, "", this._scene, !this._useGpuPositionReadback, this._downloadManager);
827
966
  const pack = parsed.sogTextures;
828
967
  if (!pack) {
829
968
  return;
@@ -854,32 +993,54 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
854
993
  /**
855
994
  * Loads one LOD source file as GPU textures, decodes it into its fixed work-buffer block, records its
856
995
  * CPU centers for sorting, frees the source textures, then promotes any nodes that were waiting for it.
857
- * Concurrent or repeat requests for the same file are ignored.
996
+ * Concurrent or repeat requests for the same file are ignored. If the file is cancelled mid-flight
997
+ * (because every node that wanted it retargeted), the decode bails cooperatively at the next checkpoint.
858
998
  * @param fileId file index to decode
859
999
  */
860
1000
  async _decodeFileAsync(fileId) {
861
- if (this._decodedFiles.has(fileId) || this._loadingFiles.has(fileId)) {
1001
+ if (this._decodedFiles.has(fileId) || this._loadingFiles.has(fileId) || !this._residency) {
862
1002
  return;
863
1003
  }
864
1004
  const meta = this._fileMeta.get(fileId);
865
- const base = this._fileBaseSplat.get(fileId);
866
1005
  const count = this._fileCounts.get(fileId);
867
- if (!meta || base === undefined || count === undefined) {
1006
+ if (!meta || count === undefined) {
868
1007
  return;
869
1008
  }
870
1009
  this._loadingFiles.add(fileId);
1010
+ this._cancelledDecodes.delete(fileId);
1011
+ let allocated = false;
871
1012
  try {
872
- const parsed = await ParseSogMetaAsTextures(meta.sogData, meta.subRootUrl, this._scene, !this._useGpuPositionReadback);
1013
+ const parsed = await ParseSogMetaAsTextures(meta.sogData, meta.subRootUrl, this._scene, !this._useGpuPositionReadback, this._downloadManager, fileId);
873
1014
  const pack = parsed.sogTextures;
874
1015
  if (!pack) {
875
1016
  return;
876
1017
  }
1018
+ // Serialize the allocate -> decode -> readback section: a relayout runs only inside it (see
1019
+ // _relayoutAndAllocateAsync), so it never moves a file whose decode has not finished writing.
1020
+ const release = await this._acquireDecodeGateAsync();
877
1021
  try {
878
- if (this._disposed || !this._workBuffer) {
1022
+ if (this._disposed || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
1023
+ return;
1024
+ }
1025
+ let base = this._residency.allocate(fileId, count);
1026
+ if (base === null) {
1027
+ // Defragment the work buffer to reclaim fragmented free space, then retry.
1028
+ base = await this._relayoutAndAllocateAsync(fileId, count);
1029
+ }
1030
+ if (base === null) {
1031
+ // No room even after evicting and compacting: refuse and keep nodes on their current LOD.
1032
+ // A file cancelled mid-flight isn't a budget problem, so don't warn for it.
1033
+ if (!this._cancelledDecodes.has(fileId)) {
1034
+ Logger.Warn(`GaussianSplattingStream: resident memory budget full; skipping LOD file ${fileId}.`);
1035
+ }
1036
+ return;
1037
+ }
1038
+ allocated = true;
1039
+ if (this._disposed || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
879
1040
  return;
880
1041
  }
881
1042
  await this._workBuffer.decodeAsync(pack, base);
882
- if (this._disposed) {
1043
+ if (this._disposed || this._cancelledDecodes.has(fileId)) {
883
1044
  return;
884
1045
  }
885
1046
  await this._applyDecodedPositionsAsync(pack, base, count);
@@ -893,14 +1054,136 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
893
1054
  }
894
1055
  }
895
1056
  finally {
896
- // Always release the GPU source textures (the decode pass is the only consumer).
897
1057
  GaussianSplattingStream._DisposePack(pack);
1058
+ release();
1059
+ }
1060
+ }
1061
+ catch (e) {
1062
+ // A cancelled file rejects its downloads on purpose — swallow that; re-throw genuine failures.
1063
+ if (!this._cancelledDecodes.has(fileId)) {
1064
+ throw e;
898
1065
  }
899
1066
  }
900
1067
  finally {
1068
+ // If a slot was allocated but the decode did not complete (cancelled/disposed), release it.
1069
+ if (allocated && !this._decodedFiles.has(fileId)) {
1070
+ this._residency.free(fileId);
1071
+ }
901
1072
  this._loadingFiles.delete(fileId);
1073
+ this._cancelledDecodes.delete(fileId);
902
1074
  }
903
1075
  }
1076
+ /**
1077
+ * Acquires the decode gate (a simple async mutex). Resolves once any prior holder releases, returning a
1078
+ * release function the caller must invoke in a `finally`.
1079
+ * @returns the release function
1080
+ */
1081
+ async _acquireDecodeGateAsync() {
1082
+ const previous = this._decodeGate;
1083
+ let release;
1084
+ this._decodeGate = new Promise((resolve) => {
1085
+ release = resolve;
1086
+ });
1087
+ await previous;
1088
+ return release;
1089
+ }
1090
+ /**
1091
+ * Defragments the work buffer to make room for a file that did not fit, then allocates its slot. Runs the
1092
+ * compaction + GPU relayout atomically inside a single `onBeforeRender` so no inconsistent CPU/GPU layout
1093
+ * is ever rendered. Returns the new slot offset, or null if even compaction cannot free enough contiguous
1094
+ * space (the caller refuses the upgrade).
1095
+ * @param fileId file to allocate after compaction
1096
+ * @param count splats the file needs
1097
+ * @returns the allocated offset, or null
1098
+ */
1099
+ async _relayoutAndAllocateAsync(fileId, count) {
1100
+ if (!this._residency || !this._workBuffer) {
1101
+ return null;
1102
+ }
1103
+ // Even a perfect compaction cannot help if the total free space is below what is needed.
1104
+ if (this._residency.freeSize < count) {
1105
+ return null;
1106
+ }
1107
+ return await new Promise((resolve) => {
1108
+ const attempt = () => {
1109
+ // Bail out (no relayout) if the file was cancelled while we waited for the shader to be ready,
1110
+ // so rapidly-changing targets don't trigger an expensive compaction for a file no longer needed.
1111
+ if (this._disposed || !this._residency || !this._workBuffer || this._cancelledDecodes.has(fileId)) {
1112
+ resolve(null);
1113
+ return;
1114
+ }
1115
+ if (!this._workBuffer.isRelayoutReady()) {
1116
+ this._scene.onBeforeRenderObservable.addOnce(attempt);
1117
+ return;
1118
+ }
1119
+ this._performRelayout();
1120
+ resolve(this._residency.allocate(fileId, count));
1121
+ };
1122
+ this._scene.onBeforeRenderObservable.addOnce(attempt);
1123
+ });
1124
+ }
1125
+ /**
1126
+ * Compacts the resident set and relocates the corresponding GPU textures and CPU positions to the new
1127
+ * layout. Must run at a frame-safe point with the work buffer's relayout shader ready.
1128
+ */
1129
+ _performRelayout() {
1130
+ if (!this._residency || !this._workBuffer || !this._splatPositions) {
1131
+ return;
1132
+ }
1133
+ const oldOffsets = this._relayoutOldOffsets;
1134
+ oldOffsets.clear();
1135
+ for (const block of this._residency.getResidentBlocks()) {
1136
+ oldOffsets.set(block.file, block.offset);
1137
+ }
1138
+ const moves = this._residency.compact();
1139
+ if (moves.length === 0) {
1140
+ return;
1141
+ }
1142
+ const capacity = this._residency.capacity;
1143
+ if (!this._relayoutSrcIndex || this._relayoutSrcIndex.length !== capacity) {
1144
+ this._relayoutSrcIndex = new Float32Array(capacity);
1145
+ }
1146
+ const srcIndexByDst = this._relayoutSrcIndex;
1147
+ srcIndexByDst.fill(-1);
1148
+ const resident = this._residency.getResidentBlocks();
1149
+ // Destination->source splat index map for the GPU relayout pass.
1150
+ for (const block of resident) {
1151
+ const oldOffset = oldOffsets.get(block.file);
1152
+ for (let k = 0; k < block.count; k++) {
1153
+ srcIndexByDst[block.offset + k] = oldOffset + k;
1154
+ }
1155
+ }
1156
+ // GPU: relocate the decoded textures in place (same texture instances).
1157
+ this._workBuffer.relayoutSync(srcIndexByDst);
1158
+ // CPU positions: compaction only ever moves a block to a lower offset, so copying in place in ascending
1159
+ // new-offset order is safe (a block's source is never overwritten by an earlier move). This avoids a
1160
+ // full capacity*4 scratch buffer.
1161
+ const positions = this._splatPositions;
1162
+ resident.sort((a, b) => a.offset - b.offset);
1163
+ for (const block of resident) {
1164
+ const oldOffset = oldOffsets.get(block.file);
1165
+ if (oldOffset !== block.offset) {
1166
+ positions.copyWithin(block.offset * 4, oldOffset * 4, (oldOffset + block.count) * 4);
1167
+ }
1168
+ }
1169
+ // Update the environment offset (it may have moved), re-post to the sort worker, and refresh ranges.
1170
+ if (this._environmentRange) {
1171
+ const envOffset = this._residency.offset(EnvironmentFileId);
1172
+ if (envOffset !== undefined) {
1173
+ this._environmentRange.offset = envOffset;
1174
+ }
1175
+ }
1176
+ this._notifyWorkerNewData();
1177
+ this._refreshActiveRanges();
1178
+ }
1179
+ /**
1180
+ * Drops a file evicted by the residency controller from the decoded set so it will be re-decoded on demand.
1181
+ * The file had no remaining references, so no node was rendering or downloading it.
1182
+ * @param fileId evicted file index
1183
+ */
1184
+ _onFileEvicted(fileId) {
1185
+ this._decodedFiles.delete(fileId);
1186
+ }
904
1187
  /**
905
1188
  * Snaps a desired LOD level to the nearest level the node provides, while never selecting a level finer
906
1189
  * than {@link maxDetailLod} (i.e. with an index below the cap). Ties prefer the finer allowed level. If
@@ -939,35 +1222,119 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
939
1222
  }
940
1223
  /**
941
1224
  * Applies each node's {@link ISOGLODNode.targetLevel}: switches a node to its target level when that
942
- * level's file is already decoded, otherwise queues the file and leaves the node on its current LOD (so
943
- * nothing ever disappears). Nodes within their post-switch cooldown are left untouched to damp oscillation.
1225
+ * level's file is already decoded, otherwise records a pending download request for the file and leaves
1226
+ * the node on its current LOD (so nothing ever disappears). Nodes within their post-switch cooldown are
1227
+ * left untouched to damp oscillation (and keep their existing pending request).
1228
+ *
1229
+ * Each node tracks the single file it currently needs but lacks ({@link ISOGLODNode.pendingFile}). When a
1230
+ * node's target changes before that file finished downloading, the old file's reference is released; if no
1231
+ * other node still needs it, its queued/in-flight download is cancelled (see {@link _releaseFileRef}).
944
1232
  * @returns true when at least one node changed LOD (callers should refresh the active ranges)
945
1233
  */
946
1234
  _applyDesiredLods() {
947
1235
  let dirty = false;
948
1236
  for (const node of this._leafNodes) {
1237
+ // Nodes in cooldown keep their current LOD and their existing pending request untouched.
949
1238
  if (node.lodCooldown && node.lodCooldown > 0) {
950
1239
  continue;
951
1240
  }
952
1241
  const desired = node.targetLevel ?? node.baseLod;
953
- if (desired === node.activeLod) {
954
- continue;
955
- }
956
- const entry = node.lods[String(desired)];
957
- if (!entry) {
958
- continue;
959
- }
960
- if (this._decodedFiles.has(entry.file)) {
961
- node.activeLod = desired;
962
- node.lodCooldown = this._lodCooldownFrames;
963
- dirty = true;
1242
+ let newPending;
1243
+ if (desired !== node.activeLod) {
1244
+ const entry = node.lods[String(desired)];
1245
+ if (entry) {
1246
+ if (this._decodedFiles.has(entry.file)) {
1247
+ this._switchActiveFile(node, entry.file);
1248
+ node.activeLod = desired;
1249
+ node.lodCooldown = this._lodCooldownFrames;
1250
+ dirty = true;
1251
+ }
1252
+ else {
1253
+ newPending = entry.file;
1254
+ }
1255
+ }
964
1256
  }
965
- else {
966
- this._enqueueDecode(entry.file);
1257
+ // Reconcile this node's pending-download reference against its (possibly changed) target.
1258
+ if (node.pendingFile !== newPending) {
1259
+ if (node.pendingFile !== undefined) {
1260
+ this._releaseFileRef(node.pendingFile);
1261
+ }
1262
+ if (newPending !== undefined) {
1263
+ this._acquirePendingFile(newPending);
1264
+ }
1265
+ node.pendingFile = newPending;
967
1266
  }
968
1267
  }
969
1268
  return dirty;
970
1269
  }
1270
+ /**
1271
+ * Moves a node's resident reference from its previous active file to the one it now renders, so the file
1272
+ * count that keeps a block in the work buffer stays accurate (and cancels any pending eviction of the new
1273
+ * file). The new file is already decoded.
1274
+ * @param node leaf node switching its rendered file
1275
+ * @param file the file the node now renders from
1276
+ */
1277
+ _switchActiveFile(node, file) {
1278
+ if (node.activeFile === file) {
1279
+ return;
1280
+ }
1281
+ if (node.activeFile !== undefined) {
1282
+ this._releaseFileRef(node.activeFile);
1283
+ }
1284
+ this._acquireFileRef(file);
1285
+ node.activeFile = file;
1286
+ }
1287
+ /**
1288
+ * Adds a reference to a file (active render or pending download), cancelling any scheduled eviction.
1289
+ * @param fileId file index
1290
+ */
1291
+ _acquireFileRef(fileId) {
1292
+ const refs = (this._fileRefs.get(fileId) ?? 0) + 1;
1293
+ this._fileRefs.set(fileId, refs);
1294
+ if (refs === 1) {
1295
+ // Referenced again before its eviction cooldown elapsed: keep it resident.
1296
+ this._residency?.cancelEviction(fileId);
1297
+ }
1298
+ }
1299
+ /**
1300
+ * Records that a node needs a not-yet-decoded file, bumping its reference count and queueing the decode.
1301
+ * @param fileId file index the node now targets
1302
+ */
1303
+ _acquirePendingFile(fileId) {
1304
+ this._acquireFileRef(fileId);
1305
+ this._enqueueDecode(fileId);
1306
+ }
1307
+ /**
1308
+ * Releases a node's reference to a file. When the last reference is dropped: a decoded file is scheduled
1309
+ * for eviction (when streaming under a budget), and a still-downloading file has its queued decode dropped
1310
+ * and any in-flight download cancelled.
1311
+ * @param fileId file index the node no longer references
1312
+ */
1313
+ _releaseFileRef(fileId) {
1314
+ const refs = (this._fileRefs.get(fileId) ?? 0) - 1;
1315
+ if (refs > 0) {
1316
+ this._fileRefs.set(fileId, refs);
1317
+ return;
1318
+ }
1319
+ this._fileRefs.delete(fileId);
1320
+ if (this._decodedFiles.has(fileId)) {
1321
+ // No node renders it anymore: schedule eviction (only when streaming under a budget).
1322
+ if (this._evictionEnabled) {
1323
+ this._residency?.scheduleEviction(fileId);
1324
+ }
1325
+ return;
1326
+ }
1327
+ // Still downloading/queued: drop the queued decode and cancel any in-flight download.
1328
+ const queueIndex = this._decodeQueue.indexOf(fileId);
1329
+ if (queueIndex !== -1) {
1330
+ this._decodeQueue.splice(queueIndex, 1);
1331
+ }
1332
+ if (this._loadingFiles.has(fileId)) {
1333
+ // Flag the in-flight decode to bail at its next checkpoint and cancel its image downloads.
1334
+ this._cancelledDecodes.add(fileId);
1335
+ this._downloadManager.cancelGroup(fileId);
1336
+ }
1337
+ }
971
1338
  /**
972
1339
  * Per-frame LOD streaming loop. Ticks cooldowns and pumps the decode queue every frame, and runs the
973
1340
  * cheap per-node frustum test every frame so the off-screen LOD bias tracks camera rotation. The LOD
@@ -984,6 +1351,10 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
984
1351
  node.lodCooldown--;
985
1352
  }
986
1353
  }
1354
+ // Tick eviction cooldowns: unreferenced files are freed once their cooldown elapses (budgeted streaming).
1355
+ if (this._evictionEnabled) {
1356
+ this._residency?.tick();
1357
+ }
987
1358
  // In-flight/queued decodes still progress every frame.
988
1359
  this._pumpDecodeQueue();
989
1360
  // Per-node frustum test runs every frame (cheap) so the off-screen LOD bias tracks camera rotation,
@@ -1102,7 +1473,7 @@ export class GaussianSplattingStream extends GaussianSplattingMesh {
1102
1473
  if (!entry) {
1103
1474
  continue;
1104
1475
  }
1105
- const base = this._fileBaseSplat.get(entry.file);
1476
+ const base = this._residency?.offset(entry.file);
1106
1477
  if (base === undefined) {
1107
1478
  continue;
1108
1479
  }