Three.js Raycasting with Camera as Origin - three.js

I'm trying to determine whether a point in space is visible to the camera or hidden behind other objects in the scene. I'm doing this by casting a ray from the position of the camera to that point in space and testing if that ray is intersected by an set of intersectable objects.
My problem is no intersections occur until the camera position itself intersects one of the objects in the set of intersectable objects.
I've created a jsfiddle in which, if an intersection is detected, a line is drawn from the camera position to the position in space i'm testing for visibility. Currently I believe, the line is only draw at specific points where the camera position intersects the set of intersectable objects.
How do I get the intersections to be registered as they should be, without having to have the camera position intersect objects in the set of intersectable objects?
the code:
var container;
var camera, controls, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 1000;
controls = new THREE.OrbitControls(camera);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [65, 83, 68];
controls.addEventListener('change', render);
// world
scene = new THREE.Scene();
var testObject_G = new THREE.CubeGeometry(100, 100, 5);
var testObject_M = new THREE.MeshBasicMaterial({
color: 0xBBBBBB
});
var testObject_Mesh = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh.position.x = -150;
scene.add(testObject_Mesh);
var testObject_Mesh2 = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh2.position.x = 0;
scene.add(testObject_Mesh2);
var testObject_Mesh3 = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh3.position.x = 150;
scene.add(testObject_Mesh3);
scene2 = new THREE.Object3D();
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
//
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.handleResize();
render();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
}
function render() {
renderer.render(scene, camera);
castRays();
}
function castRays() {
// rays
var direction = new THREE.Vector3(0, 200, -200);
var startPoint = camera.position.clone();
var ray = new THREE.Raycaster(startPoint, direction);
scene.updateMatrixWorld(); // required, since you haven't rendered yet
var rayIntersects = ray.intersectObjects(scene.children, true);
if (rayIntersects[0]) {
console.log(rayIntersects[0]);
var material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(ray.ray.origin.x, ray.ray.origin.y, ray.ray.origin.z));
geometry.vertices.push(new THREE.Vector3(ray.ray.direction.x, ray.ray.direction.y, ray.ray.direction.z));
var line = new THREE.Line(geometry, material);
scene2.add( line );
}
scene.add(scene2);
}
Thank you

For anyone currently seeing this thread, THREE.Projector has been replaced.
Three.js THREE.Projector has been moved to
The code below handles a 3D vector. If you go to the link above, the first commenter provided the code for a 2D vector.
var vector = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
var dir = new THREE.Vector3();
...
if ( camera instanceof THREE.OrthographicCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, - 1 ); // z = - 1 important!
vector.unproject( camera );
dir.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
raycaster.set( vector, dir );
} else if ( camera instanceof THREE.PerspectiveCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 ); // z = 0.5 important!
vector.unproject( camera );
raycaster.set( camera.position, vector.sub( camera.position ).normalize());
}
var intersects = raycaster.intersectObjects( objects, recursiveFlag );`

Your idea of casting a ray is good, however raycasting in three.js already does this :
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
this formula maps a pixel coordinate from Screen Space to a point in Normalized Device Coordinate ( NDC ) Space.
projector.unprojectVector( vector, camera );
maps a point from NDC Space to a point in World space
Raycaster then creates a ray from the camera position through that world point.
Here is your working Fiddle in which I changed the way of raycasting in your scene and this works, all you have to do now is creating a ray with the right coordinates that I provided you.
r.68

Related

Making breakout game in three.js - detect end of the board

i'm trying to learn Three.js by making game, sadly most resources I would be interested in are outdated, since library seem to change so often.
Currently I am able to move my paddle with my mouse and launch the ball on mouse click, however I've got no clue how to stop paddle to go over the board and make ball bounce from the edges.
Can anyone point me in the right direction? Here is my current code:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
//renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//board
geometry = new THREE.BoxGeometry(10, 6, 0.00001)
material= new THREE.MeshBasicMaterial({color: "blue"});
var board = new THREE.Mesh(geometry,material);
scene.add(board);
//paddle
geometry = new THREE.BoxGeometry(1, 0.1, 0.2);
material = new THREE.MeshBasicMaterial({ color: "red" });
var paddle = new THREE.Mesh(geometry, material);
paddle.position.set(0, -2.5, 0);
scene.add(paddle);
camera.position.z = 5;
//ball
var geometry = new THREE.SphereGeometry(0.1, 32, 32);
var material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
var ball = new THREE.Mesh(geometry, material);
ball.moving = false;
ball.position.set(0, -2.3, 0);
var velocityY = 0.05; //ball Y speed
scene.add(ball);
//mouse movements
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove(e) {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (e.clientY / window.innerHeight) * 2 + 1;
paddle.position.x = mouse.x * 5.5;
}
function onMouseClick(e) {
ball.moving = true;
}
window.addEventListener('mousemove', onMouseMove, false);
window.addEventListener('click', onMouseClick, false);
function animate() {
requestAnimationFrame(animate);
// ball.position.y += -0.01;
if (ball.moving === false) {
// console.log('jest false')
ball.position.x = paddle.position.x;
} else {
ball.position.y += velocityY;
ball.position.x += 0.01;
}
renderer.render(scene, camera);
}
animate();
Go here:
https://threejs.org/editor/
Load the arkanoid example..
Select the Scene object in the list on the right...
Click edit on the Game Logic script at the bottom...

three js raycaster not working for css2drenderer

I m trying to add 2d hotspots to a 3d model. For this, i have added a WebGLRendere for 3d model and a CSS2DRenderer for te hotspot.
I m using raycaster to detect the hotspot but it is not detecting the hotspot.
This is my code -
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.z = 7;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var platform;
var loader = new THREE.ColladaLoader();
loader.load('model/platform.dae', function(dae){
platform = dae.scene;
platform.position.set(0, -3.5, 0.5);
scene.add(platform);
scene.add(hotSpot);
});
var hotSpotDiv = document.createElement('div');
hotSpotDiv.className = 'hotSpot';
var hotSpot = new THREE.CSS2DObject(hotSpotDiv);
hotSpot.position.set(-1, 4.2, 0);
var renderer2D = new THREE.CSS2DRenderer();
renderer2D.setSize( window.innerWidth, window.innerHeight );
renderer2D.domElement.style.position = 'absolute';
renderer2D.domElement.style.top = 0;
renderer2D.domElement.style.backgroundColor = 0xff0000;
document.body.appendChild( renderer2D.domElement );
var light = new THREE.AmbientLight( 0x555555 ); // soft white light
scene.add( light );
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(-0.5, 0.5, -1);
scene.add( directionalLight );
var mouse = new THREE.Vector2();
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
var raycaster = new THREE.Raycaster();
var INTERSECTED;
function renderLoop(){
requestAnimationFrame(renderLoop);
render();
}
renderLoop();
function render(){
camera.lookAt( scene.position );
camera.updateMatrixWorld();
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children, true );
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].object ) {
console.log(intersects.length + " -- " + intersects[0].object);
INTERSECTED = intersects[ 0 ].object;
}
} else {
INTERSECTED = null;
}
renderer.render(scene, camera);
renderer2D.render(scene, camera);
console.log(body);
}
var camControls = new THREE.OrbitControls(camera);
camControls.enableDamping = true;
camControls.dampingFactor = 1;
Please let me know if there is any other better way to add 2d clickable buttons over the 3d model.
Thanks for all the help in advance!
Just a code version of what Andy has already provided as an answer.
This is in context to Amod's code.
hotSpotDiv.addEventListener('click', function(){
// Do your stuff here
});

Raycasting vertices of a mesh with three.js

with threejs i can raycast some meshes that i created like that
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
//create a triangular geometry
var material = new THREE.MeshBasicMaterial( { color : 0x00cc00 } );
var geometry = new THREE.Geometry();
geometry.vertices.push( new THREE.Vector3( -0.5, -0.5, 0 ) );
geometry.vertices.push( new THREE.Vector3( 0.5, -0.5, 0 ) );
geometry.vertices.push( new THREE.Vector3( 0.5, 0.5, 0 ) );
//create a new face using vertices 0, 1, 2
var face = new THREE.Face3( 0, 1, 2 );
//add the face to the geometry's faces array
geometry.faces.push( face );
var object1 = new THREE.Mesh( geometry, material );
scene.add( object1 );
camera.position.z = 10;
//get mouse position
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
//render funciton
function animate() {
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObject( object1 );
if ( intersects.length > 0 ) console.log( intersects );
renderer.render( scene, camera );
}
//run
window.addEventListener( 'mousemove', onDocumentMouseMove, false);
window.addEventListener( 'mousemove', animate, false);
animate();
this line of codes work very well for me. but i want to raycast of indices of this triangular shape instead of all mesh. What should i do?
i tried tihs but it does not work? why?
raycaster.insertObject( object1.geometry.vertices );
Raycaster.intersectObjects documentation
intersects should provide the intersected face, as well as the point of intersection. You can check the distance from the point to a face vertex against a minimum threshold (or simply the closest vertex to point), and return the vertex if it passes.
var iFace = intersects[0].face;
var iPoint = intersects[0].point;
var ab = iFace.a.distanceTo(iFace.b);
var ac = iFace.a.distanceTo(iFace.c);
var bc = iFace.b.distanceTo(iFace.c);
var lambda = Math.min(ab, ac, bc) - 0.1;
if(iFace.a.distanceTo(iPoint) <= lambda){
return iFace.a;
}
if(iFace.b.distanceTo(iPoint) <= lambda){
return iFace.b;
}
if(iFace.c.distanceTo(iPoint) <= lambda){
return iFace.c;
}

Create a planet orbit

i want create a red ring to visualize the Orbit of the green Sphere around the yellow Sphere. With lookat() i have orientate the rings to the green Spheres but i have no idea how i can move the rings in the right angel.
My script:
<!doctype html>
<html>
<head>
</head>
<body>
<div id="container"></div>
<!--Load three.js-->
<script src="js/three.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script>
var camera, controls, scene, renderer, raycaster;
var mouse = new THREE.Vector2();
init();
animate();
function init() {
scene = new THREE.Scene();
raycaster = new THREE.Raycaster();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
var container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 100000000000000000);
camera.position.z = 30;
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
controls.enableKeys = false;
var planet = ["-4067664386091","-710580828973","-3956610895959","2060000",
"29476716044","5149291420","-46417511315","2660000",
"124056083719","21671373654","16235707106","4810000",
"-107354576606","-18753785170","436797007078","18890000",
"-639929607985","-111789387758","-1118379774141","57970000",
"2907924314427","507985682645","-950946134275","2830000",
"-2275005926406","-397421085828","3223734974754","7480000",
"-4067664386091","-710580828973","-3956610895959","5110000"]
for ( var i = 0; i < 7; i ++ ) {
var geometry = new THREE.SphereGeometry(5, 32, 32);
var material = new THREE.MeshBasicMaterial( {color: 0x09F425} );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = planet[i * 4] / 1000000000;
mesh.position.y = planet[i * 4 + 1] / 1000000000;
mesh.position.z = planet[i * 4 + 2] / 1000000000;
scene.add( mesh );
var startPoint = new THREE.Vector3(0,0,0);
var endPoint = new THREE.Vector3(planet[i * 4] / 1000000000,planet[i * 4 + 1] / 1000000000,planet[i * 4 + 2] / 1000000000);
var direction = new THREE.Vector3().subVectors(endPoint, startPoint).normalize();
var arrow = new THREE.ArrowHelper(direction, startPoint, startPoint.distanceTo(endPoint), 0xCC0000 );
scene.add(arrow);
<!-- I want this red ring in to show the Orbit of the green Spheres -->
var geometry = new THREE.RingGeometry(startPoint.distanceTo(endPoint) - 1, startPoint.distanceTo(endPoint), 32);
var material = new THREE.MeshBasicMaterial( { color: 0xCC0000, side: THREE.DoubleSide } );
var mesh = new THREE.Mesh( geometry, material );
var testPoint = new THREE.Vector3(planet[i * 4] / 1000000000,(planet[i * 4 + 1] / 1000000000)*0.5,planet[i * 4 + 2] / 1000000000);
var pos = new THREE.Vector3();
pos.addVectors(testPoint, mesh.position);
mesh.lookAt(pos);
scene.add(mesh);
<!--------->
}
var geometry = new THREE.SphereGeometry(10, 32, 32);
var material = new THREE.MeshBasicMaterial( {color: 0xCDF409} );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
scene.add( mesh );
window.addEventListener( 'mousemove', onMouseMove, false );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
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 animate() {
requestAnimationFrame( animate );
render();
}
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);
}
</script>
</body>
</html>
If I got you right.
For orbits, there can be a rough solution:
var geometry = new THREE.CircleGeometry(startPoint.distanceTo(endPoint), 128);
geometry.vertices.shift();
geometry.rotateX(-Math.PI / 2);
var material = new THREE.LineBasicMaterial( { color: 0xCC0000 } );
var mesh = new THREE.Line( geometry, material );
and then to align your orbits to their planets:
mesh.lookAt(endPoint); // as you calculated endPoint before, then no need to calculate the same for testPoint
jsfidde example. Clarify, if I missed something from your question.

Using three.js raycaster as the developers recommend?

My code works for picking objects and turning them a different color, but what is the technique to get them back to the original color after my mouse crosses the cube? Here is my code. As it stands, when I bring my mouse over the cube, it turns red.
(document).ready(function () {
window.addEventListener('mousemove', onMouseMove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
//start a scene
var scene = new THREE.Scene();
//add a cube
var cube = new THREE.Mesh(new THREE.CubeGeometry(3, 3, 3), new THREE.MeshBasicMaterial({ color:0x454545 }));
cube.position.set(0, 0, 0);
scene.add(cube);
//get a renderer
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
$('#GameArea').append(renderer.domElement);
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 10, 10);
camera.lookAt(0,0,0);
var controls = new THREE.TrackballControls(camera);
var light = new THREE.SpotLight(0xeeeeee);
light.position.set(-10, 5, 40);
scene.add(light);
render();
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;
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);
}
}
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);
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
});

Resources