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}