diff --git a/crates/bevy_mesh/src/mesh.rs b/crates/bevy_mesh/src/mesh.rs index 8a5ac7e37d920..065a867f66df4 100644 --- a/crates/bevy_mesh/src/mesh.rs +++ b/crates/bevy_mesh/src/mesh.rs @@ -12,8 +12,12 @@ use bevy_asset::{Asset, Handle, RenderAssetUsages}; use bevy_image::Image; use bevy_math::{primitives::Triangle3d, *}; use bevy_reflect::Reflect; +use bevy_utils::hashbrown::hash_map; use bevy_utils::tracing::warn; +use bevy_utils::HashMap; use bytemuck::cast_slice; +use core::hash::{Hash, Hasher}; +use core::ptr; use wgpu::{VertexAttribute, VertexFormat, VertexStepMode}; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; @@ -547,6 +551,102 @@ impl Mesh { self } + /// Remove duplicate vertices and create the index pointing to the unique vertices. + /// + /// This function is no-op if the mesh already has [`Indices`] set, + /// even if there are duplicate vertices. If deduplication is needed with indices already set, + /// consider calling [`Mesh::duplicate_vertices`] and then this function. + pub fn deduplicate_vertices(&mut self) { + if self.indices.is_some() { + return; + } + + #[derive(Copy, Clone)] + struct VertexRef<'a> { + mesh: &'a Mesh, + i: usize, + } + impl<'a> VertexRef<'a> { + fn push_to(&self, target: &mut BTreeMap) { + for (key, this_attribute_data) in self.mesh.attributes.iter() { + let target_attribute_data = target.get_mut(key).unwrap(); + target_attribute_data + .values + .push_from(&this_attribute_data.values, self.i); + } + } + } + impl<'a> PartialEq for VertexRef<'a> { + fn eq(&self, other: &Self) -> bool { + assert!(ptr::eq(self.mesh, other.mesh)); + for values in self.mesh.attributes.values() { + if values.values.get_bytes_at(self.i) != values.values.get_bytes_at(other.i) { + return false; + } + } + true + } + } + impl<'a> Eq for VertexRef<'a> {} + impl<'a> Hash for VertexRef<'a> { + fn hash(&self, state: &mut H) { + for values in self.mesh.attributes.values() { + values.values.get_bytes_at(self.i).hash(state); + } + } + } + + let mut new_attributes: BTreeMap = self + .attributes + .iter() + .map(|(k, v)| { + ( + *k, + MeshAttributeData { + attribute: v.attribute, + values: VertexAttributeValues::new(VertexFormat::from(&v.values)), + }, + ) + }) + .collect(); + + let mut vertex_to_new_index: HashMap = HashMap::new(); + let mut indices = Vec::with_capacity(self.count_vertices()); + for i in 0..self.count_vertices() { + let len: u32 = vertex_to_new_index + .len() + .try_into() + .expect("The number of vertices exceeds u32::MAX"); + let vertex_ref = VertexRef { mesh: self, i }; + let j = match vertex_to_new_index.entry(vertex_ref) { + hash_map::Entry::Occupied(e) => *e.get(), + hash_map::Entry::Vacant(e) => { + e.insert(len); + vertex_ref.push_to(&mut new_attributes); + len + } + }; + indices.push(j); + } + drop(vertex_to_new_index); + + for v in new_attributes.values_mut() { + v.values.shrink_to_fit(); + } + + self.attributes = new_attributes; + self.indices = Some(Indices::U32(indices)); + } + + /// Consumes the mesh and returns a mesh with merged vertices. + /// + /// See [`Mesh::deduplicate_vertices`] for more information. + #[must_use] + pub fn with_deduplicated_vertices(mut self) -> Self { + self.deduplicate_vertices(); + self + } + /// Inverts the winding of the indices such that all counter-clockwise triangles are now /// clockwise and vice versa. /// For lines, their start and end indices are flipped. @@ -1478,4 +1578,63 @@ mod tests { mesh.triangles().unwrap().collect::>() ); } + + #[test] + fn deduplicate_vertices() { + let mut mesh = Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ); + // Quad made of two triangles. + let positions = vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], + // This will be deduplicated. + [1.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + // Position is equal to the first one but UV is different so it won't be deduplicated. + [0.0, 0.0, 0.0], + ]; + let uvs = vec![ + [0.0, 0.0], + [1.0, 0.0], + [1.0, 1.0], + // This will be deduplicated. + [1.0, 1.0], + [0.0, 1.0], + // Use different UV here so it won't be deduplicated. + [0.0, 0.5], + ]; + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + VertexAttributeValues::Float32x3(positions.clone()), + ); + mesh.insert_attribute( + Mesh::ATTRIBUTE_UV_0, + VertexAttributeValues::Float32x2(uvs.clone()), + ); + + mesh.deduplicate_vertices(); + assert_eq!(6, mesh.indices().unwrap().len()); + // Note we have 5 unique vertices, not 6. + assert_eq!(5, mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().len()); + assert_eq!(5, mesh.attribute(Mesh::ATTRIBUTE_UV_0).unwrap().len()); + + // Duplicate back. + mesh.duplicate_vertices(); + assert!(mesh.indices().is_none()); + let VertexAttributeValues::Float32x3(new_positions) = + mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap() + else { + panic!("Unexpected attribute type") + }; + let VertexAttributeValues::Float32x2(new_uvs) = + mesh.attribute(Mesh::ATTRIBUTE_UV_0).unwrap() + else { + panic!("Unexpected attribute type") + }; + assert_eq!(&positions, new_positions); + assert_eq!(&uvs, new_uvs); + } } diff --git a/crates/bevy_mesh/src/vertex.rs b/crates/bevy_mesh/src/vertex.rs index a6b52dac7e086..b8788fd23477f 100644 --- a/crates/bevy_mesh/src/vertex.rs +++ b/crates/bevy_mesh/src/vertex.rs @@ -3,7 +3,7 @@ use bevy_derive::EnumVariantMeta; use bevy_ecs::system::Resource; use bevy_math::Vec3; use bevy_utils::HashSet; -use bytemuck::cast_slice; +use bytemuck::{bytes_of, cast_slice}; use core::hash::{Hash, Hasher}; use derive_more::derive::{Display, Error}; use wgpu::{BufferAddress, VertexAttribute, VertexFormat, VertexStepMode}; @@ -225,6 +225,51 @@ pub enum VertexAttributeValues { } impl VertexAttributeValues { + /// Creates a new [`VertexAttributeValues`] with the storage for given [`VertexFormat`]. + /// + /// # Panics + /// + /// Panics if the [`VertexFormat`] is not supported. + pub(crate) fn new(format: VertexFormat) -> Self { + match format { + VertexFormat::Uint8x2 => VertexAttributeValues::Uint8x2(Vec::new()), + VertexFormat::Uint8x4 => VertexAttributeValues::Uint8x4(Vec::new()), + VertexFormat::Sint8x2 => VertexAttributeValues::Sint8x2(Vec::new()), + VertexFormat::Sint8x4 => VertexAttributeValues::Sint8x4(Vec::new()), + VertexFormat::Unorm8x2 => VertexAttributeValues::Unorm8x2(Vec::new()), + VertexFormat::Unorm8x4 => VertexAttributeValues::Unorm8x4(Vec::new()), + VertexFormat::Snorm8x2 => VertexAttributeValues::Snorm8x2(Vec::new()), + VertexFormat::Snorm8x4 => VertexAttributeValues::Snorm8x4(Vec::new()), + VertexFormat::Uint16x2 => VertexAttributeValues::Uint16x2(Vec::new()), + VertexFormat::Uint16x4 => VertexAttributeValues::Uint16x4(Vec::new()), + VertexFormat::Sint16x2 => VertexAttributeValues::Sint16x2(Vec::new()), + VertexFormat::Sint16x4 => VertexAttributeValues::Sint16x4(Vec::new()), + VertexFormat::Unorm16x2 => VertexAttributeValues::Unorm16x2(Vec::new()), + VertexFormat::Unorm16x4 => VertexAttributeValues::Unorm16x4(Vec::new()), + VertexFormat::Snorm16x2 => VertexAttributeValues::Snorm16x2(Vec::new()), + VertexFormat::Snorm16x4 => VertexAttributeValues::Snorm16x4(Vec::new()), + VertexFormat::Float32 => VertexAttributeValues::Float32(Vec::new()), + VertexFormat::Float32x2 => VertexAttributeValues::Float32x2(Vec::new()), + VertexFormat::Float32x3 => VertexAttributeValues::Float32x3(Vec::new()), + VertexFormat::Float32x4 => VertexAttributeValues::Float32x4(Vec::new()), + VertexFormat::Uint32 => VertexAttributeValues::Uint32(Vec::new()), + VertexFormat::Uint32x2 => VertexAttributeValues::Uint32x2(Vec::new()), + VertexFormat::Uint32x3 => VertexAttributeValues::Uint32x3(Vec::new()), + VertexFormat::Uint32x4 => VertexAttributeValues::Uint32x4(Vec::new()), + VertexFormat::Sint32 => VertexAttributeValues::Sint32(Vec::new()), + VertexFormat::Sint32x2 => VertexAttributeValues::Sint32x2(Vec::new()), + VertexFormat::Sint32x3 => VertexAttributeValues::Sint32x3(Vec::new()), + VertexFormat::Sint32x4 => VertexAttributeValues::Sint32x4(Vec::new()), + VertexFormat::Float16x2 => panic!("Float16x2 is not supported"), + VertexFormat::Float16x4 => panic!("Float16x4 is not supported"), + VertexFormat::Float64 => panic!("Float64 is not supported"), + VertexFormat::Float64x2 => panic!("Float64x2 is not supported"), + VertexFormat::Float64x3 => panic!("Float64x3 is not supported"), + VertexFormat::Float64x4 => panic!("Float64x4 is not supported"), + VertexFormat::Unorm10_10_10_2 => panic!("Unorm10_10_10_2 is not supported"), + } + } + /// Returns the number of vertices in this [`VertexAttributeValues`]. For a single /// mesh, all of the [`VertexAttributeValues`] must have the same length. #[allow(clippy::match_same_arms)] @@ -310,6 +355,206 @@ impl VertexAttributeValues { VertexAttributeValues::Unorm8x4(values) => cast_slice(values), } } + + #[allow(clippy::match_same_arms)] + pub(crate) fn get_bytes_at(&self, i: usize) -> &[u8] { + match self { + VertexAttributeValues::Float32(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint32(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint32(values) => bytes_of(&values[i]), + VertexAttributeValues::Float32x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint32x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint32x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Float32x3(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint32x3(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint32x3(values) => bytes_of(&values[i]), + VertexAttributeValues::Float32x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint32x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint32x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint16x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Snorm16x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint16x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Unorm16x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint16x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Snorm16x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint16x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Unorm16x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint8x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Snorm8x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint8x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Unorm8x2(values) => bytes_of(&values[i]), + VertexAttributeValues::Sint8x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Snorm8x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Uint8x4(values) => bytes_of(&values[i]), + VertexAttributeValues::Unorm8x4(values) => bytes_of(&values[i]), + } + } + + #[allow(clippy::match_same_arms)] + pub(crate) fn push_from(&mut self, source: &VertexAttributeValues, i: usize) { + match (self, source) { + (VertexAttributeValues::Float32(this), VertexAttributeValues::Float32(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Float32(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Sint32(this), VertexAttributeValues::Sint32(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint32(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint32(this), VertexAttributeValues::Uint32(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint32(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Float32x2(this), VertexAttributeValues::Float32x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Float32x2(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Sint32x2(this), VertexAttributeValues::Sint32x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint32x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint32x2(this), VertexAttributeValues::Uint32x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint32x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Float32x3(this), VertexAttributeValues::Float32x3(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Float32x3(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Sint32x3(this), VertexAttributeValues::Sint32x3(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint32x3(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint32x3(this), VertexAttributeValues::Uint32x3(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint32x3(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Float32x4(this), VertexAttributeValues::Float32x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Float32x4(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Sint32x4(this), VertexAttributeValues::Sint32x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint32x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint32x4(this), VertexAttributeValues::Uint32x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint32x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Sint16x2(this), VertexAttributeValues::Sint16x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint16x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Snorm16x2(this), VertexAttributeValues::Snorm16x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Snorm16x2(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Uint16x2(this), VertexAttributeValues::Uint16x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint16x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Unorm16x2(this), VertexAttributeValues::Unorm16x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Unorm16x2(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Sint16x4(this), VertexAttributeValues::Sint16x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint16x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Snorm16x4(this), VertexAttributeValues::Snorm16x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Snorm16x4(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Uint16x4(this), VertexAttributeValues::Uint16x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint16x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Unorm16x4(this), VertexAttributeValues::Unorm16x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Unorm16x4(_), _) => { + panic!("Mismatched vertex attribute values") + } + (VertexAttributeValues::Sint8x2(this), VertexAttributeValues::Sint8x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint8x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Snorm8x2(this), VertexAttributeValues::Snorm8x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Snorm8x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint8x2(this), VertexAttributeValues::Uint8x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint8x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Unorm8x2(this), VertexAttributeValues::Unorm8x2(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Unorm8x2(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Sint8x4(this), VertexAttributeValues::Sint8x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Sint8x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Snorm8x4(this), VertexAttributeValues::Snorm8x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Snorm8x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Uint8x4(this), VertexAttributeValues::Uint8x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Uint8x4(_), _) => panic!("Mismatched vertex attribute values"), + (VertexAttributeValues::Unorm8x4(this), VertexAttributeValues::Unorm8x4(source)) => { + this.push(source[i]); + } + (VertexAttributeValues::Unorm8x4(_), _) => panic!("Mismatched vertex attribute values"), + } + } + + #[allow(clippy::match_same_arms)] + pub(crate) fn shrink_to_fit(&mut self) { + match self { + VertexAttributeValues::Float32(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint32(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint32(v) => v.shrink_to_fit(), + VertexAttributeValues::Float32x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint32x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint32x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Float32x3(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint32x3(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint32x3(v) => v.shrink_to_fit(), + VertexAttributeValues::Float32x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint32x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint32x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint16x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Snorm16x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint16x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Unorm16x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint16x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Snorm16x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint16x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Unorm16x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint8x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Snorm8x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint8x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Unorm8x2(v) => v.shrink_to_fit(), + VertexAttributeValues::Sint8x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Snorm8x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Uint8x4(v) => v.shrink_to_fit(), + VertexAttributeValues::Unorm8x4(v) => v.shrink_to_fit(), + } + } } impl From<&VertexAttributeValues> for VertexFormat {