autoRotationBehavior.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Behavior } from "../../Behaviors/behavior";
  2. import { Camera } from "../../Cameras/camera";
  3. import { ArcRotateCamera } from "../../Cameras/arcRotateCamera";
  4. import { Nullable } from "../../types";
  5. import { Observer } from "../../Misc/observable";
  6. import { PointerInfoPre, PointerEventTypes } from "../../Events/pointerEvents";
  7. import { PrecisionDate } from "../../Misc/precisionDate";
  8. /**
  9. * The autoRotation behavior (AutoRotationBehavior) is designed to create a smooth rotation of an ArcRotateCamera when there is no user interaction.
  10. * @see https://doc.babylonjs.com/how_to/camera_behaviors#autorotation-behavior
  11. */
  12. export class AutoRotationBehavior implements Behavior<ArcRotateCamera> {
  13. /**
  14. * Gets the name of the behavior.
  15. */
  16. public get name(): string {
  17. return "AutoRotation";
  18. }
  19. private _zoomStopsAnimation = false;
  20. private _idleRotationSpeed = 0.05;
  21. private _idleRotationWaitTime = 2000;
  22. private _idleRotationSpinupTime = 2000;
  23. /**
  24. * Sets the flag that indicates if user zooming should stop animation.
  25. */
  26. public set zoomStopsAnimation(flag: boolean) {
  27. this._zoomStopsAnimation = flag;
  28. }
  29. /**
  30. * Gets the flag that indicates if user zooming should stop animation.
  31. */
  32. public get zoomStopsAnimation(): boolean {
  33. return this._zoomStopsAnimation;
  34. }
  35. /**
  36. * Sets the default speed at which the camera rotates around the model.
  37. */
  38. public set idleRotationSpeed(speed: number) {
  39. this._idleRotationSpeed = speed;
  40. }
  41. /**
  42. * Gets the default speed at which the camera rotates around the model.
  43. */
  44. public get idleRotationSpeed() {
  45. return this._idleRotationSpeed;
  46. }
  47. /**
  48. * Sets the time (in milliseconds) to wait after user interaction before the camera starts rotating.
  49. */
  50. public set idleRotationWaitTime(time: number) {
  51. this._idleRotationWaitTime = time;
  52. }
  53. /**
  54. * Gets the time (milliseconds) to wait after user interaction before the camera starts rotating.
  55. */
  56. public get idleRotationWaitTime() {
  57. return this._idleRotationWaitTime;
  58. }
  59. /**
  60. * Sets the time (milliseconds) to take to spin up to the full idle rotation speed.
  61. */
  62. public set idleRotationSpinupTime(time: number) {
  63. this._idleRotationSpinupTime = time;
  64. }
  65. /**
  66. * Gets the time (milliseconds) to take to spin up to the full idle rotation speed.
  67. */
  68. public get idleRotationSpinupTime() {
  69. return this._idleRotationSpinupTime;
  70. }
  71. /**
  72. * Gets a value indicating if the camera is currently rotating because of this behavior
  73. */
  74. public get rotationInProgress(): boolean {
  75. return Math.abs(this._cameraRotationSpeed) > 0;
  76. }
  77. // Default behavior functions
  78. private _onPrePointerObservableObserver: Nullable<Observer<PointerInfoPre>>;
  79. private _onAfterCheckInputsObserver: Nullable<Observer<Camera>>;
  80. private _attachedCamera: Nullable<ArcRotateCamera>;
  81. private _isPointerDown = false;
  82. private _lastFrameTime: Nullable<number> = null;
  83. private _lastInteractionTime = -Infinity;
  84. private _cameraRotationSpeed: number = 0;
  85. /**
  86. * Initializes the behavior.
  87. */
  88. public init(): void {
  89. // Do nothing
  90. }
  91. /**
  92. * Attaches the behavior to its arc rotate camera.
  93. * @param camera Defines the camera to attach the behavior to
  94. */
  95. public attach(camera: ArcRotateCamera): void {
  96. this._attachedCamera = camera;
  97. let scene = this._attachedCamera.getScene();
  98. this._onPrePointerObservableObserver = scene.onPrePointerObservable.add((pointerInfoPre) => {
  99. if (pointerInfoPre.type === PointerEventTypes.POINTERDOWN) {
  100. this._isPointerDown = true;
  101. return;
  102. }
  103. if (pointerInfoPre.type === PointerEventTypes.POINTERUP) {
  104. this._isPointerDown = false;
  105. }
  106. });
  107. this._onAfterCheckInputsObserver = camera.onAfterCheckInputsObservable.add(() => {
  108. let now = PrecisionDate.Now;
  109. let dt = 0;
  110. if (this._lastFrameTime != null) {
  111. dt = now - this._lastFrameTime;
  112. }
  113. this._lastFrameTime = now;
  114. // Stop the animation if there is user interaction and the animation should stop for this interaction
  115. this._applyUserInteraction();
  116. let timeToRotation = now - this._lastInteractionTime - this._idleRotationWaitTime;
  117. let scale = Math.max(Math.min(timeToRotation / (this._idleRotationSpinupTime), 1), 0);
  118. this._cameraRotationSpeed = this._idleRotationSpeed * scale;
  119. // Step camera rotation by rotation speed
  120. if (this._attachedCamera) {
  121. this._attachedCamera.alpha -= this._cameraRotationSpeed * (dt / 1000);
  122. }
  123. });
  124. }
  125. /**
  126. * Detaches the behavior from its current arc rotate camera.
  127. */
  128. public detach(): void {
  129. if (!this._attachedCamera) {
  130. return;
  131. }
  132. let scene = this._attachedCamera.getScene();
  133. if (this._onPrePointerObservableObserver) {
  134. scene.onPrePointerObservable.remove(this._onPrePointerObservableObserver);
  135. }
  136. this._attachedCamera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObserver);
  137. this._attachedCamera = null;
  138. }
  139. /**
  140. * Returns true if user is scrolling.
  141. * @return true if user is scrolling.
  142. */
  143. private _userIsZooming(): boolean {
  144. if (!this._attachedCamera) {
  145. return false;
  146. }
  147. return this._attachedCamera.inertialRadiusOffset !== 0;
  148. }
  149. private _lastFrameRadius = 0;
  150. private _shouldAnimationStopForInteraction(): boolean {
  151. if (!this._attachedCamera) {
  152. return false;
  153. }
  154. var zoomHasHitLimit = false;
  155. if (this._lastFrameRadius === this._attachedCamera.radius && this._attachedCamera.inertialRadiusOffset !== 0) {
  156. zoomHasHitLimit = true;
  157. }
  158. // Update the record of previous radius - works as an approx. indicator of hitting radius limits
  159. this._lastFrameRadius = this._attachedCamera.radius;
  160. return this._zoomStopsAnimation ? zoomHasHitLimit : this._userIsZooming();
  161. }
  162. /**
  163. * Applies any current user interaction to the camera. Takes into account maximum alpha rotation.
  164. */
  165. private _applyUserInteraction(): void {
  166. if (this._userIsMoving() && !this._shouldAnimationStopForInteraction()) {
  167. this._lastInteractionTime = PrecisionDate.Now;
  168. }
  169. }
  170. // Tools
  171. private _userIsMoving(): boolean {
  172. if (!this._attachedCamera) {
  173. return false;
  174. }
  175. return this._attachedCamera.inertialAlphaOffset !== 0 ||
  176. this._attachedCamera.inertialBetaOffset !== 0 ||
  177. this._attachedCamera.inertialRadiusOffset !== 0 ||
  178. this._attachedCamera.inertialPanningX !== 0 ||
  179. this._attachedCamera.inertialPanningY !== 0 ||
  180. this._isPointerDown;
  181. }
  182. }