Raycaster not working or triggering properly

I’ve been going through the web to learn about raycaster, I have a glb model made in blender, I am using a “mousedown” event on the canvas to get the object clicked by the raycaster, But the raycaster itself is not working as intended, I also don’t know what is going wrong, Please help,

This is my main code,

import * as THREE from "./three.js/build/three.module.js";
import { GLTFLoader } from "./three.js/examples/jsm/loaders/GLTFLoader.js";

import { OrbitControls } from "./three.js/examples/jsm/controls/OrbitControls.js";
import { RGBELoader } from "./three.js/examples/jsm/loaders/RGBELoader.js";

var canvas;
var renderer;
var camera;
var scene;
var clock;
var directionalLight;
var orbitControls;
var raycaster
var mouse = { x : 0, y : 0 };

var mainScene;
var mainAnimation;
var mainAnimationMixer;
var animationAction;

var rotateClockwise;
var rotateAntiClockwise;

const animationState = {
    RotateForward : "ROTATEFORWARDS",
    RotateBackward : "ROTATEBACKWARDS"
}

let animationStateToggle = animationState.RotateForward;

function init(){
    // Canvas
    canvas = document.querySelector('#threejscanvas');

    var animateButton = document.getElementById('animate-button');
    animateButton.onclick = function(){
        ToggleAnimation();
    }

    // Renderer
    renderer = new THREE.WebGLRenderer({canvas, antialias: true, alpha: true, logarithmicDepthBuffer: true});
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1;
    renderer.outputEncoding = THREE.sRGBEncoding;

    // Scene
    scene = new THREE.Scene();    
    
    clock = new THREE.Clock();

    // HDRI
    setupHDRI("three.js/main/hdr/", "christmas_photo_studio_04_1k.hdr");
    // Camera
    setupCamera();    
    // Lights
    setupLights();
    // GLFT Model Import
    importGltfModel("three.js/main/glb/NeonCorridor.glb");
    
    raycaster = new THREE.Raycaster();
    canvas.addEventListener( 'mousedown',onMouseDown , false );

    // Render
    renderer.render(scene, camera);
}

function animate(){
    requestAnimationFrame(animate);
    if (mainAnimationMixer !== undefined ){
        mainAnimationMixer.update(clock.getDelta());
    }
}

function render(time){
    time *= 0.001; // Time in seconds

    // Responsive display
    if(resizeRendererToDisplaySize(renderer)){
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
    }    

    renderer.render(scene, camera);

    // Called to start the loop
    requestAnimationFrame(render);   
}

function resizeRendererToDisplaySize(renderer){
    const pixelRatio = window.devicePixelRatio;
    const width = canvas.clientWidth * pixelRatio | 0;
    const height = canvas.clientHeight * pixelRatio | 0;
    const isResized = canvas.width !== width|| canvas.height !== height;
    if(isResized){
        renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    }
    return isResized;
}

function setupLights(){
    // Directional Light
    const color =  0xffffff;
    const intensity = 1;
    directionalLight = new THREE.DirectionalLight(color, intensity);
    directionalLight.position.set(-1, 2, 4);
    scene.add(directionalLight);
}

function setupCamera() {
    const fov = 60;
    const aspect = 2; // the canvas default
    const near = 0.1;
    const far = 100;
    camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.z = 2;
    orbitControls = new OrbitControls(camera, canvas);
    orbitControls.target.set(0, 0, 0);
    orbitControls.update();
}

function setupHDRI(path, name){
    new RGBELoader()
          .setDataType(THREE.UnsignedByteType)
          .setPath(path)
          .load(name, function (texture) {
            const envMap = pmremGenerator.fromEquirectangular(texture).texture;

            //scene.background = envMap;
            scene.environment = envMap;

            texture.dispose();
            pmremGenerator.dispose();
          });

    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    pmremGenerator.compileEquirectangularShader();
}

function importGltfModel(path){
    const gltfLoader = new GLTFLoader();    
    gltfLoader.load(path, (loadedModel) => {
        mainScene = loadedModel.scene;
        mainScene.traverse(meshes => {
            if (meshes.isMesh) {
                meshes.castShadow = true;
                meshes.receiveShadow = true;
            }
          });

        mainAnimationMixer = new THREE.AnimationMixer(mainScene);

        mainAnimation = loadedModel.animations;
        rotateClockwise =  THREE.AnimationClip.findByName(mainAnimation, "NeonRotation");
        rotateAntiClockwise = THREE.AnimationClip.findByName(mainAnimation, "Neon_Rotate_Reverse");        

        scene.add(mainScene);
        
    }); 
} 

function PlayAnimation(clipName, timeScale){    
    animationAction = mainAnimationMixer.clipAction(clipName);
    animationAction.reset();
    animationAction.setLoop(THREE.LoopOnce);    
    animationAction.timeScale = timeScale;
    animationAction.play();
    animate();
}

function ToggleAnimation(){
    if(animationStateToggle == animationState.RotateBackward){
        animationStateToggle = animationState.RotateForward;
        PlayAnimation(rotateClockwise, 1);
    }
    else{
        animationStateToggle = animationState.RotateBackward;
        PlayAnimation(rotateAntiClockwise, 1);
    }
}

function onMouseDown() {

    //1. sets the mouse position with a coordinate system where the center
    //   of the screen is the origin
    mouse.x = ( event.clientX / renderer.domElement.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / renderer.domElement.innerHeight ) * 2 + 1;

    //2. set the picking ray from the camera position and mouse coordinates
    raycaster.setFromCamera( mouse, camera );    

    //3. compute intersections
    var intersects = raycaster.intersectObjects( scene.children );

    if(intersects.length > 0){
        for ( var i = 0; i < intersects.length; i++ ) {
            console.log( intersects[ i ] ); 
            intersects[0].object.material.color.setHex("0x2f2f2f");
            /*
                An intersection has the following properties :
                    - object : intersected object (THREE.Mesh)
                    - distance : distance from camera to intersection (number)
                    - face : intersected face (THREE.Face3)
                    - faceIndex : intersected face index (number)
                    - point : intersection point (THREE.Vector3)
                    - uv : intersection point in the object's UV coordinates (THREE.Vector2)
            */
        }
    }    
}

requestAnimationFrame(render);
init();

hi Suraj_K_M
at first glance there is nothing strange in your code.
Your var mouse declaration is not an instance of THREE.Vector2 but in this case it’s almost the same because setFromCamera method should use only the constructor x and y.

It works with mousemove event? …some devices doesn’t like mousedown
Have you a console.log answer? is it executed?

So I changed my event to “click” and got my mouse down event working, In the mouse co-ord, I changed my code to,

    mouse.x = event.offsetX;
    mouse.y = event.offsetY;

Since the old mouse co-ord code was returning “nan” when the event was firing, Still the Raycaster part is not working…Not even working on a default box geometry mesh.

@Suraj_K_M

I think you are using a for loop where it is not neccisarry, as your mouse will only intersect one object at a time on mouse down, therefore this will not be an array, the intersects function already has scene.children as its array and will always return only one of them on click… Intersects[0] If that makes sense?

Try remove the for loop and do

if ( intersects.length > 0 ) {            
    if (intersects[0].object) {
   console.log(intersects[0].object);
 }
};  

Also your first mouse coordinates were right… Change to

    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
1 Like

Not working, I first tried in the mouse co-ord to use canvas.innerwidth/innerheight instead of window, That returned nan, Then using window.innerwidth/innerheight started working now,

But the Raycaster is not at all triggering any logs even without any for loops…

Here is the jsfiddle, It wont work without any local files and the model itself, But this will give you a whole preview of my code

https://jsfiddle.net/kf4tcps8/

hey @Suraj_K_M

canvas will not have innerWidth or innerHeight attributes, you have to simply use canvas.width and canvas.height…

i have got the example working and intersection works as expected… ( only thing is it’s loading a model that’s accessible online / not your original model but you can see in the console everything logs as expected…)

https://jsfiddle.net/svxk3t2q/3/

1 Like

Works like a charm, Really thanks for your help.

1 Like

@Suraj_K_M Perfect! Happy to help, feel free to mark as solved :wink:

1 Like

Raycaster not working correctly with pointsGeometry . when i click anywhere on the canvas it is not showing the intersect correctly

import React, { useLayoutEffect, useRef } from 'react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'dat.gui';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { MeshSurfaceSampler } from "three/examples/jsm/math/MeshSurfaceSampler";
import * as CANNON from "cannon-es"
import "./App.css"
const App = () => {
  useLayoutEffect(() => {
    const gui = new dat.GUI()
    const loader = new GLTFLoader();
    const canvas = document.querySelector('canvas.webgl')
    const scene = new THREE.Scene()
    // scene.background = new THREE.Color(0x000ff);
    const textureLoader = new THREE.TextureLoader();
    const envirnment = ("/env.hdr")
    const rotationSpeed = 0.005;
    const movementSpeed = 0.0002;

    let sampler
    let pointsGeometry = new THREE.BufferGeometry()
    const vertices = []
    const tempPosition = new THREE.Vector3();
    let pointPositions = [];
    let points;
    const pointer = new THREE.Vector2();
    const raycaster = new THREE.Raycaster();
    const intersectionThreshold = 0.1;

    const cursor = new THREE.Vector2();
    const floatingAmplitude = 1.5; // Adjust this to control the float height
    const floatingSpeed = 1;

    loader.load('/mesh3.glb', function (gltf) {
      const model = gltf.scene;
      gltf.scene.traverse((obj) => {
        if (obj.isMesh) {
          sampler = new MeshSurfaceSampler(obj).build()
        }
      })
      model.scale.set(0.1, 0.1, 0.1);

      for (let i = 0; i < 9; i++) {
        sampler.sample(tempPosition)
        vertices.push(tempPosition.x, tempPosition.y, tempPosition.z);
        pointPositions.push(tempPosition.clone());
      }

      pointsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))

      const pointsMaterial = new THREE.PointsMaterial({
        color: 0xfff0f0f,
        size: 0.04,
        blending: THREE.AdditiveBlending,
        uniforms: {
          mousePosition: { type: "v2", value: new THREE.Vector2() },
        },
        transparent: true,
        opacity: 0.4,
      })

      points = new THREE.Points(pointsGeometry, pointsMaterial);
      points.name = 'myPoints';
      points.position.set(0, -1, -1);
      points.scale.set(0.01, 0.01, 0.01);
      points.geometry.boundingBox = null
      scene.add(points)

      renderer.render(scene, camera)
      console.log("model Loaded");

    }, undefined, function (error) {
      console.error(error);
    });



    function getPointPositions() {
      const positions = [];

      for (let i = 0; i < pointsGeometry.attributes.position.count; i++) {
        const vertex = new THREE.Vector3();
        vertex.fromBufferAttribute(pointsGeometry.attributes.position, i);
        positions.push({ x: vertex.x, y: vertex.y });
      }
      return positions;
    }



    function RaycasterOnMouseDown(event) {
      event.preventDefault();
      cursor.x = ((event.clientX + canvas.offsetLeft) / canvas.width) * 2 - 1;
      cursor.y = -((event.clientY - canvas.offsetTop) / canvas.height) * 2 + 1;
      // console.log("x : " + cursor.x + " y : " + cursor.y);
      raycaster.setFromCamera(cursor, camera);
      var intersects = raycaster.intersectObjects(scene.children, true);
      if (intersects.length > 0) {
        if (intersects[0].object) {
          console.log(intersects[0].object.name);
        }
      }
    }


    const boxGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
    const boxMaterial = new THREE.MeshStandardMaterial();
    const box = new THREE.Mesh(boxGeometry, boxMaterial);

    scene.add(box);





    function createParticleSystem(model) {
      const particles = new THREE.Group();
      const numParticles = 50;

      for (let i = 0; i < numParticles; i++) {
        const instance = model.clone();

        const scale = Math.random() * 0.1 + 0.01;
        instance.scale.set(scale, scale, scale);

        const rotationX = Math.random() * Math.PI * 2;
        const rotationY = Math.random() * Math.PI * 2;
        const rotationZ = Math.random() * Math.PI * 2;
        instance.rotation.set(rotationX, rotationY, rotationZ);

        const position = new THREE.Vector3(
          Math.random() * 15 - 7.5,
          Math.random() * 10 - 5,
          -3
        );
        instance.position.copy(position);

        particles.add(instance);
      }

      return particles;
    }


    loader.load('/cone-green.glb', function (gltf) {
      const model = gltf.scene;
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true;
          node.name = 'cone-green';
        }
      });
      const particles = createParticleSystem(model);
      scene.add(particles);
    }, undefined, function (error) {
      console.error(error);
    });

    loader.load('/cone-yellow.glb', function (gltf) {
      const model = gltf.scene;
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true;
          node.name = 'cone-yellow'; // Set a name for identification
        }
      });
      const particles = createParticleSystem(model);
      scene.add(particles);
    }, undefined, function (error) {
      console.error(error);
    });

    loader.load('/cone-violet1.glb', function (gltf) {
      const model = gltf.scene;
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true;
          node.name = 'cone-violet'; // Set a name for identification
        }
      });
      const particles = createParticleSystem(model);
      scene.add(particles);
    }, undefined, function (error) {
      console.error(error);
    });




    const sizes = {
      width: window.innerWidth,
      height: window.innerHeight
    }


    const blurPlaneGeometry = new THREE.PlaneGeometry(sizes.width, sizes.height);
    const blurPlaneMaterial = new THREE.MeshStandardMaterial({
      transparent: true,
      opacity: 7,
      color: new THREE.Color(0, 0, 0),
      metalness: 0,
      clearcoat: 1,
      clearcoatRoughness: 0.1,
      transmission: 1,
    });
    const blurPlaneMesh = new THREE.Mesh(blurPlaneGeometry, blurPlaneMaterial);
    blurPlaneMesh.position.z = -2.5
    scene.add(blurPlaneMesh)


    window.addEventListener('resize', () => {
      sizes.width = window.innerWidth
      sizes.height = window.innerHeight

      camera.aspect = sizes.width / sizes.height
      camera.updateProjectionMatrix()

      renderer.setSize(sizes.width, sizes.height)
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    })


    const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
    camera.position.x = 0
    camera.position.y = 0
    camera.position.z = 1
    scene.add(camera)


    const controls = new OrbitControls(camera, canvas)


    const renderer = new THREE.WebGLRenderer({
      canvas: canvas
    })
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))


    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.8;


    const rgbeLoader = new RGBELoader();
    rgbeLoader.load(envirnment, function (texture) {
      texture.mapping = THREE.EquirectangularReflectionMapping
      // scene.background = texture;
      scene.environment = texture;
    });

    let mouseX = 0;
    let mouseY = 0;

    function onMouseMove(event) {
      mouseX = (event.clientX / window.innerWidth) * 2 - 1;
      mouseY = (event.clientY / window.innerHeight) * 2 - 1;
    }


    window.addEventListener('mousemove', onMouseMove, false);
    window.addEventListener('click', RaycasterOnMouseDown, false);
    // window.addEventListener('mousemove',()=>{
    //   console.log(pointsGeometry.attributes);
    // })


    function updateCameraPosition() {
      const maxCameraOffset = 0.3;
      const targetX = mouseX * maxCameraOffset;
      const targetY = mouseY * (maxCameraOffset - 0.4);
      camera.position.x = targetX;
      camera.position.y = targetY;
    }






    const clock = new THREE.Clock()
    let moveRight = true;





    const tick = () => {
      const elapsedTime = clock.getElapsedTime();
      // updateCameraPosition();
      if (points) {
        const pointPositions = getPointPositions();
      }

      const time = performance.now() * 0.001;


      // if (points) {
      //   for (let i = 0; i < pointsGeometry.attributes.position.count; i++) {
      //     const vertex = new THREE.Vector3();
      //     vertex.fromBufferAttribute(pointsGeometry.attributes.position, i);
      //     vertex.y = pointPositions[i].y + Math.sin(time * floatingSpeed + i) * floatingAmplitude;
      //     pointsGeometry.attributes.position.setXYZ(i, vertex.x, vertex.y, vertex.z);
      //   }
      //   pointsGeometry.attributes.position.needsUpdate = true;
      // }

      scene.traverse((object) => {
        if (object.isMesh && object.name.includes('cone')) {
          object.rotation.x += rotationSpeed;
          object.rotation.y += rotationSpeed;
        }
      });

      scene.traverse((object) => {
        if (object.isMesh && object.name.includes('cone')) {
          if (moveRight) {
            object.position.x += Math.sin(elapsedTime * movementSpeed) * 0.05;
          } else {
            object.position.x -= Math.sin(elapsedTime * movementSpeed) * 0.05;
          }

          if (object.position.x >= 2) {
            moveRight = false;
          } else if (object.position.x <= -2) {
            moveRight = true;
          }
        }
      });
      renderer.render(scene, camera)
      window.requestAnimationFrame(tick)
    }
    tick()
  }, [])



  return (
    <div id='app'>
      <canvas className="webgl"></canvas>
    </div>
  )
}

export default App