一、简介
它作为Bullet的一个分支,同样具有其模拟的准确性和真实性等特点,具有民认下的优点:
(1)模拟效果真实
(2)使用方便
(3)支持多类型模拟
(4)性能较好
二、常用 类
btVector3类:可以表示速度、点、力等向量、它是由3 个浮点类型的x,y,z变量组成。
btTransform类: 变换类,表示刚体的变换,如平移、旋转等,它是由位置和方向组合而成的。
btRigidBody类: 刚体类,用于存储刚体的一些属性信息,包括线性速度、角速度,摩擦系数等
btDynamicsWorld: 物理世界类,有两个子类,一个是离散物理世界类,另一个是测试类。
btCollsionShape类:碰撞形状类,该类封装了一些判断碰撞形状类型的方法。
btStaticPlaneShape: 静态平面形状类,该类表示静态平面,如地面、屋顶、墙壁等。
btSphereShape类:球体形状类,表示球体形状,如蓝球、足球等。
其它类:btBoxShape方体盒碰撞形状类,btCylinderShape圆柱形状类,btCapsuleShape胶囊形状类,btConeShape圆锥形状类,btCompound复合碰撞形状类
三、简单应用
示例是一个简单物理场景案例、在空旷的地面 上,有27个立方体木块掉落。案例主要有以下几个方法组成:
in itGraphics方法:其主要功能是创建摄像机、场景、渲染器以及初始化它们的参数
initPhysics方法:创建及初始化物理世界的方法,包括物理世界、设置碰撞检测、边界信息和重力加速度。
createObjects方法:创建物体的方法,包括创建箱子和地面的材质,纹理,碰撞形状,以及创建它们的刚体。
createRigidBody方法: 创建物体刚体的方法,包括创建刚体,设置惯性,运动状态对象,刚体信息等。
updatePhysics方法:更新物理世界的方法,例如根据时间更新加入物理世界中的物体位置。
四、刚体的碰撞
以下的示例不同的形状刚体之间碰撞,具体步骤如下所示:
(1)创建地面和各种物体的网格对象和刚体。功能是读取相关的纹理贴图,创建物体的材质、网格对象和刚体等。具体代码如下所示。
1 function createObjects(){
2 //创建地板的网格对象和刚体的相关代码
3 loader.load('textures/floor.jpg', function ( texture ){//读取地面的纹理图
4 texture.wrapS = THREE.RepeatWrapping; //将 S轴上的纹理设置为重复
5 texture.wrapT = THREE.RepeatWrapping; //将 T轴上的纹理设置为重复
6 texture.repeat.set(2,2); //设置纹理的重复次数
7 var planePos = new THREE.Vector3(-4,0,0); //设置地面的位置
8 var planeQuat = new THREE.Quaternion(0,0,0,1); //设置地面的旋转信息
9 var planeMaterial=new THREE.MeshBasicMaterial({map: texture}); //创建基本材质
10 //创建地面的网格对象
11 plane=new THREE.Mesh(new THREE.PlaneGeometry(80, 80,4,4), planeMaterial);
12 plane.rotation.x = -0.5 * Math.PI; //设置地面的旋转信息
13 //创建地面的碰撞形状
14 var planeShape=new Ammo.btStaticPlaneShape(new Ammo.btVector3(0,1,0),0);
15 createRigidBody( plane, planeShape, 0 , planePos, planeQuat); //创建地面的刚体
16 });
17 //创建圆柱体的网格对象和刚体的相关代码
18 loader.load('textures/muwen.jpg', function ( texture ){ //读取木质纹理贴图
19 var Pos = new THREE.Vector3(4,5,0); //设置圆柱的位置
20 var Quat = new THREE.Quaternion(0,0,0,1); //设置圆柱的旋转信息
21 var mass=10; //设置圆柱的质量
22 var cylinderMaterial=new THREE.MeshBasicMaterial({map: texture}); //创建基本材质
23 var cylinder=new THREE.Mesh( new THREE.CylinderGeometry(1, 1, 2,20,20),
24 cylinderMaterial ); //创建圆柱的网格对象
25 //创建圆柱的碰撞形状
26 var cylinderShape=new Ammo.btCylinderShape(new Ammo.btVector3(1, 1, 1));
27 createRigidBody( cylinder, cylinderShape, mass, Pos, Quat ); //创建圆柱的刚体
28 var conePos = new THREE.Vector3(4,5,4); //设置圆锥的位置
29 var cone = new THREE.Mesh( new THREE.CylinderGeometry(0, 2, 2,20,20),
30 cylinderMaterial ); //创建圆锥的网格对象
31 var coneShape=new Ammo.btConeShape(2,2,2); //创建圆锥的碰撞形状
32 createRigidBody(cone, coneShape, mass, conePos, Quat); //创建圆锥的刚体
33 }); }
❑ 第3~16行为创建地面网格对象和刚体的相关方法。由于地面面积较大,而纹理图分辨率较低,所以需要将纹理设置为重复。关于纹理截取和重复的相关知识,此处不再赘述,读者可查阅相关图形学的资料。
❑ 第17~32行为读取木质纹理贴图并创建圆柱和圆锥的网格对象,以及刚体的相关代码。从代码中可以看出,创建刚体时需要网格对象、碰撞形状、质量、位置信息、旋转信息等。如果质量为0,则表示刚体是静止的。
(2) 用户单击鼠标左键,场景中就会增加一个带有初始速度的箱体,代码如下:
1 document.onmousedown=function(event){ //鼠标单击事件
2 var sx=2, sy=2, sz=2; //规定正方体箱子的边长
3 mass=10; //设置正方体箱子的质量
4 var pos = new THREE.Vector3(0,10,20); //正方体箱子的初始位置
5 var quat = new THREE.Quaternion(0,0,0,1); //设置箱子的旋转信息
6 //新建正方体箱子的网格对象
7 var box=new THREE.Mesh( new THREE.BoxGeometry(sx, sy, sz,1,1,1), material );
8 createRigidBody(box, shape, mass, pos, quat); //设置正方体箱子的刚体
9 //箱子直线运动的速度-Vx、Vy、Vz 3个分量
10 box.userData.physicsBody.setLinearVelocity(new Ammo.btVector3(0,2, -12));
11 };
五、关节
它是两个物体之间的约束,其可以将两个物体以一定的方式约束在一起,合理地使用关节可以创造出有趣的运动。
btTypedConstraint类:关节的父类,其它关节类都继承该类。
btHingeConstraint类: 铰链类,它是仅有一个旋转自由度的关节。
btGearConstraint类:齿轮关节类,模拟现实世界中齿轮与齿轮之间转动的效果。
btPoint2PointConstraint类:点对点关节类,模拟两个物体上某两个点呈现的连接效果、
btSliderConstraint类:滑动关节,模拟两个物体之间呈现出相对滑动的效果
btGeneric6DofConstraint类: 六自由度关节 ,模拟骨骼关节以及机械结构。
btRaycostVehicle类:交通工具类,模拟现实世界中的交通工具,一般指四轮车。
六、软体
btSoftBodyHelps软体帮助类:创建软体必须使用的类,提供了多种创建软体的方法。在一个三维物理世界中,有一个木块从上掉落在一块晃动的软布上。当单击屏幕后,立方体木块会掉落至软布上。
(1) 示例开发重点应是设置软布的属性信息,软布的绘制和软布顶点的更新方法,核心代码如下所示:
1 createObjects() {
2 ……//此处省略了对渲染进行初始化的代码,读者可自行查阅随书源代码
3 var clothWidth = 4; //软布的宽度
4 var clothHeight = 3; //软布的高度
5 var clothNumSegmentsZ = clothWidth * 5; //z轴上的分割数
6 var clothNumSegmentsY = clothHeight * 5; //y轴上的分割数
7 var clothSegmentLengthZ=clothWidth/clothNumSegmentsZ; //分割后 z轴的段长
8 var clothSegmentLengthY = clothHeight / clothNumSegmentsY; //分割后 y轴的段长
9 var clothPos = new THREE.Vector3( -3, 3, 2 ); //软布的位置
10 var softBodyHelpers = new Ammo.btSoftBodyHelpers(); //软布的构造器
11 var clothCorner00=newAmmo.btVector3(clothPos.x+clothHeight,
12 clothPos.y, clothPos.z ); //确定软布右上角的坐标
13 var clothCorner01 = new Ammo.btVector3(clothPos.x+clothHeight,
14 clothPos.y , clothPos.z - clothWidth ); //确定软布左上角的坐标
15 //确定软布右下角的坐标
16 var clothCorner10 = new Ammo.btVector3( clothPos.x, clothPos.y, clothPos.z );
17 var clothCorner11 = new Ammo.btVector3( clothPos.x, clothPos.y,
18 clothPos.z - clothWidth ); //确定软布左下角的坐标
19 var clothSoftBody=softBodyHelpers.CreatePatch(physicsWorld.getWorldInfo(),
20 clothCorner00, clothCorner01, clothCorner10, clothCorner11, clothNumSegmentsZ + 1,
21 clothNumSegmentsY + 1, 0, true ); //创建软布
22 var sbConfig = clothSoftBody.get_m_cfg(); //获得软布的设置信息
23 sbConfig.set_viterations( 10 ); //设置迭代次数
24 sbConfig.set_piterations( 10); //设置迭代次数
25 clothSoftBody.setTotalMass( 0.9, false ); //设置软布的总重量
26 Ammo.castObject(clothSoftBody, Ammo.btCollisionObject ).
27 getCollisionShape().setMargin( margin * 3 ); //设置软布的边缘值
28 physicsWorld.addSoftBody( clothSoftBody, 1, -1 ); //将软布添加到物理世界中
29 cloth.userData.physicsBody = clothSoftBody; //将创建出的软布与绘制对象绑定
30 clothSoftBody.setActivationState( 4 ); //将软布标注为运动
31 var influence = 0.5;
32 clothSoftBody.appendAnchor(0, dingDian_zuoshang.userData.physicsBody, false,
33 influence ); //在软布左上角设置锚点
34 clothSoftBody.appendAnchor(clothNumSegmentsZ, dingDian_youshang.userData.
35 physicsBody, false, influence ); //在软布右上角设置锚点
36 clothSoftBody.appendAnchor(335, dingDian_zuoxia.userData.physicsBody,
37 false, influence ); //在软布左下角设置锚点
38 clothSoftBody.appendAnchor(315, dingDian_youxia.userData.physicsBody, false,
39 influence ); //在软布右下角设置锚点
40 }
❑ 第3~8行为确定软布长和宽的相关代码。由于一块软布实质上是由若干个点组成的,所以本案例把每个长度单位都用点平均分成5段,最后计算分割后的段长。如果程序开发时出现箱子从软布中间掉落的情况,那么可通过增加分割段数来解决。
❑ 第10~28行为根据上面的数据来计算长方形软布的4个顶点坐标,并创建软布的相关代码。软布创建完成后,还需要对其迭代次数、质量、边缘值等参数进行设置,最后将其添加到物理世界中。
❑ 第31~39行是给长方形软布中的4个顶点设置锚点的相关代码。锚点可将软布的某个点固定在确定的位置,本案例就是通过设置锚点来使软布飘浮在空中的。
(2) 由于软布位置的更新方式与普通刚体有很大的不同,所以仅完成软布的创建过程是远远不够的,最主要的是要得到软布中各个节点的位置,并更新每个位置的顶点坐标。具体代码如下所示:
1 function updatePhysics( deltaTime ) {
2 physicsWorld.stepSimulation( deltaTime, 10 ); //更新物理世界
3 var softBody = cloth.userData.physicsBody; //获得软布
4 var clothPositions = cloth.geometry.attributes.position.array; //获取软布顶点位置
5 var numVerts = clothPositions.length / 3; //得到软布顶点数量
6 var nodes = softBody.get_m_nodes(); //获得软布的节点
7 var indexFloat = 0; //初始化索引
8 for ( var i = 0; i < numVerts; i ++ ) { //对顶点进行遍历
9 var node = nodes.at( i ); //找到第 i个节点
10 var nodePos = node.get_m_x(); //找到此节点的位置
11 clothPositions[ indexFloat++ ] = nodePos.x(); //更新此节点的 x坐标
12 clothPositions[ indexFloat++ ] = nodePos.y(); //更新此节点的 y坐标
13 clothPositions[ indexFloat++ ] = nodePos.z(); //更新此节点的 z坐标
14 }
15 cloth.geometry.computeVertexNormals(); //重新计算法向量
16 cloth.geometry.attributes.position.needsUpdate = true; //更新顶点位置
17 cloth.geometry.attributes.normal.needsUpdate = true; //更新法向量数据
18 ……//此处省略了更新刚体位置的代码,读者可自行查阅随书源代码
19 }
❑ 第2~14行为获得软布的软体信息,并对所有顶点进行遍历,并且根据软体的顶点位置来更新绘制软体形状的相关代码。
❑ 第15~17行为重新计算法向量,并更新程序顶点位置和法向量数据。如果没有此步骤,那么程序还将使用之前的数据,所以这个过程是十分必要的。