@drawcall/lod 0.2.0 → 0.4.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 (355) hide show
  1. package/README.md +6 -7
  2. package/dist/bake.d.ts +36 -0
  3. package/dist/bake.js +176 -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 +14 -8
  15. package/dist/baked/index.js +24 -22
  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 +16 -11
  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.d.ts +1 -1
  24. package/dist/baked/interior/rooms.js +2 -2
  25. package/dist/baked/interior/rooms.js.map +1 -1
  26. package/dist/baked/low.js +5 -4
  27. package/dist/baked/low.js.map +1 -1
  28. package/dist/baked/raster.js +6 -6
  29. package/dist/baked/raster.js.map +1 -1
  30. package/dist/baked/textures.d.ts +17 -0
  31. package/dist/baked/textures.js +95 -0
  32. package/dist/baked/textures.js.map +1 -0
  33. package/dist/baked/transfer.js +19 -18
  34. package/dist/baked/transfer.js.map +1 -1
  35. package/dist/baked/unwrap.js +4 -3
  36. package/dist/baked/unwrap.js.map +1 -1
  37. package/dist/baked/xatlas.js.map +1 -1
  38. package/dist/codec/container.d.ts +8 -0
  39. package/dist/codec/container.js +63 -0
  40. package/dist/codec/container.js.map +1 -0
  41. package/dist/codec/mesh.d.ts +8 -0
  42. package/dist/{mesh/codec.js → codec/mesh.js} +80 -52
  43. package/dist/codec/mesh.js.map +1 -0
  44. package/dist/dispose.d.ts +8 -5
  45. package/dist/dispose.js +22 -24
  46. package/dist/dispose.js.map +1 -1
  47. package/dist/foliage.d.ts +32 -0
  48. package/dist/foliage.js +137 -0
  49. package/dist/foliage.js.map +1 -0
  50. package/dist/impostor/bake/atlas.d.ts +4 -4
  51. package/dist/impostor/bake/atlas.js +12 -12
  52. package/dist/impostor/bake/atlas.js.map +1 -1
  53. package/dist/impostor/bake/fit.d.ts +8 -0
  54. package/dist/impostor/bake/fit.js +28 -0
  55. package/dist/impostor/bake/fit.js.map +1 -0
  56. package/dist/impostor/bake/index.d.ts +22 -14
  57. package/dist/impostor/bake/index.js +50 -134
  58. package/dist/impostor/bake/index.js.map +1 -1
  59. package/dist/impostor/bake/materials.d.ts +3 -3
  60. package/dist/impostor/bake/materials.js +51 -13
  61. package/dist/impostor/bake/materials.js.map +1 -1
  62. package/dist/impostor/bake/sheet.d.ts +8 -0
  63. package/dist/impostor/bake/sheet.js +90 -0
  64. package/dist/impostor/bake/sheet.js.map +1 -0
  65. package/dist/impostor/bake/source.d.ts +3 -2
  66. package/dist/impostor/bake/source.js +9 -20
  67. package/dist/impostor/bake/source.js.map +1 -1
  68. package/dist/impostor/codec.d.ts +4 -5
  69. package/dist/impostor/codec.js +36 -109
  70. package/dist/impostor/codec.js.map +1 -1
  71. package/dist/impostor/impostor.d.ts +31 -7
  72. package/dist/impostor/impostor.js +21 -6
  73. package/dist/impostor/impostor.js.map +1 -1
  74. package/dist/impostor/ktx2/encode.d.ts +26 -0
  75. package/dist/impostor/ktx2/encode.js +120 -0
  76. package/dist/impostor/ktx2/encode.js.map +1 -0
  77. package/dist/impostor/ktx2/transcode.d.ts +3 -0
  78. package/dist/impostor/ktx2/transcode.js +99 -0
  79. package/dist/impostor/ktx2/transcode.js.map +1 -0
  80. package/dist/impostor/ktx2/uastc.d.ts +7 -0
  81. package/dist/impostor/ktx2/uastc.js +65 -0
  82. package/dist/impostor/ktx2/uastc.js.map +1 -0
  83. package/dist/impostor/ktx2/wasm/encoder.d.ts +4 -0
  84. package/dist/impostor/ktx2/wasm/encoder.js +10 -0
  85. package/dist/impostor/ktx2/wasm/encoder.js.map +1 -0
  86. package/dist/impostor/ktx2/wasm/transcoder.d.ts +3 -0
  87. package/dist/impostor/ktx2/wasm/transcoder.js +9 -0
  88. package/dist/impostor/ktx2/wasm/transcoder.js.map +1 -0
  89. package/dist/impostor/ktx2/worker.d.ts +1 -0
  90. package/dist/impostor/ktx2/worker.js +10 -0
  91. package/dist/impostor/ktx2/worker.js.map +1 -0
  92. package/dist/impostor/mesh/card.d.ts +4 -4
  93. package/dist/impostor/mesh/card.js +7 -7
  94. package/dist/impostor/mesh/card.js.map +1 -1
  95. package/dist/impostor/mesh/index.d.ts +7 -6
  96. package/dist/impostor/mesh/index.js +10 -11
  97. package/dist/impostor/mesh/index.js.map +1 -1
  98. package/dist/impostor/mesh/material.d.ts +18 -9
  99. package/dist/impostor/mesh/material.js +40 -27
  100. package/dist/impostor/mesh/material.js.map +1 -1
  101. package/dist/impostor/mesh/surface.d.ts +1 -1
  102. package/dist/impostor/mesh/surface.js +13 -9
  103. package/dist/impostor/mesh/surface.js.map +1 -1
  104. package/dist/impostor/mesh/textures.d.ts +3 -3
  105. package/dist/impostor/mesh/textures.js +24 -34
  106. package/dist/impostor/mesh/textures.js.map +1 -1
  107. package/dist/index.d.ts +13 -11
  108. package/dist/index.js +12 -10
  109. package/dist/index.js.map +1 -1
  110. package/dist/instanced/camera.d.ts +34 -0
  111. package/dist/instanced/camera.js +45 -0
  112. package/dist/instanced/camera.js.map +1 -0
  113. package/dist/instanced/clusters.d.ts +54 -0
  114. package/dist/instanced/clusters.js +151 -0
  115. package/dist/instanced/clusters.js.map +1 -0
  116. package/dist/instanced/cull.d.ts +28 -0
  117. package/dist/instanced/cull.js +22 -0
  118. package/dist/instanced/cull.js.map +1 -0
  119. package/dist/instanced/instances.d.ts +39 -0
  120. package/dist/instanced/instances.js +104 -0
  121. package/dist/instanced/instances.js.map +1 -0
  122. package/dist/instanced/key.d.ts +19 -0
  123. package/dist/instanced/key.js +45 -0
  124. package/dist/instanced/key.js.map +1 -0
  125. package/dist/instanced/levels.d.ts +60 -0
  126. package/dist/instanced/levels.js +131 -0
  127. package/dist/instanced/levels.js.map +1 -0
  128. package/dist/instanced/lists.d.ts +57 -0
  129. package/dist/instanced/lists.js +114 -0
  130. package/dist/instanced/lists.js.map +1 -0
  131. package/dist/instanced/measure.d.ts +28 -0
  132. package/dist/instanced/measure.js +203 -0
  133. package/dist/instanced/measure.js.map +1 -0
  134. package/dist/instanced/meshes.d.ts +44 -51
  135. package/dist/instanced/meshes.js +167 -179
  136. package/dist/instanced/meshes.js.map +1 -1
  137. package/dist/instanced/mirror.d.ts +24 -0
  138. package/dist/instanced/mirror.js +76 -0
  139. package/dist/instanced/mirror.js.map +1 -0
  140. package/dist/instanced/raycast.d.ts +10 -11
  141. package/dist/instanced/raycast.js +31 -41
  142. package/dist/instanced/raycast.js.map +1 -1
  143. package/dist/instanced/rows.d.ts +50 -0
  144. package/dist/instanced/rows.js +36 -0
  145. package/dist/instanced/rows.js.map +1 -0
  146. package/dist/instanced/shape.d.ts +27 -0
  147. package/dist/instanced/shape.js +54 -0
  148. package/dist/instanced/shape.js.map +1 -0
  149. package/dist/instanced/sort.d.ts +61 -0
  150. package/dist/instanced/sort.js +192 -0
  151. package/dist/instanced/sort.js.map +1 -0
  152. package/dist/instanced/update.d.ts +34 -0
  153. package/dist/instanced/update.js +180 -0
  154. package/dist/instanced/update.js.map +1 -0
  155. package/dist/levels/bounds.d.ts +21 -0
  156. package/dist/levels/bounds.js +42 -0
  157. package/dist/levels/bounds.js.map +1 -0
  158. package/dist/levels/cameras.d.ts +48 -0
  159. package/dist/levels/cameras.js +122 -0
  160. package/dist/levels/cameras.js.map +1 -0
  161. package/dist/levels/compile/contexts.d.ts +30 -0
  162. package/dist/levels/compile/contexts.js +18 -0
  163. package/dist/levels/compile/contexts.js.map +1 -0
  164. package/dist/levels/compile/index.d.ts +41 -0
  165. package/dist/levels/compile/index.js +193 -0
  166. package/dist/levels/compile/index.js.map +1 -0
  167. package/dist/levels/compile/pipelines.d.ts +26 -0
  168. package/dist/levels/compile/pipelines.js +172 -0
  169. package/dist/levels/compile/pipelines.js.map +1 -0
  170. package/dist/levels/compiles.d.ts +29 -0
  171. package/dist/levels/compiles.js +50 -0
  172. package/dist/levels/compiles.js.map +1 -0
  173. package/dist/levels/draw.d.ts +102 -0
  174. package/dist/levels/draw.js +401 -0
  175. package/dist/levels/draw.js.map +1 -0
  176. package/dist/levels/fade.d.ts +60 -0
  177. package/dist/levels/fade.js +129 -0
  178. package/dist/levels/fade.js.map +1 -0
  179. package/dist/levels/index.d.ts +99 -0
  180. package/dist/levels/index.js +155 -0
  181. package/dist/levels/index.js.map +1 -0
  182. package/dist/levels/item.d.ts +38 -0
  183. package/dist/levels/item.js +89 -0
  184. package/dist/levels/item.js.map +1 -0
  185. package/dist/levels/load.d.ts +31 -0
  186. package/dist/levels/load.js +144 -0
  187. package/dist/levels/load.js.map +1 -0
  188. package/dist/levels/manager.d.ts +158 -0
  189. package/dist/levels/manager.js +352 -0
  190. package/dist/levels/manager.js.map +1 -0
  191. package/dist/{material.d.ts → levels/material.d.ts} +2 -2
  192. package/dist/{material.js → levels/material.js} +6 -5
  193. package/dist/levels/material.js.map +1 -0
  194. package/dist/levels/renderer.d.ts +32 -0
  195. package/dist/levels/renderer.js +57 -0
  196. package/dist/levels/renderer.js.map +1 -0
  197. package/dist/levels/requests.d.ts +52 -0
  198. package/dist/levels/requests.js +97 -0
  199. package/dist/levels/requests.js.map +1 -0
  200. package/dist/levels/screen.d.ts +14 -0
  201. package/dist/levels/screen.js +21 -0
  202. package/dist/levels/screen.js.map +1 -0
  203. package/dist/loader/build.d.ts +10 -0
  204. package/dist/loader/build.js +28 -0
  205. package/dist/loader/build.js.map +1 -0
  206. package/dist/loader/context.d.ts +44 -0
  207. package/dist/loader/context.js +96 -0
  208. package/dist/loader/context.js.map +1 -0
  209. package/dist/loader/entry.d.ts +4 -0
  210. package/dist/loader/entry.js +43 -0
  211. package/dist/loader/entry.js.map +1 -0
  212. package/dist/loader/index.d.ts +76 -0
  213. package/dist/loader/index.js +272 -0
  214. package/dist/loader/index.js.map +1 -0
  215. package/dist/loader/level.d.ts +41 -0
  216. package/dist/loader/level.js +140 -0
  217. package/dist/loader/level.js.map +1 -0
  218. package/dist/loader/node.d.ts +26 -0
  219. package/dist/loader/node.js +199 -0
  220. package/dist/loader/node.js.map +1 -0
  221. package/dist/loader/request.d.ts +54 -0
  222. package/dist/loader/request.js +19 -0
  223. package/dist/loader/request.js.map +1 -0
  224. package/dist/loader/url.d.ts +10 -6
  225. package/dist/loader/url.js +22 -14
  226. package/dist/loader/url.js.map +1 -1
  227. package/dist/lod.d.ts +144 -43
  228. package/dist/lod.js +354 -163
  229. package/dist/lod.js.map +1 -1
  230. package/dist/meshopt.d.ts +6 -0
  231. package/dist/meshopt.js +20 -0
  232. package/dist/meshopt.js.map +1 -0
  233. package/dist/node-process.d.ts +5 -0
  234. package/dist/node-process.js +6 -0
  235. package/dist/node-process.js.map +1 -0
  236. package/dist/node.d.ts +2 -0
  237. package/dist/node.js +5 -0
  238. package/dist/node.js.map +1 -0
  239. package/dist/simplify/cards.d.ts +14 -0
  240. package/dist/simplify/cards.js +128 -0
  241. package/dist/simplify/cards.js.map +1 -0
  242. package/dist/{simplify.d.ts → simplify/index.d.ts} +8 -5
  243. package/dist/{simplify.js → simplify/index.js} +45 -27
  244. package/dist/simplify/index.js.map +1 -0
  245. package/dist/{bounds.d.ts → source/detail.d.ts} +9 -23
  246. package/dist/{bounds.js → source/detail.js} +28 -58
  247. package/dist/source/detail.js.map +1 -0
  248. package/dist/source/deviation.js +6 -6
  249. package/dist/source/deviation.js.map +1 -1
  250. package/dist/source/{shading.d.ts → evaluator.d.ts} +10 -10
  251. package/dist/source/{shading.js → evaluator.js} +5 -5
  252. package/dist/source/evaluator.js.map +1 -0
  253. package/dist/source/{trace.d.ts → raycaster.d.ts} +6 -6
  254. package/dist/source/{trace.js → raycaster.js} +8 -8
  255. package/dist/source/raycaster.js.map +1 -0
  256. package/dist/source/{texels.d.ts → sampler.d.ts} +1 -1
  257. package/dist/source/{texels.js → sampler.js} +8 -5
  258. package/dist/source/sampler.js.map +1 -0
  259. package/dist/source/source.d.ts +1 -1
  260. package/dist/source/source.js +1 -1
  261. package/dist/source/source.js.map +1 -1
  262. package/dist/source/surface.js +1 -1
  263. package/dist/source/surface.js.map +1 -1
  264. package/dist/task.d.ts +6 -0
  265. package/dist/{pause.js → task.js} +5 -5
  266. package/dist/task.js.map +1 -0
  267. package/dist/three.d.ts +19 -30
  268. package/dist/three.js +30 -57
  269. package/dist/three.js.map +1 -1
  270. package/dist/tsl.d.ts +8 -12
  271. package/dist/tsl.js +11 -39
  272. package/dist/tsl.js.map +1 -1
  273. package/dist/typegpu.d.ts +37 -0
  274. package/dist/typegpu.js +37 -0
  275. package/dist/typegpu.js.map +1 -0
  276. package/dist/wind/bounds.js +3 -3
  277. package/dist/wind/bounds.js.map +1 -1
  278. package/dist/wind/hooks.d.ts +7 -4
  279. package/dist/wind/hooks.js +5 -2
  280. package/dist/wind/hooks.js.map +1 -1
  281. package/dist/wind/index.d.ts +4 -4
  282. package/dist/wind/index.js +25 -23
  283. package/dist/wind/index.js.map +1 -1
  284. package/dist/wind/sway.d.ts +2 -0
  285. package/dist/wind/sway.js +4 -1
  286. package/dist/wind/sway.js.map +1 -1
  287. package/package.json +24 -10
  288. package/skills/lod/SKILL.md +120 -32
  289. package/dist/bounds.js.map +0 -1
  290. package/dist/compile.d.ts +0 -4
  291. package/dist/compile.js +0 -100
  292. package/dist/compile.js.map +0 -1
  293. package/dist/deferred.d.ts +0 -12
  294. package/dist/deferred.js +0 -27
  295. package/dist/deferred.js.map +0 -1
  296. package/dist/fade.d.ts +0 -36
  297. package/dist/fade.js +0 -112
  298. package/dist/fade.js.map +0 -1
  299. package/dist/instanced/args.d.ts +0 -20
  300. package/dist/instanced/args.js +0 -77
  301. package/dist/instanced/args.js.map +0 -1
  302. package/dist/instanced/blocks.d.ts +0 -66
  303. package/dist/instanced/blocks.js +0 -294
  304. package/dist/instanced/blocks.js.map +0 -1
  305. package/dist/instanced/dirty.d.ts +0 -15
  306. package/dist/instanced/dirty.js +0 -96
  307. package/dist/instanced/dirty.js.map +0 -1
  308. package/dist/instanced/index.d.ts +0 -90
  309. package/dist/instanced/index.js +0 -349
  310. package/dist/instanced/index.js.map +0 -1
  311. package/dist/instanced/parts.d.ts +0 -14
  312. package/dist/instanced/parts.js +0 -79
  313. package/dist/instanced/parts.js.map +0 -1
  314. package/dist/instanced/select.d.ts +0 -92
  315. package/dist/instanced/select.js +0 -315
  316. package/dist/instanced/select.js.map +0 -1
  317. package/dist/instanced/store.d.ts +0 -31
  318. package/dist/instanced/store.js +0 -96
  319. package/dist/instanced/store.js.map +0 -1
  320. package/dist/levels.d.ts +0 -95
  321. package/dist/levels.js +0 -171
  322. package/dist/levels.js.map +0 -1
  323. package/dist/loader/bundle.d.ts +0 -25
  324. package/dist/loader/bundle.js +0 -241
  325. package/dist/loader/bundle.js.map +0 -1
  326. package/dist/loader/cache.d.ts +0 -15
  327. package/dist/loader/cache.js +0 -75
  328. package/dist/loader/cache.js.map +0 -1
  329. package/dist/loader/provenance.d.ts +0 -30
  330. package/dist/loader/provenance.js +0 -105
  331. package/dist/loader/provenance.js.map +0 -1
  332. package/dist/loader.d.ts +0 -55
  333. package/dist/loader.js +0 -239
  334. package/dist/loader.js.map +0 -1
  335. package/dist/material.js.map +0 -1
  336. package/dist/mesh/codec.d.ts +0 -4
  337. package/dist/mesh/codec.js.map +0 -1
  338. package/dist/pause.d.ts +0 -6
  339. package/dist/pause.js.map +0 -1
  340. package/dist/queue.d.ts +0 -2
  341. package/dist/queue.js +0 -33
  342. package/dist/queue.js.map +0 -1
  343. package/dist/renderer.d.ts +0 -21
  344. package/dist/renderer.js +0 -83
  345. package/dist/renderer.js.map +0 -1
  346. package/dist/residency.d.ts +0 -44
  347. package/dist/residency.js +0 -205
  348. package/dist/residency.js.map +0 -1
  349. package/dist/simplify.js.map +0 -1
  350. package/dist/source/shading.js.map +0 -1
  351. package/dist/source/texels.js.map +0 -1
  352. package/dist/source/trace.js.map +0 -1
  353. package/dist/view.d.ts +0 -45
  354. package/dist/view.js +0 -131
  355. package/dist/view.js.map +0 -1
package/dist/lod.js CHANGED
@@ -1,220 +1,411 @@
1
- import { Object3D, Sphere } from "three/webgpu";
1
+ import { Box3, Color, Float32BufferAttribute, InstancedBufferAttribute, InstancedBufferGeometry, Matrix4, Mesh, MeshBasicNodeMaterial, Object3D, Sphere, } from "three/webgpu";
2
2
  import { at } from "./at.js";
3
- import { compileLevel } from "./compile.js";
4
- import { measureLevels, prepareBounds } from "./bounds.js";
5
3
  import { disposeCopies } from "./dispose.js";
6
- import { setDraw } from "./fade.js";
7
- import { levelMaterial } from "./material.js";
8
- import { Residency, handover } from "./residency.js";
9
- import { FADE, Level, cloneLevels, estimate, fadeProgress, levelAt, demandLevels, tintLevels, validate } from "./levels.js";
10
- import { inShadowPass, isImpostorMesh, isLOD, isMesh, rejectSkinned } from "./three.js";
11
- import { TYPICAL_HEIGHT, View, viewportHeight, watchViewport, watchContext, unwatchContext, inView } from "./view.js";
12
- import { WIND_BOUNDS } from "./wind/hooks.js";
13
- import { copyWind, refreshWind } from "./wind/index.js";
14
- const view = new View();
4
+ import { EncodedLevels, emptyLevels, levelsFrom } from "./levels/index.js";
5
+ import { FADE } from "./levels/draw.js";
6
+ import { fadeSourceOf } from "./levels/fade.js";
7
+ import { levelsToLoad } from "./levels/load.js";
8
+ import { LodLevelLoadManager } from "./levels/manager.js";
9
+ import { inShadowMap, maxStorageBinding } from "./levels/renderer.js";
10
+ import { isRenderer, isStorageInstanced, sameClass } from "./three.js";
11
+ import { WIND } from "./wind/hooks.js";
12
+ import { copyWind } from "./wind/index.js";
13
+ import { localCamera, shownBy } from "./instanced/camera.js";
14
+ import { ClusterView, boundClusters, checkClusters, measuredBox } from "./instanced/clusters.js";
15
+ import { cullIn, emptyCull } from "./instanced/cull.js";
16
+ import { Instances, measure } from "./instanced/instances.js";
17
+ import { MeshesByLevel } from "./instanced/levels.js";
18
+ import { InstanceLists, frustumIn, listedOf } from "./instanced/lists.js";
19
+ import { MAX_SURVIVOR_SCALE, instanceLevel } from "./instanced/measure.js";
20
+ import { drawInstances } from "./instanced/meshes.js";
21
+ import { raycastInstances } from "./instanced/raycast.js";
22
+ import { InstanceShape } from "./instanced/shape.js";
15
23
  /**
16
- * One object's levels, switched by on-screen height with dithered transitions.
17
- * The renderer updates selection before shadows; each camera uses its last drawn
18
- * viewport height (1080 until known). Orthographic views switch without fading.
19
- * Shadows follow selection and omit casters smaller than a shadow texel.
24
+ * Copies of one object at levels of detail, driven like a three InstancedMesh: `instanceMatrix`, `instanceColor`,
25
+ * `count` and `boundingBox`, and `clusters` to cull and choose levels by. Each copy draws the level levelsToDraw gives
26
+ * for its height on screen, switching with dithered fades; each camera's instances are sorted into indirect draws on
27
+ * the GPU. `new Lod(levels)` is one object at the Lod's own transform.
20
28
  *
21
- * Eager and deferred objects are cloned into this owner's space. A nested Lod selects
22
- * and fades its own levels. Lines, points and sprites draw without fading or reporting
23
- * viewport height. Raycasts hit the source only.
29
+ * The attributes are assignable. A CPU attribute's edits draw once its `needsUpdate` is set (within its update ranges,
30
+ * which the Lod takes and clears), as an InstancedMesh's do. A StorageInstancedBufferAttribute, such as one the app's
31
+ * compute pass writes, is read in place, every frame; a storage colour holds four floats per instance. A matrix of
32
+ * scale 0 draws nothing. Instance matrices may scale arbitrarily; the Lod's own transform and its parents should scale
33
+ * uniformly.
24
34
  *
25
- * Build a new Lod after adding or moving meshes within a level: geometry is measured
26
- * and hooked at construction or arrival. Skinned meshes and ShaderMaterials are
27
- * refused; use static meshes with node materials or supported built-in materials.
35
+ * Which levels load and which level meshes draw for a camera follow from the instances' bounds: per cluster, its box
36
+ * and scales; without clusters, `boundingBox` around the first `count` instances, else what the CPU measures of a CPU
37
+ * instanceMatrix. With clusters, only their instances draw. A storage instanceMatrix with neither loads every level
38
+ * (it warns once). Each camera draws only the instances in its frustum, and each shadow map is a camera of its own: it
39
+ * draws the casters in its frustum, each at the level its texels resolve, as an orthographic camera would (an instance
40
+ * smaller than a texel casts nothing). Thresholds, fades and loading follow `LodLevel`; `cullDistance` thins the
41
+ * instances out with distance. Nothing draws until the coarsest level that draws something has loaded, which loads
42
+ * first; a deferred level loads when a camera first needs it, and meanwhile the nearest loaded coarser level draws in
43
+ * its place. Arriving levels' texture uploads are paced to about one uncompressed 2048² RGBA texture's bytes per frame.
44
+ *
45
+ * It draws with WebGPU only (three's WebGPURenderer on its WebGPU backend, with `indirect-first-instance`), with the
46
+ * first renderer that draws it: it learns of it in that renderer's first frame, which draws nothing of it, and another
47
+ * renderer drawing it is its error. Sequential cameras are supported; a nested render with a camera that the outer
48
+ * render also draws with replaces that camera's instance lists, so exclude the Lod from it with camera layers.
49
+ *
50
+ * Level templates are cloned, then their meshes are instanced, as children of the Lod on its layers; it sets each
51
+ * one's `visible` per camera, so hide the Lod itself, never its level meshes. Skinned meshes, InstancedMeshes (except ImpostorMesh),
52
+ * lines, points, sprites and LODs in levels are refused, as are ShaderMaterials; use static meshes with node materials
53
+ * or supported built-in materials. Build a new Lod after adding or moving meshes within a level: geometry is measured
54
+ * at construction or arrival. For external detail, leave level 0 empty and draw the instances that `getLevelAt`
55
+ * reports there.
28
56
  */
29
57
  export class Lod extends Object3D {
30
58
  /** Multiplies every measured height: below 1, cheaper levels take over sooner. */
31
59
  static quality = 1;
32
60
  /** Tint every level by its index, to see which level draws where. */
33
61
  static debug = false;
34
- /**
35
- * Share of a level's range its fade spans: from `maxPixels × (1 - fade)` up to its
36
- * `maxPixels`, the finer level draws too.
37
- */
62
+ /** Share of a level's range its fade spans: from `maxPixels × (1 - fade)` up to its `maxPixels`, the finer level draws too. */
38
63
  static fade = FADE;
39
64
  isLOD = true;
65
+ isLod = true;
40
66
  /** Let the renderer call `update` with its camera every frame. */
41
67
  autoUpdate = true;
42
- /** Draw only this level, without fades. */
68
+ /** Draw only this level, without fades, once the coarsest level that draws something has loaded. */
43
69
  forceLevel = null;
44
- levels;
45
- entries = [];
46
- /** Per level, how its meshes draw. */
47
- draws = [];
48
- /** Bounding sphere of the source (see the constructor) in the Lod's own space. */
49
- sphere = new Sphere();
50
- casterRadius = 0;
70
+ /** The levels, each `object` the template its level meshes are made from, once there is one (never in the scene). */
71
+ levels = [];
72
+ /** Thins the instances out with distance from a camera, in every camera; null draws them at any distance. */
73
+ cullDistance = null;
74
+ /** 16 floats per instance: a CPU InstancedBufferAttribute (identities to start with) or a storage one. */
75
+ instanceMatrix;
76
+ /** RGB per instance, white without: three floats on the CPU, four in a storage attribute. */
77
+ instanceColor = null;
78
+ /** How many of `instanceMatrix`'s instances draw. */
79
+ count;
80
+ /** Bounds every instance at every level, in the Lod's space (see `computeBoundingBox`); measured when null. */
81
+ boundingBox = null;
82
+ /** Disjoint ranges of instances, each inside its box, which cull and choose levels apart; clipped to `count`. */
83
+ clusters = undefined;
84
+ /** The levels as they were given, which a copy is made from. */
85
+ inputs = [];
86
+ /** What it is made of for its levels, which a copy makes anew. */
87
+ built;
51
88
  disposed = false;
52
- residency = new Residency(this.entries, (level, object, context) => this.install(level, object, context), () => this.dispatchEvent({ type: "levelschange" }), (error) => this.dispatchEvent({ type: "levelserror", error }));
89
+ instances = new Instances();
90
+ /** What the level meshes draw. */
91
+ drawn;
92
+ /** Around every instance, survivors grown: what every level mesh is culled and boxed by. */
93
+ bounds = { sphere: new Sphere(), box: new Box3() };
94
+ view = new ClusterView();
95
+ /** The cluster ranges last checked to be disjoint. */
96
+ checked = [];
97
+ /** What `cullIn` writes cullDistance into, in this Lod's space. */
98
+ cull = emptyCull();
99
+ /**
100
+ * Drawn until a renderer has drawn the Lod, to learn which: three tells a mesh its renderer, but not a LOD. It bounds
101
+ * the instances for Box3.setFromObject, as the level meshes do once there are some.
102
+ */
103
+ sentinel;
104
+ warned = false;
105
+ /** `levels[0]` is the source; empty only as the target of `copy`. `count` instances, at the Lod's origin to start with. */
106
+ constructor(levels, count = 1, options = {}) {
107
+ super();
108
+ if (!Number.isInteger(count) || count < 0)
109
+ throw new RangeError(`Lod: count must be a nonnegative integer, not ${count}`);
110
+ this.count = count;
111
+ this.instanceMatrix = identities(count);
112
+ this.instances.refresh(this);
113
+ this.drawn = { matrices: this.instances.matrixStorage, colors: this.instances.colorStorage };
114
+ this.sentinel = this.makeSentinel();
115
+ this.built = this.init(levels);
116
+ this.built.loads.item.given = options.manager;
117
+ this.refresh();
118
+ }
53
119
  get pending() {
54
- return this.residency.pending;
120
+ return this.built.loads.pending;
55
121
  }
56
122
  get error() {
57
- return this.residency.error;
123
+ return this.built.loads.error;
124
+ }
125
+ /** Where the item of levels a camera misses goes (see `LodOptions.manager`). */
126
+ get manager() {
127
+ return this.built.loads.item.manager;
128
+ }
129
+ set manager(manager) {
130
+ this.built.loads.item.given = manager;
131
+ }
132
+ setMatrixAt(index, matrix) {
133
+ matrix.toArray(this.instanceMatrix.array, index * 16);
134
+ return this;
135
+ }
136
+ getMatrixAt(index, matrix) {
137
+ return matrix.fromArray(this.instanceMatrix.array, index * 16);
138
+ }
139
+ /** Write instance `index`'s colour, making a white `instanceColor` for every instance first if there is none. */
140
+ setColorAt(index, color) {
141
+ this.instanceColor ??= new InstancedBufferAttribute(new Float32Array(this.instanceMatrix.count * 3).fill(1), 3);
142
+ color.toArray(this.instanceColor.array, index * this.instanceColor.itemSize);
143
+ return this;
144
+ }
145
+ /** Instance `index`'s colour: white without `instanceColor`. */
146
+ getColorAt(index, color) {
147
+ const colors = this.instanceColor;
148
+ return colors ? color.fromArray(colors.array, index * colors.itemSize) : color.setRGB(1, 1, 1);
58
149
  }
59
150
  /**
60
- * `levels[0]` is the source. Empty only as the target of `copy`. A source that draws
61
- * nothing (an empty Object3D, for detail another system draws) is measured by the
62
- * first level that draws something.
151
+ * Set `boundingBox` around the first `count` instances at every level and its sway, from a CPU instanceMatrix as it
152
+ * is now; the CPU cannot read a storage one.
63
153
  */
64
- constructor(levels = []) {
65
- super();
66
- this.levels = this.entries;
67
- if (levels.length > 0)
68
- this.build(levels);
69
- }
70
- build(inputs) {
71
- const { own, placed } = measureLevels(inputs, "Lod");
72
- this.sphere = placed;
73
- this.casterRadius = placed.radius;
74
- let drawable = false;
75
- inputs.forEach((input, i) => {
76
- const level = new Level(input, i, this.entries);
77
- const draw = { kind: "object", fade: 0, diameter: 0, tint: level.tint };
78
- this.entries.push(level);
79
- this.draws.push(draw);
80
- const { object } = level;
81
- if (!object)
82
- return;
83
- rejectSkinned(object, "Lod");
84
- this.add(object);
85
- this.hook(object, draw, level.nested, object);
86
- drawable ||= drawsMesh(object);
154
+ computeBoundingBox() {
155
+ if (isStorageInstanced(this.instanceMatrix))
156
+ throw new Error("Lod.computeBoundingBox: the CPU cannot read a storage instanceMatrix: set boundingBox or clusters");
157
+ this.boundingBox = measuredBox(measure(this.instanceMatrix, this.count, null), this.built.shape, this.boundingBox ?? new Box3());
158
+ }
159
+ /** What this Lod is made of for `inputs`: its levels, their meshes and their loading. */
160
+ init(inputs) {
161
+ const shape = new InstanceShape(inputs);
162
+ const lists = new InstanceLists(listedOf(inputs));
163
+ const drawing = { shape, lists, drawn: this.drawn };
164
+ this.inputs = inputs;
165
+ if (inputs.length > 0)
166
+ this.levels.push(...levelsFrom(inputs, 2 * shape.ownRadius, "Lod"));
167
+ const encoded = new EncodedLevels(this.levels, emptyLevels(inputs), "Lod");
168
+ const drawFor = (renderer, camera) => this.built.loads.guard(() => {
169
+ this.built.lists.drawFor(this.see(renderer), camera, this.instances.matrixStorage);
87
170
  });
88
- estimate(this.entries, 2 * own.radius);
89
- this.residency.reset();
90
- validate(this.entries);
91
- if (!drawable)
92
- watchContext(this);
93
- }
94
- /** Fade the meshes under `node` by `draw` and learn the viewport height from them; an inner Lod fades its own. */
95
- hook(node, draw, nested, level) {
96
- const inner = nested || (node !== level && isLOD(node));
97
- if (isMesh(node)) {
98
- if (!inner) {
99
- setDraw(node, draw);
100
- // An ImpostorMesh reads its draw itself.
101
- if (!isImpostorMesh(node))
102
- node.material = Array.isArray(node.material) ? node.material.map((m) => levelMaterial(m)) : levelMaterial(node.material);
103
- }
104
- watchViewport(node);
171
+ const meshes = new MeshesByLevel(this, this.levels, drawing, this.bounds, drawFor);
172
+ // Its sorts show them.
173
+ meshes.show(0);
174
+ const loads = new LodLevelLoadManager(this, `Lod ${this.uuid}`, inputs, encoded.get(null, true), (level, loaded) => meshes.prepare(level, loaded), (level) => meshes.of(level));
175
+ return { ...drawing, meshes, loads, encoded };
176
+ }
177
+ /**
178
+ * The sentinel, drawn until a renderer has drawn the Lod (see `see`): no instances, on the Lod's layers, casting, and
179
+ * never hit by a raycast. Its box is the instances' bounds, and its vertices that box's corners (see `placeSentinel`),
180
+ * which precise Box3.setFromObject reads.
181
+ */
182
+ makeSentinel() {
183
+ const geometry = new InstancedBufferGeometry().setAttribute("position", new Float32BufferAttribute(new Float32Array(24), 3));
184
+ geometry.instanceCount = 0;
185
+ geometry.boundingBox = this.bounds.box;
186
+ geometry.boundingSphere = this.bounds.sphere;
187
+ const sentinel = new Mesh(geometry, SENTINEL_MATERIAL);
188
+ sentinel.frustumCulled = false;
189
+ sentinel.layers = this.layers;
190
+ sentinel.raycast = () => { };
191
+ // Shadow passes draw it too: a Lod that only casts learns its renderer there.
192
+ sentinel.castShadow = true;
193
+ sentinel.onBeforeRender = (renderer) => this.built.loads.guard(() => void this.see(renderer));
194
+ this.add(sentinel);
195
+ return sentinel;
196
+ }
197
+ /** `renderer`, which three hands a mesh's draw, draws the Lod (see `LodLevelLoadManager.see`): it, once checked. */
198
+ see(renderer) {
199
+ if (!isRenderer(renderer))
200
+ throw new Error("Lod: three drew a mesh of it with no renderer");
201
+ this.built.loads.see(renderer);
202
+ return renderer;
203
+ }
204
+ [WIND](padding, slope, baseY) {
205
+ if (this.built.loads.started)
206
+ throw new Error("Lod: apply wind before the first render");
207
+ this.built.shape.wind(padding, slope, baseY);
208
+ for (const mesh of this.built.meshes.all()) {
209
+ const source = fadeSourceOf(mesh);
210
+ if (source)
211
+ source.radius = this.built.shape.fadeRadius;
105
212
  }
106
- for (const child of node.children)
107
- this.hook(child, draw, inner, level);
213
+ this.refresh();
108
214
  }
109
- [WIND_BOUNDS](padding) {
110
- this.casterRadius += padding;
215
+ /** Take in the instances as they are now, so that its level meshes bound them (see Box3.setFromObject), then update the world matrices. */
216
+ updateMatrixWorld(force) {
217
+ if (!this.disposed && this.levels.length > 0)
218
+ this.built.loads.guard(() => this.refresh());
219
+ super.updateMatrixWorld(force);
111
220
  }
112
- /** Pick and fade levels for `camera`; detail starts after a draw supplies its render context. */
221
+ /** 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`). */
113
222
  update(camera) {
114
- const { entries, draws } = this;
115
- if (this.disposed || entries.length === 0 || inShadowPass(this))
223
+ if (this.disposed || this.levels.length === 0)
116
224
  return;
117
- validate(entries);
118
- view.begin(camera, viewportHeight(camera) ?? TYPICAL_HEIGHT, this.forceLevel);
119
- tintLevels(entries, Lod.debug);
120
- const height = view.pixels(this.matrixWorld, this.sphere) * Lod.quality;
121
- const desired = levelAt(entries, height, this.forceLevel);
122
- this.residency.demand(camera, inView(camera, this.matrixWorld, this.sphere) ? demandLevels(entries, height, this.forceLevel).map(({ index }) => index) : []);
123
- this.residency.update(camera);
124
- const { fallback, previous } = this.residency;
125
- const neighbour = Math.max(0, desired.index - 1);
126
- let level = at(fallback, desired.index);
127
- let finer = at(fallback, neighbour);
128
- const fade = view.fades ? fadeProgress(entries, desired, height) : 0;
129
- let t = level === finer ? 0 : fade;
130
- const progress = this.residency.progress;
131
- if (progress < 1) {
132
- const before = at(previous, desired.index);
133
- const beforeFiner = at(previous, neighbour);
134
- [level, finer, t] = handover([before, beforeFiner, before === beforeFiner ? 0 : fade], [level, finer, t], progress);
135
- }
136
- const diameter = 2 * this.casterRadius * this.matrixWorld.getMaxScaleOnAxis();
137
- entries.forEach((each, i) => {
138
- const draw = at(draws, i);
139
- draw.fade = i === level ? t : i === finer ? -t : 0;
140
- draw.diameter = diameter;
141
- if (each.object)
142
- each.object.visible = i === level || (t > 0 && i === finer);
225
+ // A shadow map draws nothing of a Lod whose levels cast nothing; one not loaded yet may.
226
+ const shadow = inShadowMap(this);
227
+ if (shadow && !this.levels.some(({ object }, i) => !object || this.built.meshes.of(i).some((mesh) => mesh.castShadow)))
228
+ return;
229
+ this.built.loads.guard(() => {
230
+ const { renderer } = this.built.loads;
231
+ this.sentinel.visible = renderer === null;
232
+ if (renderer)
233
+ this.sort(renderer, camera, shadow);
143
234
  });
144
235
  }
145
- async install(level, template, context) {
146
- const object = template.clone();
147
- const nested = isLOD(object);
148
- object.visible = false;
149
- rejectSkinned(object, "Lod");
150
- if (level.box)
151
- prepareBounds(object, level.box);
152
- this.hook(object, at(this.draws, level.index), nested, object);
153
- // Parented hidden only for world matrices and wind; the level arrives after compiling.
154
- this.add(object);
155
- object.updateWorldMatrix(true, true);
156
- refreshWind(this, object);
157
- try {
158
- await compileLevel(object, context);
236
+ /** Take in the attributes, clusters and cullDistance as they are now, and bound the instances. */
237
+ refresh() {
238
+ const { lists, meshes, shape } = this.built;
239
+ if (this.clusters)
240
+ checkClusters(this.clusters, this.instanceMatrix.count, this.checked);
241
+ this.instances.refresh(this);
242
+ const survivors = this.cullDistance?.scaleSurvivors ?? false;
243
+ const matrices = survivors ? lists.scaledMatrices(this.instanceMatrix.count) : this.instances.matrixStorage;
244
+ const colors = this.instances.colorStorage;
245
+ if (matrices !== this.drawn.matrices || colors !== this.drawn.colors) {
246
+ this.drawn.matrices = matrices;
247
+ this.drawn.colors = colors;
248
+ drawInstances(meshes.all(), this.drawn);
159
249
  }
160
- catch (error) {
161
- this.remove(object);
162
- disposeCopies(object);
163
- throw error;
250
+ boundClusters(this, this.instances.measured, shape, survivors ? MAX_SURVIVOR_SCALE : 1, this.bounds);
251
+ this.placeSentinel();
252
+ }
253
+ /** Put the sentinel's vertices at the corners of the instances' bounds; at the origin while nothing bounds them. */
254
+ placeSentinel() {
255
+ const { box } = this.bounds;
256
+ const position = this.sentinel.geometry.getAttribute("position");
257
+ const empty = box.isEmpty();
258
+ let moved = false;
259
+ for (let i = 0; i < 8; i++) {
260
+ const x = empty ? 0 : i & 1 ? box.max.x : box.min.x;
261
+ const y = empty ? 0 : i & 2 ? box.max.y : box.min.y;
262
+ const z = empty ? 0 : i & 4 ? box.max.z : box.min.z;
263
+ if (position.getX(i) === x && position.getY(i) === y && position.getZ(i) === z)
264
+ continue;
265
+ position.setXYZ(i, x, y, z);
266
+ moved = true;
164
267
  }
165
- if (this.disposed) {
166
- this.remove(object);
167
- disposeCopies(object);
168
- return;
268
+ if (moved)
269
+ position.needsUpdate = true;
270
+ }
271
+ /** Sort for `camera`, a shadow map's when `shadow`, drawn by `renderer`. */
272
+ sort(renderer, camera, shadow) {
273
+ const { loads, shape, lists, encoded, meshes } = this.built;
274
+ this.refresh();
275
+ const limit = maxStorageBinding(renderer);
276
+ const bytes = this.instanceMatrix.count * 64;
277
+ if (bytes > limit)
278
+ throw new RangeError(`Lod: its instanceMatrix of ${this.instanceMatrix.count} instances takes ${bytes} bytes, more than the device's storage binding of ${limit} bytes: split the instances across Lods, or raise maxStorageBufferBindingSize in the renderer's requiredLimits`);
279
+ const local = this.localCamera(camera, renderer, shadow);
280
+ const cull = cullIn(this.cullDistance, this.matrixWorld, this.cull);
281
+ const found = lists.of(camera);
282
+ const { view } = this;
283
+ view.see(this, this.instances.measured, shape, frustumIn(found, camera, this.matrixWorld), local, cull);
284
+ if (view.unbounded && !this.warned) {
285
+ this.warned = true;
286
+ console.warn(`Lod ${this.uuid}: nothing bounds its storage instanceMatrix, so every level loads and the CPU culls nothing; set boundingBox or clusters`);
169
287
  }
170
- level.object = object;
171
- level.nested = nested;
172
- if (drawsMesh(object))
173
- unwatchContext(this);
288
+ const { levels } = local;
289
+ // A shadow map draws from the levels its scene's cameras loaded.
290
+ loads.request(camera, levelsToLoad(shadow ? [] : view.heights, encoded.heights(levels)), shadow);
291
+ meshes.tint(Lod.debug);
292
+ lists.prune(camera, (other) => loads.rendering(other));
293
+ const { loaded, before, progress } = loads;
294
+ // Three projects this Lod's children for the camera after this update.
295
+ const { shown, only } = shownBy(view.seen, levels, encoded, { loaded, before, progress });
296
+ lists.sort(camera, {
297
+ ...local,
298
+ cull,
299
+ loaded,
300
+ before,
301
+ progress,
302
+ source: shape.source,
303
+ reach: shape.reach,
304
+ ranges: view.ranges,
305
+ instances: view.instances,
306
+ inside: view.inside,
307
+ onlyLevel: cull === null ? only : 0,
308
+ version: this.instances.version,
309
+ }, limit);
310
+ // Nothing draws from the camera's indirect draws before a sort writes them, which a level mesh's draw runs first.
311
+ meshes.show(found.written ? shown : 0);
312
+ }
313
+ /** `camera` in this Lod's space, drawn by `renderer` (see `localCamera`). */
314
+ localCamera(camera, renderer, shadow) {
315
+ const { loads, encoded } = this.built;
316
+ return localCamera(camera, loads.heightOf(camera, renderer, shadow), this.matrixWorld, encoded, this.forceLevel, Lod.quality);
174
317
  }
318
+ /**
319
+ * The level that draws most of instance `index`'s pixels for `camera` from where it is now, with the levels loaded
320
+ * now and no arrival under way (see `drawnLevel`); null at scale 0, thinned out by `cullDistance`, or while no level
321
+ * it could draw is loaded. From the CPU instanceMatrix as it is, edits not yet drawn included; the CPU cannot read a
322
+ * storage one. It measures with the renderer drawing the Lod, so it throws before one has drawn it.
323
+ */
324
+ getLevelAt(index, camera) {
325
+ const { renderer } = this.built.loads;
326
+ if (!renderer)
327
+ throw new Error("Lod.getLevelAt: render the Lod first: it measures with the renderer drawing it");
328
+ if (isStorageInstanced(this.instanceMatrix))
329
+ throw new Error("Lod.getLevelAt: the CPU cannot read a storage instanceMatrix");
330
+ if (!Number.isInteger(index) || index < 0 || index >= this.count)
331
+ throw new RangeError(`Lod.getLevelAt: ${index} is not one of its ${this.count} instances`);
332
+ const cull = cullIn(this.cullDistance, this.matrixWorld, this.cull);
333
+ const local = this.localCamera(camera, renderer, inShadowMap(this));
334
+ return instanceLevel(this.instanceMatrix.array, index * 16, cull, local, this.built.shape.source, this.built.loads.loaded);
335
+ }
336
+ /**
337
+ * Hits the first `count` instances on the source level, with `instanceId` set as on an InstancedMesh's hits; `object`
338
+ * is this Lod. Instances of a storage instanceMatrix, which the CPU cannot read, are not hit. Its level meshes, on its
339
+ * layers, are never hit themselves.
340
+ */
175
341
  raycast(raycaster, intersects) {
176
- const source = this.entries[0];
177
- if (source?.object)
178
- raycaster.intersectObject(source.object, true, intersects);
179
- // Stop the raycaster from descending into the other levels.
180
- return false;
342
+ if (!isStorageInstanced(this.instanceMatrix))
343
+ raycastInstances(this, { parts: this.built.meshes.source, rest: this.built.shape.rest }, this.instanceMatrix.array, this.count, raycaster, intersects);
181
344
  }
182
345
  /**
183
- * Copies `source`'s levels into this empty Lod (it throws otherwise). The copies share
184
- * geometry, materials and textures with `source`, as three's clone does; the Lod
185
- * settings and each level's `maxPixels` come along.
346
+ * Copies `source`'s levels, instances and settings into this Lod without levels (it throws otherwise); the copy
347
+ * shares geometry and textures with `source`, and holds copies of its attributes and clusters. Storage attributes
348
+ * cannot be copied: their instances live on the GPU.
186
349
  */
187
350
  copy(source) {
188
- if (this.entries.length > 0)
189
- throw new Error("Lod.copy: copy into an empty Lod");
351
+ if (this.levels.length > 0)
352
+ throw new Error("Lod.copy: copy into a Lod without levels");
353
+ const { instanceMatrix, instanceColor } = source;
354
+ if (isStorageInstanced(instanceMatrix) || (instanceColor && isStorageInstanced(instanceColor)))
355
+ throw new Error("Lod.copy: a storage attribute's instances live on the GPU and cannot be copied");
356
+ const inputs = source.inputs.map((input, i) => ({ ...input, maxPixels: at(source.levels, i).maxPixels }));
190
357
  super.copy(source, false);
191
- if (source.entries.length > 0)
192
- this.build(cloneLevels(source.entries));
358
+ this.instanceMatrix = new InstancedBufferAttribute(instanceMatrix.array.slice(), 16);
359
+ this.instanceColor = instanceColor && new InstancedBufferAttribute(instanceColor.array.slice(), 3);
360
+ this.count = source.count;
361
+ this.boundingBox = source.boundingBox?.clone() ?? null;
362
+ this.clusters = source.clusters?.map((cluster) => ({ ...cluster, boundingBox: cluster.boundingBox.clone() }));
363
+ this.cullDistance = source.cullDistance && { ...source.cullDistance };
193
364
  this.forceLevel = source.forceLevel;
194
365
  this.autoUpdate = source.autoUpdate;
366
+ this.built.lists.dispose();
367
+ this.built.loads.dispose(() => { });
368
+ this.built = this.init(inputs);
369
+ this.built.loads.item.given = source.built.loads.item.given;
195
370
  copyWind(source, this);
371
+ this.refresh();
196
372
  return this;
197
373
  }
198
- /** Release drawables and templates only after every owner sharing their geometry, materials and textures has stopped drawing. */
374
+ clone() {
375
+ const clone = new Lod([], 0).copy(this);
376
+ if (!sameClass(this, clone))
377
+ throw new Error("Lod.clone: a subclass must override clone");
378
+ return clone;
379
+ }
380
+ /**
381
+ * Release its buffers and what it made of its levels, once nothing it prepares or compiles still uses them. Objects
382
+ * you give or load, its attributes among them, are yours to dispose; CachedLodLoader's are freed when the last Lod
383
+ * holding them is disposed (see `LodLevel.hold`).
384
+ */
199
385
  dispose() {
200
386
  if (this.disposed)
201
387
  return;
202
388
  this.disposed = true;
203
- unwatchContext(this);
204
- this.residency.dispose(() => {
205
- for (const { object } of this.entries)
389
+ super.dispose();
390
+ this.sentinel.removeFromParent();
391
+ this.sentinel.geometry.dispose();
392
+ this.built.loads.dispose(() => {
393
+ this.built.meshes.dispose();
394
+ this.built.lists.dispose();
395
+ this.instances.dispose();
396
+ for (const { object } of this.levels)
206
397
  if (object)
207
398
  disposeCopies(object);
208
399
  });
209
400
  }
210
401
  }
211
- /** Whether `level` holds a mesh that draws once Lod shows it: the level's own visibility is Lod's to switch, its descendants' are not. */
212
- function drawsMesh(level) {
213
- let found = isMesh(level);
214
- for (const child of level.children)
215
- child.traverseVisible((node) => {
216
- found ||= isMesh(node);
217
- });
218
- return found;
402
+ const SENTINEL_MATERIAL = new MeshBasicNodeMaterial();
403
+ /** A CPU instanceMatrix of `count` identities. */
404
+ function identities(count) {
405
+ const array = new Float32Array(count * 16);
406
+ for (let i = 0; i < count; i++)
407
+ array.set(IDENTITY, i * 16);
408
+ return new InstancedBufferAttribute(array, 16);
219
409
  }
410
+ const IDENTITY = new Matrix4().elements;
220
411
  //# sourceMappingURL=lod.js.map