@needle-tools/engine 4.7.2-next.6f2cd71 → 4.7.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/CHANGELOG.md +15 -0
- package/dist/gltf-progressive-BCZdu3Gc.min.js +8 -0
- package/dist/gltf-progressive-C6QbvrB4.umd.cjs +8 -0
- package/dist/gltf-progressive-CCddD-3B.js +1159 -0
- package/dist/{needle-engine.bundle-B-NoV0t_.min.js → needle-engine.bundle-CnzaSk8v.min.js} +126 -125
- package/dist/{needle-engine.bundle-CFi0ugBx.js → needle-engine.bundle-DALjvqho.js} +3696 -3588
- package/dist/{needle-engine.bundle-BMnlxDfi.umd.cjs → needle-engine.bundle-RNpCdQTu.umd.cjs} +109 -108
- package/dist/needle-engine.d.ts +21 -0
- package/dist/needle-engine.js +3 -3
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-CjW23fio.umd.cjs → postprocessing-CNCT892s.umd.cjs} +1 -1
- package/dist/{postprocessing-xYQWCHFu.min.js → postprocessing-TkXiVrjY.min.js} +1 -1
- package/dist/{postprocessing-DYLNOL3W.js → postprocessing-qvgDnYKK.js} +1 -1
- package/dist/{three-examples-DaDLBuy6.min.js → three-examples-BMOhDaYR.min.js} +17 -17
- package/dist/{three-examples-X3OadjXB.umd.cjs → three-examples-DUcCNw9s.umd.cjs} +17 -17
- package/dist/{three-examples-B50TT3Iu.js → three-examples-tvuhV8Ne.js} +688 -688
- package/lib/engine/engine_context.js +1 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.js +24 -1
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_input.js +3 -0
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_lods.d.ts +1 -1
- package/lib/engine/engine_lods.js.map +1 -1
- package/lib/engine/engine_physics.d.ts +13 -1
- package/lib/engine/engine_physics.js +15 -7
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_tonemapping.js +1 -0
- package/lib/engine/engine_tonemapping.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +0 -1
- package/lib/engine/webcomponents/needle-engine.js +62 -3
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +45 -0
- package/lib/engine-components/OrbitControls.js +54 -9
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Skybox.js +24 -5
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
- package/lib/engine-components/ui/EventSystem.js +9 -10
- package/lib/engine-components/ui/EventSystem.js.map +1 -1
- package/package.json +4 -4
- package/plugins/common/logger.js +34 -16
- package/plugins/vite/logger.client.js +55 -22
- package/src/engine/engine_context.ts +0 -2
- package/src/engine/engine_create_objects.ts +24 -1
- package/src/engine/engine_input.ts +8 -1
- package/src/engine/engine_lods.ts +1 -2
- package/src/engine/engine_physics.ts +26 -8
- package/src/engine/engine_tonemapping.ts +1 -0
- package/src/engine/webcomponents/needle-engine.ts +71 -5
- package/src/engine-components/OrbitControls.ts +78 -14
- package/src/engine-components/Skybox.ts +25 -7
- package/src/engine-components/postprocessing/Effects/Tonemapping.ts +1 -1
- package/src/engine-components/ui/EventSystem.ts +8 -9
- package/dist/gltf-progressive-B9o1T8Vr.umd.cjs +0 -8
- package/dist/gltf-progressive-DO0SisAM.js +0 -1052
- package/dist/gltf-progressive-DQF10PJE.min.js +0 -8
package/plugins/common/logger.js
CHANGED
|
@@ -106,17 +106,17 @@ export function captureLogMessage(process, key, log, connectionId, time = Date.n
|
|
|
106
106
|
// #region stringify log
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
|
|
109
110
|
/**
|
|
110
111
|
* Stringifies a log message, handling circular references and formatting.
|
|
111
112
|
* @param {any} log
|
|
112
113
|
* @param {Set<any>} [seen]
|
|
113
114
|
*/
|
|
114
115
|
function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
115
|
-
|
|
116
116
|
const isServer = typeof window === "undefined";
|
|
117
117
|
const stringify_limits = {
|
|
118
|
-
string: isServer ? 100_000 :
|
|
119
|
-
object_keys: isServer ? 300 :
|
|
118
|
+
string: isServer ? 100_000 : 1_000,
|
|
119
|
+
object_keys: isServer ? 300 : 200,
|
|
120
120
|
object_depth: isServer ? 10 : 3,
|
|
121
121
|
array_items: isServer ? 2_000 : 100,
|
|
122
122
|
}
|
|
@@ -140,7 +140,20 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
140
140
|
|
|
141
141
|
if (seen.has(log)) return "<circular>";
|
|
142
142
|
|
|
143
|
-
if (Array.isArray(log)
|
|
143
|
+
if (Array.isArray(log)
|
|
144
|
+
|| log instanceof ArrayBuffer
|
|
145
|
+
|| log instanceof Uint8Array
|
|
146
|
+
|| log instanceof Float32Array
|
|
147
|
+
|| log instanceof Int32Array
|
|
148
|
+
|| log instanceof Uint32Array
|
|
149
|
+
|| log instanceof Uint16Array
|
|
150
|
+
|| log instanceof Uint8ClampedArray
|
|
151
|
+
|| log instanceof Int16Array
|
|
152
|
+
|| log instanceof Int8Array
|
|
153
|
+
|| log instanceof BigInt64Array
|
|
154
|
+
|| log instanceof BigUint64Array
|
|
155
|
+
|| log instanceof Float64Array
|
|
156
|
+
) {
|
|
144
157
|
seen.add(log);
|
|
145
158
|
return stringifyArray(log);
|
|
146
159
|
}
|
|
@@ -151,25 +164,26 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
seen.add(log);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// seen.add(value);
|
|
160
|
-
// }
|
|
161
|
-
// return value;
|
|
162
|
-
// });
|
|
163
|
-
// return str;
|
|
167
|
+
|
|
168
|
+
if(log instanceof Error) {
|
|
169
|
+
return `<Error: ${log.message}\nStack: ${log.stack}>`;
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
const keys = Object.keys(log);
|
|
165
173
|
let res = "{";
|
|
166
174
|
for (let i = 0; i < keys.length; i++) {
|
|
167
175
|
const key = keys[i];
|
|
168
176
|
let value = log[key];
|
|
177
|
+
if (i >= stringify_limits.object_keys) {
|
|
178
|
+
res += `, ... <truncated ${keys.length - i} keys>`;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
169
181
|
|
|
170
182
|
if (typeof value === "number") {
|
|
171
|
-
// clamp precision for numbers
|
|
172
|
-
value
|
|
183
|
+
// clamp precision for numbers if it has decimal places
|
|
184
|
+
if (value % 1 !== 0) {
|
|
185
|
+
value = Number(value.toFixed(4));
|
|
186
|
+
}
|
|
173
187
|
}
|
|
174
188
|
let str = stringifyLog(value, seen, depth + 1);
|
|
175
189
|
if (typeof value === "object") {
|
|
@@ -191,6 +205,7 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
191
205
|
// });
|
|
192
206
|
// return `{ ${entries.join(", ")} }`;
|
|
193
207
|
}
|
|
208
|
+
|
|
194
209
|
return String(log);
|
|
195
210
|
|
|
196
211
|
function stringifyArray(arr) {
|
|
@@ -212,6 +227,9 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
212
227
|
|
|
213
228
|
|
|
214
229
|
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
215
233
|
// #region utility functions
|
|
216
234
|
|
|
217
235
|
/**
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
|
|
2
|
+
let isStringifying = false;
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Patches console methods to capture log messages and send them to the server.
|
|
4
6
|
* This is useful for debugging and logging in the client.
|
|
@@ -6,14 +8,31 @@
|
|
|
6
8
|
* @param {any} message - The log message to capture.
|
|
7
9
|
*/
|
|
8
10
|
function sendLogToServer(level, ...message) {
|
|
11
|
+
if (isStringifying) return;
|
|
9
12
|
if ("hot" in import.meta) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
message = message
|
|
13
|
+
try {
|
|
14
|
+
isStringifying = true;
|
|
15
|
+
// console.time("sendLogToServer");
|
|
16
|
+
message = stringifyLog(message);
|
|
17
|
+
// console.timeEnd("sendLogToServer");
|
|
18
|
+
// keep messages below payload limit
|
|
19
|
+
if (message.length > 100_000) {
|
|
20
|
+
message = message.slice(0, 100_000) + "... <truncated>";
|
|
21
|
+
}
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
import.meta.hot.send("needle:client-log", { level, message: message });
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// silently fail but send a message
|
|
26
|
+
try {
|
|
27
|
+
import.meta.hot.send("needle:client-log", { level: "error", message: `Error during logging: ${e.message}` });
|
|
28
|
+
}
|
|
29
|
+
catch (e2) {
|
|
30
|
+
// fallback failed as well
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
isStringifying = false;
|
|
14
35
|
}
|
|
15
|
-
// @ts-ignore
|
|
16
|
-
import.meta.hot.send("needle:client-log", { level, message: message });
|
|
17
36
|
}
|
|
18
37
|
}
|
|
19
38
|
|
|
@@ -174,11 +193,10 @@ User Activation: ${"userActivation" in navigator ? JSON.stringify(navigator.user
|
|
|
174
193
|
* @param {Set<any>} [seen]
|
|
175
194
|
*/
|
|
176
195
|
function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
177
|
-
|
|
178
196
|
const isServer = typeof window === "undefined";
|
|
179
197
|
const stringify_limits = {
|
|
180
|
-
string: isServer ? 100_000 :
|
|
181
|
-
object_keys: isServer ? 300 :
|
|
198
|
+
string: isServer ? 100_000 : 1_000,
|
|
199
|
+
object_keys: isServer ? 300 : 200,
|
|
182
200
|
object_depth: isServer ? 10 : 3,
|
|
183
201
|
array_items: isServer ? 2_000 : 100,
|
|
184
202
|
}
|
|
@@ -202,7 +220,20 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
202
220
|
|
|
203
221
|
if (seen.has(log)) return "<circular>";
|
|
204
222
|
|
|
205
|
-
if (Array.isArray(log)
|
|
223
|
+
if (Array.isArray(log)
|
|
224
|
+
|| log instanceof ArrayBuffer
|
|
225
|
+
|| log instanceof Uint8Array
|
|
226
|
+
|| log instanceof Float32Array
|
|
227
|
+
|| log instanceof Int32Array
|
|
228
|
+
|| log instanceof Uint32Array
|
|
229
|
+
|| log instanceof Uint16Array
|
|
230
|
+
|| log instanceof Uint8ClampedArray
|
|
231
|
+
|| log instanceof Int16Array
|
|
232
|
+
|| log instanceof Int8Array
|
|
233
|
+
|| log instanceof BigInt64Array
|
|
234
|
+
|| log instanceof BigUint64Array
|
|
235
|
+
|| log instanceof Float64Array
|
|
236
|
+
) {
|
|
206
237
|
seen.add(log);
|
|
207
238
|
return stringifyArray(log);
|
|
208
239
|
}
|
|
@@ -213,25 +244,26 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
213
244
|
}
|
|
214
245
|
|
|
215
246
|
seen.add(log);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// seen.add(value);
|
|
222
|
-
// }
|
|
223
|
-
// return value;
|
|
224
|
-
// });
|
|
225
|
-
// return str;
|
|
247
|
+
|
|
248
|
+
if (log instanceof Error) {
|
|
249
|
+
return `<Error: ${log.message}\nStack: ${log.stack}>`;
|
|
250
|
+
}
|
|
251
|
+
|
|
226
252
|
const keys = Object.keys(log);
|
|
227
253
|
let res = "{";
|
|
228
254
|
for (let i = 0; i < keys.length; i++) {
|
|
229
255
|
const key = keys[i];
|
|
230
256
|
let value = log[key];
|
|
257
|
+
if (i >= stringify_limits.object_keys) {
|
|
258
|
+
res += `, ... <truncated ${keys.length - i} keys>`;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
231
261
|
|
|
232
262
|
if (typeof value === "number") {
|
|
233
|
-
// clamp precision for numbers
|
|
234
|
-
value
|
|
263
|
+
// clamp precision for numbers if it has decimal places
|
|
264
|
+
if (value % 1 !== 0) {
|
|
265
|
+
value = Number(value.toFixed(4));
|
|
266
|
+
}
|
|
235
267
|
}
|
|
236
268
|
let str = stringifyLog(value, seen, depth + 1);
|
|
237
269
|
if (typeof value === "object") {
|
|
@@ -253,6 +285,7 @@ function stringifyLog(log, seen = new Set(), depth = 0) {
|
|
|
253
285
|
// });
|
|
254
286
|
// return `{ ${entries.join(", ")} }`;
|
|
255
287
|
}
|
|
288
|
+
|
|
256
289
|
return String(log);
|
|
257
290
|
|
|
258
291
|
function stringifyArray(arr) {
|
|
@@ -1039,8 +1039,6 @@ export class Context implements IContext {
|
|
|
1039
1039
|
}
|
|
1040
1040
|
this.renderer?.setAnimationLoop(null);
|
|
1041
1041
|
|
|
1042
|
-
await delay(1);
|
|
1043
|
-
|
|
1044
1042
|
Context.Current = this;
|
|
1045
1043
|
await ContextRegistry.dispatchCallback(ContextEvent.ContextCreationStart, this);
|
|
1046
1044
|
|
|
@@ -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:
|
|
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);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import type { LOD_Results } from "@needle-tools/gltf-progressive";
|
|
1
2
|
import { LODsManager as _LODsManager, NEEDLE_progressive, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";
|
|
2
|
-
import type { LOD_Results } from "@needle-tools/gltf-progressive/src/lods_manager.js";
|
|
3
3
|
import { Box3, Camera, Mesh, PerspectiveCamera, Scene, Sphere, WebGLRenderer } from "three";
|
|
4
4
|
|
|
5
|
-
import { findResourceUsers } from "./engine_assetdatabase.js";
|
|
6
5
|
import type { Context } from "./engine_context.js";
|
|
7
6
|
import { Gizmos } from "./engine_gizmos.js";
|
|
8
7
|
import { getTempVector } from "./engine_three_utils.js";
|
|
@@ -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
|
|
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"
|
|
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
|
-
|
|
574
|
-
skinnedMesh.autoUpdateMeshBVH = false;
|
|
586
|
+
skinnedMesh.bvhNeedsUpdate = true;
|
|
575
587
|
}
|
|
576
588
|
}
|
|
577
|
-
else if (geom.boundsTree && (skinnedMesh.
|
|
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
|
-
|
|
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;
|
|
@@ -164,6 +164,7 @@ vec3 _agxLook(vec3 val) {
|
|
|
164
164
|
vec3 AgXToneMapping( vec3 color ) {
|
|
165
165
|
// apply AGX
|
|
166
166
|
color *= toneMappingExposure;
|
|
167
|
+
color = max(color, vec3(0.001)); // Prevent NaN
|
|
167
168
|
color = _agx(color);
|
|
168
169
|
color = _agxLook(color); // Optional
|
|
169
170
|
color = _agxEotf(color);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
|
|
2
2
|
import { PUBLIC_KEY, VERSION } from "../engine_constants.js";
|
|
3
|
+
import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
|
|
3
4
|
import { registerLoader } from "../engine_gltf.js";
|
|
4
5
|
import { hasCommercialLicense } from "../engine_license.js";
|
|
6
|
+
import { onStart } from "../engine_lifecycle_api.js";
|
|
5
7
|
import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "../engine_loaders.gltf.js";
|
|
6
8
|
import { NeedleLoader } from "../engine_loaders.js";
|
|
7
9
|
import { Context, ContextCreateArgs } from "../engine_setup.js";
|
|
@@ -232,6 +234,10 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
232
234
|
this.setPublicKey();
|
|
233
235
|
this.setVersion();
|
|
234
236
|
|
|
237
|
+
// If tabindex is not defined we set it to 0 to make it focusable and reachable via keyboard. Also keyboard events will be dispatched to the element (e.g. keydown, keyup) which is used by OrbitControls
|
|
238
|
+
if (this.getAttribute("tabindex") === null || this.getAttribute("tabindex") === undefined)
|
|
239
|
+
this.setAttribute("tabindex", "0");
|
|
240
|
+
|
|
235
241
|
this.addEventListener("xr-session-started", this.onXRSessionStarted);
|
|
236
242
|
this.onSetupDesktop();
|
|
237
243
|
|
|
@@ -489,6 +495,11 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
489
495
|
}
|
|
490
496
|
}
|
|
491
497
|
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
// Experimental loading blur
|
|
501
|
+
handleLoadingBlur(this);
|
|
502
|
+
|
|
492
503
|
const currentHash = this.getAttribute("hash");
|
|
493
504
|
if (currentHash !== null && currentHash !== undefined)
|
|
494
505
|
this._context.hash = currentHash;
|
|
@@ -578,10 +589,6 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
578
589
|
private onReady = () => this._loadingView?.onLoadingFinished();
|
|
579
590
|
private onError = () => this._loadingView?.setMessage("Loading failed!");
|
|
580
591
|
|
|
581
|
-
private internalSetLoadingMessage(str: string) {
|
|
582
|
-
this._loadingView?.setMessage(str);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
592
|
private getSourceFiles(): Array<string> {
|
|
586
593
|
const src: string | null | string[] = this.getAttribute("src");
|
|
587
594
|
if (!src) return [];
|
|
@@ -858,4 +865,63 @@ function getDisplayName(str: string) {
|
|
|
858
865
|
if (isDevEnvironment())
|
|
859
866
|
console.debug("Loading: use default name", name);
|
|
860
867
|
return name;
|
|
861
|
-
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
|
|
872
|
+
onStart((ctx) => {
|
|
873
|
+
const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
|
|
874
|
+
if (userBlurSetting !== null && userBlurSetting !== "0") {
|
|
875
|
+
if (ctx.domElement === needleEngineElement) {
|
|
876
|
+
|
|
877
|
+
const promise = ctx.lodsManager.manager?.awaitLoading({
|
|
878
|
+
frames: 5,
|
|
879
|
+
signal: AbortSignal.timeout(10_000), // Limit how long the page can be blurred
|
|
880
|
+
maxPromisesPerObject: 1,
|
|
881
|
+
}).catch(_ => {
|
|
882
|
+
// Ignore errors (none are expected tho...)
|
|
883
|
+
});
|
|
884
|
+
let blur = "20px";
|
|
885
|
+
if (userBlurSetting.endsWith("px")) blur = userBlurSetting;
|
|
886
|
+
const duration = 170;
|
|
887
|
+
|
|
888
|
+
// If the scene has a transparent background we apply a blur to the canvas directly to not *also* blur images
|
|
889
|
+
// But don't always use this effect because the edges don't look as good as with a backdrop filter
|
|
890
|
+
if (ctx.scene.background === null) {
|
|
891
|
+
const domElement = needleEngineElement;
|
|
892
|
+
const canvas = ctx.renderer.domElement;
|
|
893
|
+
const originalFilterValue = canvas.style.filter;
|
|
894
|
+
const originalOverflowValue = canvas.style.overflow;
|
|
895
|
+
canvas.style.filter += `blur(${blur})`;
|
|
896
|
+
domElement.style.overflow = "hidden";
|
|
897
|
+
promise?.then(() => {
|
|
898
|
+
const animation = canvas.animate([{
|
|
899
|
+
filter: "blur(0px)",
|
|
900
|
+
}
|
|
901
|
+
], { duration: duration, easing: "ease-in", });
|
|
902
|
+
animation.onfinish = () => {
|
|
903
|
+
canvas.style.filter = originalFilterValue;
|
|
904
|
+
domElement.style.overflow = originalOverflowValue;
|
|
905
|
+
};
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
const blurryElement = document.createElement("div");
|
|
910
|
+
ctx.domElement.prepend(blurryElement);
|
|
911
|
+
blurryElement.style.cssText = "position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; pointer-events: none";
|
|
912
|
+
blurryElement.style.backdropFilter = `blur(${blur})`;
|
|
913
|
+
promise?.then(() => {
|
|
914
|
+
const animation = blurryElement.animate([{
|
|
915
|
+
backdropFilter: "blur(0px)",
|
|
916
|
+
opacity: 0,
|
|
917
|
+
}
|
|
918
|
+
], { duration: duration, easing: "ease-in", });
|
|
919
|
+
animation.onfinish = () => {
|
|
920
|
+
blurryElement.remove();
|
|
921
|
+
};
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}, { once: true });
|
|
927
|
+
}
|