@needle-tools/engine 4.8.7 → 4.8.8-next.4b53212
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 +13 -0
- package/README.md +55 -42
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-B0qaChJt.js → needle-engine.bundle-9ZWPsgka.js} +7089 -7009
- package/dist/{needle-engine.bundle-CZBThBDy.min.js → needle-engine.bundle-B-wxYHwV.min.js} +144 -144
- package/dist/{needle-engine.bundle-lBmpWgFp.umd.cjs → needle-engine.bundle-CxUl1z77.umd.cjs} +147 -147
- package/dist/needle-engine.d.ts +7 -0
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_addressables.d.ts +12 -12
- package/lib/engine/engine_addressables.js +32 -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 +18 -12
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_assetdatabase.js +6 -6
- package/lib/engine/engine_assetdatabase.js.map +1 -1
- package/lib/engine/engine_camera.d.ts +23 -3
- package/lib/engine/engine_camera.js +34 -2
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +15 -0
- package/lib/engine/engine_context.js +33 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.d.ts +3 -3
- package/lib/engine/engine_create_objects.js +5 -4
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gameobject.js +2 -2
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +11 -11
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
- package/lib/engine/engine_loaders.callbacks.js +1 -0
- package/lib/engine/engine_loaders.callbacks.js.map +1 -1
- package/lib/engine/engine_loaders.js +15 -11
- package/lib/engine/engine_loaders.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
- package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
- package/lib/engine/js-extensions/Object3D.d.ts +1 -1
- package/lib/engine/js-extensions/Vector.d.ts +5 -0
- package/lib/engine/js-extensions/Vector.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.attributes.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
- package/lib/engine/webcomponents/needle-engine.js +19 -21
- package/lib/engine/webcomponents/needle-engine.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/Camera.d.ts +2 -0
- package/lib/engine-components/Camera.js +5 -1
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +12 -2
- package/lib/engine-components/ContactShadows.js +24 -4
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +21 -15
- package/lib/engine-components/DropListener.js +38 -34
- 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/NestedGltf.d.ts +9 -4
- package/lib/engine-components/NestedGltf.js +32 -26
- package/lib/engine-components/NestedGltf.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +30 -9
- package/lib/engine-components/OrbitControls.js +55 -18
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Renderer.js +2 -1
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/Skybox.js +8 -9
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/export/usdz/Extension.d.ts +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +7 -0
- package/lib/engine-components/export/usdz/USDZExporter.js +8 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +4 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js +117 -81
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +3 -3
- package/plugins/common/files.js +6 -3
- package/plugins/vite/alias.js +45 -23
- package/plugins/vite/editor-connection.js +4 -4
- package/src/engine/engine_addressables.ts +46 -33
- package/src/engine/engine_animation.ts +20 -12
- package/src/engine/engine_assetdatabase.ts +7 -7
- package/src/engine/engine_camera.ts +54 -3
- package/src/engine/engine_context.ts +39 -1
- package/src/engine/engine_create_objects.ts +8 -7
- package/src/engine/engine_gameobject.ts +2 -2
- package/src/engine/engine_gltf_builtin_components.ts +12 -11
- package/src/engine/engine_loaders.callbacks.ts +1 -0
- package/src/engine/engine_loaders.ts +18 -13
- package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
- package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
- package/src/engine/js-extensions/Vector.ts +6 -0
- package/src/engine/webcomponents/needle-engine.attributes.ts +2 -0
- package/src/engine/webcomponents/needle-engine.ts +21 -21
- package/src/engine-components/Animation.ts +1 -1
- package/src/engine-components/AnimationUtilsAutoplay.ts +1 -6
- package/src/engine-components/Camera.ts +7 -1
- package/src/engine-components/ContactShadows.ts +27 -6
- package/src/engine-components/DropListener.ts +44 -34
- package/src/engine-components/LookAtConstraint.ts +9 -1
- package/src/engine-components/NestedGltf.ts +33 -24
- package/src/engine-components/OrbitControls.ts +80 -28
- package/src/engine-components/Renderer.ts +2 -1
- package/src/engine-components/Skybox.ts +9 -10
- package/src/engine-components/api.ts +2 -1
- package/src/engine-components/export/usdz/Extension.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/USDZExporter.ts +21 -12
- package/src/engine-components/webxr/WebXRImageTracking.ts +138 -90
|
@@ -8,7 +8,7 @@ import { BlobStorage } from "./engine_networking_blob.js";
|
|
|
8
8
|
import { registerPrefabProvider, syncInstantiate, SyncInstantiateOptions } from "./engine_networking_instantiate.js";
|
|
9
9
|
import { SerializationContext, TypeSerializer } from "./engine_serialization_core.js";
|
|
10
10
|
import { Context } from "./engine_setup.js";
|
|
11
|
-
import type { GLTF, IComponent, IGameObject, SourceIdentifier } from "./engine_types.js";
|
|
11
|
+
import type { GLTF, IComponent, IGameObject, Model, SourceIdentifier } from "./engine_types.js";
|
|
12
12
|
|
|
13
13
|
const debug = getParam("debugaddressables");
|
|
14
14
|
|
|
@@ -138,21 +138,35 @@ export class AssetReference {
|
|
|
138
138
|
return ref;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
private static currentlyInstantiating: Map<string, number> = new Map<string, number>();
|
|
142
141
|
|
|
143
|
-
/** @returns true if this is an AssetReference instance */
|
|
144
|
-
get isAssetReference() { return true; }
|
|
145
142
|
|
|
146
|
-
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
readonly isAssetReference = true;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* This is the loaded asset root object. If the asset is a glb/gltf file this will be the {@link three#Scene} object.
|
|
150
|
+
*/
|
|
151
|
+
get rawAsset(): any { return this._rawAsset; }
|
|
152
|
+
|
|
153
|
+
/** The loaded asset root
|
|
154
|
+
*/
|
|
147
155
|
get asset(): Object3D | null {
|
|
148
|
-
return this._glbRoot ?? this.
|
|
156
|
+
return this._glbRoot ?? (this._rawAsset?.scene || null);
|
|
149
157
|
}
|
|
150
158
|
|
|
151
|
-
protected set asset(val:
|
|
152
|
-
|
|
159
|
+
protected set asset(val: Object3D | null) {
|
|
160
|
+
if (val) {
|
|
161
|
+
this._rawAsset = {
|
|
162
|
+
animations: val.animations,
|
|
163
|
+
scene: val,
|
|
164
|
+
scenes: [val],
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else this._rawAsset = null;
|
|
153
168
|
}
|
|
154
169
|
|
|
155
|
-
private _loading?: PromiseLike<any>;
|
|
156
170
|
|
|
157
171
|
/** The url of the loaded asset (or the asset to be loaded)
|
|
158
172
|
* @deprecated use url */
|
|
@@ -177,12 +191,7 @@ export class AssetReference {
|
|
|
177
191
|
(this._url.startsWith("http") || this._url.startsWith("blob:") || this._url.startsWith("www.") || this._url.includes("/"));
|
|
178
192
|
}
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
* This is the loaded asset root object. If the asset is a glb/gltf file this will be the {@link three#Scene} object.
|
|
182
|
-
*/
|
|
183
|
-
get rawAsset(): any { return this._asset; }
|
|
184
|
-
|
|
185
|
-
private _asset: any;
|
|
194
|
+
private _rawAsset: Model | null = null;
|
|
186
195
|
private _glbRoot?: Object3D | null;
|
|
187
196
|
private _url: string;
|
|
188
197
|
private _urlName: string;
|
|
@@ -207,7 +216,7 @@ export class AssetReference {
|
|
|
207
216
|
else {
|
|
208
217
|
this._urlName = uri;
|
|
209
218
|
}
|
|
210
|
-
|
|
219
|
+
|
|
211
220
|
if (asset !== null) this.asset = asset;
|
|
212
221
|
registerPrefabProvider(this._url, this.onResolvePrefab.bind(this));
|
|
213
222
|
}
|
|
@@ -226,6 +235,8 @@ export class AssetReference {
|
|
|
226
235
|
return !this.asset || (this.asset as any).__destroyed === true || isDestroyed(this.asset) === true;
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
private _loadingPromise: Promise<Model | null | undefined> | null = null;
|
|
239
|
+
|
|
229
240
|
/**
|
|
230
241
|
* @returns `true` if the asset has been loaded (via preload) or if it exists already (assigned to `asset`) */
|
|
231
242
|
isLoaded() { return this._rawBinary || this.asset !== undefined }
|
|
@@ -242,7 +253,7 @@ export class AssetReference {
|
|
|
242
253
|
this.asset = null;
|
|
243
254
|
this._rawBinary = undefined;
|
|
244
255
|
this._glbRoot = null;
|
|
245
|
-
this.
|
|
256
|
+
this._loadingPromise = null;
|
|
246
257
|
if (Context.Current) {
|
|
247
258
|
Context.Current.addressables.unregisterAssetReference(this);
|
|
248
259
|
}
|
|
@@ -267,16 +278,14 @@ export class AssetReference {
|
|
|
267
278
|
/** Loads the asset and creates one instance (assigned to `asset`)
|
|
268
279
|
* @returns the loaded asset
|
|
269
280
|
*/
|
|
270
|
-
async loadAssetAsync(prog?: ProgressCallback | null) {
|
|
271
|
-
if (debug)
|
|
272
|
-
console.log("loadAssetAsync", this.url);
|
|
281
|
+
async loadAssetAsync(prog?: ProgressCallback | null): Promise<Object3D | null> {
|
|
282
|
+
if (debug) console.log("[AssetReference] loadAssetAsync", this.url);
|
|
273
283
|
if (!this.mustLoad) return this.asset;
|
|
274
284
|
|
|
275
285
|
if (prog) this._progressListeners.push(prog);
|
|
276
|
-
|
|
277
|
-
if (this.
|
|
278
|
-
|
|
279
|
-
return this._loading.then(_ => this.asset);
|
|
286
|
+
|
|
287
|
+
if (this._loadingPromise !== null) {
|
|
288
|
+
return this._loadingPromise.then(_ => this.asset);
|
|
280
289
|
}
|
|
281
290
|
const context = Context.Current;
|
|
282
291
|
// TODO: technically we shouldnt call awake only when the object is added to a scene
|
|
@@ -285,31 +294,30 @@ export class AssetReference {
|
|
|
285
294
|
// console.log("START LOADING");
|
|
286
295
|
if (this._rawBinary) {
|
|
287
296
|
if (!(this._rawBinary instanceof ArrayBuffer)) {
|
|
288
|
-
console.error("Failed loading
|
|
297
|
+
console.error("[AssetReference] Failed loading – Invalid data. Must be of type ArrayBuffer. " + (typeof this._rawBinary));
|
|
289
298
|
return null;
|
|
290
299
|
}
|
|
291
|
-
this.
|
|
300
|
+
this._loadingPromise = getLoader().parseSync(context, this._rawBinary, this.url, null);
|
|
292
301
|
this.raiseProgressEvent(new ProgressEvent("progress", { loaded: this._rawBinary.byteLength, total: this._rawBinary.byteLength }));
|
|
293
302
|
}
|
|
294
303
|
else {
|
|
295
304
|
if (debug) console.log("Load async", this.url);
|
|
296
|
-
this.
|
|
305
|
+
this._loadingPromise = getLoader().loadSync(context, this.url, this.url, null, prog => {
|
|
297
306
|
this.raiseProgressEvent(prog);
|
|
298
307
|
});
|
|
299
308
|
}
|
|
300
|
-
|
|
309
|
+
this._loadingPromise.finally(() => this._loadingPromise = null);
|
|
310
|
+
const res = await this._loadingPromise;
|
|
301
311
|
// clear all progress listeners after download has finished
|
|
302
312
|
this._progressListeners.length = 0;
|
|
303
313
|
this._glbRoot = this.tryGetActualGameObjectRoot(res);
|
|
304
|
-
this._loading = undefined;
|
|
305
314
|
if (res) {
|
|
306
315
|
// Make sure the loaded roots all have a reference to this AssetReference
|
|
307
316
|
// that was originally loading it.
|
|
308
317
|
// We need this when the loaded asset is being disposed
|
|
309
318
|
// TODO: we have to prevent disposing resources that are still in use
|
|
310
319
|
res[$assetReference] = this;
|
|
311
|
-
if (this._glbRoot)
|
|
312
|
-
this._glbRoot[$assetReference] = this;
|
|
320
|
+
if (this._glbRoot) this._glbRoot[$assetReference] = this;
|
|
313
321
|
if (this.asset) this.asset[$assetReference] = this;
|
|
314
322
|
|
|
315
323
|
// we need to handle the pre_setup callsbacks before instantiating
|
|
@@ -317,7 +325,7 @@ export class AssetReference {
|
|
|
317
325
|
processNewScripts(context);
|
|
318
326
|
|
|
319
327
|
if (res.scene !== undefined) {
|
|
320
|
-
this.
|
|
328
|
+
this._rawAsset = res;
|
|
321
329
|
}
|
|
322
330
|
return this.asset;
|
|
323
331
|
}
|
|
@@ -352,6 +360,9 @@ export class AssetReference {
|
|
|
352
360
|
}
|
|
353
361
|
}
|
|
354
362
|
|
|
363
|
+
|
|
364
|
+
private static readonly currentlyInstantiating: Map<string, number> = new Map<string, number>();
|
|
365
|
+
|
|
355
366
|
private async onInstantiate(opts?: Object3D | IInstantiateOptions | SyncInstantiateOptions | null, networked: boolean = false, saveOnServer?: boolean): Promise<Object3D | null> {
|
|
356
367
|
const context = Context.Current;
|
|
357
368
|
|
|
@@ -429,6 +440,8 @@ export class AssetReference {
|
|
|
429
440
|
const scene = asset.scene as Group;
|
|
430
441
|
if (scene.isGroup && scene.children.length === 1 && scene.children[0].name + "glb" === scene.name) {
|
|
431
442
|
const root = scene.children[0];
|
|
443
|
+
// HACK we want to keep the animations on the root object. Please keep in sync with engine_loader. This is so autoplay animations works (e.g. with AnimationUtils.autoplayAnimations where we get the animations from the root object). One case where an additioanl root exists is the SOC old mcdonald scene
|
|
444
|
+
root.animations = scene.animations;
|
|
432
445
|
return root;
|
|
433
446
|
}
|
|
434
447
|
// ok the scene is the scene, just use that then
|
|
@@ -544,7 +557,7 @@ export class ImageReference {
|
|
|
544
557
|
console.error("Can not load texture without url");
|
|
545
558
|
return failedTexturePromise;
|
|
546
559
|
}
|
|
547
|
-
|
|
560
|
+
|
|
548
561
|
if (!this.loader) this.loader = new TextureLoader();
|
|
549
562
|
this.loader.setCrossOrigin("anonymous");
|
|
550
563
|
return this.loader.loadAsync(this.url).then(res => {
|
|
@@ -2,6 +2,7 @@ import { AnimationAction, AnimationClip, AnimationMixer, Object3D, PropertyBindi
|
|
|
2
2
|
|
|
3
3
|
import type { Context } from "./engine_context.js";
|
|
4
4
|
import { GLTF, IAnimationComponent, Model } from "./engine_types.js";
|
|
5
|
+
import { TypeStore } from "./engine_typestore.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Registry for animation related data. Use {@link registerAnimationMixer} to register an animation mixer instance.
|
|
@@ -88,12 +89,15 @@ export class AnimationUtils {
|
|
|
88
89
|
* This method will look for objects in the scene that have animations and assign them to the correct objects.
|
|
89
90
|
* @param file The GLTF file to assign the animations from
|
|
90
91
|
*/
|
|
91
|
-
static
|
|
92
|
+
static autoplayAnimations(file: Object3D | Pick<Model, "animations" | "scene">): Array<IAnimationComponent> | null {
|
|
92
93
|
if (!file || !file.animations) {
|
|
93
94
|
console.debug("No animations found in file");
|
|
94
|
-
return;
|
|
95
|
+
return null;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
const scene = "scene" in file ? file.scene : file as Object3D;
|
|
99
|
+
const animationComponents = new Array<IAnimationComponent>();
|
|
100
|
+
|
|
97
101
|
for (let i = 0; i < file.animations.length; i++) {
|
|
98
102
|
const animation = file.animations[i];
|
|
99
103
|
if (!animation.tracks || animation.tracks.length <= 0) {
|
|
@@ -103,12 +107,12 @@ export class AnimationUtils {
|
|
|
103
107
|
for (const t in animation.tracks) {
|
|
104
108
|
const track = animation.tracks[t];
|
|
105
109
|
const parsedPath = PropertyBinding.parseTrackName(track.name);
|
|
106
|
-
let obj = PropertyBinding.findNode(
|
|
110
|
+
let obj = PropertyBinding.findNode(scene, parsedPath.nodeName);
|
|
107
111
|
if (!obj) {
|
|
108
112
|
const objectName = track["__objectName"] ?? track.name.substring(0, track.name.indexOf("."));
|
|
109
113
|
// let obj = gltf.scene.getObjectByName(objectName);
|
|
110
114
|
// this finds unnamed objects that still have tracks targeting them
|
|
111
|
-
obj =
|
|
115
|
+
obj = scene.getObjectByProperty('uuid', objectName);
|
|
112
116
|
|
|
113
117
|
if (!obj) {
|
|
114
118
|
// console.warn("could not find " + objectName, animation, gltf.scene);
|
|
@@ -116,31 +120,35 @@ export class AnimationUtils {
|
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
let animationComponent =
|
|
123
|
+
let animationComponent = findAnimationComponentInParent(obj) || findAnimationComponentInParent(scene);
|
|
120
124
|
if (!animationComponent) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
const anim = TypeStore.get("Animation");
|
|
126
|
+
animationComponent = scene.addComponent(anim);
|
|
127
|
+
if (!animationComponent) {
|
|
128
|
+
console.error("Failed creating Animation component: No 'Animation' component found in TypeStore");
|
|
129
|
+
break;
|
|
124
130
|
}
|
|
125
|
-
animationComponent = opts.createAnimationComponent(file.scene, animation)
|
|
126
131
|
}
|
|
132
|
+
animationComponents.push(animationComponent);
|
|
127
133
|
if (animationComponent.addClip) {
|
|
128
134
|
animationComponent.addClip(animation);
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
|
-
|
|
138
|
+
return animationComponents;
|
|
139
|
+
|
|
140
|
+
function findAnimationComponentInParent(obj): IAnimationComponent | null {
|
|
133
141
|
if (!obj) return null;
|
|
134
142
|
const components = obj.userData?.components;
|
|
135
143
|
if (components && components.length > 0) {
|
|
136
144
|
for (let i = 0; i < components.length; i++) {
|
|
137
145
|
const component = components[i] as IAnimationComponent;
|
|
138
146
|
if (component.isAnimationComponent === true) {
|
|
139
|
-
return
|
|
147
|
+
return component;
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
|
-
return
|
|
151
|
+
return findAnimationComponentInParent(obj.parent);
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
}
|
|
@@ -61,7 +61,7 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if(typeof obj === "object") {
|
|
64
|
+
if (typeof obj === "object") {
|
|
65
65
|
obj[$disposed] = true;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -79,8 +79,8 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
79
79
|
disposeObjectResources(obj.bindMatrixInverse);
|
|
80
80
|
disposeObjectResources(obj.customDepthMaterial);
|
|
81
81
|
disposeObjectResources(obj.customDistanceMaterial);
|
|
82
|
-
obj.geometry =
|
|
83
|
-
obj.material =
|
|
82
|
+
obj.geometry = {};
|
|
83
|
+
obj.material = {};
|
|
84
84
|
obj.visible = false;
|
|
85
85
|
}
|
|
86
86
|
else if (obj instanceof Mesh) {
|
|
@@ -88,8 +88,8 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
88
88
|
disposeObjectResources(obj.material);
|
|
89
89
|
disposeObjectResources(obj.customDepthMaterial);
|
|
90
90
|
disposeObjectResources(obj.customDistanceMaterial);
|
|
91
|
-
obj.geometry =
|
|
92
|
-
obj.material =
|
|
91
|
+
obj.geometry = {};
|
|
92
|
+
obj.material = {};
|
|
93
93
|
obj.visible = false;
|
|
94
94
|
}
|
|
95
95
|
else if (obj instanceof BufferGeometry) {
|
|
@@ -175,8 +175,8 @@ function free(obj: any) {
|
|
|
175
175
|
|
|
176
176
|
export function __internalNotifyObjectDestroyed(obj: Object3D) {
|
|
177
177
|
if (obj instanceof Mesh || obj instanceof SkinnedMesh) {
|
|
178
|
-
obj.material =
|
|
179
|
-
obj.geometry =
|
|
178
|
+
obj.material = {};
|
|
179
|
+
obj.geometry = {};
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Camera, Object3D } from "three";
|
|
1
|
+
import { Camera, HemisphereLightHelper, Object3D, PerspectiveCamera, Vector2, WebGLRenderer } from "three";
|
|
2
2
|
|
|
3
|
+
import { Mathf } from "./engine_math.js";
|
|
3
4
|
import type { ICameraController } from "./engine_types.js";
|
|
4
5
|
|
|
5
6
|
|
|
@@ -24,7 +25,11 @@ export function setCameraController(cam: Camera, cameraController: ICameraContro
|
|
|
24
25
|
|
|
25
26
|
const autofit = "needle:autofit";
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Used by e.g. getBoundingBox via ContactShadows or OrbitControls when fitting the camera or shadow planes. Objects can be marked to be excluded from bounding box calculations via `setAutoFitEnabled(obj, <bool>)`
|
|
30
|
+
* @see setAutoFitEnabled
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
28
33
|
export function useForAutoFit(obj: Object3D): boolean {
|
|
29
34
|
// if autofit is not defined we assume it may be included
|
|
30
35
|
if (obj[autofit] === undefined) return true;
|
|
@@ -33,8 +38,54 @@ export function useForAutoFit(obj: Object3D): boolean {
|
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
|
-
* Enable or disable autofitting for the given object
|
|
41
|
+
* Enable or disable autofitting for the given object. Objects that are 'disabled' will be excluded in getBoundingBox calculations.
|
|
42
|
+
* This is used by ContactShadows or OrbitControls when fitting the shadow plane or camera to the given objects or scene.
|
|
43
|
+
* @see useForAutoFit
|
|
37
44
|
*/
|
|
38
45
|
export function setAutoFitEnabled(obj: Object3D, enabled: boolean): void {
|
|
39
46
|
obj[autofit] = enabled;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export type FocusRectSettings = {
|
|
52
|
+
/** Lower values will result in faster alignment with the rect (value ~= seconds to reach target)
|
|
53
|
+
* Minimum value is 0.
|
|
54
|
+
*/
|
|
55
|
+
damping: number
|
|
56
|
+
}
|
|
57
|
+
export type FocusRect = DOMRect | Element | { x: number, y: number, width: number, height: number };
|
|
58
|
+
|
|
59
|
+
let rendererRect: DOMRect | undefined = undefined;
|
|
60
|
+
const overlapRect = { x: 0, y: 0, width: 0, height: 0 };
|
|
61
|
+
|
|
62
|
+
/** Used internally by the Needle Engine context via 'setFocusRect(<rect>)' */
|
|
63
|
+
export function updateCameraFocusRect(focusRect: FocusRect, dt: number, camera: PerspectiveCamera, renderer: WebGLRenderer) {
|
|
64
|
+
|
|
65
|
+
if (focusRect instanceof Element) {
|
|
66
|
+
focusRect = focusRect.getBoundingClientRect();
|
|
67
|
+
}
|
|
68
|
+
rendererRect = renderer.domElement.getBoundingClientRect();
|
|
69
|
+
|
|
70
|
+
const rect = overlapRect;
|
|
71
|
+
rect.x = focusRect.x;
|
|
72
|
+
rect.y = focusRect.y;
|
|
73
|
+
rect.width = focusRect.width;
|
|
74
|
+
rect.height = focusRect.height;
|
|
75
|
+
|
|
76
|
+
rect.x -= rendererRect.x;
|
|
77
|
+
rect.y -= rendererRect.y;
|
|
78
|
+
|
|
79
|
+
const targetX = rect.width / -2 - (rect.x - (rendererRect.width / 2));
|
|
80
|
+
const targetY = rect.height / -2 - (rect.y - (rendererRect.height / 2));
|
|
81
|
+
|
|
82
|
+
const view = camera.view;
|
|
83
|
+
|
|
84
|
+
let offsetX = view?.offsetX || 0;
|
|
85
|
+
let offsetY = view?.offsetY || 0;
|
|
86
|
+
offsetX = Mathf.lerp(offsetX, targetX, dt);
|
|
87
|
+
offsetY = Mathf.lerp(offsetY, targetY, dt);
|
|
88
|
+
|
|
89
|
+
camera.setViewOffset(rendererRect.width, rendererRect.height, offsetX, offsetY, rendererRect.width, rendererRect.height);
|
|
90
|
+
camera.updateProjectionMatrix();
|
|
40
91
|
}
|
|
@@ -18,6 +18,7 @@ import { Addressables } from './engine_addressables.js';
|
|
|
18
18
|
import { AnimationsRegistry } from './engine_animation.js';
|
|
19
19
|
import { Application } from './engine_application.js';
|
|
20
20
|
import { AssetDatabase } from './engine_assetdatabase.js';
|
|
21
|
+
import { FocusRect, FocusRectSettings, updateCameraFocusRect } from './engine_camera.js';
|
|
21
22
|
import { VERSION } from './engine_constants.js';
|
|
22
23
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
|
23
24
|
import { WaitForPromise } from './engine_coroutine.js';
|
|
@@ -1371,6 +1372,35 @@ export class Context implements IContext {
|
|
|
1371
1372
|
this.internalUpdatePhysics(steps);
|
|
1372
1373
|
}
|
|
1373
1374
|
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Set a rect or dom element. The camera center will be moved to the center of the rect.
|
|
1379
|
+
* This is useful if you have Needle Engine embedded in a HTML layout and while you want the webgl background to fill e.g. the whole screen you want to move the camera center to free space.
|
|
1380
|
+
* For that you can simply pass in the rect or HMTL div that you want the camera to center on.
|
|
1381
|
+
* @param rect The focus rect or null to disable
|
|
1382
|
+
* @param settings Optional settings for the focus rect. These will override the `focusRectSettings` property
|
|
1383
|
+
*/
|
|
1384
|
+
public setCameraFocusRect(rect: FocusRect | null, settings?: Partial<FocusRectSettings>) {
|
|
1385
|
+
this._focusRect = rect;
|
|
1386
|
+
if (settings) {
|
|
1387
|
+
Object.assign(this.focusRectSettings, settings);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
get focusRect() { return this._focusRect; }
|
|
1391
|
+
/** Settings when a focus rect is set. Use `setCameraFocusRect(...)` to do so.
|
|
1392
|
+
* This can be used to offset the renderer center e.g. to a specific DOM element.
|
|
1393
|
+
*/
|
|
1394
|
+
readonly focusRectSettings: FocusRectSettings = {
|
|
1395
|
+
/** Controls how fast the rect is centered. Smaller values mean the rect is centered faster.
|
|
1396
|
+
* A minimum value of 0 means the rect is centered instantly.
|
|
1397
|
+
* @default .05
|
|
1398
|
+
*/
|
|
1399
|
+
damping: .05
|
|
1400
|
+
};
|
|
1401
|
+
private _focusRect: FocusRect | null = null;
|
|
1402
|
+
|
|
1403
|
+
|
|
1374
1404
|
private _lastTimestamp = 0;
|
|
1375
1405
|
private _accumulatedTime = 0;
|
|
1376
1406
|
private _dispatchReadyAfterFrame = false;
|
|
@@ -1421,7 +1451,7 @@ export class Context implements IContext {
|
|
|
1421
1451
|
|
|
1422
1452
|
Context.Current = this;
|
|
1423
1453
|
this.time.update();
|
|
1424
|
-
|
|
1454
|
+
|
|
1425
1455
|
if (debugframerate) console.log("FPS", (this.time.smoothedFps).toFixed(0));
|
|
1426
1456
|
|
|
1427
1457
|
|
|
@@ -1498,6 +1528,14 @@ export class Context implements IContext {
|
|
|
1498
1528
|
|
|
1499
1529
|
if (this.isVisibleToUser || this.runInBackground) {
|
|
1500
1530
|
|
|
1531
|
+
if (this._focusRect) {
|
|
1532
|
+
if (this.mainCamera instanceof PerspectiveCamera) {
|
|
1533
|
+
const settings = this.focusRectSettings;
|
|
1534
|
+
const dt = settings.damping > 0 ? this.time.deltaTime / settings.damping : 1;
|
|
1535
|
+
updateCameraFocusRect(this._focusRect, dt, this.mainCamera, this.renderer);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1501
1539
|
this._currentFrameEvent = FrameEvent.OnBeforeRender;
|
|
1502
1540
|
|
|
1503
1541
|
// should we move these callbacks in the regular three onBeforeRender events?
|
|
@@ -42,13 +42,13 @@ export type ObjectOptions = {
|
|
|
42
42
|
/**
|
|
43
43
|
* The position of the object in local space
|
|
44
44
|
*/
|
|
45
|
-
position?: Vec3 | [number, number, number],
|
|
45
|
+
position?: Partial<Vec3> | [number, number, number],
|
|
46
46
|
/** The rotation of the object in local space */
|
|
47
|
-
rotation?: Vec3 | [number, number, number],
|
|
47
|
+
rotation?: Partial<Vec3> | [number, number, number],
|
|
48
48
|
/**
|
|
49
49
|
* The scale of the object in local space
|
|
50
50
|
*/
|
|
51
|
-
scale?: Vec3 | number | [number, number, number],
|
|
51
|
+
scale?: Partial<Vec3> | number | [number, number, number],
|
|
52
52
|
/**
|
|
53
53
|
* If the object should receive shadows
|
|
54
54
|
* @default true
|
|
@@ -251,14 +251,15 @@ export class ObjectUtils {
|
|
|
251
251
|
if (opts?.position) {
|
|
252
252
|
if (Array.isArray(opts.position))
|
|
253
253
|
obj.position.set(opts.position[0], opts.position[1], opts.position[2]);
|
|
254
|
-
else
|
|
255
|
-
obj.position.set(opts.position.x, opts.position.y, opts.position.z);
|
|
254
|
+
else {
|
|
255
|
+
obj.position.set(opts.position.x || 0, opts.position.y || 0, opts.position.z || 0);
|
|
256
|
+
}
|
|
256
257
|
}
|
|
257
258
|
if (opts?.rotation) {
|
|
258
259
|
if (Array.isArray(opts.rotation))
|
|
259
260
|
obj.rotation.set(opts.rotation[0], opts.rotation[1], opts.rotation[2]);
|
|
260
261
|
else
|
|
261
|
-
obj.rotation.set(opts.rotation.x, opts.rotation.y, opts.rotation.z);
|
|
262
|
+
obj.rotation.set(opts.rotation.x || 0, opts.rotation.y || 0, opts.rotation.z || 0);
|
|
262
263
|
}
|
|
263
264
|
if (opts?.scale) {
|
|
264
265
|
if (typeof opts.scale === "number")
|
|
@@ -267,7 +268,7 @@ export class ObjectUtils {
|
|
|
267
268
|
obj.scale.set(opts.scale[0], opts.scale[1], opts.scale[2]);
|
|
268
269
|
}
|
|
269
270
|
else {
|
|
270
|
-
obj.scale.set(opts.scale.x, opts.scale.y, opts.scale.z);
|
|
271
|
+
obj.scale.set(opts.scale.x || 1, opts.scale.y || 1, opts.scale.z || 1);
|
|
271
272
|
|
|
272
273
|
}
|
|
273
274
|
}
|
|
@@ -166,9 +166,9 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
|
|
|
166
166
|
setDestroyed(obj, true);
|
|
167
167
|
if (dispose) {
|
|
168
168
|
disposeObjectResources(obj);
|
|
169
|
+
// This needs to be called after disposing because it removes the references to resources
|
|
170
|
+
__internalRemoveReferences(obj);
|
|
169
171
|
}
|
|
170
|
-
// This needs to be called after disposing because it removes the references to resources
|
|
171
|
-
__internalRemoveReferences(obj);
|
|
172
172
|
}
|
|
173
173
|
destroyed_objects.length = 0;
|
|
174
174
|
destroyed_components.length = 0;
|
|
@@ -72,13 +72,13 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
|
|
|
72
72
|
|
|
73
73
|
if (gltf.scenes) {
|
|
74
74
|
for (const scene of gltf.scenes) {
|
|
75
|
-
await onCreateBuiltinComponents(serializationContext, scene, deserializeQueue, lateResolve);
|
|
75
|
+
await onCreateBuiltinComponents(serializationContext, scene, deserializeQueue, lateResolve, 0);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
// TODO: when is the gltf here an object3d?
|
|
79
79
|
if (gltf.children) {
|
|
80
80
|
for (const ch of gltf.children) {
|
|
81
|
-
await onCreateBuiltinComponents(serializationContext, ch, deserializeQueue, lateResolve);
|
|
81
|
+
await onCreateBuiltinComponents(serializationContext, ch, deserializeQueue, lateResolve, 0);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -202,7 +202,7 @@ const unknownComponentsBuffer: Array<string> = [];
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
async function onCreateBuiltinComponents(context: SerializationContext, obj: Object3D,
|
|
205
|
-
deserialize: DeserializeData[], lateResolve: LateResolveCallback[]) {
|
|
205
|
+
deserialize: DeserializeData[], lateResolve: LateResolveCallback[], level: number) {
|
|
206
206
|
if (!obj) return;
|
|
207
207
|
|
|
208
208
|
// iterate injected data
|
|
@@ -267,20 +267,21 @@ async function onCreateBuiltinComponents(context: SerializationContext, obj: Obj
|
|
|
267
267
|
}
|
|
268
268
|
// console.debug("finished adding gltf builtin components", obj);
|
|
269
269
|
}
|
|
270
|
-
if (unknownComponentsBuffer.length > 0) {
|
|
271
|
-
const unknown = unknownComponentsBuffer.join(", ");
|
|
272
|
-
console.warn("unknown components: " + unknown);
|
|
273
|
-
unknownComponentsBuffer.length = 0;
|
|
274
|
-
if (isLocalNetwork())
|
|
275
|
-
showBalloonMessage(`<strong>Unknown components in scene</strong>:\n\n${unknown}\n\nThis could mean you forgot to add a npmdef to your ExportInfo\n<a href="https://engine.needle.tools/docs/project_structure.html#creating-and-installing-a-npmdef" target="_blank">documentation</a>`, LogType.Warn);
|
|
276
|
-
}
|
|
277
270
|
}
|
|
278
271
|
|
|
279
272
|
if (obj.children) {
|
|
280
273
|
for (const ch of obj.children) {
|
|
281
|
-
await onCreateBuiltinComponents(context, ch, deserialize, lateResolve);
|
|
274
|
+
await onCreateBuiltinComponents(context, ch, deserialize, lateResolve, level + 1);
|
|
282
275
|
}
|
|
283
276
|
}
|
|
277
|
+
|
|
278
|
+
if (unknownComponentsBuffer.length > 0 && level === 0) {
|
|
279
|
+
const unknown = unknownComponentsBuffer.join(", ");
|
|
280
|
+
console.warn(`Unknown components in scene: ${unknown}`);
|
|
281
|
+
unknownComponentsBuffer.length = 0;
|
|
282
|
+
if (isLocalNetwork())
|
|
283
|
+
showBalloonMessage(`<strong>Unknown components in scene</strong>:\n\n${unknown}\n\nThis could mean you forgot to add a npmdef to your ExportInfo\n<a href="https://engine.needle.tools/docs/project_structure.html#creating-and-installing-a-npmdef" target="_blank">documentation</a>`, LogType.Warn);
|
|
284
|
+
}
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
function handleDeserialization(data: DeserializeData, context: SerializationContext) {
|
|
@@ -98,6 +98,7 @@ export namespace NeedleEngineModelLoader {
|
|
|
98
98
|
* }
|
|
99
99
|
* return null;
|
|
100
100
|
* });
|
|
101
|
+
* ```
|
|
101
102
|
*/
|
|
102
103
|
export function onCreateCustomModelLoader(callback: CustomLoaderCallback, opts?: CustomLoaderOptions) {
|
|
103
104
|
const entry = { name: opts?.name, priority: opts?.priority ?? 0, callback };
|
|
@@ -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) => {
|
|
@@ -169,8 +169,11 @@ export class SceneLightSettings extends Behaviour {
|
|
|
169
169
|
this._hemisphereLightObj.removeFromParent();
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
if (this.sourceId)
|
|
173
|
-
this.context.
|
|
172
|
+
if (this.sourceId) {
|
|
173
|
+
if (!this.context.domElement.getAttribute("environment-image")) {
|
|
174
|
+
this.context.sceneLighting.internalEnableReflection(this.sourceId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
onDisable() {
|
|
@@ -70,7 +70,7 @@ export class NEEDLE_lightmaps implements GLTFLoaderPlugin {
|
|
|
70
70
|
console.log(entry);
|
|
71
71
|
let res: Promise<any> | null = null;
|
|
72
72
|
// Check if the pointer is a json pointer:
|
|
73
|
-
if (entry.pointer.startsWith("/textures/")) {
|
|
73
|
+
if (entry.pointer.startsWith("/textures/") || /** legacy support e.g. SOC */entry.pointer.startsWith("textures/")) {
|
|
74
74
|
if (debug) console.log("Load texture from gltf", entry.pointer);
|
|
75
75
|
res = resolveReferences(this.parser, entry.pointer).then(res => this.resolveTexture(entry, res));
|
|
76
76
|
}
|