Reorient Velocity Through Angled Portals in Matter.js
This guide explains how to calculate the relative angle between two
portals and transform a dynamic body's velocity vector when passing
between them in Matter.js. By determining the difference between the
entry and exit portal rotations, you can apply a standard 2D rotation
matrix to the body's current velocity vector and assign the new vector
via Matter.Body.setVelocity, ensuring seamless momentum
transfer across angled surfaces.
1. Understanding the Rotation Mathematics
Portals transfer an object entering the front side of Portal A out of the front side of Portal B. Because the body enters facing into Portal A and must exit facing out of Portal B, a \(180^\circ\) (\(\pi\) radians) inversion is added to the rotation difference:
\[\Delta\theta = \theta_{\text{exit}} - \theta_{\text{entry}} + \pi\]
To rotate any 2D vector \((v_x, v_y)\) by this angle change (\(\Delta\theta\)), use the standard 2D rotation formulas:
\[v_{x,\text{new}} = v_x \cdot \cos(\Delta\theta) - v_y \cdot \sin(\Delta\theta)\] \[v_{y,\text{new}} = v_x \cdot \sin(\Delta\theta) + v_y \cdot \cos(\Delta\theta)\]
2. Implementation in Matter.js
In Matter.js, a body's velocity is stored in
body.velocity. After transforming this vector, update the
physics body using Matter.Body.setVelocity(). To keep
visual orientation natural, the body's physical rotation should also be
adjusted using Matter.Body.setAngle().
function teleportBody(body, entryPortal, exitPortal) {
// 1. Calculate the change in angle (including the 180-degree flip)
const deltaAngle = exitPortal.angle - entryPortal.angle + Math.PI;
// 2. Rotate the body's current velocity vector
const currentVelocity = body.velocity;
const cos = Math.cos(deltaAngle);
const sin = Math.sin(deltaAngle);
const newVelocity = {
x: currentVelocity.x * cos - currentVelocity.y * sin,
y: currentVelocity.x * sin + currentVelocity.y * cos
};
// 3. Compute exit position with a forward offset to avoid re-triggering collision
const exitNormal = {
x: Math.cos(exitPortal.angle),
y: Math.sin(exitPortal.angle)
};
const forwardOffset = 10; // Clearance distance away from exit portal surface
const newPosition = {
x: exitPortal.position.x + exitNormal.x * forwardOffset,
y: exitPortal.position.y + exitNormal.y * forwardOffset
};
// 4. Apply changes to the Matter.js body
Matter.Body.setPosition(body, newPosition);
Matter.Body.setAngle(body, body.angle + deltaAngle);
Matter.Body.setVelocity(body, newVelocity);
}3. Handling Position Clearance and Infinite Loops
When an object exits an angled portal, spawning it directly at the portal's center can cause the exit portal's collision sensor to trigger instantly, creating an infinite teleportation loop.
To prevent this, calculate the exit portal's directional normal
vector based on its angle and displace the body's new position slightly
outward along this normal before setting the velocity. Alternatively,
attach a temporary cooldown flag or timestamp to the body (e.g.,
body.lastTeleport = Date.now()) and ignore teleportation
checks for that body for a brief window (such as 50–100
milliseconds).