【threejs】创建FPS相机

原理说明

控制器是一个很麻烦的东西,需要创建更多的类来管理相机行为,并且可自定义性差,所以将部分控制器的功能绑定到相机上,可以解决这些问题,所以我以 FlyControls为例,将控制器功能绑定到相机上,然后可以通过直接给相机发送命令控制其行为,并可以根据业务自定义业务。

相机代码

控制器的核心其实没有什么业务,只有几个简单的函数,并且都是没有实际行为的 ,所以我们直接继承想用的相机到我们的相机class上,再添加控制器事件到里面就行了

typescript 复制代码
import { PerspectiveCamera, Quaternion, Vector3 } from "three";
const _changeEvent: any = { type: 'change' };

const _EPS = 0.0001;


export default class FlyCamera extends PerspectiveCamera {
    domElement: null | HTMLElement;
    _tmpQuaternion: Quaternion;
    enabled: boolean;
    state: number;
    keys: {};
    mouseButtons: { LEFT: null; MIDDLE: null; RIGHT: null; };
    touches: { ONE: null; TWO: null; };
    movementSpeed: number;
    rollSpeed: number;
    dragToLook: boolean;
    autoForward: boolean;
    _moveState: { up: number; down: number; left: number; right: number; forward: number; back: number; pitchUp: number; pitchDown: number; yawLeft: number; yawRight: number; rollLeft: number; rollRight: number; };
    _moveVector: Vector3;
    _rotationVector: Vector3;
    _lastQuaternion: Quaternion;
    _lastPosition: Vector3;
    _status: number;
    _onKeyDown: (event: { altKey: any; code: any; }) => void;
    _onKeyUp: (event: { code: any; }) => void;
    _onPointerMove: (event: { pageX: number; pageY: number; }) => void;
    _onPointerDown: (event: { button: any; }) => void;
    _onPointerUp: (event: { button: any; }) => void;
    _onPointerCancel: (this: any) => void;
    _onContextMenu: (event: { preventDefault: () => void; }) => void;


    constructor(fov: number | undefined, aspect: number | undefined, near: number | undefined, far: number | undefined) {
        super(fov, aspect, near, far)
        this.domElement = null
        this._tmpQuaternion = new Quaternion();


        this.enabled = true;

        this.state = - 1;

        this.keys = {};
        this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null };
        this.touches = { ONE: null, TWO: null };


        this.movementSpeed = 1.0;
        this.rollSpeed =.1;

        this.dragToLook = false;
        this.autoForward = false;

        // internals

        this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
        this._moveVector = new Vector3(0, 0, 0);
        this._rotationVector = new Vector3(0, 0, 0);
        this._lastQuaternion = new Quaternion();
        this._lastPosition = new Vector3();
        this._status = 0;

        // event listeners

        this._onKeyDown = onKeyDown.bind(this);
        this._onKeyUp = onKeyUp.bind(this);
        this._onPointerMove = onPointerMove.bind(this);
        this._onPointerDown = onPointerDown.bind(this);
        this._onPointerUp = onPointerUp.bind(this);
        this._onPointerCancel = onPointerCancel.bind(this);
        this._onContextMenu = onContextMenu.bind(this);
    }

    connect(domElement: HTMLCanvasElement) {
        this.domElement = domElement

        window.addEventListener('keydown', this._onKeyDown);
        window.addEventListener('keyup', this._onKeyUp);

        this.domElement.addEventListener('pointermove', this._onPointerMove);
        this.domElement.addEventListener('pointerdown', this._onPointerDown);
        this.domElement.addEventListener('pointerup', this._onPointerUp);
        this.domElement.addEventListener('pointercancel', this._onPointerCancel);
        this.domElement.addEventListener('contextmenu', this._onContextMenu);

    }

    disconnect() {

        window.removeEventListener('keydown', this._onKeyDown);
        window.removeEventListener('keyup', this._onKeyUp);
        if (this.domElement) {
            this.domElement.removeEventListener('pointermove', this._onPointerMove);
            this.domElement.removeEventListener('pointerdown', this._onPointerDown);
            this.domElement.removeEventListener('pointerup', this._onPointerUp);
            this.domElement.removeEventListener('pointercancel', this._onPointerCancel);
            this.domElement.removeEventListener('contextmenu', this._onContextMenu);


            this.domElement = null
        }

    }

    dispose() {

        this.disconnect();

    }

    update(delta: number) {

        if (this.enabled === false) return;

        const object = this;

        const moveMult = delta * this.movementSpeed;
        const rotMult = delta * this.rollSpeed;

        object.translateX(this._moveVector.x * moveMult);
        object.translateY(this._moveVector.y * moveMult);
        object.translateZ(this._moveVector.z * moveMult);

        this._tmpQuaternion.set(this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1).normalize();
        object.quaternion.multiply(this._tmpQuaternion);

        if (
            this._lastPosition.distanceToSquared(object.position) > _EPS ||
            8 * (1 - this._lastQuaternion.dot(object.quaternion)) > _EPS
        ) {

            this.dispatchEvent(_changeEvent);
            this._lastQuaternion.copy(object.quaternion);
            this._lastPosition.copy(object.position);

        }
        console.log('update');

    }

    // private

    _updateMovementVector() {

        const forward = (this._moveState.forward || (this.autoForward && !this._moveState.back)) ? 1 : 0;

        this._moveVector.x = (- this._moveState.left + this._moveState.right);
        this._moveVector.y = (- this._moveState.down + this._moveState.up);
        this._moveVector.z = (- forward + this._moveState.back);

        //console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] );

    }

    _updateRotationVector() {

        this._rotationVector.x = (- this._moveState.pitchDown + this._moveState.pitchUp);
        this._rotationVector.y = (- this._moveState.yawRight + this._moveState.yawLeft);
        this._rotationVector.z = (- this._moveState.rollRight + this._moveState.rollLeft);

        //console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] );

    }

    _getContainerDimensions() {
        if (!this.domElement) return

        // 类型保护
        if (this.domElement instanceof HTMLElement && this.domElement === document.body) {

            return {
                size: [this.domElement.offsetWidth, this.domElement.offsetHeight],
                offset: [this.domElement.offsetLeft, this.domElement.offsetTop]
            };

        } else {

            return {
                size: [window.innerWidth, window.innerHeight],
                offset: [0, 0]
            };

        }

    }
}


function onKeyDown(this: any, event: { altKey: any; code: any; }) {

    if (event.altKey || this.enabled === false) {

        return;

    }

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = .1; break;

        case 'KeyW': this._moveState.forward = 1; break;
        case 'KeyS': this._moveState.back = 1; break;

        case 'KeyA': this._moveState.left = 1; break;
        case 'KeyD': this._moveState.right = 1; break;

        case 'KeyR': this._moveState.up = 1; break;
        case 'KeyF': this._moveState.down = 1; break;

        case 'ArrowUp': this._moveState.pitchUp = 1; break;
        case 'ArrowDown': this._moveState.pitchDown = 1; break;

        case 'ArrowLeft': this._moveState.yawLeft = 1; break;
        case 'ArrowRight': this._moveState.yawRight = 1; break;

        case 'KeyQ': this._moveState.rollLeft = 1; break;
        case 'KeyE': this._moveState.rollRight = 1; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onKeyUp(this: any, event: { code: any; }) {

    if (this.enabled === false) return;

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = 1; break;

        case 'KeyW': this._moveState.forward = 0; break;
        case 'KeyS': this._moveState.back = 0; break;

        case 'KeyA': this._moveState.left = 0; break;
        case 'KeyD': this._moveState.right = 0; break;

        case 'KeyR': this._moveState.up = 0; break;
        case 'KeyF': this._moveState.down = 0; break;

        case 'ArrowUp': this._moveState.pitchUp = 0; break;
        case 'ArrowDown': this._moveState.pitchDown = 0; break;

        case 'ArrowLeft': this._moveState.yawLeft = 0; break;
        case 'ArrowRight': this._moveState.yawRight = 0; break;

        case 'KeyQ': this._moveState.rollLeft = 0; break;
        case 'KeyE': this._moveState.rollRight = 0; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onPointerDown(this: any, event: { button: any; }) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status++;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 1; break;
            case 2: this._moveState.back = 1; break;

        }

        this._updateMovementVector();

    }

}

function onPointerMove(this: any, event: { pageX: number; pageY: number; }) {

    if (this.enabled === false) return;

    if (!this.dragToLook || this._status > 0) {

        const container = this._getContainerDimensions();
        const halfWidth = container.size[0] / 2;
        const halfHeight = container.size[1] / 2;

        this._moveState.yawLeft = - ((event.pageX - container.offset[0]) - halfWidth) / halfWidth;
        this._moveState.pitchDown = ((event.pageY - container.offset[1]) - halfHeight) / halfHeight;

        this._updateRotationVector();

    }

}

function onPointerUp(this: any, event: { button: any; }) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status--;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 0; break;
            case 2: this._moveState.back = 0; break;

        }

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onPointerCancel(this: any) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status = 0;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        this._moveState.forward = 0;
        this._moveState.back = 0;

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onContextMenu(this: any, event: { preventDefault: () => void; }) {

    if (this.enabled === false) return;

    event.preventDefault();

}

相机使用

typescript 复制代码
	import { BoxGeometry, Clock, EventDispatcher, Mesh, MeshNormalMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import FlyCamera from "../Cameras/FlyCamera";


export default class Engine extends EventDispatcher {
    scene: Scene
    camera: FlyCamera;
    renderer: WebGLRenderer
    dom: HTMLElement
    clock: Clock

    constructor(dom: HTMLElement) {
        super()
        this.dom = dom


        let w = dom.offsetWidth
        let h = dom.offsetHeight
        let scene = new Scene();

        let camera = new FlyCamera(75, w / h, 0.1, 1000);
        let renderer = new WebGLRenderer();
        renderer.setSize(w, h);
        camera.connect(renderer.domElement)//这是将鼠标互动的dom元素绑定到上面,如果不用这个dom的话,可以用其他div做成类似控制面板的东西(比如绑定触摸摇杆控件)
        dom.appendChild(renderer.domElement);

        const geometry = new BoxGeometry();
        const material = new MeshNormalMaterial();
        const cube = new Mesh(geometry, material);
        scene.add(cube);
        camera.position.set(0,0,5)
        camera.lookAt(0, 0, 0)

        dom.addEventListener('resize', this.resize)

        this.scene = scene
        this.camera = camera
        this.renderer = renderer
        this.clock = new Clock()
        this.animate();



    }


    resize = () => {
        let dom = this.dom
        let w = dom.offsetWidth
        let h = dom.offsetHeight
        this.camera.aspect = w / h
        this.renderer.setSize(w, h);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));
        this.render();
    }

    render() {
        this.renderer.render(this.scene, this.camera);
        const delta = this.clock.getDelta();
        this.camera.update(delta)
    }
}

关于自定义

这里我用js代码演示,绑定的任然是flyControl

javascript 复制代码
import { Clock, PerspectiveCamera, Quaternion, Vector3 } from "three";

export default class FPSCamera extends PerspectiveCamera {


    constructor(fov, aspect, near, far, domElement = null) {
        super(fov, aspect, near, far)
        this.domElement = null
        this._tmpQuaternion = new Quaternion();


        this.enabled = true;

        this.state = - 1;

        this.keys = {};
        this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null };
        this.touches = { ONE: null, TWO: null };


        this.movementSpeed = 1.0;
        this.rollSpeed = 0.2;

        this.dragToLook = false;
        this.autoForward = false;

        // internals

        this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
        this._moveVector = new Vector3(0, 0, 0);
        this._rotationVector = new Vector3(0, 0, 0);
        this._lastQuaternion = new Quaternion();
        this._lastPosition = new Vector3();
        this._status = 0;

        // event listeners

        this._onKeyDown = onKeyDown.bind(this);
        this._onKeyUp = onKeyUp.bind(this);
        this._onPointerMove = onPointerMove.bind(this);
        this._onPointerDown = onPointerDown.bind(this);
        this._onPointerUp = onPointerUp.bind(this);
        this._onPointerCancel = onPointerCancel.bind(this);
        this._onContextMenu = onContextMenu.bind(this);




        // animate
        this._clock = new Clock()
        this.active = false

        if (domElement) this.connect(domElement)
    }

    connect(domElement) {
        console.log('FPSCAMERA CONNECT', domElement);

        this.domElement = domElement

        window.addEventListener('keydown', this._onKeyDown);
        window.addEventListener('keyup', this._onKeyUp);

        this.domElement.addEventListener('pointermove', this._onPointerMove);
        this.domElement.addEventListener('pointerdown', this._onPointerDown);
        this.domElement.addEventListener('pointerup', this._onPointerUp);
        this.domElement.addEventListener('pointercancel', this._onPointerCancel);
        this.domElement.addEventListener('contextmenu', this._onContextMenu);

    }

    disconnect() {

        window.removeEventListener('keydown', this._onKeyDown);
        window.removeEventListener('keyup', this._onKeyUp);

        this.domElement.removeEventListener('pointermove', this._onPointerMove);
        this.domElement.removeEventListener('pointerdown', this._onPointerDown);
        this.domElement.removeEventListener('pointerup', this._onPointerUp);
        this.domElement.removeEventListener('pointercancel', this._onPointerCancel);
        this.domElement.removeEventListener('contextmenu', this._onContextMenu);


        this.domElement = null
    }

    dispose() {

        this.disconnect();

    }

    update(delta) {

        const _EPS = 0.000001;

        if (this.enabled === false) return;

        const object = this;

        const moveMult = delta * this.movementSpeed;
        const rotMult = delta * this.rollSpeed;

        object.translateX(this._moveVector.x * moveMult);
        object.translateY(this._moveVector.y * moveMult);
        object.translateZ(this._moveVector.z * moveMult);

        this._tmpQuaternion.set(this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1).normalize();
        object.quaternion.multiply(this._tmpQuaternion);

        if (
            this._lastPosition.distanceToSquared(object.position) > _EPS ||
            8 * (1 - this._lastQuaternion.dot(object.quaternion)) > _EPS
        ) {

            this.dispatchEvent({ type: 'change' });
            this._lastQuaternion.copy(object.quaternion);
            this._lastPosition.copy(object.position);

        }
        // console.log('update');

    }
    // start
    start() {
        let loop = () => {
            if (this.active)
                requestAnimationFrame(loop)
            else return
            let delta = this._clock.getDelta()
            this.update(delta)
        }

        this.active = true
        loop()
    }
    end() {
        this.active = false
    }


    // private

    _updateMovementVector() {

        const forward = (this._moveState.forward || (this.autoForward && !this._moveState.back)) ? 1 : 0;

        this._moveVector.x = (- this._moveState.left + this._moveState.right);
        this._moveVector.y = (- this._moveState.down + this._moveState.up);
        this._moveVector.z = (- forward + this._moveState.back);

        //console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] );

    }

    _updateRotationVector() {

        this._rotationVector.x = (- this._moveState.pitchDown + this._moveState.pitchUp);
        this._rotationVector.y = (- this._moveState.yawRight + this._moveState.yawLeft);
        this._rotationVector.z = (- this._moveState.rollRight + this._moveState.rollLeft);

        //console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] );

    }

    _getContainerDimensions() {

        if (this.domElement != document) {

            return {
                size: [this.domElement.offsetWidth, this.domElement.offsetHeight],
                offset: [this.domElement.offsetLeft, this.domElement.offsetTop]
            };

        } else {

            return {
                size: [window.innerWidth, window.innerHeight],
                offset: [0, 0]
            };

        }

    }
}


function onKeyDown(event) {

    if (event.altKey || this.enabled === false) {

        return;

    }

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = .1; break;

        case 'KeyW': this._moveState.forward = 1; break;
        case 'KeyS': this._moveState.back = 1; break;

        case 'KeyA': this._moveState.left = 1; break;
        case 'KeyD': this._moveState.right = 1; break;

        case 'KeyR': this._moveState.up = 1; break;
        case 'KeyF': this._moveState.down = 1; break;

        case 'ArrowUp': this._moveState.pitchUp = 1; break;
        case 'ArrowDown': this._moveState.pitchDown = 1; break;

        case 'ArrowLeft': this._moveState.yawLeft = 1; break;
        case 'ArrowRight': this._moveState.yawRight = 1; break;

        case 'KeyQ': this._moveState.rollLeft = 1; break;
        case 'KeyE': this._moveState.rollRight = 1; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onKeyUp(event) {

    if (this.enabled === false) return;

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = 1; break;

        case 'KeyW': this._moveState.forward = 0; break;
        case 'KeyS': this._moveState.back = 0; break;

        case 'KeyA': this._moveState.left = 0; break;
        case 'KeyD': this._moveState.right = 0; break;

        case 'KeyR': this._moveState.up = 0; break;
        case 'KeyF': this._moveState.down = 0; break;

        case 'ArrowUp': this._moveState.pitchUp = 0; break;
        case 'ArrowDown': this._moveState.pitchDown = 0; break;

        case 'ArrowLeft': this._moveState.yawLeft = 0; break;
        case 'ArrowRight': this._moveState.yawRight = 0; break;

        case 'KeyQ': this._moveState.rollLeft = 0; break;
        case 'KeyE': this._moveState.rollRight = 0; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onPointerDown(event) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status++;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 1; break;
            case 2: this._moveState.back = 1; break;

        }

        this._updateMovementVector();

    }

}

function onPointerMove(event) {

    if (this.enabled === false) return;

    if (!this.dragToLook || this._status > 0) {

        const container = this._getContainerDimensions();
        const halfWidth = container.size[0] / 2;
        const halfHeight = container.size[1] / 2;

        this._moveState.yawLeft = - ((event.pageX - container.offset[0]) - halfWidth) / halfWidth;
        this._moveState.pitchDown = ((event.pageY - container.offset[1]) - halfHeight) / halfHeight;

        this._updateRotationVector();

    }

}

function onPointerUp(event) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status--;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 0; break;
            case 2: this._moveState.back = 0; break;

        }

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onPointerCancel() {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status = 0;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        this._moveState.forward = 0;
        this._moveState.back = 0;

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onContextMenu(event) {

    if (this.enabled === false) return;

    event.preventDefault();

}

这里我添加了start和end方法来暂停和启动它,而在之前ts的版本中,我直接再渲染动画帧中调用了update方法

原版

这是一个最纯净的js版本

javascript 复制代码
import { PerspectiveCamera, Quaternion, Vector3 } from "three";
const _changeEvent = { type: 'change' };




export default class FlyCamera extends PerspectiveCamera {


    constructor(fov, aspect, near, far, domElement = null) {
        super(fov, aspect, near, far)
        this.domElement = null
        this._tmpQuaternion = new Quaternion();


        this.enabled = true;

        this.state = - 1;

        this.keys = {};
        this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null };
        this.touches = { ONE: null, TWO: null };


        this.movementSpeed = 1.0;
        this.rollSpeed = 0.005;

        this.dragToLook = false;
        this.autoForward = false;

        // internals

        this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
        this._moveVector = new Vector3(0, 0, 0);
        this._rotationVector = new Vector3(0, 0, 0);
        this._lastQuaternion = new Quaternion();
        this._lastPosition = new Vector3();
        this._status = 0;

        // event listeners

        this._onKeyDown = onKeyDown.bind(this);
        this._onKeyUp = onKeyUp.bind(this);
        this._onPointerMove = onPointerMove.bind(this);
        this._onPointerDown = onPointerDown.bind(this);
        this._onPointerUp = onPointerUp.bind(this);
        this._onPointerCancel = onPointerCancel.bind(this);
        this._onContextMenu = onContextMenu.bind(this);
        if (domElement) this.connect(domElement)
    }

    connect(domElement) {
        this.domElement = domElement

        window.addEventListener('keydown', this._onKeyDown);
        window.addEventListener('keyup', this._onKeyUp);

        this.domElement.addEventListener('pointermove', this._onPointerMove);
        this.domElement.addEventListener('pointerdown', this._onPointerDown);
        this.domElement.addEventListener('pointerup', this._onPointerUp);
        this.domElement.addEventListener('pointercancel', this._onPointerCancel);
        this.domElement.addEventListener('contextmenu', this._onContextMenu);

    }

    disconnect() {

        window.removeEventListener('keydown', this._onKeyDown);
        window.removeEventListener('keyup', this._onKeyUp);

        this.domElement.removeEventListener('pointermove', this._onPointerMove);
        this.domElement.removeEventListener('pointerdown', this._onPointerDown);
        this.domElement.removeEventListener('pointerup', this._onPointerUp);
        this.domElement.removeEventListener('pointercancel', this._onPointerCancel);
        this.domElement.removeEventListener('contextmenu', this._onContextMenu);


        this.domElement = null
    }

    dispose() {

        this.disconnect();

    }

    update(delta) {

        const _EPS = 0.000001;

        if (this.enabled === false) return;

        const object = this;

        const moveMult = delta * this.movementSpeed;
        const rotMult = delta * this.rollSpeed;

        object.translateX(this._moveVector.x * moveMult);
        object.translateY(this._moveVector.y * moveMult);
        object.translateZ(this._moveVector.z * moveMult);

        this._tmpQuaternion.set(this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1).normalize();
        object.quaternion.multiply(this._tmpQuaternion);

        if (
            this._lastPosition.distanceToSquared(object.position) > _EPS ||
            8 * (1 - this._lastQuaternion.dot(object.quaternion)) > _EPS
        ) {

            this.dispatchEvent({ type: 'change' });
            this._lastQuaternion.copy(object.quaternion);
            this._lastPosition.copy(object.position);

        }
        console.log('update');

    }

    // private

    _updateMovementVector() {

        const forward = (this._moveState.forward || (this.autoForward && !this._moveState.back)) ? 1 : 0;

        this._moveVector.x = (- this._moveState.left + this._moveState.right);
        this._moveVector.y = (- this._moveState.down + this._moveState.up);
        this._moveVector.z = (- forward + this._moveState.back);

        //console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] );

    }

    _updateRotationVector() {

        this._rotationVector.x = (- this._moveState.pitchDown + this._moveState.pitchUp);
        this._rotationVector.y = (- this._moveState.yawRight + this._moveState.yawLeft);
        this._rotationVector.z = (- this._moveState.rollRight + this._moveState.rollLeft);

        //console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] );

    }

    _getContainerDimensions() {

        if (this.domElement != document) {

            return {
                size: [this.domElement.offsetWidth, this.domElement.offsetHeight],
                offset: [this.domElement.offsetLeft, this.domElement.offsetTop]
            };

        } else {

            return {
                size: [window.innerWidth, window.innerHeight],
                offset: [0, 0]
            };

        }

    }
}


function onKeyDown(event) {

    if (event.altKey || this.enabled === false) {

        return;

    }

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = .1; break;

        case 'KeyW': this._moveState.forward = 1; break;
        case 'KeyS': this._moveState.back = 1; break;

        case 'KeyA': this._moveState.left = 1; break;
        case 'KeyD': this._moveState.right = 1; break;

        case 'KeyR': this._moveState.up = 1; break;
        case 'KeyF': this._moveState.down = 1; break;

        case 'ArrowUp': this._moveState.pitchUp = 1; break;
        case 'ArrowDown': this._moveState.pitchDown = 1; break;

        case 'ArrowLeft': this._moveState.yawLeft = 1; break;
        case 'ArrowRight': this._moveState.yawRight = 1; break;

        case 'KeyQ': this._moveState.rollLeft = 1; break;
        case 'KeyE': this._moveState.rollRight = 1; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onKeyUp(event) {

    if (this.enabled === false) return;

    switch (event.code) {

        case 'ShiftLeft':
        case 'ShiftRight': this.movementSpeedMultiplier = 1; break;

        case 'KeyW': this._moveState.forward = 0; break;
        case 'KeyS': this._moveState.back = 0; break;

        case 'KeyA': this._moveState.left = 0; break;
        case 'KeyD': this._moveState.right = 0; break;

        case 'KeyR': this._moveState.up = 0; break;
        case 'KeyF': this._moveState.down = 0; break;

        case 'ArrowUp': this._moveState.pitchUp = 0; break;
        case 'ArrowDown': this._moveState.pitchDown = 0; break;

        case 'ArrowLeft': this._moveState.yawLeft = 0; break;
        case 'ArrowRight': this._moveState.yawRight = 0; break;

        case 'KeyQ': this._moveState.rollLeft = 0; break;
        case 'KeyE': this._moveState.rollRight = 0; break;

    }

    this._updateMovementVector();
    this._updateRotationVector();

}

function onPointerDown(event) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status++;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 1; break;
            case 2: this._moveState.back = 1; break;

        }

        this._updateMovementVector();

    }

}

function onPointerMove(event) {

    if (this.enabled === false) return;

    if (!this.dragToLook || this._status > 0) {

        const container = this._getContainerDimensions();
        const halfWidth = container.size[0] / 2;
        const halfHeight = container.size[1] / 2;

        this._moveState.yawLeft = - ((event.pageX - container.offset[0]) - halfWidth) / halfWidth;
        this._moveState.pitchDown = ((event.pageY - container.offset[1]) - halfHeight) / halfHeight;

        this._updateRotationVector();

    }

}

function onPointerUp(event) {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status--;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        switch (event.button) {

            case 0: this._moveState.forward = 0; break;
            case 2: this._moveState.back = 0; break;

        }

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onPointerCancel() {

    if (this.enabled === false) return;

    if (this.dragToLook) {

        this._status = 0;

        this._moveState.yawLeft = this._moveState.pitchDown = 0;

    } else {

        this._moveState.forward = 0;
        this._moveState.back = 0;

        this._updateMovementVector();

    }

    this._updateRotationVector();

}

function onContextMenu(event) {

    if (this.enabled === false) return;

    event.preventDefault();

}
相关推荐
阿福的工作室1 分钟前
react跳转传参的方法
前端·react.js·前端框架
远洋录1 分钟前
大型前端应用状态管理实战:从 Redux 到 React Query 的演进之路
前端·人工智能·react
liuweni4 分钟前
Next.js系统性教学:深入理解缓存交互与API缓存管理
开发语言·前端·javascript·经验分享·缓存·前端框架·交互
m0_7482402513 分钟前
前端编程艺术(5)---Vue3(从零基础到项目开发)
前端
m0_7482574621 分钟前
使用Element UI实现前端分页,前端搜索,及el-table表格跨页选择数据,切换分页保留分页数据,限制多选数量
前端·ui·状态模式
m0_7482396324 分钟前
【HTML入门】第十六课 - 网页中的按钮们
前端·html
m0_7482451726 分钟前
Chrome浏览器调用ActiveX控件--allWebOffice控件
前端·chrome
web1508541593530 分钟前
【CSS】css 如何实现固定宽高比
前端·css
常常不爱学习37 分钟前
CSS的颜色表示方式
前端·css
Domain-zhuo1 小时前
React的功能是什么?
前端·javascript·react native·react.js·前端框架·ecmascript