Simulating Electrostatic Repulsion in Matter.js
This article explains how to simulate electrostatic repulsion between identically charged bodies using Matter.js. While Matter.js is a rigid-body 2D physics engine that lacks built-in electromagnetic mechanics, you can model electrostatic behavior by calculating repulsive forces via Coulomb's Law and applying them continuously to bodies before each physics engine update.
The Underlying Physics: Coulomb's Law
Electrostatic repulsion between two bodies carrying identical charges is governed by Coulomb's Law:
\[F = k \cdot \frac{q_1 \cdot q_2}{r^2}\]
- \(F\): Repulsive force magnitude.
- \(k\): Coulomb's constant (scaled for your simulation).
- \(q_1, q_2\): Charge values of the two interacting bodies.
- \(r\): Euclidean distance between the centers of the two bodies.
Because identical charges repel, the resulting force vector points directly away from the other body along the line connecting their centers.
Implementation Steps in Matter.js
To implement this custom force:
- Assign a Charge Property: Add a custom
chargeproperty to each body during creation. - Listen to the
beforeUpdateEvent: UseMatter.Events.on(engine, 'beforeUpdate', callback)to compute and apply forces on every frame prior to collision and position resolution. - Iterate Through Body Pairs: Loop through unique pairs of charged bodies to avoid duplicate calculations.
- Calculate and Apply Force: Compute the distance,
direction, and magnitude, then use
Matter.Body.applyForce()to push the bodies apart.
Code Example
Below is a complete implementation using Matter.js:
const { Engine, Render, Runner, Bodies, Composite, Events, Vector, Body } = Matter;
// 1. Initialize engine and world
const engine = Engine.create({ gravity: { x: 0, y: 0 } }); // Zero gravity for clear observation
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: { width: 800, height: 600, wireframes: false }
});
Render.run(render);
Runner.run(Runner.create(), engine);
// 2. Create charged bodies
const chargedBodies = [];
for (let i = 0; i < 15; i++) {
const body = Bodies.circle(
200 + Math.random() * 400,
150 + Math.random() * 300,
15,
{
restitution: 0.8,
frictionAir: 0.05,
render: { fillStyle: '#ff4757' }
}
);
// Custom charge property (identical positive charge)
body.charge = 1.0;
chargedBodies.push(body);
}
Composite.add(world, chargedBodies);
// 3. Apply electrostatic repulsion before each update
const COULOMB_CONSTANT = 50; // Adjust to scale force strength
const MIN_DISTANCE = 30; // Prevent infinite force at close proximity
Events.on(engine, 'beforeUpdate', () => {
for (let i = 0; i < chargedBodies.length; i++) {
for (let j = i + 1; j < chargedBodies.length; j++) {
const bodyA = chargedBodies[i];
const bodyB = chargedBodies[j];
// Vector from bodyA to bodyB
const delta = Vector.sub(bodyB.position, bodyA.position);
const distance = Vector.magnitude(delta);
// Clamp distance to avoid division by zero or explosive forces
const effectiveDistance = Math.max(distance, MIN_DISTANCE);
// Calculate force magnitude: F = k * (q1 * q2) / r^2
const forceMagnitude = (COULOMB_CONSTANT * bodyA.charge * bodyB.charge) / (effectiveDistance * effectiveDistance);
// Normalized direction vector
const normal = Vector.normalise(delta);
// Force applied to bodyB (repels away from bodyA)
const forceOnB = Vector.mult(normal, forceMagnitude);
// Force applied to bodyA (Newton's third law: equal and opposite)
const forceOnA = Vector.neg(forceOnB);
Body.applyForce(bodyA, bodyA.position, forceOnA);
Body.applyForce(bodyB, bodyB.position, forceOnB);
}
}
});Critical Considerations
- Distance Clamping: As two bodies approach identical
positions, distance approaches zero, which creates infinitely large
forces and causes bodies to fly off-screen. Always clamp the minimum
distance using
Math.max(distance, minDistance). - Air Friction (Damping): Without adequate
frictionAiror velocity damping, charged bodies will continually oscillate and accelerate out of control as energy is introduced. - Performance Complexity: Checking every pair of bodies has an \(O(n^2)\) time complexity. For simulations with hundreds of particles, integrate a spatial partitioning structure such as a Quadtree or a Barnes-Hut algorithm to approximate far-field electrostatic interactions.