Simulate Arterial Pulse Waves in Matter.js

This article explains how to model arterial pulse wave propagation through elastic vessel walls using the Matter.js 2D physics engine. By representing vessel walls as connected chains of rigid bodies bound by elastic constraints and driving fluid pressure using dynamic particle clusters or localized force vectors, developers can simulate biomechanical wave mechanics, wall distension, and wave velocity in a real-time web environment.


Understanding the Mechanics in a Rigid-Body Engine

Arterial pulse wave propagation is a fluid-structure interaction (FSI) problem governed by the Moens-Korteweg equation, where pulse wave velocity (PWV) depends on vessel radius, wall thickness, blood density, and the elastic modulus of the arterial wall. Because Matter.js is a discrete 2D rigid-body engine rather than a continuous Navier-Stokes solver, the simulation relies on a lumped-parameter approach:

  1. The Elastic Vessel Wall: Modeled as a series of small, interconnected rectangular or circular rigid bodies linked by stiff spring constraints with damping.
  2. The Pulse/Fluid Medium: Modeled either as an ensemble of self-repelling circular rigid bodies (discrete fluid particles) or as direct radial force impulses applied progressively along the interior wall nodes.

Step 1: Constructing the Elastic Vessel Walls

To create the upper and lower arterial boundaries, construct two parallel chains of bodies connected by distance constraints.

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

const engine = Engine.create({ gravity: { x: 0, y: 0 } });
const world = engine.world;

const segments = 40;
const segmentWidth = 20;
const segmentHeight = 8;
const vesselRadius = 60;
const startX = 100;
const centerY = 300;

const upperWall = [];
const lowerWall = [];

// Generate wall segments
for (let i = 0; i < segments; i++) {
  const x = startX + i * segmentWidth;

  const topBody = Bodies.rectangle(x, centerY - vesselRadius, segmentWidth, segmentHeight, {
    collisionFilter: { group: -1 },
    frictionAir: 0.05
  });

  const bottomBody = Bodies.rectangle(x, centerY + vesselRadius, segmentWidth, segmentHeight, {
    collisionFilter: { group: -1 },
    frictionAir: 0.05
  });

  upperWall.push(topBody);
  lowerWall.push(bottomBody);
  Composite.add(world, [topBody, bottomBody]);
}

Step 2: Linking Segments with Elastic Constraints

Elasticity is defined by the stiffness and damping of the constraints joining adjacent segments, as well as structural anchoring constraints that simulate surrounding tissue tethering.

// Link adjacent horizontal segments
for (let i = 0; i < segments - 1; i++) {
  const topLink = Constraint.create({
    bodyA: upperWall[i],
    bodyB: upperWall[i + 1],
    stiffness: 0.8,
    damping: 0.1
  });

  const bottomLink = Constraint.create({
    bodyA: lowerWall[i],
    bodyB: lowerWall[i + 1],
    stiffness: 0.8,
    damping: 0.1
  });

  Composite.add(world, [topLink, bottomLink]);
}

// Tether walls to their resting position to simulate tissue elasticity
for (let i = 0; i < segments; i++) {
  const topAnchor = Constraint.create({
    bodyA: upperWall[i],
    pointB: { x: upperWall[i].position.x, y: centerY - vesselRadius },
    stiffness: 0.15,
    damping: 0.05
  });

  const bottomAnchor = Constraint.create({
    bodyA: lowerWall[i],
    pointB: { x: lowerWall[i].position.x, y: centerY + vesselRadius },
    stiffness: 0.15,
    damping: 0.05
  });

  Composite.add(world, [topAnchor, bottomAnchor]);
}

// Pin the proximal and distal ends
upperWall[0].isStatic = true;
lowerWall[0].isStatic = true;
upperWall[segments - 1].isStatic = true;
lowerWall[segments - 1].isStatic = true;

Step 3: Injecting the Pulse Wave

The cardiac ejection cycle generates a transient high-pressure wave. You can simulate this through dynamic pressure application at the proximal inlet:

Apply a localized, transient radial force to the first few wall segments, then allow the constraint network to naturally propagate the kinetic energy downstream.

function triggerSystolicPulse() {
  const pulseMagnitude = 0.05;
  
  // Apply outward vertical force to proximal segments
  for (let i = 1; i <= 3; i++) {
    Matter.Body.applyForce(upperWall[i], upperWall[i].position, { x: 0.01, y: -pulseMagnitude });
    Matter.Body.applyForce(lowerWall[i], lowerWall[i].position, { x: 0.01, y: pulseMagnitude });
  }
}

// Trigger pulse periodically (e.g., 60 BPM)
setInterval(triggerSystolicPulse, 1000);

Method B: Particle-Based Hydraulic Fluid

Fill the lumen with high-density, low-friction circular bodies. A kinematic piston body at the inlet compresses the fluid particles forward, creating localized hydrostatic pressure that distends the adjacent walls. The wave propagates forward as particles collide and transfer momentum through the flexible tube.


Step 4: Calibrating Wave Speed and Damping

To achieve realistic wave propagation without excessive instability: