Refactor and simplify custom projections (#17063)
# Objective - Fixes https://github.com/bevyengine/bevy/issues/16556 - Closes https://github.com/bevyengine/bevy/issues/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()`.
This commit is contained in:
parent
294e0db719
commit
bed9ddf3ce
11
Cargo.toml
11
Cargo.toml
@ -3571,6 +3571,17 @@ description = "A 2D top-down camera smoothly following player movements"
|
|||||||
category = "Camera"
|
category = "Camera"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "first_person_view_model"
|
name = "first_person_view_model"
|
||||||
path = "examples/camera/first_person_view_model.rs"
|
path = "examples/camera/first_person_view_model.rs"
|
||||||
|
@ -117,22 +117,27 @@ use downcast_rs::{impl_downcast, Downcast};
|
|||||||
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||||
/// # use bevy_reflect::Reflect;
|
/// # use bevy_reflect::Reflect;
|
||||||
/// # use std::any::TypeId;
|
/// # use std::any::TypeId;
|
||||||
/// # use bevy_render::camera::PerspectiveProjection;
|
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||||
/// #[derive(Reflect)]
|
/// #[derive(Reflect)]
|
||||||
/// struct FieldOfViewProperty;
|
/// struct FieldOfViewProperty;
|
||||||
///
|
///
|
||||||
/// impl AnimatableProperty for FieldOfViewProperty {
|
/// impl AnimatableProperty for FieldOfViewProperty {
|
||||||
/// type Property = f32;
|
/// type Property = f32;
|
||||||
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||||
/// let component = entity
|
/// let component = entity
|
||||||
/// .get_mut::<PerspectiveProjection>()
|
/// .get_mut::<Projection>()
|
||||||
/// .ok_or(
|
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||||
/// AnimationEvaluationError::ComponentNotPresent(
|
/// Projection,
|
||||||
/// TypeId::of::<PerspectiveProjection>()
|
/// >(
|
||||||
/// )
|
/// )))?
|
||||||
/// )?
|
|
||||||
/// .into_inner();
|
/// .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 {
|
/// fn evaluator_id(&self) -> EvaluatorId {
|
||||||
@ -146,7 +151,7 @@ use downcast_rs::{impl_downcast, Downcast};
|
|||||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
||||||
/// # use bevy_ecs::name::Name;
|
/// # use bevy_ecs::name::Name;
|
||||||
/// # use bevy_reflect::Reflect;
|
/// # use bevy_reflect::Reflect;
|
||||||
/// # use bevy_render::camera::PerspectiveProjection;
|
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||||
/// # use std::any::TypeId;
|
/// # use std::any::TypeId;
|
||||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||||
/// # #[derive(Reflect, Clone)]
|
/// # #[derive(Reflect, Clone)]
|
||||||
@ -154,15 +159,20 @@ use downcast_rs::{impl_downcast, Downcast};
|
|||||||
/// # impl AnimatableProperty for FieldOfViewProperty {
|
/// # impl AnimatableProperty for FieldOfViewProperty {
|
||||||
/// # type Property = f32;
|
/// # type Property = f32;
|
||||||
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||||
/// # let component = entity
|
/// # let component = entity
|
||||||
/// # .get_mut::<PerspectiveProjection>()
|
/// # .get_mut::<Projection>()
|
||||||
/// # .ok_or(
|
/// # .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||||
/// # AnimationEvaluationError::ComponentNotPresent(
|
/// # Projection,
|
||||||
/// # TypeId::of::<PerspectiveProjection>()
|
/// # >(
|
||||||
/// # )
|
/// # )))?
|
||||||
/// # )?
|
|
||||||
/// # .into_inner();
|
/// # .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 {
|
/// # fn evaluator_id(&self) -> EvaluatorId {
|
||||||
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
||||||
|
@ -10,7 +10,7 @@ use bevy_render::sync_world::SyncToRenderWorld;
|
|||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{
|
camera::{
|
||||||
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
|
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
|
||||||
OrthographicProjection,
|
OrthographicProjection, Projection,
|
||||||
},
|
},
|
||||||
extract_component::ExtractComponent,
|
extract_component::ExtractComponent,
|
||||||
prelude::Msaa,
|
prelude::Msaa,
|
||||||
@ -27,7 +27,7 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
|
|||||||
Camera,
|
Camera,
|
||||||
DebandDither,
|
DebandDither,
|
||||||
CameraRenderGraph(|| CameraRenderGraph::new(Core2d)),
|
CameraRenderGraph(|| CameraRenderGraph::new(Core2d)),
|
||||||
OrthographicProjection(OrthographicProjection::default_2d),
|
Projection(|| Projection::Orthographic(OrthographicProjection::default_2d())),
|
||||||
Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))),
|
Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))),
|
||||||
Tonemapping(|| Tonemapping::None),
|
Tonemapping(|| Tonemapping::None),
|
||||||
)]
|
)]
|
||||||
@ -41,7 +41,7 @@ pub struct Camera2d;
|
|||||||
pub struct Camera2dBundle {
|
pub struct Camera2dBundle {
|
||||||
pub camera: Camera,
|
pub camera: Camera,
|
||||||
pub camera_render_graph: CameraRenderGraph,
|
pub camera_render_graph: CameraRenderGraph,
|
||||||
pub projection: OrthographicProjection,
|
pub projection: Projection,
|
||||||
pub visible_entities: VisibleEntities,
|
pub visible_entities: VisibleEntities,
|
||||||
pub frustum: Frustum,
|
pub frustum: Frustum,
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
@ -57,7 +57,7 @@ pub struct Camera2dBundle {
|
|||||||
|
|
||||||
impl Default for Camera2dBundle {
|
impl Default for Camera2dBundle {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let projection = OrthographicProjection::default_2d();
|
let projection = Projection::Orthographic(OrthographicProjection::default_2d());
|
||||||
let transform = Transform::default();
|
let transform = Transform::default();
|
||||||
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
||||||
Self {
|
Self {
|
||||||
@ -88,10 +88,10 @@ impl Camera2dBundle {
|
|||||||
pub fn new_with_far(far: f32) -> Self {
|
pub fn new_with_far(far: f32) -> Self {
|
||||||
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
|
// 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
|
// the camera's translation by far and use a right handed coordinate system
|
||||||
let projection = OrthographicProjection {
|
let projection = Projection::Orthographic(OrthographicProjection {
|
||||||
far,
|
far,
|
||||||
..OrthographicProjection::default_2d()
|
..OrthographicProjection::default_2d()
|
||||||
};
|
});
|
||||||
let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
|
let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
|
||||||
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
||||||
Self {
|
Self {
|
||||||
|
@ -46,7 +46,6 @@ mod volumetric_fog;
|
|||||||
use crate::material_bind_groups::FallbackBindlessResources;
|
use crate::material_bind_groups::FallbackBindlessResources;
|
||||||
|
|
||||||
use bevy_color::{Color, LinearRgba};
|
use bevy_color::{Color, LinearRgba};
|
||||||
use core::marker::PhantomData;
|
|
||||||
|
|
||||||
pub use bundle::*;
|
pub use bundle::*;
|
||||||
pub use cluster::*;
|
pub use cluster::*;
|
||||||
@ -121,10 +120,7 @@ use bevy_ecs::prelude::*;
|
|||||||
use bevy_image::Image;
|
use bevy_image::Image;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
alpha::AlphaMode,
|
alpha::AlphaMode,
|
||||||
camera::{
|
camera::{CameraUpdateSystem, Projection},
|
||||||
CameraProjection, CameraUpdateSystem, OrthographicProjection, PerspectiveProjection,
|
|
||||||
Projection,
|
|
||||||
},
|
|
||||||
extract_component::ExtractComponentPlugin,
|
extract_component::ExtractComponentPlugin,
|
||||||
extract_resource::ExtractResourcePlugin,
|
extract_resource::ExtractResourcePlugin,
|
||||||
render_asset::prepare_assets,
|
render_asset::prepare_assets,
|
||||||
@ -341,9 +337,7 @@ impl Plugin for PbrPlugin {
|
|||||||
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
||||||
LightmapPlugin,
|
LightmapPlugin,
|
||||||
LightProbePlugin,
|
LightProbePlugin,
|
||||||
PbrProjectionPlugin::<Projection>::default(),
|
PbrProjectionPlugin,
|
||||||
PbrProjectionPlugin::<PerspectiveProjection>::default(),
|
|
||||||
PbrProjectionPlugin::<OrthographicProjection>::default(),
|
|
||||||
GpuMeshPreprocessPlugin {
|
GpuMeshPreprocessPlugin {
|
||||||
use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
|
use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
|
||||||
},
|
},
|
||||||
@ -480,20 +474,16 @@ impl Plugin for PbrPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`CameraProjection`] specific PBR functionality.
|
/// Camera projection PBR functionality.
|
||||||
pub struct PbrProjectionPlugin<T: CameraProjection + Component>(PhantomData<T>);
|
#[derive(Default)]
|
||||||
impl<T: CameraProjection + Component> Plugin for PbrProjectionPlugin<T> {
|
pub struct PbrProjectionPlugin;
|
||||||
|
impl Plugin for PbrProjectionPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
build_directional_light_cascades::<T>
|
build_directional_light_cascades
|
||||||
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
|
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
|
||||||
.after(clear_directional_light_cascades),
|
.after(clear_directional_light_cascades),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: CameraProjection + Component> Default for PbrProjectionPlugin<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@ use bevy_ecs::{
|
|||||||
use bevy_math::{ops, Mat4, Vec3A, Vec4};
|
use bevy_math::{ops, Mat4, Vec3A, Vec4};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{Camera, CameraProjection},
|
camera::{Camera, CameraProjection, Projection},
|
||||||
extract_component::ExtractComponent,
|
extract_component::ExtractComponent,
|
||||||
extract_resource::ExtractResource,
|
extract_resource::ExtractResource,
|
||||||
mesh::Mesh3d,
|
mesh::Mesh3d,
|
||||||
@ -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>,
|
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
|
||||||
views: Query<(Entity, &GlobalTransform, &P, &Camera)>,
|
views: Query<(Entity, &GlobalTransform, &Projection, &Camera)>,
|
||||||
mut lights: Query<(
|
mut lights: Query<(
|
||||||
&GlobalTransform,
|
&GlobalTransform,
|
||||||
&DirectionalLight,
|
&DirectionalLight,
|
||||||
|
@ -741,6 +741,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
view_key |= match projection {
|
view_key |= match projection {
|
||||||
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
||||||
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
||||||
|
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +114,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
|
|||||||
view_key |= match projection {
|
view_key |= match projection {
|
||||||
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
||||||
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
||||||
|
Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
|||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
component::{Component, ComponentId, Mutable},
|
component::{Component, ComponentId},
|
||||||
entity::{Entity, EntityBorrow},
|
entity::{Entity, EntityBorrow},
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
prelude::{require, With},
|
prelude::{require, With},
|
||||||
@ -883,13 +883,7 @@ impl NormalizedRenderTarget {
|
|||||||
/// System in charge of updating a [`Camera`] when its window or projection changes.
|
/// 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
|
/// 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
|
/// [`Projection`] if needed.
|
||||||
/// 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).
|
|
||||||
///
|
///
|
||||||
/// ## World Resources
|
/// ## World Resources
|
||||||
///
|
///
|
||||||
@ -899,7 +893,7 @@ impl NormalizedRenderTarget {
|
|||||||
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
||||||
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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_resized_events: EventReader<WindowResized>,
|
||||||
mut window_created_events: EventReader<WindowCreated>,
|
mut window_created_events: EventReader<WindowCreated>,
|
||||||
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
||||||
@ -908,7 +902,7 @@ pub fn camera_system<T: CameraProjection + Component<Mutability = Mutable>>(
|
|||||||
windows: Query<(Entity, &Window)>,
|
windows: Query<(Entity, &Window)>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
manual_texture_views: Res<ManualTextureViews>,
|
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();
|
let primary_window = primary_window.iter().next();
|
||||||
|
|
||||||
|
@ -33,9 +33,7 @@ impl Plugin for CameraPlugin {
|
|||||||
.init_resource::<ManualTextureViews>()
|
.init_resource::<ManualTextureViews>()
|
||||||
.init_resource::<ClearColor>()
|
.init_resource::<ClearColor>()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
CameraProjectionPlugin::<Projection>::default(),
|
CameraProjectionPlugin,
|
||||||
CameraProjectionPlugin::<OrthographicProjection>::default(),
|
|
||||||
CameraProjectionPlugin::<PerspectiveProjection>::default(),
|
|
||||||
ExtractResourcePlugin::<ManualTextureViews>::default(),
|
ExtractResourcePlugin::<ManualTextureViews>::default(),
|
||||||
ExtractResourcePlugin::<ClearColor>::default(),
|
ExtractResourcePlugin::<ClearColor>::default(),
|
||||||
ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
|
ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use core::marker::PhantomData;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
use crate::{primitives::Frustum, view::VisibilitySystems};
|
use crate::{primitives::Frustum, view::VisibilitySystems};
|
||||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
||||||
use bevy_ecs::{component::Mutable, prelude::*};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
|
||||||
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
|
|
||||||
};
|
|
||||||
use bevy_transform::{components::GlobalTransform, TransformSystem};
|
use bevy_transform::{components::GlobalTransform, TransformSystem};
|
||||||
use derive_more::derive::From;
|
use derive_more::derive::From;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -14,49 +13,31 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.
|
/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.
|
||||||
///
|
///
|
||||||
/// If you are using `bevy_pbr`, then you need to add `PbrProjectionPlugin` along with this.
|
/// If you are using `bevy_pbr`, then you need to add `PbrProjectionPlugin` along with this.
|
||||||
pub struct CameraProjectionPlugin<T: CameraProjection + Component + GetTypeRegistration>(
|
#[derive(Default)]
|
||||||
PhantomData<T>,
|
pub struct CameraProjectionPlugin;
|
||||||
);
|
|
||||||
impl<T: CameraProjection + Component<Mutability = Mutable> + GetTypeRegistration> Plugin
|
impl Plugin for CameraProjectionPlugin {
|
||||||
for CameraProjectionPlugin<T>
|
|
||||||
{
|
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<T>()
|
app.register_type::<Projection>()
|
||||||
|
.register_type::<PerspectiveProjection>()
|
||||||
|
.register_type::<OrthographicProjection>()
|
||||||
|
.register_type::<CustomProjection>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostStartup,
|
PostStartup,
|
||||||
crate::camera::camera_system::<T>
|
crate::camera::camera_system.in_set(CameraUpdateSystem),
|
||||||
.in_set(CameraUpdateSystem)
|
|
||||||
// We assume that each camera will only have one projection,
|
|
||||||
// so we can ignore ambiguities with all other monomorphizations.
|
|
||||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
|
||||||
.ambiguous_with(CameraUpdateSystem),
|
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
crate::camera::camera_system::<T>
|
crate::camera::camera_system.in_set(CameraUpdateSystem),
|
||||||
.in_set(CameraUpdateSystem)
|
crate::view::update_frusta
|
||||||
// We assume that each camera will only have one projection,
|
|
||||||
// so we can ignore ambiguities with all other monomorphizations.
|
|
||||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
|
||||||
.ambiguous_with(CameraUpdateSystem),
|
|
||||||
crate::view::update_frusta::<T>
|
|
||||||
.in_set(VisibilitySystems::UpdateFrusta)
|
.in_set(VisibilitySystems::UpdateFrusta)
|
||||||
.after(crate::camera::camera_system::<T>)
|
.after(crate::camera::camera_system)
|
||||||
.after(TransformSystem::TransformPropagate)
|
.after(TransformSystem::TransformPropagate),
|
||||||
// We assume that no camera will have more than one projection component,
|
|
||||||
// so these systems will run independently of one another.
|
|
||||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
|
||||||
.ambiguous_with(VisibilitySystems::UpdateFrusta),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: CameraProjection + Component + GetTypeRegistration> Default for CameraProjectionPlugin<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Label for [`camera_system<T>`], shared across all `T`.
|
/// Label for [`camera_system<T>`], shared across all `T`.
|
||||||
///
|
///
|
||||||
@ -64,21 +45,40 @@ impl<T: CameraProjection + Component + GetTypeRegistration> Default for CameraPr
|
|||||||
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub struct CameraUpdateSystem;
|
pub struct CameraUpdateSystem;
|
||||||
|
|
||||||
/// Trait to control the projection matrix of a camera.
|
/// Describes a type that can generate a projection matrix, allowing it to be added to a
|
||||||
|
/// [`Camera`]'s [`Projection`] component.
|
||||||
///
|
///
|
||||||
/// Components implementing this trait are automatically polled for changes, and used
|
/// Once implemented, the projection can be added to a camera using [`Projection::custom`].
|
||||||
/// to recompute the camera projection matrix of the [`Camera`] component attached to
|
|
||||||
/// the same entity as the component implementing this trait.
|
|
||||||
///
|
///
|
||||||
/// Use the plugins [`CameraProjectionPlugin`] and `bevy::pbr::PbrProjectionPlugin` to setup the
|
/// The projection will be automatically updated as the render area is resized. This is useful when,
|
||||||
/// systems for your [`CameraProjection`] implementation.
|
/// for example, a projection type has a field like `fov` that should change when the window width
|
||||||
|
/// is changed but not when the height changes.
|
||||||
|
///
|
||||||
|
/// This trait is implemented by bevy's built-in projections [`PerspectiveProjection`] and
|
||||||
|
/// [`OrthographicProjection`].
|
||||||
///
|
///
|
||||||
/// [`Camera`]: crate::camera::Camera
|
/// [`Camera`]: crate::camera::Camera
|
||||||
pub trait CameraProjection {
|
pub trait CameraProjection {
|
||||||
|
/// Generate the projection matrix.
|
||||||
fn get_clip_from_view(&self) -> Mat4;
|
fn get_clip_from_view(&self) -> Mat4;
|
||||||
|
|
||||||
|
/// Generate the projection matrix for a [`SubCameraView`](super::SubCameraView).
|
||||||
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4;
|
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4;
|
||||||
|
|
||||||
|
/// When the area this camera renders to changes dimensions, this method will be automatically
|
||||||
|
/// called. Use this to update any projection properties that depend on the aspect ratio or
|
||||||
|
/// dimensions of the render area.
|
||||||
fn update(&mut self, width: f32, height: f32);
|
fn update(&mut self, width: f32, height: f32);
|
||||||
|
|
||||||
|
/// The far plane distance of the projection.
|
||||||
fn far(&self) -> f32;
|
fn far(&self) -> f32;
|
||||||
|
|
||||||
|
/// The eight corners of the camera frustum, as defined by this projection.
|
||||||
|
///
|
||||||
|
/// The corners should be provided in the following order: first the bottom right, top right,
|
||||||
|
/// top left, bottom left for the near plane, then similar for the far plane.
|
||||||
|
// TODO: This seems somewhat redundant with `compute_frustum`, and similarly should be possible
|
||||||
|
// to compute with a default impl.
|
||||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];
|
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];
|
||||||
|
|
||||||
/// Compute camera frustum for camera with given projection and transform.
|
/// Compute camera frustum for camera with given projection and transform.
|
||||||
@ -97,12 +97,152 @@ pub trait CameraProjection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A configurable [`CameraProjection`] that can select its projection type at runtime.
|
mod sealed {
|
||||||
|
use super::CameraProjection;
|
||||||
|
|
||||||
|
/// A wrapper trait to make it possible to implement Clone for boxed [`super::CameraProjection`]
|
||||||
|
/// trait objects, without breaking object safety rules by making it `Sized`. Additional bounds
|
||||||
|
/// are included for downcasting, and fulfilling the trait bounds on `Projection`.
|
||||||
|
pub trait DynCameraProjection:
|
||||||
|
CameraProjection + core::fmt::Debug + Send + Sync + downcast_rs::Downcast
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn DynCameraProjection>;
|
||||||
|
}
|
||||||
|
|
||||||
|
downcast_rs::impl_downcast!(DynCameraProjection);
|
||||||
|
|
||||||
|
impl<T> DynCameraProjection for T
|
||||||
|
where
|
||||||
|
T: 'static + CameraProjection + core::fmt::Debug + Send + Sync + Clone,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn DynCameraProjection> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds a dynamic [`CameraProjection`] trait object. Use [`Projection::custom()`] to construct a
|
||||||
|
/// custom projection.
|
||||||
|
///
|
||||||
|
/// The contained dynamic object can be downcast into a static type using [`CustomProjection::get`].
|
||||||
|
#[derive(Component, Debug, Reflect, Deref, DerefMut)]
|
||||||
|
#[reflect(Default)]
|
||||||
|
pub struct CustomProjection {
|
||||||
|
#[reflect(ignore)]
|
||||||
|
#[deref]
|
||||||
|
dyn_projection: Box<dyn sealed::DynCameraProjection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CustomProjection {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
dyn_projection: Box::new(PerspectiveProjection::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for CustomProjection {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
dyn_projection: self.dyn_projection.clone_box(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomProjection {
|
||||||
|
/// Returns a reference to the [`CameraProjection`] `P`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_render::prelude::{Projection, PerspectiveProjection};
|
||||||
|
/// // For simplicity's sake, use perspective as a custom projection:
|
||||||
|
/// let projection = Projection::custom(PerspectiveProjection::default());
|
||||||
|
/// let Projection::Custom(custom) = projection else { return };
|
||||||
|
///
|
||||||
|
/// // At this point the projection type is erased.
|
||||||
|
/// // We can use `get()` if we know what kind of projection we have.
|
||||||
|
/// let perspective = custom.get::<PerspectiveProjection>().unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);
|
||||||
|
/// ```
|
||||||
|
pub fn get<P>(&self) -> Option<&P>
|
||||||
|
where
|
||||||
|
P: CameraProjection + Debug + Send + Sync + Clone + 'static,
|
||||||
|
{
|
||||||
|
self.dyn_projection.downcast_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the [`CameraProjection`] `P`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_render::prelude::{Projection, PerspectiveProjection};
|
||||||
|
/// // For simplicity's sake, use perspective as a custom projection:
|
||||||
|
/// let mut projection = Projection::custom(PerspectiveProjection::default());
|
||||||
|
/// let Projection::Custom(mut custom) = projection else { return };
|
||||||
|
///
|
||||||
|
/// // At this point the projection type is erased.
|
||||||
|
/// // We can use `get_mut()` if we know what kind of projection we have.
|
||||||
|
/// let perspective = custom.get_mut::<PerspectiveProjection>().unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);
|
||||||
|
/// perspective.fov = 1.0;
|
||||||
|
/// ```
|
||||||
|
pub fn get_mut<P>(&mut self) -> Option<&mut P>
|
||||||
|
where
|
||||||
|
P: CameraProjection + Debug + Send + Sync + Clone + 'static,
|
||||||
|
{
|
||||||
|
self.dyn_projection.downcast_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Component that defines how to compute a [`Camera`]'s projection matrix.
|
||||||
|
///
|
||||||
|
/// Common projections, like perspective and orthographic, are provided out of the box to handle the
|
||||||
|
/// majority of use cases. Custom projections can be added using the [`CameraProjection`] trait and
|
||||||
|
/// the [`Projection::custom`] constructor.
|
||||||
|
///
|
||||||
|
/// ## What's a projection?
|
||||||
|
///
|
||||||
|
/// A camera projection essentially describes how 3d points from the point of view of a camera are
|
||||||
|
/// projected onto a 2d screen. This is where properties like a camera's field of view are defined.
|
||||||
|
/// More specifically, a projection is a 4x4 matrix that transforms points from view space (the
|
||||||
|
/// point of view of the camera) into clip space. Clip space is almost, but not quite, equivalent to
|
||||||
|
/// the rectangle that is rendered to your screen, with a depth axis. Any points that land outside
|
||||||
|
/// the bounds of this cuboid are "clipped" and not rendered.
|
||||||
|
///
|
||||||
|
/// You can also think of the projection as the thing that describes the shape of a camera's
|
||||||
|
/// frustum: the volume in 3d space that is visible to a camera.
|
||||||
|
///
|
||||||
|
/// [`Camera`]: crate::camera::Camera
|
||||||
#[derive(Component, Debug, Clone, Reflect, From)]
|
#[derive(Component, Debug, Clone, Reflect, From)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Component, Default, Debug)]
|
||||||
pub enum Projection {
|
pub enum Projection {
|
||||||
Perspective(PerspectiveProjection),
|
Perspective(PerspectiveProjection),
|
||||||
Orthographic(OrthographicProjection),
|
Orthographic(OrthographicProjection),
|
||||||
|
Custom(CustomProjection),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Projection {
|
||||||
|
/// Construct a new custom camera projection from a type that implements [`CameraProjection`].
|
||||||
|
pub fn custom<P>(projection: P) -> Self
|
||||||
|
where
|
||||||
|
// Implementation note: pushing these trait bounds all the way out to this function makes
|
||||||
|
// errors nice for users. If a trait is missing, they will get a helpful error telling them
|
||||||
|
// that, say, the `Debug` implementation is missing. Wrapping these traits behind a super
|
||||||
|
// trait or some other indirection will make the errors harder to understand.
|
||||||
|
//
|
||||||
|
// For example, we don't use the `DynCameraProjection`` trait bound, because it is not the
|
||||||
|
// trait the user should be implementing - they only need to worry about implementing
|
||||||
|
// `CameraProjection`.
|
||||||
|
P: CameraProjection + Debug + Send + Sync + Clone + 'static,
|
||||||
|
{
|
||||||
|
Projection::Custom(CustomProjection {
|
||||||
|
dyn_projection: Box::new(projection),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraProjection for Projection {
|
impl CameraProjection for Projection {
|
||||||
@ -110,6 +250,7 @@ impl CameraProjection for Projection {
|
|||||||
match self {
|
match self {
|
||||||
Projection::Perspective(projection) => projection.get_clip_from_view(),
|
Projection::Perspective(projection) => projection.get_clip_from_view(),
|
||||||
Projection::Orthographic(projection) => projection.get_clip_from_view(),
|
Projection::Orthographic(projection) => projection.get_clip_from_view(),
|
||||||
|
Projection::Custom(projection) => projection.get_clip_from_view(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +258,7 @@ impl CameraProjection for Projection {
|
|||||||
match self {
|
match self {
|
||||||
Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
||||||
Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
||||||
|
Projection::Custom(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +266,7 @@ impl CameraProjection for Projection {
|
|||||||
match self {
|
match self {
|
||||||
Projection::Perspective(projection) => projection.update(width, height),
|
Projection::Perspective(projection) => projection.update(width, height),
|
||||||
Projection::Orthographic(projection) => projection.update(width, height),
|
Projection::Orthographic(projection) => projection.update(width, height),
|
||||||
|
Projection::Custom(projection) => projection.update(width, height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +274,7 @@ impl CameraProjection for Projection {
|
|||||||
match self {
|
match self {
|
||||||
Projection::Perspective(projection) => projection.far(),
|
Projection::Perspective(projection) => projection.far(),
|
||||||
Projection::Orthographic(projection) => projection.far(),
|
Projection::Orthographic(projection) => projection.far(),
|
||||||
|
Projection::Custom(projection) => projection.far(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +282,7 @@ impl CameraProjection for Projection {
|
|||||||
match self {
|
match self {
|
||||||
Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far),
|
Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far),
|
||||||
Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far),
|
Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far),
|
||||||
|
Projection::Custom(projection) => projection.get_frustum_corners(z_near, z_far),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,8 +294,8 @@ impl Default for Projection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A 3D camera projection in which distant objects appear smaller than close objects.
|
/// A 3D camera projection in which distant objects appear smaller than close objects.
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
#[derive(Debug, Clone, Reflect)]
|
||||||
#[reflect(Component, Default, Debug)]
|
#[reflect(Default, Debug)]
|
||||||
pub struct PerspectiveProjection {
|
pub struct PerspectiveProjection {
|
||||||
/// The vertical field of view (FOV) in radians.
|
/// The vertical field of view (FOV) in radians.
|
||||||
///
|
///
|
||||||
@ -341,8 +486,8 @@ pub enum ScalingMode {
|
|||||||
/// ..OrthographicProjection::default_2d()
|
/// ..OrthographicProjection::default_2d()
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
#[derive(Debug, Clone, Reflect)]
|
||||||
#[reflect(Component, Debug, FromWorld)]
|
#[reflect(Debug, FromWorld)]
|
||||||
pub struct OrthographicProjection {
|
pub struct OrthographicProjection {
|
||||||
/// The distance of the near clipping plane in world units.
|
/// The distance of the near clipping plane in world units.
|
||||||
///
|
///
|
||||||
|
@ -22,7 +22,7 @@ use bevy_utils::{Parallel, TypeIdMap};
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::NoCpuCulling;
|
use super::NoCpuCulling;
|
||||||
use crate::sync_world::MainEntity;
|
use crate::{camera::Projection, sync_world::MainEntity};
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{Camera, CameraProjection},
|
camera::{Camera, CameraProjection},
|
||||||
mesh::{Mesh, Mesh3d, MeshAabb},
|
mesh::{Mesh, Mesh3d, MeshAabb},
|
||||||
@ -398,10 +398,10 @@ pub fn calculate_bounds(
|
|||||||
/// Updates [`Frustum`].
|
/// Updates [`Frustum`].
|
||||||
///
|
///
|
||||||
/// This system is used in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
|
/// This system is used in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
|
||||||
pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
|
pub fn update_frusta(
|
||||||
mut views: Query<
|
mut views: Query<
|
||||||
(&GlobalTransform, &T, &mut Frustum),
|
(&GlobalTransform, &Projection, &mut Frustum),
|
||||||
Or<(Changed<GlobalTransform>, Changed<T>)>,
|
Or<(Changed<GlobalTransform>, Changed<Projection>)>,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (transform, projection, mut frustum) in &mut views {
|
for (transform, projection, mut frustum) in &mut views {
|
||||||
|
@ -59,7 +59,7 @@ impl Plugin for SpritePickingPlugin {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn sprite_picking(
|
fn sprite_picking(
|
||||||
pointers: Query<(&PointerId, &PointerLocation)>,
|
pointers: Query<(&PointerId, &PointerLocation)>,
|
||||||
cameras: Query<(Entity, &Camera, &GlobalTransform, &OrthographicProjection)>,
|
cameras: Query<(Entity, &Camera, &GlobalTransform, &Projection)>,
|
||||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
texture_atlas_layout: Res<Assets<TextureAtlasLayout>>,
|
texture_atlas_layout: Res<Assets<TextureAtlasLayout>>,
|
||||||
@ -91,15 +91,16 @@ fn sprite_picking(
|
|||||||
pointer_location.location().map(|loc| (pointer, loc))
|
pointer_location.location().map(|loc| (pointer, loc))
|
||||||
}) {
|
}) {
|
||||||
let mut blocked = false;
|
let mut blocked = false;
|
||||||
let Some((cam_entity, camera, cam_transform, cam_ortho)) = cameras
|
let Some((cam_entity, camera, cam_transform, Projection::Orthographic(cam_ortho))) =
|
||||||
.iter()
|
cameras
|
||||||
.filter(|(_, camera, _, _)| camera.is_active)
|
.iter()
|
||||||
.find(|(_, camera, _, _)| {
|
.filter(|(_, camera, _, _)| camera.is_active)
|
||||||
camera
|
.find(|(_, camera, _, _)| {
|
||||||
.target
|
camera
|
||||||
.normalize(primary_window)
|
.target
|
||||||
.is_some_and(|x| x == location.target)
|
.normalize(primary_window)
|
||||||
})
|
.is_some_and(|x| x == location.target)
|
||||||
|
})
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -488,10 +488,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use bevy_image::Image;
|
use bevy_image::Image;
|
||||||
use bevy_math::{Rect, UVec2, Vec2};
|
use bevy_math::{Rect, UVec2, Vec2};
|
||||||
use bevy_render::{
|
use bevy_render::{camera::ManualTextureViews, prelude::Camera};
|
||||||
camera::{ManualTextureViews, OrthographicProjection},
|
|
||||||
prelude::Camera,
|
|
||||||
};
|
|
||||||
use bevy_transform::{
|
use bevy_transform::{
|
||||||
prelude::GlobalTransform,
|
prelude::GlobalTransform,
|
||||||
systems::{propagate_transforms, sync_simple_transforms},
|
systems::{propagate_transforms, sync_simple_transforms},
|
||||||
@ -543,7 +540,7 @@ mod tests {
|
|||||||
ui_schedule.add_systems(
|
ui_schedule.add_systems(
|
||||||
(
|
(
|
||||||
// UI is driven by calculated camera target info, so we need to run the camera system first
|
// UI is driven by calculated camera target info, so we need to run the camera system first
|
||||||
bevy_render::camera::camera_system::<OrthographicProjection>,
|
bevy_render::camera::camera_system,
|
||||||
update_target_camera_system,
|
update_target_camera_system,
|
||||||
ApplyDeferred,
|
ApplyDeferred,
|
||||||
ui_layout_system,
|
ui_layout_system,
|
||||||
@ -1187,7 +1184,7 @@ mod tests {
|
|||||||
ui_schedule.add_systems(
|
ui_schedule.add_systems(
|
||||||
(
|
(
|
||||||
// UI is driven by calculated camera target info, so we need to run the camera system first
|
// UI is driven by calculated camera target info, so we need to run the camera system first
|
||||||
bevy_render::camera::camera_system::<OrthographicProjection>,
|
bevy_render::camera::camera_system,
|
||||||
update_target_camera_system,
|
update_target_camera_system,
|
||||||
ApplyDeferred,
|
ApplyDeferred,
|
||||||
ui_layout_system,
|
ui_layout_system,
|
||||||
|
@ -144,8 +144,11 @@ fn rotate(time: Res<Time>, mut transforms: Query<&mut Transform, With<Rotate>>)
|
|||||||
/// Scales camera projection to fit the window (integer multiples only).
|
/// Scales camera projection to fit the window (integer multiples only).
|
||||||
fn fit_canvas(
|
fn fit_canvas(
|
||||||
mut resize_events: EventReader<WindowResized>,
|
mut resize_events: EventReader<WindowResized>,
|
||||||
mut projection: Single<&mut OrthographicProjection, With<OuterCamera>>,
|
mut projection: Single<&mut Projection, With<OuterCamera>>,
|
||||||
) {
|
) {
|
||||||
|
let Projection::Orthographic(projection) = &mut **projection else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
for event in resize_events.read() {
|
for event in resize_events.read() {
|
||||||
let h_scale = event.width / RES_WIDTH as f32;
|
let h_scale = event.width / RES_WIDTH as f32;
|
||||||
let v_scale = event.height / RES_HEIGHT as f32;
|
let v_scale = event.height / RES_HEIGHT as f32;
|
||||||
|
@ -270,6 +270,7 @@ Example | Description
|
|||||||
--- | ---
|
--- | ---
|
||||||
[2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements
|
[2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements
|
||||||
[Camera Orbit](../examples/camera/camera_orbit.rs) | Shows how to orbit a static scene using pitch, yaw, and roll.
|
[Camera Orbit](../examples/camera/camera_orbit.rs) | Shows how to orbit a static scene using pitch, yaw, and roll.
|
||||||
|
[Custom Projection](../examples/camera/custom_projection.rs) | Shows how to create custom camera projections.
|
||||||
[First person view model](../examples/camera/first_person_view_model.rs) | A first-person camera that uses a world model and a view model with different field of views (FOV)
|
[First person view model](../examples/camera/first_person_view_model.rs) | A first-person camera that uses a world model and a view model with different field of views (FOV)
|
||||||
[Projection Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom orthographic and perspective projection cameras.
|
[Projection Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom orthographic and perspective projection cameras.
|
||||||
[Screen Shake](../examples/camera/2d_screen_shake.rs) | A simple 2D screen shake effect
|
[Screen Shake](../examples/camera/2d_screen_shake.rs) | A simple 2D screen shake effect
|
||||||
|
84
examples/camera/custom_projection.rs
Normal file
84
examples/camera/custom_projection.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//! Demonstrates how to define and use custom camera projections.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::camera::CameraProjection;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like a perspective projection, but the vanishing point is not centered.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ObliquePerspectiveProjection {
|
||||||
|
horizontal_obliqueness: f32,
|
||||||
|
vertical_obliqueness: f32,
|
||||||
|
perspective: PerspectiveProjection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement the [`CameraProjection`] trait for our custom projection:
|
||||||
|
impl CameraProjection for ObliquePerspectiveProjection {
|
||||||
|
fn get_clip_from_view(&self) -> Mat4 {
|
||||||
|
let mut mat = self.perspective.get_clip_from_view();
|
||||||
|
mat.col_mut(2)[0] = self.horizontal_obliqueness;
|
||||||
|
mat.col_mut(2)[1] = self.vertical_obliqueness;
|
||||||
|
mat
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_clip_from_view_for_sub(&self, sub_view: &bevy_render::camera::SubCameraView) -> Mat4 {
|
||||||
|
let mut mat = self.perspective.get_clip_from_view_for_sub(sub_view);
|
||||||
|
mat.col_mut(2)[0] = self.horizontal_obliqueness;
|
||||||
|
mat.col_mut(2)[1] = self.vertical_obliqueness;
|
||||||
|
mat
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, width: f32, height: f32) {
|
||||||
|
self.perspective.update(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn far(&self) -> f32 {
|
||||||
|
self.perspective.far
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||||
|
self.perspective.get_frustum_corners(z_near, z_far)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
// Use our custom projection:
|
||||||
|
Projection::custom(ObliquePerspectiveProjection {
|
||||||
|
horizontal_obliqueness: 0.2,
|
||||||
|
vertical_obliqueness: 0.6,
|
||||||
|
perspective: PerspectiveProjection::default(),
|
||||||
|
}),
|
||||||
|
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Scene setup
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Circle::new(4.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||||
|
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||||
|
));
|
||||||
|
commands.spawn((
|
||||||
|
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
||||||
|
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
));
|
||||||
|
commands.spawn((
|
||||||
|
PointLight {
|
||||||
|
shadows_enabled: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
|
));
|
||||||
|
}
|
@ -126,6 +126,7 @@ fn switch_projection(
|
|||||||
},
|
},
|
||||||
..OrthographicProjection::default_3d()
|
..OrthographicProjection::default_3d()
|
||||||
}),
|
}),
|
||||||
|
_ => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,5 +163,6 @@ fn zoom(
|
|||||||
camera_settings.perspective_zoom_range.end,
|
camera_settings.perspective_zoom_range.end,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user