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);