@@ -5,6 +5,8 @@
|
|
5
5
|
import { getPosterPath } from './poster.js';
|
6
6
|
|
7
7
|
/**
|
8
|
+
* @param {string} command
|
9
|
+
* @param {import('../types').needleMeta} config
|
8
10
|
* @param {import('../types').userSettings} userSettings
|
9
11
|
*/
|
10
12
|
export const needleMeta = (command, config, userSettings) => {
|
@@ -25,14 +27,15 @@
|
|
25
27
|
enforce: 'pre',
|
26
28
|
transform(html, _ctx) {
|
27
29
|
|
28
|
-
html = insertNeedleCredits(html);
|
29
|
-
|
30
30
|
if (userSettings.allowMetaPlugin === false) return [];
|
31
31
|
|
32
32
|
// this is useful to get the latest config exported from editor
|
33
33
|
// whenever vite wants to transform the html
|
34
34
|
updateConfig();
|
35
35
|
|
36
|
+
if (!config.license)
|
37
|
+
html = insertNeedleCredits(html);
|
38
|
+
|
36
39
|
// early out of the config is invalid / doesn't contain meta information
|
37
40
|
// TODO might be better to handle these edge cases / special cases right from Unity/Blender and not here
|
38
41
|
if (!config) return [];
|
@@ -36,7 +36,12 @@
|
|
36
36
|
return userInteractionRegistered;
|
37
37
|
}
|
38
38
|
|
39
|
-
|
39
|
+
/**
|
40
|
+
* Register a callback that will be called when the user interacts with the page (click, touch, keypress, etc).
|
41
|
+
* If the user has already interacted with the page, the callback will be called immediately.
|
42
|
+
* This can be used to wait for user interaction before playing audio, for example.
|
43
|
+
*/
|
44
|
+
public static registerWaitForInteraction(cb: Function) {
|
40
45
|
if (cb !== null) {
|
41
46
|
if (userInteractionRegistered) {
|
42
47
|
cb();
|
@@ -46,6 +51,8 @@
|
|
46
51
|
userInteractionCallbacks.push(cb);
|
47
52
|
}
|
48
53
|
}
|
54
|
+
/** @deprecated use Application.registerWaitForInteraction instead */
|
55
|
+
public static readonly registerWaitForAllowAudio = Application.registerWaitForInteraction;
|
49
56
|
|
50
57
|
private _mute: boolean = false;
|
51
58
|
/** audio muted? */
|
@@ -57,7 +64,7 @@
|
|
57
64
|
this.dispatchEvent(new Event(ApplicationEvents.MuteChanged));
|
58
65
|
}
|
59
66
|
|
60
|
-
private context: Context;
|
67
|
+
private readonly context: Context;
|
61
68
|
|
62
69
|
/** @returns true if the document is focused */
|
63
70
|
public get hasFocus(): boolean {
|
@@ -190,7 +190,7 @@
|
|
190
190
|
|
191
191
|
if (!fontFamily) {
|
192
192
|
fontFamily = ThreeMeshUI.FontLibrary.addFontFamily(this.familyName);
|
193
|
-
const variant = fontFamily.addVariant("normal", "normal", "
|
193
|
+
const variant = fontFamily.addVariant("normal", "normal", "https://uploads.needle.tools/include/font-msdf.json", "https://uploads.needle.tools/include/font.png") as any as ThreeMeshUI.FontVariant;
|
194
194
|
variant?.addEventListener('ready', () => {
|
195
195
|
ThreeMeshUI.update();
|
196
196
|
});
|
@@ -4,14 +4,22 @@
|
|
4
4
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
5
5
|
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
6
6
|
|
7
|
+
import { isDevEnvironment } from './debug/index.js';
|
7
8
|
import { Context } from "./engine_setup.js"
|
8
9
|
import { getParam } from "./engine_utils.js";
|
9
10
|
|
10
11
|
const debug = getParam("debugdecoders");
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
let DEFAULT_DRACO_DECODER_LOCATION = 'https://www.gstatic.com/draco/versioned/decoders/1.4.1/';
|
14
|
+
let DEFAULT_KTX2_TRANSCODER_LOCATION = 'https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
|
14
15
|
|
16
|
+
fetch(DEFAULT_DRACO_DECODER_LOCATION + "draco_decoder.js", { method: "head" })
|
17
|
+
.catch(_ => {
|
18
|
+
if (isDevEnvironment()) console.warn("Failed to load draco decoder from \"" + DEFAULT_DRACO_DECODER_LOCATION + "\".\nFalling back to local version at \"./include/draco\"");
|
19
|
+
DEFAULT_DRACO_DECODER_LOCATION = "./include/draco/";
|
20
|
+
DEFAULT_KTX2_TRANSCODER_LOCATION = "./include/ktx2/";
|
21
|
+
});
|
22
|
+
|
15
23
|
let dracoLoader: DRACOLoader;
|
16
24
|
let meshoptDecoder: typeof MeshoptDecoder;
|
17
25
|
let ktx2Loader: KTX2Loader;
|
@@ -267,6 +267,9 @@
|
|
267
267
|
return this._peer;
|
268
268
|
}
|
269
269
|
|
270
|
+
/**
|
271
|
+
* Returns the state of a given guid.
|
272
|
+
*/
|
270
273
|
public tryGetState(guid: string): IModel | null {
|
271
274
|
if (guid === "invalid") return null;
|
272
275
|
return this._state[guid];
|
@@ -290,8 +293,21 @@
|
|
290
293
|
public get currentRoomName(): string | null { return this._currentRoomName; }
|
291
294
|
/** True when connected to a room via a regular url, otherwise (when using a view only url) false indicating that the user should not be able to modify the scene */
|
292
295
|
public get allowEditing(): boolean { return this._currentRoomAllowEditing; }
|
293
|
-
|
296
|
+
/**
|
297
|
+
* The view id of the room the user is currently connected to.
|
298
|
+
*/
|
294
299
|
public get currentRoomViewId(): string | null { return this._currentRoomViewId; }
|
300
|
+
/**
|
301
|
+
* Returns a url that can be shared with others to view the current room in view only mode.
|
302
|
+
* This is useful for sharing a room with others without allowing them to modify the scene.
|
303
|
+
* Use `connection.allowEditing` to check if the current room is in view only mode.
|
304
|
+
*/
|
305
|
+
public getViewOnlyUrl() {
|
306
|
+
if (this.currentRoomViewId === null) return null;
|
307
|
+
const url = new URL(window.location.href);
|
308
|
+
url.searchParams.set("view", this.currentRoomViewId);
|
309
|
+
return url.href;
|
310
|
+
}
|
295
311
|
|
296
312
|
/** True if connected to a networked room. Use the joinRoom function or a `SyncedRoom` component */
|
297
313
|
public get isInRoom(): boolean {
|
@@ -303,6 +319,9 @@
|
|
303
319
|
return this._currentDelay;
|
304
320
|
}
|
305
321
|
|
322
|
+
/**
|
323
|
+
* The current server url that the networking backend is connected to (e.g. the url of the websocket server)
|
324
|
+
*/
|
306
325
|
public get currentServerUrl(): string | null {
|
307
326
|
// @ts-ignore (in ts-websocket 2.x this property is exposed)
|
308
327
|
return this._ws?.url ?? null;
|
@@ -3,10 +3,24 @@
|
|
3
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
4
4
|
import { Behaviour } from "./Component.js";
|
5
5
|
|
6
|
+
/**
|
7
|
+
* A LookAtConstraint is used by OrbitControls to make the camera look at a target.
|
8
|
+
*/
|
6
9
|
export class LookAtConstraint extends Behaviour {
|
7
10
|
|
11
|
+
/**
|
12
|
+
* When true the constraint is active.
|
13
|
+
*/
|
14
|
+
@serializable()
|
8
15
|
constraintActive: boolean = true;
|
16
|
+
/**
|
17
|
+
* When true the look at is locked to the position of the assigned sources.
|
18
|
+
*/
|
19
|
+
@serializable()
|
9
20
|
locked: boolean = false;
|
21
|
+
/**
|
22
|
+
* The sources to look at.
|
23
|
+
*/
|
10
24
|
@serializable(Object3D)
|
11
25
|
sources: Object3D[] = [];
|
12
26
|
}
|
@@ -434,27 +434,21 @@
|
|
434
434
|
const ext = meshInfo?.extensions[EXTENSION_NAME] as NEEDLE_progressive_mesh_model;
|
435
435
|
if (ext && ext.lods) {
|
436
436
|
for (const key of this.parser.associations.keys()) {
|
437
|
-
if (key instanceof Mesh
|
438
|
-
const val = this.parser.associations.get(key) as { meshes: number };
|
437
|
+
if (key instanceof Mesh) {
|
438
|
+
const val = this.parser.associations.get(key) as { meshes: number, primitives: number };
|
439
439
|
if (val.meshes === index) {
|
440
440
|
const obj = key;
|
441
441
|
if (debug) console.log("> Progressive: register mesh", index, obj.name, ext, obj.uuid, obj);
|
442
442
|
const LODKEY = obj.uuid;
|
443
443
|
const LODLEVEL = ext.lods.length;
|
444
444
|
if (obj instanceof Mesh) {
|
445
|
-
applyMeshLOD(LODKEY, obj, LODLEVEL,
|
446
|
-
NEEDLE_progressive.lowresCache.
|
447
|
-
|
448
|
-
|
449
|
-
const geometries = new Array<BufferGeometry>();
|
450
|
-
for (let i = 0; i < obj.children.length; i++) {
|
451
|
-
const child = obj.children[i];
|
452
|
-
if (child instanceof Mesh) {
|
453
|
-
geometries.push(child.geometry as BufferGeometry);
|
454
|
-
applyMeshLOD(LODKEY, child, LODLEVEL, i, ext);
|
455
|
-
}
|
445
|
+
applyMeshLOD(LODKEY, obj, LODLEVEL, val.primitives, ext);
|
446
|
+
let existing = NEEDLE_progressive.lowresCache.get(LODKEY) as unknown as BufferGeometry[] | undefined;
|
447
|
+
if (existing) {
|
448
|
+
existing.push(obj.geometry as BufferGeometry);
|
456
449
|
}
|
457
|
-
|
450
|
+
else existing = [obj.geometry as BufferGeometry];
|
451
|
+
NEEDLE_progressive.lowresCache.set(LODKEY, existing);
|
458
452
|
}
|
459
453
|
}
|
460
454
|
}
|
@@ -472,7 +466,7 @@
|
|
472
466
|
/** cache of already loaded mesh lods */
|
473
467
|
private static readonly previouslyLoaded: Map<string, Promise<null | Texture | BufferGeometry | BufferGeometry[]>> = new Map();
|
474
468
|
/** this contains the geometry/textures that were originally loaded */
|
475
|
-
private static readonly lowresCache: Map<string, Texture | BufferGeometry
|
469
|
+
private static readonly lowresCache: Map<string, Texture | BufferGeometry[]> = new Map();
|
476
470
|
|
477
471
|
private static async getOrLoadLOD<T extends Texture | BufferGeometry>(
|
478
472
|
context: Context, source: SourceIdentifier | undefined, current: T & ObjectThatMightHaveLODs, level: number
|
@@ -1,9 +1,11 @@
|
|
1
|
-
import { AxesHelper, Box3, BufferGeometry, Color, InstancedMesh, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D, PerspectiveCamera, RawShaderMaterial, SkinnedMesh, Sphere, Texture, Vector3, Vector4 } from "three";
|
1
|
+
import { AxesHelper, Box3, BufferGeometry, Color, InstancedMesh, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D, PerspectiveCamera, RawShaderMaterial, SkinnedMesh, Sphere, Texture, Vector2, Vector3, Vector4 } from "three";
|
2
2
|
|
3
3
|
import { showBalloonWarning } from "../engine/debug/index.js";
|
4
4
|
import { getComponent, getOrAddComponent } from "../engine/engine_components.js";
|
5
|
+
import { ContextEvent, NeedleEngine } from "../engine/engine_context_registry.js";
|
5
6
|
import { Gizmos, LabelHandle } from "../engine/engine_gizmos.js";
|
6
7
|
import { $instancingAutoUpdateBounds, $instancingRenderer, InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js";
|
8
|
+
import { onStart } from "../engine/engine_lifecycle_api.js";
|
7
9
|
import { isLocalNetwork } from "../engine/engine_networking_utils.js";
|
8
10
|
import { getRaycastMesh } from "../engine/engine_physics.js";
|
9
11
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
@@ -20,15 +22,33 @@
|
|
20
22
|
// import { RendererCustomShader } from "./RendererCustomShader.js";
|
21
23
|
import { RendererLightmap } from "./RendererLightmap.js";
|
22
24
|
|
25
|
+
|
23
26
|
// for staying compatible with old code
|
24
27
|
export { InstancingUtil } from "../engine/engine_instancing.js";
|
25
28
|
|
26
29
|
const debugRenderer = getParam("debugrenderer");
|
27
30
|
const debugskinnedmesh = getParam("debugskinnedmesh");
|
28
31
|
const suppressInstancing = getParam("noinstancing");
|
29
|
-
|
32
|
+
let debugProgressiveLoading = getParam("debugprogressive");
|
30
33
|
const suppressProgressiveLoading = getParam("noprogressive");
|
31
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
|
+
|
32
52
|
const showWireframe = getParam("wireframe");
|
33
53
|
|
34
54
|
export enum ReflectionProbeUsage {
|
@@ -831,7 +851,8 @@
|
|
831
851
|
|
832
852
|
private _lastLodLevel = -1;
|
833
853
|
private _lastScreenCoverage = 0;
|
834
|
-
private _lastScreenspaceVolume =
|
854
|
+
private _lastScreenspaceVolume = new Vector3();
|
855
|
+
private _lastCentrality = 0;
|
835
856
|
private _nextLodTestTime = 0;
|
836
857
|
private _randomLodLevelCheckFrameOffset = Math.floor(Math.random() * 100);
|
837
858
|
private readonly _sphere = new Sphere();
|
@@ -845,7 +866,7 @@
|
|
845
866
|
|
846
867
|
const interval = 3;
|
847
868
|
|
848
|
-
if (!debugProgressiveLoading) {
|
869
|
+
if (!(debugProgressiveLoading == "density")) {
|
849
870
|
if (!force && (this.context.time.frame + this._randomLodLevelCheckFrameOffset) % interval != 0) {
|
850
871
|
return this._lastLodLevel;
|
851
872
|
}
|
@@ -855,14 +876,29 @@
|
|
855
876
|
}
|
856
877
|
}
|
857
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 */
|
858
882
|
const maxLevel = 10;
|
883
|
+
|
884
|
+
let currentAllowedDensity = desiredDensity;
|
859
885
|
let level = maxLevel + 1;
|
860
886
|
|
861
887
|
if (this.context.mainCamera) {
|
862
888
|
|
863
889
|
const isLowPerformanceDevice = isMobileDevice();
|
864
|
-
|
890
|
+
/** We probably want to make these numbers configurable. */
|
891
|
+
currentAllowedDensity = desiredDensity * (isLowPerformanceDevice ? 0.7 : 1.0);
|
865
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
|
+
*/
|
866
902
|
|
867
903
|
// TODO: we should save the LOD level in the shared mesh and not just calculate one level per renderer
|
868
904
|
for (const mesh of this.sharedMeshes) {
|
@@ -873,16 +909,9 @@
|
|
873
909
|
return this._lastLodLevel = mesh["DEBUG:LOD"];
|
874
910
|
}
|
875
911
|
|
876
|
-
|
877
|
-
// TODO: the mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
912
|
+
// The mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
878
913
|
const lodsInfo = NEEDLE_progressive.getMeshLODInformation(mesh.geometry);
|
879
|
-
|
880
|
-
if (lodsInfo?.lods && lodsInfo?.lods.length > 0)
|
881
|
-
meshDensity = (Math.log2(lodsInfo.lods[0].density || 0) / 2) - 8;
|
882
|
-
else if(debugProgressiveLoading) console.warn("No LOD information found...", mesh.name);
|
883
|
-
if (debugProgressiveLoading) {
|
884
|
-
// ^console.log("Looking at LOD levels", lodsInfo)
|
885
|
-
}
|
914
|
+
const lods = lodsInfo?.lods;
|
886
915
|
|
887
916
|
// TODO: we can skip all this if we dont have any LOD information - we can ask the progressive extension for that
|
888
917
|
const frustum = this.context.mainCameraComponent?.getFrustum();
|
@@ -915,17 +944,73 @@
|
|
915
944
|
// calculate size on screen
|
916
945
|
this._box.copy(box);
|
917
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?
|
918
956
|
const mat = this.context.mainCameraComponent!.getProjectionScreenMatrix(Renderer.tempMatrix);
|
919
957
|
this._box.applyMatrix4(mat);
|
920
|
-
|
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
|
+
}
|
921
991
|
const boxSize = this._box.getSize(getTempVector());
|
922
|
-
//
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
this._lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z) / 2;
|
927
|
-
this._lastScreenspaceVolume = boxSize.x * boxSize.y * boxSize.z / 8;
|
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;
|
928
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
|
+
|
929
1014
|
// draw screen size box
|
930
1015
|
if (debugProgressiveLoading) {
|
931
1016
|
mat.invert();
|
@@ -941,8 +1026,9 @@
|
|
941
1026
|
corner2.y = corner0.y;
|
942
1027
|
const corner3 = getTempVector();
|
943
1028
|
corner3.copy(this._box.max);
|
1029
|
+
// draw outlines at the center of the box
|
944
1030
|
const z = (corner0.z + corner3.z) * 0.5;
|
945
|
-
|
1031
|
+
// all outlines should have the same depth in screen space
|
946
1032
|
corner0.z = corner1.z = corner2.z = corner3.z = z;
|
947
1033
|
|
948
1034
|
corner0.applyMatrix4(mat);
|
@@ -956,17 +1042,18 @@
|
|
956
1042
|
Gizmos.DrawLine(corner2, corner3, 0x0000ff);
|
957
1043
|
}
|
958
1044
|
|
959
|
-
// screenSize *= .2;
|
960
|
-
|
961
1045
|
let expectedLevel = 999;
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
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
|
+
}
|
967
1054
|
}
|
968
|
-
threshold /= 2;
|
969
1055
|
}
|
1056
|
+
|
970
1057
|
// expectedLevel -= meshDensity - 5;
|
971
1058
|
// expectedLevel += meshDensity;
|
972
1059
|
const isLowerLod = expectedLevel < level;
|
@@ -985,8 +1072,8 @@
|
|
985
1072
|
if (debugProgressiveLoading == "verbose") console.warn(`LOD Level changed from ${this._lastLodLevel} to ${level} for ${this.name}`);
|
986
1073
|
this.drawGizmoLodLevel(true);
|
987
1074
|
}
|
1075
|
+
}
|
988
1076
|
|
989
|
-
}
|
990
1077
|
this._lastLodLevel = level;
|
991
1078
|
return level;
|
992
1079
|
}
|
@@ -994,8 +1081,12 @@
|
|
994
1081
|
private drawGizmoLodLevel(changed: boolean) {
|
995
1082
|
// Will be (maxLod + 1) (11) if no lod level is found
|
996
1083
|
const _level = this._lastLodLevel;
|
997
|
-
|
998
|
-
const
|
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
|
+
|
999
1090
|
for (const mesh of this.sharedMeshes) {
|
1000
1091
|
if (!mesh) continue;
|
1001
1092
|
if (mesh.geometry.boundingSphere) {
|
@@ -1018,9 +1109,11 @@
|
|
1018
1109
|
const lods = NEEDLE_progressive.getMeshLODInformation(mesh.geometry)?.lods;
|
1019
1110
|
const level = lods ? Math.min(lods?.length - 1, _level) : 0;
|
1020
1111
|
let allLods = "";
|
1021
|
-
if (lods) {
|
1112
|
+
if (lods && this._lastScreenCoverage > 0) {
|
1022
1113
|
for (let i = 0; i < lods.length; i++) {
|
1023
|
-
|
1114
|
+
const d = lods[i].density;
|
1115
|
+
const last = i == lods.length - 1;
|
1116
|
+
allLods += d.toFixed(0) + ">" + (d / this._lastScreenCoverage).toFixed(0) + (last ? "" : ",");
|
1024
1117
|
}
|
1025
1118
|
}
|
1026
1119
|
const density = lods ? lods[level]?.density : -1;
|
@@ -1035,32 +1128,39 @@
|
|
1035
1128
|
// Area is squared, so both maxBoxSize and wsMedian are squared here
|
1036
1129
|
// Here, we're basically reverting the calculations that have happened in the pipeline for debugging.
|
1037
1130
|
const surfaceArea = 1 / density * triangleCount * (maxBoxSize * maxBoxSize) * (wsMedian * wsMedian);
|
1038
|
-
const idealDensity = this._lastScreenCoverage;
|
1039
1131
|
let text = "LOD " + level;
|
1040
|
-
if(debugProgressiveLoading == "density"){
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
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
|
+
"";
|
1051
1147
|
}
|
1052
1148
|
|
1053
1149
|
// if (helper) {
|
1054
1150
|
// helper?.setText(text);
|
1055
1151
|
// continue;
|
1056
1152
|
// }
|
1057
|
-
const
|
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);
|
1058
1156
|
const distance = pos.distanceTo(camWorld);
|
1059
|
-
const vertexCount = mesh.geometry.index!.count / 3;
|
1157
|
+
// const vertexCount = mesh.geometry.index!.count / 3;
|
1060
1158
|
// const vertexCountFactor = Math.min(1, vertexCount / 1000);
|
1061
1159
|
const col = colors[Math.min(colors.length - 1, level)] + "88";
|
1062
1160
|
// const size = Math.min(10, radius);
|
1063
|
-
|
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);
|
1064
1164
|
// mesh["LOD_level_label"] = helper;
|
1065
1165
|
}
|
1066
1166
|
|
@@ -46,15 +46,18 @@
|
|
46
46
|
allowStartOnClick: boolean = true;
|
47
47
|
|
48
48
|
onPointerEnter() {
|
49
|
+
if (this.context.connection.allowEditing == false) return;
|
49
50
|
if (!this.allowStartOnClick) return;
|
50
51
|
this.context.input.setCursorPointer();
|
51
52
|
}
|
52
53
|
onPointerExit() {
|
54
|
+
if (this.context.connection.allowEditing == false) return;
|
53
55
|
if (!this.allowStartOnClick) return;
|
54
56
|
this.context.input.setCursorNormal();
|
55
57
|
}
|
56
58
|
|
57
59
|
onPointerClick(evt: PointerEventData) {
|
60
|
+
if (this.context.connection.allowEditing == false) return;
|
58
61
|
if (!this.allowStartOnClick) return;
|
59
62
|
if (evt && evt.pointerId !== 0) return;
|
60
63
|
if (this.isReceiving && this.videoPlayer?.isPlaying) {
|