先看效果
/examples/ga…
开场白
看到游戏帧这个名字有点奇怪,这个场景其实和碰撞检测有关,为什么又叫游戏帧呢?
引入的新对象
import { Octree } from './jsm/math/Octree.js';import { OctreeHelper } from './jsm/helpers/OctreeHelper.js';import { Capsule } from './jsm/math/Capsule.js';
octree的英文意思为八叉树。
八叉树(Octree)的定义是:若不为空树的话,树中任一节点的子节点恰好只会有八个,或零个,也就是子节点不会有0与8以外的数目。那么,这要用来做什么?想象一个立方体,我们最少可以切成多少个相同等分的小立方体?答案就是8个
从上面描述可知道,八叉树一般用于三维空间的管理,特别是在进行碰撞检测时,分而治之,理清碰撞检测的流程。
八叉树的直观认识
文字通常比较抽象,我们可以用代码来直观地描述它。
{root: true,value: '0',name: '根节点',children: [{ value: '0-0', name: '子节点'},// ...{ value: '0-7', name: '子节点'}]}
同时通过图形视觉上认识它。
是不是对八叉树有一个直观的了解了。
八叉树碰撞检测的流程
三维场景地形
collision-world.glb
通过glb格式的3d模型导出的三维场景。
空间管理八叉树的建立
worldOctree.fromGraphNode( gltf.scene );
fromGraphNode( group ) {group.updateWorldMatrix( true, true );group.traverse( ( obj ) => {if ( obj.isMesh === true ) {let geometry, isTemp = false;if ( obj.geometry.index !== null ) {isTemp = true;geometry = obj.geometry.toNonIndexed();} else {geometry = obj.geometry;}const positionAttribute = geometry.getAttribute( 'position' );for ( let i = 0; i < positionAttribute.count; i += 3 ) {const v1 = new Vector3().fromBufferAttribute( positionAttribute, i );const v2 = new Vector3().fromBufferAttribute( positionAttribute, i + 1 );const v3 = new Vector3().fromBufferAttribute( positionAttribute, i + 2 );v1.applyMatrix4( obj.matrixWorld );v2.applyMatrix4( obj.matrixWorld );v3.applyMatrix4( obj.matrixWorld );this.addTriangle( new Triangle( v1, v2, v3 ) );}if ( isTemp ) {geometry.dispose();}}} );this.build();return this;}
通过glb3d模型导出是一个Three.Group类型的对象。通过调用group的遍历方法,我们只对网格对象进行处理。获得网格对象的几何体对象。获取几何体的位置属性获得构建三角形的的三个顶点坐标通过addTriangle方法构建八叉树。
this.triangles.push( triangle );
不过这光是添加了顶点,并没有构建一个八叉树。
build() {this.calcBox();this.split( 0 );return this;}
这段是真正的构建。
for ( let x = 0; x < 2; x ++ ) {for ( let y = 0; y < 2; y ++ ) {for ( let z = 0; z < 2; z ++ ) {const box = new Box3();const v = _v1.set( x, y, z );box.min.copy( this.box.min ).add( v.multiply( halfsize ) );box.max.copy( box.min ).add( halfsize );subTrees.push( new Octree( box ) );}}}
2 * 2 * 2 = 8。
然后获得这个八叉树所要包裹的物体的边界值,用于大于物体的立方体包裹这个物体。
Box3表示三维空间中的一个轴对齐包围盒(axis-aligned bounding box,AABB)。
球体的碰撞检测
sphereIntersect( sphere ) {_sphere.copy( sphere );const triangles = [];let result, hit = false;this.getSphereTriangles( sphere, triangles );for ( let i = 0; i < triangles.length; i ++ ) {if ( result = this.triangleSphereIntersect( _sphere, triangles[ i ] ) ) {hit = true;_sphere.center.add( result.normal.multiplyScalar( result.depth ) );}}if ( hit ) {const collisionVector = _sphere.center.clone().sub( sphere.center );const depth = collisionVector.length();return { normal: collisionVector.normalize(), depth: depth };}return false;}
首先获得球体所经过的顶点数据。如果判断顶点和球体相交,那么则说明碰撞了。然后根据球体的状态来返回碰撞角度以供碰撞后期处理。
sphere.intersectsBox( subTree.box )
根据包裹立方体来判断物体是否和包裹立方体碰撞了。
这个时候,通过这个包裹立方体来算出具体有多少顶点与物体碰撞了。