Script: 3D Chain Physics Havok

3D Chain Physics Havok picture
Type
Typescript logo indicatortypescript
Author
matas
Date Created
Nov 10, 2023, 3:04:49 PM
Last Edit Date
Jun 27, 2025, 10:00:23 AM

Project Information

This project showcases the capabilities of the physics engine called Havok. We combine CAD algorithms together with 3D physics simulation to create a virtual world where bodies bounce of each other.

View Full Project

Script Code

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

const scene = bitbybit.babylon.scene.getScene();
const camera = scene.activeCamera as BABYLON.ArcRotateCamera;
camera.position = new BABYLON.Vector3(80, 45, 10);
camera.target = new BABYLON.Vector3(0, 25, 0);
const chainMaterial = new BABYLON.PBRMetallicRoughnessMaterial("chain");
chainMaterial.metallic = 0.8;
chainMaterial.roughness = 0.15;
chainMaterial.baseColor = new BABYLON.Color3(0.1, 0.1, 0.1);

const start = async () => {

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

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

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

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

    const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 8, segments: 32 }, scene);
    const material = new BABYLON.PBRMetallicRoughnessMaterial("spheres", scene);
    material.baseColor = new BABYLON.Color3(0, 0, 1);
    material.metallic = 0.7;
    material.roughness = 0.1;
    sphere.position.y = 4;
    sphere.material = material;

    const step = 10;
    const limit = 5;
    for (let x = 0; x < limit; x++) {
        for (let z = 0; z < limit; z++) {
            const sph = sphere.clone(`${x}-${z}`);
            sph.position = new BABYLON.Vector3(x * step - 60, 20, z * step - (step * limit / 2));
            new BABYLON.PhysicsAggregate(sph, BABYLON.PhysicsShapeType.SPHERE, { mass: 0.01, restitution: 0.75 }, scene);
        }
    }
    new BABYLON.PhysicsAggregate(sphere, BABYLON.PhysicsShapeType.SPHERE, { mass: 0.3, restitution: 0.75 }, scene);
    new BABYLON.PhysicsAggregate(boxMeshCollision, BABYLON.PhysicsShapeType.MESH, { mass: 0 }, scene);
    const positionStep = 4.5;
    const times = 8;

    for (let i = 0; i < times; i++) {
        const cloneVisual = chainModel.visualMesh.clone(`m-${i}`, undefined, false);
        cloneVisual.getChildMeshes().forEach(cm => {
            if (cm instanceof BABYLON.LinesMesh) {
                cm.enableEdgesRendering();
                cm.edgesWidth = 1;
            } else {
                cm.material.zOffset = 2;
            }
        })
        const cloneCollision = chainModel.collisionMesh.clone(`m-${i}`);
        cloneVisual.position = new BABYLON.Vector3(i * positionStep, 45, 0);
        cloneCollision.position = new BABYLON.Vector3(i * positionStep, 45, 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.5, restitution: 0.75 }, 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.5, 45, 0);
    ballChain.collisionMesh.position = new BABYLON.Vector3(times * positionStep - 0.5, 45, 0);


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

    const light = new Bit.Inputs.BabylonScene.DirectionalLightDto();
    light.intensity = 1;
    light.direction = [200, -200, 200];
    light.shadowBias = 0.01;
    light.shadowGeneratorMapSize = 2056;
    bitbybit.babylon.scene.drawDirectionalLight(light);

    const skyboxOptions = new Bit.Inputs.BabylonScene.SkyboxDto();
    skyboxOptions.skybox = Bit.Inputs.Base.skyboxEnum.city;
    skyboxOptions.blur = 0.4;
    bitbybit.babylon.scene.enableSkybox(skyboxOptions);
    
}

start();


async function createChain() {

    const chainOCCT = await createOCCTChains();

    const options = new Bit.Inputs.Draw.DrawOcctShapeOptions();
    options.precision = 0.005;
    options.drawEdges = false;
   
    options.faceMaterial = chainMaterial;

    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: 4, center: [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.faceMaterial = chainMaterial;
    const visualMesh = await bitbybit.draw.drawAnyAsync({
        entity: shapeVisual,
        options
    });

    options.precision = 0.1;
    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 createBox() {

    const box = await bitbybit.occt.shapes.solid.createBox({
        width: 200,
        length: 200,
        height: 30,
        center: [0, 13, 0],
    })

    const boxCutout = await bitbybit.occt.shapes.solid.createBox({
        width: 190,
        length: 190,
        height: 30,
        center: [0, 15, 0],
    })

    const diff = await bitbybit.occt.booleans.difference({
        shape: box,
        shapes: [boxCutout],
        keepEdges: false
    })

    const boxDrawOptions = new Bit.Inputs.Draw.DrawOcctShapeOptions();
    boxDrawOptions.precision = 0.1;
    boxDrawOptions.faceColour = "#111111";
    const boxMesh = await bitbybit.draw.drawAnyAsync({
        entity: diff,
        options: boxDrawOptions
    })

    return boxMesh

}

async function createOCCTChains() {
    const chainOffset = 0.5;
    const chaingRadiusMinor = 1;
    const chainRadiusMajor = 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