Move the camera along its LookAt axis in Three.js? - three.js

I'm new to Three.js, so if this is obvious then apologies upfront.
I set my camera using the LookAt method, and would like to move along that vector when pressing a key on the keyboard.
I've tried mimicking the feature by subtracting the camera vector from the point I'm looking at via subVector, normalized the resulting vector, then did something along the lines of:
camera.position.x += lookAtVector.x
camera.position.y += lookAtVector.y
camera.position.z += lookAtVector.z
...but I appear to be drifting a bit and not moving toward the lookAt point when I do this. How can I move along a lookAt vector in three.js?

The camera is looking down it's negative-Z axis, so you want to do this:
camera.translateZ( - distance );
three.js r.57

Related

Three.js: How can I find the camera's current plane

In three.js, when we define the camera we use something like camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, near, far); If the object is moved outside the bounds set by the two planes, far and near then it is clipped.
Suppose now I rotate the object and I zoom in/out.
How can I find the current plane my camera is on please? I am learning three.js, hence I dont know If I am explaining this clear enough.
I thought that it was camera.position.z that would give me this info. In fact, I think it gives the correct value when my camera looks down the z-axis. However when I rotate by 90 degrees, (effectively moving my camera on the x-axis) the value of camera.position.z changes by a lot.
I have added a graph in case it helps. The plane defined by the blue outline cuts through the data and is parallel to the far and near planes. As I zoom in and out, I am moving the plane forward and backward, right? Do I understand this correct or I have totally missed it? I would like to know the value (a float between far and near) indicating how far that blue plane is from the camera. If you rotate that distance shouldn't change
My end goal is to be able to find-out how close the scene is to the viewer. If it gets too close then I will be adding some more elements to the scene. If it is too far these elements will be removed.

Raycasting to intersect objects that have been displaced by vertex shader

Let's say I have a vertical list of meshes created from PlaneBufferGeometry with ShaderMaterial. The meshes are distributed vertically and evenly spaced.
The list will have two states:
Displaying the meshes as they are
Displaying meshes with each object's vertices transformed by the vertex shader to the same arbitrary value, let's say z = -50. This gives a zoomed out effect and the user can scroll through this list (in the code we do this by moving the camera y position)
In my app I'm trying to make my mouseover events work for the second state but it's tricky since the GPU transforms the vertices so the updated vertices are not reflected in the attributes on the JS side.
*Note I've looked into GPU picking and do not want to use it because I believe there should be a simpler way to do this without render targets
Attempted Solution
My current approach is to manually change the boundingBox of each plane when we are in the second state like so:
var box = new THREE.Box3().setFromObject(plane);
box.min.z = -50;
box.max.z = -50;
plane.geometry.boundingBox = box;
And then to change the boundingSphere's center to have the same z position of -50 after computing it.
I did this approach because I looked into the Raycaster and Mesh code for THREE.js and it seems like they check both boundingSphere and boundingBox for object intersections. So I thought if I modified both of them to reflect the transforms done by the GPU, the raycaster would work fine but it doesn't seem to be working for me.
The relevant raycaster code is here:
// mouse being vec2 of normalized coordinates and camera being a perspective camera
raycaster.setFromCamera( mouse, camera );
const intersects = raycaster.intersectObjects( planes );
Possible Theories
The only thing I can think of that's wrong about this approach is maybe I'm not projecting the mouse coords right? Since all the objects now lie on the plane z = -50 would I need to project those mouse coordinates to that plane?
Inspired by the link posted by #prisoner849 I found a working solution to just create additional transparent planes equal to the number of planes in the scene. In these planes, I set the z position to -50 and just intersect with these when in state #2.
A bit hacky, but works for now.

Converting a Vector3 point to screen space (Vector2) using Three.js [73]

I have initialised a perspective camera at a position looking at the origin (0,0,0). Reading around the most common solution to this I've found is the one described here https://stackoverflow.com/a/27412386/1330719.
From my understanding of the project method is, I should get a vector where the x,y coordinates are between -1 and 1. This doesn't seem to be the case at all and I end up getting coordinates that are completely out of bounds.
Furthermore, if the original vector point is at (0,0,0) I seem to get (NaN, NaN) back. If my camera is looking at position (0,0,0) I expect the Vector3 (0,0,0) to return (width/2, height/2).
In case it is needed, this is how I'm initialising my camera:
this.camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 1, 10E5);
this.camera.position.set(0, 500, -500);
this.camera.lookAt(new THREE.Vector3(0,0,0));
this.camera.updateProjectionMatrix();
Does anyone have a reason why this might not be working? Or alternatively a recommended way of mapping a Vector3 point to the screen space given a camera?
A jsfiddle of what I mean:
https://jsfiddle.net/m78wjLyc/
You should also use camera.updateMatrixWorld(true) before projecting.
Usually this is done automatically by renderer, but you don't use any, so the camera.matrixWorld stays untouched after you change the position, and makes the camera project things as if it was at the world origin.

Three.js & OrbitControls.js - pan camera parallel to ground plane (like Google Earth)

I'm working on a simple Three.js demo that uses OrbitControls.js.
I'd like to change the behavior of panning in OrbitControls. Currently, when you pan the camera, it moves the camera in a plane that is perpendicular to the viewing direction. I'd like to change it so that the camera stays a constant distance from the ground plane and moves parallel to it. Google Earth uses a similar control setup.
Edit: I should have mentioned this detail in the first place, but I'd also like the point where you click and start dragging to remain directly under the cursor throughout the entire drag. There needs to be that solid connection between the mouse movement and what the user expects to happen on the screen. Otherwise, it feels as though I'm 'slipping' when I try to move around the scene.
Can someone give me a high-level explanation of how this might be done (with or without OrbitControls.js)?
EDIT: OrbitControls now supports panning parallel to the "ground plane", and it is the default.
To pan parallel to screen-space (the legacy behavior), set:
controls.screenSpacePanning = true;
Also available is MapControls, which has an API similar to that of Google Earth.
three.js r.94
Some time ago I was working on exactly this issue, i.e. adaptation of OrbitControls.js to map navigation.
Here's the code of MapControls.js.
Here's the demo of the controls.
I figured it out. Here's the overview:
Store the mousedown event somewhere.
When the mouse moves, get the new mousedown event.
For each of those points, find the points on the plane where those clicks are located (You'll need to put the points into camera space, transform them into world space, then fire a ray from the camera through each point to find their intersections with the plane. This page explains the ray-plane intersection test).
Subtract the world-space start intersection point from the world-space end intersection point to get the offset.
Subtract that offset from the camera's target point and you're done!
In the case of OrbitControl.js, the camera always looks at the target point, and its position is relative to that point. So when you change the target, the camera moves with it. Since the target always lies on the plane, the camera moves parallel to that plane (as long as you're panning).
You should set your camera 'up' to z axe:
camera.up.set(0,0,1)
And then, the main problem with OrbitControl is its panUp() function. It should be fixed.
My pull request : https://github.com/mrdoob/three.js/pull/12727
y axe is relative to camera axes and should be relative to a fixed plan in the world. To define the expected y axe, make a 90° rotation of camera x axe, based on world z axe.
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
v.applyAxisAngle( new THREE.Vector3( 0, 0, 1 ), Math.PI / 2 );
v.multiplyScalar( distance );
panOffset.add( v )
Enjoy!

Rotating Object Around an Axis

I have a geometry object, and I'm trying to add a Torus mesh that goes around that geometry. What I'm trying to do is have the original geometry, and then when the geometry is clicked, it adds a Torus shape on the line around the location that was clicked. However, I'm having trouble getting it to rotate correctly.
I get the torus to show up at the correct place, but I can't orient it around the line. I'm using a raycaster to get the point clicked, so I have the face and the faceindex of the point clicked. On every implementation I try using rotation (using setEulerFromRotationMatrix), it simply moves the location of the torus mesh, not actually rotate it to allow the line to go through the torus.
This seems like it would be trivial, but it's giving me a lot of trouble. What am I doing wrong? Two methods I tried, both unsucessful and exhibiting the behavior above:
var rotationMatrix = new THREE.Matrix4();
rotationMatrix.makeRotationAxis(geometry.faces[fIndex].centroid.normalize(), Math.PI/2);
torusLoop.matrix.multiply(rotationMatrix);
torusLoop.rotation.setEulerFromRotationMatrix(torusLoop.matrix);
//attempt two, similar results to above attempt
tangent = geometry.tangents[segments/radiusSegments].normalize();
axis.crossVectors( up, tangent ).normalize();
var radians = Math.acos( up.dot( tangent ) );
matrix.makeRotationAxis( axis, radians );
torusLoop.rotation.setEulerFromRotationMatrix( matrix );
I need the torus knot to follow the curve of the spline, but it will only stay flat, and rotations simply cause it to move around, not change angles.
Never mind, I figured it out. For those wondering, I translated before rotating, which caused my figure to be rotating around a different axis. My solution was to rotate first, and then translate, and then after creating the mesh, moving that to the position I needed it to be.

Resources