Babylon.js - Facets demo source

Back to demo

main.ts

import { runDemo } from "../shared/demoRunner";
import { createFacetsScene } from "./scene";

runDemo({ createScene: createFacetsScene });

scene.ts

import type { Engine } from "@babylonjs/core/Engines/engine";
import { Scene } from "@babylonjs/core/scene";
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";
import { PointLight } from "@babylonjs/core/Lights/pointLight";
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
import { Color3 } from "@babylonjs/core/Maths/math.color";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { CreateTorusKnot } from "@babylonjs/core/Meshes/Builders/torusKnotBuilder";
import { CreateSphere } from "@babylonjs/core/Meshes/Builders/sphereBuilder";
import { SolidParticleSystem } from "@babylonjs/core/Particles/solidParticleSystem";
import type { SolidParticle } from "@babylonjs/core/Particles/solidParticle";

export function createFacetsScene(engine: Engine, canvas: HTMLCanvasElement): Scene {
    const scene = new Scene(engine);
    scene.clearColor = new Color3(0.15, 0.15, 0.3).toColor4(1);

    const camera = new ArcRotateCamera("Camera", 0, 0, 0, Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    camera.setPosition(new Vector3(0, 10.0, -150.0));

    const light = new HemisphericLight("light1", new Vector3(0, 1, 0), scene);
    light.intensity = 0.2;
    const pl = new PointLight("pl", camera.position, scene);
    pl.intensity = 0.9;

    new StandardMaterial("sm", scene);

    const gravity = new Vector3(0, -0.01, 0);
    const ballOrigin = new Vector3(-20.0, 60.0, 0.0);
    const restitution = 0.6;
    const speed = 0.05;
    const sceneLimit = ballOrigin.y + 2.0;

    const model1 = CreateTorusKnot("m1", { radius: 8.0, tube: 1.2, radialSegments: 48 }, scene);
    const posfunc = (p: SolidParticle): void => {
        p.position.x = ((0.5 - Math.random()) * sceneLimit) / 1.5;
        p.position.y = (0.5 - Math.random()) * sceneLimit;
        p.position.z = ((0.5 - Math.random()) * sceneLimit) / 1.5;
        p.rotation.x = 3.2 * Math.random();
        p.rotation.y = 3.2 * Math.random();
        p.rotation.z = 3.2 * Math.random();
        p.scaling.x = 0.5 + Math.random() * 0.5;
        p.scaling.y = p.scaling.x;
        p.scaling.z = p.scaling.x;
    };
    const meshSPS = new SolidParticleSystem("m", scene, { updatable: false });
    meshSPS.addShape(model1, 30, { positionFunction: posfunc });
    meshSPS.buildMesh();
    model1.dispose();

    meshSPS.refreshVisibleSize();
    const mesh = meshSPS.mesh;
    mesh.partitioningSubdivisions = 40;
    mesh.updateFacetData();

    const ballRadius = 1.0;
    const ballNb = 2000;
    const radiusSquared = ballRadius * ballRadius;
    const ball = CreateSphere("b", { diameter: ballRadius * 2.0, segments: 3 }, scene);
    const sps = new SolidParticleSystem("sps", scene);
    sps.addShape(ball, ballNb);
    ball.dispose();
    sps.buildMesh();
    sps.isAlwaysVisible = true;
    sps.computeParticleRotation = false;
    sps.computeParticleTexture = false;

    sps.recycleParticle = function (p: SolidParticle): SolidParticle {
        p.position.copyFrom(this.vars.origin);
        p.velocity.x = (Math.random() - 0.5) * this.vars.speed * 3.0 + this.vars.speed * 3.0;
        p.velocity.z = (Math.random() - 0.5) * this.vars.speed * 6.0;
        p.velocity.y = Math.random() * this.vars.speed * 10.0;
        p.scaling.x = 1.0;
        p.scaling.y = p.scaling.x;
        p.scaling.z = p.scaling.x;
        return p;
    };
    sps.initParticles = function (): void {
        for (let p = 0; p < this.nbParticles; p++) {
            this.recycleParticle(sps.particles[p]);
            this.particles[p].color!.r = 0.4 + Math.random() * 0.6;
            this.particles[p].color!.g = 0.4 + Math.random() * 0.6;
            this.particles[p].color!.b = 0.4 + Math.random() * 0.6;
            this.particles[p].rotation.x = Math.random() * 3.2;
            this.particles[p].rotation.y = Math.random() * 3.2;
            this.particles[p].rotation.z = Math.random() * 3.2;
        }
    };

    const projected = Vector3.Zero();
    const facetNorm = Vector3.Zero();
    const normPos = Vector3.Zero();
    const tmpVect = Vector3.Zero();

    sps.updateParticle = function (p: SolidParticle): SolidParticle {
        if (
            Math.abs(p.position.x) > sceneLimit ||
            Math.abs(p.position.y) > sceneLimit ||
            Math.abs(p.position.z) > sceneLimit
        ) {
            sps.recycleParticle(p);
            return p;
        }

        p.velocity.addInPlace(gravity);
        p.position.addInPlace(p.velocity);

        const closest = mesh.getClosestFacetAtCoordinates(
            p.position.x,
            p.position.y,
            p.position.z,
            projected,
            true,
            true
        );
        if (closest !== null) {
            mesh.getFacetNormalToRef(closest, facetNorm);

            p.position.subtractToRef(projected, tmpVect);

            if (tmpVect.lengthSquared() < radiusSquared) {
                const tmpDotVel = Vector3.Dot(p.velocity, facetNorm);
                p.velocity.x = (p.velocity.x - 2.0 * tmpDotVel * facetNorm.x) * restitution;
                p.velocity.y = (p.velocity.y - 2.0 * tmpDotVel * facetNorm.y) * restitution;
                p.velocity.z = (p.velocity.z - 2.0 * tmpDotVel * facetNorm.z) * restitution;

                facetNorm.scaleToRef(ballRadius * 1.01, normPos);
                projected.addToRef(normPos, p.position);
            }
        }
        p.scaling.x = 0.4 + Math.abs(p.velocity.x);
        p.scaling.y = 0.4 + Math.abs(p.velocity.y);
        p.scaling.z = 0.4 + Math.abs(p.velocity.z);
        return p;
    };

    sps.vars.origin = ballOrigin.clone();
    sps.vars.speed = speed;
    sps.initParticles();
    sps.setParticles();

    let k = 0.0;
    scene.registerBeforeRender(() => {
        mesh.rotation.y += 0.01;
        mesh.rotation.x = 0.6 * Math.sin(k);
        sps.setParticles();
        k += 0.02;
    });

    scene.onDisposeObservable.add(() => {
        mesh.disableFacetData();
    });

    return scene;
}