Cannon.js

详细内容请访问 Cannon.js 官方文档

关于 Cannon.js

Web的轻量级3D物理引擎
本身只有398KB

Cannon.js的制作者受three.jsammo.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>

Hello Cannon.js!

示例
下面的示例代码创建了一个平面和一个球体,逐步执行模拟,并将球体模拟打印到控制台。注意,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实体

查看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

CANNON.Demo框架

查看Demo Class

掌握了HelloWorld示例后,就应该开始研究Cannon.js的演示框架。CANNON.Demo是一个演示环境。以下是一些功能:

  • 平移和变焦相机
  • 场景数量可扩展
  • 用于选择场景[数字键],参数调整和调试图形选项的GUI
  • 暂停[p]和单步模拟[s]

该演示框架具有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 /。

构造器

Demo(options)
参数

options Object

方法
  • addScene(title, initfunc)

向演示应用程序添加场景

title String 场景的标题
initfunc Function 接受一个参数app并初始化物理场景的函数。该函数运行app.setWorld(Body)、app.addVisual(Body)、app.removeVisual(Body)等。

  • restartCurrentScene()

重新启动当前场景


渲染 Cannon.js 场景

一个基本的示例
创建一个带有物理属性的立方体,基本可以分为两个部分:

  • 创建一个带有物理属性的刚体外框(通过cannon.js创建)
  • 在这个外框内放入有形状、颜色等可视化属性几何体(通过three.js创建)

这里附一张UE4创建人物角色的图片,帮助理解
和UE4的基本类似胶囊体和人物

//带有物理属性的外框
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两个的不同点,上面的内容我们知道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);
  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值