Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Boolean Operations System (Kernel + JS Bindings + Three Adapter)

## What changed

This change adds a full boolean operation flow that can be executed from Rust, consumed from wasm/js bindings, and rendered through `@opengeometry/kernel-three`.

### 1) Kernel boolean operation module
- Added `main/opengeometry/src/operations/boolean.rs`.
- Introduced wasm-exported `BooleanOperation` enum:
- `Union`
- `Intersection`
- `Difference`
- Introduced wasm-exported `OGBooleanResult` that:
- accepts two serialized BReps,
- computes the result,
- returns both serialized BRep and triangulated render geometry.

### 2) Boolean algorithm and constraint model
To provide a boolean operation that works across all existing solids with the current project architecture, the implementation uses a **voxel-constrained CSG** pipeline:

1. Convert each input BRep face-loop set to triangles.
2. Build a combined bounding box.
3. Sample voxel centers on a regular grid (`voxel_size` is the constraint parameter).
4. Classify sample points as inside/outside each shape using parity ray-casting against triangle soups.
5. Evaluate boolean logic (`union`, `intersection`, `difference`).
6. Reconstruct a watertight surface by emitting only boundary voxel faces.

This is robust for heterogeneous shape inputs because it does not depend on exact face-face intersection topology repair in floating point. Instead, the user controls precision/performance through `voxel_size`.

### 3) Public API wiring
- Registered the new module in `main/opengeometry/src/lib.rs` via `pub mod boolean;` in operations.

### 4) Three.js adapter update
- Added `main/opengeometry-three/src/operations/boolean.ts` with:
- `BooleanMesh` class (extends `THREE.Mesh`),
- `compute(first, second, operation, options)` method,
- optional constraints (`voxelSize`, `color`, `opacity`),
- `getBrepData()` pass-through for chaining operations.
- Added operation exports in:
- `main/opengeometry-three/src/operations/index.ts`
- `main/opengeometry-three/index.ts`

### 5) Working example
- Added new interactive example:
- Page: `main/opengeometry-three/examples-vite/src/pages/operations-boolean.ts`
- HTML entry: `main/opengeometry-three/examples-vite/operations/boolean.html`
- Example lets the user switch operation and voxel constraint interactively.

### 6) Tests
Added kernel tests in `main/opengeometry/src/operations/boolean.rs`:
- union of overlapping cuboids returns non-empty geometry,
- difference of overlapping cuboids returns non-empty geometry.

## Why this changed

The project already has many shape generators that emit BReps but does not yet have a production path for generic shape boolean operations. This patch introduces a single, unified operation path that can accept any shape capable of producing BRep data and gives users a practical constraint knob (`voxel_size`) for balancing robustness and fidelity.

## How to test locally

### Kernel tests
```bash
cargo test --manifest-path main/opengeometry/Cargo.toml
```

### Three adapter lint smoke for the new file
```bash
npx eslint main/opengeometry-three/src/operations/boolean.ts
```

### Example build (optional)
```bash
npm run build-example-three
```
Then open `main/opengeometry-three/examples-dist/operations/boolean.html`.

## Backward compatibility

- Existing shape APIs are unchanged.
- Existing render wrappers are unchanged.
- New functionality is additive.

## Known caveats and follow-ups

1. The current implementation is voxel-based (approximate), not exact analytic BRep-BRep splitting.
2. Output triangle density grows quickly as `voxel_size` decreases.
3. Future follow-up can add adaptive grids, cached spatial acceleration, and exact predicate/splitting stages for high-fidelity CAD workflows.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><body><div id="app"></div><script type="module" src="../src/pages/operations-boolean.ts"></script></body></html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { BooleanMesh, Cuboid, Vector3 } from "@og-three";
import * as THREE from "three";
import {
bootstrapExample,
mountControls,
replaceSceneObject,
} from "../shared/runtime";

bootstrapExample({
title: "Operation: Boolean",
description:
"Voxel-constraint boolean operation between two cuboids (union / intersection / difference).",
build: ({ scene }) => {
let current: THREE.Group | null = null;

mountControls(
"Boolean Parameters",
[
{
type: "select",
key: "operation",
label: "Operation",
value: "union",
options: [
{ label: "Union", value: "union" },
{ label: "Intersection", value: "intersection" },
{ label: "Difference (A - B)", value: "difference" },
],
},
{ type: "number", key: "offsetX", label: "B Offset X", min: -1.25, max: 1.25, step: 0.05, value: 0.45 },
{ type: "number", key: "voxelSize", label: "Voxel Size", min: 0.08, max: 0.4, step: 0.02, value: 0.15 },
],
(state) => {
const a = new Cuboid({
center: new Vector3(-0.2, 0, 0),
width: 1.4,
height: 1.2,
depth: 1.1,
color: 0x1d4ed8,
});

const b = new Cuboid({
center: new Vector3(state.offsetX as number, 0, 0),
width: 1.2,
height: 1.2,
depth: 1.3,
color: 0xdc2626,
});

const result = new BooleanMesh();
result.compute(a, b, state.operation as "union" | "intersection" | "difference", {
voxelSize: state.voxelSize as number,
color: 0x16a34a,
opacity: 0.8,
});

const group = new THREE.Group();
group.add(result);

current = replaceSceneObject(scene, current, group);
}
);
},
});
5 changes: 5 additions & 0 deletions main/opengeometry-three/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,3 +666,8 @@ export * from './src/shapes/';
* Reusable example builders for quickly wiring demo scenes.
*/
export * from './src/examples/';

/**
* Boolean/constructive solid operations wrappers.
*/
export * from './src/operations/';
72 changes: 72 additions & 0 deletions main/opengeometry-three/src/operations/boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { BooleanOperation, OGBooleanResult } from "../../../opengeometry/pkg/opengeometry";
import * as THREE from "three";

export type BooleanInputShape = {
getBrepData: () => unknown;
};

export type BooleanKind = "union" | "intersection" | "difference";

export interface BooleanOptions {
voxelSize?: number;
color?: number;
opacity?: number;
}

function toKernelOperation(operation: BooleanKind): BooleanOperation {
switch (operation) {
case "union":
return BooleanOperation.Union;
case "intersection":
return BooleanOperation.Intersection;
case "difference":
return BooleanOperation.Difference;
}
}

export class BooleanMesh extends THREE.Mesh {
private readonly solver = new OGBooleanResult();

constructor() {
super(new THREE.BufferGeometry(), new THREE.MeshStandardMaterial({ color: 0x33aa33, transparent: true, opacity: 0.8 }));
}

compute(
first: BooleanInputShape,
second: BooleanInputShape,
operation: BooleanKind,
options?: BooleanOptions,
) {
const voxelSize = options?.voxelSize ?? 0.15;

this.solver.compute_from_brep_serialized(
JSON.stringify(first.getBrepData()),
JSON.stringify(second.getBrepData()),
toKernelOperation(operation),
voxelSize,
);

const geometry = new THREE.BufferGeometry();
const geometryData = JSON.parse(this.solver.get_geometry_serialized());
geometry.setAttribute("position", new THREE.Float32BufferAttribute(geometryData, 3));
geometry.computeVertexNormals();

if (this.geometry) {
this.geometry.dispose();
}

this.geometry = geometry;

const color = options?.color ?? 0x33aa33;
const opacity = options?.opacity ?? 0.8;
this.material = new THREE.MeshStandardMaterial({
color,
transparent: opacity < 1,
opacity,
});
}

getBrepData() {
return JSON.parse(this.solver.get_brep_serialized());
}
}
1 change: 1 addition & 0 deletions main/opengeometry-three/src/operations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './boolean';
1 change: 1 addition & 0 deletions main/opengeometry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod geometry {
}

pub mod operations {
pub mod boolean;
pub mod extrude;
pub mod offset;
pub mod sweep;
Expand Down
Loading
Loading