Three.js Camera Rotation Needs Update - javascript

I have a Three.js perspective camera and it orients differently in different browsers. I'm trying to normalize the behavior by rotating the camera in Firefox to match Chrome. However, rotating the camera 90 degrees in the render loop causes the x-axis rotation to be off.
camera.rotation.y = camera.rotation.y - (90 * Math.PI / 180);
It looks like the camera is rotating around the y-axis without updating so that the x-axis is in the right place, so what would normally control pitch in my environment (x), is controlling roll (which should be z). Do I have to update the euler order? That seems like a less than ideal solution.

When you use an euler you can inverse the vector order :
var a = new THREE.Euler( 0, 1, 1.57, 'XYZ' );
If the vector order doesn't corresponds your expectation you can use the reorder function.
a.reorder('ZYX');
As far as I'm concerned, that's the best you can do to solve your problem.

Related

Clamping rotated camera in Three.js

In Three.js, I have a 3D scene that contains a floor and an orthographic camera.
I have it set up so that the user can move the camera around the scene with their mouse. I want to limit the camera's movement to the dimensions of the floor.
I got it working if the camera is rotated to -90 deg on the x-axis, i.e. if the camera is looking straight down at the floor from above.
But changing the camera to any other angle causes issues with the clamp limits. For example, if I change the camera angle to -40 instead, I can pan further up and down than I should be able to, and hence see parts of the scene that I should not be able to.
How can I integrate the camera's rotation into my below solution?
// Create camera
this.camera = new THREE.OrthographicCamera(...);
this.camera.rotation.x = THREE.MathUtils.degToRad(-90);
// The size of the floor
const modelBoundingBoxWidth = 14;
const modelBoundingBoxHeight = 14;
// The limits for the camera (in world units)
const cameraLimits = {
left: -modelBoundingBoxWidth / 2,
right: modelBoundingBoxWidth / 2,
top: - modelBoundingBoxHeight / 2,
bottom: modelBoundingBoxHeight / 2,
};
// Calculate the camera's new position due to the mouse pan
// (...)
// Apply the camera limits to the new camera position
if ((cameraPosNew.x - cameraHalfWidth) < cameraLimits.left) {
cameraPosNew.x = cameraLimits.left + cameraHalfWidth;
}
else if ((cameraPosNew.x + cameraHalfWidth) > cameraLimits.right) {
cameraPosNew.x = cameraLimits.right - cameraHalfWidth;
}
if ((cameraPosNew.z - cameraHalfHeight) < cameraLimits.top) {
cameraPosNew.z = cameraLimits.top + cameraHalfHeight;
}
else if ((cameraPosNew.z + cameraHalfHeight) > cameraLimits.bottom) {
cameraPosNew.z = cameraLimits.bottom - cameraHalfHeight;
}
// Move the camera to the new position
this.camera.position.set(
cameraPosNew.x,
cameraPosNew.y,
cameraPosNew.z
);
I believe I need to project the floor's vertical length onto the camera's vertical length using the camera's rotation angle, so that I can determine how much I need to reduce the vertical clamp limits by (due to the rotation). But I don't know where to start regarding the math. I tried various dot product / vector projection approaches but didn't get anywhere.
I also noticed that at an angle of -40, the space above and below the floor is not equal, meaning either the top and bottom clamp limits need to be different, or perhaps I need to move the camera back by some value (due to the rotation)?
Also note that due to the rotation to -40, I can see more of the scene than I could at -90.
Update: I think this question is a little unclear, due to me bringing panning into it when I think I need to first improve my understanding of how to calculate what the camera sees when rotated. I have created a separate question for specifically that: How does rotation influence an orthographic camera in Three.js
If you're trying to simply clamp the position vector, you could use the THREE.Vector3.clamp() method.
const boxMin = new THREE.Vector3(-7, 0, -7);
const boxMax = new THREE.Vector3(7, 100, 7);
// Calculate the camera's new position due to the mouse pan
// (...)
// Perform clamping so it doesn't go outside box
cameraPosNew.clamp(boxMin, boxMax);
this.camera.position.copy(cameraPosNew);
You're not showing how the rotations are affecting the position within this box, but if you still have to take rotations into consideration to calculate the min/max box bounds, you might need to do some manual calculations with some basic trigonometric functions.

3D model in HTML/CSS; Calculate Euler rotation of triangle

TLDR; Given a set of triangle vertices and a normal vector (all in unit space), how do I calculate X, Y, Z Euler rotation angles of the triangle in world space?
I am attemping to display a 3D model in HTML - with actual HTML tags and CSS transforms. I've already loaded an OBJ file into a Javascript class instance.
The model is triangulated. My first aim is just to display the triangles as planes (HTML elements are rectangular) - I'll be 'cutting out' the triangle shapes with CSS clip-path later on.
I am really struggling to understand and get the triangles of the model rotated correctly.
I thought a rotation matrix could help me out, but my only experience with those is where I already have the rotation vector and I need to convert and send that to WebGL. This time there is no WebGL (or tutorials) to make things easier.
The following excerpt shows the face creation/'rendering' of faces. I'm using the face normal as the rotation but I know this is wrong.
for (const face of _obj.faces) {
const vertices = face.vertices.map(_index => _obj.vertices[_index]);
const center = [
(vertices[0][0] + vertices[1][0] + vertices[2][0]) / 3,
(vertices[0][1] + vertices[1][1] + vertices[2][1]) / 3,
(vertices[0][2] + vertices[1][2] + vertices[2][2]) / 3
];
// Each vertex has a normal but I am just picking the first vertex' normal
// to use as the 'face normal'.
const normals = face.normals.map(_index => _obj.normals[_index]);
const normal = normals[0];
// HTML element creation code goes here; reference is 'element'.
// Set face position (unit space)
element.style.setProperty('--posX', center[0]);
element.style.setProperty('--posY', center[1]);
element.style.setProperty('--posZ', center[2]);
// Set face rotation, converting to degrees also.
const rotation = [
normal[0] * toDeg,
normal[1] * toDeg,
normal[2] * toDeg,
];
element.style.setProperty('--rotX', rotation[0]);
element.style.setProperty('--rotY', rotation[1]);
element.style.setProperty('--rotZ', rotation[2]);
}
The CSS first translates the face on X,Y,Z, then rotates it on X,Y,Z in that order.
I think I need to 'decompose' my triangles' rotation into separate axis rotations - i.e rotate on X, then on Y, then on Z to get the correct rotation as per the model face.
I realise that the normal vector gives me an orientation but not a rotation around itself - I need to calculate that. I think I have to determine a vector along one triangle side and cross it with the normal, but this is something I am not clear on.
I have spent hours looking at similar questions on SO but I'm not smart enough to understand or make them work for me.
Is it possible to describe what steps to take without Latex equations? I'm good with pseudo code but my Math skills are severely lacking.
The full code is here: https://whoshotdk.co.uk/cssfps/ (view HTML source)
The mesh building function is at line 422.
The OBJ file is here: https://whoshotdk.co.uk/cssfps/data/model/test.obj
The Blender file is here: https://whoshotdk.co.uk/cssfps/data/model/test.blend
The mesh is just a single plane at an angle, displayed in my example (wrongly) in pink.
The world is setup so that -X is left, -Y is up, -Z is into the screen.
Thank You!
If you have a plane and want to rotate it to be in the same direction as some normal, you need to figure out the angles between that plane's normal vector and the normal vector you want. The Euler angles between two 3D vectors can be complicated, but in this case the initial plane normal should always be the same, so I'll assume the plane normal starts pointing towards positive X to make the maths simpler.
You also probably want to rotate before you translate, so that everything is easier since you'll be rotating around the origin of the coordinate system.
By taking the general 3D rotation matrix (all three 3D rotation matrices multiplied together, you can find it on the Wikipedia page) and applying it to the vector (1,0,0) you can then get the equations for the three angles a, b, and c needed to rotate that initial vector to the vector (x,y,z). This results in:
x = cos(a)*cos(b)
y = sin(a)*cos(b)
z = -sin(b)
Then rearranging these equations to find a, b and c, which will be the three angles you need (the three values of the rotation array, respectively):
a = atan(y/x)
b = asin(-z)
c = 0
So in your code this would look like:
const rotation = [
Math.atan2(normal[1], normal[0]) * toDeg,
Math.asin(-normal[2]) * toDeg,
0
];
It may be that you need to use a different rotation matrix (if the order of the rotations is not what you expected) or a different starting vector (although you can just use this method and then do an extra 90 degree rotation if each plane actually starts in the positive Y direction, for example).

Rotating Mesh in world axis - THREE.js

I'm having difficulty rotating an object. I am currently using THREE.Shape to configure a custom shape, after creating the shape, I configure it as a Mesh and set the position of it to:
buildingMesh.position.set( -70, -300, levelY );
Now because of the position of the mesh as well as the camera it appears as if its standing up.
Heres what that looks like:
Now I recently added a Orbital camera that rotates around the world axis, once the camera moves, this is how it looks:
Now this makes sense because the y axis was never configured when using the Three.Shape. What I am trying to figure out now is how can I turn that object so it appears to be standing up, as shown in the first image. I have tried using rotation on the x,y,z axis's but it always seems to only rotate within the objects axis.
Any suggestions?
Heres something I tried that I found on another question:
rotateAroundWorldAxis: function(object, axis, radians) {
this.rotWorldMatrix = new THREE.Matrix4();
this.rotWorldMatrix.makeRotationAxis(axis.normalize(), radians);
this.rotWorldMatrix.multiply(object.matrix); // pre-multiply
object.matrix = this.rotWorldMatrix;
object.rotation.setFromRotationMatrix(object.matrix);
}
Try:
mesh.rotate.x = Math.PI // will rotate over x axis 180 degree
where 'mesh' is object you have created (you can rotate 'y' and 'z' too)
So the primary problem here was that the mesh was built by multiple layers. All these layers actually had to be grouped together into an Object3D Which in turn allowed me to simply do: group.rotateOnAxis(axis,Math.pow(Math.PI, 2) / 2);
Hope this helps anyone in the future.

three.js getWorldPosition not returning expected results

Please refer to the gist I created at
https://gist.github.com/sparkbuzz/f1f8d0d8bbc7757b679f
It contains a TypeScript class called OrbitControls. As an attempt to learn about 3D and three.js, I've been successful so far in creating orbit style controls, with guidance and insparation taken from MrDoob's JS OrbitControls.
My implementation is slightly different, but in a nutshell, I'm using mousedown, mousemove and mouseup events to navigate a camera around the world origin. When mousedown is fired, I grab the camera's initial position using the following code:
this.subjectPositionStart = this.subject.getWorldPosition();
which is used during mousemove to calculate angle's that need to be added to the camera's initial position, as it was when mousedown was fired.
During mousemove, theta and phi angles are calculated based on the distance the mouse has moved across the HTML canvas and used to reposition the camera with:
this.subject.position.x = rho * Math.cos(theta) * Math.sin(phi);
this.subject.position.y = rho * Math.sin(theta) * Math.sin(phi);
this.subject.position.z = rho * Math.cos(phi);
Everything works fine, however, on the next mousedown, sometimes, when the rotation angle of the camera's position around the z-axis goes either above 90° , or below -90°, the camera position snaps to a position that looks inverse of what it's supposed to be.
So what I'm seeing is that the camera flips, to mirror its position, the degrees are correct, but just mirrored.
When I don't exceed the 90° the next mousedown works fine and navigation behaves as expected, and the camera position is correctly retrieved from getWorldPosition(), but I suspect that I'm using this function incorrectly, or perhaps entirely using the wrong technique to determine the angles for the camera's position in relation to the origin.
The problem is definitely to do with the mousedown handler, as while only mousemove fires, there's no problem in exceeding the 90° angles, and navigation works fine.
Any ideas on what could be wrong?
EDIT:
I think the way I'm calculating baseTheta is incorrect:
var baseTheta:number = Math.asin(this.subjectPositionStart.y / rho / Math.sin(basePhi));
If I use:
var baseTheta:number = Math.acos(this.subjectPositionStart.x / rho / Math.sin(basePhi));
I get the same problem, but it flips on between 180° and 360°, and I'm thinking that's a clue...
It turns out baseTheta and basePhi weren't being calculated correctly, and it the question actually turned up in a different question I asked.
Calculate θ and ø angles between origin and x,y,z coordinate
I changed the following code, with the help of Paul.S's answer in the link above from:
var basePhi:number = Math.acos(this.subjectPositionStart.z / rho);
var baseTheta:number = Math.asin(this.subjectPositionStart.y / rho / Math.sin(basePhi));
to
var basePhi:number = Math.acos(this.subjectPositionStart.z / rho);
var baseTheta:number = Math.atan2(this.subjectPositionStart.y, this.subjectPositionStart.x);
and the problem was solved. No more flippin flipping about.
Your problem might have something to do with the way Three.js tries to keep the camera facing up
Check this out: rotating-camera-around-the-x-axis-three-js
In the Orbit controls source there are some limits to how for one can rotate. Adding these to your code might fix your problem.
// restrict theta to be between desired limits
theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
// restrict phi to be between desired limits
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );

Get face rotation Three.js

I am getting the intersections of mouse click with Three.js like this
me.vector.set(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0.5);
me.vector.unproject(app.camera);
me.ray.set(app.camera.position, me.vector.sub(app.camera.position).normalize());
var intersects = me.ray.intersectObjects(app.colliders, false);
So, i got intersects perfectly, with following properties:
distance, face, faceIndex, object, point, and then I execute a function.
The problem is the following:
I want to detect when i click a face of a cube, that is like a floor, in the next example would be the gray face.
sorry about my engilsh D:
WebGL defines vertices and faces with coordinates, colors, and normals. A face normal is a normalized vector, perpendicular to the face plane (and generally pointing 'outside' the mesh). It defines its orientation and enables calculation of lightning for instance. In three.js you can access it via face.normal.
If your floor-like faces are stricly horizontal, then their normals are all precisely {x:0,y:1,z:0}. And since normals are normalized, simply check whether face.normal.y === 1 also checks that x and y equal 0.
If your faces are not strictly horizontal, you may need to set a limit angle with the y-axis. You can calculate this angle with var angle=Math.acos(Yaxis.dot(faceNormal)) where Yaxis=new THREE.Vector3(0,1,0).

Categories

Resources