1use std::collections::BTreeMap;
29
30use serde::Serialize;
31
32use crate::MeshData;
33
34#[derive(Debug, Clone, Serialize)]
36pub struct ExportedElement {
37 pub ifc_type: String,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub global_id: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub name: Option<String>,
42 pub vertices: Vec<[f64; 3]>,
44 pub faces: Vec<[u32; 3]>,
46 pub color: [f32; 4],
48}
49
50#[derive(Debug, Clone, Serialize)]
52pub struct ColoredExportedElement {
53 #[serde(flatten)]
54 pub geometry: ExportedElement,
55 #[serde(skip_serializing_if = "Vec::is_empty")]
57 pub palette: Vec<[f32; 4]>,
58 #[serde(skip_serializing_if = "Vec::is_empty")]
60 pub face_colors: Vec<u64>,
61}
62
63#[derive(Debug, Clone, Serialize)]
66pub struct GeometryDataExport {
67 pub schema: &'static str,
68 pub version: u32,
69 pub up_axis: &'static str,
71 pub units: &'static str,
73 pub rtc_offset: [f64; 3],
77 pub element_count: usize,
78 pub elements: BTreeMap<u32, ExportedElement>,
81}
82
83#[derive(Debug, Clone, Serialize)]
85pub struct ColoredGeometryDataExport {
86 pub schema: &'static str,
87 pub version: u32,
88 pub up_axis: &'static str,
89 pub units: &'static str,
90 pub rtc_offset: [f64; 3],
91 pub element_count: usize,
92 pub elements: BTreeMap<u32, ColoredExportedElement>,
93}
94
95pub fn build_geometry_data_export(
106 meshes: &[MeshData],
107 rtc_offset: [f64; 3],
108 site_rotation: Option<&[f64]>,
109) -> GeometryDataExport {
110 let (elements, _) = build_elements::<false>(meshes, rtc_offset, site_rotation);
111 GeometryDataExport {
112 schema: "ifc-lite-geometry-data",
113 version: 1,
114 up_axis: "Z",
115 units: "m",
116 rtc_offset,
117 element_count: elements.len(),
118 elements,
119 }
120}
121
122pub fn build_colored_geometry_data_export(
124 meshes: &[MeshData],
125 rtc_offset: [f64; 3],
126 site_rotation: Option<&[f64]>,
127) -> ColoredGeometryDataExport {
128 let (geometry, mut colors) = build_elements::<true>(meshes, rtc_offset, site_rotation);
129 let elements: BTreeMap<u32, ColoredExportedElement> = geometry
130 .into_iter()
131 .map(|(id, geometry)| {
132 let metadata = colors.remove(&id).unwrap_or_default();
133 (id, ColoredExportedElement {
134 geometry,
135 palette: metadata.palette,
136 face_colors: metadata.face_colors,
137 })
138 })
139 .collect();
140 ColoredGeometryDataExport {
141 schema: "ifc-lite-geometry-data",
142 version: 1,
143 up_axis: "Z",
144 units: "m",
145 rtc_offset,
146 element_count: elements.len(),
147 elements,
148 }
149}
150
151type ElementMap = BTreeMap<u32, ExportedElement>;
152type ColorMap = BTreeMap<u32, ColorMetadata>;
153
154fn build_elements<const COLORS: bool>(
155 meshes: &[MeshData],
156 rtc_offset: [f64; 3],
157 site_rotation: Option<&[f64]>,
158) -> (ElementMap, ColorMap) {
159 let mut elements = ElementMap::new();
162 let mut colors = ColorMap::new();
163 let rot = match site_rotation {
164 Some(m) if m.len() >= 16 => Some(m),
165 _ => None,
166 };
167
168 for m in meshes {
169 if matches!(m.geometry_class, 1 | 2) || m.indices.is_empty() {
172 continue;
173 }
174
175 let o = m.origin;
176 let verts: Vec<[f64; 3]> = m
177 .positions
178 .chunks_exact(3)
179 .map(|p| {
180 let (x, y, z) = (p[0] as f64 + o[0], p[1] as f64 + o[1], p[2] as f64 + o[2]);
182 match rot {
183 Some(r) => [
185 r[0] * x + r[4] * y + r[8] * z + rtc_offset[0],
186 r[1] * x + r[5] * y + r[9] * z + rtc_offset[1],
187 r[2] * x + r[6] * y + r[10] * z + rtc_offset[2],
188 ],
189 None => [x + rtc_offset[0], y + rtc_offset[1], z + rtc_offset[2]],
190 }
191 })
192 .collect();
193
194 let entry = elements
195 .entry(m.express_id)
196 .or_insert_with(|| ExportedElement {
197 ifc_type: m.ifc_type.clone(),
198 global_id: m.global_id.clone(),
199 name: m.name.clone(),
200 vertices: Vec::new(),
201 faces: Vec::new(),
202 color: m.color,
203 });
204 if COLORS {
205 if let Some(metadata) = colors.get_mut(&m.express_id) {
206 metadata.append_color(m.color, m.indices.len() / 3);
207 } else if entry.color != m.color {
208 let mut metadata = ColorMetadata::new(entry.color, entry.faces.len());
209 metadata.append_color(m.color, m.indices.len() / 3);
210 colors.insert(m.express_id, metadata);
211 }
212 }
213
214 let base = entry.vertices.len() as u32;
217 entry.vertices.extend_from_slice(&verts);
218 entry.faces.extend(
219 m.indices
220 .chunks_exact(3)
221 .map(|t| [t[0] + base, t[1] + base, t[2] + base]),
222 );
223 }
224
225 for (id, element) in &mut elements {
227 let metadata = if COLORS { colors.get_mut(id) } else { None };
228 let face_colors = metadata
229 .as_ref()
230 .map_or(&[][..], |m| m.face_colors.as_slice());
231 let (vertices, faces, kept) = weld_positions::<COLORS>(
232 &element.vertices,
233 &element.faces,
234 face_colors,
235 1e-6,
236 );
237 element.vertices = vertices;
238 element.faces = faces;
239 if let Some(metadata) = metadata {
240 metadata.face_colors = kept;
241 }
242 }
243 if COLORS {
244 colors.retain(|_, m| !m.face_colors.iter().all(|&index| index == 0));
247 }
248 (elements, colors)
249}
250
251#[derive(Default)]
253struct ColorMetadata {
254 palette: Vec<[f32; 4]>,
255 face_colors: Vec<u64>,
256 indices: BTreeMap<[u32; 4], u64>,
257}
258
259impl ColorMetadata {
260 fn new(fallback: [f32; 4], prior_faces: usize) -> Self {
261 let mut indices = BTreeMap::new();
262 indices.insert(color_key(fallback), 0);
263 Self {
264 palette: vec![fallback],
265 face_colors: vec![0; prior_faces],
266 indices,
267 }
268 }
269
270 fn append_color(&mut self, color: [f32; 4], face_count: usize) {
271 let next = self.palette.len() as u64;
272 let index = *self.indices.entry(color_key(color)).or_insert_with(|| {
273 self.palette.push(color);
274 next
275 });
276 self.face_colors.resize(self.face_colors.len() + face_count, index);
277 }
278}
279
280fn color_key(color: [f32; 4]) -> [u32; 4] {
281 color.map(|value| if value == 0.0 { 0 } else { value.to_bits() })
283}
284
285fn weld_positions<const COLORS: bool>(
288 verts: &[[f64; 3]],
289 faces: &[[u32; 3]],
290 colors: &[u64],
291 eps: f64,
292) -> (Vec<[f64; 3]>, Vec<[u32; 3]>, Vec<u64>) {
293 let inv = 1.0 / eps;
294 let key = |v: &[f64; 3]| -> (i64, i64, i64) {
295 (
296 (v[0] * inv).round() as i64,
297 (v[1] * inv).round() as i64,
298 (v[2] * inv).round() as i64,
299 )
300 };
301 let mut map: BTreeMap<(i64, i64, i64), u32> = BTreeMap::new();
302 let mut out_verts: Vec<[f64; 3]> = Vec::new();
303 let mut remap: Vec<u32> = Vec::with_capacity(verts.len());
304 for v in verts {
305 let k = key(v);
306 let idx = *map.entry(k).or_insert_with(|| {
307 out_verts.push(*v);
308 (out_verts.len() - 1) as u32
309 });
310 remap.push(idx);
311 }
312 let mut out_faces: Vec<[u32; 3]> = Vec::with_capacity(faces.len());
313 let mut kept = Vec::with_capacity(colors.len());
314 for (face_index, f) in faces.iter().enumerate() {
315 let (a, b, c) = (
316 remap[f[0] as usize],
317 remap[f[1] as usize],
318 remap[f[2] as usize],
319 );
320 if a != b && b != c && a != c {
321 out_faces.push([a, b, c]);
322 if COLORS && !colors.is_empty() {
323 kept.push(colors[face_index]);
324 }
325 }
326 }
327 (out_verts, out_faces, kept)
328}
329
330impl GeometryDataExport {
331 pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
333 serde_json::to_string_pretty(self)
334 }
335
336 pub fn to_json(&self) -> Result<String, serde_json::Error> {
338 serde_json::to_string(self)
339 }
340}
341
342impl ColoredGeometryDataExport {
343 pub fn to_json(&self) -> Result<String, serde_json::Error> {
345 serde_json::to_string(self)
346 }
347}