Skip to content

Commit

Permalink
Use Affine3A for GlobalTransform to allow any affine transformation (b…
Browse files Browse the repository at this point in the history
…evyengine#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 bevyengine#1755 and bevyengine#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 <[email protected]>
  • Loading branch information
2 people authored and james7132 committed Oct 28, 2022
1 parent 759f1d8 commit 6cb0d7d
Show file tree
Hide file tree
Showing 16 changed files with 160 additions and 252 deletions.
59 changes: 27 additions & 32 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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::{
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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(),
);
Expand All @@ -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 {
Expand All @@ -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()) {
Expand All @@ -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,
);
Expand All @@ -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);
Expand All @@ -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,
);
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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,
};

Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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);
Expand All @@ -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),
)
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
mod render_layers;

use bevy_math::Vec3A;
pub use render_layers::*;

use bevy_app::{CoreStage, Plugin};
Expand Down Expand Up @@ -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(),
);
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 6cb0d7d

Please sign in to comment.