Calculate Fluid Overlap Area in Matter.js

Accurately simulating buoyancy or hydrodynamic drag in Matter.js requires calculating the exact portion of a rigid body submerged beneath a fluid surface. This guide explains how to compute the surface area overlap between an arbitrary 2D Matter.js body and a horizontal fluid plane by clipping the body's polygonal vertices against the fluid line and calculating the area of the resulting submerged polygon using the Shoelace formula.

The Challenge with Matter.js

Matter.js provides collision detection using the Separating Axis Theorem (SAT), allowing you to identify when a body overlaps with a fluid sensor body. However, the engine only returns collision penetration vectors and contact points, not the intersecting polygon or its area. To determine the submerged area, you must compute the intersection geometry directly.

Step 1: Extract the Body's Vertices

Every rigid body in Matter.js contains an array of vertices transformed into world space coordinates. You can access these points directly:

const vertices = body.vertices; // Array of { x: number, y: number }

Ensure the vertices form a closed polygon defined in a consistent winding order (Matter.js vertices are ordered clockwise).

Step 2: Clip the Polygon Against the Fluid Line

Assuming a horizontal fluid plane defined by a specific vertical coordinate (fluidLevelY), the submerged region corresponds to the half-plane where y >= fluidLevelY (in Matter.js, the positive Y-axis points downward).

You can use a simplified version of the Sutherland-Hodgman polygon clipping algorithm to clip the body's vertices against this single line:

function getSubmergedVertices(vertices, fluidLevelY) {
    const submerged = [];
    const count = vertices.length;

    for (let i = 0; i < count; i++) {
        const current = vertices[i];
        const next = vertices[(i + 1) % count];

        const currentInside = current.y >= fluidLevelY;
        const nextInside = next.y >= fluidLevelY;

        if (currentInside) {
            submerged.push(current);
        }

        // If edge crosses the fluid line, calculate the intersection point
        if (currentInside !== nextInside) {
            const dy = next.y - current.y;
            const t = (fluidLevelY - current.y) / dy;
            const intersectionX = current.x + t * (next.x - current.x);

            submerged.push({ x: intersectionX, y: fluidLevelY });
        }
    }

    return submerged;
}

Step 3: Compute the Submerged Area

Once you obtain the clipped polygon vertices, calculate its area using the Shoelace formula (Gauss's area formula):

function calculatePolygonArea(vertices) {
    const count = vertices.length;
    if (count < 3) return 0;

    let area = 0;
    for (let i = 0; i < count; i++) {
        const current = vertices[i];
        const next = vertices[(i + 1) % count];
        area += (current.x * next.y) - (next.x * current.y);
    }

    return Math.abs(area) * 0.5;
}

Full Integration

Combine these steps inside your engine's beforeUpdate event to dynamically evaluate buoyant forces based on the overlap:

Matter.Events.on(engine, 'beforeUpdate', () => {
    const fluidLevelY = 300;
    
    // 1. Clip polygon below fluid level
    const submergedVertices = getSubmergedVertices(boxBody.vertices, fluidLevelY);
    
    // 2. Compute area of the overlap
    const submergedArea = calculatePolygonArea(submergedVertices);

    // 3. Apply hydrostatic force if submerged
    if (submergedArea > 0) {
        const fluidDensity = 0.001;
        const gravity = engine.gravity.y * engine.gravity.scale;
        const buoyancyForceMagnitude = submergedArea * fluidDensity * gravity;

        // Apply force upward at the body's center of mass or submerged centroid
        Matter.Body.applyForce(boxBody, boxBody.position, {
            x: 0,
            y: -buoyancyForceMagnitude
        });
    }
});

For non-horizontal fluid lines (such as waves or angled planes), substitute the 1D linear interpolation in the clipping function with a standard line-segment intersection test against the fluid boundary.