THREE.js checking how close the camera is to a mesh - three.js

I have a mesh landscape in THREE.js that the camera points down at, I'd like to maintain a certain distance from that mesh (so if there's peaks in the terrain the camera moves further away).
I thought raycasting would be the correct way to start going about this (by getting the intersection distance) but all the examples I find relate to using mouse co-ordinates; when I try to set the origin as the camera position, and the direction co-ords to be the camera position but with a 0 on the Y axis (so camera up in the air facing down) the intersect results come up empty.
For example, on the render event I have:
t.o.ray.vector = new THREE.Vector3(t.o.camera.position.x, 0, t.o.camera.position.z );
t.o.ray.cast = new THREE.Raycaster(t.o.camera.position,t.o.ray.vector );
t.o.ray.intersect = t.o.ray.cast.intersectObject(object, true);
console.log(t.o.ray.intersect);
This results in an empty array, even when I'm moving the camera, the only way I can seem to get this to work is by using the examples that rely on mouse events.
Any ideas what I'm missing?

I realised it was because that setting 0 as the Y property was not enough. I had assumed that the vector co-ordinate simply helped calculate the direction in which the ray was pointing in, but this doesn't seem to be the case. i.e. -:
t.o.ray.vector = new THREE.Vector3(t.o.camera.position.x, -1000, t.o.camera.position.z );
t.o.ray.vector.normalize();
t.o.ray.cast = new THREE.Raycaster(t.o.camera.position,t.o.ray.vector );
t.o.ray.intersect = t.o.ray.cast.intersectObject(t.Terrain.terrain, true);
Produces the expected results.

What about this approach:
console.log( t.o.camera.position.distanceTo(t.o.vector.position) );

Related

Efficient way to translate a camera dolly

I currently have a VR camera attached to a dolly in order to allow for translation and rotation.
I'm trying to translate the dolly, based on gamepad inputs, relative to the orientation of the camera (which is linked to a VR headset.)
I'm also trying to avoid letting the dolly pitch up or down relative to the camera.
My current code looks something like this:
this.camerDirectionVector = new THREE.Vector3()
this.camera.getWorldDirection(this.cameraDirectionVector)
this.moveVec.y = 0
this.dolly.translateOnAxis(this.cameraDirectionVector, this.gamepad.axes[0] * this.moveSpeed)
This works great for moving the dolly in the direction the camera is pointing (minus y rotation).
What I can't figure out is how to also translate the dolly "left and right" relative to the camera based off an additional gamepad input.
Based on the comments on the question, I think I understand. If I don't, please leave a comment, and I'll update this answer.
My understanding is that you want to be able to move left and right, with respect to the camera, all without altering the dolly's up direction.
This is actually easier than it sounds, and is even easier because you are already comfortable translating along an axis.
First, understand that the camera has its own spatial frame of reference, where it sits at the origin, with a +Y up direction, and it looks down the -Z axis. With this in mind, you already know the "left" and "right" axes: -X (-1, 0, 0) and +X (1, 0, 0).
But the camera (especially in VR) might not be so nicely aligned in world space, so you need to convert these nice uniform axes into world axes. Three.js makes this very easy using Object3D.localToWorld.
(Note: Object3D.localToWorld is destructive to the input Vector3.)
Here's a function to get the world-aligned left axis:
const getLeftWorld = (function(){
const localLeft = new THREE.Vector3(-1, 0, 0);
return function(vectorRef){ // you can give it a vector to overwrite
let out = vectorRef || new THREE.Vector3();
out.copy(localLeft);
camera.localToWorld(out);
return out;
};
))();
You can create a similar function for the "right" axis.
With your new world-aligned left axis in hand, you can translate the dolly along it, using the "speed" given by your controller input. Translation won't change the pitch of the dolly, though it may change the elevation, depending on how the camera is tipped at the time of computation (but you can just zero-out the y component like you did before, if you want).
This is the solution that ended up working best for me. I've adapted it from Brian Peiris code here: https://github.com/brianpeiris/three-firstperson-vr-controls/blob/master/FirstPersonVRControls.js#L125
// Create a dolly
this.dolly = new THREE.Group()
this.dolly.add(this.camera)
this.scene.add(this.dolly)
// Some variables for movement translations
this.dummy = new THREE.Object3D()
this.dummyDirection = new THREE.Vector3()
this.ZAXIS = new THREE.Vector3(0, 0, 1)
this.moveSpeed = 0.075
// Get controller stick positions
const stickForwardBack = this.leftStick[3]
const stickLeftRight = this.leftStick[2]
// In THREE.js when using the WebXR API, the camera rotation will not update to match the headset orientation.
// You'll need to get pose information from the XRSession or get the xr camera using the following method.
const xrCamera = globals.renderer.xr.getCamera(this.camera)
this.dummy.position.set(0, 0, 0)
this.dummy.quaternion.copy(xrCamera.quaternion)
this.collapseY(this.dummy.quaternion)
// Translate the dummy object forwards/backwards left/right relative to the direction the camera is facing
this.dummy.translateZ(stickForwardBack * this.moveSpeed)
this.dummy.translateX(stickLeftRight * this.moveSpeed)
// Add the dummy position to the dolly
this.dolly.position.add(this.dummy.position)
// Flatten out up and down rotation
collapseY(quaternion) {
this.dummyDirection.set(0, 0, 1)
this.dummyDirection.applyQuaternion(quaternion)
this.dummyDirection.y = 0
this.dummyDirection.normalize()
quaternion.setFromUnitVectors(this.ZAXIS, this.dummyDirection)
}

Rotate around World Axis

I tried to rotate an object arount the Worlds-Y-Axis, with
myObject.rotateOnWorldAxis(new THREE.Vector3(0,1,0),THREE.Math.degToRad(1));
but the result was, that the object is only rotated in object space.
To be sure that I used the correct method I looked into the documentation and found that there are three methods to rotate an object:
.RotateY(rad) // rotate in Local Space
.rotateOnAxis(axis,rad) // rotation in Object Space
.rotateOnWorldAxis(axis,rad) // rotation in World Space
It seems that I used the correct method.
Is this a bug or an understanding problem on my side?
Here is a JSFiddle which illustrates my problem (the blue cube should rotate around the world axis).
Here is a second Fiddle where thy cyan cube is a child of another object.
It looks to me like your real question isn't regarding world space or object space rotations, cause those are working as expected in your examples.
You probably meant, how to change the point of rotation of an object. If that is the case, you have two options, you can either translate all your geometry vertices in respect to a pivot point of rotation. That way, your pivot will be centered at (0,0,0) and your vertices will rotate in respect to that.
mesh.geometry.translate( x, y, z );
Or you can make your object a child of a different Object3D (pivot), position your original mesh similarly to what was described above and rotate your pivot mesh.
var cube = new THREE.Mesh( geometry, material );
var pivot = new THREE.Object3D();
cube.position.set( 0, 12, 30 ); // offset from center
pivot.add( cube );
scene.add( pivot );
//...
pivot.rotation.y += Math.PI/2;
JSFiddle

Three.js - change camera POV on click

Some project background:
I have a Sprite particle field that is randomly generated. The camera is located at position 0, 0, 0. The particle field is all around the camera. I'm using Raycaster to be able to select the particle that is clicked on and change it's color. Once clicked I would like the camera to focus on this particle. I'm also attempting to use Tween to glide the particle into view.
I've attempted several different methods and none of them work. They are described here:
A traditional lookAt method that used Raycaster to pick up the intersect point from clicking.
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
var intersects = raycaster.intersectObjects( this.starfield.children );
this.camera.lookAt(intersects[0].object.position)
A distanceTo method where the distance between the camera and the intersect coordinates is used to move the camera. This only moves the camera along the z plane. It wont actually change its POV.
var cameraPosition = new THREE.Vector3(this.camera.position.x, this.camera.position.y, this.camera.position.z);
var intersectPosition = new THREE.Vector3(intersects[0].object.position.x, intersects[0].object.position.y , intersects[0].object.position.z );
var zoomPos = intersectPosition.distanceTo( cameraPosition );
const newCameraPosition = cameraPosition.addVectors(this.camera.position, vector.setLength(zoomPos));
I calculated the angle of rotation for each X, Y, and Z axis via tan and cos equations. I then attempted to rotate the camera by those degrees. I even tried converting them to radians to see if that would make a difference with the rotation method. It didnt :(
I don't know what else to do. At this stage I'm completely open to a different approach as long as I get this camera working. I'm very stuck,
any help would be greatly appreciated!
Instead of using
intersects[0].object.position
try using
intersects[0].point
.point is the world space position of the hit.
.objectis the object the triangle belongs to. .object.position is just the origin of that object, in this case the particle system. The particle positions themselves are relative to this origin.

Spawn particle at edge of screen

I've searched far and wide, so if there's a similar question please forgive me but I just couldn't find it.
To put what I'm trying to do in context: I want to create an infinitely-generated field of stars that disappear as they go offscreen and reappear at the edge of the screen where the camera is moving. I'm working with a top-down view, so it must be pretty simple to achieve this, but alas I haven't a clue.
I'm using the following code to determine whether a star has gone off-screen and then replace it:
//update camera frustum
camera.projScreenMatrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
);
camera.frustum.setFromMatrix(camera.projScreenMatrix);
//loop through stars
var stars=scene.stars.geometry.vertices;
for(var i=0;i<stars.length;i++) {
if(!camera.frustum.containsPoint(stars[i])) {
stars[i]=new THREE.Vector3(
// fill in the blank
);
scene.stars.geometry.verticesNeedUpdate=true;
}
}
Since I'm using a perspective camera, I know I'll need to somehow factor in camera.fov and other perspective elements, but as you can tell I'm no expert on the third dimension.
Assuming I have an angle or normalized vector telling me the direction the view is panning, how would I go about creating a vertex along the edge of the screen regardless of its Z position?
If I'm not clear enough, I'll be happy to clarify. Thanks.
I know this is an old question, but I came across it while looking for an answer and found a simple, trigonometry reliant method to get the left edge of the camera frustum, and I'm sharing it in case someone else might find it useful:
// Get half of the cameras field of view angle in radians
var fov = camera.fov / 180 * Math.PI / 2;
// Get the adjacent to calculate the opposite
// This assumes you are looking at the scene
var adjacent = camera.position.distanceTo( scene.position );
// Use trig to get the leftmost point (tangent = o / a)
var left = Math.tan( fov ) * adjacent * camera.aspect;
Basically, this gets the leftmost point, but if you don't multiply by the aspect ratio you should get a point in a circle around your camera frustum, so you could translate a point any direction away from the cameras focus and it would always be outside the frustum.
It works by assuming that the imaginary plane that is the camera is perpendicular to the line connecting the camera and its focus, so there is a straight angle. This should work if you want objects further away as well (so if you want them at a further point from the camera you just need to increase the distance between the focus and the camera).
Well, countless headaches and another question later, I've come up with a fairly makeshift answer. Just in case by some unlikely chance someone else has the same question, the following function plots a point on the scene relative to the camera's current view with whatever Z specified:
//only needs to be defined once
var projector=new THREE.Projector();
//input THREE.Vector3
function(vector) {
var z=vector.z;
vector.z=0;
projector.unprojectVector(vector,camera);
return camera.position.clone().add(
vector
.sub(camera.position)
.normalize()
.multiplyScalar(
-(camera.position.z-z)/vector.z
)
);
The x and y, in this case, both range from -1 to 1 for bottom-left to top-right. You can use position/window.Width and position/window.Height for extra precision (using mouse coordinates or what have you).

Three.js plane facing away from camera aligned with viewport

I'm trying to have a plane face away from the camera with same orientation so it's aligned in the viewport.
I have a plane in front of the camera, perfectly aligned to the cameras viewport, and I want to flip it in front of the camera, along the objects Y axis, regardless of camera orientation.
The following will orient my plane to face at the camera and works for any orientation:
target.rotation.copy(camera.rotation);
The following will then flip the plane along the plane's Y axis:
target.rotation.y += Math.PI;
All good so far? Except when the camera rotation has a funky tilt to it, let's say it's looking up and to the left, tilted slightly to the right, the plane's flip is tilted, but not the same way as the camera, leaving me with a plane tilted either to the left or right...
I've tried several things such as:
target.rotation.z -= camera.rotation.z;
Nothing... Thanks for your help.
So the problem I was running into was when the camera was in negative z coordinates. This causes the flip on the Y axis to get messed up.
So basically you would do something like this:
var target = new THREE.Object3D();
//position
target.position.copy(s.camera.position);
target.position.add(THREE.Utils.cameraLookDir(s.camera).multiplyScalar(300));
//rotation
target.rotation.copy(s.camera.rotation);
target.rotation.y += PI;
target.rotation.z = -s.camera.rotation.z;
if (s.camera.position.z < 0) {
target.rotation.z = s.camera.rotation.z;
}
EDIT:
Add the following to appropriate spots in your program.
camera.rotation.eulerOrder = 'XZY';
target.rotation.eulerOrder = 'XZY';
Seems to solve previously encountered tilt issues! (see below)
RESOLVED:
Flipped planes tilted the wrong way in some instances, for example when in negative z coords and also the y rotation is not equal to 0, example: point in space hovering and looking at 0, 0, 0.
This is the solution I was looking for when I found this page (taken from this answer):
mesh.lookAt( camera.position );
The local z-axis of the mesh should then point toward the camera.

Resources