Przeglądaj źródła

Merge pull request #9056 from mrdunk/freeMouseWheel

Mouse wheel control for FreeCamera and derivatives.
Raanan Weber 4 lat temu
rodzic
commit
f1ed8af557

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

@@ -108,6 +108,7 @@
 - Added flag to TargetCamera to invert rotation direction and multiplier to adjust speed ([Exolun](https://github.com/Exolun))
 - Added upwards and downwards keyboard input to `FreeCamera` ([Pheater](https://github.com/pheater))
 - Handle scales in camera matrices ([Popov72](https://github.com/Popov72))
+- Added mouse wheel controls to FreeCamera. ([mrdunk](https://github.com/mrdunk))
 
 ### Debug
 - Added new view modes to the `SkeletonViewer` class. ([Pryme8](https://github.com/Pryme8))

+ 189 - 0
src/Cameras/Inputs/BaseCameraMouseWheelInput.ts

@@ -0,0 +1,189 @@
+import { Nullable } from "../../types";
+import { serialize } from "../../Misc/decorators";
+import { Observable, Observer } from "../../Misc/observable";
+import { Camera } from "../../Cameras/camera";
+import { ICameraInput } from "../../Cameras/cameraInputsManager";
+import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
+
+/**
+ * Base class for mouse wheel input..
+ * See FollowCameraMouseWheelInput in src/Cameras/Inputs/freeCameraMouseWheelInput.ts
+ * for example usage.
+ */
+export abstract class BaseCameraMouseWheelInput implements ICameraInput<Camera> {
+    /**
+     * Defines the camera the input is attached to.
+     */
+    public abstract camera: Camera;
+
+    /**
+     * How fast is the camera moves in relation to X axis mouseWheel events.
+     * Use negative value to reverse direction.
+     */
+    @serialize()
+    public wheelPrecisionX = 3.0;
+
+    /**
+     * How fast is the camera moves in relation to Y axis mouseWheel events.
+     * Use negative value to reverse direction.
+     */
+    @serialize()
+    public wheelPrecisionY = 3.0;
+
+    /**
+     * How fast is the camera moves in relation to Z axis mouseWheel events.
+     * Use negative value to reverse direction.
+     */
+    @serialize()
+    public wheelPrecisionZ = 3.0;
+
+    /**
+     * Observable for when a mouse wheel move event occurs.
+     */
+    public onChangedObservable = new Observable<
+        {wheelDeltaX: number, wheelDeltaY: number, wheelDeltaZ: number}>();
+
+    private _wheel: Nullable<(pointer: PointerInfo) => void>;
+    private _observer: Nullable<Observer<PointerInfo>>;
+
+    /**
+     * 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 {
+        this._wheel = (pointer) => {
+            // sanity check - this should be a PointerWheel event.
+            if (pointer.type !== PointerEventTypes.POINTERWHEEL) { return; }
+
+            const event = <MouseWheelEvent>pointer.event;
+
+            const platformScale =
+                event.deltaMode === WheelEvent.DOM_DELTA_LINE ? this._ffMultiplier : 1;
+
+            if (event.deltaY !== undefined) {
+                // Most recent browsers versions have delta properties.
+                // Firefox >= v17  (Has WebGL >= v4)
+                // Chrome >=  v31  (Has WebGL >= v8)
+                // Edge >=    v12  (Has WebGl >= v12)
+                // https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
+                this._wheelDeltaX +=
+                    this.wheelPrecisionX * platformScale * event.deltaX / this._normalize;
+                this._wheelDeltaY -=
+                    this.wheelPrecisionY * platformScale * event.deltaY / this._normalize;
+                this._wheelDeltaZ +=
+                    this.wheelPrecisionZ * platformScale * event.deltaZ / this._normalize;
+            } else if ((<any>event).wheelDeltaY !== undefined) {
+                // Unsure whether these catch anything more. Documentation
+                // online is contradictory.
+                this._wheelDeltaX +=
+                    this.wheelPrecisionX * platformScale *
+                    (<any>event).wheelDeltaX / this._normalize;
+                this._wheelDeltaY -=
+                    this.wheelPrecisionY * platformScale *
+                    (<any>event).wheelDeltaY / this._normalize;
+                this._wheelDeltaZ +=
+                    this.wheelPrecisionZ * platformScale *
+                    (<any>event).wheelDeltaZ / this._normalize;
+            } else if ((<any>event).wheelDelta) {
+                // IE >= v9   (Has WebGL >= v11)
+                // Maybe others?
+                this._wheelDeltaY -=
+                    this.wheelPrecisionY * (<any>event).wheelDelta / this._normalize;
+            }
+
+            if (event.preventDefault) {
+                if (!noPreventDefault) {
+                    event.preventDefault();
+                }
+            }
+        };
+
+        this._observer = this.camera.getScene().onPointerObservable.add(
+            this._wheel,
+            PointerEventTypes.POINTERWHEEL);
+    }
+
+    /**
+     * 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) {
+            this.camera.getScene().onPointerObservable.remove(this._observer);
+            this._observer = null;
+            this._wheel = null;
+        }
+        if (this.onChangedObservable) {
+            this.onChangedObservable.clear();
+        }
+    }
+
+    /**
+     * Called for each rendered frame.
+     */
+    public checkInputs(): void {
+        this.onChangedObservable.notifyObservers({
+            wheelDeltaX: this._wheelDeltaX,
+            wheelDeltaY: this._wheelDeltaY,
+            wheelDeltaZ: this._wheelDeltaZ
+        });
+
+        // Clear deltas.
+        this._wheelDeltaX = 0;
+        this._wheelDeltaY = 0;
+        this._wheelDeltaZ = 0;
+    }
+
+    /**
+     * Gets the class name of the current intput.
+     * @returns the class name
+     */
+    public getClassName(): string {
+        return "BaseCameraMouseWheelInput";
+    }
+
+    /**
+     * Get the friendly name associated with the input class.
+     * @returns the input friendly name
+     */
+    public getSimpleName(): string {
+        return "mousewheel";
+    }
+
+    /**
+     * Incremental value of multiple mouse wheel movements of the X axis.
+     * Should be zero-ed when read.
+     */
+    protected _wheelDeltaX: number = 0;
+
+    /**
+     * Incremental value of multiple mouse wheel movements of the Y axis.
+     * Should be zero-ed when read.
+     */
+    protected _wheelDeltaY: number = 0;
+
+    /**
+     * Incremental value of multiple mouse wheel movements of the Z axis.
+     * Should be zero-ed when read.
+     */
+    protected _wheelDeltaZ: number = 0;
+
+    /**
+     * Firefox uses a different scheme to report scroll distances to other
+     * browsers. Rather than use complicated methods to calculate the exact
+     * multiple we need to apply, let's just cheat and use a constant.
+     * https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
+     * https://stackoverflow.com/questions/20110224/what-is-the-height-of-a-line-in-a-wheel-event-deltamode-dom-delta-line
+     */
+    private readonly _ffMultiplier = 12;
+
+    /**
+     * Different event attributes for wheel data fall into a few set ranges.
+     * Some relevant but dated date here:
+     * https://stackoverflow.com/questions/5527601/normalizing-mousewheel-speed-across-browsers
+     */
+    private readonly _normalize = 120;
+}

+ 393 - 0
src/Cameras/Inputs/freeCameraMouseWheelInput.ts

@@ -0,0 +1,393 @@
+import { Nullable } from "../../types";
+import { serialize } from "../../Misc/decorators";
+import { FreeCamera } from "../../Cameras/freeCamera";
+import { CameraInputTypes } from "../../Cameras/cameraInputsManager";
+import { BaseCameraMouseWheelInput } from "../../Cameras/Inputs/BaseCameraMouseWheelInput";
+import { Matrix, Vector3 } from "../../Maths/math.vector";
+import { Coordinate } from "../../Maths/math.axis";
+
+enum _CameraProperty {
+    MoveRelative,
+    RotateRelative,
+    MoveScene
+}
+
+/**
+ * Manage the mouse wheel inputs to control a free camera.
+ * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
+ */
+export class FreeCameraMouseWheelInput extends BaseCameraMouseWheelInput {
+
+    /**
+     * Defines the camera the input is attached to.
+     */
+    public camera: FreeCamera;
+
+    /**
+     * Gets the class name of the current input.
+     * @returns the class name
+     */
+    public getClassName(): string {
+        return "FreeCameraMouseWheelInput";
+    }
+
+    /**
+     * Set which movement axis (relative to camera's orientation) the mouse
+     * wheel's X axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelXMoveRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelXAction !== _CameraProperty.MoveRelative) {
+            // Attempting to clear different _wheelXAction.
+            return;
+        }
+        this._wheelXAction = _CameraProperty.MoveRelative;
+        this._wheelXActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to camera's orientation) the
+     * mouse wheel's X axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelXMoveRelative(): Nullable<Coordinate> {
+        if (this._wheelXAction !== _CameraProperty.MoveRelative) {
+            return null;
+        }
+        return this._wheelXActionCoordinate;
+    }
+
+    /**
+     * Set which movement axis (relative to camera's orientation) the mouse
+     * wheel's Y axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelYMoveRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelYAction !== _CameraProperty.MoveRelative) {
+            // Attempting to clear different _wheelYAction.
+            return;
+        }
+        this._wheelYAction = _CameraProperty.MoveRelative;
+        this._wheelYActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to camera's orientation) the
+     * mouse wheel's Y axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelYMoveRelative(): Nullable<Coordinate> {
+        if (this._wheelYAction !== _CameraProperty.MoveRelative) {
+            return null;
+        }
+        return this._wheelYActionCoordinate;
+    }
+
+    /**
+     * Set which movement axis (relative to camera's orientation) the mouse
+     * wheel's Z axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelZMoveRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelZAction !== _CameraProperty.MoveRelative) {
+            // Attempting to clear different _wheelZAction.
+            return;
+        }
+        this._wheelZAction = _CameraProperty.MoveRelative;
+        this._wheelZActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to camera's orientation) the
+     * mouse wheel's Z axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelZMoveRelative(): Nullable<Coordinate> {
+        if (this._wheelZAction !== _CameraProperty.MoveRelative) {
+            return null;
+        }
+        return this._wheelZActionCoordinate;
+    }
+
+    /**
+     * Set which rotation axis (relative to camera's orientation) the mouse
+     * wheel's X axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelXRotateRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelXAction !== _CameraProperty.RotateRelative) {
+            // Attempting to clear different _wheelXAction.
+            return;
+        }
+        this._wheelXAction = _CameraProperty.RotateRelative;
+        this._wheelXActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured rotation axis (relative to camera's orientation) the
+     * mouse wheel's X axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelXRotateRelative(): Nullable<Coordinate> {
+        if (this._wheelXAction !== _CameraProperty.RotateRelative) {
+            return null;
+        }
+        return this._wheelXActionCoordinate;
+    }
+
+    /**
+     * Set which rotation axis (relative to camera's orientation) the mouse
+     * wheel's Y axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelYRotateRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelYAction !== _CameraProperty.RotateRelative) {
+            // Attempting to clear different _wheelYAction.
+            return;
+        }
+        this._wheelYAction = _CameraProperty.RotateRelative;
+        this._wheelYActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured rotation axis (relative to camera's orientation) the
+     * mouse wheel's Y axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelYRotateRelative(): Nullable<Coordinate> {
+        if (this._wheelYAction !== _CameraProperty.RotateRelative) {
+            return null;
+        }
+        return this._wheelYActionCoordinate;
+    }
+
+    /**
+     * Set which rotation axis (relative to camera's orientation) the mouse
+     * wheel's Z axis controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelZRotateRelative(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelZAction !== _CameraProperty.RotateRelative) {
+            // Attempting to clear different _wheelZAction.
+            return;
+        }
+        this._wheelZAction = _CameraProperty.RotateRelative;
+        this._wheelZActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured rotation axis (relative to camera's orientation) the
+     * mouse wheel's Z axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelZRotateRelative(): Nullable<Coordinate> {
+        if (this._wheelZAction !== _CameraProperty.RotateRelative) {
+            return null;
+        }
+        return this._wheelZActionCoordinate;
+    }
+
+    /**
+     * Set which movement axis (relative to the scene) the mouse wheel's X axis
+     * controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelXMoveScene(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelXAction !== _CameraProperty.MoveScene) {
+            // Attempting to clear different _wheelXAction.
+            return;
+        }
+        this._wheelXAction = _CameraProperty.MoveScene;
+        this._wheelXActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to the scene) the mouse wheel's
+     * X axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelXMoveScene(): Nullable<Coordinate> {
+        if (this._wheelXAction !== _CameraProperty.MoveScene) {
+            return null;
+        }
+        return this._wheelXActionCoordinate;
+    }
+
+    /**
+     * Set which movement axis (relative to the scene) the mouse wheel's Y axis
+     * controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelYMoveScene(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelYAction !== _CameraProperty.MoveScene) {
+            // Attempting to clear different _wheelYAction.
+            return;
+        }
+        this._wheelYAction = _CameraProperty.MoveScene;
+        this._wheelYActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to the scene) the mouse wheel's
+     * Y axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelYMoveScene(): Nullable<Coordinate> {
+        if (this._wheelYAction !== _CameraProperty.MoveScene) {
+            return null;
+        }
+        return this._wheelYActionCoordinate;
+    }
+
+    /**
+     * Set which movement axis (relative to the scene) the mouse wheel's Z axis
+     * controls.
+     * @param axis The axis to be moved. Set null to clear.
+     */
+    @serialize()
+    public set wheelZMoveScene(axis: Nullable<Coordinate>) {
+        if (axis === null && this._wheelZAction !== _CameraProperty.MoveScene) {
+            // Attempting to clear different _wheelZAction.
+            return;
+        }
+        this._wheelZAction = _CameraProperty.MoveScene;
+        this._wheelZActionCoordinate = axis;
+    }
+
+    /**
+     * Get the configured movement axis (relative to the scene) the mouse wheel's
+     * Z axis controls.
+     * @returns The configured axis or null if none.
+     */
+    public get wheelZMoveScene(): Nullable<Coordinate> {
+        if (this._wheelZAction !== _CameraProperty.MoveScene) {
+            return null;
+        }
+        return this._wheelZActionCoordinate;
+    }
+
+    /**
+     * Called for each rendered frame.
+     */
+    public checkInputs(): void {
+        if (this._wheelDeltaX === 0 &&
+                this._wheelDeltaY === 0 &&
+                this._wheelDeltaZ == 0) {
+            return;
+        }
+
+        // Clear the camera properties that we might be updating.
+        this._moveRelative.setAll(0);
+        this._rotateRelative.setAll(0);
+        this._moveScene.setAll(0);
+
+        // Set the camera properties that are to be updated.
+        this._updateCamera();
+
+        if (this.camera.getScene().useRightHandedSystem) {
+            // TODO: Does this need done for worldUpdate too?
+            this._moveRelative.z *= -1;
+        }
+
+        // Convert updates relative to camera to world position update.
+        const cameraTransformMatrix = Matrix.Zero();
+        this.camera.getViewMatrix().invertToRef(cameraTransformMatrix);
+
+        const transformedDirection = Vector3.Zero();
+        Vector3.TransformNormalToRef(
+            this._moveRelative, cameraTransformMatrix, transformedDirection);
+
+        // Apply updates to camera position.
+        this.camera.cameraRotation.x += this._rotateRelative.x / 200;
+        this.camera.cameraRotation.y += this._rotateRelative.y / 200;
+        this.camera.cameraDirection.addInPlace(transformedDirection);
+        this.camera.cameraDirection.addInPlace(this._moveScene);
+
+        // Call the base class implementation to handle observers and do cleanup.
+        super.checkInputs();
+    }
+
+    private _moveRelative = Vector3.Zero();
+    private _rotateRelative = Vector3.Zero();
+    private _moveScene = Vector3.Zero();
+
+    /**
+     * These are set to the desired default behaviour.
+     */
+    private _wheelXAction: Nullable<_CameraProperty> = _CameraProperty.MoveRelative;
+    private _wheelXActionCoordinate: Nullable<Coordinate> = Coordinate.X;
+    private _wheelYAction: Nullable<_CameraProperty> = _CameraProperty.MoveRelative;
+    private _wheelYActionCoordinate: Nullable<Coordinate> = Coordinate.Z;
+    private _wheelZAction: Nullable<_CameraProperty> = null;
+    private _wheelZActionCoordinate: Nullable<Coordinate> = null;
+
+    /**
+     * Update the camera according to any configured properties for the 3
+     * mouse-wheel axis.
+     */
+    private _updateCamera(): void {
+        const moveRelative = this._moveRelative;
+        const rotateRelative = this._rotateRelative;
+        const moveScene = this._moveScene;
+
+        let updateCameraProperty = function(/* Mouse-wheel delta. */
+                                            value: number,
+                                            /* Camera property to be changed. */
+                                            cameraProperty: Nullable<_CameraProperty>,
+                                            /* Axis of Camera property to be changed. */
+                                            coordinate: Nullable<Coordinate>): void {
+                if (value === 0) {
+                    // Mouse wheel has not moved.
+                    return;
+                }
+                if (cameraProperty === null || coordinate === null) {
+                    // Mouse wheel axis not configured.
+                    return;
+                }
+
+                let action = null;
+                switch (cameraProperty) {
+                    case _CameraProperty.MoveRelative:
+                        action = moveRelative;
+                        break;
+                    case _CameraProperty.RotateRelative:
+                        action = rotateRelative;
+                        break;
+                    case _CameraProperty.MoveScene:
+                        action = moveScene;
+                        break;
+                }
+
+                switch (coordinate) {
+                    case Coordinate.X:
+                        action.set(value, 0, 0);
+                        break;
+                    case Coordinate.Y:
+                        action.set(0, value, 0);
+                        break;
+                    case Coordinate.Z:
+                        action.set(0, 0, value);
+                        break;
+                }
+            };
+
+        // Do the camera updates for each of the 3 touch-wheel axis.
+        updateCameraProperty(
+            this._wheelDeltaX, this._wheelXAction, this._wheelXActionCoordinate);
+        updateCameraProperty(
+            this._wheelDeltaY, this._wheelYAction, this._wheelYActionCoordinate);
+        updateCameraProperty(
+            this._wheelDeltaZ, this._wheelZAction, this._wheelZActionCoordinate);
+    }
+
+}
+
+(<any>CameraInputTypes)["FreeCameraMouseWheelInput"] = FreeCameraMouseWheelInput;

+ 1 - 0
src/Cameras/Inputs/index.ts

@@ -12,5 +12,6 @@ export * from "./freeCameraDeviceOrientationInput";
 export * from "./freeCameraGamepadInput";
 export * from "./freeCameraKeyboardMoveInput";
 export * from "./freeCameraMouseInput";
+export * from "./freeCameraMouseWheelInput";
 export * from "./freeCameraTouchInput";
 export * from "./freeCameraVirtualJoystickInput";

+ 28 - 0
src/Cameras/freeCameraInputsManager.ts

@@ -2,6 +2,7 @@ import { FreeCamera } from "./freeCamera";
 import { CameraInputsManager } from "./cameraInputsManager";
 import { FreeCameraKeyboardMoveInput } from "../Cameras/Inputs/freeCameraKeyboardMoveInput";
 import { FreeCameraMouseInput } from "../Cameras/Inputs/freeCameraMouseInput";
+import { FreeCameraMouseWheelInput } from "../Cameras/Inputs/freeCameraMouseWheelInput";
 import { FreeCameraTouchInput } from "../Cameras/Inputs/freeCameraTouchInput";
 import { Nullable } from '../types';
 
@@ -16,6 +17,10 @@ export class FreeCameraInputsManager extends CameraInputsManager<FreeCamera> {
      */
     public _mouseInput: Nullable<FreeCameraMouseInput> = null;
     /**
+     * @hidden
+     */
+    public _mouseWheelInput: Nullable<FreeCameraMouseWheelInput> = null;
+    /**
      * Instantiates a new FreeCameraInputsManager.
      * @param camera Defines the camera the inputs belong to
      */
@@ -57,6 +62,29 @@ export class FreeCameraInputsManager extends CameraInputsManager<FreeCamera> {
     }
 
     /**
+     * Add mouse wheel input support to the input manager.
+     * @returns the current input manager
+     */
+    addMouseWheel(): FreeCameraInputsManager {
+        if (!this._mouseWheelInput) {
+            this._mouseWheelInput = new FreeCameraMouseWheelInput();
+            this.add(this._mouseWheelInput);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the mouse wheel input support from the manager
+     * @returns the current input manager
+     */
+    removeMouseWheel(): FreeCameraInputsManager {
+        if (this._mouseWheelInput) {
+            this.remove(this._mouseWheelInput);
+        }
+        return this;
+    }
+
+    /**
      * Add touch input support to the input manager.
      * @returns the current input manager
      */

+ 13 - 1
src/Maths/math.axis.ts

@@ -18,4 +18,16 @@ export class Axis {
     public static Y: Vector3 = new Vector3(0.0, 1.0, 0.0);
     /** Z axis */
     public static Z: Vector3 = new Vector3(0.0, 0.0, 1.0);
-}
+}
+
+/**
+ * Defines cartesian components.
+ */
+export enum Coordinate {
+    /** X axis */
+    X,
+    /** Y axis */
+    Y,
+    /** Z axis */
+    Z
+}