diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index e8ff13d57488b..672e90af5c0a6 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -613,6 +613,120 @@ impl Mesh { } } + /// Translates the vertex positions of the mesh by the given [`Vec3`]. + pub fn translated_by(mut self, translation: Vec3) -> Self { + self.translate_by(translation); + self + } + + /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. + pub fn translate_by(&mut self, translation: Vec3) { + if translation == Vec3::ZERO { + return; + } + + if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply translation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array()); + } + } + + /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. + pub fn rotated_by(mut self, rotation: Quat) -> Self { + self.rotate_by(rotation); + self + } + + /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. + pub fn rotate_by(&mut self, rotation: Quat) { + if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply rotation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (rotation * Vec3::from_slice(pos)).to_array()); + } + + // No need to transform normals or tangents if rotation is near identity + if rotation.is_near_identity() { + return; + } + + if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Transform normals + normals.iter_mut().for_each(|normal| { + *normal = (rotation * Vec3::from_slice(normal).normalize_or_zero()).to_array(); + }); + } + + if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Transform tangents + tangents.iter_mut().for_each(|tangent| { + *tangent = (rotation * Vec3::from_slice(tangent).normalize_or_zero()).to_array(); + }); + } + } + + /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. + pub fn scaled_by(mut self, scale: Vec3) -> Self { + self.scale_by(scale); + self + } + + /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. + pub fn scale_by(&mut self, scale: Vec3) { + // Needed when transforming normals and tangents + let covector_scale = scale.yzx() * scale.zxy(); + + debug_assert!( + covector_scale != Vec3::ZERO, + "mesh transform scale cannot be zero on more than one axis" + ); + + if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply scale to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (scale * Vec3::from_slice(pos)).to_array()); + } + + // No need to transform normals or tangents if scale is uniform + if scale.x == scale.y && scale.y == scale.z { + return; + } + + if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Transform normals, taking into account non-uniform scaling + normals.iter_mut().for_each(|normal| { + let scaled_normal = Vec3::from_slice(normal) * covector_scale; + *normal = scaled_normal.normalize_or_zero().to_array(); + }); + } + + if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Transform tangents, taking into account non-uniform scaling + tangents.iter_mut().for_each(|tangent| { + let scaled_tangent = Vec3::from_slice(tangent) * covector_scale; + *tangent = scaled_tangent.normalize_or_zero().to_array(); + }); + } + } + /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space /// /// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of