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