@needle-tools/engine 4.12.0-next.da19dd0 → 4.12.0-next.dcaf2b0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{gltf-progressive-Rs-ojtXy.umd.cjs → gltf-progressive-Bfpfaz84.umd.cjs} +1 -1
  3. package/dist/{gltf-progressive-DnLBuGK5.js → gltf-progressive-DPunMlEM.js} +1 -1
  4. package/dist/{gltf-progressive-BmSygnAC.min.js → gltf-progressive-hFPACYio.min.js} +1 -1
  5. package/dist/{needle-engine.bundle-DeqWDtMx.js → needle-engine.bundle-DTXsNmA-.js} +4450 -4426
  6. package/dist/{needle-engine.bundle-BoOkD4mG.umd.cjs → needle-engine.bundle-HRGeyfKM.umd.cjs} +103 -103
  7. package/dist/{needle-engine.bundle-DDdH38o3.min.js → needle-engine.bundle-OAD5_P8d.min.js} +103 -103
  8. package/dist/needle-engine.d.ts +12 -5
  9. package/dist/needle-engine.js +3 -3
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{postprocessing-DZtb9Nnn.umd.cjs → postprocessing-BHQvwehB.umd.cjs} +1 -1
  13. package/dist/{postprocessing-B5ksn9-G.min.js → postprocessing-ClLv0reO.min.js} +1 -1
  14. package/dist/{postprocessing-__7s9wON.js → postprocessing-DLI2N3LL.js} +1 -1
  15. package/dist/{three-examples-y2GeYlze.js → three-examples-D4rE49Ui.js} +10 -2
  16. package/dist/{three-examples-MsJjauyk.min.js → three-examples-DB5Uoja4.min.js} +2 -2
  17. package/dist/{three-examples-Dho7cuu4.umd.cjs → three-examples-Djbk6WA4.umd.cjs} +2 -2
  18. package/lib/engine/engine_context.js +1 -1
  19. package/lib/engine/engine_context.js.map +1 -1
  20. package/lib/engine/engine_license.js +12 -7
  21. package/lib/engine/engine_license.js.map +1 -1
  22. package/lib/engine/engine_networking.js +15 -0
  23. package/lib/engine/engine_networking.js.map +1 -1
  24. package/lib/engine/engine_three_utils.js +2 -2
  25. package/lib/engine/engine_three_utils.js.map +1 -1
  26. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -1
  27. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  28. package/lib/engine-components/DropListener.d.ts +1 -0
  29. package/lib/engine-components/DropListener.js +26 -8
  30. package/lib/engine-components/DropListener.js.map +1 -1
  31. package/lib/engine-components/EventList.js +4 -1
  32. package/lib/engine-components/EventList.js.map +1 -1
  33. package/lib/engine-components/SceneSwitcher.d.ts +3 -2
  34. package/lib/engine-components/SceneSwitcher.js +20 -11
  35. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  36. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +8 -0
  37. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  38. package/lib/engine-components/webxr/WebARSessionRoot.d.ts +5 -2
  39. package/lib/engine-components/webxr/WebARSessionRoot.js +5 -2
  40. package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
  41. package/lib/engine-components/webxr/WebXR.d.ts +3 -1
  42. package/lib/engine-components/webxr/WebXR.js +3 -1
  43. package/lib/engine-components/webxr/WebXR.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/engine/engine_context.ts +1 -1
  46. package/src/engine/engine_license.ts +13 -7
  47. package/src/engine/engine_networking.ts +15 -0
  48. package/src/engine/engine_three_utils.ts +4 -2
  49. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -1
  50. package/src/engine-components/DropListener.ts +29 -8
  51. package/src/engine-components/EventList.ts +5 -1
  52. package/src/engine-components/SceneSwitcher.ts +23 -13
  53. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +11 -0
  54. package/src/engine-components/webxr/WebARSessionRoot.ts +7 -3
  55. package/src/engine-components/webxr/WebXR.ts +4 -2
@@ -27,6 +27,7 @@ import { ObjectUtils } from "./engine_create_objects.js";
27
27
  import { destroy, foreachComponent } from './engine_gameobject.js';
28
28
  import { getLoader } from './engine_gltf.js';
29
29
  import { Input } from './engine_input.js';
30
+ import { Telemetry } from './engine_license.js';
30
31
  import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
31
32
  import { type ILightDataRegistry, LightDataRegistry } from './engine_lightdata.js';
32
33
  import { LODsManager } from "./engine_lods.js";
@@ -43,7 +44,6 @@ import { deepClone, delay, DeviceUtilities, getParam } from './engine_utils.js';
43
44
  import type { INeedleXRSessionEventReceiver, NeedleXRSession } from './engine_xr.js';
44
45
  import { NeedleMenu } from './webcomponents/needle menu/needle-menu.js';
45
46
  import type { NeedleEngineWebComponent } from './webcomponents/needle-engine.js';
46
- import { Telemetry } from './engine_license.js';
47
47
 
48
48
  const debug = getParam("debugcontext");
49
49
  const stats = getParam("stats");
@@ -1,4 +1,5 @@
1
1
  import { dof } from "three/src/nodes/TSL.js";
2
+
2
3
  import { isDevEnvironment } from "./debug/index.js";
3
4
  import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
4
5
  import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
@@ -109,13 +110,18 @@ export namespace Telemetry {
109
110
  event_name: "page_view"
110
111
  }).then(res => {
111
112
  if (res instanceof Response && res.ok && isLocalNetwork()) {
112
- sendEvent(ctx, "info", {
113
- src: ctx.domElement?.getAttribute("src") || "",
114
- version: VERSION,
115
- generator: GENERATOR,
116
- build_time: BUILD_TIME,
117
- public_key: PUBLIC_KEY,
118
- });
113
+ const src = ctx.domElement?.getAttribute("src") || "";
114
+ const sessionKey = src + VERSION + GENERATOR + BUILD_TIME + PUBLIC_KEY;
115
+ if (window.sessionStorage.getItem("session_key") !== sessionKey) {
116
+ window.sessionStorage.setItem("session_key", sessionKey);
117
+ sendEvent(ctx, "info", {
118
+ src: ctx.domElement?.getAttribute("src") || "",
119
+ version: VERSION,
120
+ generator: GENERATOR,
121
+ build_time: BUILD_TIME,
122
+ public_key: PUBLIC_KEY,
123
+ });
124
+ }
119
125
  }
120
126
  })
121
127
  }
@@ -6,6 +6,7 @@ import { type Websocket } from 'websocket-ts';
6
6
 
7
7
  import * as schemes from "../engine-schemes/schemes.js";
8
8
  import { isDevEnvironment } from './debug/index.js';
9
+ import { Telemetry } from './engine_license.js';
9
10
  import { PeerNetworking } from './engine_networking_peer.js';
10
11
  import { type IModel, type INetworkConnection, SendQueue } from './engine_networking_types.js';
11
12
  import { isHostedOnGlitch } from './engine_networking_utils.js';
@@ -658,6 +659,9 @@ export class NetworkConnection implements INetworkConnection {
658
659
  .onError((_e) => {
659
660
  console.error("Websocket connection failed...");
660
661
  resolve(false);
662
+ Telemetry.sendEvent(this.context, "networking", {
663
+ event: "connection_error",
664
+ });
661
665
  })
662
666
  .onRetry(() => { console.log("Retry connecting to networking websocket") })
663
667
  .build();
@@ -727,6 +731,9 @@ export class NetworkConnection implements INetworkConnection {
727
731
  "server did not send connection id", connection.id);
728
732
  console.debug("Your id is: " + connection.id, this.context.alias ?? "");
729
733
  this._connectionId = connection.id;
734
+ Telemetry.sendEvent(this.context, "networking", {
735
+ event: "connected",
736
+ });
730
737
  }
731
738
  }
732
739
  else console.warn("Expected connection id in " + message.key);
@@ -754,6 +761,10 @@ export class NetworkConnection implements INetworkConnection {
754
761
  }
755
762
 
756
763
  this.onSendQueued(SendQueue.OnRoomJoin);
764
+ Telemetry.sendEvent(this.context, "networking", {
765
+ event: "joined_room",
766
+ room: this._currentRoomName,
767
+ });
757
768
  break;
758
769
 
759
770
  case RoomEvents.LeftRoom:
@@ -765,6 +776,10 @@ export class NetworkConnection implements INetworkConnection {
765
776
  this._currentInRoom.length = 0;
766
777
  if (debugnetBin || isDevEnvironment()) console.debug("Left Needle Engine Room: " + model.room);
767
778
  }
779
+ Telemetry.sendEvent(this.context, "networking", {
780
+ event: "left_room",
781
+ room: model.room,
782
+ });
768
783
  break;
769
784
  case RoomEvents.UserJoinedRoom:
770
785
  if (message.data) {
@@ -242,8 +242,10 @@ export function setWorldPosition(obj: Object3D, val: Vector3): Object3D {
242
242
  const wp = _worldPositions.get();
243
243
  if (val !== wp)
244
244
  wp.copy(val);
245
- const obj2 = obj?.parent ?? obj;
246
- obj2.worldToLocal(wp);
245
+
246
+ if (obj.parent !== null)
247
+ obj.parent.worldToLocal(wp);
248
+
247
249
  obj.position.set(wp.x, wp.y, wp.z);
248
250
  return obj;
249
251
  }
@@ -104,7 +104,8 @@ export class NeedleSpatialMenu {
104
104
  }
105
105
 
106
106
  const xr = this._context.xr;
107
- if (!xr?.running) {
107
+ const isImmersiveXR = xr?.running && (xr?.isPassThrough || xr?.isVR)
108
+ if (!isImmersiveXR) {
108
109
  if (this._wasInXR) {
109
110
  this._wasInXR = false;
110
111
  this.onExitXR();
@@ -10,7 +10,7 @@ import { BlobStorage } from "../engine/engine_networking_blob.js";
10
10
  import { PreviewHelper } from "../engine/engine_networking_files.js";
11
11
  import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
12
12
  import { serializable } from "../engine/engine_serialization_decorator.js";
13
- import { fitObjectIntoVolume, getBoundingBox, placeOnSurface } from "../engine/engine_three_utils.js";
13
+ import { fitObjectIntoVolume, getBoundingBox, getWorldScale, placeOnSurface } from "../engine/engine_three_utils.js";
14
14
  import { Model, Vec3 } from "../engine/engine_types.js";
15
15
  import { getParam, setParamWithoutReload } from "../engine/engine_utils.js";
16
16
  import { determineMimeTypeFromExtension } from "../engine/engine_utils_format.js";
@@ -111,6 +111,7 @@ const blobKeyName = "blob";
111
111
 
112
112
  /** The DropListener component is used to listen for drag and drop events in the browser and add the dropped files to the scene
113
113
  * It can be used to allow users to drag and drop glTF files into the scene to add new objects.
114
+ * Existing child objects will behave like placeholders and will be removed when new files are dropped.
114
115
  *
115
116
  * If {@link useNetworking} is enabled, the DropListener will automatically synchronize dropped files to other connected clients.
116
117
  * Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
@@ -522,22 +523,42 @@ export class DropListener extends Behaviour {
522
523
 
523
524
  const obj = model.scene;
524
525
 
525
- // use attach to ignore the DropListener scale (e.g. if the parent object scale is not uniform)
526
- this.gameObject.attach(obj);
527
- obj.position.set(0, 0, 0);
528
- obj.quaternion.identity();
526
+ obj.position.copy(this.gameObject.worldPosition);
527
+ const scale = getWorldScale(this.gameObject);
529
528
 
530
- this._addedObjects.push(obj);
531
- this._addedModels.push(model);
529
+ let localPos = new Vector3(0,0,0);
530
+ scale.x = Math.abs(scale.x);
531
+ scale.y = Math.abs(scale.y);
532
+ scale.z = Math.abs(scale.z);
533
+ let localScale =obj.scale.clone();
534
+
535
+ // TODOs: handle rotation when Gizmos APIs has changed to support it
532
536
 
533
- const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize);
537
+ const volume = new Box3().setFromCenterAndSize(new Vector3(0, this.fitVolumeSize.y * scale.y * .5, 0).add(this.gameObject.worldPosition), this.fitVolumeSize.clone().multiply(scale));
534
538
  if (debug) Gizmos.DrawWireBox3(volume, 0x0000ff, 5);
535
539
  if (this.fitIntoVolume) {
540
+
536
541
  fitObjectIntoVolume(obj, volume, {
537
542
  position: !this.placeAtHitPosition
538
543
  });
544
+
545
+ // to match parent scale later, divide by it
546
+ localScale = obj.scale.clone().divide(scale);
547
+ // just take the computed offset from fitting
548
+ localPos = obj.worldPosition.clone().sub(this.gameObject.worldPosition).divide(scale);
549
+ if (debug) Gizmos.DrawSphere(localPos, 0.1, 0xff0000, 5);
539
550
  }
540
551
 
552
+ // use attach to ignore the DropListener scale (e.g. if the parent object scale is not uniform)
553
+ this.gameObject.attach(obj);
554
+ obj.position.copy(localPos);
555
+ obj.quaternion.identity();
556
+ obj.scale.copy(localScale);
557
+ if (debug) Gizmos.DrawArrow(this.gameObject.worldPosition, obj.getWorldPosition(new Vector3()), 0x00ff00, 5);
558
+
559
+ this._addedObjects.push(obj);
560
+ this._addedModels.push(model);
561
+
541
562
  if (this.placeAtHitPosition && ctx && ctx.screenposition) {
542
563
  obj.visible = false; // < don't raycast on the placed object
543
564
  const rc = this.context.physics.raycast({ screenPoint: this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone()) });
@@ -78,7 +78,11 @@ export class CallInfo {
78
78
  // If the target is a property
79
79
  else {
80
80
  if (this.arguments) {
81
- this.target[this.methodName] = this.arguments[0] || args[0];
81
+
82
+ if (args !== undefined && args.length > 0)
83
+ this.target[this.methodName] = args[0];
84
+ else
85
+ this.target[this.methodName] = this.arguments[0];
82
86
  }
83
87
  else {
84
88
  this.target[this.methodName] = args[0];
@@ -1,7 +1,7 @@
1
1
  import { EquirectangularReflectionMapping, Object3D, Scene, Texture } from "three";
2
2
 
3
3
  import { AssetReference } from "../engine/engine_addressables.js";
4
- import { destroy } from "../engine/engine_gameobject.js";
4
+ import { destroy, instantiate } from "../engine/engine_gameobject.js";
5
5
  import { InputEvents } from "../engine/engine_input.js";
6
6
  import { isLocalNetwork } from "../engine/engine_networking_utils.js";
7
7
  import { serializable } from "../engine/engine_serialization.js";
@@ -247,14 +247,23 @@ export class SceneSwitcher extends Behaviour {
247
247
 
248
248
  private _currentIndex: number = -1;
249
249
  private _currentScene: AssetReference | undefined = undefined;
250
+ private _currentSceneAsset: Object3D | undefined = undefined;
250
251
  private _engineElementOverserver: MutationObserver | undefined = undefined;
251
252
 
252
253
  private _preloadScheduler?: PreLoadScheduler;
253
254
 
254
255
  private _menuButtons?: HTMLElement[];
255
256
 
257
+ // this is the scene that was requested last
258
+ private __lastSwitchScene?: AssetReference;
259
+ private __lastSwitchScenePromise?: Promise<boolean>;
260
+
256
261
  /** @internal */
257
262
  awake(): void {
263
+ this._currentScene = undefined;
264
+ this._lastLoadingScene = undefined;
265
+ this.__lastSwitchScenePromise = undefined;
266
+
258
267
  if (this.scenes === undefined) this.scenes = [];
259
268
  // remove all scenes from the url that are in the scenes array at startup
260
269
  for (const scene of this.scenes) {
@@ -538,10 +547,6 @@ export class SceneSwitcher extends Behaviour {
538
547
  return false;
539
548
  }
540
549
 
541
- // this is the scene that was requested last
542
- private __lastSwitchScene?: AssetReference;
543
- private __lastSwitchScenePromise?: Promise<boolean>;
544
-
545
550
  /**
546
551
  * Switch to a scene by its AssetReference.
547
552
  * If the scene is already loaded it will be unloaded and the new scene will be loaded.
@@ -601,14 +606,13 @@ export class SceneSwitcher extends Behaviour {
601
606
  const res = sceneListener.sceneClosing();
602
607
  if (res instanceof Promise) await res;
603
608
  }
604
- // if the current scene has a URL (so it can be reloaded)
605
- // then we unload it
609
+
610
+ // // if the current scene has a URL (so it can be reloaded)
611
+ // // then we unload it
606
612
  if (current.hasUrl)
607
613
  current.unload();
608
- // otherwise if it's a regular Object3D we just remove it from the scene
609
- else if (current.asset instanceof Object3D) {
610
- GameObject.remove(current.asset as any as Object3D);
611
- }
614
+
615
+ if (this._currentSceneAsset) destroy(this._currentSceneAsset, true, false);
612
616
  }
613
617
  }
614
618
 
@@ -658,7 +662,6 @@ export class SceneSwitcher extends Behaviour {
658
662
  if (debug) console.log("[SceneSwitcher] ADD", scene.url);
659
663
  this._currentScene = scene;
660
664
 
661
-
662
665
  // Experimental: replace the whole content of the scene
663
666
  if (experimental_clearSceneOnLoad) {
664
667
  const camera = this.context.mainCameraComponent?.gameObject || this.context.mainCamera;
@@ -672,7 +675,14 @@ export class SceneSwitcher extends Behaviour {
672
675
  }
673
676
  }
674
677
 
675
- GameObject.add(scene.asset, this.gameObject);
678
+ // @TODO: if multiple scene switcher or scenes use this asset already it will be moved
679
+ if (!scene.asset.parent) {
680
+ this._currentSceneAsset = scene.asset;
681
+ GameObject.add(scene.asset, this.gameObject);
682
+ }
683
+ else {
684
+ this._currentSceneAsset = instantiate(scene.asset, { parent: this.gameObject });
685
+ }
676
686
 
677
687
  if (this.useSceneLighting)
678
688
  this.context.sceneLighting.enable(scene);
@@ -11,6 +11,7 @@ import { Animation } from "../../../../Animation.js";
11
11
  import { Animator } from "../../../../Animator.js";
12
12
  import { AudioSource } from "../../../../AudioSource.js";
13
13
  import { Behaviour, GameObject } from "../../../../Component.js";
14
+ import { Rigidbody } from "../../../../RigidBody.js";
14
15
  import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
15
16
  import { ObjectRaycaster,Raycaster } from "../../../../ui/Raycaster.js";
16
17
  import { makeNameSafeForUSD,USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
@@ -68,6 +69,16 @@ export class ChangeTransformOnClick extends Behaviour implements IPointerClickHa
68
69
  }
69
70
 
70
71
  onPointerClick(args: PointerEventData) {
72
+
73
+ const rbs = this.object?.getComponentsInChildren(Rigidbody);
74
+
75
+ if (rbs){
76
+ for (const rb of rbs) {
77
+ rb.resetVelocities();
78
+ rb.resetForcesAndTorques();
79
+ }
80
+ }
81
+
71
82
  args.use();
72
83
  if (this.coroutine) this.stopCoroutine(this.coroutine);
73
84
  if (!this.relativeMotion)
@@ -10,6 +10,7 @@ import type { IComponent, IGameObject } from "../../engine/engine_types.js";
10
10
  import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
11
11
  import { NeedleXRController, type NeedleXREventArgs, type NeedleXRHitTestResult, NeedleXRSession } from "../../engine/engine_xr.js";
12
12
  import { Behaviour, GameObject } from "../Component.js";
13
+ import type { WebXR } from "./WebXR.js";
13
14
 
14
15
  // https://github.com/takahirox/takahirox.github.io/blob/master/js.mmdeditor/examples/js/controls/DeviceOrientationControls.js
15
16
 
@@ -20,14 +21,17 @@ const invertForwardMatrix = new Matrix4().makeRotationY(Math.PI);
20
21
 
21
22
  /**
22
23
  * The WebARSessionRoot is the root object for a WebAR session and used to place the scene in AR.
23
- * It is also responsible for scaling the user in AR and to define the center of the AR scene. If not present in the scene it will be created automatically by the WebXR component when entering an AR session.
24
+ * It is also responsible for scaling the user in AR and to define the center of the AR scene.
25
+ * If not present in the scene it will be created automatically by the WebXR component when entering an AR session.
24
26
  *
25
- * @example
27
+ * **Note**: If the WebXR component {@link WebXR.autoCenter} option is enabled the scene will be automatically centered based on the content in the scene.
28
+ *
29
+ * @example Callback when the scene has been placed in AR:
26
30
  * ```ts
27
31
  * WebARSessionRoot.onPlaced((args) => {
28
32
  * console.log("Scene has been placed in AR");
29
33
  * });
30
- * ```
34
+ * ```
31
35
  *
32
36
  * @summary Root object for WebAR sessions, managing scene placement and user scaling in AR.
33
37
  * @category XR
@@ -23,7 +23,7 @@ const debug = getParam("debugwebxr");
23
23
  const debugQuicklook = getParam("debugusdz");
24
24
 
25
25
  /**
26
- * Use the WebXR component to enable VR, AR and Quicklook on iOS in your scene.
26
+ * Use the WebXR component to enable VR and AR on iOS and Android in your scene. VisionOS support is also provided via QuickLook USDZ export.
27
27
  *
28
28
  * The WebXR component is a simple to use wrapper around the {@link NeedleXRSession} API and adds some additional features like creating buttons for AR, VR, enabling default movement behaviour ({@link XRControllerMovement}) and controller rendering ({@link XRControllerModel}), as well as handling AR placement and Quicklook USDZ export.
29
29
  *
@@ -146,7 +146,9 @@ export class WebXR extends Behaviour {
146
146
 
147
147
  /**
148
148
  * When enabled, the AR session root center will be automatically adjusted to place the center of the scene.
149
- * This helps ensure the scene is properly aligned with detected surfaces.
149
+ * This helps ensure the scene is properly aligned with detected surfaces.
150
+ *
151
+ * **Note**: This option overrides the placement of the {@link WebARSessionRoot} component if both are used.
150
152
  */
151
153
  @serializable()
152
154
  autoCenter: boolean = false;