@needle-tools/engine 4.9.3-next.0facab6 → 4.9.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 (94) hide show
  1. package/components.needle.json +1 -1
  2. package/dist/{gltf-progressive-Iy7aSAPk.umd.cjs → gltf-progressive-DWiyqrwB.umd.cjs} +1 -1
  3. package/dist/{gltf-progressive-CoZbSfPR.min.js → gltf-progressive-DhE1A6hX.min.js} +1 -1
  4. package/dist/{gltf-progressive-DUR9TuAH.js → gltf-progressive-egsMzRdv.js} +3 -3
  5. package/dist/{needle-engine.bundle-TvT7wv7z.js → needle-engine.bundle-BAsxNKpA.js} +7343 -7525
  6. package/dist/{needle-engine.bundle-DAo7BPxQ.umd.cjs → needle-engine.bundle-C7LSzO5L.umd.cjs} +122 -144
  7. package/dist/needle-engine.bundle-ugr1bBtk.min.js +1616 -0
  8. package/dist/needle-engine.js +444 -446
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/dist/{postprocessing-BHMVuZQ1.min.js → postprocessing-BZOSD1ln.min.js} +1 -1
  12. package/dist/{postprocessing-BsnRNRRS.umd.cjs → postprocessing-Bb5StX0o.umd.cjs} +1 -1
  13. package/dist/{postprocessing-DQ2pynXW.js → postprocessing-BzFF7i-7.js} +2 -2
  14. package/dist/{three-B-jwTHao.umd.cjs → three-BK56xWDs.umd.cjs} +11 -11
  15. package/dist/{three-CJSAehtG.js → three-CsHK73Zc.js} +0 -1
  16. package/dist/{three-qw28ZtTy.min.js → three-TNFQHSFa.min.js} +10 -10
  17. package/dist/{three-examples-BivkhnvN.min.js → three-examples-Bph291U2.min.js} +1 -1
  18. package/dist/{three-examples-Doq0rvFU.js → three-examples-BvMpKSun.js} +1 -1
  19. package/dist/{three-examples-Deqc1bNw.umd.cjs → three-examples-C9WfZu-X.umd.cjs} +1 -1
  20. package/dist/{three-mesh-ui-CktOi6oI.js → three-mesh-ui-CN6aRT7i.js} +1 -1
  21. package/dist/{three-mesh-ui-CsHwj9cJ.umd.cjs → three-mesh-ui-DnxkZWNA.umd.cjs} +1 -1
  22. package/dist/{three-mesh-ui-DhYXcXZe.min.js → three-mesh-ui-n_qS2BM-.min.js} +1 -1
  23. package/dist/{vendor-D0Yvltn9.umd.cjs → vendor-BtJpSuCj.umd.cjs} +1 -1
  24. package/dist/{vendor-JyrX4DVM.min.js → vendor-XJ9xiwrv.min.js} +1 -1
  25. package/dist/{vendor-DU8tJyl_.js → vendor-k9i6CeGi.js} +1 -1
  26. package/lib/engine/api.d.ts +0 -1
  27. package/lib/engine/api.js +0 -1
  28. package/lib/engine/api.js.map +1 -1
  29. package/lib/engine/codegen/register_types.js +0 -2
  30. package/lib/engine/codegen/register_types.js.map +1 -1
  31. package/lib/engine/engine_animation.d.ts +1 -21
  32. package/lib/engine/engine_animation.js +1 -32
  33. package/lib/engine/engine_animation.js.map +1 -1
  34. package/lib/engine/engine_gizmos.d.ts +2 -2
  35. package/lib/engine/engine_gizmos.js +2 -2
  36. package/lib/engine/engine_physics.js +3 -6
  37. package/lib/engine/engine_physics.js.map +1 -1
  38. package/lib/engine/webcomponents/needle-engine.d.ts +0 -1
  39. package/lib/engine/webcomponents/needle-engine.js +0 -6
  40. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  41. package/lib/engine/webcomponents/needle-engine.loading.js +23 -59
  42. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  43. package/lib/engine-components/AnimatorController.js +0 -16
  44. package/lib/engine-components/AnimatorController.js.map +1 -1
  45. package/lib/engine-components/CameraUtils.js +9 -8
  46. package/lib/engine-components/CameraUtils.js.map +1 -1
  47. package/lib/engine-components/OrbitControls.d.ts +47 -4
  48. package/lib/engine-components/OrbitControls.js +178 -30
  49. package/lib/engine-components/OrbitControls.js.map +1 -1
  50. package/lib/engine-components/api.d.ts +1 -0
  51. package/lib/engine-components/api.js.map +1 -1
  52. package/lib/engine-components/codegen/components.d.ts +0 -1
  53. package/lib/engine-components/codegen/components.js +0 -1
  54. package/lib/engine-components/codegen/components.js.map +1 -1
  55. package/lib/engine-components/web/Clickthrough.d.ts +0 -3
  56. package/lib/engine-components/web/Clickthrough.js +0 -3
  57. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  58. package/lib/engine-components/web/CursorFollow.d.ts +0 -3
  59. package/lib/engine-components/web/CursorFollow.js +0 -3
  60. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  61. package/lib/engine-components/web/ScrollFollow.d.ts +0 -4
  62. package/lib/engine-components/web/ScrollFollow.js +0 -4
  63. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  64. package/lib/engine-components/web/index.d.ts +0 -1
  65. package/lib/engine-components/web/index.js +0 -1
  66. package/lib/engine-components/web/index.js.map +1 -1
  67. package/package.json +2 -2
  68. package/plugins/vite/alias.js +3 -5
  69. package/plugins/vite/poster-client.js +21 -22
  70. package/src/engine/api.ts +1 -2
  71. package/src/engine/codegen/register_types.ts +0 -2
  72. package/src/engine/engine_animation.ts +1 -69
  73. package/src/engine/engine_gizmos.ts +2 -2
  74. package/src/engine/engine_physics.ts +3 -6
  75. package/src/engine/webcomponents/needle-engine.loading.ts +24 -63
  76. package/src/engine/webcomponents/needle-engine.ts +1 -6
  77. package/src/engine-components/AnimatorController.ts +2 -21
  78. package/src/engine-components/CameraUtils.ts +9 -8
  79. package/src/engine-components/OrbitControls.ts +239 -30
  80. package/src/engine-components/api.ts +1 -0
  81. package/src/engine-components/codegen/components.ts +0 -1
  82. package/src/engine-components/web/Clickthrough.ts +0 -3
  83. package/src/engine-components/web/CursorFollow.ts +0 -3
  84. package/src/engine-components/web/ScrollFollow.ts +0 -4
  85. package/src/engine-components/web/index.ts +0 -1
  86. package/dist/needle-engine.bundle-DP2gYtOQ.min.js +0 -1638
  87. package/lib/engine/engine_camera.fit.d.ts +0 -68
  88. package/lib/engine/engine_camera.fit.js +0 -193
  89. package/lib/engine/engine_camera.fit.js.map +0 -1
  90. package/lib/engine-components/web/HoverAnimation.d.ts +0 -44
  91. package/lib/engine-components/web/HoverAnimation.js +0 -105
  92. package/lib/engine-components/web/HoverAnimation.js.map +0 -1
  93. package/src/engine/engine_camera.fit.ts +0 -288
  94. package/src/engine-components/web/HoverAnimation.ts +0 -99
@@ -158,18 +158,8 @@ export class EngineLoadingView implements ILoadingViewHandler {
158
158
  private onDoneLoading() {
159
159
  if (this._loadingElement) {
160
160
  if (debug) console.log("Hiding loading element");
161
- // animate alpha to 0
162
- const element = this._loadingElement;
163
- element.animate([
164
- { opacity: 1 },
165
- { opacity: 0 }
166
- ], {
167
- duration: 200,
168
- easing: 'ease-in-out',
169
- }).addEventListener('finish', () => {
170
- element.style.display = "none";
171
- element.remove();
172
- });
161
+ this._loadingElement.style.display = "none";
162
+ this._loadingElement.remove();
173
163
  }
174
164
  if (this._progressLoop)
175
165
  clearInterval(this._progressLoop);
@@ -200,7 +190,6 @@ export class EngineLoadingView implements ILoadingViewHandler {
200
190
  loadingStyle = "light";
201
191
  }
202
192
 
203
-
204
193
  const hasLicense = hasProLicense();
205
194
  if (!existing) {
206
195
  this._loadingElement.style.position = "absolute";
@@ -208,7 +197,6 @@ export class EngineLoadingView implements ILoadingViewHandler {
208
197
  this._loadingElement.style.height = "100%";
209
198
  this._loadingElement.style.left = "0";
210
199
  this._loadingElement.style.top = "0";
211
- this._loadingElement.style.overflow = "hidden";
212
200
  const loadingBackgroundColor = this._element.getAttribute("loading-background");
213
201
  if (loadingBackgroundColor) {
214
202
  this._loadingElement.style.background = loadingBackgroundColor;
@@ -239,48 +227,24 @@ export class EngineLoadingView implements ILoadingViewHandler {
239
227
  }
240
228
 
241
229
  const content = document.createElement("div");
242
- content.style.cssText = `
243
- position: relative;
244
- display: flex;
245
- flex-direction: column;
246
- align-items: center;
247
- justify-content: center;
248
- width: 100%;
249
- height: 100%;
250
- pointer-events: none;
251
- `
252
230
  this._loadingElement.appendChild(content);
253
231
 
254
- const poster = this._element.getAttribute("poster");
255
- if (poster !== null && poster !== "0") {
256
- const backgroundImage = document.createElement("div");
257
- const backgroundBlur = poster?.length ? "0px" : "50px";
258
- backgroundImage.style.cssText = `
259
- position: absolute;
260
- left: 0;
261
- top: 0;
262
- bottom: 0;
263
- right: 0;
264
- z-index: -1;
265
- overflow: hidden;
266
-
267
- margin: -${backgroundBlur};
268
- background: url('${poster?.length ? poster : "/include/poster.webp"}') center center no-repeat;
269
- background-size: cover;
270
- filter: blur(${backgroundBlur});
271
- `;
272
- this._loadingElement.appendChild(backgroundImage);
273
- }
274
-
275
232
  const logo = document.createElement("img");
276
- const logoWidth = "80%";
277
- const logoHeight = "15%";
278
- const logoDelay = ".2s";
233
+ const logoSize = 120;
234
+ logo.style.width = `${logoSize}px`;
235
+ logo.style.height = `${logoSize}px`;
236
+ logo.style.paddingTop = "20px";
237
+ logo.style.paddingBottom = "10px";
238
+ logo.style.margin = "0px";
279
239
  logo.style.userSelect = "none";
280
240
  logo.style.objectFit = "contain";
241
+ logo.style.transition = "transform 1.5s ease-out, opacity .3s ease-in-out";
281
242
  logo.style.transform = "translateY(30px)";
282
- logo.style.opacity = "0.0000001";
283
- logo.style.transition = `transform 1s ease-out ${logoDelay}, opacity .3s ease-in-out ${logoDelay}`;
243
+ logo.style.opacity = "0.05";
244
+ setTimeout(() => {
245
+ logo.style.opacity = "1";
246
+ logo.style.transform = "translateY(0px)";
247
+ }, 1);
284
248
  logo.src = needleLogoOnlySVG;
285
249
  let isUsingCustomLogo = false;
286
250
  if (hasLicense && this._element) {
@@ -288,15 +252,8 @@ export class EngineLoadingView implements ILoadingViewHandler {
288
252
  if (customLogo) {
289
253
  isUsingCustomLogo = true;
290
254
  logo.src = customLogo;
291
- setTimeout(() => {
292
- logo.style.opacity = "1";
293
- logo.style.transform = "translateY(0px)";
294
- }, 1);
295
255
  }
296
256
  }
297
-
298
- logo.style.width = `${logoWidth}`;
299
- logo.style.height = `min(1000px, max(${logoHeight}, 50px))`;
300
257
  content.appendChild(logo);
301
258
 
302
259
  const details = document.createElement("div");
@@ -316,14 +273,18 @@ export class EngineLoadingView implements ILoadingViewHandler {
316
273
  const maxWidth = 100;
317
274
  loadingBarContainer.style.display = "flex";
318
275
  loadingBarContainer.style.width = maxWidth + "%";
319
- loadingBarContainer.style.height = "5px";
276
+ loadingBarContainer.style.height = "3px";
320
277
  loadingBarContainer.style.position = "absolute";
321
278
  loadingBarContainer.style.left = "0";
322
- loadingBarContainer.style.top = "0px";
279
+ loadingBarContainer.style.bottom = "0px";
323
280
  loadingBarContainer.style.opacity = "0";
324
- loadingBarContainer.style.transition = "opacity 1s ease-in-out";
325
- loadingBarContainer.style.backgroundColor = "rgba(240,240,240,.5)"
281
+ loadingBarContainer.style.transition = "opacity 1s ease-in-out 2s";
326
282
  setTimeout(() => { loadingBarContainer.style.opacity = "1"; }, 1);
283
+ if (loadingStyle === "light")
284
+ loadingBarContainer.style.backgroundColor = "rgba(0,0,0,.2)"
285
+ else
286
+ loadingBarContainer.style.backgroundColor = "rgba(255,255,255,.2)"
287
+ // loadingBarContainer.style.alignItems = "center";
327
288
 
328
289
  this._loadingElement.appendChild(loadingBarContainer);
329
290
 
@@ -333,9 +294,9 @@ export class EngineLoadingView implements ILoadingViewHandler {
333
294
  const getGradientPos = function (t: number): string {
334
295
  return Mathf.lerp(0, maxWidth, t) + "%";
335
296
  }
336
- // `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
297
+ this._loadingBar.style.background = "#66A22F";
298
+ // `linear-gradient(90deg, #204f49 ${getGradientPos(0)}, #0BA398 ${getGradientPos(.3)}, #66A22F ${getGradientPos(.6)}, #D7DB0A ${getGradientPos(1)})`;
337
299
  this._loadingBar.style.backgroundAttachment = "fixed";
338
- this._loadingBar.style.background = "#c4c4c4ab";
339
300
  this._loadingBar.style.width = "0%";
340
301
  this._loadingBar.style.height = "100%";
341
302
  if (hasLicense && this._element) {
@@ -85,17 +85,12 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
85
85
  * <needle-engine></needle-engine>
86
86
  * @returns {boolean | null} if the attribute is not set it returns null
87
87
  */
88
- get cameraControls(): boolean | null {
88
+ public get cameraControls(): boolean | null {
89
89
  const attr = this.getAttribute("camera-controls") as NeedleEngineAttributes["camera-controls"] | ({} & string)
90
90
  if (attr == null) return null;
91
91
  if (attr === null || attr === "False" || attr === "false" || attr === "0" || attr === "none") return false;
92
92
  return true;
93
93
  }
94
- set cameraControls(value: boolean | null) {
95
- if (value === null) this.removeAttribute("camera-controls");
96
- else this.setAttribute("camera-controls", value ? "true" : "false");
97
- }
98
-
99
94
 
100
95
  /**
101
96
  * Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
@@ -276,7 +276,7 @@ export class AnimatorController {
276
276
  * @returns The found state or null if not found
277
277
  */
278
278
  FindState(name: string | number | undefined | null): State | null { return this.findState(name); }
279
-
279
+
280
280
  /**
281
281
  * Finds an animation state by name or hash.
282
282
  *
@@ -321,25 +321,6 @@ export class AnimatorController {
321
321
  return action;
322
322
  }
323
323
 
324
- // addState(state: State, layerIndex: number = 0) {
325
- // if (!this.model) throw new Error("AnimatorController model is missing");
326
- // if (layerIndex < 0 || layerIndex >= this.model.layers.length) {
327
- // throw new Error(`Invalid layer index: ${layerIndex}`);
328
- // }
329
- // const layer = this.model.layers[layerIndex];
330
- // if (!layer.stateMachine) {
331
- // layer.stateMachine = { states: [], defaultState: 0 };
332
- // }
333
- // if (!layer.stateMachine.states) {
334
- // layer.stateMachine.states = [];
335
- // }
336
- // if (state.hash === undefined) {
337
- // state.hash = stringToHash(state.name || "state" + layer.stateMachine.states.length);
338
- // }
339
-
340
- // }
341
-
342
-
343
324
  /**
344
325
  * The normalized time (0-1) to start playing the first state at.
345
326
  * This affects the initial state when the animator is first enabled.
@@ -350,7 +331,7 @@ export class AnimatorController {
350
331
  * The Animator component this controller is bound to.
351
332
  */
352
333
  animator?: Animator;
353
-
334
+
354
335
  /**
355
336
  * The data model describing the animation states and transitions.
356
337
  */
@@ -117,15 +117,16 @@ function createDefaultCameraControls(context: IContext, cam?: ICamera) {
117
117
  if (cameraObject) {
118
118
  const orbit = getOrAddComponent(cameraObject, OrbitControls) as OrbitControls;
119
119
  orbit.sourceId = cam?.sourceId ?? "unknown";
120
- // /enable auto-rotate if the auto-rotate attribute is provided
121
120
  const autoRotate = context.domElement.getAttribute("auto-rotate");
122
- orbit.autoRotate = autoRotate != "0" && autoRotate?.toLowerCase() != "false";
123
- const autoRotateSpeed = Number.parseFloat(autoRotate || ".5");
124
- orbit.autoRotateSpeed = !isNaN(autoRotateSpeed) ? autoRotateSpeed : .5;
125
- console.log("Auto-rotate", orbit.autoRotate, "speed:", orbit.autoRotateSpeed);
126
- const autoFit = context.domElement.getAttribute("auto-fit");
127
- orbit.autoFit = autoFit !== "0" && autoFit?.toLowerCase() != "false";
128
- orbit.autoTarget = true;
121
+ orbit.autoRotate = autoRotate !== undefined && autoRotate !== null && (autoRotate != "0" && autoRotate?.toLowerCase() != "false");
122
+ orbit.autoRotateSpeed = 0.5;
123
+ orbit.autoFit = true;
124
+ if (orbit.autoRotate && autoRotate) {
125
+ const autoRotateValue = parseFloat(autoRotate);
126
+ if (!isNaN(autoRotateValue)) {
127
+ orbit.autoRotateSpeed = autoRotateValue;
128
+ }
129
+ }
129
130
  }
130
131
  else {
131
132
  console.warn("Missing camera object, can not add orbit controls")
@@ -2,19 +2,18 @@ import { Camera as Camera3, Object3D, PerspectiveCamera, Ray, Vector2, Vector3,
2
2
  import { OrbitControls as ThreeOrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
3
3
 
4
4
  import { isDevEnvironment } from "../engine/debug/index.js";
5
- import { fitCamera, FitCameraOptions } from "../engine/engine_camera.fit.js";
6
5
  import { setCameraController } from "../engine/engine_camera.js";
7
6
  import { Gizmos } from "../engine/engine_gizmos.js";
8
7
  import { InputEventQueue, NEPointerEvent } from "../engine/engine_input.js";
9
8
  import { Mathf } from "../engine/engine_math.js";
10
9
  import { IRaycastOptions, RaycastOptions } from "../engine/engine_physics.js";
11
10
  import { serializable } from "../engine/engine_serialization_decorator.js";
12
- import { getTempVector, getWorldPosition } from "../engine/engine_three_utils.js";
11
+ import { getBoundingBox, getTempVector, getWorldPosition } from "../engine/engine_three_utils.js";
13
12
  import type { ICameraController } from "../engine/engine_types.js";
14
13
  import { DeviceUtilities, getParam } from "../engine/engine_utils.js";
15
- import { NeedleEngineWebComponent } from "../engine/webcomponents/needle-engine.js";
16
14
  import { Camera } from "./Camera.js";
17
15
  import { Behaviour, GameObject } from "./Component.js";
16
+ import { GroundProjectedEnv } from "./GroundProjection.js";
18
17
  import { LookAtConstraint } from "./LookAtConstraint.js";
19
18
  import { SyncedTransform } from "./SyncedTransform.js";
20
19
  import { type AfterHandleInputEvent, EventSystem, EventSystemEvents } from "./ui/EventSystem.js";
@@ -355,9 +354,6 @@ export class OrbitControls extends Behaviour implements ICameraController {
355
354
  if (debug) console.debug("OrbitControls", this);
356
355
  this._didSetTarget = 0;
357
356
  this._startedListeningToKeyEvents = false;
358
- if ((this.context.domElement as NeedleEngineWebComponent).cameraControls === false) {
359
- this.enabled = false;
360
- }
361
357
  }
362
358
 
363
359
  /** @internal */
@@ -494,13 +490,10 @@ export class OrbitControls extends Behaviour implements ICameraController {
494
490
  this._lastTimeClickOnBackground = this.context.time.time;
495
491
  if (this.clickBackgroundToFitScene <= 1 || dt < this.clickBackgroundToFitScene * .15) {
496
492
  this._clickOnBackgroundCount += 1;
497
- if (this._clickOnBackgroundCount >= this.clickBackgroundToFitScene - 1) {
498
- this.autoRotate = false;
499
- this.fitCamera({
500
- objects: this.context.scene,
501
- immediate: false,
493
+ if (this._clickOnBackgroundCount >= this.clickBackgroundToFitScene - 1)
494
+ this.fitCamera(this.context.scene.children, {
495
+ immediate: false
502
496
  });
503
- }
504
497
  }
505
498
  else {
506
499
  this._clickOnBackgroundCount = 0;
@@ -537,7 +530,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
537
530
  private _orbitStartAngle: number = 0;
538
531
  private _zoomStartDistance: number = 0;
539
532
  private onControlsChangeStarted = () => {
540
- if (debug) console.debug("OrbitControls: Change started");
533
+ if(debug) console.debug("OrbitControls: Change started");
541
534
  if (this._controls) {
542
535
  this._orbitStartAngle = this._controls.getAzimuthalAngle() + this._controls.getPolarAngle();
543
536
  this._zoomStartDistance = this._controls.getDistance();
@@ -547,7 +540,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
547
540
  }
548
541
  }
549
542
  private onControlsChangeEnded = () => {
550
- if (debug) console.debug("OrbitControls: Change ended", { autoTarget: this.autoTarget });
543
+ if(debug) console.debug("OrbitControls: Change ended", { autoTarget: this.autoTarget });
551
544
  if (this._controls) {
552
545
  if (this.autoTarget) {
553
546
  const newAngle = this._controls.getAzimuthalAngle() + this._controls.getPolarAngle();
@@ -996,26 +989,242 @@ export class OrbitControls extends Behaviour implements ICameraController {
996
989
  /**
997
990
  * Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
998
991
  */
999
- fitCamera(options?: OrbitFitCameraOptions): void {
1000
- const res = fitCamera({
1001
- ...options,
1002
- autoApply: false,
1003
- context: this.context,
1004
- camera: this._cameraObject as Camera3,
1005
- currentZoom: this._controls?.getDistance() || undefined,
1006
- minZoom: this.minZoom,
1007
- maxZoom: this.maxZoom,
1008
- });
1009
- if (!res) return;
1010
- this.setLookTargetPosition(res.lookAt, options?.immediate || false);
1011
- this.setCameraTargetPosition(res.position, options?.immediate || false);
1012
- this.setFieldOfView(options?.fov, options?.immediate || false);
992
+ fitCamera(options?: FitCameraOptions);
993
+ fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<FitCameraOptions, "objects">);
994
+ fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | FitCameraOptions, options?: FitCameraOptions): void {
995
+
996
+ if (this.context.isInXR) {
997
+ // camera fitting in XR is not supported
998
+ console.warn('[OrbitControls] Can not fit camera while XR session is active');
999
+ return;
1000
+ }
1001
+
1002
+ let objects: Object3D | Array<Object3D> | undefined = undefined;
1003
+ // If the user passed in an array as first argument
1004
+ if (Array.isArray(objectsOrOptions)) {
1005
+ objects = objectsOrOptions;
1006
+ }
1007
+ // If the user passed in an object as first argument
1008
+ else if (objectsOrOptions && "type" in objectsOrOptions) {
1009
+ objects = objectsOrOptions;
1010
+ }
1011
+ // If the user passed in an object as first argument and options as second argument
1012
+ else if (objectsOrOptions && typeof objectsOrOptions === "object") {
1013
+ if (!(objectsOrOptions instanceof Object3D) && !Array.isArray(objectsOrOptions)) {
1014
+ options = objectsOrOptions;
1015
+ objects = options.objects;
1016
+ }
1017
+ }
1018
+ // Ensure objects are setup correctly
1019
+ if (objects && !Array.isArray(objects)) {
1020
+ objects = [objects];
1021
+ }
1022
+ if (!Array.isArray(objects) || objects && objects.length <= 0) {
1023
+ objects = this.context.scene.children;
1024
+ }
1025
+
1026
+ // Make sure there's anything to fit to
1027
+ if (!Array.isArray(objects) || objects.length <= 0) {
1028
+ console.warn("No objects to fit camera to...");
1029
+ return;
1030
+ }
1031
+
1032
+ const camera = this._cameraObject as PerspectiveCamera;
1033
+ const controls = this._controls as ThreeOrbitControls | null;
1034
+
1035
+ if (!camera || !controls) {
1036
+ console.warn("No camera or controls found to fit camera to objects...");
1037
+ return;
1038
+ }
1039
+ if (!options) options = {}
1040
+ const { immediate = false, centerCamera, cameraNearFar = "auto", fitOffset = 1.1, fov = camera?.fov } = options;
1041
+ const size = new Vector3();
1042
+ const center = new Vector3();
1043
+ // TODO would be much better to calculate the bounds in camera space instead of world space -
1044
+ // we would get proper view-dependant fit.
1045
+ // Right now it's independent from where the camera is actually looking from,
1046
+ // and thus we're just getting some maximum that will work for sure.
1047
+ const box = getBoundingBox(objects, undefined, this._camera?.threeCamera?.layers);
1048
+ const boxCopy = box.clone();
1049
+ box.getCenter(center);
1050
+
1051
+ const box_size = new Vector3();
1052
+ box.getSize(box_size);
1053
+
1054
+ // project this box into camera space
1055
+ camera.updateMatrixWorld();
1056
+ box.applyMatrix4(camera.matrixWorldInverse);
1057
+
1058
+ box.getSize(size);
1059
+ box.setFromCenterAndSize(center, size);
1060
+ if (Number.isNaN(size.x) || Number.isNaN(size.y) || Number.isNaN(size.z)) {
1061
+ console.warn("Camera fit size resultet in NaN", camera, box, [...objects]);
1062
+ return;
1063
+ }
1064
+ if (size.length() <= 0.0000000001) {
1065
+ if (debugCameraFit) console.warn("Camera fit size is zero", box, [...objects]);
1066
+ return;
1067
+ }
1068
+
1069
+ const verticalFov = fov;
1070
+ const horizontalFov = 2 * Math.atan(Math.tan(verticalFov * Math.PI / 360 / 2) * camera.aspect) / Math.PI * 360;
1071
+ const fitHeightDistance = size.y / (2 * Math.atan(Math.PI * verticalFov / 360));
1072
+ const fitWidthDistance = size.x / (2 * Math.atan(Math.PI * horizontalFov / 360));
1073
+
1074
+ const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance) + size.z / 2;
1075
+
1076
+ if (debugCameraFit) {
1077
+ console.log("Fit camera to objects", { fitHeightDistance, fitWidthDistance, distance, verticalFov, horizontalFov });
1078
+ }
1079
+
1080
+ this.maxZoom = distance * 10;
1081
+ this.minZoom = distance * 0.01;
1082
+
1083
+ const verticalOffset = 0.05;
1084
+ const lookAt = center.clone();
1085
+ lookAt.y -= size.y * verticalOffset;
1086
+ if (options.targetOffset) {
1087
+ if (options.targetOffset.x !== undefined) lookAt.x += options.targetOffset.x;
1088
+ if (options.targetOffset.y !== undefined) lookAt.y += options.targetOffset.y;
1089
+ if (options.targetOffset.z !== undefined) lookAt.z += options.targetOffset.z;
1090
+ }
1091
+ if (options.relativeTargetOffset) {
1092
+ if (options.relativeTargetOffset.x !== undefined) lookAt.x += options.relativeTargetOffset.x * size.x;
1093
+ if (options.relativeTargetOffset.y !== undefined) lookAt.y += options.relativeTargetOffset.y * size.y;
1094
+ if (options.relativeTargetOffset.z !== undefined) lookAt.z += options.relativeTargetOffset.z * size.z;
1095
+ }
1096
+ this.setLookTargetPosition(lookAt, immediate);
1097
+ this.setFieldOfView(options.fov, immediate);
1098
+
1099
+ if (cameraNearFar == undefined || cameraNearFar == "auto") {
1100
+ // Check if the scene has a GroundProjectedEnv and include the scale to the far plane so that it doesnt cut off
1101
+ const groundprojection = GameObject.findObjectOfType(GroundProjectedEnv);
1102
+ const groundProjectionRadius = groundprojection ? groundprojection.radius : 0;
1103
+ const boundsMax = Math.max(box_size.x, box_size.y, box_size.z, groundProjectionRadius);
1104
+ // TODO: this doesnt take the Camera component nearClipPlane into account
1105
+ camera.near = (distance / 100);
1106
+ camera.far = boundsMax + distance * 10;
1107
+ camera.updateProjectionMatrix();
1108
+
1109
+ // adjust maxZoom so that the ground projection radius is always inside
1110
+ if (groundprojection) {
1111
+ this.maxZoom = Math.max(Math.min(this.maxZoom, groundProjectionRadius * 0.5), distance);
1112
+ }
1113
+ }
1114
+
1115
+ // ensure we're not clipping out of the current zoom level just because we're fitting
1116
+ const currentZoom = controls.getDistance();
1117
+ if (currentZoom < this.minZoom) this.minZoom = currentZoom * 0.9;
1118
+ if (currentZoom > this.maxZoom) this.maxZoom = currentZoom * 1.1;
1119
+
1120
+ const direction = center.clone();
1121
+ if (options.fitDirection) {
1122
+ direction.sub(new Vector3().copy(options.fitDirection).multiplyScalar(1_000_000));
1123
+ }
1124
+ else {
1125
+ direction.sub(camera.worldPosition);
1126
+ }
1127
+ if (centerCamera === "y")
1128
+ direction.y = 0;
1129
+ direction.normalize();
1130
+ direction.multiplyScalar(distance);
1131
+ if (centerCamera === "y")
1132
+ direction.y += -verticalOffset * 4 * distance;
1133
+
1134
+ let cameraLocalPosition = center.clone().sub(direction);
1135
+ if (options.cameraOffset) {
1136
+ if (options.cameraOffset.x !== undefined) cameraLocalPosition.x += options.cameraOffset.x;
1137
+ if (options.cameraOffset.y !== undefined) cameraLocalPosition.y += options.cameraOffset.y;
1138
+ if (options.cameraOffset.z !== undefined) cameraLocalPosition.z += options.cameraOffset.z;
1139
+ }
1140
+ if (options.relativeCameraOffset) {
1141
+ if (options.relativeCameraOffset.x !== undefined) cameraLocalPosition.x += options.relativeCameraOffset.x * size.x;
1142
+ if (options.relativeCameraOffset.y !== undefined) cameraLocalPosition.y += options.relativeCameraOffset.y * size.y;
1143
+ if (options.relativeCameraOffset.z !== undefined) cameraLocalPosition.z += options.relativeCameraOffset.z * size.z;
1144
+ }
1145
+ if (camera.parent) {
1146
+ cameraLocalPosition = camera.parent.worldToLocal(cameraLocalPosition);
1147
+ }
1148
+ this.setCameraTargetPosition(cameraLocalPosition, immediate);
1149
+
1150
+ if (debugCameraFit || options.debug) {
1151
+ Gizmos.DrawWireBox3(box, 0xffff33, 10);
1152
+ Gizmos.DrawWireBox3(boxCopy, 0x00ff00, 10);
1153
+
1154
+ if (!this._haveAttachedKeyboardEvents && debugCameraFit) {
1155
+ this._haveAttachedKeyboardEvents = true;
1156
+ document.body.addEventListener("keydown", (e) => {
1157
+ if (e.code === "KeyF") {
1158
+ // random fov for easier debugging of fov-based fitting
1159
+ let fov: number | undefined = undefined;
1160
+ if (this._cameraObject instanceof PerspectiveCamera) fov = (Math.random() * Math.random()) * 170 + 10;
1161
+ this.fitCamera({ objects, fitOffset, immediate: false, fov });
1162
+ }
1163
+ if (e.code === "KeyV") {
1164
+ if (this._cameraObject instanceof PerspectiveCamera) this._cameraObject.fov = 60;
1165
+ }
1166
+ });
1167
+ }
1168
+ }
1169
+
1013
1170
  this.onBeforeRender();
1171
+ // controls.update(); // this is not enough when calling fitCamera({immediate:true}) in an interval
1014
1172
  }
1015
1173
 
1016
1174
  private _haveAttachedKeyboardEvents: boolean = false;
1017
1175
  }
1018
1176
 
1019
- type OrbitFitCameraOptions = FitCameraOptions & {
1177
+
1178
+ /**
1179
+ * Options for fitting the camera to the scene. Used in {@link OrbitControls.fitCamera}
1180
+ */
1181
+ export type FitCameraOptions = {
1182
+ /** When enabled debug rendering will be shown */
1183
+ debug?: boolean,
1184
+ /**
1185
+ * The objects to fit the camera to. If not provided the scene children will be used
1186
+ */
1187
+ objects?: Object3D[] | Object3D;
1188
+ /** If true the camera will move immediately to the new position, otherwise it will lerp
1189
+ * @default false
1190
+ */
1020
1191
  immediate?: boolean,
1021
- }
1192
+
1193
+ /** Fit offset: A factor to multiply the distance to the objects by
1194
+ * @default 1.1
1195
+ */
1196
+ fitOffset?: number,
1197
+
1198
+ /** The direction from which the camera should be fitted in worldspace. If not defined the current camera's position will be used */
1199
+ fitDirection?: Vector3Like,
1200
+
1201
+ /** If set to "y" the camera will be centered in the y axis */
1202
+ centerCamera?: "none" | "y",
1203
+ /** Set to 'auto' to update the camera near or far plane based on the fitted-objects bounds */
1204
+ cameraNearFar?: "keep" | "auto",
1205
+
1206
+ /**
1207
+ * Offset the camera position in world space
1208
+ */
1209
+ cameraOffset?: Partial<Vector3Like>,
1210
+ /**
1211
+ * Offset the camera position relative to the size of the objects being focused on (e.g. x: 0.5).
1212
+ * Value range: -1 to 1
1213
+ */
1214
+ relativeCameraOffset?: Partial<Vector3Like>,
1215
+
1216
+ /**
1217
+ * Offset the camera target position in world space
1218
+ */
1219
+ targetOffset?: Partial<Vector3Like>,
1220
+ /**
1221
+ * Offset the camera target position relative to the size of the objects being focused on.
1222
+ * Value range: -1 to 1
1223
+ */
1224
+ relativeTargetOffset?: Partial<Vector3Like>,
1225
+
1226
+ /**
1227
+ * Field of view (FOV) for the camera
1228
+ */
1229
+ fov?: number,
1230
+ }
@@ -53,6 +53,7 @@ import "./AnimationUtilsAutoplay.js"
53
53
 
54
54
  export { DragMode } from "./DragControls.js";
55
55
  export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
56
+ export { type FitCameraOptions } from "./OrbitControls.js";
56
57
  export * from "./particlesystem/api.js"
57
58
  export * from "./splines/index.js";
58
59
  export * from "./web/index.js";
@@ -205,7 +205,6 @@ export { VideoPlayer } from "../VideoPlayer.js";
205
205
  export { Voip } from "../Voip.js";
206
206
  export { ClickThrough } from "../web/Clickthrough.js";
207
207
  export { CursorFollow } from "../web/CursorFollow.js";
208
- export { HoverAnimation } from "../web/HoverAnimation.js";
209
208
  export { ScrollFollow } from "../web/ScrollFollow.js";
210
209
  export { Avatar } from "../webxr/Avatar.js";
211
210
  export { XRControllerFollow } from "../webxr/controllers/XRControllerFollow.js";
@@ -21,9 +21,6 @@ onStart(ctx => {
21
21
  * - Alternatively, add the `clickthrough` attribute to the `<needle-engine>` HTML element (e.g. `<needle-engine clickthrough></needle-engine>`).
22
22
  *
23
23
  * @link Example https://stackblitz.com/~/github.com/needle-engine/sample-3d-over-html
24
- * @category Web
25
- * @group Components
26
- * @component
27
24
  */
28
25
  export class ClickThrough extends Behaviour {
29
26
 
@@ -4,9 +4,6 @@ import { Behaviour } from "../Component.js";
4
4
 
5
5
  /**
6
6
  * The CursorFollow component makes the object follow the cursor (or touch) position on screen.
7
- * @category Web
8
- * @group Components
9
- * @component
10
7
  */
11
8
  export class CursorFollow extends Behaviour {
12
9
 
@@ -44,10 +44,6 @@ type ScrollFollowEvent = {
44
44
  * 2. Add a ScrollFollow component to the same GameObject or another GameObject in the scene.
45
45
  * 3. Assign the PlayableDirector component to the ScrollFollow's target property.
46
46
  * 4. The timeline will now scrub based on the scroll position of the page.
47
- *
48
- * @category Web
49
- * @group Components
50
- * @component
51
47
  */
52
48
  export class ScrollFollow extends Behaviour {
53
49
 
@@ -1,4 +1,3 @@
1
1
  export * from "./Clickthrough.js";
2
2
  export * from "./CursorFollow.js";
3
- export * from "./HoverAnimation.js";
4
3
  export * from "./ScrollFollow.js";