diff --git a/crates/math/src/quaternion.rs b/crates/math/src/quaternion.rs index 7bee443bb..f510ddce3 100644 --- a/crates/math/src/quaternion.rs +++ b/crates/math/src/quaternion.rs @@ -1,5 +1,6 @@ use std::f32::consts::PI; +use crate::VecBaseFloat; use crate::Vector3; use cgmath::Rotation; use cgmath::Zero; @@ -7,6 +8,7 @@ use cgmath::Zero; pub type Quaternion = cgmath::Quaternion; pub trait Quat { + fn from_vector(v: Vector3) -> Quaternion; fn from_euler_angles(roll_yaw_pitch: Vector3) -> Quaternion; fn to_euler_angles(&self) -> Vector3; fn transform_point(&self, p: Vector3) -> Vector3; @@ -14,6 +16,9 @@ pub trait Quat { } impl Quat for Quaternion { + fn from_vector(v: Vector3) -> Quaternion { + cgmath::Quaternion::from_sv(v.length(), v) + } fn to_euler_angles(&self) -> Vector3 { let mut roll_yaw_pitch = Vector3::zero(); diff --git a/crates/plugins/binarizer/src/adjacency.rs b/crates/plugins/binarizer/src/adjacency.rs new file mode 100644 index 000000000..17d857243 --- /dev/null +++ b/crates/plugins/binarizer/src/adjacency.rs @@ -0,0 +1,371 @@ +use std::collections::HashMap; + +use inox_graphics::{MeshletData, HALF_MESHLETS_GROUP_SIZE, MESHLETS_GROUP_SIZE}; +use inox_math::{VecBaseFloat, Vector3}; +use meshopt::DecodePosition; + +const VERTICES_DISTANCE_EPSILON: f32 = 0.1; + +#[derive(Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)] +struct Edge { + v1: u32, + v2: u32, +} + +impl Edge { + fn create(i1: u32, i2: u32) -> Self { + Self { + v1: i2.min(i1), + v2: i2.max(i1), + } + } + fn add_to_hit_count(&self, edges_hit_count: &mut HashMap) { + edges_hit_count + .entry(self.clone()) + .and_modify(|v| *v += 1) + .or_insert(1); + } + fn add_to_meshlets_map( + &self, + edge_meshlets_map: &mut HashMap>, + meshlet_index: usize, + ) { + edge_meshlets_map + .entry(self.clone()) + .and_modify(|v| { + if !v.contains(&meshlet_index) { + v.push(meshlet_index) + } + }) + .or_insert(vec![meshlet_index]); + } +} + +#[derive(Debug, PartialEq, Clone)] +struct EdgePos { + v1: Vector3, + v2: Vector3, +} + +impl EdgePos { + fn create(p1: Vector3, p2: Vector3) -> Self { + let v1 = if p1.x < p2.x { + p1 + } else if p1.x > p2.x { + p2 + } else if p1.y < p2.y { + p1 + } else if p1.y > p2.y { + p2 + } else if p1.z < p2.z { + p1 + } else if p1.z > p2.z { + p2 + } else { + p1 + }; + let v2 = if v1 == p1 { p2 } else { p1 }; + Self { v1, v2 } + } + fn is_close_to(&self, other: &Self, epsilon: f32) -> bool { + (self.v1 - other.v1).length() < epsilon && (self.v2 - other.v2).length() < epsilon + } + fn add_to_meshlets_map( + &self, + edgepos_meshlets_map: &mut Vec<(EdgePos, Vec)>, + meshlet_index: usize, + epsilon: f32, + ) { + if let Some(position) = edgepos_meshlets_map + .iter() + .position(|e| self.is_close_to(&e.0, epsilon)) + { + if !edgepos_meshlets_map[position].1.contains(&meshlet_index) { + edgepos_meshlets_map[position].1.push(meshlet_index) + } + } else { + edgepos_meshlets_map.push((self.clone(), vec![meshlet_index])) + } + } +} + +#[derive(Default, Debug, Clone)] +pub(crate) struct MeshletAdjacency { + meshlet_index: u32, + border_edges: Vec, + adjacent_meshlets: Vec<(u32, usize)>, +} + +pub fn build_meshlets_adjacency( + meshlets: &[MeshletData], + vertices: &[T], + indices: &[u32], +) -> Vec +where + T: DecodePosition, +{ + let mut meshlets_info = Vec::with_capacity(meshlets.len()); + let mut edge_meshlets_map: HashMap> = HashMap::default(); + let mut edge_pos_meshlets_map: Vec<(EdgePos, Vec)> = Vec::default(); + meshlets + .iter() + .enumerate() + .for_each(|(meshlet_index, meshlet)| { + let triangle_count = meshlet.indices_count / 3; + let mut edges_hit_count: HashMap = HashMap::default(); + for triangle_index in 0..triangle_count { + let i1 = indices[(meshlet.indices_offset + triangle_index * 3) as usize]; + let i2 = indices[(meshlet.indices_offset + triangle_index * 3 + 1) as usize]; + let i3 = indices[(meshlet.indices_offset + triangle_index * 3 + 2) as usize]; + let e1 = Edge::create(i1, i2); + let e2 = Edge::create(i2, i3); + let e3 = Edge::create(i3, i1); + e1.add_to_hit_count(&mut edges_hit_count); + e2.add_to_hit_count(&mut edges_hit_count); + e3.add_to_hit_count(&mut edges_hit_count); + e1.add_to_meshlets_map(&mut edge_meshlets_map, meshlet_index); + e2.add_to_meshlets_map(&mut edge_meshlets_map, meshlet_index); + e3.add_to_meshlets_map(&mut edge_meshlets_map, meshlet_index); + } + let mut border_edges = Vec::with_capacity(edges_hit_count.len()); + for (e, count) in edges_hit_count { + if count == 1 { + let p1: Vector3 = vertices[e.v1 as usize].decode_position().into(); + let p2: Vector3 = vertices[e.v2 as usize].decode_position().into(); + let e_pos = EdgePos::create(p1, p2); + e_pos.add_to_meshlets_map( + &mut edge_pos_meshlets_map, + meshlet_index, + VERTICES_DISTANCE_EPSILON, + ); + border_edges.push(e); + } + } + meshlets_info.push(MeshletAdjacency { + meshlet_index: meshlet_index as _, + border_edges, + adjacent_meshlets: Vec::default(), + }); + }); + + let num_meshlets = meshlets_info.len(); + debug_assert!(num_meshlets == meshlets.len()); + + meshlets_info + .iter_mut() + .enumerate() + .for_each(|(info_index, info)| { + info.border_edges.iter().for_each(|e| { + if edge_meshlets_map[e].len() > 1 { + edge_meshlets_map[e].iter().for_each(|&meshlet_index| { + if meshlet_index != info_index { + if let Some(i) = info + .adjacent_meshlets + .iter() + .position(|l| l.0 == meshlet_index as u32) + { + info.adjacent_meshlets[i].1 += 1; + } else { + info.adjacent_meshlets.push((meshlet_index as u32, 1)); + } + } + }); + } + }); + }); + edge_pos_meshlets_map.iter().for_each(|(_, meshlets)| { + meshlets.iter().for_each(|&m1| { + if let Some(m1_index) = meshlets_info + .iter() + .position(|m| m.meshlet_index as usize == m1) + { + meshlets.iter().for_each(|&m2| { + if m1 != m2 { + if let Some(i) = meshlets_info[m1_index] + .adjacent_meshlets + .iter() + .position(|l| l.0 == m1 as u32) + { + meshlets_info[m1_index].adjacent_meshlets[i].1 += 1; + } else { + meshlets_info[m1_index] + .adjacent_meshlets + .push((m2 as u32, 1)); + } + if let Some(m2_index) = meshlets_info + .iter() + .position(|m| m.meshlet_index as usize == m2) + { + if let Some(i) = meshlets_info[m2_index] + .adjacent_meshlets + .iter() + .position(|l| l.0 == m1 as u32) + { + meshlets_info[m2_index].adjacent_meshlets[i].1 += 1; + } else { + meshlets_info[m2_index] + .adjacent_meshlets + .push((m1 as u32, 1)); + } + } + } + }); + } + }); + }); + let num_meshlets = meshlets_info.len(); + meshlets_info.iter_mut().for_each(|m| { + if num_meshlets > 1 && m.adjacent_meshlets.is_empty() { + println!("Meshlet {} has no adjacency", m.meshlet_index); + } + m.adjacent_meshlets + .sort_by(|(_i, a), (_j, b)| b.partial_cmp(a).unwrap()); + }); + meshlets_info +} + +fn fill_with_info_and_adjacency( + original_meshlets_info: &[MeshletAdjacency], + meshlet_info: &MeshletAdjacency, + meshlets_to_add: &mut Vec, +) { + if !meshlets_to_add + .iter() + .any(|m| m.meshlet_index == meshlet_info.meshlet_index) + { + if let Some(p) = original_meshlets_info + .iter() + .position(|m| m.meshlet_index == meshlet_info.meshlet_index) + { + let original = original_meshlets_info[p].clone(); + meshlets_to_add.push(original); + original_meshlets_info[p] + .adjacent_meshlets + .iter() + .for_each(|&(i, _)| { + fill_with_info_and_adjacency( + original_meshlets_info, + &original_meshlets_info[i as usize], + meshlets_to_add, + ); + }); + } + } +} + +pub fn group_meshlets(meshlets_info: &[MeshletAdjacency]) -> Vec> { + let mut available_meshlets = meshlets_info.to_vec(); + let mut meshlets_groups = Vec::new(); + while !available_meshlets.is_empty() { + let mut meshlet_group = Vec::new(); + meshlet_group.push(available_meshlets.remove(0)); + let mut meshlet_current_index = 0; + while meshlet_group.len() < MESHLETS_GROUP_SIZE { + let mut max_adjacency_value = -1; + let mut adjacent_index = -1; + meshlet_group.iter().enumerate().for_each(|(i, m)| { + if let Some(index) = m + .adjacent_meshlets + .iter() + .position(|v| v.1 as i32 > max_adjacency_value) + { + max_adjacency_value = m.adjacent_meshlets[index].1 as i32; + adjacent_index = index as i32; + meshlet_current_index = i; + } + }); + if max_adjacency_value < 0 || adjacent_index < 0 { + break; + } + let meshlet_info = &mut meshlet_group[meshlet_current_index]; + let (other_index, _) = meshlet_info + .adjacent_meshlets + .remove(adjacent_index as usize); + if let Some(other_available_index) = available_meshlets + .iter() + .position(|m| m.meshlet_index == other_index) + { + let mut m = available_meshlets.remove(other_available_index); + if let Some(index) = m + .adjacent_meshlets + .iter() + .position(|v| v.0 == meshlet_info.meshlet_index) + { + m.adjacent_meshlets.remove(index); + } else { + println!( + "Expecting to find meshlet {} but it's not there {:?}", + meshlet_info.meshlet_index, m.adjacent_meshlets + ) + } + meshlet_group.push(m); + } + } + let mut should_retry = meshlet_group.is_empty(); + should_retry |= meshlet_group.len() == 1 && available_meshlets.len() > MESHLETS_GROUP_SIZE; + should_retry &= !meshlet_group.iter().all(|mi| { + meshlets_info[mi.meshlet_index as usize] + .adjacent_meshlets + .is_empty() + }); + if !should_retry || available_meshlets.is_empty() { + meshlets_groups.push(meshlet_group); + } else { + //steal from groups already created + let mut stealed = Vec::new(); + meshlet_group.iter().for_each(|info| { + if let Some(p) = meshlets_info + .iter() + .position(|m| m.meshlet_index == info.meshlet_index) + { + let original = &meshlets_info[p]; + let mut a = original.adjacent_meshlets.len() as i32 - 1; + while a >= 0 && stealed.len() < HALF_MESHLETS_GROUP_SIZE { + let mut j = meshlets_groups.len() as i32 - 1; + while a >= 0 && j >= 0 && stealed.len() < HALF_MESHLETS_GROUP_SIZE { + if meshlets_groups[j as usize].len() > HALF_MESHLETS_GROUP_SIZE { + if let Some(i) = meshlets_groups[j as usize].iter().position(|m| { + m.meshlet_index == original.adjacent_meshlets[a as usize].0 + }) { + stealed.push(meshlets_groups[j as usize].remove(i)); + a -= 1; + } + } + j -= 1; + } + a -= 1; + } + } + }); + meshlet_group.append(&mut stealed); + if meshlet_group.len() == 1 { + //readd all to the available meshlets + let last_index = available_meshlets.len(); + meshlet_group.iter().for_each(|info| { + fill_with_info_and_adjacency(meshlets_info, info, &mut available_meshlets); + }); + (last_index..available_meshlets.len()).for_each(|i| { + let info = &available_meshlets[i]; + meshlets_groups.retain(|group| { + !group.iter().any(|m| m.meshlet_index == info.meshlet_index) + }); + }); + } else { + meshlets_groups.push(meshlet_group); + } + } + } + debug_assert!(available_meshlets.is_empty()); + let num_total_meslets = meshlets_groups.iter().fold(0, |i, e| i + e.len()); + debug_assert!( + num_total_meslets == meshlets_info.len(), + "Not enough meshlets {}/{} in {} groups", + num_total_meslets, + meshlets_info.len(), + meshlets_groups.len() + ); + meshlets_groups + .iter() + .map(|info| info.iter().map(|m| m.meshlet_index).collect::<_>()) + .collect::<_>() +} diff --git a/crates/plugins/binarizer/src/compilers/gltf_compiler.rs b/crates/plugins/binarizer/src/compilers/gltf_compiler.rs index 05853b521..d043e5256 100644 --- a/crates/plugins/binarizer/src/compilers/gltf_compiler.rs +++ b/crates/plugins/binarizer/src/compilers/gltf_compiler.rs @@ -5,10 +5,8 @@ use std::{ }; use crate::{ - mesh::{ - build_meshlets_adjacency, compute_clusters, compute_meshlets, create_mesh_data, - group_meshlets, optimize_mesh, MeshVertex, - }, + adjacency::{build_meshlets_adjacency, group_meshlets}, + mesh::{compute_clusters, compute_meshlets, create_mesh_data, optimize_mesh, MeshVertex}, need_to_binarize, to_local_path, ExtensionHandler, }; use gltf::{ @@ -380,7 +378,8 @@ impl GltfCompiler { let mut level = 0; while !is_meshlet_tree_created { let previous_lod_meshlets = meshlets_per_lod.last_mut().unwrap(); - let meshlets_adjacency = build_meshlets_adjacency(previous_lod_meshlets, &mesh_indices); + let meshlets_adjacency = + build_meshlets_adjacency(previous_lod_meshlets, &mesh_vertices, &mesh_indices); let groups = group_meshlets(&meshlets_adjacency); level += 1; let (mut cluster_indices, cluster_meshlets) = compute_clusters( diff --git a/crates/plugins/binarizer/src/lib.rs b/crates/plugins/binarizer/src/lib.rs index 89e3cb2ec..d4a98c8c9 100644 --- a/crates/plugins/binarizer/src/lib.rs +++ b/crates/plugins/binarizer/src/lib.rs @@ -6,6 +6,7 @@ pub use crate::plugin::*; pub use crate::system::*; pub use crate::utils::*; +mod adjacency; mod compilers; mod config; mod mesh; diff --git a/crates/plugins/binarizer/src/mesh.rs b/crates/plugins/binarizer/src/mesh.rs index 4fe9328f6..1fdae4b98 100644 --- a/crates/plugins/binarizer/src/mesh.rs +++ b/crates/plugins/binarizer/src/mesh.rs @@ -1,8 +1,6 @@ -use std::{collections::HashMap, mem::size_of}; +use std::mem::size_of; -use inox_graphics::{ - MeshData, MeshletData, VertexAttributeLayout, HALF_MESHLETS_GROUP_SIZE, MESHLETS_GROUP_SIZE, -}; +use inox_graphics::{MeshData, MeshletData, VertexAttributeLayout, MESHLETS_GROUP_SIZE}; use inox_math::{VecBase, Vector2, Vector3, Vector4}; use inox_resources::to_slice; use meshopt::DecodePosition; @@ -62,18 +60,6 @@ impl meshopt::DecodePosition for LocalVertex { } } -#[derive(Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)] -struct Edge { - v1: u32, - v2: u32, -} -#[derive(Default, Debug, Clone)] -pub(crate) struct MeshletAdjacency { - meshlet_index: u32, - edges: Vec, - adjacent_meshlets: Vec<(u32, usize)>, -} - pub fn optimize_mesh(vertices: &[T], indices: &[u32]) -> (Vec, Vec) where T: Clone + Default + DecodePosition, @@ -206,265 +192,6 @@ where (new_meshlets, new_indices) } -pub fn build_meshlets_adjacency( - meshlets: &[MeshletData], - indices: &[u32], -) -> Vec { - let mut meshlets_info = Vec::with_capacity(meshlets.len()); - let mut edge_meshlets_map: HashMap> = HashMap::default(); - meshlets - .iter() - .enumerate() - .for_each(|(meshlet_index, meshlet)| { - let triangle_count = meshlet.indices_count / 3; - let mut edges_hit_count: HashMap = HashMap::default(); - for triangle_index in 0..triangle_count { - let i1 = indices[(meshlet.indices_offset + triangle_index * 3) as usize]; - let i2 = indices[(meshlet.indices_offset + triangle_index * 3 + 1) as usize]; - let i3 = indices[(meshlet.indices_offset + triangle_index * 3 + 2) as usize]; - let e1 = Edge { - v1: i1.min(i2), - v2: i1.max(i2), - }; - let e2 = Edge { - v1: i2.min(i3), - v2: i2.max(i3), - }; - let e3 = Edge { - v1: i3.min(i1), - v2: i3.max(i1), - }; - edges_hit_count - .entry(e1.clone()) - .and_modify(|v| *v += 1) - .or_insert(1); - edges_hit_count - .entry(e2.clone()) - .and_modify(|v| *v += 1) - .or_insert(1); - edges_hit_count - .entry(e3.clone()) - .and_modify(|v| *v += 1) - .or_insert(1); - edge_meshlets_map - .entry(e1) - .and_modify(|v| { - if !v.contains(&meshlet_index) { - v.push(meshlet_index) - } - }) - .or_insert(vec![meshlet_index]); - edge_meshlets_map - .entry(e2) - .and_modify(|v| { - if !v.contains(&meshlet_index) { - v.push(meshlet_index) - } - }) - .or_insert(vec![meshlet_index]); - edge_meshlets_map - .entry(e3) - .and_modify(|v| { - if !v.contains(&meshlet_index) { - v.push(meshlet_index) - } - }) - .or_insert(vec![meshlet_index]); - } - let mut edges = Vec::new(); - for (e, count) in edges_hit_count { - if count == 1 { - edges.push(e); - } - } - meshlets_info.push(MeshletAdjacency { - meshlet_index: meshlet_index as _, - edges, - adjacent_meshlets: Vec::default(), - }); - }); - - let num_meshlets = meshlets_info.len(); - debug_assert!(num_meshlets == meshlets.len()); - - meshlets_info - .iter_mut() - .enumerate() - .for_each(|(info_index, info)| { - info.edges.iter().for_each(|e| { - if edge_meshlets_map[e].len() > 1 { - edge_meshlets_map[e].iter().for_each(|&meshlet_index| { - if meshlet_index != info_index { - if let Some(i) = info - .adjacent_meshlets - .iter() - .position(|l| l.0 == meshlet_index as u32) - { - info.adjacent_meshlets[i].1 += 1; - } else { - info.adjacent_meshlets.push((meshlet_index as u32, 1)); - } - } - }); - } - }); - }); - meshlets_info.iter_mut().for_each(|m| { - if m.adjacent_meshlets.is_empty() { - println!("Meshlet {} has no adjacency", m.meshlet_index); - } - m.adjacent_meshlets - .sort_by(|(_i, a), (_j, b)| b.partial_cmp(a).unwrap()); - }); - meshlets_info -} - -fn fill_with_info_and_adjacency( - original_meshlets_info: &[MeshletAdjacency], - meshlet_info: &MeshletAdjacency, - meshlets_to_add: &mut Vec, -) { - if !meshlets_to_add - .iter() - .any(|m| m.meshlet_index == meshlet_info.meshlet_index) - { - if let Some(p) = original_meshlets_info - .iter() - .position(|m| m.meshlet_index == meshlet_info.meshlet_index) - { - let original = original_meshlets_info[p].clone(); - meshlets_to_add.push(original); - original_meshlets_info[p] - .adjacent_meshlets - .iter() - .for_each(|&(i, _)| { - fill_with_info_and_adjacency( - original_meshlets_info, - &original_meshlets_info[i as usize], - meshlets_to_add, - ); - }); - } - } -} - -pub fn group_meshlets(meshlets_info: &[MeshletAdjacency]) -> Vec> { - let mut available_meshlets = meshlets_info.to_vec(); - let mut meshlets_groups = Vec::new(); - while !available_meshlets.is_empty() { - let mut meshlet_group = Vec::new(); - meshlet_group.push(available_meshlets.remove(0)); - let mut meshlet_current_index = 0; - while meshlet_group.len() < MESHLETS_GROUP_SIZE { - let mut max_adjacency_value = -1; - let mut adjacent_index = -1; - meshlet_group.iter().enumerate().for_each(|(i, m)| { - if let Some(index) = m - .adjacent_meshlets - .iter() - .position(|v| v.1 as i32 > max_adjacency_value) - { - max_adjacency_value = m.adjacent_meshlets[index].1 as i32; - adjacent_index = index as i32; - meshlet_current_index = i; - } - }); - if max_adjacency_value < 0 || adjacent_index < 0 { - break; - } - let meshlet_info = &mut meshlet_group[meshlet_current_index]; - let (other_index, _) = meshlet_info - .adjacent_meshlets - .remove(adjacent_index as usize); - if let Some(other_available_index) = available_meshlets - .iter() - .position(|m| m.meshlet_index == other_index) - { - let mut m = available_meshlets.remove(other_available_index); - if let Some(index) = m - .adjacent_meshlets - .iter() - .position(|v| v.0 == meshlet_info.meshlet_index) - { - m.adjacent_meshlets.remove(index); - } else { - println!( - "Expecting to find meshlet {} but it's not there {:?}", - meshlet_info.meshlet_index, m.adjacent_meshlets - ) - } - meshlet_group.push(m); - } - } - let mut should_retry = meshlet_group.is_empty(); - should_retry |= meshlet_group.len() == 1 && available_meshlets.len() > MESHLETS_GROUP_SIZE; - should_retry &= !meshlet_group.iter().all(|mi| { - meshlets_info[mi.meshlet_index as usize] - .adjacent_meshlets - .is_empty() - }); - if !should_retry || available_meshlets.is_empty() { - meshlets_groups.push(meshlet_group); - } else { - //steal from groups already created - let mut stealed = Vec::new(); - meshlet_group.iter().for_each(|info| { - if let Some(p) = meshlets_info - .iter() - .position(|m| m.meshlet_index == info.meshlet_index) - { - let original = &meshlets_info[p]; - let mut a = original.adjacent_meshlets.len() as i32 - 1; - while a >= 0 && stealed.len() < HALF_MESHLETS_GROUP_SIZE { - let mut j = meshlets_groups.len() as i32 - 1; - while a >= 0 && j >= 0 && stealed.len() < HALF_MESHLETS_GROUP_SIZE { - if meshlets_groups[j as usize].len() > HALF_MESHLETS_GROUP_SIZE { - if let Some(i) = meshlets_groups[j as usize].iter().position(|m| { - m.meshlet_index == original.adjacent_meshlets[a as usize].0 - }) { - stealed.push(meshlets_groups[j as usize].remove(i)); - a -= 1; - } - } - j -= 1; - } - a -= 1; - } - } - }); - meshlet_group.append(&mut stealed); - if meshlet_group.len() == 1 { - //readd all to the available meshlets - let last_index = available_meshlets.len(); - meshlet_group.iter().for_each(|info| { - fill_with_info_and_adjacency(meshlets_info, info, &mut available_meshlets); - }); - (last_index..available_meshlets.len()).for_each(|i| { - let info = &available_meshlets[i]; - meshlets_groups.retain(|group| { - !group.iter().any(|m| m.meshlet_index == info.meshlet_index) - }); - }); - } else { - meshlets_groups.push(meshlet_group); - } - } - } - debug_assert!(available_meshlets.is_empty()); - let num_total_meslets = meshlets_groups.iter().fold(0, |i, e| i + e.len()); - debug_assert!( - num_total_meslets == meshlets_info.len(), - "Not enough meshlets {}/{} in {} groups", - num_total_meslets, - meshlets_info.len(), - meshlets_groups.len() - ); - meshlets_groups - .iter() - .map(|info| info.iter().map(|m| m.meshlet_index).collect::<_>()) - .collect::<_>() -} - pub fn compute_clusters( groups: &[Vec], parent_meshlets: &mut [MeshletData],