今天郭先生继续说cannon.js,主演内容就是点对点约束和2D坐标转3D坐标。仍然以一个案例为例,场景由一个地面、若干网格组成的约束体和一些拥有初速度的球体组成,如下图。线案例请点击博客原文

下面来说说如何使用约束来完成一个这样的物理场景。

1. 创建three场景

这一步是基础工作,对于有一定three基础的同学都不会陌生,我就直接上代码了。

  1. initThree() {
  2. scene = new THREE.Scene();
  3.  
  4. camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
  5. camera.position.x = 40;
  6. camera.position.y = 52;
  7. camera.position.z = 78;
  8. scene.add( camera );
  9.  
  10. scene.add(new THREE.AxesHelper(40));
  11.  
  12. scene.add(new THREE.AmbientLight(0x888888));
  13.  
  14. const light = new THREE.DirectionalLight(0xbbbbbb, 1);
  15. light.position.set(0, 50, 50);
  16. const distance = 200;
  17.  
  18. let texture = new THREE.TextureLoader().load('/static/images/base/ground.png');
  19. texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  20. texture.repeat.copy(new THREE.Vector2(40, 40));
  21.  
  22. let groundGeom = new THREE.BoxBufferGeometry(100, 0.2, 100);
  23. let groundMate = new THREE.MeshPhongMaterial({color: 0xdddddd, map: texture})
  24. ground = new THREE.Mesh(groundGeom, groundMate);
  25. ground.position.y = -0.1;
  26. ground.receiveShadow = true;
  27. scene.add(ground);
  28.  
  29. geometry = new THREE.BoxGeometry( 2, 2, 2 );
  30.  
  31. renderer = new THREE.WebGLRenderer({antialias: true});
  32. renderer.setSize( window.innerWidth, window.innerHeight );
  33. renderer.shadowMap.enabled = true;
  34. renderer.setClearColor(0xbfd1e5);
  35.  
  36. controls = new OrbitControls(camera, renderer.domElement);
  37. controls.target.set(0, 10, 0);
  38. camera.lookAt(0,10,0);
  39.  
  40. this.$refs.box.appendChild( renderer.domElement );
  41.  
  42. stats = new Stats();
  43. this.$refs.box.appendChild(stats.dom);
  44. },

这里面主要进行初始化场景、相机、渲染器、灯光和地面等操作。

2. 初始化物理世界

这里面包括创建CANNON.World,创建地面刚体,每块需要被约束的刚体和设置点对点约束(在给定的偏移点连接两个实体),接下来我们仍以代码注释的形式详细的讲解对于物理世界的创建。

  1. initCannon() {
  2. world = new CANNON.World();
  3. world.gravity.set(0, -9.8, 0);
  4. world.broadphase = new CANNON.NaiveBroadphase();
  5. world.solver.iterations = 10;
  6. bodyGround = new CANNON.Body({
  7. mass: 0,
  8. position: new CANNON.Vec3(0, -0.1, 0),
  9. shape: new CANNON.Box(new CANNON.Vec3(50, 0.1, 50)),
  10. material: new CANNON.Material({friction: 0.05, restitution: params.restitution})
  11. });
  12. ground.userData = bodyGround;
  13. world.addBody(bodyGround);
  14. //上面的代码意义上一节已经讲过了,我就不多言,主要看下面的代码。
  15. //这里设置了一些变量,N表示组成约束体刚体的数量,space表示相邻两个刚体直接的距离间隔,mass为刚体的质量变量,width表示刚体半宽度,height表示刚体半高度,last表示上一个相连的刚体。
  16. var N = 20, space = 0.1, mass = 0, width = 10, hHeight = 1, last;
  17. var halfVec = new CANNON.Vec3(width, hHeight, 0.2);//刚体的长宽高的halfSize向量
  18. var boxShape = new CANNON.Box(halfVec);//定义一个长方体数据
  19. var boxGeometry = new THREE.BoxBufferGeometry(halfVec.x * 2, halfVec.y * 2, halfVec.z * 2);//定义一个长方几何体
  20. var boxMaterial = new THREE.MeshLambertMaterial( { color: 0xffaa00 } );//定义几何体材质
  21.  
  22. for(var i=0; i<N; i++) {//遍历N次,从上到下创建长方体网格和刚体,位置逐渐变低,质量逐渐变小。
  23. var boxBody = new CANNON.Body({mass: mass, material: new CANNON.Material({friction: 0.05, restitution: params.restitution})});//创建刚体,第一个刚体的质量设置成0(即为不动的刚体),定义材质,并设置摩擦系数和弹性系数
  24. boxBody.addShape(boxShape);//为刚体添加形状
  25. var boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);//创建three世界的网格
  26. boxBody.position.set(0, (N - i + 5) * (hHeight * 2 + space * 2), 0);//这里设置刚体的位置,是由上倒下的顺序
  27. boxBody.linearDamping = 0.01;//设置线性阻尼
  28. boxBody.angularDamping = 0.01;//设置旋转阻尼
  29. world.addBody(boxBody);//将刚体添加到物理世界中
  30. scene.add(boxMesh);//将网格添加到three场景中
  31. boxes.push(boxBody);//将刚体添加到数组中
  32. boxMeshes.push(boxMesh);//将网格添加到数组中,这两步可以在更新物理世界中找到他们的对应关系,也可以添加到Mesh的userData属性中去,具体可以参见上一篇文章
  33. if(i == 0) { //当i=0时,也就是第一个刚体,在刚体创建完毕后,我们将mass变量设置成1
  34. mass = 1;
  35. } else {//从第二个刚体往后都会创建两个点对点的约束,点对点约束我们下面讲
  36. var ptp1 = new CANNON.PointToPointConstraint(boxBody, new CANNON.Vec3(-width, hHeight + space, 0), last, new CANNON.Vec3(-width, -hHeight - space, 0), (N - i) / 4);
  37. var ptp2 = new CANNON.PointToPointConstraint(boxBody, new CANNON.Vec3(width, hHeight + space, 0), last, new CANNON.Vec3(width, -hHeight - space, 0), (N - i) / 4);
  38. world.addConstraint(ptp1);//将约束添加到物理世界
  39. world.addConstraint(ptp2);//将约束添加到物理世界
  40. }
  41. last = boxBody;//这里将本次创建的刚体赋值给last变量,一遍下一个循环使用
  42. }
  43. },

我们来说说这个点对点约束,他时由5个参数组成

  1. PointToPointConstraint bodyA pivotA bodyB pivotB maxForce
  • bodyA – 刚体A
  • pivotA – 相对于刚体A质心的点,刚体A被约束到该点。
  • bodyB – 将被约束到与刚体A相同的点的主体。因此,我们将获得刚体A和刚体B之间的链接。如果未指定,刚体A将被约束到一个静态点。
  • pivotB – 相对于刚体B质心的点,刚体B被约束到该点。
  • maxForce – 约束物体应施加的最大力(如果施加的力过大,刚体A和刚体B之间的链接就会被拉长)

下面就是我们设置链接点的示意图,这样我们就可以清楚上面的代码了

3. 根据鼠标点击,发射一个刚体球

这里就要应用到2D坐标转3D坐标的一些知识了,这里网上已经有很多相关的知识了,可以看threejs 世界坐标与屏幕坐标相互转换,这里我就直接上代码了

  1. document.addEventListener('click', event => { //点击鼠标
  2. event.preventDefault();//阻止默认事件
  3. let x = (event.clientX / window.innerWidth) * 2 - 1;//将鼠标点击的x值转换成[-1, 1]
  4. let y = - (event.clientY / window.innerHeight) * 2 + 1;//将鼠标点击的y值转换成[-1, 1]
  5. let p = new THREE.Vector3(x, y, -1).unproject(camera);//通过unproject方法,使用所传入的摄像机来反投影(projects)该向量,得到鼠标对应三维空间点
  6. let v = p.sub(camera.position).normalize();//用鼠标对应的三维空间点减去相机的位置向量,然后归一化得到小球的射出方向的单位向量
  7. this.createSphere(v, camera.position);//把需要的两个向量传入创建小球的方法中
  8. })
  9. createSphere(v, c) {
  10. //创建小球的方法和上一篇很相似,我就不赘述了
  11. const speed = 50;
  12. var geometry = new THREE.SphereBufferGeometry(1.5, 32, 16);
  13. let sphere = new THREE.Mesh( geometry, this.createRandomMaterial());
  14. sphere.position.copy(c);
  15. sphere.castShadow = true;
  16. sphere.receiveShadow = true;
  17. scene.add( sphere );
  18. ballMeshes.push(sphere);
  19.  
  20. let sphereBody = new CANNON.Body({
  21. mass: params.mass,
  22. position: new CANNON.Vec3(c.x, c.y, c.z),
  23. shape: new CANNON.Sphere(1.5),
  24. material: new CANNON.Material({friction: 0.1, restitution: params.restitution})
  25. });
  26. sphereBody.collisionResponse = 0.01;
  27. sphereBody.velocity.set(v.x * speed, v.y * speed, v.z * speed);//这里要注意velocity属性可以刚体带有出速度
  28. world.addBody(sphereBody);
  29. balls.push(sphereBody)
  30.  
  31. setTimeout(() => {
  32. scene.remove(sphere);
  33. sphere.material.dispose();
  34. sphere.geometry.dispose();
  35. world.removeBody(sphereBody);
  36. balls.shift();
  37. ballMeshes.shift();
  38. }, 60000)
  39. }
  40. createRandomMaterial() {
  41. color.setHSL(Math.random(), 1.0, 0.5);
  42. return new THREE.MeshPhongMaterial({color: color});
  43. }

这样就完成了点对点约束的物理效果,让原本虚拟的three世界变得更加真实。

转载请注明地址:郭先生的博客

three.js cannon.js物理引擎之约束的更多相关文章

  1. three.js cannon.js物理引擎之ConvexPolyhedron多边形

    年后第一天上班,郭先生来说一说cannon.js的ConvexPolyhedron(多边形),cannon.js是一个物理引擎,内部通过连续的计算得到各个时间点的数据的状态,three.js的模型可以 ...

  2. three.js cannon.js物理引擎之约束(二)

    今天郭先生继续讲cannon.js的物理约束,之前的一篇文章曾简单的提及过PointToPointConstraint约束,那么今天详细的说一说cannon.js的约束和使用方法.在线案例请点击博客原 ...

  3. three.js cannon.js物理引擎之制作拥有物理特性的汽车

    今天郭先生说一说使用cannon.js的车辆辅助类让我们的汽车模型拥有物理特性.效果图如下,在线案例请点击博客原文. 下面我们说一下今天要使用的两个类,并简单的看看他们的物理意义 1. Raycast ...

  4. three.js cannon.js物理引擎之齿轮动画

    郭先生今天继续说一说cannon.js物理引擎,并用之前已经学习过的知识实现一个小动画,知识点包括ConvexPolyhedron多边形.Shape几何体.Body刚体.HingeConstraint ...

  5. three.js cannon.js物理引擎地形生成器和使用指针锁定控件

    今天郭先生说一说使用cannon.js物理引擎绘制地形和使用指针锁定控件.效果如下图.线案例请点击博客原文. 这里面的生成地形的插件和指针锁定控件也是cannon.js的作者schteppe封装的,当 ...

  6. three.js cannon.js物理引擎之Heightfield

    今天郭先生说一说cannon.js物理引擎之Heightfield高度场,学过场论的朋友都知道物理学中把某个物理量在空间的一个区域内的分布称为场,高度场就是与高度相关的场,而cannon.js物理引擎 ...

  7. three.js cannon.js物理引擎制作一个保龄球游戏

    关于cannon.js我们已经学习了一些知识,今天郭先生就使用已学的cannon.js物理引擎的知识配合three基础知识来做一个保龄球小游戏,效果如下图,在线案例请点击博客原文. 我们需要掌握的技能 ...

  8. three.js 之cannon.js物理引擎

    今天郭先生说的是一个物理引擎,它十分小巧并且操作简单,没错他就是cannon.js.这些优点都源自于他是基于js编写的,对于js使用者来说cannon.js拥有其他物理引擎没有的纯粹性.从学习成本来看 ...

  9. 基于Babylon.js编写宇宙飞船模拟程序1——程序基础结构、物理引擎使用、三维罗盘

    计划做一个宇宙飞船模拟程序,首先做一些技术准备. 可以访问https://ljzc002.github.io/test/Spacetest/HTML/PAGE/spacetestwp2.html查看测 ...

随机推荐

  1. Linux下安装mysql-5.7.24

    Mysql数据库的安装对于开发者来说,是我们必然会面对的问题,它的安装过程其实并不复杂,并且网络上的安装教程也非常多,但是对于新手来说,各种不同形式的安装教程,又给新手们带来了要选择哪种方式进行安装的 ...

  2. MongoDB加索引DB崩溃的问题

    项目原因,最近在对MongoDB进行数据存储优化   原有问题: 日志数据量比较大,存到一张表,需要手工定时删除数据,且删除数据时间按天算 数据会定时打包到HDFS,查询时间很慢,需要优化 机器内存占 ...

  3. gitbook 安装和使用

    gitbook 安装和使用 安装nodejs  wget https://nodejs.org/dist/v10.22.0/node-v10.22.0-linux-arm64.tar.xz tar - ...

  4. spark踩坑--WARN ProcfsMetricsGetter: Exception when trying to compute pagesize的最全解法

    spark踩坑--WARN ProcfsMetricsGetter: Exception when trying to compute pagesize的最全解法 问题描述 大概是今年上半年的时候装了 ...

  5. 部署docker镜像仓库及高可用

      下载地址: https://github.com/goharbor/harbor/releases   安装harbor服务器: 安装harbor root@harbor-vm1:/usr/loc ...

  6. Redis 6.0 多线程性能测试结果及分析

      单线程的Redis一向以简洁高效著称,但也有其阿喀琉斯之踵:阻塞!单个线程在最容易产生瓶颈的网络读写(Redis大key)请求完成之前,其他所有请求都将会被阻塞,严重影响其效率,因此Redis的多 ...

  7. 使用xshell软件进行文件的上传和下载

    1.选择xshell的文件里面的属性-->文件传输,把上传路径和下载路径设置好. 上传路径:介绍我们需要向Linux系统里面传东西. 下载路径:就是我们把Linux系统里面的大小拷贝出来. 2. ...

  8. 远程调用post请求和get请求

    /** * 获取用户 */ @RequestMapping("getUserMassages") public Map<String,Object> getuserMa ...

  9. Java学习日报8.3

    package car;class Person{ private String name; private int age; private Car car; public Person(Strin ...

  10. 单细胞分析实录(3): Cell Hashing数据拆分

    在之前的文章里,我主要讲了如下两个内容:(1) 认识Cell Hashing:(2): 使用Cell Ranger得到表达矩阵.相信大家已经知道了cell hashing与普通10X转录组的差异,以及 ...