簡介
Cannon.js 是一個基于 JavaScript 的物理引擎,它可以在瀏覽器中模擬物理效果。它支持碰撞檢測、剛體動力學、約束等物理效果,可以用于創(chuàng)建逼真的物理場景和交互。
原理
Cannon.js 使用了歐拉角來表示物體的旋轉(zhuǎn),而不是四元數(shù)。這使得它在處理旋轉(zhuǎn)時更加直觀和易于理解。Cannon.js 還支持多種碰撞檢測算法,包括離散碰撞檢測和連續(xù)碰撞檢測。
Cannon.js 還支持多種約束類型,包括固定約束、滑動約束和鉸鏈約束等。這些約束可以用于限制物體的運動,例如將兩個物體固定在一起,或者將一個物體限制在一個滑動路徑上。這使得 Cannon.js 可以創(chuàng)建逼真的物理場景和交互。
安裝
npm install cannon-es
使用
需要要使用 Cannon.js,首先需要創(chuàng)建一個物理世界,然后向其中添加物體和約束。接下來,在每個時間步長中更新物理世界,并使用物理世界中的物體和約束來計算物體的位置和旋轉(zhuǎn),由于在物理世界中創(chuàng)建的物體是看不到的,所以需要將這些位置和旋轉(zhuǎn)應(yīng)用到 Three.js 場景中的物體上。
引入
import * as CANNON from "cannon-es";
創(chuàng)建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // 設(shè)置重力
word.gravity: 屬性用于設(shè)置物理世界的重力。
- x: 重力的 x 分量。
- y: 重力的 y 分量。
- z: 重力的 z 分量。
創(chuàng)建物體
//物理世界物體
const sphereShape = new CANNON.Sphere(1); // 創(chuàng)建一個球體形狀
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 5, 0),
}); // 創(chuàng)建一個球體物體
world.addBody(sphereBody); // 將球體添加到物理世界中
//Three.js 場景中的物體
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); // 創(chuàng)建一個球體幾何體
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 創(chuàng)建一個球體材質(zhì)
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial); // 創(chuàng)建一個球體網(wǎng)格
scene.add(sphereMesh); // 將球體網(wǎng)格添加到場景中
CANNON.Sphere(): 方法用于創(chuàng)建一個球體形狀。
- radius: 球體的半徑。
CANNON.Body(): 方法用于創(chuàng)建一個物體。
- mass: 物體的質(zhì)量。
- shape: 物體的形狀。
- position: 物體的位置。
world.addBody(): 方法用于將物體添加到物理世界中。
更新物理世界
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60); // 更新物理世界
// 將物理世界中的球體位置應(yīng)用到 Three.js 場景中的球體網(wǎng)格上
sphereMesh.position.copy(sphereBody.position);
// 將物理世界中的球體旋轉(zhuǎn)應(yīng)用到 Three.js 場景中的球體網(wǎng)格上
sphereMesh.quaternion.copy(sphereBody.quaternion);
renderer.render(scene, camera); // 渲染場景
}
animate();
world.step(): 方法用于更新物理世界中的物體和約束的狀態(tài)。
- dt: 時間步長,用于計算物體的運動和碰撞。
- time: 當前時間,用于計算物體的運動和碰撞。
- correction: 時間步長修正,用于修正時間步長導致的誤差。
copy(): 方法用于將一個向量的值復制到另一個向量中。
- v: 要復制的向量。

碰撞檢測
Cannon.js 支持多種碰撞檢測算法,包括離散碰撞檢測和連續(xù)碰撞檢測。離散碰撞檢測是在每個時間步長中檢查物體之間的碰撞,而連續(xù)碰撞檢測則是在物體之間可能發(fā)生碰撞的路徑上插入額外的檢查點。這使得 Cannon.js 可以處理復雜的碰撞場景,例如物體之間的穿透和重疊。
const groundShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
const groundBody = new CANNON.Body({
shape: groundShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
});
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);
world.addBody(groundBody);
//創(chuàng)建一個平面的幾何體
const groundGeometry = new THREE.BoxGeometry(10, 0.2, 10);
//創(chuàng)建一個平面的材質(zhì)
const groundMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
//創(chuàng)建一個平面的網(wǎng)格
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
//設(shè)置平面的位置
ground.position.x = 0.1;
//將平面添加到場景中
scene.add(ground);
CANNON.Plane(): 方法用于創(chuàng)建一個平面形狀。
CANNON.Box(): 方法用于創(chuàng)建一個盒子形狀。
setFromAxisAngle(): 方法用于設(shè)置物體的旋轉(zhuǎn)。
- axis: 旋轉(zhuǎn)軸。
- angle: 旋轉(zhuǎn)角度。
body.type: 物體的類型
- CANNON.Body.KINEMATIC: 物體是動態(tài)的,可以移動和旋轉(zhuǎn)。
- CANNON.Body.STATIC: 物體是靜態(tài)的,不能移動和旋轉(zhuǎn)。
- CANNON.Body.DYNAMIC: 物體是動態(tài)的,可以移動和旋轉(zhuǎn),并且受到物理世界的重力和其他力的作用。
const groundBody = new CANNON.Body({
shape: groundShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC, // 設(shè)置物體類型為靜態(tài)
});
示例
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
//引入CannonJS
import * as CANNON from "cannon-es";
// 創(chuàng)建場景
const scene = new THREE.Scene();
// 創(chuàng)建相機
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
camera.position.y = 5;
camera.position.x = -10;
// 創(chuàng)建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 創(chuàng)建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
// 創(chuàng)建一個球體
const sphereShape = new CANNON.Sphere(1);
const sphereBody = new CANNON.Body({
mass: 1,
shape: sphereShape,
position: new CANNON.Vec3(0, 5, 0),
});
world.addBody(sphereBody);
// 創(chuàng)建一個平面
const groundShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5));
const groundBody = new CANNON.Body({
shape: groundShape,
position: new CANNON.Vec3(0, 0, 0),
type: CANNON.Body.STATIC,
});
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1);
world.addBody(groundBody);
//創(chuàng)建一個球體的幾何體
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);
//創(chuàng)建一個球體的材質(zhì)
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
//創(chuàng)建一個球體的網(wǎng)格
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
//將球體添加到場景中
scene.add(sphere);
//創(chuàng)建一個平面的幾何體
const groundGeometry = new THREE.BoxGeometry(10, 0.2, 10);
//創(chuàng)建一個平面的材質(zhì)
const groundMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
//創(chuàng)建一個平面的網(wǎng)格
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
//設(shè)置平面的位置
ground.position.x = 0.1;
//將平面添加到場景中
scene.add(ground);
//創(chuàng)建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 啟用阻尼效果
controls.enableDamping = true;
function animate() {
requestAnimationFrame(animate);
world.step(1 / 60);
sphere.position.copy(sphereBody.position);
sphere.quaternion.copy(sphereBody.quaternion);
renderer.render(scene, camera);
}
animate();
//監(jiān)聽窗口大小變化
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
