Simulate Buoyancy in Water Using Matter.js

Simulating buoyancy in Matter.js requires applying custom upward forces and viscous drag to dynamic bodies whenever they enter a designated fluid region. Because Matter.js is a rigid-body physics engine without native fluid dynamics, developers must manually calculate immersion depth, buoyant force counteracting gravity, and fluid resistance to mimic natural floating behavior.

1. Define the Fluid Region

To simulate a body of water, create a static body with its isSensor property set to true. This prevents physical collisions while allowing you to detect when other objects intersect the water.

const water = Matter.Bodies.rectangle(400, 500, 800, 200, {
  isStatic: true,
  isSensor: true,
  render: { fillStyle: 'rgba(0, 150, 255, 0.4)' }
});
Matter.World.add(world, water);

2. Track Immersion and Apply Forces

Hook into the Matter.js update loop using Events.on(engine, 'beforeUpdate', callback). In each tick, check which bodies overlap the fluid area using Matter.Query.region or boundary checks, and apply the appropriate forces.

Matter.Events.on(engine, 'beforeUpdate', () => {
  const bodies = [box]; // Array of dynamic bodies to evaluate
  const waterBounds = water.bounds;

  bodies.forEach(body => {
    // Check if the body intersects the water area
    if (body.bounds.max.y > waterBounds.min.y && body.bounds.min.y < waterBounds.max.y) {
      
      // Calculate submerged percentage based on vertical overlap
      const submergedDepth = Math.min(body.bounds.max.y - waterBounds.min.y, body.bounds.max.y - body.bounds.min.y);
      const bodyHeight = body.bounds.max.y - body.bounds.min.y;
      const submergedRatio = Math.min(Math.max(submergedDepth / bodyHeight, 0), 1);

      // 1. Buoyant Force (Archimedes' Principle)
      // Opposes gravity: mass * gravity * submerged proportion * buoyancy scalar
      const gravity = engine.gravity.scale * engine.gravity.y;
      const buoyancyMagnitude = body.mass * gravity * submergedRatio * 1.5; 
      
      // 2. Drag Force (Fluid Resistance)
      // Slows down the body linearly to simulate water viscosity
      const dragFactor = 0.05 * submergedRatio;
      const dragX = -body.velocity.x * dragFactor;
      const dragY = -body.velocity.y * dragFactor;

      // Apply combined force to body center
      Matter.Body.applyForce(body, body.position, {
        x: dragX,
        y: -buoyancyMagnitude + dragY
      });

      // 3. Angular Damping
      // Dampen rotational movement while submerged
      Matter.Body.setAngularVelocity(body, body.angularVelocity * (1 - 0.05 * submergedRatio));
    }
  });
});

3. Fine-Tune Fluid Parameters