Simulate Soil Compaction in Matter.js

Simulating soil compaction in Matter.js involves modeling soil as a collection of discrete granular particles and using physical forces, polydispersity, and vibration to drive them into a higher packing density. This article covers how to configure particle attributes such as friction and restitution, prevent unnatural crystalline lattice formation, and apply mechanical compression and vibration to achieve realistic compaction dynamics in a 2D physics environment.

1. Representing Soil as Granular Particles

Soil is best represented in a 2D rigid-body engine like Matter.js as an ensemble of circular bodies. To achieve realistic soil behavior, particles must have varied sizes (polydispersity). Identical circles naturally align into a hexagonal crystal lattice, which produces unrealistic shear planes and artificially locks packing density.

const { Engine, Render, Runner, Bodies, Composite, Body } = Matter;

const engine = Engine.create();
const world = engine.world;

const particles = [];
const particleCount = 300;

for (let i = 0; i < particleCount; i++) {
    // Vary radii between 4px and 8px to disrupt lattice formation
    const radius = 4 + Math.random() * 4;
    const x = 200 + (Math.random() * 200 - 100);
    const y = 100 + (Math.random() * 200 - 100);

    const particle = Bodies.circle(x, y, radius, {
        restitution: 0.05,      // Low bounciness absorbs kinetic energy
        friction: 0.4,          // Surface friction between grains
        frictionStatic: 0.7,    // Resistance to initial sliding
        density: 0.002          // Standard mass density
    });

    particles.push(particle);
}

Composite.add(world, particles);

2. Tuning Particle Physics for Compaction

Compaction requires particles to overcome friction and rearrange into interstitial voids without bouncing excessively:

3. Applying External Compactive Effort

Real-world soil compaction relies on mechanical energy via static weight, kneading, or impact. In Matter.js, this is achieved by introducing a compressive plate (piston) directly above the granular aggregate.

// Create a heavy tamper/plate
const plate = Bodies.rectangle(200, 50, 220, 20, {
    density: 0.05, // Significantly heavier than individual grains
    friction: 0.5
});

Composite.add(world, plate);

// Apply continuous downward force during compaction phases
Matter.Events.on(engine, 'beforeUpdate', () => {
    Body.applyForce(plate, plate.position, { x: 0, y: 0.05 });
});

4. Overcoming Jamming with Dynamic Vibration

Under pure vertical loading, granular assemblies often experience "shear jamming," where force chains support the load and prevent further densification. To simulate dynamic vibratory compaction (like a vibratory roller or plate compactor), apply periodic horizontal or vertical impulses to the soil bed:

let frame = 0;

Matter.Events.on(engine, 'beforeUpdate', () => {
    frame++;
    // Apply a micro-oscillation to simulate vibratory shaking
    if (frame % 4 === 0) {
        particles.forEach(p => {
            const jitter = (Math.random() - 0.5) * 0.0005;
            Body.applyForce(p, p.position, { x: jitter, y: 0 });
        });
    }
});

The small oscillating disturbances break temporary force chains, enabling the heavier static load to push particles into smaller voids and substantially increasing the final packing fraction.

5. Calculating the Packing Density

To quantify the degree of compaction, measure the packing density (volume fraction) over time. This is the ratio of total particle area to the area of the bounding polygon containing the soil.

function calculatePackingDensity(particles, containerWidth, bottomY) {
    const totalParticleArea = particles.reduce((sum, p) => {
        return sum + Math.PI * Math.pow(p.circleRadius, 2);
    }, 0);

    // Determine the current height of the aggregate
    const topY = Math.min(...particles.map(p => p.position.y));
    const currentHeight = bottomY - topY;
    const totalBoundingArea = containerWidth * currentHeight;

    return totalParticleArea / totalBoundingArea;
}

As the vibratory load drives the top surface downward, the computed packing density will scale from a loose random packing configuration (around 0.75–0.80 in 2D) toward the theoretical limit of dense random packing (roughly 0.84–0.88 for polydisperse disks).