Mesh::merge to return a Result (#17475)

# Objective

Make `Mesh::merge` more resilient to use.

Currently, it's difficult to make sure `Mesh::merge` will not panic
(we'd have to check if all attributes are compatible).

- I'd appreciate it for utility function to convert different mesh
representations such as:
https://github.com/dimforge/bevy_rapier/pull/628.

## Solution

- Make `Mesh::merge` return a `Result`.

## Testing

- It builds

## Migration Guide

- `Mesh::merge` now returns a `Result<(), MeshMergeError>`.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com>
This commit is contained in:
Thierry Berger 2025-01-23 05:05:36 +01:00 committed by GitHub
parent 9387fcfbf2
commit fd2afeefda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 13 deletions

View File

@ -14,6 +14,7 @@ use bevy_image::Image;
use bevy_math::{primitives::Triangle3d, *};
use bevy_reflect::Reflect;
use bytemuck::cast_slice;
use thiserror::Error;
use tracing::warn;
use wgpu_types::{VertexAttribute, VertexFormat, VertexStepMode};
@ -280,7 +281,7 @@ impl Mesh {
self.attributes.contains_key(&id.into())
}
/// Retrieves the data currently set to the vertex attribute with the specified `name`.
/// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
#[inline]
pub fn attribute(
&self,
@ -289,6 +290,15 @@ impl Mesh {
self.attributes.get(&id.into()).map(|data| &data.values)
}
/// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`].
#[inline]
pub(crate) fn attribute_data(
&self,
id: impl Into<MeshVertexAttributeId>,
) -> Option<&MeshAttributeData> {
self.attributes.get(&id.into())
}
/// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
#[inline]
pub fn attribute_mut(
@ -788,11 +798,11 @@ impl Mesh {
///
/// `Aabb` of entities with modified mesh are not updated automatically.
///
/// # Panics
/// # Errors
///
/// Panics if the vertex attribute values of `other` are incompatible with `self`.
/// Returns [`Err(MergeMeshError)`](MergeMeshError) if the vertex attribute values of `other` are incompatible with `self`.
/// For example, [`VertexAttributeValues::Float32`] is incompatible with [`VertexAttributeValues::Float32x3`].
pub fn merge(&mut self, other: &Mesh) {
pub fn merge(&mut self, other: &Mesh) -> Result<(), MergeMeshError> {
use VertexAttributeValues::*;
// The indices of `other` should start after the last vertex of `self`.
@ -800,7 +810,6 @@ impl Mesh {
// Extend attributes of `self` with attributes of `other`.
for (attribute, values) in self.attributes_mut() {
let enum_variant_name = values.enum_variant_name();
if let Some(other_values) = other.attribute(attribute.id) {
#[expect(
clippy::match_same_arms,
@ -835,11 +844,14 @@ impl Mesh {
(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()
),
_ => {
return Err(MergeMeshError {
self_attribute: *attribute,
other_attribute: other
.attribute_data(attribute.id)
.map(|data| data.attribute),
})
}
}
}
}
@ -848,6 +860,7 @@ impl Mesh {
if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) {
indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32));
}
Ok(())
}
/// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`].
@ -1213,6 +1226,14 @@ impl core::ops::Mul<Mesh> for Transform {
}
}
/// Error that can occur when calling [`Mesh::merge`].
#[derive(Error, Debug, Clone)]
#[error("Incompatible vertex attribute types {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))]
pub struct MergeMeshError {
pub self_attribute: MeshVertexAttribute,
pub other_attribute: Option<MeshVertexAttribute>,
}
#[cfg(test)]
mod tests {
use super::Mesh;

View File

@ -216,7 +216,7 @@ where
// An extrusion of depth 0 does not need a mantel
if self.half_depth == 0. {
front_face.merge(&back_face);
front_face.merge(&back_face).unwrap();
return front_face;
}
@ -408,8 +408,8 @@ where
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
};
front_face.merge(&back_face);
front_face.merge(&mantel);
front_face.merge(&back_face).unwrap();
front_face.merge(&mantel).unwrap();
front_face
}
}