• 【Three.js基础学习】16.Physice


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    课程回顾

            物理库

                3D

                Ammo.js

                Cannon.js

                Oimo.js

                2D

                Matter.js

                P2.js

                Planck.js

                Box2D.js

            补充:一些看似3D的效果实际使用2D库来实现的

            物理 和 three.js的结合 概念补充

            假象在当前物体或场景外, 有一个看不见的物理量世界 ,那么遵循物理世界规则,若有一个小球在

            半空中掉落,碰触到地面,然后在滚落到旁边。这个时候 three.js 展示的每一帧都要获取这个物理量

            世界的位置信息,然后更新到three.js中,实现展示,因此需要资料库

            这里采用cannon.js

            1.下载,引入

            npm install --save cannon

            import CANNON from 'cannon'

            2.创建一个物理世界

            const world = new CANNON.world()

            world.gravity(0,-9.82,0) 添加重力  9.82 重力常量

            vec3 文档 https://schteppe.github.io/cannon.js/docs/classes/Vec3.html

            vec3 和 vector 区别

            vec3 是cannon.js中局部位置

            vector是three.js

            和three.js一样 想要创建网格 ,但是网格必须有个几何体

            同样cannon.js 想要创建身体 ,首先创建一个形状

            3.如何通过物理方式动起来

            在物理世界有了一个和three.js 一样的物理,但是要更新在tick

            首先应该确保在跟新步长时候,保证精度(准确性)

            保证上一个更新帧率 和这次跟新帧率没有问题

            然后将物理世界的球的位置 设置到 three.js中

           

            4.球会一直往下,再来个地板

            此时会发现,地板是正对着摄像头,因为在three.js中我们旋转地板,那么在物理世界同样

            cannon.js中的旋转是使用四元数(上方文档)

            5.球应该弹起来

                需要材料,创建两种材料对应 球和地板

                例如:混泥土材料  (当然由于球和地板设置相同材质 ,所以 简化代码)

                    塑料材料

                同时创建接触材料,就是混泥土遇到塑料材料情况

                new CANNON.ContactMaterial()

                放到world中,同时在物理世界中的物体也要应用上 这和three.js相似

                也不用一个一个设置,统一设置应用

                world.defaultContactMaterial = defaultContactMaterial

           

            6. 对物体施力的方法

                applyforce-从空间的指定点施加一个力(不一定是物体表面)比如风,在多米诺骨牌上轻轻一推,或者在愤怒的小鸟上施加一个很大的力

                applylmpulse类似applyforce,但不是增加力,而是增加速度

                applyLocalForce-与applyForce相同,但坐标是主体的局部(e,e,e将是主体的中心

                applyLocallmpulse-与applylmpulse相同,但坐标是主体的局部

            7. 将球写成一个函数,这样方便调用

           

            球体之间的碰撞 显示不出,但是不同物体 应当要旋转  quaternion

            8.GPU要测试每一个物体 之间是否碰撞 ,性能消耗 ,帧率下降

            Gannon.js 有范宽阶段

            例子:当一个球体向某一方向形式,反方向有一堆物体, 它不会去尽心测试是否该物体会碰撞反方向的物体,减少消耗

            NaiveBroadphase-tests every Bodies against every other Bodies

            GridBroadphase -quadrilles the world and only tests Bodies against other  // 网格范宽

            Bodies in the same grid box or the neighbors' grid boxes

            SAPBroadphase ((Sweep And Prune)-tests Bodies on arb!trary axes duringmultiples steps // 清除与清扫 效果更好

            world.allowSleep = true

            加入睡眠属性 :效果:让一些静止不动的物体,让它保持,当运动物体再次碰撞 ,在动起来

            同样可以优化性能问题:解决帧率下降的问题

            同时可以更改睡眠限时

            比如物体静止,过一秒,好的这个物体在睡觉

            9.添加声音

            10.如何移出物体

            11.限制条件

            HingeConstraint -acts like a door hinge.  (铰链约束:像门一样)

            DistanceConstraint -forces the bodies to keep a distance between each other (距离约束)

            LockConstraint -merges the bodies like if they were one piece (锁定约束)

            PointToPointConstraint -glues the bodies to a specific point (对点约束)


     

            12.cannonES  

            由于cannonJS ,不更新 ;因此在它的基础上cannonEs更新,替换掉

            npm uninstall --save cannon

            npm install --save cannon-es@0.15.1

            13. Amon.js

    一、代码

    1. import './style.css'
    2. import * as THREE from 'three'
    3. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
    4. import * as dat from 'dat.gui'
    5. import CANNON from 'cannon'
    6. /**
    7. * Debug
    8. */
    9. const gui = new dat.GUI()
    10. const debugObject = {}
    11. debugObject.createSphere = () =>{
    12. createSphere(Math.random() * 0.5,
    13. {
    14. x: (Math.random() - 0.5) * 3,
    15. y:3,
    16. z:(Math.random() - 0.5) * 3
    17. }
    18. )
    19. }
    20. debugObject.createBox = () =>{
    21. createBox(
    22. Math.random(),
    23. Math.random(),
    24. Math.random(),
    25. {
    26. x: (Math.random() - 0.5) * 3,
    27. y:3,
    28. z:(Math.random() - 0.5) * 3
    29. }
    30. )
    31. }
    32. debugObject.reset = () => {
    33. for(const object of objectsToUpdate){
    34. // remove
    35. object.body.removeEventListener('collide',playHitSound)
    36. world.removeBody(object.body)
    37. // Remove scene
    38. scene.remove(object.mesh)
    39. }
    40. objectsToUpdate.splice(0,objectsToUpdate.length)
    41. }
    42. gui.add(debugObject,'createSphere')
    43. gui.add(debugObject,'createBox')
    44. gui.add(debugObject,'reset')
    45. /**
    46. * Base
    47. */
    48. // Canvas
    49. const canvas = document.querySelector('canvas.webgl')
    50. // Scene
    51. const scene = new THREE.Scene()
    52. /*
    53. * sounds
    54. */
    55. const hitSound = new Audio('/sounds/hit.mp3')
    56. const playHitSound = (collisition) => {
    57. // 加入变量 ,判断碰撞产生的 冲击强度 ,小于一定程度不让播放
    58. // console.log(collisition.contact.getImpactVelocityAlongNormal())
    59. let impactStrength = collisition.contact.getImpactVelocityAlongNormal()
    60. if(impactStrength > 1.5){
    61. hitSound.volume = Math.random() // 让声音产生不同大小
    62. hitSound.currentTime = 0 // 将播放时间重置0,不至于碰撞-》播放结束;碰撞-》播放结束
    63. hitSound.play()
    64. }
    65. }
    66. /**
    67. * Textures
    68. */
    69. const textureLoader = new THREE.TextureLoader()
    70. const cubeTextureLoader = new THREE.CubeTextureLoader()
    71. const environmentMapTexture = cubeTextureLoader.load([
    72. '/textures/environmentMaps/0/px.png',
    73. '/textures/environmentMaps/0/nx.png',
    74. '/textures/environmentMaps/0/py.png',
    75. '/textures/environmentMaps/0/ny.png',
    76. '/textures/environmentMaps/0/pz.png',
    77. '/textures/environmentMaps/0/nz.png'
    78. ])
    79. /*
    80. Physice
    81. */
    82. // World
    83. const world = new CANNON.World()
    84. world.broadphase = new CANNON.SAPBroadphase(world) // 优化,帧率下降;通过范宽阶段,减少GPU为计算每个物体碰撞的计算量
    85. world.allowSleep = true
    86. world.gravity.set(0,-9.82,0)
    87. // materials
    88. const defaultMaterial = new CANNON.Material('default') // 中间的参数其实只是命名 , 球和地板都用这个材质
    89. const defaultContactMaterial = new CANNON.ContactMaterial( // 接触材质
    90. defaultMaterial,
    91. defaultMaterial,
    92. {
    93. friction:0.1, // 摩擦系数
    94. restitution:0.7 //
    95. }
    96. )
    97. world.addContactMaterial(defaultContactMaterial) // 将材质添加物理世界
    98. world.defaultContactMaterial = defaultContactMaterial // 统一 设置材质(当然也可以在body中单独设置)
    99. /*
    100. Floor
    101. */
    102. const floorShape = new CANNON.Plane()
    103. const floorBody = new CANNON.Body()
    104. floorBody.mass = 0
    105. floorBody.addShape(floorShape);
    106. // 设置四元数 旋转
    107. floorBody.quaternion.setFromAxisAngle(
    108. new CANNON.Vec3(-1,0,0),
    109. Math.PI * 0.5
    110. )
    111. world.add(floorBody);
    112. /**
    113. * Floor
    114. */
    115. const floor = new THREE.Mesh(
    116. new THREE.PlaneBufferGeometry(10, 10),
    117. new THREE.MeshStandardMaterial({
    118. color: '#777777',
    119. metalness: 0.3,
    120. roughness: 0.4,
    121. envMap: environmentMapTexture
    122. })
    123. )
    124. floor.receiveShadow = true
    125. floor.rotation.x = - Math.PI * 0.5
    126. scene.add(floor)
    127. /**
    128. * Lights
    129. */
    130. const ambientLight = new THREE.AmbientLight(0xffffff, 0.7)
    131. scene.add(ambientLight)
    132. const directionalLight = new THREE.DirectionalLight(0xffffff, 0.2)
    133. directionalLight.castShadow = true
    134. directionalLight.shadow.mapSize.set(1024, 1024)
    135. directionalLight.shadow.camera.far = 15
    136. directionalLight.shadow.camera.left = - 7
    137. directionalLight.shadow.camera.top = 7
    138. directionalLight.shadow.camera.right = 7
    139. directionalLight.shadow.camera.bottom = - 7
    140. directionalLight.position.set(5, 5, 5)
    141. scene.add(directionalLight)
    142. /**
    143. * Sizes
    144. */
    145. const sizes = {
    146. width: window.innerWidth,
    147. height: window.innerHeight
    148. }
    149. window.addEventListener('resize', () =>
    150. {
    151. // Update sizes
    152. sizes.width = window.innerWidth
    153. sizes.height = window.innerHeight
    154. // Update camera
    155. camera.aspect = sizes.width / sizes.height
    156. camera.updateProjectionMatrix()
    157. // Update renderer
    158. renderer.setSize(sizes.width, sizes.height)
    159. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    160. })
    161. /**
    162. * Camera
    163. */
    164. // Base camera
    165. const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
    166. camera.position.set(- 3, 3, 3)
    167. scene.add(camera)
    168. // Controls
    169. const controls = new OrbitControls(camera, canvas)
    170. controls.enableDamping = true
    171. /**
    172. * Renderer
    173. */
    174. const renderer = new THREE.WebGLRenderer({
    175. canvas: canvas
    176. })
    177. renderer.shadowMap.enabled = true
    178. renderer.shadowMap.type = THREE.PCFSoftShadowMap
    179. renderer.setSize(sizes.width, sizes.height)
    180. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    181. /*
    182. Unites
    183. */
    184. const objectsToUpdate = [];
    185. // sphere
    186. // 将几何体,材质放到外部,避免性能消耗,防止重复创建时,都要创建一次几何体和材质
    187. const sphereGeometry = new THREE.SphereBufferGeometry(1,20,20)
    188. const sphereMaterial = new THREE.MeshStandardMaterial({
    189. metalness:0.3,
    190. roughness:0.4,
    191. envMap:environmentMapTexture
    192. })
    193. const createSphere = (radius,position) =>{
    194. // Three.js mesh
    195. const mesh = new THREE.Mesh(
    196. sphereGeometry,
    197. sphereMaterial
    198. )
    199. mesh.scale.set(radius,radius,radius)
    200. mesh.castShadow = true ; // 投射阴影
    201. mesh.position.copy(position)
    202. scene.add(mesh)
    203. // CANNON.js body
    204. const shape = new CANNON.Sphere(radius)
    205. const body = new CANNON.Body({
    206. mass:1,
    207. position:new CANNON.Vec3(0,3,0),
    208. shape, // 这里注意大小写
    209. material:defaultMaterial
    210. })
    211. body.position.copy(position)
    212. body.addEventListener('collide',playHitSound) // 监听碰撞 collide
    213. world.addBody(body)
    214. // save in objects to update
    215. objectsToUpdate.push({
    216. mesh,
    217. body
    218. })
    219. }
    220. createSphere(0.5,{x:0,y:3,z:0})
    221. //box
    222. const boxGeometry = new THREE.BoxBufferGeometry(1,1,1) // 宽度,深度,高度
    223. const boxMaterial = new THREE.MeshStandardMaterial({
    224. metalness:0.3,
    225. roughness:0.4,
    226. envMap:environmentMapTexture
    227. })
    228. const createBox = (width,height,depth,position) =>{
    229. // Three.js mesh
    230. const mesh = new THREE.Mesh(
    231. boxGeometry,
    232. boxMaterial
    233. )
    234. mesh.scale.set(width,height,depth)
    235. mesh.castShadow = true ; // 投射阴影
    236. mesh.position.copy(position)
    237. scene.add(mesh)
    238. // CANNON.js body
    239. const shape = new CANNON.Box(new CANNON.Vec3(width * 0.5,height * 0.5,depth * 0.5))
    240. const body = new CANNON.Body({
    241. mass:1,
    242. position:new CANNON.Vec3(0,3,0),
    243. shape, // 这里注意大小写
    244. material:defaultMaterial
    245. })
    246. body.position.copy(position)
    247. body.addEventListener('collide',playHitSound) // 监听碰撞 collide
    248. world.addBody(body)
    249. // save in objects to update
    250. objectsToUpdate.push({
    251. mesh,
    252. body
    253. })
    254. }
    255. /**
    256. * Animate
    257. */
    258. const clock = new THREE.Clock()
    259. let oldElapsedTime = 0 // 创建变量 标识上一刻度时间
    260. const tick = () =>
    261. {
    262. const elapsedTime = clock.getElapsedTime()
    263. const deltaTime = elapsedTime - oldElapsedTime
    264. oldElapsedTime = elapsedTime
    265. // Update physics world
    266. world.step(1/60,deltaTime,3) // 每秒60帧率 , 上一刻度用来多少时间 ,
    267. for(const objects of objectsToUpdate){
    268. objects.mesh.position.copy(objects.body.position)
    269. objects.mesh.quaternion.copy(objects.body.quaternion)
    270. }
    271. // Update controls
    272. controls.update()
    273. // Render
    274. renderer.render(scene, camera)
    275. // Call tick again on the next frame
    276. window.requestAnimationFrame(tick)
    277. }
    278. tick()

    二、知识点 

    1.图形

    2.three.js 和cannon.js

      1.下载,引入

    npm install --save cannon

     import CANNON from 'cannon'

         由于cannonJS ,不更新 ;因此在它的基础上cannonEs更新,替换掉

            npm uninstall --save cannon

            npm install --save cannon-es@0.15.1

    效果:

    Physice


    总结

    学习,学呗!

  • 相关阅读:
    高等数值计算方法学习笔记第4章【数值积分(数值微分)】
    Vuex的使用,详细易懂
    uniapp滚动页面改变背景颜色
    9.2 链表静态添加和遍历
    面向对象编程(Object-Oriented Programming,OOP)编程思想
    简单汇编教程10 数组
    excel表格乱码怎么解决呢?
    jar包和war包的区别
    点云从入门到精通技术详解100篇-基于点云数据的奶牛体型评定指标自动测量关键技术研究
    verilog 每日一练- 移位寄存器
  • 原文地址:https://blog.csdn.net/weixin_41722662/article/details/140378173