Skip to content

Commit

Permalink
Refactor and simplify custom projections (#17063)
Browse files Browse the repository at this point in the history
# Objective

- Fixes #16556
- Closes #11807

## Solution

- Simplify custom projections by using a single source of truth -
`Projection`, removing all existing generic systems and types.
- Existing perspective and orthographic structs are no longer components
- I could dissolve these to simplify further, but keeping them around
was the fast way to implement this.
- Instead of generics, introduce a third variant, with a trait object.
- Do an object safety dance with an intermediate trait to allow cloning
boxed camera projections. This is a normal rust polymorphism papercut.
You can do this with a crate but a manual impl is short and sweet.

## Testing

- Added a custom projection example

---

## Showcase

- Custom projections and projection handling has been simplified.
- Projection systems are no longer generic, with the potential for many
different projection components on the same camera.
- Instead `Projection` is now the single source of truth for camera
projections, and is the only projection component.
- Custom projections are still supported, and can be constructed with
`Projection::custom()`.

## Migration Guide

- `PerspectiveProjection` and `OrthographicProjection` are no longer
components. Use `Projection` instead.
- Custom projections should no longer be inserted as a component.
Instead, simply set the custom projection as a value of `Projection`
with `Projection::custom()`.
  • Loading branch information
aevyrie authored Jan 1, 2025
1 parent 294e0db commit bed9ddf
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 125 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3571,6 +3571,17 @@ description = "A 2D top-down camera smoothly following player movements"
category = "Camera"
wasm = true

[[example]]
name = "custom_projection"
path = "examples/camera/custom_projection.rs"
doc-scrape-examples = true

[package.metadata.example.custom_projection]
name = "Custom Projection"
description = "Shows how to create custom camera projections."
category = "Camera"
wasm = true

[[example]]
name = "first_person_view_model"
path = "examples/camera/first_person_view_model.rs"
Expand Down
46 changes: 28 additions & 18 deletions crates/bevy_animation/src/animation_curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,27 @@ use downcast_rs::{impl_downcast, Downcast};
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
/// # use bevy_reflect::Reflect;
/// # use std::any::TypeId;
/// # use bevy_render::camera::PerspectiveProjection;
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
/// #[derive(Reflect)]
/// struct FieldOfViewProperty;
///
/// impl AnimatableProperty for FieldOfViewProperty {
/// type Property = f32;
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// let component = entity
/// .get_mut::<PerspectiveProjection>()
/// .ok_or(
/// AnimationEvaluationError::ComponentNotPresent(
/// TypeId::of::<PerspectiveProjection>()
/// )
/// )?
/// let component = entity
/// .get_mut::<Projection>()
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
/// Projection,
/// >(
/// )))?
/// .into_inner();
/// Ok(&mut component.fov)
/// match component {
/// Projection::Perspective(perspective) => Ok(&mut perspective.fov),
/// _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
/// PerspectiveProjection,
/// >(
/// ))),
/// }
/// }
///
/// fn evaluator_id(&self) -> EvaluatorId {
Expand All @@ -146,23 +151,28 @@ use downcast_rs::{impl_downcast, Downcast};
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
/// # use bevy_ecs::name::Name;
/// # use bevy_reflect::Reflect;
/// # use bevy_render::camera::PerspectiveProjection;
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
/// # use std::any::TypeId;
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
/// # #[derive(Reflect, Clone)]
/// # struct FieldOfViewProperty;
/// # impl AnimatableProperty for FieldOfViewProperty {
/// # type Property = f32;
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
/// # let component = entity
/// # .get_mut::<PerspectiveProjection>()
/// # .ok_or(
/// # AnimationEvaluationError::ComponentNotPresent(
/// # TypeId::of::<PerspectiveProjection>()
/// # )
/// # )?
/// # let component = entity
/// # .get_mut::<Projection>()
/// # .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
/// # Projection,
/// # >(
/// # )))?
/// # .into_inner();
/// # Ok(&mut component.fov)
/// # match component {
/// # Projection::Perspective(perspective) => Ok(&mut perspective.fov),
/// # _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
/// # PerspectiveProjection,
/// # >(
/// # ))),
/// # }
/// # }
/// # fn evaluator_id(&self) -> EvaluatorId {
/// # EvaluatorId::Type(TypeId::of::<Self>())
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bevy_render::sync_world::SyncToRenderWorld;
use bevy_render::{
camera::{
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
OrthographicProjection,
OrthographicProjection, Projection,
},
extract_component::ExtractComponent,
prelude::Msaa,
Expand All @@ -27,7 +27,7 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
Camera,
DebandDither,
CameraRenderGraph(|| CameraRenderGraph::new(Core2d)),
OrthographicProjection(OrthographicProjection::default_2d),
Projection(|| Projection::Orthographic(OrthographicProjection::default_2d())),
Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))),
Tonemapping(|| Tonemapping::None),
)]
Expand All @@ -41,7 +41,7 @@ pub struct Camera2d;
pub struct Camera2dBundle {
pub camera: Camera,
pub camera_render_graph: CameraRenderGraph,
pub projection: OrthographicProjection,
pub projection: Projection,
pub visible_entities: VisibleEntities,
pub frustum: Frustum,
pub transform: Transform,
Expand All @@ -57,7 +57,7 @@ pub struct Camera2dBundle {

impl Default for Camera2dBundle {
fn default() -> Self {
let projection = OrthographicProjection::default_2d();
let projection = Projection::Orthographic(OrthographicProjection::default_2d());
let transform = Transform::default();
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
Self {
Expand Down Expand Up @@ -88,10 +88,10 @@ impl Camera2dBundle {
pub fn new_with_far(far: f32) -> Self {
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
// the camera's translation by far and use a right handed coordinate system
let projection = OrthographicProjection {
let projection = Projection::Orthographic(OrthographicProjection {
far,
..OrthographicProjection::default_2d()
};
});
let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
Self {
Expand Down
24 changes: 7 additions & 17 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ mod volumetric_fog;
use crate::material_bind_groups::FallbackBindlessResources;

use bevy_color::{Color, LinearRgba};
use core::marker::PhantomData;

pub use bundle::*;
pub use cluster::*;
Expand Down Expand Up @@ -121,10 +120,7 @@ use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_render::{
alpha::AlphaMode,
camera::{
CameraProjection, CameraUpdateSystem, OrthographicProjection, PerspectiveProjection,
Projection,
},
camera::{CameraUpdateSystem, Projection},
extract_component::ExtractComponentPlugin,
extract_resource::ExtractResourcePlugin,
render_asset::prepare_assets,
Expand Down Expand Up @@ -341,9 +337,7 @@ impl Plugin for PbrPlugin {
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
LightmapPlugin,
LightProbePlugin,
PbrProjectionPlugin::<Projection>::default(),
PbrProjectionPlugin::<PerspectiveProjection>::default(),
PbrProjectionPlugin::<OrthographicProjection>::default(),
PbrProjectionPlugin,
GpuMeshPreprocessPlugin {
use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
},
Expand Down Expand Up @@ -480,20 +474,16 @@ impl Plugin for PbrPlugin {
}
}

/// [`CameraProjection`] specific PBR functionality.
pub struct PbrProjectionPlugin<T: CameraProjection + Component>(PhantomData<T>);
impl<T: CameraProjection + Component> Plugin for PbrProjectionPlugin<T> {
/// Camera projection PBR functionality.
#[derive(Default)]
pub struct PbrProjectionPlugin;
impl Plugin for PbrProjectionPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
build_directional_light_cascades::<T>
build_directional_light_cascades
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(clear_directional_light_cascades),
);
}
}
impl<T: CameraProjection + Component> Default for PbrProjectionPlugin<T> {
fn default() -> Self {
Self(Default::default())
}
}
6 changes: 3 additions & 3 deletions crates/bevy_pbr/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bevy_ecs::{
use bevy_math::{ops, Mat4, Vec3A, Vec4};
use bevy_reflect::prelude::*;
use bevy_render::{
camera::{Camera, CameraProjection},
camera::{Camera, CameraProjection, Projection},
extract_component::ExtractComponent,
extract_resource::ExtractResource,
mesh::Mesh3d,
Expand Down Expand Up @@ -305,9 +305,9 @@ pub fn clear_directional_light_cascades(mut lights: Query<(&DirectionalLight, &m
}
}

pub fn build_directional_light_cascades<P: CameraProjection + Component>(
pub fn build_directional_light_cascades(
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
views: Query<(Entity, &GlobalTransform, &P, &Camera)>,
views: Query<(Entity, &GlobalTransform, &Projection, &Camera)>,
mut lights: Query<(
&GlobalTransform,
&DirectionalLight,
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ pub fn queue_material_meshes<M: Material>(
view_key |= match projection {
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
};
}

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
view_key |= match projection {
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
};
}

Expand Down
14 changes: 4 additions & 10 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChanges,
component::{Component, ComponentId, Mutable},
component::{Component, ComponentId},
entity::{Entity, EntityBorrow},
event::EventReader,
prelude::{require, With},
Expand Down Expand Up @@ -883,13 +883,7 @@ impl NormalizedRenderTarget {
/// System in charge of updating a [`Camera`] when its window or projection changes.
///
/// The system detects window creation, resize, and scale factor change events to update the camera
/// projection if needed. It also queries any [`CameraProjection`] component associated with the same
/// entity as the [`Camera`] one, to automatically update the camera projection matrix.
///
/// The system function is generic over the camera projection type, and only instances of
/// [`OrthographicProjection`] and [`PerspectiveProjection`] are automatically added to
/// the app, as well as the runtime-selected [`Projection`].
/// The system runs during [`PostUpdate`](bevy_app::PostUpdate).
/// [`Projection`] if needed.
///
/// ## World Resources
///
Expand All @@ -899,7 +893,7 @@ impl NormalizedRenderTarget {
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
#[allow(clippy::too_many_arguments)]
pub fn camera_system<T: CameraProjection + Component<Mutability = Mutable>>(
pub fn camera_system(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
Expand All @@ -908,7 +902,7 @@ pub fn camera_system<T: CameraProjection + Component<Mutability = Mutable>>(
windows: Query<(Entity, &Window)>,
images: Res<Assets<Image>>,
manual_texture_views: Res<ManualTextureViews>,
mut cameras: Query<(&mut Camera, &mut T)>,
mut cameras: Query<(&mut Camera, &mut Projection)>,
) {
let primary_window = primary_window.iter().next();

Expand Down
4 changes: 1 addition & 3 deletions crates/bevy_render/src/camera/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ impl Plugin for CameraPlugin {
.init_resource::<ManualTextureViews>()
.init_resource::<ClearColor>()
.add_plugins((
CameraProjectionPlugin::<Projection>::default(),
CameraProjectionPlugin::<OrthographicProjection>::default(),
CameraProjectionPlugin::<PerspectiveProjection>::default(),
CameraProjectionPlugin,
ExtractResourcePlugin::<ManualTextureViews>::default(),
ExtractResourcePlugin::<ClearColor>::default(),
ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
Expand Down
Loading

0 comments on commit bed9ddf

Please sign in to comment.