I have a THREE.Mesh consisting of a THREE.BufferGeometry with "position" and "normal" THREE.BufferAttributes.
The mesh is illuminated by a THREE.SpotLight (a cone-shaped light source).
Is there a way to find all mesh faces illuminated by the cone? There are no other objects in the scene so I don't need to worry about blocking.
A basic approach
From a very basic perspective, "illuminated by" can also be read as "within or intersecting, and facing the cone."
So first, determine if the face is inside or intersecting the cone. To do this, take all three vertices of the triangle and build a Vector3 that indicates the direction from the spotlight.position to vertex.
// Note: Extracting the vertices of a face will be different
// depending on if it is indexed or not.
// Assume "vertex1", "vertex2", and "vertex3" are the vertices of the face.
// Convert the vertices into World Coordinates
mesh.localToWorld( vertex1 )
mesh.localToWorld( vertex2 )
mesh.localToWorld( vertex3 )
// Get the spotlight's "look at" direction
const spotLook = new Vector3().subVectors( spotlight.target.position, spotlight.position )
// Make the vertex vectors relative to the spotlight
vertex1.sub( spotlight.position )
vertex2.sub( spotlight.position )
vertex3.sub( spotlight.position )
// Get the angles between the vectors
const angle1 = spotLook.angleTo( vertex1 )
const angle2 = spotLook.angleTo( vertex2 )
const angle3 = spotLook.angleTo( vertex3 )
Now, if ANY of these angles is less than the spotlight.angle value, then that vertex is inside the cone of the spotlight. If they're all greater than the spotlight's angle, then they're all outside the cone.
Now you need to determine whether the face is angled toward the spotlight. You can do this by normalizing the vectors between the vertices then crossing them.
// These are the original values of the vertices
vertex1.sub( vertex2 )
vertex1.normalize()
vertex3.sub( vertex2 )
vertex3.normalize()
const crossed = new Vector3().crossVectors( vertex3, vertex1 )
This gives you the "face normal," or the direction in which the face is facing. Again, use angleTo to get the angle against the spotlight's direction. If the angle is greater than Math.PI/2 (90°), then the face leans toward the spotlight. If the angle is greater than that value, then the face leans away from the spotlight.
If a face passes both criteria--facing toward the spotlight, AND at least one vertex is inside the cone--then the face can be considered illuminated.
Caveats
Of course, this is a basic approach, and provides only basic results.
There may be scenarios where your shape has sections that occlude its own faces (self-shadow).
The actual normals of the face may also reduce its acceptance of light. Even if the face its self is angled toward the spotlight, if all the normals are angled away, then the face would be within acceptable bounds, but the shader would NOT illuminate the face.
There may also be scenarios where the penumbra of your spotlight actually causes a face to NOT be illuminated, even though some or all of it is within your spotlight cone.
These are scenarios you'll need to take into account to arrive at the results you seek.
Related
I'm working on a three.js scene in which it would be hugely beneficial to be able to determine the subset of all faces (among all geometries) that are visible to the camera at a given time.
I understand one can determine whether a vertex is visible to the camera by doing something like:
camera.updateMatrix();
camera.updateMatrixWorld();
var frustum = new THREE.Frustum();
frustum.setFromMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
// The 3d point to check
var pos = new THREE.Vector3(x, y, z);
if (frustum.containsPoint(pos)) {
// Do something crazy...
}
My geometry has tens of thousands of 2d plane faces, all sitting on a larger plane, and I'd like to determine the set of of their faces that are visible to the camera fairly often (each time the camera zooms past a certain hyperplane, if possible).
I know one can do scene.children[childIndex].visible to see if a mesh is visible, but I have many faces on a mesh and want to determine which of the faces are visible. (All of my meshes are always rendered unless the user zooms wildly). I also know one can adapt this approach:
var frustum = new THREE.Frustum();
var cameraViewProjectionMatrix = new THREE.Matrix4();
// every time the camera or objects change position (or every frame)
camera.updateMatrixWorld(); // make sure the camera matrix is updated
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
cameraViewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
frustum.setFromMatrix( cameraViewProjectionMatrix );
console.log( frustum.intersectsBox( meshes[0].geometry.vertices[0] ) );
Is there a shortcut one can take to find the set of all faces visible to the camera at a given time? In my case, I could precompute the geometric mean of each planar face then use the code above to determine which of the faces is visible, but is there anything better than O(n) in this case?
I'd be very grateful for any ideas others can offer on this question!
Better than O(n)?
Given a sorted array of integers I believe one can determine the subset of those integers that line between an upper and lower bound in ~O(log(n)). Each face's geometric mean is a 3d point, so it seems possible to determine the set of points within the frustum with 3*O(log(n)), i.e. better than O(n) complexity.
Better than O(log(n))
An approximation better than O(log(n)) after some precomputing. Suppose we're dealing with only 1D (then we can generalize to 3D). Quantize the space of each axis then create a hash table with the following structure. For each unit in the quantized space, store the index position of the first point in the sorted array with that unit +- error value. Then given an upper and lower bound, round each to the nearest quantized unit and look up the values for those keys to identify the range of index positions within the span. This returns a list. Repeat for the other two dimensions and take the set intersection. [The frustum provides the upper and lower bounds for each dimension.]
Relevant codepen:
http://codepen.io/OpherV/pen/yNebep
In my game I have a model of an alien tree.
For each face of this tree I generate a pyramid (CylinderGeometry with 4 faces), and position it at the center of the face. Then I wish for this pyramid to be perpendicular to the face, so that I'll get a tree with spikes.
I've tried achieving this with object.lookAt and point it at the face normal, but weird things happen. For example:
If I add
cylinderGeometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
The shape in the middle works as expected, but the rest is still distorted
What is the proper way to get my spiked tree?
Bonus question
How would I go about merging these spikes to the original geometry after proper creation and orientation so that I don't have so many separate objects?
You want to create cylinder and point it in a particular direction. One way to do that is to use the following pattern:
Create the geometry.
var cylinderGeometry = new THREE.CylinderGeometry( 1, 10, 25, 4, 1 );
Translate the geometry so the base sits at the origin.
cylinderGeometry.translate( 0, 12.5, 0 );
Rotate the geometry so the top points in the direction of the positive-Z axis.
cylinderGeometry.rotateX( Math.PI / 2 );
Create the mesh.
var cylinder = new THREE.Mesh( cylinderGeometry , characterSkinMaterial );
Objects in three.js are by default "looking" in the direction of their local positive-Z axis. (Except the camera, which looks down its negative-Z axis.)
Tell the cylinder to "look" in the direction you want. Since we have transformed the geometry, this will make the top of the cylinder point in the desired direction.
cylinder.lookAt( face.normal );
Now place the cylinder wherever you want it.
cylinder.position.copy( centerPoint );
obj.add( cylinder );
three.js r.91
i'm writing a server for a game me and my friends are making. I want to keep the direction a certain player is looking at in a 3D plane in a variable. I was considering having it an object with two variables of radians, i.e vertical angle and horizontal angle. But my friend told me to store it the way Three.js stores it because it would make his life easier. Could anybody help me out here?
You should brush up on Math for Game Developers series: https://www.youtube.com/watch?v=sKCF8A3XGxQ&list=PLW3Zl3wyJwWOpdhYedlD-yCB7WQoHf-My&index=1
Specifically, using vectors. You should store the orientation / facing angle of your characters or entities as a Vector3, or a 3 dimensional vector. In THREE.js, that's new THREE.Vector3( x, y, z )
To get the direction of object A to object B, relative to A you would do:
var direction = posB.clone().sub( posA )
This clones position B so we don't mess it up by subtraction, and then immediately subtract it by A.
However you'll notice how the vector now has some length. This is often undesirable in calculations, for example if you wanted to multiply this direction by something else say, a thrust force. In this case, we need to normalize the vector:
direction.normalize()
Now you can do fun stuff like:
posA.add( direction.clone().multiplyScalar( 10.0 ) );
This will move posA in the direction towards posB, 10 units of space.
SO!
I was wondering if you could help me to solve one problem:
I've got a cube, which can be rotated in three axis.
I can get information about cube's rotation which is an array of three angles from 0 to 2PI.
The question is: how can I identify, which side of cube is in the bottom from those three euler angles?
I think the perfect function would be something like that:
function getSideFromAngles(x,y,z) {
// magic goes here
// for example getSideFromAngles(Math.PI/2, 0, 0)
// if x===PI/2 and y===0 and z===0 then return "front"
// which means front side of cube "looks" down now.
}
Just in case - Three.js also allows me to get quaternions of the cube.
Thanks in advance for you help
First of all, in your faces you should have the normals, that gives you the base direction of the face.
Also, in your mesh you should have the matrixWorld, that should give the global result of the rotations (all of them).
Now, if you multiply that matrix by the normals, you will have the normals in world space.
Now, create a vector pointing down (0, 0, -1), and calculate the dot product between this and the normals in world space. The face having the highest value is the one that points more "downward"
I am relatively new to three.js and am trying to position and manipulate a plane object to have the effect of laying over the surface of a sphere object (or any for that matter), so that the plane takes the form of the object surface. The intention is to be able to move the plane on the surface later on.
I position the plane in front of the sphere and index through the plane's vertices casting a ray towards the sphere to detect the intersection with the sphere. I then try to change the z position of said vertices, but it does not achieve the desired result. Can anyone give me some guidance on how to get this working, or indeed suggest another method?
This is how I attempt to change the vertices (with an offset of 1 to be visible 'on' the sphere surface);
planeMesh.geometry.vertices[vertexIndex].z = collisionResults[0].distance - 1;
Making sure to set the following before rendering;
planeMesh.geometry.verticesNeedUpdate = true;
planeMesh.geometry.normalsNeedUpdate = true;
I have a fiddle that shows where I am, here I cast my rays in z and I do not get intersections (collisions) with the sphere, and cannot change the plane in the manner I wish.
http://jsfiddle.net/stokewoggle/vuezL/
You can rotate the camera around the scene with the left and right arrows (in chrome anyway) to see the shape of the plane. I have made the sphere see through as I find it useful to see the plane better.
EDIT: Updated fiddle and corrected description mistake.
Sorry for the delay, but it took me a couple of days to figure this one out. The reason why the collisions were not working was because (like we had suspected) the planeMesh vertices are in local space, which is essentially the same as starting in the center of the sphere and not what you're expecting. At first, I thought a quick-fix would be to apply the worldMatrix like stemkoski did on his github three.js collision example I linked to, but that didn't end up working either because the plane itself is defined in x and y coordinates, up and down, left and right - but no z information (depth) is made locally when you create a flat 2D planeMesh.
What ended up working is manually setting the z component of each vertex of the plane. You had originaly wanted the plane to be at z = 201, so I just moved that code inside the loop that goes through each vertex and I manually set each vertex to z = 201; Now, all the ray start-positions were correct (globally) and having a ray direction of (0,0,-1) resulted in correct collisions.
var localVertex = planeMesh.geometry.vertices[vertexIndex].clone();
localVertex.z = 201;
One more thing was in order to make the plane-wrap absolutely perfect in shape, instead of using (0,0,-1) as each ray direction, I manually calculated each ray direction by subtracting each vertex from the sphere's center position location and normalizing the resulting vector. Now, the collisionResult intersection point will be even better.
var directionVector = new THREE.Vector3();
directionVector.subVectors(sphereMesh.position, localVertex);
directionVector.normalize();
var ray = new THREE.Raycaster(localVertex, directionVector);
Here is a working example:
http://jsfiddle.net/FLyaY/1/
As you can see, the planeMesh fits snugly on the sphere, kind of like a patch or a band-aid. :)
Hope this helps. Thanks for posting the question on three.js's github page - I wouldn't have seen it here. At first I thought it was a bug in THREE.Raycaster but in the end it was just user (mine) error. I learned a lot about collision code from working on this problem and I will be using it later down the line in my own 3D game projects. You can check out one of my games at: https://github.com/erichlof/SpacePong3D
Best of luck to you!
-Erich
Your ray start position is not good. Probably due to vertex coordinates being local to the plane. You start the raycast from inside the sphere so it never hits anything.
I changed the ray start position like this as a test and get 726 collisions:
var rayStart = new THREE.Vector3(0, 0, 500);
var ray = new THREE.Raycaster(rayStart, new THREE.Vector3(0, 0, -1));
Forked jsfiddle: http://jsfiddle.net/H5YSL/
I think you need to transform the vertex coordinates to world coordinates to get the position correctly. That should be easy to figure out from docs and examples.