Calculate Laser Reflection Angles in Matter.js

Simulating optical laser reflections off angled polygons in Matter.js requires identifying the exact edge of collision, extracting its surface normal vector, and applying the optical law of reflection. By combining Matter.js collision queries or custom segment intersection with vector mathematics, you can compute the reflected trajectory vector and its corresponding angle to recursively bounce laser rays off any rigid body.

1. Detect the Ray-Polygon Intersection

Matter.js provides Matter.Query.ray() to detect collisions between a directed segment and a set of bodies. The function takes a collection of bodies, a start point, and an end point:

const collisions = Matter.Query.ray(bodies, startPoint, endPoint, rayWidth);

While Matter.Query.ray detects whether a body intersects the ray path, calculating the exact hit point and the specific edge struck usually requires testing the ray against each edge of the polygon using 2D line segment intersection formulas.

Iterate through the vertices of the collided body (body.vertices) in consecutive pairs \((V_i, V_{i+1})\) to find the intersecting segment.

2. Determine the Surface Normal Vector

Once you identify the edge that the ray hit, find the outward-pointing unit normal vector of that edge.

Given an edge starting at vertex \(A = (x_1, y_1)\) and ending at vertex \(B = (x_2, y_2)\):

  1. Compute the edge vector: \[\vec{E} = (x_2 - x_1, y_2 - y_1)\]

  2. Obtain a perpendicular vector (normal): \[\vec{N}_{\text{raw}} = (-(y_2 - y_1), x_2 - x_1)\]

  3. Normalize the vector to a unit length: \[\text{length} = \sqrt{N_x^2 + N_y^2}\] \[\vec{n} = \left(\frac{N_x}{\text{length}}, \frac{N_y}{\text{length}}\right)\]

Because vertices in Matter.js are arranged in clockwise order, rotating the edge vector 90 degrees counter-clockwise or clockwise will yield the correct outward normal depending on your coordinate system. Ensure \(\vec{n} \cdot \vec{d} < 0\), where \(\vec{d}\) is the incident laser direction; if the dot product is positive, negate the normal so it opposes the incoming ray.

3. Apply the Reflection Formula

The law of reflection states that the angle of incidence equals the angle of reflection. Using vector notation, the reflected direction vector \(\vec{r}\) is calculated from the normalized incident vector \(\vec{d}\) and the unit normal \(\vec{n}\):

\[\vec{r} = \vec{d} - 2(\vec{d} \cdot \vec{n})\vec{n}\]

In JavaScript:

function calculateReflection(incidentDir, normal) {
  // Dot product of incident direction and surface normal
  const dot = incidentDir.x * normal.x + incidentDir.y * normal.y;

  // Reflection vector: R = D - 2 * (D . N) * N
  return {
    x: incidentDir.x - 2 * dot * normal.x,
    y: incidentDir.y - 2 * dot * normal.y
  };
}

4. Convert the Reflected Vector to an Angle

To obtain the reflection angle in radians for rendering or projecting the next laser segment:

const reflectionAngle = Math.atan2(reflectedVector.y, reflectedVector.x);

5. Cast Subsequent Reflections

To propagate the laser after the initial hit:

  1. Set the intersection point as the new origin.
  2. Advance the new origin slightly along the reflected vector \(\vec{r}\) (e.g., by 0.001 units) to prevent the ray from immediately colliding with the same edge due to floating-point imprecision.
  3. Cast a new ray in the direction of \(\vec{r}\) up to your maximum distance or bounce limit.