Simulating Kelp Wave Attenuation in Matter.js
This article explains how to model kelp forest hydrodynamic wave attenuation in the browser using Matter.js. By constructing flexible, anchored multi-body chains to represent kelp stipes and applying custom hydrodynamic drag forces derived from orbital wave kinematics, you can approximate the dissipation of wave energy across a submerged canopy within a standard 2D rigid-body physics engine.
1. Creating Anchored Kelp Stipes
Matter.js models flexible vegetation by linking a series of rigid bodies with distance constraints. Each kelp stipe consists of several slender rectangular bodies connected end-to-end, with the base segment pinned to a static body representing the seafloor.
const { Engine, World, Bodies, Body, Constraint, Composite } = Matter;
function createKelpStipe(x, baseY, segments, segmentHeight, segmentWidth) {
const stipe = Composite.create();
let previousBody = null;
for (let i = 0; i < segments; i++) {
const y = baseY - (i * segmentHeight) - (segmentHeight / 2);
const segment = Bodies.rectangle(x, y, segmentWidth, segmentHeight, {
collisionFilter: { group: -1 }, // Prevent self-collision within the forest
frictionAir: 0.05,
density: 0.001 // Near-neutral buoyancy relative to water
});
Composite.add(stipe, segment);
if (previousBody === null) {
// Anchor base to the seabed
const anchor = Constraint.create({
pointA: { x: x, y: baseY },
bodyB: segment,
pointB: { x: 0, y: segmentHeight / 2 },
stiffness: 0.9,
damping: 0.1
});
Composite.add(stipe, anchor);
} else {
// Connect to the segment below
const joint = Constraint.create({
bodyA: previousBody,
pointA: { x: 0, y: -segmentHeight / 2 },
bodyB: segment,
pointB: { x: 0, y: segmentHeight / 2 },
stiffness: 0.8,
damping: 0.05
});
Composite.add(stipe, joint);
}
previousBody = segment;
}
return stipe;
}2. Generating Fluid Kinematics
Standard coastal waves follow linear wave theory (Airy wave theory), generating elliptical or circular velocity fields for water particles below the surface. Define the horizontal (\(u\)) and vertical (\(w\)) fluid velocities as a function of position \((x, y)\) and time (\(t\)):
function getWaveVelocity(x, y, t, wave) {
const k = (2 * Math.PI) / wave.length;
const omega = (2 * Math.PI) / wave.period;
const phase = k * x - omega * t;
// Decay with depth below water surface (y = 0 at surface, increasing downward)
const depthDecay = Math.exp(-k * Math.max(0, y));
const u = wave.amplitude * omega * Math.cos(phase) * depthDecay;
const w = wave.amplitude * omega * Math.sin(phase) * depthDecay;
return { x: u, y: w };
}3. Applying Hydrodynamic Drag Forces
Because Matter.js does not calculate native fluid-structure interaction, hydrodynamic forces must be applied manually on each physics step using Morison's equation. The total force consists of quadratic drag based on the relative velocity between the water and the segment, plus an upward buoyancy force.
const CD = 1.2; // Drag coefficient
const RHO = 1.0; // Fluid density
Matter.Events.on(engine, 'beforeUpdate', (event) => {
const t = engine.timing.timestamp / 1000;
kelpForestSegments.forEach((segment) => {
const waveVel = getWaveVelocity(segment.position.x, segment.position.y, t, waveParams);
// Relative velocity: fluid velocity minus body velocity
const relVelX = waveVel.x - segment.velocity.x;
const relVelY = waveVel.y - segment.velocity.y;
const speed = Math.hypot(relVelX, relVelY);
// Quadratic drag: F = 0.5 * rho * Cd * Area * |v_rel| * v_rel
const area = segment.bounds.max.x - segment.bounds.min.x; // Projected width
const dragMagnitude = 0.5 * RHO * CD * area * speed;
const force = {
x: dragMagnitude * relVelX * 0.001,
y: (dragMagnitude * relVelY * 0.001) - 0.0005 // Constant slight upward buoyancy
};
Body.applyForce(segment, segment.position, force);
});
});4. Modeling Wave Attenuation Across the Forest
In a complete coupled simulation, fluid forces on the kelp must produce an equal and opposite reaction on the wave field. To simulate wave attenuation without a full computational fluid dynamics solver, damp the wave amplitude as it travels across the x-axis through the forest zone.
Calculate Work Done by Kelp: For every time step, track the energy dissipated by the drag forces across all stipes: \[\Delta E = \sum F_{\text{drag}} \cdot (v_{\text{fluid}} - v_{\text{body}}) \Delta t\]
Decay Spatial Amplitude: Reduce the local wave amplitude \(A(x)\) across the forest width using an exponential decay factor matching the integrated drag: \[A(x) = A_0 \exp(-k_{\text{decay}} \cdot (x - x_{\text{start}}))\] where \(k_{\text{decay}}\) is scaled by the density of stipes and the average drag force observed in the simulation.
Feed Attenuated Field to Downstream Kelp: Pass the spatially attenuated amplitude into
getWaveVelocity(). Kelp stipes placed further downwave automatically experience diminished fluid velocities, reflecting the protective barrier effect of the seaward stipes.