Real-Time Momentum Vectors in Matter.js
This guide explains how to build an interactive educational physics sandbox using Matter.js that calculates and renders real-time momentum vector arrows on dynamic bodies. By hooking into the Matter.js render lifecycle, you will learn how to extract velocity and mass, compute the linear momentum vector (\(\vec{p} = m\vec{v}\)), and draw dynamic arrows over the physics canvas to visually demonstrate the laws of motion and momentum conservation.
Core Architecture Overview
Matter.js provides built-in rendering via HTML5 Canvas, but it does not natively draw vector overlays like momentum or force. To display real-time vectors:
- Initialize the Matter.js physics engine and render loop.
- Spawn rigid bodies with defined mass and velocity properties.
- Attach an event listener to the
afterRenderevent of theRendermodule. - Calculate momentum (\(\vec{p} = m \cdot \vec{v}\)) for each target body on every frame.
- Draw scaled vector arrows on top of the rendered simulation using standard 2D canvas drawing methods.
Step 1: Setting Up the Physics Environment
Create the baseline simulation using Matter.Engine,
Matter.Render, Matter.Runner, and
Matter.Bodies.
const { Engine, Render, Runner, Bodies, Composite, Events, Vector } = Matter;
// Create engine and world
const engine = Engine.create();
const world = engine.world;
// Create renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false,
background: '#1e1e24'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);Step 2: Adding Bodies with Variable Masses
To clearly demonstrate how momentum depends on both velocity and mass, create bodies with contrasting properties:
// Boundaries
const ground = Bodies.rectangle(400, 590, 810, 30, { isStatic: true });
const leftWall = Bodies.rectangle(10, 300, 30, 600, { isStatic: true });
const rightWall = Bodies.rectangle(790, 300, 30, 600, { isStatic: true });
// Light, fast body (mass is automatically derived from density and area)
const lightBall = Bodies.circle(200, 300, 20, {
restitution: 0.9,
render: { fillStyle: '#4ea8de' }
});
// Heavy, slow body
const heavyBall = Bodies.circle(600, 300, 40, {
density: 0.005, // Higher density results in higher mass
restitution: 0.9,
render: { fillStyle: '#e63946' }
});
Composite.add(world, [ground, leftWall, rightWall, lightBall, heavyBall]);
// Impart initial velocities toward each other
Matter.Body.setVelocity(lightBall, { x: 8, y: -2 });
Matter.Body.setVelocity(heavyBall, { x: -2, y: 0 });Step 3: Drawing Momentum Vector Arrows
The momentum vector is computed by multiplying the body's scalar mass by its 2D velocity vector:
\[\vec{p}_x = m \cdot v_x\] \[\vec{p}_y = m \cdot v_y\]
Because velocity and mass units can yield large or small pixel representations, a scaling factor is required to keep arrows readable within the canvas boundaries.
Use the afterRender event to draw directly onto the
canvas context:
const VECTOR_SCALE = 0.5; // Adjust based on visual preference
Events.on(render, 'afterRender', () => {
const context = render.context;
const dynamicBodies = Composite.allBodies(world).filter(body => !body.isStatic);
dynamicBodies.forEach(body => {
// Calculate linear momentum
const px = body.mass * body.velocity.x * VECTOR_SCALE;
const py = body.mass * body.velocity.y * VECTOR_SCALE;
const startX = body.position.x;
const startY = body.position.y;
const endX = startX + px;
const endY = startY + py;
// Only draw if the body is moving significantly
const magnitude = Math.hypot(px, py);
if (magnitude > 1) {
drawArrow(context, startX, startY, endX, endY, '#ffd166');
}
});
});Step 4: The Vector Arrow Rendering Function
Implement a reusable Canvas 2D helper function to draw the vector line and the arrowhead:
function drawArrow(ctx, fromX, fromY, toX, toY, color) {
const headLength = 10;
const dx = toX - fromX;
const dy = toY - fromY;
const angle = Math.atan2(dy, dx);
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
// Draw main vector line
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.stroke();
// Draw arrowhead
ctx.beginPath();
ctx.moveTo(toX, toY);
ctx.lineTo(
toX - headLength * Math.cos(angle - Math.PI / 6),
toY - headLength * Math.sin(angle - Math.PI / 6)
);
ctx.lineTo(
toX - headLength * Math.cos(angle + Math.PI / 6),
toY - headLength * Math.sin(angle + Math.PI / 6)
);
ctx.closePath();
ctx.fill();
ctx.restore();
}Educational Enhancements
To make the sandbox more instructional:
- Display Magnitude Values: Use
ctx.fillText()near the tip of each arrow to display the exact momentum value in \(\text{kg}\cdot\text{px/s}\) (Math.round(body.mass * Vector.magnitude(body.velocity))). - Velocity vs. Momentum Comparison: Render two separate arrows per body—one for velocity (\(\vec{v}\)) in one color and one for momentum (\(m\vec{v}\)) in another—to visually demonstrate the impact of mass on momentum transfer during collisions.