three.js第三人称控制类代码
发布时间:
2023-09-22
预览次数:
以下是一个基本的three.js第三人称控制类代码的示例:
class ThirdPersonControls {
constructor(camera, target, options = {}) {
this.camera = camera;
this.target = target;
this.enabled = true;
this.distance = options.distance || 10;
this.minDistance = options.minDistance || 0.1;
this.maxDistance = options.maxDistance || Infinity;
this.minPolarAngle = options.minPolarAngle || 0; // 最小极角
this.maxPolarAngle = options.maxPolarAngle || Math.PI; // 最大极角
this.minAzimuthAngle = options.minAzimuthAngle || -Infinity; // 最小方位角
this.maxAzimuthAngle = options.maxAzimuthAngle || Infinity; // 最大方位角
this.rotateSpeed = options.rotateSpeed || 1;
this.zoomSpeed = options.zoomSpeed || 1;
this.panSpeed = options.panSpeed || 1;
this.enableRotate = options.enableRotate !== undefined ? options.enableRotate : true;
this.enableZoom = options.enableZoom !== undefined ? options.enableZoom : true;
this.enablePan = options.enablePan !== undefined ? options.enablePan : true;
this.keys = options.keys || {
LEFT: 37, // 左箭头键
UP: 38, // 上箭头键
RIGHT: 39, // 右箭头键
BOTTOM: 40 // 下箭头键
};
this.mouseButtons = options.mouseButtons || {
ORBIT: THREE.MOUSE.LEFT, // 鼠标左键
ZOOM: THREE.MOUSE.MIDDLE, // 鼠标中键
PAN: THREE.MOUSE.RIGHT // 鼠标右键
};
this.target0 = this.target.clone();
this.position0 = this.camera.position.clone();
this.zoom0 = this.camera.zoom;
this.rotateStart = new THREE.Vector2();
this.rotateEnd = new THREE.Vector2();
this.rotateDelta = new THREE.Vector2();
this.panStart = new THREE.Vector2();
this.panEnd = new THREE.Vector2();
this.panDelta = new THREE.Vector2();
this.dollyStart = new THREE.Vector2();
this.dollyEnd = new THREE.Vector2();
this.dollyDelta = new THREE.Vector2();
this.update();
this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), false);
this.domElement.addEventListener('mousewheel', this.onMouseWheel.bind(this), false);
this.domElement.addEventListener('DOMMouseScroll', this.onMouseWheel.bind(this), false); // 兼容Firefox
this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.domElement.addEventListener('mouseout', this.onMouseUp.bind(this), false);
this.domElement.addEventListener('keydown', this.onKeyDown.bind(this), false);
this.domElement.addEventListener('keyup', this.onKeyUp.bind(this), false);
}
update() {
const offset = new THREE.Vector3();
const quat = new THREE.Quaternion().setFromUnitVectors(
this.camera.up, new THREE.Vector3(0, 1, 0)
);
const quatInverse = quat.clone().inverse();
const lastPosition = new THREE.Vector3();
const lastQuaternion = new THREE.Quaternion();
return function update() {
const position = this.camera.position;
offset.copy(position).sub(this.target);
offset.applyQuaternion(quat);
// 方位角和极角
const azimuthAngle = Math.atan2(offset.x, offset.z);
const polarAngle = Math.acos(Math.max(-1, Math.min(1, offset.y / this.distance)));
azimuthAngle += this.rotateDelta.x * this.rotateSpeed;
polarAngle += this.rotateDelta.y * this.rotateSpeed;
polarAngle = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, polarAngle));
azimuthAngle = Math.max(this.minAzimuthAngle, Math.min(this.maxAzimuthAngle, azimuthAngle));
const radius = offset.length() * Math.cos(polarAngle);
offset.x = radius * Math.sin(polarAngle) * Math.sin(azimuthAngle);
offset.y = radius * Math.cos(polarAngle);
offset.z = radius * Math.sin(polarAngle) * Math.cos(azimuthAngle);
offset.applyQuaternion(quatInverse);
position.copy(this.target).add(offset);
this.camera.lookAt(this.target);
this.rotateDelta.set(0, 0);
};
}
onMouseDown(event) {
if (!this.enabled) return;
event.preventDefault();
if (event.button === this.mouseButtons.ORBIT) {
if (this.enableRotate === false) return;
this.rotateStart.set(event.clientX, event.clientY);
} else if (event.button === this.mouseButtons.ZOOM) {
if (this.enableZoom === false) return;
this.dollyStart.set(event.clientX, event.clientY);
} else if (event.button === this.mouseButtons.PAN) {
if (this.enablePan === false) return;
this.panStart.set(event.clientX, event.clientY);
}
this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.domElement.addEventListener('mouseout', this.onMouseUp.bind(this), false);
}
onMouseMove(event) {
if (!this.enabled) return;
event.preventDefault();
if (event.button === this.mouseButtons.ORBIT) {
if (this.enableRotate === false) return;
this.rotateEnd.set(event.clientX, event.clientY);
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
this.rotateStart.copy(this.rotateEnd);
} else if (event.button === this.mouseButtons.ZOOM) {
if (this.enableZoom === false) return;
this.dollyEnd.set(event.clientX, event.clientY);
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
if (this.dollyDelta.y > 0) {
this.zoomIn();
} else if (this.dollyDelta.y < 0) {
this.zoomOut();
}
this.dollyStart.copy(this.dollyEnd);
} else if (event.button === this.mouseButtons.PAN) {
if (this.enablePan === false) return;
this.panEnd.set(event.clientX, event.clientY);
this.panDelta.subVectors(this.panEnd, this.panStart);
this.pan(this.panDelta.x, this.panDelta.y);
this.panStart.copy(this.panEnd);
}
}
onMouseUp(event) {
if (!this.enabled) return;
event.preventDefault();
this.domElement.removeEventListener('mousemove', this.onMouseMove.bind(this), false);
this.domElement.removeEventListener('mouseup', this.onMouseUp.bind(this), false);
this.domElement.removeEventListener('mouseout', this.onMouseUp.bind(this), false);
}
onMouseWheel(event) {
if (!this.enabled || this.enableZoom === false || (this.state !== STATE.NONE && this.state !== STATE.ROTATE)) return;
event.preventDefault();
event.stopPropagation();
const delta = 0;
if (event.wheelDelta !== undefined) {
delta = event.wheelDelta;
} else if (event.detail !== undefined) {
delta = -event.detail;
}
if (delta > 0) {
this.zoomOut();
} else if (delta < 0) {
this.zoomIn();
}
}
onKeyDown(event) {
if (!this.enabled || !this.enableKeys || !this.enablePan) return;
switch (event.keyCode) {
case this.keys.UP:
this.pan(0, this.panSpeed);
break;
case this.keys.BOTTOM:
this.pan(0, -this.panSpeed);
break;
case this.keys.LEFT:
this.pan(this.panSpeed, 0);
break;
case this.keys.RIGHT:
this.pan(-this.panSpeed, 0);
break;
}
}
onKeyUp(event) {
if (!this.enabled || !this.enableKeys || !this.enablePan) return;
switch (event.keyCode) {
case this.keys.UP:
case this.keys.BOTTOM:
this.pan(0, 0);
break;
case this.keys.LEFT:
case this.keys.RIGHT:
this.pan(0, 0);
break;
}
}
zoomIn() {
const factor = Math.pow(0.95, this.zoomSpeed);
if (this.camera.isPerspectiveCamera) {
this.distance *= factor;
this.distance = Math.max(this.minDistance, Math.min(this.maxDistance, this.distance));
} else if (this.camera.isOrthographicCamera) {
this.camera.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.camera.zoom * factor));
this.camera.updateProjectionMatrix();
}
}
zoomOut() {
const factor = Math.pow(0.95, this.zoomSpeed);
if (this.camera.isPerspectiveCamera) {
this.distance /= factor;
this.distance = Math.max(this.minDistance, Math.min(this.maxDistance, this.distance));
} else if (this.camera.isOrthographicCamera) {
this.camera.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, this.camera.zoom / factor));
this.camera.updateProjectionMatrix();
}
}
pan(deltaX, deltaY) {
const offset = new THREE.Vector3();
const te = this.camera.matrix.elements;
offset.set(
te[0] * deltaX + te[4] * deltaY,
te[1] * deltaX + te[5] * deltaY,
te[2] * deltaX + te[6] * deltaY
);
this.target.add(offset);
this.camera.position.add(offset);
}
}
在使用该控制类时,你需要传入相机对象和目标对象,并可以通过options参数来设置一些控制选项。例如:
复制代码
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const target = new THREE.Vector3(0, 0, 0);
const controls = new ThirdPersonControls(camera, target, {
distance: 10,
minDistance: 0.1,
maxDistance: 20,
minPolarAngle: 0,
maxPolarAngle: Math.PI,
minAzimuthAngle: -Infinity,
maxAzimuthAngle: Infinity,
rotateSpeed: 1,
zoomSpeed: 1,
panSpeed: 1,
enableRotate: true,
enableZoom: true,
enablePan: true,
keys: {
LEFT: 37,
UP: 38,
RIGHT: 39,
BOTTOM: 40
},
mouseButtons: {
ORBIT: THREE.MOUSE.LEFT,
ZOOM: THREE.MOUSE.MIDDLE,
PAN: THREE.MOUSE.RIGHT
}
});
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
这样,你就可以通过鼠标和键盘来控制相机的位置和视角了。