프로젝트/Digital Twin Bootcamp

[team 6] three.js 특정 오브젝트 클릭 이벤트 (클릭 감지)

Ail_ 2022. 3. 25. 17:02

특정 기기(특정 모델)를 클릭하면 그걸 감지하도록 코드를 짰다.

내가 시도한 코드는 더보기와 같다.

더보기

맨 처음엔 three.interaction을 써서 간단히 구현할 수 있을 줄 알았다.

// three.interaction 방식
// new a interaction, then you can add interaction-event with your free style
const interaction = new Interaction(this.renderer, this.scene, this.camera)

this.scene.resource.cursor = 'pointer'
this.scene.resource.on('click', function (ev) {
 console.log(ev, interaction)
})

안됐다!

 

TypeError: "x" is not a constructor - JavaScript | MDN

TypeError

developer.mozilla.org

이런 오류가 자꾸 떴는데 구글링하다가 저런 라이브러리는 나중에 호환 안 될 가능성이 높으니 비추한다하여 겸사겸사 많이 쓰는 raycaster로 갈아탔다.

다 안돼서 계속 넣고 수정하고 넣고 수정하고의 반복이었다.

/* raycaster 형식 클릭 이벤트 */

// 1번째 방식
this.renderer.domElement.addEventListener('click', onclick, true)
var selectedObject = this.scene.resource.obj
var raycaster = new THREE.Raycaster()
function onclick(event) {
  alert('onclick')
  var mouse = new THREE.Vector2()
  raycaster.setFromCamera(mouse, this.render.camera)
  var intersects = raycaster.intersectObjects(this.scene.resource.obj, true) //array
  if (intersects.length > 0) {
    selectedObject = intersects[0]
    alert(selectedObject)
  }
}

// 2번째 방식
let pointer,
  raycaster = false

init()

function init() {
  raycaster = new THREE.Raycaster()
  pointer = new THREE.Vector2()
  document.body.appendChild(this.renderer.domElement)
  document.addEventListener('pointerdown', onPointerDown)
}

function onPointerDown(event) {
  pointer.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1)

  raycaster.setFromCamera(pointer, this.camera)

  const intersects = raycaster.intersectObjects(this.scene.resource.obj, false)

  if (intersects.length > 0) {
    const intersect = intersects[0]
  }
}

// 3번째 방식 -> three.projector을 이제 안쓴다함
document.addEventListener('mousedown', onDocumentMouseDown, false)

var projector = new THREE.Projector()

function onDocumentMouseDown(event) {
  event.preventDefault()

  var vector = new THREE.Vector3(
    (event.clientX / window.innerWidth) * 2 - 1,
    -(event.clientY / window.innerHeight) * 2 + 1,
    0.5
  )
  projector.unprojectVector(vector, this.camera)

  var ray = new THREE.Ray(this.camera.position, vector.subSelf(this.camera.position).normalize())

  var intersects = ray.intersectObjects(this.scene.resource.obj)

  if (intersects.length > 0) {
    intersects[0].object.materials[0].color.setHex(Math.random() * 0xffffff)
  }

/*
    // Parse all the faces
    for ( var i in intersects ) {
        intersects[ i ].face.material[ 0 ].color
            .setHex( Math.random() * 0xffffff | 0x80000000 );
    }
    */
// }

// 4번째 방식
const raycaster = new THREE.Raycaster() // create once
const mouse = new THREE.Vector2() // create once

mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1
mouse.y = -(event.clientY / this.renderer.domElement.clientHeight) * 2 + 1

raycaster.setFromCamera(mouse, this.camera)

const intersects = raycaster.intersectObjects(this.scene.resource.obj)

// 5번째 방식
var projector = new THREE.Projector();

function onDocumentMouseDown(event) {
  var vector = new THREE.Vector3(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
  vector = vector.unproject(camera);
  var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
  var intersects = raycaster.intersectObjects([sphere, cylinder, cube]);

  if (intersects.length > 0) {
    intersects[0].object.material.transparent = true;
    intersects[0].object.material.opacity = 0.1;
  }

 

그런데 아무리 수정해봐도 계속 TypeError: renderer is undefined 에러가 떴다.

멘토님께 sos를 치니 선언되기 전에 호출한게 문제라고 하셨다.

큰 깨달음을 얻고 순서를 선언 후 밑으로 내렸다.

그러니 저 오류는 뜨지 않아서 여러 수정을 또 거쳤다.

이 전에도 계속 시도만 했다...

 

결국 1번 방식에 멘토님 조언대로 마우스 좌표 값 조정을 추가하고 진행했는데, 클릭 자체는 감지하고 클릭한 좌표도 들어왔으나 intersects에 값이 들어오질 않았다. 콘솔로 찍어본 intersects는 빈 배열이었다.

 

무엇이 문제인고 하여 콘솔로그 죄다 찍어보고 마우스 설정을 뺐다가 넣었다가 이것저것 시도해보았지만 해결이 안됐다. 그래서 처음에 이 코드를 따온 사이트를 찾기 위해 구글링을 하다가 다음 사이트를 발견했다.

 

Code viewer - Ancient Brain

 

ancientbrain.com

여기엔 마우스를 함수에 넣은 채로 if문에서 필터를 사용하길래 혹시나 하는 마음에 비슷하게 수정하고 추가해보았다. 그리고...드디어 작동했다!!! 

 

  /* raycaster 형식 클릭 이벤트 */

  document.addEventListener('click', onclick, true)

  let selectedObject = null
  const raycaster = new THREE.Raycaster()
  const mouse = new THREE.Vector2()
  function onclick(event) {
    // alert('onclick')
    // console.log('click', event)
    // console.log('mouse', mouse)
    // console.log('scene.resource.obj', scene.resource.obj.children)
    if (selectedObject) {
      // console.log('selectedObj', selectedObject)
    }
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    raycaster.setFromCamera(mouse, cameraElement)
    const intersects = raycaster.intersectObjects(scene.resource.obj.children, true) //array
    // console.log('intersects', intersects)
    if (intersects.length > 0) {
      const res = intersects.filter(function (res) {
        return res && res.object
      })[0]
      if (res && res.object) {
        selectedObject = res.object
        alert(selectedObject.parent.name)
      }
    }
  }

intersects에도 드디어 값이 담겼다!

처음엔 잘못 본 줄 알았다

팀원분이 클릭 시에 parent.name을 쓰면 mesh의 부모격 되는 모델명을 찾을 수 있다고 알려주셔서 그걸로 해당 모델을 클릭하면 alert가 뜨도록 코드를 추가했다.

따라서 최종 코드는 다음과 같다.

  /* raycaster 형식 클릭 이벤트 */

  document.addEventListener('click', onclick, true)

  let selectedObject = null
  const raycaster = new THREE.Raycaster()
  const mouse = new THREE.Vector2()
  function onclick(event) {
    // alert('onclick')
    // console.log('click', event)
    // console.log('mouse', mouse)
    // console.log('scene.resource.obj', scene.resource.obj.children)
    if (selectedObject) {
      // console.log('selectedObj', selectedObject)
    }
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    raycaster.setFromCamera(mouse, cameraElement)
    const intersects = raycaster.intersectObjects(scene.resource.obj.children, true) //array
    console.log('intersects', intersects)
    if (intersects.length > 0) {
      const res = intersects.filter(function (res) {
        return res && res.object
      })[0]
      if (res && res.object) {
        selectedObject = res.object
        alert(selectedObject.parent.name)
        if (selectedObject.parent.name == 'StaticMesh4') {
          alert('모델 찾았습니다')
        }
      }
    }
  }

결과 (StaticMesh4 = 본체 클릭 시)

 

정말 신기하게도 깨질듯 아프던 머리가 해결하자마자 싹 나았다.

만병통치약이 따로 없다^^