简单的基于threejs和BVH第一人称视角和第三人称视角控制器

渲染框架是基于THREE,碰撞检测是基于BVH。本来用的是three自带的octree结构做碰撞发现性能不太好

核心代码:


import * as THREE from 'three'
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
import { MeshBVH, MeshBVHHelper, StaticGeometryGenerator } from 'three-mesh-bvh';
import CameraControls from 'src/renderers/camera';
import { OrbitControls } from 'src/renderers/controls/OrbitControls'
import { Renderer } from 'src/renderers/Renderer';
class InputControls{pressKeys=new Set()releaseKeys=new Set()constructor() {this.mountEvents()}mountEvents(){window.addEventListener('keydown',this.handleKey)window.addEventListener('keyup',this.handleKey)}unmountEvents(){window.removeEventListener('keydown',this.handleKey)window.removeEventListener('keyup',this.handleKey)}isPressedKey(key:string){return this.pressKeys.has(key)}isReleaseKey(key:string){if(this.pressKeys.has(key)&&!this.releaseKeys.has(key)){this.releaseKeys.add(key)return true}return false}handleKey=(e:KeyboardEvent)=>{const type=e.typeconst key=e.key.toLowerCase()if(type==='keydown'){if(!this.pressKeys.has(key)){this.pressKeys.add(key)}}else{if(this.pressKeys.has(key)){this.releaseKeys.delete(key)this.pressKeys.delete(key)}}}
}export class CharacterPersonCamera{keys=new Set()player:THREE.Meshcollider?:THREE.MeshcolliderBox2:THREE.Box2=new THREE.Box2()colliderBox:THREE.Box3=new THREE.Box3()input:InputControlsspeed=100speedRatio=1 // 速率gravity=298 // 重力速度enableGravity=false // 是否启用重力_enableFirstPerson=false// 是否启用第一视角// 当前速度和位移playerVelocity=new THREE.Vector3()// 累积移动accumulateMovement=new THREE.Vector3()deltaPosition=new THREE.Vector3()tempPlayerPosition=new THREE.Vector2()tempVector=new THREE.Vector3()tempVector2=new THREE.Vector3()tempDirection=new THREE.Vector3()tempBox=new THREE.Box3()tempSegment=new THREE.Line3()tempMat=new THREE.Matrix4()playerIsOnGround=false // 是否在地面enable=true // 是否启用cameraControls?:CameraControlsorbitControls?:OrbitControlsupVector = new THREE.Vector3( 0, 1, 0 );colliderBoxDistance=Infinityconstructor(public context:Renderer) {this.input=new InputControls()this.player=new THREE.Mesh(new RoundedBoxGeometry(0.5,1,0.5,10,1),new THREE.MeshBasicMaterial({color:0xff0000}))//  this.player=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),generateCubeFaceTexture(512,512))this.player.userData={capsuleInfo:{radius: 0.5,segment: new THREE.Line3( new THREE.Vector3(), new THREE.Vector3( 0,0, 0.0 ) )}}this.player.position.setFromMatrixPosition(this.camera.matrixWorld)// this.root.add(this.player)}get renderer(){return this.context.renderer}get root(){return this.context.scene}get camera(){return this.context.camera}get finalSpeed(){return this.speed*this.speedRatio}get playerDirection(){return this.player.quaternion}get isAllowFalling(){this.tempPlayerPosition.set(this.player.position.x,this.player.position.z)// 是否可以下落,并且当前视角位置在碰撞检测体的z轴平面上.return this.enableGravity&&this.colliderBox2.containsPoint(this.tempPlayerPosition)}get minDropY(){return this.colliderBox.min.y}set enableFirstPerson(v){if(v!==this._enableFirstPerson){this._enableFirstPerson=v;if(!v&&this.orbitControls){this.camera.position.sub( this.orbitControls.target).normalize().multiplyScalar( 10 ).add( this.orbitControls.target); }else if(!v&&this.cameraControls){this.cameraControls.getTarget(this.tempVector)this.camera.position.sub(this.cameraControls.getTarget(this.tempVector) ).normalize().multiplyScalar( 10 ).add(this.cameraControls.getTarget(this.tempVector)); }}}get enableFirstPerson(){return this._enableFirstPerson}setupOrbitControls(){this.orbitControls=new OrbitControls(this.camera,this.renderer.domElement)this.initControlsMaxLimit()// this.orbitControls.enableDamping=true// this.orbitControls.enablePan=true// this.orbitControls.enableZoom=true// this.orbitControls.rotateSpeed=1// this.orbitControls.minAzimuthAngle=-Math.PI// this.orbitControls.maxAzimuthAngle=Math.PI}setColliderModel(colliderModel:THREE.Object3D){const staticGenerator = new StaticGeometryGenerator( colliderModel );staticGenerator.attributes = [ 'position' ];const mergedGeometry = staticGenerator.generate();mergedGeometry.boundsTree = new MeshBVH( mergedGeometry );this.collider = new THREE.Mesh( mergedGeometry );mergedGeometry.boundsTree.getBoundingBox(this.colliderBox)this.colliderBox2.min.set(this.colliderBox.min.x,this.colliderBox.min.z)this.colliderBox2.max.set(this.colliderBox.max.x,this.colliderBox.max.z)this.colliderBoxDistance=this.colliderBox.getSize(this.tempVector).length()*1.5// const visualizer = new MeshBVHHelper(this.collider,1000 );//this.root.add( visualizer );}updateControls(delta:number){const finalSpeed=this.finalSpeed*deltaif(this.orbitControls){const angle = this.orbitControls.getAzimuthalAngle();const tempVector=this.tempVectorconst upVector=this.upVectorif(this.input.isPressedKey('w')){tempVector.set( 0, 0, - 1 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('s')){tempVector.set( 0, 0, 1 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('a')){tempVector.set( -1, 0, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('d')){tempVector.set( 1, 0, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('q')){tempVector.set( 0, 1, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('e')){tempVector.set( 0, -1, 0 ).applyAxisAngle( upVector, angle ).multiplyScalar( finalSpeed );this.playerVelocity.add(this.tempVector)}}else{if(this.input.isPressedKey('w')){this.tempVector.set(0,0,1).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('s')){this.tempVector.set(0,0,1).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('a')){this.tempVector.set(1,0,0).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('d')){this.tempVector.set(1,0,0).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('q')){this.tempVector.set(0,1,0).applyQuaternion(this.playerDirection).multiplyScalar(finalSpeed)this.playerVelocity.add(this.tempVector)}if(this.input.isPressedKey('e')){this.tempVector.set(0,1,0).applyQuaternion(this.playerDirection).multiplyScalar(-finalSpeed)this.playerVelocity.add(this.tempVector)}}}updatePlayer(delta:number){// 增加阻尼const damping=0.9if (this.enableGravity&&this.isAllowFalling&&!this.playerIsOnGround) {this.playerVelocity.y -= delta * this.gravity;}this.playerVelocity.multiplyScalar(damping)this.deltaPosition.copy(this.playerVelocity).multiplyScalar(delta)this.accumulateMovement.add(this.deltaPosition)// 应用移动this.player.position.add(this.deltaPosition)// 如果重力模式,就应用物理碰撞if(this.enableGravity){this.updateCollider(delta)}if(this.orbitControls){// this.camera.translateZ(2)this.camera.position.sub(this.orbitControls.target);this.orbitControls.target.copy(this.player.position);this.camera.position.add(this.player.position);}else if(this.cameraControls){this.cameraControls.getTarget(this.tempVector,true)this.camera.position.sub(this.tempVector);this.cameraControls.setTarget(this.player.position.x,this.player.position.y,this.player.position.z,false);this.camera.position.add(this.player.position);}else{this.camera.position.copy(this.player.position)this.camera.translateZ(2)}}box3Helper?:THREE.Box3HelpervisibleBox3Helper(box:THREE.Box3){if(!this.box3Helper){this.box3Helper=new THREE.Box3Helper(box,0xff0000)this.root.add(this.box3Helper)}else{this.box3Helper.box.copy(box)}}updateCollider(delta:number){const collider=this.collider!;const player=this.playerconst boundsTree=collider.geometry.boundsTree as MeshBVHconst tempBox=this.tempBoxconst tempSegment=this.tempSegmentconst tempMat=this.tempMatconst tempVector=this.tempVectorconst tempVector2=this.tempVector2;const playerVelocity=this.playerVelocityplayer.updateMatrixWorld();//  根据碰撞调整玩家位置const capsuleInfo = player.userData.capsuleInfo;tempBox.makeEmpty();tempMat.copy( collider.matrixWorld ).invert();tempSegment.copy( capsuleInfo.segment );//获取胶囊在碰撞器局部空间中的位置tempSegment.start.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );tempSegment.end.applyMatrix4( player.matrixWorld ).applyMatrix4( tempMat );// 获取胶囊的轴对齐边界框tempBox.expandByPoint( tempSegment.start );tempBox.expandByPoint( tempSegment.end );tempBox.min.addScalar( - capsuleInfo.radius );tempBox.max.addScalar( capsuleInfo.radius );//  this.visibleBox3Helper(tempBox)boundsTree.shapecast( {intersectsBounds: box => box.intersectsBox( tempBox ),intersectsTriangle: tri => {// 检查三角形是否与胶囊相交并调整// 胶囊位置(如果是)。const triPoint = tempVector;const capsulePoint =tempVector2;const distance = tri.closestPointToSegment( tempSegment, triPoint, capsulePoint );if ( distance < capsuleInfo.radius ) {const depth = capsuleInfo.radius - distance;const direction = capsulePoint.sub( triPoint ).normalize();tempSegment.start.addScaledVector( direction, depth );tempSegment.end.addScaledVector( direction, depth );}}} );// 检查后得到胶囊碰撞器在世界空间中的调整位置// 三角形碰撞并移动它。 CapsuleInfo.segment.start 假设为// 玩家模型的起源。const newPosition = tempVector;newPosition.copy( tempSegment.start ).applyMatrix4( collider.matrixWorld );// 检查碰撞体移动了多少const deltaVector = tempVector2;deltaVector.subVectors( newPosition, player.position );// 如果玩家主要是垂直调整的,我们假设它位于我们应该考虑地面的地方this.playerIsOnGround = deltaVector.y > Math.abs( delta * 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 ) {// console.log('this.playerIsOnGround',deltaVector)deltaVector.normalize();playerVelocity.addScaledVector( deltaVector, - deltaVector.dot( playerVelocity ) );} else {playerVelocity.set( 0, 0, 0 );}// 如果玩家跌落到水平线以下太远,则将其位置重置为开始位置if ( player.position.y < this.minDropY ) {this.resetPlayerPosition();}}resetPlayerPosition(){this.playerVelocity.y=0this.player.position.y=this.minDropY}initControlsMaxLimit(){const controls=this.orbitControls||this.cameraControlsif(controls){if(this.enableFirstPerson){controls.maxPolarAngle = Math.PI;controls.minDistance = 1e-4;controls.maxDistance = 1e-4;}else{controls.maxPolarAngle = Math.PI / 2;controls.minDistance = 1;controls.maxDistance = this.colliderBoxDistance}}}onUpdate(delta:number){if(!this.enable){return}this.player.quaternion.copy(this.camera.quaternion)// this.player.quaternion.x=0// this.player.quaternion.z=0// this.player.quaternion.normalize()let controls:any;if(this.orbitControls){controls=this.orbitControls}else if(this.cameraControls){controls=this.cameraControls as any}this.initControlsMaxLimit()const MAX_STEP=5;for(let i=0;i<MAX_STEP;i++){const d=delta/MAX_STEP;this.updateControls(d)this.updatePlayer(d)}if(controls){controls.update(delta)}}dispose(){if(this.orbitControls){this.orbitControls.dispose()}this.input.unmountEvents()}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/24910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机系统基础笔记(12)——控制

前言 在持续输出ing 一、条件码 1.处理器状态&#xff08;x86-64&#xff0c;部分的&#xff09; 当前程序的执行信息 ◼ 临时数据 ◼ 运行时栈的位置&#xff08;栈顶&#xff09; ◼ 当前代码控制点的位置&#xff08;即将要执行的指令地址&#xff09; ◼ 最近一次指令执…

【C++关键字】auto的使用(C++11)

auto的使用&#xff08;C11&#xff09; auto关键字auto的使用细则auto使用场景 随着程序的复杂化&#xff0c;程序中用到的类型也越来越复杂化&#xff0c;经常体现在&#xff1a; 1.类型难以拼写 2.含义不明确导致容易出错 在C语言阶段处理这类问题的方法&#xff0c;可以使…

拉格朗日乘子将不等式约束转化为等式约束例子

拉格朗日乘子将不等式约束转化为等式约束例子 在优化问题中,常常需要将不等式约束转化为等式约束。使用拉格朗日乘子法,可以通过引入松弛变量将不等式约束转换为等式约束,然后构造拉格朗日函数进行求解。 拉格朗日乘子法简介 拉格朗日乘子法是求解带约束优化问题的一种方…

Ansible——fetch模块

目录 参数 示例1&#xff1a;最基本的用法 示例2&#xff1a;指定目标目录和主机名子目录 示例3&#xff1a;flat 参数设置为 yes 示例4&#xff1a;处理源文件不存在的情况 示例5&#xff1a;验证文件校验和 示例 Playbook 1. 拉取远程主机上的 syslog 文件 2. 直接…

Redis集群和高可用性:保障Redis服务的稳定性

I. 引言 A. 对Redis的简单介绍和其在现代Web应用中的角色 Redis(REmote DIctionary Server)是一个开源的、基于内存的键值数据库,它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。由于Redis的高性能和丰富的数据类型,使其在现代Web应用中广泛使用。例如,它…

【吊打面试官系列-Mysql面试题】BLOB 和 TEXT 有什么区别 ?

大家好&#xff0c;我是锋哥。今天分享关于 【BLOB 和 TEXT 有什么区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; BLOB 和 TEXT 有什么区别 &#xff1f; BLOB 是一个二进制对象&#xff0c;可以容纳可变数量的数据。TEXT 是一个不区分大小写的 BLOB。 1…

【调整堆】(C++ 代码实现 注释详解)

自定义结构体&#xff1a; #define sz 105 typedef struct node{int length;int l[sz]; }SqList; 调整堆的函数&#xff1a; HeapAdjust函数思路说明&#xff1a; //目标&#xff1a;将以s为根的子树调整为大根堆 //具体操作&#xff1a;将路径上比s大的都往上移动,s往下移…

gRPC(狂神说)

gRPC&#xff08;狂神说&#xff09; 视频地址&#xff1a;【狂神说】gRPC最新超详细版教程通俗易懂 | Go语言全栈教程_哔哩哔哩_bilibili 1、gRPC介绍 单体架构 一旦某个服务宕机&#xff0c;会引起整个应用不可用&#xff0c;隔离性差只能整体应用进行伸缩&#xff0c;浪…

【C++ STL】模拟实现 string

标题&#xff1a;【C :: STL】手撕 STL _string 水墨不写bug &#xff08;图片来源于网络&#xff09; C标准模板库&#xff08;STL&#xff09;中的string是一个可变长的字符序列&#xff0c;它提供了一系列操作字符串的方法和功能。 本篇文章&#xff0c;我们将模拟实现STL的…

ipables防火墙

一、Linux防火墙基础 Linux 的防火墙体系主要工作在网络层&#xff0c;针对 TCP/IP 数据包实施过滤和限制&#xff0c;属于典 型的包过滤防火墙&#xff08;或称为网络层防火墙&#xff09;。Linux 系统的防火墙体系基于内核编码实现&#xff0c; 具有非常稳定的性能和高效率&…

VB7/64位VB6开发工具office插件开发-twinbasic

全新的VB7&#xff0c;twinbasic&#xff0c;支持64位开发&#xff0c;支持EXCEL插件开发&#xff0c;老外连续3年闭关修练终成正果 官方最新版下载&#xff1a;https://github.com/twinbasic/twinbasic/releases 汉化工具用法&#xff1a;把工具和Lang_Tool目录复制到Twinbasi…

SAP PP学习笔记18 - MTO(Make-to-Order):按订单生产(受注生産) 的策略 20,50,74

前面几章讲了 MTS&#xff08;Make-to-Stock&#xff09;按库存生产的策略&#xff08;10&#xff0c;11&#xff0c;30&#xff0c;40&#xff0c;70&#xff09;。 SAP PP学习笔记14 - MTS&#xff08;Make-to-Stock) 按库存生产&#xff08;策略10&#xff09;&#xff0c;…

ChatTTS 开源文本转语音模型本地部署、API使用和搭建WebUI界面(建议收藏)

ChatTTS&#xff08;Chat Text To Speech&#xff09;是专为对话场景设计的文本生成语音(TTS)模型&#xff0c;特别适用于大型语言模型(LLM)助手的对话任务&#xff0c;以及诸如对话式音频和视频介绍等应用。它支持中文和英文&#xff0c;还可以穿插笑声、说话间的停顿、以及语…

Web前端转行简历:打造一份引人注目的职业转型之作

Web前端转行简历&#xff1a;打造一份引人注目的职业转型之作 在当今日新月异的科技时代&#xff0c;许多专业人士都面临着职业转型的挑战。对于Web前端开发者而言&#xff0c;转行简历的制作尤为关键&#xff0c;它不仅是展示自身技能和经验的重要载体&#xff0c;更是打开新…

力扣189. 轮转数组

给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1:输入: nums [1,2,3,4,5,6,7], k 3 输出:[5,6,7,1,2,3,4] 解释: 向右轮转 1 步:[7,1,2,3,4,5,6] 向右轮转 2 步:[6,7,1,2,3,4,5] 向右轮转 3 步:[5,6,7,1,2,…

计算机网络ppt和课后题总结(下)

常用端口总结 计算机网络中&#xff0c;端口是TCP/IP协议的一部分&#xff0c;用于标识运行在同一台计算机上的不同服务。端口号是一个16位的数字&#xff0c;范围从0到65535。通常&#xff0c;0到1023的端口被称为“熟知端口”或“系统端口”&#xff0c;它们被保留给一些标准…

4句话明白虚拟机和容器的区别

一、虚拟机VM的组成 服务器-HostOS-虚拟化层-GustOS-libs-App 1、此时存在几个问题&#xff1a; 1、资源消耗大 2、扩展APP副本时到重复资源浪费&#xff08;GustOS-libs&#xff09; 3、当你开发在本地但要移植到云端&#xff0c;就会出现各种兼容性问题。 4、很难集成到DevOp…

【Spring Cloud】Eureka详细介绍及底层原理解析

目录 底层原理详解 1. 服务注册与发现 2. 心跳机制 3. 服务剔除与自我保护机制 Eureka Server 核心组件 Eureka Client 核心组件 使用场景 结语 Eureka 是 Netflix 开源的一款服务发现框架&#xff0c;用于构建分布式系统中的服务注册与发现。 它包含两个核心组件&…

基于百度接口的实时流式语音识别系统

目录 基于百度接口的实时流式语音识别系统 1. 简介 2. 需求分析 3. 系统架构 4. 模块设计 4.1 音频输入模块 4.2 WebSocket通信模块 4.3 音频处理模块 4.4 结果处理模块 5. 接口设计 5.1 WebSocket接口 5.2 音频输入接口 6. 流程图 程序说明文档 1. 安装依赖 2.…

Playwright 这个强大的自动化测试工具

什么是Playwright库&#xff1f; Playwright是一个强大的自动化工具&#xff0c;用于编写和执行浏览器自动化脚本&#xff0c;它支持多种浏览器&#xff0c;包括 Chrome、Firefox 和 Safari&#xff0c;并提供了简单而灵活的 API&#xff0c;使得自动化测试和与网页的交互变得…