@needle-tools/engine 4.2.0 → 4.2.3

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 (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/gltf-progressive.js +258 -257
  3. package/dist/gltf-progressive.light.js +258 -257
  4. package/dist/gltf-progressive.light.min.js +7 -7
  5. package/dist/gltf-progressive.light.umd.cjs +7 -7
  6. package/dist/gltf-progressive.min.js +7 -7
  7. package/dist/gltf-progressive.umd.cjs +7 -7
  8. package/dist/needle-engine.bundle.js +6660 -6588
  9. package/dist/needle-engine.bundle.light.js +6651 -6579
  10. package/dist/needle-engine.bundle.light.min.js +124 -120
  11. package/dist/needle-engine.bundle.light.umd.cjs +122 -118
  12. package/dist/needle-engine.bundle.min.js +124 -120
  13. package/dist/needle-engine.bundle.umd.cjs +122 -118
  14. package/dist/needle-engine.light.d.ts +9 -9
  15. package/lib/engine/engine_context.d.ts +1 -0
  16. package/lib/engine/engine_context.js +7 -3
  17. package/lib/engine/engine_context.js.map +1 -1
  18. package/lib/engine/engine_input.d.ts +14 -2
  19. package/lib/engine/engine_input.js +41 -6
  20. package/lib/engine/engine_input.js.map +1 -1
  21. package/lib/engine/engine_loaders.js +6 -12
  22. package/lib/engine/engine_loaders.js.map +1 -1
  23. package/lib/engine/engine_physics_rapier.js +1 -1
  24. package/lib/engine/engine_physics_rapier.js.map +1 -1
  25. package/lib/engine/engine_serialization_core.js +16 -3
  26. package/lib/engine/engine_serialization_core.js.map +1 -1
  27. package/lib/engine/engine_types.d.ts +5 -0
  28. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  29. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  30. package/lib/engine-components/Duplicatable.js +2 -2
  31. package/lib/engine-components/Duplicatable.js.map +1 -1
  32. package/lib/engine-components/EventTrigger.d.ts +2 -0
  33. package/lib/engine-components/EventTrigger.js +12 -0
  34. package/lib/engine-components/EventTrigger.js.map +1 -1
  35. package/lib/engine-components/OrbitControls.d.ts +9 -2
  36. package/lib/engine-components/OrbitControls.js +62 -19
  37. package/lib/engine-components/OrbitControls.js.map +1 -1
  38. package/lib/engine-components/Renderer.js +5 -0
  39. package/lib/engine-components/Renderer.js.map +1 -1
  40. package/lib/engine-components/ScreenCapture.js +2 -2
  41. package/lib/engine-components/ScreenCapture.js.map +1 -1
  42. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +0 -1
  43. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
  44. package/lib/engine-components/export/usdz/USDZExporter.js +2 -2
  45. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  46. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +19 -11
  47. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -1
  48. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +7 -2
  49. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +33 -11
  50. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  51. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.d.ts +1 -0
  52. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js +5 -2
  53. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -1
  54. package/lib/engine-components/ui/Button.js +3 -3
  55. package/lib/engine-components/ui/Button.js.map +1 -1
  56. package/lib/engine-components/ui/EventSystem.d.ts +6 -10
  57. package/lib/engine-components/ui/EventSystem.js +35 -52
  58. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  59. package/lib/engine-components/ui/InputField.d.ts +4 -1
  60. package/lib/engine-components/ui/InputField.js +19 -0
  61. package/lib/engine-components/ui/InputField.js.map +1 -1
  62. package/lib/engine-components/utils/OpenURL.js +2 -2
  63. package/lib/engine-components/utils/OpenURL.js.map +1 -1
  64. package/lib/engine-components/webxr/WebXRImageTracking.js +5 -1
  65. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  66. package/package.json +2 -2
  67. package/plugins/types/userconfig.d.ts +2 -2
  68. package/plugins/vite/pwa.js +33 -22
  69. package/src/engine/codegen/register_types.ts +2 -2
  70. package/src/engine/engine_context.ts +7 -3
  71. package/src/engine/engine_input.ts +47 -9
  72. package/src/engine/engine_loaders.ts +6 -10
  73. package/src/engine/engine_physics_rapier.ts +1 -1
  74. package/src/engine/engine_serialization_core.ts +13 -4
  75. package/src/engine/engine_types.ts +5 -0
  76. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  77. package/src/engine-components/Duplicatable.ts +2 -2
  78. package/src/engine-components/EventTrigger.ts +14 -0
  79. package/src/engine-components/OrbitControls.ts +71 -21
  80. package/src/engine-components/Renderer.ts +5 -0
  81. package/src/engine-components/ScreenCapture.ts +2 -2
  82. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +0 -1
  83. package/src/engine-components/export/usdz/USDZExporter.ts +2 -2
  84. package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +17 -11
  85. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +41 -23
  86. package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +5 -2
  87. package/src/engine-components/ui/Button.ts +3 -3
  88. package/src/engine-components/ui/EventSystem.ts +45 -62
  89. package/src/engine-components/ui/InputField.ts +18 -1
  90. package/src/engine-components/utils/OpenURL.ts +2 -2
  91. package/src/engine-components/webxr/WebXRImageTracking.ts +5 -1
@@ -3,7 +3,7 @@ import { Intersection, Matrix4, Object3D, Ray, Vector2, Vector3 } from 'three';
3
3
  import { showBalloonMessage, showBalloonWarning } from './debug/debug.js';
4
4
  import { Context } from './engine_setup.js';
5
5
  import { getTempVector, getWorldQuaternion } from './engine_three_utils.js';
6
- import type { ButtonName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
6
+ import type { ButtonName, CursorTypeName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
7
7
  import { DeviceUtilities, type EnumToPrimitiveUnion, getParam } from './engine_utils.js';
8
8
 
9
9
  const debug = getParam("debuginput");
@@ -394,21 +394,46 @@ export class Input implements IInput {
394
394
  get mouseWheelChanged(): boolean { return this.getMouseWheelChanged(0); }
395
395
 
396
396
  /** Is the primary pointer double clicked (usually the left button). This is equivalent to `input.mouseDoubleClick` */
397
- get click() : boolean { return this._pointerClick[0]; }
397
+ get click(): boolean { return this._pointerClick[0]; }
398
398
  /** Was a double click detected for the primary pointer? */
399
- get doubleClick() : boolean { return this._pointerDoubleClick[0]; }
399
+ get doubleClick(): boolean { return this._pointerDoubleClick[0]; }
400
400
 
401
- private _specialCursorTrigger: number = 0;
401
+ private readonly _setCursorTypes: CursorTypeName[] = [];
402
402
 
403
+ /** @deprecated use setCursor("pointer") */
403
404
  setCursorPointer() {
404
- this._specialCursorTrigger += 1;
405
- this.context.domElement.style.cursor = "pointer";
405
+ this.setCursor("pointer");
406
406
  }
407
+ /** @deprecated use unsetCursor() */
407
408
  setCursorNormal() {
408
- this._specialCursorTrigger -= 1;
409
- this._specialCursorTrigger = Math.max(0, this._specialCursorTrigger);
410
- if (this._specialCursorTrigger === 0)
409
+ this.unsetCursor("pointer");
410
+ }
411
+ /**
412
+ * Set a custom cursor. This will set the cursor type until unsetCursor is called
413
+ */
414
+ setCursor(type: CursorTypeName) {
415
+ this._setCursorTypes.push(type);
416
+ if (this._setCursorTypes.length > 10) {
417
+ this._setCursorTypes.shift();
418
+ }
419
+ this.updateCursor();
420
+ }
421
+ /**
422
+ * Unset a custom cursor. This will set the cursor type to the previous type or default
423
+ */
424
+ unsetCursor(type: CursorTypeName) {
425
+ for (let i = this._setCursorTypes.length - 1; i >= 0; i--) {
426
+ if (this._setCursorTypes[i] === type) {
427
+ this._setCursorTypes.splice(i, 1);
428
+ this.updateCursor();
429
+ break;
430
+ }
431
+ }
432
+ }
433
+ private updateCursor() {
434
+ if (this._setCursorTypes?.length == 0)
411
435
  this.context.domElement.style.cursor = "default";
436
+ else this.context.domElement.style.cursor = this._setCursorTypes[this._setCursorTypes.length - 1];
412
437
  }
413
438
 
414
439
  /**
@@ -756,6 +781,10 @@ export class Input implements IInput {
756
781
  window.removeEventListener('pointerup', this.onPointerUp);
757
782
  window.removeEventListener('pointercancel', this.onPointerCancel);
758
783
 
784
+ window.removeEventListener("touchstart", this.onTouchStart);
785
+ window.removeEventListener("touchmove", this.onTouchMove);
786
+ window.removeEventListener("touchend", this.onTouchEnd);
787
+
759
788
  this._htmlEventSource?.removeEventListener('wheel', this.onMouseWheel, false);
760
789
  window.removeEventListener("wheel", this.onWheelWindow, false);
761
790
 
@@ -778,7 +807,10 @@ export class Input implements IInput {
778
807
  }
779
808
  }
780
809
 
810
+ private readonly _receivedPointerMoveEventsThisFrame = new Array<number>;
811
+
781
812
  private onEndOfFrame = () => {
813
+ this._receivedPointerMoveEventsThisFrame.length = 0;
782
814
  for (let i = 0; i < this._pointerUp.length; i++)
783
815
  this._pointerUp[i] = false;
784
816
  for (let i = 0; i < this._pointerDown.length; i++)
@@ -901,8 +933,14 @@ export class Input implements IInput {
901
933
  }
902
934
  private onPointerMove = (evt: PointerEvent) => {
903
935
  if (this.context.isInAR) return;
936
+
937
+ // Prevent multiple pointerMove events per frame
938
+ if (this._receivedPointerMoveEventsThisFrame.includes(evt.pointerId)) return;
939
+ this._receivedPointerMoveEventsThisFrame.push(evt.pointerId);
940
+
904
941
  // We want to keep receiving move events until pointerUp and not stop handling events just because we're hovering over *some* HTML element
905
942
  // if (this.canReceiveInput(evt) === false) return;
943
+
906
944
  let button = evt.button;
907
945
  if (evt.pointerType === "mouse") {
908
946
  const pressedButton = this.getFirstPressedButtonForPointer(0);
@@ -23,26 +23,22 @@ function ensureLoaders() {
23
23
  export function setDracoDecoderPath(path: string | undefined) {
24
24
  if (path !== undefined && typeof path === "string") {
25
25
  setDracoDecoderLocation(path);
26
- const loaders = ensureLoaders();
27
- if (debug) console.log("Setting draco decoder path to", path);
28
- loaders.dracoLoader.setDecoderPath(path);
29
26
  }
30
27
  }
31
28
 
32
29
  export function setDracoDecoderType(type: string | undefined) {
33
30
  if (type !== undefined && typeof type === "string") {
34
- const loaders = ensureLoaders();
35
- if (debug) console.log("Setting draco decoder type to", type);
36
- loaders.dracoLoader.setDecoderConfig({ type: type });
31
+ if (type !== "js") {
32
+ const loaders = ensureLoaders();
33
+ if (debug) console.log("Setting draco decoder type to", type);
34
+ loaders.dracoLoader.setDecoderConfig({ type: type });
35
+ }
37
36
  }
38
37
  }
39
38
 
40
39
  export function setKtx2TranscoderPath(path: string) {
41
40
  if (path !== undefined && typeof path === "string") {
42
41
  setKTX2TranscoderLocation(path);
43
- const loaders = ensureLoaders();
44
- if (debug) console.log("Setting ktx2 transcoder path to", path);
45
- loaders.ktx2Loader.setTranscoderPath(path);
46
42
  }
47
43
  }
48
44
 
@@ -78,7 +74,7 @@ export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Pick<Context
78
74
  if (!(loader as any).meshoptDecoder)
79
75
  loader.setMeshoptDecoder(loaders.meshoptDecoder);
80
76
 
81
- configureLoader(loader, {
77
+ configureLoader(loader, {
82
78
  progressive: true,
83
79
  });
84
80
 
@@ -710,7 +710,7 @@ export class RapierPhysics implements IPhysicsEngine {
710
710
  positions = this._meshCache.get(key)!;
711
711
  }
712
712
  else {
713
- if (debugPhysics || isDevEnvironment()) console.warn(`Your MeshCollider \"${collider.name}\" is scaled: consider applying the scale to the collider mesh instead (${scale.x}, ${scale.y}, ${scale.z})`);
713
+ if (debugPhysics || isDevEnvironment()) console.debug(`[Performance] Your MeshCollider \"${collider.name}\" is scaled: consider applying the scale to the collider mesh instead (${scale.x}, ${scale.y}, ${scale.z})`);
714
714
  const scaledPositions = new Float32Array(positions.length);
715
715
  for (let i = 0; i < positions.length; i += 3) {
716
716
  scaledPositions[i] = positions[i] * scale.x;
@@ -1,8 +1,8 @@
1
- import { AnimationClip, Material, Mesh, Object3D, Texture } from "three";
1
+ import { AnimationClip, BufferGeometry, InstancedBufferGeometry, Material, Mesh, Object3D, Texture, WireframeGeometry } from "three";
2
2
  import { type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
3
3
 
4
4
  import { debugExtension } from "../engine/engine_default_parameters.js";
5
- import { addLog,LogType } from "./debug/debug_overlay.js";
5
+ import { addLog, LogType } from "./debug/debug_overlay.js";
6
6
  import { isLocalNetwork } from "./engine_networking_utils.js";
7
7
  import { Context } from "./engine_setup.js";
8
8
  import type { Constructor, ConstructorConcrete, SourceIdentifier } from "./engine_types.js";
@@ -515,8 +515,17 @@ function deserializeObjectWithType(data: any, typeOrConstructor: ConstructorConc
515
515
 
516
516
  // e.g. when @serializable(Texture) and the texture is already resolved via json pointer from gltf
517
517
  // then we dont need to do anything else
518
- if (!typeIsFunction && currentValue instanceof type) return currentValue;
519
-
518
+ if (!typeIsFunction && currentValue) {
519
+ if (currentValue instanceof Material) return currentValue;
520
+ if (currentValue instanceof Texture) return currentValue;
521
+ if (currentValue instanceof Mesh) return currentValue;
522
+ if (currentValue instanceof BufferGeometry) return currentValue;
523
+ if (currentValue instanceof AnimationClip) return currentValue;
524
+ }
525
+ // Removed this line because it prevents assigning serialized values to existing instances for e.g. EventList
526
+ // https://linear.app/needle/issue/NE-5350
527
+ // if (!typeIsFunction && currentValue instanceof type) return currentValue;
528
+
520
529
  // try to resolve the serializer for a type only once
521
530
  if (!typeContext) {
522
531
  typeContext = {
@@ -528,6 +528,11 @@ export interface IPhysicsEngine {
528
528
  debugRenderRaycasts: boolean;
529
529
  }
530
530
 
531
+ /**
532
+ * Available cursor types
533
+ * @link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
534
+ */
535
+ export type CursorTypeName = "auto" | "default" | "none" | "context-menu" | "help" | "pointer" | "progress" | "wait" | "cell" | "crosshair" | "text" | "vertical-text" | "alias" | "copy" | "move" | "no-drop" | "not-allowed" | "grab" | "grabbing" | "all-scroll" | "col-resize" | "row-resize" | "n-resize" | "e-resize" | "s-resize" | "w-resize" | "nw-resize" | "se-resize" | "sw-resize" | "ew-resize" | "ns-resize" | "nesw-resize" | "nwse-resize" | "zoom-in" | "zoom-out";
531
536
 
532
537
  /** Typical mouse button names for most devices */
533
538
  export type MouseButtonName = "left" | "right" | "middle";
@@ -548,14 +548,14 @@ class OnClick implements Pick<IComponent, "__internalAwake"> {
548
548
  }
549
549
 
550
550
  onPointerEnter() {
551
- this.context.input.setCursorPointer();
551
+ this.context.input.setCursor("pointer");
552
552
  if (this.allowModifyUI) {
553
553
  this.element.set({ backgroundOpacity: 1 });
554
554
  ThreeMeshUI.update();
555
555
  }
556
556
  }
557
557
  onPointerExit() {
558
- this.context.input.setCursorNormal();
558
+ this.context.input.unsetCursor("pointer");
559
559
  if (this.allowModifyUI) {
560
560
  this.element.set({ backgroundOpacity: 0 });
561
561
  ThreeMeshUI.update();
@@ -105,14 +105,14 @@ export class Duplicatable extends Behaviour implements IPointerEventHandler {
105
105
  if (!this.object) return;
106
106
  if (!this.context.connection.allowEditing) return;
107
107
  if (args.button !== 0) return;
108
- this.context.input.setCursorPointer();
108
+ this.context.input.setCursor("pointer");
109
109
  }
110
110
  onPointerExit(args: PointerEventData) {
111
111
  if (args.used) return;
112
112
  if (!this.object) return;
113
113
  if (!this.context.connection.allowEditing) return;
114
114
  if (args.button !== 0) return;
115
- this.context.input.setCursorNormal();
115
+ this.context.input.unsetCursor("pointer");
116
116
  }
117
117
 
118
118
  /** @internal */
@@ -1,4 +1,5 @@
1
1
  import { serializable } from "../engine/engine_serialization.js";
2
+ import { EventSystem } from "./api.js";
2
3
  import { Behaviour } from "./Component.js"
3
4
  import { EventList } from "./EventList.js";
4
5
  import { EventType } from "./EventType.js"
@@ -33,6 +34,13 @@ export class EventTrigger extends Behaviour implements IPointerEventHandler {
33
34
  }
34
35
  }
35
36
 
37
+ private hasTrigger(type: EventType) {
38
+ return this.triggers?.some(t => t.eventID === type) ?? false;
39
+ }
40
+ private shouldChangeCursor() {
41
+ return this.hasTrigger(EventType.PointerClick) || this.hasTrigger(EventType.PointerDown) || this.hasTrigger(EventType.PointerUp);
42
+ }
43
+
36
44
  /** @internal */
37
45
  onPointerClick(_: PointerEventData) {
38
46
  this.invoke(EventType.PointerClick);
@@ -40,11 +48,17 @@ export class EventTrigger extends Behaviour implements IPointerEventHandler {
40
48
 
41
49
  /** @internal */
42
50
  onPointerEnter(_: PointerEventData) {
51
+ if (this.shouldChangeCursor()) {
52
+ this.context.input.setCursor("pointer");
53
+ }
43
54
  this.invoke(EventType.PointerEnter);
44
55
  }
45
56
 
46
57
  /** @internal */
47
58
  onPointerExit(_: PointerEventData) {
59
+ if (this.shouldChangeCursor()) {
60
+ this.context.input.unsetCursor("pointer");
61
+ }
48
62
  this.invoke(EventType.PointerExit);
49
63
  }
50
64
 
@@ -1,13 +1,14 @@
1
- import { Box3Helper, Object3D, PerspectiveCamera, Ray, Vector2, Vector3, Vector3Like } from "three";
1
+ import { Box3Helper, Euler, Object3D, PerspectiveCamera, Quaternion, Ray, Vector2, Vector3, Vector3Like } from "three";
2
2
  import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
3
3
 
4
+ import { isDevEnvironment } from "../engine/debug/index.js";
4
5
  import { setCameraController } from "../engine/engine_camera.js";
5
6
  import { Gizmos } from "../engine/engine_gizmos.js";
6
7
  import { InputEventQueue, NEPointerEvent } from "../engine/engine_input.js";
7
8
  import { Mathf } from "../engine/engine_math.js";
8
9
  import { RaycastOptions } from "../engine/engine_physics.js";
9
10
  import { serializable } from "../engine/engine_serialization_decorator.js";
10
- import { getBoundingBox, getWorldDirection, getWorldPosition, getWorldRotation, setWorldRotation } from "../engine/engine_three_utils.js";
11
+ import { getBoundingBox, getTempVector, getWorldDirection, getWorldPosition, getWorldRotation, setWorldRotation } from "../engine/engine_three_utils.js";
11
12
  import type { ICameraController } from "../engine/engine_types.js";
12
13
  import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
13
14
  import { Camera } from "./Camera.js";
@@ -616,14 +617,38 @@ export class OrbitControls extends Behaviour implements ICameraController {
616
617
 
617
618
 
618
619
  /**
619
- * Sets camera target position and look direction. Does perform a raycast in the forward direction of the passed in object to find an orbit point
620
+ * Sets camera target position and look direction using a raycast in forward direction of the object.
621
+ *
622
+ * @param source The object to raycast from. If a camera is passed in the camera position will be used as the source.
623
+ * @param immediateOrDuration If true the camera target will move immediately to the new position, otherwise it will lerp. If a number is passed in it will be used as the duration of the lerp.
624
+ *
625
+ * This is useful for example if you want to align your camera with an object in your scene (or another camera). Simply pass in this other camera object
626
+ * @returns true if the target was set successfully
620
627
  */
621
- public setCameraAndLookTarget(target: Object3D) {
622
- if (!target || !(target instanceof Object3D)) return;
623
- const worldPosition = getWorldPosition(target);
624
- const forward = getWorldDirection(target);
625
- this.setTargetFromRaycast(new Ray(worldPosition, forward));
626
- this.setCameraTargetPosition(worldPosition);
628
+ public setCameraAndLookTarget(source: Object3D | Camera, immediateOrDuration: number | boolean = false): boolean {
629
+ if (!source) {
630
+ if (isDevEnvironment()) console.warn("[OrbitControls] setCameraAndLookTarget target is null");
631
+ return false;
632
+ }
633
+ if (!(source instanceof Object3D) && !(source instanceof Camera)) {
634
+ if (isDevEnvironment()) console.warn("[OrbitControls] setCameraAndLookTarget target is not an Object3D or Camera");
635
+ return false;
636
+ }
637
+ if (source instanceof Camera) {
638
+ source = source.gameObject;
639
+ }
640
+ const worldPosition = source.worldPosition;
641
+ const forward = source.worldForward;
642
+ const ray = new Ray(worldPosition, forward.multiplyScalar(-1));
643
+
644
+ if (debug) Gizmos.DrawRay(ray.origin, ray.direction, 0xff0000, 10);
645
+
646
+ if (!this.setTargetFromRaycast(ray, immediateOrDuration)) {
647
+ this.setLookTargetPosition(ray.at(2, getTempVector()), immediateOrDuration);
648
+ }
649
+
650
+ this.setCameraTargetPosition(worldPosition, immediateOrDuration);
651
+ return true;
627
652
  }
628
653
 
629
654
  /** Moves the camera to position smoothly.
@@ -653,6 +678,38 @@ export class OrbitControls extends Behaviour implements ICameraController {
653
678
  else this._cameraLerpDuration = this.targetLerpDuration;
654
679
  }
655
680
  }
681
+ // public setCameraTargetRotation(rotation: Vector3 | Euler | Quaternion, immediateOrDuration: boolean | number = false): void {
682
+ // if (!this._cameraObject) return;
683
+
684
+ // if (typeof immediateOrDuration === "boolean") immediateOrDuration = immediateOrDuration ? 0 : this.targetLerpDuration;
685
+
686
+ // const ray = new Ray(this._cameraObject.worldPosition, getTempVector(0, 0, 1));
687
+
688
+ // // if the camera is in the middle of lerping we use the end position for the raycast
689
+ // if (immediateOrDuration > 0 && this._cameraEndPosition && this._cameraLerpActive) {
690
+ // ray.origin = getTempVector(this._cameraEndPosition)
691
+ // }
692
+
693
+ // if (rotation instanceof Vector3) {
694
+ // rotation = new Euler().setFromVector3(rotation);
695
+ // }
696
+ // if (rotation instanceof Euler) {
697
+ // rotation = new Quaternion().setFromEuler(rotation);
698
+ // }
699
+
700
+ // ray.direction.applyQuaternion(rotation);
701
+ // ray.direction.multiplyScalar(-1);
702
+
703
+ // const hits = this.context.physics.raycastFromRay(ray);
704
+
705
+ // if (hits.length > 0) {
706
+ // this.setCameraTargetPosition(hits[0].point, immediateOrDuration);
707
+ // }
708
+ // else {
709
+ // this.setLookTargetPosition(ray.at(2, getTempVector()));
710
+ // }
711
+ // }
712
+
656
713
  /** True while the camera position is being lerped */
657
714
  get cameraLerpActive() { return this._cameraLerpActive; }
658
715
  /** Call to stop camera position lerping */
@@ -747,8 +804,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
747
804
  else this._controls.target.lerp(position, delta);
748
805
  }
749
806
 
750
- private setTargetFromRaycast(ray?: Ray) {
751
- if (!this.controls) return;
807
+ private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
808
+ if (!this.controls) return false;
752
809
  const rc = ray ? this.context.physics.raycastFromRay(ray) : this.context.physics.raycast();
753
810
  for (const hit of rc) {
754
811
  if (hit.distance > 0 && GameObject.isActiveInHierarchy(hit.object)) {
@@ -760,18 +817,11 @@ export class OrbitControls extends Behaviour implements ICameraController {
760
817
  break;
761
818
  }
762
819
  }
763
-
764
- this.setLookTargetPosition(hit.point);
765
-
766
- // if (this.context.mainCamera) {
767
- // const pos = getWorldPosition(this.context.mainCamera);
768
- // const cameraTarget = pos.clone().sub(this.controls.target).add(this._lookTargetEndPosition);
769
- // this._cameraObject?.parent?.worldToLocal(cameraTarget);
770
- // this.setCameraTargetPosition(cameraTarget);
771
- // }
772
- break;
820
+ this.setLookTargetPosition(hit.point, immediateOrDuration);
821
+ return true;
773
822
  }
774
823
  }
824
+ return false;
775
825
  }
776
826
 
777
827
  // Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
@@ -716,6 +716,11 @@ export class Renderer extends Behaviour implements IRenderer {
716
716
  const factor = this.hasLightmap ? Math.PI : 1;
717
717
  const environmentIntensity = this.context.mainCameraComponent?.environmentIntensity ?? 1;
718
718
  material.envMapIntensity = Math.max(0, environmentIntensity * this.context.sceneLighting.environmentIntensity / factor);
719
+
720
+ // since three 163 we need to set the envMap to the scene envMap if it is not set
721
+ // otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903
722
+ // internal issue: https://linear.app/needle/issue/NE-6363
723
+ if (!material.envMap) material.envMap = this.context.scene.environment;
719
724
  }
720
725
 
721
726
  if (this._lightmaps) {
@@ -88,13 +88,13 @@ export class ScreenCapture extends Behaviour implements IPointerClickHandler {
88
88
  onPointerEnter() {
89
89
  if (this.context.connection.allowEditing == false) return;
90
90
  if (!this.allowStartOnClick) return;
91
- this.context.input.setCursorPointer();
91
+ this.context.input.setCursor("pointer");
92
92
  }
93
93
  /** @internal */
94
94
  onPointerExit() {
95
95
  if (this.context.connection.allowEditing == false) return;
96
96
  if (!this.allowStartOnClick) return;
97
- this.context.input.setCursorNormal();
97
+ this.context.input.unsetCursor("pointer");
98
98
  }
99
99
 
100
100
  /** @internal */
@@ -1966,7 +1966,6 @@ function buildMaterial( material: MeshBasicMaterial, textures: TextureMap, quick
1966
1966
 
1967
1967
  const materialName = getMaterialName(material);
1968
1968
 
1969
- console.log(material);
1970
1969
  // Special case: occluder material
1971
1970
  // Supported on iOS 18+ and visionOS 1+
1972
1971
  const isShadowCatcherMaterial =
@@ -403,7 +403,7 @@ export class USDZExporter extends Behaviour {
403
403
  if (this.autoExportAnimations) {
404
404
  implicitBehaviors.push(...registerAnimatorsImplictly(objectToExport, animExt));
405
405
  }
406
- const audioExt = this.extensions.find(ext => ext.extensionName === "Audio");
406
+ const audioExt = extensions.find(ext => ext.extensionName === "Audio");
407
407
  if (audioExt && this.autoExportAudioSources)
408
408
  implicitBehaviors.push(...registerAudioSourcesImplictly(objectToExport, audioExt as AudioExtension));
409
409
 
@@ -454,7 +454,7 @@ export class USDZExporter extends Behaviour {
454
454
  });
455
455
  }
456
456
 
457
- const behaviorExt = this.extensions.find(ext => ext.extensionName === "Behaviour") as BehaviorExtension | undefined;
457
+ const behaviorExt = extensions.find(ext => ext.extensionName === "Behaviour") as BehaviorExtension | undefined;
458
458
  if (this.interactive && behaviorExt && objectsToDisableAtSceneStart.length > 0) {
459
459
  behaviorExt.addBehavior(disableObjectsAtStart(objectsToDisableAtSceneStart));
460
460
  }
@@ -120,19 +120,21 @@ export class BehaviorExtension implements IUSDExporterExtension {
120
120
  if (actionModel.tokenId === "StartAnimation") {
121
121
  playAnimationActions.add(actionModel);
122
122
  }
123
+ let actionType = actionModel.tokenId;
124
+ if (actionModel.type !== undefined) actionType += ":" + actionModel.type;
123
125
  const affected = actionModel.affectedObjects;
124
126
  if (affected) {
125
127
  if (Array.isArray(affected)) {
126
128
  for (const a of affected) {
127
129
  actionTargets.add(a as Target);
128
130
  //@ts-ignore
129
- if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${actionModel.tokenId} --> ${a.uuid}(("${a.displayName || a.name || a.uuid}"))\n`;
131
+ if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${a.uuid}(("${a.displayName || a.name || a.uuid}"))\n`;
130
132
  }
131
133
  }
132
134
  else if (typeof affected === "object") {
133
135
  actionTargets.add(affected as Target);
134
136
  //@ts-ignore
135
- if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${actionModel.tokenId} --> ${affected.uuid}(("${affected.displayName || affected.name || affected.uuid}"))\n`;
137
+ if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${affected.uuid}(("${affected.displayName || affected.name || affected.uuid}"))\n`;
136
138
  }
137
139
  else if (typeof affected === "string") {
138
140
  actionTargets.add({uuid: affected} as any as Target);
@@ -144,7 +146,7 @@ export class BehaviorExtension implements IUSDExporterExtension {
144
146
  if (typeof xform === "object") {
145
147
  actionTargets.add(xform as Target);
146
148
  //@ts-ignore
147
- if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${actionModel.tokenId} --> ${xform.uuid}(("${xform.displayName || xform.name || xform.uuid}"))\n`;
149
+ if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${xform.uuid}(("${xform.displayName || xform.name || xform.uuid}"))\n`;
148
150
  }
149
151
  else if (typeof xform === "string") {
150
152
  actionTargets.add({uuid: xform} as any as Target);
@@ -159,19 +161,21 @@ export class BehaviorExtension implements IUSDExporterExtension {
159
161
  collectTrigger(t, action);
160
162
  }
161
163
  else if (trigger instanceof TriggerModel) {
164
+ let triggerType = trigger.tokenId;
165
+ if (trigger.type !== undefined) triggerType += ":" + trigger.type;
162
166
  if (typeof trigger.targetId === "object") {
163
167
  triggerSources.add(trigger.targetId as Target);
164
168
  //@ts-ignore
165
- if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${trigger.targetId.uuid}(("${trigger.targetId.displayName}")) --> ${trigger.id}[${trigger.id}]\n`;
169
+ if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${trigger.targetId.uuid}(("${trigger.targetId.displayName}")) --> ${trigger.id}[${trigger.id}\n${triggerType}]\n`;
166
170
  }
167
171
  //@ts-ignore
168
- if (createMermaidGraphForDebugging) mermaidGraph += `${trigger.id}((${trigger.id})) -- ${trigger.tokenId}${trigger.type ? ":" + trigger.type : ""} --> ${action.id}[${action.tokenId || action.id}]\n`;
172
+ if (createMermaidGraphForDebugging) mermaidGraph += `${trigger.id}((${trigger.id})) -- ${triggerType} --> ${action.id}[${action.tokenId || action.id}]\n`;
169
173
  }
170
174
  }
171
175
 
172
176
  // collect all targets of all triggers and actions
173
177
  for (const beh of this.behaviours) {
174
- if (createMermaidGraphForDebugging) mermaidGraph += `subgraph Behavior_${beh.id}\n`;
178
+ if (createMermaidGraphForDebugging) mermaidGraph += `subgraph ${beh.id}\n`;
175
179
  collectAction(beh.action);
176
180
  collectTrigger(beh.trigger, beh.action);
177
181
  if (createMermaidGraphForDebugging) mermaidGraph += `end\n`;
@@ -180,10 +184,12 @@ export class BehaviorExtension implements IUSDExporterExtension {
180
184
 
181
185
  if (createMermaidGraphForDebugging) {
182
186
  console.log("All USDZ behaviours", this.behaviours);
183
- console.warn("The Mermaid graph can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit. It should be in your clipboard already!");
184
- console.log(mermaidGraph);
185
- // copy to clipboard, can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit
186
- navigator.clipboard.writeText(mermaidGraph);
187
+ if (this.behaviours.length) {
188
+ console.warn("The Mermaid graph can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit. It should be in your clipboard already!");
189
+ console.log(mermaidGraph);
190
+ // copy to clipboard, can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit
191
+ navigator.clipboard.writeText(mermaidGraph);
192
+ }
187
193
  }
188
194
 
189
195
  {
@@ -212,7 +218,7 @@ export class BehaviorExtension implements IUSDExporterExtension {
212
218
  }
213
219
  }
214
220
 
215
- if (createMermaidGraphForDebugging) {
221
+ if (createMermaidGraphForDebugging && playAnimationActions.size) {
216
222
  console.log(animationsGraph);
217
223
  }
218
224