2D library와 3D library가 있다.
Three.js는 무조건 3D library만 써야하는 건 아니다.
2D로도 충분한 경우 2D library를 사용하면 더 효율적이다.
사용법
npm install --save cannon
import CANNON from 'cannon'
/**
* Physics
*/
const world = new CANNON.World()
world.gravity.set(0, - 9.82, 0)
const sphereShape = new CANNON.Sphere(0.5)
const sphereBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0, 3, 0),
shape: sphereShape
})
world.addBody(sphereBody)
const clock = new THREE.Clock()
let oldElapsedTime = 0
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - oldElapsedTime
oldElapsedTime = elapsedTime
world.step(1 / 60, deltaTime, 3)
sphere.position.copy(sphereBody.position)
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
plane은 처음 만들면 세로로 세워져있으므로 바닥으로 사용할려면 x축기준으로 반을 돌리면 된다.
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body({
mass:0,
shape: floorShape
})
floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(- 1, 0, 0), Math.PI * 0.5)
world.addBody(floorBody)
const concreteMaterial = new CANNON.Material('concrete')
const plasticMaterial = new CANNON.Material('plastic')
const concretePlasticContactMaterial = new CANNON.ContactMaterial(
concreteMaterial,
plasticMaterial,
{
friction: 0.1,
restitution: 0.7
}
)
world.addContactMaterial(concretePlasticContactMaterial)
const sphereBody = new CANNON.Body({
// ...
material: plasticMaterial
})
// ...
const floorBody = new CANNON.Body()
floorBody.material = concreteMaterial
const defaultMaterial = new CANNON.Material('default')
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial,
defaultMaterial,
{
friction: 0.1,
restitution: 0.7
}
)
world.addContactMaterial(defaultContactMaterial)
// ...
const sphereBody = new CANNON.Body({
// ...
material: defaultMaterial
})
// ...
floorBody.material = defaultMaterial
world.defaultContactMaterial = defaultContactMaterial
sphereBody.applyLocalForce(new CANNON.Vec3(150, 0, 0), new CANNON.Vec3(0, 0, 0))
const tick = () =>
{
// ...
// Update physics
sphereBody.applyForce(new CANNON.Vec3(- 0.5, 0, 0), sphereBody.position)
world.step(1 / 60, deltaTime, 3)
// ...
}
const objectsToUpdate = []
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20)
const sphereMaterial = new THREE.MeshStandardMaterial({
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture,
envMapIntensity: 0.5
})
const createSphere = (radius, position) => {
// Three.js mesh
const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial)
mesh.castShadow = true
mesh.scale.set(radius, radius, radius)
mesh.position.copy(position)
scene.add(mesh)
// Cannon.js body
const shape = new CANNON.Sphere(radius)
const body = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(0,3,0),
shape: shape,
material: defaultMaterial
})
body.position.copy(position)
world.addBody(body)
objectsToUpdate.push({mesh,body})
}
const tick = () =>
{
// ...
world.step(1 / 60, deltaTime, 3)
for(const object of objectsToUpdate)
{
object.mesh.position.copy(object.body.position)
}
}
const boxGeometry = new THREE.BoxGeometry(1, 1, 1)
const boxMaterial = new THREE.MeshStandardMaterial({
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture,
envMapIntensity: 0.5
})
const createBox = (width,height,depth, position) => {
// Three.js mesh
const mesh = new THREE.Mesh(boxGeometry, boxMaterial)
mesh.castShadow = true
mesh.scale.set(width,height,depth)
mesh.position.copy(position)
scene.add(mesh)
// Cannon.js body
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: shape,
material: defaultMaterial
})
body.position.copy(position)
world.addBody(body)
objectsToUpdate.push({mesh,body})
}
const tick = () =>
{
// ...
for(const object of objectsToUpdate)
{
object.mesh.position.copy(object.body.position)
object.mesh.quaternion.copy(object.body.quaternion)
}
// ...
}
충돌하는 object 쌍들의 리스트의 근사값을 빠르게 계산하는 알고리즘
여러 종류의 broadphase 알고리즘이 존재한다.
Naive Broadphase - 모든 body들을 다 테스트
Grid Broadphase - world를 grid화해서 본인의 grid나 바로 인접한 grid에 있는 object들과만 테스트한다.
SAP Boradphase - 무작위의 축으로 object들을 여러 단계로 테스트한다.(Sweet and Prune) - 효율적이다. 사용 추천
object가 극도로 낮은 속도가 되면 sleep을 시킬 수 있다. - 효율적이다. 사용 추천
world.broadphase = new CANNON.SAPBroadphase(world)
world.allowSleep = true
Body가 colide, sleep, wakeup의 Event가 발생하는지 체크 및 사용할 수 있다.
collide 되었을 때 sound를 내고 싶으면 아래와 같이 사용한다.
body 생성 후에 해당 body에 collide event를 연결해준다.
sound의 경우 재시작 되도록 하면 자연스럽다.
너무 많은 sound가 계속 들리면 문제이기 때문에 impact되는 정도에 따라서 sound를 play시키면 자연스럽다.
const hitSound = new Audio('/sounds/hit.mp3')
const playHitSound = (collision) =>
{
const impactStrength = collision.contact.getImpactVelocityAlongNormal()
if(impactStrength > 1.5)
{
hitSound.volume = Math.random()
hitSound.currentTime = 0
hitSound.play()
}
}
const createBox = (width, height, depth, position) =>
{
// ...
body.addEventListener('collide', playHitSound)
// ...
}
debugObject.reset = () =>
{
for(const object of objectsToUpdate)
{
// Remove body
object.body.removeEventListener('collide', playHitSound)
world.removeBody(object.body)
// Remove mesh
scene.remove(object.mesh)
}
objectsToUpdate.splice(0, objectsToUpdate.length)
}
https://schteppe.github.io/cannon.js/docs/
https://schteppe.github.io/cannon.js/
cannon은 업데이트 안되고 있으니, cannon-es를 사용해라
npm install cannon-es
import * as CANNON from 'cannon-es'
Physi.js를 사용해도 좋다.
<https://chandlerprall.github.io/Physijs/>
결과물
<https://20-physics-c37grrr7k-hwangsangjins-projects.vercel.app/>