今天郭先生繼續說cannon.js,主演內容就是點對點約束和2D座標轉3D座標。仍然以一個案例爲例,場景由一個地面、若干網格組成的約束體和一些擁有初速度的球體組成,以下圖。線案例請點擊博客原文。數組
下面來講說如何使用約束來完成一個這樣的物理場景。app
這一步是基礎工做,對於有必定three基礎的同窗都不會陌生,我就直接上代碼了。dom
initThree() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 ); camera.position.x = 40; camera.position.y = 52; camera.position.z = 78; scene.add( camera ); scene.add(new THREE.AxesHelper(40)); scene.add(new THREE.AmbientLight(0x888888)); const light = new THREE.DirectionalLight(0xbbbbbb, 1); light.position.set(0, 50, 50); const distance = 200; let texture = new THREE.TextureLoader().load('/static/images/base/ground.png'); texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.copy(new THREE.Vector2(40, 40)); let groundGeom = new THREE.BoxBufferGeometry(100, 0.2, 100); 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); geometry = new THREE.BoxGeometry( 2, 2, 2 ); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.shadowMap.enabled = true; renderer.setClearColor(0xbfd1e5); controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 10, 0); camera.lookAt(0,10,0); this.$refs.box.appendChild( renderer.domElement ); stats = new Stats(); this.$refs.box.appendChild(stats.dom); },
這裏面主要進行初始化場景、相機、渲染器、燈光和地面等操做。this
這裏麪包括建立CANNON.World,建立地面剛體,每塊須要被約束的剛體和設置點對點約束(在給定的偏移點鏈接兩個實體),接下來咱們仍以代碼註釋的形式詳細的講解對於物理世界的建立。spa
initCannon() { world = new CANNON.World(); world.gravity.set(0, -9.8, 0); world.broadphase = new CANNON.NaiveBroadphase(); world.solver.iterations = 10; bodyGround = new CANNON.Body({ mass: 0, position: new CANNON.Vec3(0, -0.1, 0), shape: new CANNON.Box(new CANNON.Vec3(50, 0.1, 50)), material: new CANNON.Material({friction: 0.05, restitution: params.restitution}) }); ground.userData = bodyGround; world.addBody(bodyGround); //上面的代碼意義上一節已經講過了,我就很少言,主要看下面的代碼。 //這裏設置了一些變量,N表示組成約束體剛體的數量,space表示相鄰兩個剛體直接的距離間隔,mass爲剛體的質量變量,width表示剛體半寬度,height表示剛體半高度,last表示上一個相連的剛體。 var N = 20, space = 0.1, mass = 0, width = 10, hHeight = 1, last; var halfVec = new CANNON.Vec3(width, hHeight, 0.2);//剛體的長寬高的halfSize向量 var boxShape = new CANNON.Box(halfVec);//定義一個長方體數據 var boxGeometry = new THREE.BoxBufferGeometry(halfVec.x * 2, halfVec.y * 2, halfVec.z * 2);//定義一個長方几何體 var boxMaterial = new THREE.MeshLambertMaterial( { color: 0xffaa00 } );//定義幾何體材質 for(var i=0; i<N; i++) {//遍歷N次,從上到下建立長方體網格和剛體,位置逐漸變低,質量逐漸變小。 var boxBody = new CANNON.Body({mass: mass, material: new CANNON.Material({friction: 0.05, restitution: params.restitution})});//建立剛體,第一個剛體的質量設置成0(即爲不動的剛體),定義材質,並設置摩擦係數和彈性係數 boxBody.addShape(boxShape);//爲剛體添加形狀 var boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);//建立three世界的網格 boxBody.position.set(0, (N - i + 5) * (hHeight * 2 + space * 2), 0);//這裏設置剛體的位置,是由上倒下的順序 boxBody.linearDamping = 0.01;//設置線性阻尼 boxBody.angularDamping = 0.01;//設置旋轉阻尼 world.addBody(boxBody);//將剛體添加到物理世界中 scene.add(boxMesh);//將網格添加到three場景中 boxes.push(boxBody);//將剛體添加到數組中 boxMeshes.push(boxMesh);//將網格添加到數組中,這兩步能夠在更新物理世界中找到他們的對應關係,也能夠添加到Mesh的userData屬性中去,具體能夠參見上一篇文章 if(i == 0) { //當i=0時,也就是第一個剛體,在剛體建立完畢後,咱們將mass變量設置成1 mass = 1; } else {//從第二個剛體日後都會建立兩個點對點的約束,點對點約束咱們下面講 var ptp1 = new CANNON.PointToPointConstraint(boxBody, new CANNON.Vec3(-width, hHeight + space, 0), last, new CANNON.Vec3(-width, -hHeight - space, 0), (N - i) / 4); var ptp2 = new CANNON.PointToPointConstraint(boxBody, new CANNON.Vec3(width, hHeight + space, 0), last, new CANNON.Vec3(width, -hHeight - space, 0), (N - i) / 4); world.addConstraint(ptp1);//將約束添加到物理世界 world.addConstraint(ptp2);//將約束添加到物理世界 } last = boxBody;//這裏將本次建立的剛體賦值給last變量,一遍下一個循環使用 } },
咱們來講說這個點對點約束,他時由5個參數組成.net
PointToPointConstraint ( bodyA pivotA bodyB pivotB maxForce )
下面就是咱們設置連接點的示意圖,這樣咱們就能夠清楚上面的代碼了rest
這裏就要應用到2D座標轉3D座標的一些知識了,這裏網上已經有不少相關的知識了,能夠看threejs 世界座標與屏幕座標相互轉換,這裏我就直接上代碼了code
document.addEventListener('click', event => { //點擊鼠標 event.preventDefault();//阻止默認事件 let x = (event.clientX / window.innerWidth) * 2 - 1;//將鼠標點擊的x值轉換成[-1, 1] let y = - (event.clientY / window.innerHeight) * 2 + 1;//將鼠標點擊的y值轉換成[-1, 1] let p = new THREE.Vector3(x, y, -1).unproject(camera);//經過unproject方法,使用所傳入的攝像機來反投影(projects)該向量,獲得鼠標對應三維空間點 let v = p.sub(camera.position).normalize();//用鼠標對應的三維空間點減去相機的位置向量,而後歸一化獲得小球的射出方向的單位向量 this.createSphere(v, camera.position);//把須要的兩個向量傳入建立小球的方法中 }) createSphere(v, c) { //建立小球的方法和上一篇很類似,我就不贅述了 const speed = 50; var geometry = new THREE.SphereBufferGeometry(1.5, 32, 16); let sphere = new THREE.Mesh( geometry, this.createRandomMaterial()); sphere.position.copy(c); sphere.castShadow = true; sphere.receiveShadow = true; scene.add( sphere ); ballMeshes.push(sphere); let sphereBody = new CANNON.Body({ mass: params.mass, position: new CANNON.Vec3(c.x, c.y, c.z), shape: new CANNON.Sphere(1.5), material: new CANNON.Material({friction: 0.1, restitution: params.restitution}) }); sphereBody.collisionResponse = 0.01; sphereBody.velocity.set(v.x * speed, v.y * speed, v.z * speed);//這裏要注意velocity屬性能夠剛體帶有出速度 world.addBody(sphereBody); balls.push(sphereBody) setTimeout(() => { scene.remove(sphere); sphere.material.dispose(); sphere.geometry.dispose(); world.removeBody(sphereBody); balls.shift(); ballMeshes.shift(); }, 60000) } createRandomMaterial() { color.setHSL(Math.random(), 1.0, 0.5); return new THREE.MeshPhongMaterial({color: color}); }
這樣就完成了點對點約束的物理效果,讓本來虛擬的three世界變得更加真實。orm
轉載請註明地址:郭先生的博客blog