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 <mcanders1@gmail.com>
This commit is contained in:
parent
8810a73e87
commit
9f8bdeeeb9
@ -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<f32>,
|
||||
}
|
||||
|
||||
impl PointLightAssignmentData {
|
||||
pub fn sphere(&self) -> Sphere {
|
||||
Sphere {
|
||||
center: self.transform.translation_vec3a(),
|
||||
radius: self.range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GlobalVisiblePointLights {
|
||||
entities: HashSet<Entity>,
|
||||
@ -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<Entity>| {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -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::<Vec<_>>();
|
||||
|
||||
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::<Shadow>::default(),
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
}),
|
||||
|
@ -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<T: Component + CameraProjection + Send + Sync + 'static>(
|
||||
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) {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
#[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=std::concat!("Return the local ", std::stringify!($neg_name), " vector (-", std::stringify!($axis) ,").")]
|
||||
#[inline]
|
||||
pub fn $neg_name(&self) -> Vec3 {
|
||||
-self.$pos_name()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl GlobalTransform {
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self {
|
||||
pub 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.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn identity() -> Self {
|
||||
GlobalTransform {
|
||||
translation: Vec3::ZERO,
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::ONE,
|
||||
}
|
||||
pub fn from_translation(translation: Vec3) -> Self {
|
||||
GlobalTransform(Affine3A::from_translation(translation))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn from_matrix(matrix: Mat4) -> Self {
|
||||
let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
|
||||
|
||||
GlobalTransform {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
}
|
||||
pub fn from_rotation(rotation: Quat) -> Self {
|
||||
GlobalTransform(Affine3A::from_rotation_translation(rotation, Vec3::ZERO))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub const fn from_translation(translation: Vec3) -> Self {
|
||||
GlobalTransform {
|
||||
translation,
|
||||
..Self::identity()
|
||||
}
|
||||
pub fn from_scale(scale: Vec3) -> Self {
|
||||
GlobalTransform(Affine3A::from_scale(scale))
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self {
|
||||
self.look_at(target, up);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_translation(mut self, translation: Vec3) -> Self {
|
||||
self.translation = translation;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_rotation(mut self, rotation: Quat) -> Self {
|
||||
self.rotation = rotation;
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_scale(mut self, scale: Vec3) -> Self {
|
||||
self.scale = scale;
|
||||
self
|
||||
}
|
||||
|
||||
/// 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)
|
||||
Mat4::from(self.0)
|
||||
}
|
||||
|
||||
/// Returns the 3d affine transformation from this transforms translation,
|
||||
/// rotation, and scale.
|
||||
/// Returns the 3d affine transformation matrix as an [`Affine3A`].
|
||||
#[inline]
|
||||
pub fn compute_affine(&self) -> Affine3A {
|
||||
Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
|
||||
pub fn affine(&self) -> Affine3A {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Get the unit vector in the local `X` direction
|
||||
/// 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 local_x(&self) -> Vec3 {
|
||||
self.rotation * Vec3::X
|
||||
}
|
||||
|
||||
/// Equivalent to [`-local_x()`][GlobalTransform::local_x]
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// Get the unit vector in the local `Y` direction
|
||||
#[inline]
|
||||
pub fn local_y(&self) -> Vec3 {
|
||||
self.rotation * Vec3::Y
|
||||
}
|
||||
|
||||
/// Equivalent to [`local_y()`][GlobalTransform::local_y]
|
||||
#[inline]
|
||||
pub fn up(&self) -> Vec3 {
|
||||
self.local_y()
|
||||
}
|
||||
|
||||
/// Equivalent to [`-local_y()`][GlobalTransform::local_y]
|
||||
#[inline]
|
||||
pub fn down(&self) -> Vec3 {
|
||||
-self.local_y()
|
||||
}
|
||||
|
||||
/// Get the unit vector in the local `Z` direction
|
||||
#[inline]
|
||||
pub fn local_z(&self) -> Vec3 {
|
||||
self.rotation * Vec3::Z
|
||||
}
|
||||
|
||||
/// Equivalent to [`-local_z()`][GlobalTransform::local_z]
|
||||
#[inline]
|
||||
pub fn forward(&self) -> Vec3 {
|
||||
-self.local_z()
|
||||
}
|
||||
|
||||
/// Equivalent to [`local_z()`][GlobalTransform::local_z]
|
||||
#[inline]
|
||||
pub fn back(&self) -> Vec3 {
|
||||
self.local_z()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, rotation: Quat) {
|
||||
self.rotation = rotation * self.rotation;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
|
||||
self.translation = point + rotation * (self.translation - point);
|
||||
self.rotate(rotation);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
pub fn compute_transform(&self) -> Transform {
|
||||
let (scale, rotation, translation) = self.0.to_scale_rotation_translation();
|
||||
Transform {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 to_scale_rotation_translation(&self) -> (Vec3, Quat, Vec3) {
|
||||
self.0.to_scale_rotation_translation()
|
||||
}
|
||||
|
||||
/// Creates a new identity [`GlobalTransform`], that maps all points in space to themselves.
|
||||
#[inline]
|
||||
pub const fn identity() -> Self {
|
||||
Self(Affine3A::IDENTITY)
|
||||
}
|
||||
|
||||
impl_local_axis!(right, left, X);
|
||||
impl_local_axis!(up, down, Y);
|
||||
impl_local_axis!(back, forward, Z);
|
||||
|
||||
/// Get the translation as a [`Vec3`].
|
||||
#[inline]
|
||||
pub fn translation(&self) -> Vec3 {
|
||||
self.0.translation.into()
|
||||
}
|
||||
|
||||
/// Mutably access the internal translation.
|
||||
#[inline]
|
||||
pub fn translation_mut(&mut self) -> &mut Vec3A {
|
||||
&mut self.0.translation
|
||||
}
|
||||
|
||||
/// Get the translation as a [`Vec3A`].
|
||||
#[inline]
|
||||
pub fn translation_vec3a(&self) -> Vec3A {
|
||||
self.0.translation
|
||||
}
|
||||
|
||||
/// Get an upper bound of the radius from the given `extents`.
|
||||
#[inline]
|
||||
pub fn radius_vec3a(&self, extents: Vec3A) -> f32 {
|
||||
(self.0.matrix3 * extents).length()
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn mul_vec3(&self, v: Vec3) -> Vec3 {
|
||||
self.0.transform_point3(v)
|
||||
}
|
||||
|
||||
#[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));
|
||||
/// Multiplies `self` with `transform` component by component, returning the
|
||||
/// resulting [`GlobalTransform`]
|
||||
pub fn mul_transform(&self, transform: Transform) -> Self {
|
||||
Self(self.0 * transform.compute_affine())
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,11 +160,19 @@ impl Default for GlobalTransform {
|
||||
|
||||
impl From<Transform> for GlobalTransform {
|
||||
fn from(transform: Transform) -> Self {
|
||||
Self {
|
||||
translation: transform.translation,
|
||||
rotation: transform.rotation,
|
||||
scale: transform.scale,
|
||||
}
|
||||
Self(transform.compute_affine())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Affine3A> for GlobalTransform {
|
||||
fn from(affine: Affine3A) -> Self {
|
||||
Self(affine)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mat4> for GlobalTransform {
|
||||
fn from(matrix: Mat4) -> Self {
|
||||
Self(Affine3A::from_mat4(matrix))
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +181,7 @@ impl Mul<GlobalTransform> for GlobalTransform {
|
||||
|
||||
#[inline]
|
||||
fn mul(self, global_transform: GlobalTransform) -> Self::Output {
|
||||
self.mul_transform(global_transform.into())
|
||||
GlobalTransform(self.0 * global_transform.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<GlobalTransform> for Transform {
|
||||
fn from(transform: GlobalTransform) -> Self {
|
||||
Self {
|
||||
translation: transform.translation,
|
||||
rotation: transform.rotation,
|
||||
scale: transform.scale,
|
||||
}
|
||||
transform.compute_transform()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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.,
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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<Time>,
|
||||
) {
|
||||
for mut global_transform in &mut cubes {
|
||||
global_transform.translation += direction.0 * timer.delta_seconds();
|
||||
*global_transform.translation_mut() += Vec3A::from(direction.0) * timer.delta_seconds();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user