Detecting collisions in three.js – a comprehensive guide

Currently, I am working with three.js and have incorporated two mesh geometries into my scene.

I am looking for a way to detect collisions if these geometries intersect or would intersect when translated. How can I carry out collision detection using three.js? Alternatively, if three.js does not offer built-in collision detection features, are there any external libraries that I could use alongside three.js?

Answer №1

Looking into Three.js, it appears that the CollisionUtils.js and Collisions.js utilities are no longer supported. mrdoob, the creator of three.js, now suggests updating to the latest version of three.js and utilizing the Ray class instead. Below is a suggestion on how to implement this.

The concept involves checking if a specific mesh named "Player" intersects with any meshes within an array known as "collidableMeshList". The approach is to generate a series of rays originating from the Player mesh's coordinates (Player.position) and extending towards each vertex in the Player mesh's geometry. Each Ray includes a method called "intersectObjects" which provides details on objects intersected by the Ray and their respective distances from the origin of the Ray. If the distance to an intersection is less than the distance between the Player's position and the vertex of the geometry, then a collision has occurred within the player's mesh - commonly referred to as an "actual" collision.

An operational example can be found at:

You can control the red wireframe cube using arrow keys for movement and W/A/S/D for rotation. When the cube intersects with one of the blue cubes, the text "Hit" will display at the top of the screen for each intersection following the described criteria. Refer to the critical section of the code 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() ) 
    {
        // handle collision scenario...
    }
}

There are potential limitations to this method.

(1) If the ray's origin lies within a mesh M, no collisions will be detected between the ray and M.

(2) Small objects relative to the Player mesh may bypass detection between rays, resulting in missed collisions. To address this, consider having small objects create the rays and conduct collision detection from their perspective or incorporate more vertices on the mesh (e.g., using CubeGeometry(100, 100, 100, 20, 20, 20) rather than CubeGeometry(100, 100, 100, 1, 1, 1)). However, adding more vertices may impact performance, so use this cautiously.

I encourage others to share their solutions and insights on this matter. It took me some time to come up with the solution detailed above, so collaboration is welcomed.

Answer №2

A revised response from Lee tailored to the most recent release of three.js

for (let index = 0; index < Player.geometry.attributes.position.array.length; index++)
{       
    let localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, index).clone();
    let globalVertex = localVertex.applyMatrix4(Player.matrix);
    let directionVector = globalVertex.sub( Player.position );

    let ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );
    let collisionResults = ray.intersectObjects( collidableMeshList );
    if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) 
    {
        // handle collision event
    }
}

Answer №3

Exploring this topic thoroughly would require more time and space than a single SO question allows. However, I'll provide a brief overview to improve the site's SEO:

For basic collision detection without a full physics engine, consider exploring alternative options (link removed due to expired website).

If you're interested in implementing collision response beyond simple detection, take a look at (link removed due to expired website), an accessible Ammo.js wrapper designed for use with Three.js.

Answer №4

This function is specifically designed for BoxGeometry and BoxBufferGeometry shapes.

Implement the following custom 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;
}

Utilize this function in conditional statements as shown below:

if (checkTouching(cube1,cube2)) {
alert("Collision Detected!")
}

You can view an example of this function in action at

Note: If you rotate or scale either cube/prism, collisions may not be accurately detected due to lack of consideration for these transformations.

Answer №5

My alternate solution provides a more accurate output, returning true only when a collision is detected and false otherwise (with some exceptions). Let's start by creating the following function:

function checkCollision(objA, objB) {
  let meshes = [objB]; 
  let origin = objA.position.clone();
  let numVertices = objA.geometry.vertices.length;
  let positionA = objA.position;
  let matrixA = objA.matrix;
  let verticesA = objA.geometry.vertices;
    for (var vertexIndex = numVertices - 1; vertexIndex >= 0; vertexIndex--) {      
        let localVertex = verticesA[vertexIndex].clone();
        let globalVertex = localVertex.applyMatrix4(matrixA);
        let directionVector = globalVertex.sub(positionA);
        
        let raycaster = new THREE.Raycaster(origin, directionVector.clone().normalize());
        let results = raycaster.intersectObjects(meshes);
        if (results.length > 0 && results[0].distance < directionVector.length()) { 
            return true;
    }
    }
 return false;
}

This function is based on an answer given by Lee Stemkoski, with optimizations to improve performance and eliminate the need for an array of meshes. Now, step 2: create this function:

function checkOverlap(objA, objB) {
  return checkCollision(objA, objB) || checkCollision(objB, objA) || (objA.position.z == objB.position.z && objA.position.x == objB.position.x && objA.position.y == objB.position.y)
}

This function evaluates to true if the center of mesh A is not within mesh B AND the center of mesh B is not within mesh A, OR their positions are equal AND they are touching. This logic accommodates scaling changes applied to one or both meshes. For a live example, visit:

Answer №6

If you're looking for a simpler solution than ray casting and creating your own physics environment, consider using CANNON.js or AMMO.js. These physics libraries are built on top of THREE.js and provide a secondary physics environment where you can tie object positions to simulate physics. While CANNON.js documentation is easy to follow, it hasn't been updated in 4 years. However, the community has forked the repo and keeps it updated as cannon-es. Here's a code snippet to demonstrate how it works:

// Code snippet demonstrating floor and ball creation in CANNON.js
// This creates a floor and a ball in both THREE.js and CANNON.js environment.

Once you set up your scene with CANNON.js, you can update the position of your THREE.js scene within the animate function based on the physics scene. For more information, refer to the documentation. Using a physics library simplifies collision simulation. You might also want to explore Physi.js, another user-friendly library that doesn't require setting up a separate physics environment.

Answer №7

Within my implementation of threejs, I solely utilize

geometry.attributes.position.array
rather than geometry.vertices. In order to convert this to vertices, I have devised the following TypeScript 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;
};

The reasoning behind passing in the object's position for each dimension is that the bufferVertices are originally relative to the object's center. For my specific requirements, I needed them to be in a global context.

In addition, I have created a small function to identify collisions based on vertices. This method provides the option to sample vertices for intricate objects or assess proximity by comparing all vertices with those of another 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) {
    // Firstly, ascertain if it resides 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;

    // Subsequently, determine if there are any 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;
        }
      }
    }
  }
};

Answer №8

If you're looking for a reliable collision detection library, I highly recommend checking out cannon.js. It's incredibly efficient and user-friendly, making collision detection tasks a breeze. Another great option to consider is ammo.js.

Answer №9

When navigating through my walls, I meticulously examine the x and z coordinates to determine camera movement adjustments.

For further details, visit this link.

Assuming that this.segments represents an array of objects called Wall:

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);
    }
  }
});

Answer №10

give this a shot:

const checkCollision = () => {
      let hasCollided = false;
      // add event listener for collision
      object1.addEventListener('collision', (event) => {
        // only perform action if not already collided
        if (!hasCollided) {
          // check if colliding with specific target
          if (event.detail.element.id === 'object2') {
            hasCollided = true;
            doSomething();
          }
        }
      })

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Electronic circuit embedded within a material-textured text field offering multiline functionality

While experimenting with TagsInput, I came across this helpful snippet on codesandbox that you can check out here. The challenge I encountered is when there are numerous chips, they extend beyond the boundaries of the text field. My goal is to implement ...

NodeJS error: The 'error' event was not properly handled, resulting in a throw er

I've developed a basic web application using React, node-postgres, expressJS, and PostgreSQL DB. The app includes two input fields and a button. Upon clicking the button, the values are saved in the database. While running the ExpressJS server with ...

Retrieve the attribute value from an HTML element in a JSON response using JavaScript

I received a JSON request result containing a blogger post. // API callback posts( { "version": "1.0", "encoding": "UTF-8", "entry": { "title": { "type": "text", "$t": "Vimeo Embed Video Post" ...

I possess a pair of items that require merging together while combining any overlapping key values in their properties

I have a scenario where I need to merge two objects and concatenate strings if they have the same key. obj1 = { name: 'John', address: 'Cairo' } obj2 = { num : '1', address: 'Egypt' } After merging, the r ...

JavaScript code to copy a specified column through the last column, and then paste it down to the last row

I have limited experience with JavaScript and I've been putting together the code I need by searching online resources and watching videos. My goal is to set multiple columns in row 4, starting from column 18 to the last column, as the active cells fo ...

Issue: Headers cannot be set again once they have been sent during page reload

Whenever I attempt to refresh a specific page, I encounter an Error: Can't set headers after they are sent. Interestingly, when I click on a link to navigate to that page, the error doesn't occur. I have meticulously reviewed the sequence of even ...

Spinning Global Lighting and Bouncing in three.js

I am trying to create a 3D object that automatically rotates around the Y axis while still allowing users to scale and custom rotate the object with their mouse. This is what I have so far: import * as THREE from 'three'; import { GLTFLoader } f ...

Navigating cross domain JSONP cookies in IE8 to IE10 can be a real headache

For reasons completely out of my control, here is the current scenario I'm faced with: I have a product listing on catalog.org When you click the "Add to Cart" button on a product, it triggers an AJAX JSONP request to secure.com/product/add/[pro ...

Conceal the loading spinner in JQuery once the image has finished loading

I am working with a jQuery function that captures the URL of an image link and displays the image. However, my issue lies in managing the loading process. I would like to display a LOADING message and hide it once the image has fully loaded, but I am strug ...

Error occurs when running Visual Studio Code locally - variable not defined

After successfully going through a Microsoft online demo on setting up an httpTrigger in Visual Studio Code with JavaScript to upload it to Azure Functions, I decided to customize the code for a specific calculation. I managed to get the calculation done a ...

Is there a way to deliver information to a specific element on a client's HTML page?

My current project involves using Node.js to serve a webpage that collects user inputs and stores them in a mongodb server. The web page also displays these user inputs. I am trying to determine the best way to pass the user inputs from node.js to the < ...

Dynamically scrolling using SuperScrollorama and Greensocks

I'm currently facing a JavaScript animated scroll challenge that has me scratching my head. My approach involves using the SuperScrollorama jQuery plugin, which relies on the Greensock JS tweening library. The main effect I'm aiming for is to " ...

Uploading images using the Drag and Drop feature in HTML

Hello, I'm having an issue with the drag and drop functionality. I want to expand the size of the input to cover the entire parent div, but for some reason, the input is appearing below the drag and drop div. Can anyone assist me with this? https://i. ...

The options for answering (True, False, Yes, and No) are not currently visible

I am struggling with displaying buttons for "True", "False", "Yes", and "No" in an appended row. Here is the application: Application To use the application, follow these steps: 1. Open the application and click on the green plus button. A modal window ...

Oops! An error occurred while trying to find the _mongodb._tcp.blog-cluster-0hb5z.mongodb.net. Please check your query settings and try again

My free Mongo Atlas cluster suddenly stopped connecting, even though everything was working fine before. Strangely, I can see that data has been collected on the MongoDB website. It's puzzling why this issue occurred and now my entire site won't ...

The initial click may not gather all the information, but the subsequent click will capture all necessary data

Issue with button logging on second click instead of first, skipping object iteration function. I attempted using promises and async await on functions to solve this issue, but without success. // Button Code const btn = document.querySelector("button") ...

In the game of rock paper scissors, the icons become unclickable once you achieve a score of 5

I have created a Rock Paper Scissors game where the score resets if either the user or computer reaches 5 points, but I want to prevent any further play after hitting 5. Currently, the game displays a message and reloads after 5 seconds, during which tim ...

The onClick event is not functioning properly with React's select and option elements

Looking for a way to get the option value from each region? const region = ['Africa','America','Asia','Europe','Oceania']; <div className="options"> <select> ...

My goal is to generate four HTML buttons that trigger specific functions: addition, subtraction, multiplication, and division

I am currently learning JavaScript, and I am facing some challenges with my assignment. The task is to create four buttons in HTML that trigger different functions - addition, subtraction, multiplication, and division. The goal is for the user to input two ...

What's the better approach for creating Maps and Sets in ES6: relying on a compiler or leveraging syntactic sugar?

It has always baffled me why we are unable to write ES6 Maps in the following way: new Map({ ['ArrayAsKey']: {foo:bar} }) Is there a workaround for this limitation? Or perhaps a technique that enhances the experience of utilizing these mode ...