From f1f83bf5bc898140bff862a7279b5cd318f38661 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Tue, 13 Feb 2024 00:04:33 +0200 Subject: [PATCH] Add `Mesh::merge` (#11456) # Objective It can sometimes be useful to combine several meshes into one. This allows constructing more complex meshes out of simple primitives without needing to use a 3D modeling program or entity hierarchies. This could also be used internally to increase code reuse by using existing mesh generation logic for e.g. circles and using that in cylinder mesh generation logic to add the top and bottom of the cylinder. **Note**: This is *not* implementing CSGs (Constructive Solid Geometry) or any boolean operations, as that is much more complex. This is simply adding the mesh data of another mesh to a mesh. ## Solution Add a `merge` method to `Mesh`. It appends the vertex attributes and indices of `other` to `self`, resulting in a `Mesh` that is the combination of the two. For example, you could do this: ```rust let mut cuboid = Mesh::from(shape::Box::default()); let mut cylinder = Mesh::from(shape::Cylinder::default()); let mut torus = Mesh::from(shape::Torus::default()); cuboid.merge(cylinder); cuboid.merge(torus); ``` This would result in `cuboid` being a `Mesh` that also has the cylinder mesh and torus mesh. In this case, they would just be placed on top of each other, but by utilizing #11454 we can transform the cylinder and torus to get a result like this: https://github.com/bevyengine/bevy/assets/57632562/557402c6-b896-4aba-bd95-312e7d1b5238 This is just a single entity and a single mesh. --- crates/bevy_render/src/mesh/mesh/mod.rs | 79 +++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 9d57f00f3fa13..07e3dd0af00d6 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -590,6 +590,85 @@ impl Mesh { Ok(self) } + /// Merges the [`Mesh`] data of `other` with `self`. The attributes and indices of `other` will be appended to `self`. + /// + /// Note that attributes of `other` that don't exist on `self` will be ignored. + /// + /// # Panics + /// + /// Panics if the vertex attribute values of `other` are incompatible with `self`. + /// For example, [`VertexAttributeValues::Float32`] is incompatible with [`VertexAttributeValues::Float32x3`]. + #[allow(clippy::match_same_arms)] + pub fn merge(&mut self, other: Mesh) { + use VertexAttributeValues::*; + + // The indices of `other` should start after the last vertex of `self`. + let index_offset = self + .attribute(Mesh::ATTRIBUTE_POSITION) + .get_or_insert(&VertexAttributeValues::Float32x3(Vec::default())) + .len(); + + // Extend attributes of `self` with attributes of `other`. + for (id, values) in self.attributes_mut() { + let enum_variant_name = values.enum_variant_name(); + if let Some(other_values) = other.attribute(id) { + match (values, other_values) { + (Float32(vec1), Float32(vec2)) => vec1.extend(vec2), + (Sint32(vec1), Sint32(vec2)) => vec1.extend(vec2), + (Uint32(vec1), Uint32(vec2)) => vec1.extend(vec2), + (Float32x2(vec1), Float32x2(vec2)) => vec1.extend(vec2), + (Sint32x2(vec1), Sint32x2(vec2)) => vec1.extend(vec2), + (Uint32x2(vec1), Uint32x2(vec2)) => vec1.extend(vec2), + (Float32x3(vec1), Float32x3(vec2)) => vec1.extend(vec2), + (Sint32x3(vec1), Sint32x3(vec2)) => vec1.extend(vec2), + (Uint32x3(vec1), Uint32x3(vec2)) => vec1.extend(vec2), + (Sint32x4(vec1), Sint32x4(vec2)) => vec1.extend(vec2), + (Uint32x4(vec1), Uint32x4(vec2)) => vec1.extend(vec2), + (Float32x4(vec1), Float32x4(vec2)) => vec1.extend(vec2), + (Sint16x2(vec1), Sint16x2(vec2)) => vec1.extend(vec2), + (Snorm16x2(vec1), Snorm16x2(vec2)) => vec1.extend(vec2), + (Uint16x2(vec1), Uint16x2(vec2)) => vec1.extend(vec2), + (Unorm16x2(vec1), Unorm16x2(vec2)) => vec1.extend(vec2), + (Sint16x4(vec1), Sint16x4(vec2)) => vec1.extend(vec2), + (Snorm16x4(vec1), Snorm16x4(vec2)) => vec1.extend(vec2), + (Uint16x4(vec1), Uint16x4(vec2)) => vec1.extend(vec2), + (Unorm16x4(vec1), Unorm16x4(vec2)) => vec1.extend(vec2), + (Sint8x2(vec1), Sint8x2(vec2)) => vec1.extend(vec2), + (Snorm8x2(vec1), Snorm8x2(vec2)) => vec1.extend(vec2), + (Uint8x2(vec1), Uint8x2(vec2)) => vec1.extend(vec2), + (Unorm8x2(vec1), Unorm8x2(vec2)) => vec1.extend(vec2), + (Sint8x4(vec1), Sint8x4(vec2)) => vec1.extend(vec2), + (Snorm8x4(vec1), Snorm8x4(vec2)) => vec1.extend(vec2), + (Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2), + (Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2), + _ => panic!( + "Incompatible vertex attribute types {} and {}", + enum_variant_name, + other_values.enum_variant_name() + ), + } + } + } + + // Extend indices of `self` with indices of `other`. + if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) { + match (indices, other_indices) { + (Indices::U16(i1), Indices::U16(i2)) => { + i1.extend(i2.iter().map(|i| *i + index_offset as u16)); + } + (Indices::U32(i1), Indices::U32(i2)) => { + i1.extend(i2.iter().map(|i| *i + index_offset as u32)); + } + (Indices::U16(i1), Indices::U32(i2)) => { + i1.extend(i2.iter().map(|i| *i as u16 + index_offset as u16)); + } + (Indices::U32(i1), Indices::U16(i2)) => { + i1.extend(i2.iter().map(|i| *i as u32 + index_offset as u32)); + } + } + } + } + /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. pub fn transformed_by(mut self, transform: Transform) -> Self { self.transform_by(transform);