当前位置: 首页 > news >正文

【threejs】创建FPS相机

原理说明

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

相机代码

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

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 = nullthis._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;// internalsthis._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 listenersthis._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 = domElementwindow.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();}

相机使用

	import { BoxGeometry, Clock, EventDispatcher, Mesh, MeshNormalMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import FlyCamera from "../Cameras/FlyCamera";export default class Engine extends EventDispatcher {scene: Scenecamera: FlyCamera;renderer: WebGLRendererdom: HTMLElementclock: Clockconstructor(dom: HTMLElement) {super()this.dom = domlet w = dom.offsetWidthlet h = dom.offsetHeightlet 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 = scenethis.camera = camerathis.renderer = rendererthis.clock = new Clock()this.animate();}resize = () => {let dom = this.domlet w = dom.offsetWidthlet h = dom.offsetHeightthis.camera.aspect = w / hthis.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

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 = nullthis._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;// internalsthis._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 listenersthis._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);// animatethis._clock = new Clock()this.active = falseif (domElement) this.connect(domElement)}connect(domElement) {console.log('FPSCAMERA CONNECT', domElement);this.domElement = domElementwindow.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');}// startstart() {let loop = () => {if (this.active)requestAnimationFrame(loop)else returnlet delta = this._clock.getDelta()this.update(delta)}this.active = trueloop()}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版本

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 = nullthis._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;// internalsthis._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 listenersthis._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 = domElementwindow.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();}

相关文章:

【threejs】创建FPS相机

原理说明 控制器是一个很麻烦的东西,需要创建更多的类来管理相机行为,并且可自定义性差,所以将部分控制器的功能绑定到相机上,可以解决这些问题,所以我以 FlyControls为例,将控制器功能绑定到相机上&#…...

路径规划之启发式算法之十一:布谷鸟搜索算法(Cuckoo Search,CS)

布谷鸟搜索算法(Cuckoo Search,CS)是一种新兴的自然启发式算法,由剑桥大学的杨新社教授和S.戴布(Xin-She Yang和Suash Deb)于2009年提出。该算法基于布谷鸟的寄生性育雏(巢寄生)行为…...

Mitel MiCollab企业协作平台存在任意文件读取漏洞(CVE-2024-41713)

免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

java+springboot+mysql党务(党员)管理系统

项目介绍: 使用javaspringbootmysql开发的党务管理系统,系统包含管理员、用户角色,功能如下: 管理员:用户管理;党支部管理;党员管理(入党申请、积极分子、发展对象、预备党员、正式…...

gozero项目迁移与新服务器环境配置,包含服务器安装包括go版本,Nginx,项目配置包括Mysql,redis,rabbit,域名

迁移 **GoZero** 项目到新服务器并配置相关环境涉及多个步骤。以下是一个系统化的指南,涵盖服务器环境安装、数据库和缓存配置、项目部署以及域名绑定。 ### 步骤概述 1. **服务器环境配置** - 安装 Go 语言环境 - 安装 Nginx - 安装 MySQL 和 Redis -…...

使用WebDAV来上传和下载文件

WebDAV是什么 基于Web的分布式编写和版本控制(WebDAV)是超文本传输协议(HTTP)的扩展,有利于用户间协同编辑和管理存储在万维网服务器文档。WebDAV 由互联网工程任务组的工作组在 RFC 4918 中定义。许多现代操作系统为 …...

集成运算放大电路反馈判断

集成运算放大电路 一种具有很高放大倍数的多级直接耦合放大电路,因最初用于信号运算而得名,简称集成运放或运放 模拟集成电路中的典型组件,是发展最快、品种最多、应用最广的一种 反馈 将放大电路输出信号的一部分或全部通过某种电路引回到输…...

什么是绩效文化?

绩效文化是一种组织文化,它将绩效视为核心价值观,贯穿于组织的各个层面和活动之中。 一、绩效文化的内涵 目标导向 绩效文化强调组织成员都朝着共同的目标努力。这个目标通常是明确、可衡量的,如企业的年度利润目标、市场份额增长目标等。例…...

【力扣】409.最长回文串

问题描述 思路解析 因为同时包含大小写字母,直接创建个ASCII表大小的桶来标记又因为是要回文子串,所以偶数个数的一定可以那么同时,对于出现奇数次数的,我没需要他们的次数-1,变为偶数,并且可以标记出现过…...

android studio创建虚拟机注意事项

emulator 启动模拟器的时候,可以用 AVD 界面,也可以用命令行启动,但命令行启 动的时候要注意,系统有两个 emulator.exe ,建议使用 emulator 目录下的那个!! 创建类型为google APIs的虚拟机可从…...

DP协议:缩略词

缩写代表的含义ACT分配更改触发(Allocation Change Trigger)API应用程序编程接口(Application Programming Interface)AUX辅助(Auxiliary)BER比特错误率(Bit Error Rate)bpc每色比特…...

【Rive】事件回调

1 前言 Android 中可以通过 RiveAnimationView 的 addEventListener 方法添加动画监听器,用于监听状态动画和过渡动画的开始和结束时机,实现动画开始和结束时的事件回调;也可以监听 Rive 事件触发的时机,在事件触发时响应回调。 …...

YOLOv8-ultralytics-8.2.103部分代码阅读笔记-tuner.py

tuner.py ultralytics\engine\tuner.py 目录 tuner.py 1.所需的库和模块 2.class Tuner: 1.所需的库和模块 # Ultralytics YOLO 🚀, AGPL-3.0 license# 模块提供用于对象检测、实例分割、图像分类、姿势估计和多对象跟踪的 Ultralytics YOLO 模型的超参数调整…...

【数据结构】堆的概念、结构、模拟实现以及应用

本篇我们来介绍一下数据结构中的堆。 1.堆的概念及结构 1)堆是一颗完全二叉树。 2)堆又分为大堆和小堆,大堆就是树里面任何一个父节点都大于子节点,所以根节点是最大值;小堆就是树里面任何一个父节点都小于子节点&am…...

推送(push)项目到gitlab

文章目录 1、git init1.1、在当前目录中显示隐藏文件:1.2、查看已有的远程仓库1.3、确保你的本地机器已经生成了 SSH 密钥:1.4、将生成的公钥文件(通常位于 ~/.ssh/id_rsa.pub)复制到 GitLab 的 SSH 设置中:1.5、测试 …...

springboot/ssm宠物商城网站系统Java代码web项目宠物用品购物论坛源码

springboot/ssm宠物商城网站系统Java代码web项目宠物用品购物论坛源码 基于springboot(可改ssm)htmlvue项目 开发语言:Java 框架:springboot/可改ssm vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库&…...

前端基础的讲解-JS(22)

什么是JSON? 1.json 是一种轻量级的数据交换格式 简单来说:json 就是一种在各个编程语言中流通的数据格式,负责不同编程语言中的数据传递和交互。 类似于: 国际通用语言 - 英语 中国 56 个民族不同地区的通用语言 - 普通话 …...

zerotier实现内网穿透(访问内网服务器)

moo 内网穿透工具 实用工具:zerotier 目录 内网穿透工具 Windows下zerotier安装 ubuntu系统下的zerotier安装 使用moon加速 Windows下zerotier安装 有了网络之后,会给你一个网络id,这个网络id是非常重要的,其它设备要加入…...

python语言中怎么调用不同级文件夹中数据文件

python语言中怎么调用文件夹中数据文件 python 怎么调用同一级文件夹中数据1. **读取同一级文件夹中的数据文件(如 .txt, .csv, .json 等)**示例: 2. **导入同一级文件夹中的 Python 模块**3. **使用相对路径导入模块**4. **使用 os.path 或 …...

spring事务源码解析

1 引入 在企业级应用开发中,事务管理 是确保数据一致性和完整性的重要手段。而在 Spring 框架中,事务管理提供了高度抽象和灵活的实现,开发者只需通过简单的注解或配置即可轻松实现复杂的事务逻辑。然而,Spring 事务背后的实现机…...

【每日刷题】Day165

【每日刷题】Day165 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. LCR 092. 将字符串翻转到单调递增 - 力扣(LeetCode) 2. 424. 替换后的最长…...

基于51单片机的智能公交车报站系统GPS定位语音播报智能安全检测人数统计

功能描述 1.LCD12864可显示当前年月日,星期,时间, 当前站名,经纬度,是否连接GPS,自动/手动模式, 2.自带GPS定位,可实时显示经纬度; 3.通过DS1302时钟芯片,获…...

计算机网络安全 —— 实体鉴别与生成大随机数

一、实体鉴别# ​ 实体鉴别(经常简称为鉴别)就是一方验证另一方身份的技术。一个实体可以是人、客户/服务器进程等。这里仅讨论如何鉴别通信对端 实体的身份,即验证正在通信的对方确实是所认为的通信实体,而不是其他的假冒者。进…...

Vue3+Pinia 状态管理持久化

一、Pinia 简介 🎖️Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。Vue3VitePinia 新三剑客逐渐替代了Vue2WebpackVuex 了,性能啥的各方面吊打。 1.1 什么是状态管理? &#x1f…...

开源项目:轻型图像分割 unet_lite

DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 需要更多数据资源和技术解决方案,知识星球: “DataBall - X 数据球(free)” -------------------------------------------------------------…...

C# 向上取整多种实现方法

1.使用 Math.Ceiling 方法: 在 C# 中,可以利用 System.Math 类下的 Math.Ceiling 方法来实现向上取整。它接受一个 double 或 decimal 类型的参数,并返回大于或等于该参数的最小整数(以 double 或 decimal 类型表示)。…...

Linux 权限及管理

目录 一、Linux权限 1、概念 2、超级用户和普通用户的相关操作 a. 添加用户,删除用户 b. 超级用户和普通用户的切换 c. sduo提权以及白名单设置 二、Linux权限管理 1、文件访问者的分类 2、文件访问类型和权限 a. 文件类型 b. 基本权限 3、文件权限值…...

【JVM】JVM基础教程(一)

目录 初识JVM JVM是什么? JVM的功能 解释、即时编译和运行 内存管理 常见的JVM JVM虚拟机规范 HotSpot的发展历程 JVM的组成 字节码文件详解 应用场景 以正确姿势打开字节码文件 ​编辑字节码文件的组成 基本信息 Magic魔数 主副版本号 常量池 接口…...

企业国内外网络互联方案全解析

面对国内市场日益饱和的现状,企业纷纷将目光投向海外,而实现国内外网络的高效互联,则成为支撑其跨国业务顺利运行的关键。本文将为您详细介绍几种实现国内外网络互联的有效策略,助您轻松应对全球化挑战。 有些企业选择使用虚拟专用…...

【优选算法 位运算】位运算算法入门详解:位运算小专题

判定字符是否唯一 题目解析 算法原理 解法一 :哈希数组 从前往后扫描字符串,把扫描到的字符先进行判断,如果对应的 val 0 ,则放入哈希表中,否则返回 false,知道扫描完整个字符;时间…...

大文件分块上传后端服务器

一、背景: 后台系统需要上传大文件、大视频等数据,耗时过长,接口等待超时,故需优化通过前端多线程分片方式进行文件上传,显著提升上传速度。 二、流程: 前端逻辑: 前端使用分片技术&#xff…...

perl Window安装教程

perl Window安装教程 下载地址 https://platform.activestate.com/tangxing806/ActivePerl-5.28/distributions 运行state-remote-installer.exe 按下图截图步骤 检查perl版本 参考文献: perl安装教程...

Scrcpy投影之后为什么声音在电脑端显示?

关于安卓设备和电脑端扬声器优先级 在使用安卓设备与电脑进行某些连接操作(比如通过 adb 相关工具交互时),确实存在音频输出的优先级选择情况。通常情况下,可能默认音频会输出到电脑端(比如通过投屏等相关操作连接后&…...

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省) 任务书

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省 任务书 模块一网络平台搭建与设备安全防护任务1:网络平台搭建 (50分)任务2:网络安全设备配置与防护(250分) 模块二网络安全事件响应、数字取证…...

java+ssm+mysql收纳培训网

项目介绍: 使用javassmmysql开发的收纳视频培训网,系统包含超级管理员,系统管理员、培训师、用户角色,功能如下: 超级管理员:管理员管理;用户管理(培训师、用户)&#…...

多表查询-概述内连接外连接子查询

一.数据准备: 1.部门表: 代码: -- 部门管理 create table tb_dept (id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null c…...

H5游戏出海如何获得更多增长机会?

海外H5小游戏的崛起给了国内众多中小厂商出海发展的机会,开发者如何在海外市场获得更多的增长机会?#APP出海# H5游戏如何在海外获得核心用户? HTML5游戏的开发与运营者们首先可以利用量多质高的HTML5游戏,维持海外用户粘性&…...

element plus的表单校验,明明输入内容了,但提示红字还是会显示着

下拉框的不隐藏,可能是 trigger为blur的原因,改为change即可 const rules reactive({name: [{ required: true, message: "请输入名称", trigger: "blur" }],price: [{ required: true, message: "请输入价格", trigger…...

MobaXterm Sessions 批量录入导入,会话批量添加,解决导入配置中文乱码

一、创建表格 创建 Excel 表格,将服务器信息写入表格 二、写入文件 新建 list.txt 文件将表格中的服务器信息复制粘贴进去 三、修改脚本 这是你需要修改的变量,其他变量不需要动 # 登录用户 ssh_userroot # 目录名称 folder_name资源池四、执行脚本 …...

Vue项目中的权限控制实践与方案详解

在现代前端开发中,权限控制是一个不可或缺的重要环节。一个完善的权限控制系统不仅能够保护应用的安全性,还能为不同角色的用户提供更好的使用体验。让我们深入探讨Vue项目中权限控制的实现方案和最佳实践。 权限控制本质上是对用户操作的一种限制&…...

C++11新特性之线程std::thread

C std::thread的定义和功能 std::thread是C11引入的标准库类,用于创建和管理线程。通过std::thread,程序可以并发执行多个任务,从而提高效率。 功能与作用: 创建线程:可以启动一个线程执行某个函数或任务。管理线程…...

西门子S7-200 SMART PLC在钢铁行业中的应用

西门子S7-200 SMART PLC在钢铁行业中的应用,主要得益于其强大的功能、简易的编程方式以及卓越的稳定性,这些特点使得它能够在钢铁行业的自动化控制中发挥重要作用。 以下是对西门子S7-200 SMART PLC在钢铁行业中应用的详细分析: 一、钢铁行业…...

Amazon SageMaker 和 Amazon Bedrock 有什么区别

Amazon SageMaker 和 Amazon Bedrock 有什么区别 文章目录 Amazon SageMaker 和 Amazon Bedrock 有什么区别1.服务定位和主要功能区别Amazon SageMakerAmazon Bedrock 2. 适用场景Amazon SageMakerAmazon Bedrock 3. 用户群体Amazon SageMakerAmazon Bedrock 4. 开发和部署流程…...

自动驾驶数据集的应用与思考

数据作为新型生产要素,是数字化、网络化、智能化的基础,是互联网时代的“石油”“煤炭”,掌握数据对于企业而言是能够持续生存和发展的不竭动力,对于需要大量数据训练自动驾驶系统的企业而言更是如此。 而随着激光雷达、毫米波雷…...

Python 中的 threading 模块和 multiprocessing 模块有何区别?

在Python编程中,threading 和 multiprocessing 模块都提供了并行处理的能力,但它们实现的方式以及适用的场景是不同的。 下面将详细解释两者的区别,并给出一些日常开发中的使用建议。 Threading(线程) threading 模…...

网络安全之常见风险端口(Common Risk Ports for Network Security)

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...

思科模拟器路由器的基本配置

一、实验目的 了解路由器的作用掌握路由器的基本配置方法 3、掌握路由器模块的使用和互连方式 二、实验环境 2811路由器一台,计算机两台,Console配置线一根,网线若干;本实验拓扑图如图8-1所示;计算机IP地址规划如表8-…...

使用ssh免密登录实现自动化部署rsync+nfs+lsync(脚本)

单机一键部署sshrsyncnfslsync 执行准备 主机信息 主机角色外网IP内网IP主机名nfs、lsync10.0.0.31176.16.1.31nfs客户端10.0.0.7176.16.1.7web01rsync、nfs10.0.0.41172.16.1.41backup 秘钥信息 #web01可以免密连接nfs和backup [rootweb01 ~]# ssh-keygen [rootweb01 ~]#…...

第425场周赛:最小正和子数组、重排子字符串以形成目标字符串、最小数组和、移除边之后的权重最大和

Q1、[简单] 最小正和子数组 1、题目描述 给你一个整数数组 nums 和 两个 整数 l 和 r。你的任务是找到一个长度在 l 和 r 之间(包含)且和大于 0 的 子数组 的 最小 和。 返回满足条件的子数组的 最小 和。如果不存在这样的子数组,则返回 -…...

常见矩阵分析法(BCG、GE、IE、SPACE、TOWS、优先、战略优先级、安索夫、风险矩阵):如何通过系统化方法助力战略决策与数据驱动决策

在快速变化的商业环境中,企业决策者面临着诸多复杂的选择与挑战。矩阵分析法作为战略分析的重要工具,能够系统化地分析企业的内外部环境,帮助管理层做出更加科学、合理的决策。本文将全面解析常见的矩阵分析法,并探讨它们在数据驱…...