I am using three.js.
I have two mesh geometries in my scene.
If these geometries are intersected (or would intersect if translated) I want to detect this as a collision.
How do I go about performing collision detection with three.js? If three.js does not have collision detection facilities, are there other libraries I might use in conjuction with three.js?
In Three.js, the utilities CollisionUtils.js and Collisions.js no longer seem to be supported, and mrdoob (creator of three.js) himself recommends updating to the most recent version of three.js and use the Ray class for this purpose instead. What follows is one way to go about it.
The idea is this: let's say that we want to check if a given mesh, called "Player", intersects any meshes contained in an array called "collidableMeshList". What we can do is create a set of rays which start at the coordinates of the Player mesh (Player.position), and extend towards each vertex in the geometry of the Player mesh. Each Ray has a method called "intersectObjects" which returns an array of objects that the Ray intersected with, and the distance to each of these objects (as measured from the origin of the Ray). If the distance to an intersection is less than the distance between the Player's position and the geometry's vertex, then the collision occurred on the interior of the player's mesh -- what we would probably call an "actual" collision.
I have posted a working example at:
http://stemkoski.github.io/Three.js/Collision-Detection.html
You can move the red wireframe cube with the arrow keys and rotate it with W/A/S/D. When it intersects one of the blue cubes, the word "Hit" will appear at the top of the screen once for every intersection as described above. The important part of the code is below.
for (var vertexIndex = 0; vertexIndex < Player.geometry.vertices.length; vertexIndex++)
{
var localVertex = Player.geometry.vertices[vertexIndex].clone();
var globalVertex = Player.matrix.multiplyVector3(localVertex);
var directionVector = globalVertex.subSelf( Player.position );
var ray = new THREE.Ray( Player.position, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
// a collision occurred... do something...
}
}
There are two potential problems with this particular approach.
(1) When the origin of the ray is within a mesh M, no collision results between the ray and M will be returned.
(2) It is possible for an object that is small (in relation to the Player mesh) to "slip" between the various rays and thus no collision will be registered. Two possible approaches to reduce the chances of this problem are to write code so that the small objects create the rays and do the collision detection effort from their perspective, or include more vertices on the mesh (e.g. using CubeGeometry(100, 100, 100, 20, 20, 20) rather than CubeGeometry(100, 100, 100, 1, 1, 1).) The latter approach will probably cause a performance hit, so I recommend using it sparingly.
I hope that others will contribute to this question with their solutions to this question. I struggled with it for quite a while myself before developing the solution described here.
An updated version of Lee's answer that works with latest version of three.js
for (var vertexIndex = 0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)
{
var localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();
var globalVertex = localVertex.applyMatrix4(Player.matrix);
var directionVector = globalVertex.sub( Player.position );
var ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
// a collision occurred... do something...
}
}
This really is far too broad of a topic to cover in a SO question, but for the sake of greasing the SEO of the site a bit, here's a couple of simple starting points:
If you want really simple collision detection and not a full-on physics engine then check out (link removed due to no more existing website)
If, on the other hand you DO want some collision response, not just "did A and B bump?", take a look at (link removed due to no more existing website), which is a super easy to use Ammo.js wrapper built around Three.js
only works on BoxGeometry and BoxBufferGeometry
create the following function:
function checkTouching(a, d) {
let b1 = a.position.y - a.geometry.parameters.height / 2;
let t1 = a.position.y + a.geometry.parameters.height / 2;
let r1 = a.position.x + a.geometry.parameters.width / 2;
let l1 = a.position.x - a.geometry.parameters.width / 2;
let f1 = a.position.z - a.geometry.parameters.depth / 2;
let B1 = a.position.z + a.geometry.parameters.depth / 2;
let b2 = d.position.y - d.geometry.parameters.height / 2;
let t2 = d.position.y + d.geometry.parameters.height / 2;
let r2 = d.position.x + d.geometry.parameters.width / 2;
let l2 = d.position.x - d.geometry.parameters.width / 2;
let f2 = d.position.z - d.geometry.parameters.depth / 2;
let B2 = d.position.z + d.geometry.parameters.depth / 2;
if (t1 < b2 || r1 < l2 || b1 > t2 || l1 > r2 || f1 > B2 || B1 < f2) {
return false;
}
return true;
}
use it in conditional statements like this:
if (checkTouching(cube1,cube2)) {
alert("collision!")
}
I have an example using this at https://3d-collion-test.glitch.me/
Note: if you rotate(or scale) one (or both) of the cubes/prisims, it will detect as though they haven't been turned(or scaled)
since my other answer is limited I made something else that is more accurate and only returns true when there is a collision and false when there isn't (but sometimes when There still is)
anyway, First make The Following Function:
function rt(a,b) {
let d = [b];
let e = a.position.clone();
let f = a.geometry.vertices.length;
let g = a.position;
let h = a.matrix;
let i = a.geometry.vertices;
for (var vertexIndex = f-1; vertexIndex >= 0; vertexIndex--) {
let localVertex = i[vertexIndex].clone();
let globalVertex = localVertex.applyMatrix4(h);
let directionVector = globalVertex.sub(g);
let ray = new THREE.Raycaster(e,directionVector.clone().normalize());
let collisionResults = ray.intersectObjects(d);
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) {
return true;
}
}
return false;
}
that above Function is the same as an answer in this question by
Lee Stemkoski (who I am giving credit for by typing that) but I made changes so it runs faster and you don't need to create an array of meshes. Ok step 2: create this function:
function ft(a,b) {
return rt(a,b)||rt(b,a)||(a.position.z==b.position.z&&a.position.x==b.position.x&&a.position.y==b.position.y)
}
it returns true if the center of mesh A isn't in mesh B AND the center of mesh B isn't in A OR There positions are equal AND they are actually touching. This DOES still work if you scale one (or both) of the meshes.
I have an example at: https://3d-collsion-test-r.glitch.me/
It seems like this has already been solved but I have an easier solution if you are not to comfortable using ray casting and creating your own physics environment.
CANNON.js and AMMO.js are both physics libraries built on top of THREE.js. They create a secondary physics environment and you tie your object positions to that scene to emulate a physics environment. the documentation is simple enough to follow for CANNON and it is what I use but it hasnt been updated since it was released 4 years ago. The repo has since been forked and a community keeps it updated as cannon-es. I will leave a code snippet here so you can see how it works
/**
* Floor
*/
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
floorBody.quaternion.setFromAxisAngle(
new CANNON.Vec3(-1,0,0),
Math.PI / 2
)
world.addBody(floorBody)
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({
color: '#777777',
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture
})
)
floor.receiveShadow = true
floor.rotation.x = - Math.PI * 0.5
scene.add(floor)
// THREE mesh
const mesh = new THREE.Mesh(
sphereGeometry,
sphereMaterial
)
mesh.scale.set(1,1,1)
mesh.castShadow = true
mesh.position.copy({x: 0, y: 3, z: 0})
scene.add(mesh)
// Cannon
const shape = new CANNON.Sphere(1)
const body = new CANNON.Body({
mass: 1,
shape,
material: concretePlasticMaterial
})
body.position.copy({x: 0, y: 3, z: 0})
world.addBody(body)
This makes a floor and a ball but also creates the same thing in the CANNON.js enironment.
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - oldElapsedTime
oldElapsedTime = elapsedTime
// Update Physics World
mesh.position.copy(body.position)
world.step(1/60,deltaTime,3)
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
After this you just update the position of your THREE.js scene in the animate function based on the position of your physics scene.
Please check out the documentation as it might seem more complicated than it really is. Using a physics library is going to be the easiest way to simulate collisions. Also check out Physi.js, I have never used it but it is supposed to be a more friendly library that doesn't require you to make a secondary environment
In my threejs version, I only have geometry.attributes.position.array and not geometry.vertices. To convert it to vertices, I use the following TS function:
export const getVerticesForObject = (obj: THREE.Mesh): THREE.Vector3[] => {
const bufferVertices = obj.geometry.attributes.position.array;
const vertices: THREE.Vector3[] = [];
for (let i = 0; i < bufferVertices.length; i += 3) {
vertices.push(
new THREE.Vector3(
bufferVertices[i] + obj.position.x,
bufferVertices[i + 1] + obj.position.y,
bufferVertices[i + 2] + obj.position.z
)
);
}
return vertices;
};
I pass in the object's position for each dimension because the bufferVertices by default are relative to the object's center, and for my purposes I wanted them to be global.
I also wrote up a little function to detect collisions based on vertices. It optionally samples vertices for very involved objects, or checks for proximity of all vertices to the vertices of the other object:
const COLLISION_DISTANCE = 0.025;
const SAMPLE_SIZE = 50;
export const detectCollision = ({
collider,
collidables,
method,
}: DetectCollisionParams): GameObject | undefined => {
const { geometry, position } = collider.obj;
if (!geometry.boundingSphere) return;
const colliderCenter = new THREE.Vector3(position.x, position.y, position.z);
const colliderSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collider.obj), SAMPLE_SIZE)
: getVerticesForObject(collider.obj);
for (const collidable of collidables) {
// First, detect if it's within the bounding box
const { geometry: colGeometry, position: colPosition } = collidable.obj;
if (!colGeometry.boundingSphere) continue;
const colCenter = new THREE.Vector3(
colPosition.x,
colPosition.y,
colPosition.z
);
const bothRadiuses =
geometry.boundingSphere.radius + colGeometry.boundingSphere.radius;
const distance = colliderCenter.distanceTo(colCenter);
if (distance > bothRadiuses) continue;
// Then, detect if there are overlapping vectors
const colSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collidable.obj), SAMPLE_SIZE)
: getVerticesForObject(collidable.obj);
for (const v1 of colliderSampleVertices) {
for (const v2 of colSampleVertices) {
if (v1.distanceTo(v2) < COLLISION_DISTANCE) {
return collidable;
}
}
}
}
};
You could try cannon.js.It makes it easy to do collision and its my favorite collision detection library. There is also ammo.js too.
Related
I'm making a 2D scatterplot with a tooltip, and currently the raycaster to detect when a point is being hovered over is broken. The tooltip only activates when touching an object, which is correct behavior, but it shows completely random data from points that aren't even close on the x/y plane, and changes information even when there are no other points close to the one being hovered over. Can anyone help me debug this behavior? Here is some relevant code (the rest can be found in the link above):
...loading in points (stored in data_points array), creating scene, etc.
raycaster = new THREE.Raycaster();
raycaster.params.Mesh.threshold = 20;
view.on("mousemove", () => {
let [mouseX, mouseY] = d3.mouse(view.node());
let mouse_position = [mouseX, mouseY];
checkIntersects(mouse_position);
});
function mouseToThree(mouseX, mouseY) {
return new THREE.Vector3(
mouseX / viz_width * 2 - 1,
-(mouseY / height) * 2 + 1,
1
);
}
function checkIntersects(mouse_position) {
let mouse_vector = mouseToThree(...mouse_position);
raycaster.setFromCamera(mouse_vector, camera);
let intersects = raycaster.intersectObjects(scene.children, true);
if (intersects[0]) {
let sorted_intersects = sortIntersectsByDistanceToRay(intersects);
let intersect = sorted_intersects[0];
let index = intersect.faceIndex;
let datum = data_points[index];
showTooltip(mouse_position, datum);
} else {
hideTooltip();
}
}
function sortIntersectsByDistanceToRay(intersects) {
return _.sortBy(intersects, "distanceToRay");
}
...tooltip functions, details
Any help would be greatly appreciated. Thank you!
Why are you using d3.mouse(view.node()); to get the mouse position? It looks like that's giving you wild results. When moving the pointer in a tiny space, I get an X range from 2200 to -97, when it should be a few pixels apart.
I recommend that on mousemove you get the exact XY screen position by using the default JavaScript method of event.clientX and event.clientY
See this example, taken directly from a Three.js Raycasting example
function onMouseMove( event ) {
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
}
Also, I recommend removing document margins via CSS so your measurements aren't off by a few pixels.
Figured out the answer. There was not a proper "index" variable for my datapoints (THREE.Group consisting of [THREE.Mesh, THREE.LineLoop]), which is why the raycasting worked but not point selection (DON'T use faceIndex). So I created one under the userData field of the mesh.
// Create circle geometries
for (var i=0; i<data_points.length; i++) {
// Circle
let geo = new THREE.CircleBufferGeometry(data_points[i].radius, 32);
let mat = new THREE.MeshBasicMaterial( {color: color_array[data_points[i].label] } );
let mesh = new THREE.Mesh(geo, mat);
mesh.userData.id = i;
...lineLoop and Group code
}
...more code
function onMouseMove(event) {
mouseRay.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouseRay.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
mouseRay.z = 1;
let mouse = [event.clientX, event.clientY];
raycaster.setFromCamera(mouseRay, camera);
let intersects = raycaster.intersectObjects(scene.children, true);
if (intersects[0]) {
let sorted_intersects = sortIntersectsByDistanceToRay(intersects);
console.log(sorted_intersects);
let intersect = sorted_intersects[0];
// Here is the change I made!!!
let index = intersect.object.userData.id;
let datum = data_points[index];
highlightPoint(datum);
showTooltip(mouse, datum);
} else {
removeHighlights();
hideTooltip();
}
}
I am trying to make my own raytracer in Javascript. So far the spheres work very well. I now want to expand the capabilities to include triangles, and from there I can go to squares, cubes and beyond. The code I have for finding intersections with triangles is as follows
function triangleIntersection(t, r) {
var norm = triangleNormal(t);
var dist = triangleDistance(t);
var a = Vector.dotProduct(r.vector, norm);
if (a === 0) {
return -1;
} else {
var b = Vector.dotProduct(norm, Vector.add(r.point, Vector.negative(Vector.multiply(norm, dist))));
var d = -1 * b / a;
var qx = Vector.scale(r.vector, d).x + r.point.x;
var qy = Vector.scale(r.vector, d).y + r.point.y;
var qz = Vector.scale(r.vector, d).z + r.point.z;
var q = new Vector(qx, qy, qz);
var ca = Vector.subtract(t.points[2], t.points[0]);
var qa = Vector.subtract(q, t.points[0]);
var t1 = Vector.dotProduct(Vector.crossProduct(ca, qa), norm);
var bc = Vector.subtract(t.points[1], t.points[2]);
var qc = Vector.subtract(q, t.points[2]);
var t2 = Vector.dotProduct(Vector.crossProduct(bc, qc), norm);
var ab = Vector.subtract(t.points[0], t.points[1]);
var qb = Vector.subtract(q, t.points[1]);
var t3 = Vector.dotProduct(Vector.crossProduct(ab, qb), norm);
if ((t1 >= 0) && (t2 >= 0) && (t3 >= 0)) {
return 1 * b / a;
} else {
return -1;
}
}
}
Triangle objects have a point array (points[]) and item 0 is point A, item 1 is point B and item 2 is point C. The parameter t is one of these triangles. The parameter r is a ray object, with properties point which is the origin, and vector, which is the direction.
I also have these functions for finding normal and distance of a triangle.
function triangleNormal(s) {
var ca = Vector.subtract(s.points[2], s.points[0]);
var ba = Vector.subtract(s.points[1], s.points[0]);
var norm = Vector.unitVector(Vector.crossProduct(ca, ba));
return norm;
}
function triangleDistance(t) {
return Vector.dotProduct(triangleNormal(t, 0), t.points[0]);
}
When I render my scene the triangle I use in my scene is red colored. No matter how far I move my camera back the triangle fills the whole scene red. I do not know why this happens.
An important error in your plane intersection code is this comparison operation:
a === 0
There are two things wrong with it:
For ray tracing you want the ray to hit planes in front of its source, not behind it, so you need a < 0.
Even if you did want the ray to hit planes behind it, you must never do equality operations between floating point values, because floating point calculations are not exact. (You must do something like abs(a) < 1e-6f or some small value)
My I am trying to do a click to zoom feature with Three.js, I have a canvas and an object loaded in the canvas.On click I am trying to place the camera near the point of intersection(Actually like zooming that point).
Here is what I have done, but doesn't work as I wanted, on click camera positions changes but kind of works partially sometimes camera is placed near the point of intersection, some times not.
onmousedown = function (event) {
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
event.preventDefault();
mouse.x = (event.clientX / self.renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / self.renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, self.camera);
var objects = [];
for (var i = 0; i < self.scene.children.length; i++) {
if (self.scene.children[i] instanceof THREE.Group) {
objects.push(self.scene.children[i]);
}
}
console.log(objects);
var intersects = raycaster.intersectObjects( objects,true );
console.log(intersects.length);
if (intersects.length > 0) {
self.camera.up = new THREE.Vector3(0, 0, 1);
self.camera.lookAt(new THREE.Vector3(0, 0, 0));
self.camera.position.z = intersects[0].point.z * .9;
self.camera.position.x = intersects[0].point.x * .9;
self.camera.position.y = intersects[0].point.y * .9;
}
};
Here self is a global viewer object which holds camera, canvas, different objects etc.
0.9 is just a number used to place camera just near the point of intersection.
camera used is PerspectiveCamera and controls is TrackballControls
new THREE.PerspectiveCamera(90, this.width / this.height, 1, 1000);
The objects loaded are from .obj or .dae files ,I expect this to work like click on any point on the object and place the camera near that point. But camera is moving but sometimes not near the point I clicked.
Does intersects[0] gives the nearest intersection point? or nearest in the direction of camera ?
What is my mistake here ?
I am new to three js , just started learning it.If something or some logic is wrong help me with that.
The position is a bit complicated to calculate; you have to find the segment between camera and intersection and than place the camera at specific distance from intersection along the segment looking to the intersection point.
try this:
var length=[the desiderated distance camera-intersection]
var dir = camera.position.clone().sub(intersects[0].point).normalize().multiplyScalar(length);
camera.position = intersects[0].point.clone().add(dir);
camera.lookAt(intersects[0].point);
I have created a fiddle: http://jsfiddle.net/h5my29aL/
It's not so difficult. Think of your object as a planet, and your camera as a satellite. You need to position the camera somewhere in an orbit near your object. Three contains a distanceTo function that makes it simple. The example uses a sphere, but it will work with an arbitrary mesh. It measures the distance from the center point to the desired vector3. In your case the vector3 is likely the face position returned by a picker ray. But anyhow, the lookAt is set to the mesh, and then a distance from the vertex is calculated so that the camera is always the same altitude regardless of a vertex's or face's distance from the object center.
var point = THREE.GeometryUtils.randomPointsInGeometry( geometry, 1 );
var altitude = 100;
var rad = mesh.position.distanceTo( point[0] );
var coeff = 1+ altitude/rad;
camera.position.x = point[0].x * coeff;
camera.position.y = point[0].y * coeff;
camera.position.z = point[0].z * coeff;
camera.lookAt(mesh.position);
I've came somewhat close to what I want with an example from Three js.
Three JS webgl_decals
this is what I have done.
function zoomCam(event) {
var point_mouse = new THREE.Vector2(),
var point_x = null;
var point_y = null;
if (event.changedTouches) {
point_x = event.changedTouches[ 0 ].pageX;
point_y = event.changedTouches[ 0 ].pageY;
} else {
point_x = event.clientX;
point_y = event.clientY;
}
point_mouse.x = (point_x / window.innerWidth) * 2 - 1;
point_mouse.y = -(point_y / window.innerHeight) * 2 + 1;
if (sceneObjects.length > 0) {
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(point_mouse, camera);
var intersects = raycaster.intersectObjects(sceneObjects, true);
if (intersects.length > 0) {
var p = intersects[ 0 ].point;
var n = intersects[ 0 ].face.normal.clone();
n.multiplyScalar(10);
n.add(intersects[ 0 ].point);
camera.position.copy(n);
camera.lookAt(p);
}
}
There might be some minor issues as I formatted/changed the code for answering here. Check the code before implementing.
Hey guys I have been working on a little project of mine utilizing the awesome library three.js
Now I have been working with the example of https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Collision-Detection.html to handle collision detection, more so on when an object over laps another object utilizing voxel's.
To reference my problem I am using the http://threejs.org/examples/#canvas_interactive_voxelpainter example.
Anyways to continue, when I render a voxel onto the screen, anything above the cube will allow me to render another voxel anything within a certain radius below the volex will not let me render:
Here is shown above cube:
Now here is my neat little function I put together using the example provided by stemkoski:
checkOverlapObject: function(voxel) // THIS IS USED TO SEE IF WE ARE OVER LAPPING ANY OBJECTS
{
var originPoint = voxel.position.clone();
var collidableObjs = this.rooms;
for (var vertexIndex = 0; vertexIndex < voxel.geometry.vertices.length; vertexIndex++)
{
var localVertex = voxel.geometry.vertices[vertexIndex].clone();
console.log(localVertex);
var globalVertex = localVertex.applyMatrix4( voxel.matrix );
console.log(globalVertex);
var directionVector = globalVertex.sub( voxel.position );
console.log(directionVector);
console.log(originPoint);
console.log(directionVector.clone().normalize());
if(collidableObjs.length > 0)
{
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableObjs );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
console.log(collisionResults);
console.log(collisionResults[0].distance);
console.log( directionVector.length() );
return false
}
}
}
return true;
},
Now what happens here is, before actually adding a rendered volex the user gets a preview of if they have permission to add the volex So we pass a volex made by:
var voxel = new THREE.Mesh( this.room.cubeGeometry, this.room.cubeTmpHoverMaterial );
voxel.geometry.computeBoundingBox();
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
voxel.material.color.setHex(this.colorTmpHover);
into our checkOverlapObject function to see if the object is overlapping a object that has been rendered onto the screen/grid
Now following my little neat function I made, I have placed console.log to output parameters and here is some of that output:
T…E.Vector3 {x: 25, y: 25, z: 25} <!-- our localVertex
T…E.Vector3 {x: 25, y: 25, z: 25} <!-- our globalVertex
T…E.Vector3 {x: 0, y: 0, z: -350} <!-- our directionVector
T…E.Vector3 {x: 25, y: 25, z: 375} <!-- our originPoint
T…E.Vector3 {x: 0, y: 0, z: -1} <!-- our directionVector.clone().normalize()
[Object, Object] <!-- our collisionResults
225 <!-- our collisionResults[0].distance
350 <!-- our directionVector.length()
This data is based off of the first picture.
Please understand that I have other volex that take up 2 blocks on the grid or more. So the reason for this is, I have a center location of the position but I need to take into account the rest of the object if it takes up 2 blocks on the grid to check if that overlaps the already rendered volex I don't care if they touch each other.
Any suggestions or thoughts as to what might be the problem?
As asked above I finally found a solution. I created a custom function for object collision/overlap.
Firstly, here is whats wrong with the above collision code as shown above, it process's the raycaster starting with the direction of our object - shooting a line straight across from its orgin to see if anything comes into contact with that ray. Now that is great and all but not very sufficient in telling when we have a overlap/collision if any part of the object is touching another object
As shwon with our rays:
So here is my solution.
When ever we have an object on our plane, we know everything about it, our coordinates of x,y,z - I am not dealing with y in this example so ignore that.
What we care about is our objects width = x and our objects depth/length = z. So in essence when we are checking to see if an object is overlapping another object we simply need to process through each min x/z and max x/z of our to be added object and our already rendered obj
How we do that is, loop through our object to be added starting min z position with a for loop. This starting min z position contains our position z + min z of object. This gives us the exact z position on our plane.
We than move on to our width or our object to be added starting min x position with a for loop. Don't forget our x gives us the exact position on our plane
Now the reason for this is simple. We want to start at position 0,0 of our object to be added. Increment by one up our length/z - increment all the way down our width/x and while doing this process each object on our plane that is already rendered and check to see if any exact point matches ours.
Here is the actual method:
checkOverlapObject: function(voxel) // THIS IS USED TO SEE IF WE ARE OVER LAPPING ANY OBJECTS
{
var collidableObjs = this.rooms;
//lets get our voxel min and max vars
var voxelMX = voxel.geometry.boundingBox.max.x;
var voxelMZ = voxel.geometry.boundingBox.max.z;
var voxelmX = voxel.geometry.boundingBox.min.x;
var voxelmZ = voxel.geometry.boundingBox.min.z;
// we need to get our voxel position ad to do some math to see if voxel min and max do not go beyound our boundries for our plane
var voxelPX = voxel.position.x;
var voxelPZ = voxel.position.z;
var totalPositionVoxelminZ = (voxelPZ + voxelmZ);
var totalPositionVoxelminX = (voxelPX + voxelmX);
var totalPositionVoxelMAXZ = (voxelPZ + voxelMZ);
var totalPositionVoxelMAXX = (voxelPX + voxelMX);
// start loop for object to add Z cordinate
for(var length = totalPositionVoxelminZ; length < totalPositionVoxelMAXZ; length++)
{
// start loop for object to add X cordinate
for(var width = totalPositionVoxelminX; width < totalPositionVoxelMAXX; width++)
{
for(var i = 0; i < collidableObjs.length;i++)
{
//lets get our voxel min and max vars
var thisvoxelMX = this.rooms[i].geometry.boundingBox.max.x;
var thisvoxelMZ = this.rooms[i].geometry.boundingBox.max.z;
var thisvoxelmX = this.rooms[i].geometry.boundingBox.min.x;
var thisvoxelmZ = this.rooms[i].geometry.boundingBox.min.z;
// we need to get our voxel position ad to do some math to see if voxel min and max do not go beyound our boundries for our plane
var thisvoxelPX = this.rooms[i].position.x;
var thisvoxelPZ = this.rooms[i].position.z;
var thistotalPositionVoxelminZ = (thisvoxelPZ + thisvoxelmZ);
var thistotalPositionVoxelminX = (thisvoxelPX + thisvoxelmX);
var thistotalPositionVoxelMAXZ = (thisvoxelPZ + thisvoxelMZ);
var thistotalPositionVoxelMAXX = (thisvoxelPX + thisvoxelMX);
for(var insideZ = thistotalPositionVoxelminZ; insideZ < thistotalPositionVoxelMAXZ; insideZ++)
{
for(var insideX = thistotalPositionVoxelminX; insideX < thistotalPositionVoxelMAXX; insideX++)
{
if(insideZ == length && insideX == width)
{
return false;
}
}
}
}
}
}
return true;
}
Here is our result:
This really does provide an exact answer on if your object is touching something it should not be touching because of the point by point it processes.
I hope this helps! Please feel free to use this at anytime.
Also note this really could effect overhead/memory usage if you are not careful on how you use this. I am working on a way to better optimize this in the case of having hundred of objects to process. So if you have any suggestions on modifying or adding to my existing code to accomplish this better and offer better performance in case of hundreds of objects passed, feel free provide details!
I've got a mesh whose yaw and pitch I want to be controlled by user input. I naively tried rotating the mesh about the z and y axes, and that seems to control yaw, but up and down is erratic.
function checkKeys(keys) {
if (keys.left) spaceship.rotation.z += .05;
if (keys.right) spaceship.rotation.z -= .05;
if (keys.up) spaceship.rotation.y += .05;
if (keys.down) spaceship.rotation.y -= .05;
}
How can I make left/right control the plane's yaw and up/down control pitch?
http://jsfiddle.net/trevordixon/d9BN9/2/
Update: I simplified FlyControls.js that wagerfield mentioned so it obeys my gamepad and handles rotation only. Here's what I ended up with (https://gist.github.com/trevordixon/5783321):
THREE.FlyControls = function(object) {
this.object = object;
// API
this.movementSpeed = 1.0;
this.rollSpeed = 0.005;
// disable default target object behavior
this.object.useQuaternion = true;
// internals
this.tmpQuaternion = new THREE.Quaternion();
this.moveState = {up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0};
this.moveVector = new THREE.Vector3(0, 0, 0);
this.rotationVector = new THREE.Vector3(0, 0, 0);
this.handleEvent = function(event) {
if (typeof this[event.type] == 'function') {
this[event.type](event);
}
};
this.update = function(delta) {
this.moveState.yawLeft = -gamepad.axes[2];
this.moveState.pitchDown = gamepad.axes[3];
this.moveState.rollLeft = (Math.abs(gamepad.axes[0]) < 0.15 ? 0 : gamepad.axes[0]) ||
gamepad.buttons[15]/2;
this.moveState.rollRight = (Math.abs(gamepad.axes[1]) < 0.15 ? 0 : gamepad.axes[1]) ||
gamepad.buttons[14]/2;
this.updateRotationVector();
var moveMult = delta * this.movementSpeed;
var rotMult = delta * this.rollSpeed;
this.object.translateX(this.moveVector.x * moveMult);
this.object.translateY(this.moveVector.y * moveMult);
this.object.translateZ(this.moveVector.z * moveMult);
this.tmpQuaternion.set(this.rotationVector.x * rotMult, this.rotationVector.y * rotMult, this.rotationVector.z * rotMult, 1).normalize();
this.object.quaternion.multiply(this.tmpQuaternion);
// expose the rotation vector for convenience
this.object.rotation.setEulerFromQuaternion(this.object.quaternion, this.object.eulerOrder);
};
this.updateRotationVector = function() {
this.rotationVector.x = ( -this.moveState.pitchDown + this.moveState.pitchUp );
this.rotationVector.y = ( -this.moveState.yawRight + this.moveState.yawLeft );
this.rotationVector.z = ( -this.moveState.rollRight + this.moveState.rollLeft );
};
function bind(scope, fn) {
return function () {
fn.apply( scope, arguments );
};
};
this.updateRotationVector();
};
The yaw axis is the up axis, or y-axis in this case, so you need to rotate your spaceship geometry so it is level to begin with:
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
Then you need to change the eulerOrder of you spaceship so yaw (Y) is first and pitch (X) is second:
spaceship.rotation.order = "YXZ"; // three.js r.65
Then you need to adjust your keycodes accordingly.
three.js r.65
The problem you are encountering is due to the way in which rotations are calculated and applied to objects in three.js (well, 3D engines in general). For each rotation axis (x, y & z) a rotation matrix is multiplied onto the object's matrix (which contains it's position, scale and rotation).
Since matrices are non-commutative; meaning that A * B != B * A, the order of multiplication matters. By default this 'Euler order' is X then Y then Z. That is why your Z rotation seems to make sense, but your Y rotation is not what you would intuitively expect it to be.
Have no fear, there is a solution! I assume that you would like your plane to rotate in the same fashion as the camera in this demo where each additional change in rotation is applied relative to the current rotation. In 3D programs like 3D Studio Max, this is known as a local rotation:
http://threejs.org/examples/webgl_geometry_terrain.html
If you inspect the source of this demo, you will see that the controls for the camera are created on line 76:
controls = new THREE.FirstPersonControls( camera );
There are also FlyControls, which may be more suitable for you?
http://threejs.org/examples/misc_controls_fly.html
Either way, I would start by playing around with these ready-made controls and using your plane object in the constructor rather than the camera.
Hope that helps.