Three.js raycast produces empty intersects array - javascript

I'm trying to detect when the user has clicked on a specific cube in my 3d scene
I've seen a few similar questions but none seem to have quite the same problem as me.
I have a 3D Array of cubes which populates and displays fine but when my mouse down function is called, the intersect array is always empty - I can't see what's wrong and would appreciate any help.
My renderer is set up like so:
function setupRenderer()
{
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColorHex( 0xEEEEEE, 1 );
renderer.domElement.addEventListener( 'mousedown', onDocumentMouseMove, false );
$('body').append(renderer.domElement);
}
and the event handler is:
function onDocumentMouseDown(event)
{
console.log("mouse clicked!");
event.preventDefault();
if(event.target == renderer.domElement)
{
var mouseX = (event.clientX / window.innerWidth)*2-1;
var mouseY = -(event.clientY /window.innerHeight)*2+1;
var vector = new THREE.Vector3(mouseX, mouseY, 0.5);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.subSelf(camera.position).normalize());
var intersects = raycaster.intersectObjects(cubes);
console.log("intersects.length: " + intersects.length);
if ( intersects.length > 0 ) {
console.log("intersected objects");
/* do stuff */
}
}
}
You can see the current project in action at http://kev-adsett.co.uk/experiments/three.js/experiment1/

You need to pass in a single array of objects into
var intersects = raycaster.intersectObjects( objects );
If the objects array is hierarchal (i.e., one of the objects has a child), then you need to specify it this way:
var intersects = raycaster.intersectObjects( objects, true );
You can also pass in scene.children.
This function will not work with your "cubes" data structure.
three.js r.54

Related

Three.js Using Raycaster to detect line and cone children of ArrowHelper

I have a functioning Raycaster for a simple painting app. I use it for a "bucket tool" in which the user can click on an object and change its color. It works for geometry objects such as BoxGeometry and CircleGeometry, but I'm struggling to apply it to the children of an ArrowHelper object. Because ArrowHelper isn't a shape and does not possess a geometry attribute, Raycaster does not detect collision with its position when checking scene.children for intersections. However, the children of ArrowHelper objects are always two things: a line and a cone, both of which have geometry, material, and position attributes.
I HAVE TRIED:
Toggling the recursive boolean of the function .intersectObjects(objects: Array, recursive: Boolean, optionalTarget: Array ) to true, so that it includes the children of the objects in the array.
Circumventing the ArrowHelper parent by iterating through scene.children for ArrowHelper objects and adding their lines and cones into a separate array of objects. From there I attempted to check for intersections with only the list of lines and cones, but no intersections were detected.
Raycaster setup:
const runRaycaster = (mouseEvent) => {
... // sets mouse and canvas bounds here
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
for (let i = 0; i < intersects.length; i++) {
// works for GEOMETRY ONLY
// needs modifications for checking ArrowHelpers
intersects[i].object.material.color.set(currentColor);
}
}
};
Here's my attempt to check the lines and cones individually, without the ArrowHelper parent:
let arrowObjectsList = [];
for (let i = 0; i < scene.children.length; i++) {
if (scene.children[i].type === 'ArrowHelper') {
arrowObjectsList.push(scene.children[i].line);
arrowObjectsList.push(scene.children[i].cone);
} else {
console.log(scene.children[i].type);
}
}
console.log(arrowObjectsList); // returns 2 objects per arrow on the canvas
// intersectsArrows always returns empty
const intersectsArrows = raycaster.intersectObjects(arrowObjectsList, true);
SOME NOTES:
Every ArrowHelper, its line, and its cone have uniquely identifiable names so they can be recolored/repositioned/deleted later.
The Raycaster runs with every onMouseDown and onMouseMove event.
Notably, the line and cone children of ArrowHelpers are BufferGeometry and CylinderBufferGeometry, respectively, rather than variations of Geometry. I'm wondering if this has anything to do with it. According to this example from the Three.JS documentation website, BufferGeometry can be detected by Raycaster in a similar fashion.
Setting recursion = true worked for me. Run the simple code below, and click on the arrow head. You will see the intersection information printed to the console. (three.js r125)
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const mesh = new THREE.ArrowHelper(
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, 0),
2,
0xff0000,
1,
1
);
scene.add(mesh);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
render();
// RAYCASTER STUFF
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
renderer.domElement.addEventListener('mousedown', function(e) {
mouse.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
});
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
After a closer inspection, it was a matter of the set position, not necessarily the arrow. The position of the arrow varied based on user mouse click to specify the start point. However, it still presented several problems: It was very difficult to select the line because the lineWidth value of LineBasicMaterial cannot have any other value besides 1, despite being editable. This is due to a limitation in the OpenGL Core Profile, as addressed in the docs and in this question. Similarly, the cone would not respond to setLength. This limits the customization of the ArrowHelper tool pretty badly.
Because of this, I decided to entirely replace ArrowHelper with two objects coupled together: tubeGeometry and coneGeometry, both assigned a MeshBasicMaterial, in a way which can be accessed by Raycasters out of the box.
... // the pos Float32Array is set according to user mouse coordinates.
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
});
// Because there are only two vectors, no actual curve occurs.
// Therefore, it's our straight line.
const tubeGeometry = new THREE.TubeBufferGeometry(
new THREE.CatmullRomCurve3([v1, v2]), 1, 3, 3, false);
const coneGeometry = new THREE.ConeGeometry(10, 10, 3, 1, false);
arrowLine = new THREE.Mesh(tubeGeometry, material);
arrowTip = new THREE.Mesh(coneGeometry, material);
// needs names to be updated later.
arrowLine.name = 'arrowLineName';
arrowTip.name = 'arrowTipName';
When placing the arrow, the user will click and drag to specify the start and end point of the arrow, so the arrow and its tip have to be updated with onMouseMove. We have to use Math.atan2 to get the angle in degrees between v1 and v2, with v1 as the center. Subtracting 90 orients the rotation to the default position.
... // on the onMouseMove event, pos is updated with new coords.
const setDirection = () => {
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
// copying the v2 pos ensures that the arrow tip is always at the end.
arrowTip.position.copy(v2);
// rotating the arrow tip according to the angle between start and end
// points, v1 and v2.
let angleDegrees = 180 - (Math.atan2(pos[1] - pos[4], pos[3] - pos[0]) * 180 / Math.PI - 90);
const angleRadians = angleDegrees * Math.PI / 180;
arrowTip.rotation.set(0, 0, angleRadians);
// NOT VERY EFFICIENT, but it does the job to "update" the curve.
arrowLine.geometry.copy( new THREE.TubeBufferGeometry(new THREE.CatmullRomCurve3([v1, v2]),1,3,3,false));
scene.add(arrowLine);
scene.add(arrowTip);
};
Out of the box, this "arrow" allows me to select and edit it with Raycaster without a problem. No worrying about line positioning, line thickness, or line length.

three.js orbit control with raycaster

I`m using three.js. To move around in my scene I am using orbit control and to select my objects I use the raycaster. The Raycaster is sent on click, as well as the orbit control. So if an object is selected and i move the camera around on mouse release, another object will be selected. Is there a way to check for camera movement in orbit control.
Or what is the common way to prevent unwanted selection?
Here is my selection handler:
function onMouseClick( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / canvasWidth) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( CentroLite.children, true );
if (intersects.length === 0){
intersects = raycaster.intersectObjects( millingTable.children, true );
}
SELECTED = intersects[0].object;
// DO SOMETHING
}
thanks
Flags might come handy here. You could prevent the selection from happening if the mouse is moved. Something like:
var doClickOnRelease = false;
document.onmousedown = function() {
// Get ready to see if the user wants to select something
doClickOnRelease = true
};
document.onmouseup = function() {
if (doClickOnRelease) {
// Your select function
};
document.onmousemove = function() {
// Since you're dragging, that must be because you
// didn't intend to select something in the first place
doClickOnRelease = false;
};

Threejs object selection issue

Im currently working on an small web-application which is using threejs. I ran into the following issue:
I build a prototype which contains my threejs content and everything works well here (The canvas is in the prototype window.innerWidth and window.innerHeight => so has the same size as my Browser window. Selecting works well but I want to use the canvas on my web page application and picking of 3d surfaces needs to work as well there.
I discovered as soon as I change the margin or top via CSS of the canvas it doesn't work anymore. The web-application is based on a scroll page and the threejs canvas is inside a div container which can only be seen by scrolling through the page.
For picking I use the following logic/code -> this one works well in the "fullscreen prototype" but not in the web application page
self.renderer.domElement.addEventListener( 'click', function(event){
event.preventDefault();
//CONVERT MOUSE POSITION TO CORRECT VECTOR
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
//TRANSLATES A 2D POINT FROM Normalized Device Coordinates TO RAYCASTER THAT CAN BE USED FOR PICKING
self.projector.unprojectVector( vector, self.camera );
//RAYCASTER IS NEEDED TO DETECT INTERACTION WITH CUBE SURFACE
var raycaster = new THREE.Raycaster( self.camera.position, vector.sub( self.camera.position ).normalize() );
var intersects = raycaster.intersectObjects( self.scene.children );
//CHANGE COLOR BASED ON INTERSECTION WITH ELEMENT
if ( intersects.length > 0 ) {
//SELECTED OBJECT
}
}, false );
I think that the calculation is wrong for the var vector but I just can't figure it out how to do it correctly.
Any help would be appreciated
Thank you
best reards
200% way
var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;
var vector = new THREE.Vector3();
vector.set( ( x / renderer.domElement.width ) * 2 - 1, - ( y / renderer.domElement.height ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
Or see this example. Look at messages in the console.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.draggable = false;
EventsControls.onclick = function() {
console.log( this.focused.name );
}
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
EventsControls.attach( mesh );
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
If you want to use it in your webpage, you probably need to calculate the vector with the width and height from your canvas element instead of the window which is your whole browser window.

Threejs update objects position when animating (Blender export, morph animation)

I've got an exported animation from blender loaded into my scene. What I’m trying to achieve is to click the object, the object animated 50% the way through it's keyframes and stops, then if I click the object again the remaining 50% of keyframes are iterated over.
So far I have the object animating to 50%. The issue is that no intersection is found in the click eventhandler.
function mousedown( event )
{
event.preventDefault();
// update mouse object
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// find intersections
// create a reay with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
var ray = projector.pickingRay( vector.clone(), camera );
//Check for intersections
var intersects = ray.intersectObjects( targetList , true);
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
console.log(intersects);
//Start animating - animation is a global var in this scene triggering
//the animation.
animation = true;
}
else
{
console.log("No intersections");
}
}
This works when the object is first instantiated. It doesn't work after the object has 'been animated' though. After some debugging I can see that even after the object has moved (the first 50% of the animation has played through) the object's geometry's position = (0, 0, 0).
Which seems to make sense as to why the mousedown event isn't picking up an intersection, as in the 'world' the object isn't actually positioned where it seems to be on the screen.
Here's the code for loading the object:
jsonLoader.load("assets/models/cheese01.js", function(geometry){
geometry.computeMorphNormals();
geometry.computeVertexNormals();
// geometry.computeBoundingSphere();
// geometry.normalsNeedUpdate = true;
// verticesNeedUpdate = true;
var material = new THREE.MeshLambertMaterial({
color: 0xaa1100,
morphTargets: true,
morphNormals: true,
wrapAround: true
});
var mesh = new THREE.MorphAnimMesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.duration = 3000;
mesh.time = 0;
mesh.scale.set( 1, 1, 1 );
mesh.rotation.x = degToRad(175);
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
geometry.normalsNeedUpdate = true;
mesh.parseAnimations();
scene.add(mesh);
//Objects subject to intersection checks (from mouse click)
targetList.push(mesh);
//Objects to animate
morphs.push(mesh);
});
Cheers in advance for any help!

Reset position of SELECTED Object after clicking Three.js

I'm trying to reset the position of my latest Object in three.js.
First of all I have this:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
controls.enabled = false;
SELECTED = intersects[ 0 ].object;
container.style.cursor = 'move';
//mouse Click goes here
buildMenu();
}
}
In my buildMenu Function I position my Object like this:
function buildMenu(){
camera.position.set(0,60,250);
//HERE I POSITION MY SELECTED OBJECT
SELECTED.position.x = 0;
SELECTED.position.y = 50;
SELECTED.position.z = 200;
$(document).bind('click',function(){
$(document).click(function(){
scene.remove(overlayBG);
**//HERE I TRY TO RESET EVERYTHING BUT NOTHING WORKS : (**
});
});
//$('#bg, #menu').fadeIn(100)
}
I thought I can easily reset my Object by mesh.position.set(0,0,0) inside the $(document).click Function but that doesn't work. How can I detect the latest (Re-Positioned) Object and how can I reposition it.
I'm not sure if I understood correctly.
Made a simple example, You don't need to bind any click events for this. Example is here
//last selected object
if ( SELECTED ){
SELECTED.position.set(0,0,0);
}
//new selected object
SELECTED = intersects[ 0 ].object;

Categories

Resources