use crate::simplify_math::{
averaged_vertex_normals, conjugate_yup_to_zup, invert_affine_row_major,
transform_point_row_major, yup_to_zup, zup_to_yup,
};
use ifc_lite_geometry::simplify::{simplify_mesh, SimplifyOptions};
use ifc_lite_geometry::Mesh;
#[derive(Debug, Clone)]
pub struct SimplifyRecordInput<'a> {
pub positions: &'a [f32],
pub normals: &'a [f32],
pub indices: &'a [u32],
pub origin: [f64; 3],
pub local_to_world: Option<[f64; 16]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SimplifySkip {
NoGeometry,
MissingPlacement,
SingularPlacement,
EmptyResult,
InvalidUnitScale,
}
impl SimplifySkip {
pub fn as_str(&self) -> &'static str {
match self {
SimplifySkip::NoGeometry => "no-geometry",
SimplifySkip::MissingPlacement => "missing-placement",
SimplifySkip::SingularPlacement => "singular-placement",
SimplifySkip::EmptyResult => "empty-result",
SimplifySkip::InvalidUnitScale => "invalid-unit-scale",
}
}
}
#[derive(Debug, Clone)]
pub struct SimplifiedElement {
pub render_positions: Vec<f32>,
pub render_normals: Vec<f32>,
pub render_indices: Vec<u32>,
pub render_origin: [f64; 3],
pub local_positions: Vec<f64>,
pub local_indices: Vec<u32>,
pub tris_before: u32,
pub tris_after: u32,
pub cavity_components_dropped: u32,
}
pub fn simplify_element(
records: &[SimplifyRecordInput<'_>],
level: u8,
rtc_offset: [f64; 3],
unit_scale: f64,
y_up: bool,
) -> Result<SimplifiedElement, SimplifySkip> {
let l2w_raw = records
.iter()
.find_map(|r| r.local_to_world)
.ok_or(SimplifySkip::MissingPlacement)?;
let l2w = if y_up {
conjugate_yup_to_zup(&l2w_raw)
} else {
l2w_raw
};
let inv_l2w = invert_affine_row_major(&l2w).ok_or(SimplifySkip::SingularPlacement)?;
if !(unit_scale.is_finite() && unit_scale > 0.0) {
return Err(SimplifySkip::InvalidUnitScale);
}
let mut world: Vec<[f64; 3]> = Vec::new();
let mut normals: Vec<f32> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut have_normals = true;
for rec in records {
let base = world.len() as u32;
let n_verts = rec.positions.len() / 3;
let origin = if y_up {
yup_to_zup(rec.origin)
} else {
rec.origin
};
for chunk in rec.positions.chunks_exact(3) {
let p = [chunk[0] as f64, chunk[1] as f64, chunk[2] as f64];
let p = if y_up { yup_to_zup(p) } else { p };
world.push([p[0] + origin[0], p[1] + origin[1], p[2] + origin[2]]);
}
if rec.normals.len() == rec.positions.len() {
for chunk in rec.normals.chunks_exact(3) {
let n = [chunk[0] as f64, chunk[1] as f64, chunk[2] as f64];
let n = if y_up { yup_to_zup(n) } else { n };
normals.extend_from_slice(&[n[0] as f32, n[1] as f32, n[2] as f32]);
}
} else {
have_normals = false;
}
for tri in rec.indices.chunks_exact(3) {
if (tri[0] as usize) >= n_verts
|| (tri[1] as usize) >= n_verts
|| (tri[2] as usize) >= n_verts
{
continue;
}
if y_up {
indices.extend_from_slice(&[tri[0] + base, tri[2] + base, tri[1] + base]);
} else {
indices.extend_from_slice(&[tri[0] + base, tri[1] + base, tri[2] + base]);
}
}
}
if world.is_empty() || indices.is_empty() {
return Err(SimplifySkip::NoGeometry);
}
let mut min = [f64::INFINITY; 3];
let mut max = [f64::NEG_INFINITY; 3];
for w in &world {
for k in 0..3 {
min[k] = min[k].min(w[k]);
max[k] = max[k].max(w[k]);
}
}
let centre = [
0.5 * (min[0] + max[0]),
0.5 * (min[1] + max[1]),
0.5 * (min[2] + max[2]),
];
let mut mesh = Mesh::new();
mesh.positions = world
.iter()
.flat_map(|w| {
[
(w[0] - centre[0]) as f32,
(w[1] - centre[1]) as f32,
(w[2] - centre[2]) as f32,
]
})
.collect();
mesh.normals = if have_normals && normals.len() == mesh.positions.len() {
normals
} else {
Vec::new()
};
mesh.indices = indices;
mesh.origin = centre;
mesh.local_to_world = Some(l2w);
let (mut out, stats) = simplify_mesh(&mesh, &SimplifyOptions::for_level(level));
if out.indices.is_empty() || out.positions.is_empty() {
return Err(SimplifySkip::EmptyResult);
}
if out.normals.len() != out.positions.len() {
out.normals = averaged_vertex_normals(&out.positions, &out.indices);
}
let n_out = out.positions.len() / 3;
let mut local_positions: Vec<f64> = Vec::with_capacity(n_out * 3);
for chunk in out.positions.chunks_exact(3) {
let tw = [
chunk[0] as f64 + out.origin[0] + rtc_offset[0],
chunk[1] as f64 + out.origin[1] + rtc_offset[1],
chunk[2] as f64 + out.origin[2] + rtc_offset[2],
];
let local = transform_point_row_major(&inv_l2w, tw);
local_positions.extend_from_slice(&[
local[0] / unit_scale,
local[1] / unit_scale,
local[2] / unit_scale,
]);
}
let local_indices = out.indices.clone();
let (render_positions, render_normals, render_indices, render_origin) = if y_up {
let positions = out
.positions
.chunks_exact(3)
.flat_map(|c| {
let p = zup_to_yup([c[0] as f64, c[1] as f64, c[2] as f64]);
[p[0] as f32, p[1] as f32, p[2] as f32]
})
.collect();
let normals = out
.normals
.chunks_exact(3)
.flat_map(|c| {
let n = zup_to_yup([c[0] as f64, c[1] as f64, c[2] as f64]);
[n[0] as f32, n[1] as f32, n[2] as f32]
})
.collect();
let mut indices = out.indices.clone();
for tri in indices.chunks_exact_mut(3) {
tri.swap(1, 2);
}
(positions, normals, indices, zup_to_yup(out.origin))
} else {
(out.positions, out.normals, out.indices, out.origin)
};
Ok(SimplifiedElement {
render_positions,
render_normals,
render_indices,
render_origin,
local_positions,
local_indices,
tris_before: stats.tris_before,
tris_after: stats.tris_after,
cavity_components_dropped: stats.cavity_components_dropped,
})
}