Raycast an object to enbable a mouse click event with Three.js
Asked Answered
S

2

3

I'm adding objects to a scene for each entry in a database. I had a cube appearing in the scene for entry, but when I tried to add raycasting to click on objects it doesnt work, the objects dont appear and the console reads "Expression unavailable".. I got parts of the code from the three.js website for Raycasting so not sure what I'm doing wrong.

Here is the JS code:

var renderer, scene, container, camera;
var geometry, material;
var controls, group;

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


init()

function onMouseMove( event ) {

    // calculate mouse position in normalized device coordinates
    // (-1 to +1) for both components

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

}

function init() {
    // init renderer
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    // document.body.appendChild( renderer.domElement );

    container = document.getElementById('container');
    container.appendChild( renderer.domElement );

    // init scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0xffffff );

    group = new THREE.Group();
    scene.add( group )

    //fetch data from database and add object for each entry
    getData()
    async function getData() {
        var response = await fetch('/api/indexvr');
        var data = await response.json();
        console.log(data) 

        for (var i=0; i<data.length; i++) {
            cube = new THREE.Mesh( geometry, material );
            cube.position.x = i;
            scene.add(cube);
            //group.add(data)
        }
    }

    // init camera
    camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 15, 15, 15 ); //camera.position.set( 5, 0, 10 );
    camera.lookAt( scene.position );
    // controls = new OrbitControls( camera, renderer.domElement );
    // controls.enableRotate = true;
}

function render() {

    // update the picking ray with the camera and mouse position
    raycaster.setFromCamera( mouse, camera );

    // calculate objects intersecting the picking ray
    var intersects = raycaster.intersectObjects( scene.children );

    for ( var i = 0; i < intersects.length; i++ ) {

        intersects[ i ].object.material.color.set( 0xff0000 );

    }

    renderer.render( scene, camera );

}

window.addEventListener( 'mousemove', onMouseMove, false );

window.requestAnimationFrame(render);

The HTML just has a div called "container" and this tag:

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>

It doesn't produce any error it only says this in the console: enter image description here

So it is fetching the data but can't render the scene

var renderer, scene, container, camera;
var geometry, material;
var controls, group;

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


init()

function onMouseMove(event) {

  // calculate mouse position in normalized device coordinates
  // (-1 to +1) for both components

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

}

function init() {
  // init renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // document.body.appendChild( renderer.domElement );

  container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // init scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);


  group = new THREE.Group();
  scene.add(group)

  //fetch data from database and add object for each entry
  getData()
  async function getData() {
    /**
     * @author TheJim01
     * Replacing DB call with fake data to make it work here.
     * Nancy: Please feel free to add appropriate data.
     */
    // var response = await fetch('/api/indexvr');
    // var data = await response.json();
    var data = [{}, {}, {}, {}, {}]
    console.log(data)

    for (var i = 0; i < data.length; i++) {
      cube = new THREE.Mesh(geometry, material);
      cube.position.x = i;
      scene.add(cube);
      //group.add(data)
    }
  }

  // init camera
  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.set(15, 15, 15); //camera.position.set( 5, 0, 10 );
  camera.lookAt(scene.position);
  // controls = new OrbitControls( camera, renderer.domElement );
  // controls.enableRotate = true;
}

function render() {

  // update the picking ray with the camera and mouse position
  raycaster.setFromCamera(mouse, camera);

  // calculate objects intersecting the picking ray
  var intersects = raycaster.intersectObjects(scene.children);

  for (var i = 0; i < intersects.length; i++) {

    intersects[i].object.material.color.set(0xff0000);

  }

  renderer.render(scene, camera);

}

window.addEventListener('mousemove', onMouseMove, false);

window.requestAnimationFrame(render);
<script src="//threejs.org/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.js"></script>
<script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script>

<div id="container"></div>
Seely answered 27/9, 2019 at 13:30 Comment(6)
Please post the full text of the error message.Rhizome
There actually isn't any error message, I've added a screenshot of the console message.Seely
When I comment out the window methods like so: ``` // window.addEventListener( 'mousemove', onMouseMove, false ); // window.requestAnimationFrame(render); ``` The background of the scene renders but not the cubes and the console produces exactly the same message..Seely
I'm not seeing the "Expressions: not available" message in my browser when I run the snippet. You may have some kind of watch enabled in your debugger? Also, just a note, You only need one <script> reference to three.js--the one to threejs.org and the other to cdnjs.cloudflare.com currently point to what amounts to the same file. I recommend only using the one from cdnjs because eventually the threejs.org file will be replaced with the latest version.Rhizome
Oh good to know thanks. Unusual that the message is exclusive to my browser, i dont have any debugger running..Seely
Is raycasting code actually functioning for you when you run the snippet?Seely
R
3

There are several things wrong that I can see. Whether that's because you omitted sections of your code, I can't say.

First, the code you provided does not define geometry, nor a material. You implied you're drawing a cube for each DB result, so I'll make an assumption and use BoxBufferGeometry. You also don't have any lights defined, so I'll just use MeshBasicMaterial which doesn't need lights.

With those out of the way, it looks like you're halfway to setting up a render loop using window.requestAnimationFrame, but you still only call render once, even though your DB fetching is asynchronous. In other words, the render might occur before you even get a response from your DB, so you would see nothing. I've added some boilerplate code to set up a render loop, similar to how three.js does in its examples.

Interestingly enough, that was all it took. The raycaster started working, and I was able to console log the results out. I did get some false-positives when the scene first started rendering, but that's because there hadn't been any mouse input yet, so it was raycasting from the middle of the screen (where the first cube exists).

Normally, you wouldn't want to raycast for every frame, but I understand the VR situation might be different (darn fidgety humans).

Finally, one last change I made was to give each cube its own material (well, clones of the original). This was necessary to ensure you could raycast against each one individually.

// Need to create geometry and material
var geometry = new THREE.BoxBufferGeometry(0.5, 0.5, 0.5);
var material = new THREE.MeshBasicMaterial({
  color: "green"
});

var renderer, scene, container, camera;
var controls, group;

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

init()

function onMouseMove(event) {

  // calculate mouse position in normalized device coordinates
  // (-1 to +1) for both components

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

}

function init() {
  // init renderer
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // document.body.appendChild( renderer.domElement );

  container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // init scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);


  group = new THREE.Group();
  scene.add(group)

  //fetch data from database and add object for each entry
  getData()
  async function getData() {
    /**
     * @author TheJim01
     * Replacing DB call with fake data to make it work here.
     * Nancy: Please feel free to add appropriate data.
     */
    // var response = await fetch('/api/indexvr');
    // var data = await response.json();
    var data = [{}, {}, {}, {}, {}]
    //console.log(data)

    for (var i = 0; i < data.length; i++) {
      cube = new THREE.Mesh(geometry, material.clone());
      cube.position.x = i;
      scene.add(cube);
      //group.add(data)
    }
  }

  // init camera
  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.set(15, 15, 15); //camera.position.set( 5, 0, 10 );
  camera.lookAt(scene.position);
  // controls = new OrbitControls( camera, renderer.domElement );
  // controls.enableRotate = true;
}

function render() {

  // update the picking ray with the camera and mouse position
  raycaster.setFromCamera(mouse, camera);

  // calculate objects intersecting the picking ray
  var intersects = raycaster.intersectObjects(scene.children);
  if (intersects.length > 0) {
    console.log(intersects);
  }

  for (var i = 0; i < intersects.length; i++) {

  intersects[i].object.material.color.set(0xff0000);
  

  }

  renderer.render(scene, camera);

}

window.addEventListener('mousemove', onMouseMove, false);

// Here's the bbasic render loop implementation
function animate() {
  requestAnimationFrame(animate);
  render();
}
animate();
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js"></script>

<div id="container"></div>
Rhizome answered 28/9, 2019 at 1:40 Comment(1)
Wonderful! Thank you very much for your efforts TheJim01. Finding three.js quite tricky so really appreciate it.Seely
L
1

I think you never add three-orbitcontrols module. According to the three.js documentation we need to add orbit controls in a file separately.

https://threejs.org/docs/#examples/en/controls/OrbitControls

https://www.npmjs.com/package/three-orbitcontrols

Have you the OrbitControls.js file in your project?

Leucocytosis answered 27/9, 2019 at 14:25 Comment(2)
Please provide detailed answer and then references.Orms
Hi Alexander. Welcome to Stack Overflow. Thank you for taking the initiative to take the tour. Regarding your answer, it is not a requirement to use any type of mouse controller in the three.js project, and Nancy has commented-out the lines referencing OrbitControls. For these reasons, including the OrbitControls.js file is not necessary.Rhizome

© 2022 - 2024 — McMap. All rights reserved.