I've a scene with tube geometry and ray intersection is working fine. Upon ray intersection I am showing a small red color sphere and a tooltip next to the cursor. Please find the image without header:
Now if I add a header with div element, the ray intersection is working but the issue is there is a distance between red sphere, tooltip and a mouse cursor. Please find the image with header:
Please find below the code of header, tool-tip div, sphere and collision detection function:
Header:
<div style="margin-top:10px;margin-left:3%;height:100px;width:70%">
<label style="color:#b0bb02;font-size:14pt">three.js</label>
<label style="color:#f5f7f9;font-size:14pt;font-weight:bold">Tube Geometry</label><br><br>
</div>
Tool-tip div:
textDiv = document.createElement( 'div' );
textDiv.style.position = 'absolute';
textDiv.style.top = '50px';
textDiv.style.margin = '8px';
textDiv.style.border = '1px solid blue';
textDiv.style.zIndex = '100';
textDiv.style.background = '#ffffff';
textDiv.style.display = 'block';
container.appendChild( textDiv );
Sphere geometry:
dot = new THREE.Mesh ( new THREE.SphereGeometry( 1, 12, 1 ), new THREE.MeshBasicMaterial( { color: 0xff0000 } ) );
Collision detection:
var intersects;
function detectCollision(event){
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1,- ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
/*var mouse_x = ((event.pageX-event.target.offsetParent.offsetLeft) / renderer.domElement.width) * 2 - 1;
var mouse_y = - ((event.pageY-event.target.offsetParent.offsetTop) / renderer.domElement.height) * 2 + 1;
var vector = new THREE.Vector3(mouse_x, mouse_y, 0.5);*/
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.subSelf( camera.position ).normalize() );
intersects = ray.intersectObjects( objects );
var pnt=0; var clickedMD = 0; var clickedDegree; var clickedTVD;
if ( intersects.length > 0 ) {
dot.position.copy( intersects[0].point );
scene.add( dot );
var fi = intersects[0].faceIndex;
pnt = Math.round(fi/11);
clickedMD = pathObjColl[pnt].md;
clickedTVD = Math.round(pathObjColl[pnt].point.z);
clickedDegree = degrees[Math.round(fi%11)];
// tooltip
var elem = document.getElementsByTagName("canvas");
var canvas = elem[0];
var x = event.pageX - canvas.offsetLeft ;
var y = event.pageY - canvas.offsetTop ;
//console.log("X: "+x+" Y: "+y);
textDiv.style.top = y+"px";
textDiv.style.left = x+"px";
textDiv.innerHTML = " Degree: "+clickedDegree+"<br/> MD: "+clickedMD+" ft<br/> TVD: "+clickedTVD+" ft";
if(textDiv.style.display == 'none'){
textDiv.style.display = 'block';
}
}
else if(intersects.length == 0){
var textDivVis = textDiv.style.display;
console.log(textDivVis);
if(textDivVis == 'block'){
textDiv.style.display = 'none';
}
}
}
demo on jsfiddle
Why there is a distance between the mouse cursor, sphere geometry and a too-tip if I add header ?
Is the textDiv positioned absolutely? Maybe the header just throws off the layout of the page, and the tooltip.. Try this:
textDiv.style.position = "absolute";
EDIT:
Well, in fact it's the header that needs to be absolutely positioned.. Otherwise it will move the canvas and your mouse position in HTML does not match mouse position in the webgl canvas.
Alternatively, if you don't want the header to be on top of canvas, you can take your container position into account when calculating mouse position. For the Vector:
var vector = new THREE.Vector3(
( (event.pageX - container.offsetLeft) / window.innerWidth ) * 2 - 1,
- ( (event.pageY - container.offsetTop) / window.innerHeight ) * 2 + 1,
0.5 );
For the tooltip:
textDiv.style.top = (container.offsetTop + y)+"px";
textDiv.style.left = (container.offsetLeft + x)+"px";
Updated jsFiddle: http://jsfiddle.net/tje8y/
Related
I understand there is no THREE.projector in version 71 (see the deprecated list), but I don't understand how to replace it, particularly in this code that detects which object has been clicked on:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
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) {
clicked = intersects[0];
console.log("my clicked object:", clicked);
}
There is now an easier pattern that works with both orthographic and perspective camera types:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects, recursiveFlag );
three.js r.84
The THREE.JS raycaster documentation actually gives all the relevant information that is laid out in these answers as well as all the missing points that may be difficult to get your head around.
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
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 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);`
Hope it helps.
You can use the latest recommended version as stated above.
To get your specific code working, replace:
projector.unprojectVector( vector, camera );
with
vector.unproject(camera);
I noted that all this that was said before is fine in a full window I think, but if you have other things besides a canvas on the page you need to get the click events target's offset and remove it.
e = event and mVec is a THREE.Vector2
let et = e.target, de = renderer.domElement;
let trueX = (e.pageX - et.offsetLeft);
let trueY = (e.pageY - et.offsetTop);
mVec.x = (((trueX / de.width) * 2) - 1);
mVec.y = (((trueY / de.height) * -2) + 1);
wup.raycaster.setFromCamera( mVec, camera );
[Then check for intersections, etc.]
Looks like you need to watch for dragging (mouse movements) too or releasing from a drag will trigger an unwanted click when using window.addEventListener( 'click', function(e) {<code>});
[You'll notice I put the negative sign where it's more logical as well.]
https://github.com/mrdoob/three.js/issues/5587
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 );
Objects = array of objects of type Object3D to check for intersection with the ray. Can be everything in your scene, but might be inefficient if you have a lot of stuff there.
recursiveFlag = If true, it also checks all descendants. Otherwise it only checks intersection with the object. Default is true.
docs
I wanted to share my experiences with more details doing this since I feel the answers above do not really explain the details to get this going.
In my case I was using STL_Viewer, you can locate it here (https://www.viewstl.com/plugin/), it helps create a threeJS scene and supplies you with the camera and scene that you will need for this to work.
function mouse_click_normalize(clientX,clientY,offset_left,offset_top){
var mouse = new THREE.Vector2(); // create once
var c = document.getElementById("stl_contEye").childNodes[0];//get canvas element created in here
mouse.x = ( (event.clientX-offset_left) / (c.clientWidth) * 2) - 1;
mouse.y = - ( (event.clientY-offset_top) / (c.clientHeight) ) * 2 + 1;
console.log(`clientX=${clientX},clientY=${clientY},mouse.x=${mouse.x},mouse.y=${mouse.y}\nwidth=${c.clientWidth},height=${c.clientHeight},offset_left=${offset_left},offset_top=${offset_top}`);
return mouse;
}
Above is my function that will automatically normalize the x,y coordinates in the space that was clicked (between -1 and 1), as this is what the raycaster requires for it's setFromCamera function.
Because you may be clicking in a section of the screen with offsets I programmed it this way so it will handle no matter how it's positioned in the DOM. Just replace "stl_contEye" with the name of your div that will contain the rendered ThreeJS or STLViewer in my case.
function model_clicked(model_id, e, distance, click_type){
console.log(e);
var pos = new THREE.Vector3(); // create once and reuse
// var vec = new THREE.Vector3(); // create once and reuse
var camera = stl_viewer1.camera;
var scene = stl_viewer1.scene;
// vec.unproject( camera );
// vec.sub( camera.position ).normalize();
// //var distance2 = (distance - camera.position.z) / vec.z;
// pos.copy( camera.position ).add( vec.multiplyScalar( distance ) );
var mouse_coords_normalized = mouse_click_normalize(e.clientX,e.clientY,e.srcElement.offsetLeft,e.srcElement.offsetTop);
var raycaster = new THREE.Raycaster(); // create once
raycaster.setFromCamera( mouse_coords_normalized, camera );
//console.log(raycaster);
//console.log(scene);
var intersects = raycaster.intersectObjects( scene.children, true );
if(intersects.length > 0){
console.log(intersects);
console.log(intersects[0].point);
pos = intersects[0].point
}
}
By doing it this way, you will get the exact point in 3D space from where you clicked. The function model_clicked simply returns an event that holds the clientX or clientY, you just have to get this yourself somehow if you are not using STLViewers event for detecting a click. There are many examples above for this from the other answers.
I hope this helps someone trying to figure this out with or without stl_viewer
camera.shooting = Date.now()
document.getElementById("div5").addEventListener("mousedown", mousedown);
document.getElementById("div5").addEventListener("mouseup", mouseup);
document.getElementById("div5").addEventListener( 'mousemove', renderq);
function mousedown(event) {
camera.calculator = 1;
}
function mouseup(event) {
camera.calculator = 0;
}
function renderq(event){
if( ai2bcz.length > 0 && ai_key3.length > 0 ){
if( camera.calculator > 0 ){
camera.mouse = new THREE.Vector2();
var fleece = document.getElementById("div5").scrollHeight;
var fleeceb = document.getElementById("div5").scrollWidth;
var fees = (event.clientX / fleeceb) - 1;
var feesa = - (event.clientY / fleece) + 1;
camera.mouse.x = fees ; camera.mouse.y = feesa;
var sphereMaterialc = new THREE.MeshBasicMaterial({color: 0x0099FF});
var sphereGeoc = new THREE.SphereGeometry(5, 5, 5);
var spherec = new THREE.Mesh(sphereGeoc, sphereMaterialc);
spherec.position.set(camera.position.x, camera.position.y, camera.position.z);
spherec.raycaster = new THREE.Raycaster();
spherec.raycaster.setFromCamera( camera.mouse, camera);
spherec.owner = 'player';
spherec.health = 100;
bulletsc.push(spherec);
scene.add(spherec);
camera.lastshot = Date.now();
camera.shooting = Date.now();
}
}
}
function render() { const controlscamera = new FirstPersonControls(camera);
controlscamera.update(100);
if( ai2bcz.length > 0 && ai_key3.length > 0 ){
if( camera.calculator > 0 && camera.shooting + 25 < Date.now() ){
renderq(event)
camera.shooting = Date.now();
}}
if(bulletsc.length > 1){
for (var i = 0; i < bulletsc.length - 1; i++) {
var bu = bulletsc[i], pu = bu.position, du = bu.raycaster.ray.direction;
if(bu.owner == "player"){
var enemybulletspeeda = window.document.getElementById("bulletsplayerspeed").value;
bu.translateX(enemybulletspeeda * du.x);
bu.translateY(enemybulletspeeda * du.y);
bu.translateZ(enemybulletspeeda * du.z);
}
}}
renderer.render( scene, camera ); }
I have been developing an 3d model viewer with three.js but I have encountered this problem and I cannot seem to find any solution to it.
I want to have 2d overlay tracking a sphere while it's rotating.
I managed to code the overlay tracking the sphere referring to http://zachberry.com/blog/tracking-3d-objects-in-2d-with-three-js.
But I want to hide the tracking overlay while the sphere is screened by the cude.
So I tried to caculate intesects between camera and the sphere to determine is visible from camera.
But rayCaster.intersectObjects always return 0.
Here is my code.
How can i determain the sphere is visible or not?
$(function() {
// Some defaults/hardcoded values:
var DEFAULT_VIEW_ANGLE = 45;
var DEFAULT_CAMERA_X = 0;
var DEFAULT_CAMERA_Y = 0;
var DEFAULT_CAMERA_Z = 200;
var PERSPECTIVE_NEAR = 0.1;
var PERSPECTIVE_FAR = 10000;
var WIDTH = $('#viewport').width();
var HEIGHT = $('#viewport').height();
// state
var rotate = true;
// three.js objects:
var renderer;
var scene;
var camera;
var projector;
var container;
var cube;
var sphere;
var pointLight;
var rayCaster;
// elements
var $trackingOverlay = $('#tracking-overlay');
// create three.js elements
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer();
rayCaster = new THREE.Raycaster();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(DEFAULT_VIEW_ANGLE, WIDTH / HEIGHT, PERSPECTIVE_NEAR, PERSPECTIVE_FAR);
camera.position.set(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, DEFAULT_CAMERA_Z);
pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.set(10, 50, 150);
container = new THREE.Object3D();
cube = new THREE.Mesh(
new THREE.BoxGeometry(50, 50, 50),
new THREE.MeshLambertMaterial({wireframe: false, color: 0x00CC00})
);
cube.position.set(0, 0, 0);
sphere = new THREE.Mesh(
new THREE.SphereGeometry(4, 32, 32),
new THREE.MeshLambertMaterial({wireframe:false, color: 0xCCCC00})
);
sphere.position.set(0, 64, 0);
container.add(cube);
container.add(sphere);
scene.add(container);
scene.add(pointLight);
scene.add(camera);
// setup viewport
viewport = document.getElementById('viewport');
renderer.setSize(WIDTH, HEIGHT);
viewport.appendChild(renderer.domElement);
// start the animation loop
requestAnimationFrame(update);
function update()
{
if(rotate)
{
// hardcoded rotation for this demo:
container.rotation.z += 0.01;
container.rotation.x += 0.001;
container.rotation.y += 0.025;
}
renderer.render(scene, camera);
positionTrackingOverlay();
requestAnimationFrame(update);
}
function positionTrackingOverlay()
{
var visibleWidth, visibleHeight, p, v, percX, percY, left, top;
// this will give us position relative to the world
//p = sphere.matrixWorld.getPosition().clone();
p =new THREE.Vector3().setFromMatrixPosition(sphere.matrixWorld);
// projectVector will translate position to 2d
v = p.project(camera);
// translate our vector so that percX=0 represents
// the left edge, percX=1 is the right edge,
// percY=0 is the top edge, and percY=1 is the bottom edge.
percX = (v.x + 1) / 2;
percY = (-v.y + 1) / 2;
// scale these values to our viewport size
left = percX * WIDTH;
top = percY * HEIGHT;
// position the overlay so that it's center is on top of
// the sphere we're tracking
$trackingOverlay
.css('left', (left - $trackingOverlay.width() / 2) + 'px')
.css('top', (top - $trackingOverlay.height() / 2) + 'px');
if(isSphereVisible()){
$trackingOverlay.show();
}else{
$trackingOverlay.hide();
}
}
function isSphereVisible(){
rayCaster.setFromCamera(sphere,camera);
var intersects = rayCaster.intersectObjects(cube.children);
console.log(intersects.length);
return intersects.length>0?false:true;
}
});
Basically what i want to create:
I have a 3D map with objects, i want to select all objects that are in the 2D box x1,y1 to x2,y2 on my screen.
Any ideas how this has to be done, because i'm clueless on how to start.
Thanks in advance!
prevX and prevY is coordinate of mouse down:
function onDocumentMouseUp(event) {
event.preventDefault();
var x = (event.clientX / window.innerWidth) * 2 - 1;
var y = -(event.clientY / window.innerHeight) * 2 + 1;
var width = (x - prevX); //* window.innerWidth;
var height = (y - prevY); //* window.innerHeight;
var dx = prevX; //* window.innerWidth;
var dy = prevY; //* window.innerHeight;
console.log(
dx + ',' +
dy + "," +
(dx + width) + "," +
(dy + height) +
", width=" + width +
", height=" + height
);
var topLeftCorner3D = new THREE.Vector3(dx, dy, 1).unproject(
camera);
var topRightCorner3D = new THREE.Vector3(dx + width, dy, 1)
.unproject(camera);
var bottomLeftCorner3D = new THREE.Vector3(dx, dy + height,
1).unproject(camera);
var bottomRightCorner3D = new THREE.Vector3(dx + width, dy +
height, 1).unproject(camera);
var topPlane = new THREE.Plane();
var rightPlane = new THREE.Plane();
var bottomPlane = new THREE.Plane();
var leftPlane = new THREE.Plane();
topPlane.setFromCoplanarPoints(camera.position,
topLeftCorner3D, topRightCorner3D);
rightPlane.setFromCoplanarPoints(camera.position,
topRightCorner3D, bottomRightCorner3D);
bottomPlane.setFromCoplanarPoints(camera.position,
bottomRightCorner3D, bottomLeftCorner3D);
leftPlane.setFromCoplanarPoints(camera.position,
bottomLeftCorner3D, topLeftCorner3D);
//var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = -sphere.radius;
if (topPlane.distanceToPoint(center) < negRadius) { return false; }
if (bottomPlane.distanceToPoint(center) < negRadius) { return false; }
if (rightPlane.distanceToPoint(center) < negRadius) { return false; }
if (leftPlane.distanceToPoint(center) < negRadius) { return false; }
return true;
}
var matches = [];
for (var i = 0; i < window.objects.length; i++) {
if (isObjectInFrustum(window.objects[i])) {
window.objects[i].material = window.selectedMaterial;
}
}
}
Intersecting a box in the screen space is equivalent to intersecting a pyramid (perspective) or a cube (orthogonal view) the 3D space. I think you should define a THREE.Frustum based on your 2D box.
For perspective camera:
convert the screen-space box corner coordinates to 3D vectors (a vector from the camera position to the given point)
var topLeftCorner3D = new THREE.Vector3( topLeftCorner2D.x, topLeftCorner2D.y, 1 ).unproject(
camera );
construct 4 planes based on these points (one plane for one box side). The third point of the plane is the camera position.
topPlane.setFromCoplanarPoints (camera.position, topLeftCorner3D , topRightCorner3D )
rightPlane.setFromCoplanarPoints (camera.position, topRightCorner3D , bottomRightCorner3D )
bottomPlane.setFromCoplanarPoints (camera.position,bottomRightCorner3D , bottomLeftCorner3D )
leftPlane.setFromCoplanarPoints (camera.position, bottomLeftCorner3D , topLeftCorner3D )
Construct a Frustum based ot these planes, adding the camera near and far planes to them.
var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
Intersect the frustum with the objects
frustum.intersectsBox(object.geometry.boundingBox)
or
frustum.intersectsSphere(object.geometry.boundingSphere)
An alternative to using the Frustum object itself: you can skip the near and far planes, you only have to check whether the object is in the space bordered with your 4 planes. You can write a function like Frustum.intersectSphere() method with only 4 planes:
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = - sphere.radius;
if ( topPlane.distanceToPoint( center )< negRadius ) return false;
if ( bottomPlane.distanceToPoint( center )< negRadius ) return false;
if ( rightPlane.distanceToPoint( center )< negRadius ) return false;
if ( leftPlane.distanceToPoint( center )< negRadius ) return false;
return true;
}
You can use the bounding boxes of the objects. THREE.Geometry contains a boundingBox property. This is null by default, you should compute it by calling computeBoundingBox() directly.
scene.traverse( function ( node ) {
if ( node.geometry )
node.geometry.computeBoundingBox();
} );
Once you have the bounding boxes, the bounding box 2D coordinates are (if 'y' is the UP axis):
mesh.geometry.boundingBox.min.x
mesh.geometry.boundingBox.min.z
mesh.geometry.boundingBox.max.x
mesh.geometry.boundingBox.max.z
See http://threejs.org/docs/#Reference/Core/Geometry
(however, if you need the exact result for meshes that have non-rectangular shape, you need further computations)
For a more accurate method than subfrustum selection see marquee selection with three js. It works by projecting the 8 corners of bounding boxes onto screen space and then intersecting with screen rectangle. See also here for a 3rd faster method (written in ClojureScript) that projects 3D bounding sphere onto bounding circle in screen space, as well as implementations of the first two methods. See also related stack overflow question that tries to use GPU to perform screen projection and picking with readpixels (I haven't looked into this method since I assume readpixels would stall the rendering pipeline too much, but let me know if I am wrong).
I made a working example of subfrustum selection at http://jsbin.com/tamoce/3/ , but it is too inaccurate. The important part is:
var rx1 = ( x1 / window.innerWidth ) * 2 - 1;
var rx2 = ( x2 / window.innerWidth ) * 2 - 1;
var ry1 = -( y1 / window.innerHeight ) * 2 + 1;
var ry2 = -( y2 / window.innerHeight ) * 2 + 1;
var projectionMatrix = new THREE.Matrix4();
projectionMatrix.makeFrustum( rx1, rx2, ry1, ry2, camera.near, camera.far );
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
var viewProjectionMatrix = new THREE.Matrix4();
viewProjectionMatrix.multiplyMatrices( projectionMatrix, camera.matrixWorldInverse );
var frustum = new THREE.Frustum();
frustum.setFromMatrix( viewProjectionMatrix );
I'm using version 68 of three.js.
I would like to click somewhere and get the X, Y, and Z coordinates. I followed the steps here, but they give me a Z value of 0: Mouse / Canvas X, Y to Three.js World X, Y, Z
Basically, if I have a mesh in the scene, and I click in the middle of it, I'm hoping to be able to calculate the same values as the position of that mesh. This is just an example. I know I could use raycasting and see if I collided with a mesh and then just check its position. However, I want this to work even if I didn't click a mesh.
Is this possible? Here is a jsfiddle: http://jsfiddle.net/j9ydgyL3/
In that jsfiddle, if I could manage to click in the center of that square, I'm hoping to calculate 10, 10, 10 for the X, Y, and Z values respectively because those are the coordinates of the square's position. Here are the two functions of concern:
function getMousePosition(clientX, clientY) {
var mouse2D = new THREE.Vector3();
var mouse3D = new THREE.Vector3();
mouse2D.x = (clientX / window.innerWidth) * 2 - 1;
mouse2D.y = -(clientY / window.innerHeight) * 2 + 1;
mouse2D.z = 0.5;
mouse3D = projector.unprojectVector(mouse2D.clone(), camera);
return mouse3D;
//var vector = new THREE.Vector3(
//( clientX / window.innerWidth ) * 2 - 1,
//- ( clientY / window.innerHeight ) * 2 + 1,
//0.5 );
//projector.unprojectVector( vector, camera );
//var dir = vector.sub( camera.position ).normalize();
//var distance = - camera.position.z / dir.z;
//var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
//return pos;
}
function onDocumentMouseUp(event) {
event.preventDefault();
var mouse3D = getMousePosition(event.clientX, event.clientY);
console.log(mouse3D.x + ' ' + mouse3D.y + ' ' + mouse3D.z);
}
I left some of the other code I tried commented out. Please note that this commented-out code didn't work in the jsfiddle website, maybe because they're still using version 54 of three.js. It works fine on my machine with version 68.
Edit: To clarify, I would like to be able to get the coordinates no matter where the mouse is. I just used a mesh in this example because it's easy to verify if it works by seeing if the calculated coordinates are the same as the mesh's. What I would really like is for it to work without using raycasting on a mesh. For example, we could have it always printing the calculated coordinates to the console every time the mouse moves, no matter what is in the scene.
You should use a THREE.Raycaster for this. When you set a list of intersectObjects you will be able to get an array of objects that intersected with the ray. So you can get the position from the 'clicked' object from returned list. Check the updated fiddle here.
I also changed your Three.js to version R68
For more advanced use of THREE.RayCaster check the examples at Threejs.org/examples like this example with interactive cubes.
Relevant code from the updated fiddle:
function getMousePosition(clientX, clientY) {
var mouse2D = new THREE.Vector3();
var mouse3D = new THREE.Vector3();
mouse2D.x = (clientX / window.innerWidth) * 2 - 1;
mouse2D.y = -(clientY / window.innerHeight) * 2 + 1;
mouse2D.z = 0.5;
mouse3D = projector.unprojectVector(mouse2D.clone(), camera);
return mouse3D;
var vector = new THREE.Vector3(
(clientX / window.innerWidth) * 2 - 1, -(clientY / window.innerHeight) * 2 + 1,
0.5);
projector.unprojectVector(vector, camera);
var dir = vector.sub(camera.position).normalize();
var distance = -camera.position.z / dir.z;
var pos = camera.position.clone().add(dir.multiplyScalar(distance));
return pos;
}
function onDocumentMouseUp(event) {
event.preventDefault();
var mouse3D = getMousePosition(event.clientX, event.clientY);
console.log(mouse3D.x + ' ' + mouse3D.y + ' ' + mouse3D.z);
var vector = new THREE.Vector3( mouse3D.x, mouse3D.y, 1 );
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects(scene.children );
if(intersects.length > 0){
console.log(intersects[0].object.position);
}
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
I have a THREE.js scene where a lot of elements appear, and I need to detect what object the user is clicking on.
What I have done so far is the following. The camera does not move to much - it only changes the vertical position by a limited amount, always looking towards the same point. My approximate method is the following:
I take the coordinates if the click relative to the canvas
I translate them into horizontal and vertical coordinates in the webGL scene by means of a simple rescaling, and add a Z coordinate which is sufficiently far away.
I take a horizontal ray starting from the point above, constructed by THREE.Ray()
I use ray.intersectObjects() to find the first element along the ray.
This method approximately works, but it is sometimes a few pixels away from the actual point.
Is there a more reliable technique to find out the object where a user has clicked?
Depends on what kind of camera are you using.
1) PerspectiveCamera: is ok link that Mr.doob provides.
2) OrthographicCamera: is quite different:
var init = function() {
camera = new THREE.OrthographicCamera( SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2, NEAR, FAR);
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( e ) {
e.preventDefault();
var mouseVector = new THREE.Vector3();
mouseVector.x = 2 * (e.clientX / SCREEN_WIDTH) - 1;
mouseVector.y = 1 - 2 * ( e.clientY / SCREEN_HEIGHT );
var raycaster = projector.pickingRay( mouseVector.clone(), camera );
var intersects = raycaster.intersectObject( TARGET );
for( var i = 0; i < intersects.length; i++ ) {
var intersection = intersects[ i ],
obj = intersection.object;
console.log("Intersected object", obj);
}
}
Check out this one:
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
var object; //your object
document.addEventListener('mousedown', onMouseDown, false);
function onMouseDown(e) {
var vectorMouse = new THREE.Vector3( //vector from camera to mouse
-(window.innerWidth/2-e.clientX)*2/window.innerWidth,
(window.innerHeight/2-e.clientY)*2/window.innerHeight,
-1/Math.tan(22.5*Math.PI/180)); //22.5 is half of camera frustum angle 45 degree
vectorMouse.applyQuaternion(camera.quaternion);
vectorMouse.normalize();
var vectorObject = new THREE.Vector3(); //vector from camera to object
vectorObject.set(object.x - camera.position.x,
object.y - camera.position.y,
object.z - camera.position.z);
vectorObject.normalize();
if (vectorMouse.angleTo(vectorObject)*180/Math.PI < 1) {
//mouse's position is near object's position
}
}
Checks for intersection of the mouse and any of the Cubes in 3d space and alters it's color. Maybe this help you.
I ran into problems trying to implement this for a canvas which does not take up the entire width and height of the screen. Here is the solution I found works quite well.
Initialize everything on an existing canvas:
var init = function() {
var canvas_model = document.getElementById('model')
var viewSize = 50 // Depending on object size, canvas size etc.
var camera = new THREE.OrthographicCamera(-canvas_model.clientWidth/viewSize, canvas_model.clientWidth/viewSize, canvas_model.clientHeight/viewSize, -canvas_model.clientHeight/viewSize, 0.01, 2000),
}
Add an event listener to the canvas:
canvas_model.addEventListener('click', function(event){
var bounds = canvas_model.getBoundingClientRect()
mouse.x = ( (event.clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
mouse.y = - ( (event.clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
// Do stuff
}
}, false)
Or for a 'touchstart' event, change the lines calculating the mouse.x and mouse.y into:
mouse.x = ( (event.touches[0].clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
mouse.y = - ( (event.touches[0].clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;