1use super::model::IfcxModel;
12use super::types::{attr, Transform4x4, UsdMesh};
13use bimifc_model::{
14 get_default_color, EntityGeometry, EntityId, GeometrySource, IfcModel, MeshData,
15};
16use rustc_hash::FxHashMap;
17use std::sync::Arc;
18
19pub struct IfcxGeometry {
21 model: Arc<IfcxModel>,
23 #[allow(dead_code)]
25 cache: FxHashMap<EntityId, Option<EntityGeometry>>,
26 entities_with_geom: Vec<EntityId>,
28}
29
30impl IfcxGeometry {
31 pub fn new(model: Arc<IfcxModel>) -> Self {
33 let mut entities_with_geom = Vec::new();
35
36 for id in model.resolver().all_ids() {
37 if let Some(node) = model.node(id) {
38 if node.attributes.contains_key(attr::MESH) {
39 entities_with_geom.push(id);
40 }
41 }
42 }
43
44 Self {
45 model,
46 cache: FxHashMap::default(),
47 entities_with_geom,
48 }
49 }
50
51 fn extract_geometry(&self, id: EntityId) -> Option<EntityGeometry> {
53 let node = self.model.node(id)?;
54
55 let mesh_value = node.attributes.get(attr::MESH)?;
57 let usd_mesh = UsdMesh::from_value(mesh_value)?;
58
59 let mesh_data = usd_mesh_to_mesh_data(&usd_mesh)?;
61
62 let transform = node
64 .attributes
65 .get(attr::TRANSFORM)
66 .and_then(Transform4x4::from_value)
67 .unwrap_or_default();
68
69 let color = extract_color(node, &self.model);
71
72 let transform_array = transform_to_array(&transform);
74
75 Some(EntityGeometry::new(
76 Arc::new(mesh_data),
77 color,
78 transform_array,
79 ))
80 }
81}
82
83impl GeometrySource for IfcxGeometry {
84 fn entities_with_geometry(&self) -> Vec<EntityId> {
85 self.entities_with_geom.clone()
86 }
87
88 fn has_geometry(&self, id: EntityId) -> bool {
89 if let Some(node) = self.model.node(id) {
90 node.attributes.contains_key(attr::MESH)
91 } else {
92 false
93 }
94 }
95
96 fn get_geometry(&self, id: EntityId) -> Option<EntityGeometry> {
97 self.extract_geometry(id)
100 }
101}
102
103fn usd_mesh_to_mesh_data(usd: &UsdMesh) -> Option<MeshData> {
105 if usd.points.is_empty() {
106 return None;
107 }
108
109 let indices = usd.triangulate();
111 if indices.is_empty() {
112 return None;
113 }
114
115 let mut positions = Vec::with_capacity(usd.points.len() * 3);
117 for p in &usd.points {
118 positions.push(p[0] as f32);
119 positions.push(p[1] as f32);
120 positions.push(p[2] as f32);
121 }
122
123 let normals = if let Some(ref usd_normals) = usd.normals {
125 let mut normals = Vec::with_capacity(usd_normals.len() * 3);
127 for n in usd_normals {
128 normals.push(n[0] as f32);
129 normals.push(n[1] as f32);
130 normals.push(n[2] as f32);
131 }
132 normals
133 } else {
134 compute_normals(&positions, &indices)
136 };
137
138 Some(MeshData {
139 positions,
140 normals,
141 indices,
142 })
143}
144
145fn compute_normals(positions: &[f32], indices: &[u32]) -> Vec<f32> {
147 let vertex_count = positions.len() / 3;
148 let mut normals = vec![0.0f32; vertex_count * 3];
149 let mut counts = vec![0u32; vertex_count];
150
151 for tri in indices.chunks(3) {
153 if tri.len() < 3 {
154 continue;
155 }
156
157 let i0 = tri[0] as usize;
158 let i1 = tri[1] as usize;
159 let i2 = tri[2] as usize;
160
161 if i0 * 3 + 2 >= positions.len()
162 || i1 * 3 + 2 >= positions.len()
163 || i2 * 3 + 2 >= positions.len()
164 {
165 continue;
166 }
167
168 let v0 = [
170 positions[i0 * 3],
171 positions[i0 * 3 + 1],
172 positions[i0 * 3 + 2],
173 ];
174 let v1 = [
175 positions[i1 * 3],
176 positions[i1 * 3 + 1],
177 positions[i1 * 3 + 2],
178 ];
179 let v2 = [
180 positions[i2 * 3],
181 positions[i2 * 3 + 1],
182 positions[i2 * 3 + 2],
183 ];
184
185 let e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
187 let e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
188
189 let nx = e1[1] * e2[2] - e1[2] * e2[1];
191 let ny = e1[2] * e2[0] - e1[0] * e2[2];
192 let nz = e1[0] * e2[1] - e1[1] * e2[0];
193
194 for &idx in &[i0, i1, i2] {
196 normals[idx * 3] += nx;
197 normals[idx * 3 + 1] += ny;
198 normals[idx * 3 + 2] += nz;
199 counts[idx] += 1;
200 }
201 }
202
203 for i in 0..vertex_count {
205 if counts[i] > 0 {
206 let nx = normals[i * 3];
207 let ny = normals[i * 3 + 1];
208 let nz = normals[i * 3 + 2];
209 let len = (nx * nx + ny * ny + nz * nz).sqrt();
210 if len > 1e-6 {
211 normals[i * 3] = nx / len;
212 normals[i * 3 + 1] = ny / len;
213 normals[i * 3 + 2] = nz / len;
214 } else {
215 normals[i * 3] = 0.0;
217 normals[i * 3 + 1] = 1.0;
218 normals[i * 3 + 2] = 0.0;
219 }
220 }
221 }
222
223 normals
224}
225
226fn extract_color(node: &super::types::ComposedNode, model: &IfcxModel) -> [f32; 4] {
228 if let Some(color_val) = node.attributes.get(attr::DIFFUSE_COLOR) {
230 if let Some(arr) = color_val.as_array() {
231 if arr.len() >= 3 {
232 let r = arr[0].as_f64().unwrap_or(0.7) as f32;
233 let g = arr[1].as_f64().unwrap_or(0.7) as f32;
234 let b = arr[2].as_f64().unwrap_or(0.7) as f32;
235
236 let a = node
238 .attributes
239 .get(attr::OPACITY)
240 .and_then(|v| v.as_f64())
241 .unwrap_or(1.0) as f32;
242
243 return [r, g, b, a];
244 }
245 }
246 }
247
248 if let Some(entity) = model
250 .resolver()
251 .get(model.id_for_path(&node.path).unwrap_or_default())
252 {
253 return get_default_color(&entity.ifc_type);
254 }
255
256 [0.7, 0.7, 0.7, 1.0]
258}
259
260fn transform_to_array(transform: &Transform4x4) -> [f32; 16] {
262 let m = &transform.matrix;
263 [
265 m[0][0] as f32,
266 m[1][0] as f32,
267 m[2][0] as f32,
268 m[3][0] as f32,
269 m[0][1] as f32,
270 m[1][1] as f32,
271 m[2][1] as f32,
272 m[3][1] as f32,
273 m[0][2] as f32,
274 m[1][2] as f32,
275 m[2][2] as f32,
276 m[3][2] as f32,
277 m[0][3] as f32,
278 m[1][3] as f32,
279 m[2][3] as f32,
280 m[3][3] as f32,
281 ]
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_usd_mesh_conversion() {
290 let usd = UsdMesh {
291 points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
292 face_vertex_indices: vec![0, 1, 2],
293 face_vertex_counts: None,
294 normals: None,
295 };
296
297 let mesh = usd_mesh_to_mesh_data(&usd).unwrap();
298
299 assert_eq!(mesh.positions.len(), 9); assert_eq!(mesh.indices.len(), 3); assert_eq!(mesh.normals.len(), 9); }
303
304 #[test]
305 fn test_triangulation() {
306 let usd = UsdMesh {
308 points: vec![
309 [0.0, 0.0, 0.0],
310 [1.0, 0.0, 0.0],
311 [1.0, 1.0, 0.0],
312 [0.0, 1.0, 0.0],
313 ],
314 face_vertex_indices: vec![0, 1, 2, 3],
315 face_vertex_counts: Some(vec![4]), normals: None,
317 };
318
319 let indices = usd.triangulate();
320 assert_eq!(indices.len(), 6); }
322}