Tutorial: Rapier

Rapier

The Rapier Physics Engine in Croquet Microverse

https://croquet.io

Introduction

The Croquet Microverse can use the Rapier Physics Engine to build a world with cards that obey the simulated law of physics.

We use the Rapier bindings provided by Worldcore. Rapier simulates the motion of the objects bit-identically on the model side. In other words, the simulation is decoupled from the visual appearances. The Microverse provides a behavior module called "Rapier" (behaviors/croquet/rapier.js) that replaces AM_RapierPhysics from Worldcore. The rest is all done in the "user land"; you can see an example behavior module called "Cascade" in behaviors/default/cascade.js and the default world where it is used.

First let us look at default.js. You can see that several cards have Rapier and Collider as their behavior modules:

{
    card: {
        name:"base",
        type: "object",
        layers: ["pointer", "walk"],
        rotation: [-Math.PI / 6, 0, 0],
        translation: [-20, 6, 48],
        behaviorModules: ["Rapier", "Cascade"],
        rapierSize: baseSize,
        color: 0x997777,
        rapierShape: "cuboid",
        rapierType: "positionBased",
    }
},

We can then check cascade.js to see how it is used.

class CascadeActor {
    setup() {
        let kinematic;
        let rapierType = this._cardData.rapierType;
        let rapierShape = this._cardData.rapierShape;
        if (rapierType === "positionBased") {
            kinematic = Worldcore.RAPIER.RigidBodyDesc.newKinematicPositionBased();
	...

        if (rapierShape === "ball") {
            let s = this._cardData.rapierSize || 1;
            s = s / 2;
            cd = Worldcore.RAPIER.ColliderDesc.ball(s);


The setup() method of CascadeActor checks the rapierType and rapierShape, and creates a rigid body description and a collider description. There are two calls to "Rapier$RapierActor", which invoke the method defined in the Rapier behavior module. The last part of setup() adds a bit of an interactive feature, and also the "kill plane". The translated() method is called when the rigid body moves. The method checks the y-coodinates of the object, and then destroys itself when it falls out of the simulation. When a position of a rigid body goes to infinity, the Rapier simulation crashes. So it is a good idea to have a boundary in your simulation.

        this.addEventListener("pointerTap", "jolt");
        this.listen("translating", "translated");

Alternatively, Rapier bindings enables the contactEvents and intersectionEvents to fire. See the part of setup() that uses the rapierSensor property and how it enables the contact- and intersectionEvent callbacks.

The CascadePawn behavior creates a Three JS mesh with a simple geometry that matches with the value in "rapierShape". For the demo purpose of the demo, the creation of the mesh is guarded by if (this.shape.children.length === 0), meaning that it does not replace a shape that is already there.

class CascadePawn {
    setup() {
        if (this.shape.children.length === 0) {
            let rapierShape = this.actor._cardData.rapierShape;
            if (rapierShape === "ball") {
                let s = this.actor._cardData.rapierSize || 1;
                let geometry = new Worldcore.THREE.SphereGeometry(s / 2, 32, 16);
                let material = new Worldcore.THREE.MeshStandardMaterial({color: this.actor._cardData.color || 0xff0000});
                this.obj = new Worldcore.THREE.Mesh(geometry, material);

To prevent a double tap action from triggering the "jump to" feature, we remove the default handler for pointerDoubleDown and install a "no operation" action.

        this.removeEventListener("pointerDoubleDown", "onPointerDoubleDown");
        this.addEventListener("pointerDoubleDown", "nop");

So this is it! Note again that the property names, such as rapierType, rapierShape, rapierSize are all "user defined". Those are used in this example, but you can define your own property and use it from your behaviors.

Also note that adding and removing a behavior can be done dynamically. You can start with a card that does not participate in the simulation but later you can add the card to the simulation by attaching "Rapier" behavior. This gives you more flexibility in creating your own worlds.

Copyright (c) 2022 Croquet Corporation