@needle-tools/engine 4.8.6 → 4.8.7-next.3c34e8b
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 +4 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-DXRy9EQz.js → gltf-progressive-BcHT3Nyo.js} +1 -1
- package/dist/{gltf-progressive-C-U_onhf.umd.cjs → gltf-progressive-CH3Q4H06.umd.cjs} +1 -1
- package/dist/{gltf-progressive-DViD_J_l.min.js → gltf-progressive-DR6HqF_h.min.js} +1 -1
- package/dist/{needle-engine.bundle-CaNItSG7.umd.cjs → needle-engine.bundle-BgFqEj5W.umd.cjs} +128 -128
- package/dist/{needle-engine.bundle-CQIq7Zg-.min.js → needle-engine.bundle-BthqMTPI.min.js} +128 -128
- package/dist/{needle-engine.bundle-BTJDRZkJ.js → needle-engine.bundle-Fj5SzBaF.js} +4378 -4355
- package/dist/needle-engine.js +402 -400
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-61aXdqNz.umd.cjs → postprocessing-CVb_x9YY.umd.cjs} +1 -1
- package/dist/{postprocessing-D9jDHD0U.js → postprocessing-ORx-0eCx.js} +1 -1
- package/dist/{postprocessing-Be9Ds4NK.min.js → postprocessing-Ywv5oKkX.min.js} +1 -1
- package/dist/three-examples-BX_Sktc9.min.js +501 -0
- package/dist/{three-examples-BihZ_R96.js → three-examples-CNexix3E.js} +2436 -2781
- package/dist/{three-examples-Ce6Th3bv.umd.cjs → three-examples-DWxXVnws.umd.cjs} +21 -21
- package/dist/{vendor-BRpzuoJE.min.js → vendor-C43vobGc.min.js} +37 -37
- package/dist/{vendor-p_xp9KuJ.js → vendor-Z4SPrTcP.js} +2402 -2047
- package/dist/vendor-xfQ8tKF3.umd.cjs +1121 -0
- package/lib/engine/api.d.ts +2 -0
- package/lib/engine/api.js +2 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_addressables.d.ts +12 -12
- package/lib/engine/engine_addressables.js +30 -23
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_animation.d.ts +1 -3
- package/lib/engine/engine_animation.js +15 -9
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_feature_flags.d.ts +3 -0
- package/lib/engine/engine_feature_flags.js +6 -0
- package/lib/engine/engine_feature_flags.js.map +1 -0
- package/lib/engine/engine_gameobject.js +0 -4
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_loaders.js +15 -11
- package/lib/engine/engine_loaders.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.d.ts +2 -1
- package/lib/engine/engine_mainloop_utils.js +18 -6
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_pmrem.d.ts +6 -0
- package/lib/engine/engine_pmrem.js +9 -40
- package/lib/engine/engine_pmrem.js.map +1 -1
- package/lib/engine/extensions/extensions.js +2 -2
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine/js-extensions/Object3D.js +19 -0
- package/lib/engine/js-extensions/Object3D.js.map +1 -1
- package/lib/engine-components/Animation.js +2 -1
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimationUtilsAutoplay.js +1 -6
- package/lib/engine-components/AnimationUtilsAutoplay.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +17 -12
- package/lib/engine-components/DropListener.js +34 -31
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/LookAtConstraint.d.ts +5 -1
- package/lib/engine-components/LookAtConstraint.js +8 -0
- package/lib/engine-components/LookAtConstraint.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +5 -7
- package/lib/engine-components/OrbitControls.js +12 -11
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Skybox.js +9 -3
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +12 -10
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +4 -3
- package/plugins/vite/alias.js +45 -23
- package/src/engine/api.ts +3 -1
- package/src/engine/engine_addressables.ts +44 -33
- package/src/engine/engine_animation.ts +17 -9
- package/src/engine/engine_feature_flags.ts +8 -0
- package/src/engine/engine_gameobject.ts +0 -4
- package/src/engine/engine_loaders.ts +18 -13
- package/src/engine/engine_mainloop_utils.ts +24 -8
- package/src/engine/engine_pmrem.ts +9 -45
- package/src/engine/extensions/extensions.ts +2 -2
- package/src/engine/js-extensions/Object3D.ts +25 -2
- package/src/engine-components/Animation.ts +1 -1
- package/src/engine-components/AnimationUtilsAutoplay.ts +1 -6
- package/src/engine-components/DropListener.ts +40 -31
- package/src/engine-components/LookAtConstraint.ts +9 -1
- package/src/engine-components/OrbitControls.ts +19 -16
- package/src/engine-components/Skybox.ts +8 -4
- package/src/engine-components/webxr/WebXRImageTracking.ts +16 -14
- package/dist/three-examples-DKY9Nfge.min.js +0 -501
- package/dist/vendor-Ja-vKV-a.umd.cjs +0 -1121
|
@@ -105,11 +105,7 @@ export class InstantiateOptions implements IInstantiateOptions {
|
|
|
105
105
|
// });
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
const $isActive = Symbol("isActive");
|
|
109
108
|
export function isActiveSelf(go: Object3D): boolean {
|
|
110
|
-
// if (go[$isActive] === undefined) {
|
|
111
|
-
// go[$isActive] = true;
|
|
112
|
-
// }
|
|
113
109
|
return go.visible;
|
|
114
110
|
}
|
|
115
111
|
|
|
@@ -61,7 +61,7 @@ export async function onCreateLoader(url: string, context: Context): Promise<Cus
|
|
|
61
61
|
switch (type) {
|
|
62
62
|
case "unsupported":
|
|
63
63
|
return null;
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
default:
|
|
66
66
|
case "unknown":
|
|
67
67
|
{
|
|
@@ -287,15 +287,6 @@ async function onAfterLoaded(loader: Loader | CustomLoader, context: Context, gl
|
|
|
287
287
|
gltfId = gltfId.split("?")[0];
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
// assign animations of loaded glTF to all scenes
|
|
291
|
-
if ("scenes" in model) {
|
|
292
|
-
for (const scene of model.scenes) {
|
|
293
|
-
if (scene && !scene.animations?.length) {
|
|
294
|
-
scene.animations = [...model.animations];
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
290
|
// E.g. fbx material cleanup
|
|
300
291
|
postprocessLoadedFile(loader, model);
|
|
301
292
|
|
|
@@ -357,13 +348,27 @@ function checkIfUserAttemptedToLoadALocalFile(url: string) {
|
|
|
357
348
|
/**
|
|
358
349
|
* Postprocess the loaded file. This is used to apply any custom postprocessing to the loaded file.
|
|
359
350
|
*/
|
|
360
|
-
function postprocessLoadedFile(loader: object,
|
|
351
|
+
function postprocessLoadedFile(loader: object, model: Model) {
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
// assign animations of loaded glTF to all scenes
|
|
355
|
+
if ("scenes" in model) {
|
|
356
|
+
for (const scene of model.scenes) {
|
|
357
|
+
if (scene && !scene.animations?.length) {
|
|
358
|
+
for (const anim of model.animations) {
|
|
359
|
+
if (!scene.animations.includes(anim)) {
|
|
360
|
+
scene.animations.push(anim);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
361
366
|
|
|
362
367
|
if (loader instanceof FBXLoader || loader instanceof OBJLoader) {
|
|
363
368
|
|
|
364
|
-
let obj: Object3D | Model =
|
|
369
|
+
let obj: Object3D | Model = model;
|
|
365
370
|
if (!(obj instanceof Object3D)) {
|
|
366
|
-
obj = (
|
|
371
|
+
obj = (model as GLTF).scene || model.scenes.find(s => s);
|
|
367
372
|
}
|
|
368
373
|
|
|
369
374
|
obj.traverse((child) => {
|
|
@@ -3,6 +3,7 @@ import { CubeCamera, Object3D, Scene, WebGLCubeRenderTarget } from 'three';
|
|
|
3
3
|
import { isDevEnvironment } from "./debug/index.js";
|
|
4
4
|
import * as constants from "./engine_constants.js";
|
|
5
5
|
import { ContextRegistry } from "./engine_context_registry.js";
|
|
6
|
+
import { NEEDLE_ENGINE_FEATURE_FLAGS } from './engine_feature_flags.js';
|
|
6
7
|
import { isActiveSelf } from './engine_gameobject.js';
|
|
7
8
|
import { safeInvoke } from "./engine_generic_utils.js";
|
|
8
9
|
import type { IComponent, IContext } from './engine_types.js';
|
|
@@ -31,7 +32,7 @@ export function processNewScripts(context: IContext) {
|
|
|
31
32
|
if (debug)
|
|
32
33
|
console.log("Register new components", context.new_scripts.length, [...context.new_scripts], context.alias ? ("element: " + context.alias) : context["hash"], context);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
if (context.new_scripts_pre_setup_callbacks.length > 0) {
|
|
36
37
|
for (const cb of context.new_scripts_pre_setup_callbacks) {
|
|
37
38
|
if (!cb) continue;
|
|
@@ -39,7 +40,7 @@ export function processNewScripts(context: IContext) {
|
|
|
39
40
|
}
|
|
40
41
|
context.new_scripts_pre_setup_callbacks.length = 0;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
if (context.new_scripts.length <= 0) return;
|
|
44
45
|
|
|
45
46
|
// TODO: update all the code from above to use this logic
|
|
@@ -269,8 +270,22 @@ export function isNeedleXRSessionEventReceiver(script: any, mode: XRSessionMode
|
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
|
|
273
|
+
let needsUpdate = true;
|
|
274
|
+
export function markHierarchyDirty() {
|
|
275
|
+
needsUpdate = true;
|
|
276
|
+
}
|
|
277
|
+
|
|
272
278
|
/** @internal */
|
|
273
|
-
export function updateIsActive(obj?: Object3D) {
|
|
279
|
+
export function updateIsActive(obj?: Object3D, force: boolean = false) {
|
|
280
|
+
|
|
281
|
+
if (NEEDLE_ENGINE_FEATURE_FLAGS.experimentalSmartHierarchyUpdate) {
|
|
282
|
+
|
|
283
|
+
if (!force) {
|
|
284
|
+
if (!needsUpdate) return;
|
|
285
|
+
}
|
|
286
|
+
needsUpdate = false;
|
|
287
|
+
}
|
|
288
|
+
|
|
274
289
|
if (!obj) obj = ContextRegistry.Current.scene;
|
|
275
290
|
if (!obj) {
|
|
276
291
|
console.trace("Invalid call - no current context.");
|
|
@@ -295,11 +310,11 @@ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarc
|
|
|
295
310
|
return false;
|
|
296
311
|
}
|
|
297
312
|
|
|
298
|
-
const
|
|
313
|
+
const activeSelf = isActiveSelf(go);
|
|
299
314
|
if (activeInHierarchy) {
|
|
300
|
-
activeInHierarchy =
|
|
315
|
+
activeInHierarchy = activeSelf;
|
|
301
316
|
// IF we update activeInHierarchy within a disabled hierarchy we need to check the parent
|
|
302
|
-
if (activeInHierarchy && go.parent) {
|
|
317
|
+
if (activeInHierarchy && go.parent && level === 0) {
|
|
303
318
|
const parent = go.parent;
|
|
304
319
|
activeInHierarchy = parent[constants.activeInHierarchyFieldName];
|
|
305
320
|
if (activeInHierarchy === undefined) {
|
|
@@ -315,12 +330,13 @@ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarc
|
|
|
315
330
|
|
|
316
331
|
const prevActive = go[constants.activeInHierarchyFieldName];
|
|
317
332
|
const changed = prevActive !== activeInHierarchy;
|
|
318
|
-
go[constants.activeInHierarchyFieldName] = activeInHierarchy;
|
|
319
333
|
|
|
320
334
|
// only raise events here if we didnt call enable etc already
|
|
321
335
|
if (changed) {
|
|
336
|
+
go[constants.activeInHierarchyFieldName] = activeInHierarchy;
|
|
337
|
+
|
|
322
338
|
if (debugHierarchy)
|
|
323
|
-
console.warn("ACTIVE CHANGE", go.name,
|
|
339
|
+
console.warn("ACTIVE CHANGE", go.name, activeSelf, go.visible, activeInHierarchy, "changed?" + changed, go);
|
|
324
340
|
if (allowEventCall) {
|
|
325
341
|
perComponent(go, comp => {
|
|
326
342
|
if (activeInHierarchy) {
|
|
@@ -4,12 +4,16 @@ import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader";
|
|
|
4
4
|
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
|
|
5
5
|
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
|
|
6
6
|
|
|
7
|
-
import { disposeObjectResources, setDisposable } from "./engine_assetdatabase.js";
|
|
8
|
-
|
|
9
7
|
const running: Map<string, Promise<Texture | null>> = new Map();
|
|
10
8
|
|
|
11
9
|
// #region api
|
|
12
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Loads a PMREM texture from the given URL. This also supports the ultra-fast preprocessed environment maps (PMREM) format.
|
|
13
|
+
* @param url The URL of the PMREM texture to load.
|
|
14
|
+
* @param renderer The WebGLRenderer to use for loading the texture.
|
|
15
|
+
* @returns A promise that resolves to the loaded texture or null if loading failed.
|
|
16
|
+
*/
|
|
13
17
|
export function loadPMREM(url: string, renderer: WebGLRenderer): Promise<Texture | null> {
|
|
14
18
|
if (running.has(url)) {
|
|
15
19
|
return running.get(url)!;
|
|
@@ -17,47 +21,13 @@ export function loadPMREM(url: string, renderer: WebGLRenderer): Promise<Texture
|
|
|
17
21
|
const actualUrl = new URL(url, window.location.href);
|
|
18
22
|
const promise = internalLoadPMREM(actualUrl, renderer);
|
|
19
23
|
running.set(url, promise);
|
|
24
|
+
promise.finally(() => {
|
|
25
|
+
running.delete(url);
|
|
26
|
+
});
|
|
20
27
|
return promise;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
// #region Cache
|
|
26
|
-
|
|
27
|
-
declare type SkyboxCacheEntry = { src: string, texture: Promise<Texture | null> };
|
|
28
|
-
function ensureGlobalCache() {
|
|
29
|
-
if (!globalThis["NEEDLE_ENGINE_SKYBOX_TEXTURES"])
|
|
30
|
-
globalThis["NEEDLE_ENGINE_SKYBOX_TEXTURES"] = new Array<SkyboxCacheEntry>();
|
|
31
|
-
return globalThis["NEEDLE_ENGINE_SKYBOX_TEXTURES"] as Array<SkyboxCacheEntry>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function tryGetPreviouslyLoadedTexture(src: string) {
|
|
35
|
-
const cache = ensureGlobalCache();
|
|
36
|
-
const found = cache.find(x => x.src === src);
|
|
37
|
-
if (found) {
|
|
38
|
-
return found.texture;
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
async function disposeCachedTexture(tex: Promise<Texture | null>) {
|
|
43
|
-
const texture = await tex;
|
|
44
|
-
if (!texture) return;
|
|
45
|
-
setDisposable(texture, true);
|
|
46
|
-
disposeObjectResources(texture);
|
|
47
|
-
}
|
|
48
|
-
function registerPromise(src: string, texture: Promise<Texture | null>) {
|
|
49
|
-
const cache = ensureGlobalCache();
|
|
50
|
-
// Make sure the cache doesnt get too big
|
|
51
|
-
while (cache.length > 5) {
|
|
52
|
-
const entry = cache.shift();
|
|
53
|
-
if (entry) { disposeCachedTexture(entry.texture); }
|
|
54
|
-
}
|
|
55
|
-
texture.then(t => { return setDisposable(t, false) });
|
|
56
|
-
cache.push({ src, texture });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
31
|
// #region loading
|
|
62
32
|
|
|
63
33
|
|
|
@@ -67,11 +37,6 @@ async function internalLoadPMREM(url: URL, renderer: WebGLRenderer) {
|
|
|
67
37
|
const pathname = url.pathname;
|
|
68
38
|
const isPMREM_URL: boolean = url.toString().toLowerCase().includes("pmrem") || url.searchParams.get("pmrem") != null;
|
|
69
39
|
|
|
70
|
-
const cached = tryGetPreviouslyLoadedTexture(pathname);
|
|
71
|
-
if (cached) {
|
|
72
|
-
const res = await cached;
|
|
73
|
-
if (res?.source?.data?.length > 0 || res?.source?.data?.data?.length) return res;
|
|
74
|
-
}
|
|
75
40
|
const isEXR = pathname.endsWith(".exr");
|
|
76
41
|
const isHdr = pathname.endsWith(".hdr");
|
|
77
42
|
const isKtx2 = pathname.endsWith(".ktx2");
|
|
@@ -110,7 +75,6 @@ async function internalLoadPMREM(url: URL, renderer: WebGLRenderer) {
|
|
|
110
75
|
return tex;
|
|
111
76
|
});
|
|
112
77
|
|
|
113
|
-
registerPromise(str, promise);
|
|
114
78
|
const texture = await promise.catch(_err => {
|
|
115
79
|
console.warn("Failed to load texture from url:", url);
|
|
116
80
|
return null;
|
|
@@ -22,11 +22,11 @@ const debug = getParam("debugextensions");
|
|
|
22
22
|
|
|
23
23
|
// lazily import the GLTFAnimationPointerExtension in case it doesnt exist (e.g. using vanilla three)
|
|
24
24
|
let GLTFAnimationPointerExtension: any;
|
|
25
|
-
const KHR_ANIMATIONPOINTER_IMPORT = import("three
|
|
25
|
+
const KHR_ANIMATIONPOINTER_IMPORT = import("@needle-tools/three-animation-pointer").then(async mod => {
|
|
26
26
|
GLTFAnimationPointerExtension = mod.GLTFAnimationPointerExtension;
|
|
27
27
|
return GLTFAnimationPointerExtension;
|
|
28
28
|
}).catch(e => {
|
|
29
|
-
console.warn("Failed to import GLTFLoaderAnimationPointer. Please use @needle-tools/three for full KHR_animation support", e);
|
|
29
|
+
console.warn("Failed to import GLTFLoaderAnimationPointer. Please use @needle-tools/three-animationpointer for full KHR_animation support", e);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Object3D, Quaternion, Vector3 } from "three";
|
|
2
2
|
import { TransformControlsGizmo } from "three/examples/jsm/controls/TransformControls.js";
|
|
3
3
|
|
|
4
|
-
import { addComponent,
|
|
4
|
+
import { addComponent, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, removeComponent } from "../../engine/engine_components.js";
|
|
5
5
|
import { destroy, isActiveSelf, setActive } from "../../engine/engine_gameobject.js";
|
|
6
6
|
import {
|
|
7
7
|
getTempVector,
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
setWorldScale
|
|
16
16
|
}
|
|
17
17
|
from "../../engine/engine_three_utils.js";
|
|
18
|
-
import type { ComponentInit, Constructor, ConstructorConcrete, HideFlags,IComponent as Component, IComponent } from "../../engine/engine_types.js";
|
|
18
|
+
import type { ComponentInit, Constructor, ConstructorConcrete, HideFlags, IComponent as Component, IComponent } from "../../engine/engine_types.js";
|
|
19
|
+
import { NEEDLE_ENGINE_FEATURE_FLAGS } from "../engine_feature_flags.js";
|
|
20
|
+
import { markHierarchyDirty } from "../engine_mainloop_utils.js";
|
|
19
21
|
import { applyPrototypeExtensions, registerPrototypeExtensions } from "./ExtensionUtils.js";
|
|
20
22
|
|
|
21
23
|
|
|
@@ -150,6 +152,27 @@ export function apply(object: Object3D) {
|
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
if (NEEDLE_ENGINE_FEATURE_FLAGS.experimentalSmartHierarchyUpdate) {
|
|
156
|
+
|
|
157
|
+
const addFn = Object3D.prototype.add;
|
|
158
|
+
Object3D.prototype.add = function (...args: any) {
|
|
159
|
+
markHierarchyDirty();
|
|
160
|
+
return addFn.apply(this, args);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const attachFn = Object3D.prototype.attach;
|
|
164
|
+
Object3D.prototype.attach = function (...args: any) {
|
|
165
|
+
markHierarchyDirty();
|
|
166
|
+
return attachFn.apply(this, args);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const removeFn = Object3D.prototype.remove;
|
|
170
|
+
Object3D.prototype.remove = function (...args: any) {
|
|
171
|
+
markHierarchyDirty();
|
|
172
|
+
return removeFn.apply(this, args);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
153
176
|
|
|
154
177
|
Object3D.prototype["SetActive"] = function (active: boolean) {
|
|
155
178
|
this.visible = active;
|
|
@@ -67,7 +67,7 @@ export class Animation extends Behaviour implements IAnimationComponent {
|
|
|
67
67
|
get isAnimationComponent(): boolean { return true; }
|
|
68
68
|
addClip(clip: AnimationClip) {
|
|
69
69
|
if (!this.animations) this.animations = [];
|
|
70
|
-
this.animations.push(clip);
|
|
70
|
+
if (!this.animations.includes(clip)) this.animations.push(clip);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
2
2
|
|
|
3
3
|
import { AnimationUtils } from "../engine/engine_animation.js";
|
|
4
|
-
import { addComponent } from "../engine/engine_components.js";
|
|
5
4
|
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
|
|
6
5
|
import { Animation } from "./Animation.js";
|
|
7
6
|
import { Animator } from "./Animator.js";
|
|
@@ -29,11 +28,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreated, args => {
|
|
|
29
28
|
return undefined;
|
|
30
29
|
}, true);
|
|
31
30
|
if (hasAnimation !== true) {
|
|
32
|
-
AnimationUtils.
|
|
33
|
-
createAnimationComponent: (obj, _clip) => {
|
|
34
|
-
return addComponent(obj, Animation);
|
|
35
|
-
},
|
|
36
|
-
});
|
|
31
|
+
AnimationUtils.autoplayAnimations(file.file as GLTF);
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
34
|
}
|
|
@@ -143,13 +143,6 @@ const blobKeyName = "blob";
|
|
|
143
143
|
*/
|
|
144
144
|
export class DropListener extends Behaviour {
|
|
145
145
|
|
|
146
|
-
/**
|
|
147
|
-
* When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
148
|
-
* When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
|
|
149
|
-
*/
|
|
150
|
-
@serializable()
|
|
151
|
-
useNetworking: boolean = true;
|
|
152
|
-
|
|
153
146
|
/**
|
|
154
147
|
* When assigned, the DropListener will only accept files that are dropped on this specific object.
|
|
155
148
|
* This allows creating designated drop zones in your scene.
|
|
@@ -159,7 +152,10 @@ export class DropListener extends Behaviour {
|
|
|
159
152
|
|
|
160
153
|
/**
|
|
161
154
|
* When enabled, dropped objects will be automatically scaled to fit within the volume defined by fitVolumeSize.
|
|
162
|
-
* Useful for ensuring dropped models appear at an appropriate scale.
|
|
155
|
+
* Useful for ensuring dropped models appear at an appropriate scale.
|
|
156
|
+
*
|
|
157
|
+
* **Tip**: Use the handy `fitObjectIntoVolume` function (`import { fitObjectIntoVolume } from "@needle-tools/engine"`) for custom fitting needs.
|
|
158
|
+
*
|
|
163
159
|
* @default false
|
|
164
160
|
*/
|
|
165
161
|
@serializable()
|
|
@@ -180,6 +176,14 @@ export class DropListener extends Behaviour {
|
|
|
180
176
|
@serializable()
|
|
181
177
|
placeAtHitPosition: boolean = true;
|
|
182
178
|
|
|
179
|
+
/**
|
|
180
|
+
* When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
181
|
+
* When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
|
|
182
|
+
* @default false
|
|
183
|
+
*/
|
|
184
|
+
@serializable()
|
|
185
|
+
useNetworking: boolean = false;
|
|
186
|
+
|
|
183
187
|
/**
|
|
184
188
|
* Event list that gets invoked after a file has been successfully added to the scene.
|
|
185
189
|
* Receives {@link DropListenerOnDropArguments} containing the added object and related information.
|
|
@@ -193,6 +197,27 @@ export class DropListener extends Behaviour {
|
|
|
193
197
|
@serializable(EventList)
|
|
194
198
|
onDropped: EventList<DropListenerOnDropArguments> = new EventList();
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Loads a file from the given URL and adds it to the scene.
|
|
202
|
+
* @returns A promise that resolves to the loaded object or null if loading failed.
|
|
203
|
+
*/
|
|
204
|
+
loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }): Promise<Object3D | null> {
|
|
205
|
+
return this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, false);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Forgets all previously added objects.
|
|
210
|
+
* The droplistener will then not be able to remove previously added objects.
|
|
211
|
+
*/
|
|
212
|
+
forgetObjects() {
|
|
213
|
+
this.removePreviouslyAddedObjects(false);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// #region internals
|
|
220
|
+
|
|
196
221
|
/** @internal */
|
|
197
222
|
onEnable(): void {
|
|
198
223
|
this.context.renderer.domElement.addEventListener("dragover", this.onDrag);
|
|
@@ -208,21 +233,6 @@ export class DropListener extends Behaviour {
|
|
|
208
233
|
this.context.connection.stopListen("droplistener", this.onNetworkEvent);
|
|
209
234
|
}
|
|
210
235
|
|
|
211
|
-
/**
|
|
212
|
-
* Loads a file from the given URL and adds it to the scene.
|
|
213
|
-
*/
|
|
214
|
-
loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }) {
|
|
215
|
-
this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, true);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Forgets all previously added objects.
|
|
220
|
-
* The droplistener will then not be able to remove previously added objects.
|
|
221
|
-
*/
|
|
222
|
-
forgetObjects() {
|
|
223
|
-
this.removePreviouslyAddedObjects(false);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
236
|
/**
|
|
227
237
|
* Handles network events received from other clients containing information about dropped objects
|
|
228
238
|
* @param evt Network event data containing object information, position, and content URL
|
|
@@ -326,7 +336,7 @@ export class DropListener extends Behaviour {
|
|
|
326
336
|
}
|
|
327
337
|
}
|
|
328
338
|
if (files.length > 0) {
|
|
329
|
-
await this.
|
|
339
|
+
await this.addFromFiles(files, ctx);
|
|
330
340
|
}
|
|
331
341
|
}
|
|
332
342
|
|
|
@@ -359,6 +369,7 @@ export class DropListener extends Behaviour {
|
|
|
359
369
|
// Ignore dropped images
|
|
360
370
|
const lowercaseUrl = url.toLowerCase();
|
|
361
371
|
if (lowercaseUrl.endsWith(".hdr") || lowercaseUrl.endsWith(".hdri") || lowercaseUrl.endsWith(".exr") || lowercaseUrl.endsWith(".png") || lowercaseUrl.endsWith(".jpg") || lowercaseUrl.endsWith(".jpeg")) {
|
|
372
|
+
console.warn(`Fileformat is not supported: ${lowercaseUrl}`);
|
|
362
373
|
return null;
|
|
363
374
|
}
|
|
364
375
|
|
|
@@ -374,7 +385,7 @@ export class DropListener extends Behaviour {
|
|
|
374
385
|
});
|
|
375
386
|
if (res && this._addedObjects.length <= 0) {
|
|
376
387
|
ctx.url = url;
|
|
377
|
-
const obj = this.
|
|
388
|
+
const obj = this.onObjectLoaded(res, ctx, isRemote);
|
|
378
389
|
return obj;
|
|
379
390
|
}
|
|
380
391
|
}
|
|
@@ -393,7 +404,7 @@ export class DropListener extends Behaviour {
|
|
|
393
404
|
* @param fileList Array of dropped files
|
|
394
405
|
* @param ctx Context information about where the drop occurred
|
|
395
406
|
*/
|
|
396
|
-
private async
|
|
407
|
+
private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
|
|
397
408
|
if (debug) console.log("Add files", fileList)
|
|
398
409
|
if (!Array.isArray(fileList)) return;
|
|
399
410
|
if (!fileList.length) return;
|
|
@@ -427,7 +438,7 @@ export class DropListener extends Behaviour {
|
|
|
427
438
|
if (res) {
|
|
428
439
|
this.dispatchEvent(new CustomEvent(DropListenerEvents.FileDropped, { detail: file }));
|
|
429
440
|
ctx.file = file;
|
|
430
|
-
const obj = this.
|
|
441
|
+
const obj = this.onObjectLoaded(res, ctx, false);
|
|
431
442
|
|
|
432
443
|
// handle uploading the dropped object and networking the event
|
|
433
444
|
if (obj && this.context.connection.isConnected && this.useNetworking) {
|
|
@@ -478,7 +489,7 @@ export class DropListener extends Behaviour {
|
|
|
478
489
|
* @param isRemote Whether this object was shared from a remote client
|
|
479
490
|
* @returns The added object or null if adding failed
|
|
480
491
|
*/
|
|
481
|
-
private
|
|
492
|
+
private onObjectLoaded(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
|
|
482
493
|
|
|
483
494
|
const { model, contentMD5 } = data;
|
|
484
495
|
|
|
@@ -522,9 +533,7 @@ export class DropListener extends Behaviour {
|
|
|
522
533
|
}
|
|
523
534
|
}
|
|
524
535
|
|
|
525
|
-
AnimationUtils.
|
|
526
|
-
createAnimationComponent: obj => addComponent(obj, Animation)
|
|
527
|
-
});
|
|
536
|
+
AnimationUtils.autoplayAnimations(model);
|
|
528
537
|
|
|
529
538
|
const evt = new DropListenerAddedEvent({
|
|
530
539
|
sender: this,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Object3D } from "three";
|
|
1
|
+
import { Object3D, Vector3 } from "three";
|
|
2
2
|
|
|
3
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
4
4
|
import { Behaviour } from "./Component.js";
|
|
@@ -23,4 +23,12 @@ export class LookAtConstraint extends Behaviour {
|
|
|
23
23
|
*/
|
|
24
24
|
@serializable(Object3D)
|
|
25
25
|
sources: Object3D[] = [];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the position of the constraint.
|
|
29
|
+
*/
|
|
30
|
+
setConstraintPosition(worldPosition: Vector3) {
|
|
31
|
+
const source = this.sources[0];
|
|
32
|
+
if (source) source.worldPosition = worldPosition;
|
|
33
|
+
}
|
|
26
34
|
}
|
|
@@ -649,12 +649,12 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
649
649
|
if (this._lookTargetLerpActive) {
|
|
650
650
|
this._lookTargetLerp01 += this.context.time.deltaTime / this._lookTargetLerpDuration;
|
|
651
651
|
if (this._lookTargetLerp01 >= 1) {
|
|
652
|
-
this.
|
|
652
|
+
this.lerpLookTarget(this._lookTargetEndPosition, this._lookTargetEndPosition, 1);
|
|
653
653
|
this._lookTargetLerpActive = false;
|
|
654
654
|
this.dispatchEvent(new CameraTargetReachedEvent(this, "lookat"));
|
|
655
655
|
} else {
|
|
656
656
|
const t = Mathf.easeInOutCubic(this._lookTargetLerp01);
|
|
657
|
-
this.
|
|
657
|
+
this.lerpLookTarget(this._lookTargetStartPosition, this._lookTargetEndPosition, t);
|
|
658
658
|
}
|
|
659
659
|
}
|
|
660
660
|
|
|
@@ -729,7 +729,9 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
729
729
|
|
|
730
730
|
// this._controls.zoomToCursor = this.zoomToCursor;
|
|
731
731
|
if (!this.context.isInXR) {
|
|
732
|
-
if (!freeCam && this.lookAtConstraint?.locked
|
|
732
|
+
if (!freeCam && this.lookAtConstraint?.locked && !this._lookTargetLerpActive) {
|
|
733
|
+
this.setLookTargetFromConstraint(0, this.lookAtConstraint01);
|
|
734
|
+
}
|
|
733
735
|
this._controls.update(this.context.time.deltaTime);
|
|
734
736
|
|
|
735
737
|
if (debug) {
|
|
@@ -910,7 +912,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
910
912
|
}
|
|
911
913
|
|
|
912
914
|
if (immediateOrDuration === true) {
|
|
913
|
-
this.
|
|
915
|
+
this.lerpLookTarget(this._lookTargetEndPosition, this._lookTargetEndPosition, 1);
|
|
914
916
|
}
|
|
915
917
|
else {
|
|
916
918
|
this._lookTargetLerpActive = true;
|
|
@@ -941,20 +943,18 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
941
943
|
const target = sources[index];
|
|
942
944
|
if (target) {
|
|
943
945
|
target.getWorldPosition(this._lookTargetEndPosition);
|
|
944
|
-
this.lerpLookTarget(this._lookTargetEndPosition, t);
|
|
946
|
+
this.lerpLookTarget(this._controls.target, this._lookTargetEndPosition, t);
|
|
945
947
|
return true;
|
|
946
948
|
}
|
|
947
949
|
}
|
|
948
950
|
return false;
|
|
949
951
|
}
|
|
950
952
|
|
|
951
|
-
|
|
952
|
-
public lerpTarget(position: Vector3, delta: number) { return this.lerpLookTarget(position, delta); }
|
|
953
|
-
|
|
954
|
-
private lerpLookTarget(position: Vector3, delta: number) {
|
|
953
|
+
private lerpLookTarget(start: Vector3, position: Vector3, t: number) {
|
|
955
954
|
if (!this._controls) return;
|
|
956
|
-
if (
|
|
957
|
-
else this._controls.target.
|
|
955
|
+
if (t >= 1) this._controls.target.copy(position);
|
|
956
|
+
else this._controls.target.lerpVectors(start, position, t);
|
|
957
|
+
if (this.lookAtConstraint) this.lookAtConstraint.setConstraintPosition(this._controls.target);
|
|
958
958
|
}
|
|
959
959
|
|
|
960
960
|
private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
|
|
@@ -1063,7 +1063,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1063
1063
|
return;
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
-
const verticalFov =
|
|
1066
|
+
const verticalFov = fov;
|
|
1067
1067
|
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov * Math.PI / 360 / 2) * camera.aspect) / Math.PI * 360;
|
|
1068
1068
|
const fitHeightDistance = size.y / (2 * Math.atan(Math.PI * verticalFov / 360));
|
|
1069
1069
|
const fitWidthDistance = size.x / (2 * Math.atan(Math.PI * horizontalFov / 360));
|
|
@@ -1161,16 +1161,19 @@ declare type FitCameraOptions = {
|
|
|
1161
1161
|
* The objects to fit the camera to. If not provided the scene children will be used
|
|
1162
1162
|
*/
|
|
1163
1163
|
objects?: Object3D[] | Object3D;
|
|
1164
|
-
/** Fit offset: A factor to multiply the distance to the objects by
|
|
1165
|
-
* @default 1.1
|
|
1166
|
-
*/
|
|
1167
|
-
fitOffset?: number,
|
|
1168
1164
|
/** If true the camera will move immediately to the new position, otherwise it will lerp
|
|
1169
1165
|
* @default false
|
|
1170
1166
|
*/
|
|
1171
1167
|
immediate?: boolean,
|
|
1168
|
+
|
|
1172
1169
|
/** If set to "y" the camera will be centered in the y axis */
|
|
1173
1170
|
centerCamera?: "none" | "y",
|
|
1174
1171
|
cameraNearFar?: "keep" | "auto",
|
|
1172
|
+
|
|
1175
1173
|
fov?: number,
|
|
1174
|
+
|
|
1175
|
+
/** Fit offset: A factor to multiply the distance to the objects by
|
|
1176
|
+
* @default 1.1
|
|
1177
|
+
*/
|
|
1178
|
+
fitOffset?: number,
|
|
1176
1179
|
}
|
|
@@ -217,9 +217,9 @@ export class RemoteSkybox extends Behaviour {
|
|
|
217
217
|
if (debug) console.warn("RemoteSkybox: Failed to load texture from url", url);
|
|
218
218
|
return false;
|
|
219
219
|
}
|
|
220
|
-
// Check if we're
|
|
221
|
-
if (!this.enabled) {
|
|
222
|
-
if (debug) console.warn("RemoteSkybox: Component is
|
|
220
|
+
// Check if we're not disabled or destroyed
|
|
221
|
+
if (!this.enabled || this.destroyed) {
|
|
222
|
+
if (debug) console.warn("RemoteSkybox: Component is disabled or destroyed");
|
|
223
223
|
return false;
|
|
224
224
|
}
|
|
225
225
|
// Check if the url has changed while loading
|
|
@@ -239,7 +239,6 @@ export class RemoteSkybox extends Behaviour {
|
|
|
239
239
|
const envMap = this._prevLoadedEnvironment;
|
|
240
240
|
if (!envMap) return;
|
|
241
241
|
|
|
242
|
-
|
|
243
242
|
if ((envMap instanceof CubeTexture || envMap instanceof CompressedCubeTexture) || envMap.mapping == CubeUVReflectionMapping) {
|
|
244
243
|
// Nothing to do
|
|
245
244
|
}
|
|
@@ -248,6 +247,11 @@ export class RemoteSkybox extends Behaviour {
|
|
|
248
247
|
envMap.needsUpdate = true;
|
|
249
248
|
}
|
|
250
249
|
|
|
250
|
+
if(this.destroyed) return;
|
|
251
|
+
if(!this.context) {
|
|
252
|
+
console.warn("RemoteSkybox: Context is not available - can not apply skybox.");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
251
255
|
|
|
252
256
|
// capture state
|
|
253
257
|
if (this.context.scene.background !== envMap)
|