Patch Matter.js Engine Solver Using Custom Plugins
This article provides a practical guide on how to patch internal
engine solver steps in Matter.js using its built-in plugin architecture.
Matter.js delegates collision resolution and constraint solving to
internal sub-modules during each engine update tick. By leveraging the
Matter.Plugin API, you can intercept, extend, or replace
core solver routines—such as position and velocity resolution
passes—without modifying the upstream library source code.
Understanding the Matter.js Plugin Mechanism
Matter.js includes a modular plugin interface defined under
Matter.Plugin. Plugins define an install
function that executes when the plugin is registered with the engine
using Matter.use() or Matter.Plugin.use().
The install function receives the root
Matter object, granting direct mutable access to internal
namespaces, including Matter.Resolver,
Matter.Engine, and Matter.Constraint. This
access allows you to intercept internal physics solver functions safely
before instances of an engine start updating.
Identifying Key Solver Methods
During each call to Engine.update, collision impulses
and separations are resolved by the Resolver module. The
primary functions responsible for the solver passes are:
Matter.Resolver.preSolvePosition: Prepares pairs for positional correction.Matter.Resolver.solvePosition: Iteratively pushes overlapping bodies apart to correct penetration errors.Matter.Resolver.postSolvePosition: Updates body positions according to corrected coordinate offsets.Matter.Resolver.preSolveVelocity: Prepares initial contact velocities and friction parameters.Matter.Resolver.solveVelocity: Calculates and applies dynamic impulse adjustments to handle bouncing, friction, and kinetic transfer.
Modifying solvePosition affects stability and
anti-tunneling behavior, while modifying solveVelocity
alters restitution, friction, and impulse propagation.
Creating and Installing the Custom Plugin
To patch one of these internal passes, define a plugin object with
name, version, and install
properties. Inside install, cache the reference to the
original method, implement your custom logic, and reassign the solver
function on the target module.
// Define the custom solver plugin
const CustomSolverPlugin = {
name: 'matter-custom-solver',
version: '1.0.0',
install: function(base) {
// 1. Cache the original solver methods
const originalSolvePosition = base.Resolver.solvePosition;
const originalSolveVelocity = base.Resolver.solveVelocity;
// 2. Patch the position solver step
base.Resolver.solvePosition = function(pairs) {
// Execute pre-solver logic or custom positional constraints
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i];
if (pair.bodyA.isCustomSolverIgnored || pair.bodyB.isCustomSolverIgnored) {
continue;
}
}
// Execute original algorithm or supply a custom impulse formula
originalSolvePosition(pairs);
// Execute post-solver hooks
};
// 3. Patch the velocity solver step
base.Resolver.solveVelocity = function(pairs) {
// Custom velocity resolution, e.g., custom damping or directional stiffness
originalSolveVelocity(pairs);
};
}
};
// Register the plugin globally with Matter.js
Matter.Plugin.register(CustomSolverPlugin);
// Apply the plugin to the active Matter namespace
Matter.use(CustomSolverPlugin);Hooking into Engine Lifecycle Events
If you need your solver patch to execute custom passes outside the
default Resolver execution loop without replacing the
mathematical core entirely, hook into internal engine events within your
plugin's install method:
install: function(base) {
// Intercept the update loop right before constraints are resolved
base.Events.on(base.Engine, 'beforeUpdate', function(event) {
const engine = event.source;
// Perform custom constraint relaxation or solver pre-passes here
});
// Intercept right after collisions are computed but before solving
base.Events.on(base.Engine, 'collisionActive', function(event) {
const pairs = event.pairs;
// Apply custom bias adjustments to the contact pairs
});
}Best Practices for Solver Patching
- Retain Signatures: Always match the argument lists
of the native functions.
Resolver.solvePositionandResolver.solveVelocityboth expect an array of active collisionpairs. - Handle Performance: Solver steps run multiple times
per frame (governed by
engine.positionIterationsandengine.velocityIterations). Avoid allocations, object creation, and garbage-collection triggers inside patched loops. - Preserve Idempotency: Ensure the
installmethod does not re-wrap functions multiple times ifMatter.use()is called concurrently across different application modules. Check for an internal flag before reassigning functions.