I have tried many solutions without success: it only detects on plane and wrong position of cube.
Because of angular event be easy to call.
1.Add a tag in html.
<div (click)="onClickCanvas($event)">
2.Add a function in typescript like this:
onClickCanvas(event) {
let intersects;
mouse['x'] = (event.clientX / window.innerWidth) * 2 - 1;
mouse['y'] = - (event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(scene.children);
if (intersects.length !== 0) console.log(intersects[0]['object'])
}
3.I use the PerspectiveCamera in THREE.js.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
camera.position.x = 0;
camera.position.y = 50;
camera.position.z = 45;
4.Let me show you a video.
I am using OrbitControls at same time.
But even I don't call the controls it didn't work...
MY QUESTION IS:
when I click the object I want to console.log, it never shows!
There's only plane I can see!
I click the sphere or cube didn't happened!
And sometime I click plane it console.log the cube...
To see what is happening there, you can add some simple object to your scene and position it on click to the point of intersection (intersections[0].position iirc). I would expect that is not where you think it is. Here is why:
You are using calculations that are only appropriate when using a fullscreen-canvas element although your canvas seems to be limited in size and not aligned with the page-origin.
So instead of
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
You need to use something like
var canvasWidth = renderer.domElement.offsetWidth;
var canvasHeight = renderer.domElement.offsetHeight;
camera = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 1, 1000)
And instead of
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
You need to compute these values relative to the canvas. The x and y values for the mouse need to be normalized coordinates. This means the left edge of the canvas has x === -1 and the right edge x === 1. The same goes for top (y === 1) and bottom (y === -1).
For this you can do something like
var rect = renderer.domElement.getBoundingClientRect();
var dx = ev.clientX - rect.x;
var dy = ev.clientY - rect.y;
mouse.x = (dx / rect.width) * 2 - 1;
mouse.y = (dy / rect.height) * 2 + 1;
Related
I'm currently working on a googly eye that follows your mouse movements. I've been able to center the googly and listen for mouse movements, but I'm having trouble finding the center of the page after I've resized it.
var DrawEye = function(eyecontainer, pupil, eyeposx, eyeposy){
// Initialise core variables
var r = $(pupil).width()/2;
var center = {
x: $(eyecontainer).width()/2 - r,
y: $(eyecontainer).height()/2 - r
};
var distanceThreshold = $(eyecontainer).width()/2.2 - r;
var mouseX = 0, mouseY = 0;
// Listen for mouse movement
$(window).mousemove(function(e){
var d = {
x: e.pageX - r - eyeposx - center.x,
y: e.pageY - r - eyeposy - center.y
};
var distance = Math.sqrt(d.x*d.x + d.y*d.y);
if (distance < distanceThreshold) {
mouseX = e.pageX - eyeposx - r;
mouseY = e.pageY - eyeposy - r;
} else {
mouseX = d.x / distance * distanceThreshold + center.x;
mouseY = d.y / distance * distanceThreshold + center.y;
}
});
// Update pupil location
var pupil = $(pupil);
var xp = 0, yp = 0;
var loop = setInterval(function(){
// change 1 to alter damping/momentum - higher is slower
xp += (mouseX - xp) / 5;
yp += (mouseY - yp) / 5;
pupil.css({left:xp, top:yp});
}, 1);
};
var pariseye1 = new DrawEye("#eyeleft", "#pupilleft", 650, 300);
I'm trying to get it to follow the mouse no matter how big or small the window size is, I'm just having trouble figuring that out.
As of right now, if you resize the page the googly eye still follows the mouse, but it becomes slight ajar and doesn't quite follow the mouse exactly. It seems like where it's actually tracking the mouse stays the same.
I'm fairly new to javascript, so if anyone could help that would be great!
Thanks, James
I am trying to detect clicks on my Plane mesh. I set up a raycaster using the examples as a guide.
Here is the code:
http://jsfiddle.net/BAR24/o24eexo4/2/
When you click below the marker line, no click will be detected even though the click was inside the plane (marker line has no effect).
Also try resizing the screen. Then, even clicks above the marker line may not work.
Maybe this has to do with use of an orthographic camera? Or not updating some required matrix?
function onMouseDown(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//console.log("x: " + mouse.x + ", y: " + mouse.y);
raycaster.setFromCamera(mouse, camera)
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
console.log("touched:" + intersects[0]);
} else {
console.log("not touched");
}
}
Your CSS will affect your raycasting calculations. One thing you can do is set
body {
margin: 0px;
}
For more information, see THREE.js Ray Intersect fails by adding div.
You also have to handle window resizing correctly. The typical pattern looks like this:
function onWindowResize() {
var aspect = window.innerWidth / window.innerHeight;
camera.left = - frustumSize * aspect / 2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = - frustumSize / 2;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
Study the three.js examples. In particular, see http://threejs.org/examples/webgl_interactive_cubes_ortho.html.
Also, read this answer, which describes how to properly instantiate an orthographic camera.
three.js r.80
Try this... It will work anywhere even you have margin and scroll!
function onMouseDown(event) {
event.preventDefault();
var position = $(WGL.ctx.domElement).offset(); // i'm using jquery to get position
var scrollUp = $(document).scrollTop();
if (event.clientX != undefined) {
mouse.x = ((event.clientX - position.left) / WGL.ctx.domElement.clientWidth) * 2 - 1;
mouse.y = - ((event.clientY - position.top + scrollUp) / WGL.ctx.domElement.clientHeight) * 2 + 1;
} else {
mouse.x = ((event.originalEvent.touches[0].pageX - position.left) / WGL.ctx.domElement.clientWidth) * 2 - 1;
mouse.y = - ((event.originalEvent.touches[0].pageY + position.top - scrollUp) / WGL.ctx.domElement.clientHeight) * 2 + 1;
}
raycaster.setFromCamera(mouse, camera)
var intersects = raycaster.intersectObjects(objects);
}
Where can I change the zoom direction in three.js? I would like to zoom in the direction of the mouse cursor but I don't get where you can change the zoom target.
updated wetwipe's solution to support revision 71 of Three.js, and cleaned it up a little bit, works like a charm, see http://www.tectractys.com/market_globe.html for a full usage example:
mX = ( event.clientX / window.innerWidth ) * 2 - 1;
mY = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 1 );
vector.unproject(camera);
vector.sub(camera.position);
camera.position.addVectors(camera.position,vector.setLength(factor));
controls.target.addVectors(controls.target,vector.setLength(factor));
OK! I solved the problem like this...just disable the zoom which is provided by THREEJS.
controls.noZoom = true;
$('body').on('mousewheel', function (e){
var mouseX = (e.clientX - (WIDTH/2)) * 10;
var mouseY = (e.clientY - (HEIGHT/2)) * 10;
if(e.originalEvent.deltaY < 0){ // zoom to the front
camera.position.x -= mouseX * .00125;
camera.position.y += mouseY * .00125;
camera.position.z += 1.1 * 10;
controls.target.x -= mouseX * .00125;
controls.target.y += mouseY * .00125;
controls.target.z += 1.1 * 10;
}else{ // zoom to the back
camera.position.x += mouseX * .00125;
camera.position.y -= mouseY * .00125;
camera.position.z -= 1.1 * 10;
controls.target.x += mouseX * .00125;
controls.target.y -= mouseY * .00125;
controls.target.z -= 1.1 * 10;
}
});
I know it's not perfect...but I hop it will help you a little bit....anyway...I'll work on it to make it even better.
So I recently ran into a similar problem, but I need the zoom to apply in a broader space. I've taken the code presented by Niekes in his solution, and come up with the following:
container.on('mousewheel', function ( ev ){
var factor = 10;
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var mX = ( ev.clientX / WIDTH ) * 2 - 1;
var mY = - ( ev.clientY / HEIGHT ) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 1 );
projector.unprojectVector( vector, camera );
vector.sub( camera.position ).normalize();
if( ev.originalEvent.deltaY < 0 ){
camera.position.x += vector.x * factor;
camera.position.y += vector.y * factor;
camera.position.z += vector.z * factor;
controls.target.x += vector.x * factor;
controls.target.y += vector.y * factor;
controls.target.z += vector.z * factor;
} else{
camera.position.x -= vector.x * factor;
camera.position.y -= vector.y * factor;
camera.position.z -= vector.z * factor;
controls.target.x -= vector.x * factor;
controls.target.y -= vector.y * factor;
controls.target.z -= vector.z * factor;
}
});
Its not pretty, but is at least functional. Improvements are welcome :)
Never heard of zoom direction,
you might want to inspect the FOV parameter of the camera,
as well as call this to apply the change:
yourCam.updateProjectionMatrix();
If you are using trackball controls,set
trackBallControls.noZoom=true;
and in mousewheel event use this code,
mousewheel = function (event) {
event.preventDefault();
var factor = 15;
var mX = (event.clientX / jQuery(container).width()) * 2 - 1;
var mY = -(event.clientY / jQuery(container).height()) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.1);
vector.unproject(Camera);
vector.sub(Camera.position);
if (event.deltaY < 0) {
Camera.position.addVectors(Camera.position, vector.setLength(factor));
trackBallControls.target.addVectors(trackBallControls.target, vector.setLength(factor));
Camera.updateProjectionMatrix();
} else {
Camera.position.subVectors(Camera.position, vector.setLength(factor));
trackBallControls.target.subVectors(trackBallControls.target, vector.setLength(factor));
}
};
I am completely new to Three.js but it's.... wonderful. I am not even a good developer. I am practicing.
I was facing the problem of zooming to the mouse location and I think I improved the code a little bit. Here it is.
// zooming to the mouse position
window.addEventListener('mousewheel', function (e) { mousewheel(e); }, false);
function mousewheel(event) {
orbitControl.enableZoom = false;
event.preventDefault();
// the following are constants depending on the scale of the scene
// they need be adjusted according to your model scale
var factor = 5;
// factor determines how fast the user can zoom-in/out
var minTargetToCameraDistanceAllowed = 15;
// this is the minimum radius the camera can orbit around a target.
// calculate the mouse distance from the center of the window
var mX = (event.clientX / window.innerWidth) * 2 - 1;
var mY = -(event.clientY / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);
if (event.deltaY < 0) {
// zoom-in -> the camera is approaching the scene
// with OrbitControls the target is always in front of the camera (in the center of the screen)
// So when the user zoom-in, the target distance from the camera decrease.
// This is achieved because the camera position changes, not the target.
camera.position.addVectors(camera.position, vector.setLength(factor));
} else {
// zoom-out -> the camera is moving away from the scene -> the target distance increase
camera.position.subVectors(camera.position, vector.setLength(factor));
}
// Now camera.position is changed but not the control target. As a result:
// - the distance from the camera to the target is changed, and this is ok.
// - the target is no more in the center of the screen and needs to be repositioned.
// The new target will be in front of the camera (in the direction of the camera.getWorldDirection() )
// at a suitable distance (no less than the value of minTargetToCameraDistanceAllowed constant).
// Thus, the target is pushed a little further if the user approaches too much the target.
var targetToCameraDistance = Math.max(minTargetToCameraDistanceAllowed,
orbitControl.target.distanceTo(camera.position));
var newTarget = camera.getWorldDirection().setLength( targetToCameraDistance ).add(camera.position);
orbitControl.target = newTarget;
camera.updateProjectionMatrix();
}
Another improvement could be to set the targetToCameraDistance to the distance of an object hit by the mouse when the user starts orbiting.
If the mouse hit an object, and the distance > minTargetToCameraDistanceAllowed,
then the new target is calculated and set.
... but I still don't know how to do this.
Thank you for your consideration, as my problem is in title already I will get directly to the point:
UPDATE 2:
Problem solved. I did not transformed them into meshes, or so it seems. With the hint got from supernova and a failed attempt to enable shadowmap it became so clear, although half a day was lost...
UPDATE 1:
The example is here: JSFiddle
.
.
Original problem:
The standard example of ray casting for object picking using the mouse coordinates, and I mean this one:
var vector = new THREE.Vector3((event.clientX / window.innerWidth ) * 2 - 1, -(event.clientY / window.innerHeight ) * 2 + 1, 1);
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObjects(cubes);
if (intersects.length > 0)
{
console.log('hit');
}
, didn't work at all.
After taking some time to think about it I have tried this:
// start a ray from your camera to the coordinates on
// x,y plane you pointed with the mouse:
// *I also draw the actual ray on the screen, it looked perfect! :( but...
var cx = event.clientX;
var cy = event.clientY;
var rx = cx - (SCREEN_WIDTH / 2);
var ry = (SCREEN_HEIGHT / 2) - cy;
var origin = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z - 200);
var direction = new THREE.Vector3(rx, ry, -20);
var ray = new THREE.Ray(origin, direction);
var intersects = ray.intersectObjects(cubes);
But the "intersect" variable is still empty.
Although it seems logical to work, because my elements are as follows:
for ( i = 0; i < cols; i++)
{
for ( j = 0; j < rows; j++)
{
var cwidth = (SCREEN_WIDTH / cols) / scale;
var cheight = (SCREEN_HEIGHT / rows) / scale;
var cdepth = 10;
var cube = THREE.SceneUtils.createMultiMaterialObject(new THREE.CubeGeometry(cwidth, cheight, cdepth, 1, 1, 1), getMaterials());
// getMaterials() is a simple function returning an Array of materials
cube.position.x = cwidth * i - (SCREEN_WIDTH / 2) + cwidth / 2;
cube.position.x /= scale;
cube.position.y = cheight * j - (SCREEN_HEIGHT / 2) + cheight / 2;
cube.position.y /= scale;
cube.position.z = 0;
cubes.push(cube);
scene.add(cube);
}
}
And my camera is:
camera.position.z = getDistance(window.innerHeight);
// or {x: 0, y:0, z: 403.00000000000006}
I am trying to fix this for over two hours now, can anybody see what I am missing? And I have searched the internet with no avail.
P.S. I hope i did not tagged this question erroneous with "ray".
There are several excellent stack questions (1, 2) about unprojecting in Three.js, that is how to convert (x,y) mouse coordinates in the browser to the (x,y,z) coordinates in Three.js canvas space. Mostly they follow this pattern:
var elem = renderer.domElement,
boundingRect = elem.getBoundingClientRect(),
x = (event.clientX - boundingRect.left) * (elem.width / boundingRect.width),
y = (event.clientY - boundingRect.top) * (elem.height / boundingRect.height);
var vector = new THREE.Vector3(
( x / WIDTH ) * 2 - 1,
- ( y / HEIGHT ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( scene.children );
I have been attempting to do the reverse - instead of going from "screen to world" space, to go from "world to screen" space. If I know the position of the object in Three.js, how do I determine its position on the screen?
There does not seem to be any published solution to this problem. Another question about this just showed up on Stack, but the author claims to have solved the problem with a function that is not working for me. Their solution does not use a projected Ray, and I am pretty sure that since 2D to 3D uses unprojectVector(), that the 3D to 2D solution will require projectVector().
There is also this issue opened on Github.
Any help is appreciated.
Try with this:
var width = 640, height = 480;
var widthHalf = width / 2, heightHalf = height / 2;
var vector = new THREE.Vector3();
var projector = new THREE.Projector();
projector.projectVector( vector.setFromMatrixPosition( object.matrixWorld ), camera );
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
For modern Three.js (r75), a vector can be projected onto the screen with:
var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;
var pos = object.position.clone();
pos.project(camera);
pos.x = ( pos.x * widthHalf ) + widthHalf;
pos.y = - ( pos.y * heightHalf ) + heightHalf;
For everyone getting deprecated or warnings logs, the accepted answer is for older Three.js versions. Now it's even easier with:
let pos = new THREE.Vector3();
pos = pos.setFromMatrixPosition(object.matrixWorld);
pos.project(camera);
let widthHalf = canvasWidth / 2;
let heightHalf = canvasHeight / 2;
pos.x = (pos.x * widthHalf) + widthHalf;
pos.y = - (pos.y * heightHalf) + heightHalf;
pos.z = 0;
console.log(pos);
None of these answers worked for me but they were very close, so I investigated a little bit more and combining some code from those answers plus this article I was able to make it work with the following snippet
const vector = new THREE.Vector3();
const canvas = renderer.domElement; // `renderer` is a THREE.WebGLRenderer
obj.updateMatrixWorld(); // `obj´ is a THREE.Object3D
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera); // `camera` is a THREE.PerspectiveCamera
const x = Math.round((0.5 + vector.x / 2) * (canvas.width / window.devicePixelRatio));
const y = Math.round((0.5 - vector.y / 2) * (canvas.height / window.devicePixelRatio));