@drawcall/lod 0.2.0 → 0.3.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 (364) hide show
  1. package/README.md +8 -6
  2. package/dist/bake.d.ts +22 -0
  3. package/dist/bake.js +88 -0
  4. package/dist/bake.js.map +1 -0
  5. package/dist/baked/codec.d.ts +3 -6
  6. package/dist/baked/codec.js +48 -154
  7. package/dist/baked/codec.js.map +1 -1
  8. package/dist/baked/coverage.d.ts +6 -3
  9. package/dist/baked/coverage.js +6 -3
  10. package/dist/baked/coverage.js.map +1 -1
  11. package/dist/baked/fields.d.ts +9 -0
  12. package/dist/baked/fields.js +22 -0
  13. package/dist/baked/fields.js.map +1 -0
  14. package/dist/baked/index.d.ts +12 -6
  15. package/dist/baked/index.js +22 -20
  16. package/dist/baked/index.js.map +1 -1
  17. package/dist/baked/interior/faces.d.ts +4 -3
  18. package/dist/baked/interior/faces.js +13 -10
  19. package/dist/baked/interior/faces.js.map +1 -1
  20. package/dist/baked/interior/material.d.ts +7 -10
  21. package/dist/baked/interior/material.js +29 -36
  22. package/dist/baked/interior/material.js.map +1 -1
  23. package/dist/baked/interior/rooms.js +1 -1
  24. package/dist/baked/interior/rooms.js.map +1 -1
  25. package/dist/baked/low.js +5 -4
  26. package/dist/baked/low.js.map +1 -1
  27. package/dist/baked/raster.js +6 -6
  28. package/dist/baked/raster.js.map +1 -1
  29. package/dist/baked/textures.d.ts +17 -0
  30. package/dist/baked/textures.js +95 -0
  31. package/dist/baked/textures.js.map +1 -0
  32. package/dist/baked/transfer.js +19 -18
  33. package/dist/baked/transfer.js.map +1 -1
  34. package/dist/baked/unwrap.js +4 -3
  35. package/dist/baked/unwrap.js.map +1 -1
  36. package/dist/baked/xatlas.js.map +1 -1
  37. package/dist/codec/container.d.ts +8 -0
  38. package/dist/codec/container.js +63 -0
  39. package/dist/codec/container.js.map +1 -0
  40. package/dist/codec/mesh.d.ts +8 -0
  41. package/dist/{mesh/codec.js → codec/mesh.js} +80 -52
  42. package/dist/codec/mesh.js.map +1 -0
  43. package/dist/dispose.d.ts +7 -4
  44. package/dist/dispose.js +20 -22
  45. package/dist/dispose.js.map +1 -1
  46. package/dist/gpu.d.ts +39 -0
  47. package/dist/gpu.js +41 -0
  48. package/dist/gpu.js.map +1 -0
  49. package/dist/impostor/bake/atlas.d.ts +4 -4
  50. package/dist/impostor/bake/atlas.js +12 -12
  51. package/dist/impostor/bake/atlas.js.map +1 -1
  52. package/dist/impostor/bake/fit.d.ts +8 -0
  53. package/dist/impostor/bake/fit.js +28 -0
  54. package/dist/impostor/bake/fit.js.map +1 -0
  55. package/dist/impostor/bake/index.d.ts +22 -14
  56. package/dist/impostor/bake/index.js +41 -134
  57. package/dist/impostor/bake/index.js.map +1 -1
  58. package/dist/impostor/bake/materials.d.ts +3 -3
  59. package/dist/impostor/bake/materials.js +51 -13
  60. package/dist/impostor/bake/materials.js.map +1 -1
  61. package/dist/impostor/bake/sheet.d.ts +8 -0
  62. package/dist/impostor/bake/sheet.js +90 -0
  63. package/dist/impostor/bake/sheet.js.map +1 -0
  64. package/dist/impostor/bake/source.d.ts +2 -2
  65. package/dist/impostor/bake/source.js +8 -19
  66. package/dist/impostor/bake/source.js.map +1 -1
  67. package/dist/impostor/codec.d.ts +4 -5
  68. package/dist/impostor/codec.js +36 -109
  69. package/dist/impostor/codec.js.map +1 -1
  70. package/dist/impostor/impostor.d.ts +31 -7
  71. package/dist/impostor/impostor.js +21 -6
  72. package/dist/impostor/impostor.js.map +1 -1
  73. package/dist/impostor/ktx2/encode.d.ts +26 -0
  74. package/dist/impostor/ktx2/encode.js +117 -0
  75. package/dist/impostor/ktx2/encode.js.map +1 -0
  76. package/dist/impostor/ktx2/transcode.d.ts +3 -0
  77. package/dist/impostor/ktx2/transcode.js +99 -0
  78. package/dist/impostor/ktx2/transcode.js.map +1 -0
  79. package/dist/impostor/ktx2/uastc.d.ts +7 -0
  80. package/dist/impostor/ktx2/uastc.js +64 -0
  81. package/dist/impostor/ktx2/uastc.js.map +1 -0
  82. package/dist/impostor/ktx2/wasm/encoder.d.ts +4 -0
  83. package/dist/impostor/ktx2/wasm/encoder.js +10 -0
  84. package/dist/impostor/ktx2/wasm/encoder.js.map +1 -0
  85. package/dist/impostor/ktx2/wasm/transcoder.d.ts +3 -0
  86. package/dist/impostor/ktx2/wasm/transcoder.js +9 -0
  87. package/dist/impostor/ktx2/wasm/transcoder.js.map +1 -0
  88. package/dist/impostor/ktx2/worker.d.ts +1 -0
  89. package/dist/impostor/ktx2/worker.js +12 -0
  90. package/dist/impostor/ktx2/worker.js.map +1 -0
  91. package/dist/impostor/mesh/card.d.ts +3 -3
  92. package/dist/impostor/mesh/card.js +7 -7
  93. package/dist/impostor/mesh/card.js.map +1 -1
  94. package/dist/impostor/mesh/index.d.ts +5 -4
  95. package/dist/impostor/mesh/index.js +8 -9
  96. package/dist/impostor/mesh/index.js.map +1 -1
  97. package/dist/impostor/mesh/material.d.ts +18 -9
  98. package/dist/impostor/mesh/material.js +40 -27
  99. package/dist/impostor/mesh/material.js.map +1 -1
  100. package/dist/impostor/mesh/surface.d.ts +1 -1
  101. package/dist/impostor/mesh/surface.js +13 -9
  102. package/dist/impostor/mesh/surface.js.map +1 -1
  103. package/dist/impostor/mesh/textures.d.ts +3 -3
  104. package/dist/impostor/mesh/textures.js +24 -34
  105. package/dist/impostor/mesh/textures.js.map +1 -1
  106. package/dist/index.d.ts +15 -10
  107. package/dist/index.js +13 -8
  108. package/dist/index.js.map +1 -1
  109. package/dist/instanced/camera.d.ts +46 -0
  110. package/dist/instanced/camera.js +70 -0
  111. package/dist/instanced/camera.js.map +1 -0
  112. package/dist/instanced/chunk.d.ts +97 -0
  113. package/dist/instanced/chunk.js +156 -0
  114. package/dist/instanced/chunk.js.map +1 -0
  115. package/dist/instanced/chunks.d.ts +60 -0
  116. package/dist/instanced/chunks.js +208 -0
  117. package/dist/instanced/chunks.js.map +1 -0
  118. package/dist/instanced/cull.d.ts +28 -0
  119. package/dist/instanced/cull.js +22 -0
  120. package/dist/instanced/cull.js.map +1 -0
  121. package/dist/instanced/index.d.ts +96 -66
  122. package/dist/instanced/index.js +229 -288
  123. package/dist/instanced/index.js.map +1 -1
  124. package/dist/instanced/key.d.ts +19 -0
  125. package/dist/instanced/key.js +45 -0
  126. package/dist/instanced/key.js.map +1 -0
  127. package/dist/instanced/levels.d.ts +60 -0
  128. package/dist/instanced/levels.js +130 -0
  129. package/dist/instanced/levels.js.map +1 -0
  130. package/dist/instanced/lists.d.ts +70 -0
  131. package/dist/instanced/lists.js +133 -0
  132. package/dist/instanced/lists.js.map +1 -0
  133. package/dist/instanced/measure.d.ts +28 -0
  134. package/dist/instanced/measure.js +203 -0
  135. package/dist/instanced/measure.js.map +1 -0
  136. package/dist/instanced/meshes.d.ts +42 -51
  137. package/dist/instanced/meshes.js +158 -180
  138. package/dist/instanced/meshes.js.map +1 -1
  139. package/dist/instanced/mirror.d.ts +20 -0
  140. package/dist/instanced/mirror.js +37 -0
  141. package/dist/instanced/mirror.js.map +1 -0
  142. package/dist/instanced/pages/buffers.d.ts +37 -0
  143. package/dist/instanced/pages/buffers.js +15 -0
  144. package/dist/instanced/pages/buffers.js.map +1 -0
  145. package/dist/instanced/pages/chunk.d.ts +51 -0
  146. package/dist/instanced/pages/chunk.js +81 -0
  147. package/dist/instanced/pages/chunk.js.map +1 -0
  148. package/dist/instanced/pages/index.d.ts +74 -0
  149. package/dist/instanced/pages/index.js +269 -0
  150. package/dist/instanced/pages/index.js.map +1 -0
  151. package/dist/instanced/pages/passes.d.ts +21 -0
  152. package/dist/instanced/pages/passes.js +52 -0
  153. package/dist/instanced/pages/passes.js.map +1 -0
  154. package/dist/instanced/pages/slots.d.ts +11 -0
  155. package/dist/instanced/pages/slots.js +90 -0
  156. package/dist/instanced/pages/slots.js.map +1 -0
  157. package/dist/instanced/pages/workgroup.d.ts +14 -0
  158. package/dist/instanced/pages/workgroup.js +38 -0
  159. package/dist/instanced/pages/workgroup.js.map +1 -0
  160. package/dist/instanced/raycast.d.ts +13 -11
  161. package/dist/instanced/raycast.js +33 -40
  162. package/dist/instanced/raycast.js.map +1 -1
  163. package/dist/instanced/rows.d.ts +108 -0
  164. package/dist/instanced/rows.js +209 -0
  165. package/dist/instanced/rows.js.map +1 -0
  166. package/dist/instanced/shape.d.ts +25 -0
  167. package/dist/instanced/shape.js +51 -0
  168. package/dist/instanced/shape.js.map +1 -0
  169. package/dist/instanced/sort.d.ts +63 -0
  170. package/dist/instanced/sort.js +162 -0
  171. package/dist/instanced/sort.js.map +1 -0
  172. package/dist/instanced/update.d.ts +30 -0
  173. package/dist/instanced/update.js +207 -0
  174. package/dist/instanced/update.js.map +1 -0
  175. package/dist/levels/bounds.d.ts +21 -0
  176. package/dist/levels/bounds.js +42 -0
  177. package/dist/levels/bounds.js.map +1 -0
  178. package/dist/levels/cameras.d.ts +48 -0
  179. package/dist/levels/cameras.js +122 -0
  180. package/dist/levels/cameras.js.map +1 -0
  181. package/dist/levels/compile/contexts.d.ts +30 -0
  182. package/dist/levels/compile/contexts.js +18 -0
  183. package/dist/levels/compile/contexts.js.map +1 -0
  184. package/dist/levels/compile/index.d.ts +39 -0
  185. package/dist/levels/compile/index.js +185 -0
  186. package/dist/levels/compile/index.js.map +1 -0
  187. package/dist/levels/compile/pipelines.d.ts +26 -0
  188. package/dist/levels/compile/pipelines.js +177 -0
  189. package/dist/levels/compile/pipelines.js.map +1 -0
  190. package/dist/levels/compiles.d.ts +29 -0
  191. package/dist/levels/compiles.js +50 -0
  192. package/dist/levels/compiles.js.map +1 -0
  193. package/dist/levels/draw.d.ts +102 -0
  194. package/dist/levels/draw.js +401 -0
  195. package/dist/levels/draw.js.map +1 -0
  196. package/dist/levels/fade.d.ts +69 -0
  197. package/dist/levels/fade.js +150 -0
  198. package/dist/levels/fade.js.map +1 -0
  199. package/dist/levels/index.d.ts +99 -0
  200. package/dist/levels/index.js +155 -0
  201. package/dist/levels/index.js.map +1 -0
  202. package/dist/levels/item.d.ts +38 -0
  203. package/dist/levels/item.js +97 -0
  204. package/dist/levels/item.js.map +1 -0
  205. package/dist/levels/load.d.ts +31 -0
  206. package/dist/levels/load.js +142 -0
  207. package/dist/levels/load.js.map +1 -0
  208. package/dist/levels/manager.d.ts +159 -0
  209. package/dist/levels/manager.js +339 -0
  210. package/dist/levels/manager.js.map +1 -0
  211. package/dist/{material.js → levels/material.js} +4 -3
  212. package/dist/levels/material.js.map +1 -0
  213. package/dist/levels/renderer.d.ts +29 -0
  214. package/dist/levels/renderer.js +41 -0
  215. package/dist/levels/renderer.js.map +1 -0
  216. package/dist/levels/requests.d.ts +49 -0
  217. package/dist/levels/requests.js +95 -0
  218. package/dist/levels/requests.js.map +1 -0
  219. package/dist/levels/screen.d.ts +14 -0
  220. package/dist/levels/screen.js +21 -0
  221. package/dist/levels/screen.js.map +1 -0
  222. package/dist/levels/wait.d.ts +27 -0
  223. package/dist/levels/wait.js +61 -0
  224. package/dist/levels/wait.js.map +1 -0
  225. package/dist/loader/build.d.ts +10 -0
  226. package/dist/loader/build.js +28 -0
  227. package/dist/loader/build.js.map +1 -0
  228. package/dist/loader/cache.d.ts +6 -9
  229. package/dist/loader/cache.js +17 -24
  230. package/dist/loader/cache.js.map +1 -1
  231. package/dist/loader/context.d.ts +46 -0
  232. package/dist/loader/context.js +91 -0
  233. package/dist/loader/context.js.map +1 -0
  234. package/dist/loader/{provenance.d.ts → dependencies.d.ts} +5 -3
  235. package/dist/loader/{provenance.js → dependencies.js} +12 -4
  236. package/dist/loader/dependencies.js.map +1 -0
  237. package/dist/loader/index.d.ts +62 -0
  238. package/dist/loader/index.js +284 -0
  239. package/dist/loader/index.js.map +1 -0
  240. package/dist/loader/level.d.ts +41 -0
  241. package/dist/loader/level.js +140 -0
  242. package/dist/loader/level.js.map +1 -0
  243. package/dist/loader/request.d.ts +87 -0
  244. package/dist/loader/request.js +59 -0
  245. package/dist/loader/request.js.map +1 -0
  246. package/dist/loader/url.d.ts +5 -2
  247. package/dist/loader/url.js +10 -6
  248. package/dist/loader/url.js.map +1 -1
  249. package/dist/lod.d.ts +81 -40
  250. package/dist/lod.js +196 -149
  251. package/dist/lod.js.map +1 -1
  252. package/dist/meshopt.d.ts +6 -0
  253. package/dist/meshopt.js +19 -0
  254. package/dist/meshopt.js.map +1 -0
  255. package/dist/simplify/cards.d.ts +14 -0
  256. package/dist/simplify/cards.js +128 -0
  257. package/dist/simplify/cards.js.map +1 -0
  258. package/dist/{simplify.d.ts → simplify/index.d.ts} +7 -4
  259. package/dist/{simplify.js → simplify/index.js} +41 -23
  260. package/dist/simplify/index.js.map +1 -0
  261. package/dist/{bounds.d.ts → source/detail.d.ts} +4 -23
  262. package/dist/{bounds.js → source/detail.js} +19 -51
  263. package/dist/source/detail.js.map +1 -0
  264. package/dist/source/deviation.js +6 -6
  265. package/dist/source/deviation.js.map +1 -1
  266. package/dist/source/{shading.d.ts → evaluator.d.ts} +10 -10
  267. package/dist/source/{shading.js → evaluator.js} +5 -5
  268. package/dist/source/evaluator.js.map +1 -0
  269. package/dist/source/{trace.d.ts → raycaster.d.ts} +6 -6
  270. package/dist/source/{trace.js → raycaster.js} +8 -8
  271. package/dist/source/raycaster.js.map +1 -0
  272. package/dist/source/{texels.d.ts → sampler.d.ts} +1 -1
  273. package/dist/source/{texels.js → sampler.js} +3 -3
  274. package/dist/source/sampler.js.map +1 -0
  275. package/dist/source/source.d.ts +1 -1
  276. package/dist/source/source.js +1 -1
  277. package/dist/source/source.js.map +1 -1
  278. package/dist/source/surface.js +1 -1
  279. package/dist/source/surface.js.map +1 -1
  280. package/dist/task.d.ts +6 -0
  281. package/dist/{pause.js → task.js} +5 -5
  282. package/dist/task.js.map +1 -0
  283. package/dist/three.d.ts +13 -24
  284. package/dist/three.js +26 -53
  285. package/dist/three.js.map +1 -1
  286. package/dist/tsl.d.ts +8 -12
  287. package/dist/tsl.js +11 -39
  288. package/dist/tsl.js.map +1 -1
  289. package/dist/typegpu.d.ts +37 -0
  290. package/dist/typegpu.js +37 -0
  291. package/dist/typegpu.js.map +1 -0
  292. package/dist/wind/bounds.js +2 -2
  293. package/dist/wind/bounds.js.map +1 -1
  294. package/dist/wind/hooks.d.ts +7 -4
  295. package/dist/wind/hooks.js +5 -2
  296. package/dist/wind/hooks.js.map +1 -1
  297. package/dist/wind/index.d.ts +2 -2
  298. package/dist/wind/index.js +15 -15
  299. package/dist/wind/index.js.map +1 -1
  300. package/dist/wind/sway.d.ts +2 -0
  301. package/dist/wind/sway.js +4 -1
  302. package/dist/wind/sway.js.map +1 -1
  303. package/package.json +18 -8
  304. package/skills/lod/SKILL.md +108 -31
  305. package/dist/bounds.js.map +0 -1
  306. package/dist/compile.d.ts +0 -4
  307. package/dist/compile.js +0 -100
  308. package/dist/compile.js.map +0 -1
  309. package/dist/deferred.d.ts +0 -12
  310. package/dist/deferred.js +0 -27
  311. package/dist/deferred.js.map +0 -1
  312. package/dist/fade.d.ts +0 -36
  313. package/dist/fade.js +0 -112
  314. package/dist/fade.js.map +0 -1
  315. package/dist/instanced/args.d.ts +0 -20
  316. package/dist/instanced/args.js +0 -77
  317. package/dist/instanced/args.js.map +0 -1
  318. package/dist/instanced/blocks.d.ts +0 -66
  319. package/dist/instanced/blocks.js +0 -294
  320. package/dist/instanced/blocks.js.map +0 -1
  321. package/dist/instanced/dirty.d.ts +0 -15
  322. package/dist/instanced/dirty.js +0 -96
  323. package/dist/instanced/dirty.js.map +0 -1
  324. package/dist/instanced/parts.d.ts +0 -14
  325. package/dist/instanced/parts.js +0 -79
  326. package/dist/instanced/parts.js.map +0 -1
  327. package/dist/instanced/select.d.ts +0 -92
  328. package/dist/instanced/select.js +0 -315
  329. package/dist/instanced/select.js.map +0 -1
  330. package/dist/instanced/store.d.ts +0 -31
  331. package/dist/instanced/store.js +0 -96
  332. package/dist/instanced/store.js.map +0 -1
  333. package/dist/levels.d.ts +0 -95
  334. package/dist/levels.js +0 -171
  335. package/dist/levels.js.map +0 -1
  336. package/dist/loader/bundle.d.ts +0 -25
  337. package/dist/loader/bundle.js +0 -241
  338. package/dist/loader/bundle.js.map +0 -1
  339. package/dist/loader/provenance.js.map +0 -1
  340. package/dist/loader.d.ts +0 -55
  341. package/dist/loader.js +0 -239
  342. package/dist/loader.js.map +0 -1
  343. package/dist/material.js.map +0 -1
  344. package/dist/mesh/codec.d.ts +0 -4
  345. package/dist/mesh/codec.js.map +0 -1
  346. package/dist/pause.d.ts +0 -6
  347. package/dist/pause.js.map +0 -1
  348. package/dist/queue.d.ts +0 -2
  349. package/dist/queue.js +0 -33
  350. package/dist/queue.js.map +0 -1
  351. package/dist/renderer.d.ts +0 -21
  352. package/dist/renderer.js +0 -83
  353. package/dist/renderer.js.map +0 -1
  354. package/dist/residency.d.ts +0 -44
  355. package/dist/residency.js +0 -205
  356. package/dist/residency.js.map +0 -1
  357. package/dist/simplify.js.map +0 -1
  358. package/dist/source/shading.js.map +0 -1
  359. package/dist/source/texels.js.map +0 -1
  360. package/dist/source/trace.js.map +0 -1
  361. package/dist/view.d.ts +0 -45
  362. package/dist/view.js +0 -131
  363. package/dist/view.js.map +0 -1
  364. /package/dist/{material.d.ts → levels/material.d.ts} +0 -0
@@ -1,349 +1,290 @@
1
- import { Box3, Matrix4, Object3D, Sphere, Vector3 } from "three/webgpu";
1
+ import { Box3, Object3D, Sphere } from "three/webgpu";
2
2
  import { at } from "../at.js";
3
- import { levelBox, measureLevels, prepareBounds } from "../bounds.js";
4
3
  import { disposeCopies } from "../dispose.js";
5
- import { Level, cloneLevels, estimate, fadeProgress, levelAt, tintLevels, validate } from "../levels.js";
4
+ import { EncodedLevels, emptyLevels, levelsFrom } from "../levels/index.js";
5
+ import { fadeSourceOf } from "../levels/fade.js";
6
+ import { levelsToLoad } from "../levels/load.js";
7
+ import { LodLevelLoadManager } from "../levels/manager.js";
6
8
  import { Lod } from "../lod.js";
7
- import { Residency } from "../residency.js";
8
- import { inShadowPass, sameClass } from "../three.js";
9
- import { TYPICAL_HEIGHT, View, viewportHeight, watchContext, unwatchContext, inView } from "../view.js";
10
- import { compileLevel } from "../compile.js";
11
- import { WIND_BOUNDS } from "../wind/hooks.js";
12
- import { copyWind, refreshWind } from "../wind/index.js";
13
- import { Meshes } from "./meshes.js";
14
- import { parts } from "./parts.js";
15
- import { Blocks } from "./blocks.js";
16
- import { Probes } from "./raycast.js";
17
- import { ARGS_BYTES } from "./args.js";
18
- import { Selection } from "./select.js";
19
- import { Store } from "./store.js";
20
- const view = new View();
21
- const inverse = new Matrix4();
9
+ import { inShadowMap } from "../levels/renderer.js";
10
+ import { sameClass } from "../three.js";
11
+ import { WIND } from "../wind/hooks.js";
12
+ import { copyWind } from "../wind/index.js";
13
+ import { ChunkSet } from "./chunks.js";
14
+ import { cullIn, emptyCull } from "./cull.js";
15
+ import { MeshesByLevel } from "./levels.js";
16
+ import { InstanceLists, frustumIn, listedOf } from "./lists.js";
17
+ import { localCamera, shownBy, walk } from "./camera.js";
18
+ import { drawMatrices } from "./meshes.js";
19
+ import { raycastInstances } from "./raycast.js";
20
+ import { InstanceShape } from "./shape.js";
21
+ import { MAX_SURVIVOR_SCALE, instanceLevel } from "./measure.js";
22
+ const DEFAULT_INSTANCES = 16384;
22
23
  /**
23
- * Independently selected copies of one object, batched into indirect draws per view.
24
- * Thresholds, fades, global settings and shadows follow Lod. Each level's bounds tighten
25
- * to the region where its instances can draw. Instance matrices may scale arbitrarily;
26
- * the crowd's own transform and its parents should scale uniformly. Scale 0 draws nothing.
24
+ * Independently drawn copies of one object, sorted into indirect draws per camera on the GPU. Instances arrive and
25
+ * leave in chunks (see `addChunk`), from the CPU or written by the app's GPU passes; `cullDistance` thins them out with
26
+ * distance. Thresholds, fades, loading and global settings follow Lod: each instance draws what levelsToDraw gives for
27
+ * its height. Each camera draws only the instances in its frustum, and each shadow map is a camera of its own: it draws
28
+ * the casters in its frustum, each at the level its texels resolve, as an orthographic camera would (an instance
29
+ * smaller than a texel casts nothing). Instance matrices may scale arbitrarily; the InstancedLod's own transform and its
30
+ * parents should scale uniformly. Scale 0 draws nothing.
27
31
  *
28
- * Level templates are cloned, then their meshes are instanced. Skinned meshes,
29
- * InstancedMeshes (except ImpostorMesh) and nested Lods are unsupported. For external
30
- * detail, leave level 0 empty and draw the instances that `levelOf` reports there.
32
+ * Level templates are cloned, then their meshes are instanced; it sets each level mesh's `visible` per camera, so hide
33
+ * the InstancedLod itself, never its level meshes. Skinned meshes, InstancedMeshes (except ImpostorMesh)
34
+ * and nested Lods are unsupported. For external detail, leave level 0 empty and draw the instances that
35
+ * `chunk.getLevelAt` reports there.
31
36
  *
32
- * Requires WebGPU `indirect-first-instance`. Sequential cameras are supported; nested
33
- * renders replace the outer selection, so exclude this crowd with camera layers.
37
+ * Every level mesh's `geometry.boundingBox` and `boundingSphere` bound all its instances, not one, so
38
+ * `new Box3().setFromObject(lod)` bounds them once a level has loaded; with `precise` it walks a level's vertices once,
39
+ * at no instance, as it cannot see them. Measure one instance by a level's `object`, scaled by its matrix.
40
+ *
41
+ * Instances live in its LodGpu's pool, and every InstancedLod of a LodGpu sorts for a camera in one compute call.
42
+ * Requires WebGPU `indirect-first-instance`. Sequential cameras are supported; a nested render with a camera that the
43
+ * outer render also draws with replaces that camera's instance lists, so exclude this InstancedLod from it with camera
44
+ * layers.
34
45
  */
35
46
  export class InstancedLod extends Object3D {
47
+ gpu;
36
48
  isLOD = true;
37
49
  isInstancedLod = true;
38
50
  /** Let the renderer call `update` with its camera every frame. */
39
51
  autoUpdate = true;
40
- /** Draw only this level for every instance, without fades. */
52
+ /** Draw only this level, without fades, once the coarsest level that draws something has loaded. */
41
53
  forceLevel = null;
42
- levels;
43
- count;
44
- entries = [];
45
- probes;
46
- selection = null;
47
- meshes = null;
48
- residency = new Residency(this.entries, (level, object, context) => this.install(level, object, context), () => {
49
- this.selection?.route(this.residency.fallback, true);
50
- this.dispatchEvent({ type: "levelschange" });
51
- }, (error) => this.dispatchEvent({ type: "levelserror", error }));
54
+ /** The levels, each `object` the template its level meshes are made from, once there is one (never in the scene). */
55
+ levels = [];
56
+ /** Thins the instances out with distance from a camera, in every camera; null draws them at any distance. */
57
+ cullDistance = null;
58
+ /** The levels as they were given, which a copy is made from. */
59
+ inputs = [];
60
+ /** What it is made of for its levels and sizes, which a copy makes anew. */
61
+ built;
52
62
  disposed = false;
53
- store;
54
- /** Bounding sphere of the source in an instance's space. */
55
- sphere = new Sphere();
56
- /** Every placed level, kept separate from the source sphere used for LOD selection. */
57
- envelope = new Box3();
58
- rest = new Sphere();
59
- /** Around every instance; each level may use tighter bounds. */
60
- bounds = new Sphere();
61
- /** Per level, what its meshes are culled by: `bounds`, or less where its instances must be near the camera. */
62
- reaches = [];
63
- /** Logical demand at the initial default viewport, separate from first-draw visibility. */
64
- demand = [];
65
- /** Source-radius padding for the shadow caster cutoff; culling pads every level separately. */
66
- padding = 0;
67
- blocks;
68
- last = { selected: false, eye: new Vector3(), scale: 0, ortho: false, fades: false, forced: null };
69
- get pending() {
70
- return this.residency.pending;
71
- }
72
- get error() {
73
- return this.residency.error;
74
- }
75
- /** `levels[0]` is the source. Empty only as the target of `copy`. */
76
- constructor(levels = [], count = 0) {
63
+ /** Around every instance, survivors grown: what every level mesh is culled and boxed by. */
64
+ bounds = { sphere: new Sphere(), box: new Box3() };
65
+ /** What `cullIn` writes cullDistance into, in this InstancedLod's space. */
66
+ cull = emptyCull();
67
+ /**
68
+ * `levels[0]` is the source. Empty only as the target of `copy`. It holds no instances until chunks are added. `gpu`
69
+ * is what it shares with every Lod drawing with its renderer.
70
+ */
71
+ constructor(levels, gpu, options = {}) {
77
72
  super();
78
- if (!Number.isInteger(count) || count < 0)
79
- throw new Error(`InstancedLod: count must be an integer >= 0, got ${count}`);
80
- this.levels = this.entries;
81
- this.count = count;
82
- this.store = new Store(count);
83
- this.probes = new Probes(this.store.matrices, this.rest);
84
- const draws = levels.length > 0 ? this.build(levels) : 0;
85
- this.blocks = new Blocks(this.store.matrices, this.store.spheres, this.sphere, this.bounds, draws, this.rest);
86
- }
87
- build(inputs) {
88
- const { own, placed } = measureLevels(inputs, "InstancedLod");
89
- this.sphere.copy(placed);
90
- inputs.forEach((input, i) => {
91
- this.entries.push(new Level(input, i, this.entries));
92
- this.reaches.push(new Sphere());
93
- this.demand.push(new Sphere());
94
- this.envelope.union(levelBox(input));
95
- });
96
- estimate(this.entries, 2 * own.radius);
97
- validate(this.entries);
98
- this.residency.reset();
99
- this.boundLevels();
100
- const templates = this.entries.map(({ object }, i) => (object ? parts(object, i) : []));
101
- this.probes.add(templates[0] ?? []);
102
- const draws = templates.flatMap((parts) => parts.map((part) => part.draw));
103
- const selection = new Selection(this.store.matrices, this.sphere, templates.map((parts, i) => parts.length > 0 || at(inputs, i).box?.isEmpty() === false), draws, this.residency.fallback);
104
- this.selection = selection;
105
- this.store.attach(selection);
106
- const meshes = new Meshes(selection, this.reaches, this.drawRadius, {
107
- live: () => !this.disposed,
108
- drawn: (camera, mesh) => this.residency.drawn(camera, mesh),
109
- });
110
- this.meshes = meshes;
111
- let offset = 0;
112
- templates.forEach((parts, i) => {
113
- const made = meshes.make(at(this.entries, i), parts, offset);
114
- offset += made.length * ARGS_BYTES;
115
- meshes.byLevel[i] = made;
116
- for (const mesh of made)
117
- this.add(mesh);
118
- });
119
- if (draws.length === 0)
120
- watchContext(this);
121
- return draws.length;
73
+ this.gpu = gpu;
74
+ // Each level's draws start at its list (see update.ts).
75
+ if (!gpu.renderer.hasFeature("indirect-first-instance"))
76
+ throw new Error("InstancedLod: needs the GPU feature indirect-first-instance, which this device lacks");
77
+ const maxInstanceCount = options.maxInstanceCount ?? DEFAULT_INSTANCES;
78
+ if (!Number.isInteger(maxInstanceCount) || maxInstanceCount < 1)
79
+ throw new RangeError(`InstancedLod: maxInstanceCount must be a positive integer, got ${maxInstanceCount}`);
80
+ this.built = this.init(levels, maxInstanceCount);
81
+ this.built.loads.item.given = options.manager;
122
82
  }
123
- /** Compile only the arriving level; bounds and instance buffers stay fixed. */
124
- async install(level, template, context) {
125
- const { selection, meshes } = this;
126
- if (!selection || !meshes)
127
- throw new Error("InstancedLod: missing selection");
128
- const staged = new Object3D();
129
- const object = template.clone();
130
- if (level.box)
131
- prepareBounds(object, level.box);
132
- const found = parts(object, level.index);
133
- const offset = selection.append(found.map((part) => part.draw));
134
- // A deferred level has no upfront mesh count, so its indirect draw table can relocate.
135
- meshes.relocate();
136
- const made = meshes.make(level, found, offset);
137
- for (const mesh of made)
138
- staged.add(mesh);
139
- const abandon = () => {
140
- for (const mesh of made)
141
- meshes.free(mesh);
142
- disposeCopies(object);
143
- };
144
- try {
145
- refreshWind(this, staged);
146
- await compileLevel(staged, context);
147
- }
148
- catch (error) {
149
- abandon();
150
- throw error;
151
- }
152
- if (this.disposed) {
153
- abandon();
154
- return;
155
- }
156
- level.object = object;
157
- if (made.length > 0)
158
- unwatchContext(this);
159
- if (level.index === 0)
160
- this.probes.add(found);
161
- meshes.byLevel[level.index] = made;
162
- for (const mesh of made)
163
- this.add(mesh);
83
+ get pending() {
84
+ return this.built.loads.pending;
164
85
  }
165
- /** Metadata includes unloaded levels, so later arrivals cannot enlarge this envelope. */
166
- boundLevels() {
167
- this.rest.copy(this.sphere);
168
- if (this.envelope.isEmpty())
169
- return;
170
- const { center } = this.rest;
171
- const { min, max } = this.envelope;
172
- this.rest.radius = Math.max(this.rest.radius, Math.hypot(Math.max(Math.abs(min.x - center.x), Math.abs(max.x - center.x)), Math.max(Math.abs(min.y - center.y), Math.abs(max.y - center.y)), Math.max(Math.abs(min.z - center.z), Math.abs(max.z - center.z))));
86
+ get error() {
87
+ return this.built.loads.error;
173
88
  }
174
- setMatrixAt(index, matrix) {
175
- this.store.setMatrix(index, matrix);
89
+ /** Where the item of levels a camera misses goes (see `LodOptions.manager`). */
90
+ get manager() {
91
+ return this.built.loads.item.manager;
176
92
  }
177
- getMatrixAt(index, matrix) {
178
- return this.store.getMatrix(index, matrix);
93
+ set manager(manager) {
94
+ this.built.loads.item.given = manager;
179
95
  }
180
- /** Tint instance `index` by `color`, as InstancedMesh.setColorAt does, in every level. */
181
- setColorAt(index, color) {
182
- const first = this.store.colors === null;
183
- this.store.setColor(index, color);
184
- if (first)
185
- this.meshes?.recolor();
96
+ /** Its chunks, in the order they were added. */
97
+ get chunks() {
98
+ return this.built.chunks.keys();
186
99
  }
187
- /** Instance `index`'s colour: white unless set. */
188
- getColorAt(index, color) {
189
- return this.store.getColor(index, color);
100
+ /** What this InstancedLod is made of for `inputs` and `maxInstanceCount`: its levels, their meshes and their loading. */
101
+ init(inputs, maxInstanceCount) {
102
+ const shape = new InstanceShape(inputs);
103
+ const { pool, renderer } = this.gpu;
104
+ const lists = new InstanceLists(this.gpu.sorter, listedOf(inputs), maxInstanceCount);
105
+ const chunks = new ChunkSet(pool, lists.row, this.bounds, () => shape.originReach, (chunk, index, camera) => this.levelAt(chunk, index, camera));
106
+ const drawn = { matrices: pool.matrices, colors: pool.colors };
107
+ const drawing = { shape, lists, drawn };
108
+ this.inputs = inputs;
109
+ if (inputs.length > 0)
110
+ this.levels.push(...levelsFrom(inputs, 2 * shape.ownRadius, "InstancedLod"));
111
+ const encoded = new EncodedLevels(this.levels, emptyLevels(inputs), "InstancedLod");
112
+ const drawFor = (renderer, camera) => this.built.loads.guard(() => {
113
+ if (renderer !== this.gpu.renderer)
114
+ throw new Error("InstancedLod: another renderer than its LodGpu's drew it");
115
+ lists.drawFor(camera);
116
+ });
117
+ const meshes = new MeshesByLevel(this, this.levels, drawing, this.bounds, drawFor);
118
+ const loads = new LodLevelLoadManager(this, this.gpu, `InstancedLod ${this.uuid}`, inputs, encoded.get(null, true), (level, loaded) => meshes.prepare(level, loaded), (level) => meshes.of(level), async () => {
119
+ const instances = await pool.status(renderer, chunks.slots());
120
+ if (instances !== null && instances > maxInstanceCount)
121
+ throw new Error(`InstancedLod: its chunks hold ${instances} instances, more than its lists hold: raise its maxInstanceCount (${maxInstanceCount})`);
122
+ return instances !== null;
123
+ });
124
+ return { maxInstanceCount, chunks, ...drawing, meshes, loads, encoded };
190
125
  }
191
126
  /**
192
- * The finest level instance `index` drew in the last update, or null before the first
193
- * (or at scale 0). Inside a fade the next coarser level draws too.
127
+ * Add `parameters`' instances as one chunk, from the CPU or the GPU (see `InstancedLodChunkParameters`). The next
128
+ * render that updates this InstancedLod copies them into its pages. Its item (see `LodOptions.manager`) stays held
129
+ * until the GPU shows every chunk copied; a chunk the pool lacks pages for draws the instances it got until it is
130
+ * removed, and is this InstancedLod's error (see `error`).
194
131
  */
195
- levelOf(index) {
196
- const { last, store } = this;
197
- store.check(index);
198
- if (!last.selected)
199
- return null;
200
- store.measure(this.blocks);
201
- const offset = index * 4;
202
- const r = at(store.spheres, offset + 3);
203
- if (r === 0)
204
- return null;
205
- let height = 2 * r * last.scale;
206
- if (!last.ortho) {
207
- const dx = at(store.spheres, offset) - last.eye.x;
208
- const dy = at(store.spheres, offset + 1) - last.eye.y;
209
- const dz = at(store.spheres, offset + 2) - last.eye.z;
210
- height /= Math.max(Math.sqrt(dx * dx + dy * dy + dz * dz), 1e-6);
211
- }
212
- const level = levelAt(this.entries, height, last.forced);
213
- const selected = last.fades && fadeProgress(this.entries, level, height) > 0 ? level.index - 1 : level.index;
214
- const ready = at(this.residency.fallback, selected);
215
- return ready < 0 ? null : ready;
132
+ addChunk(parameters) {
133
+ if (this.disposed)
134
+ throw new Error("InstancedLod: it was disposed");
135
+ if (this.levels.length === 0)
136
+ throw new Error("InstancedLod: give it levels before chunks");
137
+ const chunk = this.built.chunks.add(parameters);
138
+ this.measureBounds();
139
+ this.built.loads.chunksChanged();
140
+ return chunk;
216
141
  }
217
- /** Source radius padded for the level shaders' shadow caster cutoff. */
218
- get drawRadius() {
219
- return this.sphere.radius * (1 + this.padding);
142
+ /** Remove `chunk`'s instances; its pages return to the pool in the next render. */
143
+ removeChunk(chunk) {
144
+ this.built.chunks.remove(chunk);
145
+ this.measureBounds();
146
+ this.built.loads.chunksChanged();
220
147
  }
221
- [WIND_BOUNDS](padding, slope, baseY) {
222
- this.padding = padding / this.sphere.radius;
223
- this.meshes?.setRadius(this.drawRadius);
224
- // The wind shape comes from the source, but taller/offset levels can sway farther.
225
- const reach = slope * Math.max(Math.abs(this.envelope.min.y - baseY), Math.abs(this.envelope.max.y - baseY));
226
- this.blocks.pad(this.rest.radius > 0 ? reach / this.rest.radius : 0);
227
- this.blocks.refit();
148
+ /** Bound every chunk's instances, survivors grown. */
149
+ measureBounds() {
150
+ this.built.chunks.bound(this.cullDistance?.scaleSurvivors ? MAX_SURVIVOR_SCALE : 1);
228
151
  }
229
- /** Sort instances for `camera`; demand begins after its first drawing context exists. */
152
+ [WIND](padding, slope, baseY) {
153
+ if (this.built.loads.started)
154
+ throw new Error("InstancedLod: apply wind before the first render");
155
+ this.built.shape.wind(padding, slope, baseY);
156
+ for (const mesh of this.built.meshes.all()) {
157
+ const source = fadeSourceOf(mesh);
158
+ if (source?.kind === "instances")
159
+ source.radius = this.built.shape.fadeRadius;
160
+ }
161
+ this.measureBounds();
162
+ }
163
+ /** Sort the instances into level lists for `camera`, a camera's or a shadow map's, and load the levels they need. What fails is reported (see `error`). */
230
164
  update(camera) {
231
- if (this.disposed || inShadowPass(this))
165
+ if (this.disposed || this.levels.length === 0)
232
166
  return;
233
- const { entries, selection, meshes } = this;
234
- if (!selection || !meshes)
167
+ // A shadow map draws nothing of an InstancedLod whose levels cast nothing; one not loaded yet may.
168
+ const shadow = inShadowMap(this);
169
+ if (shadow && !this.levels.some(({ object }, i) => !object || this.built.meshes.of(i).some((mesh) => mesh.castShadow)))
235
170
  return;
236
- validate(entries);
237
- if (this.forceLevel !== null && !entries[this.forceLevel])
238
- throw new Error(`Lod: forceLevel ${this.forceLevel} is not a level`);
239
- this.store.measure(this.blocks);
240
- const height = viewportHeight(camera);
241
- view.begin(camera, height ?? TYPICAL_HEIGHT, this.forceLevel);
242
- tintLevels(entries, Lod.debug);
243
- const worldScale = this.matrixWorld.getMaxScaleOnAxis();
244
- const scale = view.scale * Lod.quality * (view.ortho ? worldScale : 1);
245
- const { last } = this;
246
- last.eye.copy(view.eye).applyMatrix4(inverse.copy(this.matrixWorld).invert());
247
- last.selected = true;
248
- last.scale = scale;
249
- last.ortho = view.ortho;
250
- last.fades = view.fades;
251
- last.forced = this.forceLevel;
252
- const stable = this.blocks.select(entries, last, this.reaches, height !== null, this.matrixWorld);
253
- const { residency } = this;
254
- const progress = residency.progress;
255
- if (selection.progress.value !== progress) {
256
- selection.progress.value = progress;
257
- selection.changed();
258
- }
259
- const needed = [];
260
- if (this.bounds.isEmpty()) {
261
- // Reusable crowds can be populated and drawn in the same frame.
262
- for (const level of entries)
263
- if (level.object)
264
- needed.push(level.index);
265
- }
266
- else if (residency.needsDemand(camera, meshes.byLevel)) {
267
- this.blocks.demand(entries, last, this.demand, this.matrixWorld);
268
- // Coarsest first, so its load starts first.
269
- for (let i = this.demand.length - 1; i >= 0; i--)
270
- if (inView(camera, this.matrixWorld, at(this.demand, i)))
271
- needed.push(i);
272
- this.reroute(progress);
273
- }
274
- residency.demand(camera, needed);
275
- residency.update(camera, meshes.byLevel);
276
- selection.aim(last.eye, scale, view.ortho, view.fades, this.forceLevel, entries, stable);
277
- // Selected only when something may draw: an InstancedLod with every level hidden
278
- // keeps its last selection.
279
- meshes.schedule(meshes.show(camera, this.matrixWorld));
171
+ this.built.loads.guard(() => this.sort(camera, shadow));
280
172
  }
281
- /** Move each level's reach to the level that draws in its place: its fallback, and during a handover also its previous one. */
282
- reroute(progress) {
283
- const { fallback, previous } = this.residency;
284
- for (let i = this.reaches.length - 1; i >= 0; i--) {
285
- const reach = at(this.reaches, i);
286
- const target = at(fallback, i);
287
- const before = progress < 1 ? at(previous, i) : -1;
288
- if (before >= 0 && before !== target)
289
- at(this.reaches, before).union(reach);
290
- if (target === i)
291
- continue;
292
- if (target >= 0)
293
- at(this.reaches, target).union(reach);
294
- reach.makeEmpty();
295
- }
173
+ /** Sort for `camera`, a shadow map's when `shadow`. */
174
+ sort(camera, shadow) {
175
+ const { loads, shape, lists, chunks, drawn, encoded, meshes } = this.built;
176
+ const { levels, eye, scale, ortho } = this.localCamera(camera, shadow);
177
+ if (chunks.refresh())
178
+ this.measureBounds();
179
+ // The pages change before any sorts with them.
180
+ const { pool } = this.gpu;
181
+ pool.prepare(this.gpu.renderer);
182
+ const survivors = this.cullDistance?.scaleSurvivors ?? false;
183
+ if (survivors !== (drawn.matrices !== pool.matrices))
184
+ this.scaleSurvivors(survivors);
185
+ const cull = cullIn(this.cullDistance, this.matrixWorld, this.cull);
186
+ const local = { levels, eye, scale, ortho, cull };
187
+ const found = lists.of(camera);
188
+ // Outside the camera's frustum, nothing draws: a shadow map, which loads nothing by heights, walks no chunk then.
189
+ const seen = frustumIn(found, camera, this.matrixWorld).intersectsSphere(this.bounds.sphere);
190
+ if (!shadow || seen)
191
+ walk(found, local, this.built);
192
+ // A shadow map draws from the levels its scene's cameras loaded.
193
+ loads.request(camera, shadow ? levelsToLoad([], encoded.heights(levels)) : found.wanted, shadow);
194
+ meshes.tint(Lod.debug);
195
+ lists.prune(camera, (other) => loads.requests.rendering(other));
196
+ const { loaded, before, progress } = loads;
197
+ // Three projects this InstancedLod's children for the camera after this update.
198
+ const shown = seen ? shownBy(found, levels, encoded, { loaded, before, progress }) : 0;
199
+ lists.sort(camera, {
200
+ ...local,
201
+ loaded,
202
+ before,
203
+ progress,
204
+ bounds: this.bounds.sphere,
205
+ box: this.bounds.box,
206
+ source: shape.source,
207
+ reach: shape.reach,
208
+ onlyLevel: cull === null ? found.onlyLevel : 0,
209
+ chunks: chunks.version,
210
+ });
211
+ // Nothing draws from the camera's indirect draws before a sort writes them, which a level mesh's draw runs first.
212
+ meshes.show(found.written ? shown : 0);
213
+ }
214
+ /** `camera` in this InstancedLod's space (see `localCamera`). */
215
+ localCamera(camera, shadow) {
216
+ const { loads, encoded } = this.built;
217
+ return localCamera(camera, loads.heightOf(camera, shadow), this.matrixWorld, encoded, this.forceLevel);
218
+ }
219
+ /** Draw survivors' scaled matrices, or the pool's again. */
220
+ scaleSurvivors(survivors) {
221
+ const { pool, sorter } = this.gpu;
222
+ this.built.drawn.matrices = survivors ? sorter.scaledMatrices : pool.matrices;
223
+ drawMatrices(this.built.meshes.all(), this.built.drawn);
224
+ this.measureBounds();
225
+ }
226
+ /** The level instance `index` of `chunk` draws most of its pixels with for `camera` (see `InstancedLodChunk.getLevelAt`). */
227
+ levelAt(chunk, index, camera) {
228
+ const cull = cullIn(this.cullDistance, this.matrixWorld, this.cull);
229
+ const local = this.localCamera(camera, inShadowMap(this));
230
+ return instanceLevel(chunk.instanceMatrix.array, (chunk.start + index) * 16, cull, local, this.built.shape.source, this.built.loads.loaded);
296
231
  }
297
232
  /**
298
- * Hits every instance's source level, with `instanceId` set; `object` is this
299
- * InstancedLod. Meshes of the source on layers the raycaster does not test are skipped.
233
+ * Hits every instance of its CPU chunks on the source level, with `instanceId` and `chunk` set (see
234
+ * `InstancedLodIntersection`); `object` is this InstancedLod. A GPU chunk's instances the CPU cannot see, and meshes
235
+ * of the source on layers the raycaster does not test, are not hit.
300
236
  */
301
237
  raycast(raycaster, intersects) {
302
- this.probes.raycast(this, raycaster, intersects);
238
+ raycastInstances(this, { parts: this.built.meshes.source, rest: this.built.shape.rest }, this.built.chunks.cpu(), raycaster, intersects);
303
239
  return false;
304
240
  }
305
241
  /**
306
- * Copies `source`'s levels, instances and settings into this empty InstancedLod of the
307
- * same count (it throws otherwise); the copy shares geometry and textures with `source`.
242
+ * Copies `source`'s levels, CPU chunks and settings into this empty InstancedLod, one without levels or chunks (it
243
+ * throws otherwise); the copy shares geometry and textures with `source`, and its chunks hold copies of the source's
244
+ * attributes. A GPU chunk cannot be copied: its instances live on the GPU.
308
245
  */
309
246
  copy(source) {
310
- if (this.entries.length > 0 || this.count !== source.count)
311
- throw new Error("InstancedLod.copy: copy into an empty InstancedLod of the same count");
247
+ if (this.levels.length > 0 || this.built.chunks.size > 0)
248
+ throw new Error("InstancedLod.copy: copy into an InstancedLod without levels or chunks");
249
+ const chunks = source.built.chunks.copies();
250
+ const inputs = source.inputs.map((input, i) => ({ ...input, maxPixels: at(source.levels, i).maxPixels }));
312
251
  super.copy(source, false);
313
- this.store.copy(source.store);
314
- const draws = source.entries.length > 0 ? this.build(cloneLevels(source.entries)) : 0;
315
- this.blocks = new Blocks(this.store.matrices, this.store.spheres, this.sphere, this.bounds, draws, this.rest);
252
+ this.built.chunks.dispose();
253
+ this.built.lists.dispose();
254
+ this.built = this.init(inputs, source.built.maxInstanceCount);
255
+ this.built.loads.item.given = source.built.loads.item.given;
316
256
  this.forceLevel = source.forceLevel;
317
257
  this.autoUpdate = source.autoUpdate;
258
+ this.cullDistance = source.cullDistance && { ...source.cullDistance };
318
259
  copyWind(source, this);
260
+ for (const chunk of chunks)
261
+ this.addChunk(chunk);
319
262
  return this;
320
263
  }
321
264
  clone() {
322
- // three's clone builds `new this.constructor()`, which has no count.
323
- const clone = new InstancedLod([], this.count).copy(this);
265
+ const clone = new InstancedLod([], this.gpu).copy(this);
324
266
  if (!sameClass(this, clone))
325
267
  throw new Error("InstancedLod.clone: a subclass must override clone");
326
268
  return clone;
327
269
  }
328
- /** Release drawables and templates only after every owner sharing their geometry, materials and textures has stopped drawing. */
270
+ /**
271
+ * Release its buffers and what it made of its levels, once nothing it prepares or compiles still uses them. Objects
272
+ * you give or load, chunk attributes among them, are yours to dispose; LodLoader's are freed when the last Lod
273
+ * holding them is disposed (see `LodLevel.hold`).
274
+ */
329
275
  dispose() {
330
276
  if (this.disposed)
331
277
  return;
332
278
  this.disposed = true;
333
- unwatchContext(this);
334
- this.residency.dispose(() => {
335
- this.clearDraws();
336
- for (const { object } of this.entries)
279
+ super.dispose();
280
+ this.built.chunks.dispose();
281
+ this.built.loads.dispose(() => {
282
+ this.built.meshes.dispose();
283
+ this.built.lists.dispose();
284
+ for (const { object } of this.levels)
337
285
  if (object)
338
286
  disposeCopies(object);
339
287
  });
340
288
  }
341
- clearDraws() {
342
- this.meshes?.dispose();
343
- this.meshes = null;
344
- this.selection?.dispose();
345
- this.selection = null;
346
- this.store.attach(null);
347
- }
348
289
  }
349
290
  //# sourceMappingURL=index.js.map