Script: Bowling Pin

Bowling Pin picture
Type
Typescript logo indicatortypescript
Date Created
Jun 14, 2024, 12:40:03 PM
Last Edit Date
Jan 8, 2026, 6:29:59 PM

Project Information

This is a small physics simulation with gaussian splatting 3D scanned human that serves as a bowling pin.

View Full Project

Script Code

const { occt } = bitbybit;

const engine = bitbybit.babylon.engine.getEngine();

const scene = bitbybit.babylon.scene.getScene();
const camera = scene.activeCamera as BABYLON.ArcRotateCamera;
camera.position = new BABYLON.Vector3(-10, 1.6, -15);
camera.target = new BABYLON.Vector3(0, 2.5, 0);


const start = async () => {

    const skyboxDto = new Bit.Inputs.BabylonScene.SkyboxDto();
    skyboxDto.skybox = Bit.Inputs.Base.skyboxEnum.clearSky;
    bitbybit.babylon.scene.enableSkybox(skyboxDto);

    const chainModel = await createChain();
    const ballChain = await createBallChain();

    chainModel.collisionMesh.isVisible = false;
    ballChain.collisionMesh.isVisible = false;

    bitbybit.babylon.scene.enablePhysics({ vector: [0, -0.0981, 0] });

    const boxMeshRes = await createCylBase();
    const boxMeshCollision = boxMeshRes.getChildMeshes()[0] as BABYLON.Mesh;

    const asset = await bitbybit.asset.getFile({ fileName: "matas.splat" });
    const assetUrl = await bitbybit.asset.createObjectURL({ file: asset });

    const gs = await bitbybit.babylon.gaussianSplatting.create({
        url: assetUrl
    });
    gs.rotate(new BABYLON.Vector3(0, 1, 0), Math.PI);
    gs.position = new BABYLON.Vector3(0, 0.3, 0);
    const podium = BABYLON.MeshBuilder.CreateCylinder("sphere", { height: 0.3, diameter: 0.8, subdivisions: 10 }, scene);
    const podiumShadow = BABYLON.MeshBuilder.CreateCylinder("sphere", { height: 3, diameter: 0.8, subdivisions: 10 }, scene);
    podiumShadow.position.y = 1.84;
    podiumShadow.layerMask = 0x10000000;
    podium.position.y = 0.15;
    const cylinder = BABYLON.MeshBuilder.CreateCylinder("sphere", { height: 3, diameter: 0.8, subdivisions: 10 }, scene);
    cylinder.position.y = 1.5;
    cylinder.visibility = 0;
    cylinder.addChild(gs);
    cylinder.addChild(podiumShadow);
    cylinder.addChild(podium);

    const step = 2;
    const limit = 3;
    const positions = [];
    for (let x = 0; x < limit; x++) {
        for (let z = 0; z < limit; z++) {
            positions.push(new BABYLON.Vector3(x * step - 8, 1.5, z * step - (step * limit / 2) + step / 2));
        }
    }

    const gaussianPromises = positions.map(_ => {
        return bitbybit.babylon.gaussianSplatting.create({
            url: assetUrl
        });
    });

    const gaussians = await Promise.all(gaussianPromises);
    console.log(gaussians);
    gaussians.forEach((g, index) => {
        const cyl = BABYLON.MeshBuilder.CreateCylinder("sphere", { height: 3.0, diameter: 0.8, subdivisions: 1.0 }, scene);
        cyl.position = positions[index];
        cyl.visibility = 1;
        cyl.visibility = 0;
        g.position.y = -1.2;
        g.parent = cyl;
        g.rotate(new BABYLON.Vector3(0, 1, 0), Math.PI);
        const clonedPodiumShadow = podiumShadow.clone();
        clonedPodiumShadow.parent = cyl;
        const clonedPodium = podium.clone();
        clonedPodium.parent = cyl;
        new BABYLON.PhysicsAggregate(cyl, BABYLON.PhysicsShapeType.CYLINDER, { mass: 0.001, restitution: 0.75 }, scene);
    });

    new BABYLON.PhysicsAggregate(cylinder, BABYLON.PhysicsShapeType.CYLINDER, { mass: 0.03, restitution: 0.75 }, scene);
    new BABYLON.PhysicsAggregate(boxMeshCollision, BABYLON.PhysicsShapeType.MESH, { mass: 0 }, scene);
    const positionStep = 0.45;
    const times = 8;

    const chaingHeight = 5;
    for (let i = 0; i < times; i++) {
        const cloneVisual = chainModel.visualMesh.clone(`m-${i}`);
        const cloneCollision = chainModel.collisionMesh.clone(`m-${i}`);
        cloneVisual.position = new BABYLON.Vector3(i * positionStep, chaingHeight, 0);
        cloneCollision.position = new BABYLON.Vector3(i * positionStep, chaingHeight, 0);
        if (i % 2 === 0) {
            cloneVisual.rotateAround(cloneVisual.position, new BABYLON.Vector3(1, 0, 0), Math.PI / 2);
            cloneCollision.rotateAround(cloneCollision.position, new BABYLON.Vector3(1, 0, 0), Math.PI / 2);
        }
        cloneCollision.isVisible = false;
        if (i !== 0) {
            new BABYLON.PhysicsAggregate(cloneVisual, BABYLON.PhysicsShapeType.MESH, { mesh: cloneCollision, mass: 0.05, restitution: 0.5 }, scene)
        } else {
            new BABYLON.PhysicsAggregate(cloneVisual, BABYLON.PhysicsShapeType.MESH, { mesh: cloneCollision, mass: 0, restitution: 0.75 }, scene)
        }
    }

    chainModel.visualMesh.getChildMeshes().forEach(m => m.isVisible = false);

    ballChain.visualMesh.rotateAround(ballChain.visualMesh.position, new BABYLON.Vector3(1, 0, 0), Math.PI / 2);
    ballChain.collisionMesh.rotateAround(ballChain.collisionMesh.position, new BABYLON.Vector3(1, 0, 0), Math.PI / 2);
    ballChain.visualMesh.position = new BABYLON.Vector3(times * positionStep - 0.05, chaingHeight, 0);
    ballChain.collisionMesh.position = new BABYLON.Vector3(times * positionStep - 0.05, chaingHeight, 0);


    new BABYLON.PhysicsAggregate(ballChain.visualMesh, BABYLON.PhysicsShapeType.MESH, { mesh: ballChain.collisionMesh, mass: 0.2, restitution: 0.75 }, scene)

    const light = new Bit.Inputs.BabylonScene.DirectionalLightDto();
    light.intensity = 3;
    light.direction = [-100, -100, -100];
    light.shadowGeneratorMapSize = 4000;
    light.shadowDarkness = 0;
    bitbybit.babylon.scene.drawDirectionalLight(light);

}

start();


async function createChain() {

    const chainOCCT = await createOCCTChains();

    const options = new Bit.Inputs.Draw.DrawOcctShapeOptions();
    options.precision = 0.001;
    options.drawEdges = false;
    options.drawTwoSided = false;
    options.faceColour = "#0000ff";

    const visualMesh = await bitbybit.draw.drawAnyAsync({
        entity: chainOCCT.fillet,
        options
    });

    options.precision = 0.1;
    options.drawEdges = false;

    const collisionMeshRes = await bitbybit.draw.drawAnyAsync({
        entity: chainOCCT.extruded,
        options
    });

    const collisionMesh = collisionMeshRes.getChildMeshes()[0] as BABYLON.Mesh;

    return { visualMesh, collisionMesh }
}

async function createBallChain() {
    const chainOCCT = await createOCCTChains();

    const sphere = await bitbybit.occt.shapes.solid.createSphere({ radius: 0.4, center: [0.6, 0, 0] });

    const shapeCollision = await bitbybit.occt.booleans.union({
        shapes: [chainOCCT.extruded, sphere],
        keepEdges: false
    })

    const shapeVisual = await bitbybit.occt.booleans.union({
        shapes: [chainOCCT.fillet, sphere],
        keepEdges: false
    })

    const options = new Bit.Inputs.Draw.DrawOcctShapeOptions();
    options.precision = 0.005;
    options.drawEdges = false;
    options.drawTwoSided = false;
    options.faceColour = "#0000ff";
    const visualMesh = await bitbybit.draw.drawAnyAsync({
        entity: shapeVisual,
        options
    });

    options.precision = 0.01;
    options.drawEdges = false;

    const collisionMeshRes = await bitbybit.draw.drawAnyAsync({
        entity: shapeCollision,
        options
    });

    const collisionMesh = collisionMeshRes.getChildMeshes()[0] as BABYLON.Mesh;

    return { visualMesh, collisionMesh }
}

async function createCylBase() {

    const cyl = await bitbybit.occt.shapes.solid.createCylinder({
        radius: 15,
        height: 0.1,
        center: [-4, -0.1, 0],
    })

    const boxDrawOptions = new Bit.Inputs.Draw.DrawOcctShapeOptions();
    boxDrawOptions.precision = 0.01;
    boxDrawOptions.drawEdges = false;
    boxDrawOptions.faceColour = "#7766ff";

    const boxMesh = await bitbybit.draw.drawAnyAsync({
        entity: cyl,
        options: boxDrawOptions
    })

    return boxMesh

}

async function createOCCTChains() {
    const chainOffset = 0.05;
    const chaingRadiusMinor = 0.1;
    const chainRadiusMajor = 0.3;
    const radiuses = [[chaingRadiusMinor, chainRadiusMajor - chainOffset], [chaingRadiusMinor + chainOffset, chainRadiusMajor + chainOffset]];

    const contourEllipses = await Promise.all(radiuses.map((r) => {
        return bitbybit.occt.shapes.wire.createEllipseWire({
            radiusMinor: r[0],
            radiusMajor: r[1],
            center: [0, 0, -chainOffset / 2],
            direction: [0, 0, 1],
        })
    }));

    const loft = await bitbybit.occt.operations.loft({
        shapes: contourEllipses,
        makeSolid: false
    });

    const extruded = await bitbybit.occt.operations.extrude({
        shape: loft,
        direction: [0, 0, chainOffset],
    });

    const fillet = await bitbybit.occt.fillets.filletEdges({
        shape: extruded,
        radius: chainOffset / 3
    });

    return { extruded, fillet };
}
Plans & Pricing

Choose Your Plan

Editor plans for 3D development, API keys for server-side CAD algorithms

B2B

ENTERPRISE

Custom pricing

Custom software development, dedicated servers & CAD automation at scale.

CAD Automation & Software
  • Custom software development
  • Cloud CAD automation pipelines
  • 3D configurators (STEP & GLTF)
  • Batch export jobs
  • Custom algorithms & deployment
Infrastructure & Support
  • Custom compute allocation
  • Dedicated / VPS server tenants
  • Long-running computation jobs
  • Custom upload limits & overage
  • SLA & premium support