用Physijs在場景中添加物理效果
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在場景中添加物理效果