733 lines
29 KiB
TypeScript
733 lines
29 KiB
TypeScript
/**
|
||
* @author ErSan
|
||
* @email mlt131220@163.com
|
||
* @date 2024/08/28
|
||
* @description 漫游类,使用BVH检测碰撞,人物模型必须包含动画:Enter,Idle, Walking, WalkingBackward,Jumping
|
||
*/
|
||
import * as THREE from 'three';
|
||
import CameraControls from 'camera-controls';
|
||
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
||
import {RoundedBoxGeometry} from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
|
||
import {GenerateMeshBVHWorker} from '@/workers/bvh/GenerateMeshBVHWorker.js';
|
||
import {useDispatchSignal} from "@/hooks";
|
||
import {getMeshByInstancedMesh} from "@/utils";
|
||
import {RoamingStatus} from "./RoamingStatus";
|
||
import Loader from "@/core/loader/Loader";
|
||
import App from "@/core/app/App";
|
||
import Viewer from "@/core/viewer/Viewer";
|
||
import MergeGeometriesWorker from "@/workers/mergeGeometries.worker.ts?worker&url";
|
||
import {TYPED_ARRAYS} from "@/constant";
|
||
|
||
let keyDownFn, keyUpFn;
|
||
|
||
class Roaming {
|
||
private viewer: Viewer;
|
||
private controls: CameraControls;
|
||
|
||
group: THREE.Group;
|
||
private collider: THREE.Mesh | undefined; // 碰撞器
|
||
private player: THREE.Mesh | undefined; // 碰撞胶囊体
|
||
person: THREE.Group | undefined; // 人物
|
||
|
||
private playerIsOnGround = true;
|
||
private playerVelocity = new THREE.Vector3();
|
||
private gravity = -20; // 重力
|
||
private playerSpeed = 2.2; // 人物移动速度
|
||
playerInitPos = new THREE.Vector3(0, 0, 0); // 人物初始位置
|
||
private firstPerson = true; // 是否第一人称
|
||
|
||
// 按键监听
|
||
private fwdPressed = false;
|
||
private bkdPressed = false;
|
||
private lftPressed = false;
|
||
private rgtPressed = false;
|
||
|
||
private upVector = new THREE.Vector3(0, 1, 0);
|
||
private tempVector = new THREE.Vector3();
|
||
private tempVector2 = new THREE.Vector3();
|
||
private tempBox = new THREE.Box3();
|
||
private tempMat = new THREE.Matrix4();
|
||
private tempSegment = new THREE.Line3();
|
||
|
||
public isRoaming = false; // 是否在漫游
|
||
mergeWorker: Worker;
|
||
private generateMeshBVHWorker: GenerateMeshBVHWorker;
|
||
private personStatus: RoamingStatus | null = null;
|
||
|
||
constructor(viewer: Viewer) {
|
||
this.viewer = viewer;
|
||
this.controls = viewer.modules.controls;
|
||
|
||
keyDownFn = this.keyDown.bind(this);
|
||
window.addEventListener('keydown', keyDownFn);
|
||
|
||
keyUpFn = this.keyUp.bind(this);
|
||
window.addEventListener('keyup', keyUpFn);
|
||
|
||
this.group = new THREE.Group();
|
||
this.group.name = "es-3d-roaming-group";
|
||
this.group.visible = false;
|
||
this.group.ignore = true;
|
||
|
||
this.mergeWorker = new Worker(MergeGeometriesWorker, {type: 'module'});
|
||
this.generateMeshBVHWorker = new GenerateMeshBVHWorker();
|
||
|
||
this.addPlayer();
|
||
}
|
||
|
||
keyDown(e: KeyboardEvent) {
|
||
if (!this.isRoaming || e.repeat) return;
|
||
|
||
switch (e.code) {
|
||
case 'KeyW':
|
||
this.fwdPressed = true;
|
||
this.personStatus?.setStatus("w", true);
|
||
break;
|
||
case 'KeyS':
|
||
this.bkdPressed = true;
|
||
this.personStatus?.setStatus("s", true);
|
||
break;
|
||
case 'KeyD':
|
||
this.rgtPressed = true;
|
||
this.personStatus?.setStatus("d", true);
|
||
break;
|
||
case 'KeyA':
|
||
this.lftPressed = true;
|
||
this.personStatus?.setStatus("a", true);
|
||
break;
|
||
case 'Space':
|
||
if (this.personStatus?.keyDownStatus.space) return;
|
||
|
||
if (this.playerIsOnGround) {
|
||
// 跳跃动画有30FPS准备动作
|
||
setTimeout(() => {
|
||
this.playerVelocity.y = 10.0;
|
||
this.playerIsOnGround = false;
|
||
}, (30 / App.FPS) * 1000)
|
||
}
|
||
this.personStatus?.setStatus("space", true);
|
||
break;
|
||
case "ShiftLeft":
|
||
case "ShiftRight":
|
||
if (this.personStatus?.isWalkingForward) {
|
||
this.playerSpeed = 6;
|
||
this.personStatus?.setStatus("shift", true);
|
||
}
|
||
break;
|
||
case 'KeyV': // 切换第一/第三人称视角
|
||
this.firstPerson = !this.firstPerson;
|
||
|
||
if (this.firstPerson) { //人称切换
|
||
// 第一人称
|
||
this.controls.maxPolarAngle = Math.PI / 2;
|
||
this.controls.minDistance = 0.8;
|
||
this.controls.maxDistance = 0.8;
|
||
this.controls.distance = 0.8;
|
||
} else {
|
||
this.controls.maxPolarAngle = Math.PI / 2;
|
||
this.controls.minDistance = 6;
|
||
this.controls.maxDistance = 6;
|
||
this.controls.distance = 6;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
keyUp(e: KeyboardEvent) {
|
||
if (!this.isRoaming || e.repeat) return;
|
||
|
||
switch (e.code) {
|
||
case 'KeyW':
|
||
this.personStatus?.setStatus("w", false);
|
||
this.fwdPressed = false;
|
||
break;
|
||
case 'KeyS':
|
||
this.personStatus?.setStatus("s", false);
|
||
this.bkdPressed = false;
|
||
break;
|
||
case 'KeyD':
|
||
this.personStatus?.setStatus("d", false);
|
||
this.rgtPressed = false;
|
||
break;
|
||
case 'KeyA':
|
||
this.personStatus?.setStatus("a", false);
|
||
this.lftPressed = false;
|
||
break;
|
||
case "ShiftLeft":
|
||
case "ShiftRight":
|
||
this.playerSpeed = 3;
|
||
this.personStatus?.setStatus("shift", false);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 添加漫游所需人物模型
|
||
addPlayer(){
|
||
// 几何圆柱体 用于碰撞检测
|
||
const cylinder = new THREE.Mesh(
|
||
new RoundedBoxGeometry(0.5, 1.7, 0.5, 10, 0.5),
|
||
new THREE.MeshStandardMaterial()
|
||
)
|
||
cylinder.geometry.translate(0, -0.6, 0);
|
||
// @ts-ignore
|
||
cylinder.capsuleInfo = {
|
||
radius: 0.4,
|
||
segment: new THREE.Line3(new THREE.Vector3(), new THREE.Vector3(0, -1.0, 0.0))
|
||
}
|
||
cylinder.name = 'es-3d-roaming-cylinder';
|
||
cylinder.visible = false;
|
||
|
||
this.player = cylinder;
|
||
this.group.add(cylinder);
|
||
|
||
this.reloadPerson();
|
||
}
|
||
|
||
/**
|
||
* 重置漫游人物模型
|
||
*/
|
||
async reloadPerson() {
|
||
// 加载人物模型glb
|
||
const loader = await Loader.createGLTFLoader();
|
||
|
||
const done = (blob) => {
|
||
// 加载人物模型Blob
|
||
loader.loadAsync(URL.createObjectURL(blob)).then(result => {
|
||
const person = result.scene as THREE.Group;
|
||
person.name = "es-3d-roaming-player";
|
||
|
||
if(this.person){
|
||
person.matrix.copy(this.person.matrix);
|
||
person.matrixWorld.copy(this.person.matrixWorld);
|
||
|
||
this.person.removeFromParent();
|
||
}
|
||
|
||
this.person = person;
|
||
this.group.add(person);
|
||
|
||
// 漫游人物动画状态机
|
||
if(this.personStatus){
|
||
this.personStatus.dispose();
|
||
}
|
||
this.personStatus = new RoamingStatus(person, result.animations);
|
||
});
|
||
}
|
||
|
||
// 从本地DB读取人物模型
|
||
const playerConfig= App.config.getKey("roamingCharacter")
|
||
App.storage.getModel(`player-${playerConfig}`).then((file: Blob | unknown) => {
|
||
if (!file) {
|
||
const playerGlbUrl = new URL(`${import.meta.env.BASE_URL}resource/model/${playerConfig}.glb`, import.meta.url).href;
|
||
// 加载默认人物模型
|
||
fetch(playerGlbUrl).then(res => res.blob()).then(blob => {
|
||
App.storage.setModel(`player-${playerConfig}`, blob)
|
||
done(blob);
|
||
})
|
||
} else {
|
||
done(file);
|
||
}
|
||
})
|
||
}
|
||
|
||
// 生成碰撞器环境
|
||
generateColliderEnvironment() {
|
||
let mergedGeometry:any;
|
||
|
||
// TODO:20251003 - environment组好像没有存在的意义?运行两个月无误后删除
|
||
//const environment = new THREE.Group();
|
||
//environment.name = "astral-3d-roaming-collider-environment";
|
||
|
||
const generateBVH = () => {
|
||
return new Promise(resolve => {
|
||
this.generateMeshBVHWorker.generate(mergedGeometry).then(bvh => {
|
||
// @ts-ignore
|
||
mergedGeometry.boundsTree = bvh;
|
||
|
||
this.collider = new THREE.Mesh(mergedGeometry);
|
||
// @ts-ignore
|
||
this.collider.material.wireframe = false;
|
||
this.collider.name = "astral-3d-roaming-collider";
|
||
this.collider.visible = false;
|
||
|
||
// @ts-ignore
|
||
this.group.add(this.collider);
|
||
|
||
resolve("");
|
||
|
||
this.generateMeshBVHWorker.dispose();
|
||
});
|
||
|
||
//environment.visible = false;
|
||
//this.group.add(environment);
|
||
this.viewer.scene.add(this.group);
|
||
})
|
||
}
|
||
|
||
const generateMergedGeometry = () => {
|
||
return new Promise((resolve,reject) => {
|
||
const cloneGeom = (me) => {
|
||
const src = me.geometry;
|
||
// 检查对应属性是否存在
|
||
if (!src || !src.attributes || !src.attributes.position) return;
|
||
|
||
// 先得到“无索引”的Geometry,toNonIndexed不会改变原Geometry
|
||
const noIndexGeom = src.index ? src.toNonIndexed() : src.clone();
|
||
|
||
let posAttr = noIndexGeom.attributes.position;
|
||
let outGeom = new THREE.BufferGeometry();
|
||
|
||
// 交错缓冲区单独处理取出
|
||
if (posAttr.isInterleavedBufferAttribute) {
|
||
const { data, itemSize, count, normalized, offset } = posAttr; // data: InterleavedBuffer
|
||
const { array, stride } = data; // stride: 步幅
|
||
|
||
const out = new TYPED_ARRAYS[array.constructor.name || 'Float32Array'](count * itemSize);
|
||
for (let i = 0; i < count; i++) {
|
||
const base = i * stride + offset;
|
||
// 假设 itemSize 通常为 3(x,y,z),但这里写通用拷贝
|
||
for (let k = 0; k < itemSize; k++) {
|
||
out[i * itemSize + k] = array[base + k];
|
||
}
|
||
}
|
||
posAttr = new THREE.BufferAttribute(out, itemSize, normalized);
|
||
} else {
|
||
// 普通 BufferAttribute,直接克隆即可
|
||
posAttr = posAttr.clone();
|
||
}
|
||
|
||
// 仅保留position即可
|
||
outGeom.setAttribute('position', posAttr);
|
||
// 手动纠正有些模型没有顶点索引的问题
|
||
outGeom.index = null;
|
||
|
||
// 应用世界矩阵
|
||
outGeom.applyMatrix4(me.matrixWorld);
|
||
|
||
this.mergeWorker.postMessage({
|
||
type: "push",
|
||
// geometry: outGeom
|
||
// 合并在容差范围内的具有相似属性的顶点
|
||
geometry: BufferGeometryUtils.mergeVertices(outGeom)
|
||
})
|
||
}
|
||
|
||
this.viewer.scene.traverseByCondition(c => {
|
||
// requestIdleCallback(()=>{
|
||
// 只合并网格
|
||
if (c.geometry) {
|
||
// @ts-ignore
|
||
if (!c.isInstancedMesh) {
|
||
cloneGeom(c);
|
||
} else {
|
||
const meshes = getMeshByInstancedMesh(c as THREE.InstancedMesh);
|
||
meshes.forEach((m: THREE.Mesh) => {
|
||
cloneGeom(m);
|
||
});
|
||
}
|
||
}
|
||
// })
|
||
}, (c) => !c.ignore && !c.isTilesGroup && !c.isTiles && c.visible)
|
||
|
||
// requestIdleCallback(()=>{
|
||
this.mergeWorker.postMessage({
|
||
type: "merge"
|
||
})
|
||
// })
|
||
|
||
this.mergeWorker.onmessage = (event) => {
|
||
if(event.data.type === "error") {
|
||
// 有可能是纯3DTiles场景
|
||
if(this.viewer.modules.tilesManage.tilesMap.size === 0){
|
||
reject(event.data.message);
|
||
}else{
|
||
resolve("");
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (!event.data.geometry) return;
|
||
|
||
mergedGeometry = event.data.geometry;
|
||
mergedGeometry.__proto__ = THREE.BufferGeometry.prototype;
|
||
mergedGeometry.index && (mergedGeometry.index.__proto__ = THREE.BufferAttribute.prototype);
|
||
mergedGeometry.attributes.position.__proto__ = THREE.BufferAttribute.prototype;
|
||
mergedGeometry.attributes.normal && (mergedGeometry.attributes.normal.__proto__ = THREE.BufferAttribute.prototype);
|
||
|
||
// 删除uv属性
|
||
if (mergedGeometry.attributes.uv) {
|
||
mergedGeometry.deleteAttribute("uv");
|
||
}
|
||
|
||
// const newMesh = new THREE.Mesh(mergedGeometry, new THREE.MeshBasicMaterial());
|
||
//const newMesh = new THREE.Mesh(BufferGeometryUtils.mergeVertices(mergedGeometry), new THREE.MeshBasicMaterial());
|
||
//newMesh.visible = false;
|
||
|
||
//environment.add(newMesh);
|
||
|
||
generateBVH().then(() => {
|
||
resolve("");
|
||
});
|
||
|
||
// 关闭 worker
|
||
this.mergeWorker.terminate();
|
||
}
|
||
})
|
||
}
|
||
|
||
return generateMergedGeometry();
|
||
}
|
||
|
||
// 重置人物位置
|
||
resetPlayer() {
|
||
const player = this.player as THREE.Mesh;
|
||
|
||
this.playerVelocity.set(0, 0, 0);
|
||
player.position.copy(this.playerInitPos);
|
||
|
||
// 播放模型进入动画
|
||
this.personStatus?.init();
|
||
|
||
const _target = new THREE.Vector3();
|
||
this.controls.getTarget(_target);
|
||
this.viewer.camera.position.sub(_target);
|
||
this.controls.setTarget(player.position.x, player.position.y + 2, player.position.z, false);
|
||
this.controls.distance = this.firstPerson ? 0.8 : 6;
|
||
this.viewer.camera.position.add(player.position);
|
||
this.controls.update(0.016);
|
||
}
|
||
|
||
// 进入漫游
|
||
startRoaming() {
|
||
if (this.isRoaming) return;
|
||
|
||
this.group.visible = true;
|
||
|
||
this.viewer.computedSceneBox3();
|
||
|
||
this.resetPlayer();
|
||
|
||
this.isRoaming = true;
|
||
}
|
||
|
||
// 退出漫游
|
||
exitRoaming(lastRoadCameraPos = new THREE.Vector3(1, 1, 1), lastRoadCameraTarget = new THREE.Vector3()) {
|
||
this.group.visible = false;
|
||
|
||
lastRoadCameraPos && this.controls.setPosition(lastRoadCameraPos.x, lastRoadCameraPos.y, lastRoadCameraPos.z, true);
|
||
lastRoadCameraTarget && this.controls.setTarget(lastRoadCameraTarget.x, lastRoadCameraTarget.y, lastRoadCameraTarget.z, true);
|
||
|
||
this.controls.maxPolarAngle = Math.PI;
|
||
this.controls.minDistance = 0;
|
||
this.controls.maxDistance = Infinity;
|
||
|
||
this.controls.update(0.016);
|
||
this.isRoaming = false;
|
||
|
||
// 停用混合器上所有预定的动作
|
||
this.personStatus?.stopAllAction();
|
||
|
||
useDispatchSignal("sceneGraphChanged");
|
||
}
|
||
|
||
render(delta: number) {
|
||
if (!delta) return;
|
||
|
||
const player = this.player as THREE.Object3D;
|
||
|
||
// =========================
|
||
// 重力与竖直方向
|
||
// =========================
|
||
if (this.playerIsOnGround) {
|
||
this.playerVelocity.y = delta * this.gravity;
|
||
} else {
|
||
this.playerVelocity.y += delta * this.gravity;
|
||
}
|
||
player.position.addScaledVector(this.playerVelocity, delta);
|
||
|
||
// =========================
|
||
// 水平方向移动
|
||
// =========================
|
||
const angle = this.controls.azimuthAngle;
|
||
if (this.fwdPressed) {
|
||
this.tempVector.set(0, 0, -1).applyAxisAngle(this.upVector, angle);
|
||
player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
}
|
||
if (this.bkdPressed) {
|
||
this.tempVector.set(0, 0, 1).applyAxisAngle(this.upVector, angle);
|
||
player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
}
|
||
if (this.lftPressed) {
|
||
this.tempVector.set(-1, 0, 0).applyAxisAngle(this.upVector, angle);
|
||
player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
}
|
||
if (this.rgtPressed) {
|
||
this.tempVector.set(1, 0, 0).applyAxisAngle(this.upVector, angle);
|
||
player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
}
|
||
|
||
player.updateMatrixWorld();
|
||
|
||
// =========================
|
||
// 碰撞检测
|
||
// =========================
|
||
// @ts-ignore
|
||
const capsuleInfo = (player as any).capsuleInfo;
|
||
const worldSegStart = capsuleInfo.segment.start.clone().applyMatrix4(player.matrixWorld);
|
||
|
||
// 收集所有 collider mesh
|
||
const colliders: THREE.Mesh[] = [];
|
||
if (this.viewer.modules.tilesManage.mergeMesh) {
|
||
colliders.push(this.viewer.modules.tilesManage.mergeMesh);
|
||
}
|
||
if (this.collider) colliders.push(this.collider);
|
||
|
||
let chosenNewPositionWorld: THREE.Vector3 | null = null;
|
||
let maxOffsetLen = -Infinity;
|
||
|
||
for (const mesh of colliders) {
|
||
if (!mesh.geometry?.boundsTree) continue;
|
||
|
||
// 胶囊段:player.local → world → mesh.local
|
||
this.tempMat.copy(mesh.matrixWorld).invert();
|
||
this.tempSegment.copy(capsuleInfo.segment);
|
||
this.tempSegment.start.applyMatrix4(player.matrixWorld).applyMatrix4(this.tempMat);
|
||
this.tempSegment.end.applyMatrix4(player.matrixWorld).applyMatrix4(this.tempMat);
|
||
|
||
// AABB for shapecast
|
||
this.tempBox.makeEmpty();
|
||
this.tempBox.expandByPoint(this.tempSegment.start);
|
||
this.tempBox.expandByPoint(this.tempSegment.end);
|
||
this.tempBox.min.addScalar(-capsuleInfo.radius);
|
||
this.tempBox.max.addScalar(capsuleInfo.radius);
|
||
|
||
// 执行 shapecast,会修改 this.tempSegment
|
||
mesh.geometry.boundsTree.shapecast({
|
||
intersectsBounds: box => box.intersectsBox(this.tempBox),
|
||
intersectsTriangle: tri => {
|
||
// 检查三角形是否与胶囊相交,如果相交则调整胶囊位置。
|
||
const triPoint = this.tempVector;
|
||
const capsulePoint = this.tempVector2;
|
||
|
||
const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
|
||
if (distance < (this.player as THREE.Object3D).capsuleInfo.radius) {
|
||
const depth = (this.player as THREE.Object3D).capsuleInfo.radius - distance;
|
||
const direction = capsulePoint.sub(triPoint).normalize();
|
||
|
||
this.tempSegment.start.addScaledVector(direction, depth);
|
||
this.tempSegment.end.addScaledVector(direction, depth);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
});
|
||
|
||
// 结果变回 world 空间
|
||
const adjustedWorld = this.tempSegment.start.clone().applyMatrix4(mesh.matrixWorld);
|
||
|
||
const offsetLen = adjustedWorld.distanceTo(worldSegStart);
|
||
if (offsetLen > maxOffsetLen) {
|
||
maxOffsetLen = offsetLen;
|
||
chosenNewPositionWorld = adjustedWorld;
|
||
}
|
||
}
|
||
|
||
// 应用最终选择的位移
|
||
if (chosenNewPositionWorld) {
|
||
const deltaVector = this.tempVector2.subVectors(chosenNewPositionWorld, player.position);
|
||
|
||
this.playerIsOnGround = deltaVector.y > Math.abs(delta * this.playerVelocity.y * 0.25);
|
||
|
||
const offset = Math.max(0.0, deltaVector.length() - 1e-5);
|
||
deltaVector.normalize().multiplyScalar(offset);
|
||
|
||
player.position.add(deltaVector);
|
||
|
||
if (!this.playerIsOnGround) {
|
||
deltaVector.normalize();
|
||
this.playerVelocity.addScaledVector(deltaVector, -deltaVector.dot(this.playerVelocity));
|
||
} else {
|
||
this.playerVelocity.set(0, 0, 0);
|
||
}
|
||
}
|
||
|
||
// =========================
|
||
// 相机调整
|
||
// =========================
|
||
const v = new THREE.Vector3(player.position.x, player.position.y + 0.2, player.position.z);
|
||
const _target = new THREE.Vector3();
|
||
this.controls.getTarget(_target);
|
||
this.viewer.camera.position.sub(_target);
|
||
this.controls.setTarget(v.x, v.y, v.z, false);
|
||
this.controls.distance = this.firstPerson ? 0.8 : 6;
|
||
this.viewer.camera.position.add(v);
|
||
this.controls.polarAngle = Math.PI / 2;
|
||
|
||
// 人物模型位置跟随
|
||
if (this.person) {
|
||
this.person.position.set(player.position.x, player.position.y - 1.415, player.position.z);
|
||
}
|
||
|
||
// 跌落检测
|
||
if (this.viewer.sceneBox3 && (this.viewer.sceneBox3.min.y - player.position.y > 15)) {
|
||
requestAnimationFrame(() => this.resetPlayer());
|
||
}
|
||
|
||
// 动画状态更新
|
||
this.personStatus?.update(delta);
|
||
}
|
||
|
||
// render(delta: number) {
|
||
// if (!delta) return;
|
||
//
|
||
// const player = this.player as THREE.Object3D;
|
||
//
|
||
// if (this.playerIsOnGround) {
|
||
// this.playerVelocity.y = delta * this.gravity;
|
||
// } else {
|
||
// this.playerVelocity.y += delta * this.gravity;
|
||
// }
|
||
//
|
||
// // 人物竖直方向移动(跳跃)
|
||
// player.position.addScaledVector(this.playerVelocity, delta);
|
||
//
|
||
// /* 人物移动 */
|
||
// const angle = this.controls.azimuthAngle;
|
||
// if (this.fwdPressed) {
|
||
// this.tempVector.set(0, 0, -1).applyAxisAngle(this.upVector, angle);
|
||
// player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
// }
|
||
//
|
||
// if (this.bkdPressed) {
|
||
// this.tempVector.set(0, 0, 1).applyAxisAngle(this.upVector, angle);
|
||
// player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
// }
|
||
//
|
||
// if (this.lftPressed) {
|
||
// this.tempVector.set(-1, 0, 0).applyAxisAngle(this.upVector, angle);
|
||
// player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
// }
|
||
//
|
||
// if (this.rgtPressed) {
|
||
// this.tempVector.set(1, 0, 0).applyAxisAngle(this.upVector, angle);
|
||
// player.position.addScaledVector(this.tempVector, this.playerSpeed * delta);
|
||
// }
|
||
//
|
||
// player.updateMatrixWorld();
|
||
//
|
||
// // @ts-ignore 根据碰撞调整位置
|
||
// const capsuleInfo = player.capsuleInfo;
|
||
// this.tempBox.makeEmpty();
|
||
// this.tempMat.copy((this.collider as THREE.Mesh).matrixWorld).invert();
|
||
// this.tempSegment.copy(capsuleInfo.segment);
|
||
//
|
||
// // 获得胶囊在碰撞器的局部空间中的位置
|
||
// this.tempSegment.start.applyMatrix4(player.matrixWorld).applyMatrix4(this.tempMat);
|
||
// this.tempSegment.end.applyMatrix4(player.matrixWorld).applyMatrix4(this.tempMat);
|
||
//
|
||
// // 获取胶囊的轴对齐边界框
|
||
// this.tempBox.expandByPoint(this.tempSegment.start);
|
||
// this.tempBox.expandByPoint(this.tempSegment.end);
|
||
// this.tempBox.min.addScalar(-capsuleInfo.radius);
|
||
// this.tempBox.max.addScalar(capsuleInfo.radius);
|
||
//
|
||
// this.collider?.geometry.boundsTree?.shapecast({
|
||
// intersectsBounds: box => box.intersectsBox(this.tempBox),
|
||
// intersectsTriangle: tri => {
|
||
// // 检查三角形是否与胶囊相交,如果相交则调整胶囊位置。
|
||
// const triPoint = this.tempVector;
|
||
// const capsulePoint = this.tempVector2;
|
||
//
|
||
// const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
|
||
// if (distance < (this.player as THREE.Object3D).capsuleInfo.radius) {
|
||
// const depth = (this.player as THREE.Object3D).capsuleInfo.radius - distance;
|
||
// const direction = capsulePoint.sub(triPoint).normalize();
|
||
//
|
||
// this.tempSegment.start.addScaledVector(direction, depth);
|
||
// this.tempSegment.end.addScaledVector(direction, depth);
|
||
// }
|
||
//
|
||
// return false;
|
||
// }
|
||
// });
|
||
//
|
||
// if(this.viewer.modules.tilesManage.tilesMap.size > 0){
|
||
// this.viewer.modules.tilesManage.mergeMesh?.geometry.boundsTree?.shapecast({
|
||
// intersectsBounds: box => box.intersectsBox(this.tempBox),
|
||
// intersectsTriangle: tri => {
|
||
// // 检查三角形是否与胶囊相交,如果相交则调整胶囊位置。
|
||
// const triPoint = this.tempVector;
|
||
// const capsulePoint = this.tempVector2;
|
||
//
|
||
// const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint);
|
||
// if (distance < (this.player as THREE.Object3D).capsuleInfo.radius) {
|
||
// const depth = (this.player as THREE.Object3D).capsuleInfo.radius - distance;
|
||
// const direction = capsulePoint.sub(triPoint).normalize();
|
||
//
|
||
// this.tempSegment.start.addScaledVector(direction, depth);
|
||
// this.tempSegment.end.addScaledVector(direction, depth);
|
||
// }
|
||
//
|
||
// return false;
|
||
// }
|
||
// });
|
||
// }
|
||
//
|
||
// // 在检查三角形碰撞并移动后,获得胶囊碰撞器在世界空间中的调整位置。假设capsule.info.segment.start是玩家模型的原点。
|
||
// const newPosition = this.tempVector;
|
||
// newPosition.copy(this.tempSegment.start).applyMatrix4((this.collider as THREE.Mesh).matrixWorld);
|
||
//
|
||
// // 检查碰撞器移动了多少
|
||
// const deltaVector = this.tempVector2;
|
||
// deltaVector.subVectors(newPosition, player.position);
|
||
//
|
||
// // 如果玩家主要是垂直调整,我们就会认为它是在地面上
|
||
// this.playerIsOnGround = deltaVector.y > Math.abs(delta * this.playerVelocity.y * 0.25);
|
||
//
|
||
// const offset = Math.max(0.0, deltaVector.length() - 1e-5);
|
||
// deltaVector.normalize().multiplyScalar(offset);
|
||
//
|
||
// // 调整玩家模型的位置;
|
||
// player.position.add(deltaVector);
|
||
// if (!this.playerIsOnGround) {
|
||
// deltaVector.normalize();
|
||
// this.playerVelocity.addScaledVector(deltaVector, -deltaVector.dot(this.playerVelocity));
|
||
// } else {
|
||
// this.playerVelocity.set(0, 0, 0);
|
||
// }
|
||
//
|
||
// // 调整相机
|
||
// const v = new THREE.Vector3(player.position.x, player.position.y + 0.2, player.position.z);
|
||
// const _target = new THREE.Vector3();
|
||
// this.controls.getTarget(_target);
|
||
// this.viewer.camera.position.sub(_target);
|
||
// this.controls.setTarget(v.x, v.y, v.z, false);
|
||
// this.controls.distance = this.firstPerson ? 0.8 : 6;
|
||
// this.viewer.camera.position.add(v);
|
||
// this.controls.polarAngle = Math.PI / 2;
|
||
//
|
||
// if (this.person) {
|
||
// const p = player.position.clone();
|
||
// this.person.position.set(p.x, p.y - 1.415, p.z);
|
||
// }
|
||
//
|
||
// //如果玩家跌得太低,将他们的位置重置到起点
|
||
// if (this.viewer.sceneBox3 && (this.viewer.sceneBox3.min.y - player.position.y > 15)) {
|
||
// this.resetPlayer();
|
||
// }
|
||
//
|
||
// this.personStatus?.update(delta);
|
||
// }
|
||
|
||
dispose() {
|
||
window.removeEventListener('keydown', keyDownFn);
|
||
window.removeEventListener('keyup', keyUpFn);
|
||
|
||
App.removeObject(this.group);
|
||
|
||
this.personStatus?.dispose();
|
||
}
|
||
}
|
||
|
||
export {Roaming} |