Преглед изворни кода

Merge pull request #5286 from phuein/master

FlyCamera
David Catuhe пре 6 година
родитељ
комит
bedcd3a8e7

+ 13 - 0
Tools/Gulp/config.json

@@ -44,6 +44,7 @@
         "all": [
             "pbrMaterial",
             "freeCamera",
+            "flyCamera",
             "arcRotateCamera",
             "hemisphericLight",
             "pointLight",
@@ -131,6 +132,7 @@
         "minimal": [
             "meshBuilder",
             "freeCamera",
+            "flyCamera",
             "hemisphericLight"
         ],
         "360Viewer": [
@@ -540,6 +542,17 @@
                 "targetCamera"
             ]
         },
+        "flyCamera": {
+            "files": [
+                "../../src/Cameras/Inputs/babylon.flyCameraMouseInput.js",
+                "../../src/Cameras/Inputs/babylon.flyCameraKeyboardInput.js",
+                "../../src/Cameras/babylon.flyCameraInputsManager.js",
+                "../../src/Cameras/babylon.flyCamera.js"
+            ],
+            "dependUpon": [
+                "targetCamera"
+            ]
+        },
         "arcRotateCamera": {
             "files": [
                 "../../src/Cameras/Inputs/babylon.arcRotateCameraKeyboardMoveInput.js",

+ 2 - 0
dist/preview release/what's new.md

@@ -24,6 +24,8 @@
 
 ### Core Engine
 
+- Added FlyCamera for free navigation in 3D space, with a limited set of settings.
+
 ### Viewer
 
 ### Loaders

+ 194 - 0
src/Cameras/Inputs/babylon.flyCameraKeyboardInput.ts

@@ -0,0 +1,194 @@
+module BABYLON {
+    /**
+     * Listen to keyboard events to control the camera.
+     * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
+     */
+    export class FlyCameraKeyboardInput implements ICameraInput<FlyCamera> {
+        /**
+         * Defines the camera the input is attached to.
+         */
+        public camera: FlyCamera;
+
+        /**
+         * The list of keyboard keys used to control the forward move of the camera.
+         */
+        @serialize()
+        public keysForward = [87];
+
+        /**
+         * The list of keyboard keys used to control the backward move of the camera.
+         */
+        @serialize()
+        public keysBackward = [83];
+
+        /**
+         * The list of keyboard keys used to control the forward move of the camera.
+         */
+        @serialize()
+        public keysUp = [69];
+
+        /**
+         * The list of keyboard keys used to control the backward move of the camera.
+         */
+        @serialize()
+        public keysDown = [81];
+
+        /**
+         * The list of keyboard keys used to control the right strafe move of the camera.
+         */
+        @serialize()
+        public keysRight = [68];
+
+        /**
+         * The list of keyboard keys used to control the left strafe move of the camera.
+         */
+        @serialize()
+        public keysLeft = [65];
+
+        private _keys = new Array<number>();
+        private _onCanvasBlurObserver: Nullable<Observer<Engine>>;
+        private _onKeyboardObserver: Nullable<Observer<KeyboardInfo>>;
+        private _engine: Engine;
+        private _scene: Scene;
+
+        /**
+         * Attach the input controls to a specific dom element to get the input from.
+         * @param element Defines the element the controls should be listened from
+         * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
+         */
+        public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
+            if (this._onCanvasBlurObserver) {
+                return;
+            }
+
+            this._scene = this.camera.getScene();
+            this._engine = this._scene.getEngine();
+
+            this._onCanvasBlurObserver = this._engine.onCanvasBlurObservable.add(() => {
+                this._keys = [];
+            });
+
+            this._onKeyboardObserver = this._scene.onKeyboardObservable.add((info) => {
+                let evt = info.event;
+
+                if (info.type === KeyboardEventTypes.KEYDOWN) {
+                    if (this.keysForward.indexOf(evt.keyCode) !== -1 ||
+                        this.keysBackward.indexOf(evt.keyCode) !== -1 ||
+                        this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+
+                        if (index === -1) {
+                            this._keys.push(evt.keyCode);
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                } else {
+                    if (this.keysForward.indexOf(evt.keyCode) !== -1 ||
+                        this.keysBackward.indexOf(evt.keyCode) !== -1 ||
+                        this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+
+                        if (index >= 0) {
+                            this._keys.splice(index, 1);
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                }
+            });
+        }
+
+        /**
+         * Detach the current controls from the specified dom element.
+         * @param element Defines the element to stop listening the inputs from
+         */
+        public detachControl(element: Nullable<HTMLElement>): void {
+            if (this._scene) {
+                if (this._onKeyboardObserver) {
+                    this._scene.onKeyboardObservable.remove(this._onKeyboardObserver);
+                }
+
+                if (this._onCanvasBlurObserver) {
+                    this._engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver);
+                }
+                this._onKeyboardObserver = null;
+                this._onCanvasBlurObserver = null;
+            }
+            this._keys = [];
+        }
+
+        /**
+         * Gets the class name of the current intput.
+         * @returns the class name
+         */
+        public getClassName(): string {
+            return "FlyCameraKeyboardInput";
+        }
+
+        /** @hidden */
+        public _onLostFocus(e: FocusEvent): void {
+            this._keys = [];
+        }
+
+        /**
+         * Get the friendly name associated with the input class.
+         * @returns the input friendly name
+         */
+        public getSimpleName(): string {
+            return "keyboard";
+        }
+
+        /**
+         * Update the current camera state depending on the inputs that have been used this frame.
+         * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
+         */
+        public checkInputs(): void {
+            if (this._onKeyboardObserver) {
+                var camera = this.camera;
+                // Keyboard
+                for (var index = 0; index < this._keys.length; index++) {
+                    var keyCode = this._keys[index];
+                    var speed = camera._computeLocalCameraSpeed();
+
+                    if (this.keysForward.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(0, 0, speed);
+                    } else
+                    if (this.keysBackward.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(0, 0, -speed);
+                    } else
+                    if (this.keysUp.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(0, speed, 0);
+                    } else
+                    if (this.keysDown.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(0, -speed, 0);
+                    } else
+                    if (this.keysRight.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(speed, 0, 0);
+                    } else
+                    if (this.keysLeft.indexOf(keyCode) !== -1) {
+                      camera._localDirection.copyFromFloats(-speed, 0, 0);
+                    }
+
+                    if (camera.getScene().useRightHandedSystem) {
+                        camera._localDirection.z *= -1;
+                    }
+
+                    camera.getViewMatrix().invertToRef(camera._cameraTransformMatrix);
+                    Vector3.TransformNormalToRef(camera._localDirection, camera._cameraTransformMatrix, camera._transformedDirection);
+                    camera.cameraDirection.addInPlace(camera._transformedDirection);
+                }
+            }
+        }
+    }
+
+    (<any>CameraInputTypes)["FlyCameraKeyboardInput"] = FlyCameraKeyboardInput;
+}

+ 306 - 0
src/Cameras/Inputs/babylon.flyCameraMouseInput.ts

@@ -0,0 +1,306 @@
+module BABYLON {
+    /**
+     * Listen to mouse events to control the camera.
+     * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
+     */
+    export class FlyCameraMouseInput implements ICameraInput<FlyCamera> {
+        /**
+         * Defines the camera the input is attached to.
+         */
+        public camera: FlyCamera;
+
+        /**
+         * Defines if touch is enabled. (Default is true.)
+         */
+        public touchEnabled: boolean;
+
+        /**
+         * Defines the buttons associated with the input to handle camera rotation.
+         */
+        @serialize()
+        public buttons = [0, 1, 2];
+
+        /**
+         * Assign buttons for Yaw control.
+         */
+        public buttonsYaw: number[]    = [-1, 0, 1];
+
+        /**
+        * Assign buttons for Pitch control.
+        */
+        public buttonsPitch: number[]  = [-1, 0, 1];
+
+        /**
+        * Assign buttons for Roll control.
+        */
+        public buttonsRoll: number[]   = [2];
+
+        /**
+         * Detect if any button is being pressed while mouse is moved.
+         * -1 = Mouse locked.
+         * 0 = Left button.
+         * 1 = Middle Button.
+         * 2 = Right Button.
+         */
+        public activeButton: number = -1;
+
+        /**
+         * Defines the pointer's angular sensibility, to control the camera rotation speed.
+         * Higher values reduce its sensitivity.
+         */
+        @serialize()
+        public angularSensibility = 1000.0;
+
+        private _mousemoveCallback: (e: MouseEvent) => void;
+        private _observer: Nullable<Observer<PointerInfo>>;
+        private _rollObserver: Nullable<Observer<Scene>>;
+        private previousPosition: Nullable<{ x: number, y: number }> = null;
+        private noPreventDefault: boolean | undefined;
+        private element: HTMLElement;
+
+        /**
+         * Listen to mouse events to control the camera.
+         * @param touchEnabled Define if touch is enabled. (Default is true.)
+         * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
+         */
+        constructor(touchEnabled = true) {
+        }
+
+        /**
+         * Attach the mouse control to the HTML DOM element.
+         * @param element Defines the element that listens to the input events.
+         * @param noPreventDefault Defines whether events caught by the controls should call preventdefault().
+         */
+        public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
+            this.element = element;
+            this.noPreventDefault = noPreventDefault;
+
+            this._observer = this.camera.getScene().onPointerObservable.add(
+                (p: any, s: any) => {
+                    this._pointerInput(p, s);
+                },
+                PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE
+            );
+
+            // Correct Roll by rate, if enabled.
+            this._rollObserver = this.camera.getScene().onBeforeRenderObservable.add(
+                () => {
+                    if (this.camera.rollCorrect) {
+                        this.camera.restoreRoll(this.camera.rollCorrect);
+                    }
+                }
+            );
+
+            // Helper function to keep 'this'.
+            this._mousemoveCallback = (e: any) => {
+                this._onMouseMove(e);
+            };
+            element.addEventListener("mousemove", this._mousemoveCallback, false);
+        }
+
+        /**
+         * Detach the current controls from the specified dom element.
+         * @param element Defines the element to stop listening the inputs from
+         */
+        public detachControl(element: Nullable<HTMLElement>): void {
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+
+                this.camera.getScene().onBeforeRenderObservable.remove(this._rollObserver);
+
+                if (this._mousemoveCallback) {
+                    element.removeEventListener("mousemove", this._mousemoveCallback);
+                }
+
+                this._observer = null;
+                this._rollObserver = null;
+                this.previousPosition = null;
+                this.noPreventDefault = undefined;
+            }
+        }
+
+        /**
+         * Gets the class name of the current input.
+         * @returns the class name.
+         */
+        public getClassName(): string {
+            return "FlyCameraMouseInput";
+        }
+
+        /**
+         * Get the friendly name associated with the input class.
+         * @returns the input's friendly name.
+         */
+        public getSimpleName(): string {
+            return "mouse";
+        }
+
+        // Track mouse movement, when the pointer is not locked.
+        private _pointerInput(p: any, s: any): void {
+            var e = <PointerEvent>p.event;
+
+            let camera = this.camera;
+            let engine = camera.getEngine();
+
+            if (engine.isInVRExclusivePointerMode) {
+                return;
+            }
+
+            if (!this.touchEnabled && e.pointerType === "touch") {
+                return;
+            }
+
+            // Mouse is moved but an unknown mouse button is pressed.
+            if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(e.button) === -1) {
+                return;
+            }
+
+            var srcElement = <HTMLElement>(e.srcElement || e.target);
+
+            // Mouse down.
+            if (p.type === PointerEventTypes.POINTERDOWN && srcElement) {
+                try {
+                    srcElement.setPointerCapture(e.pointerId);
+                } catch (e) {
+                    // Nothing to do with the error. Execution continues.
+                }
+
+                this.previousPosition = {
+                    x: e.clientX,
+                    y: e.clientY
+                };
+
+                this.activeButton = e.button;
+
+                if (!this.noPreventDefault) {
+                    e.preventDefault();
+                    this.element.focus();
+                }
+            } else
+            // Mouse up.
+            if (p.type === PointerEventTypes.POINTERUP && srcElement) {
+                try {
+                    srcElement.releasePointerCapture(e.pointerId);
+                } catch (e) {
+                    // Nothing to do with the error. Execution continues.
+                }
+
+                this.activeButton = -1;
+
+                this.previousPosition = null;
+                if (!this.noPreventDefault) {
+                    e.preventDefault();
+                }
+            } else
+            // Mouse move.
+            if (p.type === PointerEventTypes.POINTERMOVE) {
+                if (!this.previousPosition || engine.isPointerLock) {
+                    return;
+                }
+
+                var offsetX = e.clientX - this.previousPosition.x;
+                var offsetY = e.clientY - this.previousPosition.y;
+
+                this.rotateCamera(offsetX, offsetY);
+
+                this.previousPosition = {
+                    x: e.clientX,
+                    y: e.clientY
+                };
+
+                if (!this.noPreventDefault) {
+                    e.preventDefault();
+                }
+            }
+        }
+
+        // Track mouse movement, when pointer is locked.
+        private _onMouseMove(e: any): void {
+            let camera = this.camera;
+            let engine = camera.getEngine();
+
+            if (!engine.isPointerLock || engine.isInVRExclusivePointerMode) {
+                return;
+            }
+
+            var offsetX = e.movementX || e.mozMovementX || e.webkitMovementX || e.msMovementX || 0;
+            var offsetY = e.movementY || e.mozMovementY || e.webkitMovementY || e.msMovementY || 0;
+
+            this.rotateCamera(offsetX, offsetY);
+
+            this.previousPosition = null;
+
+            if (!this.noPreventDefault) {
+                e.preventDefault();
+            }
+        }
+
+        /**
+         * Rotate camera by mouse offset.
+         */
+        private rotateCamera(offsetX: number, offsetY: number): void {
+            let camera = this.camera;
+            let scene = this.camera.getScene();
+
+            if (scene.useRightHandedSystem) {
+              offsetX *= -1;
+            }
+
+            if (camera.parent && camera.parent._getWorldMatrixDeterminant() < 0) {
+                offsetX *= -1;
+            }
+
+            var x = offsetX / this.angularSensibility;
+            var y = offsetY / this.angularSensibility;
+
+            // Initialize to current rotation.
+            var currentRotation = BABYLON.Quaternion.RotationYawPitchRoll(
+                camera.rotation.y,
+                camera.rotation.x,
+                camera.rotation.z
+            );
+            var rotationChange: BABYLON.Quaternion;
+
+            // Pitch.
+            if (this.buttonsPitch.some((v) => { return v === this.activeButton; })) {
+                // Apply change in Radians to vector Angle.
+                rotationChange = BABYLON.Quaternion.RotationAxis(BABYLON.Axis.X, y);
+                // Apply Pitch to quaternion.
+                currentRotation.multiplyInPlace(rotationChange);
+            }
+
+            // Yaw.
+            if (this.buttonsYaw.some((v) => { return v === this.activeButton; })) {
+                // Apply change in Radians to vector Angle.
+                rotationChange = BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Y, x);
+                // Apply Yaw to quaternion.
+                currentRotation.multiplyInPlace(rotationChange);
+
+                // Add Roll, if banked turning is enabled, within Roll limit.
+                let limit = (camera.bankedTurnLimit) + camera._trackRoll; // Defaults to 90° plus manual roll.
+                if (camera.bankedTurn && -limit < camera.rotation.z && camera.rotation.z < limit) {
+                    let bankingDelta = camera.bankedTurnMultiplier * -x;
+                    // Apply change in Radians to vector Angle.
+                    rotationChange = BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Z, bankingDelta);
+                    // Apply Yaw to quaternion.
+                    currentRotation.multiplyInPlace(rotationChange);
+                }
+            }
+
+            // Roll.
+            if (this.buttonsRoll.some((v) => { return v === this.activeButton; })) {
+                // Apply change in Radians to vector Angle.
+                rotationChange = BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Z, -x);
+                // Track Rolling.
+                camera._trackRoll -= x;
+                // Apply Pitch to quaternion.
+                currentRotation.multiplyInPlace(rotationChange);
+            }
+
+            // Apply rotationQuaternion to Euler camera.rotation.
+            currentRotation.toEulerAnglesToRef(camera.rotation);
+        }
+    }
+
+    (<any>CameraInputTypes)["FlyCameraMouseInput"] = FlyCameraMouseInput;
+}

+ 420 - 0
src/Cameras/babylon.flyCamera.ts

@@ -0,0 +1,420 @@
+module BABYLON {
+    /**
+     * This is a flying camera, designed for 3D movement and rotation in all directions,
+     * such as in a 3D Space Shooter or a Flight Simulator.
+     */
+    export class FlyCamera extends TargetCamera {
+        /**
+         * Define the collision ellipsoid of the camera.
+         * This is helpful for simulating a camera body, like a player's body.
+         * @see http://doc.babylonjs.com/babylon101/cameras,_mesh_collisions_and_gravity#arcrotatecamera
+         */
+        @serializeAsVector3()
+        public ellipsoid = new Vector3(1, 1, 1);
+
+        /**
+         * Define an offset for the position of the ellipsoid around the camera.
+         * This can be helpful if the camera is attached away from the player's body center,
+         * such as at its head.
+         */
+        @serializeAsVector3()
+        public ellipsoidOffset = new Vector3(0, 0, 0);
+
+        /**
+         * Enable or disable collisions of the camera with the rest of the scene objects.
+         */
+        @serialize()
+        public checkCollisions = false;
+
+        /**
+         * Enable or disable gravity on the camera.
+         */
+        @serialize()
+        public applyGravity = false;
+
+        /**
+         * Define the current direction the camera is moving to.
+         */
+        public cameraDirection = BABYLON.Vector3.Zero();
+
+        /**
+         * Define the current local rotation of the camera as a quaternion to prevent Gimbal lock.
+         * This overrides and empties cameraRotation.
+         */
+        public rotationQuaternion: BABYLON.Quaternion;
+
+        /**
+         * Track Roll to maintain the wanted Rolling when looking around.
+         */
+        public _trackRoll: number = 0;
+
+        /**
+        * Slowly correct the Roll to its original value after a Pitch+Yaw rotation.
+        */
+        public rollCorrect: number = 100;
+
+        /**
+         * Mimic a banked turn, Rolling the camera when Yawing.
+         * It's recommended to use rollCorrect = 10 for faster banking correction.
+         */
+        public bankedTurn: boolean = false;
+
+        /**
+         * Limit in radians for how much Roll banking will add. (Default: 90°)
+         */
+        public bankedTurnLimit: number = Math.PI / 4;
+
+        /**
+         * Value of 0 disables the banked Roll.
+         * Value of 1 is equal to the Yaw angle in radians.
+         */
+        public bankedTurnMultiplier: number = 1;
+
+        /**
+         * The inputs manager loads all the input sources, such as keyboard and mouse.
+         */
+        public inputs: FlyCameraInputsManager;
+
+        /**
+         * Gets the input sensibility for mouse input.
+         * Higher values reduce sensitivity.
+         */
+        public get angularSensibility(): number {
+            var mouse = <FlyCameraMouseInput>this.inputs.attached["mouse"];
+            if (mouse) {
+                return mouse.angularSensibility;
+            }
+
+            return 0;
+        }
+
+        /**
+         * Sets the input sensibility for a mouse input.
+         * Higher values reduce sensitivity.
+         */
+        public set angularSensibility(value: number) {
+            var mouse = <FlyCameraMouseInput>this.inputs.attached["mouse"];
+            if (mouse) {
+                mouse.angularSensibility = value;
+            }
+        }
+
+        /**
+         * Get the keys for camera movement forward.
+         */
+        public get keysForward(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysForward;
+            }
+
+            return [];
+        }
+
+        /**
+        * Set the keys for camera movement forward.
+        */
+        public set keysForward(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysForward = value;
+            }
+        }
+
+        /**
+         * Get the keys for camera movement backward.
+         */
+        public get keysBackward(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysBackward;
+            }
+
+            return [];
+        }
+
+        public set keysBackward(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysBackward = value;
+            }
+        }
+
+        /**
+         * Get the keys for camera movement up.
+         */
+        public get keysUp(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysUp;
+            }
+
+            return [];
+        }
+
+        /**
+        * Set the keys for camera movement up.
+        */
+        public set keysUp(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysUp = value;
+            }
+        }
+
+        /**
+         * Get the keys for camera movement down.
+         */
+        public get keysDown(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysDown;
+            }
+
+            return [];
+        }
+
+        /**
+        * Set the keys for camera movement down.
+        */
+        public set keysDown(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysDown = value;
+            }
+        }
+
+        /**
+         * Get the keys for camera movement left.
+         */
+        public get keysLeft(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysLeft;
+            }
+
+            return [];
+        }
+
+        /**
+        * Set the keys for camera movement left.
+        */
+        public set keysLeft(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysLeft = value;
+            }
+        }
+
+        /**
+         * Set the keys for camera movement right.
+         */
+        public get keysRight(): number[] {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                return keyboard.keysRight;
+            }
+
+            return [];
+        }
+
+        /**
+        * Set the keys for camera movement right.
+        */
+        public set keysRight(value: number[]) {
+            var keyboard = <FlyCameraKeyboardInput>this.inputs.attached["keyboard"];
+            if (keyboard) {
+                keyboard.keysRight = value;
+            }
+        }
+
+        /**
+         * Event raised when the camera collides with a mesh in the scene.
+         */
+        public onCollide: (collidedMesh: AbstractMesh) => void;
+
+        private _collider: Collider;
+        private _needMoveForGravity = false;
+        private _oldPosition = Vector3.Zero();
+        private _diffPosition = Vector3.Zero();
+        private _newPosition = Vector3.Zero();
+
+        /** @hidden */
+        public _localDirection: Vector3;
+        /** @hidden */
+        public _transformedDirection: Vector3;
+
+        /**
+         * Instantiates a FlyCamera.
+         * This is a flying camera, designed for 3D movement and rotation in all directions,
+         * such as in a 3D Space Shooter or a Flight Simulator.
+         * @param name Define the name of the camera in the scene.
+         * @param position Define the starting position of the camera in the scene.
+         * @param scene Define the scene the camera belongs to.
+         * @param setActiveOnSceneIfNoneActive Defines wheter the camera should be marked as active, if no other camera has been defined as active.
+        */
+        constructor(name: string, position: Vector3, scene: Scene, setActiveOnSceneIfNoneActive = true) {
+            super(name, position, scene, setActiveOnSceneIfNoneActive);
+            this.inputs = new FlyCameraInputsManager(this);
+            this.inputs.addKeyboard().addMouse();
+        }
+
+        /**
+         * Attach a control to the HTML DOM element.
+         * @param element Defines the element that listens to the input events.
+         * @param noPreventDefault Defines whether events caught by the controls should call preventdefault(). https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
+         */
+        public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
+            this.inputs.attachElement(element, noPreventDefault);
+        }
+
+        /**
+         * Detach a control from the HTML DOM element.
+         * The camera will stop reacting to that input.
+         * @param element Defines the element that listens to the input events.
+         */
+        public detachControl(element: HTMLElement): void {
+            this.inputs.detachElement(element);
+
+            this.cameraDirection = new Vector3(0, 0, 0);
+        }
+
+        // Collisions.
+        private _collisionMask = -1;
+
+        /**
+         * Get the mask that the camera ignores in collision events.
+         */
+        public get collisionMask(): number {
+            return this._collisionMask;
+        }
+
+        /**
+        * Set the mask that the camera ignores in collision events.
+        */
+        public set collisionMask(mask: number) {
+            this._collisionMask = !isNaN(mask) ? mask : -1;
+        }
+
+        /** @hidden */
+        public _collideWithWorld(displacement: Vector3): void {
+            var globalPosition: Vector3;
+
+            if (this.parent) {
+                globalPosition = Vector3.TransformCoordinates(this.position, this.parent.getWorldMatrix());
+            } else {
+                globalPosition = this.position;
+            }
+
+            globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPosition);
+            this._oldPosition.addInPlace(this.ellipsoidOffset);
+
+            if (!this._collider) {
+                this._collider = new Collider();
+            }
+
+            this._collider._radius = this.ellipsoid;
+            this._collider.collisionMask = this._collisionMask;
+
+            // No need for clone, as long as gravity is not on.
+            var actualDisplacement = displacement;
+
+            // Add gravity to direction to prevent dual-collision checking.
+            if (this.applyGravity) {
+                // This prevents mending with cameraDirection, a global variable of the fly camera class.
+                actualDisplacement = displacement.add(this.getScene().gravity);
+            }
+
+            this.getScene().collisionCoordinator.getNewPosition(this._oldPosition, actualDisplacement, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
+        }
+
+        /** @hidden */
+        private _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: Nullable<AbstractMesh> = null) => {
+            // TODO Move this to the collision coordinator!
+            if (this.getScene().workerCollisions) {
+                newPosition.multiplyInPlace(this._collider._radius);
+            }
+
+            var updatePosition = (newPos: Vector3) => {
+                this._newPosition.copyFrom(newPos);
+
+                this._newPosition.subtractToRef(this._oldPosition, this._diffPosition);
+
+                if (this._diffPosition.length() > Engine.CollisionsEpsilon) {
+                    this.position.addInPlace(this._diffPosition);
+                    if (this.onCollide && collidedMesh) {
+                        this.onCollide(collidedMesh);
+                    }
+                }
+            };
+
+            updatePosition(newPosition);
+        }
+
+        /** @hidden */
+        public _checkInputs(): void {
+            if (!this._localDirection) {
+                this._localDirection = Vector3.Zero();
+                this._transformedDirection = Vector3.Zero();
+            }
+
+            this.inputs.checkInputs();
+
+            super._checkInputs();
+        }
+
+        /** @hidden */
+        public _decideIfNeedsToMove(): boolean {
+            return this._needMoveForGravity || Math.abs(this.cameraDirection.x) > 0 || Math.abs(this.cameraDirection.y) > 0 || Math.abs(this.cameraDirection.z) > 0;
+        }
+
+        /** @hidden */
+        public _updatePosition(): void {
+            if (this.checkCollisions && this.getScene().collisionsEnabled) {
+                this._collideWithWorld(this.cameraDirection);
+            } else {
+                super._updatePosition();
+            }
+        }
+
+        /**
+         * Restore the Roll to its target value at the rate specified.
+         * @param rate - Higher means slower restoring.
+         * @hidden
+         */
+        public restoreRoll(rate: number): void {
+            let limit = this._trackRoll;    // Target Roll.
+            let z = this.rotation.z; // Current Roll.
+            let delta = limit - z;          // Difference in Roll.
+
+            let minRad = 0.001; // Tenth of a radian is a barely noticable difference.
+
+            // If the difference is noticable, restore the Roll.
+            if (Math.abs(delta) >= minRad) {
+                // Change Z rotation towards the target Roll.
+                this.rotation.z += delta / rate;
+
+                // Match when near enough.
+                if (Math.abs(limit - this.rotation.z) <= minRad) {
+                    this.rotation.z = limit;
+                }
+            }
+        }
+
+        /**
+         * Destroy the camera and release the current resources held by it.
+         */
+        public dispose(): void {
+            this.inputs.clear();
+            super.dispose();
+        }
+
+        /**
+         * Get the current object class name.
+         * @returns the class name.
+         */
+        public getClassName(): string {
+            return "FlyCamera";
+        }
+    }
+}

+ 35 - 0
src/Cameras/babylon.flyCameraInputsManager.ts

@@ -0,0 +1,35 @@
+module BABYLON {
+    /**
+     * Default Inputs manager for the FlyCamera.
+     * It groups all the default supported inputs for ease of use.
+     * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
+     */
+    export class FlyCameraInputsManager extends CameraInputsManager<FlyCamera> {
+        /**
+         * Instantiates a new FlyCameraInputsManager.
+         * @param camera Defines the camera the inputs belong to.
+         */
+        constructor(camera: FlyCamera) {
+            super(camera);
+        }
+
+        /**
+         * Add keyboard input support to the input manager.
+         * @returns the new FlyCameraKeyboardMoveInput().
+         */
+        addKeyboard(): FlyCameraInputsManager {
+            this.add(new FlyCameraKeyboardInput());
+            return this;
+        }
+
+        /**
+         * Add mouse input support to the input manager.
+         * @param touchEnabled Enable touch screen support.
+         * @returns the new FlyCameraMouseInput().
+         */
+        addMouse(touchEnabled = true): FlyCameraInputsManager {
+            this.add(new FlyCameraMouseInput(touchEnabled));
+            return this;
+        }
+    }
+}