@drawcall/lod 0.1.2 → 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 (358) hide show
  1. package/README.md +13 -70
  2. package/dist/at.d.ts +2 -0
  3. package/dist/at.js +8 -0
  4. package/dist/at.js.map +1 -0
  5. package/dist/bake.d.ts +22 -0
  6. package/dist/bake.js +88 -0
  7. package/dist/bake.js.map +1 -0
  8. package/dist/baked/codec.d.ts +5 -5
  9. package/dist/baked/codec.js +58 -158
  10. package/dist/baked/codec.js.map +1 -1
  11. package/dist/baked/coverage.d.ts +6 -3
  12. package/dist/baked/coverage.js +6 -3
  13. package/dist/baked/coverage.js.map +1 -1
  14. package/dist/baked/fields.d.ts +9 -0
  15. package/dist/baked/fields.js +22 -0
  16. package/dist/baked/fields.js.map +1 -0
  17. package/dist/baked/index.d.ts +16 -6
  18. package/dist/baked/index.js +23 -21
  19. package/dist/baked/index.js.map +1 -1
  20. package/dist/baked/interior/faces.d.ts +4 -3
  21. package/dist/baked/interior/faces.js +13 -10
  22. package/dist/baked/interior/faces.js.map +1 -1
  23. package/dist/baked/interior/material.d.ts +7 -10
  24. package/dist/baked/interior/material.js +29 -36
  25. package/dist/baked/interior/material.js.map +1 -1
  26. package/dist/baked/interior/rooms.js +1 -1
  27. package/dist/baked/interior/rooms.js.map +1 -1
  28. package/dist/baked/low.d.ts +2 -2
  29. package/dist/baked/low.js +9 -8
  30. package/dist/baked/low.js.map +1 -1
  31. package/dist/baked/raster.js +6 -6
  32. package/dist/baked/raster.js.map +1 -1
  33. package/dist/baked/textures.d.ts +17 -0
  34. package/dist/baked/textures.js +95 -0
  35. package/dist/baked/textures.js.map +1 -0
  36. package/dist/baked/transfer.js +19 -18
  37. package/dist/baked/transfer.js.map +1 -1
  38. package/dist/baked/unwrap.js +4 -3
  39. package/dist/baked/unwrap.js.map +1 -1
  40. package/dist/baked/xatlas.js.map +1 -1
  41. package/dist/codec/container.d.ts +8 -0
  42. package/dist/codec/container.js +63 -0
  43. package/dist/codec/container.js.map +1 -0
  44. package/dist/codec/mesh.d.ts +8 -0
  45. package/dist/codec/mesh.js +220 -0
  46. package/dist/codec/mesh.js.map +1 -0
  47. package/dist/dispose.d.ts +11 -4
  48. package/dist/dispose.js +39 -12
  49. package/dist/dispose.js.map +1 -1
  50. package/dist/gpu.d.ts +39 -0
  51. package/dist/gpu.js +41 -0
  52. package/dist/gpu.js.map +1 -0
  53. package/dist/impostor/bake/atlas.d.ts +4 -4
  54. package/dist/impostor/bake/atlas.js +12 -12
  55. package/dist/impostor/bake/atlas.js.map +1 -1
  56. package/dist/impostor/bake/fit.d.ts +8 -0
  57. package/dist/impostor/bake/fit.js +28 -0
  58. package/dist/impostor/bake/fit.js.map +1 -0
  59. package/dist/impostor/bake/index.d.ts +23 -14
  60. package/dist/impostor/bake/index.js +42 -134
  61. package/dist/impostor/bake/index.js.map +1 -1
  62. package/dist/impostor/bake/materials.d.ts +3 -3
  63. package/dist/impostor/bake/materials.js +60 -12
  64. package/dist/impostor/bake/materials.js.map +1 -1
  65. package/dist/impostor/bake/sheet.d.ts +8 -0
  66. package/dist/impostor/bake/sheet.js +90 -0
  67. package/dist/impostor/bake/sheet.js.map +1 -0
  68. package/dist/impostor/bake/source.d.ts +2 -2
  69. package/dist/impostor/bake/source.js +8 -19
  70. package/dist/impostor/bake/source.js.map +1 -1
  71. package/dist/impostor/codec.d.ts +5 -11
  72. package/dist/impostor/codec.js +37 -121
  73. package/dist/impostor/codec.js.map +1 -1
  74. package/dist/impostor/impostor.d.ts +31 -7
  75. package/dist/impostor/impostor.js +21 -6
  76. package/dist/impostor/impostor.js.map +1 -1
  77. package/dist/impostor/ktx2/encode.d.ts +26 -0
  78. package/dist/impostor/ktx2/encode.js +117 -0
  79. package/dist/impostor/ktx2/encode.js.map +1 -0
  80. package/dist/impostor/ktx2/transcode.d.ts +3 -0
  81. package/dist/impostor/ktx2/transcode.js +99 -0
  82. package/dist/impostor/ktx2/transcode.js.map +1 -0
  83. package/dist/impostor/ktx2/uastc.d.ts +7 -0
  84. package/dist/impostor/ktx2/uastc.js +64 -0
  85. package/dist/impostor/ktx2/uastc.js.map +1 -0
  86. package/dist/impostor/ktx2/wasm/encoder.d.ts +4 -0
  87. package/dist/impostor/ktx2/wasm/encoder.js +10 -0
  88. package/dist/impostor/ktx2/wasm/encoder.js.map +1 -0
  89. package/dist/impostor/ktx2/wasm/transcoder.d.ts +3 -0
  90. package/dist/impostor/ktx2/wasm/transcoder.js +9 -0
  91. package/dist/impostor/ktx2/wasm/transcoder.js.map +1 -0
  92. package/dist/impostor/ktx2/worker.d.ts +1 -0
  93. package/dist/impostor/ktx2/worker.js +12 -0
  94. package/dist/impostor/ktx2/worker.js.map +1 -0
  95. package/dist/impostor/mesh/card.d.ts +3 -3
  96. package/dist/impostor/mesh/card.js +11 -8
  97. package/dist/impostor/mesh/card.js.map +1 -1
  98. package/dist/impostor/mesh/index.d.ts +10 -7
  99. package/dist/impostor/mesh/index.js +15 -18
  100. package/dist/impostor/mesh/index.js.map +1 -1
  101. package/dist/impostor/mesh/material.d.ts +24 -13
  102. package/dist/impostor/mesh/material.js +67 -51
  103. package/dist/impostor/mesh/material.js.map +1 -1
  104. package/dist/impostor/mesh/surface.d.ts +20 -16
  105. package/dist/impostor/mesh/surface.js +81 -58
  106. package/dist/impostor/mesh/surface.js.map +1 -1
  107. package/dist/impostor/mesh/textures.d.ts +3 -3
  108. package/dist/impostor/mesh/textures.js +24 -34
  109. package/dist/impostor/mesh/textures.js.map +1 -1
  110. package/dist/impostor/octahedral.d.ts +1 -8
  111. package/dist/impostor/octahedral.js +3 -18
  112. package/dist/impostor/octahedral.js.map +1 -1
  113. package/dist/index.d.ts +16 -10
  114. package/dist/index.js +14 -8
  115. package/dist/index.js.map +1 -1
  116. package/dist/instanced/camera.d.ts +46 -0
  117. package/dist/instanced/camera.js +70 -0
  118. package/dist/instanced/camera.js.map +1 -0
  119. package/dist/instanced/chunk.d.ts +97 -0
  120. package/dist/instanced/chunk.js +156 -0
  121. package/dist/instanced/chunk.js.map +1 -0
  122. package/dist/instanced/chunks.d.ts +60 -0
  123. package/dist/instanced/chunks.js +208 -0
  124. package/dist/instanced/chunks.js.map +1 -0
  125. package/dist/instanced/cull.d.ts +28 -0
  126. package/dist/instanced/cull.js +22 -0
  127. package/dist/instanced/cull.js.map +1 -0
  128. package/dist/instanced/index.d.ts +96 -92
  129. package/dist/instanced/index.js +229 -512
  130. package/dist/instanced/index.js.map +1 -1
  131. package/dist/instanced/key.d.ts +19 -0
  132. package/dist/instanced/key.js +45 -0
  133. package/dist/instanced/key.js.map +1 -0
  134. package/dist/instanced/levels.d.ts +60 -0
  135. package/dist/instanced/levels.js +130 -0
  136. package/dist/instanced/levels.js.map +1 -0
  137. package/dist/instanced/lists.d.ts +70 -0
  138. package/dist/instanced/lists.js +133 -0
  139. package/dist/instanced/lists.js.map +1 -0
  140. package/dist/instanced/measure.d.ts +28 -0
  141. package/dist/instanced/measure.js +203 -0
  142. package/dist/instanced/measure.js.map +1 -0
  143. package/dist/instanced/meshes.d.ts +46 -14
  144. package/dist/instanced/meshes.js +137 -22
  145. package/dist/instanced/meshes.js.map +1 -1
  146. package/dist/instanced/mirror.d.ts +20 -0
  147. package/dist/instanced/mirror.js +37 -0
  148. package/dist/instanced/mirror.js.map +1 -0
  149. package/dist/instanced/pages/buffers.d.ts +37 -0
  150. package/dist/instanced/pages/buffers.js +15 -0
  151. package/dist/instanced/pages/buffers.js.map +1 -0
  152. package/dist/instanced/pages/chunk.d.ts +51 -0
  153. package/dist/instanced/pages/chunk.js +81 -0
  154. package/dist/instanced/pages/chunk.js.map +1 -0
  155. package/dist/instanced/pages/index.d.ts +74 -0
  156. package/dist/instanced/pages/index.js +269 -0
  157. package/dist/instanced/pages/index.js.map +1 -0
  158. package/dist/instanced/pages/passes.d.ts +21 -0
  159. package/dist/instanced/pages/passes.js +52 -0
  160. package/dist/instanced/pages/passes.js.map +1 -0
  161. package/dist/instanced/pages/slots.d.ts +11 -0
  162. package/dist/instanced/pages/slots.js +90 -0
  163. package/dist/instanced/pages/slots.js.map +1 -0
  164. package/dist/instanced/pages/workgroup.d.ts +14 -0
  165. package/dist/instanced/pages/workgroup.js +38 -0
  166. package/dist/instanced/pages/workgroup.js.map +1 -0
  167. package/dist/instanced/raycast.d.ts +15 -0
  168. package/dist/instanced/raycast.js +46 -0
  169. package/dist/instanced/raycast.js.map +1 -0
  170. package/dist/instanced/rows.d.ts +108 -0
  171. package/dist/instanced/rows.js +209 -0
  172. package/dist/instanced/rows.js.map +1 -0
  173. package/dist/instanced/shape.d.ts +25 -0
  174. package/dist/instanced/shape.js +51 -0
  175. package/dist/instanced/shape.js.map +1 -0
  176. package/dist/instanced/sort.d.ts +63 -0
  177. package/dist/instanced/sort.js +162 -0
  178. package/dist/instanced/sort.js.map +1 -0
  179. package/dist/instanced/update.d.ts +30 -0
  180. package/dist/instanced/update.js +207 -0
  181. package/dist/instanced/update.js.map +1 -0
  182. package/dist/levels/bounds.d.ts +21 -0
  183. package/dist/levels/bounds.js +42 -0
  184. package/dist/levels/bounds.js.map +1 -0
  185. package/dist/levels/cameras.d.ts +48 -0
  186. package/dist/levels/cameras.js +122 -0
  187. package/dist/levels/cameras.js.map +1 -0
  188. package/dist/levels/compile/contexts.d.ts +30 -0
  189. package/dist/levels/compile/contexts.js +18 -0
  190. package/dist/levels/compile/contexts.js.map +1 -0
  191. package/dist/levels/compile/index.d.ts +39 -0
  192. package/dist/levels/compile/index.js +185 -0
  193. package/dist/levels/compile/index.js.map +1 -0
  194. package/dist/levels/compile/pipelines.d.ts +26 -0
  195. package/dist/levels/compile/pipelines.js +177 -0
  196. package/dist/levels/compile/pipelines.js.map +1 -0
  197. package/dist/levels/compiles.d.ts +29 -0
  198. package/dist/levels/compiles.js +50 -0
  199. package/dist/levels/compiles.js.map +1 -0
  200. package/dist/levels/draw.d.ts +102 -0
  201. package/dist/levels/draw.js +401 -0
  202. package/dist/levels/draw.js.map +1 -0
  203. package/dist/levels/fade.d.ts +69 -0
  204. package/dist/levels/fade.js +150 -0
  205. package/dist/levels/fade.js.map +1 -0
  206. package/dist/levels/index.d.ts +99 -0
  207. package/dist/levels/index.js +155 -0
  208. package/dist/levels/index.js.map +1 -0
  209. package/dist/levels/item.d.ts +38 -0
  210. package/dist/levels/item.js +97 -0
  211. package/dist/levels/item.js.map +1 -0
  212. package/dist/levels/load.d.ts +31 -0
  213. package/dist/levels/load.js +142 -0
  214. package/dist/levels/load.js.map +1 -0
  215. package/dist/levels/manager.d.ts +159 -0
  216. package/dist/levels/manager.js +339 -0
  217. package/dist/levels/manager.js.map +1 -0
  218. package/dist/{material.js → levels/material.js} +13 -7
  219. package/dist/levels/material.js.map +1 -0
  220. package/dist/levels/renderer.d.ts +29 -0
  221. package/dist/levels/renderer.js +41 -0
  222. package/dist/levels/renderer.js.map +1 -0
  223. package/dist/levels/requests.d.ts +49 -0
  224. package/dist/levels/requests.js +95 -0
  225. package/dist/levels/requests.js.map +1 -0
  226. package/dist/levels/screen.d.ts +14 -0
  227. package/dist/levels/screen.js +21 -0
  228. package/dist/levels/screen.js.map +1 -0
  229. package/dist/levels/wait.d.ts +27 -0
  230. package/dist/levels/wait.js +61 -0
  231. package/dist/levels/wait.js.map +1 -0
  232. package/dist/loader/build.d.ts +10 -0
  233. package/dist/loader/build.js +28 -0
  234. package/dist/loader/build.js.map +1 -0
  235. package/dist/loader/cache.d.ts +12 -15
  236. package/dist/loader/cache.js +64 -33
  237. package/dist/loader/cache.js.map +1 -1
  238. package/dist/loader/context.d.ts +46 -0
  239. package/dist/loader/context.js +91 -0
  240. package/dist/loader/context.js.map +1 -0
  241. package/dist/loader/dependencies.d.ts +32 -0
  242. package/dist/loader/dependencies.js +113 -0
  243. package/dist/loader/dependencies.js.map +1 -0
  244. package/dist/loader/index.d.ts +62 -0
  245. package/dist/loader/index.js +284 -0
  246. package/dist/loader/index.js.map +1 -0
  247. package/dist/loader/level.d.ts +41 -0
  248. package/dist/loader/level.js +140 -0
  249. package/dist/loader/level.js.map +1 -0
  250. package/dist/loader/request.d.ts +87 -0
  251. package/dist/loader/request.js +59 -0
  252. package/dist/loader/request.js.map +1 -0
  253. package/dist/loader/url.d.ts +16 -0
  254. package/dist/loader/url.js +35 -0
  255. package/dist/loader/url.js.map +1 -0
  256. package/dist/lod.d.ts +79 -49
  257. package/dist/lod.js +209 -153
  258. package/dist/lod.js.map +1 -1
  259. package/dist/meshopt.d.ts +6 -0
  260. package/dist/meshopt.js +19 -0
  261. package/dist/meshopt.js.map +1 -0
  262. package/dist/simplify/cards.d.ts +14 -0
  263. package/dist/simplify/cards.js +128 -0
  264. package/dist/simplify/cards.js.map +1 -0
  265. package/dist/{simplify.d.ts → simplify/index.d.ts} +7 -4
  266. package/dist/{simplify.js → simplify/index.js} +42 -22
  267. package/dist/simplify/index.js.map +1 -0
  268. package/dist/{bounds.d.ts → source/detail.d.ts} +4 -13
  269. package/dist/{bounds.js → source/detail.js} +62 -32
  270. package/dist/source/detail.js.map +1 -0
  271. package/dist/source/{error.d.ts → deviation.d.ts} +3 -3
  272. package/dist/source/{error.js → deviation.js} +12 -12
  273. package/dist/source/deviation.js.map +1 -0
  274. package/dist/source/{shading.d.ts → evaluator.d.ts} +10 -10
  275. package/dist/source/{shading.js → evaluator.js} +5 -5
  276. package/dist/source/evaluator.js.map +1 -0
  277. package/dist/source/{trace.d.ts → raycaster.d.ts} +6 -6
  278. package/dist/source/{trace.js → raycaster.js} +8 -8
  279. package/dist/source/raycaster.js.map +1 -0
  280. package/dist/source/{texels.d.ts → sampler.d.ts} +1 -1
  281. package/dist/source/{texels.js → sampler.js} +3 -3
  282. package/dist/source/sampler.js.map +1 -0
  283. package/dist/source/source.d.ts +2 -2
  284. package/dist/source/source.js +2 -2
  285. package/dist/source/source.js.map +1 -1
  286. package/dist/source/surface.js +1 -1
  287. package/dist/source/surface.js.map +1 -1
  288. package/dist/task.d.ts +6 -0
  289. package/dist/{pause.js → task.js} +5 -5
  290. package/dist/task.js.map +1 -0
  291. package/dist/three.d.ts +20 -19
  292. package/dist/three.js +29 -35
  293. package/dist/three.js.map +1 -1
  294. package/dist/tsl.d.ts +10 -14
  295. package/dist/tsl.js +12 -47
  296. package/dist/tsl.js.map +1 -1
  297. package/dist/typegpu.d.ts +37 -0
  298. package/dist/typegpu.js +37 -0
  299. package/dist/typegpu.js.map +1 -0
  300. package/dist/wind/bounds.js +2 -2
  301. package/dist/wind/bounds.js.map +1 -1
  302. package/dist/wind/hooks.d.ts +7 -4
  303. package/dist/wind/hooks.js +5 -2
  304. package/dist/wind/hooks.js.map +1 -1
  305. package/dist/wind/index.d.ts +4 -4
  306. package/dist/wind/index.js +43 -40
  307. package/dist/wind/index.js.map +1 -1
  308. package/dist/wind/sway.d.ts +4 -1
  309. package/dist/wind/sway.js +4 -1
  310. package/dist/wind/sway.js.map +1 -1
  311. package/package.json +24 -8
  312. package/skills/lod/SKILL.md +147 -108
  313. package/dist/bounds.js.map +0 -1
  314. package/dist/fade.d.ts +0 -44
  315. package/dist/fade.js +0 -78
  316. package/dist/fade.js.map +0 -1
  317. package/dist/impostor/cache.d.ts +0 -15
  318. package/dist/impostor/cache.js +0 -44
  319. package/dist/impostor/cache.js.map +0 -1
  320. package/dist/instanced/args.d.ts +0 -20
  321. package/dist/instanced/args.js +0 -77
  322. package/dist/instanced/args.js.map +0 -1
  323. package/dist/instanced/bounds.d.ts +0 -50
  324. package/dist/instanced/bounds.js +0 -271
  325. package/dist/instanced/bounds.js.map +0 -1
  326. package/dist/instanced/dirty.d.ts +0 -12
  327. package/dist/instanced/dirty.js +0 -61
  328. package/dist/instanced/dirty.js.map +0 -1
  329. package/dist/instanced/select.d.ts +0 -84
  330. package/dist/instanced/select.js +0 -264
  331. package/dist/instanced/select.js.map +0 -1
  332. package/dist/lazy.d.ts +0 -64
  333. package/dist/lazy.js +0 -187
  334. package/dist/lazy.js.map +0 -1
  335. package/dist/levels.d.ts +0 -72
  336. package/dist/levels.js +0 -146
  337. package/dist/levels.js.map +0 -1
  338. package/dist/loader/dispose.d.ts +0 -3
  339. package/dist/loader/dispose.js +0 -25
  340. package/dist/loader/dispose.js.map +0 -1
  341. package/dist/loader.d.ts +0 -99
  342. package/dist/loader.js +0 -234
  343. package/dist/loader.js.map +0 -1
  344. package/dist/material.js.map +0 -1
  345. package/dist/pause.d.ts +0 -6
  346. package/dist/pause.js.map +0 -1
  347. package/dist/renderer.d.ts +0 -21
  348. package/dist/renderer.js +0 -83
  349. package/dist/renderer.js.map +0 -1
  350. package/dist/simplify.js.map +0 -1
  351. package/dist/source/error.js.map +0 -1
  352. package/dist/source/shading.js.map +0 -1
  353. package/dist/source/texels.js.map +0 -1
  354. package/dist/source/trace.js.map +0 -1
  355. package/dist/view.d.ts +0 -30
  356. package/dist/view.js +0 -75
  357. package/dist/view.js.map +0 -1
  358. /package/dist/{material.d.ts → levels/material.d.ts} +0 -0
@@ -1,573 +1,290 @@
1
- import { Box3, InstancedMesh, Matrix4, Object3D, Ray, Sphere, Vector3, } from "three/webgpu";
2
- import { measureLevels } from "../bounds.js";
3
- import { disposeTree } from "../dispose.js";
4
- import { drawOf, setDraw } from "../fade.js";
5
- import { placeInterior } from "../baked/interior/material.js";
6
- import { impostorInstances } from "../impostor/mesh/index.js";
7
- import { Level, cloneLevels, copyThresholds, estimate, fadeProgress, levelAt, tintLevels, validate } from "../levels.js";
1
+ import { Box3, Object3D, Sphere } from "three/webgpu";
2
+ import { at } from "../at.js";
3
+ import { disposeCopies } from "../dispose.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";
8
8
  import { Lod } from "../lod.js";
9
- import { available, fallbacks, inView } from "../lazy.js";
10
- import { gpuRenderer, inShadowPass, isLazyImpostor, isImpostorMesh, isInstancedMesh, isLOD, isMesh, rejectSkinned, sameClass } from "../three.js";
11
- import { TYPICAL_HEIGHT, View, viewportHeight, watchViewport } from "../view.js";
12
- import { WIND_BOUNDS } from "../wind/hooks.js";
13
- import { copyWind, refreshWind } from "../wind/index.js";
14
- import { groupMaterial, indirect, placed, ranges } from "./meshes.js";
15
- import { Bounds, stretch } from "./bounds.js";
16
- import { Dirty } from "./dirty.js";
17
- import { ARGS_BYTES, Selection } from "./select.js";
18
- const IDENTITY = new Matrix4();
19
- const view = new View();
20
- const inverse = new Matrix4();
21
- const ray = new Ray();
22
- const sphere = new Sphere();
23
- const instance = new Matrix4();
24
- const world = new Matrix4();
25
- const box = new Box3();
26
- const hits = [];
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;
27
23
  /**
28
- * Selections due before the next draw of any InstancedLod: the first level mesh to draw
29
- * runs them all in one compute call (each call has a cost of its own: an encoder, a
30
- * pass, a submit). Each renderer runs those its own buffers are not current with.
31
- */
32
- const pending = new Set();
33
- /**
34
- * `count` copies of one object with levels of detail, each switched by its own height on
35
- * screen: `Lod` for instances. The GPU sorts the instances into levels once per view
36
- * (see select.ts) and every mesh of a level draws the instances it holds in one indirect
37
- * draw; an instance near a switch is in both neighbours and they split its pixels with
38
- * the dither. Thresholds, `forceLevel`, `Lod.quality`, `Lod.debug` and shadows work as
39
- * for `Lod`; nothing of an instance is drawn into a directional light's shadow map whose
40
- * texels are wider than it. Each level is culled by the crowd's bounds, tightened for
41
- * finer levels to the region near the camera where their instances can draw.
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.
31
+ *
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.
42
36
  *
43
- * Instance matrices are model transforms of the object, as for an InstancedMesh; set
44
- * them with `setMatrixAt`, and instance colours with `setColorAt`. An instance at scale 0
45
- * draws nothing. Its own transform (and its parents') should scale uniformly; instances
46
- * may scale any way.
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.
47
40
  *
48
- * Levels are templates, read when it is built: their meshes are drawn instanced, not
49
- * added to the scene. They cannot contain skinned meshes, InstancedMeshes (other than an
50
- * ImpostorMesh) or nested Lods. To build a hierarchy on top, leave level 0 empty (see
51
- * `Lod`) and draw the detail of the instances `levelOf` reports at level 0 yourself.
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.
52
45
  */
53
46
  export class InstancedLod extends Object3D {
47
+ gpu;
54
48
  isLOD = true;
55
49
  isInstancedLod = true;
56
50
  /** Let the renderer call `update` with its camera every frame. */
57
51
  autoUpdate = true;
58
- /** 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. */
59
53
  forceLevel = null;
60
- levels;
61
- count;
62
- entries = [];
63
- /** Per level, the meshes drawing it. */
64
- meshes = [];
65
- probes = [];
66
- selection = null;
67
- fallback = [];
68
- installed = new Set();
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;
69
62
  disposed = false;
70
- /** Instance matrices, 16 floats each: the selection uploads them from here. */
71
- matrices;
72
- /** Instance colours, 4 floats each, once `setColorAt` was called. */
73
- colors = null;
74
- /** Each instance's bounding sphere, centre and radius, in the InstancedLod's space. */
75
- spheres;
76
- /** Bounding sphere of the source in an instance's space. */
77
- sphere = new Sphere();
78
- /** Every placed level, kept separate from the source sphere used for LOD selection. */
79
- envelope = new Box3();
80
- rest = new Sphere();
81
- /** Around every instance; each level may use tighter bounds. */
82
- bounds = new Sphere();
83
- /** Per level, what its meshes are culled by: `bounds`, or less where its instances must be near the camera. */
84
- reaches = [];
85
- /** Logical demand at the initial default viewport, separate from first-draw visibility. */
86
- demand = [];
87
- /** Source-radius padding for the shadow caster cutoff; culling pads every level separately. */
88
- padding = 0;
89
- matrixChanges;
90
- colorChanges;
91
- blocks;
92
- last = { selected: false, eye: new Vector3(), scale: 0, ortho: false, fades: false, forced: null };
93
- /** `levels[0]` is the source. Empty only as the target of `copy`. */
94
- 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 = {}) {
95
72
  super();
96
- if (!Number.isInteger(count) || count < 0)
97
- throw new Error(`InstancedLod: count must be an integer >= 0, got ${count}`);
98
- this.levels = this.entries;
99
- this.count = count;
100
- this.matrices = new Float32Array(count * 16);
101
- this.spheres = new Float32Array(count * 4);
102
- for (let i = 0; i < count; i++)
103
- IDENTITY.toArray(this.matrices, i * 16);
104
- this.matrixChanges = new Dirty(count);
105
- this.colorChanges = new Dirty(count);
106
- this.matrixChanges.all();
107
- const draws = levels.length > 0 ? this.build(levels) : 0;
108
- this.blocks = new Bounds(this.matrices, this.spheres, this.sphere, this.bounds, draws, this.rest);
109
- }
110
- build(objects, rebuilding = false) {
111
- const [source] = objects;
112
- if (!source)
113
- throw new Error("InstancedLod: needs at least the source level");
114
- if (isLazyImpostor(source))
115
- throw new Error("InstancedLod: the source level must be eager");
116
- for (const object of objects) {
117
- if (isLazyImpostor(object)) {
118
- object.materialize();
119
- }
120
- if (available(object))
121
- this.installed.add(object);
122
- }
123
- this.fallback = fallbacks(objects);
124
- for (const object of objects) {
125
- rejectSkinned(object, "InstancedLod");
126
- object.traverse((node) => {
127
- if (isLOD(node))
128
- throw new Error("InstancedLod: levels cannot contain a Lod or InstancedLod; nest them with Lod");
129
- if (isInstancedMesh(node) && !isImpostorMesh(node))
130
- throw new Error(`InstancedLod: levels cannot contain an InstancedMesh ("${node.name}")`);
131
- });
132
- }
133
- const { own, placed } = measureLevels(objects, "InstancedLod");
134
- this.sphere.copy(placed);
135
- if (!rebuilding) {
136
- objects.forEach((object, i) => this.entries.push(new Level(object, i, false, this.entries)));
137
- estimate(this.entries, 2 * own.radius);
138
- }
139
- validate(this.entries);
140
- // Every template mesh, split per material group: an indirect draw has one range.
141
- const parts = objects.map((object, level) => {
142
- // A template's own transform places it in the instance's space, as a Lod's child.
143
- object.updateWorldMatrix(true, true);
144
- const outside = object.parent ? object.parent.matrixWorld.clone().invert() : new Matrix4();
145
- const found = [];
146
- object.traverse((node) => {
147
- if (!isMesh(node))
148
- return;
149
- const placement = outside.clone().multiply(node.matrixWorld);
150
- if (level === 0)
151
- this.probes.push({ mesh: node, placement });
152
- ranges(node).forEach((range, group) => found.push({ template: node, placement, group, draw: { level, ...range } }));
153
- });
154
- return found;
155
- });
156
- const draws = parts.flat().map((part) => part.draw);
157
- const selection = this.selection ??
158
- new Selection(this.matrices, this.sphere, parts.map((found, i) => found.length > 0 || isLazyImpostor(at(objects, i))), draws, this.fallback);
159
- if (this.selection)
160
- selection.replace(draws, this.fallback);
161
- this.selection = selection;
162
- if (this.colors)
163
- this.useColors(selection, this.colors);
164
- let k = 0;
165
- parts.forEach((found, i) => {
166
- const level = at(this.entries, i);
167
- this.reaches[i] ??= new Sphere();
168
- this.demand[i] ??= new Sphere();
169
- const previous = this.meshes[i];
170
- if (previous?.length && previous.length !== found.length)
171
- throw new Error("InstancedLod: an existing level's meshes changed");
172
- this.meshes[i] = found.map((part, index) => {
173
- const offset = k++ * ARGS_BYTES, mesh = previous?.[index];
174
- if (!mesh)
175
- return this.instance(level, part.template, part.placement, part.group, offset, selection);
176
- this.bind(mesh, level, selection, offset);
177
- return mesh;
178
- });
179
- });
180
- this.boundLevels();
181
- if (!rebuilding)
182
- for (const object of objects)
183
- if (isLazyImpostor(object))
184
- object.retain();
185
- 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;
186
82
  }
187
- /** Install ready atlas templates atomically before selecting this view. */
188
- refresh() {
189
- if (this.installed.size === this.entries.length)
190
- return;
191
- let changed = false;
192
- for (const { object } of this.entries) {
193
- if (!isLazyImpostor(object) || this.installed.has(object))
194
- continue;
195
- object.materialize();
196
- changed ||= available(object);
197
- }
198
- if (!changed)
199
- return;
200
- if (this.selection)
201
- pending.delete(this.selection);
202
- this.probes.length = 0;
203
- this.envelope.makeEmpty();
204
- const draws = this.build(this.entries.map((entry) => entry.object), true);
205
- this.blocks = new Bounds(this.matrices, this.spheres, this.sphere, this.bounds, draws, this.rest);
206
- this.matrixChanges.all();
207
- this.measure();
208
- refreshWind(this);
209
- this.updateWorldMatrix(true, true);
210
- this.dispatchEvent({ type: "levelschange" });
211
- }
212
- /** Bound what the level meshes draw, including template placement and impostor boxes. */
213
- boundLevels() {
214
- for (const meshes of this.meshes) {
215
- for (const mesh of meshes) {
216
- if (!isImpostorMesh(mesh))
217
- mesh.geometry.computeBoundingBox();
218
- const bounds = mesh.geometry.boundingBox;
219
- if (!bounds)
220
- throw new Error("InstancedLod: a level mesh has no geometry bounds");
221
- box.copy(bounds);
222
- if (isImpostorMesh(mesh))
223
- box.applyMatrix4(mesh.placement);
224
- this.envelope.union(box);
225
- }
226
- }
227
- this.rest.copy(this.sphere);
228
- if (this.envelope.isEmpty())
229
- return;
230
- const { center } = this.rest;
231
- const { min, max } = this.envelope;
232
- 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))));
83
+ get pending() {
84
+ return this.built.loads.pending;
233
85
  }
234
- /** Material group `group` of `template` drawn for every instance in `level`'s slots, from indirect draw arguments at byte `offset`. */
235
- instance(level, template, placement, group, offset, selection) {
236
- // An impostor card is placed in the shader; any other mesh draws its geometry moved
237
- // into the instance's space, so the instance matrix is all it needs.
238
- const mesh = isImpostorMesh(template)
239
- ? impostorInstances(template, this.count, placement)
240
- : new InstancedMesh(placed(template.geometry, placement), groupMaterial(template, group), this.count);
241
- mesh.geometry = indirect(mesh.geometry, selection, offset);
242
- this.bind(mesh, level, selection, offset);
243
- if (!isImpostorMesh(template))
244
- placeInterior(mesh, placement);
245
- mesh.name = template.name;
246
- mesh.castShadow = template.castShadow;
247
- mesh.receiveShadow = template.receiveShadow;
248
- this.reaches[level.index] ??= this.bounds.clone();
249
- mesh.boundingSphere = this.reaches[level.index] ?? null;
250
- const previous = mesh.onBeforeRender;
251
- // Selected once per view, before the first level mesh draws: in the view, or in a
252
- // shadow pass that comes first. The GPU runs it before anything drawn after it.
253
- mesh.onBeforeRender = (renderer, scene, camera, geometry, material, group) => {
254
- previous.call(mesh, renderer, scene, camera, geometry, material, group);
255
- if (pending.size === 0)
256
- return;
257
- const gpu = gpuRenderer(renderer);
258
- // Each level's draws start at its slots (see select.ts).
259
- if (!gpu.hasFeature("indirect-first-instance"))
260
- throw new Error("InstancedLod: needs the GPU feature indirect-first-instance, which this device lacks");
261
- const passes = [...pending].flatMap((due) => due.take(gpu));
262
- pending.clear();
263
- if (passes.length > 0)
264
- gpu.compute(passes);
265
- };
266
- watchViewport(mesh);
267
- this.add(mesh);
268
- return mesh;
86
+ get error() {
87
+ return this.built.loads.error;
269
88
  }
270
- bind(mesh, level, selection, offset) {
271
- mesh.geometry.setIndirect(selection.args, selection.argsOffset + offset);
272
- mesh.instanceMatrix = selection.slots.matrices;
273
- mesh.instanceColor = selection.slots.colors;
274
- setDraw(mesh, { kind: "instances", fades: selection.slots.fades, radius: this.drawRadius, tint: level.tint });
89
+ /** Where the item of levels a camera misses goes (see `LodOptions.manager`). */
90
+ get manager() {
91
+ return this.built.loads.item.manager;
275
92
  }
276
- setMatrixAt(index, matrix) {
277
- this.check(index);
278
- matrix.toArray(this.matrices, index * 16);
279
- this.matrixChanges.add(index);
93
+ set manager(manager) {
94
+ this.built.loads.item.given = manager;
280
95
  }
281
- getMatrixAt(index, matrix) {
282
- this.check(index);
283
- return matrix.fromArray(this.matrices, index * 16);
96
+ /** Its chunks, in the order they were added. */
97
+ get chunks() {
98
+ return this.built.chunks.keys();
284
99
  }
285
- /** Tint instance `index` by `color`, as InstancedMesh.setColorAt does, in every level. */
286
- setColorAt(index, color) {
287
- this.check(index);
288
- if (!this.colors) {
289
- this.colors = new Float32Array(this.count * 4).fill(1);
290
- if (this.selection)
291
- this.useColors(this.selection, this.colors);
292
- }
293
- color.toArray(this.colors, index * 4);
294
- this.colorChanges.add(index);
295
- }
296
- /** Instance `index`'s colour: white unless set. */
297
- getColorAt(index, color) {
298
- this.check(index);
299
- return this.colors ? color.fromArray(this.colors, index * 4) : color.setRGB(1, 1, 1);
300
- }
301
- /** Carry `colors` with the instances from now on: every level's meshes draw them. */
302
- useColors(selection, colors) {
303
- selection.useColors(colors);
304
- for (const mesh of this.meshes.flat()) {
305
- mesh.instanceColor = selection.slots.colors;
306
- // Instance colours change the shader.
307
- for (const material of [mesh.material].flat())
308
- material.needsUpdate = true;
309
- }
310
- this.colorChanges.all();
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 };
311
125
  }
312
126
  /**
313
- * The finest level instance `index` drew in the last update, or null before the first
314
- * (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`).
315
131
  */
316
- levelOf(index) {
317
- this.check(index);
318
- const { last } = this;
319
- if (!last.selected)
320
- return null;
321
- this.measure();
322
- const offset = index * 4;
323
- const r = at(this.spheres, offset + 3);
324
- if (r === 0)
325
- return null;
326
- let height = 2 * r * last.scale;
327
- if (!last.ortho) {
328
- const x = this.spheres[offset];
329
- const y = this.spheres[offset + 1];
330
- const z = this.spheres[offset + 2];
331
- if (x === undefined || y === undefined || z === undefined)
332
- throw new Error(`InstancedLod: missing sphere ${index}`);
333
- const dx = x - last.eye.x;
334
- const dy = y - last.eye.y;
335
- const dz = z - last.eye.z;
336
- height /= Math.max(Math.sqrt(dx * dx + dy * dy + dz * dz), 1e-6);
337
- }
338
- const level = levelAt(this.entries, height, last.forced);
339
- const selected = last.fades && fadeProgress(this.entries, level, height) > 0 ? level.index - 1 : level.index;
340
- return this.fallback[selected] ?? selected;
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;
341
141
  }
342
- check(index) {
343
- if (!Number.isInteger(index) || index < 0 || index >= this.count)
344
- throw new Error(`InstancedLod: index ${index} is not in 0..${this.count - 1}`);
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();
345
147
  }
346
- /** Source radius padded for the level shaders' shadow caster cutoff. */
347
- get drawRadius() {
348
- return this.sphere.radius * (1 + this.padding);
148
+ /** Bound every chunk's instances, survivors grown. */
149
+ measureBounds() {
150
+ this.built.chunks.bound(this.cullDistance?.scaleSurvivors ? MAX_SURVIVOR_SCALE : 1);
349
151
  }
350
- [WIND_BOUNDS](padding, slope, baseY) {
351
- this.padding = padding / this.sphere.radius;
352
- for (const mesh of this.meshes.flat()) {
353
- const draw = drawOf(mesh);
354
- if (draw?.kind === "instances")
355
- draw.radius = this.drawRadius;
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;
356
160
  }
357
- // The wind shape comes from the source, but taller/offset levels can sway farther.
358
- const reach = slope * Math.max(Math.abs(this.envelope.min.y - baseY), Math.abs(this.envelope.max.y - baseY));
359
- this.blocks.pad(this.rest.radius > 0 ? reach / this.rest.radius : 0);
360
- this.blocks.refit();
161
+ this.measureBounds();
361
162
  }
362
- /** Measure only changed matrices; matrix and colour uploads have separate ranges. */
363
- measure() {
364
- if (this.matrixChanges.empty && this.colorChanges.empty)
365
- return;
366
- this.measureChanges();
367
- }
368
- // Keep the closure-bearing work out of the unchanged path: V8 can allocate a
369
- // lexical context on entry even when a function returns before its callbacks.
370
- measureChanges() {
371
- const { selection } = this;
372
- const matricesChanged = !this.matrixChanges.empty;
373
- this.matrixChanges.drain((start, end) => {
374
- this.blocks.measure(start, end);
375
- if (selection)
376
- upload(selection.matrices, start, end);
377
- });
378
- this.colorChanges.drain((start, end) => {
379
- if (selection?.colors)
380
- upload(selection.colors, start, end);
381
- });
382
- selection?.changed();
383
- if (matricesChanged)
384
- this.blocks.refit();
385
- }
386
- /** Sort the instances into levels for `camera`; three's renderer calls this itself while `autoUpdate`. */
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`). */
387
164
  update(camera) {
388
- if (this.disposed || inShadowPass(this))
165
+ if (this.disposed || this.levels.length === 0)
389
166
  return;
390
- this.refresh();
391
- const { entries, selection } = this;
392
- if (!selection)
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)))
393
170
  return;
394
- validate(entries);
395
- if (this.forceLevel !== null && !entries[this.forceLevel])
396
- throw new Error(`Lod: forceLevel ${this.forceLevel} is not a level`);
397
- this.measure();
398
- const height = viewportHeight(camera);
399
- view.begin(camera, height ?? TYPICAL_HEIGHT, this.forceLevel);
400
- tintLevels(entries, Lod.debug);
401
- const worldScale = this.matrixWorld.getMaxScaleOnAxis();
402
- const scale = view.scale * Lod.quality * (view.ortho ? worldScale : 1);
403
- const { last } = this;
404
- last.eye.copy(view.eye).applyMatrix4(inverse.copy(this.matrixWorld).invert());
405
- Object.assign(last, { selected: true, scale, ortho: view.ortho, fades: view.fades, forced: this.forceLevel });
406
- const stable = this.blocks.select(entries, last, this.reaches, height !== null, this.matrixWorld);
407
- if (this.installed.size < entries.length) {
408
- let demand = this.reaches;
409
- if (height === null) {
410
- // Every mesh must get a first chance to report its viewport. That conservative
411
- // visibility is not a reason to load every lazy tier: use the actual default
412
- // selection, including forced and empty levels, for initial atlas demand.
413
- demand = this.demand;
414
- this.blocks.select(entries, last, demand, true, this.matrixWorld);
415
- }
416
- for (let i = this.reaches.length - 1; i >= 0; i--) {
417
- const reach = at(this.reaches, i), object = at(entries, i).object;
418
- if (isLazyImpostor(object) && object.status === "idle" && inView(camera, this.matrixWorld, at(demand, i)))
419
- object.request();
420
- const target = this.fallback[i] ?? i;
421
- if (target !== i) {
422
- at(this.reaches, target).union(reach);
423
- reach.makeEmpty();
424
- }
425
- }
426
- }
427
- selection.aim(last.eye, scale, view.ortho, view.fades, this.forceLevel, entries, stable);
428
- // Selected only when something may draw: an InstancedLod with every level hidden
429
- // keeps its last selection.
430
- if (this.showLevels())
431
- pending.add(selection);
171
+ this.built.loads.guard(() => this.sort(camera, shadow));
432
172
  }
433
- /** Only levels whose conservative bounds contain a possible selected instance. */
434
- showLevels() {
435
- let shown = false;
436
- this.meshes.forEach((meshes, level) => {
437
- const visible = !this.reaches[level]?.isEmpty();
438
- for (const mesh of meshes)
439
- mesh.visible = visible;
440
- shown ||= visible && meshes.length > 0;
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,
441
210
  });
442
- return shown;
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);
443
231
  }
444
232
  /**
445
- * Hits every instance's source level, with `instanceId` set; `object` is this
446
- * 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.
447
236
  */
448
237
  raycast(raycaster, intersects) {
449
- ray.copy(raycaster.ray).applyMatrix4(inverse.copy(this.matrixWorld).invert());
450
- for (let i = 0; i < this.count; i++) {
451
- instance.fromArray(this.matrices, i * 16);
452
- sphere.center.copy(this.rest.center).applyMatrix4(instance);
453
- sphere.radius = this.rest.radius * stretch(instance);
454
- if (!ray.intersectsSphere(sphere))
455
- continue;
456
- for (const { mesh, placement } of this.probes) {
457
- if (!mesh.layers.test(raycaster.layers))
458
- continue;
459
- // The template's own raycast, as if it stood where this instance does.
460
- const own = mesh.matrixWorld;
461
- mesh.matrixWorld = world.multiplyMatrices(this.matrixWorld, instance).multiply(placement);
462
- hits.length = 0;
463
- mesh.raycast(raycaster, hits);
464
- mesh.matrixWorld = own;
465
- for (const hit of hits)
466
- intersects.push(Object.assign(hit, { object: this, instanceId: i }));
467
- }
468
- }
238
+ raycastInstances(this, { parts: this.built.meshes.source, rest: this.built.shape.rest }, this.built.chunks.cpu(), raycaster, intersects);
469
239
  return false;
470
240
  }
471
241
  /**
472
- * Copies `source`'s levels, instances and settings into this empty InstancedLod of the
473
- * 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.
474
245
  */
475
246
  copy(source) {
476
- if (this.entries.length > 0 || this.count !== source.count)
477
- 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 }));
478
251
  super.copy(source, false);
479
- this.matrices.set(source.matrices);
480
- this.colors = source.colors?.slice() ?? null;
481
- this.matrixChanges.all();
482
- if (this.colors)
483
- this.colorChanges.all();
484
- const draws = source.entries.length > 0 ? this.build(cloneLevels(source.entries)) : 0;
485
- this.blocks = new Bounds(this.matrices, this.spheres, this.sphere, this.bounds, draws, this.rest);
486
- copyThresholds(source.entries, this.entries);
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;
487
256
  this.forceLevel = source.forceLevel;
488
257
  this.autoUpdate = source.autoUpdate;
258
+ this.cullDistance = source.cullDistance && { ...source.cullDistance };
489
259
  copyWind(source, this);
260
+ for (const chunk of chunks)
261
+ this.addChunk(chunk);
490
262
  return this;
491
263
  }
492
264
  clone() {
493
- // three's clone builds `new this.constructor()`, which has no count.
494
- const clone = new InstancedLod([], this.count).copy(this);
265
+ const clone = new InstancedLod([], this.gpu).copy(this);
495
266
  if (!sameClass(this, clone))
496
267
  throw new Error("InstancedLod.clone: a subclass must override clone");
497
268
  return clone;
498
269
  }
499
270
  /**
500
- * Frees every level, the source included, and the instanced meshes. Clones share
501
- * geometry and textures, so dispose only once no clone draws any more.
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`).
502
274
  */
503
275
  dispose() {
504
276
  if (this.disposed)
505
277
  return;
506
278
  this.disposed = true;
507
- this.clearDraws();
508
- for (const { object } of this.entries)
509
- isLazyImpostor(object) ? object.release() : disposeTree(object);
510
- }
511
- clearDraws() {
512
- // An ImpostorMesh frees its own material and geometry; the other meshes' faded
513
- // materials go with the levels' originals.
514
- for (const mesh of this.meshes.flat()) {
515
- this.remove(mesh);
516
- mesh.dispose();
517
- if (!isImpostorMesh(mesh))
518
- mesh.geometry.dispose();
519
- }
520
- if (this.selection)
521
- pending.delete(this.selection);
522
- this.selection?.dispose();
523
- this.selection = null;
524
- this.meshes.length = 0;
525
- }
526
- }
527
- function at(values, i) {
528
- const value = values[i];
529
- if (value === undefined)
530
- throw new Error(`InstancedLod: index ${i} out of range`);
531
- return value;
532
- }
533
- /** Union pending uploads, including repeated edits while a crowd is not drawn. */
534
- function upload(attribute, start, end) {
535
- start *= attribute.itemSize;
536
- end *= attribute.itemSize;
537
- const ranges = attribute.updateRanges;
538
- let low = 0;
539
- let high = ranges.length;
540
- while (low < high) {
541
- const middle = (low + high) >>> 1;
542
- const range = at(ranges, middle);
543
- if (range.start + range.count < start)
544
- low = middle + 1;
545
- else
546
- high = middle;
547
- }
548
- const first = ranges[low];
549
- if (!first || first.start > end) {
550
- const range = { start, count: end - start };
551
- if (low === ranges.length)
552
- ranges.push(range);
553
- else
554
- ranges.splice(low, 0, range);
555
- }
556
- else {
557
- end = Math.max(end, first.start + first.count);
558
- first.start = Math.min(start, first.start);
559
- let next = low + 1;
560
- while (next < ranges.length) {
561
- const range = at(ranges, next);
562
- if (range.start > end)
563
- break;
564
- end = Math.max(end, range.start + range.count);
565
- next++;
566
- }
567
- first.count = end - first.start;
568
- if (next > low + 1)
569
- ranges.splice(low + 1, next - low - 1);
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)
285
+ if (object)
286
+ disposeCopies(object);
287
+ });
570
288
  }
571
- attribute.needsUpdate = true;
572
289
  }
573
290
  //# sourceMappingURL=index.js.map