Search code examples
javascriptdomrapier

Collision detection not aligning properly when rendering Rapier.js to the DOM


I've made a simple example with Rapier.js to illustrate my problem on a CodePen.

I'm trying to use the JavaScript API of the physics engine Rapier to render DOM elements with collision detection. If the elements are the same size then it works as expected, but if two rectangles of different sizes interact with each other then the alignment is off. In the CodePen you can see two boxes that fall to the ground with gravity. The bottom one stops correctly on the ground but the top one remains hovering above the bottom one. If I change the boxes to be the same height then they work as expected. What am I doing wrong?

import RAPIER from "https://cdn.skypack.dev/@dimforge/rapier2d-compat";

const createBox = (id) => {
  const element = document.getElementById(id);
  const rect = element.getBoundingClientRect();

  const rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
    rect.x,
    rect.y
  );

  const rigidBody = world.createRigidBody(rigidBodyDesc);

  const colliderDesc = RAPIER.ColliderDesc.cuboid(
    rect.width / 2,
    rect.height / 2
  );

  world.createCollider(colliderDesc, rigidBody);
  return [rigidBody, element];
};

await RAPIER.init();
const gravity = { x: 0.0, y: 9.81 };
const world = new RAPIER.World(gravity);

const groundRect = document.getElementById(`ground`).getBoundingClientRect();

const colliderDesc = RAPIER.ColliderDesc.cuboid(
  groundRect.width / 2,
  groundRect.height / 2
).setTranslation(groundRect.x, groundRect.y);

world.createCollider(colliderDesc);

const [oneBody, oneElement] = createBox(`one`);
const [twoBody, twoElement] = createBox(`two`);

const gameLoop = () => {
  world.step();

  const oneTranslation = oneBody.translation();
  oneElement.style.left = `${oneTranslation.x}px`;
  oneElement.style.top = `${oneTranslation.y}px`;

  const twoTranslation = twoBody.translation();
  twoElement.style.left = `${twoTranslation.x}px`;
  twoElement.style.top = `${twoTranslation.y}px`;

  requestAnimationFrame(gameLoop);
};

gameLoop();

Solution

  • The center of Rapier’s cuboids are located at the middle of the rectangle, not its top-left corner. So you need to account for this in your DOM element positioning:

    import RAPIER from "https://cdn.skypack.dev/@dimforge/rapier2d-compat";
    
    const createBox = (id) => {
      const element = document.getElementById(id);
      const rect = element.getBoundingClientRect();
    
      console.log(rect);
      const rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
        rect.x + rect.width / 2,
        rect.y + rect.height / 2
      );
    
      const rigidBody = world.createRigidBody(rigidBodyDesc);
    
      const colliderDesc = RAPIER.ColliderDesc.cuboid(
        rect.width / 2,
        rect.height / 2
      );
    
      world.createCollider(colliderDesc, rigidBody);
      return [rigidBody, element, rect];
    };
    
    await RAPIER.init();
    const gravity = { x: 0.0, y: 9.81 };
    const world = new RAPIER.World(gravity);
    
    const groundRect = document.getElementById(`ground`).getBoundingClientRect();
    
    const colliderDesc = RAPIER.ColliderDesc.cuboid(
      groundRect.width / 2,
      groundRect.height / 2
    ).setTranslation(groundRect.x + groundRect.width / 2, groundRect.y + groundRect.height / 2);
    
    world.createCollider(colliderDesc);
    
    const [oneBody, oneElement, oneRect] = createBox(`one`);
    const [twoBody, twoElement, twoRect] = createBox(`two`);
    
    const gameLoop = () => {
      world.step();
    
      const oneTranslation = oneBody.translation();
      oneElement.style.left = `${oneTranslation.x - oneRect.width / 2}px`;
      oneElement.style.top = `${oneTranslation.y - oneRect.height / 2}px`;
    
      const twoTranslation = twoBody.translation();
      twoElement.style.left = `${twoTranslation.x - twoRect.width / 2}px`;
      twoElement.style.top = `${twoTranslation.y - twoRect.height / 2}px`;
    
      requestAnimationFrame(gameLoop);
    };
    
    gameLoop();
    

    Also keep in mind that you are not taking the rotation into account here. It doesn’t mater for this particular example since they won’t rotate, but if they do, your rendering won’t match what the physics does.