Skip to content

Triggers and Collision Events

A rigidbody can act as a trigger (Sensor) to detect objects entering and leaving a region, and can also listen for collision contact events. Both are implemented through callbacks on the Rigidbody.

Note

isSensor and enableEvents must be set before the component's start() (i.e. after adding the component and before the object is added to the scene).

Trigger (Sensor)

A trigger produces no physical collision response; it only detects other objects entering/leaving, and is commonly used for region detection (entering a zone, picking up items, etc.):

ts
import { Object3D, Vector3 } from '@orillusion/core';
import { Rigidbody, BodyType, CollisionShapeUtil } from '@orillusion/physics-rapier';

const sensor = obj.addComponent(Rigidbody);
sensor.bodyType = BodyType.Static;
sensor.shape = CollisionShapeUtil.createBoxShape(obj, new Vector3(2, 2, 2));
sensor.isSensor = true;       // Set as a trigger
sensor.enableEvents = true;   // Enable events

sensor.onTriggerEnter = (other) => console.log('enter', other.object3D.name);
sensor.onTriggerExit  = (other) => console.log('exit', other.object3D.name);
CallbackWhen it fires
onTriggerEnter(other)An object enters the trigger
onTriggerExit(other)An object leaves the trigger

Collision Contact Events

By enabling enableEvents on a dynamic rigidbody, you can listen for contact with other objects:

ts
const rb = body.addComponent(Rigidbody);
rb.bodyType = BodyType.Dynamic;
rb.mass = 1;
rb.shape = CollisionShapeUtil.createBoxShape(body, new Vector3(1, 1, 1));
rb.enableEvents = true;

rb.onContactBegin = (other) => { /* Contact begins (one-time) */ };
rb.onContactStay  = (other) => { /* Contact ongoing (every frame) */ };
rb.onContactEnd   = (other) => { /* Contact ends (one-time) */ };
CallbackWhen it fires
onContactBegin(other)Contact just started
onContactStay(other)Contact is ongoing (every frame)
onContactEnd(other)Contact ended

The callback parameter other is the other party's Rigidbody; you can access its owning node via other.object3D.

Example

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Scene3D, CameraUtil, HoverCameraController, View3D, DirectLight, AtmosphericComponent, Object3D, LitMaterial, Engine3D, BoxGeometry, MeshRenderer, Vector3, PlaneGeometry, Color, SphereGeometry, BlendMode } from "@orillusion/core";
import { Physics, Rigidbody, BodyType, CollisionShapeUtil } from "@orillusion/physics-rapier";

class Sample_RapierTriggers {
    async run() {
        await Physics.init();
        const engine = await Engine3D.init({ renderLoop: () => Physics.update() });

        let scene = new Scene3D();

        let camera = CameraUtil.createCamera3DObject(scene);
        camera.perspective(60, engine.aspect, 0.1, 800.0);

        let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
        hoverCtrl.setCamera(0, -25, 100);
        hoverCtrl.dragSmooth = 4;

        let lightObj3D = new Object3D();
        lightObj3D.localRotation = new Vector3(-35, -143, 92);

        let light = lightObj3D.addComponent(DirectLight);
        light.lightColor = Color.COLOR_WHITE;
        light.castShadow = true;
        light.enableCSM = true;
        light.intensity = 2.2;
        scene.addChild(light.object3D);

        let atmosphericSky = scene.addComponent(AtmosphericComponent);
        atmosphericSky.sunY = 0.6;

        const floor = new Object3D();
        const fr = floor.addComponent(MeshRenderer);
        fr.geometry = new PlaneGeometry(40, 40);
        const fm = new LitMaterial(); fm.baseColor = new Color(0.4, 0.4, 0.45); fr.material = fm;
        const fb = floor.addComponent(Rigidbody);
        fb.bodyType = BodyType.Static; fb.shape = CollisionShapeUtil.createPlaneShape(20, 0.05);
        scene.addChild(floor);

        const zone = new Object3D(); zone.y = 2;
        const zmr = zone.addComponent(MeshRenderer);
        zmr.geometry = new BoxGeometry(6, 4, 6);
        const zmat = new LitMaterial();
        zmat.baseColor = new Color(0.2, 0.9, 0.4, 0.25);
        zmat.blendMode = BlendMode.NORMAL;
        zmat.transparent = true;
        zmr.material = zmat;
        const zrb = zone.addComponent(Rigidbody);
        zrb.bodyType = BodyType.Static;
        zrb.shape = CollisionShapeUtil.createBoxShape(zone, new Vector3(6, 4, 6));
        zrb.isSensor = true;
        zrb.enableEvents = true;

        let inside = 0;
        zrb.onTriggerEnter = (other) => {
            inside++;
            console.log('triggerEnter', other.object3D.name, 'count:', inside);
            zmat.baseColor = new Color(1.0, 0.4, 0.4, 0.35);
        };
        zrb.onTriggerExit = (other) => {
            inside--;
            console.log('triggerExit', other.object3D.name, 'count:', inside);
            if (inside <= 0) zmat.baseColor = new Color(0.2, 0.9, 0.4, 0.25);
        };
        scene.addChild(zone);

        let id = 0;
        const dropInterval = setInterval(() => {
            const o = new Object3D();
            o.name = 'ball_' + (id++);
            o.x = (Math.random() - 0.5) * 4;
            o.z = (Math.random() - 0.5) * 4;
            o.y = 12;
            const mr = o.addComponent(MeshRenderer);
            mr.geometry = new SphereGeometry(0.4, 16, 16);
            const m = new LitMaterial();
            m.baseColor = new Color(Math.random(), Math.random(), Math.random()); mr.material = m;
            const rb = o.addComponent(Rigidbody);
            rb.bodyType = BodyType.Dynamic; rb.mass = 1; rb.restitution = 0.3;
            rb.shape = CollisionShapeUtil.createSphereShape(o, 0.4);
            rb.enableEvents = true;
            scene.addChild(o);
            if (id > 30) clearInterval(dropInterval);
        }, 250);

        let view = new View3D();
        view.camera = camera;
        view.scene = scene;
        engine.startRenderView(view);
    }
}

new Sample_RapierTriggers().run();

Released under the MIT License