Skip to content

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

Coordinate Transformations

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
    });
  }
}

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