use brepkit_operations::tessellate::{EdgeLines, TriangleMesh};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub struct JsPoint3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[wasm_bindgen]
impl JsPoint3 {
#[wasm_bindgen(constructor)]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub struct JsVec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[wasm_bindgen]
impl JsVec3 {
#[wasm_bindgen(constructor)]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[must_use]
pub fn length(&self) -> f64 {
self.x
.mul_add(self.x, self.y.mul_add(self.y, self.z * self.z))
.sqrt()
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct JsMesh {
positions: Vec<f64>,
normals: Vec<f64>,
indices: Vec<u32>,
}
#[wasm_bindgen]
impl JsMesh {
#[wasm_bindgen(getter)]
#[must_use]
pub fn positions(&self) -> Vec<f64> {
self.positions.clone()
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn normals(&self) -> Vec<f64> {
self.normals.clone()
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn indices(&self) -> Vec<u32> {
self.indices.clone()
}
#[wasm_bindgen(getter, js_name = "vertexCount")]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn vertex_count(&self) -> u32 {
(self.positions.len() / 3) as u32
}
#[wasm_bindgen(getter, js_name = "triangleCount")]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn triangle_count(&self) -> u32 {
(self.indices.len() / 3) as u32
}
#[wasm_bindgen(js_name = "packedBuffer")]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn packed_buffer(&self) -> Vec<u8> {
let pos_bytes = self.positions.len() * 8; let norm_bytes = self.normals.len() * 8;
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);
buf.extend_from_slice(&(pos_bytes as u32).to_le_bytes());
buf.extend_from_slice(&(norm_bytes as u32).to_le_bytes());
buf.extend_from_slice(&(idx_bytes as u32).to_le_bytes());
for &v in &self.positions {
buf.extend_from_slice(&v.to_le_bytes());
}
for &v in &self.normals {
buf.extend_from_slice(&v.to_le_bytes());
}
for &i in &self.indices {
buf.extend_from_slice(&i.to_le_bytes());
}
buf
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct JsGroupedMesh {
positions: Vec<f32>,
normals: Vec<f32>,
indices: Vec<u32>,
face_offsets: Vec<u32>,
}
#[wasm_bindgen]
impl JsGroupedMesh {
#[wasm_bindgen(getter)]
#[must_use]
pub fn positions(&self) -> Vec<f32> {
self.positions.clone()
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn normals(&self) -> Vec<f32> {
self.normals.clone()
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn indices(&self) -> Vec<u32> {
self.indices.clone()
}
#[wasm_bindgen(getter, js_name = "faceOffsets")]
#[must_use]
pub fn face_offsets(&self) -> Vec<u32> {
self.face_offsets.clone()
}
}
impl JsGroupedMesh {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn new(mesh: TriangleMesh, face_offsets: Vec<u32>) -> Self {
let mut positions = Vec::with_capacity(mesh.positions.len() * 3);
for p in &mesh.positions {
positions.push(p.x() as f32);
positions.push(p.y() as f32);
positions.push(p.z() as f32);
}
let mut normals = Vec::with_capacity(mesh.normals.len() * 3);
for n in &mesh.normals {
normals.push(n.x() as f32);
normals.push(n.y() as f32);
normals.push(n.z() as f32);
}
Self {
positions,
normals,
indices: mesh.indices,
face_offsets,
}
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct JsEdgeLines {
positions: Vec<f64>,
offsets: Vec<u32>,
}
#[wasm_bindgen]
impl JsEdgeLines {
#[wasm_bindgen(getter)]
#[must_use]
pub fn positions(&self) -> Vec<f64> {
self.positions.clone()
}
#[wasm_bindgen(getter)]
#[must_use]
pub fn offsets(&self) -> Vec<u32> {
self.offsets.clone()
}
#[wasm_bindgen(getter, js_name = "edgeCount")]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn edge_count(&self) -> u32 {
self.offsets.len() as u32
}
#[wasm_bindgen(js_name = "packedBuffer")]
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn packed_buffer(&self) -> Vec<u8> {
let pos_bytes = self.positions.len() * 8;
let off_bytes = self.offsets.len() * 4;
let header_size = 8;
let mut buf = Vec::with_capacity(header_size + pos_bytes + off_bytes);
buf.extend_from_slice(&(pos_bytes as u32).to_le_bytes());
buf.extend_from_slice(&(off_bytes as u32).to_le_bytes());
for &v in &self.positions {
buf.extend_from_slice(&v.to_le_bytes());
}
for &o in &self.offsets {
buf.extend_from_slice(&o.to_le_bytes());
}
buf
}
}
impl From<EdgeLines> for JsEdgeLines {
#[allow(clippy::cast_possible_truncation)]
fn from(edge_lines: EdgeLines) -> Self {
let positions = edge_lines
.positions
.iter()
.flat_map(|p| [p.x(), p.y(), p.z()])
.collect();
let offsets = edge_lines.offsets.iter().map(|&o| (o * 3) as u32).collect();
Self { positions, offsets }
}
}
impl From<TriangleMesh> for JsMesh {
fn from(mesh: TriangleMesh) -> Self {
let positions = mesh
.positions
.iter()
.flat_map(|p| [p.x(), p.y(), p.z()])
.collect();
let normals = mesh
.normals
.iter()
.flat_map(|n| [n.x(), n.y(), n.z()])
.collect();
Self {
positions,
normals,
indices: mesh.indices,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use brepkit_math::vec::{Point3, Vec3};
#[test]
fn grouped_mesh_packs_f32_moves_indices_and_keeps_offsets() {
let mesh = TriangleMesh {
positions: vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 1.0, 0.0),
],
normals: vec![Vec3::new(0.0, 0.0, 1.0); 4],
indices: vec![0, 1, 2, 1, 3, 2],
};
let offsets = vec![0, 3, 6];
let gm = JsGroupedMesh::new(mesh, offsets.clone());
assert_eq!(gm.positions().len(), 12);
assert_eq!(
gm.positions(),
vec![
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
]
);
assert_eq!(gm.normals(), [0.0_f32, 0.0, 1.0].repeat(4));
assert_eq!(gm.indices(), vec![0_u32, 1, 2, 1, 3, 2]);
assert_eq!(gm.face_offsets(), offsets);
assert_eq!(
*gm.face_offsets().last().unwrap() as usize,
gm.indices().len()
);
}
}