1use brepkit_operations::tessellate::{EdgeLines, TriangleMesh};
4use wasm_bindgen::prelude::*;
5
6#[wasm_bindgen]
8#[derive(Debug, Clone, Copy)]
9pub struct JsPoint3 {
10 pub x: f64,
12 pub y: f64,
14 pub z: f64,
16}
17
18#[wasm_bindgen]
19impl JsPoint3 {
20 #[wasm_bindgen(constructor)]
22 #[must_use]
23 #[allow(clippy::missing_const_for_fn)]
24 pub fn new(x: f64, y: f64, z: f64) -> Self {
25 Self { x, y, z }
26 }
27}
28
29#[wasm_bindgen]
31#[derive(Debug, Clone, Copy)]
32pub struct JsVec3 {
33 pub x: f64,
35 pub y: f64,
37 pub z: f64,
39}
40
41#[wasm_bindgen]
42impl JsVec3 {
43 #[wasm_bindgen(constructor)]
45 #[must_use]
46 #[allow(clippy::missing_const_for_fn)]
47 pub fn new(x: f64, y: f64, z: f64) -> Self {
48 Self { x, y, z }
49 }
50
51 #[must_use]
53 pub fn length(&self) -> f64 {
54 self.x
55 .mul_add(self.x, self.y.mul_add(self.y, self.z * self.z))
56 .sqrt()
57 }
58}
59
60#[wasm_bindgen]
65#[derive(Debug)]
66pub struct JsMesh {
67 positions: Vec<f64>,
68 normals: Vec<f64>,
69 indices: Vec<u32>,
70}
71
72#[wasm_bindgen]
73impl JsMesh {
74 #[wasm_bindgen(getter)]
76 #[must_use]
77 pub fn positions(&self) -> Vec<f64> {
78 self.positions.clone()
79 }
80
81 #[wasm_bindgen(getter)]
83 #[must_use]
84 pub fn normals(&self) -> Vec<f64> {
85 self.normals.clone()
86 }
87
88 #[wasm_bindgen(getter)]
90 #[must_use]
91 pub fn indices(&self) -> Vec<u32> {
92 self.indices.clone()
93 }
94
95 #[wasm_bindgen(getter, js_name = "vertexCount")]
97 #[must_use]
98 #[allow(clippy::cast_possible_truncation)]
99 pub fn vertex_count(&self) -> u32 {
100 (self.positions.len() / 3) as u32
101 }
102
103 #[wasm_bindgen(getter, js_name = "triangleCount")]
105 #[must_use]
106 #[allow(clippy::cast_possible_truncation)]
107 pub fn triangle_count(&self) -> u32 {
108 (self.indices.len() / 3) as u32
109 }
110
111 #[wasm_bindgen(js_name = "packedBuffer")]
119 #[must_use]
120 #[allow(clippy::cast_possible_truncation)]
121 pub fn packed_buffer(&self) -> Vec<u8> {
122 let pos_bytes = self.positions.len() * 8; let norm_bytes = self.normals.len() * 8;
124 let idx_bytes = self.indices.len() * 4; let header_size = 12; let mut buf = Vec::with_capacity(header_size + pos_bytes + norm_bytes + idx_bytes);
128
129 buf.extend_from_slice(&(pos_bytes as u32).to_le_bytes());
131 buf.extend_from_slice(&(norm_bytes as u32).to_le_bytes());
132 buf.extend_from_slice(&(idx_bytes as u32).to_le_bytes());
133
134 for &v in &self.positions {
136 buf.extend_from_slice(&v.to_le_bytes());
137 }
138 for &v in &self.normals {
140 buf.extend_from_slice(&v.to_le_bytes());
141 }
142 for &i in &self.indices {
144 buf.extend_from_slice(&i.to_le_bytes());
145 }
146
147 buf
148 }
149}
150
151#[wasm_bindgen]
159#[derive(Debug)]
160pub struct JsGroupedMesh {
161 positions: Vec<f32>,
162 normals: Vec<f32>,
163 indices: Vec<u32>,
164 face_offsets: Vec<u32>,
165}
166
167#[wasm_bindgen]
168impl JsGroupedMesh {
169 #[wasm_bindgen(getter)]
171 #[must_use]
172 pub fn positions(&self) -> Vec<f32> {
173 self.positions.clone()
174 }
175
176 #[wasm_bindgen(getter)]
178 #[must_use]
179 pub fn normals(&self) -> Vec<f32> {
180 self.normals.clone()
181 }
182
183 #[wasm_bindgen(getter)]
185 #[must_use]
186 pub fn indices(&self) -> Vec<u32> {
187 self.indices.clone()
188 }
189
190 #[wasm_bindgen(getter, js_name = "faceOffsets")]
193 #[must_use]
194 pub fn face_offsets(&self) -> Vec<u32> {
195 self.face_offsets.clone()
196 }
197}
198
199impl JsGroupedMesh {
200 #[must_use]
205 #[allow(clippy::cast_possible_truncation)]
206 pub(crate) fn new(mesh: TriangleMesh, face_offsets: Vec<u32>) -> Self {
207 let mut positions = Vec::with_capacity(mesh.positions.len() * 3);
208 for p in &mesh.positions {
209 positions.push(p.x() as f32);
210 positions.push(p.y() as f32);
211 positions.push(p.z() as f32);
212 }
213 let mut normals = Vec::with_capacity(mesh.normals.len() * 3);
214 for n in &mesh.normals {
215 normals.push(n.x() as f32);
216 normals.push(n.y() as f32);
217 normals.push(n.z() as f32);
218 }
219 Self {
220 positions,
221 normals,
222 indices: mesh.indices,
223 face_offsets,
224 }
225 }
226}
227
228#[wasm_bindgen]
233#[derive(Debug)]
234pub struct JsEdgeLines {
235 positions: Vec<f64>,
236 offsets: Vec<u32>,
237}
238
239#[wasm_bindgen]
240impl JsEdgeLines {
241 #[wasm_bindgen(getter)]
243 #[must_use]
244 pub fn positions(&self) -> Vec<f64> {
245 self.positions.clone()
246 }
247
248 #[wasm_bindgen(getter)]
254 #[must_use]
255 pub fn offsets(&self) -> Vec<u32> {
256 self.offsets.clone()
257 }
258
259 #[wasm_bindgen(getter, js_name = "edgeCount")]
261 #[must_use]
262 #[allow(clippy::cast_possible_truncation)]
263 pub fn edge_count(&self) -> u32 {
264 self.offsets.len() as u32
265 }
266
267 #[wasm_bindgen(js_name = "packedBuffer")]
272 #[must_use]
273 #[allow(clippy::cast_possible_truncation)]
274 pub fn packed_buffer(&self) -> Vec<u8> {
275 let pos_bytes = self.positions.len() * 8;
276 let off_bytes = self.offsets.len() * 4;
277 let header_size = 8; let mut buf = Vec::with_capacity(header_size + pos_bytes + off_bytes);
280
281 buf.extend_from_slice(&(pos_bytes as u32).to_le_bytes());
282 buf.extend_from_slice(&(off_bytes as u32).to_le_bytes());
283
284 for &v in &self.positions {
285 buf.extend_from_slice(&v.to_le_bytes());
286 }
287 for &o in &self.offsets {
288 buf.extend_from_slice(&o.to_le_bytes());
289 }
290
291 buf
292 }
293}
294
295impl From<EdgeLines> for JsEdgeLines {
296 #[allow(clippy::cast_possible_truncation)]
297 fn from(edge_lines: EdgeLines) -> Self {
298 let positions = edge_lines
299 .positions
300 .iter()
301 .flat_map(|p| [p.x(), p.y(), p.z()])
302 .collect();
303
304 let offsets = edge_lines.offsets.iter().map(|&o| (o * 3) as u32).collect();
305
306 Self { positions, offsets }
307 }
308}
309
310impl From<TriangleMesh> for JsMesh {
311 fn from(mesh: TriangleMesh) -> Self {
312 let positions = mesh
313 .positions
314 .iter()
315 .flat_map(|p| [p.x(), p.y(), p.z()])
316 .collect();
317
318 let normals = mesh
319 .normals
320 .iter()
321 .flat_map(|n| [n.x(), n.y(), n.z()])
322 .collect();
323
324 Self {
325 positions,
326 normals,
327 indices: mesh.indices,
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 #![allow(clippy::unwrap_used, clippy::expect_used)]
335 use super::*;
336 use brepkit_math::vec::{Point3, Vec3};
337
338 #[test]
339 fn grouped_mesh_packs_f32_moves_indices_and_keeps_offsets() {
340 let mesh = TriangleMesh {
341 positions: vec![
342 Point3::new(0.0, 0.0, 0.0),
343 Point3::new(1.0, 0.0, 0.0),
344 Point3::new(0.0, 1.0, 0.0),
345 Point3::new(1.0, 1.0, 0.0),
346 ],
347 normals: vec![Vec3::new(0.0, 0.0, 1.0); 4],
348 indices: vec![0, 1, 2, 1, 3, 2],
349 };
350 let offsets = vec![0, 3, 6];
352 let gm = JsGroupedMesh::new(mesh, offsets.clone());
353
354 assert_eq!(gm.positions().len(), 12);
356 assert_eq!(
357 gm.positions(),
358 vec![
359 0.0_f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0
360 ]
361 );
362 assert_eq!(gm.normals(), [0.0_f32, 0.0, 1.0].repeat(4));
363 assert_eq!(gm.indices(), vec![0_u32, 1, 2, 1, 3, 2]);
364
365 assert_eq!(gm.face_offsets(), offsets);
367 assert_eq!(
368 *gm.face_offsets().last().unwrap() as usize,
369 gm.indices().len()
370 );
371 }
372}