Geometry Pipeline
Detailed architecture of geometry processing in IFClite.
Overview
The geometry pipeline transforms IFC shape representations into GPU-ready triangle meshes:
flowchart TB
subgraph Input["IFC Geometry"]
Extrusion["IfcExtrudedAreaSolid"]
Brep["IfcFacetedBrep"]
Boolean["IfcBooleanResult"]
Mapped["IfcMappedItem"]
Surface["IfcSurfaceModel"]
end
subgraph Router["Geometry Router"]
Detect["Type Detection"]
Select["Processor Selection"]
end
subgraph Processors["Specialized Processors"]
ExtProc["ExtrusionProcessor"]
BrepProc["BrepProcessor"]
BoolProc["BooleanProcessor"]
MapProc["MappedItemProcessor"]
SurfProc["SurfaceProcessor"]
end
subgraph Output["Output"]
Mesh["Triangle Mesh"]
end
Input --> Router --> Processors --> Output
Geometry Representation Types
IFC Geometry Hierarchy
classDiagram
class IfcRepresentationItem {
<<abstract>>
}
class IfcSolidModel {
<<abstract>>
}
class IfcSweptAreaSolid {
+IfcProfileDef SweptArea
+IfcAxis2Placement3D Position
}
class IfcExtrudedAreaSolid {
+IfcDirection ExtrudedDirection
+IfcPositiveLengthMeasure Depth
}
class IfcFacetedBrep {
+IfcClosedShell Outer
}
class IfcBooleanResult {
+IfcBooleanOperand FirstOperand
+IfcBooleanOperand SecondOperand
+IfcBooleanOperator Operator
}
IfcRepresentationItem <|-- IfcSolidModel
IfcSolidModel <|-- IfcSweptAreaSolid
IfcSweptAreaSolid <|-- IfcExtrudedAreaSolid
IfcSolidModel <|-- IfcFacetedBrep
IfcSolidModel <|-- IfcBooleanResult
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
flowchart TB
subgraph Input["Input"]
Profile["2D Profile"]
Direction["Extrusion Direction"]
Depth["Depth"]
Position["Placement"]
end
subgraph Profile["Profile Processing"]
Extract["Extract Outer Boundary"]
Holes["Extract Inner Boundaries"]
Flatten["Flatten to 2D"]
end
subgraph Triangulate["Triangulation"]
Earcut["earcutr Algorithm"]
Bottom["Bottom Face"]
Top["Top Face"]
end
subgraph Extrude["Extrusion"]
Walls["Generate Side Walls"]
Join["Join Vertices"]
Normals["Compute Normals"]
end
subgraph Output["Output"]
Mesh["Triangle Mesh"]
end
Input --> Profile --> Triangulate --> Extrude --> Output
Profile Types
classDiagram
class IfcProfileDef {
<<abstract>>
+IfcProfileTypeEnum ProfileType
+IfcLabel ProfileName
}
class IfcRectangleProfileDef {
+IfcPositiveLengthMeasure XDim
+IfcPositiveLengthMeasure YDim
}
class IfcCircleProfileDef {
+IfcPositiveLengthMeasure Radius
}
class IfcArbitraryClosedProfileDef {
+IfcCurve OuterCurve
}
class IfcArbitraryProfileDefWithVoids {
+SET~IfcCurve~ InnerCurves
}
IfcProfileDef <|-- IfcRectangleProfileDef
IfcProfileDef <|-- IfcCircleProfileDef
IfcProfileDef <|-- IfcArbitraryClosedProfileDef
IfcArbitraryClosedProfileDef <|-- IfcArbitraryProfileDefWithVoids
Earcut Algorithm
flowchart LR
subgraph Input["Input"]
Poly["Polygon with Holes"]
end
subgraph Process["earcutr Process"]
Flatten["Flatten coordinates"]
Ear["Find ear"]
Clip["Clip ear"]
Repeat["Repeat until done"]
end
subgraph Output["Output"]
Indices["Triangle indices"]
end
Input --> Process --> Output
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
flowchart TB
subgraph Input["IfcFacetedBrep"]
Shell["IfcClosedShell"]
Faces["IfcFace[]"]
end
subgraph Process["Processing"]
Extract["Extract face bounds"]
Orient["Check orientation"]
Triangulate["Fan triangulation"]
Normals["Compute normals"]
end
subgraph Output["Output"]
Mesh["Triangle Mesh"]
end
Input --> Process --> Output
Face Triangulation
graph LR
subgraph Polygon["Face Polygon"]
V0["V0"]
V1["V1"]
V2["V2"]
V3["V3"]
V4["V4"]
end
subgraph Triangles["Fan Triangulation"]
T1["V0-V1-V2"]
T2["V0-V2-V3"]
T3["V0-V3-V4"]
end
V0 --> T1
V1 --> T1
V2 --> T1
V0 --> T2
V2 --> T2
V3 --> T2
Boolean Operations
CSG Pipeline
flowchart TB
subgraph Input["Input"]
First["First Operand"]
Second["Second Operand"]
Op["Operator"]
end
subgraph Prepare["Preparation"]
Mesh1["Triangulate First"]
Mesh2["Triangulate Second"]
end
subgraph CSG["CSG Operation"]
Intersect["Find Intersections"]
Classify["Classify Triangles"]
Combine["Combine Result"]
end
subgraph Output["Output"]
Result["Result Mesh"]
end
Input --> Prepare --> CSG --> Output
Boolean Operators
| Operator |
Description |
Common Use |
| DIFFERENCE |
A - B |
Wall openings |
| UNION |
A + B |
Composite shapes |
| INTERSECTION |
A ∩ B |
Clipping |
Placement Stack
flowchart TB
subgraph Stack["Transformation Stack"]
World["World Origin"]
Site["Site Placement"]
Building["Building Placement"]
Storey["Storey Placement"]
Element["Element Placement"]
Local["Local Placement"]
end
subgraph Matrix["Combined Matrix"]
M["4x4 Transform"]
end
World --> Site --> Building --> Storey --> Element --> Local
Local --> M
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
flowchart LR
subgraph Problem["Problem"]
Large["Large Coords<br/>(487234.5, 5234891.2, 0)"]
Float32["Float32 Precision<br/>(7 digits)"]
Jitter["Visual Jitter"]
end
subgraph Solution["Solution"]
Detect["Detect large values"]
Shift["Compute origin shift"]
Apply["Apply to all vertices"]
Store["Store offset"]
end
Problem --> Solution
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
graph LR
subgraph Circle["Circle Approximation"]
Fast["FAST: 8 segments"]
Balanced["BALANCED: 16 segments"]
High["HIGH: 32 segments"]
end
| Mode |
Segments |
Triangles |
Use Case |
| FAST |
8 |
Fewer |
Mobile, preview |
| BALANCED |
16 |
Medium |
Default |
| HIGH |
32 |
More |
Detailed viewing |
Instancing
MappedItem Processing
flowchart TB
subgraph Definition["Mapped Representation"]
Source["Source Geometry"]
Transform["Transform Matrix"]
end
subgraph Detection["Instance Detection"]
Hash["Hash source ID"]
Lookup["Lookup in cache"]
end
subgraph Output["Output"]
Reuse["Reuse existing mesh"]
Transforms["Instance transforms[]"]
end
Definition --> Detection
Detection -->|"Cache hit"| Reuse
Detection -->|"Cache miss"| Source
Source --> Reuse
Reuse --> Transforms
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
sequenceDiagram
participant Parser
participant Queue as Entity Queue
participant Router
participant Processor
participant Collector as Mesh Collector
participant GPU
Parser->>Queue: Entities with geometry
loop Batch Processing
Queue->>Router: Entity batch
Router->>Processor: Dispatch by type
Processor->>Processor: Triangulate
Processor->>Collector: Mesh batch
Collector->>GPU: Upload buffers
end
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
});
}
}
| 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