Skip to content

Inner Components

To facilitate the use of the physics system, the engine has encapsulated some commonly used physics components. For additional functionality, users can call the Ammo object to access more native APIs.

Rigid Body

A rigid body is an object whose own deformation can be neglected after being subjected to external forces. Although ideal rigid bodies cannot exist in reality, many hard objects can be assumed to be perfect rigid bodies under the condition of speeds much smaller than the speed of light. Based on the characteristics of rigid bodies, the physics system of the engine can simulate the motion and collision logic of objects in the real world, creating realistic animation effects.

The rigid body is an important component in the engine's physics system. After connecting a rigid body, we can make the model object have mass and respond to gravity like a real-world object. For user convenience, we have encapsulated the rigid body component Rigidbody. By adding the component, the rigid body can be added to the object:

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

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

Basic Operations

Set the mass (unit: kg) for the rigid body:

ts
rigidbody.mass = 50;

If a static rigid body is required, set the mass to 0:

ts
rigidbody.mass = 0;

Add a velocity to the rigid body:

ts
rigidbody.velocity = new Vector(10, 0, 0);

If you want to manipulate the native Ammo.js rigid body, you can get it through the following method:

ts
let bt = rigidbody.btRigidbody;
// native rigidbody API
bt.getCollisionShape(); 
bt.getWorldTransform();
...

Collision Shape

The Collision Shape defines the actual physical shape that a rigid body uses for collision responses. The physics system uses the Shape to determine whether two objects intersect, which in turn produces collision effects.

TIP

Starting from @orillusion/physics@0.3, we recommend directly using the native Ammo.btShape to manage collision shapes.

  1. Box Collider

Box Collider

ParameterTypeDescription
sizebtVector3The size of a box-shaped collision shape. By default, this is specified as half the length along the x, y, and z axes from the center of the object, which corresponds to half the width, height, and depth of the object
ts
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'

let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new BoxGeometry(1, 1, 1); // size 1
mr.material = new LitMaterial();
let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;

// half length of BoxGeometry size
let shape = new Ammo.btBoxShape(new Ammo.btVector3(1/2, 1/2, 1/2));
rigidbody.shape = shape;
  1. Sphere Collider

Sphere Collider

ParameterTypeDescription
radiusnumberThe radius of a spherical collision shape
ts
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'

let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new SphereGeometry(1, 8, 8); // radius 1
mr.material = new LitMaterial();
let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;

// shape with radius 1
let shape = new Ammo.btSphereShape(1);
rigidbody.shape = shape;
  1. Capsule Collider

Capsule Collider

ParameterTypeDescription
radiusnumberThe radius of the top and bottom hemispheres of a capsule collision shape
heightnumberand the height of the cylindrical middle section
ts
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'

// cylinder
let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new CylinderGeometry(1, 1, 5); // radius 1, height 5
// top sphere
let topSphere = new Object3D();
topSphere.y = 2.5;
let mrTop = topSphere.addComponent(MeshRenderer);
mrTop.geometry = new SphereGeometry(1, 8, 8);
object.addChild(topSphere);
// bottom sphere
let bottomSphere = new Object3D();
bottomSphere.y = -2.5;
let mrBottom = bottomSphere.addComponent(MeshRenderer);
mrBottom.geometry = new SphereGeometry(1, 8, 8);
object.addChild(bottomSphere);
mr.material = mrBottom.material = mrTop.material = new LitMaterial();

let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;

// CapsuleShape with radius 1, body height 5
let shape = new Ammo.btCapsuleShape(1, 5);
rigidbody.shape = shape;

In addition to the common shapes mentioned above,Ammo.js also provides more complex collision shapes such as btStaticPlaneShape, btCylinderShape, btConeShape, btConvexHullShape, btConcaveShape and btHeightfieldTerrainShape.

Using these collision shapes, we can simulate more complex physical scenarios. Let's proceed with an example to further demonstrate the usage of various shapes in the physics system.

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

<
ts
import { Engine3D, LitMaterial, MeshRenderer, BoxGeometry, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, SphereGeometry, CameraUtil, HoverCameraController, BitmapTexture2D, VertexAttributeName, Color, CylinderGeometry, TorusGeometry, ComponentBase } from "@orillusion/core";
import { TerrainGeometry } from "@orillusion/geometry";
import { Graphic3D } from "@orillusion/graphic";
import { Ammo, CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics";

class Sample_MultipleShapes {
    scene: Scene3D;
    terrain: Object3D;
    gui: dat.GUI;

    async run() {
        // init physics and engine
        await Physics.init();
        await Engine3D.init({
            renderLoop: () => Physics.update()
        });

        // shadow settings
        Engine3D.setting.shadow.shadowBias = 0.01;
        Engine3D.setting.shadow.shadowSize = 1024 * 4;
        Engine3D.setting.shadow.csmMargin = 0.1;
        Engine3D.setting.shadow.csmScatteringExp = 0.8;
        Engine3D.setting.shadow.csmAreaScale = 0.1;
        Engine3D.setting.shadow.updateFrameRate = 1;

        this.scene = new Scene3D();

        // Setup camera
        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, Engine3D.aspect, 0.1, 800.0);
        camera.enableCSM = true;

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

        // Create directional light
        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.intensity = 2.2;
        this.scene.addChild(light.object3D);

        // init sky
        let atmosphericSky = this.scene.addComponent(AtmosphericComponent);
        atmosphericSky.sunY = 0.6;

        // Setup view
        let view = new View3D();
        view.camera = camera;
        view.scene = this.scene;

        Engine3D.startRenderView(view);

        // init terrain and create static planes
        await this.initTerrain();
        this.createStaticPlanes();

        this.scene.addComponent(BoxGenerator);
    }

    async initTerrain() {
        // Load textures
        let bitmapTexture = await Engine3D.res.loadTexture('https://cdn.orillusion.com/terrain/test01/bitmap.png');
        let heightTexture = await Engine3D.res.loadTexture('https://cdn.orillusion.com/terrain/test01/height.png');

        const width = 100;
        const height = 100;
        const terrainMaxHeight = 60;
        const segment = 60

        // Create terrain geometry
        let terrainGeometry = new TerrainGeometry(width, height, segment, segment);
        terrainGeometry.setHeight(heightTexture as BitmapTexture2D, terrainMaxHeight);

        let terrain = new Object3D();
        let mr = terrain.addComponent(MeshRenderer);
        mr.geometry = terrainGeometry;

        let mat = new LitMaterial();
        mat.baseMap = bitmapTexture;
        mat.metallic = 0;
        mat.roughness = 1.3;
        mr.material = mat;

        this.terrain = terrain;
        this.scene.addChild(terrain);

        // Add rigidbody to terrain
        let terrainRb = terrain.addComponent(Rigidbody);
        terrainRb.shape = Rigidbody.collisionShape.createHeightfieldTerrainShape(terrain);
        terrainRb.mass = 0; // Static rigidbody
        terrainRb.margin = 0.05;
        terrainRb.isDisableDebugVisible = true;
        terrainRb.friction = 1;
    }

    // Create static planes for boundaries
    createStaticPlanes() {
        // Create bottom static plane
        let staticFloorBottom = Object3DUtil.GetPlane(Engine3D.res.whiteTexture);
        staticFloorBottom.y = -500;
        staticFloorBottom.transform.enable = false;
        this.scene.addChild(staticFloorBottom);

        let bottomRb = staticFloorBottom.addComponent(Rigidbody);
        bottomRb.shape = CollisionShapeUtil.createStaticPlaneShape();
        bottomRb.mass = 0;

        // Create top static plane
        let staticFloorTop = Object3DUtil.GetPlane(Engine3D.res.whiteTexture);
        staticFloorTop.y = 100;
        staticFloorTop.transform.enable = false;
        this.scene.addChild(staticFloorTop);

        let topRb = staticFloorTop.addComponent(Rigidbody);
        topRb.shape = CollisionShapeUtil.createStaticPlaneShape(Vector3.DOWN);
        topRb.mass = 0;
    }
}

class BoxGenerator extends ComponentBase {
    private lastTime: number = performance.now(); // Save last time

    public container: Object3D;
    public interval: number = 1000; // Interval for adding shapes
    public totalShapes: number = 30; // Maximum number of shapes

    async start() {
        this.container = new Object3D();
        this.object3D.addChild(this.container);
    }

    // Update loop
    public onUpdate(): void {
        let now: number = performance.now();
        if (now - this.lastTime > this.interval) {
            if (this.container.numChildren >= this.totalShapes) {
                let index = Math.floor(now / this.interval) % this.totalShapes;
                let shapeObject = this.container.getChildByIndex(index) as Object3D;
                shapeObject.localPosition.set(Math.random() * 60 - 60 / 2, 40, Math.random() * 60 - 60 / 2);
                shapeObject.getComponent(Rigidbody).updateTransform(shapeObject.localPosition, null, true);
            } else {
                this.addRandomShape();
            }
            this.lastTime = now; // Save current time
        }
    }

    private addRandomShape(): void {
        const shapeObject = new Object3D();
        let mr = shapeObject.addComponent(MeshRenderer);
        let mat = new LitMaterial();
        mat.baseColor = Color.random();

        let size = 1 + Math.random() / 2;
        let height = 1 + Math.random() * (3 - 1);
        let radius = 0.5 + Math.random() / 2;
        const segments = 32;

        let shape: Ammo.btCollisionShape;
        let shapeType = Math.floor(Math.random() * 6); // Six basic shapes
        switch (shapeType) {
            case 0: // Box shape
                mr.geometry = new BoxGeometry(size, size, size);
                mr.material = mat;
                shape = CollisionShapeUtil.createBoxShape(shapeObject);
                break;
            case 1: // Sphere shape
                mr.geometry = new SphereGeometry(radius, segments, segments);
                mr.material = mat;
                shape = CollisionShapeUtil.createSphereShape(shapeObject);
                break;
            case 2: // Cylinder shape
                mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments);
                mr.materials = [mat, mat, mat];
                shape = CollisionShapeUtil.createCylinderShape(shapeObject);
                break;
            case 3: // Cone shape
                mr.geometry = new CylinderGeometry(0.01, radius, height, segments, segments);
                mr.materials = [mat, mat, mat];
                shape = CollisionShapeUtil.createConeShape(shapeObject);
                break;
            case 4: // Capsule shape
                mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments);
                mr.material = mat;
                const { r, g, b } = mat.baseColor;
                let topSphere = Object3DUtil.GetSingleSphere(radius, r, g, b);
                topSphere.y = height / 2;
                let bottomSphere = topSphere.clone();
                bottomSphere.y = -height / 2;
                shapeObject.addChild(topSphere);
                shapeObject.addChild(bottomSphere);
                shape = CollisionShapeUtil.createCapsuleShape(shapeObject);
                break;
            case 5: // Torus shape (convex hull shape)
                mr.geometry = new TorusGeometry(radius, size / 5, segments / 2, segments / 2);
                mr.material = mat;
                shape = CollisionShapeUtil.createConvexHullShape(shapeObject);
                break;
            default:
                break;
        }

        const posRange = 60;
        shapeObject.x = Math.random() * posRange - posRange / 2;
        shapeObject.y = 40;
        shapeObject.z = Math.random() * posRange - posRange / 2;

        shapeObject.localRotation = new Vector3(Math.random() * 360, Math.random() * 360, Math.random() * 360);
        this.container.addChild(shapeObject);

        // Add rigidbody to shape
        let rigidbody = shapeObject.addComponent(Rigidbody);
        rigidbody.shape = shape;
        rigidbody.mass = Math.random() * 10 + 0.1;
        rigidbody.rollingFriction = 0.5;
        rigidbody.damping = [0.1, 0.1];

        // Enable continuous collision detection (CCD)
        const maxDimension = Math.max(size, height, radius);
        const ccdMotionThreshold = maxDimension * 0.1; // Set motion threshold to 10% of max dimension
        const ccdSweptSphereRadius = maxDimension * 0.05; // Set swept sphere radius to 5% of max dimension
        rigidbody.ccdSettings = [ccdMotionThreshold, ccdSweptSphereRadius];
    }
}

new Sample_MultipleShapes().run();

SoftBody and Constraint

Similar to rigid bodies, the engine encapsulates native Ammo.js objects such as btSoftBody, btFixedConstraint, btGeneric6DofConstraint, btHingeConstraint and btSliderConstraint to simulate more complex physical effects like cloth, springs, hinges, and sliders.

Since these complex Ammo objects contain a large number of physical parameter settings, we cannot cover all the details here. Users can search for Bullet documentation to understand the physical meanings and usage rules of these APIs.

Here, we will demonstrate basic usage methods for some components through an example. For more Ammo physics examples, please refer to the example page.

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

<
ts
import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, PlaneGeometry, GPUCullMode, Color } from "@orillusion/core";
import { Stats } from "@orillusion/stats";
import { ActivationState, CollisionShapeUtil, DebugDrawMode, FixedConstraint, HingeConstraint, Physics, PointToPointConstraint, Rigidbody, SliderConstraint, ClothSoftbody, RopeSoftbody } from "@orillusion/physics";
import dat from "dat.gui";
import { Graphic3D } from "@orillusion/graphic";

/**
 * Sample class demonstrating the use of multiple constraints in a physics simulation.
 */
class Sample_MultipleConstraints {
    scene: Scene3D;
    gui: dat.GUI;

    async run() {
        // init physics and engine
        await Physics.init({ useSoftBody: true, useDrag: true });
        await Engine3D.init({ renderLoop: () => Physics.update() });

        this.gui = new dat.GUI();

        this.scene = new Scene3D();
        this.scene.addComponent(Stats);

        // 在引擎启动后初始化物理调试功能,需要为调试器传入 graphic3D 对象
        const graphic3D = new Graphic3D();
        this.scene.addChild(graphic3D);
        Physics.initDebugDrawer(graphic3D, {
            enable: false,
            debugDrawMode: DebugDrawMode.DrawConstraintLimits
        })

        let camera = CameraUtil.createCamera3DObject(this.scene);
        camera.perspective(60, Engine3D.aspect, 0.1, 800.0);
        camera.object3D.addComponent(HoverCameraController).setCamera(60, -25, 50);

        // create directional light
        let light = new Object3D();
        light.localRotation = new Vector3(36, -130, 60);
        let dl = light.addComponent(DirectLight);
        dl.castShadow = true;
        dl.intensity = 3;
        this.scene.addChild(light);

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

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

        this.physicsDebug();

        Engine3D.startRenderView(view);

        // Create ground, turntable, and chains
        this.createGround();
        this.createTurntable();
        this.createChains();

        // Create impactor and softBody
        let impactorRb = this.createImpactor();
        this.createClothSoftbody(impactorRb);
        this.createRopeSoftbody(impactorRb);
    }

    private physicsDebug() {
        let physicsFolder = this.gui.addFolder('PhysicsDebug');
        physicsFolder.add(Physics.debugDrawer, 'enable');
        physicsFolder.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList);
        physicsFolder.add(Physics, 'isStop');
        physicsFolder.add({ hint: "Drag dynamic rigid bodies with the mouse." }, "hint");
        physicsFolder.open();
    }

    private async createGround() {
        // Create ground
        let ground = Object3DUtil.GetSingleCube(80, 2, 20, 1, 1, 1);
        ground.y = -1; // Set ground half-height
        this.scene.addChild(ground);

        // Add rigidbody to ground
        let groundRb = ground.addComponent(Rigidbody);
        groundRb.shape = CollisionShapeUtil.createBoxShape(ground);
        groundRb.mass = 0;
    }

    private createImpactor(): Rigidbody {
        // Create shelves
        const shelfSize = 0.5;
        const shelfHeight = 5;

        let shelfLeft = Object3DUtil.GetCube();
        shelfLeft.localScale = new Vector3(shelfSize, shelfHeight, shelfSize);
        shelfLeft.localPosition = new Vector3(-30, shelfHeight / 2, 0);

        let shelfRight = shelfLeft.clone();
        shelfRight.localPosition = new Vector3(30, shelfHeight / 2, 0);

        let shelfTop = Object3DUtil.GetCube();
        shelfTop.localScale = new Vector3(60 - shelfSize, shelfSize, shelfSize);
        shelfTop.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 0);

        // Add rigidbodies to shelves
        let shelfRightRb = this.addBoxShapeRigidBody(shelfRight, 0);
        let shelfLeftRb = this.addBoxShapeRigidBody(shelfLeft, 0);
        this.addBoxShapeRigidBody(shelfTop, 0);

        this.scene.addChild(shelfLeft);
        this.scene.addChild(shelfRight);
        this.scene.addChild(shelfTop);

        // Create slider
        let slider = Object3DUtil.GetSingleCube(4, 1, 1, Math.random(), Math.random(), Math.random());
        this.scene.addChild(slider);

        // Add rigidbody to slider
        let sliderRb = this.addBoxShapeRigidBody(slider, 500, true, [0.2, 0]);

        // Create Impactor
        let impactor = Object3DUtil.GetCube();
        impactor.localScale = new Vector3(1, 1, 5);
        impactor.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 3);
        this.scene.addChild(impactor);

        let impactorRb = this.addBoxShapeRigidBody(impactor, 200, true);

        // Create fixed constraint to attach slider to impactor
        let fixedConstraint = slider.addComponent(FixedConstraint);
        fixedConstraint.targetRigidbody = impactorRb;
        fixedConstraint.pivotTarget = new Vector3(0, 0, -3);

        // Create slider constraint
        let sliderConstraint = shelfTop.addComponent(SliderConstraint);
        sliderConstraint.targetRigidbody = sliderRb;
        sliderConstraint.lowerLinLimit = -30;
        sliderConstraint.upperLinLimit = 30;
        sliderConstraint.lowerAngLimit = 0;
        sliderConstraint.upperAngLimit = 0;
        sliderConstraint.poweredLinMotor = true;
        sliderConstraint.maxLinMotorForce = 1;
        sliderConstraint.targetLinMotorVelocity = 20;

        // Setup slider motor event controller
        this.sliderMotorEventController(shelfLeftRb, shelfRightRb, sliderConstraint);

        return impactorRb;
    }

    private sliderMotorEventController(leftRb: Rigidbody, rightRb: Rigidbody, slider: SliderConstraint) {
        // Control slider movement based on collision events
        const timer = { pauseDuration: 1000 };

        leftRb.collisionEvent = () => {
            rightRb.enableCollisionEvent = true;
            leftRb.enableCollisionEvent = false;
            setTimeout(() => {
                slider.targetLinMotorVelocity = Math.abs(slider.targetLinMotorVelocity);
                setTimeout(() => leftRb.enableCollisionEvent = true, 1000);
            }, timer.pauseDuration);
        };

        rightRb.collisionEvent = () => {
            rightRb.enableCollisionEvent = false;
            leftRb.enableCollisionEvent = true;
            setTimeout(() => {
                slider.targetLinMotorVelocity = -Math.abs(slider.targetLinMotorVelocity);
                setTimeout(() => rightRb.enableCollisionEvent = true, 1000);
            }, timer.pauseDuration);
        };

        // GUI controls for slider motor
        let folder = this.gui.addFolder('Slider Motor Controller');
        folder.open();
        folder.add(slider, 'poweredLinMotor');
        folder.add(slider, 'maxLinMotorForce', 0, 30, 1);
        folder.add({ velocity: slider.targetLinMotorVelocity }, 'velocity', 0, 30, 1).onChange(v => {
            slider.targetLinMotorVelocity = slider.targetLinMotorVelocity > 0 ? v : -v;
        });
        folder.add(timer, 'pauseDuration', 0, 3000, 1000);
    }

    private createTurntable() {
        // Create turntable components
        const columnWidth = 0.5;
        const columnHeight = 4.75 - columnWidth / 2;
        const columnDepth = 0.5;

        let column = Object3DUtil.GetCube();
        column.localScale = new Vector3(columnWidth, columnHeight, columnDepth);
        column.localPosition = new Vector3(0, columnHeight / 2, 8);
        this.scene.addChild(column);
        this.addBoxShapeRigidBody(column, 0); // Add rigidbodies to turntable components


        // Create arm compound shape
        let armParent = new Object3D();
        armParent.localPosition = new Vector3(0, columnHeight + columnWidth / 2, 8);

        let armChild1 = Object3DUtil.GetCube();
        armChild1.rotationY = 45;
        armChild1.localScale = new Vector3(10, 0.5, 0.5);

        let armChild2 = armChild1.clone();
        armChild2.rotationY = 135;

        armParent.addChild(armChild1);
        armParent.addChild(armChild2);
        this.scene.addChild(armParent);

        let armRigidbody = armParent.addComponent(Rigidbody);
        armRigidbody.shape = CollisionShapeUtil.createCompoundShapeFromObject(armParent);
        armRigidbody.mass = 500;
        armRigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;

        // Create hinge constraint to attach arm1 to column
        let hinge = column.addComponent(HingeConstraint);
        hinge.targetRigidbody = armRigidbody;
        hinge.pivotSelf.set(0, columnHeight / 2 + columnWidth / 2, 0);
        hinge.enableAngularMotor(true, 5, 50);
    }

    private createChains() {
        const chainHeight = 1;

        let chainLink = Object3DUtil.GetCube();
        chainLink.localScale = new Vector3(0.25, chainHeight, 0.25);
        chainLink.localPosition = new Vector3(5, 16, 5);
        this.scene.addChild(chainLink);

        // Add static rigidbody to the first chain link
        let chainRb = this.addBoxShapeRigidBody(chainLink, 0);
        let prevRb = chainRb;

        // Create chain links and add point-to-point constraints
        for (let i = 0; i < 10; i++) {
            let link = chainLink.clone();
            link.y -= (i + 1) * chainHeight;
            this.scene.addChild(link);

            let linkRb = this.addBoxShapeRigidBody(link, 1, false, [0.3, 0.3]);
            linkRb.isSilent = true; // Disable collision events

            let p2p = link.addComponent(PointToPointConstraint);
            p2p.targetRigidbody = prevRb;
            p2p.pivotTarget.y = -chainHeight / 2;
            p2p.pivotSelf.y = chainHeight / 2;

            prevRb = linkRb;
        }

        // Create a sphere and add point-to-point constraint to the last chain link
        const sphereRadius = 0.8;
        let sphere = Object3DUtil.GetSingleSphere(sphereRadius, 1, 1, 1);
        let sphereMaterial = (sphere.getComponent(MeshRenderer).material as LitMaterial);

        sphere.localPosition = new Vector3(5, 4.5, 5);
        this.scene.addChild(sphere);

        let sphereRb = sphere.addComponent(Rigidbody);
        sphereRb.shape = CollisionShapeUtil.createSphereShape(sphere);
        sphereRb.mass = 2;
        sphereRb.damping = [0.3, 0.3];
        sphereRb.enablePhysicsTransformSync = true;

        // Sphere collision event to change color
        let timer: number | null = null;
        sphereRb.collisionEvent = () => {
            if (timer !== null) clearTimeout(timer);
            else sphereMaterial.baseColor = new Color(Color.SALMON);

            timer = setTimeout(() => {
                sphereMaterial.baseColor = Color.COLOR_WHITE;
                timer = null;
            }, 1000);
        };

        let p2p = sphere.addComponent(PointToPointConstraint);
        p2p.disableCollisionsBetweenLinkedBodies = true;
        p2p.targetRigidbody = prevRb;
        p2p.pivotTarget.y = -chainHeight / 2;
        p2p.pivotSelf.y = sphereRadius;
    }

    private createClothSoftbody(anchorRb: Rigidbody) {
        const cloth = new Object3D();
        let meshRenderer = cloth.addComponent(MeshRenderer);
        meshRenderer.geometry = new PlaneGeometry(3, 3, 10, 10, Vector3.X_AXIS); // Set the plane direction to determine the four corners
        let material = new LitMaterial();
        material.baseMap = Engine3D.res.redTexture;
        material.cullMode = GPUCullMode.none;
        meshRenderer.material = material;
        this.scene.addChild(cloth);

        // Add cloth softbody component
        let softBody = cloth.addComponent(ClothSoftbody);
        softBody.mass = 5;
        softBody.margin = 0.1;
        softBody.anchorRigidbody = anchorRb; // Anchor rigidbody
        softBody.anchorIndices = ['leftTop', 'top', 'rightTop']; // Anchor points
        softBody.influence = 1; // Attachment influence
        softBody.disableCollision = false; // Enable collision with rigidbody
        softBody.anchorPosition = new Vector3(0, -2.1, 0); // Relative position to anchor

        softBody.wait().then(btSoftbody => {
            // native softbody API
            let sbConfig = btSoftbody.get_m_cfg(); // configure softbody parameters 
            sbConfig.set_kDF(0.2);
            sbConfig.set_kDP(0.01);
            sbConfig.set_kLF(0.02);
            sbConfig.set_kDG(0.001);
        });

    }

    private createRopeSoftbody(headRb: Rigidbody) {

        const box = Object3DUtil.GetSingleCube(1, 1, 1, 1, 1, 1);
        box.localPosition = new Vector3(0, 10, 0);
        this.scene.addChild(box);
        let tailRb = this.addBoxShapeRigidBody(box, 1, true, [0.2, 0.2]);

        const rope = new Object3D();
        let mr = rope.addComponent(MeshRenderer);
        let startPos = new Vector3(0, 4.75, 3);
        let endPos = new Vector3(0, 10, 0);
        mr.geometry = RopeSoftbody.buildRopeGeometry(10, startPos, endPos);

        mr.material = new LitMaterial();
        mr.material.topology = 'line-list';
        this.scene.addChild(rope);

        // Add rope softbody component
        let softBody = rope.addComponent(RopeSoftbody);
        softBody.mass = 1;
        softBody.elasticity = 0.1;
        softBody.anchorRigidbodyHead = headRb;
        softBody.anchorOffsetHead = new Vector3(0, -0.5, 2.1);
        softBody.anchorRigidbodyTail = tailRb;
        softBody.anchorOffsetTail = new Vector3(0, 0.5, 0);

    }

    private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean, damping?: [number, number]) {
        let rigidbody = obj.addComponent(Rigidbody);
        rigidbody.shape = CollisionShapeUtil.createBoxShape(obj);
        rigidbody.mass = mass;

        if (disableHibernation) rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;
        if (damping) rigidbody.damping = damping;

        return rigidbody;
    }
}

new Sample_MultipleConstraints().run();

Released under the MIT License