简单的基于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;可以使…

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

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

【吊打面试官系列-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;还可以穿插笑声、说话间的停顿、以及语…

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

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

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

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

RHEL8/Centos8 install for PXE

PXE介绍 PXE&#xff08;Preboot Execution Environment&#xff09;是预引导执行环境的缩写。它是由Intel设计的&#xff0c;允许客户端计算机通过网络从服务器上加载操作系统镜像。PXE通常用于大规模部署操作系统&#xff0c;例如在企业或学校环境中。 PXE工作流程如下&…

【复现】含能量路由器的交直流混合配电网潮流计算

目录 1 主要内容 2 理论及模型 3 程序结果 4 下载链接 1 主要内容 程序复现《含能量路由器的交直流混合配电网潮流计算》&#xff0c;主要是对算例4.1进行建模分析&#xff0c;理论和方法按照文献所述。能量路由器&#xff08;ER&#xff09;作为新兴的电力元器件&#xff…

Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

FuTalk设计周刊-Vol.040

&#x1f525;AI漫谈 热点捕手 1、零代码定制游戏NPC&#xff0c;百川智能发布角色大模型 百川智能此次推出了“角色创建平台搜索增强知识库”的定制化解决方案。通过这一方案&#xff0c;游戏厂商无需编写任何代码&#xff0c;只需通过简单的文字描述&#xff0c;便可以快速…

IT人的拖延——都是“分心”惹的祸?

典型表现 我们说到拖延的原因有很多&#xff0c;还有一个原因是因为“分心太多“造成的&#xff0c;分心太多的拖延大致上有以下表现&#xff1a; 无法集中注意力&#xff1a; 分心太多会导致我们无法集中注意力在当前的工作任务上&#xff0c;我们可能会经常性地走神或者在工…

Vue12-计算属性

一、姓名案例 1-1、插值语法实现 1、v-bind v-bind的问题&#xff1a; 所以&#xff1a;v-bind是单向绑定。 2、v-model 解决v-bind的问题。 3、输出全名 方式一&#xff1a; 方式二&#xff1a; 需求优化&#xff1a;全名中的姓氏&#xff0c;只取输入框中的前三位&#xf…

VSCode数据库插件

Visual Studio Code (VS Code) 是一个非常流行的源代码编辑器&#xff0c;它通过丰富的插件生态系统提供了大量的功能扩展。对于数据库操作&#xff0c;VS Code 提供了几种插件&#xff0c;其中“Database Client”系列插件是比较受欢迎的选择之一&#xff0c;它包括了对多种数…