babylon.bouncingBehavior.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. module BABYLON {
  2. /**
  3. * Add a bouncing effect to an ArcRotateCamera when reaching a specified minimum and maximum radius
  4. */
  5. export class BouncingBehavior implements Behavior<ArcRotateCamera> {
  6. public get name(): string {
  7. return "Bouncing";
  8. }
  9. /**
  10. * The easing function used by animations
  11. */
  12. public static EasingFunction = new BackEase(0.3);
  13. /**
  14. * The easing mode used by animations
  15. */
  16. public static EasingMode = EasingFunction.EASINGMODE_EASEOUT;
  17. /**
  18. * The duration of the animation, in milliseconds
  19. */
  20. public transitionDuration = 450;
  21. /**
  22. * Length of the distance animated by the transition when lower radius is reached
  23. */
  24. public lowerRadiusTransitionRange = 2;
  25. /**
  26. * Length of the distance animated by the transition when upper radius is reached
  27. */
  28. public upperRadiusTransitionRange = -2;
  29. private _autoTransitionRange = false;
  30. /**
  31. * Gets a value indicating if the lowerRadiusTransitionRange and upperRadiusTransitionRange are defined automatically
  32. */
  33. public get autoTransitionRange(): boolean {
  34. return this._autoTransitionRange;
  35. }
  36. /**
  37. * Sets a value indicating if the lowerRadiusTransitionRange and upperRadiusTransitionRange are defined automatically
  38. * Transition ranges will be set to 5% of the bounding box diagonal in world space
  39. */
  40. public set autoTransitionRange(value: boolean) {
  41. if (this._autoTransitionRange === value) {
  42. return;
  43. }
  44. this._autoTransitionRange = value;
  45. let camera = this._attachedCamera;
  46. if (!camera) {
  47. return;
  48. }
  49. if (value) {
  50. this._onMeshTargetChangedObserver = camera.onMeshTargetChangedObservable.add((mesh) => {
  51. if (!mesh) {
  52. return;
  53. }
  54. mesh.computeWorldMatrix(true);
  55. let diagonal = mesh.getBoundingInfo().diagonalLength;
  56. this.lowerRadiusTransitionRange = diagonal * 0.05;
  57. this.upperRadiusTransitionRange = diagonal * 0.05;
  58. });
  59. } else if (this._onMeshTargetChangedObserver) {
  60. camera.onMeshTargetChangedObservable.remove(this._onMeshTargetChangedObserver);
  61. }
  62. }
  63. // Connection
  64. private _attachedCamera: Nullable<ArcRotateCamera>;
  65. private _onAfterCheckInputsObserver: Nullable<Observer<Camera>>;
  66. private _onMeshTargetChangedObserver: Nullable<Observer<Nullable<AbstractMesh>>>;
  67. public init(): void {
  68. // Do notihng
  69. }
  70. public attach(camera: ArcRotateCamera): void {
  71. this._attachedCamera = camera;
  72. this._onAfterCheckInputsObserver = camera.onAfterCheckInputsObservable.add(() => {
  73. if (!this._attachedCamera) {
  74. return;
  75. }
  76. // Add the bounce animation to the lower radius limit
  77. if (this._isRadiusAtLimit(this._attachedCamera.lowerRadiusLimit)) {
  78. this._applyBoundRadiusAnimation(this.lowerRadiusTransitionRange);
  79. }
  80. // Add the bounce animation to the upper radius limit
  81. if (this._isRadiusAtLimit(this._attachedCamera.upperRadiusLimit)) {
  82. this._applyBoundRadiusAnimation(this.upperRadiusTransitionRange);
  83. }
  84. });
  85. }
  86. public detach(): void {
  87. if (!this._attachedCamera) {
  88. return;
  89. }
  90. if (this._onAfterCheckInputsObserver) {
  91. this._attachedCamera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObserver);
  92. }
  93. if (this._onMeshTargetChangedObserver) {
  94. this._attachedCamera.onMeshTargetChangedObservable.remove(this._onMeshTargetChangedObserver);
  95. }
  96. this._attachedCamera = null;
  97. }
  98. // Animations
  99. private _radiusIsAnimating: boolean = false;
  100. private _radiusBounceTransition: Nullable<Animation> = null;
  101. private _animatables = new Array<Animatable>();
  102. private _cachedWheelPrecision: number;
  103. /**
  104. * Checks if the camera radius is at the specified limit. Takes into account animation locks.
  105. * @param radiusLimit The limit to check against.
  106. * @return Bool to indicate if at limit.
  107. */
  108. private _isRadiusAtLimit(radiusLimit: Nullable<number>): boolean {
  109. if (!this._attachedCamera) {
  110. return false;
  111. }
  112. if (this._attachedCamera.radius === radiusLimit && !this._radiusIsAnimating) {
  113. return true;
  114. }
  115. return false;
  116. }
  117. /**
  118. * Applies an animation to the radius of the camera, extending by the radiusDelta.
  119. * @param radiusDelta The delta by which to animate to. Can be negative.
  120. */
  121. private _applyBoundRadiusAnimation(radiusDelta: number): void {
  122. if (!this._attachedCamera) {
  123. return;
  124. }
  125. if (!this._radiusBounceTransition) {
  126. BouncingBehavior.EasingFunction.setEasingMode(BouncingBehavior.EasingMode);
  127. this._radiusBounceTransition = Animation.CreateAnimation("radius", Animation.ANIMATIONTYPE_FLOAT, 60, BouncingBehavior.EasingFunction);
  128. }
  129. // Prevent zoom until bounce has completed
  130. this._cachedWheelPrecision = this._attachedCamera.wheelPrecision;
  131. this._attachedCamera.wheelPrecision = Infinity;
  132. this._attachedCamera.inertialRadiusOffset = 0;
  133. // Animate to the radius limit
  134. this.stopAllAnimations();
  135. this._radiusIsAnimating = true;
  136. let animatable = Animation.TransitionTo("radius", this._attachedCamera.radius + radiusDelta, this._attachedCamera, this._attachedCamera.getScene(), 60,
  137. this._radiusBounceTransition, this.transitionDuration, () => this._clearAnimationLocks());
  138. if (animatable) {
  139. this._animatables.push(animatable);
  140. }
  141. }
  142. /**
  143. * Removes all animation locks. Allows new animations to be added to any of the camera properties.
  144. */
  145. protected _clearAnimationLocks(): void {
  146. this._radiusIsAnimating = false;
  147. if (this._attachedCamera) {
  148. this._attachedCamera.wheelPrecision = this._cachedWheelPrecision;
  149. }
  150. }
  151. /**
  152. * Stops and removes all animations that have been applied to the camera
  153. */
  154. public stopAllAnimations(): void {
  155. if (this._attachedCamera) {
  156. this._attachedCamera.animations = [];
  157. }
  158. while (this._animatables.length) {
  159. this._animatables[0].onAnimationEnd = null;
  160. this._animatables[0].stop();
  161. this._animatables.shift();
  162. }
  163. }
  164. }
  165. }