Laser Tripwires with Raycasting in Matter.js

This article explains how to implement laser tripwires in Matter.js by utilizing its built-in raycasting query methods. You will learn how to set up the ray coordinates, evaluate line-of-sight intersections against dynamic bodies using Matter.Query.ray, trigger events when the beam is broken, and visually render the laser beam on an HTML5 canvas.

Understanding Raycasting in Matter.js

Matter.js provides a built-in module called Matter.Query for spatial queries. The Query.ray function tests a directed line segment against a collection of physics bodies and returns any intersections.

The syntax for Query.ray is:

Matter.Query.ray(bodies, startPoint, endPoint, [rayWidth])

The method returns an array of collision objects containing the intersected bodies and collision details.

Setting Up the Tripwire Logic

To detect when an entity trips the laser, perform the raycast inside the engine's update loop using the beforeUpdate or afterUpdate event. This ensures the query checks the current positions of all active bodies on every frame.

const { Engine, Render, Runner, Bodies, Composite, Query, Events } = Matter;

// Initialize engine and world
const engine = Engine.create();
const world = engine.world;

// Create obstacles and moving bodies
const player = Bodies.rectangle(100, 200, 40, 40, { label: 'Player' });
const wall = Bodies.rectangle(400, 300, 800, 50, { isStatic: true });
Composite.add(world, [player, wall]);

// Define laser tripwire points
const laserStart = { x: 50, y: 200 };
const laserEnd = { x: 500, y: 200 };
const laserWidth = 2;

// Check for collisions every physics frame
Events.on(engine, 'afterUpdate', () => {
    // Get all bodies currently in the world
    const bodies = Composite.allBodies(world);

    // Perform the raycast
    const collisions = Query.ray(bodies, laserStart, laserEnd, laserWidth);

    // Filter out static walls or the emitter body if necessary
    const validHits = collisions.filter(collision => collision.body.label === 'Player');

    if (validHits.length > 0) {
        onTripwireTriggered(validHits);
    }
});

function onTripwireTriggered(hits) {
    console.log('Tripwire triggered by:', hits.map(h => h.body.label));
    // Trigger alarms, open doors, or penalize the player here
}

Rendering the Laser Beam

While Query.ray handles the physics logic, it does not draw anything to the screen. To display the laser, draw directly onto the rendering canvas using the Canvas 2D API.

Hook into the afterRender event of the Matter.Render instance:

const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Events.on(render, 'afterRender', () => {
    const context = render.context;
    
    // Check if the laser is currently obstructed
    const bodies = Composite.allBodies(world);
    const collisions = Query.ray(bodies, laserStart, laserEnd, laserWidth);
    const isTripped = collisions.some(c => c.body.label === 'Player');

    context.beginPath();
    context.moveTo(laserStart.x, laserStart.y);
    context.lineTo(laserEnd.x, laserEnd.y);
    
    // Visual feedback: red when clear, yellow when tripped
    context.strokeStyle = isTripped ? 'rgba(255, 255, 0, 0.9)' : 'rgba(255, 0, 0, 0.7)';
    context.lineWidth = laserWidth;
    context.shadowColor = isTripped ? 'yellow' : 'red';
    context.shadowBlur = 10;
    
    context.stroke();
    
    // Reset shadow properties for other elements
    context.shadowBlur = 0;
});

Dynamic Beam Truncation

In realistic tripwire scenarios, a laser beam should stop at the surface of the first obstacle it hits rather than passing through it.

To achieve this:

  1. Iterate through the array returned by Query.ray.
  2. Determine which intersecting body is closest to laserStart.
  3. Read the collision contact point or project the distance to truncate the laserEnd rendering coordinate to that hit point.