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
71 changes: 71 additions & 0 deletions AI-DOCs/2026-03-04-boolean-operations-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 2026-03-04 — boolean-operations-implementation

## What changed

Implemented a new boolean operation system in `opengeometry-three` that can combine **any two `THREE.Mesh`-compatible OpenGeometry shapes** (cuboid, cylinder, sphere, wedge, opening, sweep, polygon-derived meshes) using:

- `union`
- `subtract`
- `intersect`

The implementation introduces:

1. `src/operations/boolean.ts`
- `booleanOperation(left, right, options)` API
- `BooleanShape` mesh result type
- Configurable constraints (`gridResolution`, `constrainResultToPositiveY`, material controls)
2. `src/operations/index.ts` export barrel
3. Public package exports in `main/opengeometry-three/index.ts`
4. Runnable Vite example:
- `examples-vite/operations/boolean.html`
- `examples-vite/src/pages/operations-boolean.ts`
5. Example index update to surface the Boolean operation card.

## Why it changed

The project needed an end-to-end boolean workflow to generate a new resulting shape from two input shapes under configurable constraints, and make that available through JS bindings and the `opengeometry-three` package surface.

Given the environment restriction preventing new registry downloads during this task, the implementation uses a dependency-free voxel classification strategy over existing `three` APIs rather than external CSG packages.

## Primary-source alignment (RobustBoolean paper)

The implementation follows the robustness direction from robust boolean literature by prioritizing **classification stability** over exact floating-point surface-surface reconstruction:

- Input solids are transformed into a common field domain.
- Occupancy is decided with consistent inside/outside tests.
- Boolean logic is applied on classified cells.
- Surface is extracted from cell boundary transitions.

This avoids common triangle-triangle degeneracy failure modes in direct mesh clipping and gives predictable behavior across diverse shape families.

## How to test locally

From repository root:

1. Build/check Rust core:
- `cargo check --manifest-path main/opengeometry/Cargo.toml`
- `cargo test --manifest-path main/opengeometry/Cargo.toml`
2. Build Three examples:
- `npm --prefix main/opengeometry-three run build-example-three`
3. Run example dev server:
- `npm --prefix main/opengeometry-three run dev-example-three`
4. Open:
- `/operations/boolean.html`

Interactive controls:
- operation (union/subtract/intersect)
- grid resolution
- shape offset
- positive-Y clamp

## Backward compatibility

- No existing APIs were removed.
- Existing shape wrappers are unchanged.
- New functionality is additive and exposed via new exports.

## Known caveats / follow-ups

1. Current boolean result is voxelized (resolution-controlled), so edges are stepped.
2. Higher grid resolution improves fidelity but increases compute time.
3. Next iteration: optional exact-surface backend (when dependency/network policy allows).
10 changes: 9 additions & 1 deletion main/opengeometry-three/examples-vite/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ <h2 class="og-specs-section-title">Shapes</h2>
<section class="og-specs-section">
<div class="og-specs-section-head">
<h2 class="og-specs-section-title">Operations</h2>
<p class="og-specs-count">3 items</p>
<p class="og-specs-count">4 items</p>
</div>
<div class="og-specs-grid">
<a class="og-spec-card" href="./operations/offset.html">
Expand All @@ -153,6 +153,14 @@ <h2 class="og-specs-section-title">Operations</h2>
<div class="og-spec-meta-row"><dt>Control</dt><dd>Thickness</dd></div>
</dl>
</a>
<a class="og-spec-card" href="./operations/boolean.html">
<div class="og-spec-card-head"><h3 class="og-spec-card-title">Boolean</h3><p class="og-spec-badge">new</p></div>
<p class="og-spec-desc">Boolean union/subtract/intersection across kernel shapes with resolution constraints.</p>
<dl class="og-spec-meta">
<div class="og-spec-meta-row"><dt>Domain</dt><dd>Kernel Ops</dd></div>
<div class="og-spec-meta-row"><dt>Control</dt><dd>Op, Grid, Clamp</dd></div>
</dl>
</a>
<a class="og-spec-card" href="./operations/sweep-path-profile.html">
<div class="og-spec-card-head"><h3 class="og-spec-card-title">Sweep Path + Profile</h3><p class="og-spec-badge">ready</p></div>
<p class="og-spec-desc">Operation-level sweep from path primitive + profile primitive.</p>
Expand Down
12 changes: 12 additions & 0 deletions main/opengeometry-three/examples-vite/operations/boolean.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenGeometry • Boolean Operation</title>
</head>
<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,73 @@
import { Cuboid, Cylinder, Sphere, Vector3, booleanOperation, type BooleanOperationType } from "@og-three";
import * as THREE from "three";
import { bootstrapExample, mountControls, replaceSceneObject } from "../shared/runtime";

function resolveOperation(mode: number): BooleanOperationType {
if (mode < 0.5) {
return "union";
}
if (mode < 1.5) {
return "subtract";
}
return "intersect";
}

bootstrapExample({
title: "Operation: Boolean",
description: "Voxel-robust boolean operation between any two OpenGeometry shapes.",
build: ({ scene }) => {
let resultMesh: THREE.Mesh | null = null;

mountControls(
"Boolean Parameters",
[
{ type: "number", key: "operation", label: "Operation (0:union,1:subtract,2:intersect)", min: 0, max: 2, step: 1, value: 0 },
{ type: "number", key: "resolution", label: "Grid Resolution", min: 8, max: 40, step: 1, value: 26 },
{ type: "number", key: "offset", label: "Offset", min: -1.2, max: 1.2, step: 0.05, value: 0.35 },
{ type: "boolean", key: "positiveY", label: "Clamp to Positive Y", value: false },
],
(state) => {
const left = new Cuboid({
center: new Vector3(-0.2, 0, 0),
width: 2.2,
height: 1.8,
depth: 2.0,
color: 0x9ca3af,
});

const right = new Cylinder({
center: new Vector3(state.offset as number, 0, 0.15),
radius: 0.9,
height: 2.2,
segments: 48,
color: 0x6b7280,
});

const cap = new Sphere({
center: new Vector3((state.offset as number) * 0.5, 0.35, 0),
radius: 0.85,
widthSegments: 28,
heightSegments: 18,
color: 0x4b5563,
});

const stagedRight = booleanOperation(right, cap, {
operation: "union",
gridResolution: Math.max(8, (state.resolution as number) - 6),
color: 0x6b7280,
opacity: 0.2,
});

const result = booleanOperation(left, stagedRight, {
operation: resolveOperation(state.operation as number),
gridResolution: state.resolution as number,
constrainResultToPositiveY: state.positiveY as boolean,
color: 0x2563eb,
opacity: 0.72,
});

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

export * from "./src/shapes";
export * from "./src/primitives";
export * from "./src/operations";
33 changes: 33 additions & 0 deletions main/opengeometry-three/src/examples/boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as THREE from "three";
import { Vector3 } from "../../../opengeometry/pkg/opengeometry";
import { Cuboid } from "../shapes/cuboid";
import { Cylinder } from "../shapes/cylinder";
import { booleanOperation } from "../operations";

export function createBooleanExample(scene: THREE.Scene) {
const left = new Cuboid({
center: new Vector3(0, 0, 0),
width: 2,
height: 2,
depth: 2,
color: 0x6b7280,
});

const right = new Cylinder({
center: new Vector3(0.55, 0, 0),
radius: 0.95,
height: 2.2,
segments: 42,
color: 0x9ca3af,
});

const result = booleanOperation(left, right, {
operation: "subtract",
gridResolution: 30,
color: 0x2563eb,
});

scene.add(result);

return { left, right, result };
}
2 changes: 2 additions & 0 deletions main/opengeometry-three/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from './shapes';
export * from './sweep';
export * from './offset';
export * from './wall-from-offsets';

export * from './boolean';
Loading
Loading