@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.
- package/README.md +13 -70
- package/dist/at.d.ts +2 -0
- package/dist/at.js +8 -0
- package/dist/at.js.map +1 -0
- package/dist/bake.d.ts +22 -0
- package/dist/bake.js +88 -0
- package/dist/bake.js.map +1 -0
- package/dist/baked/codec.d.ts +5 -5
- package/dist/baked/codec.js +58 -158
- package/dist/baked/codec.js.map +1 -1
- package/dist/baked/coverage.d.ts +6 -3
- package/dist/baked/coverage.js +6 -3
- package/dist/baked/coverage.js.map +1 -1
- package/dist/baked/fields.d.ts +9 -0
- package/dist/baked/fields.js +22 -0
- package/dist/baked/fields.js.map +1 -0
- package/dist/baked/index.d.ts +16 -6
- package/dist/baked/index.js +23 -21
- package/dist/baked/index.js.map +1 -1
- package/dist/baked/interior/faces.d.ts +4 -3
- package/dist/baked/interior/faces.js +13 -10
- package/dist/baked/interior/faces.js.map +1 -1
- package/dist/baked/interior/material.d.ts +7 -10
- package/dist/baked/interior/material.js +29 -36
- package/dist/baked/interior/material.js.map +1 -1
- package/dist/baked/interior/rooms.js +1 -1
- package/dist/baked/interior/rooms.js.map +1 -1
- package/dist/baked/low.d.ts +2 -2
- package/dist/baked/low.js +9 -8
- package/dist/baked/low.js.map +1 -1
- package/dist/baked/raster.js +6 -6
- package/dist/baked/raster.js.map +1 -1
- package/dist/baked/textures.d.ts +17 -0
- package/dist/baked/textures.js +95 -0
- package/dist/baked/textures.js.map +1 -0
- package/dist/baked/transfer.js +19 -18
- package/dist/baked/transfer.js.map +1 -1
- package/dist/baked/unwrap.js +4 -3
- package/dist/baked/unwrap.js.map +1 -1
- package/dist/baked/xatlas.js.map +1 -1
- package/dist/codec/container.d.ts +8 -0
- package/dist/codec/container.js +63 -0
- package/dist/codec/container.js.map +1 -0
- package/dist/codec/mesh.d.ts +8 -0
- package/dist/codec/mesh.js +220 -0
- package/dist/codec/mesh.js.map +1 -0
- package/dist/dispose.d.ts +11 -4
- package/dist/dispose.js +39 -12
- package/dist/dispose.js.map +1 -1
- package/dist/gpu.d.ts +39 -0
- package/dist/gpu.js +41 -0
- package/dist/gpu.js.map +1 -0
- package/dist/impostor/bake/atlas.d.ts +4 -4
- package/dist/impostor/bake/atlas.js +12 -12
- package/dist/impostor/bake/atlas.js.map +1 -1
- package/dist/impostor/bake/fit.d.ts +8 -0
- package/dist/impostor/bake/fit.js +28 -0
- package/dist/impostor/bake/fit.js.map +1 -0
- package/dist/impostor/bake/index.d.ts +23 -14
- package/dist/impostor/bake/index.js +42 -134
- package/dist/impostor/bake/index.js.map +1 -1
- package/dist/impostor/bake/materials.d.ts +3 -3
- package/dist/impostor/bake/materials.js +60 -12
- package/dist/impostor/bake/materials.js.map +1 -1
- package/dist/impostor/bake/sheet.d.ts +8 -0
- package/dist/impostor/bake/sheet.js +90 -0
- package/dist/impostor/bake/sheet.js.map +1 -0
- package/dist/impostor/bake/source.d.ts +2 -2
- package/dist/impostor/bake/source.js +8 -19
- package/dist/impostor/bake/source.js.map +1 -1
- package/dist/impostor/codec.d.ts +5 -11
- package/dist/impostor/codec.js +37 -121
- package/dist/impostor/codec.js.map +1 -1
- package/dist/impostor/impostor.d.ts +31 -7
- package/dist/impostor/impostor.js +21 -6
- package/dist/impostor/impostor.js.map +1 -1
- package/dist/impostor/ktx2/encode.d.ts +26 -0
- package/dist/impostor/ktx2/encode.js +117 -0
- package/dist/impostor/ktx2/encode.js.map +1 -0
- package/dist/impostor/ktx2/transcode.d.ts +3 -0
- package/dist/impostor/ktx2/transcode.js +99 -0
- package/dist/impostor/ktx2/transcode.js.map +1 -0
- package/dist/impostor/ktx2/uastc.d.ts +7 -0
- package/dist/impostor/ktx2/uastc.js +64 -0
- package/dist/impostor/ktx2/uastc.js.map +1 -0
- package/dist/impostor/ktx2/wasm/encoder.d.ts +4 -0
- package/dist/impostor/ktx2/wasm/encoder.js +10 -0
- package/dist/impostor/ktx2/wasm/encoder.js.map +1 -0
- package/dist/impostor/ktx2/wasm/transcoder.d.ts +3 -0
- package/dist/impostor/ktx2/wasm/transcoder.js +9 -0
- package/dist/impostor/ktx2/wasm/transcoder.js.map +1 -0
- package/dist/impostor/ktx2/worker.d.ts +1 -0
- package/dist/impostor/ktx2/worker.js +12 -0
- package/dist/impostor/ktx2/worker.js.map +1 -0
- package/dist/impostor/mesh/card.d.ts +3 -3
- package/dist/impostor/mesh/card.js +11 -8
- package/dist/impostor/mesh/card.js.map +1 -1
- package/dist/impostor/mesh/index.d.ts +10 -7
- package/dist/impostor/mesh/index.js +15 -18
- package/dist/impostor/mesh/index.js.map +1 -1
- package/dist/impostor/mesh/material.d.ts +24 -13
- package/dist/impostor/mesh/material.js +67 -51
- package/dist/impostor/mesh/material.js.map +1 -1
- package/dist/impostor/mesh/surface.d.ts +20 -16
- package/dist/impostor/mesh/surface.js +81 -58
- package/dist/impostor/mesh/surface.js.map +1 -1
- package/dist/impostor/mesh/textures.d.ts +3 -3
- package/dist/impostor/mesh/textures.js +24 -34
- package/dist/impostor/mesh/textures.js.map +1 -1
- package/dist/impostor/octahedral.d.ts +1 -8
- package/dist/impostor/octahedral.js +3 -18
- package/dist/impostor/octahedral.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/instanced/camera.d.ts +46 -0
- package/dist/instanced/camera.js +70 -0
- package/dist/instanced/camera.js.map +1 -0
- package/dist/instanced/chunk.d.ts +97 -0
- package/dist/instanced/chunk.js +156 -0
- package/dist/instanced/chunk.js.map +1 -0
- package/dist/instanced/chunks.d.ts +60 -0
- package/dist/instanced/chunks.js +208 -0
- package/dist/instanced/chunks.js.map +1 -0
- package/dist/instanced/cull.d.ts +28 -0
- package/dist/instanced/cull.js +22 -0
- package/dist/instanced/cull.js.map +1 -0
- package/dist/instanced/index.d.ts +96 -92
- package/dist/instanced/index.js +229 -512
- package/dist/instanced/index.js.map +1 -1
- package/dist/instanced/key.d.ts +19 -0
- package/dist/instanced/key.js +45 -0
- package/dist/instanced/key.js.map +1 -0
- package/dist/instanced/levels.d.ts +60 -0
- package/dist/instanced/levels.js +130 -0
- package/dist/instanced/levels.js.map +1 -0
- package/dist/instanced/lists.d.ts +70 -0
- package/dist/instanced/lists.js +133 -0
- package/dist/instanced/lists.js.map +1 -0
- package/dist/instanced/measure.d.ts +28 -0
- package/dist/instanced/measure.js +203 -0
- package/dist/instanced/measure.js.map +1 -0
- package/dist/instanced/meshes.d.ts +46 -14
- package/dist/instanced/meshes.js +137 -22
- package/dist/instanced/meshes.js.map +1 -1
- package/dist/instanced/mirror.d.ts +20 -0
- package/dist/instanced/mirror.js +37 -0
- package/dist/instanced/mirror.js.map +1 -0
- package/dist/instanced/pages/buffers.d.ts +37 -0
- package/dist/instanced/pages/buffers.js +15 -0
- package/dist/instanced/pages/buffers.js.map +1 -0
- package/dist/instanced/pages/chunk.d.ts +51 -0
- package/dist/instanced/pages/chunk.js +81 -0
- package/dist/instanced/pages/chunk.js.map +1 -0
- package/dist/instanced/pages/index.d.ts +74 -0
- package/dist/instanced/pages/index.js +269 -0
- package/dist/instanced/pages/index.js.map +1 -0
- package/dist/instanced/pages/passes.d.ts +21 -0
- package/dist/instanced/pages/passes.js +52 -0
- package/dist/instanced/pages/passes.js.map +1 -0
- package/dist/instanced/pages/slots.d.ts +11 -0
- package/dist/instanced/pages/slots.js +90 -0
- package/dist/instanced/pages/slots.js.map +1 -0
- package/dist/instanced/pages/workgroup.d.ts +14 -0
- package/dist/instanced/pages/workgroup.js +38 -0
- package/dist/instanced/pages/workgroup.js.map +1 -0
- package/dist/instanced/raycast.d.ts +15 -0
- package/dist/instanced/raycast.js +46 -0
- package/dist/instanced/raycast.js.map +1 -0
- package/dist/instanced/rows.d.ts +108 -0
- package/dist/instanced/rows.js +209 -0
- package/dist/instanced/rows.js.map +1 -0
- package/dist/instanced/shape.d.ts +25 -0
- package/dist/instanced/shape.js +51 -0
- package/dist/instanced/shape.js.map +1 -0
- package/dist/instanced/sort.d.ts +63 -0
- package/dist/instanced/sort.js +162 -0
- package/dist/instanced/sort.js.map +1 -0
- package/dist/instanced/update.d.ts +30 -0
- package/dist/instanced/update.js +207 -0
- package/dist/instanced/update.js.map +1 -0
- package/dist/levels/bounds.d.ts +21 -0
- package/dist/levels/bounds.js +42 -0
- package/dist/levels/bounds.js.map +1 -0
- package/dist/levels/cameras.d.ts +48 -0
- package/dist/levels/cameras.js +122 -0
- package/dist/levels/cameras.js.map +1 -0
- package/dist/levels/compile/contexts.d.ts +30 -0
- package/dist/levels/compile/contexts.js +18 -0
- package/dist/levels/compile/contexts.js.map +1 -0
- package/dist/levels/compile/index.d.ts +39 -0
- package/dist/levels/compile/index.js +185 -0
- package/dist/levels/compile/index.js.map +1 -0
- package/dist/levels/compile/pipelines.d.ts +26 -0
- package/dist/levels/compile/pipelines.js +177 -0
- package/dist/levels/compile/pipelines.js.map +1 -0
- package/dist/levels/compiles.d.ts +29 -0
- package/dist/levels/compiles.js +50 -0
- package/dist/levels/compiles.js.map +1 -0
- package/dist/levels/draw.d.ts +102 -0
- package/dist/levels/draw.js +401 -0
- package/dist/levels/draw.js.map +1 -0
- package/dist/levels/fade.d.ts +69 -0
- package/dist/levels/fade.js +150 -0
- package/dist/levels/fade.js.map +1 -0
- package/dist/levels/index.d.ts +99 -0
- package/dist/levels/index.js +155 -0
- package/dist/levels/index.js.map +1 -0
- package/dist/levels/item.d.ts +38 -0
- package/dist/levels/item.js +97 -0
- package/dist/levels/item.js.map +1 -0
- package/dist/levels/load.d.ts +31 -0
- package/dist/levels/load.js +142 -0
- package/dist/levels/load.js.map +1 -0
- package/dist/levels/manager.d.ts +159 -0
- package/dist/levels/manager.js +339 -0
- package/dist/levels/manager.js.map +1 -0
- package/dist/{material.js → levels/material.js} +13 -7
- package/dist/levels/material.js.map +1 -0
- package/dist/levels/renderer.d.ts +29 -0
- package/dist/levels/renderer.js +41 -0
- package/dist/levels/renderer.js.map +1 -0
- package/dist/levels/requests.d.ts +49 -0
- package/dist/levels/requests.js +95 -0
- package/dist/levels/requests.js.map +1 -0
- package/dist/levels/screen.d.ts +14 -0
- package/dist/levels/screen.js +21 -0
- package/dist/levels/screen.js.map +1 -0
- package/dist/levels/wait.d.ts +27 -0
- package/dist/levels/wait.js +61 -0
- package/dist/levels/wait.js.map +1 -0
- package/dist/loader/build.d.ts +10 -0
- package/dist/loader/build.js +28 -0
- package/dist/loader/build.js.map +1 -0
- package/dist/loader/cache.d.ts +12 -15
- package/dist/loader/cache.js +64 -33
- package/dist/loader/cache.js.map +1 -1
- package/dist/loader/context.d.ts +46 -0
- package/dist/loader/context.js +91 -0
- package/dist/loader/context.js.map +1 -0
- package/dist/loader/dependencies.d.ts +32 -0
- package/dist/loader/dependencies.js +113 -0
- package/dist/loader/dependencies.js.map +1 -0
- package/dist/loader/index.d.ts +62 -0
- package/dist/loader/index.js +284 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/loader/level.d.ts +41 -0
- package/dist/loader/level.js +140 -0
- package/dist/loader/level.js.map +1 -0
- package/dist/loader/request.d.ts +87 -0
- package/dist/loader/request.js +59 -0
- package/dist/loader/request.js.map +1 -0
- package/dist/loader/url.d.ts +16 -0
- package/dist/loader/url.js +35 -0
- package/dist/loader/url.js.map +1 -0
- package/dist/lod.d.ts +79 -49
- package/dist/lod.js +209 -153
- package/dist/lod.js.map +1 -1
- package/dist/meshopt.d.ts +6 -0
- package/dist/meshopt.js +19 -0
- package/dist/meshopt.js.map +1 -0
- package/dist/simplify/cards.d.ts +14 -0
- package/dist/simplify/cards.js +128 -0
- package/dist/simplify/cards.js.map +1 -0
- package/dist/{simplify.d.ts → simplify/index.d.ts} +7 -4
- package/dist/{simplify.js → simplify/index.js} +42 -22
- package/dist/simplify/index.js.map +1 -0
- package/dist/{bounds.d.ts → source/detail.d.ts} +4 -13
- package/dist/{bounds.js → source/detail.js} +62 -32
- package/dist/source/detail.js.map +1 -0
- package/dist/source/{error.d.ts → deviation.d.ts} +3 -3
- package/dist/source/{error.js → deviation.js} +12 -12
- package/dist/source/deviation.js.map +1 -0
- package/dist/source/{shading.d.ts → evaluator.d.ts} +10 -10
- package/dist/source/{shading.js → evaluator.js} +5 -5
- package/dist/source/evaluator.js.map +1 -0
- package/dist/source/{trace.d.ts → raycaster.d.ts} +6 -6
- package/dist/source/{trace.js → raycaster.js} +8 -8
- package/dist/source/raycaster.js.map +1 -0
- package/dist/source/{texels.d.ts → sampler.d.ts} +1 -1
- package/dist/source/{texels.js → sampler.js} +3 -3
- package/dist/source/sampler.js.map +1 -0
- package/dist/source/source.d.ts +2 -2
- package/dist/source/source.js +2 -2
- package/dist/source/source.js.map +1 -1
- package/dist/source/surface.js +1 -1
- package/dist/source/surface.js.map +1 -1
- package/dist/task.d.ts +6 -0
- package/dist/{pause.js → task.js} +5 -5
- package/dist/task.js.map +1 -0
- package/dist/three.d.ts +20 -19
- package/dist/three.js +29 -35
- package/dist/three.js.map +1 -1
- package/dist/tsl.d.ts +10 -14
- package/dist/tsl.js +12 -47
- package/dist/tsl.js.map +1 -1
- package/dist/typegpu.d.ts +37 -0
- package/dist/typegpu.js +37 -0
- package/dist/typegpu.js.map +1 -0
- package/dist/wind/bounds.js +2 -2
- package/dist/wind/bounds.js.map +1 -1
- package/dist/wind/hooks.d.ts +7 -4
- package/dist/wind/hooks.js +5 -2
- package/dist/wind/hooks.js.map +1 -1
- package/dist/wind/index.d.ts +4 -4
- package/dist/wind/index.js +43 -40
- package/dist/wind/index.js.map +1 -1
- package/dist/wind/sway.d.ts +4 -1
- package/dist/wind/sway.js +4 -1
- package/dist/wind/sway.js.map +1 -1
- package/package.json +24 -8
- package/skills/lod/SKILL.md +147 -108
- package/dist/bounds.js.map +0 -1
- package/dist/fade.d.ts +0 -44
- package/dist/fade.js +0 -78
- package/dist/fade.js.map +0 -1
- package/dist/impostor/cache.d.ts +0 -15
- package/dist/impostor/cache.js +0 -44
- package/dist/impostor/cache.js.map +0 -1
- package/dist/instanced/args.d.ts +0 -20
- package/dist/instanced/args.js +0 -77
- package/dist/instanced/args.js.map +0 -1
- package/dist/instanced/bounds.d.ts +0 -50
- package/dist/instanced/bounds.js +0 -271
- package/dist/instanced/bounds.js.map +0 -1
- package/dist/instanced/dirty.d.ts +0 -12
- package/dist/instanced/dirty.js +0 -61
- package/dist/instanced/dirty.js.map +0 -1
- package/dist/instanced/select.d.ts +0 -84
- package/dist/instanced/select.js +0 -264
- package/dist/instanced/select.js.map +0 -1
- package/dist/lazy.d.ts +0 -64
- package/dist/lazy.js +0 -187
- package/dist/lazy.js.map +0 -1
- package/dist/levels.d.ts +0 -72
- package/dist/levels.js +0 -146
- package/dist/levels.js.map +0 -1
- package/dist/loader/dispose.d.ts +0 -3
- package/dist/loader/dispose.js +0 -25
- package/dist/loader/dispose.js.map +0 -1
- package/dist/loader.d.ts +0 -99
- package/dist/loader.js +0 -234
- package/dist/loader.js.map +0 -1
- package/dist/material.js.map +0 -1
- package/dist/pause.d.ts +0 -6
- package/dist/pause.js.map +0 -1
- package/dist/renderer.d.ts +0 -21
- package/dist/renderer.js +0 -83
- package/dist/renderer.js.map +0 -1
- package/dist/simplify.js.map +0 -1
- package/dist/source/error.js.map +0 -1
- package/dist/source/shading.js.map +0 -1
- package/dist/source/texels.js.map +0 -1
- package/dist/source/trace.js.map +0 -1
- package/dist/view.d.ts +0 -30
- package/dist/view.js +0 -75
- package/dist/view.js.map +0 -1
- /package/dist/{material.d.ts → levels/material.d.ts} +0 -0
package/dist/instanced/index.js
CHANGED
|
@@ -1,573 +1,290 @@
|
|
|
1
|
-
import { Box3,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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 {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
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
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
*
|
|
44
|
-
* them
|
|
45
|
-
*
|
|
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
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
|
52
|
+
/** Draw only this level, without fades, once the coarsest level that draws something has loaded. */
|
|
59
53
|
forceLevel = null;
|
|
60
|
-
levels
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
/**
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
277
|
-
this.
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
return
|
|
96
|
+
/** Its chunks, in the order they were added. */
|
|
97
|
+
get chunks() {
|
|
98
|
+
return this.built.chunks.keys();
|
|
284
99
|
}
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
*
|
|
314
|
-
*
|
|
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
|
-
|
|
317
|
-
this.
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
this.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
/**
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
[
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 ||
|
|
165
|
+
if (this.disposed || this.levels.length === 0)
|
|
389
166
|
return;
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
if (!
|
|
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
|
-
|
|
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
|
-
/**
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
this.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
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
|
|
446
|
-
* InstancedLod.
|
|
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
|
-
|
|
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,
|
|
473
|
-
*
|
|
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.
|
|
477
|
-
throw new Error("InstancedLod.copy: copy into an
|
|
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.
|
|
480
|
-
this.
|
|
481
|
-
this.
|
|
482
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
501
|
-
*
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|