Geometry Pipeline¶
Detailed architecture of geometry processing in IFClite.
Overview¶
The geometry pipeline transforms IFC shape representations into GPU-ready triangle meshes:
Geometry Representation Types¶
IFC Geometry Hierarchy¶
Coverage by Type¶
| Geometry Type | Coverage | Notes |
|---|---|---|
| IfcExtrudedAreaSolid | Full | Most common |
| IfcFacetedBrep | Full | Pre-triangulated |
| IfcBooleanClippingResult | Partial | CSG operations |
| IfcMappedItem | Full | Instancing |
| IfcSurfaceModel | Partial | Surface meshes |
| IfcTriangulatedFaceSet | Full | IFC4 triangles |
Extrusion Processing¶
Pipeline¶
Profile Types¶
Earcut Algorithm¶
use earcutr::earcut;
fn triangulate_profile(
outer: &[Point2],
holes: &[Vec<Point2>]
) -> Vec<u32> {
// Flatten to coordinate array
let mut coords: Vec<f64> = Vec::new();
let mut hole_indices: Vec<usize> = Vec::new();
// Add outer boundary
for p in outer {
coords.push(p.x);
coords.push(p.y);
}
// Add holes
for hole in holes {
hole_indices.push(coords.len() / 2);
for p in hole {
coords.push(p.x);
coords.push(p.y);
}
}
// Triangulate
earcut(&coords, &hole_indices, 2)
.unwrap()
.into_iter()
.map(|i| i as u32)
.collect()
}
Brep Processing¶
FacetedBrep Pipeline¶
Face Triangulation¶
Boolean Operations¶
CSG Pipeline¶
Boolean Operators¶
| Operator | Description | Common Use |
|---|---|---|
| DIFFERENCE | A - B | Wall openings |
| UNION | A + B | Composite shapes |
| INTERSECTION | A ∩ B | Clipping |
Coordinate Transformations¶
Placement Stack¶
Matrix Operations¶
use nalgebra::{Matrix4, Point3, Vector3};
fn compute_transform(placements: &[Placement]) -> Matrix4<f64> {
let mut result = Matrix4::identity();
for placement in placements {
let local = Matrix4::new_translation(&placement.location)
* Matrix4::from_axis_angle(&placement.axis, placement.angle);
result = result * local;
end
result
}
fn transform_point(point: Point3<f64>, matrix: &Matrix4<f64>) -> Point3<f64> {
matrix.transform_point(&point)
}
Large Coordinate Handling¶
function computeOriginShift(bounds: BoundingBox): Vector3 {
const threshold = 10000; // Shift if > 10km from origin
if (Math.abs(bounds.center.x) > threshold ||
Math.abs(bounds.center.y) > threshold) {
return {
x: -bounds.center.x,
y: -bounds.center.y,
z: 0
};
}
return { x: 0, y: 0, z: 0 };
}
Quality Modes¶
Curve Discretization¶
| Mode | Segments | Triangles | Use Case |
|---|---|---|---|
| FAST | 8 | Fewer | Mobile, preview |
| BALANCED | 16 | Medium | Default |
| HIGH | 32 | More | Detailed viewing |
Instancing¶
MappedItem Processing¶
Instance Data Structure¶
interface InstancedMesh {
baseMesh: Mesh;
transforms: Matrix4[];
expressIds: number[];
}
// GPU instancing data
interface InstanceData {
positions: Float32Array; // Shared geometry
normals: Float32Array;
indices: Uint32Array;
instanceMatrices: Float32Array; // Per-instance transforms
instanceColors: Float32Array; // Per-instance colors
}
Streaming Pipeline¶
Batch Processing¶
async function processGeometryBatches(
entities: Entity[],
batchSize: number,
onBatch: (batch: MeshBatch) => Promise<void>
): Promise<void> {
const geoEntities = entities.filter(e => e.hasGeometry);
for (let i = 0; i < geoEntities.length; i += batchSize) {
const batch = geoEntities.slice(i, i + batchSize);
const meshes = await Promise.all(
batch.map(e => processEntity(e))
);
await onBatch({
meshes,
bounds: computeBounds(meshes),
progress: (i + batch.length) / geoEntities.length
});
}
}
CSG Kernel¶
Two boolean / CSG kernels coexist behind a Cargo feature flag.
Default (legacy BSP)¶
rust/geometry/src/bsp_csg.rs — a Rust port of csg.js (Evan Wallace,
MIT). Triangle-mesh BSP. Hard-caps at 24 polygons per operand
(csg.rs:117). On cap exceeded or kernel error, falls back to the
un-cut host mesh and emits a structured BoolFailure record (drainable
via GeometryRouter::take_csg_failures). This is the default for the
wasm32-unknown-unknown build target since the alternative (Manifold)
has unresolved upstream toolchain dependencies on that target.
Optional (Manifold)¶
Behind --features manifold-csg. Uses Manifold
via the manifold-csg crate (Apache-2/MIT, native C++ kernel built
through cmake). No operand cap, manifold-by-construction output, real
solid-solid IfcBooleanResult.{DIFFERENCE, UNION, INTERSECTION}. A
vertex-weld pre-pass in rust/geometry/src/manifold_kernel.rs
collapses the polygon-soup mesh layout ifc-lite's extruded-solid
builder produces (24 verts per cube → 8) so Manifold accepts the
input.
BoolFailure records and GeometryRouter::take_csg_failures work
identically under both kernels. Sprint 2 acceptance gates assert
total_failures == 0 on AC20-FZK-Haus.ifc and
C20-Institute-Var-2.ifc under --features manifold-csg; both pass.
WASM status¶
--features manifold-csg-wasm-uu (which implies manifold-csg plus
upstream's unstable-wasm-uu) is currently blocked on a libc++ /
wasm-cxx-shim incompatibility in the manifold-csg-sys crate:
- libc++-18:
_LIBCPP_AVAILABILITY_VERBOSE_ABORTundefined when the shim's__assertion_handleris loaded. - libc++-20: musl locale headers
(
__locale_dir/locale_base_api/musl.h) are pulled in despite the shim's_LIBCPP_HAS_LOCALIZATION 0define;locale.hthen fails to resolve in thewasm32-unknown-unknownno-libc environment.
Both are upstream issues (zmerlynn/manifold-csg-sys and its bundled
zmerlynn/wasm-cxx-shim); not patchable from this repo. Track
upstream and re-attempt once the shim is updated for current libc++
versions. Until then manifold-csg stays opt-in for native builds
only and wasm32-unknown-unknown builds continue to use the legacy
BSP path.
Performance Metrics¶
| Operation | Time (typical) | Notes |
|---|---|---|
| Profile extraction | 0.1 ms | Per entity |
| Earcut triangulation | 0.5 ms | Simple profile |
| Extrusion | 0.2 ms | Per entity |
| Boolean operation | 5-50 ms | Complex |
| Transform application | 0.01 ms | Per vertex |
Throughput¶
- Simple extrusions: ~2000 entities/sec
- Complex Breps: ~200 entities/sec
- Boolean operations: ~20 entities/sec
Next Steps¶
- Rendering Pipeline - WebGPU rendering
- API Reference - Geometry API