gkjohnson / three-mesh-bvh

A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.
https://gkjohnson.github.io/three-mesh-bvh/example/bundle/raycast.html
MIT License
2.38k stars 247 forks source link

[Question] Collision effect using BVH Shapecast #679

Closed AshhadDevLab closed 1 week ago

AshhadDevLab commented 1 week ago

Description

Hi there, I am trying to create a collision effect using .shapecast to prevent objects to phase through each other. So far I have created 2 meshes namely boxMesh1 and boxMesh4. boxMesh1 is made draggable using DragControls where as boxMesh4 is idle and I want to make the moveable mesh to collide with the idle mesh instead of phasing through it.

Issue

Currently as soon as the meshes collide the moveable mesh gets stuck. Also even if I try the previous position method the meshes still phase through a little bit.

Demo

Here's a working demo with code for better visuals: Glitch

Code Snippet

Also here's the snippet that's responsible for checking and preventing the phase:

import * as THREE from "three";
import { DragControls } from "three/addons/controls/DragControls.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import {
  acceleratedRaycast,
  computeBoundsTree,
  disposeBoundsTree,
  MeshBVHHelper,
} from "three-mesh-bvh";

let renderer, scene, camera, dragcontrols, controls;
let objects, boxMesh1, boxMesh2, boxMesh3, boxMesh4, boxMesh5;
let lastSafePosition = new THREE.Vector3();
let transformMatrix, hit

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

function init() {
  const bgColor = 0x263238;
  renderer = new THREE.WebGLRenderer({ antialias: false });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setClearColor(bgColor, 1);
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  document.body.appendChild(renderer.domElement);

  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.far = 100;
  camera.updateProjectionMatrix();
  camera.position.set(1, 1, -5);

  controls = new OrbitControls(camera, renderer.domElement);

  boxMesh1 = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshStandardMaterial()
  );
  scene.add(boxMesh1);

  boxMesh4 = new THREE.Mesh(
    new THREE.BoxGeometry(5, 2.5, 0.15),
    new THREE.MeshBasicMaterial({ color: 0xff0000 })
  );
  boxMesh4.position.set(0, 0.75, 2.5);
  boxMesh4.rotation.y = Math.PI * 0.25;
  boxMesh4.geometry.computeBoundsTree();
  scene.add(boxMesh4);

  dragcontrols = new DragControls([boxMesh1], camera, renderer.domElement);

  dragcontrols.addEventListener("dragstart", (event) => {
    controls.enabled = false;
  });

  dragcontrols.addEventListener("drag", (event) => {
    boxMesh1.position.y = 0;
  });

  dragcontrols.addEventListener("dragend", () => {
    controls.enabled = true;
  });

  const metreSize = 10.0;
  const metreDivisions = 40.0;
  const decimetreSize = 10.0;
  const decimetreDivisions = 400.0;

  const gridHelper = new THREE.GridHelper(
    metreSize,
    metreDivisions,
    0x8fc8df,
    0x8fc8df
  );
  scene.add(gridHelper);

  const gridHelper2 = new THREE.GridHelper(
    decimetreSize,
    decimetreDivisions,
    0xd7eef5,
    0xd7eef5
  );
  scene.add(gridHelper2);

  gridHelper.position.set(0, -0.5, 0);
  gridHelper2.position.set(0, -0.5, 0);
}

function render() {
  requestAnimationFrame(render);

  transformMatrix = new THREE.Matrix4()
    .copy(boxMesh4.matrixWorld)
    .invert()
    .multiply(boxMesh1.matrixWorld);

  hit = boxMesh4.geometry.boundsTree.intersectsGeometry(
    boxMesh1.geometry,
    transformMatrix
  );

  if (hit) {
    boxMesh1.material.color.set(0xe91e63);
    boxMesh1.material.emissive.set(0xe91e63).multiplyScalar(0.25);
    boxMesh1.position.copy(lastSafePosition);
  } else {
    boxMesh1.material.color.set(0x666666);
    boxMesh1.material.emissive.set(0xe91e63);
    lastSafePosition.copy(boxMesh1.position);
  }
  controls.update();
  renderer.render(scene, camera);
}

init();
render();

I'll be very grateful and thankful if anyone can help me regarding this.

rafaelbotazini commented 1 week ago

You sould call .updateMatrixWorld() after dragging the object.

Tip: check collisions on drag event to prevent querying every render:

dragcontrols.addEventListener("drag", (event) => {
    boxMesh1.position.y = 0;

    // position was changed, update world matrix before checking collision
    boxMesh1.updateMatrixWorld();

    transformMatrix = new THREE.Matrix4()
      .copy(boxMesh4.matrixWorld)
      .invert()
      .multiply(boxMesh1.matrixWorld);

    hit = boxMesh4.geometry.boundsTree.intersectsGeometry(
      boxMesh1.geometry,
      transformMatrix
    );

    if (hit) {
      boxMesh1.material.color.set(0xe91e63);
      boxMesh1.material.emissive.set(0xe91e63).multiplyScalar(0.25);
      boxMesh1.position.copy(lastSafePosition);
    } else {
      console.log(false);
      boxMesh1.material.color.set(0x666666);
      boxMesh1.material.emissive.set(0xe91e63);
      lastSafePosition.copy(boxMesh1.position);
    }
  });
AshhadDevLab commented 1 week ago

You sould call .updateMatrixWorld() after dragging the object.

Tip: check collisions on drag event to prevent querying every render:

dragcontrols.addEventListener("drag", (event) => {
    boxMesh1.position.y = 0;

    // position was changed, update world matrix before checking collision
    boxMesh1.updateMatrixWorld();

    transformMatrix = new THREE.Matrix4()
      .copy(boxMesh4.matrixWorld)
      .invert()
      .multiply(boxMesh1.matrixWorld);

    hit = boxMesh4.geometry.boundsTree.intersectsGeometry(
      boxMesh1.geometry,
      transformMatrix
    );

    if (hit) {
      boxMesh1.material.color.set(0xe91e63);
      boxMesh1.material.emissive.set(0xe91e63).multiplyScalar(0.25);
      boxMesh1.position.copy(lastSafePosition);
    } else {
      console.log(false);
      boxMesh1.material.color.set(0x666666);
      boxMesh1.material.emissive.set(0xe91e63);
      lastSafePosition.copy(boxMesh1.position);
    }
  });

Well thanks for the code it turns out to be working as expected!