@needle-tools/engine 4.7.2-next.d24ebbc → 4.7.3-next.1603bb0

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 (54) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/gltf-progressive-BCZdu3Gc.min.js +8 -0
  3. package/dist/gltf-progressive-C6QbvrB4.umd.cjs +8 -0
  4. package/dist/{gltf-progressive-CNdBjvz6.js → gltf-progressive-CCddD-3B.js} +413 -399
  5. package/dist/{needle-engine.bundle-CGXifNgp.min.js → needle-engine.bundle-6bPQlS1q.min.js} +102 -102
  6. package/dist/{needle-engine.bundle-CHfSBXXT.js → needle-engine.bundle-CL5SiInU.js} +1962 -1889
  7. package/dist/{needle-engine.bundle-Dcn5L5IY.umd.cjs → needle-engine.bundle-Dm9L7JpU.umd.cjs} +70 -70
  8. package/dist/needle-engine.d.ts +21 -0
  9. package/dist/needle-engine.js +3 -3
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{postprocessing-xYQWCHFu.min.js → postprocessing-BzY0H7ry.min.js} +9 -9
  13. package/dist/{postprocessing-CjW23fio.umd.cjs → postprocessing-Dw2OCMp4.umd.cjs} +9 -9
  14. package/dist/{postprocessing-DYLNOL3W.js → postprocessing-vKBVFpSz.js} +10 -10
  15. package/dist/{three-examples-DaDLBuy6.min.js → three-examples-BMOhDaYR.min.js} +17 -17
  16. package/dist/{three-examples-X3OadjXB.umd.cjs → three-examples-DUcCNw9s.umd.cjs} +17 -17
  17. package/dist/{three-examples-B50TT3Iu.js → three-examples-tvuhV8Ne.js} +688 -688
  18. package/lib/engine/engine_create_objects.js +24 -1
  19. package/lib/engine/engine_create_objects.js.map +1 -1
  20. package/lib/engine/engine_input.js +3 -0
  21. package/lib/engine/engine_input.js.map +1 -1
  22. package/lib/engine/engine_physics.d.ts +13 -1
  23. package/lib/engine/engine_physics.js +15 -7
  24. package/lib/engine/engine_physics.js.map +1 -1
  25. package/lib/engine/webcomponents/needle-engine.js +14 -11
  26. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  27. package/lib/engine-components/OrbitControls.d.ts +57 -0
  28. package/lib/engine-components/OrbitControls.js +68 -9
  29. package/lib/engine-components/OrbitControls.js.map +1 -1
  30. package/lib/engine-components/Skybox.d.ts +1 -0
  31. package/lib/engine-components/Skybox.js +14 -18
  32. package/lib/engine-components/Skybox.js.map +1 -1
  33. package/lib/engine-components/postprocessing/PostProcessingHandler.js +1 -1
  34. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  35. package/lib/engine-components/postprocessing/Volume.d.ts +3 -1
  36. package/lib/engine-components/postprocessing/Volume.js +2 -0
  37. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  38. package/lib/engine-components/ui/EventSystem.js +9 -10
  39. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  40. package/package.json +3 -3
  41. package/plugins/common/logger.js +34 -16
  42. package/plugins/vite/logger.client.js +123 -52
  43. package/plugins/vite/logger.js +5 -5
  44. package/src/engine/engine_create_objects.ts +24 -1
  45. package/src/engine/engine_input.ts +8 -1
  46. package/src/engine/engine_physics.ts +26 -8
  47. package/src/engine/webcomponents/needle-engine.ts +15 -13
  48. package/src/engine-components/OrbitControls.ts +94 -14
  49. package/src/engine-components/Skybox.ts +15 -21
  50. package/src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  51. package/src/engine-components/postprocessing/Volume.ts +2 -1
  52. package/src/engine-components/ui/EventSystem.ts +8 -9
  53. package/dist/gltf-progressive-C_oN6wCA.umd.cjs +0 -8
  54. package/dist/gltf-progressive-MgWOszRl.min.js +0 -8
@@ -1,3 +1,6 @@
1
+ import path from "path";
2
+
3
+ let isStringifying = false;
1
4
 
2
5
  /**
3
6
  * Patches console methods to capture log messages and send them to the server.
@@ -6,52 +9,108 @@
6
9
  * @param {any} message - The log message to capture.
7
10
  */
8
11
  function sendLogToServer(level, ...message) {
12
+ if (isStringifying) return;
9
13
  if ("hot" in import.meta) {
10
- message = stringifyLog(message);
11
- // keep messages below payload limit
12
- if(message.length > 100_000) {
13
- message = message.slice(0, 100_000) + "... <truncated>";
14
+ try {
15
+ isStringifying = true;
16
+ // console.time("sendLogToServer");
17
+ message = stringifyLog(message);
18
+ // console.timeEnd("sendLogToServer");
19
+ // keep messages below payload limit
20
+ if (message.length > 100_000) {
21
+ message = message.slice(0, 100_000) + "... <truncated>";
22
+ }
23
+ // @ts-ignore
24
+ import.meta.hot.send("needle:client-log", { level, message: message });
25
+ } catch (e) {
26
+ // silently fail but send a message
27
+ try {
28
+ import.meta.hot.send("needle:client-log", { level: "error", message: `Error during logging: ${e.message}` });
29
+ }
30
+ catch (e2) {
31
+ // fallback failed as well
32
+ }
33
+ }
34
+ finally {
35
+ isStringifying = false;
14
36
  }
15
- // @ts-ignore
16
- import.meta.hot.send("needle:client-log", { level, message: message });
17
37
  }
18
38
  }
19
39
 
20
- // const obj = {
21
- // hello: "world"
22
- // }
23
- // obj["test"] = obj;
24
- // sendLogToServer("internal", "Test circular reference", obj);
40
+ function logHelper(fn, args) {
41
+ const error = new Error();
42
+ const stack = error.stack;
43
+ const caller = stack?.split('\n')[3]; // Get the actual caller
44
+ const path = caller?.trim();
45
+ if (!path) {
46
+ return fn(...args);
47
+ }
48
+ const pathWithoutBrackets = path.replaceAll("(", "").replaceAll(")", "");
49
+ fn(...args, `\n» ${pathWithoutBrackets}`);
50
+ }
25
51
 
26
52
  if (import.meta && "hot" in import.meta) {
27
53
 
28
- const originalLog = console.log;
29
- const originalWarn = console.warn;
30
- const originalInfo = console.info;
31
- const originalDebug = console.debug;
32
- const originalError = console.error;
54
+ function patchLogs() {
55
+ const originalLog = console.log.bind(console);
56
+ const originalWarn = console.warn.bind(console);
57
+ const originalInfo = console.info.bind(console);
58
+ const originalDebug = console.debug.bind(console);
59
+ const originalError = console.error.bind(console);
33
60
 
34
- console.log = (...args) => {
35
- originalLog(...args);
36
- sendLogToServer("log", ...args);
37
- }
38
- console.warn = (...args) => {
39
- originalWarn(...args);
40
- sendLogToServer("warn", ...args);
41
- }
42
- console.info = (...args) => {
43
- originalInfo(...args);
44
- sendLogToServer("info", ...args);
61
+ console.log = function (...args) {
62
+ logHelper(originalLog, args);
63
+ sendLogToServer("log", ...args);
64
+ }
65
+ console.warn = (...args) => {
66
+ logHelper(originalWarn, args);
67
+ sendLogToServer("warn", ...args);
68
+ }
69
+ console.info = (...args) => {
70
+ logHelper(originalInfo, args);
71
+ sendLogToServer("info", ...args);
72
+ }
73
+ console.debug = (...args) => {
74
+ logHelper(originalDebug, args);
75
+ sendLogToServer("debug", ...args);
76
+ }
77
+ console.error = (...args) => {
78
+ logHelper(originalError, args);
79
+ sendLogToServer("error", ...args);
80
+ }
81
+ return () => {
82
+ console.log = originalLog;
83
+ console.warn = originalWarn;
84
+ console.info = originalInfo;
85
+ console.debug = originalDebug;
86
+ console.error = originalError;
87
+ }
45
88
  }
46
- console.debug = (...args) => {
47
- originalDebug(...args);
48
- sendLogToServer("debug", ...args);
89
+
90
+ const query = new URLSearchParams(window.location.search);
91
+ if (query.has("needle-debug")) {
92
+ patchLogs();
49
93
  }
50
- console.error = (...args) => {
51
- originalError(...args);
52
- sendLogToServer("error", ...args);
94
+ else {
95
+ // const unpatch = patchLogs();
96
+ // setTimeout(() => {
97
+ // sendLogToServer("internal", "Stop listening to console.log.");
98
+ // unpatch();
99
+ // }, 10_000);
100
+
101
+ const threshold = 100;
102
+ const devToolsArePotentiallyOpen = window.outerHeight - window.innerHeight > threshold || window.outerWidth - window.innerWidth > threshold;
103
+ if (devToolsArePotentiallyOpen) {
104
+ sendLogToServer("internal", "Console logging is disabled (devtools are open)");
105
+ }
106
+ else {
107
+ sendLogToServer("internal", "Console logging is enabled");
108
+ patchLogs();
109
+ }
53
110
  }
54
111
 
112
+
113
+
55
114
  try {
56
115
  sendLogToServer("internal", `Page loaded
57
116
  URL: ${window.location.href}
@@ -67,7 +126,6 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
67
126
  `);
68
127
 
69
128
  if ("gpu" in navigator) {
70
-
71
129
  // @ts-ignore
72
130
  navigator.gpu.requestAdapter()
73
131
  .then(adapter => adapter ? adapter.requestDevice() : null)
@@ -102,10 +160,9 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
102
160
  sendLogToServer("error", `Unhandled promise rejection: ${reason}`);
103
161
  });
104
162
  window.addEventListener('beforeunload', () => {
105
- sendLogToServer("internal", "Page is unloading");
163
+ sendLogToServer("internal", "Page is unloading\n\n");
106
164
  });
107
165
  document.addEventListener('visibilitychange', () => {
108
- console.log("Visibility changed:", document.visibilityState);
109
166
  if (document.visibilityState === 'hidden') {
110
167
  sendLogToServer("internal", "Page is hidden");
111
168
  }
@@ -174,11 +231,10 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
174
231
  * @param {Set<any>} [seen]
175
232
  */
176
233
  function stringifyLog(log, seen = new Set(), depth = 0) {
177
-
178
234
  const isServer = typeof window === "undefined";
179
235
  const stringify_limits = {
180
- string: isServer ? 100_000 : 2000,
181
- object_keys: isServer ? 300 : 100,
236
+ string: isServer ? 100_000 : 1_000,
237
+ object_keys: isServer ? 300 : 200,
182
238
  object_depth: isServer ? 10 : 3,
183
239
  array_items: isServer ? 2_000 : 100,
184
240
  }
@@ -202,7 +258,20 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
202
258
 
203
259
  if (seen.has(log)) return "<circular>";
204
260
 
205
- if (Array.isArray(log)) {
261
+ if (Array.isArray(log)
262
+ || log instanceof ArrayBuffer
263
+ || log instanceof Uint8Array
264
+ || log instanceof Float32Array
265
+ || log instanceof Int32Array
266
+ || log instanceof Uint32Array
267
+ || log instanceof Uint16Array
268
+ || log instanceof Uint8ClampedArray
269
+ || log instanceof Int16Array
270
+ || log instanceof Int8Array
271
+ || log instanceof BigInt64Array
272
+ || log instanceof BigUint64Array
273
+ || log instanceof Float64Array
274
+ ) {
206
275
  seen.add(log);
207
276
  return stringifyArray(log);
208
277
  }
@@ -213,25 +282,26 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
213
282
  }
214
283
 
215
284
  seen.add(log);
216
- // const str = JSON.stringify(log, (key, value) => {
217
- // if (typeof value === "function") return "<function>";
218
- // if (typeof value === "string") return stringifyLog(value, seen, depth + 1);
219
- // if (typeof value === "object") {
220
- // if (seen.has(value)) return "<circular>";
221
- // seen.add(value);
222
- // }
223
- // return value;
224
- // });
225
- // return str;
285
+
286
+ if (log instanceof Error) {
287
+ return `<Error: ${log.message}\nStack: ${log.stack}>`;
288
+ }
289
+
226
290
  const keys = Object.keys(log);
227
291
  let res = "{";
228
292
  for (let i = 0; i < keys.length; i++) {
229
293
  const key = keys[i];
230
294
  let value = log[key];
295
+ if (i >= stringify_limits.object_keys) {
296
+ res += `, ... <truncated ${keys.length - i} keys>`;
297
+ break;
298
+ }
231
299
 
232
300
  if (typeof value === "number") {
233
- // clamp precision for numbers
234
- value = Number(value.toFixed(6));
301
+ // clamp precision for numbers if it has decimal places
302
+ if (value % 1 !== 0) {
303
+ value = Number(value.toFixed(4));
304
+ }
235
305
  }
236
306
  let str = stringifyLog(value, seen, depth + 1);
237
307
  if (typeof value === "object") {
@@ -253,6 +323,7 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
253
323
  // });
254
324
  // return `{ ${entries.join(", ")} }`;
255
325
  }
326
+
256
327
  return String(log);
257
328
 
258
329
  function stringifyArray(arr) {
@@ -92,10 +92,10 @@ function logRequests(server, log_http_requests = false) {
92
92
  });
93
93
  }
94
94
  // Log HTTP requests
95
- if (log_http_requests) {
96
- server.middlewares.use((req, res, next) => {
95
+ server.middlewares.use((req, res, next) => {
96
+ if (log_http_requests) {
97
97
  captureLogMessage("client-http", "info", [req.method, req.url], null);
98
- next();
99
- });
100
- }
98
+ }
99
+ next();
100
+ });
101
101
  }
@@ -1,5 +1,5 @@
1
1
  import { createLoaders } from "@needle-tools/gltf-progressive";
2
- import { BoxGeometry, BufferGeometry, Color, ColorRepresentation, CylinderGeometry, DoubleSide, ExtrudeGeometry, Group, Material, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, PlaneGeometry, Shape, SphereGeometry, Sprite, SpriteMaterial, Texture } from "three"
2
+ import { BoxGeometry, BufferGeometry, Color, ColorRepresentation, CylinderGeometry, DoubleSide, ExtrudeGeometry, Group, Material, Mesh, MeshBasicMaterial, MeshStandardMaterial, Object3D, PlaneGeometry, Shape, SphereGeometry, Sprite, SpriteMaterial, Texture, Vector2 } from "three"
3
3
  import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";
4
4
  import { Font, FontLoader } from "three/examples/jsm/loaders/FontLoader.js";
5
5
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
@@ -300,9 +300,29 @@ function createBoxWithRoundedEdges(width: number, height: number, _depth: number
300
300
  bevelSize: radius,
301
301
  bevelThickness: radius0,
302
302
  curveSegments: smoothness,
303
+ UVGenerator: {
304
+ generateTopUV: (_, vertices: number[]) => {
305
+ const uvs: Vector2[] = [];
306
+ for (let i = 0; i < vertices.length; i += 3) {
307
+ uvs.push(new Vector2(vertices[i] / width, vertices[i + 1] / height));
308
+ }
309
+ return uvs;
310
+ },
311
+ generateSideWallUV: (_, vertices: number[], indexA: number, indexB: number, indexC: number, indexD: number) => {
312
+ const uvs: Vector2[] = [];
313
+ uvs.push(new Vector2(vertices[indexA] / width, vertices[indexA + 1] / height));
314
+ uvs.push(new Vector2(vertices[indexB] / width, vertices[indexB + 1] / height));
315
+ uvs.push(new Vector2(vertices[indexC] / width, vertices[indexC + 1] / height));
316
+ uvs.push(new Vector2(vertices[indexD] / width, vertices[indexD + 1] / height));
317
+ return uvs;
318
+ }
319
+ },
303
320
  });
304
321
  geometry.scale(1, 1, 1 - radius0)
305
322
  geometry.center();
323
+ // Ensure we have an index buffer
324
+ if (!geometry.index)
325
+ geometry.setIndex(Array.from({ length: geometry.attributes.position.count }, (_, i) => i));
306
326
  geometry.computeVertexNormals();
307
327
  return geometry;
308
328
  }
@@ -389,6 +409,9 @@ function loadShaderball(group: Group, opts?: ObjectOptions) {
389
409
  const instance = res.clone();
390
410
  const mesh = instance.children[0] as Mesh;
391
411
  if (mesh?.type === "Mesh") {
412
+ // Ensure we have tangents for the Shaderball mesh
413
+ if (!mesh.geometry.attributes.tangent)
414
+ mesh.geometry.computeTangents();
392
415
  updateShaderballMaterial(mesh, opts);
393
416
  }
394
417
  group.add(instance);
@@ -241,6 +241,9 @@ declare type EventListenerOptions = {
241
241
  signal?: AbortSignal;
242
242
  }
243
243
 
244
+
245
+ type RegisteredEventListenerValue = Array<{ priority: number, listeners: Array<{ callback: InputEventListener, options: EventListenerOptions }> }>;
246
+
244
247
  /**
245
248
  * The input system is responsible for handling all input events like pointer events (mouse, touch, xr controllers) and keyboard events.
246
249
  */
@@ -250,7 +253,7 @@ export class Input implements IInput {
250
253
  * That way users can control if they want to receive events before or after other listeners (e.g subscribe to pointer events before the EventSystem receives them) - this allows certain listeners to be always invoked first (or last) and stop propagation
251
254
  * Listeners per event are sorted
252
255
  */
253
- private readonly _eventListeners: { [key: string]: Array<{ priority: number, listeners: Array<{ callback: InputEventListener, options: EventListenerOptions }> }> } = {};
256
+ private readonly _eventListeners: Record<string, RegisteredEventListenerValue> = {};
254
257
 
255
258
  /** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
256
259
  * @param type The event type to listen for
@@ -846,6 +849,10 @@ export class Input implements IInput {
846
849
  }
847
850
 
848
851
  unbindEvents() {
852
+ for (const key in this._eventListeners) {
853
+ this._eventListeners[key].length = 0; // clear all listeners for this event
854
+ }
855
+
849
856
  window.removeEventListener('contextmenu', this.onContextMenu);
850
857
 
851
858
  this._htmlEventSource?.removeEventListener('pointerdown', this.onPointerDown);
@@ -68,9 +68,15 @@ export declare interface IRaycastOptions {
68
68
 
69
69
  /**
70
70
  * Use MeshBVH for raycasting. This is faster than the default threejs raycaster but uses more memory.
71
- * @default undefined
71
+ * @default true
72
72
  */
73
73
  useAcceleratedRaycast?: boolean;
74
+
75
+ /**
76
+ * When enabled raycasting will use the 'slower' traditional three.js raycasting method while the MeshBVH is being generated in the background. When disabled objects that don't have a BVH available *Yet* because it's still being generated will be ignored and not generate any hits. This is useful to improve performance for cases where raycasting happens frequently and it won't matter if raycasts don't produce hits for a few frames.
77
+ * @default true
78
+ */
79
+ allowSlowRaycastFallback?: boolean;
74
80
  }
75
81
 
76
82
  export class RaycastOptions implements IRaycastOptions {
@@ -90,6 +96,7 @@ export class RaycastOptions implements IRaycastOptions {
90
96
  ignore?: Object3D[];
91
97
  testObject?: RaycastTestObjectCallback;
92
98
  useAcceleratedRaycast?: boolean | undefined;
99
+ allowSlowRaycastFallback?: boolean = true;
93
100
 
94
101
  screenPointFromOffset(ox: number, oy: number) {
95
102
  if (this.screenPoint === undefined) this.screenPoint = new Vector2();
@@ -356,7 +363,7 @@ export class Physics {
356
363
  // did handle raycast
357
364
  }
358
365
  else if (options.useAcceleratedRaycast !== false) {
359
- NEMeshBVH.runMeshBVHRaycast(raycaster, mesh, results, this.context);
366
+ NEMeshBVH.runMeshBVHRaycast(raycaster, mesh, results, this.context, options);
360
367
  }
361
368
  else {
362
369
  raycaster.intersectObject(mesh, false, results);
@@ -416,7 +423,7 @@ export class Physics {
416
423
  sphere.center.copy(spherePos);
417
424
  sphere.radius = radius;
418
425
  const previousResults = results.length;
419
- NEMeshBVH.runMeshBVHRaycast(this.sphere, mesh, results, this.context);
426
+ NEMeshBVH.runMeshBVHRaycast(this.sphere, mesh, results, this.context, {});
420
427
  if (previousResults != results.length && !traverseChildsAfterHit) {
421
428
  return;
422
429
  }
@@ -539,7 +546,13 @@ declare module 'three' {
539
546
  acceleratedRaycast?: any;
540
547
  }
541
548
  export interface SkinnedMesh {
549
+ /** @deprecated use autoUpdateMeshBvhInterval */
542
550
  autoUpdateMeshBVH?: boolean;
551
+ /**
552
+ * Interval in milliseconds to automatically update the mesh BVH. When set to >= 0 the BVH will be updated every x milliseconds.
553
+ * @default undefined (disabled)
554
+ */
555
+ autoUpdateMeshBvhInterval?: number;
543
556
  bvhNeedsUpdate?: boolean;
544
557
  }
545
558
  }
@@ -547,7 +560,7 @@ declare module 'three' {
547
560
 
548
561
 
549
562
  namespace NEMeshBVH {
550
- export function runMeshBVHRaycast(method: Raycaster | Sphere, mesh: Mesh, results: Intersection[], context: Pick<Context, "xr">): boolean {
563
+ export function runMeshBVHRaycast(method: Raycaster | Sphere, mesh: Mesh, results: Intersection[], context: Pick<Context, "xr">, options: { allowSlowRaycastFallback?: boolean }): boolean {
551
564
  if (!mesh.geometry) {
552
565
  return false;
553
566
  }
@@ -570,15 +583,16 @@ namespace NEMeshBVH {
570
583
  skinnedMesh.staticGeometry = skinnedMesh.staticGenerator.generate();
571
584
  geom.boundsTree = _computeBoundsTree?.call(skinnedMesh.staticGeometry);
572
585
  skinnedMesh.staticGeometryLastUpdate = performance.now() + Math.random() * 200;
573
- if (skinnedMesh.autoUpdateMeshBVH === undefined)
574
- skinnedMesh.autoUpdateMeshBVH = false;
586
+ skinnedMesh.bvhNeedsUpdate = true;
575
587
  }
576
588
  }
577
- else if (geom.boundsTree && (skinnedMesh.autoUpdateMeshBVH === true || skinnedMeshBVHNeedsUpdate === true)) {
589
+ else if (geom.boundsTree && ((skinnedMesh.autoUpdateMeshBvhInterval !== undefined && skinnedMesh.autoUpdateMeshBvhInterval >= 0) || skinnedMeshBVHNeedsUpdate === true)) {
578
590
  // automatically refit the tree every 300ms
579
591
  const now = performance.now();
580
592
  const timeSinceLastUpdate = now - skinnedMesh.staticGeometryLastUpdate!;
581
- if (skinnedMeshBVHNeedsUpdate || timeSinceLastUpdate > 100) {
593
+ const interval = skinnedMesh.autoUpdateMeshBvhInterval ?? 100;
594
+ if (skinnedMeshBVHNeedsUpdate || timeSinceLastUpdate > interval) {
595
+ if(debugPhysics) console.warn(`Physics: updating skinned mesh bvh for ${mesh.name} after ${timeSinceLastUpdate.toFixed(2)}ms`);
582
596
  skinnedMesh.bvhNeedsUpdate = false;
583
597
  skinnedMesh.staticGeometryLastUpdate = now;
584
598
  skinnedMesh.staticGenerator?.generate(skinnedMesh.staticGeometry);
@@ -694,6 +708,10 @@ namespace NEMeshBVH {
694
708
  }
695
709
  else {
696
710
  if (debugPhysics) console.warn("No bounds tree found for mesh", mesh.name, { workerTask: geom[workerTaskSymbol], hasAcceleratedRaycast: _acceleratedRaycast != null });
711
+ if (options.allowSlowRaycastFallback === false) {
712
+ if (debugPhysics) console.warn("Skipping raycast because no bounds tree is available and allowSlowRaycastFallback is false");
713
+ return false;
714
+ }
697
715
  }
698
716
  const prevFirstHitOnly = raycaster.firstHitOnly;
699
717
  raycaster.firstHitOnly = false;
@@ -869,19 +869,21 @@ function getDisplayName(str: string) {
869
869
 
870
870
 
871
871
  function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
872
- const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
873
- if (userBlurSetting !== null && userBlurSetting !== "0") {
874
- onStart((ctx) => {
872
+ onStart((ctx) => {
873
+ const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
874
+ if (userBlurSetting !== null && userBlurSetting !== "0") {
875
875
  if (ctx.domElement === needleEngineElement) {
876
876
 
877
877
  const promise = ctx.lodsManager.manager?.awaitLoading({
878
+ frames: 5,
878
879
  signal: AbortSignal.timeout(10_000), // Limit how long the page can be blurred
880
+ maxPromisesPerObject: 1,
879
881
  }).catch(_ => {
880
882
  // Ignore errors (none are expected tho...)
881
883
  });
882
- let blur = "10px";
884
+ let blur = "20px";
883
885
  if (userBlurSetting.endsWith("px")) blur = userBlurSetting;
884
- const duration = 500;
886
+ const duration = 170;
885
887
 
886
888
  // If the scene has a transparent background we apply a blur to the canvas directly to not *also* blur images
887
889
  // But don't always use this effect because the edges don't look as good as with a backdrop filter
@@ -896,30 +898,30 @@ function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
896
898
  const animation = canvas.animate([{
897
899
  filter: "blur(0px)",
898
900
  }
899
- ], { duration: duration, easing: "ease-in" });
901
+ ], { duration: duration, easing: "ease-in", });
900
902
  animation.onfinish = () => {
901
903
  canvas.style.filter = originalFilterValue;
902
904
  domElement.style.overflow = originalOverflowValue;
903
905
  };
904
906
  });
905
907
  }
906
- else
907
- {
908
+ else {
908
909
  const blurryElement = document.createElement("div");
909
910
  ctx.domElement.prepend(blurryElement);
910
- blurryElement.style.cssText = "position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10;";
911
+ blurryElement.style.cssText = "position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; pointer-events: none";
911
912
  blurryElement.style.backdropFilter = `blur(${blur})`;
912
913
  promise?.then(() => {
913
914
  const animation = blurryElement.animate([{
914
915
  backdropFilter: "blur(0px)",
916
+ opacity: 0,
915
917
  }
916
- ], { duration: duration, easing: "ease-in" });
918
+ ], { duration: duration, easing: "ease-in", });
917
919
  animation.onfinish = () => {
918
920
  blurryElement.remove();
919
921
  };
920
922
  });
921
923
  }
922
924
  }
923
- }, { once: true });
924
- }
925
- }
925
+ }
926
+ }, { once: true });
927
+ }