From c94bd174eec5dcc076265fe38b27426b3fd3e82d Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 21 Jan 2024 14:50:36 +0200 Subject: [PATCH 01/10] Add `Mesh::transform_by` --- crates/bevy_render/src/mesh/mesh/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 270fd86cb1705..3c891ba66cee3 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -1,5 +1,6 @@ mod conversions; pub mod skinning; +use bevy_transform::components::Transform; pub use wgpu::PrimitiveTopology; use crate::{ @@ -572,6 +573,27 @@ impl Mesh { Ok(self) } + /// Transforms the vertex positions and normals of the mesh by the given [`Transform`]. + pub fn transform_by(&mut self, transform: Transform) { + if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply scale, rotation, and translation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); + } + + if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Rotate normals by transform rotation + normals.iter_mut().for_each(|normal| { + *normal = (transform.rotation * Vec3::from_slice(normal)).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 From 188a4e343629a37cd3089260c11a2607ca986bc9 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 21 Jan 2024 15:09:10 +0200 Subject: [PATCH 02/10] Transform mesh tangents --- crates/bevy_render/src/mesh/mesh/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 3c891ba66cee3..d96d7cd53164c 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -573,7 +573,7 @@ impl Mesh { Ok(self) } - /// Transforms the vertex positions and normals of the mesh by the given [`Transform`]. + /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) @@ -592,6 +592,15 @@ impl Mesh { *normal = (transform.rotation * Vec3::from_slice(normal)).to_array(); }); } + + if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Rotate tangents by transform rotation + tangents.iter_mut().for_each(|tangent| { + *tangent = (transform.rotation * Vec3::from_slice(tangent)).to_array(); + }); + } } /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space From c217dd8db1ab70ae0d5eb6f76dd8e30cfb6a5080 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 21 Jan 2024 15:49:54 +0200 Subject: [PATCH 03/10] Add `Mesh::transformed_by` --- crates/bevy_render/src/mesh/mesh/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index d96d7cd53164c..8a3dd69fc027d 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -573,6 +573,12 @@ impl Mesh { Ok(self) } + /// 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); + self + } + /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = @@ -584,6 +590,11 @@ impl Mesh { .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); } + // No need to rotate normals or tangents if rotation is near identity + if transform.rotation.is_near_identity() { + return; + } + if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { From f144a9ee63b2352f538008c7031317836ac938f5 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 21 Jan 2024 15:54:01 +0200 Subject: [PATCH 04/10] Impl `Mul` for `Transform` --- crates/bevy_render/src/mesh/mesh/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 8a3dd69fc027d..32eef44bdd6b4 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -579,7 +579,7 @@ impl Mesh { self } - /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. + /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) @@ -674,6 +674,14 @@ impl Mesh { } } +impl core::ops::Mul for Transform { + type Output = Mesh; + + fn mul(self, rhs: Mesh) -> Self::Output { + rhs.transformed_by(self) + } +} + #[derive(Debug, Clone)] pub struct MeshVertexAttribute { /// The friendly name of the vertex attribute From f270bfa17b8002419419aa14831b0fa1338fd48e Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 01:18:23 +0200 Subject: [PATCH 05/10] Fix normals and tangents with non-uniform scaling --- crates/bevy_render/src/mesh/mesh/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 32eef44bdd6b4..b3f3cbe774026 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -600,7 +600,9 @@ impl Mesh { { // Rotate normals by transform rotation normals.iter_mut().for_each(|normal| { - *normal = (transform.rotation * Vec3::from_slice(normal)).to_array(); + // Normals must be divided by scale and then normalized to account for non-uniform scaling + let n = (Vec3::from_slice(normal) / transform.scale).normalize(); + *normal = (transform.rotation * n).to_array(); }); } @@ -609,7 +611,9 @@ impl Mesh { { // Rotate tangents by transform rotation tangents.iter_mut().for_each(|tangent| { - *tangent = (transform.rotation * Vec3::from_slice(tangent)).to_array(); + // Tangents must be divided by scale and then normalized to account for non-uniform scaling + let t = (Vec3::from_slice(tangent) / transform.scale).normalize(); + *tangent = (transform.rotation * t).to_array(); }); } } From 783c991394db9c2b56ba508940bc5c2ef8774bc4 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 02:49:17 +0200 Subject: [PATCH 06/10] Avoid division by zero and fix comments --- crates/bevy_render/src/mesh/mesh/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index b796ff746e410..e05d69dbd097d 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -579,30 +579,31 @@ impl Mesh { .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); } - // No need to rotate normals or tangents if rotation is near identity - if transform.rotation.is_near_identity() { + // No need to transform normals or tangents if rotation is near identity and scale is uniform + if transform.rotation.is_near_identity() + && transform.scale.x == transform.scale.y + && transform.scale.y == transform.scale.z + { return; } if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { - // Rotate normals by transform rotation + // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { - // Normals must be divided by scale and then normalized to account for non-uniform scaling - let n = (Vec3::from_slice(normal) / transform.scale).normalize(); - *normal = (transform.rotation * n).to_array(); + let n = Vec3::from_slice(normal) * transform.scale.yzx() * transform.scale.zxy(); + *normal = (transform.rotation * n.normalize()).to_array(); }); } if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { - // Rotate tangents by transform rotation + // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { - // Tangents must be divided by scale and then normalized to account for non-uniform scaling - let t = (Vec3::from_slice(tangent) / transform.scale).normalize(); - *tangent = (transform.rotation * t).to_array(); + let t = Vec3::from_slice(tangent) * transform.scale.yzx() * transform.scale.zxy(); + *tangent = (transform.rotation * t.normalize()).to_array(); }); } } From 6d95cc33a653e5bfcb74663fdff7e97aef03fbd5 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 02:59:26 +0200 Subject: [PATCH 07/10] Debug assert scale is non-zero on at least one axis --- crates/bevy_render/src/mesh/mesh/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index e05d69dbd097d..d590ebd4289a4 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -570,6 +570,12 @@ impl Mesh { /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { + debug_assert!( + transform.scale.x == 0.0 && (transform.scale.y == 0.0 || transform.scale.z == 0.0) + || transform.scale.y == 0.0 && transform.scale.z == 0.0, + "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) { From 0e67373d388e223be93b2bb579e6801d7f275f1b Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 03:19:34 +0200 Subject: [PATCH 08/10] Use `normalize_or_zero` --- crates/bevy_render/src/mesh/mesh/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index d590ebd4289a4..20aa33bf3677d 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -599,7 +599,7 @@ impl Mesh { // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { let n = Vec3::from_slice(normal) * transform.scale.yzx() * transform.scale.zxy(); - *normal = (transform.rotation * n.normalize()).to_array(); + *normal = (transform.rotation * n.normalize_or_zero()).to_array(); }); } @@ -609,7 +609,7 @@ impl Mesh { // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { let t = Vec3::from_slice(tangent) * transform.scale.yzx() * transform.scale.zxy(); - *tangent = (transform.rotation * t.normalize()).to_array(); + *tangent = (transform.rotation * t.normalize_or_zero()).to_array(); }); } } From 2690be7e4614c8e2fc4082446b9a8e578f68894d Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 03:36:23 +0200 Subject: [PATCH 09/10] Move scaling factor to top of `transform_by` --- crates/bevy_render/src/mesh/mesh/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 20aa33bf3677d..a2cad97127e06 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -570,9 +570,11 @@ impl Mesh { /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { + // Needed when transforming normals and tangents + let bivector_scaling = transform.scale.yzx() * transform.scale.zxy(); + debug_assert!( - transform.scale.x == 0.0 && (transform.scale.y == 0.0 || transform.scale.z == 0.0) - || transform.scale.y == 0.0 && transform.scale.z == 0.0, + bivector_scaling != Vec3::ZERO, "mesh transform scale cannot be zero on more than one axis" ); @@ -598,8 +600,8 @@ impl Mesh { { // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { - let n = Vec3::from_slice(normal) * transform.scale.yzx() * transform.scale.zxy(); - *normal = (transform.rotation * n.normalize_or_zero()).to_array(); + let scaled_normal = Vec3::from_slice(normal) * bivector_scaling; + *normal = (transform.rotation * scaled_normal.normalize_or_zero()).to_array(); }); } @@ -608,8 +610,8 @@ impl Mesh { { // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { - let t = Vec3::from_slice(tangent) * transform.scale.yzx() * transform.scale.zxy(); - *tangent = (transform.rotation * t.normalize_or_zero()).to_array(); + let scaled_tangent = Vec3::from_slice(tangent) * bivector_scaling; + *tangent = (transform.rotation * scaled_tangent.normalize_or_zero()).to_array(); }); } } From dc3358a92528784fbfc005c8a787a8322a8d9e8c Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 29 Jan 2024 03:37:43 +0200 Subject: [PATCH 10/10] Rename --- crates/bevy_render/src/mesh/mesh/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index a2cad97127e06..702d9bc301e0a 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -571,10 +571,10 @@ impl Mesh { /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. pub fn transform_by(&mut self, transform: Transform) { // Needed when transforming normals and tangents - let bivector_scaling = transform.scale.yzx() * transform.scale.zxy(); + let covector_scale = transform.scale.yzx() * transform.scale.zxy(); debug_assert!( - bivector_scaling != Vec3::ZERO, + covector_scale != Vec3::ZERO, "mesh transform scale cannot be zero on more than one axis" ); @@ -600,7 +600,7 @@ impl Mesh { { // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { - let scaled_normal = Vec3::from_slice(normal) * bivector_scaling; + let scaled_normal = Vec3::from_slice(normal) * covector_scale; *normal = (transform.rotation * scaled_normal.normalize_or_zero()).to_array(); }); } @@ -610,7 +610,7 @@ impl Mesh { { // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { - let scaled_tangent = Vec3::from_slice(tangent) * bivector_scaling; + let scaled_tangent = Vec3::from_slice(tangent) * covector_scale; *tangent = (transform.rotation * scaled_tangent.normalize_or_zero()).to_array(); }); }