Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_camera/src/projection.rs
9358 views
1
use core::fmt::Debug;
2
use core::ops::{Deref, DerefMut};
3
4
use crate::{primitives::Frustum, visibility::VisibilitySystems};
5
use bevy_app::{App, Plugin, PostUpdate};
6
use bevy_ecs::prelude::*;
7
use bevy_math::{ops, primitives::ViewFrustum, vec4, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
8
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
9
use bevy_transform::{components::GlobalTransform, TransformSystems};
10
use derive_more::derive::From;
11
use 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)]
17
pub struct CameraProjectionPlugin;
18
19
impl 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
43
pub 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(ViewFrustum::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
81
mod 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)]
111
pub struct CustomProjection {
112
#[reflect(ignore)]
113
dyn_projection: Box<dyn sealed::DynCameraProjection>,
114
}
115
116
impl Default for CustomProjection {
117
fn default() -> Self {
118
Self {
119
dyn_projection: Box::new(PerspectiveProjection::default()),
120
}
121
}
122
}
123
124
impl Clone for CustomProjection {
125
fn clone(&self) -> Self {
126
Self {
127
dyn_projection: self.dyn_projection.clone_box(),
128
}
129
}
130
}
131
132
impl 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
181
impl Deref for CustomProjection {
182
type Target = dyn CameraProjection;
183
184
fn deref(&self) -> &Self::Target {
185
self.dyn_projection.as_ref()
186
}
187
}
188
189
impl 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)]
216
pub enum Projection {
217
Perspective(PerspectiveProjection),
218
Orthographic(OrthographicProjection),
219
Custom(CustomProjection),
220
}
221
222
impl 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
252
impl 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
264
impl 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
274
impl 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)]
283
pub 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
/// The orientation of a custom clipping plane, as well as its distance from
312
/// the camera.
313
///
314
/// If you supply a plane here, anything in front of the plane will be
315
/// clipped out. This is useful for portals and mirrors, in order to clip
316
/// any geometry that would pass through the plane of the portal or mirror.
317
///
318
/// The X, Y, and Z components of the vector describe its normal, in view
319
/// space. This normal vector must have length 1, and it should point away
320
/// from the camera. (That is, only geometry on the side of the plane that
321
/// the normal points toward will be rendered.) The W component of the
322
/// vector must be the *negative shortest signed distance* from the camera
323
/// to the plane, again in view space. This final component can also be
324
/// computed as -(N · Q), where N is the normal of the plane and Q is any
325
/// point on it.
326
///
327
/// By default, this is (0, 0, -1, -[`Self::near`]), which describes a near
328
/// plane located [`Self::near`] meters away pointing directly away from the
329
/// camera.
330
///
331
/// See the `calculate_mirror_camera_transform_and_projection` function in
332
/// the `mirror` example for an exhaustive example of usage.
333
pub near_clip_plane: Vec4,
334
}
335
336
impl CameraProjection for PerspectiveProjection {
337
fn get_clip_from_view(&self) -> Mat4 {
338
let mut matrix =
339
Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near);
340
self.adjust_perspective_matrix_for_clip_plane(&mut matrix);
341
matrix
342
}
343
344
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
345
let full_width = sub_view.full_size.x as f32;
346
let full_height = sub_view.full_size.y as f32;
347
let sub_width = sub_view.size.x as f32;
348
let sub_height = sub_view.size.y as f32;
349
let offset_x = sub_view.offset.x;
350
// Y-axis increases from top to bottom
351
let offset_y = full_height - (sub_view.offset.y + sub_height);
352
353
let full_aspect = full_width / full_height;
354
355
// Original frustum parameters
356
let top = self.near * ops::tan(0.5 * self.fov);
357
let bottom = -top;
358
let right = top * full_aspect;
359
let left = -right;
360
361
// Calculate scaling factors
362
let width = right - left;
363
let height = top - bottom;
364
365
// Calculate the new frustum parameters
366
let left_prime = left + (width * offset_x) / full_width;
367
let right_prime = left + (width * (offset_x + sub_width)) / full_width;
368
let bottom_prime = bottom + (height * offset_y) / full_height;
369
let top_prime = bottom + (height * (offset_y + sub_height)) / full_height;
370
371
// Compute the new projection matrix
372
let x = (2.0 * self.near) / (right_prime - left_prime);
373
let y = (2.0 * self.near) / (top_prime - bottom_prime);
374
let a = (right_prime + left_prime) / (right_prime - left_prime);
375
let b = (top_prime + bottom_prime) / (top_prime - bottom_prime);
376
377
let mut matrix = Mat4::from_cols(
378
Vec4::new(x, 0.0, 0.0, 0.0),
379
Vec4::new(0.0, y, 0.0, 0.0),
380
Vec4::new(a, b, 0.0, -1.0),
381
Vec4::new(0.0, 0.0, self.near, 0.0),
382
);
383
384
self.adjust_perspective_matrix_for_clip_plane(&mut matrix);
385
matrix
386
}
387
388
fn update(&mut self, width: f32, height: f32) {
389
self.aspect_ratio = AspectRatio::try_new(width, height)
390
.expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values")
391
.ratio();
392
}
393
394
fn far(&self) -> f32 {
395
self.far
396
}
397
398
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
399
let tan_half_fov = ops::tan(self.fov / 2.);
400
let a = z_near.abs() * tan_half_fov;
401
let b = z_far.abs() * tan_half_fov;
402
let aspect_ratio = self.aspect_ratio;
403
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
404
[
405
Vec3A::new(a * aspect_ratio, -a, z_near), // bottom right
406
Vec3A::new(a * aspect_ratio, a, z_near), // top right
407
Vec3A::new(-a * aspect_ratio, a, z_near), // top left
408
Vec3A::new(-a * aspect_ratio, -a, z_near), // bottom left
409
Vec3A::new(b * aspect_ratio, -b, z_far), // bottom right
410
Vec3A::new(b * aspect_ratio, b, z_far), // top right
411
Vec3A::new(-b * aspect_ratio, b, z_far), // top left
412
Vec3A::new(-b * aspect_ratio, -b, z_far), // bottom left
413
]
414
}
415
}
416
417
impl Default for PerspectiveProjection {
418
fn default() -> Self {
419
PerspectiveProjection {
420
fov: core::f32::consts::PI / 4.0,
421
near: 0.1,
422
far: 1000.0,
423
aspect_ratio: 1.0,
424
near_clip_plane: vec4(0.0, 0.0, -1.0, -0.1),
425
}
426
}
427
}
428
429
impl PerspectiveProjection {
430
/// Adjusts the perspective matrix for an oblique clip plane if necessary.
431
///
432
/// This changes the near and (infinite) far planes so that they correctly
433
/// clip everything in front of the [`Self::near_clip_plane`]. See [Lengyel
434
/// 2005] for an exhaustive treatment of the way this works. Custom near
435
/// clip planes are typically used for portals and mirrors; see
436
/// `examples/3d/mirror.rs` for an example of usage.
437
fn adjust_perspective_matrix_for_clip_plane(&self, matrix: &mut Mat4) {
438
// If we don't have an oblique clip plane, save ourselves the trouble.
439
if self.near_clip_plane == vec4(0.0, 0.0, -1.0, -self.near) {
440
return;
441
}
442
443
// To understand this, refer to [Lengyel 2005]. The notation follows the
444
// paper. The formulas are different because the paper uses a standard
445
// OpenGL convention (near -1, far 1), while we use a reversed Vulkan
446
// convention (near 1, far 0).
447
//
448
// [Lengyel 2005]: https://terathon.com/lengyel/Lengyel-Oblique.pdf
449
let c = self.near_clip_plane;
450
451
// First, calculate the position of Q′, the corner in clip space lying
452
// opposite from the near clip plane. This is identical to equation (7).
453
// Note that this is a point at infinity in view space, because we use
454
// an infinite far plane, but in clip space it's finite.
455
let q_prime = vec4(c.x.signum(), c.y.signum(), 0.0, 1.0);
456
457
// Now convert that point to view space. This *will* be a point at
458
// infinity, but that's OK because we're in homogeneous coordinates.
459
let q = matrix.inverse() * q_prime;
460
461
// Here we're computing the scaling factor to apply to the near plane so
462
// that the far plane will intersect Q. This one differs from the paper.
463
// Using the notation Mᵢ to mean the *i*th row of the matrix M, start by
464
// observing that the near plane (z = 1) is described by M₄ - M₃ and the
465
// far plane (z = 0) is described by simply M₃. So:
466
//
467
// * Equation (4) becomes C = M₄ - M₃.
468
// * Equation (5) becomes M′₃ = M₄ - C.
469
// * Equation (6) becomes F = M′₃ = M₄ - C.
470
// * Equation (8) becomes F = M₄ - aC.
471
// * Equation (9) becomes F · Q = 0 ⇒ (M₄ - aC) · Q = 0.
472
//
473
// And, solving the modified equation (9), we get:
474
//
475
// M₄ · Q
476
// a = ⎯⎯⎯⎯⎯⎯
477
// C · Q
478
//
479
// Because M₄ = (0, 0, -1, 0) (just as it is in the paper), this reduces to:
480
//
481
// -Qz
482
// a = ⎯⎯⎯⎯⎯
483
// C · Q
484
//
485
// Which is what we calculate here.
486
let a = -q.z / c.dot(q);
487
488
// Finally, we have the revised equation (10), which is M′₃ = M₄ - aC.
489
// Similarly to the above, this simplifies to M′₃ = (0, 0, -1, 0) - aC.
490
let m3_prime = Vec4::NEG_Z - c * a;
491
492
// We have the replacement third row; write it in.
493
matrix.x_axis.z = m3_prime.x;
494
matrix.y_axis.z = m3_prime.y;
495
matrix.z_axis.z = m3_prime.z;
496
matrix.w_axis.z = m3_prime.w;
497
}
498
}
499
500
/// Scaling mode for [`OrthographicProjection`].
501
///
502
/// The effect of these scaling modes are combined with the [`OrthographicProjection::scale`] property.
503
///
504
/// For example, if the scaling mode is `ScalingMode::Fixed { width: 100.0, height: 300 }` and the scale is `2.0`,
505
/// the projection will be 200 world units wide and 600 world units tall.
506
///
507
/// # Examples
508
///
509
/// Configure the orthographic projection to two world units per window height:
510
///
511
/// ```
512
/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};
513
/// let projection = Projection::Orthographic(OrthographicProjection {
514
/// scaling_mode: ScalingMode::FixedVertical { viewport_height: 2.0 },
515
/// ..OrthographicProjection::default_2d()
516
/// });
517
/// ```
518
#[derive(Default, Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
519
#[reflect(Serialize, Deserialize, Default, Clone)]
520
pub enum ScalingMode {
521
/// Match the viewport size.
522
///
523
/// With a scale of 1, lengths in world units will map 1:1 with the number of pixels used to render it.
524
/// For example, if we have a 64x64 sprite with a [`Transform::scale`](bevy_transform::prelude::Transform) of 1.0,
525
/// no custom size and no inherited scale, the sprite will be 64 world units wide and 64 world units tall.
526
/// When rendered with [`OrthographicProjection::scaling_mode`] set to `WindowSize` when the window scale factor is 1
527
/// the sprite will be rendered at 64 pixels wide and 64 pixels tall.
528
///
529
/// Changing any of these properties will multiplicatively affect the final size.
530
#[default]
531
WindowSize,
532
/// Manually specify the projection's size, ignoring window resizing. The image will stretch.
533
///
534
/// Arguments describe the area of the world that is shown (in world units).
535
Fixed { width: f32, height: f32 },
536
/// Keeping the aspect ratio while the axes can't be smaller than given minimum.
537
///
538
/// Arguments are in world units.
539
AutoMin { min_width: f32, min_height: f32 },
540
/// Keeping the aspect ratio while the axes can't be bigger than given maximum.
541
///
542
/// Arguments are in world units.
543
AutoMax { max_width: f32, max_height: f32 },
544
/// Keep the projection's height constant; width will be adjusted to match aspect ratio.
545
///
546
/// The argument is the desired height of the projection in world units.
547
FixedVertical { viewport_height: f32 },
548
/// Keep the projection's width constant; height will be adjusted to match aspect ratio.
549
///
550
/// The argument is the desired width of the projection in world units.
551
FixedHorizontal { viewport_width: f32 },
552
}
553
554
/// Project a 3D space onto a 2D surface using parallel lines, i.e., unlike [`PerspectiveProjection`],
555
/// the size of objects remains the same regardless of their distance to the camera.
556
///
557
/// The volume contained in the projection is called the *view frustum*. Since the viewport is rectangular
558
/// and projection lines are parallel, the view frustum takes the shape of a cuboid.
559
///
560
/// Note that the scale of the projection and the apparent size of objects are inversely proportional.
561
/// As the size of the projection increases, the size of objects decreases.
562
///
563
/// # Examples
564
///
565
/// Configure the orthographic projection to one world unit per 100 window pixels:
566
///
567
/// ```
568
/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};
569
/// let projection = Projection::Orthographic(OrthographicProjection {
570
/// scaling_mode: ScalingMode::WindowSize,
571
/// scale: 0.01,
572
/// ..OrthographicProjection::default_2d()
573
/// });
574
/// ```
575
#[derive(Debug, Clone, Reflect)]
576
#[reflect(Debug, FromWorld, Clone)]
577
pub struct OrthographicProjection {
578
/// The distance of the near clipping plane in world units.
579
///
580
/// Objects closer than this will not be rendered.
581
///
582
/// Defaults to `0.0`
583
pub near: f32,
584
/// The distance of the far clipping plane in world units.
585
///
586
/// Objects further than this will not be rendered.
587
///
588
/// Defaults to `1000.0`
589
pub far: f32,
590
/// Specifies the origin of the viewport as a normalized position from 0 to 1, where (0, 0) is the bottom left
591
/// and (1, 1) is the top right. This determines where the camera's position sits inside the viewport.
592
///
593
/// When the projection scales due to viewport resizing, the position of the camera, and thereby `viewport_origin`,
594
/// remains at the same relative point.
595
///
596
/// Consequently, this is pivot point when scaling. With a bottom left pivot, the projection will expand
597
/// upwards and to the right. With a top right pivot, the projection will expand downwards and to the left.
598
/// Values in between will caused the projection to scale proportionally on each axis.
599
///
600
/// Defaults to `(0.5, 0.5)`, which makes scaling affect opposite sides equally, keeping the center
601
/// point of the viewport centered.
602
pub viewport_origin: Vec2,
603
/// How the projection will scale to the viewport.
604
///
605
/// Defaults to [`ScalingMode::WindowSize`],
606
/// and works in concert with [`OrthographicProjection::scale`] to determine the final effect.
607
///
608
/// For simplicity, zooming should be done by changing [`OrthographicProjection::scale`],
609
/// rather than changing the parameters of the scaling mode.
610
pub scaling_mode: ScalingMode,
611
/// Scales the projection.
612
///
613
/// As scale increases, the apparent size of objects decreases, and vice versa.
614
///
615
/// Note: scaling can be set by [`scaling_mode`](Self::scaling_mode) as well.
616
/// This parameter scales on top of that.
617
///
618
/// This property is particularly useful in implementing zoom functionality.
619
///
620
/// Defaults to `1.0`, which under standard settings corresponds to a 1:1 mapping of world units to rendered pixels.
621
/// See [`ScalingMode::WindowSize`] for more information.
622
pub scale: f32,
623
/// The area that the projection covers relative to `viewport_origin`.
624
///
625
/// Bevy's `camera_system` automatically
626
/// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.
627
/// In this case, `area` should not be manually modified.
628
///
629
/// It may be necessary to set this manually for shadow projections and such.
630
pub area: Rect,
631
}
632
633
impl CameraProjection for OrthographicProjection {
634
fn get_clip_from_view(&self) -> Mat4 {
635
Mat4::orthographic_rh(
636
self.area.min.x,
637
self.area.max.x,
638
self.area.min.y,
639
self.area.max.y,
640
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
641
// This is for interoperability with pipelines using infinite reverse perspective projections.
642
self.far,
643
self.near,
644
)
645
}
646
647
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
648
let full_width = sub_view.full_size.x as f32;
649
let full_height = sub_view.full_size.y as f32;
650
let offset_x = sub_view.offset.x;
651
let offset_y = sub_view.offset.y;
652
let sub_width = sub_view.size.x as f32;
653
let sub_height = sub_view.size.y as f32;
654
655
let full_aspect = full_width / full_height;
656
657
// Base the vertical size on self.area and adjust the horizontal size
658
let top = self.area.max.y;
659
let bottom = self.area.min.y;
660
let ortho_height = top - bottom;
661
let ortho_width = ortho_height * full_aspect;
662
663
// Center the orthographic area horizontally
664
let center_x = (self.area.max.x + self.area.min.x) / 2.0;
665
let left = center_x - ortho_width / 2.0;
666
let right = center_x + ortho_width / 2.0;
667
668
// Calculate scaling factors
669
let scale_w = (right - left) / full_width;
670
let scale_h = (top - bottom) / full_height;
671
672
// Calculate the new orthographic bounds
673
let left_prime = left + scale_w * offset_x;
674
let right_prime = left_prime + scale_w * sub_width;
675
let top_prime = top - scale_h * offset_y;
676
let bottom_prime = top_prime - scale_h * sub_height;
677
678
Mat4::orthographic_rh(
679
left_prime,
680
right_prime,
681
bottom_prime,
682
top_prime,
683
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
684
// This is for interoperability with pipelines using infinite reverse perspective projections.
685
self.far,
686
self.near,
687
)
688
}
689
690
fn update(&mut self, width: f32, height: f32) {
691
let (projection_width, projection_height) = match self.scaling_mode {
692
ScalingMode::WindowSize => (width, height),
693
ScalingMode::AutoMin {
694
min_width,
695
min_height,
696
} => {
697
// Compare Pixels of current width and minimal height and Pixels of minimal width with current height.
698
// 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.
699
if width * min_height > min_width * height {
700
(width * min_height / height, min_height)
701
} else {
702
(min_width, height * min_width / width)
703
}
704
}
705
ScalingMode::AutoMax {
706
max_width,
707
max_height,
708
} => {
709
// Compare Pixels of current width and maximal height and Pixels of maximal width with current height.
710
// 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.
711
if width * max_height < max_width * height {
712
(width * max_height / height, max_height)
713
} else {
714
(max_width, height * max_width / width)
715
}
716
}
717
ScalingMode::FixedVertical { viewport_height } => {
718
(width * viewport_height / height, viewport_height)
719
}
720
ScalingMode::FixedHorizontal { viewport_width } => {
721
(viewport_width, height * viewport_width / width)
722
}
723
ScalingMode::Fixed { width, height } => (width, height),
724
};
725
726
let origin_x = projection_width * self.viewport_origin.x;
727
let origin_y = projection_height * self.viewport_origin.y;
728
729
self.area = Rect::new(
730
self.scale * -origin_x,
731
self.scale * -origin_y,
732
self.scale * (projection_width - origin_x),
733
self.scale * (projection_height - origin_y),
734
);
735
}
736
737
fn far(&self) -> f32 {
738
self.far
739
}
740
741
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
742
let area = self.area;
743
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
744
[
745
Vec3A::new(area.max.x, area.min.y, z_near), // bottom right
746
Vec3A::new(area.max.x, area.max.y, z_near), // top right
747
Vec3A::new(area.min.x, area.max.y, z_near), // top left
748
Vec3A::new(area.min.x, area.min.y, z_near), // bottom left
749
Vec3A::new(area.max.x, area.min.y, z_far), // bottom right
750
Vec3A::new(area.max.x, area.max.y, z_far), // top right
751
Vec3A::new(area.min.x, area.max.y, z_far), // top left
752
Vec3A::new(area.min.x, area.min.y, z_far), // bottom left
753
]
754
}
755
}
756
757
impl FromWorld for OrthographicProjection {
758
fn from_world(_world: &mut World) -> Self {
759
OrthographicProjection::default_3d()
760
}
761
}
762
763
impl OrthographicProjection {
764
/// Returns the default orthographic projection for a 2D context.
765
///
766
/// The near plane is set to a negative value so that the camera can still
767
/// render the scene when using positive z coordinates to order foreground elements.
768
pub fn default_2d() -> Self {
769
OrthographicProjection {
770
near: -1000.0,
771
..OrthographicProjection::default_3d()
772
}
773
}
774
775
/// Returns the default orthographic projection for a 3D context.
776
///
777
/// The near plane is set to 0.0 so that the camera doesn't render
778
/// objects that are behind it.
779
pub fn default_3d() -> Self {
780
OrthographicProjection {
781
scale: 1.0,
782
near: 0.0,
783
far: 1000.0,
784
viewport_origin: Vec2::new(0.5, 0.5),
785
scaling_mode: ScalingMode::WindowSize,
786
area: Rect::new(-1.0, -1.0, 1.0, 1.0),
787
}
788
}
789
}
790
791