1use crate::api::IfcAPI;
17use ifc_lite_processing::simplify_session::{simplify_element, SimplifyRecordInput, SimplifySkip};
18use wasm_bindgen::prelude::*;
19
20#[wasm_bindgen]
28pub struct SimplifiedMeshes {
29 element_ids: Vec<u32>,
30 levels: Vec<u8>,
31 vertex_counts: Vec<u32>,
32 index_counts: Vec<u32>,
33 render_positions: Vec<f32>,
34 render_normals: Vec<f32>,
35 render_indices: Vec<u32>,
36 render_origins: Vec<f64>,
37 local_positions: Vec<f64>,
38 local_indices: Vec<u32>,
39 tris_before: Vec<u32>,
40 tris_after: Vec<u32>,
41 cavities_dropped: Vec<u32>,
42 skipped_ids: Vec<u32>,
43 skipped_reasons: Vec<JsValue>,
44}
45
46#[wasm_bindgen]
47impl SimplifiedMeshes {
48 #[wasm_bindgen(getter, js_name = elementIds)]
49 pub fn element_ids(&self) -> Vec<u32> {
50 self.element_ids.clone()
51 }
52
53 #[wasm_bindgen(getter)]
54 pub fn levels(&self) -> Vec<u8> {
55 self.levels.clone()
56 }
57
58 #[wasm_bindgen(getter, js_name = vertexCounts)]
59 pub fn vertex_counts(&self) -> Vec<u32> {
60 self.vertex_counts.clone()
61 }
62
63 #[wasm_bindgen(getter, js_name = indexCounts)]
64 pub fn index_counts(&self) -> Vec<u32> {
65 self.index_counts.clone()
66 }
67
68 #[wasm_bindgen(getter, js_name = renderPositions)]
69 pub fn render_positions(&self) -> Vec<f32> {
70 self.render_positions.clone()
71 }
72
73 #[wasm_bindgen(getter, js_name = renderNormals)]
74 pub fn render_normals(&self) -> Vec<f32> {
75 self.render_normals.clone()
76 }
77
78 #[wasm_bindgen(getter, js_name = renderIndices)]
79 pub fn render_indices(&self) -> Vec<u32> {
80 self.render_indices.clone()
81 }
82
83 #[wasm_bindgen(getter, js_name = renderOrigins)]
85 pub fn render_origins(&self) -> Vec<f64> {
86 self.render_origins.clone()
87 }
88
89 #[wasm_bindgen(getter, js_name = localPositions)]
90 pub fn local_positions(&self) -> Vec<f64> {
91 self.local_positions.clone()
92 }
93
94 #[wasm_bindgen(getter, js_name = localIndices)]
95 pub fn local_indices(&self) -> Vec<u32> {
96 self.local_indices.clone()
97 }
98
99 #[wasm_bindgen(getter, js_name = trisBefore)]
100 pub fn tris_before(&self) -> Vec<u32> {
101 self.tris_before.clone()
102 }
103
104 #[wasm_bindgen(getter, js_name = trisAfter)]
105 pub fn tris_after(&self) -> Vec<u32> {
106 self.tris_after.clone()
107 }
108
109 #[wasm_bindgen(getter, js_name = cavitiesDropped)]
110 pub fn cavities_dropped(&self) -> Vec<u32> {
111 self.cavities_dropped.clone()
112 }
113
114 #[wasm_bindgen(getter, js_name = skippedIds)]
115 pub fn skipped_ids(&self) -> Vec<u32> {
116 self.skipped_ids.clone()
117 }
118
119 #[wasm_bindgen(getter, js_name = skippedReasons)]
123 pub fn skipped_reasons(&self) -> Vec<JsValue> {
124 self.skipped_reasons.clone()
125 }
126}
127
128#[wasm_bindgen]
129impl IfcAPI {
130 #[wasm_bindgen(js_name = simplifyMeshes)]
146 #[allow(clippy::too_many_arguments)]
147 pub fn simplify_meshes(
148 &self,
149 express_ids: &[u32],
150 levels: &[u8],
151 positions: &[f32],
152 normals: &[f32],
153 indices: &[u32],
154 vertex_counts: &[u32],
155 index_counts: &[u32],
156 origins: &[f64],
157 local_to_world: &[f64],
158 local_to_world_present: &[u8],
159 rtc_x: f64,
160 rtc_y: f64,
161 rtc_z: f64,
162 unit_scale: f64,
163 y_up: bool,
164 ) -> Result<SimplifiedMeshes, JsValue> {
165 let n = express_ids.len();
166 if levels.len() != n
167 || vertex_counts.len() != n
168 || index_counts.len() != n
169 || origins.len() != n * 3
170 || local_to_world.len() != n * 16
171 || local_to_world_present.len() != n
172 {
173 return Err(
174 js_sys::Error::new("simplifyMeshes: per-record array lengths disagree").into(),
175 );
176 }
177 let has_normals = !normals.is_empty();
178 if has_normals && normals.len() != positions.len() {
179 return Err(js_sys::Error::new(
180 "simplifyMeshes: normals must be empty or 1:1 with positions",
181 )
182 .into());
183 }
184
185 let mut order: Vec<u32> = Vec::new();
191 let mut groups: rustc_hash::FxHashMap<u32, Vec<usize>> = rustc_hash::FxHashMap::default();
192 let mut pos_offsets: Vec<u64> = Vec::with_capacity(n);
193 let mut idx_offsets: Vec<u64> = Vec::with_capacity(n);
194 let (mut pos_off, mut idx_off) = (0u64, 0u64);
195 for i in 0..n {
196 pos_offsets.push(pos_off);
197 idx_offsets.push(idx_off);
198 pos_off += vertex_counts[i] as u64 * 3;
199 idx_off += index_counts[i] as u64;
200 if !groups.contains_key(&express_ids[i]) {
201 order.push(express_ids[i]);
202 }
203 groups.entry(express_ids[i]).or_default().push(i);
204 }
205 if pos_off != positions.len() as u64 || idx_off != indices.len() as u64 {
208 return Err(js_sys::Error::new(
209 "simplifyMeshes: counts do not match concatenated array lengths",
210 )
211 .into());
212 }
213
214 let mut out = SimplifiedMeshes {
215 element_ids: Vec::new(),
216 levels: Vec::new(),
217 vertex_counts: Vec::new(),
218 index_counts: Vec::new(),
219 render_positions: Vec::new(),
220 render_normals: Vec::new(),
221 render_indices: Vec::new(),
222 render_origins: Vec::new(),
223 local_positions: Vec::new(),
224 local_indices: Vec::new(),
225 tris_before: Vec::new(),
226 tris_after: Vec::new(),
227 cavities_dropped: Vec::new(),
228 skipped_ids: Vec::new(),
229 skipped_reasons: Vec::new(),
230 };
231
232 for id in order {
233 let record_indices = &groups[&id];
234 let level = levels[record_indices[0]];
235 if record_indices.iter().any(|&i| levels[i] != level) {
236 return Err(js_sys::Error::new(&format!(
237 "simplifyMeshes: records for element {id} have conflicting levels"
238 ))
239 .into());
240 }
241 let records: Vec<SimplifyRecordInput<'_>> = record_indices
242 .iter()
243 .map(|&i| {
244 let (po, pn) = (
247 pos_offsets[i] as usize,
248 (vertex_counts[i] as u64 * 3) as usize,
249 );
250 let io = idx_offsets[i] as usize;
251 let pos = &positions[po..po + pn];
252 let nrm = if has_normals {
253 &normals[po..po + pn]
254 } else {
255 &[][..]
256 };
257 let idx = &indices[io..io + index_counts[i] as usize];
258 let l2w = if local_to_world_present[i] != 0 {
259 let mut m = [0.0f64; 16];
260 m.copy_from_slice(&local_to_world[i * 16..i * 16 + 16]);
261 Some(m)
262 } else {
263 None
264 };
265 SimplifyRecordInput {
266 positions: pos,
267 normals: nrm,
268 indices: idx,
269 origin: [origins[i * 3], origins[i * 3 + 1], origins[i * 3 + 2]],
270 local_to_world: l2w,
271 }
272 })
273 .collect();
274
275 match simplify_element(&records, level, [rtc_x, rtc_y, rtc_z], unit_scale, y_up) {
276 Ok(res) => {
277 out.element_ids.push(id);
278 out.levels.push(level);
279 out.vertex_counts
280 .push((res.render_positions.len() / 3) as u32);
281 out.index_counts.push(res.render_indices.len() as u32);
282 out.render_positions
283 .extend_from_slice(&res.render_positions);
284 out.render_normals.extend_from_slice(&res.render_normals);
285 out.render_indices.extend_from_slice(&res.render_indices);
286 out.render_origins.extend_from_slice(&res.render_origin);
287 out.local_positions.extend_from_slice(&res.local_positions);
288 out.local_indices.extend_from_slice(&res.local_indices);
289 out.tris_before.push(res.tris_before);
290 out.tris_after.push(res.tris_after);
291 out.cavities_dropped.push(res.cavity_components_dropped);
292 }
293 Err(skip) => {
294 out.skipped_ids.push(id);
295 out.skipped_reasons.push(JsValue::from_str(skip.as_str()));
296 if matches!(skip, SimplifySkip::EmptyResult) {
297 web_sys::console::warn_1(&JsValue::from_str(&format!(
299 "[ifc-lite] simplifyMeshes: element {id} produced an empty result; kept original"
300 )));
301 }
302 }
303 }
304 }
305
306 Ok(out)
307 }
308}