@@ -229,10 +229,10 @@
|
|
229
229
|
|
230
230
|
// Ensure that the progressive textures have been loaded for all variants and materials
|
231
231
|
if (this.materialToSwitch) {
|
232
|
-
await NEEDLE_progressive.assignTextureLOD(this.
|
232
|
+
await NEEDLE_progressive.assignTextureLOD(this.materialToSwitch, 0);
|
233
233
|
}
|
234
234
|
if (this.variantMaterial) {
|
235
|
-
await NEEDLE_progressive.assignTextureLOD(this.
|
235
|
+
await NEEDLE_progressive.assignTextureLOD(this.variantMaterial, 0);
|
236
236
|
}
|
237
237
|
}
|
238
238
|
|
@@ -242,6 +242,10 @@
|
|
242
242
|
}
|
243
243
|
|
244
244
|
private _frustum?: Frustum;
|
245
|
+
/**
|
246
|
+
* Get a frustum - it will be created the first time this method is called and updated every frame in onBeforeRender when it exists.
|
247
|
+
* You can also manually update it using the updateFrustum method.
|
248
|
+
*/
|
245
249
|
public getFrustum(): Frustum {
|
246
250
|
if (!this._frustum) {
|
247
251
|
this._frustum = new Frustum();
|
@@ -254,14 +258,19 @@
|
|
254
258
|
if (!this._frustum) this._frustum = new Frustum();
|
255
259
|
this._frustum.setFromProjectionMatrix(this.getProjectionScreenMatrix(this._projScreenMatrix, true), this.context.renderer.coordinateSystem);
|
256
260
|
}
|
261
|
+
/**
|
262
|
+
* @returns {Matrix4} this camera's projection screen matrix.
|
263
|
+
*/
|
257
264
|
public getProjectionScreenMatrix(target: Matrix4, forceUpdate?: boolean) {
|
258
265
|
if (forceUpdate) {
|
259
266
|
this._projScreenMatrix.multiplyMatrices(this.cam.projectionMatrix, this.cam.matrixWorldInverse);
|
260
267
|
}
|
261
268
|
if (target === this._projScreenMatrix) return target;
|
262
269
|
return target.copy(this._projScreenMatrix);
|
263
|
-
}
|
270
|
+
}
|
271
|
+
private readonly _projScreenMatrix = new Matrix4();
|
264
272
|
|
273
|
+
|
265
274
|
/** @internal */
|
266
275
|
awake() {
|
267
276
|
if (debugscreenpointtoray) {
|
@@ -292,7 +301,6 @@
|
|
292
301
|
this.context.removeCamera(this);
|
293
302
|
}
|
294
303
|
|
295
|
-
private readonly _projScreenMatrix = new Matrix4();
|
296
304
|
|
297
305
|
/** @internal */
|
298
306
|
onBeforeRender() {
|
@@ -185,7 +185,7 @@
|
|
185
185
|
|
186
186
|
if (this.sharedMesh?.isMesh) {
|
187
187
|
this.context.physics.engine.addMeshCollider(this, this.sharedMesh, this.convex, getWorldScale(this.gameObject));
|
188
|
-
NEEDLE_progressive.assignMeshLOD(this.
|
188
|
+
NEEDLE_progressive.assignMeshLOD(this.sharedMesh, LOD).then(res => {
|
189
189
|
if (res && this.activeAndEnabled && this.context.physics.engine && this.sharedMesh) {
|
190
190
|
this.context.physics.engine.removeBody(this);
|
191
191
|
this.sharedMesh.geometry = res;
|
@@ -202,7 +202,7 @@
|
|
202
202
|
const child = group.children[ch] as Mesh;
|
203
203
|
if (child.isMesh) {
|
204
204
|
this.context.physics.engine.addMeshCollider(this, child, this.convex, getWorldScale(this.gameObject));
|
205
|
-
promises.push(NEEDLE_progressive.assignMeshLOD(
|
205
|
+
promises.push(NEEDLE_progressive.assignMeshLOD(child, LOD));
|
206
206
|
}
|
207
207
|
}
|
208
208
|
Promise.all(promises).then(res => {
|
@@ -19,9 +19,9 @@
|
|
19
19
|
import { Input } from './engine_input.js';
|
20
20
|
import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
|
21
21
|
import { type ILightDataRegistry, LightDataRegistry } from './engine_lightdata.js';
|
22
|
+
import { LODsManager } from "./engine_lods.js";
|
22
23
|
import * as looputils from './engine_mainloop_utils.js';
|
23
24
|
import { NetworkConnection } from './engine_networking.js';
|
24
|
-
import { isLocalNetwork } from './engine_networking_utils.js';
|
25
25
|
import { Physics } from './engine_physics.js';
|
26
26
|
import { PlayerViewManager } from './engine_playerview.js';
|
27
27
|
import { RendererData as SceneLighting } from './engine_scenelighting.js';
|
@@ -376,6 +376,7 @@
|
|
376
376
|
addressables: Addressables;
|
377
377
|
lightmaps: ILightDataRegistry;
|
378
378
|
players: PlayerViewManager;
|
379
|
+
readonly lodsManager: LODsManager;
|
379
380
|
readonly menu: NeedleMenu;
|
380
381
|
|
381
382
|
get isCreated() { return this._isCreated; }
|
@@ -413,6 +414,7 @@
|
|
413
414
|
this.lightmaps = new LightDataRegistry(this);
|
414
415
|
this.players = new PlayerViewManager(this);
|
415
416
|
this.menu = new NeedleMenu(this);
|
417
|
+
this.lodsManager = new LODsManager(this);
|
416
418
|
|
417
419
|
|
418
420
|
const resizeCallback = () => this._sizeChanged = true;
|
@@ -463,6 +465,7 @@
|
|
463
465
|
this.renderer.shadowMap.type = PCFSoftShadowMap;
|
464
466
|
this.renderer.setSize(this.domWidth, this.domHeight);
|
465
467
|
this.renderer.outputColorSpace = SRGBColorSpace;
|
468
|
+
this.lodsManager.setRenderer(this.renderer);
|
466
469
|
|
467
470
|
this.input.bindEvents();
|
468
471
|
}
|
@@ -786,7 +789,7 @@
|
|
786
789
|
console.error("Needle Engine dependencies failed to load", err)
|
787
790
|
})
|
788
791
|
.then(() => {
|
789
|
-
if(debug) console.log("Needle Engine dependencies are ready");
|
792
|
+
if (debug) console.log("Needle Engine dependencies are ready");
|
790
793
|
});
|
791
794
|
}
|
792
795
|
|
@@ -88,7 +88,7 @@
|
|
88
88
|
loader.register(p => new NEEDLE_lighting_settings(p, sourceId, context));
|
89
89
|
loader.register(p => new NEEDLE_techniques_webgl(p, sourceId));
|
90
90
|
loader.register(p => new NEEDLE_render_objects(p, sourceId));
|
91
|
-
loader.register(p => new NEEDLE_progressive(p, sourceId
|
91
|
+
loader.register(p => new NEEDLE_progressive(p, sourceId));
|
92
92
|
loader.register(p => new EXT_texture_exr(p));
|
93
93
|
if (isResourceTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p))
|
94
94
|
|
@@ -222,7 +222,7 @@
|
|
222
222
|
}
|
223
223
|
// }
|
224
224
|
this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
|
225
|
-
NEEDLE_progressive.assignTextureLOD(
|
225
|
+
NEEDLE_progressive.assignTextureLOD(tex, 0).then(res => {
|
226
226
|
if (res instanceof Texture) {
|
227
227
|
this.setOptions({ backgroundImage: res });
|
228
228
|
}
|
@@ -1,878 +1,1 @@
|
|
1
|
-
|
2
|
-
import { type GLTF, GLTFLoader, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
3
|
-
|
4
|
-
import { showBalloonMessage } from "../debug/index.js";
|
5
|
-
import { findResourceUsers, getResourceUserCount } from "../engine_assetdatabase.js";
|
6
|
-
import { ContextEvent, NeedleEngine } from "../engine_context_registry.js";
|
7
|
-
import { addDracoAndKTX2Loaders } from "../engine_loaders.js";
|
8
|
-
import { getRaycastMesh, setRaycastMesh } from "../engine_physics.js";
|
9
|
-
import { Context } from "../engine_setup.js";
|
10
|
-
import { type SourceIdentifier } from "../engine_types.js";
|
11
|
-
import { delay, getParam, PromiseAllWithErrors, PromiseErrorResult, resolveUrl } from "../engine_utils.js";
|
12
|
-
|
13
|
-
export const EXTENSION_NAME = "NEEDLE_progressive";
|
14
|
-
|
15
|
-
const debug = getParam("debugprogressive");
|
16
|
-
|
17
|
-
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
18
|
-
|
19
|
-
/** Removes the readonly attribute from all properties of an object */
|
20
|
-
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
|
21
|
-
|
22
|
-
|
23
|
-
const debug_toggle_maps: Map<object, { keys: string[], sourceId: string }> = new Map();
|
24
|
-
if (debug) {
|
25
|
-
let currentDebugLodLevel = -1;
|
26
|
-
let maxLevel = 2;
|
27
|
-
function debugToggleProgressive() {
|
28
|
-
currentDebugLodLevel += 1;
|
29
|
-
showBalloonMessage(`Toggle LOD level: ${currentDebugLodLevel}<br/>Registered objects: ${debug_toggle_maps.size}`);
|
30
|
-
console.log("Toggle LOD level", currentDebugLodLevel, debug_toggle_maps);
|
31
|
-
const context = NeedleEngine.Current!;
|
32
|
-
debug_toggle_maps.forEach((arr, obj) => {
|
33
|
-
for (const key of arr.keys) {
|
34
|
-
const cur = obj[key];
|
35
|
-
if (cur instanceof BufferGeometry) {
|
36
|
-
const info = NEEDLE_progressive.getMeshLODInformation(cur);
|
37
|
-
const level = !info ? 0 : Math.min(currentDebugLodLevel, info.lods.length);
|
38
|
-
obj["DEBUG:LOD"] = level;
|
39
|
-
NEEDLE_progressive.assignMeshLOD(context, arr.sourceId, obj as Mesh, level);
|
40
|
-
if (info) maxLevel = Math.max(maxLevel, info.lods.length - 1);
|
41
|
-
}
|
42
|
-
else if (obj instanceof Material) {
|
43
|
-
NEEDLE_progressive.assignTextureLOD(context, arr.sourceId, obj, currentDebugLodLevel);
|
44
|
-
break;
|
45
|
-
}
|
46
|
-
}
|
47
|
-
});
|
48
|
-
if (currentDebugLodLevel >= maxLevel) {
|
49
|
-
currentDebugLodLevel = -1;
|
50
|
-
}
|
51
|
-
}
|
52
|
-
window.addEventListener("keyup", evt => {
|
53
|
-
if (evt.key === "p") debugToggleProgressive();
|
54
|
-
});
|
55
|
-
NeedleEngine.registerCallback(ContextEvent.ContextCreated, (ctx) => {
|
56
|
-
const button = document.createElement("button");
|
57
|
-
button.innerText = "Toggle Progressive";
|
58
|
-
button.onclick = debugToggleProgressive;
|
59
|
-
ctx.context.menu.appendChild(button);
|
60
|
-
});
|
61
|
-
}
|
62
|
-
function registerDebug(obj: object, key: string, sourceId: string,) {
|
63
|
-
if (!debug) return;
|
64
|
-
if (!debug_toggle_maps.has(obj)) {
|
65
|
-
debug_toggle_maps.set(obj, { keys: [], sourceId });
|
66
|
-
}
|
67
|
-
const existing = debug_toggle_maps.get(obj);
|
68
|
-
if (existing?.keys?.includes(key) == false) {
|
69
|
-
existing.keys.push(key);
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
|
-
declare type NEEDLE_progressive_model_LOD = {
|
74
|
-
path: string,
|
75
|
-
hash?: string,
|
76
|
-
}
|
77
|
-
|
78
|
-
/** This is the data structure we have in the NEEDLE_progressive extension */
|
79
|
-
declare type NEEDLE_progressive_model = {
|
80
|
-
guid: string,
|
81
|
-
lods: Array<NEEDLE_progressive_model_LOD>
|
82
|
-
}
|
83
|
-
|
84
|
-
declare type NEEDLE_progressive_texture_model = NEEDLE_progressive_model & {
|
85
|
-
|
86
|
-
}
|
87
|
-
declare type NEEDLE_progressive_mesh_model = NEEDLE_progressive_model & {
|
88
|
-
density: number;
|
89
|
-
lods: Array<NEEDLE_progressive_model_LOD & {
|
90
|
-
density: number,
|
91
|
-
indexCount: number;
|
92
|
-
vertexCount: number;
|
93
|
-
}>
|
94
|
-
}
|
95
|
-
|
96
|
-
/**
|
97
|
-
* This is the result of a progressive texture loading event for a material's texture slot in {@link NEEDLE_progressive.assignTextureLOD}
|
98
|
-
* @internal
|
99
|
-
*/
|
100
|
-
export declare type ProgressiveMaterialTextureLoadingResult = {
|
101
|
-
/** the material the progressive texture was loaded for */
|
102
|
-
material: Material,
|
103
|
-
/** the slot in the material where the texture was loaded */
|
104
|
-
slot: string,
|
105
|
-
/** the texture that was loaded (if any) */
|
106
|
-
texture: Texture | null;
|
107
|
-
/** the level of detail that was loaded */
|
108
|
-
level: number;
|
109
|
-
}
|
110
|
-
|
111
|
-
/**
|
112
|
-
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
113
|
-
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
114
|
-
* @example
|
115
|
-
* ```javascript
|
116
|
-
* const loader = new GLTFLoader();
|
117
|
-
* loader.register(new NEEDLE_progressive());
|
118
|
-
* loader.load("model.glb", (gltf) => {
|
119
|
-
* const mesh = gltf.scene.children[0] as Mesh;
|
120
|
-
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
121
|
-
* console.log("Mesh with LOD level 1 loaded", mesh);
|
122
|
-
* });
|
123
|
-
* });
|
124
|
-
* ```
|
125
|
-
*/
|
126
|
-
export class NEEDLE_progressive implements GLTFLoaderPlugin {
|
127
|
-
|
128
|
-
/** The name of the extension */
|
129
|
-
get name(): string {
|
130
|
-
return EXTENSION_NAME;
|
131
|
-
}
|
132
|
-
|
133
|
-
static getMeshLODInformation(geo: BufferGeometry) {
|
134
|
-
const info = this.getAssignedLODInformation(geo);
|
135
|
-
if (info?.key) {
|
136
|
-
return this.lodInfos.get(info.key) as NEEDLE_progressive_mesh_model;
|
137
|
-
}
|
138
|
-
return null;
|
139
|
-
}
|
140
|
-
|
141
|
-
/** Check if a LOD level is available for a mesh or a texture
|
142
|
-
* @param obj the mesh or texture to check
|
143
|
-
* @param level the level of detail to check for (0 is the highest resolution). If undefined, the function checks if any LOD level is available
|
144
|
-
* @returns true if the LOD level is available (or if any LOD level is available if level is undefined)
|
145
|
-
*/
|
146
|
-
static hasLODLevelAvailable(obj: Mesh | Texture | Material, level?: number): boolean {
|
147
|
-
|
148
|
-
if (obj instanceof Material) {
|
149
|
-
for (const slot of Object.keys(obj)) {
|
150
|
-
const val = obj[slot];
|
151
|
-
if (val instanceof Texture) {
|
152
|
-
if (this.hasLODLevelAvailable(val, level)) return true;
|
153
|
-
}
|
154
|
-
}
|
155
|
-
return false;
|
156
|
-
}
|
157
|
-
else if (obj instanceof Group) {
|
158
|
-
for (const child of obj.children) {
|
159
|
-
if (child instanceof Mesh) {
|
160
|
-
if (this.hasLODLevelAvailable(child, level)) return true;
|
161
|
-
}
|
162
|
-
}
|
163
|
-
}
|
164
|
-
|
165
|
-
|
166
|
-
let lodObject: ObjectThatMightHaveLODs | undefined;
|
167
|
-
let lodInformation: NEEDLE_progressive_model | undefined;
|
168
|
-
|
169
|
-
if (obj instanceof Mesh) {
|
170
|
-
lodObject = obj.geometry as BufferGeometry;
|
171
|
-
}
|
172
|
-
else if (obj instanceof Texture) {
|
173
|
-
lodObject = obj;
|
174
|
-
}
|
175
|
-
if (lodObject) {
|
176
|
-
if (lodObject?.userData?.LODS) {
|
177
|
-
const lods = lodObject.userData.LODS;
|
178
|
-
lodInformation = this.lodInfos.get(lods.key);
|
179
|
-
if (level === undefined) return lodInformation != undefined;
|
180
|
-
if (lodInformation) {
|
181
|
-
if (Array.isArray(lodInformation.lods)) {
|
182
|
-
return level < lodInformation.lods.length;
|
183
|
-
}
|
184
|
-
return level === 0;
|
185
|
-
}
|
186
|
-
}
|
187
|
-
}
|
188
|
-
|
189
|
-
return false;
|
190
|
-
}
|
191
|
-
|
192
|
-
/** Load a different resolution of a mesh (if available)
|
193
|
-
* @param context the context
|
194
|
-
* @param source the sourceid of the file from which the mesh is loaded (this is usually the component's sourceId)
|
195
|
-
* @param mesh the mesh to load the LOD for
|
196
|
-
* @param level the level of detail to load (0 is the highest resolution)
|
197
|
-
* @returns a promise that resolves to the mesh with the requested LOD level
|
198
|
-
* @example
|
199
|
-
* ```javascript
|
200
|
-
* const mesh = this.gameObject as Mesh;
|
201
|
-
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
202
|
-
* console.log("Mesh with LOD level 1 loaded", mesh);
|
203
|
-
* });
|
204
|
-
* ```
|
205
|
-
*/
|
206
|
-
static assignMeshLOD(context: Context, source: SourceIdentifier, mesh: Mesh, level: number): Promise<BufferGeometry | null> {
|
207
|
-
|
208
|
-
if (!mesh) return Promise.resolve(null);
|
209
|
-
|
210
|
-
|
211
|
-
if (mesh instanceof Mesh) {
|
212
|
-
|
213
|
-
const currentGeometry = mesh.geometry;
|
214
|
-
const lodinfo = this.getAssignedLODInformation(currentGeometry);
|
215
|
-
if (!lodinfo) {
|
216
|
-
return Promise.resolve(null);
|
217
|
-
}
|
218
|
-
|
219
|
-
if (!getRaycastMesh(mesh)) {
|
220
|
-
setRaycastMesh(mesh, mesh.geometry as BufferGeometry);
|
221
|
-
}
|
222
|
-
|
223
|
-
const info = this.onProgressiveLoadStart(context, source, mesh, null);
|
224
|
-
mesh["LOD:requested level"] = level;
|
225
|
-
return NEEDLE_progressive.getOrLoadLOD<BufferGeometry>(context, source, currentGeometry, level).then(geo => {
|
226
|
-
if (mesh["LOD:requested level"] === level) {
|
227
|
-
delete mesh["LOD:requested level"];
|
228
|
-
|
229
|
-
if (Array.isArray(geo)) {
|
230
|
-
const index = lodinfo.index || 0;
|
231
|
-
geo = geo[index];
|
232
|
-
}
|
233
|
-
|
234
|
-
if (geo && currentGeometry != geo) {
|
235
|
-
if (debug == "verbose") console.log("Progressive Mesh " + mesh.name + " loaded", currentGeometry, "→", geo, "\n", mesh)
|
236
|
-
if (geo instanceof BufferGeometry) {
|
237
|
-
mesh.geometry = geo;
|
238
|
-
if (debug) registerDebug(mesh, "geometry", source);
|
239
|
-
}
|
240
|
-
}
|
241
|
-
}
|
242
|
-
this.onProgressiveLoadEnd(info);
|
243
|
-
return geo;
|
244
|
-
|
245
|
-
}).catch(err => {
|
246
|
-
this.onProgressiveLoadEnd(info);
|
247
|
-
console.error("Error loading mesh LOD", mesh, err);
|
248
|
-
return null;
|
249
|
-
});
|
250
|
-
}
|
251
|
-
else if (debug) {
|
252
|
-
console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh", mesh);
|
253
|
-
}
|
254
|
-
|
255
|
-
return Promise.resolve(null);
|
256
|
-
}
|
257
|
-
|
258
|
-
/** Load a different resolution of a texture (if available)
|
259
|
-
* @param context the context
|
260
|
-
* @param source the sourceid of the file from which the texture is loaded (this is usually the component's sourceId)
|
261
|
-
* @param materialOrTexture the material or texture to load the LOD for (if passing in a material all textures in the material will be loaded)
|
262
|
-
* @param level the level of detail to load (0 is the highest resolution) - currently only 0 is supported
|
263
|
-
* @returns a promise that resolves to the material or texture with the requested LOD level
|
264
|
-
*/
|
265
|
-
static assignTextureLOD(context: Context, source: SourceIdentifier, materialOrTexture: Material | Texture, level: number = 0)
|
266
|
-
: Promise<Array<ProgressiveMaterialTextureLoadingResult> | Texture | null> {
|
267
|
-
|
268
|
-
if (!materialOrTexture) return Promise.resolve(null);
|
269
|
-
|
270
|
-
if (materialOrTexture instanceof Material) {
|
271
|
-
const material = materialOrTexture;
|
272
|
-
const promises: Array<Promise<Texture | null>> = [];
|
273
|
-
const slots = new Array<string>();
|
274
|
-
|
275
|
-
if (material instanceof RawShaderMaterial) {
|
276
|
-
// iterate uniforms of custom shaders
|
277
|
-
for (const slot of Object.keys(material.uniforms)) {
|
278
|
-
const val = material.uniforms[slot].value;
|
279
|
-
if (val instanceof Texture) {
|
280
|
-
const task = this.assignTextureLODForSlot(context, source, val, level, material, slot);
|
281
|
-
promises.push(task);
|
282
|
-
slots.push(slot);
|
283
|
-
}
|
284
|
-
}
|
285
|
-
}
|
286
|
-
else {
|
287
|
-
for (const slot of Object.keys(material)) {
|
288
|
-
const val = material[slot];
|
289
|
-
if (val instanceof Texture) {
|
290
|
-
const task = this.assignTextureLODForSlot(context, source, val, level, material, slot);
|
291
|
-
promises.push(task);
|
292
|
-
slots.push(slot);
|
293
|
-
}
|
294
|
-
}
|
295
|
-
}
|
296
|
-
return PromiseAllWithErrors(promises).then(res => {
|
297
|
-
const textures = new Array<ProgressiveMaterialTextureLoadingResult>();
|
298
|
-
for (let i = 0; i < res.results.length; i++) {
|
299
|
-
const tex = res.results[i];
|
300
|
-
const slot = slots[i];
|
301
|
-
if (tex instanceof Texture) {
|
302
|
-
textures.push({ material, slot, texture: tex, level });
|
303
|
-
}
|
304
|
-
else {
|
305
|
-
textures.push({ material, slot, texture: null, level });
|
306
|
-
}
|
307
|
-
}
|
308
|
-
return textures;
|
309
|
-
});
|
310
|
-
}
|
311
|
-
|
312
|
-
if (materialOrTexture instanceof Texture) {
|
313
|
-
const texture = materialOrTexture;
|
314
|
-
return this.assignTextureLODForSlot(context, source, texture, level, null, null);
|
315
|
-
}
|
316
|
-
|
317
|
-
return Promise.resolve(null);
|
318
|
-
}
|
319
|
-
|
320
|
-
private static assignTextureLODForSlot(context: Context, source: SourceIdentifier, current: Texture, level: number, material: Material | null, slot: string | null): Promise<Texture | null> {
|
321
|
-
if (current?.isTexture !== true) return Promise.resolve(null);
|
322
|
-
|
323
|
-
// if (debug) console.log("-----------\n", "FIND", material?.name, slot, current?.name, current?.userData, current, material);
|
324
|
-
|
325
|
-
const info = this.onProgressiveLoadStart(context, source, material, slot);
|
326
|
-
return NEEDLE_progressive.getOrLoadLOD<Texture>(context, source, current, level).then(tex => {
|
327
|
-
|
328
|
-
// this can currently not happen
|
329
|
-
if (Array.isArray(tex)) return null;
|
330
|
-
|
331
|
-
if (tex?.isTexture === true) {
|
332
|
-
if (tex != current) {
|
333
|
-
// if (debug) console.warn("Assign LOD", material?.name, slot, tex.name, tex["guid"], material, "Prev:", current, "Now:", tex, "\n--------------");
|
334
|
-
|
335
|
-
// tex.needsUpdate = true;
|
336
|
-
|
337
|
-
if (material && slot) {
|
338
|
-
material[slot] = tex;
|
339
|
-
// material.needsUpdate = true;
|
340
|
-
}
|
341
|
-
|
342
|
-
if (debug && slot && material) registerDebug(material, slot, source);
|
343
|
-
|
344
|
-
// check if the old texture is still used by other objects
|
345
|
-
// if not we dispose it...
|
346
|
-
// this could also be handled elsewhere and not be done immediately
|
347
|
-
// const users = getResourceUserCount(current);
|
348
|
-
// if (!users) {
|
349
|
-
// if (debug) console.log("Progressive: Dispose texture", current.name, current.source.data, current.uuid);
|
350
|
-
// current?.dispose();
|
351
|
-
// }
|
352
|
-
}
|
353
|
-
|
354
|
-
this.onProgressiveLoadEnd(info);
|
355
|
-
return tex;
|
356
|
-
}
|
357
|
-
else if (debug == "verbose") {
|
358
|
-
console.warn("No LOD found for", current, level);
|
359
|
-
}
|
360
|
-
|
361
|
-
this.onProgressiveLoadEnd(info);
|
362
|
-
return null;
|
363
|
-
|
364
|
-
}).catch(err => {
|
365
|
-
this.onProgressiveLoadEnd(info);
|
366
|
-
console.error("Error loading LOD", current, err);
|
367
|
-
return null;
|
368
|
-
});
|
369
|
-
}
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
private readonly parser: GLTFParser;
|
375
|
-
private readonly sourceId: SourceIdentifier;
|
376
|
-
private readonly context: Context;
|
377
|
-
|
378
|
-
constructor(parser: GLTFParser, sourceId: SourceIdentifier, context: Context) {
|
379
|
-
this.parser = parser;
|
380
|
-
this.sourceId = sourceId;
|
381
|
-
this.context = context;
|
382
|
-
}
|
383
|
-
|
384
|
-
afterRoot(gltf: GLTF): null {
|
385
|
-
if (debug)
|
386
|
-
console.log("AFTER", this.sourceId, gltf);
|
387
|
-
|
388
|
-
this.parser.json.textures?.forEach((textureInfo, index) => {
|
389
|
-
if (textureInfo?.extensions) {
|
390
|
-
const ext: NEEDLE_progressive_texture_model = textureInfo?.extensions[EXTENSION_NAME];
|
391
|
-
if (ext) {
|
392
|
-
for (const key of this.parser.associations.keys()) {
|
393
|
-
if (key instanceof Texture) {
|
394
|
-
const val = this.parser.associations.get(key) as { textures: number };
|
395
|
-
if (val.textures === index) {
|
396
|
-
const tex = key;
|
397
|
-
if (debug)
|
398
|
-
console.log("> Progressive: register texture", index, tex.name, tex.uuid, tex, ext);
|
399
|
-
// Put the extension info into the source (seems like tiled textures are cloned and the userdata etc is not properly copied BUT the source of course is not cloned)
|
400
|
-
// see https://github.com/needle-tools/needle-engine-support/issues/133
|
401
|
-
if (tex.source)
|
402
|
-
tex.source[$progressiveTextureExtension] = ext;
|
403
|
-
const LODKEY = tex.uuid;
|
404
|
-
NEEDLE_progressive.assignLODInformation(tex, LODKEY, 0, 0, undefined);
|
405
|
-
NEEDLE_progressive.lodInfos.set(LODKEY, ext);
|
406
|
-
NEEDLE_progressive.lowresCache.set(LODKEY, tex);
|
407
|
-
}
|
408
|
-
}
|
409
|
-
|
410
|
-
}
|
411
|
-
}
|
412
|
-
}
|
413
|
-
});
|
414
|
-
|
415
|
-
|
416
|
-
const applyMeshLOD = (key: string, mesh: Mesh, level: number, index: number | undefined, ext: NEEDLE_progressive_mesh_model) => {
|
417
|
-
const geometry = mesh.geometry as BufferGeometry;
|
418
|
-
geometry["needle:raycast-mesh"] = true;
|
419
|
-
// save the low res mesh as a raycast mesh (if we have a geometry and no mesh assigned already)
|
420
|
-
if (geometry && !getRaycastMesh(mesh)) {
|
421
|
-
if (debug) console.log("Set raycast mesh", mesh.name, mesh.uuid, geometry);
|
422
|
-
setRaycastMesh(mesh, geometry);
|
423
|
-
findResourceUsers(geometry, true).forEach(user => {
|
424
|
-
if (user instanceof Mesh) {
|
425
|
-
setRaycastMesh(user, geometry);
|
426
|
-
}
|
427
|
-
});
|
428
|
-
}
|
429
|
-
if (!geometry.userData) geometry.userData = {};
|
430
|
-
NEEDLE_progressive.assignLODInformation(geometry, key, level, index, ext.density);
|
431
|
-
NEEDLE_progressive.lodInfos.set(key, ext);
|
432
|
-
};
|
433
|
-
this.parser.json.meshes?.forEach((meshInfo, index: number) => {
|
434
|
-
if (meshInfo?.extensions) {
|
435
|
-
const ext = meshInfo?.extensions[EXTENSION_NAME] as NEEDLE_progressive_mesh_model;
|
436
|
-
if (ext && ext.lods) {
|
437
|
-
for (const key of this.parser.associations.keys()) {
|
438
|
-
if (key instanceof Mesh) {
|
439
|
-
const val = this.parser.associations.get(key) as { meshes: number, primitives: number };
|
440
|
-
if (val.meshes === index) {
|
441
|
-
const obj = key;
|
442
|
-
if (debug) console.log("> Progressive: register mesh", index, obj.name, ext, obj.uuid, obj);
|
443
|
-
const LODKEY = obj.uuid;
|
444
|
-
const LODLEVEL = ext.lods.length;
|
445
|
-
if (obj instanceof Mesh) {
|
446
|
-
applyMeshLOD(LODKEY, obj, LODLEVEL, val.primitives, ext);
|
447
|
-
let existing = NEEDLE_progressive.lowresCache.get(LODKEY) as unknown as BufferGeometry[] | undefined;
|
448
|
-
if (existing) {
|
449
|
-
existing.push(obj.geometry as BufferGeometry);
|
450
|
-
}
|
451
|
-
else existing = [obj.geometry as BufferGeometry];
|
452
|
-
NEEDLE_progressive.lowresCache.set(LODKEY, existing);
|
453
|
-
}
|
454
|
-
}
|
455
|
-
}
|
456
|
-
}
|
457
|
-
|
458
|
-
}
|
459
|
-
}
|
460
|
-
});
|
461
|
-
|
462
|
-
return null;
|
463
|
-
}
|
464
|
-
|
465
|
-
/** A map of key = asset uuid and value = LOD information */
|
466
|
-
private static readonly lodInfos = new Map<string, NEEDLE_progressive_model>();
|
467
|
-
/** cache of already loaded mesh lods */
|
468
|
-
private static readonly previouslyLoaded: Map<string, Promise<null | Texture | BufferGeometry | BufferGeometry[]>> = new Map();
|
469
|
-
/** this contains the geometry/textures that were originally loaded */
|
470
|
-
private static readonly lowresCache: Map<string, Texture | BufferGeometry[]> = new Map();
|
471
|
-
|
472
|
-
private static async getOrLoadLOD<T extends Texture | BufferGeometry>(
|
473
|
-
context: Context, source: SourceIdentifier | undefined, current: T & ObjectThatMightHaveLODs, level: number
|
474
|
-
): Promise<T | null> {
|
475
|
-
|
476
|
-
const debugverbose = debug == "verbose";
|
477
|
-
|
478
|
-
/** this key is used to lookup the LOD information */
|
479
|
-
const LOD: LODInformation | undefined = current.userData.LODS;
|
480
|
-
|
481
|
-
if (!LOD) {
|
482
|
-
return null;
|
483
|
-
}
|
484
|
-
|
485
|
-
const LODKEY = LOD?.key;
|
486
|
-
|
487
|
-
let progressiveInfo: NEEDLE_progressive_model | undefined;
|
488
|
-
|
489
|
-
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
490
|
-
if (current instanceof Texture) {
|
491
|
-
if (current.source && current.source[$progressiveTextureExtension])
|
492
|
-
progressiveInfo = current.source[$progressiveTextureExtension];
|
493
|
-
}
|
494
|
-
|
495
|
-
|
496
|
-
if (!progressiveInfo) progressiveInfo = NEEDLE_progressive.lodInfos.get(LODKEY);
|
497
|
-
|
498
|
-
if (progressiveInfo) {
|
499
|
-
|
500
|
-
if (level > 0) {
|
501
|
-
let useLowRes = false;
|
502
|
-
const hasMultipleLevels = Array.isArray(progressiveInfo.lods);
|
503
|
-
if (hasMultipleLevels && level >= progressiveInfo.lods.length) {
|
504
|
-
useLowRes = true;
|
505
|
-
}
|
506
|
-
else if (!hasMultipleLevels) {
|
507
|
-
useLowRes = true;
|
508
|
-
}
|
509
|
-
if (useLowRes) {
|
510
|
-
const lowres = this.lowresCache.get(LODKEY) as T;
|
511
|
-
return lowres;
|
512
|
-
}
|
513
|
-
}
|
514
|
-
|
515
|
-
/** the unresolved LOD url */
|
516
|
-
const unresolved_lod_url = Array.isArray(progressiveInfo.lods) ? progressiveInfo.lods[level].path : progressiveInfo.lods;
|
517
|
-
|
518
|
-
// check if we have a uri
|
519
|
-
if (!unresolved_lod_url) {
|
520
|
-
if (debug && !progressiveInfo["missing:uri"]) {
|
521
|
-
progressiveInfo["missing:uri"] = true;
|
522
|
-
console.warn("Missing uri for progressive asset for LOD " + level, progressiveInfo);
|
523
|
-
}
|
524
|
-
return null;
|
525
|
-
}
|
526
|
-
|
527
|
-
/** the resolved LOD url */
|
528
|
-
let lod_url = resolveUrl(source, unresolved_lod_url);
|
529
|
-
|
530
|
-
// check if the requested file needs to be loaded via a GLTFLoader
|
531
|
-
if (lod_url.endsWith(".glb") || lod_url.endsWith(".gltf")) {
|
532
|
-
if (!progressiveInfo.guid) {
|
533
|
-
console.warn("missing pointer for glb/gltf texture", progressiveInfo);
|
534
|
-
return null;
|
535
|
-
}
|
536
|
-
// check if the requested file has already been loaded
|
537
|
-
const KEY = lod_url + "_" + progressiveInfo.guid;
|
538
|
-
|
539
|
-
// check if the requested file is currently being loaded
|
540
|
-
const existing = this.previouslyLoaded.get(KEY);
|
541
|
-
if (existing !== undefined) {
|
542
|
-
if (debugverbose) console.log(`LOD ${level} was already loading/loaded: ${KEY}`);
|
543
|
-
let res = await existing.catch(err => {
|
544
|
-
console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
|
545
|
-
return null;
|
546
|
-
});
|
547
|
-
let resouceIsDisposed = false;
|
548
|
-
if (res == null) {
|
549
|
-
// if the resource is null the last loading result didnt succeed (maybe because the url doesnt exist)
|
550
|
-
// in which case we don't attempt to load it again
|
551
|
-
}
|
552
|
-
else if (res instanceof Texture && current instanceof Texture) {
|
553
|
-
// check if the texture has been disposed or not
|
554
|
-
if (res.image?.data || res.source?.data) {
|
555
|
-
res = this.copySettings(current, res);
|
556
|
-
}
|
557
|
-
// if it has been disposed we need to load it again
|
558
|
-
else {
|
559
|
-
resouceIsDisposed = true;
|
560
|
-
this.previouslyLoaded.delete(KEY);
|
561
|
-
}
|
562
|
-
}
|
563
|
-
else if (res instanceof BufferGeometry && current instanceof BufferGeometry) {
|
564
|
-
if (res.attributes.position?.array) {
|
565
|
-
// the geometry is OK
|
566
|
-
}
|
567
|
-
else {
|
568
|
-
resouceIsDisposed = true;
|
569
|
-
this.previouslyLoaded.delete(KEY);
|
570
|
-
}
|
571
|
-
}
|
572
|
-
if (!resouceIsDisposed) {
|
573
|
-
return res as T;
|
574
|
-
}
|
575
|
-
}
|
576
|
-
|
577
|
-
const ext = progressiveInfo;
|
578
|
-
const request = new Promise<null | Texture | BufferGeometry | BufferGeometry[]>(async (resolve, _) => {
|
579
|
-
const loader = new GLTFLoader();
|
580
|
-
addDracoAndKTX2Loaders(loader, context);
|
581
|
-
|
582
|
-
if (progressiveInfo && Array.isArray(progressiveInfo.lods)) {
|
583
|
-
const lodinfo = progressiveInfo.lods[level];
|
584
|
-
if (lodinfo.hash) {
|
585
|
-
lod_url += "?v=" + lodinfo.hash;
|
586
|
-
}
|
587
|
-
}
|
588
|
-
|
589
|
-
|
590
|
-
if (debug) {
|
591
|
-
await delay(Math.random() * 1000);
|
592
|
-
if (debugverbose) console.warn("Start loading (delayed) " + lod_url, ext.guid);
|
593
|
-
}
|
594
|
-
|
595
|
-
const gltf = await loader.loadAsync(lod_url).catch(err => {
|
596
|
-
console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
|
597
|
-
return null;
|
598
|
-
});
|
599
|
-
if (!gltf) return null;
|
600
|
-
|
601
|
-
const parser = gltf.parser;
|
602
|
-
if (debugverbose) console.log("Loading finished " + lod_url, ext.guid);
|
603
|
-
let index = 0;
|
604
|
-
|
605
|
-
if (gltf.parser.json.textures) {
|
606
|
-
let found = false;
|
607
|
-
for (const tex of gltf.parser.json.textures) {
|
608
|
-
// find the texture index
|
609
|
-
if (tex?.extensions) {
|
610
|
-
const other: NEEDLE_progressive_model = tex?.extensions[EXTENSION_NAME];
|
611
|
-
if (other?.guid) {
|
612
|
-
if (other.guid === ext.guid) {
|
613
|
-
found = true;
|
614
|
-
break;
|
615
|
-
}
|
616
|
-
}
|
617
|
-
}
|
618
|
-
index++;
|
619
|
-
}
|
620
|
-
if (found) {
|
621
|
-
let tex = await parser.getDependency("texture", index) as Texture;
|
622
|
-
if (debugverbose) console.log("change \"" + current.name + "\" → \"" + tex.name + "\"", lod_url, index, tex, KEY);
|
623
|
-
if (current instanceof Texture)
|
624
|
-
tex = this.copySettings(current, tex);
|
625
|
-
if (tex) {
|
626
|
-
(tex as any).guid = ext.guid;
|
627
|
-
}
|
628
|
-
return resolve(tex);
|
629
|
-
}
|
630
|
-
}
|
631
|
-
|
632
|
-
index = 0;
|
633
|
-
|
634
|
-
if (gltf.parser.json.meshes) {
|
635
|
-
let found = false;
|
636
|
-
for (const mesh of gltf.parser.json.meshes) {
|
637
|
-
// find the mesh index
|
638
|
-
if (mesh?.extensions) {
|
639
|
-
const other: NEEDLE_progressive_model = mesh?.extensions[EXTENSION_NAME];
|
640
|
-
if (other?.guid) {
|
641
|
-
if (other.guid === ext.guid) {
|
642
|
-
found = true;
|
643
|
-
break;
|
644
|
-
}
|
645
|
-
}
|
646
|
-
}
|
647
|
-
index++;
|
648
|
-
}
|
649
|
-
if (found) {
|
650
|
-
const mesh = await parser.getDependency("mesh", index) as Mesh | Group;
|
651
|
-
|
652
|
-
const meshExt = ext as NEEDLE_progressive_mesh_model;
|
653
|
-
|
654
|
-
if (debugverbose) console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
|
655
|
-
|
656
|
-
if (mesh instanceof Mesh) {
|
657
|
-
const geo = mesh.geometry as BufferGeometry;
|
658
|
-
NEEDLE_progressive.assignLODInformation(geo, LODKEY, level, undefined, meshExt.density);
|
659
|
-
return resolve(geo);
|
660
|
-
}
|
661
|
-
else {
|
662
|
-
const geometries = new Array<BufferGeometry>();
|
663
|
-
for (let i = 0; i < mesh.children.length; i++) {
|
664
|
-
const child = mesh.children[i];
|
665
|
-
if (child instanceof Mesh) {
|
666
|
-
const geo = child.geometry as BufferGeometry;
|
667
|
-
NEEDLE_progressive.assignLODInformation(geo, LODKEY, level, i, meshExt.density);
|
668
|
-
geometries.push(geo);
|
669
|
-
}
|
670
|
-
}
|
671
|
-
return resolve(geometries);
|
672
|
-
}
|
673
|
-
}
|
674
|
-
}
|
675
|
-
|
676
|
-
// we could not find a texture or mesh with the given guid
|
677
|
-
return resolve(null);
|
678
|
-
});
|
679
|
-
this.previouslyLoaded.set(KEY, request);
|
680
|
-
const res = await request;
|
681
|
-
return res as T;
|
682
|
-
}
|
683
|
-
else {
|
684
|
-
if (current instanceof Texture) {
|
685
|
-
if (debugverbose) console.log("Load texture from uri: " + lod_url);
|
686
|
-
const loader = new TextureLoader();
|
687
|
-
const tex = await loader.loadAsync(lod_url);
|
688
|
-
if (tex) {
|
689
|
-
(tex as any).guid = progressiveInfo.guid;
|
690
|
-
tex.flipY = false;
|
691
|
-
tex.needsUpdate = true;
|
692
|
-
tex.colorSpace = current.colorSpace;
|
693
|
-
if (debugverbose)
|
694
|
-
console.log(progressiveInfo, tex);
|
695
|
-
}
|
696
|
-
else if (debug) console.warn("failed loading", lod_url);
|
697
|
-
return tex as T;
|
698
|
-
}
|
699
|
-
}
|
700
|
-
}
|
701
|
-
else {
|
702
|
-
if (debug)
|
703
|
-
console.warn(`Can not load LOD ${level}: no LOD info found for \"${LODKEY}\" ${current.name}`, current.type);
|
704
|
-
}
|
705
|
-
return null;
|
706
|
-
}
|
707
|
-
|
708
|
-
private static assignLODInformation(res: DeepWriteable<ObjectThatMightHaveLODs>, key: string, level: number, index?: number, density?: number) {
|
709
|
-
if (!res) return;
|
710
|
-
if (!res.userData) res.userData = {};
|
711
|
-
const info: LODInformation = new LODInformation(key, level, index, density);
|
712
|
-
res.userData.LODS = info;
|
713
|
-
res.userData.LOD = level;
|
714
|
-
}
|
715
|
-
private static getAssignedLODInformation(res: ObjectThatMightHaveLODs | null | undefined): null | LODInformation {
|
716
|
-
return res?.userData?.LODS || null;
|
717
|
-
}
|
718
|
-
|
719
|
-
private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
|
720
|
-
|
721
|
-
private static copySettings(source: Texture, target: Texture): Texture {
|
722
|
-
// don't copy again if the texture was processed before
|
723
|
-
const existingClone = this._copiedTextures.get(source);
|
724
|
-
if (existingClone) {
|
725
|
-
return existingClone;
|
726
|
-
}
|
727
|
-
// We need to clone e.g. when the same texture is used multiple times (but with e.g. different wrap settings)
|
728
|
-
// This is relatively cheap since it only stores settings
|
729
|
-
// This should only happen once ever for every texture
|
730
|
-
target = target.clone();
|
731
|
-
this._copiedTextures.set(source, target);
|
732
|
-
// we re-use the offset and repeat settings because it might be animated
|
733
|
-
target.offset = source.offset;
|
734
|
-
target.repeat = source.repeat;
|
735
|
-
target.colorSpace = source.colorSpace;
|
736
|
-
return target;
|
737
|
-
|
738
|
-
}
|
739
|
-
|
740
|
-
|
741
|
-
/** subscribe to events whenever a loading event starts, invoked for every single loading process that starts */
|
742
|
-
static beginListenStart(context: Context, evt: ProgressiveLoadingEvent) {
|
743
|
-
if (!this._progressiveEventListeners.has(context)) {
|
744
|
-
this._progressiveEventListeners.set(context, new ProgressiveLoadingEventHandler());
|
745
|
-
}
|
746
|
-
this._progressiveEventListeners.get(context)!.start.push(evt);
|
747
|
-
}
|
748
|
-
static stopListenStart(context: Context, evt: ProgressiveLoadingEvent) {
|
749
|
-
if (!this._progressiveEventListeners.has(context)) {
|
750
|
-
return;
|
751
|
-
}
|
752
|
-
const listeners = this._progressiveEventListeners.get(context)!.start;
|
753
|
-
const index = listeners.indexOf(evt);
|
754
|
-
if (index >= 0) {
|
755
|
-
listeners.splice(index, 1);
|
756
|
-
}
|
757
|
-
}
|
758
|
-
|
759
|
-
/** subscribe to loading event ended event */
|
760
|
-
static beginListenEnd(context: Context, evt: ProgressiveLoadingEvent) {
|
761
|
-
if (!this._progressiveEventListeners.has(context)) {
|
762
|
-
this._progressiveEventListeners.set(context, new ProgressiveLoadingEventHandler());
|
763
|
-
}
|
764
|
-
this._progressiveEventListeners.get(context)!.end.push(evt);
|
765
|
-
}
|
766
|
-
static stopListenEnd(context: Context, evt: ProgressiveLoadingEvent) {
|
767
|
-
if (!this._progressiveEventListeners.has(context)) {
|
768
|
-
return;
|
769
|
-
}
|
770
|
-
const listeners = this._progressiveEventListeners.get(context)!.end;
|
771
|
-
const index = listeners.indexOf(evt);
|
772
|
-
if (index >= 0) {
|
773
|
-
listeners.splice(index, 1);
|
774
|
-
}
|
775
|
-
}
|
776
|
-
|
777
|
-
/** event listeners per context */
|
778
|
-
private static _progressiveEventListeners: Map<Context, ProgressiveLoadingEventHandler> = new Map();
|
779
|
-
//** loading info per context, contains an array of urls that are currently being loaded */
|
780
|
-
private static _currentProgressiveLoadingInfo: Map<Context, ProgressiveLoadingInfo[]> = new Map();
|
781
|
-
|
782
|
-
// called whenever a progressive loading event starts
|
783
|
-
private static onProgressiveLoadStart(context: Context, source: SourceIdentifier | undefined, material: Mesh | Material | null, slot: string | null): ProgressiveLoadingInfo {
|
784
|
-
if (!this._currentProgressiveLoadingInfo.has(context)) {
|
785
|
-
this._currentProgressiveLoadingInfo.set(context, []);
|
786
|
-
}
|
787
|
-
const info = new ProgressiveLoadingInfo(context, source, material, slot);
|
788
|
-
const current = this._currentProgressiveLoadingInfo.get(context)!;
|
789
|
-
const listener = this._progressiveEventListeners.get(context);
|
790
|
-
if (listener) listener.onStart(info);
|
791
|
-
|
792
|
-
current.push(info);
|
793
|
-
return info;
|
794
|
-
}
|
795
|
-
|
796
|
-
private static onProgressiveLoadEnd(info: ProgressiveLoadingInfo) {
|
797
|
-
if (!info) return;
|
798
|
-
const context = info.context;
|
799
|
-
if (!this._currentProgressiveLoadingInfo.has(context)) {
|
800
|
-
return;
|
801
|
-
}
|
802
|
-
const current = this._currentProgressiveLoadingInfo.get(context)!;
|
803
|
-
const index = current.indexOf(info);
|
804
|
-
if (index < 0) {
|
805
|
-
return;
|
806
|
-
}
|
807
|
-
current.splice(index, 1);
|
808
|
-
const listener = this._progressiveEventListeners.get(context);
|
809
|
-
if (listener) listener.onEnd(info);
|
810
|
-
}
|
811
|
-
}
|
812
|
-
|
813
|
-
declare type ObjectThatMightHaveLODs = { userData?: { LODS?: LODInformation, readonly LOD?: number } };
|
814
|
-
|
815
|
-
// declare type GetLODInformation = () => LODInformation | null;
|
816
|
-
|
817
|
-
class LODInformation {
|
818
|
-
/** the key to lookup the LOD information */
|
819
|
-
readonly key: string;
|
820
|
-
readonly level: number;
|
821
|
-
/** For multi objects (e.g. a group of meshes) this is the index of the object */
|
822
|
-
readonly index?: number;
|
823
|
-
/** the mesh density */
|
824
|
-
readonly density?: number;
|
825
|
-
|
826
|
-
constructor(key: string, level: number, index?: number, density?: number) {
|
827
|
-
this.key = key;
|
828
|
-
this.level = level;
|
829
|
-
if (index != undefined)
|
830
|
-
this.index = index;
|
831
|
-
if (density != undefined)
|
832
|
-
this.density = density;
|
833
|
-
}
|
834
|
-
};
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
/** info object that holds information about a file that is currently being loaded */
|
839
|
-
export class ProgressiveLoadingInfo {
|
840
|
-
readonly context: Context;
|
841
|
-
readonly source: SourceIdentifier | undefined;
|
842
|
-
readonly material?: Material | null;
|
843
|
-
readonly slot?: string | null;
|
844
|
-
readonly mesh?: Mesh | null;
|
845
|
-
// TODO: can contain information if the event is a background process / preloading or if the object is currently visible
|
846
|
-
|
847
|
-
constructor(context: Context, source: SourceIdentifier | undefined, object?: Mesh | Material | null, slot?: string | null) {
|
848
|
-
this.context = context;
|
849
|
-
this.source = source;
|
850
|
-
if (object instanceof Mesh) {
|
851
|
-
this.mesh = object;
|
852
|
-
}
|
853
|
-
else
|
854
|
-
this.material = object;
|
855
|
-
this.slot = slot;
|
856
|
-
}
|
857
|
-
};
|
858
|
-
|
859
|
-
|
860
|
-
export declare type ProgressiveLoadingEvent = (info: ProgressiveLoadingInfo) => void;
|
861
|
-
|
862
|
-
/** progressive loading event handler implementation */
|
863
|
-
class ProgressiveLoadingEventHandler {
|
864
|
-
start: ProgressiveLoadingEvent[] = [];
|
865
|
-
end: ProgressiveLoadingEvent[] = [];
|
866
|
-
|
867
|
-
onStart(listener: ProgressiveLoadingInfo) {
|
868
|
-
for (const l of this.start) {
|
869
|
-
l(listener);
|
870
|
-
}
|
871
|
-
}
|
872
|
-
|
873
|
-
onEnd(listener: ProgressiveLoadingInfo) {
|
874
|
-
for (const l of this.end) {
|
875
|
-
l(listener);
|
876
|
-
}
|
877
|
-
}
|
878
|
-
}
|
1
|
+
export * from "@needle-tools/gltf-progressive"
|
@@ -102,7 +102,7 @@
|
|
102
102
|
if (debugProgressiveLoading) {
|
103
103
|
console.log("Load material LOD", material.name);
|
104
104
|
}
|
105
|
-
NEEDLE_progressive.assignTextureLOD(
|
105
|
+
NEEDLE_progressive.assignTextureLOD(material, 0);
|
106
106
|
}
|
107
107
|
|
108
108
|
return material;
|
@@ -29,26 +29,6 @@
|
|
29
29
|
const debugRenderer = getParam("debugrenderer");
|
30
30
|
const debugskinnedmesh = getParam("debugskinnedmesh");
|
31
31
|
const suppressInstancing = getParam("noinstancing");
|
32
|
-
let debugProgressiveLoading = getParam("debugprogressive");
|
33
|
-
const suppressProgressiveLoading = getParam("noprogressive");
|
34
|
-
|
35
|
-
if (debugProgressiveLoading && document) {
|
36
|
-
onStart(ctx => {
|
37
|
-
const button = document.createElement("button");
|
38
|
-
button.innerText = "Prog Debug Mode: " + debugProgressiveLoading;
|
39
|
-
button.onclick = () => {
|
40
|
-
if (debugProgressiveLoading == "density")
|
41
|
-
debugProgressiveLoading = false;
|
42
|
-
else if (debugProgressiveLoading == false)
|
43
|
-
debugProgressiveLoading = true;
|
44
|
-
else if (debugProgressiveLoading)
|
45
|
-
debugProgressiveLoading = "density";
|
46
|
-
button.innerText = "Prog Debug Mode: " + debugProgressiveLoading;
|
47
|
-
};
|
48
|
-
ctx.menu.appendChild(button);
|
49
|
-
});
|
50
|
-
}
|
51
|
-
|
52
32
|
const showWireframe = getParam("wireframe");
|
53
33
|
|
54
34
|
export enum ReflectionProbeUsage {
|
@@ -620,7 +600,7 @@
|
|
620
600
|
|
621
601
|
this.updateReflectionProbe();
|
622
602
|
|
623
|
-
this.testIfLODLevelsAreAvailable();
|
603
|
+
// this.testIfLODLevelsAreAvailable();
|
624
604
|
}
|
625
605
|
|
626
606
|
onDisable() {
|
@@ -701,33 +681,14 @@
|
|
701
681
|
}
|
702
682
|
}
|
703
683
|
|
704
|
-
if (this._wasVisible && this.allowProgressiveLoading && !suppressProgressiveLoading) {
|
705
|
-
if (this.automaticallyUpdateLODLevel) {
|
706
|
-
this.updateLODs();
|
707
|
-
}
|
708
|
-
// else {
|
709
|
-
// for (const mat of this.sharedMaterials) {
|
710
|
-
// if (!mat) continue;
|
711
|
-
// this.loadProgressiveTextures(mat, 0)
|
712
|
-
// }
|
713
|
-
// }
|
714
|
-
}
|
715
|
-
|
716
684
|
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
717
685
|
this._reflectionProbe.onSet(this);
|
718
686
|
}
|
719
687
|
|
720
688
|
}
|
721
689
|
|
722
|
-
/** true when the object was rendered - this is used to start loading LOD levels */
|
723
|
-
private _wasVisible = false;
|
724
|
-
|
725
690
|
private onBeforeRenderThree = (_renderer, _scene, _camera, _geometry, material, _group) => {
|
726
691
|
|
727
|
-
if (!this._wasVisible && this.context.time.frame > this._firstFrame + 1) {
|
728
|
-
if (debugProgressiveLoading) console.debug("onBeforeRenderThree: Object becomes visible for the first time", this.name, this.context.time.frame);
|
729
|
-
this._wasVisible = true;
|
730
|
-
}
|
731
692
|
|
732
693
|
if (material.envMapIntensity !== undefined) {
|
733
694
|
const factor = this.hasLightmap ? Math.PI : 1;
|
@@ -780,7 +741,6 @@
|
|
780
741
|
if (this._isInstancingEnabled && this.handles) {
|
781
742
|
for (let i = 0; i < this.handles.length; i++) {
|
782
743
|
const handle = this.handles[i];
|
783
|
-
this._wasVisible = true;
|
784
744
|
setCustomVisibility(handle.object, true);
|
785
745
|
}
|
786
746
|
}
|
@@ -788,437 +748,16 @@
|
|
788
748
|
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
789
749
|
this._reflectionProbe.onUnset(this)
|
790
750
|
}
|
791
|
-
|
792
|
-
if (debugProgressiveLoading && this._lastLodLevel >= 0)
|
793
|
-
this.drawGizmoLodLevel(false);
|
794
751
|
}
|
795
752
|
|
796
|
-
private testIfLODLevelsAreAvailable() {
|
797
|
-
this.automaticallyUpdateLODLevel = false;
|
798
|
-
this.hasMeshLODs = false;
|
799
|
-
this.hasTextureLODs = false;
|
800
|
-
|
801
|
-
for (const mesh of this.sharedMeshes) {
|
802
|
-
if (NEEDLE_progressive.hasLODLevelAvailable(mesh)) {
|
803
|
-
this.automaticallyUpdateLODLevel = true;
|
804
|
-
this.hasMeshLODs = true;
|
805
|
-
break;
|
806
|
-
}
|
807
|
-
}
|
808
|
-
for (const mat of this.sharedMaterials) {
|
809
|
-
if (!mat) continue;
|
810
|
-
if (NEEDLE_progressive.hasLODLevelAvailable(mat)) {
|
811
|
-
this.automaticallyUpdateLODLevel = true;
|
812
|
-
this.hasTextureLODs = true;
|
813
|
-
break;
|
814
|
-
}
|
815
|
-
}
|
816
|
-
if (debugProgressiveLoading) {
|
817
|
-
console.log(`${this.name}\nMesh LODs available? ${this.hasMeshLODs ? "YES" : "NO"}\nTexture LODs available? ${this.hasTextureLODs ? "YES" : "NO"}`);
|
818
|
-
}
|
819
|
-
}
|
820
|
-
|
821
|
-
/** Enable to automatically update the LOD level for the renderers meshes and materials.
|
822
|
-
* When disabled you can manually call `updateLODLevels` to update the LOD levels.
|
823
|
-
*/
|
824
|
-
automaticallyUpdateLODLevel = true;
|
825
|
-
private hasTextureLODs: boolean = true;
|
826
|
-
private hasMeshLODs: boolean = true;
|
827
|
-
|
828
|
-
/** Update the LOD levels for the renderer. */
|
829
|
-
updateLODs() {
|
830
|
-
let level = 0;
|
831
|
-
// we currently only support auto LOD changes for meshes
|
832
|
-
if (this.hasMeshLODs) {
|
833
|
-
level = this.calculateLodLevel();
|
834
|
-
}
|
835
|
-
|
836
|
-
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
837
|
-
const textureLOD = 0;// Math.max(0, LODlevel - 2)
|
838
|
-
|
839
|
-
for (const mat of this.sharedMaterials) {
|
840
|
-
if (mat) this.loadProgressiveTextures(mat, textureLOD);
|
841
|
-
}
|
842
|
-
|
843
|
-
if (level >= 0) {
|
844
|
-
for (let i = 0; i < this.sharedMeshes.length; i++) {
|
845
|
-
const mesh = this.sharedMeshes[i];
|
846
|
-
if (!mesh) continue;
|
847
|
-
this.loadProgressiveMeshes(mesh, level);
|
848
|
-
}
|
849
|
-
}
|
850
|
-
}
|
851
|
-
|
852
|
-
private _lastLodLevel = -1;
|
853
|
-
private _lastScreenCoverage = 0;
|
854
|
-
private _lastScreenspaceVolume = new Vector3();
|
855
|
-
private _lastCentrality = 0;
|
856
|
-
private _nextLodTestTime = 0;
|
857
|
-
private _randomLodLevelCheckFrameOffset = Math.floor(Math.random() * 100);
|
858
|
-
private readonly _sphere = new Sphere();
|
859
|
-
private readonly _box = new Box3();
|
860
|
-
private static readonly tempMatrix = new Matrix4();
|
861
|
-
|
862
|
-
private calculateLodLevel(force: boolean = false): number {
|
863
|
-
|
864
|
-
// if this is using instancing we always load level 0
|
865
|
-
// if (this.isInstancingActive) return 0;
|
866
|
-
|
867
|
-
const interval = 3;
|
868
|
-
|
869
|
-
if (!(debugProgressiveLoading == "density")) {
|
870
|
-
if (!force && (this.context.time.frame + this._randomLodLevelCheckFrameOffset) % interval != 0) {
|
871
|
-
return this._lastLodLevel;
|
872
|
-
}
|
873
|
-
|
874
|
-
if (this.context.time.realtimeSinceStartup < this._nextLodTestTime) {
|
875
|
-
return this._lastLodLevel;
|
876
|
-
}
|
877
|
-
}
|
878
|
-
|
879
|
-
/** rough measure of "triangles on quadratic screen" – we're switching LODs based on this metric. */
|
880
|
-
const desiredDensity = 100_000;
|
881
|
-
/** highest LOD level we'd ever expect to be generated */
|
882
|
-
const maxLevel = 10;
|
883
|
-
|
884
|
-
let currentAllowedDensity = desiredDensity;
|
885
|
-
let level = maxLevel + 1;
|
886
|
-
|
887
|
-
if (this.context.mainCamera) {
|
888
|
-
|
889
|
-
const isLowPerformanceDevice = isMobileDevice();
|
890
|
-
/** We probably want to make these numbers configurable. */
|
891
|
-
currentAllowedDensity = desiredDensity * (isLowPerformanceDevice ? 0.7 : 1.0);
|
892
|
-
|
893
|
-
// Experiment: quick & dirty performance-adaptive LODs
|
894
|
-
/*
|
895
|
-
if (this.context.time.smoothedFps < 59) {
|
896
|
-
currentAllowedDensity *= 0.5;
|
897
|
-
}
|
898
|
-
else if (this.context.time.smoothedFps >= 59) {
|
899
|
-
currentAllowedDensity *= 1.25;
|
900
|
-
}
|
901
|
-
*/
|
902
|
-
|
903
|
-
// TODO: we should save the LOD level in the shared mesh and not just calculate one level per renderer
|
904
|
-
for (const mesh of this.sharedMeshes) {
|
905
|
-
if (level <= 0) break;
|
906
|
-
if (!mesh) continue;
|
907
|
-
|
908
|
-
if (debugProgressiveLoading && mesh["DEBUG:LOD"] != undefined) {
|
909
|
-
return this._lastLodLevel = mesh["DEBUG:LOD"];
|
910
|
-
}
|
911
|
-
|
912
|
-
// The mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
913
|
-
const lodsInfo = NEEDLE_progressive.getMeshLODInformation(mesh.geometry);
|
914
|
-
const lods = lodsInfo?.lods;
|
915
|
-
|
916
|
-
// TODO: we can skip all this if we dont have any LOD information - we can ask the progressive extension for that
|
917
|
-
const frustum = this.context.mainCameraComponent?.getFrustum();
|
918
|
-
if (!frustum?.intersectsObject(mesh)) {
|
919
|
-
if (debugProgressiveLoading && mesh.geometry.boundingSphere) {
|
920
|
-
const bounds = mesh.geometry.boundingSphere;
|
921
|
-
this._sphere.copy(bounds);
|
922
|
-
this._sphere.applyMatrix4(mesh.matrixWorld);
|
923
|
-
Gizmos.DrawWireSphere(this._sphere.center, this._sphere.radius * 1.01, 0xff5555, .5);
|
924
|
-
}
|
925
|
-
// the object is not visible by the camera
|
926
|
-
continue;
|
927
|
-
}
|
928
|
-
|
929
|
-
const box = mesh.geometry.boundingBox;
|
930
|
-
if (box && this.context.mainCamera instanceof PerspectiveCamera) {
|
931
|
-
|
932
|
-
// hack: if the mesh has vertex colors, has less than 100 vertices we always select the highest LOD
|
933
|
-
if (mesh.geometry.attributes.color && mesh.geometry.attributes.color.count < 100) {
|
934
|
-
if (mesh.geometry.boundingSphere) {
|
935
|
-
this._sphere.copy(mesh.geometry.boundingSphere);
|
936
|
-
this._sphere.applyMatrix4(mesh.matrixWorld);
|
937
|
-
if (this._sphere.containsPoint(getWorldPosition(this.context.mainCamera))) {
|
938
|
-
level = 0;
|
939
|
-
break;
|
940
|
-
}
|
941
|
-
}
|
942
|
-
}
|
943
|
-
|
944
|
-
// calculate size on screen
|
945
|
-
this._box.copy(box);
|
946
|
-
this._box.applyMatrix4(mesh.matrixWorld);
|
947
|
-
const cam = this.context.mainCamera;
|
948
|
-
|
949
|
-
// Converting into projection space has the disadvantage that objects further to the side
|
950
|
-
// will have a much larger coverage, especially with high-field-of-view situations like in VR.
|
951
|
-
// Alternatively, we could attempt to calculate angular coverage (some kind of polar coordinates maybe?)
|
952
|
-
// or introduce a correction factor based on "expected distortion" of the object.
|
953
|
-
// High distortions would lead to lower LOD levels.
|
954
|
-
// "Centrality" of the calculated screen-space bounding box could be a factor here –
|
955
|
-
// what's the distance of the bounding box to the center of the screen?
|
956
|
-
const mat = this.context.mainCameraComponent!.getProjectionScreenMatrix(Renderer.tempMatrix);
|
957
|
-
this._box.applyMatrix4(mat);
|
958
|
-
|
959
|
-
// TODO might need to be adjusted for cameras that are rendered during an XR session but are
|
960
|
-
// actually not XR cameras (e.g. a render texture)
|
961
|
-
if (this.context.isInXR && cam.fov > 70) {
|
962
|
-
// calculate centrality of the bounding box - how close is it to the screen center
|
963
|
-
const min = this._box.min;
|
964
|
-
const max = this._box.max;
|
965
|
-
|
966
|
-
let minX = min.x;
|
967
|
-
let minY = min.y;
|
968
|
-
let maxX = max.x;
|
969
|
-
let maxY = max.y;
|
970
|
-
|
971
|
-
// enlarge
|
972
|
-
const enlargementFactor = 2.0;
|
973
|
-
const centerBoost = 1.5;
|
974
|
-
const centerX = (min.x + max.x) * 0.5;
|
975
|
-
const centerY = (min.y + max.y) * 0.5;
|
976
|
-
minX = (minX - centerX) * enlargementFactor + centerX;
|
977
|
-
minY = (minY - centerY) * enlargementFactor + centerY;
|
978
|
-
maxX = (maxX - centerX) * enlargementFactor + centerX;
|
979
|
-
maxY = (maxY - centerY) * enlargementFactor + centerY;
|
980
|
-
|
981
|
-
const xCentrality = minX < 0 && maxX > 0 ? 0 : Math.min(Math.abs(min.x), Math.abs(max.x));
|
982
|
-
const yCentrality = minY < 0 && maxY > 0 ? 0 : Math.min(Math.abs(min.y), Math.abs(max.y));
|
983
|
-
const centrality = Math.max(xCentrality, yCentrality);
|
984
|
-
|
985
|
-
// heuristically determined to lower quality for objects at the edges of vision
|
986
|
-
this._lastCentrality = (centerBoost - centrality) * (centerBoost - centrality) * (centerBoost - centrality);
|
987
|
-
}
|
988
|
-
else {
|
989
|
-
this._lastCentrality = 1;
|
990
|
-
}
|
991
|
-
const boxSize = this._box.getSize(getTempVector());
|
992
|
-
boxSize.multiplyScalar(0.5); // goes from -1..1, we want -0.5..0.5 for coverage in percent
|
993
|
-
if (screen.availHeight > 0)
|
994
|
-
boxSize.multiplyScalar(this.context.domHeight / screen.availHeight); // correct for size of context on screen
|
995
|
-
boxSize.x *= cam.aspect;
|
996
|
-
|
997
|
-
const matView = cam.matrixWorldInverse;
|
998
|
-
const box2 = new Box3();
|
999
|
-
box2.copy(box);
|
1000
|
-
box2.applyMatrix4(mesh.matrixWorld);
|
1001
|
-
box2.applyMatrix4(matView);
|
1002
|
-
const boxSize2 = box2.getSize(getTempVector());
|
1003
|
-
|
1004
|
-
// approximate depth coverage in relation to screenspace size
|
1005
|
-
const max2 = Math.max(boxSize2.x, boxSize2.y);
|
1006
|
-
const max1 = Math.max(boxSize.x, boxSize.y);
|
1007
|
-
if (max1 != 0 && max2 != 0)
|
1008
|
-
boxSize.z = boxSize2.z / Math.max(boxSize2.x, boxSize2.y) * Math.max(boxSize.x, boxSize.y);
|
1009
|
-
|
1010
|
-
this._lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
1011
|
-
this._lastScreenspaceVolume.copy(boxSize);
|
1012
|
-
this._lastScreenCoverage *= this._lastCentrality;
|
1013
|
-
|
1014
|
-
// draw screen size box
|
1015
|
-
if (debugProgressiveLoading) {
|
1016
|
-
mat.invert();
|
1017
|
-
|
1018
|
-
// get box corners, transform with camera space, and draw as quad lines
|
1019
|
-
const corner0 = getTempVector();
|
1020
|
-
corner0.copy(this._box.min);
|
1021
|
-
const corner1 = getTempVector();
|
1022
|
-
corner1.copy(this._box.max);
|
1023
|
-
corner1.x = corner0.x;
|
1024
|
-
const corner2 = getTempVector();
|
1025
|
-
corner2.copy(this._box.max);
|
1026
|
-
corner2.y = corner0.y;
|
1027
|
-
const corner3 = getTempVector();
|
1028
|
-
corner3.copy(this._box.max);
|
1029
|
-
// draw outlines at the center of the box
|
1030
|
-
const z = (corner0.z + corner3.z) * 0.5;
|
1031
|
-
// all outlines should have the same depth in screen space
|
1032
|
-
corner0.z = corner1.z = corner2.z = corner3.z = z;
|
1033
|
-
|
1034
|
-
corner0.applyMatrix4(mat);
|
1035
|
-
corner1.applyMatrix4(mat);
|
1036
|
-
corner2.applyMatrix4(mat);
|
1037
|
-
corner3.applyMatrix4(mat);
|
1038
|
-
|
1039
|
-
Gizmos.DrawLine(corner0, corner1, 0x0000ff);
|
1040
|
-
Gizmos.DrawLine(corner0, corner2, 0x0000ff);
|
1041
|
-
Gizmos.DrawLine(corner1, corner3, 0x0000ff);
|
1042
|
-
Gizmos.DrawLine(corner2, corner3, 0x0000ff);
|
1043
|
-
}
|
1044
|
-
|
1045
|
-
let expectedLevel = 999;
|
1046
|
-
const framerate = this.context.time.smoothedFps;
|
1047
|
-
if (lods && this._lastScreenCoverage > 0) {
|
1048
|
-
for (let l = 0; l < lods.length; l++) {
|
1049
|
-
const densityForThisLevel = lods[l].density;
|
1050
|
-
if (densityForThisLevel / this._lastScreenCoverage < currentAllowedDensity) {
|
1051
|
-
expectedLevel = l;
|
1052
|
-
break;
|
1053
|
-
}
|
1054
|
-
}
|
1055
|
-
}
|
1056
|
-
|
1057
|
-
// expectedLevel -= meshDensity - 5;
|
1058
|
-
// expectedLevel += meshDensity;
|
1059
|
-
const isLowerLod = expectedLevel < level;
|
1060
|
-
if (isLowerLod) {
|
1061
|
-
level = expectedLevel;
|
1062
|
-
}
|
1063
|
-
}
|
1064
|
-
}
|
1065
|
-
}
|
1066
|
-
|
1067
|
-
level = Math.round(level);
|
1068
|
-
|
1069
|
-
if (this._lastLodLevel != level) {
|
1070
|
-
this._nextLodTestTime = this.context.time.realtimeSinceStartup + .5;
|
1071
|
-
if (debugProgressiveLoading) {
|
1072
|
-
if (debugProgressiveLoading == "verbose") console.warn(`LOD Level changed from ${this._lastLodLevel} to ${level} for ${this.name}`);
|
1073
|
-
this.drawGizmoLodLevel(true);
|
1074
|
-
}
|
1075
|
-
}
|
1076
|
-
|
1077
|
-
this._lastLodLevel = level;
|
1078
|
-
return level;
|
1079
|
-
}
|
1080
|
-
|
1081
|
-
private drawGizmoLodLevel(changed: boolean) {
|
1082
|
-
// Will be (maxLod + 1) (11) if no lod level is found
|
1083
|
-
const _level = this._lastLodLevel;
|
1084
|
-
|
1085
|
-
const cam = this.context.mainCamera as any as PerspectiveCamera;
|
1086
|
-
const camGO = cam as any as GameObject;
|
1087
|
-
const camForward = camGO.worldForward;
|
1088
|
-
const camWorld = camGO.worldPosition;
|
1089
|
-
|
1090
|
-
for (const mesh of this.sharedMeshes) {
|
1091
|
-
if (!mesh) continue;
|
1092
|
-
if (mesh.geometry.boundingSphere) {
|
1093
|
-
const bounds = mesh.geometry.boundingSphere;
|
1094
|
-
this._sphere.copy(bounds);
|
1095
|
-
this._sphere.applyMatrix4(mesh.matrixWorld);
|
1096
|
-
const boundsCenter = this._sphere.center;
|
1097
|
-
const radius = this._sphere.radius;
|
1098
|
-
const colors = ["#76c43e", "#bcc43e", "#c4ac3e", "#c4673e", "#ff3e3e"];
|
1099
|
-
// if the lod has changed we just want to draw the gizmo for the changed mesh
|
1100
|
-
if (changed) {
|
1101
|
-
Gizmos.DrawWireSphere(boundsCenter, radius, colors[_level], .1);
|
1102
|
-
}
|
1103
|
-
else {
|
1104
|
-
// Mesh Density is calculated as: triangle count per square meter of surface area, normalized to the bounding box size of the model.
|
1105
|
-
// Our goal for automatic switching of LODs is that the resulting triangle count per screen area is constant.
|
1106
|
-
// We assume a uniform distribution of triangles over the surface area; which means that
|
1107
|
-
// we can express a ratio of "screen area to surface area".
|
1108
|
-
const triangleCount = mesh.geometry.index!.count / 3;
|
1109
|
-
const lods = NEEDLE_progressive.getMeshLODInformation(mesh.geometry)?.lods;
|
1110
|
-
const level = lods ? Math.min(lods?.length - 1, _level) : 0;
|
1111
|
-
let allLods = "";
|
1112
|
-
if (lods && this._lastScreenCoverage > 0) {
|
1113
|
-
for (let i = 0; i < lods.length; i++) {
|
1114
|
-
const d = lods[i].density;
|
1115
|
-
const last = i == lods.length - 1;
|
1116
|
-
allLods += d.toFixed(0) + ">" + (d / this._lastScreenCoverage).toFixed(0) + (last ? "" : ",");
|
1117
|
-
}
|
1118
|
-
}
|
1119
|
-
const density = lods ? lods[level]?.density : -1;
|
1120
|
-
const box = mesh.geometry.boundingBox;
|
1121
|
-
const boxSize = box ? box.getSize(getTempVector()) : new Vector3();
|
1122
|
-
const maxBoxSize = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
1123
|
-
|
1124
|
-
// Surface area is in local space of the model;
|
1125
|
-
// we need to scale it by the model's world scale and the model's geometry bounding box size.
|
1126
|
-
const ws = mesh.getWorldScale(getTempVector());
|
1127
|
-
const wsMedian = (ws.x + ws.y + ws.z) / 3;
|
1128
|
-
// Area is squared, so both maxBoxSize and wsMedian are squared here
|
1129
|
-
// Here, we're basically reverting the calculations that have happened in the pipeline for debugging.
|
1130
|
-
const surfaceArea = 1 / density * triangleCount * (maxBoxSize * maxBoxSize) * (wsMedian * wsMedian);
|
1131
|
-
let text = "LOD " + level;
|
1132
|
-
if(debugProgressiveLoading == "density") {
|
1133
|
-
text +=
|
1134
|
-
"\n" + triangleCount + " tris" +
|
1135
|
-
// This is key – basically how we're switching
|
1136
|
-
"\n" + (density / this._lastScreenCoverage).toFixed(0) + " dens" +
|
1137
|
-
"\n" + (this._lastScreenCoverage * 100).toFixed(1) + "% cov" +
|
1138
|
-
// "\n" + (this._lastScreenspaceVolume.x.toFixed(2) + "x" + this._lastScreenspaceVolume.y.toFixed(2) + "x" + this._lastScreenspaceVolume.z.toFixed(2)) + " vol" +
|
1139
|
-
// + "\n" + (surfaceArea).toFixed(2) + " m2" +
|
1140
|
-
"\n" + (this._lastCentrality * 100).toFixed(1) + "% centr" +
|
1141
|
-
"\n" + (this._box.min.x.toFixed(2) + "-" + this._box.max.x.toFixed(2) + "x" + this._box.min.y.toFixed(2) + "-" + this._box.max.y.toFixed(2)) + " scr" +
|
1142
|
-
// "\n" + (ws.x).toFixed(2) + "x" + " " + maxBoxSize.toFixed(2) + "b" + "\n" +
|
1143
|
-
// allLods + "\n" +
|
1144
|
-
//"----" + "\n" +
|
1145
|
-
// "1000" + " ideal dens"
|
1146
|
-
"";
|
1147
|
-
}
|
1148
|
-
|
1149
|
-
// if (helper) {
|
1150
|
-
// helper?.setText(text);
|
1151
|
-
// continue;
|
1152
|
-
// }
|
1153
|
-
const fwd = getTempVector(camForward);
|
1154
|
-
// for debugging very close LDOs, we need to flip the radius...
|
1155
|
-
const pos = fwd.multiplyScalar(radius * .7).add(boundsCenter);
|
1156
|
-
const distance = pos.distanceTo(camWorld);
|
1157
|
-
// const vertexCount = mesh.geometry.index!.count / 3;
|
1158
|
-
// const vertexCountFactor = Math.min(1, vertexCount / 1000);
|
1159
|
-
const col = colors[Math.min(colors.length - 1, level)] + "88";
|
1160
|
-
// const size = Math.min(10, radius);
|
1161
|
-
const windowScale = this.context.domHeight > 0 ? screen.height / this.context.domHeight : 1;
|
1162
|
-
const fieldOfViewScale = Math.tan(cam.fov * Math.PI / 180 / 2);
|
1163
|
-
Gizmos.DrawLabel(pos, text, distance * .01 * windowScale * fieldOfViewScale, undefined, 0xffffff, col);
|
1164
|
-
// mesh["LOD_level_label"] = helper;
|
1165
|
-
}
|
1166
|
-
|
1167
|
-
}
|
1168
|
-
}
|
1169
|
-
}
|
1170
|
-
|
1171
753
|
/** Applies stencil settings for this renderer's objects (if stencil settings are available) */
|
1172
754
|
applyStencil() {
|
1173
755
|
NEEDLE_render_objects.applyStencil(this);
|
1174
756
|
}
|
1175
757
|
|
1176
758
|
|
1177
|
-
/** Load progressive textures for the given material
|
1178
|
-
* @param material the material to load the textures for
|
1179
|
-
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
1180
|
-
* @returns Promise with true if the LOD was loaded, false if not
|
1181
|
-
*/
|
1182
|
-
loadProgressiveTextures(material: Material, level: number): Promise<ProgressiveMaterialTextureLoadingResult[] | Texture | null> {
|
1183
|
-
if (!material) return Promise.resolve(null);
|
1184
|
-
if (material.userData && material.userData.LOD !== level) {
|
1185
|
-
material.userData.LOD = level;
|
1186
|
-
return NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId!, material, level);
|
1187
|
-
}
|
1188
|
-
return Promise.resolve(null);
|
1189
|
-
}
|
1190
759
|
|
1191
|
-
/** Load progressive meshes for the given mesh
|
1192
|
-
* @param mesh the mesh to load the LOD for
|
1193
|
-
* @param index the index of the mesh if it's part of a group
|
1194
|
-
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
1195
|
-
* @returns Promise with true if the LOD was loaded, false if not
|
1196
|
-
*/
|
1197
|
-
loadProgressiveMeshes(mesh: Mesh, level: number) : Promise<BufferGeometry | null> {
|
1198
|
-
if (!mesh) return Promise.resolve(null);
|
1199
|
-
if (!mesh.userData) mesh.userData = {};
|
1200
|
-
if (mesh.userData.LOD !== level) {
|
1201
|
-
mesh.userData.LOD = level;
|
1202
|
-
const originalGeometry = mesh.geometry;
|
1203
|
-
return NEEDLE_progressive.assignMeshLOD(this.context, this.sourceId!, mesh, level).then(res => {
|
1204
|
-
if (res && mesh.userData.LOD == level && originalGeometry != mesh.geometry) {
|
1205
|
-
// update the lightmap
|
1206
|
-
this.applyLightmapping();
|
1207
760
|
|
1208
|
-
if (this.handles) {
|
1209
|
-
for (const inst of this.handles) {
|
1210
|
-
// if (inst["LOD"] < level) continue;
|
1211
|
-
// inst["LOD"] = level;
|
1212
|
-
inst.setGeometry(mesh.geometry);
|
1213
|
-
}
|
1214
|
-
}
|
1215
|
-
}
|
1216
|
-
return res;
|
1217
|
-
})
|
1218
|
-
}
|
1219
|
-
return Promise.resolve(null);
|
1220
|
-
}
|
1221
|
-
|
1222
761
|
/** Apply the settings of this renderer to the given object
|
1223
762
|
* Settings include shadow casting and receiving (e.g. this.receiveShadows, this.shadowCastingMode)
|
1224
763
|
*/
|
@@ -28,7 +28,7 @@
|
|
28
28
|
renderer.applySettings(obj);
|
29
29
|
const res = this.tryCreateOrAddInstance(obj, context, args);
|
30
30
|
if (res) {
|
31
|
-
|
31
|
+
NEEDLE_progressive.assignTextureLOD(res.renderer.material, 0);
|
32
32
|
// renderer.loadProgressiveMeshes(res.instancer.mesh, 0);
|
33
33
|
if (handlesArray === null) handlesArray = [];
|
34
34
|
handlesArray.push(res);
|
@@ -45,17 +45,20 @@
|
|
45
45
|
@serializable()
|
46
46
|
allowStartOnClick: boolean = true;
|
47
47
|
|
48
|
+
/** @internal */
|
48
49
|
onPointerEnter() {
|
49
50
|
if (this.context.connection.allowEditing == false) return;
|
50
51
|
if (!this.allowStartOnClick) return;
|
51
52
|
this.context.input.setCursorPointer();
|
52
53
|
}
|
54
|
+
/** @internal */
|
53
55
|
onPointerExit() {
|
54
56
|
if (this.context.connection.allowEditing == false) return;
|
55
57
|
if (!this.allowStartOnClick) return;
|
56
58
|
this.context.input.setCursorNormal();
|
57
59
|
}
|
58
60
|
|
61
|
+
/** @internal */
|
59
62
|
onPointerClick(evt: PointerEventData) {
|
60
63
|
if (this.context.connection.allowEditing == false) return;
|
61
64
|
if (!this.allowStartOnClick) return;
|
@@ -130,6 +133,7 @@
|
|
130
133
|
private _currentStream: MediaStream | null = null;
|
131
134
|
private _currentMode: ScreenCaptureMode = ScreenCaptureMode.Idle;
|
132
135
|
|
136
|
+
/** @internal */
|
133
137
|
awake() {
|
134
138
|
if (debug)
|
135
139
|
console.log("Screensharing", this.name, this);
|
@@ -143,6 +147,7 @@
|
|
143
147
|
this._net = new NetworkedStreams(this.context, handle);
|
144
148
|
}
|
145
149
|
|
150
|
+
/** @internal */
|
146
151
|
onEnable(): void {
|
147
152
|
this._net?.enable();
|
148
153
|
//@ts-ignore
|
@@ -159,6 +164,7 @@
|
|
159
164
|
}
|
160
165
|
}
|
161
166
|
|
167
|
+
/** @internal */
|
162
168
|
onDisable(): void {
|
163
169
|
//@ts-ignore
|
164
170
|
this._net?.removeEventListener(NetworkedStreamEvents.StreamReceived, this.onReceiveStream);
|
@@ -146,7 +146,7 @@
|
|
146
146
|
@serializable()
|
147
147
|
index: number = 0;
|
148
148
|
|
149
|
-
update(
|
149
|
+
update(material: Material | undefined) {
|
150
150
|
if (!this.spriteSheet) return;
|
151
151
|
const index = this.index;
|
152
152
|
if (index < 0 || index >= this.spriteSheet.sprites.length)
|
@@ -163,7 +163,7 @@
|
|
163
163
|
if (!sprite["__hasLoadedProgressive"]) {
|
164
164
|
sprite["__hasLoadedProgressive"] = true;
|
165
165
|
const previousTexture = tex;
|
166
|
-
NEEDLE_progressive.assignTextureLOD(
|
166
|
+
NEEDLE_progressive.assignTextureLOD(tex, 0).then(res => {
|
167
167
|
if (res instanceof Texture) {
|
168
168
|
sprite.texture = res;
|
169
169
|
const shouldUpdateInMaterial = material?.["map"] === previousTexture;
|
@@ -300,7 +300,7 @@
|
|
300
300
|
}
|
301
301
|
this.sharedMaterial = mat;
|
302
302
|
this._currentSprite = new Mesh(SpriteUtils.getOrCreateGeometry(sprite), mat);
|
303
|
-
NEEDLE_progressive.assignTextureLOD(
|
303
|
+
NEEDLE_progressive.assignTextureLOD(mat, 0);
|
304
304
|
}
|
305
305
|
else {
|
306
306
|
this._currentSprite.geometry = SpriteUtils.getOrCreateGeometry(sprite);
|
@@ -323,6 +323,6 @@
|
|
323
323
|
this.sharedMaterial.transparent = this.transparent;
|
324
324
|
}
|
325
325
|
this._currentSprite.castShadow = this.castShadows;
|
326
|
-
this._spriteSheet?.update(
|
326
|
+
this._spriteSheet?.update( this.sharedMaterial);
|
327
327
|
}
|
328
328
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
|
1
2
|
import { Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "three";
|
2
3
|
|
3
4
|
import { showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
@@ -299,11 +300,9 @@
|
|
299
300
|
let progressiveTasks = 0;
|
300
301
|
// TODO: it would be better to directly integrate this into the exporter and *on export* request the correct LOD level for textures and meshes instead of relying on the renderer etc
|
301
302
|
for (const rend of renderers) {
|
302
|
-
rend["didAutomaticallyUpdateLODLevel"] = rend.automaticallyUpdateLODLevel;
|
303
|
-
rend.automaticallyUpdateLODLevel = false;
|
304
303
|
for (const mesh of rend.sharedMeshes) {
|
305
304
|
if (mesh) {
|
306
|
-
const task =
|
305
|
+
const task = NEEDLE_progressive.assignMeshLOD(mesh, 0);
|
307
306
|
if (task instanceof Promise)
|
308
307
|
progressiveLoading.push(new Promise<void>((resolve, reject) => {
|
309
308
|
task.then(() => {
|
@@ -316,7 +315,7 @@
|
|
316
315
|
}
|
317
316
|
for (const mat of rend.sharedMaterials) {
|
318
317
|
if (mat) {
|
319
|
-
const task =
|
318
|
+
const task = NEEDLE_progressive.assignTextureLOD(mat, 0);
|
320
319
|
if (task instanceof Promise)
|
321
320
|
progressiveLoading.push(new Promise<void>((resolve, reject) => {
|
322
321
|
task.then(() => {
|
@@ -415,12 +414,6 @@
|
|
415
414
|
for (const go of implicitBehaviors) {
|
416
415
|
GameObject.destroy(go);
|
417
416
|
}
|
418
|
-
// restore renderer state
|
419
|
-
for (const rend of renderers) {
|
420
|
-
const prevState = rend["didAutomaticallyUpdateLODLevel"];
|
421
|
-
if (prevState != undefined)
|
422
|
-
rend.automaticallyUpdateLODLevel = prevState;
|
423
|
-
}
|
424
417
|
|
425
418
|
// restore XR flags
|
426
419
|
XRState.Global.Set(currentXRState);
|
@@ -104,7 +104,7 @@
|
|
104
104
|
if (!entry) return;
|
105
105
|
|
106
106
|
this._models.splice(indexInArray, 1);
|
107
|
-
|
107
|
+
|
108
108
|
if (entry.handmesh) {
|
109
109
|
entry.handmesh.handModel?.removeFromParent();
|
110
110
|
entry.handmesh = undefined;
|
@@ -272,7 +272,7 @@
|
|
272
272
|
if (NeedleXRSession.active?.isPassThrough)
|
273
273
|
this.makeOccluder(child);
|
274
274
|
if (child instanceof Mesh) {
|
275
|
-
NEEDLE_progressive.assignMeshLOD(
|
275
|
+
NEEDLE_progressive.assignMeshLOD(child, 0);
|
276
276
|
}
|
277
277
|
});
|
278
278
|
if (!controller.connected) {
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import { LODsManager as _LODsManager, NEEDLE_progressive,NEEDLE_progressive_mesh_model, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";
|
2
|
+
import { Box3, BufferGeometry, Camera, Mesh, PerspectiveCamera, Scene, Sphere, Vector3, WebGLRenderer } from "three";
|
3
|
+
|
4
|
+
import { findResourceUsers } from "./engine_assetdatabase.js";
|
5
|
+
import type { Context } from "./engine_context.js";
|
6
|
+
import { Gizmos } from "./engine_gizmos.js";
|
7
|
+
import { getRaycastMesh, setRaycastMesh } from "./engine_physics.js";
|
8
|
+
import { getTempVector } from "./engine_three_utils.js";
|
9
|
+
import { IGameObject } from "./engine_types.js";
|
10
|
+
import { getParam } from "./engine_utils.js";
|
11
|
+
|
12
|
+
const debug = getParam("debugprogressive");
|
13
|
+
|
14
|
+
const _tempBox: Box3 = new Box3();
|
15
|
+
const _tempSphere: Sphere = new Sphere();
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Needle Engine LODs manager. Wrapper around the internal LODs manager.
|
19
|
+
*/
|
20
|
+
export class LODsManager implements NEEDLE_progressive_plugin {
|
21
|
+
readonly context: Context;
|
22
|
+
private _lodsManager?: _LODsManager;
|
23
|
+
|
24
|
+
constructor(context: Context) {
|
25
|
+
this.context = context;
|
26
|
+
}
|
27
|
+
|
28
|
+
/** @internal */
|
29
|
+
setRenderer(renderer: WebGLRenderer) {
|
30
|
+
this._lodsManager?.disable();
|
31
|
+
this._lodsManager = new _LODsManager(renderer);
|
32
|
+
this._lodsManager.plugins.push(this);
|
33
|
+
this._lodsManager.enable();
|
34
|
+
_LODsManager.debugDrawLine = Gizmos.DrawLine;
|
35
|
+
}
|
36
|
+
|
37
|
+
/** @internal */
|
38
|
+
onBeforeGetLODMesh(mesh: Mesh): void {
|
39
|
+
if (!getRaycastMesh(mesh)) {
|
40
|
+
setRaycastMesh(mesh, mesh.geometry as BufferGeometry);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
/** @internal */
|
45
|
+
onRegisteredNewMesh(mesh: Mesh, _ext: NEEDLE_progressive_mesh_model): void {
|
46
|
+
const geometry = mesh.geometry as BufferGeometry;
|
47
|
+
if (geometry) {
|
48
|
+
geometry["needle:raycast-mesh"] = true;
|
49
|
+
if (!getRaycastMesh(mesh)) {
|
50
|
+
if (debug) console.log("Set raycast mesh", mesh.name, mesh.uuid, geometry);
|
51
|
+
setRaycastMesh(mesh, geometry);
|
52
|
+
findResourceUsers(geometry, true).forEach(user => {
|
53
|
+
if (user instanceof Mesh) {
|
54
|
+
setRaycastMesh(user, geometry);
|
55
|
+
}
|
56
|
+
});
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
/** @internal */
|
64
|
+
onAfterUpdatedLOD(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, mesh: Mesh, level: number): void {
|
65
|
+
if(debug) this.onRenderDebug(camera, mesh, level);
|
66
|
+
}
|
67
|
+
|
68
|
+
private onRenderDebug(camera: Camera, mesh: Mesh, level: number) {
|
69
|
+
|
70
|
+
if (!mesh.geometry) return;
|
71
|
+
if (!NEEDLE_progressive.hasLODLevelAvailable(mesh.geometry)) return;
|
72
|
+
|
73
|
+
const state = _LODsManager.getObjectLODState(mesh);
|
74
|
+
if (!state) return;
|
75
|
+
|
76
|
+
|
77
|
+
const changed = level != state.lastLodLevel;;
|
78
|
+
|
79
|
+
if (debug && mesh.geometry.boundingSphere) {
|
80
|
+
const bounds = mesh.geometry.boundingSphere;
|
81
|
+
_tempSphere.copy(bounds);
|
82
|
+
_tempSphere.applyMatrix4(mesh.matrixWorld);
|
83
|
+
const boundsCenter = _tempSphere.center;
|
84
|
+
const radius = _tempSphere.radius;
|
85
|
+
const colors = ["#76c43e", "#bcc43e", "#c4ac3e", "#c4673e", "#ff3e3e"];
|
86
|
+
// if the lod has changed we just want to draw the gizmo for the changed mesh
|
87
|
+
if (changed) {
|
88
|
+
Gizmos.DrawWireSphere(boundsCenter, radius, colors[level], .1);
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
// Mesh Density is calculated as: triangle count per square meter of surface area, normalized to the bounding box size of the model.
|
92
|
+
// Our goal for automatic switching of LODs is that the resulting triangle count per screen area is constant.
|
93
|
+
// We assume a uniform distribution of triangles over the surface area; which means that
|
94
|
+
// we can express a ratio of "screen area to surface area".
|
95
|
+
const triangleCount = mesh.geometry.index?.count ?? 0 / 3;
|
96
|
+
const lods = NEEDLE_progressive.getMeshLODInformation(mesh.geometry)?.lods;
|
97
|
+
level = lods ? Math.min(lods?.length - 1, level) : 0;
|
98
|
+
let allLods = "";
|
99
|
+
if (lods && state.lastScreenCoverage > 0) {
|
100
|
+
for (let i = 0; i < lods.length; i++) {
|
101
|
+
const d = lods[i].density;
|
102
|
+
const last = i == lods.length - 1;
|
103
|
+
allLods += d.toFixed(0) + ">" + (d / state.lastScreenCoverage).toFixed(0) + (last ? "" : ",");
|
104
|
+
}
|
105
|
+
}
|
106
|
+
const density = lods ? lods[level]?.density : -1;
|
107
|
+
|
108
|
+
// const box = mesh.geometry.boundingBox;
|
109
|
+
// const boxSize = box ? box.getSize(getTempVector()) : new Vector3();
|
110
|
+
// const maxBoxSize = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
111
|
+
|
112
|
+
// Surface area is in local space of the model;
|
113
|
+
// we need to scale it by the model's world scale and the model's geometry bounding box size.
|
114
|
+
// const ws = mesh.getWorldScale(getTempVector());
|
115
|
+
// const wsMedian = (ws.x + ws.y + ws.z) / 3;
|
116
|
+
// Area is squared, so both maxBoxSize and wsMedian are squared here
|
117
|
+
// Here, we're basically reverting the calculations that have happened in the pipeline for debugging.
|
118
|
+
// const surfaceArea = 1 / density * triangleCount * (maxBoxSize * maxBoxSize) * (wsMedian * wsMedian);
|
119
|
+
let text = "LOD " + level;
|
120
|
+
if (debug == "density") {
|
121
|
+
text +=
|
122
|
+
"\n" + triangleCount + " tris" +
|
123
|
+
// This is key – basically how we're switching
|
124
|
+
"\n" + (density / state.lastScreenCoverage).toFixed(0) + " dens" +
|
125
|
+
"\n" + (state.lastScreenCoverage * 100).toFixed(1) + "% cov" +
|
126
|
+
// "\n" + (this._lastScreenspaceVolume.x.toFixed(2) + "x" + this._lastScreenspaceVolume.y.toFixed(2) + "x" + this._lastScreenspaceVolume.z.toFixed(2)) + " vol" +
|
127
|
+
// + "\n" + (surfaceArea).toFixed(2) + " m2" +
|
128
|
+
"\n" + (state.lastCentrality * 100).toFixed(1) + "% centr" +
|
129
|
+
"\n" + (_tempBox.min.x.toFixed(2) + "-" + _tempBox.max.x.toFixed(2) + "x" + _tempBox.min.y.toFixed(2) + "-" + _tempBox.max.y.toFixed(2)) + " scr" +
|
130
|
+
// "\n" + (ws.x).toFixed(2) + "x" + " " + maxBoxSize.toFixed(2) + "b" + "\n" +
|
131
|
+
// allLods + "\n" +
|
132
|
+
//"----" + "\n" +
|
133
|
+
// "1000" + " ideal dens"
|
134
|
+
"";
|
135
|
+
}
|
136
|
+
|
137
|
+
// if (helper) {
|
138
|
+
// helper?.setText(text);
|
139
|
+
// continue;
|
140
|
+
// }
|
141
|
+
const cam = camera as any as IGameObject;
|
142
|
+
const camForward = cam.worldForward;
|
143
|
+
const camWorld = cam.worldPosition;
|
144
|
+
|
145
|
+
const fwd = getTempVector(camForward);
|
146
|
+
// for debugging very close LDOs, we need to flip the radius...
|
147
|
+
const pos = fwd.multiplyScalar(radius * .7).add(boundsCenter);
|
148
|
+
const distance = pos.distanceTo(camWorld);
|
149
|
+
// const vertexCount = mesh.geometry.index!.count / 3;
|
150
|
+
// const vertexCountFactor = Math.min(1, vertexCount / 1000);
|
151
|
+
const col = colors[Math.min(colors.length - 1, level)] + "88";
|
152
|
+
// const size = Math.min(10, radius);
|
153
|
+
const windowScale = this.context.domHeight > 0 ? screen.height / this.context.domHeight : 1;
|
154
|
+
const fieldOfViewScale = (camera as PerspectiveCamera).isPerspectiveCamera ? Math.tan((camera as PerspectiveCamera).fov * Math.PI / 180 / 2) : 1;
|
155
|
+
Gizmos.DrawLabel(pos, text, distance * .01 * windowScale * fieldOfViewScale, undefined, 0xffffff, col);
|
156
|
+
// mesh["LOD_level_label"] = helper;
|
157
|
+
}
|
158
|
+
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
}
|