1use crate::simplify_math::{
26 averaged_vertex_normals, conjugate_yup_to_zup, invert_affine_row_major,
27 transform_point_row_major, yup_to_zup, zup_to_yup,
28};
29use ifc_lite_geometry::simplify::{simplify_mesh, SimplifyOptions};
30use ifc_lite_geometry::Mesh;
31
32#[derive(Debug, Clone)]
35pub struct SimplifyRecordInput<'a> {
36 pub positions: &'a [f32],
38 pub normals: &'a [f32],
40 pub indices: &'a [u32],
42 pub origin: [f64; 3],
44 pub local_to_world: Option<[f64; 16]>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SimplifySkip {
53 NoGeometry,
55 MissingPlacement,
59 SingularPlacement,
61 EmptyResult,
64 InvalidUnitScale,
68}
69
70impl SimplifySkip {
71 pub fn as_str(&self) -> &'static str {
72 match self {
73 SimplifySkip::NoGeometry => "no-geometry",
74 SimplifySkip::MissingPlacement => "missing-placement",
75 SimplifySkip::SingularPlacement => "singular-placement",
76 SimplifySkip::EmptyResult => "empty-result",
77 SimplifySkip::InvalidUnitScale => "invalid-unit-scale",
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
85pub struct SimplifiedElement {
86 pub render_positions: Vec<f32>,
88 pub render_normals: Vec<f32>,
89 pub render_indices: Vec<u32>,
91 pub render_origin: [f64; 3],
93 pub local_positions: Vec<f64>,
96 pub local_indices: Vec<u32>,
99 pub tris_before: u32,
100 pub tris_after: u32,
101 pub cavity_components_dropped: u32,
102}
103
104pub fn simplify_element(
110 records: &[SimplifyRecordInput<'_>],
111 level: u8,
112 rtc_offset: [f64; 3],
113 unit_scale: f64,
114 y_up: bool,
115) -> Result<SimplifiedElement, SimplifySkip> {
116 let l2w_raw = records
119 .iter()
120 .find_map(|r| r.local_to_world)
121 .ok_or(SimplifySkip::MissingPlacement)?;
122 let l2w = if y_up {
123 conjugate_yup_to_zup(&l2w_raw)
124 } else {
125 l2w_raw
126 };
127 let inv_l2w = invert_affine_row_major(&l2w).ok_or(SimplifySkip::SingularPlacement)?;
128 if !(unit_scale.is_finite() && unit_scale > 0.0) {
129 return Err(SimplifySkip::InvalidUnitScale);
130 }
131
132 let mut world: Vec<[f64; 3]> = Vec::new();
135 let mut normals: Vec<f32> = Vec::new();
136 let mut indices: Vec<u32> = Vec::new();
137 let mut have_normals = true;
138 for rec in records {
139 let base = world.len() as u32;
140 let n_verts = rec.positions.len() / 3;
141 let origin = if y_up {
142 yup_to_zup(rec.origin)
143 } else {
144 rec.origin
145 };
146 for chunk in rec.positions.chunks_exact(3) {
147 let p = [chunk[0] as f64, chunk[1] as f64, chunk[2] as f64];
148 let p = if y_up { yup_to_zup(p) } else { p };
149 world.push([p[0] + origin[0], p[1] + origin[1], p[2] + origin[2]]);
150 }
151 if rec.normals.len() == rec.positions.len() {
152 for chunk in rec.normals.chunks_exact(3) {
153 let n = [chunk[0] as f64, chunk[1] as f64, chunk[2] as f64];
154 let n = if y_up { yup_to_zup(n) } else { n };
155 normals.extend_from_slice(&[n[0] as f32, n[1] as f32, n[2] as f32]);
156 }
157 } else {
158 have_normals = false;
159 }
160 for tri in rec.indices.chunks_exact(3) {
161 if (tri[0] as usize) >= n_verts
162 || (tri[1] as usize) >= n_verts
163 || (tri[2] as usize) >= n_verts
164 {
165 continue;
166 }
167 indices.extend_from_slice(&[tri[0] + base, tri[1] + base, tri[2] + base]);
170 }
171 }
172 if world.is_empty() || indices.is_empty() {
173 return Err(SimplifySkip::NoGeometry);
174 }
175
176 let mut min = [f64::INFINITY; 3];
180 let mut max = [f64::NEG_INFINITY; 3];
181 for w in &world {
182 for k in 0..3 {
183 min[k] = min[k].min(w[k]);
184 max[k] = max[k].max(w[k]);
185 }
186 }
187 let centre = [
188 0.5 * (min[0] + max[0]),
189 0.5 * (min[1] + max[1]),
190 0.5 * (min[2] + max[2]),
191 ];
192 let mut mesh = Mesh::new();
193 mesh.positions = world
194 .iter()
195 .flat_map(|w| {
196 [
197 (w[0] - centre[0]) as f32,
198 (w[1] - centre[1]) as f32,
199 (w[2] - centre[2]) as f32,
200 ]
201 })
202 .collect();
203 mesh.normals = if have_normals && normals.len() == mesh.positions.len() {
204 normals
205 } else {
206 Vec::new()
207 };
208 mesh.indices = indices;
209 mesh.origin = centre;
210 mesh.local_to_world = Some(l2w);
211
212 let (mut out, stats) = simplify_mesh(&mesh, &SimplifyOptions::for_level(level));
214 if out.indices.is_empty() || out.positions.is_empty() {
215 return Err(SimplifySkip::EmptyResult);
216 }
217 if out.normals.len() != out.positions.len() {
218 out.normals = averaged_vertex_normals(&out.positions, &out.indices);
219 }
220
221 let n_out = out.positions.len() / 3;
223 let mut local_positions: Vec<f64> = Vec::with_capacity(n_out * 3);
224 for chunk in out.positions.chunks_exact(3) {
225 let tw = [
226 chunk[0] as f64 + out.origin[0] + rtc_offset[0],
227 chunk[1] as f64 + out.origin[1] + rtc_offset[1],
228 chunk[2] as f64 + out.origin[2] + rtc_offset[2],
229 ];
230 let local = transform_point_row_major(&inv_l2w, tw);
231 local_positions.extend_from_slice(&[
232 local[0] / unit_scale,
233 local[1] / unit_scale,
234 local[2] / unit_scale,
235 ]);
236 }
237 let local_indices = out.indices.clone();
238
239 let (render_positions, render_normals, render_indices, render_origin) = if y_up {
241 let positions = out
242 .positions
243 .chunks_exact(3)
244 .flat_map(|c| {
245 let p = zup_to_yup([c[0] as f64, c[1] as f64, c[2] as f64]);
246 [p[0] as f32, p[1] as f32, p[2] as f32]
247 })
248 .collect();
249 let normals = out
250 .normals
251 .chunks_exact(3)
252 .flat_map(|c| {
253 let n = zup_to_yup([c[0] as f64, c[1] as f64, c[2] as f64]);
254 [n[0] as f32, n[1] as f32, n[2] as f32]
255 })
256 .collect();
257 (positions, normals, out.indices, zup_to_yup(out.origin))
258 } else {
259 (out.positions, out.normals, out.indices, out.origin)
260 };
261
262 Ok(SimplifiedElement {
263 render_positions,
264 render_normals,
265 render_indices,
266 render_origin,
267 local_positions,
268 local_indices,
269 tris_before: stats.tris_before,
270 tris_after: stats.tris_after,
271 cavity_components_dropped: stats.cavity_components_dropped,
272 })
273}
274