Threejs与Connonjs实现物体碰撞

2023-08-26-20-45-27

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./assets/css/style.css">
</head>
<body>
    <script src="./main/index.js" type="module"></script>
    <div id="ContainerID"></div>
</body>
</html>
import * as THREE from 'three';
window.THREE = THREE
import gsap from "gsap";
import * as CANNON from 'cannon-es'

// ...
/* 
**
*/
let ContainerIDDiv = document.querySelector("#ContainerID")
let width = ContainerIDDiv.getBoundingClientRect().width
let height = ContainerIDDiv.getBoundingClientRect().height
console.log(`width,height`, width, height);

/* 
**
*/
//  1.创建场景
const scene = new THREE.Scene()

//  2.创建并添加相机
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 0, 10) //设置相机位置
scene.add(camera)


/* 
**
*/

//加载管理器
let event = {}
event.onLoad = () => {

}
event.onProgress = (url, num, total) => {
    console.log(`url,num,total`, url, num, total, `${Math.round(num / total * 100)}%`);
}
event.onError = () => {

}
let textureManage = new THREE.LoadingManager(
    event.onLoad,
    event.onProgress,
    event.onError
)

/* 
**
*/
let ambientLight = new THREE.AmbientLight(0x404040, 1)
scene.add(ambientLight)

let spotLight = new THREE.SpotLight(0xffffff, 1)
spotLight.position.set(20, 20, 20)
scene.add(spotLight)

let directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.set(0, 5, 0)
directionalLight.castShadow = true
//设置阴影边缘模糊度
directionalLight.shadow.radius = 20
//设置阴影分辨率
directionalLight.shadow.mapSize.set(4096, 4096)
scene.add(directionalLight)

/* 
**
*/
// 创建物理世界
const world = new CANNON.World();
world.gravity.set(0, -9.8, 0);
let planeGeometry = new THREE.PlaneGeometry(10,10)
let planMesh = new THREE.Mesh(
    planeGeometry,
    new THREE.MeshStandardMaterial({
        side: THREE.DoubleSide,
        // transparent:true,
        // opacity:0
    })
)
planMesh.position.set(0, -5, 0)
planMesh.rotation.x = -Math.PI / 2
planMesh.receiveShadow = true
scene.add(planMesh)
//创建物理世界地面形状
const floorShape = new CANNON.Plane()
//创建物理世界地面
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.position.set(0, -5, 0)
floorBody.addShape(floorShape)
world.addBody(floorBody)
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1, 0, 0), Math.PI * 0.5)
//创建用以保存更新网格和刚体对象的数组
let cubeTextureLoader = new THREE.CubeTextureLoader(textureManage)
let envcubeTexture = cubeTextureLoader.load(
    [
        require('../assets/textures/cube/Bridge2/posx.jpg'),
        require('../assets/textures/cube/Bridge2/negx.jpg'),
        require('../assets/textures/cube/Bridge2/posy.jpg'),
        require('../assets/textures/cube/Bridge2/negy.jpg'),
        require('../assets/textures/cube/Bridge2/posz.jpg'),
        require('../assets/textures/cube/Bridge2/negz.jpg'),

    ]
)
envcubeTexture.center.set(0.5, 0.5)

const sphereGeometry = new THREE.SphereGeometry(1, 20, 20)
const sphereMaterial = new THREE.MeshStandardMaterial({
    metalness: 0.3,
    roughness: 0.4,
    envMap: envcubeTexture
})
scene.background = envcubeTexture
scene.environment = envcubeTexture
//创建默认材质
const defaultMaterial = new CANNON.Material('default')
//创建默认联系材质
const defaultContactMaterial = new CANNON.ContactMaterial(
    defaultMaterial,
    defaultMaterial,
    {
        friction: 0.1,
        restitution: 0.7,
    }
)
//把默认联系材质添加到世界中
world.addContactMaterial(defaultContactMaterial)
world.broadphase = new CANNON.SAPBroadphase(world)
world.allowSleep = true
const hitSound = new Audio(require('../assets/mp3/撞击声_爱给网_aigei_com.mp3'))
//播放音效
const playHitSound = collision => {
    const impactStrength = collision.contact.getImpactVelocityAlongNormal()
    if (impactStrength > 1.5) {
        hitSound.currentTime = 0
        hitSound.play()
    }
}


const objectToUpdate = []
const createSphere = (radius, position) => {
    //Three.js 网格
    const mesh = new THREE.Mesh(
        sphereGeometry,
        sphereMaterial
    )
    mesh.scale.set(radius, radius, radius)
    mesh.castShadow = true
    mesh.position.copy(position)
    scene.add(mesh)

    //Cannon.js 刚体
    const shape = new CANNON.Sphere(radius)
    const body = new CANNON.Body({
        mass: 1,
        position: new CANNON.Vec3(0, 3, 0),
        shape,
        material: defaultMaterial,
    })
    body.position.copy(position)
    body.addEventListener('collide', playHitSound)
    world.addBody(body)

    //保存对象更新数组中
    objectToUpdate.push({
        mesh: mesh,
        body: body,
    })
}


const createBox = (width, height, depth, position) => {
    //Three.js 网格
    let boxGeometry = new THREE.BoxGeometry(1, 1, 1)
    let boxMaterial = new THREE.MeshStandardMaterial()

    const mesh = new THREE.Mesh(boxGeometry, boxMaterial)
    mesh.scale.set(width, height, depth)
    mesh.castShadow = true
    mesh.position.copy(position)
    scene.add(mesh)

    //Cannon.js 刚体
    const shape = new CANNON.Box(new CANNON.Vec3(width * 0.5, height * 0.5, depth * 0.5))
    const body = new CANNON.Body({
        mass: 1,
        position: new CANNON.Vec3(0, 3, 0),
        shape,
        material: defaultMaterial,
    })
    body.position.copy(position)
    body.addEventListener('collide', playHitSound)
    world.addBody(body)

    //保存对象更新数组中
    objectToUpdate.push({
        mesh: mesh,
        body: body,
    })
}

/* 
**
*/

/* 
**
*/



/* 
**
*/

//  4.设置渲染器并渲染场景
const renderer = new THREE.WebGL1Renderer() //初始化渲染器
renderer.shadowMap.enabled = true
renderer.setSize(width, height)  //设置渲染的尺寸大小
ContainerIDDiv.appendChild(renderer.domElement);// 将webgl渲染的内容添加到body
/* 
**
*/

// 引入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 5.添加控制器-轨道控制器 可以使得相机围绕目标进行轨道运动  通过不断地重新渲染来实现移动视角的效果
const controls = new OrbitControls(camera, renderer.domElement);
// 10.设置控制器阻尼 更真实  需要在每一帧重新生成时调用controls.update();
controls.enableDamping = true;

/* 
**
*/
let clock = new THREE.Clock()
let oldElapsedTime = 0

function render() {//递归实现每一帧的重新渲染
    const elapsedTime = clock.getElapsedTime()
    const deltaTime = elapsedTime - oldElapsedTime
    oldElapsedTime = elapsedTime

    //Update physics world
    world.step(1 / 60, deltaTime, 3)

    // for (const object of objectToUpdate) {
    //     object.mesh.position.copy(object.body.position)
    // }
    for (const object of objectToUpdate) {
        object.mesh.position.copy(object.body.position)
        object.mesh.quaternion.copy(object.body.quaternion)
    }

    /
    controls.update()
    renderer.render(scene, camera)
    requestAnimationFrame(render);


}
render();

/* 
**
*/

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

/* 
**
*/
window.addEventListener("resize", () => {
    width = ContainerIDDiv.getBoundingClientRect().width
    height = ContainerIDDiv.getBoundingClientRect().height
    camera.aspect = width / height
    camera.updateProjectionMatrix()
    renderer.setSize(width, height)
    renderer.setPixelRatio(window.devicePixelRatio)

})

/* 
**
*/

window.addEventListener("dblclick", () => {
    let fullscreen = document.fullscreenElement
    if (!fullscreen) {
        renderer.domElement.requestFullscreen()
    } else {
        document.exitFullscreen()
    }
})


/*
**
*/

//先创建对象来存储createSphere函数
//因为gui.add()第一个参数必须是一个对象,第二个参数是对象的一个属性
import * as dat from 'dat.gui';
const gui = new dat.GUI();
const debugObject = {}
debugObject.createSphere = () => {
    createSphere(
        Math.random() * 0.5,
        {
            x: (Math.random() - 0.5) * 3,
            y: 3,
            z: (Math.random() - 0.5) * 3,
        })
}
gui.add(debugObject, 'createSphere')


debugObject.createBox = () => {
    createBox(
        Math.random(),
        Math.random(),
        Math.random(),
        {
            x: (Math.random() - 0.5) * 3,
            y: 3,
            z: (Math.random() - 0.5) * 3,
        })
}

gui.add(debugObject, 'createBox')

//重置场景
debugObject.reset = () => {
    for (const object of objectToUpdate) {
        //移除刚体body
        object.body.removeEventListener('collide', playHitSound)
        world.removeBody(object.body)
        // 移除网格mesh
        scene.remove(object.mesh)
    }
}

gui.add(debugObject, 'reset')

/*
**
 */
//console.log(`CANNON`, CANNON);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vitenode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值