OrbitControl - restrict panning movement - javascript

Is there any way to restrict the panning movement of a camera in scene?
Tried altering the pan method in orbitControls but I'm not really satisfied with the result, I wish there was more convenient/proper way to do it..
if ( scope.object instanceof THREE.PerspectiveCamera ) {
// perspective
var position = scope.object.position;
var offset = position.clone().sub( scope.target );
var targetDistance = offset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
// we actually don't use screenWidth, since perspective camera is fixed to screen height
var dist_l = ( 2 * deltaX * targetDistance / screenHeight );
var dist_u = ( 2 * deltaY * targetDistance / screenHeight );
/////// X,Y limit calculation //////
var limit = 100;
if( (position.x - dist_l) <= -limit ){
dist_l = -0.1;
}else if( (position.x - dist_l) >= limit){
dist_l = 0.1;
}
if( (position.z - dist_u) <= -limit ){
dist_u = -0.1;
}else if( (position.z - dist_u) >= (limit*2.5) ){
dist_u = 0.1;
}
/////// X,Y limit calculation //////
scope.panLeft( dist_l );
scope.panUp( dist_u );
} else if ( scope.object instanceof THREE.OrthographicCamera ) {
// orthographic
scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth );
scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight );
}

I have encountered the same problem. The solution is not to touch the pan() function but to check the limits in the update() function. Locate the line 162:
// move target to panned location
scope.target.add( panOffset );
Do your limit calculations right after this line:
if (scope.target.x > 1000)
scope.target.setX(1000);
if (scope.target.x < 0)
scope.target.setX (0);
...
This will clamp the target x-position. It works quite smoothly.

I have the exact same problem and thanks to David's solution, which gives me a lot of inspiration. I've some add up to David's answer:
If we only set target X, when keep panning to that limit, I have some unwanted rotation effect. This is because OrbitControls is working with 2 things: the target and the camera. To solve that, we need to set both target and the camera.
scope.target.setX(0);
camera.position.setX(0);
In this way, we guarantee the camera is always on the top of the object, hence no unwanted rotation happens.
If we want to keep the current rotation angle, we need to do some math. For example in my case, I only enabled the polar rotation:
let polarAngle = scope.getPolarAngle();
scope.target.set(0, camera.position.y + camera.position.z * Math.tan(polarAngle), 0);
camera.position.setX(0);
The idea is to set both target and camera position, but don't try to change the rotation angle. If there is rotation, do some math to calculate the target position first.

Related

Three.js - Trouble with raycaster.intersectObjects

I have a 3D scatter plot with spheres to represent the points and I'm trying to display information from the points when clicked. Based on answers to a couple different questions here on SO, I think I'm on the right track. Here is my onCanvasMouseDown:
event.preventDefault();
mouse.x = ( (event.clientX - renderer.domElement.offsetLeft) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( (event.clientY - renderer.domElement.offsetTop) / renderer.domElement.height ) * 2 + 1;
mouse.z = 0.5;
projector.unprojectVector( mouse, camera);
raycaster.setFromCamera( mouse, camera);
var intersects = raycaster.intersectObjects( scene.children );
if( intersects.length > 0 ) {
intersects.map(function(d) {
if(d.object.name === "sphere") {
//etc...
}
})
}
But intersects keeps coming back empty. I have a feeling the problem has something to do with the way I'm setting up the mouse coordinates, but I'm not sure how to resolve the issue.
EDIT: I seem to be able to find a sphere if I rotate the plot so that I am looking at it from above (but I need to be able to detect it regardless of the rotation). Does this indicate anything specific?
If your objects (points) belong to, for example, THREE.Group(), which is a child of the scene, then
var intersects = raycaster.intersectObjects( scene.children );
will give you negative result of intersection.
To reach group's children it's better to use
var intersects = raycaster.intersectObjects( scene.children, true );
Or you can try a different option: put the objects you want to check for intersection into an array and then pass the array to intersectObjects method.
var objects = [];
for(i=0; i<10; i++){
...
var meshobj = new THREE.Mesh(...);
scene.add(meshobj); // or group.add(meshobj);
objects.push(meshobj);
}
...
var intersects = raycaster.intersectObjects( objects ); // here you don't need recursive parameter, as you have the array with objects you indeed want to check for intersection
if you have offset, use the the boundingRect.
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
see the demo of my answer.

MouseEvent on THREE.js 3d spheres do not trigger on the right place

I'm working on someone else's project to finish it, and it's pretty cool. It is supposed to be a diagram that will dynamically generate nodes(spheres) and be able to click on them and then a new diagram with related nodes will appear.
I am working on the clic kable part now, it seems harder than i thought. I have read into raycasting and vectors. I have even found examples that get pretty close to what i want: http://mrdoob.github.io/three.js/examples/canvas_interactive_cubes.html
BUT, for some reason when i hover next to the nodes (the hover spots seem very arbitrary, as highlighted in the image) they change color. For some most of them i cant even find the "hotspots".
I think i lack some understanding of the unprojecting/converting/transformation from 3d to 2d part. And maybe that's why my mouse doesn't intersect properly with the nodes on screen i guess.
My onmousemove event:
function onDocumentMouseMove2(event){
event.preventDefault();
var canvasSvg= d3.select("#canvas-svg")[0][0];
mouse.x = ( event.clientX / canvasSvg.clientWidth) * 2 - 1;
mouse.y = - ( event.clientY / canvasSvg.clientHeight ) * 2 + 1;
var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
//raycaster.setFromCamera( mouse, camera );
raycaster.set(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects( scene.children );
//console logs
console.log("current 'canvas' div");
console.log(canvasSvg)
console.log("mouse");
console.log(mouse);
console.log("vector");
console.log(vector);
if ( intersects.length > 0 ) {
intersects[0].object.material.color.setHex( Math.random() * 0xffffff ); //gives another color to the node i hover over
intersects[0].object.callback(); //this calls the funcion "callback" i attached to the nodes.
}
for ( var i in intersects ) {
intersects[ i ].object.material.color.setHex( Math.random() * 0xffffff | 0x80000000 );
}
}
My camera
// Set camera attributes and create camera
var VIEW_ANGLE = 7, //field of view
ASPECT = WIDTH / HEIGHT,
//ASPECT = $container[0].clientWidth / $container[0].clientHeight,
NEAR = 0.1,
FAR = 10000;
var camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
http://i.imgur.com/wWlEYQT.png?1
Good day! I managed to fix it with this example:
http://jsfiddle.net/fek9ddg5/62/
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
The ray was indeed confused on where it had to be projected. I had to take note of the offSet from the top of thescreen to my canvas.

Three.js 2D X, Y coords to 3D X, Y, Z coords always in front of camera

I've looked at this question:
Mouse / Canvas X, Y to Three.js World X, Y, Z
and have implemented it in my code, the problem is that I can't seem to get it to work as others have stated.
I need to place an object in front of the camera via X and Y screen coords, not necessarily from the mouse.
This object will be at a specified distance in front of the camera, either from a pre-defined maximum distance or a calculated object distance.
Note: My Camera can be at any position in my scene and facing any direction
Here is my code:
this.reticlePos.x = 500;
this.reticlePos.y = 300;
this.reticlePos.z = 0.5;
this.projector.unprojectVector(this.reticlePos, this.camera);
//check for collisions, if collisions set distance to collision distance
var distance = this.RETICLE_RADIUS;
var direction = new THREE.Vector3( 0, 0, -1 );
direction.applyQuaternion( this.camera.quaternion );
this.rayCaster.set(this.camera.position, direction);
var collisionResults = this.rayCaster.intersectObjects(this.sceneController.obstacles);
if( collisionResults.length !== 0 ) {
// console.log('Ray collides with mesh. Distance :' + collisionResults[0].distance);
distance = collisionResults[0].distance - 20;
}
// set the reticle position
var dir = this.reticlePos.clone().sub( this.camera.position ).normalize();
var pos = this.camera.position.clone().add( dir.multiplyScalar( distance ) );
this.reticleMesh.position.copy( pos );
As you can see is is very similar to the linked question, yet I cannot see the object in front of my camera.
Any insight into this would be greatly appreciated.
I figured out the answer myself for anyone that checks this question later on.
To get my screen coordinates I needed to convert some input data from a gyroscope, and didn't realize that I still needed to do the following to my calculated screen coordinates:
this.reticlePos.x = ( this.reticlePos.x / window.innerWidth ) * 2 - 1;
this.reticlePos.y = - ( this.reticlePos.y / window.innerHeight ) * 2 + 1;
After doing this, everything works as expected.

webgl three.js get mouse position on Mesh

Honestly, i tried to find solution for question below, please help me. And sorry for my English :)
I have created a map, added a camera + added camera control of all dimensions.
And when I get the position of the cursor relative to the mesh, the position is always different from different angles.
View all code in jsfiddle.net: http://jsfiddle.net/BT3g3/
I use Three.js r49.
This code is just responsible for calculating the position.
function onDocumentMouseMove(event) {
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 intersect = ray.intersectObject( island );
if ( intersect.length > 0) { // !!
document.getElementById('z').value = intersect[0].point.z;
document.getElementById('x').value = intersect[0].point.x;
}
}
I was browsing internet and came across an article where the cursor position is caught on the map, but i fail move this method to my project :(
Please, help!
Have you tried with following ?
mouseX = ( ( event.clientX - canvas.offsetLeft ) / canvas.clientWidth ) * 2 - 1;
mouseY = - ( ( event.clientY - canvas.offsetTop ) / canvas.clientHeight ) * 2 + 1;

Compute top,left,width,height in Three.js projetion

I'm struggling with a problem: I need to position a DIV over a WebGL animation. I rotate a mesh, based on a PlaneGeometry to occupy a rectangular size, then I'd like top position there the DIV, so I need to know what is the X,Y coordinate and the rendered dimensions of the plane.
I've tried the THREE.Projection class, but didn't help, even if I projected the [0] verticle using .projectVector. It computed:
x: -0.1994540991160383
y: 0.17936202821347358
z: 0.9970982652556688
...which was little help to me.
To project a 3D point position to screen coordinates, relative to the renderer's canvas, do this:
var projector = new THREE.Projector();
var pos = projector.projectVector( position, camera );
var xcoord = Math.round( ( pos.x + 1 ) * canvas.width / 2 );
var ycoord = Math.round( ( -pos.y + 1 ) * canvas.height / 2 );
where canvas is, in this case, renderer.domElement.
A point in the upper left corner of your visible world will project to ( 0, 0 ).
three.js r.53
I found the solution. The top left point is indeed the 0 index of the plane's vertices, but you have to take into account the already performed transformations as well.
function calculateLayer()
{
// multiplyVector3 applies the performed transformations to the object's coordinates.
var topLeft = tubeCard.matrixWorld.multiplyVector3( tubeCard.geometry.vertices[0].clone() );
// index 24 is the bottom right, because the plane has 4x4 faces
var bottomRight = tubeCard.matrixWorld.multiplyVector3( tubeCard.geometry.vertices[24].clone() );
return {
topLeft: convert3Dto2D( topLeft ),
bottomRight: convert3Dto2D( bottomRight )
}
}
function convert3Dto2D( positionVector )
{
var projector = new THREE.Projector();
var pos = projector.projectVector( positionVector, camera );
var xcoord = Math.round( ( pos.x + 1 ) * window.innerWidth / 2 );
var ycoord = Math.round( ( -pos.y + 1 ) * window.innerHeight / 2 );
return { x: xcoord, y: ycoord };
}
So once you have the correct coordinates, applied with the transformations, you just have to use the 3d to 2d conversion thanks to WestLangley.

Categories