From 4659a98cfa1ffde4eba849814b1f38099a80b77f Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Sat, 16 Jul 2022 00:51:12 +0000 Subject: [PATCH] Use Affine3A for GlobalTransform to allow any affine transformation (#4379) # Objective - Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis. - Related to #1755 and #2026 ## Solution - `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`. - The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types. - using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support. --- ## Changelog - `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`. Co-authored-by: Carter Anderson --- crates/bevy_pbr/src/light.rs | 59 ++-- crates/bevy_pbr/src/render/light.rs | 16 +- crates/bevy_pbr/src/render/mesh.rs | 2 +- crates/bevy_render/src/view/mod.rs | 2 +- crates/bevy_render/src/view/visibility/mod.rs | 5 +- crates/bevy_sprite/src/render/mod.rs | 6 +- crates/bevy_text/src/text2d.rs | 12 +- .../src/components/global_transform.rs | 257 ++++++------------ .../src/components/transform.rs | 17 +- crates/bevy_transform/src/systems.rs | 8 +- crates/bevy_ui/src/focus.rs | 2 +- crates/bevy_ui/src/render/mod.rs | 16 +- crates/bevy_ui/src/update.rs | 2 +- examples/ecs/iter_combinations.rs | 2 +- examples/tools/scene_viewer.rs | 2 +- .../transforms/global_vs_local_translation.rs | 4 +- 16 files changed, 160 insertions(+), 252 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index ff505467a0e3e..5033b273b1b32 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use bevy_ecs::prelude::*; -use bevy_math::{Mat4, Quat, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles}; +use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_reflect::prelude::*; use bevy_render::{ camera::{Camera, CameraProjection, OrthographicProjection}, @@ -12,7 +12,7 @@ use bevy_render::{ renderer::RenderDevice, view::{ComputedVisibility, RenderLayers, VisibleEntities}, }; -use bevy_transform::components::GlobalTransform; +use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::tracing::warn; use crate::{ @@ -755,13 +755,21 @@ pub(crate) fn point_light_order( // data required for assigning lights to clusters pub(crate) struct PointLightAssignmentData { entity: Entity, - translation: Vec3, - rotation: Quat, + transform: GlobalTransform, range: f32, shadows_enabled: bool, spot_light_angle: Option, } +impl PointLightAssignmentData { + pub fn sphere(&self) -> Sphere { + Sphere { + center: self.transform.translation_vec3a(), + radius: self.range, + } + } +} + #[derive(Default)] pub struct GlobalVisiblePointLights { entities: HashSet, @@ -815,8 +823,7 @@ pub(crate) fn assign_lights_to_clusters( .map( |(entity, transform, point_light, _visibility)| PointLightAssignmentData { entity, - translation: transform.translation, - rotation: Quat::default(), + transform: GlobalTransform::from_translation(transform.translation()), shadows_enabled: point_light.shadows_enabled, range: point_light.range, spot_light_angle: None, @@ -830,8 +837,7 @@ pub(crate) fn assign_lights_to_clusters( .map( |(entity, transform, spot_light, _visibility)| PointLightAssignmentData { entity, - translation: transform.translation, - rotation: transform.rotation, + transform: *transform, shadows_enabled: spot_light.shadows_enabled, range: spot_light.range, spot_light_angle: Some(spot_light.outer_angle), @@ -872,11 +878,7 @@ pub(crate) fn assign_lights_to_clusters( if lights_in_view_count == MAX_UNIFORM_BUFFER_POINT_LIGHTS + 1 { false } else { - let light_sphere = Sphere { - center: Vec3A::from(light.translation), - radius: light.range, - }; - + let light_sphere = light.sphere(); let light_in_view = frusta .iter() .any(|frustum| frustum.intersects_sphere(&light_sphere, true)); @@ -932,7 +934,8 @@ pub(crate) fn assign_lights_to_clusters( lights .iter() .map(|light| { - -inverse_view_row_2.dot(light.translation.extend(1.0)) + light.range + -inverse_view_row_2.dot(light.transform.translation().extend(1.0)) + + light.range }) .reduce(f32::max) .unwrap_or(0.0) @@ -966,10 +969,7 @@ pub(crate) fn assign_lights_to_clusters( if config.dynamic_resizing() { let mut cluster_index_estimate = 0.0; for light in lights.iter() { - let light_sphere = Sphere { - center: Vec3A::from(light.translation), - radius: light.range, - }; + let light_sphere = light.sphere(); // Check if the light is within the view frustum if !frustum.intersects_sphere(&light_sphere, true) { @@ -1124,10 +1124,7 @@ pub(crate) fn assign_lights_to_clusters( let mut update_from_light_intersections = |visible_lights: &mut Vec| { for light in lights.iter() { - let light_sphere = Sphere { - center: Vec3A::from(light.translation), - radius: light.range, - }; + let light_sphere = light.sphere(); // Check if the light is within the view frustum if !frustum.intersects_sphere(&light_sphere, true) { @@ -1177,8 +1174,7 @@ pub(crate) fn assign_lights_to_clusters( let spot_light_dir_sin_cos = light.spot_light_angle.map(|angle| { let (angle_sin, angle_cos) = angle.sin_cos(); ( - (inverse_view_transform * (light.rotation * Vec3::Z).extend(0.0)) - .truncate(), + (inverse_view_transform * light.transform.back().extend(0.0)).truncate(), angle_sin, angle_cos, ) @@ -1432,7 +1428,7 @@ pub fn update_directional_light_frusta( * transform.compute_matrix().inverse(); *frustum = Frustum::from_view_projection( &view_projection, - &transform.translation, + &transform.translation(), &transform.back(), directional_light.shadow_projection.far(), ); @@ -1451,7 +1447,7 @@ pub fn update_point_light_frusta( Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z); let view_rotations = CUBE_MAP_FACES .iter() - .map(|CubeMapFace { target, up }| GlobalTransform::identity().looking_at(*target, *up)) + .map(|CubeMapFace { target, up }| Transform::identity().looking_at(*target, *up)) .collect::>(); for (entity, transform, point_light, mut cubemap_frusta) in &mut views { @@ -1467,7 +1463,7 @@ pub fn update_point_light_frusta( // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects // and ignore rotation because we want the shadow map projections to align with the axes - let view_translation = GlobalTransform::from_translation(transform.translation); + let view_translation = Transform::from_translation(transform.translation()); let view_backward = transform.back(); for (view_rotation, frustum) in view_rotations.iter().zip(cubemap_frusta.iter_mut()) { @@ -1476,7 +1472,7 @@ pub fn update_point_light_frusta( *frustum = Frustum::from_view_projection( &view_projection, - &transform.translation, + &transform.translation(), &view_backward, point_light.range, ); @@ -1503,7 +1499,6 @@ pub fn update_spot_light_frusta( // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects - let view_translation = GlobalTransform::from_translation(transform.translation); let view_backward = transform.back(); let spot_view = spot_light_view_matrix(transform); @@ -1512,7 +1507,7 @@ pub fn update_spot_light_frusta( *frustum = Frustum::from_view_projection( &view_projection, - &view_translation.translation, + &transform.translation(), &view_backward, spot_light.range, ); @@ -1623,7 +1618,7 @@ pub fn check_light_mesh_visibility( let view_mask = maybe_view_mask.copied().unwrap_or_default(); let light_sphere = Sphere { - center: Vec3A::from(transform.translation), + center: Vec3A::from(transform.translation()), radius: point_light.range, }; @@ -1686,7 +1681,7 @@ pub fn check_light_mesh_visibility( let view_mask = maybe_view_mask.copied().unwrap_or_default(); let light_sphere = Sphere { - center: Vec3A::from(transform.translation), + center: Vec3A::from(transform.translation()), radius: point_light.range, }; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 8a9c037b1bdc6..29c3d0b5b439d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -31,7 +31,7 @@ use bevy_render::{ }, Extract, }; -use bevy_transform::components::GlobalTransform; +use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::FloatOrd; use bevy_utils::{ tracing::{error, warn}, @@ -728,7 +728,7 @@ pub fn calculate_cluster_factors( // could move this onto transform but it's pretty niche pub(crate) fn spot_light_view_matrix(transform: &GlobalTransform) -> Mat4 { // the matrix z_local (opposite of transform.forward()) - let fwd_dir = transform.local_z().extend(0.0); + let fwd_dir = transform.back().extend(0.0); let sign = 1f32.copysign(fwd_dir.z); let a = -1.0 / (fwd_dir.z + sign); @@ -745,7 +745,7 @@ pub(crate) fn spot_light_view_matrix(transform: &GlobalTransform) -> Mat4 { right_dir, up_dir, fwd_dir, - transform.translation.extend(1.0), + transform.translation().extend(1.0), ) } @@ -779,7 +779,7 @@ pub fn prepare_lights( Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z); let cube_face_rotations = CUBE_MAP_FACES .iter() - .map(|CubeMapFace { target, up }| GlobalTransform::identity().looking_at(*target, *up)) + .map(|CubeMapFace { target, up }| Transform::identity().looking_at(*target, *up)) .collect::>(); global_light_meta.entity_to_index.clear(); @@ -893,7 +893,7 @@ pub fn prepare_lights( * light.intensity) .xyz() .extend(1.0 / (light.range * light.range)), - position_radius: light.transform.translation.extend(light.radius), + position_radius: light.transform.translation().extend(light.radius), flags: flags.bits, shadow_depth_bias: light.shadow_depth_bias, shadow_normal_bias: light.shadow_normal_bias, @@ -989,7 +989,7 @@ pub fn prepare_lights( // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects // and ignore rotation because we want the shadow map projections to align with the axes - let view_translation = GlobalTransform::from_translation(light.transform.translation); + let view_translation = GlobalTransform::from_translation(light.transform.translation()); for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() { let depth_texture_view = @@ -1042,7 +1042,7 @@ pub fn prepare_lights( .enumerate() { let spot_view_matrix = spot_light_view_matrix(&light.transform); - let spot_view_transform = GlobalTransform::from_matrix(spot_view_matrix); + let spot_view_transform = spot_view_matrix.into(); let angle = light.spot_light_angles.expect("lights should be sorted so that \ [point_light_shadow_maps_count..point_light_shadow_maps_count + spot_light_shadow_maps_count] are spot lights").1; @@ -1152,7 +1152,7 @@ pub fn prepare_lights( ExtractedView { width: directional_light_shadow_map.size as u32, height: directional_light_shadow_map.size as u32, - transform: GlobalTransform::from_matrix(view.inverse()), + transform: GlobalTransform::from(view.inverse()), projection, }, RenderPhase::::default(), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 0e33b5caaabd7..0bb456bcaa5fa 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -189,7 +189,7 @@ impl SkinnedMeshJoints { let start = buffer.len(); for (inverse_bindpose, joint) in bindposes.zip(skin_joints).take(MAX_JOINTS) { if let Ok(joint) = joints.get(*joint) { - buffer.push(joint.compute_affine() * *inverse_bindpose); + buffer.push(joint.affine() * *inverse_bindpose); } else { buffer.truncate(start); return None; diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index ade4be0e10f4b..71e3597e3127b 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -162,7 +162,7 @@ fn prepare_view_uniforms( inverse_view, projection, inverse_projection, - world_position: camera.transform.translation, + world_position: camera.transform.translation(), width: camera.width as f32, height: camera.height as f32, }), diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 771151630fbae..a45985e35440e 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,6 +1,5 @@ mod render_layers; -use bevy_math::Vec3A; pub use render_layers::*; use bevy_app::{CoreStage, Plugin}; @@ -205,7 +204,7 @@ pub fn update_frusta( projection.get_projection_matrix() * transform.compute_matrix().inverse(); *frustum = Frustum::from_view_projection( &view_projection, - &transform.translation, + &transform.translation(), &transform.back(), projection.far(), ); @@ -324,7 +323,7 @@ pub fn check_visibility( let model = transform.compute_matrix(); let model_sphere = Sphere { center: model.transform_point3a(model_aabb.center), - radius: (Vec3A::from(transform.scale) * model_aabb.half_extents).length(), + radius: transform.radius_vec3a(model_aabb.half_extents), }; // Do quick sphere-based frustum culling if !frustum.intersects_sphere(&model_sphere, false) { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index bd95a06d8e637..fb07f73069447 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -410,9 +410,9 @@ pub fn queue_sprites( extracted_sprites.sort_unstable_by(|a, b| { match a .transform - .translation + .translation() .z - .partial_cmp(&b.transform.translation.z) + .partial_cmp(&b.transform.translation().z) { Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), Some(other) => other, @@ -517,7 +517,7 @@ pub fn queue_sprites( }); // These items will be sorted by depth with other phase items - let sort_key = FloatOrd(extracted_sprite.transform.translation.z); + let sort_key = FloatOrd(extracted_sprite.transform.translation().z); // Store the vertex data and add the item to the render phase if current_batch.colored { diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 9209fd8d32a3e..20b17fcfb493b 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -82,7 +82,8 @@ pub fn extract_text2d_sprite( ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, computed_visibility, text, transform, calculated_size) in text2d_query.iter() { + for (entity, computed_visibility, text, text_transform, calculated_size) in text2d_query.iter() + { if !computed_visibility.is_visible() { continue; } @@ -100,9 +101,6 @@ pub fn extract_text2d_sprite( HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0), }; - let mut text_transform = *transform; - text_transform.scale /= scale_factor; - for text_glyph in text_glyphs { let color = text.sections[text_glyph.section_index] .style @@ -118,8 +116,10 @@ pub fn extract_text2d_sprite( let glyph_transform = Transform::from_translation( alignment_offset * scale_factor + text_glyph.position.extend(0.), ); - - let transform = text_transform.mul_transform(glyph_transform); + // NOTE: Should match `bevy_ui::render::extract_text_uinodes` + let transform = *text_transform + * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) + * glyph_transform; extracted_sprites.sprites.push(ExtractedSprite { entity, diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 1e8aec21fd612..96e7c24637e7e 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -1,8 +1,9 @@ +use std::ops::Mul; + use super::Transform; use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_math::{Affine3A, Mat3, Mat4, Quat, Vec3}; -use bevy_reflect::prelude::*; -use std::ops::Mul; +use bevy_math::{Affine3A, Mat4, Quat, Vec3, Vec3A}; +use bevy_reflect::Reflect; /// Describe the position of an entity relative to the reference frame. /// @@ -25,223 +26,129 @@ use std::ops::Mul; /// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. #[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)] -#[reflect(Component, Default, PartialEq)] -pub struct GlobalTransform { - /// The position of the global transform - pub translation: Vec3, - /// The rotation of the global transform - pub rotation: Quat, - /// The scale of the global transform - pub scale: Vec3, -} - -impl GlobalTransform { - #[doc(hidden)] - #[inline] - pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self { - Self::from_translation(Vec3::new(x, y, z)) - } - - /// Creates a new identity [`GlobalTransform`], with no translation, rotation, and a scale of 1 - /// on all axes. - #[inline] - pub const fn identity() -> Self { - GlobalTransform { - translation: Vec3::ZERO, - rotation: Quat::IDENTITY, - scale: Vec3::ONE, +#[reflect(Component, PartialEq)] +pub struct GlobalTransform(Affine3A); + +macro_rules! impl_local_axis { + ($pos_name: ident, $neg_name: ident, $axis: ident) => { + #[doc=std::concat!("Return the local ", std::stringify!($pos_name), " vector (", std::stringify!($axis) ,").")] + #[inline] + pub fn $pos_name(&self) -> Vec3 { + (self.0.matrix3 * Vec3::$axis).normalize() } - } - - #[doc(hidden)] - #[inline] - pub fn from_matrix(matrix: Mat4) -> Self { - let (scale, rotation, translation) = matrix.to_scale_rotation_translation(); - GlobalTransform { - translation, - rotation, - scale, + #[doc=std::concat!("Return the local ", std::stringify!($neg_name), " vector (-", std::stringify!($axis) ,").")] + #[inline] + pub fn $neg_name(&self) -> Vec3 { + -self.$pos_name() } - } - - #[doc(hidden)] - #[inline] - pub const fn from_translation(translation: Vec3) -> Self { - GlobalTransform { - translation, - ..Self::identity() - } - } - - #[doc(hidden)] - #[inline] - pub const fn from_rotation(rotation: Quat) -> Self { - GlobalTransform { - rotation, - ..Self::identity() - } - } - - #[doc(hidden)] - #[inline] - pub const fn from_scale(scale: Vec3) -> Self { - GlobalTransform { - scale, - ..Self::identity() - } - } + }; +} +impl GlobalTransform { #[doc(hidden)] #[inline] - #[must_use] - pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { - self.look_at(target, up); - self + pub fn from_xyz(x: f32, y: f32, z: f32) -> Self { + Self::from_translation(Vec3::new(x, y, z)) } #[doc(hidden)] #[inline] - #[must_use] - pub const fn with_translation(mut self, translation: Vec3) -> Self { - self.translation = translation; - self + pub fn from_translation(translation: Vec3) -> Self { + GlobalTransform(Affine3A::from_translation(translation)) } #[doc(hidden)] #[inline] - #[must_use] - pub const fn with_rotation(mut self, rotation: Quat) -> Self { - self.rotation = rotation; - self + pub fn from_rotation(rotation: Quat) -> Self { + GlobalTransform(Affine3A::from_rotation_translation(rotation, Vec3::ZERO)) } #[doc(hidden)] #[inline] - #[must_use] - pub const fn with_scale(mut self, scale: Vec3) -> Self { - self.scale = scale; - self + pub fn from_scale(scale: Vec3) -> Self { + GlobalTransform(Affine3A::from_scale(scale)) } - /// Returns the 3d affine transformation matrix from this transforms translation, - /// rotation, and scale. + /// Returns the 3d affine transformation matrix as a [`Mat4`]. #[inline] pub fn compute_matrix(&self) -> Mat4 { - Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) - } - - /// Returns the 3d affine transformation from this transforms translation, - /// rotation, and scale. - #[inline] - pub fn compute_affine(&self) -> Affine3A { - Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + Mat4::from(self.0) } - /// Get the unit vector in the local `X` direction + /// Returns the 3d affine transformation matrix as an [`Affine3A`]. #[inline] - pub fn local_x(&self) -> Vec3 { - self.rotation * Vec3::X + pub fn affine(&self) -> Affine3A { + self.0 } - /// Equivalent to [`-local_x()`][GlobalTransform::local_x] + /// Returns the transformation as a [`Transform`]. + /// + /// The transform is expected to be non-degenerate and without shearing, or the output + /// will be invalid. #[inline] - pub fn left(&self) -> Vec3 { - -self.local_x() - } - - /// Equivalent to [`local_x()`][GlobalTransform::local_x] - #[inline] - pub fn right(&self) -> Vec3 { - self.local_x() + pub fn compute_transform(&self) -> Transform { + let (scale, rotation, translation) = self.0.to_scale_rotation_translation(); + Transform { + translation, + rotation, + scale, + } } - /// Get the unit vector in the local `Y` direction + /// Extracts `scale`, `rotation` and `translation` from `self`. + /// + /// The transform is expected to be non-degenerate and without shearing, or the output + /// will be invalid. #[inline] - pub fn local_y(&self) -> Vec3 { - self.rotation * Vec3::Y + pub fn to_scale_rotation_translation(&self) -> (Vec3, Quat, Vec3) { + self.0.to_scale_rotation_translation() } - /// Equivalent to [`local_y()`][GlobalTransform::local_y] + /// Creates a new identity [`GlobalTransform`], that maps all points in space to themselves. #[inline] - pub fn up(&self) -> Vec3 { - self.local_y() + pub const fn identity() -> Self { + Self(Affine3A::IDENTITY) } - /// Equivalent to [`-local_y()`][GlobalTransform::local_y] - #[inline] - pub fn down(&self) -> Vec3 { - -self.local_y() - } + impl_local_axis!(right, left, X); + impl_local_axis!(up, down, Y); + impl_local_axis!(back, forward, Z); - /// Get the unit vector in the local `Z` direction + /// Get the translation as a [`Vec3`]. #[inline] - pub fn local_z(&self) -> Vec3 { - self.rotation * Vec3::Z + pub fn translation(&self) -> Vec3 { + self.0.translation.into() } - /// Equivalent to [`-local_z()`][GlobalTransform::local_z] + /// Mutably access the internal translation. #[inline] - pub fn forward(&self) -> Vec3 { - -self.local_z() + pub fn translation_mut(&mut self) -> &mut Vec3A { + &mut self.0.translation } - /// Equivalent to [`local_z()`][GlobalTransform::local_z] + /// Get the translation as a [`Vec3A`]. #[inline] - pub fn back(&self) -> Vec3 { - self.local_z() + pub fn translation_vec3a(&self) -> Vec3A { + self.0.translation } - #[doc(hidden)] + /// Get an upper bound of the radius from the given `extents`. #[inline] - pub fn rotate(&mut self, rotation: Quat) { - self.rotation = rotation * self.rotation; + pub fn radius_vec3a(&self, extents: Vec3A) -> f32 { + (self.0.matrix3 * extents).length() } - #[doc(hidden)] + /// Returns a [`Vec3`] of this [`Transform`] applied to `value`. #[inline] - pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) { - self.translation = point + rotation * (self.translation - point); - self.rotate(rotation); + pub fn mul_vec3(&self, v: Vec3) -> Vec3 { + self.0.transform_point3(v) } /// Multiplies `self` with `transform` component by component, returning the /// resulting [`GlobalTransform`] - #[inline] - #[must_use] pub fn mul_transform(&self, transform: Transform) -> Self { - let translation = self.mul_vec3(transform.translation); - let rotation = self.rotation * transform.rotation; - let scale = self.scale * transform.scale; - Self { - translation, - rotation, - scale, - } - } - - /// Returns a [`Vec3`] of this [`Transform`] applied to `value`. - #[inline] - pub fn mul_vec3(&self, mut value: Vec3) -> Vec3 { - value = self.scale * value; - value = self.rotation * value; - value += self.translation; - value - } - - #[doc(hidden)] - #[inline] - pub fn apply_non_uniform_scale(&mut self, scale: Vec3) { - self.scale *= scale; - } - - #[doc(hidden)] - #[inline] - pub fn look_at(&mut self, target: Vec3, up: Vec3) { - let forward = Vec3::normalize(self.translation - target); - let right = up.cross(forward).normalize(); - let up = forward.cross(right); - self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward)); + Self(self.0 * transform.compute_affine()) } } @@ -253,11 +160,19 @@ impl Default for GlobalTransform { impl From for GlobalTransform { fn from(transform: Transform) -> Self { - Self { - translation: transform.translation, - rotation: transform.rotation, - scale: transform.scale, - } + Self(transform.compute_affine()) + } +} + +impl From for GlobalTransform { + fn from(affine: Affine3A) -> Self { + Self(affine) + } +} + +impl From for GlobalTransform { + fn from(matrix: Mat4) -> Self { + Self(Affine3A::from_mat4(matrix)) } } @@ -266,7 +181,7 @@ impl Mul for GlobalTransform { #[inline] fn mul(self, global_transform: GlobalTransform) -> Self::Output { - self.mul_transform(global_transform.into()) + GlobalTransform(self.0 * global_transform.0) } } diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 6fb0ac7bd4efe..667850a1eb9d0 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -1,6 +1,6 @@ use super::GlobalTransform; use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_math::{Mat3, Mat4, Quat, Vec3}; +use bevy_math::{Affine3A, Mat3, Mat4, Quat, Vec3}; use bevy_reflect::prelude::*; use bevy_reflect::Reflect; use std::ops::Mul; @@ -141,6 +141,13 @@ impl Transform { Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) } + /// Returns the 3d affine transformation matrix from this transforms translation, + /// rotation, and scale. + #[inline] + pub fn compute_affine(&self) -> Affine3A { + Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + } + /// Get the unit vector in the local `X` direction. #[inline] pub fn local_x(&self) -> Vec3 { @@ -332,13 +339,11 @@ impl Default for Transform { } } +/// The transform is expected to be non-degenerate and without shearing, or the output +/// will be invalid. impl From for Transform { fn from(transform: GlobalTransform) -> Self { - Self { - translation: transform.translation, - rotation: transform.rotation, - scale: transform.scale, - } + transform.compute_transform() } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 5ca16e7db1ad6..9406149fb0847 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -320,13 +320,7 @@ mod test { let mut state = app.world.query::<&GlobalTransform>(); for global in state.iter(&app.world) { - assert_eq!( - global, - &GlobalTransform { - translation, - ..Default::default() - }, - ); + assert_eq!(global, &GlobalTransform::from_translation(translation)); } } diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 11a7290bf0e0d..57a1f7f14a29f 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -112,7 +112,7 @@ pub fn ui_focus_system( .iter_mut() .filter_map( |(entity, node, global_transform, interaction, focus_policy, clip)| { - let position = global_transform.translation; + let position = global_transform.translation(); let ui_position = position.truncate(); let extents = node.size / 2.0; let mut min = ui_position - extents; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 08b25a42f08ba..e26d91ae23ac4 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -283,7 +283,7 @@ pub fn extract_text_uinodes( >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() { + for (entity, uinode, global_transform, text, visibility, clip) in uinode_query.iter() { if !visibility.is_visible() { continue; } @@ -305,15 +305,15 @@ pub fn extract_text_uinodes( let rect = atlas.textures[index]; let atlas_size = Some(atlas.size); - let transform = - Mat4::from_rotation_translation(transform.rotation, transform.translation) - * Mat4::from_scale(transform.scale / scale_factor) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); extracted_uinodes.uinodes.push(ExtractedUiNode { - transform, + transform: extracted_transform, color, rect, image: texture, diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index dbdef551c2da6..05628555f1980 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -108,7 +108,7 @@ fn update_clipping( let children_clip = match style.overflow { Overflow::Visible => clip, Overflow::Hidden => { - let node_center = global_transform.translation.truncate(); + let node_center = global_transform.translation().truncate(); let node_rect = Rect { min: node_center - node.size / 2., max: node_center + node.size / 2., diff --git a/examples/ecs/iter_combinations.rs b/examples/ecs/iter_combinations.rs index 73bc15519bbba..b66f9148b33a1 100644 --- a/examples/ecs/iter_combinations.rs +++ b/examples/ecs/iter_combinations.rs @@ -152,7 +152,7 @@ fn interact_bodies(mut query: Query<(&Mass, &GlobalTransform, &mut Acceleration) while let Some([(Mass(m1), transform1, mut acc1), (Mass(m2), transform2, mut acc2)]) = iter.fetch_next() { - let delta = transform2.translation - transform1.translation; + let delta = transform2.translation() - transform1.translation(); let distance_sq: f32 = delta.length_squared(); let f = GRAVITY_CONSTANT / distance_sq; diff --git a/examples/tools/scene_viewer.rs b/examples/tools/scene_viewer.rs index d62ca5ec1614e..0bb91b3573712 100644 --- a/examples/tools/scene_viewer.rs +++ b/examples/tools/scene_viewer.rs @@ -226,7 +226,7 @@ fn setup_scene_after_load( // a Sphere, and then back to an Aabb to find the conservative min and max points. let sphere = Sphere { center: Vec3A::from(transform.mul_vec3(Vec3::from(aabb.center))), - radius: (Vec3A::from(transform.scale) * aabb.half_extents).length(), + radius: transform.radius_vec3a(aabb.half_extents), }; let aabb = Aabb::from(sphere); min = min.min(aabb.min()); diff --git a/examples/transforms/global_vs_local_translation.rs b/examples/transforms/global_vs_local_translation.rs index 67377b4e67d5f..815cdab22f465 100644 --- a/examples/transforms/global_vs_local_translation.rs +++ b/examples/transforms/global_vs_local_translation.rs @@ -1,7 +1,7 @@ //! Illustrates the difference between direction of a translation in respect to local object or //! global object Transform. -use bevy::prelude::*; +use bevy::{math::Vec3A, prelude::*}; // Define a marker for entities that should be changed via their global transform. #[derive(Component)] @@ -129,7 +129,7 @@ fn move_cubes_according_to_global_transform( timer: Res