@needle-tools/engine 4.8.8 → 4.8.9-next.5967018

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 (161) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +50 -45
  3. package/components.needle.json +1 -1
  4. package/dist/gltf-progressive-DWiyqrwB.umd.cjs +8 -0
  5. package/dist/gltf-progressive-DhE1A6hX.min.js +8 -0
  6. package/dist/{gltf-progressive-BcHT3Nyo.js → gltf-progressive-egsMzRdv.js} +384 -369
  7. package/dist/{needle-engine.bundle--H5L0liy.min.js → needle-engine.bundle-BO4qGXtR.min.js} +148 -148
  8. package/dist/{needle-engine.bundle-C071tpzX.js → needle-engine.bundle-Cw3iV8mu.js} +8244 -7980
  9. package/dist/{needle-engine.bundle-CK8rTkFm.umd.cjs → needle-engine.bundle-Dco5pIbB.umd.cjs} +145 -145
  10. package/dist/needle-engine.d.ts +7 -0
  11. package/dist/needle-engine.js +305 -301
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/dist/{postprocessing-Ywv5oKkX.min.js → postprocessing-B_FzkwDz.min.js} +1 -1
  15. package/dist/{postprocessing-ORx-0eCx.js → postprocessing-DP1U_BpT.js} +2 -2
  16. package/dist/{postprocessing-CVb_x9YY.umd.cjs → postprocessing-DTX5VXrj.umd.cjs} +1 -1
  17. package/dist/rapier-BJaux8TQ.js +5217 -0
  18. package/dist/rapier-Bd0qRV1r.umd.cjs +1 -0
  19. package/dist/rapier-CnHGx3sO.min.js +1 -0
  20. package/dist/{three-Dceyffus.umd.cjs → three-BK56xWDs.umd.cjs} +24 -24
  21. package/dist/{three-BRSLmpyi.js → three-CsHK73Zc.js} +1 -0
  22. package/dist/{three-CsmWHVn7.min.js → three-TNFQHSFa.min.js} +25 -25
  23. package/dist/{three-examples-BX_Sktc9.min.js → three-examples-Bph291U2.min.js} +1 -1
  24. package/dist/{three-examples-CNexix3E.js → three-examples-BvMpKSun.js} +1 -1
  25. package/dist/{three-examples-DWxXVnws.umd.cjs → three-examples-C9WfZu-X.umd.cjs} +1 -1
  26. package/dist/{three-mesh-ui-gqAXlGNB.js → three-mesh-ui-CN6aRT7i.js} +1 -1
  27. package/dist/{three-mesh-ui-Cdh2iW8b.umd.cjs → three-mesh-ui-DnxkZWNA.umd.cjs} +1 -1
  28. package/dist/{three-mesh-ui-Bwy12Qvg.min.js → three-mesh-ui-n_qS2BM-.min.js} +1 -1
  29. package/dist/{vendor-xfQ8tKF3.umd.cjs → vendor-8le8g4MS.umd.cjs} +1 -1
  30. package/dist/{vendor-Z4SPrTcP.js → vendor-Msb9AgYE.js} +1 -1
  31. package/dist/{vendor-C43vobGc.min.js → vendor-yDFiCnCw.min.js} +1 -1
  32. package/lib/engine/codegen/register_types.js +4 -0
  33. package/lib/engine/codegen/register_types.js.map +1 -1
  34. package/lib/engine/engine_addressables.js +2 -0
  35. package/lib/engine/engine_addressables.js.map +1 -1
  36. package/lib/engine/engine_animation.js +4 -4
  37. package/lib/engine/engine_animation.js.map +1 -1
  38. package/lib/engine/engine_assetdatabase.js +6 -6
  39. package/lib/engine/engine_assetdatabase.js.map +1 -1
  40. package/lib/engine/engine_camera.d.ts +15 -2
  41. package/lib/engine/engine_camera.js +9 -2
  42. package/lib/engine/engine_camera.js.map +1 -1
  43. package/lib/engine/engine_context.d.ts +8 -2
  44. package/lib/engine/engine_context.js +21 -3
  45. package/lib/engine/engine_context.js.map +1 -1
  46. package/lib/engine/engine_create_objects.d.ts +3 -3
  47. package/lib/engine/engine_create_objects.js +5 -4
  48. package/lib/engine/engine_create_objects.js.map +1 -1
  49. package/lib/engine/engine_gameobject.js +2 -2
  50. package/lib/engine/engine_gameobject.js.map +1 -1
  51. package/lib/engine/engine_gltf_builtin_components.js +11 -11
  52. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  53. package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
  54. package/lib/engine/engine_loaders.callbacks.js +1 -0
  55. package/lib/engine/engine_loaders.callbacks.js.map +1 -1
  56. package/lib/engine/engine_physics_rapier.js +3 -2
  57. package/lib/engine/engine_physics_rapier.js.map +1 -1
  58. package/lib/engine/engine_three_utils.js +4 -2
  59. package/lib/engine/engine_three_utils.js.map +1 -1
  60. package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
  61. package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
  62. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  63. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  64. package/lib/engine/js-extensions/Object3D.d.ts +1 -1
  65. package/lib/engine/js-extensions/Vector.d.ts +5 -0
  66. package/lib/engine/js-extensions/Vector.js.map +1 -1
  67. package/lib/engine/js-extensions/index.d.ts +2 -1
  68. package/lib/engine/js-extensions/index.js +1 -1
  69. package/lib/engine/js-extensions/index.js.map +1 -1
  70. package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
  71. package/lib/engine/webcomponents/needle-engine.js +11 -18
  72. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  73. package/lib/engine/xr/NeedleXRController.js +30 -9
  74. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  75. package/lib/engine/xr/NeedleXRSession.js +18 -13
  76. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  77. package/lib/engine-components/Camera.d.ts +2 -0
  78. package/lib/engine-components/Camera.js +5 -1
  79. package/lib/engine-components/Camera.js.map +1 -1
  80. package/lib/engine-components/ContactShadows.d.ts +12 -2
  81. package/lib/engine-components/ContactShadows.js +24 -4
  82. package/lib/engine-components/ContactShadows.js.map +1 -1
  83. package/lib/engine-components/DropListener.d.ts +4 -3
  84. package/lib/engine-components/DropListener.js +4 -3
  85. package/lib/engine-components/DropListener.js.map +1 -1
  86. package/lib/engine-components/NestedGltf.d.ts +9 -4
  87. package/lib/engine-components/NestedGltf.js +32 -26
  88. package/lib/engine-components/NestedGltf.js.map +1 -1
  89. package/lib/engine-components/OrbitControls.d.ts +29 -6
  90. package/lib/engine-components/OrbitControls.js +45 -9
  91. package/lib/engine-components/OrbitControls.js.map +1 -1
  92. package/lib/engine-components/Renderer.js +2 -1
  93. package/lib/engine-components/Renderer.js.map +1 -1
  94. package/lib/engine-components/Skybox.js +8 -9
  95. package/lib/engine-components/Skybox.js.map +1 -1
  96. package/lib/engine-components/api.d.ts +2 -0
  97. package/lib/engine-components/api.js +1 -0
  98. package/lib/engine-components/api.js.map +1 -1
  99. package/lib/engine-components/codegen/components.d.ts +3 -0
  100. package/lib/engine-components/codegen/components.js +3 -0
  101. package/lib/engine-components/codegen/components.js.map +1 -1
  102. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +1 -1
  103. package/lib/engine-components/splines/Spline.d.ts +58 -0
  104. package/lib/engine-components/splines/Spline.js +270 -0
  105. package/lib/engine-components/splines/Spline.js.map +1 -0
  106. package/lib/engine-components/splines/SplineUtils.d.ts +12 -0
  107. package/lib/engine-components/splines/SplineUtils.js +33 -0
  108. package/lib/engine-components/splines/SplineUtils.js.map +1 -0
  109. package/lib/engine-components/splines/SplineWalker.d.ts +47 -0
  110. package/lib/engine-components/splines/SplineWalker.js +113 -0
  111. package/lib/engine-components/splines/SplineWalker.js.map +1 -0
  112. package/lib/engine-components/splines/index.d.ts +3 -0
  113. package/lib/engine-components/splines/index.js +4 -0
  114. package/lib/engine-components/splines/index.js.map +1 -0
  115. package/lib/engine-components/webxr/controllers/XRControllerModel.js +2 -2
  116. package/lib/engine-components/webxr/controllers/XRControllerModel.js.map +1 -1
  117. package/lib/engine-components/webxr/controllers/XRControllerMovement.js +4 -0
  118. package/lib/engine-components/webxr/controllers/XRControllerMovement.js.map +1 -1
  119. package/package.json +5 -5
  120. package/plugins/common/files.js +6 -3
  121. package/plugins/vite/alias.js +3 -2
  122. package/plugins/vite/editor-connection.js +4 -4
  123. package/src/engine/codegen/register_types.ts +4 -0
  124. package/src/engine/engine_addressables.ts +2 -0
  125. package/src/engine/engine_animation.ts +4 -4
  126. package/src/engine/engine_assetdatabase.ts +7 -7
  127. package/src/engine/engine_camera.ts +15 -3
  128. package/src/engine/engine_context.ts +22 -4
  129. package/src/engine/engine_create_objects.ts +8 -7
  130. package/src/engine/engine_gameobject.ts +2 -2
  131. package/src/engine/engine_gltf_builtin_components.ts +12 -11
  132. package/src/engine/engine_loaders.callbacks.ts +1 -0
  133. package/src/engine/engine_physics_rapier.ts +3 -2
  134. package/src/engine/engine_three_utils.ts +5 -2
  135. package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
  136. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  137. package/src/engine/js-extensions/Vector.ts +6 -0
  138. package/src/engine/js-extensions/index.ts +3 -2
  139. package/src/engine/webcomponents/needle-engine.ts +10 -17
  140. package/src/engine/xr/NeedleXRController.ts +39 -15
  141. package/src/engine/xr/NeedleXRSession.ts +18 -13
  142. package/src/engine-components/Camera.ts +7 -1
  143. package/src/engine-components/ContactShadows.ts +27 -6
  144. package/src/engine-components/DropListener.ts +4 -3
  145. package/src/engine-components/NestedGltf.ts +33 -24
  146. package/src/engine-components/OrbitControls.ts +67 -21
  147. package/src/engine-components/Renderer.ts +2 -1
  148. package/src/engine-components/Skybox.ts +9 -10
  149. package/src/engine-components/api.ts +3 -1
  150. package/src/engine-components/codegen/components.ts +3 -0
  151. package/src/engine-components/splines/Spline.ts +284 -0
  152. package/src/engine-components/splines/SplineUtils.ts +32 -0
  153. package/src/engine-components/splines/SplineWalker.ts +106 -0
  154. package/src/engine-components/splines/index.ts +3 -0
  155. package/src/engine-components/webxr/controllers/XRControllerModel.ts +3 -2
  156. package/src/engine-components/webxr/controllers/XRControllerMovement.ts +5 -1
  157. package/dist/gltf-progressive-CH3Q4H06.umd.cjs +0 -8
  158. package/dist/gltf-progressive-DR6HqF_h.min.js +0 -8
  159. package/dist/rapier--oeYP_h7.umd.cjs +0 -1
  160. package/dist/rapier-B3xpyPtq.js +0 -5142
  161. package/dist/rapier-CyWhltHY.min.js +0 -1
@@ -398,8 +398,8 @@ export class NeedleXRController implements IPointerHitEventReceiver {
398
398
  C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inputSource.hand ? "x" : "-"} Pen: ${this._isMxInk ? "x" : "-"}`;
399
399
  if (this.inputSource.hand) debugStr += `\nPinch: ${this.getGesture("pinch")?.value.toFixed(3)}`;
400
400
  debugStr += "\n" + profileStr;
401
- debugStr += "\n" + (this.inputSource.targetRaySpace ? `Ray: x` : "Ray: -") +
402
- (this.inputSource.gripSpace ? " Grip: x" : " Grip: -") +
401
+ debugStr += "\n" + (this.inputSource.targetRaySpace ? `Ray: x` : "Ray: -") +
402
+ (this.inputSource.gripSpace ? " Grip: x" : " Grip: -") +
403
403
  (this.inputSource.gamepad ? ` Gamepad: ${this.inputSource.gamepad.mapping}` : " Gamepad: -");
404
404
  if (this.inputSource.gamepad) {
405
405
  const gp = this.inputSource.gamepad;
@@ -415,7 +415,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
415
415
  this._handJointPoses.clear();
416
416
  this._hand_wristDotUp = undefined;
417
417
 
418
- if (!this.xr.referenceSpace) {
418
+ if (!this.xr.referenceSpace || !this.inputSource.gamepad?.connected) {
419
419
  this._isTracking = false;
420
420
  return;
421
421
  }
@@ -460,7 +460,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
460
460
 
461
461
  // update controller object parent – needs to be parented to the rig, which
462
462
  // implicitly is the same object as the camera parent.
463
- if (this.xr.context.mainCamera?.parent) {
463
+ if (this.xr.context.mainCamera?.parent) {
464
464
  if (this._object.parent !== this.xr.context.mainCamera?.parent)
465
465
  this.xr.context.mainCamera.parent.add(this._object);
466
466
  if (this._gripSpaceObject !== undefined && this._gripSpaceObject?.parent !== this.xr.context.mainCamera?.parent)
@@ -594,6 +594,24 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
594
594
  return this.getGesture("pinch");
595
595
  }
596
596
  return this.toNeedleGamepadButton(0, key);
597
+
598
+ case "xr-standard-trigger":
599
+ if (this.inputSource.gamepad) {
600
+ return this.toNeedleGamepadButton(0, key);
601
+ }
602
+ break;
603
+
604
+ case "xr-standard-squeeze":
605
+ if (this.inputSource.gamepad) {
606
+ return this.toNeedleGamepadButton(1, key);
607
+ }
608
+ break;
609
+
610
+ case "xr-standard-thumbstick":
611
+ if (this.inputSource.gamepad) {
612
+ return this.toNeedleGamepadButton(3, key);
613
+ }
614
+ break;
597
615
  }
598
616
 
599
617
 
@@ -694,12 +712,18 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
694
712
  getStick(key: StickName | "primary"): Vec3 {
695
713
  if (!this._layout) return { x: 0, y: 0, z: 0 };
696
714
 
715
+ // Hands dont have thumbsicks
716
+ if (this.isHand) {
717
+ return { x: 0, y: 0, z: 0 };
718
+ }
719
+
697
720
  if (key === "primary") {
698
- const x = this.inputSource.gamepad?.axes[0] || 0;
699
- const y = this.inputSource.gamepad?.axes[1] || 0;
700
- // the primary thumbstick is button 3 (see gamepads module explainer)
701
- const z = this.inputSource.gamepad?.buttons[3]?.value || 0;
702
- return { x, y, z }
721
+ if (this._layout.components["xr-standard-thumbstick"]) key = "xr-standard-thumbstick";
722
+ // const x = this.inputSource.gamepad?.axes[0] || 0;
723
+ // const y = this.inputSource.gamepad?.axes[1] || 0;
724
+ // // the primary thumbstick is button 3 (see gamepads module explainer)
725
+ // const z = this.inputSource.gamepad?.buttons[3]?.value || 0;
726
+ // return { x, y, z }
703
727
  }
704
728
 
705
729
  const componentModel = this._layout?.components[key];
@@ -709,12 +733,12 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
709
733
  if (this.inputSource.gamepad) {
710
734
  const xIndex = componentModel.gamepadIndices!.xAxis!;
711
735
  const yIndex = componentModel.gamepadIndices!.yAxis!;
712
- let x = this.inputSource.gamepad?.axes[xIndex];
713
- let y = this.inputSource.gamepad?.axes[yIndex];
736
+ let x = this.inputSource.gamepad.axes[xIndex] || 0;
737
+ let y = this.inputSource.gamepad.axes[yIndex] || 0;
714
738
  x *= -1;
715
739
  y *= -1;
716
740
  const buttonIndex = componentModel.gamepadIndices!.button!;
717
- const z = this.inputSource.gamepad?.buttons[buttonIndex]?.value;
741
+ const z = this.inputSource.gamepad?.buttons[buttonIndex]?.value || 0;
718
742
  return { x, y, z }
719
743
  }
720
744
  }
@@ -731,13 +755,13 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
731
755
  private initialize() {
732
756
  // WORKAROUND for hand controllers that don't have a select event
733
757
  this._hasSelectEvent = this.profiles.includes("generic-hand-select") || this.profiles.some(p => p.startsWith("generic-trigger"));
734
-
758
+
735
759
  // Used to determine special layout for Quest controllers, e.g. last button is menu button
736
760
  this._isMetaQuestTouchController = this.profiles.includes("meta-quest-touch-plus") || this.profiles.includes("oculus-touch-v3");
737
761
 
738
762
  // Proper profile starting with v69 and browser 35.1
739
763
  this._isMxInk = this.profiles.includes("logitech-mx-ink")
740
-
764
+
741
765
  if (!this._layout) {
742
766
  // Ignore transient-pointer since we likely don't want to spawn a controller visual just for a temporary pointer.
743
767
  // TODO we should check how this is actually handled on Quest Browser when the transient-pointer flag is on.
@@ -953,7 +977,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
953
977
  if (menuButtonState) {
954
978
  if (menuButtonState.isDown) {
955
979
  const menu = this.context.menu;
956
- if (menu.spatialMenuIsVisible)
980
+ if (menu.spatialMenuIsVisible)
957
981
  menu.setSpatialMenuVisible(false);
958
982
  else
959
983
  this.context.menu.setSpatialMenuVisible(true);
@@ -530,7 +530,7 @@ export class NeedleXRSession implements INeedleXRSession {
530
530
  listener({ mode, init });
531
531
  }
532
532
  if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
533
- this._currentSessionRequest = navigator.xr?.requestSession(mode, init);
533
+ this._currentSessionRequest = navigator?.xr?.requestSession(mode, init);
534
534
  this._currentSessionRequestMode = mode;
535
535
  /**@type {XRSystem} */
536
536
  const newSession = await (this._currentSessionRequest)?.catch(e => {
@@ -1053,10 +1053,13 @@ export class NeedleXRSession implements INeedleXRSession {
1053
1053
 
1054
1054
  /** Disconnects the controller, invokes events and notifies previou controller (if any) */
1055
1055
  private disconnectInputSource(inputSource: XRInputSource) {
1056
- const handleRemove = (oldController: NeedleXRController, _array: Array<NeedleXRController>, i: number) => {
1056
+ const handleRemove = (oldController: NeedleXRController, array: Array<NeedleXRController>) => {
1057
1057
  if (oldController.inputSource === inputSource) {
1058
1058
  if (debug) console.log("Disconnecting controller", oldController.index);
1059
- this.controllers.splice(i, 1);
1059
+
1060
+ const index = array.indexOf(oldController);
1061
+ if(index >= 0) array.splice(index, 1);
1062
+
1060
1063
  this.invokeControllerEvent(oldController, this._controllerRemoved, "removed");
1061
1064
  const args: NeedleXRControllerEventArgs = {
1062
1065
  xr: this,
@@ -1069,13 +1072,15 @@ export class NeedleXRSession implements INeedleXRSession {
1069
1072
  oldController.onDisconnected();
1070
1073
  }
1071
1074
  }
1072
- for (let i = this.controllers.length - 1; i >= 0; i--) {
1073
- const oldController = this.controllers[i];
1074
- handleRemove(oldController, this.controllers, i);
1075
+ const copy = [...this.controllers];
1076
+ for (let i = copy.length - 1; i >= 0; i--) {
1077
+ const oldController = copy[i];
1078
+ handleRemove(oldController, this.controllers);
1075
1079
  }
1076
- for (let i = this._newControllers.length - 1; i >= 0; i--) {
1077
- const oldController = this._newControllers[i];
1078
- handleRemove(oldController, this._newControllers, i);
1080
+ const copyNew = [...this._newControllers];
1081
+ for (let i = copyNew.length - 1; i >= 0; i--) {
1082
+ const oldController = copyNew[i];
1083
+ handleRemove(oldController, this._newControllers);
1079
1084
  }
1080
1085
  }
1081
1086
 
@@ -1130,8 +1135,8 @@ export class NeedleXRSession implements INeedleXRSession {
1130
1135
  for (let i = 0; i < copy.length; i++) {
1131
1136
  this.disconnectInputSource(copy[i].inputSource);
1132
1137
  }
1133
- this._newControllers.length = 0;
1134
1138
  this.controllers.length = 0;
1139
+ this._newControllers.length = 0;
1135
1140
 
1136
1141
  // we want to call leave XR for *all* scripts that are still registered
1137
1142
  // even if they might already be destroyed e.g. by the WebXR component (it destroys the default controller scripts)
@@ -1318,8 +1323,6 @@ export class NeedleXRSession implements INeedleXRSession {
1318
1323
  continue;
1319
1324
  }
1320
1325
  this.invokeCallback_ControllerAdded(script, controller);
1321
- // if (script.onXRControllerAdded)
1322
- // script.onXRControllerAdded({ xr: this, controller, change: "added" });
1323
1326
  }
1324
1327
  }
1325
1328
  this.controllers.sort((a, b) => a.index - b.index);
@@ -1558,9 +1561,10 @@ export class NeedleXRSession implements INeedleXRSession {
1558
1561
  const rigWorldScale = getWorldScale(this.rig.gameObject);
1559
1562
  minNearPlane *= rigWorldScale.x;
1560
1563
  }
1561
- if (this._camera instanceof PerspectiveCamera && this._camera.near > minNearPlane) {
1564
+ if (this._camera instanceof PerspectiveCamera && Math.abs(this._camera.near - minNearPlane) > .0001) {
1562
1565
  this.originalCameraNearPlane = this._camera.near;
1563
1566
  this._camera.near = minNearPlane;
1567
+ if(debug) console.debug(`Setting camera near plane to ${minNearPlane} (was ${this.originalCameraNearPlane}) to account for XR rendering scale`);
1564
1568
  }
1565
1569
  }
1566
1570
  }
@@ -1572,6 +1576,7 @@ export class NeedleXRSession implements INeedleXRSession {
1572
1576
 
1573
1577
  if (this._camera instanceof PerspectiveCamera && this.originalCameraNearPlane != undefined) {
1574
1578
  this._camera.near = this.originalCameraNearPlane;
1579
+ this.originalCameraNearPlane = undefined;
1575
1580
  }
1576
1581
  }
1577
1582
 
@@ -9,6 +9,7 @@ import { RenderTexture } from "../engine/engine_texture.js";
9
9
  import { getTempColor, getWorldPosition } from "../engine/engine_three_utils.js";
10
10
  import type { ICamera } from "../engine/engine_types.js"
11
11
  import { getParam } from "../engine/engine_utils.js";
12
+ import { NeedleXREventArgs } from "../engine/engine_xr.js";
12
13
  import { RGBAColor } from "../engine/js-extensions/index.js";
13
14
  import { Behaviour, GameObject } from "./Component.js";
14
15
  import { OrbitControls } from "./OrbitControls.js";
@@ -446,6 +447,11 @@ export class Camera extends Behaviour implements ICamera {
446
447
  this.context.removeCamera(this);
447
448
  }
448
449
 
450
+ onLeaveXR(_args: NeedleXREventArgs): void {
451
+ // Restore previous FOV
452
+ this.fieldOfView = this._fov;
453
+ }
454
+
449
455
 
450
456
  /** @internal */
451
457
  onBeforeRender() {
@@ -553,7 +559,7 @@ export class Camera extends Behaviour implements ICamera {
553
559
  }
554
560
 
555
561
  // restore previous fov (e.g. when user was in VR or AR and the camera's fov has changed)
556
- this.fieldOfView = this._fov;
562
+ this.fieldOfView = this.fieldOfView;
557
563
 
558
564
  if (debug) {
559
565
  const msg = `[Camera] Apply ClearFlags: ${ClearFlags[this._clearFlags]} - \"${this.name}\"`;
@@ -1,4 +1,4 @@
1
- import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, WebGLRenderTarget } from "three";
1
+ import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, Vector3Like, WebGLRenderTarget } from "three";
2
2
  import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
3
3
  import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
4
4
 
@@ -27,7 +27,14 @@ onStart(ctx => {
27
27
  shadows.darkness = intensity;
28
28
  }
29
29
  }
30
- })
30
+ });
31
+
32
+
33
+ type FitParameters = {
34
+ object?: Object3D | Object3D[];
35
+ positionOffset?: Partial<Vector3Like>;
36
+ scaleFactor?: Partial<Vector3Like>;
37
+ }
31
38
 
32
39
  // Adapted from https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html.
33
40
 
@@ -50,7 +57,7 @@ export class ContactShadows extends Behaviour {
50
57
  * @param context The context to create the contact shadows in.
51
58
  * @returns The instance of the contact shadows.
52
59
  */
53
- static auto(context?: Context): ContactShadows {
60
+ static auto(context?: Context, params?: FitParameters): ContactShadows {
54
61
  if (!context) context = Context.Current;
55
62
  if (!context) {
56
63
  throw new Error("No context provided and no current context set.");
@@ -65,12 +72,13 @@ export class ContactShadows extends Behaviour {
65
72
  this._instances.set(context, instance);
66
73
  }
67
74
  context.scene.add(instance.gameObject);
68
- instance.fitShadows();
75
+ instance.fitShadows(params);
69
76
  return instance;
70
77
  }
71
78
 
72
79
  /**
73
80
  * When enabled the contact shadows component will be created to fit the whole scene.
81
+ * @default false
74
82
  */
75
83
  @serializable()
76
84
  autoFit: boolean = false;
@@ -107,12 +115,14 @@ export class ContactShadows extends Behaviour {
107
115
 
108
116
  /**
109
117
  * The minimum size of the shadows box
118
+ * @default undefined
110
119
  */
111
120
  minSize?: Partial<Vec3>;
112
121
 
113
122
  /**
114
123
  * When enabled the shadows will not be updated automatically. Use `needsUpdate()` to update the shadows manually.
115
124
  * This is useful when you want to update the shadows only when the scene changes.
125
+ * @default false
116
126
  */
117
127
  manualUpdate: boolean = false;
118
128
  /**
@@ -150,10 +160,11 @@ export class ContactShadows extends Behaviour {
150
160
  /**
151
161
  * Call to fit the shadows to the scene.
152
162
  */
153
- fitShadows() {
163
+ fitShadows(params: FitParameters = {}) {
154
164
  if (debug) console.warn("Fitting shadows to scene");
155
165
  setAutoFitEnabled(this.shadowsRoot, false);
156
- const box = getBoundingBox(this.context.scene.children, [this.shadowsRoot]);
166
+ const objectToFit = params.object || this.context.scene;
167
+ const box = getBoundingBox(objectToFit, [this.shadowsRoot]);
157
168
  // expand box in all directions (except below ground)
158
169
  // 0.75 expands by 75% in each direction
159
170
  // The "32" is pretty much heuristically determined – adjusting the value until we don't get a visible border anymore.
@@ -175,6 +186,16 @@ export class ContactShadows extends Behaviour {
175
186
  // We can't move GroundProjection down because of immersive-ar mesh/plane tracking where occlusion would otherwise hide GroundProjection
176
187
  this.shadowsRoot.position.set((min.x + box.max.x) / 2, min.y - offset, (min.z + box.max.z) / 2);
177
188
  this.shadowsRoot.scale.set(box.max.x - min.x, box.max.y - min.y, box.max.z - min.z);
189
+ if (params.positionOffset) {
190
+ if (params.positionOffset.x !== undefined) this.shadowsRoot.position.x += params.positionOffset.x;
191
+ if (params.positionOffset.y !== undefined) this.shadowsRoot.position.y += params.positionOffset.y;
192
+ if (params.positionOffset.z !== undefined) this.shadowsRoot.position.z += params.positionOffset.z;
193
+ }
194
+ if (params.scaleFactor) {
195
+ if (params.scaleFactor.x !== undefined) this.shadowsRoot.scale.x *= params.scaleFactor.x;
196
+ if (params.scaleFactor.y !== undefined) this.shadowsRoot.scale.y *= params.scaleFactor.y;
197
+ if (params.scaleFactor.z !== undefined) this.shadowsRoot.scale.z *= params.scaleFactor.z;
198
+ }
178
199
  this.applyMinSize();
179
200
  this.shadowsRoot.matrixWorldNeedsUpdate = true;
180
201
  if (debug) console.log("Fitted shadows to scene", this.shadowsRoot.scale.clone());
@@ -399,10 +399,11 @@ export class DropListener extends Behaviour {
399
399
  private _abort: AbortController | null = null;
400
400
 
401
401
  /**
402
- * Processes dropped files, loads them as 3D models, and handles networking if enabled
403
- * Creates an abort controller to cancel previous uploads if new files are dropped
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.
404
405
  * @param fileList Array of dropped files
405
- * @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
406
407
  */
407
408
  private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
408
409
  if (debug) console.log("Add files", fileList)
@@ -4,6 +4,7 @@ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.j
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
5
5
  import { getParam } from "../engine/engine_utils.js";
6
6
  import { Behaviour } from "../engine-components/Component.js";
7
+ import { EventList } from "./EventList.js";
7
8
 
8
9
  const debug = getParam("debugnestedgltf");
9
10
 
@@ -11,17 +12,21 @@ const debug = getParam("debugnestedgltf");
11
12
  * It will load the gltf file and instantiate it as a child of the parent of the GameObject that has this component
12
13
  */
13
14
  export class NestedGltf extends Behaviour {
14
- /**
15
- * A reference to the gltf file that should be loaded
16
- */
15
+
16
+ /** A reference to the gltf file that should be loaded */
17
17
  @serializable(AssetReference)
18
18
  filePath?: AssetReference;
19
19
 
20
+ /** Invoked when the nested glTF file has been instantiated */
21
+ @serializable(EventList)
22
+ loaded: EventList<{ component: NestedGltf, instance: any, asset: AssetReference }> = new EventList();
23
+
20
24
  /**
21
25
  * EXPERIMENTAL for cloud asset loading
22
26
  */
23
27
  loadAssetInParent = true;
24
28
 
29
+
25
30
  private _isLoadingOrDoneLoading: boolean = false;
26
31
 
27
32
  /** Register a callback that will be called when the progress of the loading changes */
@@ -31,7 +36,7 @@ export class NestedGltf extends Behaviour {
31
36
 
32
37
  /** Begin loading the referenced gltf file in filePath */
33
38
  preload() {
34
- this.filePath?.preload();
39
+ return this.filePath?.preload() || null;
35
40
  }
36
41
 
37
42
  /** @internal */
@@ -41,27 +46,31 @@ export class NestedGltf extends Behaviour {
41
46
 
42
47
  const parent = this.gameObject.parent;
43
48
  if (parent) {
44
- this._isLoadingOrDoneLoading = true;
45
- const opts = new InstantiateOptions();
46
- // we need to provide stable guids for creating nested gltfs
47
- opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
48
- opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
49
- this.gameObject.updateMatrix();
50
- const matrix = this.gameObject.matrix;
51
- if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
52
- const res = await this.filePath?.instantiate?.call(this.filePath, opts);
53
- if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
54
- if (res && this.loadAssetInParent !== false) {
55
- res.matrixAutoUpdate = false;
56
- res.matrix.identity();
57
- res.applyMatrix4(matrix);
58
- res.matrixAutoUpdate = true;
59
- res.layers.disableAll();
60
- res.layers.set(this.layer);
61
-
62
- this.dispatchEvent(new CustomEvent("loaded", { detail: { instance: res, assetReference: this.filePath } }));
49
+
50
+ if (this.filePath) {
51
+
52
+ this._isLoadingOrDoneLoading = true;
53
+ const opts = new InstantiateOptions();
54
+ // we need to provide stable guids for creating nested gltfs
55
+ opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
56
+ opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
57
+ this.gameObject.updateMatrix();
58
+ const matrix = this.gameObject.matrix;
59
+ if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
60
+ const res = await this.filePath?.instantiate?.call(this.filePath, opts);
61
+ if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
62
+ if (res && this.loadAssetInParent !== false) {
63
+ res.matrixAutoUpdate = false;
64
+ res.matrix.identity();
65
+ res.applyMatrix4(matrix);
66
+ res.matrixAutoUpdate = true;
67
+ res.layers.disableAll();
68
+ res.layers.set(this.layer);
69
+ this.loaded.invoke({ component: this, instance: res, asset: this.filePath });
70
+ }
71
+ if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
63
72
  }
64
- if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
73
+
65
74
  }
66
75
  }
67
76
 
@@ -889,6 +889,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
889
889
  }
890
890
  else this._fovLerpDuration = this.targetLerpDuration;
891
891
  }
892
+
893
+ // if (this.context.mainCameraComponent) this.context.mainCameraComponent.fieldOfView = fov;
892
894
  }
893
895
 
894
896
  /** Moves the camera look-at target to a position smoothly.
@@ -984,22 +986,22 @@ 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
 
994
997
  let objects: Object3D | Array<Object3D> | undefined = undefined;
995
-
996
998
  // If the user passed in an array as first argument
997
999
  if (Array.isArray(objectsOrOptions)) {
998
1000
  objects = objectsOrOptions;
999
1001
  }
1000
1002
  // If the user passed in an object as first argument
1001
1003
  else if (objectsOrOptions && "type" in objectsOrOptions) {
1002
- objects = objectsOrOptions.children;
1004
+ objects = objectsOrOptions;
1003
1005
  }
1004
1006
  // If the user passed in an object as first argument and options as second argument
1005
1007
  else if (objectsOrOptions && typeof objectsOrOptions === "object") {
@@ -1008,12 +1010,10 @@ export class OrbitControls extends Behaviour implements ICameraController {
1008
1010
  objects = options.objects;
1009
1011
  }
1010
1012
  }
1011
-
1012
1013
  // Ensure objects are setup correctly
1013
1014
  if (objects && !Array.isArray(objects)) {
1014
- objects = objects.children;
1015
+ objects = [objects];
1015
1016
  }
1016
-
1017
1017
  if (!Array.isArray(objects) || objects && objects.length <= 0) {
1018
1018
  objects = this.context.scene.children;
1019
1019
  }
@@ -1032,7 +1032,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
1032
1032
  return;
1033
1033
  }
1034
1034
  if (!options) options = {}
1035
- const { immediate = false, centerCamera = "y", cameraNearFar = "auto", fitOffset = 1.1, fov = camera?.fov } = options;
1035
+ const { immediate = false, centerCamera, cameraNearFar = "auto", fitOffset = 1.1, fov = camera?.fov } = options;
1036
1036
  const size = new Vector3();
1037
1037
  const center = new Vector3();
1038
1038
  // TODO would be much better to calculate the bounds in camera space instead of world space -
@@ -1041,15 +1041,13 @@ export class OrbitControls extends Behaviour implements ICameraController {
1041
1041
  // and thus we're just getting some maximum that will work for sure.
1042
1042
  const box = getBoundingBox(objects, undefined, this._camera?.threeCamera?.layers);
1043
1043
  const boxCopy = box.clone();
1044
-
1045
- camera.updateMatrixWorld();
1046
- camera.updateProjectionMatrix();
1047
1044
  box.getCenter(center);
1048
1045
 
1049
1046
  const box_size = new Vector3();
1050
1047
  box.getSize(box_size);
1051
1048
 
1052
1049
  // project this box into camera space
1050
+ camera.updateMatrixWorld();
1053
1051
  box.applyMatrix4(camera.matrixWorldInverse);
1054
1052
 
1055
1053
  box.getSize(size);
@@ -1078,9 +1076,18 @@ export class OrbitControls extends Behaviour implements ICameraController {
1078
1076
  this.minZoom = distance * 0.01;
1079
1077
 
1080
1078
  const verticalOffset = 0.05;
1081
-
1082
1079
  const lookAt = center.clone();
1083
1080
  lookAt.y -= size.y * verticalOffset;
1081
+ if (options.targetOffset) {
1082
+ if (options.targetOffset.x !== undefined) lookAt.x += options.targetOffset.x;
1083
+ if (options.targetOffset.y !== undefined) lookAt.y += options.targetOffset.y;
1084
+ if (options.targetOffset.z !== undefined) lookAt.z += options.targetOffset.z;
1085
+ }
1086
+ if (options.relativeTargetOffset) {
1087
+ if (options.relativeTargetOffset.x !== undefined) lookAt.x += options.relativeTargetOffset.x * size.x;
1088
+ if (options.relativeTargetOffset.y !== undefined) lookAt.y += options.relativeTargetOffset.y * size.y;
1089
+ if (options.relativeTargetOffset.z !== undefined) lookAt.z += options.relativeTargetOffset.z * size.z;
1090
+ }
1084
1091
  this.setLookTargetPosition(lookAt, immediate);
1085
1092
  this.setFieldOfView(options.fov, immediate);
1086
1093
 
@@ -1092,6 +1099,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
1092
1099
  // TODO: this doesnt take the Camera component nearClipPlane into account
1093
1100
  camera.near = (distance / 100);
1094
1101
  camera.far = boundsMax + distance * 10;
1102
+ camera.updateProjectionMatrix();
1095
1103
 
1096
1104
  // adjust maxZoom so that the ground projection radius is always inside
1097
1105
  if (groundprojection) {
@@ -1104,12 +1112,13 @@ export class OrbitControls extends Behaviour implements ICameraController {
1104
1112
  if (currentZoom < this.minZoom) this.minZoom = currentZoom * 0.9;
1105
1113
  if (currentZoom > this.maxZoom) this.maxZoom = currentZoom * 1.1;
1106
1114
 
1107
- camera.updateMatrixWorld();
1108
- camera.updateProjectionMatrix();
1109
-
1110
- const cameraWp = getWorldPosition(camera);
1111
1115
  const direction = center.clone();
1112
- direction.sub(cameraWp);
1116
+ if (options.fitDirection) {
1117
+ direction.sub(new Vector3().copy(options.fitDirection).multiplyScalar(1_000_000));
1118
+ }
1119
+ else {
1120
+ direction.sub(camera.worldPosition);
1121
+ }
1113
1122
  if (centerCamera === "y")
1114
1123
  direction.y = 0;
1115
1124
  direction.normalize();
@@ -1118,6 +1127,16 @@ export class OrbitControls extends Behaviour implements ICameraController {
1118
1127
  direction.y += -verticalOffset * 4 * distance;
1119
1128
 
1120
1129
  let cameraLocalPosition = center.clone().sub(direction);
1130
+ if (options.cameraOffset) {
1131
+ if (options.cameraOffset.x !== undefined) cameraLocalPosition.x += options.cameraOffset.x;
1132
+ if (options.cameraOffset.y !== undefined) cameraLocalPosition.y += options.cameraOffset.y;
1133
+ if (options.cameraOffset.z !== undefined) cameraLocalPosition.z += options.cameraOffset.z;
1134
+ }
1135
+ if (options.relativeCameraOffset) {
1136
+ if (options.relativeCameraOffset.x !== undefined) cameraLocalPosition.x += options.relativeCameraOffset.x * size.x;
1137
+ if (options.relativeCameraOffset.y !== undefined) cameraLocalPosition.y += options.relativeCameraOffset.y * size.y;
1138
+ if (options.relativeCameraOffset.z !== undefined) cameraLocalPosition.z += options.relativeCameraOffset.z * size.z;
1139
+ }
1121
1140
  if (camera.parent) {
1122
1141
  cameraLocalPosition = camera.parent.worldToLocal(cameraLocalPosition);
1123
1142
  }
@@ -1154,7 +1173,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
1154
1173
  /**
1155
1174
  * Options for fitting the camera to the scene. Used in {@link OrbitControls.fitCamera}
1156
1175
  */
1157
- declare type FitCameraOptions = {
1176
+ export type FitCameraOptions = {
1158
1177
  /** When enabled debug rendering will be shown */
1159
1178
  debug?: boolean,
1160
1179
  /**
@@ -1166,14 +1185,41 @@ declare type FitCameraOptions = {
1166
1185
  */
1167
1186
  immediate?: boolean,
1168
1187
 
1188
+ /** Fit offset: A factor to multiply the distance to the objects by
1189
+ * @default 1.1
1190
+ */
1191
+ fitOffset?: number,
1192
+
1193
+ /** The direction from which the camera should be fitted in worldspace. If not defined the current camera's position will be used */
1194
+ fitDirection?: Vector3Like,
1195
+
1169
1196
  /** If set to "y" the camera will be centered in the y axis */
1170
1197
  centerCamera?: "none" | "y",
1198
+ /** Set to 'auto' to update the camera near or far plane based on the fitted-objects bounds */
1171
1199
  cameraNearFar?: "keep" | "auto",
1172
1200
 
1173
- fov?: number,
1201
+ /**
1202
+ * Offset the camera position in world space
1203
+ */
1204
+ cameraOffset?: Partial<Vector3Like>,
1205
+ /**
1206
+ * Offset the camera position relative to the size of the objects being focused on (e.g. x: 0.5).
1207
+ * Value range: -1 to 1
1208
+ */
1209
+ relativeCameraOffset?: Partial<Vector3Like>,
1174
1210
 
1175
- /** Fit offset: A factor to multiply the distance to the objects by
1176
- * @default 1.1
1211
+ /**
1212
+ * Offset the camera target position in world space
1177
1213
  */
1178
- fitOffset?: number,
1214
+ targetOffset?: Partial<Vector3Like>,
1215
+ /**
1216
+ * Offset the camera target position relative to the size of the objects being focused on.
1217
+ * Value range: -1 to 1
1218
+ */
1219
+ relativeTargetOffset?: Partial<Vector3Like>,
1220
+
1221
+ /**
1222
+ * Field of view (FOV) for the camera
1223
+ */
1224
+ fov?: number,
1179
1225
  }
@@ -706,7 +706,8 @@ export class Renderer extends Behaviour implements IRenderer {
706
706
  }
707
707
 
708
708
  if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
709
- this._reflectionProbe.onSet(this);
709
+ if (!this._lightmaps?.length) // Currently reflectionprobes cant be used with lightmaps
710
+ this._reflectionProbe.onSet(this);
710
711
  }
711
712
  // since three 163 we need to set the envMap to the scene envMap if it is not set
712
713
  // otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903