Buoyant Force Proportional to Depth in Matter.js
This article explains how to simulate Archimedes' buoyant force
proportional to submerged depth in Matter.js. Because Matter.js does not
provide native fluid simulation, you must compute the submerged portion
of a rigid body relative to a defined water line and manually apply an
upward force using Matter.Body.applyForce during each
engine update tick.
The Physics Principles
Archimedes' principle states that the buoyant force exerted on a body submerged in a fluid is equal to the weight of the fluid displaced by the body:
\[F_b = \rho \cdot V_{\text{submerged}} \cdot g\]
Where:
- \(\rho\) is the fluid density.
- \(V_{\text{submerged}}\) is the submerged volume (or submerged area in 2D).
- \(g\) is the acceleration due to gravity.
In a discrete 2D physics engine, we can approximate \(V_{\text{submerged}}\) by determining the ratio of the body that lies below the fluid surface line (\(y_{\text{surface}}\)).
Implementation Steps
- Define Fluid Properties: Establish a horizontal water surface line, fluid density, and linear fluid drag (viscosity) to dampen oscillations.
- Listen to Engine Updates: Register an event
listener on
Events.on(engine, 'beforeUpdate', callback). - Calculate Submergence: Compare the body's bounding box against the water surface line to calculate the fraction of the body below water.
- Apply Force and Drag: Compute the net upward force proportional to the submerged fraction and apply opposing linear drag forces to simulate fluid resistance.
Code Example
Below is a complete implementation using Matter.js:
const { Engine, Render, Runner, Bodies, Composite, Events, Body, Vector } = Matter;
// 1. Initialize Matter.js environment
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: { width: 800, height: 600, wireframes: false }
});
Render.run(render);
Runner.run(Runner.create(), engine);
// 2. Define water line and fluid parameters
const waterLevel = 350; // Y-coordinate of fluid surface
const fluidDensity = 0.0015;
const fluidDrag = 0.05;
// 3. Create a floating body
const box = Bodies.rectangle(400, 100, 60, 60, {
density: 0.001 // Less dense than fluid to allow floating
});
Composite.add(world, box);
// 4. Hook into beforeUpdate to compute buoyancy
Events.on(engine, 'beforeUpdate', () => {
const bounds = box.bounds;
const bodyHeight = bounds.max.y - bounds.min.y;
// Check if body touches the water
if (bounds.max.y > waterLevel) {
// Calculate submerged depth clamped between 0 and the total height
const submergedDepth = Math.min(bounds.max.y - waterLevel, bodyHeight);
const submergedFraction = submergedDepth / bodyHeight;
// Archimedes force: Proportional to displaced area/volume and gravity
const gravity = engine.gravity.y * engine.gravity.scale;
const displacedMass = (box.mass / box.density) * fluidDensity * submergedFraction;
const buoyancyMagnitude = displacedMass * gravity;
// Apply upward buoyant force at center of mass
Body.applyForce(box, box.position, {
x: 0,
y: -buoyancyMagnitude
});
// Apply fluid resistance (linear drag) to stabilize motion
const dragForce = {
x: -box.velocity.x * fluidDrag * submergedFraction,
y: -box.velocity.y * fluidDrag * submergedFraction
};
Body.applyForce(box, box.position, dragForce);
// Apply angular drag to dampen rotation underwater
box.torque -= box.angularVelocity * fluidDrag * submergedFraction * 1000;
}
});Key Considerations
- Center of Buoyancy vs. Center of Mass: For simple
approximations, applying the force at
box.positionprevents unwanted torque. For realistic rolling and righting behaviors, apply the force at the centroid of the submerged portion instead of the center of mass. - Complex Geometries: For irregular polygons, bounding boxes provide only a rough estimate. For high accuracy, use polygon clipping algorithms (such as the Sutherland-Hodgman algorithm) against the fluid plane to calculate the exact submerged polygon area and its centroid.