bevy/crates/bevy_render/src/camera/projection.rs
Martín Maita cd1260585b
Revert "Update defaults for OrthographicProjection (#9537)" (#9878)
# Objective

- Fixes #9876

## Solution

- Reverted commit `5012a0fd57748ab6f146776368b4cf988bba1eaa` to restore
the previous default values for `OrthographicProjection`.

---

## Migration Guide

- Migration guide steps from #9537 should be removed for next release.
2023-09-20 19:19:47 +00:00

326 lines
12 KiB
Rust

use std::marker::PhantomData;
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::{Mat4, Rect, Vec2};
use bevy_reflect::{
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
};
use serde::{Deserialize, Serialize};
/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.
pub struct CameraProjectionPlugin<T: CameraProjection>(PhantomData<T>);
impl<T: CameraProjection> Default for CameraProjectionPlugin<T> {
fn default() -> Self {
Self(Default::default())
}
}
/// Label for [`camera_system<T>`], shared across all `T`.
///
/// [`camera_system<T>`]: crate::camera::camera_system
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
pub struct CameraUpdateSystem;
impl<T: CameraProjection + Component + GetTypeRegistration> Plugin for CameraProjectionPlugin<T> {
fn build(&self, app: &mut App) {
app.register_type::<T>()
.add_systems(
PostStartup,
crate::camera::camera_system::<T>
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
)
.add_systems(
PostUpdate,
crate::camera::camera_system::<T>
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
);
}
}
/// Trait to control the projection matrix of a camera.
///
/// Components implementing this trait are automatically polled for changes, and used
/// to recompute the camera projection matrix of the [`Camera`] component attached to
/// the same entity as the component implementing this trait.
///
/// [`Camera`]: crate::camera::Camera
pub trait CameraProjection {
fn get_projection_matrix(&self) -> Mat4;
fn update(&mut self, width: f32, height: f32);
fn far(&self) -> f32;
}
/// A configurable [`CameraProjection`] that can select its projection type at runtime.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default)]
pub enum Projection {
Perspective(PerspectiveProjection),
Orthographic(OrthographicProjection),
}
impl From<PerspectiveProjection> for Projection {
fn from(p: PerspectiveProjection) -> Self {
Self::Perspective(p)
}
}
impl From<OrthographicProjection> for Projection {
fn from(p: OrthographicProjection) -> Self {
Self::Orthographic(p)
}
}
impl CameraProjection for Projection {
fn get_projection_matrix(&self) -> Mat4 {
match self {
Projection::Perspective(projection) => projection.get_projection_matrix(),
Projection::Orthographic(projection) => projection.get_projection_matrix(),
}
}
fn update(&mut self, width: f32, height: f32) {
match self {
Projection::Perspective(projection) => projection.update(width, height),
Projection::Orthographic(projection) => projection.update(width, height),
}
}
fn far(&self) -> f32 {
match self {
Projection::Perspective(projection) => projection.far(),
Projection::Orthographic(projection) => projection.far(),
}
}
}
impl Default for Projection {
fn default() -> Self {
Projection::Perspective(Default::default())
}
}
/// A 3D camera projection in which distant objects appear smaller than close objects.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct PerspectiveProjection {
/// The vertical field of view (FOV) in radians.
///
/// Defaults to a value of π/4 radians or 45 degrees.
pub fov: f32,
/// The aspect ratio (width divided by height) of the viewing frustum.
///
/// Bevy's [`camera_system`](crate::camera::camera_system) automatically
/// updates this value when the aspect ratio of the associated window changes.
///
/// Defaults to a value of `1.0`.
pub aspect_ratio: f32,
/// The distance from the camera in world units of the viewing frustum's near plane.
///
/// Objects closer to the camera than this value will not be visible.
///
/// Defaults to a value of `0.1`.
pub near: f32,
/// The distance from the camera in world units of the viewing frustum's far plane.
///
/// Objects farther from the camera than this value will not be visible.
///
/// Defaults to a value of `1000.0`.
pub far: f32,
}
impl CameraProjection for PerspectiveProjection {
fn get_projection_matrix(&self) -> Mat4 {
Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near)
}
fn update(&mut self, width: f32, height: f32) {
self.aspect_ratio = width / height;
}
fn far(&self) -> f32 {
self.far
}
}
impl Default for PerspectiveProjection {
fn default() -> Self {
PerspectiveProjection {
fov: std::f32::consts::PI / 4.0,
near: 0.1,
far: 1000.0,
aspect_ratio: 1.0,
}
}
}
#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub enum ScalingMode {
/// Manually specify the projection's size, ignoring window resizing. The image will stretch.
/// Arguments are in world units.
Fixed { width: f32, height: f32 },
/// Match the viewport size.
/// The argument is the number of pixels that equals one world unit.
WindowSize(f32),
/// Keeping the aspect ratio while the axes can't be smaller than given minimum.
/// Arguments are in world units.
AutoMin { min_width: f32, min_height: f32 },
/// Keeping the aspect ratio while the axes can't be bigger than given maximum.
/// Arguments are in world units.
AutoMax { max_width: f32, max_height: f32 },
/// Keep the projection's height constant; width will be adjusted to match aspect ratio.
/// The argument is the desired height of the projection in world units.
FixedVertical(f32),
/// Keep the projection's width constant; height will be adjusted to match aspect ratio.
/// The argument is the desired width of the projection in world units.
FixedHorizontal(f32),
}
/// Project a 3D space onto a 2D surface using parallel lines, i.e., unlike [`PerspectiveProjection`],
/// the size of objects remains the same regardless of their distance to the camera.
///
/// The volume contained in the projection is called the *view frustum*. Since the viewport is rectangular
/// and projection lines are parallel, the view frustum takes the shape of a cuboid.
///
/// Note that the scale of the projection and the apparent size of objects are inversely proportional.
/// As the size of the projection increases, the size of objects decreases.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct OrthographicProjection {
/// The distance of the near clipping plane in world units.
///
/// Objects closer than this will not be rendered.
///
/// Defaults to `0.0`
pub near: f32,
/// The distance of the far clipping plane in world units.
///
/// Objects further than this will not be rendered.
///
/// Defaults to `1000.0`
pub far: f32,
/// Specifies the origin of the viewport as a normalized position from 0 to 1, where (0, 0) is the bottom left
/// and (1, 1) is the top right. This determines where the camera's position sits inside the viewport.
///
/// When the projection scales due to viewport resizing, the position of the camera, and thereby `viewport_origin`,
/// remains at the same relative point.
///
/// Consequently, this is pivot point when scaling. With a bottom left pivot, the projection will expand
/// upwards and to the right. With a top right pivot, the projection will expand downwards and to the left.
/// Values in between will caused the projection to scale proportionally on each axis.
///
/// Defaults to `(0.5, 0.5)`, which makes scaling affect opposite sides equally, keeping the center
/// point of the viewport centered.
pub viewport_origin: Vec2,
/// How the projection will scale when the viewport is resized.
///
/// Defaults to `ScalingMode::WindowSize(1.0)`
pub scaling_mode: ScalingMode,
/// Scales the projection in world units.
///
/// As scale increases, the apparent size of objects decreases, and vice versa.
///
/// Defaults to `1.0`
pub scale: f32,
/// The area that the projection covers relative to `viewport_origin`.
///
/// Bevy's [`camera_system`](crate::camera::camera_system) automatically
/// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.
/// In this case, `area` should not be manually modified.
///
/// It may be necessary to set this manually for shadow projections and such.
pub area: Rect,
}
impl CameraProjection for OrthographicProjection {
fn get_projection_matrix(&self) -> Mat4 {
Mat4::orthographic_rh(
self.area.min.x,
self.area.max.x,
self.area.min.y,
self.area.max.y,
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
// This is for interoperability with pipelines using infinite reverse perspective projections.
self.far,
self.near,
)
}
fn update(&mut self, width: f32, height: f32) {
let (projection_width, projection_height) = match self.scaling_mode {
ScalingMode::WindowSize(pixel_scale) => (width / pixel_scale, height / pixel_scale),
ScalingMode::AutoMin {
min_width,
min_height,
} => {
// Compare Pixels of current width and minimal height and Pixels of minimal width with current height.
// 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.
if width * min_height > min_width * height {
(width * min_height / height, min_height)
} else {
(min_width, height * min_width / width)
}
}
ScalingMode::AutoMax {
max_width,
max_height,
} => {
// Compare Pixels of current width and maximal height and Pixels of maximal width with current height.
// 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.
if width * max_height < max_width * height {
(width * max_height / height, max_height)
} else {
(max_width, height * max_width / width)
}
}
ScalingMode::FixedVertical(viewport_height) => {
(width * viewport_height / height, viewport_height)
}
ScalingMode::FixedHorizontal(viewport_width) => {
(viewport_width, height * viewport_width / width)
}
ScalingMode::Fixed { width, height } => (width, height),
};
let origin_x = projection_width * self.viewport_origin.x;
let origin_y = projection_height * self.viewport_origin.y;
self.area = Rect::new(
self.scale * -origin_x,
self.scale * -origin_y,
self.scale * (projection_width - origin_x),
self.scale * (projection_height - origin_y),
);
}
fn far(&self) -> f32 {
self.far
}
}
impl Default for OrthographicProjection {
fn default() -> Self {
OrthographicProjection {
scale: 1.0,
near: 0.0,
far: 1000.0,
viewport_origin: Vec2::new(0.5, 0.5),
scaling_mode: ScalingMode::WindowSize(1.0),
area: Rect::new(-1.0, -1.0, 1.0, 1.0),
}
}
}