Symplectic Euler Integration in Matter.js
This article explores how the popular 2D physics engine Matter.js employs the Symplectic Euler integration scheme—also known as semi-implicit Euler—within its core update cycle. It outlines the step-by-step mathematical transition from forces to motion, details why this specific integrator is chosen over standard explicit Euler, and demonstrates where and how integration occurs alongside collision detection and constraint resolution during an engine update tick.
The Mathematics of Symplectic Euler
Standard Explicit Euler calculates a body's new position using its current velocity, and then updates the velocity using current acceleration:
- \(x_{t+1} = x_t + v_t \Delta t\)
- \(v_{t+1} = v_t + a_t \Delta t\)
While computationally cheap, Explicit Euler artificially injects energy into the system over time, leading to instability, exploding constraints, and orbital drift.
Symplectic Euler solves this instability by reversing the order of updates. It computes the new velocity first, and then uses that updated velocity to determine the new position:
- \(v_{t+1} = v_t + a_t \Delta t\)
- \(x_{t+1} = x_t + v_{t+1} \Delta t\)
By pairing the updated velocity with the next position, Symplectic Euler acts as a symplectic integrator, preserving phase space volume. This produces pseudo-energy-conserving behavior, providing long-term physical stability for springs, pendulums, and resting contacts with negligible computational overhead.
The Matter.js Engine Update Cycle
The core loop in Matter.js is driven by
Engine.update(engine, delta). During this call, the engine
processes world dynamics in distinct phases:
- Collision Detection: The broadphase (using bounding volume hierarchies) and narrowphase identify intersecting shapes and construct collision pairs with contact manifolds.
- Velocity and Constraint Solving: The constraint solver iteratively resolves penetrations, friction, and joint limits through impulse application, modifying rigid body velocities directly.
- Integration (
Body.update): The engine iterates through all active bodies and applies numerical integration to translate accumulated forces and velocities into new world transformations.
Symplectic Integration
Inside Body.update
Matter.js abstracts integration inside the Body.update
function. The process applies equally to linear motion and angular
rotation:
- Accumulating Forces: Any external forces (such as
world gravity or user-applied pushes via
Body.applyForce) are divided by the body's mass to calculate acceleration (\(a = \frac{F}{m}\)). - Updating Velocities: The linear velocity is
advanced using this acceleration, scaled by the time step and any
damping factors such as air friction (
body.frictionAir): \[\vec{v}_{t+1} = (\vec{v}_t + \vec{a} \Delta t) \times (1 - \text{frictionAir})\] The angular velocity (\(\omega\)) is similarly updated using torque and rotational inertia. - Updating Positions: With \(\vec{v}_{t+1}\) established, the position vector \(\vec{x}\) is incremented: \[\vec{x}_{t+1} = \vec{x}_t + \vec{v}_{t+1} \Delta t\] The body's angle is updated in the same manner using the newly computed angular velocity: \[\theta_{t+1} = \theta_t + \omega_{t+1} \Delta t\]
- Synchronizing Bounds and Vertices: Once the new origin and rotation angle are set, Matter.js transforms the body's local vertices into world coordinates and refreshes its Axis-Aligned Bounding Box (AABB), preparing the body for the next cycle's collision detection.
By executing the velocity modification before translating vertex coordinates, Matter.js ensures that constraints solved in the current frame directly inform the position updates of the same frame, minimizing lag between solver impulses and physical displacement.