three.js 之cannon.js物理引擎

郭先生的部落格發表於2021-01-18

今天郭先生說的是一個物理引擎,它十分小巧並且操作簡單,沒錯他就是cannon.js。這些優點都源自於他是基於js編寫的,對於js使用者來說cannon.js擁有其他物理引擎沒有的純粹性。從學習成本來看,cannon.js的學習成本比較低,對於新手來說比較友好,因為它有相對完善的api,學習cannon.js之前我們不妨來看看cannons.js的官方網站以及他的API,對於js學習者來說這是十分必要的。官網上面有一些example,他們十分典型並囊括了大多數的知識點,配合api一起學習是個不錯的選擇。線上案例請點選部落格原文。效果如下圖,接下來以一個小案例,簡單的介紹一下cannon.js。

1. 初始化three場景

建立three場景(或者說three世界)來作為物理世界的載體,這一步很簡單,主要就是新增渲染器、場景、相機和網格等three元素,沒必要多說。

scene = new THREE.Scene();//step 1 建立場景

camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.y = 30;
camera.position.z = 20;
camera.lookAt(0,5,0);
scene.add( camera ); //step 2 場景中新增相機

scene.add(new THREE.AmbientLight(0x888888));
const light = new THREE.DirectionalLight(0xbbbbbb, 1);
light.position.set(6, 30, 6);
scene.add(light); //step 3 場景中新增另種光源

renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.setClearColor(0xbfd1e5);
this.$refs.box.appendChild(renderer.domElement); //step 4 dom中新增渲染器

let groundGeom = new THREE.BoxBufferGeometry(40, 0.2, 40);
let groundMate = new THREE.MeshPhongMaterial({color: 0xdddddd, map: texture})
ground = new THREE.Mesh(groundGeom, groundMate);
ground.position.y = -0.1;
ground.receiveShadow = true;
scene.add(ground); //step 5 新增地面網格

2. 初始化物理世界

這裡是初始化物理世界,我們詳細的講一下他們的用法。

initCannon() {
    world = new CANNON.World(); //該方法初始化物理世界,裡面包含著物理世界的相關資料(如剛體資料,世界中所受外力等等)
    world.gravity.set(0,-9.8,0); //設定物理世界的重力為沿y軸向上-9.8米每二次方秒
    world.broadphase = new CANNON.NaiveBroadphase();//NaiveBroadphase是預設的碰撞檢測方式,該碰撞檢測速度比較高
    world.solver.iterations = 5;//解算器的迭代次數,更高的迭代次數意味著更加精確同時效能將會降低

    bodyGround = new CANNON.Body({ //建立一個剛體(物理世界的剛體資料)
        mass: 0, //剛體的質量,這裡單位為kg
        position: new CANNON.Vec3(0, -0.1, 0), //剛體的位置,單位是米
        shape: new CANNON.Box(new CANNON.Vec3(20, 0.1, 20)), //剛體的形狀(這裡是立方體,立方體的引數是一個包含半長、半寬、半高的三維向量,具體我們以後會說)
        material: new CANNON.Material({friction: 0.05, restitution: 0}) //材質資料,裡面規定了摩擦係數和彈性係數
    });
    ground.userData = bodyGround; //將剛體的資料賦值給地面網格的userData屬性
    world.addBody(bodyGround); //物理世界新增地面剛體
},

3. 向場景中新增網格並向物理世界中新增剛體資料

這裡我們通過setInterval函式我們定時向場景中新增網格並向物理世界中新增剛體資料,

interval = setInterval(() => {
    this.createBox(); //建立網格和剛體的方法
}, 200);

下面是具體的方法

createBox() {
    let x = Math.random() * 10 - 5;
    let z = Math.random() * 10 - 5;
    let box = new THREE.Mesh( geometry, this.createRandomMaterial() ); //createRandomMaterial建立隨機顏色的材質
    box.position.set(x, 20, z);
    scene.add( box ); //建立box,並新增到場景

    let bodyBox = new CANNON.Body({
        mass: 1,
        position: new CANNON.Vec3(x, 20, z),
        shape: new CANNON.Box(new CANNON.Vec3(1,1,1)),
        material: new CANNON.Material({friction: 0.1, restitution: 0})
    });//建立一個質量為1kg,位置為(x,20,z),形狀為halfSize為1,1,1的正方形剛體,材質中摩擦係數為0.1,彈性係數為0。
    box.userData = bodyBox;//給box的userData屬性新增剛體資料
    world.addBody(bodyBox);//在物理世界中新增該剛體

    setTimeout(() => { //10秒鐘之後在場景中移除box,並在物理世界中移除該剛體
        scene.remove(box);
        box.material.dispose();
        box.geometry.dispose();
        world.removeBody(bodyBox);
    }, 10000)
},

4. 根據物理引擎的資料更新three網格資料

這一步我們逐幀根據物理引擎的資料渲染three場景

animation() { //requestAnimationFrame動畫中呼叫render方法
    this.globalID = requestAnimationFrame(this.animation);
    this.render();
},
render() { //更新效能外掛,根據物理引擎資料更新網格資料,最後渲染場景
    stats.update();
    this.updatePhysics();
    renderer.render( scene, camera );
},
updatePhysics() { // world.step
    world.step(timeStep); //第一個引數是以固定步長更新物理世界引數(詳情請看api)
    scene.children.forEach(d => {//遍歷場景中的子物件,如果物件的isMesh屬性為true,我們就將更新改物件的position和quaternion屬性(他們對應的剛體資料存在對應的userData中)。
        if(d.isMesh == true) {
            d.position.copy(d.userData.position);
            d.quaternion.copy(d.userData.quaternion);
        }
    })
}

這樣我們就將cannon.js物理引擎應用到了three中。不出意外的話,接下來我會講解一下官方的examples。

 

轉載請註明地址:郭先生的部落格

相關文章