详细内容请访问 Cannon.js 官方文档
Web的轻量级3D物理引擎
本身只有398KB
Cannon.js的制作者受three.js和ammo.js的启发,并由于缺乏支持浏览器的物理引擎,最终cannon.js出现了。Cannon.js物理引擎包括简单的碰撞检测,各种体形,接触,摩擦和约束。
Cannon.js是用于游戏的刚体模拟库。程序员可以使用它来使他们的游戏对象以真实的方式移动和交互。Cannon.js是用JavaScript编写的,可以与任何支持浏览器的渲染或游戏引擎一起使用。
Cannon.js的安装与使用与Physijs相比要更容易,只要在html中包含cannon.js或cannon.min.js,就可以完成:
<script src="./cannon.min.js"></script>
示例
下面的示例代码创建了一个平面和一个球体,逐步执行模拟,并将球体模拟打印到控制台。注意,Cannon.js使用国际单位(m,kg,s,等)。
// 设置我们的世界
var world = new CANNON.World();
world.gravity.set(0, 0, -9.82); // m/s²
// 创建球体
var radius = 1; // m
var sphereBody = new CANNON.Body({
mass: 5, // kg
position: new CANNON.Vec3(0, 0, 10), // m
shape: new CANNON.Sphere(radius)
});
world.addBody(sphereBody);
// 创建一个平面
var groundBody = new CANNON.Body({
mass: 0 // mass == 0 makes the body static
});
var groundShape = new CANNON.Plane();
groundBody.addShape(groundShape);
world.addBody(groundBody);
var fixedTimeStep = 1.0 / 60.0; // seconds
var maxSubSteps = 3;
// 启动模拟循环
var lastTime;
(function simloop(time){
requestAnimationFrame(simloop);
if(lastTime !== undefined){
var dt = (time - lastTime) / 1000;
world.step(fixedTimeStep, dt, maxSubSteps);
}
console.log("Sphere z position: " + sphereBody.position.z);
lastTime = time;
})();
注意: 以上代码所创建的物体无法在浏览器中被渲染, 需要配合three.js来使用
每个Cannon.js应用程序都从创建一个CANNON.World对象开始。CANNON.World是管理对象和模拟的物理中心。创建Cannon.js世界很容易。首先,创建CANNON.World对象。然后,在Z轴的负方向设置重力为9.82 m/s²:
var world = new CANNON.World();
world.gravity.set(0,0,-9.82);
之后必须向全世界提供一个 BroadPhase 算法,这样它才能找到相撞的物体。这里使用默认的NAIVEBroadPhase
查看Broadphase Class
world.broadphase = new CANNON.NaiveBroadphase();
现在我们有了我们的物理世界,让我们开始为它添加一些东西。
关于物理引擎,碰撞检测一般分成三个部分,粗测阶段(BroadPhase),细测(NarrowPhase)和中间(MiddlePhase),所谓BroadPhase,就是对所有的Shape进行初步检测,具体做法是检测每两个Shape的包围盒是否相交,是则送入下一阶段处理。这里的包围盒一般是AABB,原因显然是它的性价比最好。下一步就是NarrowPhase了,这时,我们必须根据不同类型的Shape给出不同的算法。在物理引擎中,碰撞检测的工作并不是到找到哪些Shape相交就结束了。还有很多工作要做,我们要选取合适的碰撞点,对每个碰撞点要得到法线,相交深度等信息。
查看Body Class
使用以下步骤构建body实体:
var mass = 5, radius = 1;
var sphereShape = new CANNON.Sphere(radius); // Step 1
var sphereBody = new CANNON.Body({
mass: mass, shape: sphereShape}); // Step 2
sphereBody.position.set(0,0,0);
world.add(sphereBody); // Step 3
在第一步中,我们创建了一个半径为1的球体形状。这只是一个球体的数学描述,在它可以移动和碰撞之前,它必须放入刚体中。在步骤二中,我们创建此RigidBody。如果将质量设置为零,则实体将变为静态,但在本例中,我们将质量设置为5千克,这将使球体处于动态状态。静态物体不与其他静态物体碰撞,动态物体与所有其他物体碰撞。对于第三步,我们将 sphereBody 实体添加到世界中。通过将它添加到世界中,它将根据它所受的力移动,并与其他物体碰撞。
第一步是创建平面形状,然后创建实体。设置物体质量为零,这将确保地面物体始终保持静止。默认情况下,平面的法向量是(0,0,1)。如果我们想让它朝向其他方向,我们必须改变实体的方向(参见Body.quaternion):
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0),-Math.PI/2);
最后,将地面实体添加到场景中。
查看Plane Class
var groundShape = new CANNON.Plane();
var groundBody = new CANNON.Body({
mass: 0, shape: groundShape });
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0),-Math.PI/2);
world.add(groundBody);
不建议使用CANNON.Plane()创建地面,可以通过使用CANNON.Body来创建一个有一定厚度的平面
以上是用于初始化的内容。我们现在可以开始模拟了。
除了已经初始化了的静态地平面和动态球体,还有几个问题要考虑。cannon.js使用一种称为积分器(integrator)的计算算法。积分器在离散时间点处模拟物理方程(Integrators simulate the physics equations at discrete points of time)。这与传统的游戏循环是一致的,在传统的游戏循环中,我们的屏幕基本上如同一本活页移动的书。所以我们需要为Cannon.js选择一个时间步长。一般来说,游戏的物理引擎的一个时间步长,至少快到60赫兹或1/60秒(1/帧数)。虽然可以用更大的时间步长,但是在设置时必须格外小心。
var timeStep = 1.0 / 60.0; // seconds
除了积分器之外,Cannon.js还使用约束解算器。约束求解器(constraint solver)求解仿真中的所有约束。可以完美地解决单个约束。但是,当我们求解一个约束时,会影响其他约束。要获得好的解决方案,我们需要多次迭代所有约束。在此阶段中,解算器(solver)计算物体正确移动所需的脉冲。建议的Cannon.js迭代次数约为5次。您可以根据自己的喜好调整这个数字,但请记住,这需要在速度和准确性之间进行权衡。使用较少的迭代可以提高性能,但准确性会受到影响。同样,使用更多迭代会降低性能,但会提高模拟质量。对于这个简单的示例,我们不需要太多迭代。以下是我们选择的迭代计数。iterations=2;请注意,时间步长与迭代计数完全无关。迭代不是子步骤。一次求解器迭代是对时间步长内的所有约束进行一次遍历。您可以在单个时间步长内多次遍历约束。
现在我们准备开始模拟循环。在游戏中,模拟循环(simulation loop)可以与游戏循环(game loop)合并并在游戏循环的每个过程中调用 world.step(Timestep)
。根据您的帧率和物理时间步长,只调用一次通常就足够了。
Hello Cannon.js!程序设计得很简单,因此没有图形输出。代码只打印出动态体的位置。
以下是模拟循环,它在1秒的时间内模拟60个时间点。
for (var i = 0; i < 60; ++i){
world.step(timeStep);
console.log(sphereBody.position.x, sphereBody.position.y, sphereBody.position.z);
}
以下输出显示盒子在下落和着陆时的位置关系
0 0 4
0 0 3.99
0 0 3.98
...
0 0 1.25
0 0 1.13
0 0 1.01
查看Demo Class
掌握了HelloWorld示例后,就应该开始研究Cannon.js的演示框架。CANNON.Demo是一个演示环境。以下是一些功能:
该演示框架具有Cannon.js用法及其框架本身的许多示例。鼓励您在学习Cannon.js时探索CANNON.Demo。注意:测试平台是使用Three.js编写的。CANNON.Demo不属于Cannon.js库(build / cannon.demo.js)。Cannon.js库与渲染无关。如Hello world示例所示,您不需要渲染器即可使用Cannon.js。
如果您想学习如何将Cannon.js与Three.js连接起来,请改为查看examples /。
options
Object
向演示应用程序添加场景
title
String 场景的标题
initfunc
Function 接受一个参数app并初始化物理场景的函数。该函数运行app.setWorld(Body)、app.addVisual(Body)、app.removeVisual(Body)等。
重新启动当前场景
一个基本的示例
创建一个带有物理属性的立方体,基本可以分为两个部分:
//带有物理属性的外框
const box = new CANNON.Body({
mass: 1,
shape: new CANNON.Box(new CANNON.Vec3(1, 1, 1))
});
world.addBody(box); //将创建好的立方体加入到世界中
//在这个外框内放入几何体
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshLambertMaterial({
color: 0xff0000
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
但是如何保证这个几何体始终在这个外框内,以及其旋转角度相同呢?
使用最方便的方法之一是启用四元数(Quaternions)。
mesh.useQuaternion = true;
注意:在最新版本的Three.js上,useQuaternion默认为true。
然后,将位置+方向(position+orientation)数据复制到Three.js网格变得非常简单:
mesh.position.x = body.position.x;
mesh.position.y = body.position.y;
mesh.position.z = body.position.z;
mesh.quaternion.x = body.quaternion.x;
mesh.quaternion.y = body.quaternion.y;
mesh.quaternion.z = body.quaternion.z;
mesh.quaternion.w = body.quaternion.w;
//或者将坐标从Cannon.js复制到Three.js
mesh.position.copy(box.position);
mesh.quaternion.copy(box.quaternion);
这里说一下Cannon.js和Physijs两个的不同点,上面的内容我们知道Cannon.js是如何让物体具有物理属性的,而Physijs让物体具有物理属性的方法是在物体的材质(Material)和网格体(Mesh)上着手。
Physijs带有创建材质的方法,如下
// Physijs----Materials
const ground_material = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
color: 0xffffff,
}),
.2, // 定义了材质的摩擦系数
.4 // 定义了材质的弹性系数,
);
之后通过Physijs.BoxMesh
创建网格体
const ground = new Physijs.BoxMesh(
new THREE.BoxGeometry(200, 4, 200),
ground_material,
0 // mass
);
scene.add(ground);