use std::collections::BTreeMap;
use serde::Serialize;
use crate::MeshData;
#[derive(Debug, Clone, Serialize)]
pub struct ExportedElement {
pub ifc_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub global_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub vertices: Vec<[f64; 3]>,
pub faces: Vec<[u32; 3]>,
pub color: [f32; 4],
}
#[derive(Debug, Clone, Serialize)]
pub struct GeometryDataExport {
pub schema: &'static str,
pub version: u32,
pub up_axis: &'static str,
pub units: &'static str,
pub rtc_offset: [f64; 3],
pub element_count: usize,
pub elements: BTreeMap<u32, ExportedElement>,
}
pub fn build_geometry_data_export(
meshes: &[MeshData],
rtc_offset: [f64; 3],
site_rotation: Option<&[f64]>,
) -> GeometryDataExport {
let mut elements: BTreeMap<u32, ExportedElement> = BTreeMap::new();
let rot = match site_rotation {
Some(m) if m.len() >= 16 => Some(m),
_ => None,
};
for m in meshes {
if m.geometry_class != 0 || m.indices.is_empty() {
continue;
}
let o = m.origin;
let verts: Vec<[f64; 3]> = m
.positions
.chunks_exact(3)
.map(|p| {
let (x, y, z) = (p[0] as f64 + o[0], p[1] as f64 + o[1], p[2] as f64 + o[2]);
match rot {
Some(r) => [
r[0] * x + r[4] * y + r[8] * z + rtc_offset[0],
r[1] * x + r[5] * y + r[9] * z + rtc_offset[1],
r[2] * x + r[6] * y + r[10] * z + rtc_offset[2],
],
None => [x + rtc_offset[0], y + rtc_offset[1], z + rtc_offset[2]],
}
})
.collect();
let entry = elements
.entry(m.express_id)
.or_insert_with(|| ExportedElement {
ifc_type: m.ifc_type.clone(),
global_id: m.global_id.clone(),
name: m.name.clone(),
vertices: Vec::new(),
faces: Vec::new(),
color: m.color,
});
let base = entry.vertices.len() as u32;
entry.vertices.extend_from_slice(&verts);
entry.faces.extend(
m.indices
.chunks_exact(3)
.map(|t| [t[0] + base, t[1] + base, t[2] + base]),
);
}
for el in elements.values_mut() {
let (v, f) = weld_positions(&el.vertices, &el.faces, 1.0e-6);
el.vertices = v;
el.faces = f;
}
let element_count = elements.len();
GeometryDataExport {
schema: "ifc-lite-geometry-data",
version: 1,
up_axis: "Z",
units: "m",
rtc_offset,
element_count,
elements,
}
}
fn weld_positions(
verts: &[[f64; 3]],
faces: &[[u32; 3]],
eps: f64,
) -> (Vec<[f64; 3]>, Vec<[u32; 3]>) {
let inv = 1.0 / eps;
let key = |v: &[f64; 3]| -> (i64, i64, i64) {
(
(v[0] * inv).round() as i64,
(v[1] * inv).round() as i64,
(v[2] * inv).round() as i64,
)
};
let mut map: BTreeMap<(i64, i64, i64), u32> = BTreeMap::new();
let mut out_verts: Vec<[f64; 3]> = Vec::new();
let mut remap: Vec<u32> = Vec::with_capacity(verts.len());
for v in verts {
let k = key(v);
let idx = *map.entry(k).or_insert_with(|| {
out_verts.push(*v);
(out_verts.len() - 1) as u32
});
remap.push(idx);
}
let mut out_faces: Vec<[u32; 3]> = Vec::with_capacity(faces.len());
for f in faces {
let (a, b, c) = (
remap[f[0] as usize],
remap[f[1] as usize],
remap[f[2] as usize],
);
if a != b && b != c && a != c {
out_faces.push([a, b, c]);
}
}
(out_verts, out_faces)
}
impl GeometryDataExport {
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}