By default, the rotation matrix uses the origin point as the center of the rotation. To rotate around an arbitrary point, you have to subtract the distance to the origin using a translation matrix, do the rotation, and then translate back. Except that this doesn't seem to work so well for me. I have the following code (assume my object is 100x100 with a center at 50,50):
t = IDENTITY;
t = translate(t, -50, -50);
t = rotate(t, theta);
t = translate(t, 50, 50);
Unfortunately, if I apply this transform matrix t to my object, the object is positioned incorrectly.
I've implemented a quick jsfiddle to demonstrate my problem: http://jsfiddle.net/9M3uy/67/
In the JSFiddle, the red rotated square is where the rotation should have ended up (courtesy of CSS3's built in transform-origin), and the blue rotated square is where my computation ends up (the green would have been the original non-rotated square).
Any ideas? Am I just not understanding how the translate,rotate,translate back mechanic works or am I doing something horribly wrong?
There are two problems in your code:
The matrix multiplications are done in the opposite order than you probably intend. It looks like you intend rotate(t, theta) to return a matrix that applies t followed by a rotation, but in fact it's the opposite - the rotation will be applied before t. You need to reverse the parameter order in the calls to matrixMultiply in rotate and translate.
The parameters to the CSS matrix function are in the wrong order. It should be a11, a21, a12, a22, a13, a23. What you are passing in is a11, a12, a21, a22, a13, a23.
Here's the fixed version.
Try to implement first the "standard" 3x3 matrix multiplication for 2d -cases and only after try to optimize away the elements, which result from multiplying by zero. It a bit hard to see if the formulas are correct, when the indexing scheme is far too unorthodox.
The rotation matrix = [c -s 0; s c 0; 0 0 1];
The translation matrix = [1 0 x; 0 1 y; 0 0 1];
I have to assume css -translation function also assumes 3x3 result.
Both multiplication and rotation is performed by multiplying vectors (x,y,1) by the corresponding matrix.
EDIT: After fiddling a bit, it appears that either the matrix m should be translated, or the operators could be defined as
return MatMul([rot matrix],m) and
return MatMul([trans matrix],m)
EDIT2: Now I can see something strange: translating only by +-50, +-50 and rotating by ~10 degrees, the corner doesn't stay in the middle of the red square. But don't understand the format of css matrix anyway. Sorry...
Related
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).
Video example: https://drive.google.com/file/d/18Ep4i1JMs7QvW9m-3U4oyQ4sM0CfIFzP/view
What you can see here is that I have the world position of a ray hitting the globe under the mouse. Then I lookAt() with a THREE.Group to that position to get a quaternion with the correct rotation. The red dot always under my mouse proves that this quaternion is fine. Next, from the quaternion that represents the big yellow dome's center I use rotateTowards (which uses slerp internally, and I tried using directly the slerp method, but that gave me the same results) towards the mouse position's quaternion (the red dot) and set this quaternion as the rotation to the blue dot that's been following the mouse. This in theory should always "stick" to that dome when my mouse is farther away. You can see that it is indeed "sticking" to it when I'm doing these closer to the southern hemisphere. But near the north pole it goes haywire. It calculates shorter distances like it should, and not even on the correct great circle.
Relevant code:
// using hammerjs pan events I send an event to the blue sphere with the position on the sphere whats under the mouse, event.point is correct, the red sphere always under the mouse proves this.
this.helperGroup.lookAt(event.point); // To get the requested rotation
const p = this.helperGroup.quaternion.clone(); // helpergroup is just an empty group in (0, 0, 0) to get quaternions with lookAt more easily
// p is now a rotation towards the point under the mouse
const requestedDistance = dome.quaternion.angleTo(p); // dome is the center of the yellow dome in the video, allowedDistance is the arc-length of said dome in radians.
// The reason I rotate the parent of blueSphere because its parent is another group in (0, 0, 0) and the (relative) position of the blue sphere is (0, 0, 1), the planets radius is 1 too.
if (allowedDistance >= requestedDistance) {
blueSphere.parent.lookAt(event.point);
} else {
blueSphere.parent.quaternion.copy(
dome.quaternion.clone().rotateTowards(p, allowedAngle)
);
}
// this snippet is heavily modified for the sake of an example.
Update, and different approach:
I originally used this lookAt() and rotation based placements to avoid as much math as I can. But it back-lashed. So now I'm doing it correctly simply with cartesian coordinates, normal vectors and simple axis based rotations. (Turned out using math is actually simpler than avoiding it)
const requestedDistance = blueSphere.angleTo(event.point);
let norm = dome.position.clone().cross(event.point).normalize();
if (allowedDistance >= requestedDistance) {
blueSphere.position.set(event.point); // Not using a group as parent anymore
} else {
blueSphere.position.set(dome.position.clone()
.applyAxisAngle(norm, allowedAngle);
}
A singularity near the poles is part of the nature of the quaternion slerp function; it can't be avoided except by using a different approach. Jonathan Blow's article, "Understanding Slerp, Then Not Using It", discusses the slerp function and its problems, and suggests that an alternative to slerp (normalized lerp or nlerp) is the quaternion interpolator to be preferred most of the time.
Note that even the C++ code for slerp in that article acknowledges the singularity present in the slerp function.
I am trying to find the closest distance from a point to large, complex Mesh along a plane in a direction range:
for (var zDown in verticalDistances) {
var myIntersect = {};
for (var theta = Math.PI / 2 - 0.5; theta < Math.PI / 2 + 0.5; theta += 0.3) {
var rayDirection = new THREE.Vector3(
Math.cos(theta),
Math.sin(theta),
0
).transformDirection(object.matrixWorld);
// console.log(rayDirection);
_raycaster.set(verticalDistances[zDown].minFacePoint, rayDirection, 0, 50);
// console.time('raycast: ');
var intersect = _raycaster.intersectObject(planeBufferMesh);
// console.timeEnd('raycast: '); // this is huge!!! ~ 2,300 ms
// console.log(_raycaster);
// console.log(intersect);
if (intersect.length == 0) continue;
if ((!('distance' in myIntersect)) || myIntersect.distance > intersect[0].distance) {
myIntersect.distance = intersect[0].distance;
myIntersect.point = intersect[0].point.clone();
}
}
// do stuff
}
I get great results with mouse hover on the same surface but when performing this loop the raycasting is taking over 2 seconds per cast. The only thing i can think of is that the BackSide of the DoubleSide Material is a ton slower?
Also i notice as I space out my verticalDistances[zDown].minFacePoint to be farther apart raycast starts to speed up up (500ms /cast). So as the distance between verticalDistances[i].minFacePoint and verticalDistances[i+1].minFacePoint increases, the raycaster performs faster.
I would go the route of using octree but the mouse hover event works extremely well on the exact same planeBuffer. Is this a side of Material issue,. that could be solved by loading 2 FrontSide meshes pointing in opposite directions?
Thank You!!!!
EDIT: it is not a front back issue. I ran my raycast down the front and back side of the plane buffer geometry with the same spot result. Live example coming.
EDIT 2: working example here. Performance is little better than Original case but still too slow. I need to move the cylinder in real time. I can optimize a bit by finding certain things, but mouse hover is instant. When you look at the console time the first two(500ms) are the results i am getting for all results.
EDIT 3: added a mouse hover event, that performs the same as the other raycasters. I am not getting results in my working code that i get in this sample though. The results I get for all raycast are the same as i get for the first 1 or 2 in the sample around 500ms. If i could get it down to 200ms i can target the items i am looking for and do way less raycasting. I am completely open to suggestions on better methods. Is octree the way to go?
raycast: : 467.27001953125ms
raycast: : 443.830810546875ms
EDIT 4: #pailhead Here is my plan.
1. find closest grid vertex to point on the plane. I can do a scan of vertex in x/y direction then calculate the min distance.
2. once i have that closest vertex i know that my closest point has to be on a face containing that vertex. So i will find all faces with that vertex using the object.mesh.index.array and calculate the plane to point of each face. Seems like a ray cast should be a little bit smarter than a full scan when intersecting a mesh and at least cull points based on max distance? #WestLangley any suggestions?
EDIT 5:
#pailhead thank you for the help. Its appreciated. I have really simplified my example(<200 lines with tons more comments); Is raycaster checking every face? Much quicker to pick out the faces within the set raycasting range specified in the constructor and do a face to point calc. There is no way this should be looping over every face to raycast. I'm going to write my own PlaneBufferGeometry raycast function tonight, after taking a peak at the source code and checking octree. I would think if we have a range in the raycaster constructor, pull out plane buffer vertices within that range ignoring z. Then just raycast those or do a point to plane calculation. I guess i could just create a "mini" surface from that bounding circle and then raycast against it. But the fact that the max distance(manual uses "far") doesn't effect the speed of the raycaster makes me wonder how much it is optimized for planeBuffer geometries. FYI your 300k loop is ~3ms on jsfiddle.
EDIT 6: Looks like all meshes are treated the same in the raycast function. That means it wont smart hunt out the area for a plane Buffer Geometry. Looking at mesh.js lines 266 we loop over the entire index array. I guess for a regular mesh you dont know what faces are where because its a TIN, but a planeBuffer could really use a bounding box/sphere rule, because your x/y are known order positions and only the Z are unknown. Last edit, Answer will be next
FYI: for max speed, you could use math. There is no need to use ray casting. https://brilliant.org/wiki/3d-coordinate-geometry-equation-of-a-plane/
The biggest issue resolved is filtering out faces of planeBufferGeometry based on vertex index. With a planeBufferGeometry you can find a bounding sphere or rectangle that will give you the faces you need to check. they are ordered in x/y in the index array so that filters out many of the faces. I did an indexOf the bottom left position and lastIndexOf the top right corner position in the index array. RAYCASTING CHECKS EVERY FACE
I also gave up on finding the distance from each face of the object and instead used vertical path down the center of the object. This decreased the ray castings needed.
Lastly I did my own face walk through and used the traingle.closestPointToPoint() function on each face.
Ended up getting around 10ms per point to surface calculation(single raycast) and around 100 ms per object (10 vertical slices) to surface. I was seeing 2.5 seconds per raycast and 25+ seconds per object prior to optimization.
I'm still new for using three js. When I'm rotating a camera object, there is a rotation property has x,y,z value.
I'm wondering where the x,y,z in Object 3D rotation come from? I know the x,y,z represent the radians of Object Euler angle, but according the link three.js document provide:https://en.wikipedia.org/wiki/Euler_angles the range of α and γ covers 2π radians, and the range of β covers π radians. However, the range of all the x,y,z only covers π radians when i test it.
From initially x=0,y=0,z=0, straight look up, why only x value changed? And if the Object3D is a camera, is that means the center pixel in the camera view represents the x-axis?
Appreciate of your help.
You might work on formulating your question a bit clearer and give code to reproduce your error.
On the question why only the x-vaue changes when you look straight up from (0,0,0) is because x, y and z represents the axis that you rotate around.
The standard in 3js and in 3D-graphics in general is to have x as the horizontal axis, y as the vertical axis and z is the "depth" axis.
Looking straight up you will rotate 90 degrees aound the horiontal x-axis, thus changing the x-value in the rotation.
I can give you the link to
https://threejs.org/docs/#api/math/Euler
Three.js provides two ways of representing 3D rotations: Euler angles and Quaternions, as well as methods for converting between the two. Euler angles are subject to a problem called "gimbal lock," where certain configurations can lose a degree of freedom (preventing the object from being rotated about one axis). For this reason, object rotations are always stored in the object's quaternion.
Previous versions of the library included a useQuaternion property which, when set to false, would cause the object's matrix to be calculated from an Euler angle. This practice is deprecated---instead, you should use the setRotationFromEuler method, which will update the quaternion.
https://threejs.org/docs/manual/introduction/Matrix-transformations.html
In Three.JS, I am capable of rotating an object about its origin. If I were to do this with a line, for instance, the line rotates, but the positions of its vertices are not updated with their new locations. Is there some way to apply the rotation matrix to the position of the vertices to find the new position of the point? Say I rotate a line with points at (0,0,0) and (0,100,100) by 45° on the x, 20° on the y, and 100° on the z. How would I go about finding the actual position of the vertices with respect to the entire scene.
Thanks
yes, 'entire scene' means world position.
THREE.Vector3() has a applyMatrix4() method,
you can do the same things that the shader does so in order to project a vertex into world space you would do this
yourPoint.applyMatrix4(yourObject.matrixWorld);
to project that into camera space you can apply this next
yourPoint.applyMatrix4(camera.matrixWorld);
to get an actual screen position in -1 to 1
yourPoint.applyMatrix4(camera.projectionMatrix);
you would access your point like this
var yourPoint = yourObject.geometry.vertices[0]; //first vertex
also, rather than doing this three times, you can just combine the matrices. Didnt test this, but something along the lines of this. Might go the other way:
var neededPVMmatrix = new THREE.Matrix4().multiplyMatrices(yourObject.matrixWorld, camera.matrixWorld);
neededPVMmatrix.multiplyMatrices(neededPVMmatrix, camera.projectionMatrix);
if you need a good tutorial on what this does under the hood i recommend this
Alteredq posted everything there is to know about three.js matrices here
edit
One thing to note though, if you want just the rotation, not the translation, you need to use the upper 3x3 portion which is the rotation matrix, of the models world matrix. This might be slightly more complicated. I forgot what three.js gives you, but i think the normalMatrix would do the trick, or perhaps you can convert your THREE.Vector3() to THREE.Vector4(), and set .w to 0, this will prevent any translation from being applied.
edit2
if you want to move the line point in your example, instead of applying it to the particle, apply it to
var yourVertexWorldPosition = new THREE.Vector3().clone(geo.vertices[1]); //this is your second line point, to whatever you set it in your init function
yourVertexWorldPosition.applyMatrix4();//this transforms the new vector into world space based on the matrix you provide (line.matrixWorld)