RayCaster(레이캐스터)

송기영개발잘하고싶다·2024년 10월 9일
0

Three.js

목록 보기
10/14

개요

Three.js에서 마우스를 사용해서 어떤 오브젝트를 클릭해 작업을 처리할때 사용하는 RayCaster가 있는데 이는 쉽게 생각하면 광선을 쏜다고 생각하면된다.

이 광선을 물체를 관통할 수 있으며 물체가 맞았는지 안맞았는지를 확인 할 수 있다.

자세한 내용은 공식문서 RayCaster를 방문하시면 됩니다.

실습

RayCaster 시각화

일반적으로 Three.js에서 RayCaster를 사용하면 눈에 보이지 않기때문에 선을 이용해서 광선을 표시하보는작업을 실습을 할건데 광선을 쏴서 광선이 물체를 통과할때만 색상이 변경되도록 표현한다.

Material은 LineBasicMateiral을 사용하고 Geometry는 BufferGeometry를 사용한다.

광선의 시작점은 카메라의 위치라고 생각하고 시작점과 끝점을 이어주면 광선이 생성이된다.

  // Mesh
  const lineMaterial = new THREE.LineBasicMaterial({ color: "yellow" });

  const points = [];
  points.push(new THREE.Vector3(0, 0, 100)); // 광선의 시작점
  points.push(new THREE.Vector3(0, 0, -100)); // 광선의 끝점
  const lineGeomtry = new THREE.BufferGeometry().setFromPoints(points);
  const guide = new THREE.Line(lineGeomtry, lineMaterial);
  scene.add(guide);

  const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
  const boxMaterial = new THREE.MeshStandardMaterial({ color: "plum" });
  const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
  boxMesh.name = "box";

  const torusGeometry = new THREE.TorusGeometry(2, 0.5, 16, 100);
  const torusMaterial = new THREE.MeshStandardMaterial({ color: "lime" });
  const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial);
  torusMesh.name = "torus";
  scene.add(boxMesh, torusMesh);

  const meshes = [boxMesh, torusMesh];

  // 실제 눈에 보이지 않는 광선
  const raycaster = new THREE.Raycaster();

  // 그리기
  const clock = new THREE.Clock();

  function draw() {
    const time = clock.getElapsedTime();

    // 박스매쉬의 위치를 위아래로 이동하도록 처리
    boxMesh.position.y = Math.sin(time) * 2;
    torusMesh.position.y = Math.cos(time) * 2;
    boxMesh.material.color.set("plum");
    torusMesh.material.color.set("lime");

    // 광선의 시작점
    const origin = new THREE.Vector3(0, 0, 100);
    
    // 광선의 방향
    const direction = new THREE.Vector3(0, 0, -100);
    
    // 정규화
    direction.normalize();
    raycaster.set(origin, direction);
    
    // 레이캐스터가 통과된 값은 intersectObjects메서드를 사용해 값을 얻을 수 있음
    const intersects = raycaster.intersectObjects(meshes);
    intersects.forEach((item) => {
      // 색상변경
      item.object.material.color.set("red");
    });

    renderer.render(scene, camera);
    renderer.setAnimationLoop(draw);
  }

결과

광선이 오브젝트에 관통될때만 색상이 변경되는 것을 확인할 수 있다.

매쉬 클릭 감지

이제 시각적으로 표시되는 광선을 없애고 RayCaster를 이용해서 오브젝트가 클릭이 되었을때 처리를 해보자. 오브젝트가 중복되는 부분은 RayCaster에서는 모두 관통했다고 인지하지만 필자는 제일 처음 관통한 오브젝트의 이름만 출력했다.

마우스 클릭의 X좌표와 Y좌표는 0에서 해상도(1920x1080) 크기만큼 늘어나지만 Three.js의 x,y,z의 좌표는 가운데를 기준으로 x는 (왼쪽 -, 오른쪽 +) y는 (위 +, 아래 -) z는(가까움 +, 멈 -)의 값으로 처리되기 때문에 마우스를 정규화 처리를 해주어야 한다.

  // 생략

  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();

  // 그리기
  const clock = new THREE.Clock();

  function draw() {
    const time = clock.getElapsedTime();

    renderer.render(scene, camera);
    renderer.setAnimationLoop(draw);
  }

  function checkIntersects() {
    // 카메라 시점에서 클릭했을때 처리
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(meshes);
    for (const item of intersects) {
      console.log(item.object.name);
      // 클릭된 오브젝트 하나만 선택되게 처리
      break;
    }
  }

  // 이벤트
   canvas.addEventListener("click", (e) => {
    // Three.js는 위로갈수록 Y값이 증가함 따라서 Three.js 형식으로 값을 변경해주어야한다.
    // 비율을 먼저 구하고 * 2하고 1을 빼줌
    mouse.x = (e.clientX / canvas.clientWidth) * 2 - 1;
    mouse.y = -((e.clientY / canvas.clientHeight) * 2 - 1);
    // 범위를 -1 ~ +1 로 지정해서 처리해줌
    checkIntersects();
  });

결과

마우스 드래그 클릭 방지

사용자가 카메라 회전을 목적으로 마우스를 드래그를 했지만 오브젝트 위에서 마우스를 떼면 매쉬가 클릭되는 현상이 발생하는데 이는 마우스를 누른상태에서 드래그가 일정 시간 혹은 픽셀값이 증가하면 RayCast를 하지 않는 방식으로 처리할 수 있다.

  • PreventDragClick
export class PreventDragClick {
  constructor(elem) {
    this.mouseMoved;
    let clickStartX;
    let clickstartY;
    let clickStartTime;
    elem.addEventListener("mousedown", (e) => {
      // 처음 클릭 위치를 넣음
      clickStartX = e.clientX;
      clickstartY = e.clientY;
      clickStartTime = Date.now();
    });
    elem.addEventListener("mouseup", (e) => {
      // 마우스를 떼었을때 한 100픽셀을 이동하고 떼었을때 크기 차이가 있으면 클릭으로 처리하지 않음
      const xGap = Math.abs(e.clientX - clickStartX);
      const yGap = Math.abs(e.clientY - clickstartY);
      const timeGap = Date.now() - clickStartTime;

      if (xGap > 5 || yGap > 5 || timeGap > 500) {
        // 이때는 드래그 처리
        this.mouseMoved = true;
      } else {
        this.mouseMoved = false;
      }
    });
  }
}

사용

  import { PreventDragClick } from "./PreventDragClick";
  /-- 생략 --/
  canvas.addEventListener("click", (e) => {
    // Three.js는 위로갈수록 Y값이 증가함 따라서 Three.js 형식으로 값을 변경해주어야한다.
    // 비율을 먼저 구하고 * 2하고 1을 빼줌
    mouse.x = (e.clientX / canvas.clientWidth) * 2 - 1;
    mouse.y = -((e.clientY / canvas.clientHeight) * 2 - 1);
    // 범위를 -1 ~ +1 로 지정해서 처리해줌
    checkIntersects();
  });

  const preventDragClick = new PreventDragClick(canvas);
profile
업무하면서 쌓인 노하우를 정리하는 블로그🚀 풀스택 개발자를 지향하고 있습니다👻

0개의 댓글