bevy_render/camera/projection.rs
1use core::fmt::Debug;
2
3use crate::{primitives::Frustum, view::VisibilitySystems};
4use bevy_app::{App, Plugin, PostStartup, PostUpdate};
5use bevy_asset::AssetEvents;
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::prelude::*;
8use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
9use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
10use bevy_transform::{components::GlobalTransform, TransformSystem};
11use derive_more::derive::From;
12use serde::{Deserialize, Serialize};
13
14/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.
15///
16/// If you are using `bevy_pbr`, then you need to add `PbrProjectionPlugin` along with this.
17#[derive(Default)]
18pub struct CameraProjectionPlugin;
19
20impl Plugin for CameraProjectionPlugin {
21 fn build(&self, app: &mut App) {
22 app.register_type::<Projection>()
23 .register_type::<PerspectiveProjection>()
24 .register_type::<OrthographicProjection>()
25 .register_type::<CustomProjection>()
26 .add_systems(
27 PostStartup,
28 crate::camera::camera_system.in_set(CameraUpdateSystem),
29 )
30 .add_systems(
31 PostUpdate,
32 (
33 crate::camera::camera_system
34 .in_set(CameraUpdateSystem)
35 .before(AssetEvents),
36 crate::view::update_frusta
37 .in_set(VisibilitySystems::UpdateFrusta)
38 .after(crate::camera::camera_system)
39 .after(TransformSystem::TransformPropagate),
40 ),
41 );
42 }
43}
44
45/// Label for [`camera_system<T>`], shared across all `T`.
46///
47/// [`camera_system<T>`]: crate::camera::camera_system
48#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
49pub struct CameraUpdateSystem;
50
51/// Describes a type that can generate a projection matrix, allowing it to be added to a
52/// [`Camera`]'s [`Projection`] component.
53///
54/// Once implemented, the projection can be added to a camera using [`Projection::custom`].
55///
56/// The projection will be automatically updated as the render area is resized. This is useful when,
57/// for example, a projection type has a field like `fov` that should change when the window width
58/// is changed but not when the height changes.
59///
60/// This trait is implemented by bevy's built-in projections [`PerspectiveProjection`] and
61/// [`OrthographicProjection`].
62///
63/// [`Camera`]: crate::camera::Camera
64pub trait CameraProjection {
65 /// Generate the projection matrix.
66 fn get_clip_from_view(&self) -> Mat4;
67
68 /// Generate the projection matrix for a [`SubCameraView`](super::SubCameraView).
69 fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4;
70
71 /// When the area this camera renders to changes dimensions, this method will be automatically
72 /// called. Use this to update any projection properties that depend on the aspect ratio or
73 /// dimensions of the render area.
74 fn update(&mut self, width: f32, height: f32);
75
76 /// The far plane distance of the projection.
77 fn far(&self) -> f32;
78
79 /// The eight corners of the camera frustum, as defined by this projection.
80 ///
81 /// The corners should be provided in the following order: first the bottom right, top right,
82 /// top left, bottom left for the near plane, then similar for the far plane.
83 // TODO: This seems somewhat redundant with `compute_frustum`, and similarly should be possible
84 // to compute with a default impl.
85 fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];
86
87 /// Compute camera frustum for camera with given projection and transform.
88 ///
89 /// This code is called by [`update_frusta`](crate::view::visibility::update_frusta) system
90 /// for each camera to update its frustum.
91 fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum {
92 let clip_from_world =
93 self.get_clip_from_view() * camera_transform.compute_matrix().inverse();
94 Frustum::from_clip_from_world_custom_far(
95 &clip_from_world,
96 &camera_transform.translation(),
97 &camera_transform.back(),
98 self.far(),
99 )
100 }
101}
102
103mod sealed {
104 use super::CameraProjection;
105
106 /// A wrapper trait to make it possible to implement Clone for boxed [`super::CameraProjection`]
107 /// trait objects, without breaking object safety rules by making it `Sized`. Additional bounds
108 /// are included for downcasting, and fulfilling the trait bounds on `Projection`.
109 pub trait DynCameraProjection:
110 CameraProjection + core::fmt::Debug + Send + Sync + downcast_rs::Downcast
111 {
112 fn clone_box(&self) -> Box<dyn DynCameraProjection>;
113 }
114
115 downcast_rs::impl_downcast!(DynCameraProjection);
116
117 impl<T> DynCameraProjection for T
118 where
119 T: 'static + CameraProjection + core::fmt::Debug + Send + Sync + Clone,
120 {
121 fn clone_box(&self) -> Box<dyn DynCameraProjection> {
122 Box::new(self.clone())
123 }
124 }
125}
126
127/// Holds a dynamic [`CameraProjection`] trait object. Use [`Projection::custom()`] to construct a
128/// custom projection.
129///
130/// The contained dynamic object can be downcast into a static type using [`CustomProjection::get`].
131#[derive(Component, Debug, Reflect, Deref, DerefMut)]
132#[reflect(Default, Clone)]
133pub struct CustomProjection {
134 #[reflect(ignore)]
135 #[deref]
136 dyn_projection: Box<dyn sealed::DynCameraProjection>,
137}
138
139impl Default for CustomProjection {
140 fn default() -> Self {
141 Self {
142 dyn_projection: Box::new(PerspectiveProjection::default()),
143 }
144 }
145}
146
147impl Clone for CustomProjection {
148 fn clone(&self) -> Self {
149 Self {
150 dyn_projection: self.dyn_projection.clone_box(),
151 }
152 }
153}
154
155impl CustomProjection {
156 /// Returns a reference to the [`CameraProjection`] `P`.
157 ///
158 /// Returns `None` if this dynamic object is not a projection of type `P`.
159 ///
160 /// ```
161 /// # use bevy_render::prelude::{Projection, PerspectiveProjection};
162 /// // For simplicity's sake, use perspective as a custom projection:
163 /// let projection = Projection::custom(PerspectiveProjection::default());
164 /// let Projection::Custom(custom) = projection else { return };
165 ///
166 /// // At this point the projection type is erased.
167 /// // We can use `get()` if we know what kind of projection we have.
168 /// let perspective = custom.get::<PerspectiveProjection>().unwrap();
169 ///
170 /// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);
171 /// ```
172 pub fn get<P>(&self) -> Option<&P>
173 where
174 P: CameraProjection + Debug + Send + Sync + Clone + 'static,
175 {
176 self.dyn_projection.downcast_ref()
177 }
178
179 /// Returns a mutable reference to the [`CameraProjection`] `P`.
180 ///
181 /// Returns `None` if this dynamic object is not a projection of type `P`.
182 ///
183 /// ```
184 /// # use bevy_render::prelude::{Projection, PerspectiveProjection};
185 /// // For simplicity's sake, use perspective as a custom projection:
186 /// let mut projection = Projection::custom(PerspectiveProjection::default());
187 /// let Projection::Custom(mut custom) = projection else { return };
188 ///
189 /// // At this point the projection type is erased.
190 /// // We can use `get_mut()` if we know what kind of projection we have.
191 /// let perspective = custom.get_mut::<PerspectiveProjection>().unwrap();
192 ///
193 /// assert_eq!(perspective.fov, PerspectiveProjection::default().fov);
194 /// perspective.fov = 1.0;
195 /// ```
196 pub fn get_mut<P>(&mut self) -> Option<&mut P>
197 where
198 P: CameraProjection + Debug + Send + Sync + Clone + 'static,
199 {
200 self.dyn_projection.downcast_mut()
201 }
202}
203
204/// Component that defines how to compute a [`Camera`]'s projection matrix.
205///
206/// Common projections, like perspective and orthographic, are provided out of the box to handle the
207/// majority of use cases. Custom projections can be added using the [`CameraProjection`] trait and
208/// the [`Projection::custom`] constructor.
209///
210/// ## What's a projection?
211///
212/// A camera projection essentially describes how 3d points from the point of view of a camera are
213/// projected onto a 2d screen. This is where properties like a camera's field of view are defined.
214/// More specifically, a projection is a 4x4 matrix that transforms points from view space (the
215/// point of view of the camera) into clip space. Clip space is almost, but not quite, equivalent to
216/// the rectangle that is rendered to your screen, with a depth axis. Any points that land outside
217/// the bounds of this cuboid are "clipped" and not rendered.
218///
219/// You can also think of the projection as the thing that describes the shape of a camera's
220/// frustum: the volume in 3d space that is visible to a camera.
221///
222/// [`Camera`]: crate::camera::Camera
223#[derive(Component, Debug, Clone, Reflect, From)]
224#[reflect(Component, Default, Debug, Clone)]
225pub enum Projection {
226 Perspective(PerspectiveProjection),
227 Orthographic(OrthographicProjection),
228 Custom(CustomProjection),
229}
230
231impl Projection {
232 /// Construct a new custom camera projection from a type that implements [`CameraProjection`].
233 pub fn custom<P>(projection: P) -> Self
234 where
235 // Implementation note: pushing these trait bounds all the way out to this function makes
236 // errors nice for users. If a trait is missing, they will get a helpful error telling them
237 // that, say, the `Debug` implementation is missing. Wrapping these traits behind a super
238 // trait or some other indirection will make the errors harder to understand.
239 //
240 // For example, we don't use the `DynCameraProjection`` trait bound, because it is not the
241 // trait the user should be implementing - they only need to worry about implementing
242 // `CameraProjection`.
243 P: CameraProjection + Debug + Send + Sync + Clone + 'static,
244 {
245 Projection::Custom(CustomProjection {
246 dyn_projection: Box::new(projection),
247 })
248 }
249}
250
251impl CameraProjection for Projection {
252 fn get_clip_from_view(&self) -> Mat4 {
253 match self {
254 Projection::Perspective(projection) => projection.get_clip_from_view(),
255 Projection::Orthographic(projection) => projection.get_clip_from_view(),
256 Projection::Custom(projection) => projection.get_clip_from_view(),
257 }
258 }
259
260 fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
261 match self {
262 Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view),
263 Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view),
264 Projection::Custom(projection) => projection.get_clip_from_view_for_sub(sub_view),
265 }
266 }
267
268 fn update(&mut self, width: f32, height: f32) {
269 match self {
270 Projection::Perspective(projection) => projection.update(width, height),
271 Projection::Orthographic(projection) => projection.update(width, height),
272 Projection::Custom(projection) => projection.update(width, height),
273 }
274 }
275
276 fn far(&self) -> f32 {
277 match self {
278 Projection::Perspective(projection) => projection.far(),
279 Projection::Orthographic(projection) => projection.far(),
280 Projection::Custom(projection) => projection.far(),
281 }
282 }
283
284 fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
285 match self {
286 Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far),
287 Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far),
288 Projection::Custom(projection) => projection.get_frustum_corners(z_near, z_far),
289 }
290 }
291}
292
293impl Default for Projection {
294 fn default() -> Self {
295 Projection::Perspective(Default::default())
296 }
297}
298
299/// A 3D camera projection in which distant objects appear smaller than close objects.
300#[derive(Debug, Clone, Reflect)]
301#[reflect(Default, Debug, Clone)]
302pub struct PerspectiveProjection {
303 /// The vertical field of view (FOV) in radians.
304 ///
305 /// Defaults to a value of π/4 radians or 45 degrees.
306 pub fov: f32,
307
308 /// The aspect ratio (width divided by height) of the viewing frustum.
309 ///
310 /// Bevy's [`camera_system`](crate::camera::camera_system) automatically
311 /// updates this value when the aspect ratio of the associated window changes.
312 ///
313 /// Defaults to a value of `1.0`.
314 pub aspect_ratio: f32,
315
316 /// The distance from the camera in world units of the viewing frustum's near plane.
317 ///
318 /// Objects closer to the camera than this value will not be visible.
319 ///
320 /// Defaults to a value of `0.1`.
321 pub near: f32,
322
323 /// The distance from the camera in world units of the viewing frustum's far plane.
324 ///
325 /// Objects farther from the camera than this value will not be visible.
326 ///
327 /// Defaults to a value of `1000.0`.
328 pub far: f32,
329}
330
331impl CameraProjection for PerspectiveProjection {
332 fn get_clip_from_view(&self) -> Mat4 {
333 Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near)
334 }
335
336 fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
337 let full_width = sub_view.full_size.x as f32;
338 let full_height = sub_view.full_size.y as f32;
339 let sub_width = sub_view.size.x as f32;
340 let sub_height = sub_view.size.y as f32;
341 let offset_x = sub_view.offset.x;
342 // Y-axis increases from top to bottom
343 let offset_y = full_height - (sub_view.offset.y + sub_height);
344
345 let full_aspect = full_width / full_height;
346
347 // Original frustum parameters
348 let top = self.near * ops::tan(0.5 * self.fov);
349 let bottom = -top;
350 let right = top * full_aspect;
351 let left = -right;
352
353 // Calculate scaling factors
354 let width = right - left;
355 let height = top - bottom;
356
357 // Calculate the new frustum parameters
358 let left_prime = left + (width * offset_x) / full_width;
359 let right_prime = left + (width * (offset_x + sub_width)) / full_width;
360 let bottom_prime = bottom + (height * offset_y) / full_height;
361 let top_prime = bottom + (height * (offset_y + sub_height)) / full_height;
362
363 // Compute the new projection matrix
364 let x = (2.0 * self.near) / (right_prime - left_prime);
365 let y = (2.0 * self.near) / (top_prime - bottom_prime);
366 let a = (right_prime + left_prime) / (right_prime - left_prime);
367 let b = (top_prime + bottom_prime) / (top_prime - bottom_prime);
368
369 Mat4::from_cols(
370 Vec4::new(x, 0.0, 0.0, 0.0),
371 Vec4::new(0.0, y, 0.0, 0.0),
372 Vec4::new(a, b, 0.0, -1.0),
373 Vec4::new(0.0, 0.0, self.near, 0.0),
374 )
375 }
376
377 fn update(&mut self, width: f32, height: f32) {
378 self.aspect_ratio = AspectRatio::try_new(width, height)
379 .expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values")
380 .ratio();
381 }
382
383 fn far(&self) -> f32 {
384 self.far
385 }
386
387 fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
388 let tan_half_fov = ops::tan(self.fov / 2.);
389 let a = z_near.abs() * tan_half_fov;
390 let b = z_far.abs() * tan_half_fov;
391 let aspect_ratio = self.aspect_ratio;
392 // NOTE: These vertices are in the specific order required by [`calculate_cascade`].
393 [
394 Vec3A::new(a * aspect_ratio, -a, z_near), // bottom right
395 Vec3A::new(a * aspect_ratio, a, z_near), // top right
396 Vec3A::new(-a * aspect_ratio, a, z_near), // top left
397 Vec3A::new(-a * aspect_ratio, -a, z_near), // bottom left
398 Vec3A::new(b * aspect_ratio, -b, z_far), // bottom right
399 Vec3A::new(b * aspect_ratio, b, z_far), // top right
400 Vec3A::new(-b * aspect_ratio, b, z_far), // top left
401 Vec3A::new(-b * aspect_ratio, -b, z_far), // bottom left
402 ]
403 }
404}
405
406impl Default for PerspectiveProjection {
407 fn default() -> Self {
408 PerspectiveProjection {
409 fov: core::f32::consts::PI / 4.0,
410 near: 0.1,
411 far: 1000.0,
412 aspect_ratio: 1.0,
413 }
414 }
415}
416
417/// Scaling mode for [`OrthographicProjection`].
418///
419/// The effect of these scaling modes are combined with the [`OrthographicProjection::scale`] property.
420///
421/// For example, if the scaling mode is `ScalingMode::Fixed { width: 100.0, height: 300 }` and the scale is `2.0`,
422/// the projection will be 200 world units wide and 600 world units tall.
423///
424/// # Examples
425///
426/// Configure the orthographic projection to two world units per window height:
427///
428/// ```
429/// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
430/// let projection = Projection::Orthographic(OrthographicProjection {
431/// scaling_mode: ScalingMode::FixedVertical { viewport_height: 2.0 },
432/// ..OrthographicProjection::default_2d()
433/// });
434/// ```
435#[derive(Default, Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
436#[reflect(Serialize, Deserialize, Default, Clone)]
437pub enum ScalingMode {
438 /// Match the viewport size.
439 ///
440 /// With a scale of 1, lengths in world units will map 1:1 with the number of pixels used to render it.
441 /// For example, if we have a 64x64 sprite with a [`Transform::scale`](bevy_transform::prelude::Transform) of 1.0,
442 /// no custom size and no inherited scale, the sprite will be 64 world units wide and 64 world units tall.
443 /// When rendered with [`OrthographicProjection::scaling_mode`] set to `WindowSize` when the window scale factor is 1
444 /// the sprite will be rendered at 64 pixels wide and 64 pixels tall.
445 ///
446 /// Changing any of these properties will multiplicatively affect the final size.
447 #[default]
448 WindowSize,
449 /// Manually specify the projection's size, ignoring window resizing. The image will stretch.
450 ///
451 /// Arguments describe the area of the world that is shown (in world units).
452 Fixed { width: f32, height: f32 },
453 /// Keeping the aspect ratio while the axes can't be smaller than given minimum.
454 ///
455 /// Arguments are in world units.
456 AutoMin { min_width: f32, min_height: f32 },
457 /// Keeping the aspect ratio while the axes can't be bigger than given maximum.
458 ///
459 /// Arguments are in world units.
460 AutoMax { max_width: f32, max_height: f32 },
461 /// Keep the projection's height constant; width will be adjusted to match aspect ratio.
462 ///
463 /// The argument is the desired height of the projection in world units.
464 FixedVertical { viewport_height: f32 },
465 /// Keep the projection's width constant; height will be adjusted to match aspect ratio.
466 ///
467 /// The argument is the desired width of the projection in world units.
468 FixedHorizontal { viewport_width: f32 },
469}
470
471/// Project a 3D space onto a 2D surface using parallel lines, i.e., unlike [`PerspectiveProjection`],
472/// the size of objects remains the same regardless of their distance to the camera.
473///
474/// The volume contained in the projection is called the *view frustum*. Since the viewport is rectangular
475/// and projection lines are parallel, the view frustum takes the shape of a cuboid.
476///
477/// Note that the scale of the projection and the apparent size of objects are inversely proportional.
478/// As the size of the projection increases, the size of objects decreases.
479///
480/// # Examples
481///
482/// Configure the orthographic projection to one world unit per 100 window pixels:
483///
484/// ```
485/// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
486/// let projection = Projection::Orthographic(OrthographicProjection {
487/// scaling_mode: ScalingMode::WindowSize,
488/// scale: 0.01,
489/// ..OrthographicProjection::default_2d()
490/// });
491/// ```
492#[derive(Debug, Clone, Reflect)]
493#[reflect(Debug, FromWorld, Clone)]
494pub struct OrthographicProjection {
495 /// The distance of the near clipping plane in world units.
496 ///
497 /// Objects closer than this will not be rendered.
498 ///
499 /// Defaults to `0.0`
500 pub near: f32,
501 /// The distance of the far clipping plane in world units.
502 ///
503 /// Objects further than this will not be rendered.
504 ///
505 /// Defaults to `1000.0`
506 pub far: f32,
507 /// Specifies the origin of the viewport as a normalized position from 0 to 1, where (0, 0) is the bottom left
508 /// and (1, 1) is the top right. This determines where the camera's position sits inside the viewport.
509 ///
510 /// When the projection scales due to viewport resizing, the position of the camera, and thereby `viewport_origin`,
511 /// remains at the same relative point.
512 ///
513 /// Consequently, this is pivot point when scaling. With a bottom left pivot, the projection will expand
514 /// upwards and to the right. With a top right pivot, the projection will expand downwards and to the left.
515 /// Values in between will caused the projection to scale proportionally on each axis.
516 ///
517 /// Defaults to `(0.5, 0.5)`, which makes scaling affect opposite sides equally, keeping the center
518 /// point of the viewport centered.
519 pub viewport_origin: Vec2,
520 /// How the projection will scale to the viewport.
521 ///
522 /// Defaults to [`ScalingMode::WindowSize`],
523 /// and works in concert with [`OrthographicProjection::scale`] to determine the final effect.
524 ///
525 /// For simplicity, zooming should be done by changing [`OrthographicProjection::scale`],
526 /// rather than changing the parameters of the scaling mode.
527 pub scaling_mode: ScalingMode,
528 /// Scales the projection.
529 ///
530 /// As scale increases, the apparent size of objects decreases, and vice versa.
531 ///
532 /// Note: scaling can be set by [`scaling_mode`](Self::scaling_mode) as well.
533 /// This parameter scales on top of that.
534 ///
535 /// This property is particularly useful in implementing zoom functionality.
536 ///
537 /// Defaults to `1.0`, which under standard settings corresponds to a 1:1 mapping of world units to rendered pixels.
538 /// See [`ScalingMode::WindowSize`] for more information.
539 pub scale: f32,
540 /// The area that the projection covers relative to `viewport_origin`.
541 ///
542 /// Bevy's [`camera_system`](crate::camera::camera_system) automatically
543 /// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.
544 /// In this case, `area` should not be manually modified.
545 ///
546 /// It may be necessary to set this manually for shadow projections and such.
547 pub area: Rect,
548}
549
550impl CameraProjection for OrthographicProjection {
551 fn get_clip_from_view(&self) -> Mat4 {
552 Mat4::orthographic_rh(
553 self.area.min.x,
554 self.area.max.x,
555 self.area.min.y,
556 self.area.max.y,
557 // NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
558 // This is for interoperability with pipelines using infinite reverse perspective projections.
559 self.far,
560 self.near,
561 )
562 }
563
564 fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
565 let full_width = sub_view.full_size.x as f32;
566 let full_height = sub_view.full_size.y as f32;
567 let offset_x = sub_view.offset.x;
568 let offset_y = sub_view.offset.y;
569 let sub_width = sub_view.size.x as f32;
570 let sub_height = sub_view.size.y as f32;
571
572 let full_aspect = full_width / full_height;
573
574 // Base the vertical size on self.area and adjust the horizontal size
575 let top = self.area.max.y;
576 let bottom = self.area.min.y;
577 let ortho_height = top - bottom;
578 let ortho_width = ortho_height * full_aspect;
579
580 // Center the orthographic area horizontally
581 let center_x = (self.area.max.x + self.area.min.x) / 2.0;
582 let left = center_x - ortho_width / 2.0;
583 let right = center_x + ortho_width / 2.0;
584
585 // Calculate scaling factors
586 let scale_w = (right - left) / full_width;
587 let scale_h = (top - bottom) / full_height;
588
589 // Calculate the new orthographic bounds
590 let left_prime = left + scale_w * offset_x;
591 let right_prime = left_prime + scale_w * sub_width;
592 let top_prime = top - scale_h * offset_y;
593 let bottom_prime = top_prime - scale_h * sub_height;
594
595 Mat4::orthographic_rh(
596 left_prime,
597 right_prime,
598 bottom_prime,
599 top_prime,
600 // NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
601 // This is for interoperability with pipelines using infinite reverse perspective projections.
602 self.far,
603 self.near,
604 )
605 }
606
607 fn update(&mut self, width: f32, height: f32) {
608 let (projection_width, projection_height) = match self.scaling_mode {
609 ScalingMode::WindowSize => (width, height),
610 ScalingMode::AutoMin {
611 min_width,
612 min_height,
613 } => {
614 // Compare Pixels of current width and minimal height and Pixels of minimal width with current height.
615 // 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.
616 if width * min_height > min_width * height {
617 (width * min_height / height, min_height)
618 } else {
619 (min_width, height * min_width / width)
620 }
621 }
622 ScalingMode::AutoMax {
623 max_width,
624 max_height,
625 } => {
626 // Compare Pixels of current width and maximal height and Pixels of maximal width with current height.
627 // 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.
628 if width * max_height < max_width * height {
629 (width * max_height / height, max_height)
630 } else {
631 (max_width, height * max_width / width)
632 }
633 }
634 ScalingMode::FixedVertical { viewport_height } => {
635 (width * viewport_height / height, viewport_height)
636 }
637 ScalingMode::FixedHorizontal { viewport_width } => {
638 (viewport_width, height * viewport_width / width)
639 }
640 ScalingMode::Fixed { width, height } => (width, height),
641 };
642
643 let origin_x = projection_width * self.viewport_origin.x;
644 let origin_y = projection_height * self.viewport_origin.y;
645
646 self.area = Rect::new(
647 self.scale * -origin_x,
648 self.scale * -origin_y,
649 self.scale * (projection_width - origin_x),
650 self.scale * (projection_height - origin_y),
651 );
652 }
653
654 fn far(&self) -> f32 {
655 self.far
656 }
657
658 fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
659 let area = self.area;
660 // NOTE: These vertices are in the specific order required by [`calculate_cascade`].
661 [
662 Vec3A::new(area.max.x, area.min.y, z_near), // bottom right
663 Vec3A::new(area.max.x, area.max.y, z_near), // top right
664 Vec3A::new(area.min.x, area.max.y, z_near), // top left
665 Vec3A::new(area.min.x, area.min.y, z_near), // bottom left
666 Vec3A::new(area.max.x, area.min.y, z_far), // bottom right
667 Vec3A::new(area.max.x, area.max.y, z_far), // top right
668 Vec3A::new(area.min.x, area.max.y, z_far), // top left
669 Vec3A::new(area.min.x, area.min.y, z_far), // bottom left
670 ]
671 }
672}
673
674impl FromWorld for OrthographicProjection {
675 fn from_world(_world: &mut World) -> Self {
676 OrthographicProjection::default_3d()
677 }
678}
679
680impl OrthographicProjection {
681 /// Returns the default orthographic projection for a 2D context.
682 ///
683 /// The near plane is set to a negative value so that the camera can still
684 /// render the scene when using positive z coordinates to order foreground elements.
685 pub fn default_2d() -> Self {
686 OrthographicProjection {
687 near: -1000.0,
688 ..OrthographicProjection::default_3d()
689 }
690 }
691
692 /// Returns the default orthographic projection for a 3D context.
693 ///
694 /// The near plane is set to 0.0 so that the camera doesn't render
695 /// objects that are behind it.
696 pub fn default_3d() -> Self {
697 OrthographicProjection {
698 scale: 1.0,
699 near: 0.0,
700 far: 1000.0,
701 viewport_origin: Vec2::new(0.5, 0.5),
702 scaling_mode: ScalingMode::WindowSize,
703 area: Rect::new(-1.0, -1.0, 1.0, 1.0),
704 }
705 }
706}