@needle-tools/gltf-progressive 4.0.0-alpha.1 → 4.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/LICENSE +21 -0
  3. package/README.md +47 -11
  4. package/examples/modelviewer-multiple.html +4 -4
  5. package/examples/modelviewer.html +4 -4
  6. package/examples/offscreen/index.html +15 -0
  7. package/examples/offscreen/main.js +98 -0
  8. package/examples/react-three-fiber/index.html +2 -2
  9. package/examples/react-three-fiber/package-lock.json +482 -484
  10. package/examples/react-three-fiber/package.json +4 -4
  11. package/examples/react-three-fiber/src/App.tsx +76 -21
  12. package/examples/react-three-fiber/src/styles.css +2 -4
  13. package/examples/react-three-fiber/vite.config.js +2 -2
  14. package/examples/shared/example-utils.js +297 -0
  15. package/examples/shared/example.css +44 -0
  16. package/examples/shared/runtime.js +34 -0
  17. package/examples/threejs/index.html +6 -10
  18. package/examples/threejs/main.js +5 -23
  19. package/examples/webgpu/index.html +15 -0
  20. package/examples/webgpu/main.js +105 -0
  21. package/examples/worker-rendering/index.html +15 -0
  22. package/examples/worker-rendering/main.js +109 -0
  23. package/examples/worker-rendering/worker.js +166 -0
  24. package/gltf-progressive.js +1025 -669
  25. package/gltf-progressive.min.js +9 -9
  26. package/gltf-progressive.umd.cjs +9 -9
  27. package/lib/extension.d.ts +73 -7
  28. package/lib/extension.js +351 -111
  29. package/lib/index.d.ts +1 -1
  30. package/lib/index.js +1 -1
  31. package/lib/loaders.js +17 -3
  32. package/lib/lods.debug.js +2 -2
  33. package/lib/lods.manager.d.ts +93 -15
  34. package/lib/lods.manager.js +331 -157
  35. package/lib/lods.promise.js +1 -1
  36. package/lib/plugins/modelviewer.js +1 -1
  37. package/lib/utils.internal.js +20 -6
  38. package/lib/version.js +1 -1
  39. package/lib/worker/loader.mainthread.js +4 -1
  40. package/package.json +10 -3
package/lib/loaders.js CHANGED
@@ -116,7 +116,9 @@ function prepareLoaders() {
116
116
  dracoLoader = new DRACOLoader();
117
117
  dracoLoader[$dracoDecoderPath] = DEFAULT_DRACO_DECODER_LOCATION;
118
118
  dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
119
- dracoLoader.setDecoderConfig({ type: 'js' });
119
+ // three r185+ auto-selects WASM (preferred) or JS based on platform support, and
120
+ // setDecoderConfig is deprecated (removed in r194). The gstatic decoder path hosts both
121
+ // decoders, so dropping the forced-JS config lets it use the faster WASM decoder.
120
122
  dracoLoader.preload();
121
123
  }
122
124
  if (!ktx2Loader) {
@@ -143,8 +145,8 @@ const originalLoadFunction = GLTFLoader.prototype.load;
143
145
  function onLoad(...args) {
144
146
  const config = gltfLoaderConfigurations.get(this);
145
147
  let url_str = args[0];
146
- const url = new URL(url_str, window.location.href);
147
- if (url.hostname.endsWith("needle.tools")) {
148
+ const url = tryResolveUrl(url_str);
149
+ if (url?.hostname.endsWith("needle.tools")) {
148
150
  const progressive = config?.progressive !== undefined ? config.progressive : true;
149
151
  const usecase = config?.usecase ? config.usecase : "default";
150
152
  if (progressive) {
@@ -160,3 +162,15 @@ function onLoad(...args) {
160
162
  return res;
161
163
  }
162
164
  GLTFLoader.prototype.load = onLoad;
165
+ function tryResolveUrl(url) {
166
+ try {
167
+ if (url instanceof URL) {
168
+ return url;
169
+ }
170
+ const base = globalThis.location?.href;
171
+ return base ? new URL(url, base) : new URL(url);
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ }
package/lib/lods.debug.js CHANGED
@@ -2,8 +2,8 @@ import { getParam } from "./utils.internal.js";
2
2
  export const debug = getParam("debugprogressive");
3
3
  let debug_RenderWireframe = undefined;
4
4
  export let debug_OverrideLodLevel = -1; // -1 is automatic
5
- if (debug) {
6
- let maxLevel = 6;
5
+ if (debug && typeof window !== "undefined") {
6
+ const maxLevel = 6;
7
7
  function debugToggleProgressive() {
8
8
  debug_OverrideLodLevel += 1;
9
9
  if (debug_OverrideLodLevel >= maxLevel) {
@@ -1,4 +1,4 @@
1
- import { Camera, Material, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
1
+ import { Box3, BufferGeometry, Camera, Color, Material, Matrix4, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
2
2
  import { NEEDLE_progressive_plugin } from "./plugins/plugin.js";
3
3
  import { PromiseGroupOptions } from "./lods.promise.js";
4
4
  export type LODManagerContext = {
@@ -8,6 +8,29 @@ export declare type LOD_Results = {
8
8
  mesh_lod: number;
9
9
  texture_lod: number;
10
10
  };
11
+ export declare const lodDebugColors: number[];
12
+ export type MeshLODSelectionOptions = {
13
+ geometry: BufferGeometry;
14
+ matrixWorld: Matrix4;
15
+ camera: Camera;
16
+ projectionScreenMatrix: Matrix4;
17
+ desiredDensity: number;
18
+ canvasHeight?: number;
19
+ currentLevel?: number;
20
+ boundingBox?: Box3 | null;
21
+ xrEnabled?: boolean;
22
+ debugDrawLine?: (a: Vector3, b: Vector3, color: number) => void;
23
+ warnMissingPrimitiveDensities?: boolean;
24
+ target?: MeshLODSelectionResult;
25
+ };
26
+ export type MeshLODSelectionResult = {
27
+ level: number;
28
+ primitiveIndex: number;
29
+ screenCoverage: number;
30
+ screenspaceVolume: Vector3;
31
+ centrality: number;
32
+ };
33
+ export declare function calculateMeshLODLevel(options: MeshLODSelectionOptions): MeshLODSelectionResult;
11
34
  declare type LODChangedEventListener = (args: {
12
35
  type: "mesh" | "texture";
13
36
  level: number;
@@ -52,6 +75,8 @@ export declare class LODsManager {
52
75
  static getObjectLODState(object: Object3D): LOD_state | undefined;
53
76
  static addPlugin(plugin: NEEDLE_progressive_plugin): void;
54
77
  static removePlugin(plugin: NEEDLE_progressive_plugin): void;
78
+ /** Read-only snapshot of the currently registered plugins, for inspection. Use {@link addPlugin} / {@link removePlugin} to modify the registry. */
79
+ static getPlugins(): readonly NEEDLE_progressive_plugin[];
55
80
  /**
56
81
  * Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
57
82
  * @param renderer The renderer to get the LODsManager for.
@@ -98,17 +123,64 @@ export declare class LODsManager {
98
123
  private readonly _newPromiseGroups;
99
124
  private _promiseGroupIds;
100
125
  /**
101
- * Call to await LODs loading during the next render cycle.
126
+ * Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
127
+ * This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
128
+ *
129
+ * By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
130
+ * Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
131
+ *
132
+ * @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
133
+ * @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * // Wait for initial LODs to finish loading, then remove a blur overlay
138
+ * const result = await lodsManager.awaitLoading({
139
+ * frames: 5,
140
+ * signal: AbortSignal.timeout(10_000),
141
+ * });
142
+ * console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
143
+ * document.querySelector('.blur-overlay')?.remove();
144
+ * ```
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * // Wait until at least one LOD starts loading before resolving
149
+ * await lodsManager.awaitLoading({ waitForFirstCapture: true });
150
+ * ```
102
151
  */
103
152
  awaitLoading(opts?: PromiseGroupOptions): Promise<{
104
153
  cancelled: boolean;
105
154
  awaited_count: number;
106
155
  resolved_count: number;
107
156
  }>;
157
+ /** Track LOD work started outside this manager so {@link awaitLoading} waits for it too. */
158
+ trackLoadingPromise<T>(type: "mesh" | "texture", object: object, promise: Promise<T>): Promise<T>;
108
159
  private _postprocessPromiseGroups;
109
160
  private readonly _lodchangedlisteners;
110
- addEventListener(evt: "changed", listener: LODChangedEventListener): void;
111
- removeEventListener(evt: "changed", listener: LODChangedEventListener): void;
161
+ /**
162
+ * Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
163
+ * The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
164
+ *
165
+ * @param evt - The event type. Currently only `"changed"` is supported.
166
+ * @param listener - Callback invoked after a LOD swap completes.
167
+ * @return A function to unregister the listener.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * lodsManager.addEventListener("changed", ({ type, level, object }) => {
172
+ * console.log(`${type} LOD changed to level ${level}`, object);
173
+ * });
174
+ * ```
175
+ */
176
+ addEventListener(evt: "changed", listener: LODChangedEventListener): () => void;
177
+ /**
178
+ * Remove a previously registered `"changed"` event listener.
179
+ * @param evt - The event type (`"changed"`).
180
+ * @param listener - The listener to remove.
181
+ * @return `true` if the listener was found and removed, `false` otherwise.
182
+ */
183
+ removeEventListener(evt: "changed", listener: LODChangedEventListener): boolean;
112
184
  private constructor();
113
185
  private _fpsBuffer;
114
186
  /**
@@ -116,12 +188,28 @@ export declare class LODsManager {
116
188
  */
117
189
  enable(): void;
118
190
  disable(): void;
191
+ /**
192
+ * Manually trigger a LOD update for a scene and camera.
193
+ * Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
194
+ *
195
+ * @param scene - The scene containing objects with progressive LODs.
196
+ * @param camera - The camera used to determine screen coverage and LOD levels.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const lodsManager = LODsManager.get(renderer);
201
+ * lodsManager.manual = true;
202
+ * // ... later, trigger an update at a specific point:
203
+ * lodsManager.update(scene, camera);
204
+ * ```
205
+ */
119
206
  update(scene: Scene, camera: Camera): void;
120
207
  private onAfterRender;
121
208
  /**
122
209
  * Update LODs in a scene
123
210
  */
124
211
  private internalUpdate;
212
+ private getRenderList;
125
213
  /** Update the LOD levels for the renderer. */
126
214
  private updateLODs;
127
215
  /** Load progressive textures for the given material
@@ -138,18 +226,7 @@ export declare class LODsManager {
138
226
  */
139
227
  private loadProgressiveMeshes;
140
228
  private readonly _sphere;
141
- private readonly _tempBox;
142
- private readonly _tempBox2;
143
- private readonly tempMatrix;
144
229
  private readonly _tempWorldPosition;
145
- private readonly _tempBoxSize;
146
- private readonly _tempBox2Size;
147
- private static corner0;
148
- private static corner1;
149
- private static corner2;
150
- private static corner3;
151
- private static readonly _tempPtInside;
152
- private static isInside;
153
230
  private static skinnedMeshBoundsFrameOffsetCounter;
154
231
  private static $skinnedMeshBoundsOffset;
155
232
  private calculateLodLevel;
@@ -162,4 +239,5 @@ declare class LOD_state {
162
239
  readonly lastScreenspaceVolume: Vector3;
163
240
  lastCentrality: number;
164
241
  }
242
+ export declare function getLODColor(level: number, target: Color): Color;
165
243
  export {};