Three.js--》探寻Cannon.js构建震撼的3D物理交互体验(一)

我们用three.js可以绘制出各种酷炫的画面,但是当我们想要一个更加真实的物理效果的话,这个时候我们就需要一个物理的库,接下来我们就讲解一下今天要学习的canon,它可以给我们提供一个更加真实的物理效果,像物体的张力、摩擦力、拉伸、反弹等等各种真实的物理效果。该库都能够有一个非常好的模拟。

PS:目前博主在一家互联网公司工作,该公司的编码风格是vue+tsx,所以接下来的项目以该编码风格进行举例,详细了解参考我之前的文章:地址 。

目录

canon基本使用

基础碰撞使用

材质与摩擦系数设置

弹性与接触材质设置

碰撞与碰撞组


canon基本使用

Cannon 是一种轻量级的 JavaScript 3D 物理引擎,用于实现虚拟世界中的物理模拟和交互。它提供了一套强大的功能,能够处理刚体碰撞、力学模拟、约束系统等物理效果,使开发者能够在 Web 应用程序和游戏中实现逼真的物理行为。

Cannon的官网:地址 ,提供了一系列关于物理运动在three世界的实现,实现案例 的效果非常直观,展示了物理运动的魅力,如下:

接下来我们在three.js的vue项目中使用Cannon,终端执行如下命令安装,具体参考:官网

npm i cannon-es

接下来我们通过tsx风格语法撰写three基础项目实现:

import { defineComponent } from "vue";
import * as THREE from 'three'
import { OrbitControls }  from 'three/examples/jsm/controls/OrbitControls.js'
import * as CANNON from 'cannon-es'
import './index.scss'
import { div } from "three/examples/jsm/nodes/Nodes.js";

export default defineComponent({
    setup() {
        // 初始化物理世界
        const world = new CANNON.World()
        // 初始化物理世界的重力
        world.gravity.set(0, -9.82, 0)
        
        // 初始化3D世界
        const scene = new THREE.Scene()
        // 初始化相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        camera.position.z = 3
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(window.innerWidth, window.innerHeight)
        document.body.appendChild(renderer.domElement)
        // 初始化控制器
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true

        // 渲染
        let clock = new THREE.Clock()
        const animate = () => {
            let delta = clock.getDelta()
            controls.update()
            renderer.render(scene, camera)
            requestAnimationFrame(animate)
        }
        animate()
        return () => {
            <div></div>
        }
    }
})

接下来我们需要给场景添加一些物体,如下:

// 创建一个物理球体,半径为0.5
const sphereShape = new CANNON.Sphere(0.5)
// 创建一个刚体
const sphereBody = new CANNON.Body({
    mass: 1,
    shape: sphereShape,
    position: new CANNON.Vec3(0, 5, 0)
})
// 将刚体添加到物理世界中
world.addBody(sphereBody)
// 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个球
const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32) // 创建一个几何体
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) // 创建一个球体材质
const shpereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial) // 创建一个球体网格
// 将网格添加到3D场景中
scene.add(shpereMesh)

打开控制台,可以看到我们的球体已经显示出来了:

接下来我们要把物理世界的球体给到我们three渲染出来的球体,让两者开始关联起来。在每一帧中,根据物理引擎模拟的结果来更新 Three.js 场景中球体网格的位置和旋转状态,从而实现了基于物理引擎的球体模拟效果,如下:

得到的结果如下:

基础碰撞使用

上面我们实现了一个物体的自由下落,接下来我们实现物体与平面的碰撞效果。如下添加平面:

// 创建一个物理世界的平面
const planeShape = new CANNON.Plane()
// 创建一个刚体
const planeBody = new CANNON.Body({
    mass: 0, // 设置质量为0,不受碰撞的影响
    shape: planeShape,
    position: new CANNON.Vec3(0, 0, 0)
})
// 设置刚体旋转(设置旋转X轴)
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
// 将刚体添加到物理世界当中
world.addBody(planeBody)
// 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个平面
const planeGeometry = new THREE.PlaneGeometry(10, 10) // 因为渲染的东西不是无限衍生,这里给10x10
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
// 旋转平面90度让其平铺
planeMesh.rotation.x = -Math.PI / 2
// 将网格添加到3D场景当中
scene.add(planeMesh)

当然除了我们设置平面质量为0之外,我们也可以设置平面为静态效果,也不受碰撞影响:

最终得到的效果如下:

那我们让物理世界和渲染世界的平面倾斜度加上0.1,小球是否会滑落而掉下去呢?测试一下:

得到的效果如下,可见小球是不会掉落下去的,因为物理世界的平面是无限衍生的,即使渲染世界的平面有限,小球仍然会走物理世界的规律,如下:

如果我们想在物理世界有一个有限大的平面的话, 我们可以通过构建一个立方体,然后把立方体压扁形成一个平面来使用,因为立方体已经有高度了,所以我们也不需要在旋转90度了,稍微给点倾斜度0.1即可,代码如下:

得到的效果如下,可见到我们的小球已经实现了掉落的效果:

上面两标题的案例代码如下:

import { defineComponent } from "vue";
import * as THREE from 'three'
import { OrbitControls }  from 'three/examples/jsm/controls/OrbitControls.js'
import * as CANNON from 'cannon-es'
import './index.scss'

export default defineComponent({
    setup() {
        // 初始化物理世界
        const world = new CANNON.World()
        // 初始化物理世界的重力
        world.gravity.set(0, -9.82, 0)
        
        // 初始化3D世界
        const scene = new THREE.Scene()
        // 初始化相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        camera.position.z = 3
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(window.innerWidth, window.innerHeight)
        document.body.appendChild(renderer.domElement)
        // 初始化控制器
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true

        // 创建一个物理球体,半径为0.5
        const sphereShape = new CANNON.Sphere(0.5)
        // 创建一个刚体
        const sphereBody = new CANNON.Body({
            mass: 1,
            shape: sphereShape,
            position: new CANNON.Vec3(0, 5, 0)
        })
        // 将刚体添加到物理世界中
        world.addBody(sphereBody)
        // 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个球
        const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32) // 创建一个几何体
        const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) // 创建一个球体材质
        const shpereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial) // 创建一个球体网格
        // 将网格添加到3D场景中
        scene.add(shpereMesh)

        // 创建一个物理世界的平面
        // const planeShape = new CANNON.Plane()
        const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5))
        // 创建一个刚体
        const planeBody = new CANNON.Body({
            // mass: 0, // 设置质量为0,不受碰撞的影响
            shape: planeShape,
            position: new CANNON.Vec3(0, 0, 0),
            type: CANNON.Body.STATIC // 设置物体为静态,不受碰撞的影响
        })
        // 设置刚体旋转(设置旋转X轴)
        planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1)
        // 将刚体添加到物理世界当中
        world.addBody(planeBody)
        // 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个平面
        // const planeGeometry = new THREE.PlaneGeometry(10, 10) // 因为渲染的东西不是无限衍生,这里给10x10
        const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10)
        // 创建一个平面材质
        const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
        // 创建一个平面网格
        const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
        // 旋转平面90度让其平铺
        planeMesh.rotation.x = 0.1
        // 将网格添加到3D场景当中
        scene.add(planeMesh)
        
        // 渲染
        let clock = new THREE.Clock()
        const animate = () => {
            // 获取了两次渲染之间的时间差,通常用于控制动画和物理模拟。
            let delta = clock.getDelta()
            // 使用时间差来推进物理世界的模拟
            world.step(delta)
            // 更新球体网格的位置和旋转
            // 将物理引擎中球体的位置赋值给 Three.js 中球体网格(shpereMesh)的位置,从而将物理模拟的结果更新到可视化场景中。
            shpereMesh.position.copy(sphereBody.position)
            // 将物理引擎中球体的旋转状态赋值给 Three.js 中球体网格(shpereMesh)的旋转状态,确保网格的旋转与物理模拟一致。
            shpereMesh.quaternion.copy(sphereBody.quaternion)
            controls.update()
            renderer.render(scene, camera)
            requestAnimationFrame(animate)
        }
        animate()

        return () => {
            <div></div>
        }
    }
})

材质与摩擦系数设置

cannon的材质可以模拟我们现实生活当中的物理的效果,比如说我们可以设置它的摩擦系数,弹性系数来实现我们这个物体的滑动的有摩擦的效果。借助上面的案例,我们将球体换成立方体,因为要创建多个立方体,这里我们设置一个变量用于存储

// 创建网格数组
let phyMeshes: any[] = [] // 物理世界
let meshes: any[] = [] // 渲染世界

// 创建物理立方体
const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))
// 设置立方体的材质
const boxMaterialCon = new CANNON.Material("boxMaterial")
// 创建一个刚体
const boxBody = new CANNON.Body({
    shape: boxShape,
    position: new CANNON.Vec3(0, 15, 0),
    mass: 1,
    material: boxMaterialCon
})
// 将刚体添加到物理世界当中
world.addBody(boxBody)
phyMeshes.push(boxBody)
// 创建立方体几何体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
// 创建立方体材质
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
// 创建立方体网格
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
// 将网格添加到3D场景当中
scene.add(boxMesh)
meshes.push(boxMesh)

在渲染函数处,我们变量数组来推进物理世界模拟:

最终得到是效果如下:

接下来我们添加第二个物体,将第二个物体的摩擦系数设置为0,第一个物体和平面的摩擦系数设置为0.7,代码如下:

// 创建网格数组
let phyMeshes: any[] = [] // 物理世界
let meshes: any[] = [] // 渲染世界

// 创建物理立方体
const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))
// 设置立方体的材质
const boxMaterialCon = new CANNON.Material("boxMaterial")
boxMaterialCon.friction = 0.7
// 创建一个刚体
const boxBody = new CANNON.Body({
    shape: boxShape,
    position: new CANNON.Vec3(0, 15, 0),
    mass: 1,
    material: boxMaterialCon
})
// 将刚体添加到物理世界当中
world.addBody(boxBody)
phyMeshes.push(boxBody)
// 创建立方体几何体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
// 创建立方体材质
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
// 创建立方体网格
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
// 将网格添加到3D场景当中
scene.add(boxMesh)
meshes.push(boxMesh)

// 创建第二个物理立方体(使用第一个物理立方体的内容,材质不同)
const boxSlipperyMaterial = new CANNON.Material("boxSlipperyMaterial")
boxSlipperyMaterial.friction = 0 // 摩擦系数为0
// 创建刚体
const boxBody2 = new CANNON.Body({
    shape: boxShape,
    position: new CANNON.Vec3(1, 5, 0), // 区别于第一个物体,位置改变一下
    mass: 1,
    material: boxSlipperyMaterial
})
// 将刚体添加到物理世界当中
world.addBody(boxBody2)
phyMeshes.push(boxBody2)
// 创建立方体几何体(使用第一个物体的内容)
const boxMesh2 = new THREE.Mesh(boxGeometry, boxMaterial)
// 将网格添加到3D场景当中
scene.add(boxMesh2)
meshes.push(boxMesh2)

// 创建一个物理世界的平面
// const planeShape = new CANNON.Plane()
const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5))
// 创建一个刚体
const planeBody = new CANNON.Body({
    // mass: 0, // 设置质量为0,不受碰撞的影响
    shape: planeShape,
    position: new CANNON.Vec3(0, 0, 0),
    type: CANNON.Body.STATIC, // 设置物体为静态,不受碰撞的影响
    material: boxMaterialCon
})
// 设置刚体旋转(设置旋转X轴)
planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1)
// 将刚体添加到物理世界当中
world.addBody(planeBody)
// 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个平面
// const planeGeometry = new THREE.PlaneGeometry(10, 10) // 因为渲染的东西不是无限衍生,这里给10x10
const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10)
// 创建一个平面材质
const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// 创建一个平面网格
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
// 旋转平面90度让其平铺
planeMesh.rotation.x = 0.1
// 将网格添加到3D场景当中
scene.add(planeMesh)

最终得到的效果如下,可以看到我们设置的第二个物体因为很光滑,所以很容易就滑落下去:

弹性与接触材质设置

上文我们介绍了摩擦效果的操作,接下来我们继续开始讲解物体的弹性操作,我们根据上文的代码,再创建第三个立方体,然后给该立方体添加弹性系数

// 创建第三个物理立方体(使用第一个物理立方体的内容,材质不同)
const boxBouncyMaterial = new CANNON.Material("boxBouncyMaterial")
boxBouncyMaterial.friction = 0.1
boxBouncyMaterial.restitution = 1 // 设置弹性系数为1
// 创建刚体
const boxBody3 = new CANNON.Body({
    shape: boxShape,
    mass: 1,
    position: new CANNON.Vec3(2, 5, 3),
    material: boxBouncyMaterial
})
// 将刚体添加到物理世界当中
world.addBody(boxBody3)
phyMeshes.push(boxBody3)
// 创建几何体(使用第一个立方体的内容以及材质)
const boxMesh3 = new THREE.Mesh(boxGeometry, boxMaterial) // 添加网格
// 将网格添加到3D场景当中
scene.add(boxMesh3)
meshes.push(boxMesh3)

给立方体设置弹性系数之后,如果我们想让弹性效果奏效的话,我们也需要给平面网格设置相同的弹性系数,因为平面网格使用的材质是第一个立方体的材质,所以我们只要给第一个立方体设置弹性系数即可,如下:

最终得到的效果如下,可以看到设置高度高的物体,从高处下落反弹的效果是很直观的:

当然我们也没有必要单独设置一下立方体和平面的弹性和摩擦系数,我们也可以通过接触材质的系数设置两个材质之间的一个弹性和摩擦系数,来实现相应的效果,如下:

// 创建第三个物理立方体(使用第一个物理立方体的内容,材质不同)
const boxBouncyMaterial = new CANNON.Material("boxBouncyMaterial")
// boxBouncyMaterial.friction = 0.1
// boxBouncyMaterial.restitution = 1 // 设置弹性系数为1
// 创建刚体
const boxBody3 = new CANNON.Body({
    shape: boxShape,
    mass: 1,
    position: new CANNON.Vec3(2, 5, 3),
    material: boxBouncyMaterial
})
// 将刚体添加到物理世界当中
world.addBody(boxBody3)
phyMeshes.push(boxBody3)
// 创建几何体(使用第一个立方体的内容以及材质)
const boxMesh3 = new THREE.Mesh(boxGeometry, boxMaterial) // 添加网格
// 将网格添加到3D场景当中
scene.add(boxMesh3)
meshes.push(boxMesh3)
// 定义接触材质
const material3toplane = new CANNON.ContactMaterial(
    boxMaterialCon,
    boxBouncyMaterial,
    {
        friction: 0,
        restitution:  1
    }
)
// 将接触材质添加到物理世界当中
world.addContactMaterial(material3toplane)

最终呈现的效果依然很明显:

碰撞与碰撞组

Cannon中的碰撞指的是游戏开发中物体之间的相互作用,通常包括物体之间的碰撞检测和碰撞响应两个部分。碰撞检测用于判断物体是否发生了碰撞,而碰撞响应则是在发生碰撞时对物体进行相应的处理,比如改变物体的速度、方向等。如下我们设置代码来实现:

依次创建立方体、球体、圆柱体到场景当中,举例代码如下:

接下来我们给创建到场景的立方体添加一个初速度使其运动来碰撞另外两个物体,如下:

这里给出完整的代码来给大家进行学习:

import { defineComponent } from "vue";
import * as THREE from 'three'
import { OrbitControls }  from 'three/examples/jsm/controls/OrbitControls.js'
import * as CANNON from 'cannon-es'
import './index.scss'

export default defineComponent({
    setup() {
        // 初始化物理世界
        const world = new CANNON.World()
        // 初始化物理世界的重力
        world.gravity.set(0, -9.82, 0)
        
        // 初始化3D世界
        const scene = new THREE.Scene()
        // 初始化相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        camera.position.z = 8
        camera.position.y = 5
        camera.position.x = 2
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(window.innerWidth, window.innerHeight)
        document.body.appendChild(renderer.domElement)
        // 初始化控制器
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true

        // 创建网格数组
        let phyMeshes: any[] = [] // 物理世界
        let meshes: any[] = [] // 渲染世界

        // 创建物理立方体
        const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))
        // 设置立方体的材质
        const boxMaterialCon = new CANNON.Material("boxMaterial")
        boxMaterialCon.friction = 0
        // 创建一个刚体
        const boxBody = new CANNON.Body({
            shape: boxShape,
            position: new CANNON.Vec3(2, 0.8, 0),
            mass: 1,
            material: boxMaterialCon
        })
        // 将刚体添加到物理世界当中
        world.addBody(boxBody)
        phyMeshes.push(boxBody)
        // 创建立方体几何体
        const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
        // 创建立方体材质
        const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
        // 创建立方体网格
        const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
        // 将网格添加到3D场景当中
        scene.add(boxMesh)
        meshes.push(boxMesh)

        // 创建物理球
        const spereShape = new CANNON.Sphere(0.5)
        // 创建一个刚体
        const sphereBody = new CANNON.Body({
            shape: spereShape,
            position: new CANNON.Vec3(0, 0.8, 0),
            mass: 1,
            material: boxMaterialCon
        })
        // 将刚体添加到物理世界当中
        world.addBody(sphereBody)
        phyMeshes.push(sphereBody)
        // 创建球的几何体
        const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32)
        // 创建球的材质
        const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff })
        // 创建球网格
        const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial)
        // 将网格添加到3D场景当中
        scene.add(sphereMesh)
        meshes.push(sphereMesh)

        // 创建物理圆柱体
        const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1, 32)
        // 创建一个刚体
        const cylinderBody = new CANNON.Body({
            shape: cylinderShape,
            position: new CANNON.Vec3(-2, 0.8, 0),
            mass: 1,
            material: boxMaterialCon
        })
        // 将刚体添加到物理世界当中
        world.addBody(cylinderBody)
        phyMeshes.push(cylinderBody)
        // 创建圆柱体几何体
        const cylinderGeometry = new THREE.CylinderGeometry(0.5 ,0.5, 1, 32)
        // 创建圆柱体材质
        const cylinderMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 })
        // 创建圆柱体网格
        const cylinderMesh = new THREE.Mesh(cylinderGeometry, cylinderMaterial)
        // 将网格添加到3D场景当中
        scene.add(cylinderMesh)
        meshes.push(cylinderMesh)

        // 创建一个物理世界的平面
        // const planeShape = new CANNON.Plane()
        const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5))
        // 创建一个刚体
        const planeBody = new CANNON.Body({
            // mass: 0, // 设置质量为0,不受碰撞的影响
            shape: planeShape,
            position: new CANNON.Vec3(0, 0, 0),
            type: CANNON.Body.STATIC, // 设置物体为静态,不受碰撞的影响
            material: boxMaterialCon
        })
        // 设置刚体旋转(设置旋转X轴)
        // planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1)
        // 将刚体添加到物理世界当中
        world.addBody(planeBody)
        // 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个平面
        // const planeGeometry = new THREE.PlaneGeometry(10, 10) // 因为渲染的东西不是无限衍生,这里给10x10
        const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10)
        // 创建一个平面材质
        const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
        // 创建一个平面网格
        const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
        // 旋转平面90度让其平铺
        // planeMesh.rotation.x = 0.1
        // 将网格添加到3D场景当中
        scene.add(planeMesh)

        // 设置立方体的初始速度
        boxBody.velocity.set(-2, 0, 0)
        
        // 渲染
        let clock = new THREE.Clock()
        const animate = () => {
            // 获取了两次渲染之间的时间差,通常用于控制动画和物理模拟。
            let delta = clock.getDelta()
            world.step(delta)
            // 使用时间差来推进物理世界的模拟
            for(let i = 0; i < phyMeshes.length; i++) {
                meshes[i].position.copy(phyMeshes[i].position)
                meshes[i].quaternion.copy(phyMeshes[i].quaternion)
            }
            controls.update()
            renderer.render(scene, camera)
            requestAnimationFrame(animate)
        }
        animate()

        return () => {
            <div></div>
        }
    }
})

接下来实现碰撞组,碰撞组是为了更高效地管理和处理碰撞而引入的概念。通过将具有相似碰撞特性的物体分组,可以在碰撞检测和碰撞响应时只考虑同一组内的物体之间的碰撞,从而减少不必要的计算量,提高游戏的性能和效率。代码如下:

我们设置立方体为组1,然后碰撞掩码就是能够和谁发生碰撞,我们设置立方体可以和所有物体碰撞:

在球体的分组当中,我们设置碰撞掩码如下,可以看到我们的球不能碰撞圆柱体:

最终呈现的效果如下:

给出案例的完整代码供大家学习:

import { defineComponent } from "vue";
import * as THREE from 'three'
import { OrbitControls }  from 'three/examples/jsm/controls/OrbitControls.js'
import * as CANNON from 'cannon-es'
import './index.scss'

export default defineComponent({
    setup() {
        // 初始化物理世界
        const world = new CANNON.World()
        // 初始化物理世界的重力
        world.gravity.set(0, -9.82, 0)
        
        // 初始化3D世界
        const scene = new THREE.Scene()
        // 初始化相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        camera.position.z = 8
        camera.position.y = 5
        camera.position.x = 2
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(window.innerWidth, window.innerHeight)
        document.body.appendChild(renderer.domElement)
        // 初始化控制器
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true

        // 创建网格数组
        let phyMeshes: any[] = [] // 物理世界
        let meshes: any[] = [] // 渲染世界

        // 设置碰撞组,数值要用2的幂
        const GROUP1 = 1 // 分组立方体
        const GROUP2 = 2 // 分组球体
        const GROUP3 = 4 // 分组圆柱体
        const GROUP4 = 8 // 分组平面

        // 创建物理立方体
        const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))
        // 设置立方体的材质
        const boxMaterialCon = new CANNON.Material("boxMaterial")
        boxMaterialCon.friction = 0
        // 创建一个刚体
        const boxBody = new CANNON.Body({
            shape: boxShape,
            position: new CANNON.Vec3(2, 0.8, 0),
            mass: 1,
            material: boxMaterialCon,
            collisionFilterGroup: GROUP1, // 设置碰撞组
            collisionFilterMask: GROUP2 | GROUP3 | GROUP4, // 碰撞掩码,可以和二组和三、四组碰撞
        })
        // 将刚体添加到物理世界当中
        world.addBody(boxBody)
        phyMeshes.push(boxBody)
        // 创建立方体几何体
        const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
        // 创建立方体材质
        const boxMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
        // 创建立方体网格
        const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
        // 将网格添加到3D场景当中
        scene.add(boxMesh)
        meshes.push(boxMesh)

        // 创建物理球
        const spereShape = new CANNON.Sphere(0.5)
        // 创建一个刚体
        const sphereBody = new CANNON.Body({
            shape: spereShape,
            position: new CANNON.Vec3(0, 0.8, 0),
            mass: 1,
            material: boxMaterialCon,
            collisionFilterGroup: GROUP2, // 设置碰撞组
            collisionFilterMask: GROUP1 | GROUP4, // 碰撞掩码,可以和一、四组碰撞
        })
        // 将刚体添加到物理世界当中
        world.addBody(sphereBody)
        phyMeshes.push(sphereBody)
        // 创建球的几何体
        const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32)
        // 创建球的材质
        const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff })
        // 创建球网格
        const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial)
        // 将网格添加到3D场景当中
        scene.add(sphereMesh)
        meshes.push(sphereMesh)

        // 创建物理圆柱体
        const cylinderShape = new CANNON.Cylinder(0.5, 0.5, 1, 32)
        // 创建一个刚体
        const cylinderBody = new CANNON.Body({
            shape: cylinderShape,
            position: new CANNON.Vec3(-2, 0.8, 0),
            mass: 1,
            material: boxMaterialCon,
            collisionFilterGroup: GROUP3, // 设置碰撞组
            collisionFilterMask: GROUP1 | GROUP4, // 碰撞掩码,可以和一、四组碰撞
        })
        // 将刚体添加到物理世界当中
        world.addBody(cylinderBody)
        phyMeshes.push(cylinderBody)
        // 创建圆柱体几何体
        const cylinderGeometry = new THREE.CylinderGeometry(0.5 ,0.5, 1, 32)
        // 创建圆柱体材质
        const cylinderMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 })
        // 创建圆柱体网格
        const cylinderMesh = new THREE.Mesh(cylinderGeometry, cylinderMaterial)
        // 将网格添加到3D场景当中
        scene.add(cylinderMesh)
        meshes.push(cylinderMesh)

        // 创建一个物理世界的平面
        // const planeShape = new CANNON.Plane()
        const planeShape = new CANNON.Box(new CANNON.Vec3(5, 0.1, 5))
        // 创建一个刚体
        const planeBody = new CANNON.Body({
            // mass: 0, // 设置质量为0,不受碰撞的影响
            shape: planeShape,
            position: new CANNON.Vec3(0, 0.1, 0),
            type: CANNON.Body.STATIC, // 设置物体为静态,不受碰撞的影响
            material: boxMaterialCon,
            collisionFilterGroup: GROUP4, // 设置碰撞组
            collisionFilterMask: GROUP1 | GROUP2 | GROUP3, // 碰撞掩码,可以和一组、二组和三组碰撞
        })
        // 设置刚体旋转(设置旋转X轴)
        // planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), 0.1)
        // 将刚体添加到物理世界当中
        world.addBody(planeBody)
        // 物理世界创建的东西不显示,所以我们要再通过three.js再创建一个平面
        // const planeGeometry = new THREE.PlaneGeometry(10, 10) // 因为渲染的东西不是无限衍生,这里给10x10
        const planeGeometry = new THREE.BoxGeometry(10, 0.2, 10)
        // 创建一个平面材质
        const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
        // 创建一个平面网格
        const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
        // 旋转平面90度让其平铺
        // planeMesh.rotation.x = 0.1
        // 将网格添加到3D场景当中
        scene.add(planeMesh)

        // 设置立方体的初始速度
        boxBody.velocity.set(-2, 0, 0)

        
        // 渲染
        let clock = new THREE.Clock()
        const animate = () => {
            // 获取了两次渲染之间的时间差,通常用于控制动画和物理模拟。
            let delta = clock.getDelta()
            world.step(delta)
            // 使用时间差来推进物理世界的模拟
            for(let i = 0; i < phyMeshes.length; i++) {
                meshes[i].position.copy(phyMeshes[i].position)
                meshes[i].quaternion.copy(phyMeshes[i].quaternion)
            }
            controls.update()
            renderer.render(scene, camera)
            requestAnimationFrame(animate)
        }
        animate()

        return () => {
            <div></div>
        }
    }
})

本篇文章对canon的学习暂时结束,下篇文章将对canon更加深入讲解!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/428693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux安装mysql5.7

linux安装mysql5.7 一、下载mysql5.7二、解压包介绍三、上传包到linux四、卸载mariadb五、安装mysql六、修改权限七、启动mysql八、使用过navicat创作不易&#xff0c;笔记不易&#xff0c;如觉不错&#xff0c;请三连&#xff0c;谢谢~~ 一、下载mysql5.7 去mysql官方下载&am…

kafka启动命令、查看topic命令、查看消息内容命令

kafka启动命令 cd /opt/kafka/kafka_2.12-3.5.1/bin ./kafka-server-start.sh ../config/server.properties Windows环境下用kafka Tool 连不上虚拟机的broker报了unable to connect broker 0&#xff0c; 但是zookeeper可以连接上 server.properties的listeners改为listene…

数据结构从入门到精通——链表

链表 前言一、链表1.1 链表的概念及结构1.2 链表的分类1.3 链表的实现1.4 链表面试题1.5 双向链表的实现 二、顺序表和链表的区别三、单项链表实现具体代码text.htext.cmain.c单链表的打印空间的开辟链表的头插、尾插链表的头删、尾删链表中元素的查找链表在指定位置之前、之后…

力扣 分割回文串

输出的是不同的分割方案 class Solution { public:vector<vector<bool>>flag;vector<string>ans;vector<vector<string>>nums;void dfs(string &s,int i){int ns.size();if(in){i表示s长度&#xff0c;等于即全部分割完毕nums.push_back(ans…

大文件传输之udp收发包错误如何解决

数据传输的速度和稳定性对于企业运营至关重要。UDP&#xff08;用户数据报协议&#xff09;作为一种无连接的网络协议&#xff0c;以其高效的数据传输能力在实时应用中得到了广泛应用。然而&#xff0c;UDP的不可靠性也带来了收发包错误的问题&#xff0c;这在需要高数据完整性…

【MGR】MySQL Group Replication快速开始

目录 17.2 Getting Started 17.2.1 Deploying Group Replication in Single-Primary Mode 17.2.1.1 Deploying Instances for Group Replication 17.2.1.2 Configuring an Instance for Group Replication Storage Engines Replication Framework Group Replication Sett…

【好书推荐-第七期】《RTC程序设计:实时音视频权威指南》(音视频开发必看!)

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;洲与AI。 &#x1f388; 本文专栏&#xff1a;本文收录…

物联网边缘计算云边协同

文章目录 一、物联网云边协同1.IoT云边协同设计2.物联网平台设计3.物联网平台实现 二、部署环境1.节点配置2.版本信息 三、IoT云边协同部署1.部署Kubernetes集群2.部署KubeEdge3.部署ThingsBoard集群4.部署Node-RED边缘网关4.1.边缘网关功能4.2.部署EMQX4.2.部署Node-RED 5.配置…

【sgCollapseBtn】自定义组件:底部折叠/展开按钮

特性&#xff1a; 支持自定义折叠状态支持自定义标签名称 sgCollapseBtn源码 <template><div :class"$options.name" click"show !show" :placement"placement"><div class"collapse-btns"><div class"c…

YOLOv9独家原创改进|加入幽灵卷积Ghost Convolution模块,轻量化!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、论文摘要 由于内存和计算资源有限&#xff0c;在嵌入式设备上部署卷积神经网络是困难的。特征图中的冗余是那些成功的细胞神经网络的一个重要特征…

Linux基础命令[10]-cmp

文章目录 1. cmp 命令说明2. cmp 命令语法3. cmp 命令示例3.1 不加参数3.2 -b&#xff08;显示不同的字节&#xff09;3.3 -i&#xff08;跳过字节&#xff09;3.4 -l&#xff08;显示所有不同&#xff09;3.5 -n&#xff08;比较n个字节&#xff09;3.6 -s&#xff08;不显示信…

深圳牵头打造鸿蒙原生应用软件生态 | 百能云芯

深圳市工业和信息化局、深圳市政务服务和数据管理局于3月3日联合印发了《深圳市支持开源鸿蒙原生应用发展2024年行动计划》。这一计划旨在通过政策引导、市场推动、社会协同的方式&#xff0c;将深圳打造成一个鸿蒙原生应用软件生态的中心&#xff0c;推动鸿蒙系统在当地的发展…

Spring Boot2.2.4版本启动项目时,访问登录接口显示页面不存在

问题触发场景&#xff1a;IDEA 2023.3.4 SpringBoot 2.2.4 上面4张图片分别是项目结构、Spring Boot启动配置、SpringMVC配置和页面展示在项目中存放的位置&#xff0c;表面上看上去没有太大问题&#xff0c;项目应该会达到预期结果&#xff0c;但是bug总是在不经意间出现&…

HarmonyOS端云体化开发—创建端云一体化开发工程

云开发工程模板 DevEco Studio目前提供了两种云开发工程模板&#xff1a;通用云开发模板和商城模板。您可根据工程向导轻松创建端云一体化开发工程&#xff0c;并自动生成对应的代码和资源模板。在创建端云一体化开发工程前&#xff0c;请提前了解云开发工程模板的相关信息。 …

基于 Vue3打造前台+中台通用提效解决方案(下)

47、通用组件 - 倒计时组件 特惠部分存在一个倒计时的功能,所以我们需要先处理对应的倒计时模块,并把它处理成一个通用组件。 那么对于倒计时模块我们又应该如何进行处理呢? 所谓倒计时,其实更多的是一个时间的处理,那么对于时间的处理,此时我们就需要使用到一个第三方…

Kubernetes(k8s第一部分)

这个技术一定会成为以后企业技术的标准 尙硅谷的王阳这个部分这个讲的特别好&#xff0c;可以自己去看 1&#xff0c;发展经历 阿里云iaas Infrastructure as a Service 平台及服务paas新浪云 platform as a service(号称免运维)&#xff08;docker是下一代的标准&#x…

Docker镜像操作介绍

一、镜像操作 镜像的操作可分为&#xff1a; 拉取镜像&#xff1a;拉取远程仓库的镜像到本地 docker pull重命名镜像&#xff1a;使用docker tag 命令重命名镜像查看镜像&#xff1a;使用docker image ls 或者 docker images命令查看本地已经存在的镜像删除镜像&#xff1a;删…

IP-guard邮件管控再升级,记录屏幕画面,智能阻断泄密邮件

邮件是工作沟通以及文件传输的重要工具,却也成为了信息泄露的常见渠道。员工通过邮件对外发送了什么内容,是否含有敏感信息都无从得知,机密通过邮件渠道外泄也难以制止。想要防止企业的重要信息通过邮件方式泄露,我们不仅需要通过技术措施对外发邮件的行为进行规范,也要对…

(学习日记)2024.03.01:UCOSIII第三节 + 函数指针 (持续更新文件结构)

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

Oracle定时任务和存储过程

--1.声明定时任务 DECLAREjob NUMBER; BIGIN dbms_job.sumit(job, --任务ID,系统定义的test_prcedure(19)&#xff0c;--调用存储过程&#xff1f;to_date(20240305 02:00&#xff0c;yyyymmdd hh24:mi) --任务开始时间sysdate1/(24*60) --任务执行周期 [每分钟执行…