I am trying to implement 360 degree rotation camera with offset to another 3d object and lookAt to point. Camera offset and lookAt points are relative to 3d object quaternion.
I have 3d object which locates at center of the world coordinates. Then i want to offset camera like third-person camera implementation. Position isn't a problem, i tried like this:
const cameraOffset = new Vector3(0, 2, 3)
cameraOffset.applyQuaternion(this.ship.quaternion)
cameraOffset.add(this.ship.position)
this.position.lerp(cameraOffset, 0.1)
this.tjsObject.position.copy(this.position)
this.tjsObejct: Three.js Object3D type.
It works and the offset vector follows object when it is rotating.
But i want make focus camera at the different direction with the object like so:
const cameraLookAt = new Vector3(0, 1, -5)
cameraLookAt.applyQuaternion(this.ship.quaternion)
cameraLookAt.add(this.ship.position)
this.lookAt.lerp(cameraLookAt, 0.1)
this.tjsObject.lookAt(this.lookAt)
lookAt works fine, but when 3d object rotation higher 180 or lower -180 degrees, lookAt method uses camera.up vector, so camera always oriented to it.
I tried find euler angles between cameraLookAt vector and camera world direction, but have no luck with it:
const f = new Vector3()
this.tjsObject.getWorldDirection(f)
f.sub(this.tjsObject.position)
f.normalize()
cameraLookAt.sub(this.tjsObject.position)
cameraLookAt.normalize()
const x = new Vector3(1, 0, 0)
const y = new Vector3(0, 1, 0)
const z = new Vector3(0, 0, 1)
x.normalize()
y.normalize()
z.normalize()
const fProjectXY = f.clone().projectOnPlane(z)
const fProjectXZ = f.clone().projectOnPlane(y)
const fProjectZY = f.clone().projectOnPlane(x)
const cameraLookAtProjectXY = cameraLookAt.clone().projectOnPlane(z)
const cameraLookAtProjectXZ = cameraLookAt.clone().projectOnPlane(y)
const cameraLookAtProjectZY = cameraLookAt.clone().projectOnPlane(x)
console.log(fProjectXY, fProjectXZ, fProjectZY)
const zAngleFCamera = fProjectXY.angleTo(cameraLookAtProjectXY)
const yAngleFCamera = fProjectXZ.angleTo(cameraLookAtProjectXZ)
const xAngleFCamera = fProjectZY.angleTo(cameraLookAtProjectZY)
const eulerRotation = new Euler()
eulerRotation.set(xAngleFCamera, yAngleFCamera, zAngleFCamera)
const q = new Quaternion().setFromEuler(eulerRotation)
this.tjsObject.quaternion.slerp(q, 0.1)
I tried change camera.up vector when 3d object rotation higher 180 or lower -180 degrees too, but it was noticeable that she was switching
Related
How do I rotate a model and keep the model from rolling sideways? Using quaternions work fine when applied directly on an axis. As soon as I do more than one axis, the model starts twisting.
Original orientation is 0,0,1
Rotated to 1,0,0 works fine.
Rotated to 1,1,0 does not work. It starts rolling/twisting around its original z axis.
Does anyone know how to fix this? It essentially needs to lock the z-axis when applying the quaternion.
Here is the basic code:
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
q.setFromUnitVectors(vB, vC);
//Box with connection
const boxG = new THREE.BoxGeometry(4, 8, 10);
const boxM = new THREE.Mesh(boxG, matSS);
const conG = new THREE.CylinderGeometry(0.5, 0.5, 2, 16);
conG.rotateX(Math.PI / 2);
const conM = new THREE.Mesh(conG, matSS);
conM.translateZ(5);
boxM.applyQuaternion(q);
boxM.add(conM);
scene.add(boxM);
//Cable to connect
const cabG = new THREE.CylinderGeometry(0.25, 0.25, 100, 16);
cabG.rotateX(Math.PI / 2);
const cabM = new THREE.Mesh(cabG, matBL);
const cabP = vC.clone().setLength(50);
cabM.applyQuaternion(q);
cabM.position.set(cabP.x,cabP.y,cabP.z);
scene.add(cabM);
Tried a number of things like rotating the Z-axis back, but that did not work.
Depending on how z-axis to be locked (respect to which plane), solution varies.
Solution #1: Polar angle rotation respect to Z-axis + azimuthal angle rotation in X-Y plane
In case box natural direction coincides pole,
it's possible to control the box direction by simply specifying another box-axis (e.g. rotation axis) to adjust roll angle.
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
q.setFromUnitVectors(vB, vC);
console.log("q before", q);
// Box's rotation axis
let vAxisFrom = new THREE.Vector3(0, 1, 0); // or (1, 0, 0)
// Axis of quaternion rotation (from vB to vC)
let vAxisTo = vB.clone().cross(vC).normalize();
let q_roll = new THREE.Quaternion();
q_roll.setFromUnitVectors(vAxisFrom, vAxisTo);
q.multiply(q_roll);
console.log("q after", q);
Solution #2: Polar angle rotation respect to Y-axis + azimuthal angle rotation in Z-X plane
In this case, composing quaternion as a combination of yaw angle rotation around Y-axis and pitch angle rotation around X axis would be a simple way.
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
let vAxisInPlane = vC.clone().setY(0.0).normalize();
if (vAxisInPlane.lengthSq() < 1.0) {
q.setFromUnitVectors(vB, vC);
}
else {
q.setFromUnitVectors(vB, vAxisInPlane);
console.log("q before", q);
let q_pitch = new THREE.Quaternion();
q_pitch.setFromUnitVectors(vAxisInPlane, vC);
q.premultiply(q_pitch);
console.log("q after", q);
}
Like this site http://www.gsmlondon.ac.uk/global-oil-map/
I would like to click on this marker, let this marker turn to the center of the screen.
Now know the mark latitude and longitude, how to turn after the click? I do not understand.
Interesting question, I tried Matrix to get right orientation, but the result is a little bit strange. I choose another way using spherical coordinates system, and it works now.
we need to get two points coordinates, one is the point on the sphere surface which is closest to the camera we note it as P(the line from camera to center of sphere intersect with the sphere). another point is where we click on the sphere surface, we note it as Q .
we use raycaster to get P and Q Cartesian Coordinates. and convert the Cartesian Coordinates to Spherical Coordinates(always described like (r,θ,φ)).
then, we calculate the angular displacement from Q to P. and make the displacement as an addition to sphere rotation.
Here is my snippet:
//get the point coordinates which a line from camera to sphere center intersect with the sphere
var vector = new THREE.Vector3().copy(sphere.position);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
var intersected_point = new THREE.Vector3().copy(intersects[0].point);
//calculate the intersected point spherical coordinates
var radius = sphere.children[0].geometry.parameters.radius;
var heading = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch = Math.asin(-(intersects[0].point.y)/radius);
document.addEventListener("click",OnDocumentClick,false);
function OnDocumentClick(event)
{
//get the point coordinates which you click on the sphere surface
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
if(intersects.length > 0)
{
//get click point spherical coordinates
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
var target_pitch = parseFloat(sphere.rotation.x) +delta_pitch;
var target_heading = parseFloat(sphere.rotation.y) + delta_heading;
//using an animation to rotate the sphere
anime({
targets:sphere.rotation,
x:target_pitch,
y:target_heading,
elasticity: 0
});
}
}
At the end, I use an animation lib to make the rotation smooth.
Here is my demo:rotate the earth.
I made a little progress, the previous version has a little bit off. when I turn up and down the earth, I got a bad result. I think the code sphere.rotation.x += delta_pitch make sphere rotate on object axises. but what we need is making the sphere rotate on the world space axises. we know world axises coordinates are always x_axis = (1,0,0) ; y_axis = (0,1,0) ; z_axis = (0,0,1); then, I convert the world coordinates to object coordinates, Sphere matrix interpret sphere rotate from indentity rotation to current rotation. and the inverse matrix interpret the backword. so we can apply the inverse matrix to basic axises to get object space coordinates. make sphere rotate on new axises. I just make a little change in OnDcoumentClick function:
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//get the sphere inverse matrix;
var sphere_matrix = new THREE.Matrix4().copy(sphere.matrix);
var inverse_sphere_matrix = new THREE.Matrix4();
inverse_sphere_matrix.getInverse(sphere_matrix);
//convert world space x and y axises to sphere object space coordinates.
var x_axis = new THREE.Vector3(1,0,0);
var y_axis = new THREE.Vector3(0,1,0);
x_axis.applyMatrix4(inverse_sphere_matrix);
y_axis.applyMatrix4(inverse_sphere_matrix);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
//make sphere rotate around whith world x and y axises.
sphere.rotateOnAxis(x_axis,delta_pitch);
sphere.rotateOnAxis(y_axis,delta_heading);
Here is my new demo: rotate earth new version.
I would like to take the world space coordinates of a sphere to screenspace coordinates in order to get the screen space bounds, which I can then use to overlay a div.
Ideally I would like to extend this function to return the height and width of the object, as well as the x & y :
toScreenPosition : function (obj, camera)
{
var vector = new THREE.Vector3();
var widthHalf = 0.5*world.renderer.context.canvas.width;
var heightHalf = 0.5*world.renderer.context.canvas.height;
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera);
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
return {
x: vector.x,
y: vector.y
};
},
You can create few THREE.Object3D and locate them in the scene in position of the border of the main object you want to project to the screen.
then you can use the method you used on the main object on the other empty objects and get the pixels position on the screen of the border of the main object.
If for example you want to know the screen coordinates of the border of a sphere that has a radius of 5:
var first = new THREE.Object3D();
var second = new THREE.Object3D();
var third = new THREE.Object3D();
var fourth = new THREE.Object3D();
first.position.set(sphere.x,sphere.y+5,sphere.z);
second.position.set(sphere.x,sphere.y-5,sphere.z);
then you can apply the function you wrote, but instead of:
obj.updateMatrixWorld();
etc...
you will do:
first.updateMatrixWorld();
second.updateMatrixWorld();
etc...
then you will have the x,y coordinates of those two objects (that are on border of the main object) on screen and you can check the height by subtracting.
I would write a function that takes as input a 3D point and returns the 2D screenpoint, like the one presented there. Then if you deal with a sphere, that would be easy: get the center of the sphere in 3D, compute the 2D point, that will be the center of the overlay div, get any 3D point on the surface of the sphere, compute the 2D point, you will know the required radius of the overlay div, from there you can easily compute a rectangular area.
I'm trying to use part of a video as a texture in a Three.js mesh.
Video is here, http://video-processing.s3.amazonaws.com/example.MP4 it's a fisheye lens and I want to only use the part with actual content, i.e. the circle in the middle.
I want to somehow mask, crop or position and stretch the video on the mesh so that only this part shows and the black part is ignored.
Video code
var video = document.createElement( 'video' );
video.loop = true;
video.crossOrigin = 'anonymous';
video.preload = 'auto';
video.src = "http://video-processing.s3.amazonaws.com/example.MP4";
video.play();
var texture = new THREE.VideoTexture( video );
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
var material = new THREE.MeshBasicMaterial( { map : texture } );
The video is then projected onto a 220 degree sphere, to give the VR impression.
var geometry = new THREE.SphereGeometry( 200,100,100, 0, 220 * Math.PI / 180, 0, Math.PI);
Here is a code pen
http://codepen.io/bknill/pen/vXBWGv
Can anyone let me know how I'm best to do this?
You can use texture.repeat to scale the texture
http://threejs.org/docs/#Reference/Textures/Texture
for example, to scale 2x on both axis
texture.repeat.set(0.5, 0.5);
In short, you need to update the UV-Map of the sphere so that the relevant area of your texture is assigned to the corresponding vertices of the sphere.
The UV-coordinates for each vertex define the coordinates within the texture that is assigned to that vertex (in a range [0..1], so coordinates (0, 0) are the top left corner and (1,1) the bottom right corner of your video). This example should give you an Idea what this is about.
Those UV-coordinates are stored in your geometry as geometry.faceVertexUvs[0] such that every vertex of every face has a THREE.Vector2 value for the UV-coordinate. This is a two-dimensional array, the first index is the face-index and the second one the vertex-index for the face (see example).
As for generating the UV-map there are at least two ways to do this. The probably easier way (ymmv, but I'd always go this route) would be to create the UV-map using 3D-editing software like blender and export the resulting object using the three.js exporter-plugin.
The other way is to compute the values by hand. I would suggest you first try to simply use an orthographic projection of the sphere. So basically, if you have a unit-sphere at the origin, simply drop the z-coordinate of the vertices and use u = x/2 + 0.5 and v = y/2 + 0.5 as UV-coordinates.
In JS that would be something like this:
// create the geometry (note that for simplicity, we're
// a) using a unit-sphere and
// b) use an exact half-sphere)
const geometry = new THREE.SphereGeometry(1, 18, 18, Math.PI, Math.PI)
const uvs = geometry.faceVertexUvs[0];
const vertices = geometry.vertices;
// compute the UV from the vertices of the sphere. You will probably need
// something a bit more elaborate than this for the 220degree FOV, also maybe
// some lens-distorion, but it will boild down to something like this:
for(let i = 0; i<geometry.faces.length; i++) {
const face = geometry.faces[i];
const faceVertices = [vertices[face.a], vertices[face.b], vertices[face.c]];
for(let j = 0; j<3; j++) {
const vertex = faceVertices[j];
uvs[i][j].set(vertex.x/2 + 0.5, vertex.y/2 + 0.5);
}
}
geometry.uvsNeedUpdate = true;
(if you need more information in either direction, drop a comment and i will elaborate)
Here I want to rotate 'endpoint' vector around 'startpoint' vector.
This rotates endpoint vector around world's z-axis:
endpoint.copy(new THREE.Vector3(startpoint.x,startpoint.y,startpoint.z));
endpoint.add(new THREE.Vector3(0, scale, 0 ));
var matrix = new THREE.Matrix4().makeRotationAxis( axis_z, angle );
endpoint.applyMatrix4( matrix );
I have tried to save vector's translation to temporary matrix, and after applying rotation, restore translation back to endpoint vector:
endpoint.copy(new THREE.Vector3(startpoint.x,startpoint.y,startpoint.z));
endpoint.add(new THREE.Vector3(0, scale, 0 ));
var translateMatrix = new THREE.Matrix4().makeTranslation(endpoint.x, endpoint.y, endpoint.z);
var translation = new THREE.Matrix4().copyPosition(translateMatrix);
translateMatrix.makeRotationAxis(axis_z, angle);
translateMatrix.copyPosition(translation);
endpoint.applyMatrix4(translateMatrix);
This code works for rotations, but all the translations are wrong.
Is there an easy way for vector rotation relative to another vector?
UPD. Resolved this. I was blind. No need for translations. What I had to do: apply rotation to vectors delta (endpoint - startpoint), and in the end, add the rotated delta to startpoint (startpoint + delta):
var vector_delta = new THREE.Vector3().subVectors(endpoint, startpoint);
vector_delta.applyAxisAngle( axis_z, rota );
endpoint.addVectors(startpoint, vector_delta);