@needle-tools/gltf-progressive 1.0.0-alpha.8 → 1.1.0-alpha
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/CHANGELOG.md +42 -0
- package/README.md +92 -0
- package/examples/modelviewer.html +27 -0
- package/examples/react-three-fiber/.prettierrc +10 -0
- package/examples/react-three-fiber/favicon.png +0 -0
- package/examples/react-three-fiber/index.html +24 -0
- package/examples/react-three-fiber/package-lock.json +4023 -0
- package/examples/react-three-fiber/package.json +34 -0
- package/examples/react-three-fiber/tsconfig.json +22 -0
- package/examples/react-three-fiber/vite.config.js +39 -0
- package/examples/threejs/index.html +51 -0
- package/examples/threejs/main.js +76 -0
- package/gltf-progressive.js +465 -318
- package/gltf-progressive.min.js +5 -3
- package/gltf-progressive.umd.cjs +5 -3
- package/lib/extension.d.ts +8 -2
- package/lib/extension.js +108 -15
- package/lib/index.d.ts +5 -4
- package/lib/index.js +6 -4
- package/lib/loaders.d.ts +10 -0
- package/lib/loaders.js +21 -2
- package/lib/lods_manager.d.ts +50 -3
- package/lib/lods_manager.js +354 -219
- package/lib/plugins/index.d.ts +1 -1
- package/lib/plugins/index.js +0 -1
- package/lib/plugins/modelviewer.js +7 -3
- package/lib/plugins/plugin.d.ts +4 -5
- package/lib/plugins/plugin.js +0 -6
- package/lib/utils.d.ts +13 -0
- package/lib/utils.js +24 -0
- package/package.json +1 -1
package/lib/lods_manager.js
CHANGED
|
@@ -2,13 +2,39 @@ import { Box3, Frustum, Matrix4, Mesh, Sphere, Vector3 } from "three";
|
|
|
2
2
|
import { NEEDLE_progressive } from "./extension.js";
|
|
3
3
|
import { createLoaders } from "./loaders.js";
|
|
4
4
|
import { getParam } from "./utils.js";
|
|
5
|
+
import { plugins } from "./plugins/plugin.js";
|
|
5
6
|
const debugProgressiveLoading = getParam("debugprogressive");
|
|
6
7
|
const suppressProgressiveLoading = getParam("noprogressive");
|
|
8
|
+
const $lodsManager = Symbol("Needle:LODSManager");
|
|
9
|
+
const levels = { mesh_lod: -1, texture_lod: -1 };
|
|
7
10
|
/**
|
|
8
11
|
* The LODsManager class is responsible for managing the LODs and progressive assets in the scene. It will automatically update the LODs based on the camera position, screen coverage and mesh density of the objects.
|
|
9
12
|
* It must be enabled by calling the `enable` method.
|
|
10
13
|
*
|
|
11
14
|
* Instead of using the LODs manager directly you can also call `useNeedleProgressive` to enable progressive loading for a GLTFLoader
|
|
15
|
+
*
|
|
16
|
+
* ### Plugins
|
|
17
|
+
* Use {@link LODsManager.addPlugin} to add a plugin to the LODsManager. A plugin can be used to hook into the LOD update process and modify the LOD levels or perform other actions.
|
|
18
|
+
*
|
|
19
|
+
* @example Adding a LODsManager to a Three.js scene:
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { LODsManager } from "@needle-tools/gltf-progressive";
|
|
22
|
+
* import { WebGLRenderer, Scene, Camera, Mesh } from "three";
|
|
23
|
+
*
|
|
24
|
+
* const renderer = new WebGLRenderer();
|
|
25
|
+
* const lodsManager = LODsManager.get(renderer);
|
|
26
|
+
* lodsManager.enable();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example Using the LODsManager with a GLTFLoader:
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { useNeedleProgressive } from "@needle-tools/gltf-progressive";
|
|
32
|
+
* import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
33
|
+
*
|
|
34
|
+
* const url = 'https://yourdomain.com/yourmodel.glb';
|
|
35
|
+
* const loader = new GLTFLoader();
|
|
36
|
+
* const lodsManager = useNeedleProgressive(url, renderer, loader);
|
|
37
|
+
* ```
|
|
12
38
|
*/
|
|
13
39
|
export class LODsManager {
|
|
14
40
|
/** Assign a function to draw debug lines for the LODs. This function will be called with the start and end position of the line and the color of the line when the `debugprogressive` query parameter is set.
|
|
@@ -18,9 +44,36 @@ export class LODsManager {
|
|
|
18
44
|
static getObjectLODState(object) {
|
|
19
45
|
return object.userData?.LOD_state;
|
|
20
46
|
}
|
|
47
|
+
static addPlugin(plugin) {
|
|
48
|
+
plugins.push(plugin);
|
|
49
|
+
}
|
|
50
|
+
static removePlugin(plugin) {
|
|
51
|
+
const index = plugins.indexOf(plugin);
|
|
52
|
+
if (index >= 0)
|
|
53
|
+
plugins.splice(index, 1);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
|
|
57
|
+
* @param renderer The renderer to get the LODsManager for.
|
|
58
|
+
* @returns The LODsManager instance.
|
|
59
|
+
*/
|
|
60
|
+
static get(renderer) {
|
|
61
|
+
if (renderer[$lodsManager]) {
|
|
62
|
+
return renderer[$lodsManager];
|
|
63
|
+
}
|
|
64
|
+
const lodsManager = new LODsManager(renderer);
|
|
65
|
+
return lodsManager;
|
|
66
|
+
}
|
|
21
67
|
renderer;
|
|
22
68
|
projectionScreenMatrix = new Matrix4();
|
|
23
69
|
cameraFrustrum = new Frustum();
|
|
70
|
+
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
|
|
71
|
+
get plugins() { return plugins; }
|
|
72
|
+
/**
|
|
73
|
+
* The target triangle density is the desired max amount of triangles on screen when the mesh is filling the screen.
|
|
74
|
+
* @default 200_000
|
|
75
|
+
*/
|
|
76
|
+
targetTriangleDensity = 200_000;
|
|
24
77
|
/**
|
|
25
78
|
* The update interval in frames. If set to 0, the LODs will be updated every frame. If set to 1, the LODs will be updated every second frame, etc.
|
|
26
79
|
*/
|
|
@@ -29,10 +82,11 @@ export class LODsManager {
|
|
|
29
82
|
* If set to true, the LODsManager will not update the LODs.
|
|
30
83
|
*/
|
|
31
84
|
pause = false;
|
|
32
|
-
plugins = [];
|
|
85
|
+
// readonly plugins: NEEDLE_progressive_plugin[] = [];
|
|
33
86
|
constructor(renderer) {
|
|
34
87
|
this.renderer = renderer;
|
|
35
88
|
}
|
|
89
|
+
_frame = 0;
|
|
36
90
|
_originalRender;
|
|
37
91
|
/**
|
|
38
92
|
* Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
|
|
@@ -44,15 +98,21 @@ export class LODsManager {
|
|
|
44
98
|
// Save the original render method
|
|
45
99
|
this._originalRender = this.renderer.render;
|
|
46
100
|
const self = this;
|
|
47
|
-
let frames = 0;
|
|
48
101
|
createLoaders(this.renderer);
|
|
49
102
|
this.renderer.render = function (scene, camera) {
|
|
50
|
-
|
|
103
|
+
// check if this render call is rendering to a texture or the canvas
|
|
104
|
+
// if it's rendering to a texture we don't want to update the LODs
|
|
105
|
+
// This might need to be loosened later - e.g. we might want to update LODs for a render texture - but then we need to store the last LOD level differently and we also might not want to perform all the plugin calls?
|
|
106
|
+
const renderTarget = self.renderer.getRenderTarget();
|
|
107
|
+
if (renderTarget == null) {
|
|
108
|
+
stack = 0;
|
|
109
|
+
self._frame += 1;
|
|
110
|
+
}
|
|
111
|
+
const frame = self._frame;
|
|
51
112
|
const stack_level = stack++;
|
|
52
113
|
self.onBeforeRender(scene, camera, stack_level, frame);
|
|
53
114
|
self._originalRender.call(this, scene, camera);
|
|
54
115
|
self.onAfterRender(scene, camera, stack_level, frame);
|
|
55
|
-
stack--;
|
|
56
116
|
};
|
|
57
117
|
}
|
|
58
118
|
disable() {
|
|
@@ -63,91 +123,116 @@ export class LODsManager {
|
|
|
63
123
|
}
|
|
64
124
|
onBeforeRender(_scene, _camera, _stack, _frame) {
|
|
65
125
|
}
|
|
66
|
-
onAfterRender(scene, camera,
|
|
67
|
-
if (suppressProgressiveLoading)
|
|
68
|
-
return;
|
|
126
|
+
onAfterRender(scene, camera, _stack, frame) {
|
|
69
127
|
if (this.pause)
|
|
70
128
|
return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
129
|
+
const renderList = this.renderer.renderLists.get(scene, 0);
|
|
130
|
+
const opaque = renderList.opaque;
|
|
131
|
+
let updateLODs = true;
|
|
132
|
+
// check if we're rendering a postprocessing pass
|
|
133
|
+
if (opaque.length === 1) {
|
|
134
|
+
const material = opaque[0].material;
|
|
135
|
+
// pmndrs postprocessing
|
|
136
|
+
if (material.name === "EffectMaterial") {
|
|
137
|
+
updateLODs = false;
|
|
138
|
+
}
|
|
139
|
+
// builtin three postprocessing
|
|
140
|
+
else if (material.name === "CopyShader") {
|
|
141
|
+
updateLODs = false;
|
|
142
|
+
}
|
|
81
143
|
}
|
|
82
|
-
|
|
83
|
-
|
|
144
|
+
// don't update LODs for cube map rendering cameras
|
|
145
|
+
if (camera.parent && camera.parent.type === "CubeCamera") {
|
|
146
|
+
updateLODs = false;
|
|
84
147
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
148
|
+
if (updateLODs) {
|
|
149
|
+
if (suppressProgressiveLoading)
|
|
150
|
+
return;
|
|
151
|
+
if (this.updateInterval > 0 && frame % this.updateInterval != 0)
|
|
152
|
+
return;
|
|
153
|
+
this.projectionScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
154
|
+
this.cameraFrustrum.setFromProjectionMatrix(this.projectionScreenMatrix, this.renderer.coordinateSystem);
|
|
155
|
+
const desiredDensity = this.targetTriangleDensity;
|
|
156
|
+
// const isLowPerformanceDevice = false;// isMobileDevice();
|
|
157
|
+
// Experiment: quick & dirty performance-adaptive LODs
|
|
158
|
+
/*
|
|
159
|
+
if (this.context.time.smoothedFps < 59) {
|
|
160
|
+
currentAllowedDensity *= 0.5;
|
|
161
|
+
}
|
|
162
|
+
else if (this.context.time.smoothedFps >= 59) {
|
|
163
|
+
currentAllowedDensity *= 1.25;
|
|
164
|
+
}
|
|
165
|
+
*/
|
|
166
|
+
for (const entry of opaque) {
|
|
167
|
+
if (entry.material && (entry.geometry?.type === "BoxGeometry" || entry.geometry?.type === "BufferGeometry")) {
|
|
168
|
+
// Ignore the skybox
|
|
169
|
+
if (entry.material.name === "SphericalGaussianBlur" || entry.material.name == "BackgroundCubeMaterial" || entry.material.name === "CubemapFromEquirect" || entry.material.name === "EquirectangularToCubeUV") {
|
|
170
|
+
if (debugProgressiveLoading) {
|
|
171
|
+
if (!entry.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]) {
|
|
172
|
+
entry.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] = true;
|
|
173
|
+
console.warn("Ignoring skybox or BLIT object", entry, entry.material.name, entry.material.type);
|
|
174
|
+
}
|
|
96
175
|
}
|
|
176
|
+
continue;
|
|
97
177
|
}
|
|
98
|
-
|
|
178
|
+
}
|
|
179
|
+
switch (entry.material.type) {
|
|
180
|
+
case "LineBasicMaterial":
|
|
181
|
+
case "LineDashedMaterial":
|
|
182
|
+
case "PointsMaterial":
|
|
183
|
+
case "ShadowMaterial":
|
|
184
|
+
case "MeshDistanceMaterial":
|
|
185
|
+
case "MeshDepthMaterial":
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const object = entry.object;
|
|
189
|
+
if (object instanceof Mesh || (object.isMesh)) {
|
|
190
|
+
this.updateLODs(scene, camera, object, desiredDensity, frame);
|
|
99
191
|
}
|
|
100
192
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
for (const entry of transparent) {
|
|
108
|
-
const object = entry.object;
|
|
109
|
-
if (object instanceof Mesh || (object.isMesh)) {
|
|
110
|
-
this.updateLODs(scene, camera, object, desiredDensity);
|
|
193
|
+
const transparent = renderList.transparent;
|
|
194
|
+
for (const entry of transparent) {
|
|
195
|
+
const object = entry.object;
|
|
196
|
+
if (object instanceof Mesh || (object.isMesh)) {
|
|
197
|
+
this.updateLODs(scene, camera, object, desiredDensity, frame);
|
|
198
|
+
}
|
|
111
199
|
}
|
|
112
200
|
}
|
|
113
201
|
}
|
|
114
202
|
/** Update the LOD levels for the renderer. */
|
|
115
|
-
updateLODs(scene, camera, object, desiredDensity) {
|
|
116
|
-
for (const plugin of this.plugins) {
|
|
117
|
-
plugin.onBeforeUpdateLOD?.(this.renderer, scene, camera, object);
|
|
118
|
-
}
|
|
119
|
-
// we currently only support auto LOD changes for meshes
|
|
203
|
+
updateLODs(scene, camera, object, desiredDensity, _frame) {
|
|
120
204
|
let state = object.userData.LOD_state;
|
|
121
205
|
if (!state) {
|
|
122
206
|
state = new LOD_state();
|
|
123
207
|
object.userData.LOD_state = state;
|
|
124
208
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.
|
|
209
|
+
// Wait a few frames before updating the LODs to make sure the object is loaded, matrices are updated, etc.
|
|
210
|
+
if (state.frames++ < 2) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
for (const plugin of plugins) {
|
|
214
|
+
plugin.onBeforeUpdateLOD?.(this.renderer, scene, camera, object);
|
|
215
|
+
}
|
|
216
|
+
this.calculateLodLevel(camera, object, state, desiredDensity, levels);
|
|
217
|
+
levels.mesh_lod = Math.round(levels.mesh_lod);
|
|
218
|
+
levels.texture_lod = Math.round(levels.texture_lod);
|
|
219
|
+
// we currently only support auto LOD changes for meshes
|
|
220
|
+
if (levels.mesh_lod >= 0) {
|
|
221
|
+
this.loadProgressiveMeshes(object, levels.mesh_lod);
|
|
131
222
|
}
|
|
132
223
|
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
|
133
|
-
let textureLOD =
|
|
134
|
-
if (object.material) {
|
|
224
|
+
let textureLOD = levels.texture_lod;
|
|
225
|
+
if (object.material && textureLOD >= 0) {
|
|
135
226
|
const debugLevel = object["DEBUG:LOD"];
|
|
136
227
|
if (debugLevel != undefined)
|
|
137
228
|
textureLOD = debugLevel;
|
|
138
|
-
|
|
139
|
-
for (const mat of object.material) {
|
|
140
|
-
this.loadProgressiveTextures(mat, textureLOD);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
this.loadProgressiveTextures(object.material, textureLOD);
|
|
145
|
-
}
|
|
229
|
+
this.loadProgressiveTextures(object.material, textureLOD);
|
|
146
230
|
}
|
|
147
|
-
for (const plugin of
|
|
148
|
-
plugin.onAfterUpdatedLOD?.(this.renderer, scene, camera, object,
|
|
231
|
+
for (const plugin of plugins) {
|
|
232
|
+
plugin.onAfterUpdatedLOD?.(this.renderer, scene, camera, object, levels);
|
|
149
233
|
}
|
|
150
|
-
state.
|
|
234
|
+
state.lastLodLevel_Mesh = levels.mesh_lod;
|
|
235
|
+
state.lasLodLevel_Texture = levels.texture_lod;
|
|
151
236
|
}
|
|
152
237
|
/** Load progressive textures for the given material
|
|
153
238
|
* @param material the material to load the textures for
|
|
@@ -156,12 +241,27 @@ export class LODsManager {
|
|
|
156
241
|
*/
|
|
157
242
|
loadProgressiveTextures(material, level) {
|
|
158
243
|
if (!material)
|
|
159
|
-
return
|
|
160
|
-
if (
|
|
161
|
-
material
|
|
162
|
-
|
|
244
|
+
return;
|
|
245
|
+
if (Array.isArray(material)) {
|
|
246
|
+
for (const mat of material) {
|
|
247
|
+
this.loadProgressiveTextures(mat, level);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const LOD_material = material;
|
|
252
|
+
// Check if the material LOD was already updated to a certain level
|
|
253
|
+
// We don't use the userData here because we want to re-run assigning textures if the material has been cloned
|
|
254
|
+
let update = false;
|
|
255
|
+
if (LOD_material.NEEDLE_LOD == undefined) {
|
|
256
|
+
update = true;
|
|
257
|
+
}
|
|
258
|
+
else if (level < LOD_material.NEEDLE_LOD) {
|
|
259
|
+
update = true;
|
|
260
|
+
}
|
|
261
|
+
if (update) {
|
|
262
|
+
LOD_material.NEEDLE_LOD = level;
|
|
263
|
+
NEEDLE_progressive.assignTextureLOD(material, level);
|
|
163
264
|
}
|
|
164
|
-
return Promise.resolve(null);
|
|
165
265
|
}
|
|
166
266
|
/** Load progressive meshes for the given mesh
|
|
167
267
|
* @param mesh the mesh to load the LOD for
|
|
@@ -179,8 +279,6 @@ export class LODsManager {
|
|
|
179
279
|
const originalGeometry = mesh.geometry;
|
|
180
280
|
return NEEDLE_progressive.assignMeshLOD(mesh, level).then(res => {
|
|
181
281
|
if (res && mesh.userData.LOD == level && originalGeometry != mesh.geometry) {
|
|
182
|
-
// update the lightmap
|
|
183
|
-
// this.applyLightmapping();
|
|
184
282
|
// if (this.handles) {
|
|
185
283
|
// for (const inst of this.handles) {
|
|
186
284
|
// // if (inst["LOD"] < level) continue;
|
|
@@ -197,6 +295,7 @@ export class LODsManager {
|
|
|
197
295
|
// private testIfLODLevelsAreAvailable() {
|
|
198
296
|
_sphere = new Sphere();
|
|
199
297
|
_tempBox = new Box3();
|
|
298
|
+
_tempBox2 = new Box3();
|
|
200
299
|
tempMatrix = new Matrix4();
|
|
201
300
|
_tempWorldPosition = new Vector3();
|
|
202
301
|
_tempBoxSize = new Vector3();
|
|
@@ -205,168 +304,204 @@ export class LODsManager {
|
|
|
205
304
|
static corner1 = new Vector3();
|
|
206
305
|
static corner2 = new Vector3();
|
|
207
306
|
static corner3 = new Vector3();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
307
|
+
static _tempPtInside = new Vector3();
|
|
308
|
+
static isInside(box, matrix) {
|
|
309
|
+
const min = box.min;
|
|
310
|
+
const max = box.max;
|
|
311
|
+
const centerx = (min.x + max.x) * 0.5;
|
|
312
|
+
const centery = (min.y + max.y) * 0.5;
|
|
313
|
+
const pt1 = this._tempPtInside.set(centerx, centery, min.z).applyMatrix4(matrix);
|
|
314
|
+
return pt1.z < 0;
|
|
315
|
+
}
|
|
316
|
+
calculateLodLevel(camera, mesh, state, desiredDensity, result) {
|
|
317
|
+
if (!mesh) {
|
|
318
|
+
result.mesh_lod = -1;
|
|
319
|
+
result.texture_lod = -1;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (!camera) {
|
|
323
|
+
result.mesh_lod = -1;
|
|
324
|
+
result.texture_lod = -1;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
211
327
|
// if this is using instancing we always load level 0
|
|
212
328
|
// if (this.isInstancingActive) return 0;
|
|
213
329
|
/** rough measure of "triangles on quadratic screen" – we're switching LODs based on this metric. */
|
|
214
330
|
/** highest LOD level we'd ever expect to be generated */
|
|
215
331
|
const maxLevel = 10;
|
|
216
|
-
let
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
332
|
+
let mesh_level = maxLevel + 1;
|
|
333
|
+
if (debugProgressiveLoading && mesh["DEBUG:LOD"] != undefined) {
|
|
334
|
+
return mesh["DEBUG:LOD"];
|
|
335
|
+
}
|
|
336
|
+
// The mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
|
337
|
+
const mesh_lods_info = NEEDLE_progressive.getMeshLODInformation(mesh.geometry);
|
|
338
|
+
const mesh_lods = mesh_lods_info?.lods;
|
|
339
|
+
const has_mesh_lods = mesh_lods && mesh_lods.length > 0;
|
|
340
|
+
const texture_lods_minmax = NEEDLE_progressive.getMaterialMinMaxLODsCount(mesh.material);
|
|
341
|
+
const has_texture_lods = texture_lods_minmax?.min != Infinity && texture_lods_minmax.min > 0 && texture_lods_minmax.max > 0;
|
|
342
|
+
// We can skip all this if we dont have any LOD information
|
|
343
|
+
if (!has_mesh_lods && !has_texture_lods) {
|
|
344
|
+
result.mesh_lod = 0;
|
|
345
|
+
result.texture_lod = 0;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!has_mesh_lods) {
|
|
349
|
+
mesh_level = 0;
|
|
350
|
+
}
|
|
351
|
+
if (!this.cameraFrustrum?.intersectsObject(mesh)) {
|
|
352
|
+
// console.log("Mesh not visible");
|
|
353
|
+
// if (debugProgressiveLoading && mesh.geometry.boundingSphere) {
|
|
354
|
+
// const bounds = mesh.geometry.boundingSphere;
|
|
355
|
+
// this._sphere.copy(bounds);
|
|
356
|
+
// this._sphere.applyMatrix4(mesh.matrixWorld);
|
|
357
|
+
// Gizmos.DrawWireSphere(this._sphere.center, this._sphere.radius * 1.01, 0xff5555, .5);
|
|
358
|
+
// }
|
|
359
|
+
// the object is not visible by the camera
|
|
360
|
+
result.mesh_lod = 99;
|
|
361
|
+
result.texture_lod = 99;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const boundingBox = mesh.geometry.boundingBox;
|
|
365
|
+
if (boundingBox && camera.isPerspectiveCamera) {
|
|
366
|
+
const cam = camera;
|
|
367
|
+
// hack: if the mesh has vertex colors, has less than 100 vertices we always select the highest LOD
|
|
368
|
+
if (mesh.geometry.attributes.color && mesh.geometry.attributes.color.count < 100) {
|
|
369
|
+
if (mesh.geometry.boundingSphere) {
|
|
370
|
+
this._sphere.copy(mesh.geometry.boundingSphere);
|
|
371
|
+
this._sphere.applyMatrix4(mesh.matrixWorld);
|
|
372
|
+
const worldPosition = camera.getWorldPosition(this._tempWorldPosition);
|
|
373
|
+
if (this._sphere.containsPoint(worldPosition)) {
|
|
374
|
+
result.mesh_lod = 0;
|
|
375
|
+
result.texture_lod = 0;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
220
379
|
}
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
// calculate size on screen
|
|
381
|
+
this._tempBox.copy(boundingBox);
|
|
382
|
+
this._tempBox.applyMatrix4(mesh.matrixWorld);
|
|
383
|
+
// Converting into projection space has the disadvantage that objects further to the side
|
|
384
|
+
// will have a much larger coverage, especially with high-field-of-view situations like in VR.
|
|
385
|
+
// Alternatively, we could attempt to calculate angular coverage (some kind of polar coordinates maybe?)
|
|
386
|
+
// or introduce a correction factor based on "expected distortion" of the object.
|
|
387
|
+
// High distortions would lead to lower LOD levels.
|
|
388
|
+
// "Centrality" of the calculated screen-space bounding box could be a factor here –
|
|
389
|
+
// what's the distance of the bounding box to the center of the screen?
|
|
390
|
+
if (LODsManager.isInside(this._tempBox, this.projectionScreenMatrix)) {
|
|
391
|
+
result.mesh_lod = 0;
|
|
392
|
+
result.texture_lod = 0;
|
|
393
|
+
return;
|
|
227
394
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
395
|
+
this._tempBox.applyMatrix4(this.projectionScreenMatrix);
|
|
396
|
+
// TODO might need to be adjusted for cameras that are rendered during an XR session but are
|
|
397
|
+
// actually not XR cameras (e.g. a render texture)
|
|
398
|
+
if (this.renderer.xr.enabled && cam.fov > 70) {
|
|
399
|
+
// calculate centrality of the bounding box - how close is it to the screen center
|
|
400
|
+
const min = this._tempBox.min;
|
|
401
|
+
const max = this._tempBox.max;
|
|
402
|
+
let minX = min.x;
|
|
403
|
+
let minY = min.y;
|
|
404
|
+
let maxX = max.x;
|
|
405
|
+
let maxY = max.y;
|
|
406
|
+
// enlarge
|
|
407
|
+
const enlargementFactor = 2.0;
|
|
408
|
+
const centerBoost = 1.5;
|
|
409
|
+
const centerX = (min.x + max.x) * 0.5;
|
|
410
|
+
const centerY = (min.y + max.y) * 0.5;
|
|
411
|
+
minX = (minX - centerX) * enlargementFactor + centerX;
|
|
412
|
+
minY = (minY - centerY) * enlargementFactor + centerY;
|
|
413
|
+
maxX = (maxX - centerX) * enlargementFactor + centerX;
|
|
414
|
+
maxY = (maxY - centerY) * enlargementFactor + centerY;
|
|
415
|
+
const xCentrality = minX < 0 && maxX > 0 ? 0 : Math.min(Math.abs(min.x), Math.abs(max.x));
|
|
416
|
+
const yCentrality = minY < 0 && maxY > 0 ? 0 : Math.min(Math.abs(min.y), Math.abs(max.y));
|
|
417
|
+
const centrality = Math.max(xCentrality, yCentrality);
|
|
418
|
+
// heuristically determined to lower quality for objects at the edges of vision
|
|
419
|
+
state.lastCentrality = (centerBoost - centrality) * (centerBoost - centrality) * (centerBoost - centrality);
|
|
238
420
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const box2 = new Box3();
|
|
299
|
-
box2.copy(box);
|
|
300
|
-
box2.applyMatrix4(mesh.matrixWorld);
|
|
301
|
-
box2.applyMatrix4(matView);
|
|
302
|
-
const boxSize2 = box2.getSize(this._tempBox2Size);
|
|
303
|
-
// approximate depth coverage in relation to screenspace size
|
|
304
|
-
const max2 = Math.max(boxSize2.x, boxSize2.y);
|
|
305
|
-
const max1 = Math.max(boxSize.x, boxSize.y);
|
|
306
|
-
if (max1 != 0 && max2 != 0)
|
|
307
|
-
boxSize.z = boxSize2.z / Math.max(boxSize2.x, boxSize2.y) * Math.max(boxSize.x, boxSize.y);
|
|
308
|
-
state.lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
|
309
|
-
state.lastScreenspaceVolume.copy(boxSize);
|
|
310
|
-
state.lastScreenCoverage *= state.lastCentrality;
|
|
311
|
-
// draw screen size box
|
|
312
|
-
if (debugProgressiveLoading && LODsManager.debugDrawLine) {
|
|
313
|
-
const mat = this.tempMatrix.copy(this.projectionScreenMatrix);
|
|
314
|
-
mat.invert();
|
|
315
|
-
const corner0 = LODsManager.corner0;
|
|
316
|
-
const corner1 = LODsManager.corner1;
|
|
317
|
-
const corner2 = LODsManager.corner2;
|
|
318
|
-
const corner3 = LODsManager.corner3;
|
|
319
|
-
// get box corners, transform with camera space, and draw as quad lines
|
|
320
|
-
corner0.copy(this._tempBox.min);
|
|
321
|
-
corner1.copy(this._tempBox.max);
|
|
322
|
-
corner1.x = corner0.x;
|
|
323
|
-
corner2.copy(this._tempBox.max);
|
|
324
|
-
corner2.y = corner0.y;
|
|
325
|
-
corner3.copy(this._tempBox.max);
|
|
326
|
-
// draw outlines at the center of the box
|
|
327
|
-
const z = (corner0.z + corner3.z) * 0.5;
|
|
328
|
-
// all outlines should have the same depth in screen space
|
|
329
|
-
corner0.z = corner1.z = corner2.z = corner3.z = z;
|
|
330
|
-
corner0.applyMatrix4(mat);
|
|
331
|
-
corner1.applyMatrix4(mat);
|
|
332
|
-
corner2.applyMatrix4(mat);
|
|
333
|
-
corner3.applyMatrix4(mat);
|
|
334
|
-
LODsManager.debugDrawLine(corner0, corner1, 0x0000ff);
|
|
335
|
-
LODsManager.debugDrawLine(corner0, corner2, 0x0000ff);
|
|
336
|
-
LODsManager.debugDrawLine(corner1, corner3, 0x0000ff);
|
|
337
|
-
LODsManager.debugDrawLine(corner2, corner3, 0x0000ff);
|
|
338
|
-
}
|
|
339
|
-
let expectedLevel = 999;
|
|
340
|
-
// const framerate = this.context.time.smoothedFps;
|
|
341
|
-
if (lods && state.lastScreenCoverage > 0) {
|
|
342
|
-
for (let l = 0; l < lods.length; l++) {
|
|
343
|
-
const densityForThisLevel = lods[l].density;
|
|
344
|
-
const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
|
|
345
|
-
if (resultingDensity < desiredDensity) {
|
|
346
|
-
expectedLevel = l;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
421
|
+
else {
|
|
422
|
+
state.lastCentrality = 1;
|
|
423
|
+
}
|
|
424
|
+
const boxSize = this._tempBox.getSize(this._tempBoxSize);
|
|
425
|
+
boxSize.multiplyScalar(0.5); // goes from -1..1, we want -0.5..0.5 for coverage in percent
|
|
426
|
+
if (screen.availHeight > 0)
|
|
427
|
+
boxSize.multiplyScalar(this.renderer.domElement.clientHeight / screen.availHeight); // correct for size of context on screen
|
|
428
|
+
boxSize.x *= cam.aspect;
|
|
429
|
+
const matView = camera.matrixWorldInverse;
|
|
430
|
+
const box2 = this._tempBox2;
|
|
431
|
+
box2.copy(boundingBox);
|
|
432
|
+
box2.applyMatrix4(mesh.matrixWorld);
|
|
433
|
+
box2.applyMatrix4(matView);
|
|
434
|
+
const boxSize2 = box2.getSize(this._tempBox2Size);
|
|
435
|
+
// approximate depth coverage in relation to screenspace size
|
|
436
|
+
const max2 = Math.max(boxSize2.x, boxSize2.y);
|
|
437
|
+
const max1 = Math.max(boxSize.x, boxSize.y);
|
|
438
|
+
if (max1 != 0 && max2 != 0)
|
|
439
|
+
boxSize.z = boxSize2.z / Math.max(boxSize2.x, boxSize2.y) * Math.max(boxSize.x, boxSize.y);
|
|
440
|
+
state.lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
|
441
|
+
state.lastScreenspaceVolume.copy(boxSize);
|
|
442
|
+
state.lastScreenCoverage *= state.lastCentrality;
|
|
443
|
+
// draw screen size box
|
|
444
|
+
if (debugProgressiveLoading && LODsManager.debugDrawLine) {
|
|
445
|
+
const mat = this.tempMatrix.copy(this.projectionScreenMatrix);
|
|
446
|
+
mat.invert();
|
|
447
|
+
const corner0 = LODsManager.corner0;
|
|
448
|
+
const corner1 = LODsManager.corner1;
|
|
449
|
+
const corner2 = LODsManager.corner2;
|
|
450
|
+
const corner3 = LODsManager.corner3;
|
|
451
|
+
// get box corners, transform with camera space, and draw as quad lines
|
|
452
|
+
corner0.copy(this._tempBox.min);
|
|
453
|
+
corner1.copy(this._tempBox.max);
|
|
454
|
+
corner1.x = corner0.x;
|
|
455
|
+
corner2.copy(this._tempBox.max);
|
|
456
|
+
corner2.y = corner0.y;
|
|
457
|
+
corner3.copy(this._tempBox.max);
|
|
458
|
+
// draw outlines at the center of the box
|
|
459
|
+
const z = (corner0.z + corner3.z) * 0.5;
|
|
460
|
+
// all outlines should have the same depth in screen space
|
|
461
|
+
corner0.z = corner1.z = corner2.z = corner3.z = z;
|
|
462
|
+
corner0.applyMatrix4(mat);
|
|
463
|
+
corner1.applyMatrix4(mat);
|
|
464
|
+
corner2.applyMatrix4(mat);
|
|
465
|
+
corner3.applyMatrix4(mat);
|
|
466
|
+
LODsManager.debugDrawLine(corner0, corner1, 0x0000ff);
|
|
467
|
+
LODsManager.debugDrawLine(corner0, corner2, 0x0000ff);
|
|
468
|
+
LODsManager.debugDrawLine(corner1, corner3, 0x0000ff);
|
|
469
|
+
LODsManager.debugDrawLine(corner2, corner3, 0x0000ff);
|
|
470
|
+
}
|
|
471
|
+
let expectedLevel = 999;
|
|
472
|
+
// const framerate = this.context.time.smoothedFps;
|
|
473
|
+
if (mesh_lods && state.lastScreenCoverage > 0) {
|
|
474
|
+
for (let l = 0; l < mesh_lods.length; l++) {
|
|
475
|
+
const densityForThisLevel = mesh_lods[l].density;
|
|
476
|
+
const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
|
|
477
|
+
if (resultingDensity < desiredDensity) {
|
|
478
|
+
expectedLevel = l;
|
|
479
|
+
break;
|
|
349
480
|
}
|
|
350
481
|
}
|
|
351
|
-
const isLowerLod = expectedLevel < level;
|
|
352
|
-
if (isLowerLod) {
|
|
353
|
-
level = expectedLevel;
|
|
354
|
-
}
|
|
355
482
|
}
|
|
356
|
-
|
|
483
|
+
const isLowerLod = expectedLevel < mesh_level;
|
|
484
|
+
if (isLowerLod) {
|
|
485
|
+
mesh_level = expectedLevel;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
result.mesh_lod = mesh_level;
|
|
489
|
+
if (texture_lods_minmax?.min && texture_lods_minmax.min > 0 && texture_lods_minmax.max > 0) {
|
|
490
|
+
const t = Math.min(1, Math.max(0, state.lastScreenCoverage * 3));
|
|
491
|
+
result.texture_lod = lerp(texture_lods_minmax.max, 0, t);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
result.texture_lod = 0;
|
|
357
495
|
}
|
|
358
|
-
// if (this._lastLodLevel != level) {
|
|
359
|
-
// this._nextLodTestTime = this.context.time.realtimeSinceStartup + .5;
|
|
360
|
-
// if (debugProgressiveLoading) {
|
|
361
|
-
// if (debugProgressiveLoading == "verbose") console.warn(`LOD Level changed from ${this._lastLodLevel} to ${level} for ${this.name}`);
|
|
362
|
-
// this.drawGizmoLodLevel(true);
|
|
363
|
-
// }
|
|
364
|
-
// }
|
|
365
|
-
return level;
|
|
366
496
|
}
|
|
367
497
|
}
|
|
498
|
+
function lerp(a, b, t) {
|
|
499
|
+
return a + (b - a) * t;
|
|
500
|
+
}
|
|
368
501
|
class LOD_state {
|
|
369
|
-
|
|
502
|
+
frames = 0;
|
|
503
|
+
lastLodLevel_Mesh = 0;
|
|
504
|
+
lasLodLevel_Texture = 0;
|
|
370
505
|
lastScreenCoverage = 0;
|
|
371
506
|
lastScreenspaceVolume = new Vector3();
|
|
372
507
|
lastCentrality = 0;
|