@needle-tools/engine 4.8.7 → 4.8.8-next.5cb3196
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 +31 -23
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-lBmpWgFp.umd.cjs → needle-engine.bundle-D83YACrg.umd.cjs} +147 -147
- package/dist/{needle-engine.bundle-B0qaChJt.js → needle-engine.bundle-DJA-sz_-.js} +6955 -6904
- package/dist/{needle-engine.bundle-CZBThBDy.min.js → needle-engine.bundle-SWdTk441.min.js} +144 -144
- 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 +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_camera.d.ts +8 -1
- package/lib/engine/engine_camera.js +25 -0
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +9 -0
- package/lib/engine/engine_context.js +15 -0
- package/lib/engine/engine_context.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/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/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/OrbitControls.d.ts +30 -9
- package/lib/engine-components/OrbitControls.js +53 -14
- package/lib/engine-components/OrbitControls.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/vite/alias.js +45 -23
- package/plugins/vite/editor-connection.js +4 -4
- package/src/engine/engine_addressables.ts +44 -33
- package/src/engine/engine_animation.ts +17 -9
- package/src/engine/engine_camera.ts +40 -1
- package/src/engine/engine_context.ts +21 -1
- package/src/engine/engine_loaders.callbacks.ts +1 -0
- package/src/engine/engine_loaders.ts +18 -13
- 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/DropListener.ts +44 -34
- package/src/engine-components/LookAtConstraint.ts +9 -1
- package/src/engine-components/OrbitControls.ts +78 -22
- 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
|
@@ -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
|
}
|
|
@@ -388,12 +399,13 @@ export class DropListener extends Behaviour {
|
|
|
388
399
|
private _abort: AbortController | null = null;
|
|
389
400
|
|
|
390
401
|
/**
|
|
391
|
-
* Processes dropped files
|
|
392
|
-
*
|
|
402
|
+
* Processes dropped files and loads them as 3D models.
|
|
403
|
+
* When enabled, it also handles network drops (sending files between clients).
|
|
404
|
+
* Automatically handles cancelling previous uploads if new files are dropped.
|
|
393
405
|
* @param fileList Array of dropped files
|
|
394
|
-
* @param ctx Context information about where the drop occurred
|
|
406
|
+
* @param ctx Context information about where on the screen or in 3D space the drop occurred
|
|
395
407
|
*/
|
|
396
|
-
private async
|
|
408
|
+
private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
|
|
397
409
|
if (debug) console.log("Add files", fileList)
|
|
398
410
|
if (!Array.isArray(fileList)) return;
|
|
399
411
|
if (!fileList.length) return;
|
|
@@ -427,7 +439,7 @@ export class DropListener extends Behaviour {
|
|
|
427
439
|
if (res) {
|
|
428
440
|
this.dispatchEvent(new CustomEvent(DropListenerEvents.FileDropped, { detail: file }));
|
|
429
441
|
ctx.file = file;
|
|
430
|
-
const obj = this.
|
|
442
|
+
const obj = this.onObjectLoaded(res, ctx, false);
|
|
431
443
|
|
|
432
444
|
// handle uploading the dropped object and networking the event
|
|
433
445
|
if (obj && this.context.connection.isConnected && this.useNetworking) {
|
|
@@ -478,7 +490,7 @@ export class DropListener extends Behaviour {
|
|
|
478
490
|
* @param isRemote Whether this object was shared from a remote client
|
|
479
491
|
* @returns The added object or null if adding failed
|
|
480
492
|
*/
|
|
481
|
-
private
|
|
493
|
+
private onObjectLoaded(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
|
|
482
494
|
|
|
483
495
|
const { model, contentMD5 } = data;
|
|
484
496
|
|
|
@@ -522,9 +534,7 @@ export class DropListener extends Behaviour {
|
|
|
522
534
|
}
|
|
523
535
|
}
|
|
524
536
|
|
|
525
|
-
AnimationUtils.
|
|
526
|
-
createAnimationComponent: obj => addComponent(obj, Animation)
|
|
527
|
-
});
|
|
537
|
+
AnimationUtils.autoplayAnimations(model);
|
|
528
538
|
|
|
529
539
|
const evt = new DropListenerAddedEvent({
|
|
530
540
|
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) {
|
|
@@ -887,6 +889,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
887
889
|
}
|
|
888
890
|
else this._fovLerpDuration = this.targetLerpDuration;
|
|
889
891
|
}
|
|
892
|
+
|
|
893
|
+
// if (this.context.mainCameraComponent) this.context.mainCameraComponent.fieldOfView = fov;
|
|
890
894
|
}
|
|
891
895
|
|
|
892
896
|
/** Moves the camera look-at target to a position smoothly.
|
|
@@ -910,7 +914,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
910
914
|
}
|
|
911
915
|
|
|
912
916
|
if (immediateOrDuration === true) {
|
|
913
|
-
this.
|
|
917
|
+
this.lerpLookTarget(this._lookTargetEndPosition, this._lookTargetEndPosition, 1);
|
|
914
918
|
}
|
|
915
919
|
else {
|
|
916
920
|
this._lookTargetLerpActive = true;
|
|
@@ -941,20 +945,18 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
941
945
|
const target = sources[index];
|
|
942
946
|
if (target) {
|
|
943
947
|
target.getWorldPosition(this._lookTargetEndPosition);
|
|
944
|
-
this.lerpLookTarget(this._lookTargetEndPosition, t);
|
|
948
|
+
this.lerpLookTarget(this._controls.target, this._lookTargetEndPosition, t);
|
|
945
949
|
return true;
|
|
946
950
|
}
|
|
947
951
|
}
|
|
948
952
|
return false;
|
|
949
953
|
}
|
|
950
954
|
|
|
951
|
-
|
|
952
|
-
public lerpTarget(position: Vector3, delta: number) { return this.lerpLookTarget(position, delta); }
|
|
953
|
-
|
|
954
|
-
private lerpLookTarget(position: Vector3, delta: number) {
|
|
955
|
+
private lerpLookTarget(start: Vector3, position: Vector3, t: number) {
|
|
955
956
|
if (!this._controls) return;
|
|
956
|
-
if (
|
|
957
|
-
else this._controls.target.
|
|
957
|
+
if (t >= 1) this._controls.target.copy(position);
|
|
958
|
+
else this._controls.target.lerpVectors(start, position, t);
|
|
959
|
+
if (this.lookAtConstraint) this.lookAtConstraint.setConstraintPosition(this._controls.target);
|
|
958
960
|
}
|
|
959
961
|
|
|
960
962
|
private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
|
|
@@ -984,10 +986,11 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
984
986
|
*/
|
|
985
987
|
fitCamera(options?: FitCameraOptions);
|
|
986
988
|
fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<FitCameraOptions, "objects">);
|
|
987
|
-
fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | FitCameraOptions, options?: FitCameraOptions) {
|
|
989
|
+
fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | FitCameraOptions, options?: FitCameraOptions): void {
|
|
988
990
|
|
|
989
991
|
if (this.context.isInXR) {
|
|
990
992
|
// camera fitting in XR is not supported
|
|
993
|
+
console.warn('[OrbitControls] Can not fit camera while XR session is active');
|
|
991
994
|
return;
|
|
992
995
|
}
|
|
993
996
|
|
|
@@ -1032,7 +1035,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1032
1035
|
return;
|
|
1033
1036
|
}
|
|
1034
1037
|
if (!options) options = {}
|
|
1035
|
-
const { immediate = false, centerCamera
|
|
1038
|
+
const { immediate = false, centerCamera, cameraNearFar = "auto", fitOffset = 1.1, fov = camera?.fov } = options;
|
|
1036
1039
|
const size = new Vector3();
|
|
1037
1040
|
const center = new Vector3();
|
|
1038
1041
|
// TODO would be much better to calculate the bounds in camera space instead of world space -
|
|
@@ -1063,7 +1066,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1063
1066
|
return;
|
|
1064
1067
|
}
|
|
1065
1068
|
|
|
1066
|
-
const verticalFov =
|
|
1069
|
+
const verticalFov = fov;
|
|
1067
1070
|
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov * Math.PI / 360 / 2) * camera.aspect) / Math.PI * 360;
|
|
1068
1071
|
const fitHeightDistance = size.y / (2 * Math.atan(Math.PI * verticalFov / 360));
|
|
1069
1072
|
const fitWidthDistance = size.x / (2 * Math.atan(Math.PI * horizontalFov / 360));
|
|
@@ -1078,9 +1081,18 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1078
1081
|
this.minZoom = distance * 0.01;
|
|
1079
1082
|
|
|
1080
1083
|
const verticalOffset = 0.05;
|
|
1081
|
-
|
|
1082
1084
|
const lookAt = center.clone();
|
|
1083
1085
|
lookAt.y -= size.y * verticalOffset;
|
|
1086
|
+
if (options.targetOffset) {
|
|
1087
|
+
if (options.targetOffset.x !== undefined) lookAt.x += options.targetOffset.x;
|
|
1088
|
+
if (options.targetOffset.y !== undefined) lookAt.y += options.targetOffset.y;
|
|
1089
|
+
if (options.targetOffset.z !== undefined) lookAt.z += options.targetOffset.z;
|
|
1090
|
+
}
|
|
1091
|
+
if (options.relativeTargetOffset) {
|
|
1092
|
+
if (options.relativeTargetOffset.x !== undefined) lookAt.x += options.relativeTargetOffset.x * size.x;
|
|
1093
|
+
if (options.relativeTargetOffset.y !== undefined) lookAt.y += options.relativeTargetOffset.y * size.y;
|
|
1094
|
+
if (options.relativeTargetOffset.z !== undefined) lookAt.z += options.relativeTargetOffset.z * size.z;
|
|
1095
|
+
}
|
|
1084
1096
|
this.setLookTargetPosition(lookAt, immediate);
|
|
1085
1097
|
this.setFieldOfView(options.fov, immediate);
|
|
1086
1098
|
|
|
@@ -1107,9 +1119,13 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1107
1119
|
camera.updateMatrixWorld();
|
|
1108
1120
|
camera.updateProjectionMatrix();
|
|
1109
1121
|
|
|
1110
|
-
const cameraWp = getWorldPosition(camera);
|
|
1111
1122
|
const direction = center.clone();
|
|
1112
|
-
|
|
1123
|
+
if (options.fitDirection) {
|
|
1124
|
+
direction.sub(new Vector3().copy(options.fitDirection).multiplyScalar(1_000_000));
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
direction.sub(camera.worldPosition);
|
|
1128
|
+
}
|
|
1113
1129
|
if (centerCamera === "y")
|
|
1114
1130
|
direction.y = 0;
|
|
1115
1131
|
direction.normalize();
|
|
@@ -1118,6 +1134,16 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1118
1134
|
direction.y += -verticalOffset * 4 * distance;
|
|
1119
1135
|
|
|
1120
1136
|
let cameraLocalPosition = center.clone().sub(direction);
|
|
1137
|
+
if (options.cameraOffset) {
|
|
1138
|
+
if (options.cameraOffset.x !== undefined) cameraLocalPosition.x += options.cameraOffset.x;
|
|
1139
|
+
if (options.cameraOffset.y !== undefined) cameraLocalPosition.y += options.cameraOffset.y;
|
|
1140
|
+
if (options.cameraOffset.z !== undefined) cameraLocalPosition.z += options.cameraOffset.z;
|
|
1141
|
+
}
|
|
1142
|
+
if (options.relativeCameraOffset) {
|
|
1143
|
+
if (options.relativeCameraOffset.x !== undefined) cameraLocalPosition.x += options.relativeCameraOffset.x * size.x;
|
|
1144
|
+
if (options.relativeCameraOffset.y !== undefined) cameraLocalPosition.y += options.relativeCameraOffset.y * size.y;
|
|
1145
|
+
if (options.relativeCameraOffset.z !== undefined) cameraLocalPosition.z += options.relativeCameraOffset.z * size.z;
|
|
1146
|
+
}
|
|
1121
1147
|
if (camera.parent) {
|
|
1122
1148
|
cameraLocalPosition = camera.parent.worldToLocal(cameraLocalPosition);
|
|
1123
1149
|
}
|
|
@@ -1154,23 +1180,53 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1154
1180
|
/**
|
|
1155
1181
|
* Options for fitting the camera to the scene. Used in {@link OrbitControls.fitCamera}
|
|
1156
1182
|
*/
|
|
1157
|
-
|
|
1183
|
+
export type FitCameraOptions = {
|
|
1158
1184
|
/** When enabled debug rendering will be shown */
|
|
1159
1185
|
debug?: boolean,
|
|
1160
1186
|
/**
|
|
1161
1187
|
* The objects to fit the camera to. If not provided the scene children will be used
|
|
1162
1188
|
*/
|
|
1163
1189
|
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
1190
|
/** If true the camera will move immediately to the new position, otherwise it will lerp
|
|
1169
1191
|
* @default false
|
|
1170
1192
|
*/
|
|
1171
1193
|
immediate?: boolean,
|
|
1194
|
+
|
|
1195
|
+
/** Fit offset: A factor to multiply the distance to the objects by
|
|
1196
|
+
* @default 1.1
|
|
1197
|
+
*/
|
|
1198
|
+
fitOffset?: number,
|
|
1199
|
+
|
|
1200
|
+
/** The direction from which the camera should be fitted in worldspace. If not defined the current camera's position will be used */
|
|
1201
|
+
fitDirection?: Vector3Like,
|
|
1202
|
+
|
|
1172
1203
|
/** If set to "y" the camera will be centered in the y axis */
|
|
1173
1204
|
centerCamera?: "none" | "y",
|
|
1205
|
+
/** Set to 'auto' to update the camera near or far plane based on the fitted-objects bounds */
|
|
1174
1206
|
cameraNearFar?: "keep" | "auto",
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Offset the camera position in world space
|
|
1210
|
+
*/
|
|
1211
|
+
cameraOffset?: Partial<Vector3Like>,
|
|
1212
|
+
/**
|
|
1213
|
+
* Offset the camera position relative to the size of the objects being focused on (e.g. x: 0.5).
|
|
1214
|
+
* Value range: -1 to 1
|
|
1215
|
+
*/
|
|
1216
|
+
relativeCameraOffset?: Partial<Vector3Like>,
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Offset the camera target position in world space
|
|
1220
|
+
*/
|
|
1221
|
+
targetOffset?: Partial<Vector3Like>,
|
|
1222
|
+
/**
|
|
1223
|
+
* Offset the camera target position relative to the size of the objects being focused on.
|
|
1224
|
+
* Value range: -1 to 1
|
|
1225
|
+
*/
|
|
1226
|
+
relativeTargetOffset?: Partial<Vector3Like>,
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Field of view (FOV) for the camera
|
|
1230
|
+
*/
|
|
1175
1231
|
fov?: number,
|
|
1176
1232
|
}
|
|
@@ -48,21 +48,20 @@ function createRemoteSkyboxComponent(context: IContext, url: string, skybox: boo
|
|
|
48
48
|
const promises = new Array<Promise<any>>();
|
|
49
49
|
ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, (args) => {
|
|
50
50
|
const context = args.context;
|
|
51
|
-
const
|
|
51
|
+
const backgroundImage = context.domElement.getAttribute("background-image");
|
|
52
52
|
const environmentImage = context.domElement.getAttribute("environment-image");
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (debug) console.log("Creating remote skybox to load " + skyboxImage);
|
|
53
|
+
|
|
54
|
+
if (backgroundImage) {
|
|
55
|
+
if (debug) console.log("Creating RemoteSkybox to load background " + backgroundImage);
|
|
57
56
|
// if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
|
|
58
57
|
// checks if we have this attribute set and then sets the skybox clearflags accordingly
|
|
59
58
|
// if the user has a GLB with a camera but set to solid color then the skybox image is not visible -> we will just warn then and not override the camera settings
|
|
60
|
-
const promise = createRemoteSkyboxComponent(context,
|
|
59
|
+
const promise = createRemoteSkyboxComponent(context, backgroundImage, true, false, "background-image");
|
|
61
60
|
if (promise) promises.push(promise);
|
|
62
61
|
}
|
|
63
62
|
if (environmentImage) {
|
|
64
|
-
if (debug) console.log("Creating
|
|
65
|
-
const promise = createRemoteSkyboxComponent(context, environmentImage,
|
|
63
|
+
if (debug) console.log("Creating RemoteSkybox to load environment " + environmentImage);
|
|
64
|
+
const promise = createRemoteSkyboxComponent(context, environmentImage, false, true, "environment-image");
|
|
66
65
|
if (promise) promises.push(promise);
|
|
67
66
|
}
|
|
68
67
|
});
|
|
@@ -200,7 +199,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
200
199
|
console.warn("Potentially invalid skybox URL: \"" + name + "\" on " + (this.name || this.gameObject?.name || "context"));
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
if (debug) console.log("Set
|
|
202
|
+
if (debug) console.log("Set RemoteSkybox url: " + url);
|
|
204
203
|
|
|
205
204
|
if (this._prevUrl === url && this._prevLoadedEnvironment) {
|
|
206
205
|
this.apply();
|
|
@@ -258,7 +257,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
258
257
|
this._prevBackground = this.context.scene.background;
|
|
259
258
|
if (this.context.scene.environment !== envMap)
|
|
260
259
|
this._prevEnvironment = this.context.scene.environment;
|
|
261
|
-
if (debug) console.log("Set
|
|
260
|
+
if (debug) console.log("Set RemoteSkybox (" + ((this.environment && this.background) ? "environment and background" : this.environment ? "environment" : this.background ? "background" : "none") + ")", this.url, !Camera.backgroundShouldBeTransparent(this.context));
|
|
262
261
|
if (this.environment)
|
|
263
262
|
this.context.scene.environment = envMap;
|
|
264
263
|
if (this.background && !Camera.backgroundShouldBeTransparent(this.context))
|
|
@@ -51,7 +51,8 @@ import "./CameraUtils.js"
|
|
|
51
51
|
import "./AnimationUtils.js"
|
|
52
52
|
import "./AnimationUtilsAutoplay.js"
|
|
53
53
|
|
|
54
|
-
export { DragMode } from "./DragControls.js"
|
|
54
|
+
export { DragMode } from "./DragControls.js";
|
|
55
|
+
export { type FitCameraOptions } from "./OrbitControls.js";
|
|
55
56
|
export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
|
|
56
57
|
export * from "./particlesystem/api.js"
|
|
57
58
|
|
|
@@ -21,5 +21,5 @@ export interface IUSDExporterExtension {
|
|
|
21
21
|
onAfterBuildDocument?(context: USDZExporterContext);
|
|
22
22
|
onExportObject?(object: Object3D, model: USDObject, context: USDZExporterContext);
|
|
23
23
|
onAfterSerialize?(context: USDZExporterContext);
|
|
24
|
-
onAfterHierarchy?(context: USDZExporterContext, writer: USDWriter)
|
|
24
|
+
onAfterHierarchy?(context: USDZExporterContext, writer: USDWriter) : void | Promise<void>;
|
|
25
25
|
}
|
|
@@ -1082,7 +1082,7 @@ async function parseDocument( context: USDZExporterContext, afterStageRoot: () =
|
|
|
1082
1082
|
Progress.end("export-usdz-xforms");
|
|
1083
1083
|
|
|
1084
1084
|
Progress.report("export-usdz", "invoke onAfterHierarchy");
|
|
1085
|
-
invokeAll( context, 'onAfterHierarchy', writer );
|
|
1085
|
+
await invokeAll( context, 'onAfterHierarchy', writer );
|
|
1086
1086
|
|
|
1087
1087
|
writer.closeBlock();
|
|
1088
1088
|
writer.closeBlock();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
|
|
2
|
-
import { Euler, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "three";
|
|
2
|
+
import { Euler, EventDispatcher, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "three";
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
|
5
5
|
import { hasProLicense } from "../../../engine/engine_license.js";
|
|
@@ -12,6 +12,7 @@ import { InstancingHandler } from "../../../engine-components/RendererInstancing
|
|
|
12
12
|
import { Collider } from "../../Collider.js";
|
|
13
13
|
import { Behaviour, GameObject } from "../../Component.js";
|
|
14
14
|
import { ContactShadows } from "../../ContactShadows.js";
|
|
15
|
+
import { EventList } from "../../EventList.js";
|
|
15
16
|
import { GroundProjectedEnv } from "../../GroundProjection.js";
|
|
16
17
|
import { Renderer } from "../../Renderer.js"
|
|
17
18
|
import { Rigidbody } from "../../RigidBody.js";
|
|
@@ -71,6 +72,9 @@ export class CustomBranding {
|
|
|
71
72
|
*/
|
|
72
73
|
export class USDZExporter extends Behaviour {
|
|
73
74
|
|
|
75
|
+
static readonly beforeExport = new EventList<{ exporter: USDZExporter }>();
|
|
76
|
+
static readonly afterExport = new EventList<{ exporter: USDZExporter }>();
|
|
77
|
+
|
|
74
78
|
/**
|
|
75
79
|
* Assign the object to export as USDZ file. If undefined or null, the whole scene will be exported.
|
|
76
80
|
*/
|
|
@@ -210,7 +214,7 @@ export class USDZExporter extends Behaviour {
|
|
|
210
214
|
* Creates an USDZ file from the current scene or assigned objectToExport and opens it in QuickLook.
|
|
211
215
|
* @returns a Promise<Blob> containing the USDZ file
|
|
212
216
|
*/
|
|
213
|
-
async exportAndOpen()
|
|
217
|
+
async exportAndOpen(): Promise<Blob | null> {
|
|
214
218
|
|
|
215
219
|
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
|
|
216
220
|
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
|
|
@@ -221,7 +225,7 @@ export class USDZExporter extends Behaviour {
|
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
if (!this.link) this.link = ensureQuicklookLinkIsCreated(this.context, DeviceUtilities.supportsQuickLookAR());
|
|
224
|
-
|
|
228
|
+
|
|
225
229
|
// ability to specify a custom USDZ file to be used instead of a dynamic one
|
|
226
230
|
if (this.customUsdzFile) {
|
|
227
231
|
if (debug) console.log("Exporting custom usdz", this.customUsdzFile)
|
|
@@ -234,14 +238,19 @@ export class USDZExporter extends Behaviour {
|
|
|
234
238
|
return null;
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
USDZExporter.beforeExport.invoke({ exporter: this });
|
|
242
|
+
const blob = await this.export(this.objectToExport)
|
|
243
|
+
.finally(() => {
|
|
244
|
+
USDZExporter.afterExport.invoke({ exporter: this });
|
|
245
|
+
});
|
|
246
|
+
|
|
238
247
|
if (!blob) {
|
|
239
248
|
console.error("USDZ generation failed. Please report a bug", this);
|
|
240
249
|
return null;
|
|
241
250
|
}
|
|
242
251
|
|
|
243
252
|
if (debug) console.log("USDZ generation done. Downloading as " + name);
|
|
244
|
-
|
|
253
|
+
|
|
245
254
|
// TODO Potentially we have to detect QuickLook availability here,
|
|
246
255
|
// and download the file instead. But browsers keep changing how they deal with non-user-initiated downloads...
|
|
247
256
|
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/#:~:text=inside%20the%20anchor.-,Feature%20Detection,-To%20detect%20support
|
|
@@ -364,7 +373,7 @@ export class USDZExporter extends Behaviour {
|
|
|
364
373
|
if (this.interactive) {
|
|
365
374
|
defaultExtensions.push(new BehaviorExtension());
|
|
366
375
|
defaultExtensions.push(new AudioExtension());
|
|
367
|
-
|
|
376
|
+
|
|
368
377
|
// If physics are enabled, and there are any Rigidbody components in the scene,
|
|
369
378
|
// add the PhysicsExtension to the default extensions.
|
|
370
379
|
if (globalThis["NEEDLE_USE_RAPIER"]) {
|
|
@@ -632,13 +641,13 @@ export class USDZExporter extends Behaviour {
|
|
|
632
641
|
private _rootPositionBeforeExport: Vector3 = new Vector3();
|
|
633
642
|
private _rootRotationBeforeExport: Quaternion = new Quaternion();
|
|
634
643
|
private _rootScaleBeforeExport: Vector3 = new Vector3();
|
|
635
|
-
|
|
636
|
-
getARScaleAndTarget(): { scale: number, _invertForward: boolean, target: Object3D, sessionRoot: Object3D | null} {
|
|
637
|
-
if (!this.objectToExport) return { scale: 1, _invertForward: false, target: this.gameObject, sessionRoot: null};
|
|
644
|
+
|
|
645
|
+
getARScaleAndTarget(): { scale: number, _invertForward: boolean, target: Object3D, sessionRoot: Object3D | null } {
|
|
646
|
+
if (!this.objectToExport) return { scale: 1, _invertForward: false, target: this.gameObject, sessionRoot: null };
|
|
638
647
|
|
|
639
648
|
const xr = GameObject.findObjectOfType(WebXR);
|
|
640
649
|
let sessionRoot = GameObject.getComponentInParent(this.objectToExport, WebARSessionRoot);
|
|
641
|
-
if(!sessionRoot) sessionRoot = GameObject.getComponentInChildren(this.objectToExport, WebARSessionRoot);
|
|
650
|
+
if (!sessionRoot) sessionRoot = GameObject.getComponentInChildren(this.objectToExport, WebARSessionRoot);
|
|
642
651
|
|
|
643
652
|
let arScale = 1;
|
|
644
653
|
let _invertForward = false;
|
|
@@ -653,7 +662,7 @@ export class USDZExporter extends Behaviour {
|
|
|
653
662
|
// eslint-disable-next-line deprecation/deprecation
|
|
654
663
|
_invertForward = sessionRoot.invertForward;
|
|
655
664
|
}
|
|
656
|
-
|
|
665
|
+
|
|
657
666
|
const scale = 1 / arScale;
|
|
658
667
|
const result = { scale, _invertForward, target, sessionRoot: sessionRoot?.gameObject ?? null };
|
|
659
668
|
return result;
|
|
@@ -699,7 +708,7 @@ export class USDZExporter extends Behaviour {
|
|
|
699
708
|
private createQuicklookButton() {
|
|
700
709
|
const buttoncontainer = WebXRButtonFactory.getOrCreate();
|
|
701
710
|
const button = buttoncontainer.createQuicklookButton();
|
|
702
|
-
if(!button.parentNode) this.context.menu.appendChild(button);
|
|
711
|
+
if (!button.parentNode) this.context.menu.appendChild(button);
|
|
703
712
|
return button;
|
|
704
713
|
}
|
|
705
714
|
}
|