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

news2024/9/22 1:36:06

我们用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.coloradmin.cn/o/1488909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何把chatgpt-on-wechat 与RWKV-Runner结合打造本地微信chatgpt机器人

环境&#xff1a; Win10 11代i7 64G内存 500G硬盘 chatgpt-on-wechat RWKV-Runner 问题描述&#xff1a; 如何把chatgpt-on-wechat 与RWKV-Runner结合打造本地微信chatgpt机器人 解决方案&#xff1a; chatgpt-on-wechat项目 有4种运行程序的方式供你选择&#xff1a; …

国科大计算机网络实验 HTTP服务器

UCAS_CN_LAB HTTP服务器实验 实验要求 •实现&#xff1a;使用C语言实现最简单的HTTP服务器 •同时支持HTTP&#xff08;80端口&#xff09;和HTTPS&#xff08;443端口&#xff09; •使用两个线程分别监听各自端口 •只需支持GET方法&#xff0c;解析请求报文&#xff…

多点通信与域套接字:2024/3/4

作业1&#xff1a;广播 发送端&#xff1a; #include <myhead.h> int main(int argc, const char *argv[]) {//1.创建套接字int sfdsocket(AF_INET,SOCK_DGRAM,0);if(sfd-1){perror("socket error");return -1;}printf("sfd%d\n",sfd);//2.设置当前…

Android res/values/locale_config.xml文件

Android res/values/locale_config.xml文件 各个国家/地区在android系统里面的缩写代码。最典型的用途是本地化。 <?xml version"1.0" encoding"utf-8"?> <!-- Copyright (C) 2015 The Android Open Source ProjectLicensed under the Apache L…

大模型推荐落地啦!融合知识图谱,蚂蚁集团发布!

引言&#xff1a;电商推荐系统的新突破 随着电子商务平台的蓬勃发展&#xff0c;推荐系统已成为帮助用户在信息过载时代中筛选和发现产品的关键工具。然而&#xff0c;传统的推荐系统主要依赖历史数据和用户反馈&#xff0c;这限制了它们在新商品推出和用户意图转变时的有效性…

【洛谷 P8682】[蓝桥杯 2019 省 B] 等差数列 题解(数学+排序+辗转相除法)

[蓝桥杯 2019 省 B] 等差数列 题目描述 数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列&#xff0c;只记得其中 N N N 个整数。 现在给出这 N N N 个整数&#xff0c;小明想知道包含这 N N N 个整数的最短的等差数列有几项&#xff1f; 输…

ROS 2基础概念#3:主题(Topic)| ROS 2学习笔记

在ROS&#xff08;Robot Operating System&#xff09;中&#xff0c;主题&#xff08;Topics&#xff09;是实现节点之间通信的主要机制之一。节点&#xff08;Node&#xff09;可以发布&#xff08;publish&#xff09;消息到话题&#xff0c;或者订阅&#xff08;subscribe&…

搜索回溯算法(DFS)1------递归

目录 简介&#xff1a; 递归问题解题的思路模板 例题1&#xff1a;汉诺塔 例题2&#xff1a;合并两个有序链表 例题3&#xff1a;反转链表 例题4&#xff1a;两两交换链表中的节点 例题5&#xff1a;Pow&#xff08;x,n&#xff09;-快速幂 结语&#xff1a; 简介&…

攻防世界-get_post

题目信息 相关知识 -G&#xff1a;表示GET请求&#xff0c;缺省POST -d参数用于发送 POST 请求的数据体 使用-d参数以后&#xff0c;HTTP 请求会自动加上标头Content-Type : application/x-www-form-urlencoded。并且会自动将请求转为 POST 方法&#xff0c;因此可以省略-X PO…

Vue.js 深度解析:模板编译原理与过程

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Springboot+vue的船舶监造系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的船舶监造系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的船舶监造系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff…

山西电力市场日前价格预测【2024-02-25】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-25&#xff09;山西电力市场全天平均日前电价为386.45元/MWh。其中&#xff0c;最高日前电价为765.92元/MWh&#xff0c;预计出现在18:45。最低日前电价为203.79元/MWh&#xff0c;预计…

【计算机毕业设计】044学生管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C# 不可识别数据库格式问题

C#是一种流行的编程语言&#xff0c;用于开发各种类型的应用程序&#xff0c;包括与数据库交互的应用程序。然而&#xff0c;在处理数据库时&#xff0c;有时会遇到一些错误和问题。其中之一就是数据库格式不可识别的错误。 在C#中&#xff0c;我们通常使用ADO.NET来连接和操作…

【教程】Kotlin语言学习笔记(四)——方法(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 文章目录 【…

openGauss学习笔记-235 openGauss性能调优-系统调优-资源负载管理-资源管理准备-创建资源池

文章目录 openGauss学习笔记-235 openGauss性能调优-系统调优-资源负载管理-资源管理准备-创建资源池235.1 背景信息235.2 前提条件235.3 操作过程235.3.1 创建资源池235.3.2 管理资源池235.3.3 删除资源池 235.4 查看资源池的信息 openGauss学习笔记-235 openGauss性能调优-系…

探索Ubuntu命令行:常见问题与解决方案

一、引言 Ubuntu&#xff0c;作为一款流行的Linux发行版&#xff0c;其命令行界面&#xff08;CLI&#xff09;为用户提供了丰富的功能和灵活性。然而&#xff0c;对于新手来说&#xff0c;命令行可能会带来一些挑战。本文将探讨一些在使用Ubuntu命令行时可能遇到的问题及其解决…

Python算法100例-3.1 回文数

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展7.巧用字符串技巧 1&#xff0e;问题描述 打印所有不超过n&#xff08;取n<256&#xff09;的其平方具有对称性质的数&#xff08;也称回…

加密与安全_ 凯撒密码

文章目录 Pre概述Code 实现 凯撒密码字母频率分析攻击Code解密凯撒密码 小结 Pre PKI - 02 对称与非对称密钥算法 概述 凯撒密码是一种简单的替换加密技术&#xff0c;也称为移位密码。它是古典密码学中最早的密码之一&#xff0c;得名于古罗马军队领袖凯撒尤利乌斯&#xff…

VMware虚拟机安装Linux

1.新建虚拟机 2. 安装操作系统 等待 选择中文 点软件选择 选择下面的GNOME桌面 禁用KDUMP 点进安装位置&#xff0c;点完成就可以了 网络连接&#xff0c;右上角打开 开始安装&#xff0c;输入ROOT密码&#xff0c;创建用户 点击重启&#xff0c;等待 重启完成之后出现下面的界…