도미노 객체를 만들어서 넘어뜨리는 시뮬레이션을 구현해보자.
도미노 객체 정의
// Domino.js
import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three';
import { Body, Box, Vec3 } from 'cannon-es';
export class Domino{
constructor(info) {
this.scene = info.scene;
this.cannonWorld = info.cannonWorld;
this.width = info.width || 0.6;
this.height = info.height || 1;
this.depth = info.depth || 0.2;
this.x = info.x || 0;
this.y = info.y || 0.5;
this.z = info.z || 0;
this.rotationY = info.rotationY || 0;
info.gltfLoader.load(
'./models/domino.glb',
glb => {
this.modelMesh = glb.scene.children[0];
this.modelMesh.castShadow = true;
this.modelMesh.position.set(this.x, this.y, this.z);
this.scene.add(this.modelMesh);
}
);
}
}
도미노 객체를 정의하고 생성자에서 각종 매개변수 값들과 모델을 load 하도록 한다.
// main.js
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
...
// create domino
const dominos = [];
let domino;
for(i = 0; i < 20; i++){
domino = new Domino({
scene,
cannonWorld,
gltfLoader,
z: -i * 0.8
});
dominos.push(domino);
}
도미노 객체를 생성하고 배열에 넣어놓는다. 도미노의 mesh를 scene에 넣는 것은 생성자에서 진행한다.
물리엔진 적용
생성한 도미노에 물리엔진을 적용해보자.
// Domino.js
export class Domino{
constructor(info) {
...
info.gltfLoader.load(
'./models/domino.glb',
glb => {
...
this.setCannonBody();
}
);
}
setCannonBody() {
const shape = new Box(new Vec3(this.width/2, this.height/2, this.depth/2));
this.cannonBody = new Body({
mass: 1,
position: new Vec3(this.x, this.y, this.z),
shape
})
this.cannonBody.quaternion.setFromAxisAngle(
new Vec3(0, 1, 0), // y축
this.rotationY
);
this.cannonWorld.addBody(this.cannonBody);
}
}
도미노 클래스에 body를 설정하는 함수를 정의한다. 질량과 위치, 모양, rotation등을 설정하고 world에 body를 추가하여 적용한다.
// main.js
...
function draw() {
const delta = clock.getDelta();
let cannonStepTime = 1/60;
if (delta < 0.01) cannonStepTime = 1/120;
cannonWorld.step(cannonStepTime, delta, 3)
dominos.forEach(item => {
if(item.cannonBody){
item.modelMesh.position.copy(item.cannonBody.position);
item.modelMesh.quaternion.copy(item.cannonBody.quaternion);
}
});
renderer.render(scene, camera);
renderer.setAnimationLoop(draw);
}
물리엔진을 적용하기 위해 rendering에서 domino에 body가 존재하면 body의 위치와 mesh를 동기화시킨다.
Raycast로 도미노 체크하기
이번에는 raycast를 이용해서 특정 도미노를 클릭하면 넘어뜨리도록 해보자.
// main.js
...
// create domino
const dominos = [];
let domino;
for(let i = -3; i < 17; i++){
domino = new Domino({
index: i,
scene,
cannonWorld,
gltfLoader,
z: -i * 0.8
});
dominos.push(domino);
}
function checkIntersects() {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
console.log(intersects[0].object.name);
}
// event
window.addEventListener('resize', setSize);
canvas.addEventListener('click', (e) => {
mouse.x = e.clientX / canvas.clientWidth * 2 - 1;
mouse.y = -(e.clientY / canvas.clientHeight * 2 - 1);
checkIntersects();
});
클릭이벤트로 마우스가 클릭한 위치에 raycaster를 쏴서 충돌검사를 한다.
domino를 생성할 때는 index를 매개변수에 포함한다.
// Domino.js
export class Domino{
constructor(info) {
this.scene = info.scene;
this.cannonWorld = info.cannonWorld;
this.index = info.index;
...
info.gltfLoader.load(
'./models/domino.glb',
glb => {
this.modelMesh = glb.scene.children[0];
this.modelMesh.name = `${this.index}번 도미노`;
this.modelMesh.castShadow = true;
this.modelMesh.position.set(this.x, this.y, this.z);
this.scene.add(this.modelMesh);
this.setCannonBody();
}
);
}
...
}
도미노 객체에서는 매개변수로 받아온 index를 name으로 설정한다.
도미노 넘어뜨리기
이제 힘을 가해서 도미노를 넘어뜨리자
// Domino.js
export class Domino{
...
setCannonBody() {
...
this.modelMesh.cannonBody = this.cannonBody;
this.cannonWorld.addBody(this.cannonBody);
console.log('ee');
}
}
Domino 객체에서 Body를 가져올 수 있도록 하기 위해서 modelMesh에 cannonBody를 집어넣는다.
// main.js
...
// Contact Material
const defaultMaterial = new CANNON.Material('default');
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial,
defaultMaterial,
{
friction: 0.01,
restitution: 0.9
}
);
cannonWorld.defaultContactMaterial = defaultContactMaterial;
...
function checkIntersects() {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
/// another method to applyForce
// for(const item of intersects){
// if(item.object.cannonBody){
// item.object.cannonBody.applyForce(
// new CANNON.Vec3(0, 0, -100),
// new CANNON.Vec3(0, 0, 0)
// );
// break;
// }
// }
if(intersects[0].object.cannonBody){
intersects[0].object.cannonBody.applyForce(
new CANNON.Vec3(0, 0, -100),
new CANNON.Vec3(0, 0, 0)
);
}
}
click하여 raycast를 쏘면 맞은 도미노 객체 body에 힘을 준다. 이때 도미노가 아닌 다른 오브젝트를 건드릴 것을 대비하여 body가 있는지를 검토한다.
raycast로 감지한 intersects의 오브젝트 body에 접근하는 방법은 for-break문을 이용해서도 할 수 있다.
넘어질 때 마찰력이나 반발력에 따라서 잘 넘어가지 않을 수 있으므로 ContactMaterial의 값들을 조절한다.
'개발 · 컴퓨터공학' 카테고리의 다른 글
three.js 이미지를 사용한 particle (0) | 2022.10.28 |
---|---|
three.js particle 랜덤으로 생성하기 (0) | 2022.10.27 |
three.js 오브젝트 삭제하기 (0) | 2022.10.25 |
three.js collision과 sound 적용해보기 (0) | 2022.10.24 |
three.js body 물리 테스트 최적화 (0) | 2022.10.21 |