物理运动
threejs本身不具备模拟物理的效果,需要借助cannonjs来实现模拟物理效果。
可以理解为threejs构建看得见的物体,cannonjs构建看不见的物理物体,将两者进行关联。
cannonjs
官网 https://schteppe.github.io/cannon.js/
文档 https://schteppe.github.io/cannon.js/docs/
物体运动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as CANNON from 'cannon-es'
import { Mesh } from "three";
import { Sphere, Vec3 } from "cannon-es";
/**
* Debug
*/
const hitSound=new Audio("sounds/hit.mp3")
const playHitSound=(collision)=>{
if(collision.contact.getImpactVelocityAlongNormal()>1.5){
hitSound.currentTime=0;
hitSound.volume=Math.random()
hitSound.play();
}
}
/**
* Base
*/
// Canvas
const textureLoader=new THREE.TextureLoader()
let objsToUpdate=[]
// Scene
const scene = new THREE.Scene();
const world=new CANNON.World()
world.gravity.set(0,-9.82,0) //设置重力方向
const defaultMat=new CANNON.Material('default')
const defaultContactMaterial=new CANNON.ContactMaterial(defaultMat,defaultMat,{
friction:0.1,
restitution:0.6
})
world.addContactMaterial(defaultContactMaterial)
world.broadphase=new CANNON.SAPBroadphase(world)
world.allowSleep=true
const floorBody=new CANNON.Body({
mass:0,
shape:new CANNON.Plane(),
material:defaultContactMaterial
})
floorBody.quaternion.setFromAxisAngle(
new CANNON.Vec3(-1,0,0),
Math.PI*0.5
)
world.addBody(floorBody)
/**
* Light
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.2);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.set(1024, 1024);
directionalLight.shadow.camera.far = 15;
directionalLight.shadow.camera.left = -7;
directionalLight.shadow.camera.top = 7;
directionalLight.shadow.camera.right = 7;
directionalLight.shadow.camera.bottom = -7;
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
/**
* Objects
*/
const sphere=new THREE.SphereGeometry(1,20,20)
const sphereMateral=new THREE.MeshStandardMaterial({
roughness:0.4,
metalness:0.3,
})
const box=new THREE.BoxGeometry(1,1,1)
const boxMaterial=new THREE.MeshStandardMaterial({
roughness:0.4,
metalness:0.3,
})
const createBox=(width,height,depth,position)=>{
const mesh=new Mesh(box,boxMaterial)
mesh.scale.set(width,height,depth)
mesh.castShadow=true
mesh.position.copy(position)
const body=new CANNON.Body({
mass:1,
position:new Vec3().copy(position),
shape:new CANNON.Box(new CANNON.Vec3(width,height,depth)),
material:defaultContactMaterial
})
body.addEventListener("collide",playHitSound)
objsToUpdate.push({mesh,body})
scene.add(mesh)
world.addBody(body)
}
const createSphere=(r,ps)=>{
const mesh=new Mesh(sphere,sphereMateral)
mesh.scale.set(r,r,r)
mesh.castShadow=true
mesh.position.copy(ps)
scene.add(mesh)
const body=new CANNON.Body({
mass:1,
position:new Vec3().copy(ps),
shape:new Sphere(r),
material:defaultContactMaterial
})
objsToUpdate.push({mesh,body})
world.addBody(body)
}
createSphere(0.5,{x:0,y:3,z:0})
const floor=new Mesh(
new THREE.PlaneGeometry(10,10),
new THREE.MeshStandardMaterial({
color: "#777777",
metalness: 0.3,
roughness: 0.4,
})
)
floor.castShadow=true
floor.receiveShadow = true;
floor.rotation.x = -Math.PI * 0.5;
scene.add(floor)
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
};
window.addEventListener("resize", () => {
// Update sizes
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// Update camera
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
// Update renderer
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
/**
* Camera
*/
// Base camera
const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
0.1,
100
);
camera.position.set(-3, 3, 3);
scene.add(camera);
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled=true
// sphereBody.applyLocalForce(new CANNON.Vec3(150, 0, 0));
document.body.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const clock = new THREE.Clock();
const oldTime=0;
const tick = () => {
const elapsedTime = clock.getElapsedTime();
const deltaTime=elapsedTime-oldTime;
objsToUpdate.forEach(obj=>{
obj.mesh.quaternion.copy(obj.body.quaternion)
obj.mesh.position.copy(obj.body.position)
})
world.step(1/60,deltaTime,3)
// Update controls
controls.update();
// Render
renderer.render(scene, camera);
// Call tick again on the next frame
window.requestAnimationFrame(tick);
};
tick();
// const guiObj={
// }
// guiObj.createSphere=()=>{
// createSphere(Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
// }
// guiObj.createBox=()=>{
// createBox(Math.random()*0.5,Math.random()*0.5,Math.random()*0.5,{x:(Math.random()-0.5)*3,y:3,z:(Math.random()-0.5)*3})
// }
// guiObj.restCanvas=()=>{
// objsToUpdate.forEach(obj=>{
// scene.remove(obj.mesh)
// world.removeBody(obj.body)
// obj.body.removeEventListener("collide",()=>playHitSound())
// })
// }
// gui.add(guiObj,'createSphere')
// gui.add(guiObj,'createBox')
// gui.add(guiObj,'restCanvas')
</script>
</body>
</html>