Browse Source

New behavior: AutoRotation (for ArcRotateCamera)

David Catuhe 8 years ago
parent
commit
5ead201cb6

+ 2 - 1
Tools/Gulp/config.json

@@ -101,7 +101,8 @@
         {
         {
             "files": [
             "files": [
                 "../../src/Behaviors/Cameras/babylon.framingBehavior.js",
                 "../../src/Behaviors/Cameras/babylon.framingBehavior.js",
-                "../../src/Behaviors/Cameras/babylon.bouncingBehavior.js"
+                "../../src/Behaviors/Cameras/babylon.bouncingBehavior.js",
+                "../../src/Behaviors/Cameras/babylon.autoRotationBehavior.js"
             ],
             ],
             "dependUpon" : [
             "dependUpon" : [
                 "core"
                 "core"

File diff suppressed because it is too large
+ 6064 - 5867
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 6064 - 5867
dist/preview release/babylon.module.d.ts


+ 5 - 1
dist/preview release/what's new.md

@@ -3,7 +3,11 @@
 ## Major updates
 ## Major updates
 - Added support for non-pow2 textures when in WebGL2 mode ([deltakosh](https://github.com/deltakosh))
 - Added support for non-pow2 textures when in WebGL2 mode ([deltakosh](https://github.com/deltakosh))
 - Engine can now be initialized with an existing webgl context ([deltakosh](https://github.com/deltakosh))
 - Engine can now be initialized with an existing webgl context ([deltakosh](https://github.com/deltakosh))
-- Introducing behaviors. (Doc here)[] ([deltakosh](https://github.com/deltakosh))
+- Introduced behaviors. (Doc here)[http://doc.babylonjs.com/overviews/behaviors] ([deltakosh](https://github.com/deltakosh))
+- New behaviors for ArcRotateCamera:
+ - AutoRotation ([deltakosh](https://github.com/deltakosh))
+ - Framing ([deltakosh](https://github.com/deltakosh))
+ - Bouncing ([deltakosh](https://github.com/deltakosh))
 
 
 ## Updates
 ## Updates
 - POW2 textures rescale is now done by shaders (It was done using canvas before) ([deltakosh](https://github.com/deltakosh))
 - POW2 textures rescale is now done by shaders (It was done using canvas before) ([deltakosh](https://github.com/deltakosh))

+ 1 - 1
src/Animations/babylon.easing.ts

@@ -124,7 +124,7 @@
         }
         }
     }
     }
 
 
-    export class ExponentialEase  extends EasingFunction implements IEasingFunction {
+    export class ExponentialEase extends EasingFunction implements IEasingFunction {
         constructor(public exponent: number= 2) {
         constructor(public exponent: number= 2) {
             super();
             super();
         }
         }

+ 157 - 0
src/Behaviors/Cameras/babylon.autoRotationBehavior.ts

@@ -0,0 +1,157 @@
+module BABYLON {
+    export class AutoRotationBehavior implements Behavior<ArcRotateCamera> {
+        public get name(): string {
+            return "AutoRotation";
+        }
+
+        private _zoomStopsAnimation = false;
+		private _idleRotationSpeed = 0.05;
+		private _idleRotationWaitTime = 2000;
+        private _idleRotationSpinupTime = 2000;  
+       
+		/**
+		* Sets the flag that indicates if user zooming should stop model animation.
+		*/
+		public set zoomStopsAnimation(flag: boolean) {
+			this._zoomStopsAnimation = flag;
+		}
+
+		/**
+		* Gets the flag that indicates if user zooming should stop model animation.
+		*/
+		public get zoomStopsAnimation(): boolean {
+			return this._zoomStopsAnimation;
+        }       
+        
+		/**
+		* Sets the default speed at which the camera rotates around the model.
+		*/
+		public set idleRotationSpeed(speed: number) {
+			this._idleRotationSpeed = speed;
+		}
+
+		/**
+		* Gets the default speed at which the camera rotates around the model.
+		*/
+		public get idleRotationSpeed() {
+			return this._idleRotationSpeed;
+		}
+
+		/**
+		* Sets the time (in milliseconds) to wait after user interaction before the camera starts rotating.
+		*/
+		public set idleRotationWaitTime(time: number) {
+			this._idleRotationWaitTime = time;
+		}
+
+		/**
+		* Gets the time (milliseconds) to wait after user interaction before the camera starts rotating.
+		*/
+		public get idleRotationWaitTime() {
+			return this._idleRotationWaitTime;
+		}
+
+		/**
+		* Sets the time (milliseconds) to take to spin up to the full idle rotation speed.
+		*/
+		public set idleRotationSpinupTime(time: number) {
+			this._idleRotationSpinupTime = time;
+		}
+
+		/**
+		* Gets the time (milliseconds) to take to spin up to the full idle rotation speed.
+		*/
+		public get idleRotationSpinupTime() {
+			return this._idleRotationSpinupTime;
+		}
+        
+        // Default behavior functions
+        private _onPrePointerObservableObserver: Observer<PointerInfoPre>;
+        private _onAfterCheckInputsObserver: Observer<Camera>;
+        private _attachedCamera: ArcRotateCamera;
+        private _isPointerDown = false;
+        private _lastFrameTime: number = null;
+        private _lastInteractionTime = -Infinity;
+
+        public attach(camera: ArcRotateCamera): void {
+            this._attachedCamera = camera;
+            let scene = this._attachedCamera.getScene();
+
+            this._onPrePointerObservableObserver = scene.onPrePointerObservable.add((pointerInfoPre) => {
+                if (pointerInfoPre.type === PointerEventTypes.POINTERDOWN) {
+                    this._isPointerDown = true;
+                    return
+                }
+
+                if (pointerInfoPre.type === PointerEventTypes.POINTERUP) {
+                    this._isPointerDown = false;
+                }
+            });
+
+            this._onAfterCheckInputsObserver = camera.onAfterCheckInputsObservable.add(() => {      
+                let now = Tools.Now;
+                let dt = 16;
+                if (this._lastFrameTime != null) {
+                    dt =  now - this._lastFrameTime;
+                }
+                this._lastFrameTime = now;
+
+                // Stop the animation if there is user interaction and the animation should stop for this interaction
+                this._applyUserInteraction();
+    
+                let timeToRotation = now - this._lastInteractionTime - this._idleRotationWaitTime;
+				let scale = Math.max(Math.min(timeToRotation / (this._idleRotationSpinupTime), 1), 0);
+                let cameraRotationSpeed = this._idleRotationSpeed * scale;
+    
+                // Step camera rotation by rotation speed
+                this._attachedCamera.alpha -= cameraRotationSpeed * (dt / 1000);
+            });
+        }
+             
+        public detach(camera: ArcRotateCamera): void {
+            let scene = this._attachedCamera.getScene();
+            
+            scene.onPrePointerObservable.remove(this._onPrePointerObservableObserver);
+            camera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObserver);
+		}
+
+		/**
+		 * Returns true if user is scrolling. 
+		 * @return true if user is scrolling.
+		 */
+		private _userIsZooming(): boolean {
+			return this._attachedCamera.inertialRadiusOffset !== 0;
+		}   		
+		
+		private _lastFrameRadius = 0;
+		private _shouldAnimationStopForInteraction(): boolean {
+			var zoomHasHitLimit = false;
+			if (this._lastFrameRadius === this._attachedCamera.radius && this._attachedCamera.inertialRadiusOffset !== 0) {
+				zoomHasHitLimit = true;
+			}
+
+			// Update the record of previous radius - works as an approx. indicator of hitting radius limits
+			this._lastFrameRadius = this._attachedCamera.radius;
+			return this._zoomStopsAnimation ? zoomHasHitLimit : this._userIsZooming();
+		}   		
+
+		/**
+		 *  Applies any current user interaction to the camera. Takes into account maximum alpha rotation.
+		 */          
+        private _applyUserInteraction(): void {
+			if (this._userIsMoving() && !this._shouldAnimationStopForInteraction()) {
+                this._lastInteractionTime = Tools.Now;
+			}
+        }                
+   
+        // Tools
+        private _userIsMoving(): boolean {
+			return this._attachedCamera.inertialAlphaOffset !== 0 ||
+				this._attachedCamera.inertialBetaOffset !== 0 ||
+				this._attachedCamera.inertialRadiusOffset !== 0 ||
+				this._attachedCamera.inertialPanningX !== 0 ||
+				this._attachedCamera.inertialPanningY !== 0 ||
+				this._isPointerDown;
+		}
+    }
+}

+ 3 - 3
src/Behaviors/Cameras/babylon.bouncingBehavior.ts

@@ -35,10 +35,10 @@ module BABYLON {
         
         
         // Connection
         // Connection
         private _attachedCamera: ArcRotateCamera;
         private _attachedCamera: ArcRotateCamera;
-        private _onAfterCheckInputsObservabler: Observer<Camera>;
+        private _onAfterCheckInputsObserver: Observer<Camera>;
         public attach(camera: ArcRotateCamera): void {
         public attach(camera: ArcRotateCamera): void {
             this._attachedCamera = camera;
             this._attachedCamera = camera;
-            this._onAfterCheckInputsObservabler = camera.onAfterCheckInputsObservable.add(() => {
+            this._onAfterCheckInputsObserver = camera.onAfterCheckInputsObservable.add(() => {
 				// Add the bounce animation to the lower radius limit
 				// Add the bounce animation to the lower radius limit
 				if (this._isRadiusAtLimit(this._attachedCamera.lowerRadiusLimit)) {
 				if (this._isRadiusAtLimit(this._attachedCamera.lowerRadiusLimit)) {
 					this._applyBoundRadiusAnimation(this.lowerRadiusTransitionRange);
 					this._applyBoundRadiusAnimation(this.lowerRadiusTransitionRange);
@@ -52,7 +52,7 @@ module BABYLON {
         }
         }
         
         
         public detach(camera: ArcRotateCamera): void {
         public detach(camera: ArcRotateCamera): void {
-            camera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObservabler);
+            camera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObserver);
         }
         }
 
 
         // Animations
         // Animations

+ 306 - 1
src/Behaviors/Cameras/babylon.framingBehavior.ts

@@ -3,13 +3,318 @@ module BABYLON {
         public get name(): string {
         public get name(): string {
             return "Framing";
             return "Framing";
         }
         }
+
+        private _mode = FramingBehavior.IgnoreBoundsSizeMode;
+        private _radius = 0;
+        private _elevation = 0;
+        private _positionY = 0;
+        private _defaultElevation = 0;
+        private _elevationReturnTime = 2;
+        private _elevationReturnWaitTime = 0;
+        private _zoomStopsAnimation = false;
+		private _framingTime = 1.0;
+        private _easingFunction: EasingFunction = new ExponentialEase();
+        private _easingMode = EasingFunction.EASINGMODE_EASEINOUT;
         
         
-        public attach(camera: ArcRotateCamera): void {
+		/**
+		 * Gets the easing function to use for transitions
+		 */
+		public get easingFunction(): EasingFunction {
+            return this._easingFunction;
+        }
+        
+        /**
+		 * Sets the easing function to use for transitions
+		 */
+		public set easingFunction(value: EasingFunction) {
+            this._easingFunction = value;
+        }    
 
 
+		/**
+		 * Gets the easing function to use for transitions
+		 */
+		public get easingMode(): number {
+            return this._easingMode;
         }
         }
         
         
+        /**
+		 * Sets the easing function to use for transitions
+		 */
+		public set easingMode(value: number) {
+            this._easingMode = value;
+        }          
+
+        /**
+		 * Sets the current mode used by the behavior
+		 */
+		public set mode(mode: number) {
+			this._mode = mode;
+		}
+
+		/**
+		 * Gets current mode used by the behavior.
+		 */
+		public get mode(): number {
+			return this._mode;
+        }
+        
+	    /**
+		 * Sets the radius of the camera relative to the framed model's bounding box.
+		 */
+		public set radius(radius: number) {
+			this._radius = radius;
+		}
+
+		/**
+		 * Gets the radius of the camera relative to the framed model's bounding box.
+		 */
+		public get radius(): number {
+			return this._radius;
+		}
+
+		/**
+		 * Sets the elevation of the camera from the framed model, in radians.
+		 */
+		public set elevation(elevation: number) {
+			this._elevation = elevation;
+		}
+
+		/**
+		 * Gets the elevation of the camera from the framed model, in radians.
+		 */
+		public get elevation(): number {
+			return this._elevation;
+		}
+
+		/**
+		 * Sets the Y offset of the primary model from the camera's focus.
+		 */
+		public set positionY(positionY: number) {
+			this._positionY = positionY;
+		}
+
+		/**
+		 * Gets the Y offset of the primary model from the camera's focus.
+		 */
+		public get positionY(): number {
+			return this._positionY;
+		}
+
+		/**
+		* Sets the angle above/below the horizontal plane to return to when the return to default elevation idle
+		* behaviour is triggered, in radians.
+		*/
+		public set defaultElevation(elevation: number) {
+			this._defaultElevation = elevation;
+		}
+
+		/**
+		* Gets the angle above/below the horizontal plane to return to when the return to default elevation idle
+		* behaviour is triggered, in radians.
+		*/
+		public get defaultElevation() {
+			return this._defaultElevation;
+		}
+
+		/**
+		 * Sets the time (in milliseconds) taken to return to the default beta position.
+		 * Negative value indicates camera should not return to default.
+		 */
+		public set elevationReturnTime(speed: number) {
+			this._elevationReturnTime = speed;
+		}
+
+		/**
+		 * Gets the time (in milliseconds) taken to return to the default beta position.
+		 * Negative value indicates camera should not return to default.
+		 */
+		public get elevationReturnTime(): number {
+			return this._elevationReturnTime;
+		}
+
+		/**
+		 * Sets the delay (in milliseconds) taken before the camera returns to the default beta position.
+		 */
+		public set elevationReturnWaitTime(time: number) {
+			this._elevationReturnWaitTime = time;
+		}
+
+		/**
+		 * Gets the delay (in milliseconds) taken before the camera returns to the default beta position.
+		 */
+		public get elevationReturnWaitTime(): number {
+			return this._elevationReturnWaitTime;
+		}
+
+		/**
+		* Sets the flag that indicates if user zooming should stop model animation.
+		*/
+		public set zoomStopsAnimation(flag: boolean) {
+			this._zoomStopsAnimation = flag;
+		}
+
+		/**
+		* Gets the flag that indicates if user zooming should stop model animation.
+		*/
+		public get zoomStopsAnimation(): boolean {
+			return this._zoomStopsAnimation;
+        }       
+        	
+		/**
+		 * Sets the transition time when framing the model, in milliseconds
+		*/
+		public set framingTime(time: number) {
+			this._framingTime = time;
+		}
+
+        /**
+         * Gets the transition time when framing the model, in milliseconds
+        */
+        public get framingTime() {
+            return this._framingTime;
+		}        
+        
+        // Default behavior functions
+        private _onPrePointerObservableObserver: Observer<PointerInfoPre>;
+        private _onAfterCheckInputsObserver: Observer<Camera>;
+        private _attachedCamera: ArcRotateCamera;
+        private _isPointerDown = false;
+        private _lastFrameTime: number = null;
+        private _lastInteractionTime = -Infinity;
+
+        public attach(camera: ArcRotateCamera): void {
+            this._attachedCamera = camera;
+            let scene = this._attachedCamera.getScene();
+
+            this._onPrePointerObservableObserver = scene.onPrePointerObservable.add((pointerInfoPre) => {
+                if (pointerInfoPre.type === PointerEventTypes.POINTERDOWN) {
+                    this._isPointerDown = true;
+                    return
+                }
+
+                if (pointerInfoPre.type === PointerEventTypes.POINTERUP) {
+                    this._isPointerDown = false;
+                }
+            });
+
+            this._onAfterCheckInputsObserver = camera.onAfterCheckInputsObservable.add(() => {      
+                // Stop the animation if there is user interaction and the animation should stop for this interaction
+                this._applyUserInteraction();
+       
+                // Maintain the camera above the ground. If the user pulls the camera beneath the ground plane, lift it
+                // back to the default position after a given timeout
+                this._maintainCameraAboveGround();                
+            });
+        }
+             
         public detach(camera: ArcRotateCamera): void {
         public detach(camera: ArcRotateCamera): void {
+            let scene = this._attachedCamera.getScene();
             
             
+            scene.onPrePointerObservable.remove(this._onPrePointerObservableObserver);
+            camera.onAfterCheckInputsObservable.remove(this._onAfterCheckInputsObserver);
         }
         }
+
+        // Framing control
+        private _animatables = new Array<BABYLON.Animatable>();
+        private _betaIsAnimating = false;
+        private _betaTransition: BABYLON.Animation;
+        private _lastFrameRadius = 0;
+
+		/**
+		 * Keeps the camera above the ground plane. If the user pulls the camera below the ground plane, the camera
+		 * is automatically returned to its default position (expected to be above ground plane). 
+		 */
+		private _maintainCameraAboveGround(): void {
+			let timeSinceInteraction = Tools.Now - this._lastInteractionTime;
+			let defaultBeta = Math.PI * 0.5 - this._defaultElevation;
+			let limitBeta = Math.PI * 0.5;
+            
+            // Bring the camera back up if below the ground plane
+			if (!this._betaIsAnimating && this._attachedCamera.beta > limitBeta && timeSinceInteraction >= this._elevationReturnWaitTime) {
+                this._betaIsAnimating = true;
+                
+				//Transition to new position
+                this.stopAllAnimations();
+                
+                if (!this._betaTransition) {
+                    this.easingFunction.setEasingMode(this.easingMode);
+                    this._betaTransition = Animation.CreateAnimation("beta", Animation.ANIMATIONTYPE_FLOAT, 60, this.easingFunction);
+                }
+
+				Animation.TransitionTo("beta", defaultBeta, this._attachedCamera, this._attachedCamera.getScene(), 60,
+                    this._betaTransition, this._elevationReturnTime, 
+                    () => {
+						this.stopAllAnimations();
+					});
+			}
+		}        
+
+		/**
+		 * Returns true if user is scrolling. 
+		 * @return true if user is scrolling.
+		 */
+		protected _userIsZooming(): boolean {
+			return this._attachedCamera.inertialRadiusOffset !== 0;
+		}        
+
+		/**
+		 * Indicates if default model animation (rotation) should stop for the current user interaction.
+		 * 
+		 * @return True if the model animation (rotation) should stop when given the current user interaction.
+		 */
+		protected _shouldAnimationStopForInteraction(): boolean {
+			var zoomHasHitLimit = false;
+			if (this._lastFrameRadius === this._attachedCamera.radius && this._attachedCamera.inertialRadiusOffset !== 0) {
+				zoomHasHitLimit = true;
+			}
+
+			// Update the record of previous radius - works as an approx. indicator of hitting radius limits
+			this._lastFrameRadius = this._attachedCamera.radius;
+			return this._zoomStopsAnimation ? zoomHasHitLimit : this._userIsZooming();
+		}        
+
+		/**
+		 *  Applies any current user interaction to the camera. Takes into account maximum alpha rotation.
+		 */          
+        private _applyUserInteraction(): void {
+			if (this._userIsMoving() && !this._shouldAnimationStopForInteraction()) {
+                this._lastInteractionTime = Tools.Now;
+				this.stopAllAnimations();
+			}
+        }                
+        
+		/**
+		 * Stops and removes all animations that have been applied to the camera
+		 */        
+        public stopAllAnimations(): void {
+			this._attachedCamera.animations = [];
+			while (this._animatables.length) {
+				this._animatables[0].onAnimationEnd = null;
+				this._animatables[0].stop();
+				this._animatables.shift();
+			}
+		}        
+
+        // Tools
+        private _userIsMoving(): boolean {
+			return this._attachedCamera.inertialAlphaOffset !== 0 ||
+				this._attachedCamera.inertialBetaOffset !== 0 ||
+				this._attachedCamera.inertialRadiusOffset !== 0 ||
+				this._attachedCamera.inertialPanningX !== 0 ||
+				this._attachedCamera.inertialPanningY !== 0 ||
+				this._isPointerDown;
+		}
+
+        // Statics
+
+        /**
+         * The camera can move all the way towards the model.
+         */
+        public static IgnoreBoundsSizeMode = 0;
+
+        /**
+         * The camera is not allowed to zoom closer to the model than the point at which the adjusted bounding sphere touches the frustum sides
+         */
+        public static FitFrustumSidesMode = 0;
     }
     }
 }
 }

+ 39 - 0
src/Cameras/babylon.arcRotateCamera.ts

@@ -214,6 +214,45 @@ module BABYLON {
             }
             }
         }
         }
 
 
+        private _framingBehavior: FramingBehavior;
+        public get useFramingBehavior(): boolean {
+            return this._framingBehavior != null;
+        }
+
+        public set useFramingBehavior(value: boolean) {
+            if (value === this.useFramingBehavior) {
+                return;
+            }
+
+            if (value) {
+                this._framingBehavior = new FramingBehavior();
+                this.addBehavior(this._framingBehavior);
+            } else {
+                this.removeBehavior(this._framingBehavior);
+                this._framingBehavior = null;
+            }
+        }        
+
+        private _autoRotationBehavior: AutoRotationBehavior;
+        public get useAutoRotationBehavior(): boolean {
+            return this._autoRotationBehavior != null;
+        }
+
+        public set useAutoRotationBehavior(value: boolean) {
+            if (value === this.useAutoRotationBehavior) {
+                return;
+            }
+
+            if (value) {
+                this._autoRotationBehavior = new AutoRotationBehavior();
+                this.addBehavior(this._autoRotationBehavior);
+            } else {
+                this.removeBehavior(this._autoRotationBehavior);
+                this._autoRotationBehavior = null;
+            }
+        }        
+        
+
         // Collisions
         // Collisions
         public onCollide: (collidedMesh: AbstractMesh) => void;
         public onCollide: (collidedMesh: AbstractMesh) => void;
         public checkCollisions = false;
         public checkCollisions = false;