Skip to main content

bimifc_model/
geometry.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Geometry source trait for 3D rendering
6
7use crate::{EntityId, IfcType, MeshData};
8use std::sync::Arc;
9
10/// Entity geometry with mesh, color, and transform
11#[derive(Clone, Debug)]
12pub struct EntityGeometry {
13    /// Processed mesh data (shared via Arc)
14    pub mesh: Arc<MeshData>,
15    /// RGBA color [r, g, b, a] where values are 0.0-1.0
16    pub color: [f32; 4],
17    /// 4x4 transformation matrix (column-major order)
18    pub transform: [f32; 16],
19}
20
21impl EntityGeometry {
22    /// Create new entity geometry
23    pub fn new(mesh: Arc<MeshData>, color: [f32; 4], transform: [f32; 16]) -> Self {
24        Self {
25            mesh,
26            color,
27            transform,
28        }
29    }
30
31    /// Create geometry with identity transform
32    pub fn with_identity_transform(mesh: Arc<MeshData>, color: [f32; 4]) -> Self {
33        Self {
34            mesh,
35            color,
36            transform: [
37                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
38            ],
39        }
40    }
41
42    /// Check if geometry is empty
43    pub fn is_empty(&self) -> bool {
44        self.mesh.is_empty()
45    }
46
47    /// Get triangle count
48    pub fn triangle_count(&self) -> usize {
49        self.mesh.triangle_count()
50    }
51}
52
53impl Default for EntityGeometry {
54    fn default() -> Self {
55        Self {
56            mesh: Arc::new(MeshData::default()),
57            color: [0.8, 0.8, 0.8, 1.0], // Light gray default
58            transform: [
59                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
60            ],
61        }
62    }
63}
64
65/// Geometry source for rendering
66///
67/// Provides access to processed geometry data ready for GPU rendering.
68/// Implementations handle geometry processing, caching, and color assignment.
69///
70/// # Example
71///
72/// ```ignore
73/// use bimifc_model::{GeometrySource, EntityId};
74///
75/// fn render_model(geometry: &dyn GeometrySource) {
76///     // Get all entities with geometry
77///     let entities = geometry.entities_with_geometry();
78///     println!("Rendering {} entities", entities.len());
79///
80///     // Process each entity
81///     for id in entities {
82///         if let Some(geom) = geometry.get_geometry(id) {
83///             println!("Entity {:?}: {} triangles", id, geom.triangle_count());
84///             // Submit to GPU...
85///         }
86///     }
87///
88///     // Or batch process for efficiency
89///     let all_geom = geometry.batch_geometry(&entities);
90///     for (id, geom) in all_geom {
91///         // Submit to GPU...
92///     }
93/// }
94/// ```
95pub trait GeometrySource: Send + Sync {
96    /// Get all entity IDs that have processable geometry
97    ///
98    /// # Returns
99    /// A vector of entity IDs that have geometry representations
100    fn entities_with_geometry(&self) -> Vec<EntityId>;
101
102    /// Check if an entity has processable geometry
103    ///
104    /// # Arguments
105    /// * `id` - The entity ID to check
106    ///
107    /// # Returns
108    /// `true` if the entity has geometry that can be processed
109    fn has_geometry(&self, id: EntityId) -> bool;
110
111    /// Get processed geometry for a single entity
112    ///
113    /// # Arguments
114    /// * `id` - The entity ID to get geometry for
115    ///
116    /// # Returns
117    /// The processed geometry if available
118    fn get_geometry(&self, id: EntityId) -> Option<EntityGeometry>;
119
120    /// Batch process geometry for multiple entities
121    ///
122    /// This is more efficient than calling `get_geometry` repeatedly
123    /// as it can leverage caching and parallel processing.
124    ///
125    /// # Arguments
126    /// * `ids` - The entity IDs to process
127    ///
128    /// # Returns
129    /// A vector of (entity_id, geometry) pairs
130    fn batch_geometry(&self, ids: &[EntityId]) -> Vec<(EntityId, EntityGeometry)> {
131        ids.iter()
132            .filter_map(|id| self.get_geometry(*id).map(|g| (*id, g)))
133            .collect()
134    }
135
136    /// Get default color for an entity type
137    ///
138    /// Returns a color based on the IFC type for consistent visualization.
139    ///
140    /// # Arguments
141    /// * `ifc_type` - The IFC type
142    ///
143    /// # Returns
144    /// RGBA color array [r, g, b, a]
145    fn default_color(&self, ifc_type: &IfcType) -> [f32; 4] {
146        get_default_color(ifc_type)
147    }
148
149    /// Get total triangle count for all geometry
150    fn total_triangle_count(&self) -> usize {
151        self.entities_with_geometry()
152            .iter()
153            .filter_map(|id| self.get_geometry(*id))
154            .map(|g| g.triangle_count())
155            .sum()
156    }
157}
158
159/// Get default color for an IFC type
160///
161/// Provides consistent colors for different element types.
162pub fn get_default_color(ifc_type: &IfcType) -> [f32; 4] {
163    match ifc_type {
164        // Walls - light beige/tan
165        IfcType::IfcWall | IfcType::IfcWallStandardCase => [0.85, 0.80, 0.70, 1.0],
166
167        // Curtain walls - blue-gray glass
168        IfcType::IfcCurtainWall => [0.6, 0.7, 0.8, 0.7],
169
170        // Slabs/floors - light gray concrete
171        IfcType::IfcSlab => [0.75, 0.75, 0.75, 1.0],
172
173        // Roofs - terracotta/clay
174        IfcType::IfcRoof => [0.72, 0.45, 0.35, 1.0],
175
176        // Beams - structural blue-gray
177        IfcType::IfcBeam => [0.55, 0.60, 0.65, 1.0],
178
179        // Columns - structural gray
180        IfcType::IfcColumn => [0.60, 0.60, 0.60, 1.0],
181
182        // Doors - wood brown
183        IfcType::IfcDoor => [0.55, 0.40, 0.25, 1.0],
184
185        // Windows - light blue glass
186        IfcType::IfcWindow => [0.7, 0.85, 0.95, 0.5],
187
188        // Stairs - warm gray
189        IfcType::IfcStair | IfcType::IfcStairFlight => [0.70, 0.68, 0.65, 1.0],
190
191        // Ramps
192        IfcType::IfcRamp | IfcType::IfcRampFlight => [0.70, 0.68, 0.65, 1.0],
193
194        // Railings - metallic gray
195        IfcType::IfcRailing => [0.50, 0.50, 0.55, 1.0],
196
197        // Coverings - white
198        IfcType::IfcCovering => [0.95, 0.95, 0.95, 1.0],
199
200        // Plates - steel blue
201        IfcType::IfcPlate => [0.60, 0.65, 0.70, 1.0],
202
203        // Members - structural
204        IfcType::IfcMember => [0.58, 0.58, 0.58, 1.0],
205
206        // Footings - concrete gray
207        IfcType::IfcFooting => [0.65, 0.65, 0.65, 1.0],
208
209        // Piles - dark concrete
210        IfcType::IfcPile => [0.55, 0.55, 0.55, 1.0],
211
212        // Furniture - wood tones
213        IfcType::IfcFurnishingElement | IfcType::IfcFurniture => [0.65, 0.50, 0.35, 1.0],
214
215        // MEP elements - various colors
216        IfcType::IfcDistributionElement | IfcType::IfcDistributionFlowElement => {
217            [0.5, 0.7, 0.5, 1.0]
218        }
219        IfcType::IfcFlowTerminal => [0.7, 0.7, 0.5, 1.0],
220        IfcType::IfcFlowSegment => [0.5, 0.5, 0.7, 1.0],
221        IfcType::IfcFlowFitting => [0.6, 0.5, 0.6, 1.0],
222
223        // Openings - transparent red (usually not rendered)
224        IfcType::IfcOpeningElement | IfcType::IfcOpeningStandardCase => [1.0, 0.3, 0.3, 0.3],
225
226        // Light fixtures - warm yellow
227        IfcType::IfcLightFixture => [1.0, 0.9, 0.3, 1.0],
228
229        // Element assembly - light blue (panel/bracket)
230        IfcType::IfcElementAssembly => [0.6, 0.7, 0.85, 1.0],
231
232        // Building element proxy - purple (catch-all)
233        IfcType::IfcBuildingElementProxy => [0.7, 0.5, 0.8, 1.0],
234
235        // Infrastructure elements
236        IfcType::IfcRoad | IfcType::IfcRoadPart => [0.4, 0.4, 0.4, 1.0],
237        IfcType::IfcBridge | IfcType::IfcBridgePart => [0.6, 0.6, 0.55, 1.0],
238        IfcType::IfcRailway | IfcType::IfcRailwayPart => [0.5, 0.45, 0.4, 1.0],
239        IfcType::IfcPavement => [0.35, 0.35, 0.35, 1.0],
240
241        // Default - medium gray
242        _ => [0.7, 0.7, 0.7, 1.0],
243    }
244}
245
246/// Geometry processing options
247#[derive(Clone, Debug, Default)]
248pub struct GeometryOptions {
249    /// Whether to compute normals if not provided
250    pub compute_normals: bool,
251    /// Whether to deduplicate identical meshes
252    pub deduplicate: bool,
253    /// Whether to merge small meshes
254    pub merge_small_meshes: bool,
255    /// Minimum triangle count for merging
256    pub merge_threshold: usize,
257}
258
259impl GeometryOptions {
260    /// Create options for fast processing (less optimization)
261    pub fn fast() -> Self {
262        Self {
263            compute_normals: true,
264            deduplicate: false,
265            merge_small_meshes: false,
266            merge_threshold: 0,
267        }
268    }
269
270    /// Create options for optimized output (slower processing)
271    pub fn optimized() -> Self {
272        Self {
273            compute_normals: true,
274            deduplicate: true,
275            merge_small_meshes: true,
276            merge_threshold: 100,
277        }
278    }
279}