Soft Body Balloon Pressure in Matter.js

Simulating a realistic, pressurized soft body balloon in Matter.js requires augmenting standard distance constraints with an active volume preservation mechanism. By default, soft bodies created with perimeter springs collapse under external forces because the physics engine does not natively calculate enclosed volume or internal fluid dynamics. To maintain internal pressure and prevent collapse, you must either dynamically apply outward normal forces based on the balloon's changing area or reinforce the structure with internal radial constraints.

Why Default Soft Bodies Collapse

Matter.js provides Composites.softBody, which generates a grid or ring of rigid bodies linked by Constraint instances. While perimeter constraints maintain the distance between adjacent vertices, they have zero resistance to shear forces and buckling. When external forces compress the balloon, the perimeter bends inward without resistance because the engine does not treat the enclosed space as an airtight, gas-filled chamber.

Solution 1: Dynamic Outward Normal Forces (Ideal Gas Simulation)

The most physically accurate way to maintain internal pressure is to compute the balloon's 2D area at each physics step and apply an outward force to every perimeter vertex relative to the volume displacement.

1. Calculate the Enclosed Area

Use the Shoelace formula (Gauss's area formula) on the ordered array of perimeter vertices to find the current area \(A\) during the beforeUpdate engine event:

function getPolygonArea(vertices) {
    let area = 0;
    const n = vertices.length;
    for (let i = 0; i < n; i++) {
        const j = (i + 1) % n;
        area += vertices[i].position.x * vertices[j].position.y;
        area -= vertices[j].position.x * vertices[i].position.y;
    }
    return Math.abs(area) / 2;
}

2. Compute Pressure Magnitude

Define a target rest area (\(A_0\)) representing the uncompressed volume of the balloon, along with an internal stiffness constant (\(k\)):

\[\text{Pressure} = k \times \max(0, A_0 - A)\]

If the balloon compresses (\(A < A_0\)), the pressure increases, simulating Boyle's Law.

3. Apply Outward Normal Forces

For each vertex along the perimeter, determine the normal vector pointing outward from the interior. Apply the pressure force along this normal vector directly to each vertex body using Matter.Body.applyForce:

Matter.Events.on(engine, 'beforeUpdate', () => {
    const currentArea = getPolygonArea(balloonVertices);
    const pressure = Math.max(0, (restArea - currentArea) * pressureStiffness);

    for (let i = 0; i < balloonVertices.length; i++) {
        const prev = balloonVertices[(i - 1 + balloonVertices.length) % balloonVertices.length].position;
        const next = balloonVertices[(i + 1) % balloonVertices.length].position;

        // Tangent vector between neighbors
        const tx = next.x - prev.x;
        const ty = next.y - prev.y;

        // Perpendicular normal vector (outward facing)
        let nx = -ty;
        let ny = tx;
        const length = Math.hypot(nx, ny);

        if (length > 0) {
            nx /= length;
            ny /= length;

            Matter.Body.applyForce(balloonVertices[i], balloonVertices[i].position, {
                x: nx * pressure,
                y: ny * pressure
            });
        }
    }
});

Solution 2: Radial Structural Constraints

If dynamic force calculation creates instability or performance overhead, you can maintain shape using structural distance constraints.

  1. Central Anchor Node: Place a lightweight central body inside the balloon and link every perimeter vertex to this center using Matter.Constraint.create with a stiffness between 0.1 and 0.5.
  2. Cross-Diameter Struts: Connect opposing vertices across the balloon directly to each other using spring constraints.

While radial constraints do not behave like a continuous fluid or gas, they provide an efficient geometric approximation that resists crushing and restores the balloon to its circular shape when compressed.