use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct GpuMeshMetadata {
express_id: u32,
ifc_type_idx: u16,
vertex_offset: u32,
vertex_count: u32,
index_offset: u32,
index_count: u32,
color: [f32; 4],
}
#[wasm_bindgen]
impl GpuMeshMetadata {
#[wasm_bindgen(getter, js_name = expressId)]
pub fn express_id(&self) -> u32 {
self.express_id
}
#[wasm_bindgen(getter, js_name = ifcTypeIdx)]
pub fn ifc_type_idx(&self) -> u16 {
self.ifc_type_idx
}
#[wasm_bindgen(getter, js_name = vertexOffset)]
pub fn vertex_offset(&self) -> u32 {
self.vertex_offset
}
#[wasm_bindgen(getter, js_name = vertexCount)]
pub fn vertex_count(&self) -> u32 {
self.vertex_count
}
#[wasm_bindgen(getter, js_name = indexOffset)]
pub fn index_offset(&self) -> u32 {
self.index_offset
}
#[wasm_bindgen(getter, js_name = indexCount)]
pub fn index_count(&self) -> u32 {
self.index_count
}
#[wasm_bindgen(getter)]
pub fn color(&self) -> Vec<f32> {
self.color.to_vec()
}
}
#[wasm_bindgen]
pub struct GpuGeometry {
vertex_data: Vec<f32>,
indices: Vec<u32>,
mesh_metadata: Vec<GpuMeshMetadata>,
ifc_type_names: Vec<String>,
rtc_offset_x: f64,
rtc_offset_y: f64,
rtc_offset_z: f64,
}
#[wasm_bindgen]
impl GpuGeometry {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
vertex_data: Vec::new(),
indices: Vec::new(),
mesh_metadata: Vec::new(),
ifc_type_names: Vec::new(),
rtc_offset_x: 0.0,
rtc_offset_y: 0.0,
rtc_offset_z: 0.0,
}
}
pub fn set_rtc_offset(&mut self, x: f64, y: f64, z: f64) {
self.rtc_offset_x = x;
self.rtc_offset_y = y;
self.rtc_offset_z = z;
}
#[wasm_bindgen(getter, js_name = rtcOffsetX)]
pub fn rtc_offset_x(&self) -> f64 {
self.rtc_offset_x
}
#[wasm_bindgen(getter, js_name = rtcOffsetY)]
pub fn rtc_offset_y(&self) -> f64 {
self.rtc_offset_y
}
#[wasm_bindgen(getter, js_name = rtcOffsetZ)]
pub fn rtc_offset_z(&self) -> f64 {
self.rtc_offset_z
}
#[wasm_bindgen(getter, js_name = hasRtcOffset)]
pub fn has_rtc_offset(&self) -> bool {
self.rtc_offset_x != 0.0 || self.rtc_offset_y != 0.0 || self.rtc_offset_z != 0.0
}
#[wasm_bindgen(getter, js_name = vertexDataPtr)]
pub fn vertex_data_ptr(&self) -> *const f32 {
self.vertex_data.as_ptr()
}
#[wasm_bindgen(getter, js_name = vertexDataLen)]
pub fn vertex_data_len(&self) -> usize {
self.vertex_data.len()
}
#[wasm_bindgen(getter, js_name = vertexDataByteLength)]
pub fn vertex_data_byte_length(&self) -> usize {
self.vertex_data.len() * 4 }
#[wasm_bindgen(getter, js_name = indicesPtr)]
pub fn indices_ptr(&self) -> *const u32 {
self.indices.as_ptr()
}
#[wasm_bindgen(getter, js_name = indicesLen)]
pub fn indices_len(&self) -> usize {
self.indices.len()
}
#[wasm_bindgen(getter, js_name = indicesByteLength)]
pub fn indices_byte_length(&self) -> usize {
self.indices.len() * 4 }
#[wasm_bindgen(getter, js_name = meshCount)]
pub fn mesh_count(&self) -> usize {
self.mesh_metadata.len()
}
#[wasm_bindgen(getter, js_name = totalVertexCount)]
pub fn total_vertex_count(&self) -> usize {
self.vertex_data.len() / 6 }
#[wasm_bindgen(getter, js_name = totalTriangleCount)]
pub fn total_triangle_count(&self) -> usize {
self.indices.len() / 3
}
#[wasm_bindgen(js_name = getMeshMetadata)]
pub fn get_mesh_metadata(&self, index: usize) -> Option<GpuMeshMetadata> {
self.mesh_metadata.get(index).cloned()
}
#[wasm_bindgen(js_name = getIfcTypeName)]
pub fn get_ifc_type_name(&self, index: u16) -> Option<String> {
self.ifc_type_names.get(index as usize).cloned()
}
#[wasm_bindgen(getter, js_name = isEmpty)]
pub fn is_empty(&self) -> bool {
self.vertex_data.is_empty()
}
}
impl GpuGeometry {
pub fn with_capacity(vertex_capacity: usize, index_capacity: usize) -> Self {
Self {
vertex_data: Vec::with_capacity(vertex_capacity),
indices: Vec::with_capacity(index_capacity),
mesh_metadata: Vec::with_capacity(256),
ifc_type_names: Vec::with_capacity(64),
rtc_offset_x: 0.0,
rtc_offset_y: 0.0,
rtc_offset_z: 0.0,
}
}
pub fn add_mesh(
&mut self,
express_id: u32,
ifc_type: &str,
positions: &[f32],
normals: &[f32],
indices: &[u32],
color: [f32; 4],
) {
let vertex_count = positions.len() / 3;
if vertex_count == 0 {
return;
}
if normals.len() < positions.len() {
return;
}
let ifc_type_idx = self.get_or_add_ifc_type(ifc_type);
let vertex_offset = (self.vertex_data.len() / 6) as u32;
let index_offset = self.indices.len() as u32;
self.vertex_data.reserve(vertex_count * 6);
for i in 0..vertex_count {
let pi = i * 3;
if pi + 2 >= positions.len() || pi + 2 >= normals.len() {
break;
}
let px = positions[pi];
let py = positions[pi + 2]; let pz = -positions[pi + 1];
let nx = normals[pi];
let ny = normals[pi + 2]; let nz = -normals[pi + 1];
self.vertex_data.push(px);
self.vertex_data.push(py);
self.vertex_data.push(pz);
self.vertex_data.push(nx);
self.vertex_data.push(ny);
self.vertex_data.push(nz);
}
self.indices.reserve(indices.len());
for &idx in indices {
self.indices.push(idx + vertex_offset);
}
self.mesh_metadata.push(GpuMeshMetadata {
express_id,
ifc_type_idx,
vertex_offset,
vertex_count: vertex_count as u32,
index_offset,
index_count: indices.len() as u32,
color,
});
}
fn get_or_add_ifc_type(&mut self, ifc_type: &str) -> u16 {
for (i, name) in self.ifc_type_names.iter().enumerate() {
if name == ifc_type {
return i as u16;
}
}
let idx = self.ifc_type_names.len() as u16;
self.ifc_type_names.push(ifc_type.to_string());
idx
}
pub fn clear(&mut self) {
self.vertex_data.clear();
self.indices.clear();
self.mesh_metadata.clear();
}
}
impl Default for GpuGeometry {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen]
pub struct GpuInstancedGeometry {
geometry_id: u64,
vertex_data: Vec<f32>,
indices: Vec<u32>,
instance_data: Vec<f32>,
instance_express_ids: Vec<u32>,
}
#[wasm_bindgen]
impl GpuInstancedGeometry {
#[wasm_bindgen(constructor)]
pub fn new(geometry_id: u64) -> Self {
Self {
geometry_id,
vertex_data: Vec::new(),
indices: Vec::new(),
instance_data: Vec::new(),
instance_express_ids: Vec::new(),
}
}
#[wasm_bindgen(getter, js_name = geometryId)]
pub fn geometry_id(&self) -> u64 {
self.geometry_id
}
#[wasm_bindgen(getter, js_name = vertexDataPtr)]
pub fn vertex_data_ptr(&self) -> *const f32 {
self.vertex_data.as_ptr()
}
#[wasm_bindgen(getter, js_name = vertexDataLen)]
pub fn vertex_data_len(&self) -> usize {
self.vertex_data.len()
}
#[wasm_bindgen(getter, js_name = vertexDataByteLength)]
pub fn vertex_data_byte_length(&self) -> usize {
self.vertex_data.len() * 4
}
#[wasm_bindgen(getter, js_name = indicesPtr)]
pub fn indices_ptr(&self) -> *const u32 {
self.indices.as_ptr()
}
#[wasm_bindgen(getter, js_name = indicesLen)]
pub fn indices_len(&self) -> usize {
self.indices.len()
}
#[wasm_bindgen(getter, js_name = indicesByteLength)]
pub fn indices_byte_length(&self) -> usize {
self.indices.len() * 4
}
#[wasm_bindgen(getter, js_name = instanceDataPtr)]
pub fn instance_data_ptr(&self) -> *const f32 {
self.instance_data.as_ptr()
}
#[wasm_bindgen(getter, js_name = instanceDataLen)]
pub fn instance_data_len(&self) -> usize {
self.instance_data.len()
}
#[wasm_bindgen(getter, js_name = instanceDataByteLength)]
pub fn instance_data_byte_length(&self) -> usize {
self.instance_data.len() * 4
}
#[wasm_bindgen(getter, js_name = instanceExpressIdsPtr)]
pub fn instance_express_ids_ptr(&self) -> *const u32 {
self.instance_express_ids.as_ptr()
}
#[wasm_bindgen(getter, js_name = instanceCount)]
pub fn instance_count(&self) -> usize {
self.instance_express_ids.len()
}
#[wasm_bindgen(getter, js_name = vertexCount)]
pub fn vertex_count(&self) -> usize {
self.vertex_data.len() / 6
}
#[wasm_bindgen(getter, js_name = triangleCount)]
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
}
impl GpuInstancedGeometry {
pub fn set_geometry(&mut self, positions: &[f32], normals: &[f32], indices: &[u32]) {
self.vertex_data.clear();
self.indices.clear();
if normals.len() < positions.len() {
return;
}
let vertex_count = positions.len() / 3;
self.vertex_data.reserve(vertex_count * 6);
self.indices.reserve(indices.len());
for i in 0..vertex_count {
let pi = i * 3;
if pi + 2 >= positions.len() || pi + 2 >= normals.len() {
break;
}
self.vertex_data.push(positions[pi]);
self.vertex_data.push(positions[pi + 2]); self.vertex_data.push(-positions[pi + 1]);
self.vertex_data.push(normals[pi]);
self.vertex_data.push(normals[pi + 2]); self.vertex_data.push(-normals[pi + 1]); }
self.indices.extend_from_slice(indices);
}
pub fn add_instance(&mut self, express_id: u32, transform: &[f32; 16], color: [f32; 4]) {
self.instance_data.extend_from_slice(transform);
self.instance_data.extend_from_slice(&color);
self.instance_express_ids.push(express_id);
}
}
#[wasm_bindgen]
pub struct GpuInstancedGeometryCollection {
geometries: Vec<GpuInstancedGeometry>,
}
#[wasm_bindgen]
impl GpuInstancedGeometryCollection {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
geometries: Vec::new(),
}
}
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
self.geometries.len()
}
#[wasm_bindgen]
pub fn get(&self, index: usize) -> Option<GpuInstancedGeometry> {
self.geometries.get(index).map(|g| GpuInstancedGeometry {
geometry_id: g.geometry_id,
vertex_data: g.vertex_data.clone(),
indices: g.indices.clone(),
instance_data: g.instance_data.clone(),
instance_express_ids: g.instance_express_ids.clone(),
})
}
#[wasm_bindgen(js_name = getRef)]
pub fn get_ref(&self, index: usize) -> Option<GpuInstancedGeometryRef> {
self.geometries.get(index).map(|g| GpuInstancedGeometryRef {
geometry_id: g.geometry_id,
vertex_data: g.vertex_data.clone(),
indices: g.indices.clone(),
instance_data: g.instance_data.clone(),
instance_express_ids: g.instance_express_ids.clone(),
})
}
}
impl GpuInstancedGeometryCollection {
pub fn add(&mut self, geometry: GpuInstancedGeometry) {
self.geometries.push(geometry);
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut GpuInstancedGeometry> {
self.geometries.get_mut(index)
}
}
impl Default for GpuInstancedGeometryCollection {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen]
pub struct GpuInstancedGeometryRef {
geometry_id: u64,
vertex_data: Vec<f32>,
indices: Vec<u32>,
instance_data: Vec<f32>,
instance_express_ids: Vec<u32>,
}
#[wasm_bindgen]
impl GpuInstancedGeometryRef {
#[wasm_bindgen(getter, js_name = geometryId)]
pub fn geometry_id(&self) -> u64 {
self.geometry_id
}
#[wasm_bindgen(getter, js_name = vertexDataPtr)]
pub fn vertex_data_ptr(&self) -> *const f32 {
self.vertex_data.as_ptr()
}
#[wasm_bindgen(getter, js_name = vertexDataLen)]
pub fn vertex_data_len(&self) -> usize {
self.vertex_data.len()
}
#[wasm_bindgen(getter, js_name = vertexDataByteLength)]
pub fn vertex_data_byte_length(&self) -> usize {
self.vertex_data_len() * 4
}
#[wasm_bindgen(getter, js_name = indicesPtr)]
pub fn indices_ptr(&self) -> *const u32 {
self.indices.as_ptr()
}
#[wasm_bindgen(getter, js_name = indicesLen)]
pub fn indices_len(&self) -> usize {
self.indices.len()
}
#[wasm_bindgen(getter, js_name = indicesByteLength)]
pub fn indices_byte_length(&self) -> usize {
self.indices_len() * 4
}
#[wasm_bindgen(getter, js_name = instanceDataPtr)]
pub fn instance_data_ptr(&self) -> *const f32 {
self.instance_data.as_ptr()
}
#[wasm_bindgen(getter, js_name = instanceDataLen)]
pub fn instance_data_len(&self) -> usize {
self.instance_data.len()
}
#[wasm_bindgen(getter, js_name = instanceDataByteLength)]
pub fn instance_data_byte_length(&self) -> usize {
self.instance_data_len() * 4
}
#[wasm_bindgen(getter, js_name = instanceExpressIdsPtr)]
pub fn instance_express_ids_ptr(&self) -> *const u32 {
self.instance_express_ids.as_ptr()
}
#[wasm_bindgen(getter, js_name = instanceCount)]
pub fn instance_count(&self) -> usize {
self.instance_express_ids.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gpu_geometry_creation() {
let geom = GpuGeometry::new();
assert!(geom.is_empty());
assert_eq!(geom.mesh_count(), 0);
}
#[test]
fn test_gpu_geometry_add_mesh() {
let mut geom = GpuGeometry::new();
let positions = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 1.0];
let normals = vec![0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0];
let indices = vec![0, 1, 2];
let color = [1.0, 0.0, 0.0, 1.0];
geom.add_mesh(123, "IfcWall", &positions, &normals, &indices, color);
assert!(!geom.is_empty());
assert_eq!(geom.mesh_count(), 1);
assert_eq!(geom.total_vertex_count(), 3);
assert_eq!(geom.total_triangle_count(), 1);
let meta = geom.get_mesh_metadata(0).unwrap();
assert_eq!(meta.express_id, 123);
assert_eq!(meta.vertex_count, 3);
assert_eq!(meta.index_count, 3);
}
#[test]
fn test_coordinate_conversion() {
let mut geom = GpuGeometry::new();
let positions = vec![1.0, 2.0, 3.0];
let normals = vec![0.0, 0.0, 1.0]; let indices = vec![0];
let color = [1.0, 1.0, 1.0, 1.0];
geom.add_mesh(1, "Test", &positions, &normals, &indices, color);
assert_eq!(geom.vertex_data[0], 1.0); assert_eq!(geom.vertex_data[1], 3.0); assert_eq!(geom.vertex_data[2], -2.0);
assert_eq!(geom.vertex_data[3], 0.0); assert_eq!(geom.vertex_data[4], 1.0); assert_eq!(geom.vertex_data[5], 0.0); }
#[test]
fn test_instanced_geometry() {
let mut geom = GpuInstancedGeometry::new(12345);
let positions = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 1.0];
let normals = vec![0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0];
let indices = vec![0, 1, 2];
geom.set_geometry(&positions, &normals, &indices);
let transform = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
let color = [1.0, 0.0, 0.0, 1.0];
geom.add_instance(100, &transform, color);
geom.add_instance(101, &transform, color);
assert_eq!(geom.instance_count(), 2);
assert_eq!(geom.vertex_count(), 3);
assert_eq!(geom.triangle_count(), 1);
}
}