How can I calculate normals of closed shape in three.js? - javascript

I am trying to write my own mesh importer for my own file format. In my file format there is no normal data. So I am trying to calculate normals of a close shape and apply those normals to mesh.
My solution is calculating a normal vector for every face of geometry and create a raycast from middle of these faces in the direction of normal vector. If that raycast hit something (another plane) it means this direction is inside. In that case I flip normal, If it does not hit something I leave it that way.
While I wrote a function with this logic, normals don't change at all.
function calculateNormals(object){
for (var i = 0; i < object.geometry.faces.length; i++) {
var vertices= object.geometry.vertices;
var face=object.geometry.faces[i];
var a=vertices[face.a];
var b=vertices[face.b];
var c=vertices[face.c];
console.log(face.a+" "+face.b+" "+face.c+" "+face.normal.z);
console.log(face);
console.log(face[4]);
var edge0=new THREE.Vector3(0,0,0);
edge0.subVectors(a,b);
var edge1=new THREE.Vector3(0,0,0);
edge1.subVectors(b,c);
var planeNormal=new THREE.Vector3(0,0,0)
planeNormal.crossVectors(edge0,edge1);
// console.log(planeNormal);
//Raycast from middle point towards plane nrmal direction
//If it hits anything it means normal direction is wrong
var midPoint=calculateMiddlePoint([a,b,c]);
var raycaster = new THREE.Raycaster(midPoint,planeNormal);
var intersects = raycaster.intersectObjects([object]);
if(intersects.length==0){
console.log("Normal is true");
face.normal=planeNormal;
}else{
console.log("Normal is wrong, you should flip normal direction, length: "+intersects.length);
console.log("Old face");
console.log(face.normal);
var newNormal=new THREE.Vector3(-1*planeNormal.x,-1*planeNormal.y,-1*planeNormal.z);
console.log(newNormal);
face.normal=newNormal;
console.log("new face");
console.log(face.normal);
console.log(face);
}
object.geometry.faces[i]=face;
// console.log(object.geometry.faces);
};
return object;
}

Matey makes a good point. The winding order is what determines the face normal, meaning which side is considered "front." Vertex normals are used for shading (with MeshPhongMaterial for example). If your vertex normals point in the opposite direction from your face normal, you'll end up with unintended results (anything from bad shading to a totally black face).
All that said, Geometry has helper functions for calculating normals.
Geometry.computeFaceNormals (based on winding order)
Geometry.computeFlatVertexNormals (sets a vertex normal to be the same as the associated face normal)
Geometry.computeVertexNormals (sets a vertex normal to the average of its surrounding face normals)
Once you've computed the normals, you could make a second pass to try and correct them, either by re-ordering the vertices (to correct the face normal), or by re-calculating the vertex normals yourself.

Related

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).

ThreeJS | Detect when an object leaves another object

I'm making a ThreeJS project in which I have planes (Object3D) flying inside a sphere (Mesh).
I'm trying to detect the collision between a plane and the border of the sphere so I can delete the plane and make it reappear at another place inside the sphere.
My question is how do I detect when an object leaves another object ?
The code I have now :
detectCollision(plane, sphere) {
var boxPlane = new THREE.Box3().setFromObject(plane);
boxPlane.applyMatrix4(plane.matrixWorld);
var boxSphere = new THREE.Box3().setFromObject(sphere);
boxSphere.applyMatrix4(sphere.matrixWorld);
return boxPlane.intersectsBox(boxSphere);
}
In my render function :
var collision = this.detectCollision(plane, this.radar)
if (collision == true) {
console.log("the plane is inside the sphere")
}
else {
console.log("the plane is outside the sphere")
}
})
The problem is that when the planes are inside the sphere I get true and false basically all the time until all the planes leave the sphere. At that point I have a false and no more true.
Box3 is not what you want to use to calculate sphere and plane collisions because the box won't respect the sphere's curvature, nor will it follow the plane's rotation.
Three.js has a class THREE.Sphere that is closer to what you need. Keep in mind that this class is not the same as a Mesh with a SphereGeometry, this is more of a math helper that doesn't render to the canvas. You can use its .containsPoint() method for what you need:
var sphereCalc = new THREE.Sphere( center, radius );
var point = new THREE.Vector3(10, 4, -6);
detectCollision() {
var collided = sphereCalc.containsPoint(point);
if (collided) {
console.log("Point is in sphere");
} else {
console.log("No collision");
}
return collided;
}
You'll have to apply transforms and check all 4 points of each plane in a loop. Notice there's a Sphere.intersectsPlane() method that sounds like it would do this for you, but it's not the same because it uses an infinite plane to calculate the intersection, not one with a defined width and height, so don't use this.
Edit:
To clarify, each plane typically has 4 verts, so you'll have to check each vertex in a for() loop to see if the sphere contains each one of the 4 points.
Additionally, the plane will probably have been moved and rotated, so its original vertex positions will have a transform matrix applied to them. I think you were already taking this into account in your example, but it would be something like:
point.copy(vertex1);
point.applyMatrix4(plane.matrixWorld)
sphereCalc.containsPoint(point);
point.copy(vertex2);
point.applyMatrix4(plane.matrixWorld)
sphereCalc.containsPoint(point);
// ... and so on

Three.js quaternion slerp gives bad result near poles

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.

THREE .JS raycasting performance

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.

Detect if Object3D is receiving shadows (r86)

I'd like to do be able to detect if an object is in shadow or not. What would be the best, most performant way to do this?
Eg., There's a tree in the scene and a directional light. Move a character under the tree and they are now standing in the tree'a shadow which it casts. How can you detect at what point they have entered / left the tree's cast shadow?
This is not the ultimate solution, this is just an option you can use as the start point.
And this is just a translation of my answer from another segment of stackoverflow.com, dedicated to Three.js.
The idea: you set a ray from the point along the direction to a light source, and, if it intersects any object, then the point is in the shadow, otherwise, it's not.
Given: point on a plane pointOnPlane, a normalized vector of the position of a directional light source (which will be our direction) direction and an array of objects in the scene sceneObjects (which we want to find intersection with). To determine, if the point is shaded or not, we'll need a short function:
var raycasterPoint = new THREE.Raycaster();
var direction = new THREE.Vector3();// for re-use
function isShaded(pointOnPlane){
direction.copy(light.position).normalize();
raycasterPoint.set(pointOnPlane, direction); // ray's origin and direction
var retVal = false;
var pointIntersects = raycasterPoint.intersectObjects( sceneObjects );
if (pointIntersects.length > 0) retVal = true;
return retVal;
}
jsfiddle example.

Categories

Resources