Search code examples
typescriptthree.jsraycasting

Raycaster doesn't detect rotated objects


I'm new in Three.js and I'm trying to find out, why is raycasting not finding some parts of rotated mesh object. For example, on the image bellow, mouse at position of green circle is detected, like it's on the Box, so intersectObjects return array with that box. Otherwise, mouse at position of red circle behaves like it's not on the box and intersectObjects returns empty array.

BoxGeometry

import * as THREE from 'three'
import { Mesh, MeshLambertMaterial } from 'three';

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 1000);
camera.position.z = 5;

var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor("#e5e5e5");
renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

var raycaser = new THREE.Raycaster();
var mouse = new THREE.Vector2();

var geometry = new THREE.BoxGeometry(3.5, .5, 3);
var material = new THREE.MeshLambertMaterial({ color: 0xFFCC00 });
var mesh = new THREE.Mesh(geometry, material);
mesh.rotation.set(.5, .8, 0);
scene.add(mesh);

var light = new THREE.PointLight(0xFFFFFF, 1, 500);
light.position.set(0, 1, 7);
scene.add(light);

var render = function () {
    requestAnimationFrame(render);    
    renderer.render(scene, camera);
}
render();

document.body.addEventListener("mousemove", (event) => {
    event.preventDefault();

    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = (event.clientY / window.innerHeight) * 2 - 1;
    raycaser.setFromCamera(mouse, camera);

    var intersects = Array.from(raycaser.intersectObjects(scene.children, true)).map(inter => inter.object as Mesh);

    document.body.style.cursor = intersects.length > 0 ? "pointer" : "default";

    for (var i = 0; i < scene.children.length - 1; i++) {
        var object = scene.children[i] as Mesh;
        var material = object.material as MeshLambertMaterial;
        material.emissive.set(intersects.includes(object) ? 0x666666 : 0x000000);
    }
});

Maybe it's not because of rotation of an object, but still, how to achieve the correct behavior of Raycaster?


Solution

  • You have to compute the mouse coordinates slightly differently by the usage of getBoundingClientRect(). Check out the following live example:

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 1000);
    camera.position.z = 5;
    
    const renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setClearColor("#e5e5e5");
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    
    document.body.appendChild(renderer.domElement);
    
    const raycaser = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    
    const geometry = new THREE.BoxGeometry(3.5, .5, 3);
    const material = new THREE.MeshLambertMaterial({
      color: 0xFFCC00
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.rotation.set(.5, .8, 0);
    scene.add(mesh);
    
    const light = new THREE.PointLight(0xFFFFFF, 1, 500);
    light.position.set(0, 1, 7);
    scene.add(light);
    
    function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }
    animate();
    
    document.body.addEventListener("pointermove", (event) => {
    
      const rect = renderer.domElement.getBoundingClientRect();
      mouse.x = ((event.clientX - rect.left) / (rect.right - rect.left)) * 2 - 1;
      mouse.y = -((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1;
    
      raycaser.setFromCamera(mouse, camera);
    
      const intersects = raycaser.intersectObject(scene, true);
    
      document.body.style.cursor = intersects.length > 0 ? "pointer" : "default";
    
    });
    body {
          margin: 0;
    }
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>