Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_camera/src/projection.rs
6598 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, 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::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
312
impl 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
387
impl 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)]
418
pub 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)]
475
pub 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
531
impl 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
655
impl FromWorld for OrthographicProjection {
656
fn from_world(_world: &mut World) -> Self {
657
OrthographicProjection::default_3d()
658
}
659
}
660
661
impl 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
}
688
689