use core::fmt::Debug;1use core::ops::{Deref, DerefMut};23use crate::{primitives::Frustum, visibility::VisibilitySystems};4use bevy_app::{App, Plugin, PostUpdate};5use bevy_ecs::prelude::*;6use bevy_math::{ops, primitives::ViewFrustum, vec4, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};7use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};8use bevy_transform::{components::GlobalTransform, TransformSystems};9use derive_more::derive::From;10use serde::{Deserialize, Serialize};1112/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.13///14/// If you are using `bevy_pbr`, then you need to add `PbrProjectionPlugin` along with this.15#[derive(Default)]16pub struct CameraProjectionPlugin;1718impl Plugin for CameraProjectionPlugin {19fn build(&self, app: &mut App) {20app.add_systems(21PostUpdate,22crate::visibility::update_frusta23.in_set(VisibilitySystems::UpdateFrusta)24.after(TransformSystems::Propagate),25);26}27}2829/// Describes a type that can generate a projection matrix, allowing it to be added to a30/// [`Camera`]'s [`Projection`] component.31///32/// Once implemented, the projection can be added to a camera using [`Projection::custom`].33///34/// The projection will be automatically updated as the render area is resized. This is useful when,35/// for example, a projection type has a field like `fov` that should change when the window width36/// is changed but not when the height changes.37///38/// This trait is implemented by bevy's built-in projections [`PerspectiveProjection`] and39/// [`OrthographicProjection`].40///41/// [`Camera`]: crate::camera::Camera42pub trait CameraProjection {43/// Generate the projection matrix.44fn get_clip_from_view(&self) -> Mat4;4546/// Generate the projection matrix for a [`SubCameraView`](super::SubCameraView).47fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4;4849/// When the area this camera renders to changes dimensions, this method will be automatically50/// called. Use this to update any projection properties that depend on the aspect ratio or51/// dimensions of the render area.52fn update(&mut self, width: f32, height: f32);5354/// The far plane distance of the projection.55fn far(&self) -> f32;5657/// The eight corners of the camera frustum, as defined by this projection.58///59/// The corners should be provided in the following order: first the bottom right, top right,60/// top left, bottom left for the near plane, then similar for the far plane.61// TODO: This seems somewhat redundant with `compute_frustum`, and similarly should be possible62// to compute with a default impl.63fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];6465/// Compute camera frustum for camera with given projection and transform.66///67/// This code is called by [`update_frusta`](crate::visibility::update_frusta) system68/// for each camera to update its frustum.69fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum {70let clip_from_world = self.get_clip_from_view() * camera_transform.affine().inverse();71Frustum(ViewFrustum::from_clip_from_world_custom_far(72&clip_from_world,73&camera_transform.translation(),74&camera_transform.back(),75self.far(),76))77}78}7980mod sealed {81use super::CameraProjection;8283/// A wrapper trait to make it possible to implement Clone for boxed [`CameraProjection`](`super::CameraProjection`)84/// trait objects, without breaking object safety rules by making it `Sized`. Additional bounds85/// are included for downcasting, and fulfilling the trait bounds on `Projection`.86pub trait DynCameraProjection:87CameraProjection + core::fmt::Debug + Send + Sync + downcast_rs::Downcast88{89fn clone_box(&self) -> Box<dyn DynCameraProjection>;90}9192downcast_rs::impl_downcast!(DynCameraProjection);9394impl<T> DynCameraProjection for T95where96T: 'static + CameraProjection + core::fmt::Debug + Send + Sync + Clone,97{98fn clone_box(&self) -> Box<dyn DynCameraProjection> {99Box::new(self.clone())100}101}102}103104/// Holds a dynamic [`CameraProjection`] trait object. Use [`Projection::custom()`] to construct a105/// custom projection.106///107/// The contained dynamic object can be downcast into a static type using [`CustomProjection::get`].108#[derive(Debug, Reflect)]109#[reflect(Default, Clone)]110pub struct CustomProjection {111#[reflect(ignore)]112dyn_projection: Box<dyn sealed::DynCameraProjection>,113}114115impl Default for CustomProjection {116fn default() -> Self {117Self {118dyn_projection: Box::new(PerspectiveProjection::default()),119}120}121}122123impl Clone for CustomProjection {124fn clone(&self) -> Self {125Self {126dyn_projection: self.dyn_projection.clone_box(),127}128}129}130131impl CustomProjection {132/// Returns a reference to the [`CameraProjection`] `P`.133///134/// Returns `None` if this dynamic object is not a projection of type `P`.135///136/// ```137/// # use bevy_camera::{Projection, PerspectiveProjection};138/// // For simplicity's sake, use perspective as a custom projection:139/// let projection = Projection::custom(PerspectiveProjection::default());140/// let Projection::Custom(custom) = projection else { return };141///142/// // At this point the projection type is erased.143/// // We can use `get()` if we know what kind of projection we have.144/// let perspective = custom.get::<PerspectiveProjection>().unwrap();145///146/// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);147/// ```148pub fn get<P>(&self) -> Option<&P>149where150P: CameraProjection + Debug + Send + Sync + Clone + 'static,151{152self.dyn_projection.downcast_ref()153}154155/// Returns a mutable reference to the [`CameraProjection`] `P`.156///157/// Returns `None` if this dynamic object is not a projection of type `P`.158///159/// ```160/// # use bevy_camera::{Projection, PerspectiveProjection};161/// // For simplicity's sake, use perspective as a custom projection:162/// let mut projection = Projection::custom(PerspectiveProjection::default());163/// let Projection::Custom(mut custom) = projection else { return };164///165/// // At this point the projection type is erased.166/// // We can use `get_mut()` if we know what kind of projection we have.167/// let perspective = custom.get_mut::<PerspectiveProjection>().unwrap();168///169/// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);170/// perspective.fov = 1.0;171/// ```172pub fn get_mut<P>(&mut self) -> Option<&mut P>173where174P: CameraProjection + Debug + Send + Sync + Clone + 'static,175{176self.dyn_projection.downcast_mut()177}178}179180impl Deref for CustomProjection {181type Target = dyn CameraProjection;182183fn deref(&self) -> &Self::Target {184self.dyn_projection.as_ref()185}186}187188impl DerefMut for CustomProjection {189fn deref_mut(&mut self) -> &mut Self::Target {190self.dyn_projection.as_mut()191}192}193194/// Component that defines how to compute a [`Camera`]'s projection matrix.195///196/// Common projections, like perspective and orthographic, are provided out of the box to handle the197/// majority of use cases. Custom projections can be added using the [`CameraProjection`] trait and198/// the [`Projection::custom`] constructor.199///200/// ## What's a projection?201///202/// A camera projection essentially describes how 3d points from the point of view of a camera are203/// projected onto a 2d screen. This is where properties like a camera's field of view are defined.204/// More specifically, a projection is a 4x4 matrix that transforms points from view space (the205/// point of view of the camera) into clip space. Clip space is almost, but not quite, equivalent to206/// the rectangle that is rendered to your screen, with a depth axis. Any points that land outside207/// the bounds of this cuboid are "clipped" and not rendered.208///209/// You can also think of the projection as the thing that describes the shape of a camera's210/// frustum: the volume in 3d space that is visible to a camera.211///212/// [`Camera`]: crate::camera::Camera213#[derive(Component, Debug, Clone, Reflect, From)]214#[reflect(Component, Default, Debug, Clone)]215pub enum Projection {216Perspective(PerspectiveProjection),217Orthographic(OrthographicProjection),218Custom(CustomProjection),219}220221impl Projection {222/// Construct a new custom camera projection from a type that implements [`CameraProjection`].223pub fn custom<P>(projection: P) -> Self224where225// Implementation note: pushing these trait bounds all the way out to this function makes226// errors nice for users. If a trait is missing, they will get a helpful error telling them227// that, say, the `Debug` implementation is missing. Wrapping these traits behind a super228// trait or some other indirection will make the errors harder to understand.229//230// For example, we don't use the `DynCameraProjection` trait bound, because it is not the231// trait the user should be implementing - they only need to worry about implementing232// `CameraProjection`.233P: CameraProjection + Debug + Send + Sync + Clone + 'static,234{235Projection::Custom(CustomProjection {236dyn_projection: Box::new(projection),237})238}239240/// Check if the projection is perspective.241/// For [`CustomProjection`], this checks if the projection matrix's w-axis's w is 0.0.242pub fn is_perspective(&self) -> bool {243match self {244Projection::Perspective(_) => true,245Projection::Orthographic(_) => false,246Projection::Custom(projection) => projection.get_clip_from_view().w_axis.w == 0.0,247}248}249}250251impl Deref for Projection {252type Target = dyn CameraProjection;253254fn deref(&self) -> &Self::Target {255match self {256Projection::Perspective(projection) => projection,257Projection::Orthographic(projection) => projection,258Projection::Custom(projection) => projection.deref(),259}260}261}262263impl DerefMut for Projection {264fn deref_mut(&mut self) -> &mut Self::Target {265match self {266Projection::Perspective(projection) => projection,267Projection::Orthographic(projection) => projection,268Projection::Custom(projection) => projection.deref_mut(),269}270}271}272273impl Default for Projection {274fn default() -> Self {275Projection::Perspective(Default::default())276}277}278279/// A 3D camera projection in which distant objects appear smaller than close objects.280#[derive(Debug, Clone, Reflect)]281#[reflect(Default, Debug, Clone)]282pub struct PerspectiveProjection {283/// The vertical field of view (FOV) in radians.284///285/// Defaults to a value of π/4 radians or 45 degrees.286pub fov: f32,287288/// The aspect ratio (width divided by height) of the viewing frustum.289///290/// Bevy's `camera_system` automatically updates this value when the aspect ratio291/// of the associated window changes.292///293/// Defaults to a value of `1.0`.294pub aspect_ratio: f32,295296/// The distance from the camera in world units of the viewing frustum's near plane.297///298/// Objects closer to the camera than this value will not be visible.299///300/// Defaults to a value of `0.1`.301pub near: f32,302303/// The distance from the camera in world units of the viewing frustum's far plane.304///305/// Objects farther from the camera than this value will not be visible.306///307/// Defaults to a value of `1000.0`.308pub far: f32,309310/// The orientation of a custom clipping plane, as well as its distance from311/// the camera.312///313/// If you supply a plane here, anything in front of the plane will be314/// clipped out. This is useful for portals and mirrors, in order to clip315/// any geometry that would pass through the plane of the portal or mirror.316///317/// The X, Y, and Z components of the vector describe its normal, in view318/// space. This normal vector must have length 1, and it should point away319/// from the camera. (That is, only geometry on the side of the plane that320/// the normal points toward will be rendered.) The W component of the321/// vector must be the *negative shortest signed distance* from the camera322/// to the plane, again in view space. This final component can also be323/// computed as -(N · Q), where N is the normal of the plane and Q is any324/// point on it.325///326/// By default, this is (0, 0, -1, -[`Self::near`]), which describes a near327/// plane located [`Self::near`] meters away pointing directly away from the328/// camera.329///330/// See the `calculate_mirror_camera_transform_and_projection` function in331/// the `mirror` example for an exhaustive example of usage.332pub near_clip_plane: Vec4,333}334335impl CameraProjection for PerspectiveProjection {336fn get_clip_from_view(&self) -> Mat4 {337let mut matrix =338Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near);339self.adjust_perspective_matrix_for_clip_plane(&mut matrix);340matrix341}342343fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {344let full_width = sub_view.full_size.x as f32;345let full_height = sub_view.full_size.y as f32;346let sub_width = sub_view.size.x as f32;347let sub_height = sub_view.size.y as f32;348let offset_x = sub_view.offset.x;349// Y-axis increases from top to bottom350let offset_y = full_height - (sub_view.offset.y + sub_height);351352let full_aspect = full_width / full_height;353354// Original frustum parameters355let top = self.near * ops::tan(0.5 * self.fov);356let bottom = -top;357let right = top * full_aspect;358let left = -right;359360// Calculate scaling factors361let width = right - left;362let height = top - bottom;363364// Calculate the new frustum parameters365let left_prime = left + (width * offset_x) / full_width;366let right_prime = left + (width * (offset_x + sub_width)) / full_width;367let bottom_prime = bottom + (height * offset_y) / full_height;368let top_prime = bottom + (height * (offset_y + sub_height)) / full_height;369370// Compute the new projection matrix371let x = (2.0 * self.near) / (right_prime - left_prime);372let y = (2.0 * self.near) / (top_prime - bottom_prime);373let a = (right_prime + left_prime) / (right_prime - left_prime);374let b = (top_prime + bottom_prime) / (top_prime - bottom_prime);375376let mut matrix = Mat4::from_cols(377Vec4::new(x, 0.0, 0.0, 0.0),378Vec4::new(0.0, y, 0.0, 0.0),379Vec4::new(a, b, 0.0, -1.0),380Vec4::new(0.0, 0.0, self.near, 0.0),381);382383self.adjust_perspective_matrix_for_clip_plane(&mut matrix);384matrix385}386387fn update(&mut self, width: f32, height: f32) {388self.aspect_ratio = AspectRatio::try_new(width, height)389.expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values")390.ratio();391}392393fn far(&self) -> f32 {394self.far395}396397fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {398let tan_half_fov = ops::tan(self.fov / 2.);399let a = z_near.abs() * tan_half_fov;400let b = z_far.abs() * tan_half_fov;401let aspect_ratio = self.aspect_ratio;402// NOTE: These vertices are in the specific order required by [`calculate_cascade`].403[404Vec3A::new(a * aspect_ratio, -a, z_near), // bottom right405Vec3A::new(a * aspect_ratio, a, z_near), // top right406Vec3A::new(-a * aspect_ratio, a, z_near), // top left407Vec3A::new(-a * aspect_ratio, -a, z_near), // bottom left408Vec3A::new(b * aspect_ratio, -b, z_far), // bottom right409Vec3A::new(b * aspect_ratio, b, z_far), // top right410Vec3A::new(-b * aspect_ratio, b, z_far), // top left411Vec3A::new(-b * aspect_ratio, -b, z_far), // bottom left412]413}414}415416impl Default for PerspectiveProjection {417fn default() -> Self {418PerspectiveProjection {419fov: core::f32::consts::PI / 4.0,420near: 0.1,421far: 1000.0,422aspect_ratio: 1.0,423near_clip_plane: vec4(0.0, 0.0, -1.0, -0.1),424}425}426}427428impl PerspectiveProjection {429/// Adjusts the perspective matrix for an oblique clip plane if necessary.430///431/// This changes the near and (infinite) far planes so that they correctly432/// clip everything in front of the [`Self::near_clip_plane`]. See [Lengyel433/// 2005] for an exhaustive treatment of the way this works. Custom near434/// clip planes are typically used for portals and mirrors; see435/// `examples/3d/mirror.rs` for an example of usage.436fn adjust_perspective_matrix_for_clip_plane(&self, matrix: &mut Mat4) {437// If we don't have an oblique clip plane, save ourselves the trouble.438if self.near_clip_plane == vec4(0.0, 0.0, -1.0, -self.near) {439return;440}441442// To understand this, refer to [Lengyel 2005]. The notation follows the443// paper. The formulas are different because the paper uses a standard444// OpenGL convention (near -1, far 1), while we use a reversed Vulkan445// convention (near 1, far 0).446//447// [Lengyel 2005]: https://terathon.com/lengyel/Lengyel-Oblique.pdf448let c = self.near_clip_plane;449450// First, calculate the position of Q′, the corner in clip space lying451// opposite from the near clip plane. This is identical to equation (7).452// Note that this is a point at infinity in view space, because we use453// an infinite far plane, but in clip space it's finite.454let q_prime = vec4(c.x.signum(), c.y.signum(), 0.0, 1.0);455456// Now convert that point to view space. This *will* be a point at457// infinity, but that's OK because we're in homogeneous coordinates.458let q = matrix.inverse() * q_prime;459460// Here we're computing the scaling factor to apply to the near plane so461// that the far plane will intersect Q. This one differs from the paper.462// Using the notation Mᵢ to mean the *i*th row of the matrix M, start by463// observing that the near plane (z = 1) is described by M₄ - M₃ and the464// far plane (z = 0) is described by simply M₃. So:465//466// * Equation (4) becomes C = M₄ - M₃.467// * Equation (5) becomes M′₃ = M₄ - C.468// * Equation (6) becomes F = M′₃ = M₄ - C.469// * Equation (8) becomes F = M₄ - aC.470// * Equation (9) becomes F · Q = 0 ⇒ (M₄ - aC) · Q = 0.471//472// And, solving the modified equation (9), we get:473//474// M₄ · Q475// a = ⎯⎯⎯⎯⎯⎯476// C · Q477//478// Because M₄ = (0, 0, -1, 0) (just as it is in the paper), this reduces to:479//480// -Qz481// a = ⎯⎯⎯⎯⎯482// C · Q483//484// Which is what we calculate here.485let a = -q.z / c.dot(q);486487// Finally, we have the revised equation (10), which is M′₃ = M₄ - aC.488// Similarly to the above, this simplifies to M′₃ = (0, 0, -1, 0) - aC.489let m3_prime = Vec4::NEG_Z - c * a;490491// We have the replacement third row; write it in.492matrix.x_axis.z = m3_prime.x;493matrix.y_axis.z = m3_prime.y;494matrix.z_axis.z = m3_prime.z;495matrix.w_axis.z = m3_prime.w;496}497}498499/// Scaling mode for [`OrthographicProjection`].500///501/// The effect of these scaling modes are combined with the [`OrthographicProjection::scale`] property.502///503/// For example, if the scaling mode is `ScalingMode::Fixed { width: 100.0, height: 300 }` and the scale is `2.0`,504/// the projection will be 200 world units wide and 600 world units tall.505///506/// # Examples507///508/// Configure the orthographic projection to two world units per window height:509///510/// ```511/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};512/// let projection = Projection::Orthographic(OrthographicProjection {513/// scaling_mode: ScalingMode::FixedVertical { viewport_height: 2.0 },514/// ..OrthographicProjection::default_2d()515/// });516/// ```517#[derive(Default, Debug, Clone, Copy, Reflect, Serialize, Deserialize)]518#[reflect(Serialize, Deserialize, Default, Clone)]519pub enum ScalingMode {520/// Match the viewport size.521///522/// With a scale of 1, lengths in world units will map 1:1 with the number of pixels used to render it.523/// For example, if we have a 64x64 sprite with a [`Transform::scale`](bevy_transform::prelude::Transform) of 1.0,524/// no custom size and no inherited scale, the sprite will be 64 world units wide and 64 world units tall.525/// When rendered with [`OrthographicProjection::scaling_mode`] set to `WindowSize` when the window scale factor is 1526/// the sprite will be rendered at 64 pixels wide and 64 pixels tall.527///528/// Changing any of these properties will multiplicatively affect the final size.529#[default]530WindowSize,531/// Manually specify the projection's size, ignoring window resizing. The image will stretch.532///533/// Arguments describe the area of the world that is shown (in world units).534Fixed { width: f32, height: f32 },535/// Keeping the aspect ratio while the axes can't be smaller than given minimum.536///537/// Arguments are in world units.538AutoMin { min_width: f32, min_height: f32 },539/// Keeping the aspect ratio while the axes can't be bigger than given maximum.540///541/// Arguments are in world units.542AutoMax { max_width: f32, max_height: f32 },543/// Keep the projection's height constant; width will be adjusted to match aspect ratio.544///545/// The argument is the desired height of the projection in world units.546FixedVertical { viewport_height: f32 },547/// Keep the projection's width constant; height will be adjusted to match aspect ratio.548///549/// The argument is the desired width of the projection in world units.550FixedHorizontal { viewport_width: f32 },551}552553/// Project a 3D space onto a 2D surface using parallel lines, i.e., unlike [`PerspectiveProjection`],554/// the size of objects remains the same regardless of their distance to the camera.555///556/// The volume contained in the projection is called the *view frustum*. Since the viewport is rectangular557/// and projection lines are parallel, the view frustum takes the shape of a cuboid.558///559/// Note that the scale of the projection and the apparent size of objects are inversely proportional.560/// As the size of the projection increases, the size of objects decreases.561///562/// # Examples563///564/// Configure the orthographic projection to one world unit per 100 window pixels:565///566/// ```567/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};568/// let projection = Projection::Orthographic(OrthographicProjection {569/// scaling_mode: ScalingMode::WindowSize,570/// scale: 0.01,571/// ..OrthographicProjection::default_2d()572/// });573/// ```574#[derive(Debug, Clone, Reflect)]575#[reflect(Debug, FromWorld, Clone)]576pub struct OrthographicProjection {577/// The distance of the near clipping plane in world units.578///579/// Objects closer than this will not be rendered.580///581/// Defaults to `0.0`582pub near: f32,583/// The distance of the far clipping plane in world units.584///585/// Objects further than this will not be rendered.586///587/// Defaults to `1000.0`588pub far: f32,589/// Specifies the origin of the viewport as a normalized position from 0 to 1, where (0, 0) is the bottom left590/// and (1, 1) is the top right. This determines where the camera's position sits inside the viewport.591///592/// When the projection scales due to viewport resizing, the position of the camera, and thereby `viewport_origin`,593/// remains at the same relative point.594///595/// Consequently, this is pivot point when scaling. With a bottom left pivot, the projection will expand596/// upwards and to the right. With a top right pivot, the projection will expand downwards and to the left.597/// Values in between will caused the projection to scale proportionally on each axis.598///599/// Defaults to `(0.5, 0.5)`, which makes scaling affect opposite sides equally, keeping the center600/// point of the viewport centered.601pub viewport_origin: Vec2,602/// How the projection will scale to the viewport.603///604/// Defaults to [`ScalingMode::WindowSize`],605/// and works in concert with [`OrthographicProjection::scale`] to determine the final effect.606///607/// For simplicity, zooming should be done by changing [`OrthographicProjection::scale`],608/// rather than changing the parameters of the scaling mode.609pub scaling_mode: ScalingMode,610/// Scales the projection.611///612/// As scale increases, the apparent size of objects decreases, and vice versa.613///614/// Note: scaling can be set by [`scaling_mode`](Self::scaling_mode) as well.615/// This parameter scales on top of that.616///617/// This property is particularly useful in implementing zoom functionality.618///619/// Defaults to `1.0`, which under standard settings corresponds to a 1:1 mapping of world units to rendered pixels.620/// See [`ScalingMode::WindowSize`] for more information.621pub scale: f32,622/// The area that the projection covers relative to `viewport_origin`.623///624/// Bevy's `camera_system` automatically625/// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.626/// In this case, `area` should not be manually modified.627///628/// It may be necessary to set this manually for shadow projections and such.629pub area: Rect,630}631632impl CameraProjection for OrthographicProjection {633fn get_clip_from_view(&self) -> Mat4 {634Mat4::orthographic_rh(635self.area.min.x,636self.area.max.x,637self.area.min.y,638self.area.max.y,639// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]640// This is for interoperability with pipelines using infinite reverse perspective projections.641self.far,642self.near,643)644}645646fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {647let full_width = sub_view.full_size.x as f32;648let full_height = sub_view.full_size.y as f32;649let offset_x = sub_view.offset.x;650let offset_y = sub_view.offset.y;651let sub_width = sub_view.size.x as f32;652let sub_height = sub_view.size.y as f32;653654let full_aspect = full_width / full_height;655656// Base the vertical size on self.area and adjust the horizontal size657let top = self.area.max.y;658let bottom = self.area.min.y;659let ortho_height = top - bottom;660let ortho_width = ortho_height * full_aspect;661662// Center the orthographic area horizontally663let center_x = (self.area.max.x + self.area.min.x) / 2.0;664let left = center_x - ortho_width / 2.0;665let right = center_x + ortho_width / 2.0;666667// Calculate scaling factors668let scale_w = (right - left) / full_width;669let scale_h = (top - bottom) / full_height;670671// Calculate the new orthographic bounds672let left_prime = left + scale_w * offset_x;673let right_prime = left_prime + scale_w * sub_width;674let top_prime = top - scale_h * offset_y;675let bottom_prime = top_prime - scale_h * sub_height;676677Mat4::orthographic_rh(678left_prime,679right_prime,680bottom_prime,681top_prime,682// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]683// This is for interoperability with pipelines using infinite reverse perspective projections.684self.far,685self.near,686)687}688689fn update(&mut self, width: f32, height: f32) {690let (projection_width, projection_height) = match self.scaling_mode {691ScalingMode::WindowSize => (width, height),692ScalingMode::AutoMin {693min_width,694min_height,695} => {696// Compare Pixels of current width and minimal height and Pixels of minimal width with current height.697// Then use bigger (min_height when true) as what it refers to (height when true) and calculate rest so it can't get under minimum.698if width * min_height > min_width * height {699(width * min_height / height, min_height)700} else {701(min_width, height * min_width / width)702}703}704ScalingMode::AutoMax {705max_width,706max_height,707} => {708// Compare Pixels of current width and maximal height and Pixels of maximal width with current height.709// Then use smaller (max_height when true) as what it refers to (height when true) and calculate rest so it can't get over maximum.710if width * max_height < max_width * height {711(width * max_height / height, max_height)712} else {713(max_width, height * max_width / width)714}715}716ScalingMode::FixedVertical { viewport_height } => {717(width * viewport_height / height, viewport_height)718}719ScalingMode::FixedHorizontal { viewport_width } => {720(viewport_width, height * viewport_width / width)721}722ScalingMode::Fixed { width, height } => (width, height),723};724725let origin_x = projection_width * self.viewport_origin.x;726let origin_y = projection_height * self.viewport_origin.y;727728self.area = Rect::new(729self.scale * -origin_x,730self.scale * -origin_y,731self.scale * (projection_width - origin_x),732self.scale * (projection_height - origin_y),733);734}735736fn far(&self) -> f32 {737self.far738}739740fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {741let area = self.area;742// NOTE: These vertices are in the specific order required by [`calculate_cascade`].743[744Vec3A::new(area.max.x, area.min.y, z_near), // bottom right745Vec3A::new(area.max.x, area.max.y, z_near), // top right746Vec3A::new(area.min.x, area.max.y, z_near), // top left747Vec3A::new(area.min.x, area.min.y, z_near), // bottom left748Vec3A::new(area.max.x, area.min.y, z_far), // bottom right749Vec3A::new(area.max.x, area.max.y, z_far), // top right750Vec3A::new(area.min.x, area.max.y, z_far), // top left751Vec3A::new(area.min.x, area.min.y, z_far), // bottom left752]753}754}755756impl FromWorld for OrthographicProjection {757fn from_world(_world: &mut World) -> Self {758OrthographicProjection::default_3d()759}760}761762impl OrthographicProjection {763/// Returns the default orthographic projection for a 2D context.764///765/// The near plane is set to a negative value so that the camera can still766/// render the scene when using positive z coordinates to order foreground elements.767pub fn default_2d() -> Self {768OrthographicProjection {769near: -1000.0,770..OrthographicProjection::default_3d()771}772}773774/// Returns the default orthographic projection for a 3D context.775///776/// The near plane is set to 0.0 so that the camera doesn't render777/// objects that are behind it.778pub fn default_3d() -> Self {779OrthographicProjection {780scale: 1.0,781near: 0.0,782far: 1000.0,783viewport_origin: Vec2::new(0.5, 0.5),784scaling_mode: ScalingMode::WindowSize,785area: Rect::new(-1.0, -1.0, 1.0, 1.0),786}787}788}789790791