一、简介

目前在 Github 上搜索到的 3D 物理引擎库有 Cannon.js、Oimo.js、Ammo.js、Energy.js、Physijs 等等,大部分都已许久没有更新迭代了(长达好几年),项目的 Star 数量和 Issues 数量也不多,我们该如何选择?

Energy.js :使用 C++ 编写转 JavaScript 的 3D 物理引擎,源码不可读,目前 Github 比较冷清。

Oimo.js :一款轻量级的 3D 物理引擎,文件大小 153 KB。

Physijs:号称专为threejs开发的物理引擎,但是git上从15年就已经不再更新了。并且我已经测试过在最新的three.js 101dev 中已经无法正常使用了。

Cannon.js :完全使用 JavaScript 编写的优秀 3D 物理引擎,包含简单的碰撞检测、各种形状的摩擦力、弹力、约束等功能。

从综合性来看,我更偏向于 Cannon.js ,因为这个我也测试了。^_^ ,虽然也是多年没有更新。但是它可以在最新版的three.js中正常使用。所以下面主要讲讲 Cannon.js。

二、正文

查看官方demo点击这里

首先建立世界,当然你要先把cannon.js 引入到你的文件里。

// 建立物理世界
world = new CANNON.World()

// 設定重力場為 y 軸 -9.8 m/s²
world.gravity.set(0, -9.8, 0)

// 碰撞偵測
world.broadphase = new CANNON.NaiveBroadphase()

首先需建立一個 CANNON.World 物件,用它作为物理世界的容器来管理世界中的刚体与模型行为,在这里简单设定物理世界的重力为 y 轴向下 9.8 m/s²,Cannon.js 提供了 Broadphase、NaiveBroadphase 两种碰撞检测阶段,默认是 NaiveBroadphase。

建立动态球形刚体

            //创建球形刚体
            var sphereShape = new CANNON.Sphere(1); // 形状
            var sphere_cm = new CANNON.Material();  // 材质
            sphereBody = new CANNON.Body({ // 刚体
                mass: 5,    //质量
                position: new CANNON.Vec3(0, 10, 0),    // 位置
                shape: sphereShape, 
                material: sphere_cm
            })

            world.add(sphereBody)

这里的刚体添加到舞台是看不到的。

建立静态地板刚体

            // 平面 Body
            var groundShape = new CANNON.Plane();   // 形状
            var ground_cm = new CANNON.Material(); // 材质
            var groundBody = new CANNON.Body({  // 刚体
                mass: 0,    // 质量,质量为0时为静态刚体
                shape: groundShape,
                material: ground_cm 
            })
            // setFromAxisAngle 旋转 X 轴 -90 度
            groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)

            world.add(groundBody)

默认情况下平面的方向是朝向 Z 方向的(竖立着),所以这里需要旋转一下。这里自己可以测试一下,毕竟知识需要知其然知其所以然 才能贯通。

设定碰撞时交互属性,交互属性是建立在材质上的,所以前面建立刚体的时候一定要建立材质。

            var sphere_ground = new CANNON.ContactMaterial(ground_cm, sphere_cm, { //  定义两个刚体相遇后会发生什么
                friction: 1,    // 摩擦系数
                restitution: 0.4    // 恢复系数
            })
            world.addContactMaterial(sphere_ground) // 添加到世界中

这里通过 CANNON.ContactMaterial 来设定在物理世界中的两个刚体碰撞时的交互作用,设定恢复系数(restitution)可以決定刚体碰撞后的反弹程度,另外也可以设定摩擦力(friction),不过在这个例子感受不出来摩擦力的差別。

在three.js世界中创建平面和球

           // 平面网格
            var groundGeometry = new THREE.PlaneGeometry(20, 20, 32)
            var groundMaterial = new THREE.MeshStandardMaterial({
                color: 0x7f7f7f,
                side: THREE.DoubleSide, //这个属性只知道是两面,具体的不清楚,哪位知道可以留言告知,谢谢
            })
            var ground = new THREE.Mesh(groundGeometry, groundMaterial)
            ground.rotation.x = -Math.PI / 2;   // 跟随 前面的物理平面角度
            scene.add(ground)

            // 球网格
            var sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
            var sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
            sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
            scene.add(sphere)

更新物理世界

            world.step(1 / 60)

            if (sphere) {
                sphere.position.copy(sphereBody.position)
                sphere.quaternion.copy(sphereBody.quaternion)
            }

这里将创建的球刚体与球网格关联起来。这里的world.step大致可以理解为每秒钟的模拟次数,数值越大模拟更精准,当然性能也是成正比的。

以下是这个文章的完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="js/three.js"></script>
    <script src="js/cannon.js"></script>
    <title>cannonjs物理引擎之弹跳的小球</title>
</head>
<body>
    <div id="canvas"></div>
    <script>
        var scene,camera,renderer,container;
        var WIDTH,HEIGHT;
        var world,sphere,sphereBody;
        // 创建物理世界
        function initCannon(){
            world = new CANNON.World();
            world.gravity.set(0,-9.82,0);
            world.broadphase = new CANNON.NaiveBroadphase();
        }
        // 创建渲染器
        function initRender(){
            WIDTH = window.innerWidth;
            HEIGHT = window.innerHeight;
            container = document.getElementById('canvas');
            renderer = new THREE.WebGLRenderer({
                alpha:true
            });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(WIDTH,HEIGHT);
            container.appendChild(renderer.domElement);
        }   
        // 创建场景 
        function initScene(){
            scene = new THREE.Scene();
        }
        // 创建相机
        function initCamera(){
            camera = new THREE.PerspectiveCamera(70,WIDTH/HEIGHT,1,10000);
            camera.position.set(0,5,20);
            //camera.lookAt(0,0,0);
        }
        // 创建环境光
        function initLight(){
            var light = new THREE.AmbientLight( 0xffffff ); // soft white light
            scene.add( light );
        }
        // 创建对象
        function initObject(){
            //创建球形刚体
            var sphereShape = new CANNON.Sphere(1); // 形状
            var sphere_cm = new CANNON.Material();  // 材质
            sphereBody = new CANNON.Body({ // 刚体
                mass: 5,    //质量
                position: new CANNON.Vec3(0, 10, 0),    // 位置
                shape: sphereShape, 
                material: sphere_cm
            })

            world.add(sphereBody)
            // 平面 Body
            var groundShape = new CANNON.Plane();   // 形状
            var ground_cm = new CANNON.Material(); // 材质
            var groundBody = new CANNON.Body({  // 刚体
                mass: 0,    // 质量,质量为0时为静态刚体
                shape: groundShape,
                material: ground_cm 
            })
            // setFromAxisAngle 旋转 X 轴 -90 度
            groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)

            world.add(groundBody)
            var sphere_ground = new CANNON.ContactMaterial(ground_cm, sphere_cm, { //  定义两个刚体相遇后会发生什么
                friction: 1,    // 摩擦系数
                restitution: 0.4    // 恢复系数
            })
            world.addContactMaterial(sphere_ground) // 添加到世界中

            // 平面网格
            var groundGeometry = new THREE.PlaneGeometry(20, 20, 32)
            var groundMaterial = new THREE.MeshStandardMaterial({
                color: 0x7f7f7f,
                side: THREE.DoubleSide, //这个属性只知道是两面,具体的不清楚,哪位知道可以留言告知,谢谢
            })
            var ground = new THREE.Mesh(groundGeometry, groundMaterial)
            ground.rotation.x = -Math.PI / 2;   // 跟随 前面的物理平面角度
            scene.add(ground)

            // 球网格
            var sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
            var sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
            sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
            scene.add(sphere)
        }
        function initThree(){
            initCannon();
            initRender();
            initScene();
            initCamera();
            initLight();
            initObject();
            animation();
        }
        function animation(){
            requestAnimationFrame(animation);
            renderer.render(scene,camera);

            world.step(1 / 60)

            if (sphere) {
                sphere.position.copy(sphereBody.position)
                sphere.quaternion.copy(sphereBody.quaternion)
            }
        }
        initThree();
    </script>
    
</body>
</html>

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐