Skip to content

Rigidbody

A rigidbody refers to an object whose own deformation can be neglected when subjected to external forces. Although an ideal rigidbody cannot truly exist, under conditions where the speed is much smaller than the speed of light, many hard objects can usually be assumed to be perfect rigidbodies. Based on the characteristics of rigidbodies, the engine's physics system can simulate the motion and collision logic of objects in the real world, producing realistic animation effects.

In the engine's physics system, the rigidbody is a key component. After adding the rigidbody component Rigidbody to a model object, the object will have mass and be able to respond to gravity and other physical forces, exhibiting dynamic characteristics similar to those in the real world.

The Synchronization Mechanism Between Rigidbody and Model Object

After a rigidbody component is added to a model object, the physics engine takes over the object's transform (usually position and rotation). In each physics simulation step (usually 60Hz, i.e., 60 frames per second), the physics engine calculates the rigidbody's motion state based on its physical properties (such as mass, force, collision, etc.) and updates the rigidbody's transform information in real time with the calculated results. In each rendered frame, the rigidbody component's update function obtains the rigidbody's interpolated transform and synchronizes it to the model object, so that it exhibits realistic physical behavior in the scene. Please note that once a rigidbody component is added, the model object's transform will be automatically managed by the physics engine.

Rigidbody and Collision Shape

The rigidbody is responsible for handling an object's dynamic properties and motion, but the rigidbody alone is not enough to complete a physics simulation, because it does not contain information about the object's specific shape or size. To achieve a complete physics simulation, the rigidbody must be associated with a collision shape. The collision shape defines the object's boundaries in physical space, and the rigidbody participates in collision detection and handling through these boundaries.

Properties and Methods

The Rigidbody component is designed to encapsulate many APIs. The commonly used properties are shown in the table below:

PropertyTypeDescription
btRigidbodyAmmo.btRigidBodyGets the native rigidbody object of Ammo.js
shapeAmmo.btCollisionShapeThe collision shape of the rigidbody, defining the object's physical boundaries
massnumberThe mass of the rigidbody (unit: kg), which determines the object's inertia. Default value is 0.01
restitutionnumberThe coefficient of restitution, which determines how much the object bounces after a collision. Default value is 0.5
frictionnumberFriction, which affects the sliding behavior of the rigidbody when in contact with other objects. Default value is 0.5
velocityVector3The force vector applied at the center of the rigidbody
damping[number, number]Damping coefficients, which control the decay of the rigidbody's linear and angular velocity
enablePhysicsTransformSyncbooleanWhether to enable transform synchronization between the rigidbody and the model object. Default value is false
isSilentbooleanWhether it is in a silent state; when set to true, the collision callbacks of both parties will not be triggered
enableCollisionEventbooleanWhether to enable collision events. Default value is true
collisionEventFunctionCollision event callback
MethodDescription
wait()Asynchronously gets the native rigidbody instance once initialization is complete
updateTransform()Updates the position and rotation of the rigidbody and synchronizes the object
clearForcesAndVelocities()Clears all forces and velocities of the rigidbody, resetting its motion state
More APIs
APITypeDescription
collisionShapeCollisionShapeUtilA tool for creating collision shapes
rollingFrictionnumberRolling friction, which affects the sliding behavior of the rigidbody when it rolls
ccdSettings[number, number]Settings for continuous collision detection, used to prevent high-speed moving objects from passing through other objects
gravityVector3The gravity vector applied to the rigidbody, which can be customized to be different from the global gravity vector
linearVelocityVector3The linear velocity of the rigidbody
angularVelocityVector3The angular velocity of the rigidbody
activationStateActivationStateThe activation state of the rigidbody
isKinematicbooleanSet as a kinematic rigidbody, which will automatically enable enablePhysicsTransformSync
isTriggerbooleanSet as a trigger, which does not participate in physical reactions and does not trigger collision events
isDisableDebugVisiblebooleanSet whether the rigidbody is visible in debug mode
userIndexnumberUser index, which can be used as a rigidbody identifier
groupnumberThe collision group of the rigidbody
masknumberThe collision mask of the rigidbody
marginnumberDefines the collision margin of the collision shape
collisionFlagsnumberGets the collision flags
addCollisionFlag()CollisionFlagsAdds a single collision flag. Used to set specific behaviors of the rigidbody, such as static, kinematic, etc.
removeCollisionFlag()CollisionFlagsRemoves a single collision flag

Basic Usage

Add a Rigidbody component to an object:

ts
import { Object3D } from '@orillusion/core'
import { Rigidbody, CollisionShapeUtil } from '@orillusion/physics'

let object = new Object3D();
let rigidbody = object.addComponent(Rigidbody);

After adding the component, you also need to set a collision shape for the rigidbody. Based on the content introduced in the previous section on collision shapes, we can create an appropriate collision shape according to the object's geometry:

ts
rigidbody.shape = CollisionShapeUtil.createShapeFromObject(object);

Set the mass (unit: kg) for the rigidbody:

ts
rigidbody.mass = 50;

If you need a static rigidbody, simply set mass to 0:

ts
rigidbody.mass = 0;

You can manipulate the native Ammo.js rigidbody in the following way:

ts
// Use the wait method to ensure the rigidbody initialization is complete
let bt = await rigidbody.wait();
bt.getCollisionShape(); // native rigidbody API

Core Features

Collision Detection and Event Handling

The rigidbody component supports detailed collision detection and provides the enableCollisionEvent property and collisionEvent callback function, allowing developers to listen for and handle the rigidbody's collision events.

ts
rigidbody.enableCollisionEvent = true;
rigidbody.collisionEvent = (contactPoint: Ammo.btManifoldPoint, selfBody: Ammo.btRigidBody, otherBody: Ammo.btRigidBody) => {
    // Handle the collision event here
};

Since the physics engine may detect the collision of the same pair of rigidbodies multiple times within each simulation step, the callback function will be triggered continuously during the collision.

To avoid performance degradation, you can add debounce logic in the callback function, or limit the execution frequency of certain calculations.

Usually, after registering a collision event, the callback will be triggered whenever the rigidbody collides with other objects. For objects that do not need to be handled (such as the ground), you can set isSilent to true to avoid triggering the callback.

Synchronization Between Rigidbody and Model Object

By enabling the enablePhysicsTransformSync property, you can ensure that the model object's transform (position, rotation, scale) is synchronized to the physical rigidbody in real time, thereby keeping the visual and physical behaviors consistent.

ts
rigidbody.enablePhysicsTransformSync = true;

// After enabling synchronization, modifying the object's position, rotation, or scale will be synchronized to the rigidbody in real time
object.transform.x += 10;
object.transform.rotationX += 10;
object.transform.scaleX = 2;

Ghost Object

A Ghost Object is a special kind of collision object used to detect overlap between objects without producing a physical reaction. Unlike a rigidbody, a ghost object is not affected by forces, nor does it exert forces on other objects, but it can detect contact with other objects and trigger corresponding events. This makes ghost objects very suitable for scenarios that require area detection or triggering events.

Introduction to the Ghost Component

The physics system encapsulates the ghost object and provides the ghost trigger component GhostTrigger. Similar to the rigidbody, the ghost trigger component has many of the same properties and methods. The main APIs are shown in the table below:

PropertyTypeDescription
ghostObjectAmmo.btPairCachingGhostObjectGets the native ghost object of Ammo.js
shapeAmmo.btCollisionShapeThe collision shape of the ghost object, defining the object's physical boundaries
enableCollisionEventbooleanWhether to enable collision events
collisionEventFunctionCollision event callback
MethodDescription
wait()Asynchronously gets the fully initialized native ghost object instance
createAndAddGhostObject()A static method that creates a ghost object and adds it to the physical world

Basic Usage

Similar to the usage of the rigidbody component, we can directly add the ghost trigger component and configure shape and collisionEvent:

ts
import { GhostTrigger, CollisionShapeUtil } from "@orillusion/physics";

let ghostTrigger = object.addComponent(GhostTrigger);
ghostTrigger.shape = CollisionShapeUtil.createBoxShape(object);
ghostTrigger.collisionEvent = (contactPoint, selfBody, otherBody) => {
    // Handle the ghost object's collision event here
}

TIP

After adding the component, the ghost trigger will automatically synchronize the model object's transform, ensuring that when the model moves or is adjusted, the position and shape of the ghost object are also updated in real time.

Ghost objects are usually used for area detection, and in many cases there is no need to associate them with a specific model object. For this purpose, the GhostTrigger component provides a static method, allowing developers to directly call createAndAddGhostObject() to create a native ghost object without adding it in component form:

ts
import { Ammo, CollisionShapeUtil, GhostTrigger, ContactProcessedUtil } from "@orillusion/physics";

let size = new Vector3(10, 5, 5);
let shape = CollisionShapeUtil.createBoxShape(null, size);
let position = new Vector3(0, 2.5, 0);
let rotation = Vector3.ZERO;
// Pass in the collision shape, position, and rotation information to create the ghost object and automatically add it to the physical world
let ghostObj = GhostTrigger.createAndAddGhostObject(shape, position, rotation);
// Register the event using the collision utility
ContactProcessedUtil.registerCollisionCallback(ghostObj.kB, (contactPoint, selfBody, otherBody) => {
    // Handle the ghost object's collision event here
});

Example

In the following example, a simple area detection is implemented by applying the Rigidbody and GhostTrigger components.

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

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

class Sample_AreaDetection {
    async run() {
        // Initialize physics and engine
        await Physics.init();
        let 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);
        camera.object3D.addComponent(HoverCameraController).setCamera(0, -25, 50);

        // Create directional light
        let lightObj3D = new Object3D();
        lightObj3D.localRotation = new Vector3(151, -39, -35);
        lightObj3D.addComponent(DirectLight).castShadow = true;
        scene.addChild(lightObj3D);

        // Initialize sky
        scene.addComponent(AtmosphericComponent).sunY = 0.6;

        let view = new View3D();
        view.camera = camera;
        view.scene = scene;

        engine.startRenderView(view);

        this.createGround(scene);
        this.createBall(scene);
        await this.createGhostTrigger(scene);
    }

    createGround(scene: Scene3D) {
        let obj = new GridObject(50, 5);
        scene.addChild(obj);

        // add rigidbody to ground
        let rb = obj.addComponent(Rigidbody);
        rb.shape = CollisionShapeUtil.createBoxShape(obj);
        rb.mass = 0;
    }

    createBall(scene: Scene3D) {
        const ball = new Object3D();
        let mr = ball.addComponent(MeshRenderer);
        mr.geometry = new SphereGeometry(0.7, 32, 32);
        mr.material = new LitMaterial();

        ball.y = 20;
        scene.addChild(ball);

        // add rigidbody to ball
        let rigidbody = ball.addComponent(Rigidbody);
        rigidbody.shape = CollisionShapeUtil.createSphereShape(ball);
        rigidbody.mass = 1;
        rigidbody.restitution = 1.98; // set high elasticity
        rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;
    }

    async createGhostTrigger(scene: Scene3D) {
        const obj = new Object3D();
        let mr = obj.addComponent(MeshRenderer);
        mr.geometry = new BoxGeometry(10, 5, 10);
        let material = new LitMaterial();

        const baseColor = new Color(0, 1, 0.5, 1.0);
        material.baseColor = baseColor;
        material.transparent = true;
        material.cullMode = 'none';
        // material.depthCompare = 'always';
        material.blendMode = BlendMode.ADD;

        let texture = new BitmapTexture2D();
        await texture.load('https://cdn.orillusion.com/textures/grid.webp');

        material.baseMap = texture;
        mr.material = material;

        obj.y = 10;
        scene.addChild(obj);

        let ghostTrigger = obj.addComponent(GhostTrigger);
        ghostTrigger.shape = CollisionShapeUtil.createBoxShape(obj);

        // ghost collision event to change color
        let timer: number | null = null;
        ghostTrigger.collisionEvent = (contactPoint, selfBody, otherBody) => {
            if (timer !== null) clearTimeout(timer);
            else material.baseColor = new Color(Color.SALMON);

            timer = setTimeout(() => {
                material.baseColor = baseColor;
                timer = null;
            }, 100);
        }

    }

}

new Sample_AreaDetection().run();

Released under the MIT License