Three.js结合物理引擎实现掉落效果

物理引擎

用于模拟和计算与物理学相关的效果,如重力、碰撞、摩擦、流体动力学等
可通过模拟物理世界中的物体运动来更新场景元素 cannon-es

在这里插入图片描述

<template>
</template>

<script setup>
import * as THREE from 'three'
import gsap from 'gsap'
//导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入 dat.gui
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

import * as CANNON from 'cannon-es'

// const gui = new GUI();
//1.创建场景
const scene = new THREE.Scene()
//2.创建相机 角度  宽高比  近端  远端
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 30)
// 设置相机位置  x y z 
camera.position.set(0, 0, 20)
// 把相机添加到场景中
scene.add(camera)

// 创建球和平面
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20)
const sphereMaterial = new THREE.MeshStandardMaterial()
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
sphere.castShadow = true
scene.add(sphere)
// 平面
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new THREE.MeshStandardMaterial()
)
floor.position.set(0, -5, 0)
floor.rotation.x = -Math.PI / 2
floor.receiveShadow = true
scene.add(floor)
// 环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5)
dirLight.castShadow = true
scene.add(dirLight)

// 创建物理世界
const world = new CANNON.World()
world.gravity.set(0, -9.8, 0) // y轴向下的重力
// 创建物理世界小球形状
const sphereShape = new CANNON.Sphere(1)
// 设置物体材质
const shperWorldMaterial = new CANNON.Material('default')
// 创建物理世界的物体
const sphereBody = new CANNON.Body({
  shape: sphereShape,
  position: new CANNON.Vec3(0, 0, 0),
  mass: 1, // 小球的重量
  material: shperWorldMaterial
})
world.addBody(sphereBody)
// 地面形状
const floorShape = new CANNON.Plane()
// 设置地板材质
const floorMaterial = new CANNON.Material('name')
// 地面物体
const floorBody = new CANNON.Body()
floorBody.material = floorMaterial
// 当质量为0的时候,可以使物体保持不动
floorBody.mass = 0
floorBody.addShape(floorShape)
// 地面位置
floorBody.position.set(0, -5, 0)
// 旋转地面的位置,围绕x旋转
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
world.addBody(floorBody)

// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(shperWorldMaterial, floorMaterial, {
  friction: 0.1, // 摩擦力
  restitution: 0.7 // 弹性
})
world.addContactMaterial(defaultContactMaterial) // 将材料的关联添加到物理世界

// 创建打击声音
const hitSound = new Audio('matalHit.mp3')
// 监听碰撞事件
sphereBody.addEventListener('collide', (e) => {
  console.log(e);
  // 碰撞的强度
  const impactStrength = e.contact.getImpactVelocityAlongNormal()
  console.log(impactStrength);
  if (impactStrength > 2) {
    hitSound.currentTime = 0 // 声音从0开始播放
    hitSound.play() // 浏览器不支持自动触发了
  }
})

// 初始化渲染器
const renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true
// 将webgel渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement)

// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
// 设置控制器的阻尼 更真实 惯性
controls.enableDamping = true

// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)

// 设置时钟
const clock = new THREE.Clock()
function render() {
  let deltaTime = clock.getDelta()
  // 更新物理引擎里世界的物体
  world.step(1 / 120, deltaTime) // 时间步长被设置为大约0.0083秒(即1/120秒)

  sphere.position.copy(sphereBody.position)

  // 使用渲染器,通过相机将场景渲染进来
  renderer.render(scene, camera);
  // 渲染下一帧的时候 调用 render函数
  requestAnimationFrame(render)
}
render()

// 监听窗口尺寸变化,更新渲染画面
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix()

  // 更新渲染器
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 设置渲染器的像素比
  renderer.setPixelRatio(window.devicePixelRatio)
})

</script>

<style scoped></style>

在这里插入图片描述

<template>
</template>

<script setup>
import * as THREE from 'three'
import gsap from 'gsap'
//导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入 dat.gui
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

import * as CANNON from 'cannon-es'

// const gui = new GUI();
//1.创建场景
const scene = new THREE.Scene()
//2.创建相机 角度  宽高比  近端  远端
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 30)
// 设置相机位置  x y z 
camera.position.set(0, 0, 20)
// 把相机添加到场景中
scene.add(camera)

const cubeArr = []
// 设置物体材质
const cubeWorldMaterial = new CANNON.Material('default')
const createCube = () => {
  // 创建立方体和平面
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
  const cubeMaterial = new THREE.MeshStandardMaterial()
  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
  cube.castShadow = true
  scene.add(cube)

  // 创建物理世界小球形状
  const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))

  // 创建物理世界的物体
  const cubeBody = new CANNON.Body({
    shape: cubeShape,
    position: new CANNON.Vec3(0, 0, 0),
    mass: 1, // 小球的重量
    material: cubeWorldMaterial
  })
  // 给物体施加作用力
  cubeBody.applyLocalForce(
    new CANNON.Vec3(180, 0, 0), // 添加的力的大小和方向(此设置x物体会向右偏移,数值越大偏移越多)
    new CANNON.Vec3(0, 0, 0) // 施加力所在物体里的哪个位置
  )
  world.addBody(cubeBody)

  // 创建打击声音
  const hitSound = new Audio('matalHit.mp3')
  // 监听碰撞事件
  cubeBody.addEventListener('collide', (e) => {
    console.log(e);
    // 碰撞的强度
    const impactStrength = e.contact.getImpactVelocityAlongNormal()
    console.log(impactStrength);
    if (impactStrength > 2) {
      hitSound.currentTime = 0 // 声音从0开始播放
      hitSound.volume = impactStrength / 12 // 音量 [0, 1]
      hitSound.play() // 浏览器不支持自动触发了
    }
  })
  cubeArr.push({
    mesh: cube,
    body: cubeBody
  })
}

window.addEventListener('click', createCube)

// 平面
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new THREE.MeshStandardMaterial()
)
floor.position.set(0, -5, 0)
floor.rotation.x = -Math.PI / 2
floor.receiveShadow = true
scene.add(floor)
// 环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5)
dirLight.castShadow = true
scene.add(dirLight)

// 创建物理世界
const world = new CANNON.World()
world.gravity.set(0, -9.8, 0) // y轴向下的重力

// 地面形状
const floorShape = new CANNON.Plane()
// 设置地板材质
const floorMaterial = new CANNON.Material('name')
// 地面物体
const floorBody = new CANNON.Body()
floorBody.material = floorMaterial
// 当质量为0的时候,可以使物体保持不动
floorBody.mass = 0
floorBody.addShape(floorShape)
// 地面位置
floorBody.position.set(0, -5, 0)
// 旋转地面的位置,围绕x旋转
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
world.addBody(floorBody)

// 设置2种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(cubeWorldMaterial, floorMaterial, {
  friction: 0.1, // 摩擦力
  restitution: 0.7 // 弹性
})
world.addContactMaterial(defaultContactMaterial) // 将材料的关联添加到物理世界

// 设置世界碰撞的默认材料,如果材料没有设置都用这个
world.defaultContactMaterial = defaultContactMaterial

// 初始化渲染器
const renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 开启场景中的阴影贴图
renderer.shadowMap.enabled = true
// 将webgel渲染的canvas内容添加到body
document.body.appendChild(renderer.domElement)

// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
// 设置控制器的阻尼 更真实 惯性
controls.enableDamping = true

// 添加坐标轴辅助器
const axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)

// 设置时钟
const clock = new THREE.Clock()
function render() {
  let deltaTime = clock.getDelta()
  // 更新物理引擎里世界的物体
  world.step(1 / 120, deltaTime) // 时间步长被设置为大约0.0083秒(即1/120秒)

  // cube.position.copy(cubeBody.position)
  console.log(cubeArr);
  cubeArr.forEach(item => {
    item.mesh.position.copy(item.body.position)
    // 设置渲染的物体跟随物理的物体旋转
    item.mesh.quaternion.copy(item.body.quaternion)
  })

  // 使用渲染器,通过相机将场景渲染进来
  renderer.render(scene, camera);
  // 渲染下一帧的时候 调用 render函数
  requestAnimationFrame(render)
}
render()

// 监听窗口尺寸变化,更新渲染画面
window.addEventListener("resize", () => {
  // 更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight
  // 更新摄像机的投影矩阵
  camera.updateProjectionMatrix()

  // 更新渲染器
  renderer.setSize(window.innerWidth, window.innerHeight)
  // 设置渲染器的像素比
  renderer.setPixelRatio(window.devicePixelRatio)
})

</script>

<style scoped></style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值