How to detect collision in three.js?
Asked Answered
U

10

76

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?

Utmost answered 13/7, 2012 at 15:38 Comment(5)
I searched it on google, but I've only found ray collision.Utmost
I think ray collision is the way to go... the CollisionUtils.js and Collisions.js files that Adnan is (presumably) referencing are out of date and are not part of the most recent (v49 at time of writing) three.js version.Concierge
Looks like a good question to me. SO can be so dumb.Gabriello
@Adi I did google it. And this IS the first result.Boehmenism
I have to create a wall and add a window on it which user user and drag over the wall before they decide where to put in on the wall. I have to limit the dragging of the window in the limits of the wall. I think I have to detect collision and get vertices or something. I am not sure, Please suggest something. I am new to three.js or any kind of 3D App.Sessoms
C
124

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.

Concierge answered 14/7, 2012 at 3:13 Comment(11)
While this method seems to test if any vertex intersects from the center of the obect, it would be twice as slow but 100%(?) accurate to test all edges(connected vertices). So to elaborate, you'd need to loop through each face, and take vertex[n] and vertex[(n + 1)%len] to get all the edges. If I hug someone, they intersect the center of my position and my hand, but they don't intersect my skin, as doing an edge check would test.Eunuchoidism
That's a nice idea! For 100%(?) accuracy, I think you would need to test the edges on each of the two meshes, and you would need to test them going in both directions, since collisions are only detected in one direction, when the ray goes from the outside to the inside of the mesh. Sure it might be a bit slower, but you could speed it up with a preliminary bounding sphere radius check. But most importantly, I think you may be right about 100% accuracy...Concierge
I would suggest that you avoid instantiating a new Raycaster inside the render loop. Instantiate one and reuse it.Sympathy
Great answer. Just to clarify: collisionResults[0].distance is a distance from a ray origin to a place where the ray intersected an object (it is not necessarily the shortest distance from the ray origin to the object, especially for objects of complex shapes)Paletot
This is great, solved my problem which included rotated objects. In the demo I had to change it to this to make it work var globalVertex = localVertex.applyMatrix4( MovingCube.matrixWorld ); for anyone else that is trying it and it may not be working!Heymann
Just in case this is helpful - I couldn't make the above code work (I don't know if three.js has changed since the answer) but changing the THREE.Ray instantiation to a THREE.Raycaster did the trick.Understand
It works. But, unfortunately, it is pretty slow if collidable model is complex. And I can't use fast gpu picking here instead of reaycasting. I will try to use spatial indexing (for mesh faces) and use with this mathod.Yours
What if there is no geometry.vertices data in mesh geometry. In obj models there is geometry.attributes.position.array but no geometry.verticesCaitlyncaitrin
Since three.js now on his actual version does not accept the THREE.CubeGeometry method you should be using THREE.BoxGeometry instead or it won't workMatted
how to make it work for BufferGeometry?Vahe
I am having trouble following which variable stores the object which collided with the player? I need to iterate through all objects the player has hit.Speechless
B
6

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...
    }
}
Bullfinch answered 10/1, 2022 at 11:38 Comment(2)
is it index in the third line or vertexIndex?Vahe
Yes it is. Fixed it.Bullfinch
C
4

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

Crooks answered 13/7, 2012 at 17:54 Comment(1)
The demo you linked is ray collisionUtmost
H
3

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)

Hux answered 16/7, 2020 at 0:36 Comment(0)
H
1

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/

Hux answered 15/8, 2020 at 1:53 Comment(0)
F
1

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

Fishplate answered 3/2, 2021 at 23:29 Comment(0)
B
0

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;
        }
      }
    }
  }
};
Bonn answered 26/9, 2021 at 1:35 Comment(0)
C
0

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.

Celestinacelestine answered 16/11, 2022 at 20:46 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewSpitsbergen
F
0

I just go through all my walls. I check the x and z coordinates. If they match, I send the camera back a notch. this is how I did it. this can be seen at 7

assume this.segments is a array of Wall objects.

this.segments.forEach(elem => {
  var pos = elem.position;
  if ((Math.round(this.camera.position.x) === (pos.x | pos.z)) || (Math.round(this.camera.position.z) === (pos.x | pos.z))) {
  console.log(Math.round(this.camera.position.x) + "x" + pos.x + " " + pos.z);
  console.log(Math.round(this.camera.position.z) + "z" + pos.x + " " + pos.z);
  if ((Math.round(this.camera.position.z) === (pos.z)) && this.hid.moveForward) {
    this.hid.controls.moveForward(-moveSpeed);
  } else if ((Math.round(this.camera.position.z) !== (pos.z)) && this.hid.moveForward) {
    this.hid.controls.moveForward(moveSpeed);
  }
  else if ((Math.round(this.camera.position.z) === (pos.z)) && this.hid.moveBackward) {
    this.hid.controls.moveForward(-moveSpeed);
  }
  else if ((Math.round(this.camera.position.z) !== (pos.z)) && this.hid.moveBackward) {
    this.hid.controls.moveForward(moveSpeed);
  }
  if (Math.round(this.camera.position.x) === (pos.x) && this.hid.moveLeft) {
    this.hid.controls.moveRight(moveSpeed);
  }
  else if (Math.round(this.camera.position.x) !== (pos.x) && this.hid.moveLeft) {
    this.hid.controls.moveRight(-moveSpeed);
  }
  else if (Math.round(this.camera.position.x) === (pos.x) && this.hid.moveRight) {
    this.hid.controls.moveRight(-moveSpeed);
  }
  else if (Math.round(this.camera.position.x) !== (pos.x) && this.hid.moveRight) {
    this.hid.controls.moveRight(moveSpeed);
  }
  if (Math.round(this.camera.position.x) === (pos.z) && this.hid.moveLeft) {
    this.hid.controls.moveRight(moveSpeed);
  }
  else if (Math.round(this.camera.position.x) !== (pos.z) && this.hid.moveLeft) {
    this.hid.controls.moveRight(-moveSpeed);
  }
  else if (Math.round(this.camera.position.x) === (pos.z) && this.hid.moveRight) {
    this.hid.controls.moveRight(-moveSpeed);
  }
  else if (Math.round(this.camera.position.x) !== (pos.z) && this.hid.moveRight) {
    this.hid.controls.moveRight(moveSpeed);
  }
  }
});
Feebleminded answered 21/10, 2023 at 1:6 Comment(0)
H
-1

try this:

let hasCollided = false
      // listener for collision
      thing1.addEventListener('collide', (e) => {
        // make sure it only fires once
        if (!hasCollided) {
          // make sure its colliding with the right target
          if (e.detail.body.el.id === 'thing2') {
            hasCollided = true
            doThing()
          }
        }
      }) 
Helbonnas answered 6/7, 2023 at 15:31 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Decal

© 2022 - 2024 — McMap. All rights reserved.