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

2024-06-09 07:20

本文主要是介绍简单的基于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()}
}

这篇关于简单的基于threejs和BVH第一人称视角和第三人称视角控制器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1044530

相关文章

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用。如果你看不懂,请留言。 完整代码: <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><ti

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

宝塔面板部署青龙面板教程【简单易上手】

首先,你得有一台部署了宝塔面板的服务器(自己用本地电脑也可以)。 宝塔面板部署自行百度一下,很简单,这里就不走流程了,官网版本就可以,无需开心版。 首先,打开宝塔面板的软件商店,找到下图这个软件(Docker管理器)安装,青龙面板还是安装在docker里,这里依赖宝塔面板安装和管理docker。 安装完成后,进入SSH终端管理,输入代码安装青龙面板。ssh可以直接宝塔里操作,也可以安装ssh连接

XMG Quartz2D的简单使用

// //  Quratz2DView.m //  Quartz2D // //  Created by 王宁 on 16/5/6. //  Copyright © 2016年 ylshmacmini. All rights reserved. // #import "Quratz2DView.h" //Quartz@2D是一个二维绘图引擎,同时支

网页脚本输入这么简单

如何在网页中进行脚本操作呢? 研究了一下,很简单,用google浏览器的Console直接操作javaScript。思路: Created with Raphaël 2.1.0 开始 输入(如何输入) 点击(如何点击) 结束 下面是,通过脚本刷直播屏的实现,直接在Console输入即可 var words=new Arra

Linux网络编程之简单并发服务器

1.概念 与前面介绍的循环服务器不同,并发服务器对服务请求并发处理。而循环服务器只能够一个一个的处理客户端的请求,显然效率很低. 并发服务器通过建立多个子进程来实现对请求的并发处理,但是由于不清楚请求客户端的数目,因此很难确定子进程的数目。因此可以动态增加子进程与事先分配的子进程相结合的方法来实现并发服务器。 2. 算法流程 (1)TCP简单并发服务器:     服务器子进程1:

理解什么是DSR,嗅探器视角下的IP和MAC地址识别(C/C++代码实现)

网络嗅探器是监控和分析网络流量的一种工具,它能够捕获数据包并提取出关键的信息,比如IP地址和MAC地址。 网络嗅探器工作原理基于网卡的工作模式。正常情况下,网卡只处理发送给它的数据包,忽略其他数据。但是,如果将网卡设置为“混杂模式”,那么它可以接收到网络上所有的数据包,而不仅仅是发给它的数据包。网络嗅探器就是利用了这一特性来捕获网络上的数据交换。 数据包是网络通信的基本单位,包含了传输数据和控

简单 使用 的makefile编写 框架

1、指定编译器,如海思平台:CROSS_COMPILE=arm-hisiv100nptl-linux-; 2、指定编译工具:GCC=$(CROSS_COMPILE)gcc   CC=$(CROSS_COMPILE)g++; 3、使用 export 导出 子makefile 要用的变量; 4、定义变量的形式  指定 工程源文件 需要使用到的 “宏”,在后面的 LDFLAGS 里面使用 -D将其