Преглед на файлове

Mouse wheel for FreeCamera (and derivetives).

duncan law преди 4 години
родител
ревизия
4346ba397c

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

@@ -0,0 +1,162 @@
+import { Nullable } from "../../types";
+import { serialize } from "../../Misc/decorators";
+import { EventState, 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;
+
+    private _wheel: Nullable<(pointer: PointerInfo, _: EventState) => 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;
+
+            // Chrome, Safari: event.deltaY
+            // IE: event.wheelDelta
+            // Firefox: event.detail (inverted)
+            //const wheelDelta = Math.max(-1, Math.min(1,
+            //    (event.deltaY || (<any>event).wheelDelta || -event.detail)));
+
+            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._wheelDeltaY -= this.wheelPrecisionY * platformScale * event.deltaY;
+                this._wheelDeltaZ += this.wheelPrecisionZ * platformScale * event.deltaZ;
+            } 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._wheelDeltaY -=
+                    this.wheelPrecisionY * platformScale * (<any>event).wheelDeltaY;
+                this._wheelDeltaZ +=
+                    this.wheelPrecisionZ * platformScale * (<any>event).wheelDeltaZ;
+            } else if((<any>event).wheelDelta) {
+                // IE >= v9   (Has WebGL >= v11)
+                // Maybe others?
+                this._wheelDeltaY -= this.wheelPrecisionY * (<any>event).wheelDelta;
+            } else if(event.detail) {
+                // Firefox < v17  (Has WebGL >= v4)
+                // TODO How should we scale this?
+                // Since it's Firefox, it's probably the same as
+                // WheelEvent.DOM_DELTA_LINE.
+                // ie: we can presume it needs scaled to match per-pixel.
+                this._wheelDeltaY +=
+                    this.wheelPrecisionY * this._ffMultiplier * event.detail;
+            }
+
+            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 && element) {
+            this.camera.getScene().onPointerObservable.remove(this._observer);
+            this._observer = null;
+            this._wheel = null;
+        }
+    }
+
+    /**
+     * 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;
+}

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

@@ -0,0 +1,324 @@
+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";
+
+/**
+ * Defines Mouse wheel axis. A typical laptop trackpad emulates a mouse wheel in
+ * X and Y axis. Many modern browsers permit X, Y and Z.
+ */
+export enum FreeCameraMouseWheelAxis {
+    /**
+     * No mouse wheel set.
+     */
+    NONE,
+    /**
+     * Mouse wheel X axis is set.
+     */
+    X,
+    /**
+     * Mouse wheel Y axis is set.
+     */
+    Y,
+    /**
+     * Mouse wheel Z axis is set.
+     */
+    Z
+}
+
+/**
+ * A user configurable callback to be called on mouse wheel movement.
+ */
+export interface FreeCameraMouseWheelCustomCallback {
+    /**
+     * @param camera The camera instance the mouse wheel is attached to.
+     * @param wheelDeltaX The change in value of the mouse wheel's X axis since last called.
+     * @param wheelDeltaY The change in value of the mouse wheel's X axis since last called.
+     * @param wheelDeltaZ The change in value of the mouse wheel's X axis since last called.
+     */
+    (camera: FreeCamera, wheelDeltaX: number, wheelDeltaY: number, wheelDeltaZ: number): void;
+}
+
+/**
+ * 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 {
+    /**
+     * Manage the mouse scroll wheel input to control the movement of a free camera.
+     * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
+     */
+    constructor() {
+        super();
+        this._setAllAxis();
+    }
+
+    /**
+     * 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";
+    }
+
+    /**
+     * Log error messages if basic misconfiguration has occurred.
+     */
+    public warningEnable: boolean = true;
+
+    private _cameraMoveX: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.X;
+
+    /**
+     * Get which mouse wheel axis moves along camera's X axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraMoveX(): FreeCameraMouseWheelAxis {
+        return this._cameraMoveX;
+    }
+
+    /**
+     * Set which mouse wheel axis moves along camera's X axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraMoveX(axis: FreeCameraMouseWheelAxis) {
+        this._cameraMoveX = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+    private _cameraMoveY: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.NONE;
+
+    /**
+     * Get which mouse wheel axis moves along camera's Y axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraMoveY(): FreeCameraMouseWheelAxis {
+        return this._cameraMoveY;
+    }
+
+    /**
+     * Set which mouse wheel axis moves along camera's Y axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraMoveY(axis: FreeCameraMouseWheelAxis) {
+        this._cameraMoveY = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+    private _cameraMoveZ: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.Y;
+
+    /**
+     * Get which mouse wheel axis moves along camera's X axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraMoveZ(): FreeCameraMouseWheelAxis {
+        return this._cameraMoveZ;
+    }
+
+    /**
+     * Set which mouse wheel axis moves along camera's Z axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraMoveZ(axis: FreeCameraMouseWheelAxis) {
+        this._cameraMoveZ = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+    private _cameraWorldPosX: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.NONE;
+
+    /**
+     * Get which mouse wheel axis moves the camera along the scene's X axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraWorldPosX(): FreeCameraMouseWheelAxis {
+        return this._cameraWorldPosX;
+    }
+
+    /**
+     * Set which mouse wheel axis moves the camera along the scene's X axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraWorldPosX(axis: FreeCameraMouseWheelAxis) {
+        this._cameraWorldPosX = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+    private _cameraWorldPosY: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.NONE;
+
+    /**
+     * Get which mouse wheel axis moves the camera along the scene's Y axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraWorldPosY(): FreeCameraMouseWheelAxis {
+        return this._cameraWorldPosY;
+    }
+
+    /**
+     * Set which mouse wheel axis moves the camera along the scene's Y axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraWorldPosY(axis: FreeCameraMouseWheelAxis) {
+        this._cameraWorldPosY = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+
+    private _cameraWorldPosZ: FreeCameraMouseWheelAxis = FreeCameraMouseWheelAxis.NONE;
+
+    /**
+     * Get which mouse wheel axis moves the camera along the scene's Z axis (if any).
+     * @returns the currently configured mouse wheel axis.
+     */
+    @serialize()
+    public get cameraWorldPosZ(): FreeCameraMouseWheelAxis {
+        return this._cameraWorldPosZ;
+    }
+
+    /**
+     * Set which mouse wheel axis moves the camera along the scene's Z axis (if any).
+     * @param axis is the desired mouse wheel axis.
+     */
+    public set cameraWorldPosZ(axis: FreeCameraMouseWheelAxis) {
+        this._cameraWorldPosZ = axis;
+        this._setAllAxis();
+        this._sanityCheck();
+    }
+
+    /**
+     * Set user configurable callback to be called on mouse wheel movement
+     * to be used whenever the default functionality of this class does not
+     * change the required camera parameter by default.
+     * @param customCallback is a callback function which if set will get called
+     * whenever the mouse wheel value(s) change.
+     */
+    @serialize()
+    public customCallback: Nullable<FreeCameraMouseWheelCustomCallback> = null;
+
+    private _cameraUpdate = Vector3.Zero();
+    private _worldUpdate = Vector3.Zero();
+
+    /**
+     * Called for each rendered frame.
+     */
+    public checkInputs(): void {
+        if(this._wheelDeltaX === 0 &&
+                this._wheelDeltaY === 0 &&
+                this._wheelDeltaZ == 0) {
+            return;
+        }
+
+        this._cameraUpdate.setAll(0);
+        this._worldUpdate.setAll(0);
+
+        // Iterate over all camera properties we might want to update.
+        this._allAxis.forEach((axis) => {
+            const [wheelAxis, updater, component] = axis;
+
+            if(this._wheelDeltaX !== 0 && wheelAxis === FreeCameraMouseWheelAxis.X) {
+                updater.set(component == "x" ? this._wheelDeltaX/100 : 0,
+                            component == "y" ? this._wheelDeltaX/100 : 0,
+                            component == "z" ? this._wheelDeltaX/100 : 0);
+            }
+            if(this._wheelDeltaY !== 0 && wheelAxis === FreeCameraMouseWheelAxis.Y) {
+                updater.set(component == "x" ? this._wheelDeltaY/100 : 0,
+                            component == "y" ? this._wheelDeltaY/100 : 0,
+                            component == "z" ? this._wheelDeltaY/100 : 0);
+            }
+            if(this._wheelDeltaZ !== 0 && wheelAxis === FreeCameraMouseWheelAxis.Z) {
+                updater.set(component == "x" ? this._wheelDeltaZ/100 : 0,
+                            component == "y" ? this._wheelDeltaZ/100 : 0,
+                            component == "z" ? this._wheelDeltaZ/100 : 0);
+            }
+        });
+
+        if (this.camera.getScene().useRightHandedSystem) {
+            // TODO: Does this need done for worldUpdate too?
+            this._cameraUpdate.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._cameraUpdate, cameraTransformMatrix, transformedDirection);
+
+        // Apply updates to camera position.
+        this.camera.cameraDirection.addInPlace(transformedDirection);
+        this.camera.cameraDirection.addInPlace(this._worldUpdate);
+
+        // Do the user defined customCallback if set.
+        if(this.customCallback !== null) {
+            this.customCallback(
+                this.camera, this._wheelDeltaX, this._wheelDeltaY, this._wheelDeltaZ);
+        }
+
+        // Clear deltas.
+        this._wheelDeltaX = 0;
+        this._wheelDeltaY = 0;
+        this._wheelDeltaZ = 0;
+    }
+    
+    /**
+     * Gather all user configurable axis into one collection so we can itterate
+     * over them later.
+     */
+    private _allAxis: [FreeCameraMouseWheelAxis, Vector3, string][];
+    private _setAllAxis(): void {
+        this._allAxis = [
+            [this._cameraMoveX, this._cameraUpdate, "x"],
+            [this._cameraMoveY, this._cameraUpdate, "y"],
+            [this._cameraMoveZ, this._cameraUpdate, "z"],
+            [this._cameraWorldPosX, this._worldUpdate, "x"],
+            [this._cameraWorldPosY, this._worldUpdate, "y"],
+            [this._cameraWorldPosZ, this._worldUpdate, "z"] ];
+    }
+
+    /**
+     * Display a warning on console if there are obvious misconfiguration.
+     * Eg: A single mouse wheel axis controlling multiple camera attributes.
+     */
+    private _sanityCheck(): void {
+        if(!this.warningEnable) {
+            return;
+        }
+        const labels: {[id: number]: string} = {1: "X", 2: "Y", 3: "Z"};
+        
+        const configuredCount: [number, number, number, number] = [0, 0, 0, 0];
+        this._allAxis.forEach((axis) => {
+            const wheelAxis = axis[0];
+
+            configuredCount[wheelAxis] += 1;
+        });
+
+        // Warn of misconfiguration.
+        for(let axis = FreeCameraMouseWheelAxis.X;
+            axis <= FreeCameraMouseWheelAxis.Z; 
+            axis += 1) {
+                if(configuredCount[axis] > 1) {
+                    console.warn(
+                        `Touch pad ${labels[axis]} axis assigned to ` +
+                        `${configuredCount[axis]} tasks. (Only 1 will have any effect.)`);
+                }
+        }
+    }
+}
+
+(<any>CameraInputTypes)["FreeCameraMouseWheelInput"] = FreeCameraMouseWheelInput;

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

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

+ 1 - 1
src/Cameras/freeCamera.ts

@@ -217,7 +217,7 @@ export class FreeCamera extends TargetCamera {
     constructor(name: string, position: Vector3, scene: Scene, setActiveOnSceneIfNoneActive = true) {
     constructor(name: string, position: Vector3, scene: Scene, setActiveOnSceneIfNoneActive = true) {
         super(name, position, scene, setActiveOnSceneIfNoneActive);
         super(name, position, scene, setActiveOnSceneIfNoneActive);
         this.inputs = new FreeCameraInputsManager(this);
         this.inputs = new FreeCameraInputsManager(this);
-        this.inputs.addKeyboard().addMouse();
+        this.inputs.addKeyboard().addMouse().addMouseWheel();
     }
     }
 
 
     /**
     /**

+ 28 - 0
src/Cameras/freeCameraInputsManager.ts

@@ -2,6 +2,7 @@ import { FreeCamera } from "./freeCamera";
 import { CameraInputsManager } from "./cameraInputsManager";
 import { CameraInputsManager } from "./cameraInputsManager";
 import { FreeCameraKeyboardMoveInput } from "../Cameras/Inputs/freeCameraKeyboardMoveInput";
 import { FreeCameraKeyboardMoveInput } from "../Cameras/Inputs/freeCameraKeyboardMoveInput";
 import { FreeCameraMouseInput } from "../Cameras/Inputs/freeCameraMouseInput";
 import { FreeCameraMouseInput } from "../Cameras/Inputs/freeCameraMouseInput";
+import { FreeCameraMouseWheelInput } from "../Cameras/Inputs/freeCameraMouseWheelInput";
 import { FreeCameraTouchInput } from "../Cameras/Inputs/freeCameraTouchInput";
 import { FreeCameraTouchInput } from "../Cameras/Inputs/freeCameraTouchInput";
 import { Nullable } from '../types';
 import { Nullable } from '../types';
 
 
@@ -16,6 +17,10 @@ export class FreeCameraInputsManager extends CameraInputsManager<FreeCamera> {
      */
      */
     public _mouseInput: Nullable<FreeCameraMouseInput> = null;
     public _mouseInput: Nullable<FreeCameraMouseInput> = null;
     /**
     /**
+     * @hidden
+     */
+    public _mouseWheelInput: Nullable<FreeCameraMouseWheelInput> = null;
+    /**
      * Instantiates a new FreeCameraInputsManager.
      * Instantiates a new FreeCameraInputsManager.
      * @param camera Defines the camera the inputs belong to
      * @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.
      * Add touch input support to the input manager.
      * @returns the current input manager
      * @returns the current input manager
      */
      */