@needle-tools/gltf-progressive 1.0.0-alpha → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@needle-tools-gltf-progressive.js +651 -0
- package/dist/@needle-tools-gltf-progressive.min.js +3 -0
- package/dist/@needle-tools-gltf-progressive.umd.cjs +3 -0
- package/package.json +14 -11
- package/types/extension.d.ts +109 -0
- package/types/index.d.ts +10 -0
- package/types/loaders.d.ts +4 -0
- package/types/lods_manager.d.ts +51 -0
- package/types/plugins/index.d.ts +2 -0
- package/types/plugins/modelviewer.d.ts +15 -0
- package/types/plugins/plugin.d.ts +14 -0
- package/types/utils.d.ts +2 -0
- package/examples/@needle-tools-gltf-progressive.min.js +0 -3
- package/examples/modelviewer.html +0 -27
- package/src/extension.ts +0 -768
- package/src/index.ts +0 -14
- package/src/loaders.ts +0 -49
- package/src/lods_manager.ts +0 -387
- package/src/plugins/index.ts +0 -2
- package/src/plugins/modelviewer.ts +0 -113
- package/src/plugins/plugin.ts +0 -21
- package/src/utils.ts +0 -41
- package/tsconfig.json +0 -33
- package/vite.config.ts +0 -69
package/src/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { patchModelViewer } from "./plugins/modelviewer.js";
|
|
2
|
-
|
|
3
|
-
export * from "./extension.js"
|
|
4
|
-
export { LODsManager } from "./lods_manager.js"
|
|
5
|
-
export * from "./plugins/index.js"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
9
|
-
applyPatches();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
function applyPatches(){
|
|
13
|
-
patchModelViewer();
|
|
14
|
-
}
|
package/src/loaders.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { WebGLRenderer } from 'three';
|
|
3
|
-
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
4
|
-
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
5
|
-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
6
|
-
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
7
|
-
|
|
8
|
-
let DEFAULT_DRACO_DECODER_LOCATION = 'https://www.gstatic.com/draco/versioned/decoders/1.4.1/';
|
|
9
|
-
let DEFAULT_KTX2_TRANSCODER_LOCATION = 'https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
|
|
10
|
-
|
|
11
|
-
const check = fetch(DEFAULT_DRACO_DECODER_LOCATION + "draco_decoder.js", { method: "head" })
|
|
12
|
-
.catch(_ => {
|
|
13
|
-
console.log("Use local loaders");
|
|
14
|
-
DEFAULT_DRACO_DECODER_LOCATION = "./include/draco/";
|
|
15
|
-
DEFAULT_KTX2_TRANSCODER_LOCATION = "./include/ktx2/";
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
let dracoLoader: DRACOLoader;
|
|
19
|
-
let meshoptDecoder: typeof MeshoptDecoder;
|
|
20
|
-
let ktx2Loader: KTX2Loader;
|
|
21
|
-
|
|
22
|
-
export async function createLoaders(renderer: WebGLRenderer) {
|
|
23
|
-
await check;
|
|
24
|
-
|
|
25
|
-
if (!dracoLoader) {
|
|
26
|
-
dracoLoader = new DRACOLoader();
|
|
27
|
-
dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
|
|
28
|
-
dracoLoader.setDecoderConfig({ type: 'js' });
|
|
29
|
-
}
|
|
30
|
-
if (!ktx2Loader) {
|
|
31
|
-
ktx2Loader = new KTX2Loader();
|
|
32
|
-
ktx2Loader.setTranscoderPath(DEFAULT_KTX2_TRANSCODER_LOCATION);
|
|
33
|
-
}
|
|
34
|
-
if (!meshoptDecoder) {
|
|
35
|
-
meshoptDecoder = MeshoptDecoder;
|
|
36
|
-
}
|
|
37
|
-
if (renderer) {
|
|
38
|
-
ktx2Loader.detectSupport(renderer);
|
|
39
|
-
}
|
|
40
|
-
else
|
|
41
|
-
console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures will probably fail");
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function addDracoAndKTX2Loaders(loader: GLTFLoader) {
|
|
46
|
-
loader.setDRACOLoader(dracoLoader);
|
|
47
|
-
loader.setKTX2Loader(ktx2Loader);
|
|
48
|
-
loader.setMeshoptDecoder(meshoptDecoder);
|
|
49
|
-
}
|
package/src/lods_manager.ts
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { Box3, BufferGeometry, Camera, Frustum, Material, Matrix4, Mesh, Object3D, PerspectiveCamera, Scene, Sphere, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
|
-
import { NEEDLE_progressive, ProgressiveMaterialTextureLoadingResult } from "./extension.js";
|
|
3
|
-
import { createLoaders } from "./loaders.js"
|
|
4
|
-
import { getParam } from "./utils.js"
|
|
5
|
-
import { NEEDLE_progressive_plugin } from "./plugins/plugin.js";
|
|
6
|
-
|
|
7
|
-
let debugProgressiveLoading = getParam("debugprogressive");
|
|
8
|
-
const suppressProgressiveLoading = getParam("noprogressive");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export class LODsManager {
|
|
12
|
-
readonly renderer: WebGLRenderer;
|
|
13
|
-
readonly projectionScreenMatrix = new Matrix4();
|
|
14
|
-
readonly cameraFrustrum = new Frustum();
|
|
15
|
-
|
|
16
|
-
updateInterval: number = 0;
|
|
17
|
-
pause: boolean = false;
|
|
18
|
-
|
|
19
|
-
readonly plugins: NEEDLE_progressive_plugin[] = [];
|
|
20
|
-
|
|
21
|
-
constructor(renderer: WebGLRenderer) {
|
|
22
|
-
this.renderer = renderer;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private _originalRender?: (scene: Scene, camera: Camera) => void;
|
|
26
|
-
|
|
27
|
-
enable() {
|
|
28
|
-
if (this._originalRender) return;
|
|
29
|
-
let stack = 0;
|
|
30
|
-
// Save the original render method
|
|
31
|
-
this._originalRender = this.renderer.render;
|
|
32
|
-
const self = this;
|
|
33
|
-
let frames = 0;
|
|
34
|
-
createLoaders(this.renderer);
|
|
35
|
-
this.renderer.render = function (scene: Scene, camera: Camera) {
|
|
36
|
-
const frame = frames++;
|
|
37
|
-
const level = stack++;
|
|
38
|
-
self._originalRender!.call(this, scene, camera);
|
|
39
|
-
self.onUpdateLODs(scene, camera, level, frame);
|
|
40
|
-
stack--;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
disable() {
|
|
44
|
-
if (!this._originalRender) return;
|
|
45
|
-
this.renderer.render = this._originalRender;
|
|
46
|
-
this._originalRender = undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
private onUpdateLODs(scene: Scene, camera: Camera, stack: number, frame: number) {
|
|
52
|
-
|
|
53
|
-
if (suppressProgressiveLoading) return;
|
|
54
|
-
if (this.pause) return;
|
|
55
|
-
if (this.updateInterval > 0 && frame % this.updateInterval != 0) return;
|
|
56
|
-
|
|
57
|
-
this.projectionScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
58
|
-
this.cameraFrustrum.setFromProjectionMatrix(this.projectionScreenMatrix, this.renderer.coordinateSystem);
|
|
59
|
-
const desiredDensity = 100_000;
|
|
60
|
-
|
|
61
|
-
// const isLowPerformanceDevice = false;// isMobileDevice();
|
|
62
|
-
|
|
63
|
-
// Experiment: quick & dirty performance-adaptive LODs
|
|
64
|
-
/*
|
|
65
|
-
if (this.context.time.smoothedFps < 59) {
|
|
66
|
-
currentAllowedDensity *= 0.5;
|
|
67
|
-
}
|
|
68
|
-
else if (this.context.time.smoothedFps >= 59) {
|
|
69
|
-
currentAllowedDensity *= 1.25;
|
|
70
|
-
}
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
const renderList = this.renderer.renderLists.get(scene, stack);
|
|
74
|
-
const opaque = renderList.opaque;
|
|
75
|
-
for (const entry of opaque) {
|
|
76
|
-
const object = entry.object as any;
|
|
77
|
-
|
|
78
|
-
for (const plugin of this.plugins) {
|
|
79
|
-
plugin.onBeforeUpdateLOD?.(this.renderer, scene, camera, object);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (object instanceof Mesh || (object.isMesh)) {
|
|
83
|
-
this.updateLODs(camera, object, desiredDensity);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Update the LOD levels for the renderer. */
|
|
89
|
-
private updateLODs(camera: Camera, object: Mesh, desiredDensity: number) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// we currently only support auto LOD changes for meshes
|
|
93
|
-
// if (this.hasMeshLODs) {
|
|
94
|
-
let state = object.userData.LOD_state as LOD_state;
|
|
95
|
-
if (!state) {
|
|
96
|
-
state = new LOD_state();
|
|
97
|
-
object.userData.LOD_state = state;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let level = this.calculateLodLevel(camera, object, state, desiredDensity);
|
|
101
|
-
level = Math.round(level);
|
|
102
|
-
state.lastLodLevel = level;
|
|
103
|
-
// }
|
|
104
|
-
|
|
105
|
-
if (level >= 0) {
|
|
106
|
-
this.loadProgressiveMeshes(object, level);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
|
110
|
-
let textureLOD = 0;// Math.max(0, LODlevel - 2)
|
|
111
|
-
|
|
112
|
-
if (object.material) {
|
|
113
|
-
const debugLevel = object["DEBUG:LOD"];
|
|
114
|
-
if (debugLevel != undefined) textureLOD = debugLevel;
|
|
115
|
-
|
|
116
|
-
if (Array.isArray(object.material)) {
|
|
117
|
-
for (const mat of object.material) {
|
|
118
|
-
this.loadProgressiveTextures(mat, textureLOD);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
this.loadProgressiveTextures(object.material, textureLOD);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
/** Load progressive textures for the given material
|
|
129
|
-
* @param material the material to load the textures for
|
|
130
|
-
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
|
131
|
-
* @returns Promise with true if the LOD was loaded, false if not
|
|
132
|
-
*/
|
|
133
|
-
private loadProgressiveTextures(material: Material, level: number): Promise<ProgressiveMaterialTextureLoadingResult[] | Texture | null> {
|
|
134
|
-
if (!material) return Promise.resolve(null);
|
|
135
|
-
if (material.userData && material.userData.LOD !== level) {
|
|
136
|
-
material.userData.LOD = level;
|
|
137
|
-
return NEEDLE_progressive.assignTextureLOD(material, level);
|
|
138
|
-
}
|
|
139
|
-
return Promise.resolve(null);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Load progressive meshes for the given mesh
|
|
143
|
-
* @param mesh the mesh to load the LOD for
|
|
144
|
-
* @param index the index of the mesh if it's part of a group
|
|
145
|
-
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
|
146
|
-
* @returns Promise with true if the LOD was loaded, false if not
|
|
147
|
-
*/
|
|
148
|
-
private loadProgressiveMeshes(mesh: Mesh, level: number): Promise<BufferGeometry | null> {
|
|
149
|
-
level = 0;
|
|
150
|
-
if (!mesh) return Promise.resolve(null);
|
|
151
|
-
if (!mesh.userData) mesh.userData = {};
|
|
152
|
-
if (mesh.userData.LOD !== level) {
|
|
153
|
-
mesh.userData.LOD = level;
|
|
154
|
-
const originalGeometry = mesh.geometry;
|
|
155
|
-
return NEEDLE_progressive.assignMeshLOD(mesh, level).then(res => {
|
|
156
|
-
if (res && mesh.userData.LOD == level && originalGeometry != mesh.geometry) {
|
|
157
|
-
// update the lightmap
|
|
158
|
-
// this.applyLightmapping();
|
|
159
|
-
|
|
160
|
-
// if (this.handles) {
|
|
161
|
-
// for (const inst of this.handles) {
|
|
162
|
-
// // if (inst["LOD"] < level) continue;
|
|
163
|
-
// // inst["LOD"] = level;
|
|
164
|
-
// inst.setGeometry(mesh.geometry);
|
|
165
|
-
// }
|
|
166
|
-
// }
|
|
167
|
-
}
|
|
168
|
-
return res;
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
return Promise.resolve(null);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// private testIfLODLevelsAreAvailable() {
|
|
175
|
-
|
|
176
|
-
private readonly _sphere = new Sphere();
|
|
177
|
-
private readonly _box = new Box3();
|
|
178
|
-
private readonly tempMatrix = new Matrix4();
|
|
179
|
-
private readonly _tempWorldPosition = new Vector3();
|
|
180
|
-
private readonly _tempBoxSize = new Vector3();
|
|
181
|
-
private readonly _tempBox2Size = new Vector3();
|
|
182
|
-
|
|
183
|
-
private static corner0 = new Vector3();
|
|
184
|
-
private static corner1 = new Vector3();
|
|
185
|
-
private static corner2 = new Vector3();
|
|
186
|
-
private static corner3 = new Vector3();
|
|
187
|
-
|
|
188
|
-
static debugDrawLine: (a: Vector3, b: Vector3, color: number) => void | null;
|
|
189
|
-
|
|
190
|
-
private calculateLodLevel(camera: Camera, mesh: Mesh, state: LOD_state, desiredDensity: number): number {
|
|
191
|
-
if (!mesh) return -1;
|
|
192
|
-
|
|
193
|
-
// if this is using instancing we always load level 0
|
|
194
|
-
// if (this.isInstancingActive) return 0;
|
|
195
|
-
|
|
196
|
-
/** rough measure of "triangles on quadratic screen" – we're switching LODs based on this metric. */
|
|
197
|
-
/** highest LOD level we'd ever expect to be generated */
|
|
198
|
-
const maxLevel = 10;
|
|
199
|
-
let level = maxLevel + 1;
|
|
200
|
-
|
|
201
|
-
if (camera) {
|
|
202
|
-
if (debugProgressiveLoading && mesh["DEBUG:LOD"] != undefined) {
|
|
203
|
-
return mesh["DEBUG:LOD"];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// The mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
|
207
|
-
const lodsInfo = NEEDLE_progressive.getMeshLODInformation(mesh.geometry);
|
|
208
|
-
const lods = lodsInfo?.lods;
|
|
209
|
-
|
|
210
|
-
// We can skip all this if we dont have any LOD information - we can ask the progressive extension for that
|
|
211
|
-
if (!lods || lods.length <= 0) {
|
|
212
|
-
return 99;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!this.cameraFrustrum?.intersectsObject(mesh)) {
|
|
216
|
-
console.log("Mesh not visible");
|
|
217
|
-
// if (debugProgressiveLoading && mesh.geometry.boundingSphere) {
|
|
218
|
-
// const bounds = mesh.geometry.boundingSphere;
|
|
219
|
-
// this._sphere.copy(bounds);
|
|
220
|
-
// this._sphere.applyMatrix4(mesh.matrixWorld);
|
|
221
|
-
// Gizmos.DrawWireSphere(this._sphere.center, this._sphere.radius * 1.01, 0xff5555, .5);
|
|
222
|
-
// }
|
|
223
|
-
// the object is not visible by the camera
|
|
224
|
-
return 99;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const box = mesh.geometry.boundingBox;
|
|
228
|
-
if (box && camera instanceof PerspectiveCamera) {
|
|
229
|
-
|
|
230
|
-
// hack: if the mesh has vertex colors, has less than 100 vertices we always select the highest LOD
|
|
231
|
-
if (mesh.geometry.attributes.color && mesh.geometry.attributes.color.count < 100) {
|
|
232
|
-
if (mesh.geometry.boundingSphere) {
|
|
233
|
-
this._sphere.copy(mesh.geometry.boundingSphere);
|
|
234
|
-
this._sphere.applyMatrix4(mesh.matrixWorld);
|
|
235
|
-
const worldPosition = camera.getWorldPosition(this._tempWorldPosition)
|
|
236
|
-
if (this._sphere.containsPoint(worldPosition)) {
|
|
237
|
-
return 0;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// calculate size on screen
|
|
243
|
-
this._box.copy(box);
|
|
244
|
-
this._box.applyMatrix4(mesh.matrixWorld);
|
|
245
|
-
|
|
246
|
-
// Converting into projection space has the disadvantage that objects further to the side
|
|
247
|
-
// will have a much larger coverage, especially with high-field-of-view situations like in VR.
|
|
248
|
-
// Alternatively, we could attempt to calculate angular coverage (some kind of polar coordinates maybe?)
|
|
249
|
-
// or introduce a correction factor based on "expected distortion" of the object.
|
|
250
|
-
// High distortions would lead to lower LOD levels.
|
|
251
|
-
// "Centrality" of the calculated screen-space bounding box could be a factor here –
|
|
252
|
-
// what's the distance of the bounding box to the center of the screen?
|
|
253
|
-
this._box.applyMatrix4(this.projectionScreenMatrix);
|
|
254
|
-
|
|
255
|
-
// TODO might need to be adjusted for cameras that are rendered during an XR session but are
|
|
256
|
-
// actually not XR cameras (e.g. a render texture)
|
|
257
|
-
if (this.renderer.xr.enabled && camera.fov > 70) {
|
|
258
|
-
// calculate centrality of the bounding box - how close is it to the screen center
|
|
259
|
-
const min = this._box.min;
|
|
260
|
-
const max = this._box.max;
|
|
261
|
-
|
|
262
|
-
let minX = min.x;
|
|
263
|
-
let minY = min.y;
|
|
264
|
-
let maxX = max.x;
|
|
265
|
-
let maxY = max.y;
|
|
266
|
-
|
|
267
|
-
// enlarge
|
|
268
|
-
const enlargementFactor = 2.0;
|
|
269
|
-
const centerBoost = 1.5;
|
|
270
|
-
const centerX = (min.x + max.x) * 0.5;
|
|
271
|
-
const centerY = (min.y + max.y) * 0.5;
|
|
272
|
-
minX = (minX - centerX) * enlargementFactor + centerX;
|
|
273
|
-
minY = (minY - centerY) * enlargementFactor + centerY;
|
|
274
|
-
maxX = (maxX - centerX) * enlargementFactor + centerX;
|
|
275
|
-
maxY = (maxY - centerY) * enlargementFactor + centerY;
|
|
276
|
-
|
|
277
|
-
const xCentrality = minX < 0 && maxX > 0 ? 0 : Math.min(Math.abs(min.x), Math.abs(max.x));
|
|
278
|
-
const yCentrality = minY < 0 && maxY > 0 ? 0 : Math.min(Math.abs(min.y), Math.abs(max.y));
|
|
279
|
-
const centrality = Math.max(xCentrality, yCentrality);
|
|
280
|
-
|
|
281
|
-
// heuristically determined to lower quality for objects at the edges of vision
|
|
282
|
-
state.lastCentrality = (centerBoost - centrality) * (centerBoost - centrality) * (centerBoost - centrality);
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
state.lastCentrality = 1;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const boxSize = this._box.getSize(this._tempBoxSize);
|
|
289
|
-
boxSize.multiplyScalar(0.5); // goes from -1..1, we want -0.5..0.5 for coverage in percent
|
|
290
|
-
if (screen.availHeight > 0)
|
|
291
|
-
boxSize.multiplyScalar(this.renderer.domElement.clientHeight / screen.availHeight); // correct for size of context on screen
|
|
292
|
-
boxSize.x *= camera.aspect;
|
|
293
|
-
|
|
294
|
-
const matView = camera.matrixWorldInverse;
|
|
295
|
-
const box2 = new Box3();
|
|
296
|
-
box2.copy(box);
|
|
297
|
-
box2.applyMatrix4(mesh.matrixWorld);
|
|
298
|
-
box2.applyMatrix4(matView);
|
|
299
|
-
const boxSize2 = box2.getSize(this._tempBox2Size);
|
|
300
|
-
|
|
301
|
-
// approximate depth coverage in relation to screenspace size
|
|
302
|
-
const max2 = Math.max(boxSize2.x, boxSize2.y);
|
|
303
|
-
const max1 = Math.max(boxSize.x, boxSize.y);
|
|
304
|
-
if (max1 != 0 && max2 != 0)
|
|
305
|
-
boxSize.z = boxSize2.z / Math.max(boxSize2.x, boxSize2.y) * Math.max(boxSize.x, boxSize.y);
|
|
306
|
-
|
|
307
|
-
state.lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z);
|
|
308
|
-
state.lastScreenspaceVolume.copy(boxSize);
|
|
309
|
-
state.lastScreenCoverage *= state.lastCentrality;
|
|
310
|
-
|
|
311
|
-
// draw screen size box
|
|
312
|
-
if (debugProgressiveLoading && LODsManager.debugDrawLine) {
|
|
313
|
-
const mat = this.tempMatrix.copy(this.projectionScreenMatrix);
|
|
314
|
-
mat.invert();
|
|
315
|
-
|
|
316
|
-
const corner0 = LODsManager.corner0;
|
|
317
|
-
const corner1 = LODsManager.corner1;
|
|
318
|
-
const corner2 = LODsManager.corner2;
|
|
319
|
-
const corner3 = LODsManager.corner3;
|
|
320
|
-
|
|
321
|
-
// get box corners, transform with camera space, and draw as quad lines
|
|
322
|
-
corner0.copy(this._box.min);
|
|
323
|
-
corner1.copy(this._box.max);
|
|
324
|
-
corner1.x = corner0.x;
|
|
325
|
-
corner2.copy(this._box.max);
|
|
326
|
-
corner2.y = corner0.y;
|
|
327
|
-
corner3.copy(this._box.max);
|
|
328
|
-
// draw outlines at the center of the box
|
|
329
|
-
const z = (corner0.z + corner3.z) * 0.5;
|
|
330
|
-
// all outlines should have the same depth in screen space
|
|
331
|
-
corner0.z = corner1.z = corner2.z = corner3.z = z;
|
|
332
|
-
|
|
333
|
-
corner0.applyMatrix4(mat);
|
|
334
|
-
corner1.applyMatrix4(mat);
|
|
335
|
-
corner2.applyMatrix4(mat);
|
|
336
|
-
corner3.applyMatrix4(mat);
|
|
337
|
-
|
|
338
|
-
LODsManager.debugDrawLine(corner0, corner1, 0x0000ff);
|
|
339
|
-
LODsManager.debugDrawLine(corner0, corner2, 0x0000ff);
|
|
340
|
-
LODsManager.debugDrawLine(corner1, corner3, 0x0000ff);
|
|
341
|
-
LODsManager.debugDrawLine(corner2, corner3, 0x0000ff);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
let expectedLevel = 999;
|
|
345
|
-
// const framerate = this.context.time.smoothedFps;
|
|
346
|
-
if (lods && state.lastScreenCoverage > 0) {
|
|
347
|
-
for (let l = 0; l < lods.length; l++) {
|
|
348
|
-
const densityForThisLevel = lods[l].density;
|
|
349
|
-
if (densityForThisLevel / state.lastScreenCoverage < desiredDensity) {
|
|
350
|
-
expectedLevel = l;
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// expectedLevel -= meshDensity - 5;
|
|
357
|
-
// expectedLevel += meshDensity;
|
|
358
|
-
const isLowerLod = expectedLevel < level;
|
|
359
|
-
if (isLowerLod) {
|
|
360
|
-
level = expectedLevel;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
// }
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// if (this._lastLodLevel != level) {
|
|
368
|
-
// this._nextLodTestTime = this.context.time.realtimeSinceStartup + .5;
|
|
369
|
-
// if (debugProgressiveLoading) {
|
|
370
|
-
// if (debugProgressiveLoading == "verbose") console.warn(`LOD Level changed from ${this._lastLodLevel} to ${level} for ${this.name}`);
|
|
371
|
-
// this.drawGizmoLodLevel(true);
|
|
372
|
-
// }
|
|
373
|
-
// }
|
|
374
|
-
|
|
375
|
-
return level;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
class LOD_state {
|
|
383
|
-
lastLodLevel: number = 0;
|
|
384
|
-
lastScreenCoverage: number = 0;
|
|
385
|
-
readonly lastScreenspaceVolume: Vector3 = new Vector3();
|
|
386
|
-
lastCentrality: number = 0;
|
|
387
|
-
}
|
package/src/plugins/index.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { Scene, Camera, Object3D, Object3DEventMap, WebGLRenderer, Mesh, Texture, Material } from "three";
|
|
2
|
-
import { LODsManager } from "../lods_manager.js";
|
|
3
|
-
import { NEEDLE_progressive_plugin } from "./plugin.js";
|
|
4
|
-
import { EXTENSION_NAME, NEEDLE_progressive, NEEDLE_progressive_mesh_model } from "../extension.js";
|
|
5
|
-
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
6
|
-
|
|
7
|
-
const $meshLODSymbol = Symbol("NEEDLE_mesh_lod");
|
|
8
|
-
const $textureLODSymbol = Symbol("NEEDLE_texture_lod");
|
|
9
|
-
|
|
10
|
-
export function patchModelViewer() {
|
|
11
|
-
const modelviewer = document.querySelector("model-viewer") as HTMLElement;
|
|
12
|
-
if (!modelviewer)
|
|
13
|
-
return;
|
|
14
|
-
let renderer: WebGLRenderer | null = null;
|
|
15
|
-
for (let p = modelviewer; p != null; p = Object.getPrototypeOf(p)) {
|
|
16
|
-
const privateAPI = Object.getOwnPropertySymbols(p);
|
|
17
|
-
const rendererSymbol = privateAPI.find((value) => value.toString() == 'Symbol(renderer)');
|
|
18
|
-
if (!renderer && rendererSymbol != null) {
|
|
19
|
-
renderer = modelviewer[rendererSymbol].threeRenderer;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (renderer) {
|
|
23
|
-
console.log("Adding Needle LODs to modelviewer");
|
|
24
|
-
const lod = new LODsManager(renderer);
|
|
25
|
-
lod.plugins.push(new RegisterModelviewerDataPlugin(modelviewer))
|
|
26
|
-
lod.enable();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* LODs manager plugin that registers LOD data to the NEEDLE progressive system
|
|
33
|
-
*/
|
|
34
|
-
export class RegisterModelviewerDataPlugin implements NEEDLE_progressive_plugin {
|
|
35
|
-
readonly modelviewer: HTMLElement;
|
|
36
|
-
|
|
37
|
-
constructor(modelviewer: HTMLElement) {
|
|
38
|
-
this.modelviewer = modelviewer;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
onBeforeUpdateLOD(_renderer: WebGLRenderer, scene: Scene, _camera: Camera, object: Object3D<Object3DEventMap>): void {
|
|
42
|
-
this.tryParseMeshLOD(scene, object);
|
|
43
|
-
this.tryParseTextureLOD(scene, object);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private getUrl() {
|
|
47
|
-
return this.modelviewer.getAttribute("src");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private tryGetCurrentGLTF(scene: Scene): GLTF | undefined {
|
|
51
|
-
return (scene as any)._currentGLTF;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private tryParseTextureLOD(scene: Scene, object: Object3D<Object3DEventMap>) {
|
|
55
|
-
if (object[$textureLODSymbol] == true) return;
|
|
56
|
-
object[$textureLODSymbol] = true;
|
|
57
|
-
const currentGLTF = this.tryGetCurrentGLTF(scene);
|
|
58
|
-
const url = this.getUrl();
|
|
59
|
-
if (!url) {
|
|
60
|
-
console.error("No url found in modelviewer");
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (currentGLTF) {
|
|
64
|
-
if ((object as Mesh).material) {
|
|
65
|
-
const mat = (object as Mesh).material;
|
|
66
|
-
if (Array.isArray(mat)) for (const m of mat) handleMaterial(m);
|
|
67
|
-
else handleMaterial(mat);
|
|
68
|
-
|
|
69
|
-
function handleMaterial(mat: Material) {
|
|
70
|
-
if (mat[$textureLODSymbol] == true) return;
|
|
71
|
-
mat[$textureLODSymbol] = true;
|
|
72
|
-
|
|
73
|
-
// make sure to force the material to be updated
|
|
74
|
-
if (mat.userData) mat.userData.LOD = -1;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const keys = Object.keys(mat);
|
|
78
|
-
for (let i = 0; i < keys.length; i++) {
|
|
79
|
-
const key = keys[i];
|
|
80
|
-
const value = mat[key] as Texture & { userData: { associations: { textures: number } } };
|
|
81
|
-
if (value?.isTexture === true) {
|
|
82
|
-
const textureIndex = value.userData?.associations?.textures;
|
|
83
|
-
const textureData = currentGLTF!.parser.json.textures[textureIndex];
|
|
84
|
-
if (textureData.extensions?.[EXTENSION_NAME]) {
|
|
85
|
-
const ext = textureData.extensions[EXTENSION_NAME] as NEEDLE_progressive_mesh_model;
|
|
86
|
-
if (ext && url) {
|
|
87
|
-
NEEDLE_progressive.registerTexture(url, value, ext.lods.length, ext);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private tryParseMeshLOD(_scene: Scene, object: Object3D<Object3DEventMap>) {
|
|
99
|
-
if (object[$meshLODSymbol] == true) return;
|
|
100
|
-
object[$meshLODSymbol] = true;
|
|
101
|
-
const url = this.getUrl();
|
|
102
|
-
if (!url) {
|
|
103
|
-
console.error("No url found in modelviewer");
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// modelviewer has all the information we need in the userData (associations + gltfExtensions)
|
|
107
|
-
const ext = object.userData?.["gltfExtensions"]?.[EXTENSION_NAME] as NEEDLE_progressive_mesh_model;
|
|
108
|
-
if (ext && url) {
|
|
109
|
-
const lodKey = object.uuid;
|
|
110
|
-
NEEDLE_progressive.registerMesh(url, lodKey, object as Mesh, 0, ext.lods.length, ext);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/plugins/plugin.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { WebGLRenderer, Scene, Camera, Object3D, Mesh } from 'three';
|
|
2
|
-
import { NEEDLE_progressive_mesh_model } from '../extension';
|
|
3
|
-
|
|
4
|
-
export interface NEEDLE_progressive_plugin {
|
|
5
|
-
/**
|
|
6
|
-
* Called by the LODs manager before the LODs are updated
|
|
7
|
-
*/
|
|
8
|
-
onBeforeUpdateLOD?(renderer: WebGLRenderer, scene: Scene, camera: Camera, object: Object3D): void;
|
|
9
|
-
|
|
10
|
-
/** Called when a new mesh is registered */
|
|
11
|
-
onRegisteredMesh?(mesh: Mesh, ext: NEEDLE_progressive_mesh_model): void;
|
|
12
|
-
|
|
13
|
-
/** Called before the LOD mesh is fetched */
|
|
14
|
-
onBeforeGetLODMesh?(mesh: Mesh, level: number): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const plugins = new Array<NEEDLE_progressive_plugin>();
|
|
18
|
-
|
|
19
|
-
export function registerPlugin(plugin: NEEDLE_progressive_plugin) {
|
|
20
|
-
plugins.push(plugin);
|
|
21
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export function getParam(name: string): boolean | string {
|
|
9
|
-
const url = new URL(window.location.href);
|
|
10
|
-
let param = url.searchParams.get(name);
|
|
11
|
-
if (param == null || param === "0" || param === "false") return false;
|
|
12
|
-
if (param === "") return true;
|
|
13
|
-
return param;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function resolveUrl(source: string | undefined, uri: string): string {
|
|
17
|
-
if (uri === undefined) {
|
|
18
|
-
return uri;
|
|
19
|
-
}
|
|
20
|
-
if (uri.startsWith("./")) {
|
|
21
|
-
return uri;
|
|
22
|
-
}
|
|
23
|
-
if (uri.startsWith("http")) {
|
|
24
|
-
return uri;
|
|
25
|
-
}
|
|
26
|
-
if (source === undefined) {
|
|
27
|
-
return uri;
|
|
28
|
-
}
|
|
29
|
-
const pathIndex = source.lastIndexOf("/");
|
|
30
|
-
if (pathIndex >= 0) {
|
|
31
|
-
// Take the source uri as the base path
|
|
32
|
-
const basePath = source.substring(0, pathIndex + 1);
|
|
33
|
-
// make sure we don't have double slashes
|
|
34
|
-
while (basePath.endsWith("/") && uri.startsWith("/")) uri = uri.substring(1);
|
|
35
|
-
// Append the relative uri
|
|
36
|
-
const newUri = basePath + uri;
|
|
37
|
-
// newUri = new URL(newUri, globalThis.location.href).href;
|
|
38
|
-
return newUri;
|
|
39
|
-
}
|
|
40
|
-
return uri;
|
|
41
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"outDir": "dist",
|
|
6
|
-
"useDefineForClassFields": true,
|
|
7
|
-
"lib": ["ESNext", "DOM"],
|
|
8
|
-
"moduleResolution": "Node",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"sourceMap": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"noEmit": true,
|
|
14
|
-
"noUnusedLocals": false,
|
|
15
|
-
"noUnusedParameters": true,
|
|
16
|
-
"noImplicitReturns": true,
|
|
17
|
-
"noImplicitAny": false,
|
|
18
|
-
"isolatedModules": true,
|
|
19
|
-
"jsx": "preserve",
|
|
20
|
-
"incremental": true,
|
|
21
|
-
"experimentalDecorators": true,
|
|
22
|
-
"skipLibCheck": true,
|
|
23
|
-
"allowJs": true,
|
|
24
|
-
"types": [
|
|
25
|
-
"three",
|
|
26
|
-
"vite/client"
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
"include": [
|
|
30
|
-
"src/*.ts"
|
|
31
|
-
, "src/plugins/modelviewer.ts" ],
|
|
32
|
-
"exclude": ["node_modules", "dist", "lib", "vite.config.ts", "tests"]
|
|
33
|
-
}
|