1. 程式人生 > >用Physijs在場景中添加物理效果

用Physijs在場景中添加物理效果

角度 彌補 relax 任務 blog 柱狀圖 要約 back borde

1.創建可用Physijs的基本Three.js場景

創建一個可用Physijs的Three.js場景非常簡單,只要幾個步驟即可。首先我們要包含正確的文件, 需要引入physi.js文件。實際模擬物理場景時非常耗費CPU的,如果我麽能在render線程中做的話,場景的幀頻會受到嚴重的影響。為了彌補這一點,Physijs選擇在後臺線程中執行計算。這裏的後臺是有Web workers(網頁線程)規範定義的額,現在大多數瀏覽器都實現了該功能。

對Physijs來說也就意味著我們需要配置一個帶有執行任務的JavaScipt文件,並告訴Physijs在哪裏可以找到用來模擬場景的ammo.js文件。所以需要添加以下代碼:

Physijs.scripts.worker = "../libs/physijs_worker.js";
        Physijs.scripts.ammo = "../libs/ammo.js";

Physijs在Three.js的普通場景外又提供了一個包裝器,所以我們代碼可以想這樣創建場景:

scene = new Physijs.Scene();
            scene.setGravity(new THREE.Vector3(0, -50, 0));

在模擬物理效果之前,我們需要在場景中添加一些對象。為此,我們可以使用Three.js的普通方法來定義對象,但必須用一個特定的Physijs對象將這些對象包裹起來:

var stoneGeom = new THREE.BoxGeometry(0.6, 6, 2);
                        var stone = new Physijs.BoxMesh(stoneGeom, Physijs.createMaterial(new THREE.MeshPhongMaterial({
                            color: scale(Math.random()).hex(),
                            transparent: true,
                            opacity: 
0.8 }))); ... scene.add(stone);

我們第一個Physijs場景中的各個部分都有了。剩下要做的就是告訴Physijs模擬物理效果,並更新場景中各對象的位置和角色。為此,我們可以調用創建的場景的simulate方法。修改基礎render循環代碼:

render = function(){
            requestAnimationFrame(render);
            renderer.render(scene, camera);
            render_stats.update();

            scene.simulate(undefined, 1);
        }

假設我們要實現下面圖片中放倒多米若骨牌的效果。

技術分享

下面是實現功能的一段核心代碼,points是所有多米諾骨牌的點集合。遍歷每個骨牌的頂點,創建一個類型為BoxMesh對象(多米諾骨牌)。這裏需要註意的是通過stone.lookAt()函數設置了對象的旋轉角度,在手動更新了Physijs包裝的對象的角度(或位置)之後,我們必須告訴Physijs有什麽東西改變了。對於角度,我麽可以將__dirtRotation設置為true;對於位置,我們可以將__dirtyPosition設置為true。

this.resetScene = function(){
                    scene.setGravity(new THREE.Vector3(controls.gravityX, controls.gravityY, controls.gravityZ));
                    stones.forEach(function(st){
                        scene.remove(st);
                    });
                    stones = [];

                    points.forEach(function(point){
                        var stoneGeom = new THREE.BoxGeometry(0.6, 6, 2);
                        var stone = new Physijs.BoxMesh(stoneGeom, Physijs.createMaterial(new THREE.MeshPhongMaterial({
                            color: scale(Math.random()).hex(),
                            transparent: true,
                            opacity: 0.8
                        })))
                        //console.log(stone.position);
                        stone.position.copy(point);
                        stone.lookAt(scene.position);
                        stone.__dirtyRotation = true;
                        stone.position.y = 3.5;

                        scene.add(stone);
                        stones.push(stone);
                    });

                    stones[0].rotation.x = 0.2;
                    stones[0].__dirtyRotation = true;
                }

2.材質屬性

Physijs中材質對象最重要的兩個屬性分別是restitution和firction。restitution設置材質彈性,值越大,彈性越強;值越小彈性越弱。而restitution設置摩擦系數,值越小,摩擦就越小,物體越容易移動;值越大,摩擦越大,物體越難移動。

假如我們要實現下圖的效果。地板一直都在左右旋轉,球體也會跟著地板一起移動。這裏我們主要看下球體的實現代碼如何。

技術分享

下面的代碼是圓球的實現代碼。首先生成了一個隨機顏色colorSphere,每次我們批量創建五個球體。創建球體對象使用Physijs.SphereMesh類創建。這裏主要看下如何創建材質。創建材質和我們普通的方法不同,必須使用Physijs.createMaterial函數創建。第三個參數friction用來設置摩擦系數,範圍0到1。第四個參數restitution設置彈性,範圍0到1。只要我們修改這兩個參數,我們就能看到球體落到地板時以及移動時的效果區別。

this.addSpheres = function () {
                    var colorSphere = scale(Math.random()).hex();
                    for(var i = 0; i < 5; i++){
                        box = new Physijs.SphereMesh(
                                new THREE.SphereGeometry(2, 20),
                                Physijs.createMaterial(
                                        new THREE.MeshPhongMaterial({
                                            color: colorSphere,
                                            opacity: 0.8,
                                            transparent: true
                                        }),
                                        controls.sphereFriction,
                                        controls.sphereRestitution
                                )
                        );
                        box.position.set(
                                Math.random() * 50 - 25,
                                20 + Math.random() * 5,
                                Math.random() * 50 - 25
                        );
                        meshes.push(box);
                        scene.add(box);
                    }
                };

3.基礎圖形

Physijs提供了一些可以用來包裝幾何體的圖形類。使用這些幾何體唯一要做的就是講THREE.Mesh的構造函數替換成這些網格對象的構造函數。下表是Physijs中所有網格對象的概覽:

Physijs.PlaneMesh/這個網格可以用來創建一個厚度為0的平面。這樣的平面也可以用BoxMesh對象包裝一個高度很低的THREE.CubeGeometry來表示

Physijs.BoxMesh/如果是類似方塊的幾何體,你可以使用這個網格。例如,它的屬性跟THREE.CubeGeometry的屬性很相配

Physijs.SphereMesh/對於球形可以使用這個網格。它跟THREE.SphereGeometry的屬性很相配

Physijs.CylinderMesh/通過設置THREE.Cylinder的屬性你可以創建出各種柱狀圖形。Physijs為各種柱性提供了不同網格。Physijs.CylinderMesh可以用於一般的、上下一致的圓柱形

Physijs.ConeMesh/如果頂部的半徑為0,底部的半徑值大於0,那麽你可以用THREE.Cylinder創建一個圓錐體。如果你想在這樣一個對象上應用物理效果,那麽可以使用的、最相匹配的網格類就是ConeMesh

Physijs.CapsuleMesh(膠囊網格)/跟THREE.Cylinder屬性很相似,但其底部和底部是圓的

Physijs.ConvexMesh(凸包網格)/Physijs.ConvexMesh是一種比較粗略的圖形,可用於多數復雜退行。它可以創建一個模擬復雜圖形的凸包

Physijs.ConcaveMesh/ConvexMesh是一個比較粗略的圖形,而ConcaveMesh則可以對負責圖形進行比較細致的表現。需要註意的是使用ConcaveMesh對效率的影響比較大

Physijs.HeightfieldMesh(高度場網格)/這是一種非常特別的網格。通過該網格你可以從一個THREE.PlaneGeometry對象創建出一個高度場。

4.使用約束限制對象移動

我們已經了解到各種圖形如何對重力、摩擦和彈性做出反應。並影響碰撞。Physijs還提供了一些高級對象,讓i可以限制對象的移動。在Physijs裏,這些對象唄稱作約束。下表是Physijs中可用約束概覽:

PointConstraint/通過這個約束,你可以將一個對象與另一個對象之間的位置固定下來。例如一個對象動了,另一個對象也會隨著移動,它們之間的距離和方向保持不變

HingeConstraint/通過活頁約束,你可以限制一個對象只能像活頁一樣移動,例如門

SliderConstraint/將對象的移動限制在一個軸上。例如移門

ConeTwistConstraint/通過這個約束,你可以用一個對象限制另一個對象的旋轉和移動。這個約束的功能類似於一個球削式關節。例如,胳膊在肩關節中的活動

DOFConstraint/通過自由度約束,你可以限制對象在任意軸上的活動,你可以設置對象活動的額最小、最大角度。這是最靈活的約束方式

5.用PointConstraint限制亮點間的移動

實現代碼如下,我們在這段代碼裏可以看到,我們使用特定的Physijs網格創建對象,然後將它們添加到場景中。我們使用Physijs.PointConstraint構造函數創建約束。

function createPointToPoint() {
            var obj1 = new THREE.SphereGeometry(2);
            var obj2 = new THREE.SphereGeometry(2);

            var objectOne = new Physijs.SphereMesh(obj1, Physijs.createMaterial(
                    new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0));
            objectOne.position.z = -18;
            objectOne.position.x = -10;
            objectOne.position.y = 2;
            objectOne.castShadow = true;
            scene.add(objectOne);

            var objectTwo = new Physijs.SphereMesh(obj2, Physijs.createMaterial(
                    new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0));
            objectTwo.position.z = -5;
            objectTwo.position.x = -20;
            objectTwo.position.y = 2;
            objectTwo.castShadow = true;
            scene.add(objectTwo);

            // if no position two, its fixed to a position. Else fixed to objectTwo and both will move
            var constraint = new Physijs.PointConstraint(objectOne, objectTwo, objectTwo.position);
            scene.addConstraint(constraint);
        }

構造函數有三個參數。前兩個參數指定要連接的兩個對象。第三個參數指定約束綁定的位置。一般來說,如果你指向將兩個對象連在一起,那麽你最好將這個位置設置在第二個對象的位置上。如果你不想將一個對象綁定到另一個對象,而綁定到場景中某個固定的點,那麽你可以忽略第二個參數。這樣第一個對象就會跟著你指定的位置保持固定距離。

6.用HingeConstraint創建類似們的約束

顧名思義,通過HingeConstraint你可以創建一個行為類似活頁的對象。它可以繞固定的軸旋轉,並可限制在一定角度內。假如我們現在要實現下圖中框選部分的活頁門效果,白色長條隨著右邊的小方塊旋轉。

技術分享

實現代碼如下,HingeConstraint構造函數包含四個參數,定義為new Physijs.HingeConstraint(mesh_a, mesh_b, position, axis)。mesh_a第一個對象是將要被約束的對象;mesh_b指定mesh_a受哪個對象約束。這裏flipperLeft受flipperLetPivot小方塊影響;position約束應用的點。在本例中這個點就是Mesh_a繞著旋轉的點;axis活頁繞著旋轉的軸。在本例中我們將活頁設置在水平方向(0, 1, 0)。最後我們還需要設置約束對象的屬性,為此我們調用setLimits函數。該函數包含四個參數,分別是low(指定旋轉的最下弧度)、high(指定旋轉的最大弧度)、bias_factor(該屬性指定處於錯誤位置時,約束進行糾正的速度)、relaxation_factor(改屬性指定約束以什麽樣的比例改變速度)。如果該屬性的值越高,哪兒對象在達到最小或最大角度時會被彈回來。

function createLeftFlipper() {
            var flipperLeft = new Physijs.BoxMesh(
                    new THREE.BoxGeometry(12, 2, 2), Physijs.createMaterial(new THREE.MeshPhongMaterial(
                            {opacity: 0.6, transparent: true}
                    )), 0.3
            );
            flipperLeft.position.x = -6;
            flipperLeft.position.y = 2;
            flipperLeft.position.z = 0;
            flipperLeft.castShadow = true;
            scene.add(flipperLeft);
            var flipperLeftPivot = new Physijs.SphereMesh(
                    new THREE.BoxGeometry(1, 1, 1), ground_material, 0);

            flipperLeftPivot.position.y = 1;
            flipperLeftPivot.position.x = -15;
            flipperLeftPivot.position.z = 0;
            flipperLeftPivot.rotation.y = 1.4;
            flipperLeftPivot.castShadow = true;

            scene.add(flipperLeftPivot);

            // when looking at the axis, the axis of object two are used.
            // so as long as that one is the same as the scene, no problems
            // rotation and axis are relative to object2. If position == cube2.position it works as expected
            var constraint = new Physijs.HingeConstraint(flipperLeft, flipperLeftPivot, flipperLeftPivot.position, new THREE.Vector3(0, 1, 0));
            scene.addConstraint(constraint);

            constraint.setLimits(
                    -2.2, // minimum angle of motion, in radians, from the point object 1 starts (going back)
                    -0.6, // maximum angle of motion, in radians, from the point object 1 starts (going forward)
                    0.1, // applied as a factor to constraint error, how big the kantelpunt is moved when a constraint is hit
                    0 // controls bounce at limit (0.0 == no bounce)
            );

            return constraint;
        }

7.用SliderConstraint將移動限制到一個軸

通過SliderConstraint約束,你可以將某個對象的移動限制到某個軸上。用代碼 創建這些約束非常簡單:

var constraint = new Physijs.SliderConstraint(sliderMesh, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0));

            scene.addConstraint(constraint);
            constraint.setLimits(-10, 10, 0, 0);
            constraint.setRestitution(0.1, 0.1);

該約束對象接收三個參數(或者四個,如果想將一個對象約束到另外一個對象)。構造函數定義為new Physijs.SliderConstraint(mesh_a, mesh_b, position, axis)。這些參數和HingeConstraint的參數相似。我麽還需要通過constraint.setLimits函數限定滑塊能滑多遠:constriant.setLimits(-10, 10, 0, 0)。參數依次為linear_lower指定對象的線性下限;linear_upper該屬性指定對象的線性上限;anguar_lower該屬性指定對象的角度下限;angular_higher該屬性指定對象的角度上限。

8.用ConeTwistConstraint創建類似球削的約束

通過ConeTwistConstraint可以創建出一個移動受一系列角度限制的約束。我們可以指定一個對象繞著另一個對象轉動時在x、y、z軸上的最小角度和最大角度。理解ConeTwistConstraint最好的方法就是看看創建約束的代碼:

function createConeTwist() {
            var baseMesh = new THREE.SphereGeometry(1);
            var armMesh = new THREE.BoxGeometry(2, 12, 3);

            var objectOne = new Physijs.BoxMesh(baseMesh, Physijs.createMaterial(
                    new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 0);
            objectOne.position.z = 0;
            objectOne.position.x = 20;
            objectOne.position.y = 15.5;
            objectOne.castShadow = true;
            scene.add(objectOne);


            var objectTwo = new Physijs.SphereMesh(armMesh, Physijs.createMaterial(
                    new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 10);
            objectTwo.position.z = 0;
            objectTwo.position.x = 20;
            objectTwo.position.y = 7.5;
            scene.add(objectTwo);

            objectTwo.castShadow = true;

            //position is the position of the axis, relative to the ref, based on the current position
            var constraint = new Physijs.ConeTwistConstraint(objectOne, objectTwo, objectOne.position);

            scene.addConstraint(constraint);
            // set limit to quarter circle for each axis
            constraint.setLimit(0.5 * Math.PI, 0.5 * Math.PI, 0.5 * Math.PI);
            constraint.setMaxMotorImpulse(1);
            constraint.setMotorTarget(new THREE.Vector3(0, 0, 0)); // desired rotation

            return constraint;
        }

我們先是創建出幾個用約束連接起來的對象:ojectOne(球)和objectTwo(盒子)。ConeTwistConstraint的第一個參數是要約束的對象,第二個參數是第一個參數要約束到的對象,最後一個參數是約束應用的位置(在本例中,這個位置就是objectOne繞著旋轉的位置)。將約束添加到場景中之後,我們就可以通過setLimts函數設置它的限制。setLimit函數接收三個弧度值,表示對象繞每個軸旋轉的最大角度。

用Physijs在場景中添加物理效果