작성
·
516
·
수정됨
0
- 질문에 대한 답변은 강의자가 하는 경우도 있고, 수강생 여러분들이 해주시는 경우도 있습니다. 같이 도와가며 공부해요! :)
- 작성하신 소스코드 자체의 오류보다는, 개념이나 원리가 이해되지 않는 부분을 질문해주시는게 좋습니다. 그대로 따라했는데 소스코드에서 버그가 나는 경우는 99%가 오타에 의한거라서, 완성된 소스랑 찬찬히 비교해보시면 직접 찾으실 수 있을 거예요. 개발자도구 console에 오류로 표시된 부분만 완성 코드에서 복사->붙여넣기를 해보시는 것도 방법입니다.
- 먼저 유사한 질문이 있었는지 검색해보세요.
- 서로 예의를 지키며 존중하는 문화를 만들어가요.
- 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
안녕하세요. cannon body가 웹페이지에서 보이는 캔버스 영역 바깥으로 나가지 않게 하고 싶습니다.
캔버스 영역 경계면에 해당하는 cannon js position을 어떻게 가져올 수 있을까요?
좋은 강의 감사합니다!
답변 1
0
캔버스 경계를 기준으로 한다면 카메라에 보이는지 안보이는지로 판별하시면 될 것 같아요.
아래 코드는 카메라 Sphere의 중심점이 카메라에서 벗어나면 cannon js에서 더이상 컨트롤 하지 않도록 만들어본 코드입니다. 우리 강의에서 ex01.js 이런 파일들처럼 같은 방식으로 실행해보시면 됩니다.
포인트는 frustum 객체를 이용하는 건데요, 아래 코드에서 frustum이 들어간 곳들이 해당 처리 부분입니다. 클릭할 때마다 Sphere 객체가 생성되고, 화면 끝에 다다르면 spheres 배열에서 제거되면서 그 객체는 더이상 cannon js에서 컨트롤 하는 걸 멈출 거예요.
만약 보이지 않는 벽이 있어서 튕겨나와야 한다든지.. 그런 기능을 생각하시는 거라면, 그 시점의 position 값을 활용해서 그 곳에 cannon body 벽을 세워주는 식으로 하시면 될 것 같습니다(쉽지는 않을 것 같아요^^;).
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import * as CANNON from 'cannon-es';
import { PreventDragClick } from './PreventDragClick';
// ----- 주제: 화면 밖으로 나간 메쉬는 cannon js에서 처리 안하기
// cannon.js 문서
// http://schteppe.github.io/cannon.js/docs/
// 주의! https 아니고 http
export default function example() {
// Renderer
const canvas = document.querySelector('#three-canvas');
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Scene
const scene = new THREE.Scene();
// Camera
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.y = 1.5;
camera.position.z = 4;
scene.add(camera);
// Light
const ambientLight = new THREE.AmbientLight('white', 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight('white', 1);
directionalLight.position.x = 1;
directionalLight.position.z = 2;
directionalLight.castShadow = true;
scene.add(directionalLight);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
// Cannon(물리 엔진)
const cannonWorld = new CANNON.World();
cannonWorld.gravity.set(0, -10, 0);
// 성능을 위한 세팅
cannonWorld.allowSleep = true; // body가 엄청 느려지면, 테스트 안함
cannonWorld.broadphase = new CANNON.SAPBroadphase(cannonWorld);
// SAPBroadphase // 제일 좋음
// NaiveBroadphase // 기본값
// GridBroadphase // 구역을 나누어 테스트
// Contact Material
const defaultMaterial = new CANNON.Material('default');
const defaultContactMaterial = new CANNON.ContactMaterial(
defaultMaterial,
defaultMaterial,
{
friction: 0.5,
restitution: 0.3
}
);
cannonWorld.defaultContactMaterial = defaultContactMaterial;
// MySphere2
class MySphere2 {
constructor(info) {
this.index = info.index;
this.scene = info.scene;
this.cannonWorld = info.cannonWorld;
this.geometry = info.geometry;
this.material = info.material;
this.x = info.x;
this.y = info.y;
this.z = info.z;
this.scale = info.scale;
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.scale.set(this.scale, this.scale, this.scale);
this.mesh.castShadow = true;
this.mesh.position.set(this.x, this.y, this.z);
this.scene.add(this.mesh);
this.setCannonBody();
}
setCannonBody() {
const shape = new CANNON.Sphere(0.5 * this.scale);
this.cannonBody = new CANNON.Body({
mass: 1,
position: new CANNON.Vec3(this.x, this.y, this.z),
shape
});
this.cannonWorld.addBody(this.cannonBody);
}
}
const floorShape = new CANNON.Plane();
const floorBody = new CANNON.Body({
mass: 0,
position: new CANNON.Vec3(0, 0, 0),
shape: floorShape,
material: defaultMaterial
});
floorBody.quaternion.setFromAxisAngle(
new CANNON.Vec3(-1, 0, 0),
Math.PI / 2
);
cannonWorld.addBody(floorBody);
// Mesh
const floorMesh = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({
color: 'slategray'
})
);
floorMesh.rotation.x = -Math.PI / 2;
floorMesh.receiveShadow = true;
scene.add(floorMesh);
const spheres = [];
const sphereGeometry = new THREE.SphereGeometry(0.5);
const sphereMaterial = new THREE.MeshStandardMaterial({
color: 'seagreen'
});
// 그리기
const clock = new THREE.Clock();
const frustum = new THREE.Frustum();
function draw() {
const delta = clock.getDelta();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
let cannonStepTime = 1/60;
if (delta < 0.01) cannonStepTime = 1/120;
cannonWorld.step(cannonStepTime, delta, 3);
spheres.forEach(item => {
item.mesh.position.copy(item.cannonBody.position);
item.mesh.quaternion.copy(item.cannonBody.quaternion);
if (!frustum.containsPoint(item.mesh.position)) {
console.log(`${item.index}번 멈춤`);
spheres.splice(item.index, 1);
}
});
// 인덱스 리셋
spheres.forEach((item, index) => {
item.index = index;
});
renderer.render(scene, camera);
renderer.setAnimationLoop(draw);
}
function setSize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
}
// 이벤트
window.addEventListener('resize', setSize);
let sphereIndex = 0;
canvas.addEventListener('click', () => {
sphereIndex++;
spheres.push(new MySphere2({
index: sphereIndex,
scene,
cannonWorld,
geometry: sphereGeometry,
material: sphereMaterial,
x: (Math.random() - 0.5) * 2,
y: 1.5,
z: (Math.random() - 0.5) * 2,
scale: Math.random() + 0.2
}));
});
const preventDragClick = new PreventDragClick(canvas);
// 처음에 1개 생성
spheres.push(new MySphere2({
index: sphereIndex,
scene,
cannonWorld,
geometry: sphereGeometry,
material: sphereMaterial,
x: 0,
y: 1,
z: 0,
scale: 0.5
}));
draw();
}