Skip to content

Dominoes


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, Color, Quaternion, ExtrudeGeometry, BlendMode, BitmapTexture2D } from "@orillusion/core";
import { CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics";
import { Stats } from "@orillusion/stats";
import dat from "dat.gui";
import { Graphic3D } from '@orillusion/graphic'

/**
 * Sample class demonstrating the creation of a domino effect with physics interactions.
 */
class Sample_Dominoes {
    async run() {
        // init physics and engine
        await Physics.init({ useDrag: true });
        await Engine3D.init({ renderLoop: () => Physics.update() });

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

        // 启用物理调试功能时,需要为绘制器传入graphic3D对象
        const graphic3D = new Graphic3D();
        scene.addChild(graphic3D);
        Physics.initDebugDrawer(graphic3D, { enable: false });

        let camera = CameraUtil.createCamera3DObject(scene);
        camera.perspective(60, Engine3D.aspect, 0.1, 800.0);
        camera.object3D.addComponent(HoverCameraController).setCamera(0, -32, 80);

        // Create directional light
        let lightObj3D = new Object3D();
        lightObj3D.localPosition = new Vector3(0, 30, -40);
        lightObj3D.localRotation = new Vector3(20, 160, 0);
        let directLight = lightObj3D.addComponent(DirectLight);
        directLight.castShadow = true;
        directLight.intensity = 2;
        scene.addChild(lightObj3D);

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

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

        Engine3D.startRenderView(view);

        await this.initScene(scene);

        this.debug(scene)
    }

    // init the scene with ground, Pipe, ball, and dominoes.
    private async initScene(scene: Scene3D) {
        // Create ground and add rigidbody
        let ground = Object3DUtil.GetSingleCube(100, 0.1, 100, 1, 1, 1);
        scene.addChild(ground);

        let rigidbody = ground.addComponent(Rigidbody);
        rigidbody.shape = CollisionShapeUtil.createBoxShape(ground);
        rigidbody.mass = 0;
        rigidbody.friction = 100; // Set high friction for the ground
        rigidbody.isSilent = true; // Disable collision events

        // Create dominoes
        this.createDominoes(scene);

        // Create Pipe
        this.createPipe(scene);

        // Create ball
        this.createBall(scene);
    }

    private async createPipe(scene: Scene3D) {
        // create a object
        const obj: Object3D = new Object3D();
        // add MeshRenderer to the object
        let mr: MeshRenderer = obj.addComponent(MeshRenderer);

        // build shape
        let shape: Vector3[] = [],
            vertexCount = 8,
            shapeRadius = 1;
        for (let i = 0; i < vertexCount; i++) {
            let angle = (Math.PI * 2 * i) / vertexCount;
            let point = new Vector3(Math.sin(angle), 0, Math.cos(angle)).multiplyScalar(shapeRadius);
            shape.push(point);
        }
        // build curve path
        let curve: Vector3[] = [],
            sectionCount = 44,
            modelRadius = 4;
        for (let i = 0; i < sectionCount; i++) {
            let angle = (Math.PI * 2 * i) / 22;
            modelRadius += (0.1 * i) / sectionCount;
            let offsetY = 0.6 - Math.sqrt(i / sectionCount);
            let point = new Vector3(Math.sin(angle), offsetY * 6, Math.cos(angle)).multiplyScalar(modelRadius);
            curve.push(point);
        }

        // build ExtrudeGeometry from shape & curve
        mr.geometry = new ExtrudeGeometry().build(shape, true, curve, 0.2);
        // set a pbr lit material
        let material = new LitMaterial();
        material.cullMode = 'none';
        material.depthCompare = 'always';
        material.blendMode = BlendMode.ADD;
        material.baseColor = new Color(0, 1, 0.5, 1.0);
        material.transparent = true;

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

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

        obj.localPosition = new Vector3(-30, 20, -3);
        scene.addChild(obj);

        let rigidbody = obj.addComponent(Rigidbody);
        rigidbody.shape = CollisionShapeUtil.createBvhTriangleMeshShape(obj);
        rigidbody.mass = 0;
    }

    // Create a series of dominoes with rigid bodies and arrange them in an S-shaped curve.
    private createDominoes(scene: Scene3D) {
        const width = 0.5;
        const height = 5;
        const depth = 2;

        const originX = -30;
        const originZ = 4.7;

        const totalDominoes = 40;
        const segmentLength = 2; // Distance between dominoes

        let previousX = originX;
        let previousZ = originZ;

        for (let i = 0; i < totalDominoes; i++) {
            let box = Object3DUtil.GetSingleCube(width, height, depth, Math.random(), Math.random(), Math.random());

            let angle = (Math.PI / (totalDominoes / 2)) * i;
            let x = originX + segmentLength * i;
            let z = originZ + Math.sin(angle) * 15; // Adjust sine curve amplitude for S-shape

            box.localPosition = new Vector3(x, height / 2, z);

            // Adjust each domino's rotation to align with the curve
            let deltaX = x - previousX;
            let deltaZ = z - previousZ;
            box.rotationY = i === 0 ? -48 : -Math.atan2(deltaZ, deltaX) * (180 / Math.PI);
            scene.addChild(box);
            previousX = x;
            previousZ = z;

            let rigidbody = box.addComponent(Rigidbody);
            rigidbody.shape = Rigidbody.collisionShape.createBoxShape(box);
            rigidbody.mass = 30;
            rigidbody.friction = 0.1;
            rigidbody.collisionEvent = (contactPoint, selfBody, otherBody) => {
                rigidbody.enableCollisionEvent = false; // Handle collision only once
                (box.getComponent(MeshRenderer).material as LitMaterial).baseColor = Color.random();
            };
        }
    }

    // Create a ball with a rigid body.
    private createBall(scene: Scene3D) {
        let ball = Object3DUtil.GetSingleSphere(0.8, 1, 0, 0);
        ball.name = 'ball';
        ball.localPosition = new Vector3(-30, 40, 1);
        scene.addChild(ball);

        let rigidbody = ball.addComponent(Rigidbody);
        rigidbody.shape = Rigidbody.collisionShape.createSphereShape(ball);
        rigidbody.mass = 50;
    }

    private debug(scene: Scene3D) {
        let gui = new dat.GUI();
        let f = gui.addFolder('PhysicsDebug');
        f.add(Physics.debugDrawer, 'enable');
        f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList);
        gui.add({
            ResetBall: () => {
                const ballObj = scene.getChildByName('ball') as Object3D;
                ballObj?.getComponent(Rigidbody).updateTransform(new Vector3(-30, 40, 1), Quaternion._zero, true);
            }
        }, 'ResetBall');
    }
}

new Sample_Dominoes().run();

Released under the MIT License