Zoom to object in ThreeJS - javascript

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.

Related

Click object in THREE.js with angular

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;

Three.js Inaccurate Raycaster

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);
}

Why are some of my angles incorrect when changing?

I have programed a program with JavaScript and the DOM that shows a ball bouncing in a box; it is a canvas element. The only things that do not work correctly are the angles. This is not noticeable until the angles are small, when it is clear the ball did not bounce back correctly from the box. The angle may be coming to a box gradually, and then bounce very steeply back. This seems like perhaps instead of the angle-in=angle-out, the angle that was being headed from was what was output angle. This would be equivalent of an angle in and its compliment out. The problem seems to happen with only half the types of bounces: it might not happen on one wall coming in a direction, but would on another wall coming in a direction.
: http://i.stack.imgur.com/WviEd.gif
I have posted all the code for ability for the test of the code, and a gradual angle is used, so the problem can be seen, but the angles that are the problem are in the checkAngle function.
<!doctype html>
<script src="code/chapter/15_game.js"></script>
<script src="code/game_levels.js"></script>
<script src="code/chapter/16_canvas.js"></script>
<canvas width="400" height="400"></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
var lastTime = null;
function frame(time) {
if (lastTime != null)
updateAnimation(Math.min(100, time - lastTime) / 1000);
lastTime = time;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
var x = y = 200//, angle = 2 * Math.PI * Math.random();
var angle = 2 * Math.PI / 40 + Math.PI;
function checkAngle(angle) {
if(x + 10 >= 400) {
if(angle <= Math.PI)
return angle = (Math.PI/2) + ((Math.PI / 2) - reduceAngle(angle));
else if(angle > Math.PI)
return angle = (3 * Math.PI / 2) - ((Math.PI / 2) - reduceAngle(angle));
}else if(x - 10 <= 0) {
if(angle <= Math.PI)
return angle = Math.PI/2 - reduceAngle(angle);
else if(angle > Math.PI)
return angle = 3* Math.PI/2 + (Math.PI/2 - reduceAngle(angle));
}else if(y - 10 <= 0) {
if(angle >= 3 * Math.PI /2)
return angle = Math.PI/2 - reduceAngle(angle);
else if(angle < 3 * Math.PI/2)
return angle = Math.PI - (Math.PI / 2 - reduceAngle(angle));
}else if(y + 10 >= 400) {
if(angle <= Math.PI/2)
return angle = 2*Math.PI - (Math.PI / 2 - reduceAngle(angle));
else if(angle > Math.PI/2)
return angle = Math.PI + (Math.PI / 2 - reduceAngle(angle));
}else
return angle;
}
function reduceAngle(angle) {
if(angle < Math.PI / 2) {
return angle;
}else{
angle = angle - (Math.PI / 2);
return reduceAngle (angle);
}
}
function updateAnimation(step) {
cx.clearRect(0, 0, 400, 400);
cx.lineWidth = 4;
cx.strokeRect(0, 0, 400, 400);
angle = checkAngle(angle);
x += Math.cos(angle) * step * 200;
y += Math.sin(angle) * step * 200;
cx.lineWidth = 2;
cx.beginPath();
cx.arc(x, y, 20, 0, 7);
cx.stroke();
}
</script>
When dealing with reflections in a non-rotated box you don't really need to deal with angles when reflecting. Just define an initial vector based on angle.
var vector = {
x: speed * Math.cos(angle),
y: speed * Math.sin(angle)
};
Then you simply check bounds for x and y separately and inverse the slope-value for each axis:
if (x - radius <= 0 || x + radius>= 400) {
vector.x = -vector.x; // reflect x
}
if (y - radius<= 0 || y + radius> 400) {
vector.y = -vector.y; // reflect y
}
You can always adjust the angle on the fly by adding another vector with the delta-angle.
If your bounce box would not be 0° rotated, then check out this answer for vector-reflection.
For example
Using your code as a basis, this would be implemented like this:
var cx = document.querySelector("canvas").getContext("2d");
var lastTime = null;
var x, y;
// calculate an initial vector
var angle = 20 / 180 * Math.PI; // angle -> radians
var speed = 5;
var vector = {
x: speed * Math.cos(angle),
y: speed * Math.sin(angle)
};
x = y = 200;
function frame(time) {
if (lastTime != null)
updateAnimation(Math.min(100, time - lastTime) / 1000);
lastTime = time;
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
function checkAngle() {
var dlt = 10 + 2 + 4; //include radius and line-widths;
if (x - dlt <= 0 || x + dlt >= 400) {
vector.x = -vector.x; // reflect x
}
if (y - dlt <= 0 || y + dlt > 400) {
vector.y = -vector.y; // reflect y
}
}
function updateAnimation(step) {
cx.clearRect(0, 0, 400, 400);
cx.lineWidth = 4;
cx.strokeRect(0, 0, 400, 400);
x += vector.x; // use our vector
y += vector.y;
checkAngle(); // test for hits
cx.lineWidth = 2;
cx.beginPath();
cx.arc(x, y, 20, 0, 7);
cx.stroke();
}
<canvas width="400" height="400"></canvas>
Judging by what I've seen in your drawings and demonstrations, the angles are being taken off the wrong axis. If approaching at 20 degrees, you'd expect the ball to leave at 180-20 degrees. Instead what it's doing is leaving at 90+20 degrees.
While I can't find the precise place in your code that makes this error, I felt I had to point this out in the hopes that someone can improve upon it.

RaphaelJs ViewBox zoom to point

I made a jsfiddle long time ago to demonstrate how to zoom to center (http://jsfiddle.net/Y69nm/1/). now i want to zoom to a given point (cursor), just like googleMap, but no idea how to do. I send the actual mouse coordinate to the function which handels the zoom.
here is the fiddle:
http://jsfiddle.net/Y69nm/3/
and here is the function for zooming:
function handle(delta, mousex, mousey) {
if (delta < 0) {
viewBoxWidth *= 0.95;
viewBoxHeight *= 0.95;
} else {
viewBoxWidth *= 1.05;
viewBoxHeight *= 1.05;
}
scale = paper.width / viewBoxWidth ;
console.log(scale);
// zoom to center
x = (paper.width / 2) - (viewBoxWidth / 2);
y = (paper.height / 2) - (viewBoxHeight / 2);
// i try to zoom to mouse cursor
var moveX = (mousex - (mousex * scale));
var moveY = (mousey - (mousey * scale));
x = 0 - moveX;
y = 0 - moveY;
paper.setViewBox(x, y, viewBoxWidth, viewBoxHeight);
}
I can get you a little closer:
x = 0 - moveX / scale;
y = 0 - moveY / scale;
Here is your updated fiddle. It zooms to point, however, once zoomed, if you move to another point and zoom it jumps.
UPDATE (4-30-2012):
As I needed this too, I worked on it further to eliminate the jumpiness when zooming to another point. Here is the Fiddle updated one more time with a more complete solution.

ray intersect object, picking object with mouse-pointer

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".

Categories

Resources