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:
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>
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>