今天郭先生继续说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