Skip to content

Commit d11c87d

Browse files
Implemented examples for generic texture and materials on draw level of bitbybit that work with all supported game engines via unified API. Removed backFaceCulling property from material - generic materials use easier to understand doubleSided property name. Fixed some worker issues that existed in threejs due to faceMaterial deletion.
1 parent c81584c commit d11c87d

15 files changed

Lines changed: 347 additions & 178 deletions

File tree

examples/vite/babylonjs/starter-template/src/main.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ async function start() {
5454

5555
// --- 4. Create Drawing Examples (Lines, Points, Curves, etc.) ---
5656
await createDrawingExamples(bitbybit);
57-
57+
58+
// --- 5. Create Textured OCCT Cube Example ---
59+
if (kernelOptions.enableOCCT) {
60+
await createTexturedOCCTCube(bitbybit);
61+
}
62+
5863
// Start the Babylon.js render loop
5964
engine.runRenderLoop(() => {
6065
if (scene.activeCamera) {
@@ -323,6 +328,41 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) {
323328
console.log("JSCAD geometry created and drawn.");
324329
}
325330

331+
async function createTexturedOCCTCube(bitbybit: BitByBitBase) {
332+
console.log("Creating textured OCCT cube...");
333+
334+
// Create texture from URL
335+
const textureOptions = new Inputs.Draw.GenericTextureDto();
336+
textureOptions.url = "https://cdn.polyhaven.com/asset_img/primary/worn_asphalt.png?height=760&quality=95";
337+
textureOptions.uScale = 0.05;
338+
textureOptions.vScale = 0.05;
339+
const texture = await bitbybit.draw.createTexture(textureOptions);
340+
341+
// Create material with texture
342+
const materialOptions = new Inputs.Draw.GenericPBRMaterialDto();
343+
materialOptions.baseColorTexture = texture;
344+
materialOptions.baseColor = "#ffffff"; // White to show texture colors accurately
345+
const material = await bitbybit.draw.createPBRMaterial(materialOptions);
346+
347+
// Create OCCT cube
348+
const cubeOptions = new Inputs.OCCT.CubeDto();
349+
cubeOptions.size = 20;
350+
cubeOptions.center = [-50, 0, -50];
351+
const cube = await bitbybit.occt.shapes.solid.createCube(cubeOptions);
352+
353+
// Draw cube with material
354+
const drawOptions = new Inputs.Draw.DrawOcctShapeOptions();
355+
drawOptions.faceMaterial = material;
356+
drawOptions.backFaceOpacity = 1;
357+
drawOptions.edgeWidth = 10;
358+
await bitbybit.draw.drawAnyAsync({
359+
entity: cube,
360+
options: drawOptions,
361+
});
362+
363+
console.log("Textured OCCT cube created and drawn.");
364+
}
365+
326366
// --- 7. Drawing Examples Function ---
327367
async function createDrawingExamples(bitbybit: BitByBitBase) {
328368
console.log("Creating drawing examples...");

examples/vite/playcanvas/starter-template/src/main.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ async function start() {
5858

5959
// --- 4. Create Drawing Examples (Lines, Points, Curves, etc.) ---
6060
await createDrawingExamples(bitbybit);
61+
62+
// --- 5. Create Textured OCCT Cube Example ---
63+
if (kernelOptions.enableOCCT) {
64+
await createTexturedOCCTCube(bitbybit);
65+
}
6166
}
6267

6368
// --- 4. PlayCanvas Scene Initialization ---
@@ -324,6 +329,40 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) {
324329
console.log("JSCAD geometry created and drawn.");
325330
}
326331

332+
async function createTexturedOCCTCube(bitbybit: BitByBitBase) {
333+
console.log("Creating textured OCCT cube...");
334+
335+
// Create texture from URL
336+
const textureOptions = new Inputs.Draw.GenericTextureDto();
337+
textureOptions.url = "https://cdn.polyhaven.com/asset_img/primary/worn_asphalt.png?height=760&quality=95";
338+
textureOptions.uScale = 0.05;
339+
textureOptions.vScale = 0.05;
340+
const texture = await bitbybit.draw.createTexture(textureOptions);
341+
342+
// Create material with texture
343+
const materialOptions = new Inputs.Draw.GenericPBRMaterialDto();
344+
materialOptions.baseColorTexture = texture;
345+
materialOptions.baseColor = "#ffffff"; // White to show texture colors accurately
346+
const material = await bitbybit.draw.createPBRMaterial(materialOptions);
347+
348+
// Create OCCT cube
349+
const cubeOptions = new Inputs.OCCT.CubeDto();
350+
cubeOptions.size = 20;
351+
cubeOptions.center = [-50, 0, -50];
352+
const cube = await bitbybit.occt.shapes.solid.createCube(cubeOptions);
353+
354+
// Draw cube with material
355+
const drawOptions = new Inputs.Draw.DrawOcctShapeOptions();
356+
drawOptions.faceMaterial = material;
357+
drawOptions.backFaceOpacity = 1;
358+
await bitbybit.draw.drawAnyAsync({
359+
entity: cube,
360+
options: drawOptions,
361+
});
362+
363+
console.log("Textured OCCT cube created and drawn.");
364+
}
365+
327366
// --- 7. Drawing Examples Function ---
328367
async function createDrawingExamples(bitbybit: BitByBitBase) {
329368
console.log("Creating drawing examples...");

examples/vite/threejs/starter-template/src/main.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ async function start() {
5454

5555
// --- 4. Create Drawing Examples (Lines, Points, Curves, etc.) ---
5656
await createDrawingExamples(bitbybit);
57+
58+
// --- 5. Create Textured OCCT Cube Example ---
59+
if (kernelOptions.enableOCCT) {
60+
await createTexturedOCCTCube(bitbybit);
61+
}
5762
}
5863

5964
// --- 4. Three.js Scene Initialization ---
@@ -311,6 +316,40 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) {
311316
console.log("JSCAD geometry created and drawn.");
312317
}
313318

319+
async function createTexturedOCCTCube(bitbybit: BitByBitBase) {
320+
console.log("Creating textured OCCT cube...");
321+
322+
// Create texture from URL
323+
const textureOptions = new Inputs.Draw.GenericTextureDto();
324+
textureOptions.url = "https://cdn.polyhaven.com/asset_img/primary/worn_asphalt.png?height=760&quality=95";
325+
textureOptions.uScale = 0.05;
326+
textureOptions.vScale = 0.05;
327+
const texture = await bitbybit.draw.createTexture(textureOptions);
328+
329+
// Create material with texture
330+
const materialOptions = new Inputs.Draw.GenericPBRMaterialDto();
331+
materialOptions.baseColorTexture = texture;
332+
materialOptions.baseColor = "#ffffff"; // White to show texture colors accurately
333+
const material = await bitbybit.draw.createPBRMaterial(materialOptions);
334+
335+
// Create OCCT cube
336+
const cubeOptions = new Inputs.OCCT.CubeDto();
337+
cubeOptions.size = 20;
338+
cubeOptions.center = [-50, 0, -50];
339+
const cube = await bitbybit.occt.shapes.solid.createCube(cubeOptions);
340+
341+
// Draw cube with material
342+
const drawOptions = new Inputs.Draw.DrawOcctShapeOptions();
343+
drawOptions.faceMaterial = material;
344+
drawOptions.backFaceOpacity = 1;
345+
await bitbybit.draw.drawAnyAsync({
346+
entity: cube,
347+
options: drawOptions,
348+
});
349+
350+
console.log("Textured OCCT cube created and drawn.");
351+
}
352+
314353
// --- 7. Drawing Examples Function ---
315354
async function createDrawingExamples(bitbybit: BitByBitBase) {
316355
console.log("Creating drawing examples...");

packages/dev/babylonjs/lib/api/draw-helper.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,9 +1148,9 @@ export class DrawHelper extends DrawHelperCore {
11481148
}
11491149

11501150
async drawManifoldsOrCrossSections(inputs: Inputs.Manifold.DrawManifoldsOrCrossSectionsDto<Inputs.Manifold.ManifoldPointer | Inputs.Manifold.CrossSectionPointer, BABYLON.PBRMetallicRoughnessMaterial>): Promise<BABYLON.Mesh> {
1151-
const options = this.deleteFaceMaterialForWorker(inputs);
1152-
const decomposedMesh: Inputs.Manifold.DecomposedManifoldMeshDto[] = await this.manifoldWorkerManager.genericCallToWorkerPromise("decomposeManifoldsOrCrossSections", inputs);
1153-
const meshes = decomposedMesh.map(dec => this.handleDecomposedManifold(dec, options));
1151+
const safeWorkerOptions = this.getSafeWorkerOptions(inputs);
1152+
const decomposedMesh: Inputs.Manifold.DecomposedManifoldMeshDto[] = await this.manifoldWorkerManager.genericCallToWorkerPromise("decomposeManifoldsOrCrossSections", safeWorkerOptions);
1153+
const meshes = decomposedMesh.map(dec => this.handleDecomposedManifold(dec, inputs));
11541154
const manifoldMeshContainer = new BABYLON.Mesh(this.generateEntityId("manifoldMeshContainer"), this.context.scene);
11551155
meshes.filter(s => s !== undefined).forEach(mesh => {
11561156
mesh.parent = manifoldMeshContainer;
@@ -1159,29 +1159,29 @@ export class DrawHelper extends DrawHelperCore {
11591159
}
11601160

11611161
async drawManifoldOrCrossSection(inputs: Inputs.Manifold.DrawManifoldOrCrossSectionDto<Inputs.Manifold.ManifoldPointer | Inputs.Manifold.CrossSectionPointer, BABYLON.PBRMetallicRoughnessMaterial>): Promise<BABYLON.Mesh> {
1162-
const options = this.deleteFaceMaterialForWorker(inputs);
1163-
const decomposedMesh: Inputs.Manifold.DecomposedManifoldMeshDto = await this.manifoldWorkerManager.genericCallToWorkerPromise("decomposeManifoldOrCrossSection", inputs);
1164-
return this.handleDecomposedManifold(decomposedMesh, options);
1162+
const safeWorkerOptions = this.getSafeWorkerOptions(inputs);
1163+
const decomposedMesh: Inputs.Manifold.DecomposedManifoldMeshDto = await this.manifoldWorkerManager.genericCallToWorkerPromise("decomposeManifoldOrCrossSection", safeWorkerOptions);
1164+
return this.handleDecomposedManifold(decomposedMesh, inputs);
11651165
}
11661166

11671167
async drawShape(inputs: Inputs.OCCT.DrawShapeDto<Inputs.OCCT.TopoDSShapePointer>): Promise<BABYLON.Mesh> {
1168-
const options = this.deleteFaceMaterialForWorker(inputs);
1169-
const decomposedMesh: Inputs.OCCT.DecomposedMeshDto = await this.occWorkerManager.genericCallToWorkerPromise("shapeToMesh", inputs);
1170-
return this.handleDecomposedMesh(inputs, decomposedMesh, options);
1168+
const safeWorkerOptions = this.getSafeWorkerOptions(inputs);
1169+
const decomposedMesh: Inputs.OCCT.DecomposedMeshDto = await this.occWorkerManager.genericCallToWorkerPromise("shapeToMesh", safeWorkerOptions);
1170+
return this.handleDecomposedMesh(inputs, decomposedMesh, inputs);
11711171
}
11721172

11731173
async drawShapes(inputs: Inputs.OCCT.DrawShapesDto<Inputs.OCCT.TopoDSShapePointer>): Promise<BABYLON.Mesh> {
1174-
const options = this.deleteFaceMaterialForWorker(inputs);
1175-
const meshes: Inputs.OCCT.DecomposedMeshDto[] = await this.occWorkerManager.genericCallToWorkerPromise("shapesToMeshes", inputs);
1176-
const meshesSolved = await Promise.all(meshes.map(async decomposedMesh => this.handleDecomposedMesh(inputs, decomposedMesh, options)));
1174+
const safeWorkerOptions = this.getSafeWorkerOptions(inputs);
1175+
const meshes: Inputs.OCCT.DecomposedMeshDto[] = await this.occWorkerManager.genericCallToWorkerPromise("shapesToMeshes", safeWorkerOptions);
1176+
const meshesSolved = await Promise.all(meshes.map(async decomposedMesh => this.handleDecomposedMesh(inputs, decomposedMesh, inputs)));
11771177
const shapesMeshContainer = new BABYLON.Mesh(this.generateEntityId("shapesMeshContainer"), this.context.scene);
11781178
meshesSolved.forEach(mesh => {
11791179
mesh.parent = shapesMeshContainer;
11801180
});
11811181
return shapesMeshContainer;
11821182
}
11831183

1184-
private async handleDecomposedMesh(inputs: Inputs.OCCT.DrawShapeDto<Inputs.OCCT.TopoDSShapePointer>, decomposedMesh: Inputs.OCCT.DecomposedMeshDto, options: Inputs.Draw.DrawOcctShapeOptions): Promise<BABYLON.Mesh> {
1184+
private async handleDecomposedMesh(inputs: Inputs.OCCT.DrawShapeDto<Inputs.OCCT.TopoDSShapePointer>, decomposedMesh: Inputs.OCCT.DecomposedMeshDto, options: Partial<Inputs.Draw.DrawOcctShapeOptions>): Promise<BABYLON.Mesh> {
11851185
const shapeMesh = new BABYLON.Mesh(this.generateEntityId("brepMesh"), this.context.scene);
11861186
shapeMesh.isVisible = false;
11871187
let dummy;
@@ -1487,12 +1487,11 @@ export class DrawHelper extends DrawHelperCore {
14871487
return countIndices;
14881488
}
14891489

1490-
// sometimes we must delete face material property for the web worker not to complain about complex (circular) objects and use cloned object later
1491-
private deleteFaceMaterialForWorker(inputs: any) {
1492-
const options = { ...inputs };
1493-
if (inputs.faceMaterial) {
1494-
delete inputs.faceMaterial;
1495-
}
1496-
return options;
1490+
// Creates a shallow copy of inputs without the faceMaterial property for safe worker communication
1491+
// Workers cannot handle complex circular objects like Babylon.js materials
1492+
private getSafeWorkerOptions<T extends { faceMaterial?: BABYLON.Material }>(inputs: T): Omit<T, "faceMaterial"> {
1493+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1494+
const { faceMaterial, ...safeOptions } = inputs;
1495+
return safeOptions as Omit<T, "faceMaterial">;
14971496
}
14981497
}

packages/dev/babylonjs/lib/api/inputs/draw-inputs.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,6 @@ describe("Draw DTO unit tests", () => {
329329
const alpha = 0.9;
330330
const emissiveColor = "#ffff00";
331331
const emissiveIntensity = 2.5;
332-
const backFaceCulling = true;
333332
const zOffset = 0.5;
334333
const zOffsetUnits = 2;
335334
const baseColorTexture = { url: "base.png" } as Base.Texture;
@@ -352,7 +351,6 @@ describe("Draw DTO unit tests", () => {
352351
alpha,
353352
emissiveColor,
354353
emissiveIntensity,
355-
backFaceCulling,
356354
zOffset,
357355
zOffsetUnits,
358356
baseColorTexture,
@@ -375,7 +373,6 @@ describe("Draw DTO unit tests", () => {
375373
expect(result.alpha).toBe(0.9);
376374
expect(result.emissiveColor).toBe("#ffff00");
377375
expect(result.emissiveIntensity).toBe(2.5);
378-
expect(result.backFaceCulling).toBe(true);
379376
expect(result.zOffset).toBe(0.5);
380377
expect(result.zOffsetUnits).toBe(2);
381378
expect(result.baseColorTexture).toBe(baseColorTexture);
@@ -402,7 +399,6 @@ describe("Draw DTO unit tests", () => {
402399
expect(result.alpha).toBe(1);
403400
expect(result.emissiveColor).toBe("#000000");
404401
expect(result.emissiveIntensity).toBe(1);
405-
expect(result.backFaceCulling).toBe(false);
406402
expect(result.zOffset).toBe(0);
407403
expect(result.zOffsetUnits).toBe(0);
408404
expect(result.baseColorTexture).toBeUndefined();

packages/dev/babylonjs/lib/api/inputs/draw-inputs.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,6 @@ export namespace Draw {
737737
alpha?: number,
738738
emissiveColor?: Base.Color,
739739
emissiveIntensity?: number,
740-
backFaceCulling?: boolean,
741740
zOffset?: number,
742741
zOffsetUnits?: number,
743742
baseColorTexture?: Base.Texture,
@@ -758,7 +757,6 @@ export namespace Draw {
758757
if (alpha !== undefined) { this.alpha = alpha; }
759758
if (emissiveColor !== undefined) { this.emissiveColor = emissiveColor; }
760759
if (emissiveIntensity !== undefined) { this.emissiveIntensity = emissiveIntensity; }
761-
if (backFaceCulling !== undefined) { this.backFaceCulling = backFaceCulling; }
762760
if (zOffset !== undefined) { this.zOffset = zOffset; }
763761
if (zOffsetUnits !== undefined) { this.zOffsetUnits = zOffsetUnits; }
764762
if (baseColorTexture !== undefined) { this.baseColorTexture = baseColorTexture; }
@@ -819,11 +817,6 @@ export namespace Draw {
819817
* @step 0.1
820818
*/
821819
emissiveIntensity = 1;
822-
/**
823-
* Whether to cull (hide) back-facing polygons
824-
* @default false
825-
*/
826-
backFaceCulling = false;
827820
/**
828821
* Z-buffer depth offset factor to help with z-fighting on coplanar surfaces
829822
* @default 0

0 commit comments

Comments
 (0)