Advanced Parametric 3D Model with BabylonJS & Bitbybit
This tutorial explores building a sophisticated, interactive parametric 3D model utilizing Bitbybit's robust integration with the BabylonJS rendering engine. We will construct a configurable "Hex Shell" 3D shape, where its geometry is dynamically controlled by parameters from a lil-gui interface. The underlying complex CAD operations will be handled by the OpenCascade (OCCT) kernel, accessed via Bitbybit.
You can see what the results of this app look like (rendered in Unreal Engine):
Through this example, you will learn to:
- Set up a BabylonJS scene and rendering engine.
- Initialize Bitbybit with specific geometry kernels (OCCT in this case) for a BabylonJS environment.
- Create intricate parametric geometry using Bitbybit's OCCT API.
- Employ
lil-guito generate a user interface for real-time control over model parameters. - Dynamically update the 3D model in the BabylonJS scene as UI parameters change.
- Implement a Level of Detail (LOD) strategy for efficient shape generation during interaction and finalization.
- Integrate functionality to export the 3D model in common formats like STEP, STL, and GLB.
We are providing a higher level explanations of the codebase below, but for working reference always check this live example on StackBlitz, which, as platform evolves could change slightly.
Find the source code on Bitbybit GitHub Examples
Project Structure Overview
A well-organized project structure is key for managing more complex applications. This example typically involves:
index.html: The entry point for the browser, hosting the canvas and loading our scripts.style.css: Contains basic CSS for page layout, canvas presentation, and UI elements such as a loading spinner.src/main.ts: The primary TypeScript file that orchestrates the entire application, from scene setup and Bitbybit initialization to GUI interactions and geometry updates.src/models/: A directory to define the data structures (TypeScript interfaces/types and initial values) for:model.ts: Parameters controlling the 3D shape's geometry.current.ts: References to current scene objects (meshes, lights, GUI instance).
src/helpers/: This directory houses utility functions, each with a distinct responsibility:init-babylonjs.ts: Responsible for setting up the BabylonJSEngine,Scene,Camera, default lighting, and a ground plane.create-shape.ts: The core of the geometric logic, containing the functions that use Bitbybit's OCCT API to generate the parametric "Hex Shell" model.create-gui.ts: Configures thelil-guipanel, linking its controls to the parameters inmodel.tsand connecting them to the geometry update functions.downloads.ts: Implements the logic for exporting the generated 3D model to STEP, STL, and GLB file formats.gui-helper.ts: Provides simple utility functions for managing the GUI's visual state (e.g., showing/hiding a loading spinner, enabling/disabling GUI controls).
Since version 1.0.0-rc.1, Bitbybit provides a simplified initBitByBit() helper function that handles all worker creation and kernel initialization automatically by loading kernels from CDN. This eliminates the need for manual worker file setup. For details, see our BabylonJS Integration Starter Tutorial. If you need to host assets on your own infrastructure, see Self-Hosting Assets.
1. HTML Setup (index.html)
The index.html file provides the basic webpage structure.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bitbybit & BabylonJS Hex Shell Example</title>
</head>
<body>
<a
class="logo"
href="https://bitbybit.dev"
target="_blank"
rel="noopener noreferrer"
>
<img
alt="Logo of Bit by bit developers company"
src="https://bitbybit.dev/assets/logo-gold-small.png"
/>
<div>bitbybit.dev</div>
<br />
<div>support the mission - subscribe</div>
</a>
<canvas id="babylon-canvas"></canvas>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Key elements:
- A
<canvas id="babylon-canvas">element: This is where BabylonJS will render the 3D scene. - A
<script>tag: Loads the main application logic fromsrc/main.ts.
2. Main Application Logic (src/main.ts)
This file is the central coordinator for our application, bringing together the BabylonJS scene, Bitbybit's functionalities, the GUI, and the dynamic update logic.
import './style.css';
import {
BitByBitBase,
Inputs,
initBitByBit,
type InitBitByBitOptions,
} from '@bitbybit-dev/babylonjs';
import { model, current } from './models';
import {
initBabylonJS,
createGui,
createShapeLod1,
createShapeLod2,
createDirLightsAndGround,
disableGUI,
enableGUI,
hideSpinner,
showSpinner,
downloadGLB,
downloadSTL,
downloadStep,
} from './helpers';
// Configuration for enabling Bitbybit geometry kernels
const options: InitBitByBitOptions = {
enableOCCT: true, // This example primarily uses OCCT for its CAD operations
enableJSCAD: false,
enableManifold: false,
};
// Application entry point
start();
async function start() {
// 1. Initialize the BabylonJS Engine and Scene
const { scene, engine } = initBabylonJS();
// 2. Initialize BitByBitBase for BabylonJS
const bitbybit = new BitByBitBase();
// Add default lighting and a ground plane to the scene
createDirLightsAndGround(bitbybit, current);
// Initialize Bitbybit with the selected geometry kernels using the helper function.
// This automatically creates workers and loads kernels from CDN.
await initBitByBit(scene, bitbybit, options);
// Variables to store the final OCCT shape and intermediate shapes for cleanup
let finalShape: Inputs.OCCT.TopoDSShapePointer | undefined;
let shapesToClean: Inputs.OCCT.TopoDSShapePointer[] = []; // Crucial for OCCT memory management
// 3. Connect download functions to the model object (for GUI buttons)
model.downloadStep = () => downloadStep(bitbybit, finalShape);
model.downloadGLB = () => downloadGLB(bitbybit);
model.downloadSTL = () => downloadSTL(bitbybit, finalShape);
// 4. Create the GUI panel and link its controls
createGui(current, model, updateShape);
// 5. Setup a simple rotation animation for the generated groups
const rotationSpeed = 0.0005;
const rotateGroup = () => {
if (
model.rotationEnabled &&
current.group1 && // 'current' holds references to BabylonJS Meshes/TransformNodes
current.group2 &&
current.dimensions
) {
current.group1.rotation.y -= rotationSpeed;
current.group2.rotation.y -= rotationSpeed;
current.dimensions.rotation.y -= rotationSpeed;
}
};
// Register the rotation function with BabylonJS's before render observable
scene.onBeforeRenderObservable.add(() => rotateGroup());
// Start BabylonJS's main render loop
engine.runRenderLoop(() => {
scene.render(true, false); // Render the scene on each frame
});
// 6. Generate and draw the initial 3D model (using Level of Detail 1 for faster preview)
finalShape = await createShapeLod1(
bitbybit,
scene,
model, // Contains current parameters from the GUI/defaults
shapesToClean, // Array for OCCT shapes that need explicit cleanup
current // Stores references to created BabylonJS meshes/groups
);
// 7. Define the function to update the shape when GUI parameters change
async function updateShape(finish: boolean) {
disableGUI(); // Temporarily disable GUI during processing
showSpinner(); // Show a loading indicator
// Dispose of previously created BabylonJS Meshes to clear the scene
// The second parameter 'true' ensures child meshes are also disposed.
current.group1?.dispose(false, true);
current.group2?.dispose(false, true);
current.dimensions?.dispose(false, true);
// Note: OCCT shapes are managed and cleaned up within the createShapeLod1/2 functions
// using the 'shapesToClean' array.
if (finish) { // 'finish' is true if the "Finalize" GUI button is pressed
// Generate the shape with higher detail (LOD2)
finalShape = await createShapeLod2(
bitbybit, scene, model, shapesToClean, current
);
} else { // Default update (e.g., when a slider is dragged)
// Generate the shape with lower detail for faster interactive feedback (LOD1)
finalShape = await createShapeLod1(
bitbybit, scene, model, shapesToClean, current
);
}
hideSpinner(); // Hide loading indicator
enableGUI(); // Re-enable GUI
}
}
Explanation of main.ts:
- Imports: Includes
BitByBitBase,Inputs,initBitByBit, andInitBitByBitOptionsfrom@bitbybit-dev/babylonjs, along with local models and helper functions. options: Specifies that only the OCCT kernel should be enabled and initialized for this particular application, as it's the one used for the Hex Shell's CAD operations.start()function (Main Application Flow):- BabylonJS Setup:
initBabylonJS()initializes the BabylonJSEngineandScene. - Bitbybit Initialization: An instance of
BitByBitBaseis created. TheinitBitByBit()helper function is then called to initialize the Bitbybit instance with the scene and the kernel options. This helper automatically creates web workers, loads kernel WASM files from CDN, and waits for the selected kernels to be ready. - Scene Elements:
createDirLightsAndGround()adds basic lighting and a ground plane using Bitbybit's BabylonJS helpers. - Shape Management:
finalShapewill store the primary OCCT geometry.shapesToCleanis an array used to track intermediate OCCT shapes created during geometry generation; these need to be explicitly deleted usingbitbybit.occt.deleteShapes()to manage memory effectively, especially with complex CAD operations. - Download Functions: The
downloadStep,downloadGLB, anddownloadSTLfunctions (fromhelpers/downloads.ts) are attached to themodelobject so they can be easily triggered by GUI buttons. - GUI Creation:
createGui()sets up thelil-guipanel, linking its controls to the parameters defined inmodel.ts. Changes in the GUI will trigger theupdateShapefunction. - Animation: A simple
rotateGroupfunction is defined and added to BabylonJS'sscene.onBeforeRenderObservableto animate parts of the model ifmodel.rotationEnabledis true. - Render Loop:
engine.runRenderLoop()starts BabylonJS's continuous rendering process. - Initial Shape:
createShapeLod1()is called to generate and display the initial version of the Hex Shell model using a lower level of detail for quicker startup. updateShape(finish: boolean)function: This is the core of the dynamic updates.- It's triggered by GUI interactions.
- It disables the GUI and shows a spinner.
- It disposes of previous BabylonJS meshes using
mesh.dispose(false, true)to clear the old geometry from the scene. - It then calls either
createShapeLod1(for faster, interactive previews) orcreateShapeLod2(for a more detailed, finalized version) based on thefinishflag. - After regeneration, it hides the spinner and re-enables the GUI.
- BabylonJS Setup:
3. Helper Functions (src/helpers/)
Helper functions promote modularity and code organization.
init-babylonjs.ts
initBabylonJS(): This module is responsible for all the initial BabylonJS setup. It:- Gets the
<canvas>element. - Creates the BabylonJS
Engine. - Creates the
Sceneand sets basic properties like clear color. - Sets up an
ArcRotateCamerafor user navigation. - Adds a
HemisphericLightfor ambient illumination. - Initializes the render loop via
engine.runRenderLoop(). - Handles window resize events to keep the rendering correct.
- Gets the
createDirLightsAndGround(): This helper specifically adds directional lights (important for casting shadows and defining highlights) and a ground mesh (e.g., a cylinder or plane) to the BabylonJS scene. It might use Bitbybit's BabylonJS API helpers for convenience.
create-shape.ts (Core OCCT Geometry Logic)
This file is where the detailed CAD work for the "Hex Shell" happens, using Bitbybit's OCCT API. Its structure typically involves:
- LOD Functions:
createShapeLod1for a faster, simplified version andcreateShapeLod2for the full-detail version. - OCCT Memory Management: A crucial aspect. Before generating new OCCT geometry, it calls
await bitbybit.occt.deleteShapes({ shapes: shapesToClean })to free memory from shapes created in the previous update. Any new intermediate OCCT shapes created during the current generation are added to theshapesToCleanarray. - Parametric Geometry Creation:
- Uses
modelparameters (e.g.,model.uHex,model.height) to drive the dimensions and features. - Employs a sequence of OCCT operations via
bitbybit.occt.*(e.g.,shapes.wire.createEllipseWire,transforms.rotate,operations.loft,operations.offset,shapes.face.subdivideToHexagonWires,shapes.compound.makeCompound). - The example creates a base lofted surface, then subdivides it into hexagonal wire patterns on several offset surfaces, and finally creates solids by lofting between these patterns.
- Uses
- Dimensioning (Optional but present in example): The code also includes logic to create OCCT 3D dimension entities (lines, angles, labels) using
bitbybit.occt.dimensions.*functions, which are then compounded with the main shape. - Drawing with BabylonJS Integration:
- After OCCT shapes are generated,
bitbybit.draw.drawAnyAsync({ entity: occtShape, options: drawOptions })is called. The@bitbybit-dev/babylonjsversion of this function converts the OCCT shape data into BabylonJSMeshobjects. - It creates and applies BabylonJS materials (e.g.,
PBRMetallicRoughnessMaterialfrom@babylonjs/core) to these meshes. - The resulting meshes are often parented to BabylonJS
Meshobjects (acting as groups, stored incurrent.group1,current.group2) for organization and collective transformations (like rotation).
- After OCCT shapes are generated,
The specific sequence of OCCT operations in create-shape.ts (lofting, offsetting, subdividing, compounding) is tailored to generate the intricate "Hex Shell" design. To understand each step in detail, you would refer to the Bitbybit API documentation for the corresponding OCCT functions.
create-gui.ts (BabylonJS Context)
This sets up the lil-gui panel. Its operation is very similar across different rendering engines, but material updates are engine-specific.
// ... (other GUI setup) ...
gui
.addColor(model, 'color1')
.name('Color 1')
.onChange((value: string) => {
// Iterate through meshes in the group and update their material
current.group1?.getChildren().forEach((node) => { // A group node
node.getChildMeshes().forEach(mesh => { // Get actual meshes
if (mesh.material && mesh.material instanceof PBRMetallicRoughnessMaterial) {
const mat = mesh.material as PBRMetallicRoughnessMaterial;
mat.baseColor = Color3.FromHexString(value); // BabylonJS Color3
}
});
});
});
// Similar logic for 'color2' and current.group2
- When a color control in the GUI changes, the callback iterates through the child meshes of the relevant group (
current.group1orcurrent.group2). - It accesses the mesh's
materialproperty. - It updates the material's
baseColor(assumingPBRMetallicRoughnessMaterial) using BabylonJS'sColor3.FromHexString().
downloads.ts (BabylonJS Context)
This module handles file exports:
downloadStep(): Usesbitbybit.occt.io.saveShapeSTEP()to save thefinalShape(the OCCT compound object) as a STEP file. This is an OCCT-level operation.downloadSTL(): Your example usesbitbybit.occt.io.saveShapeStl()to export thefinalShapefrom OCCT directly as an STL. This is efficient for CAD-derived STL.downloadGLB(): This usesbitbybit.babylon.io.exportGLB(). This function is specific to the@bitbybit-dev/babylonjspackage and exports the current state of the BabylonJS scene (or selected parts of it) to a GLB file.
gui-helper.ts
These DOM manipulation utilities (disable/enable GUI, show/hide spinner) are generic.
4. Data Models (src/models/)
model.ts: Defines theModeltype for geometric parameters and an initialmodelobject (same structure as before).current.ts: This is where BabylonJS types become apparent. TheCurrenttype now holds references to BabylonJS objects:
import { DirectionalLight, Mesh } from '@babylonjs/core'; // BabylonJS specific imports
import { GUI } from 'lil-gui';
export type Current = {
group1: Mesh | undefined; // BabylonJS Mesh (can act as a group/parent)
group2: Mesh | undefined; // BabylonJS Mesh
dimensions: Mesh | undefined; // BabylonJS Mesh for dimensions
light1: DirectionalLight | undefined; // BabylonJS Light
ground: Mesh | undefined; // BabylonJS Mesh for the ground
gui: GUI | undefined;
};
export const current: Current = { /* ... initial undefined values ... */ };
5. Styles (style.css)
The CSS primarily styles the page layout, logo, and the loading spinner. A key selector is for the canvas:
/* ... other styles ... */
#babylon-canvas { /* Targets the canvas ID used in index.html */
width: 100%;
height: 100%;
overflow: hidden;
position: absolute; /* Often used for full-viewport canvas */
outline: none;
}
/* ... spinner styles ... */
Conclusion
This advanced tutorial has demonstrated how to construct a complex, parametric 3D model using Bitbybit's OCCT capabilities and render it interactively with BabylonJS. You've seen how to:
- Structure a project with separate modules for different functionalities.
- Initialize and use Bitbybit within a BabylonJS engine and scene context.
- Implement parametric geometry generation that responds to GUI controls.
- Manage different Levels of Detail (LOD) for performance.
- Handle OCCT memory by cleaning up intermediate shapes.
- Dispose of BabylonJS meshes before redrawing to update the scene.
- Integrate export functionalities for common 3D file formats.
This robust approach allows for the creation of highly interactive and detailed 3D web applications, leveraging the strengths of both Bitbybit's CAD engine and BabylonJS's rendering power.
