Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Improve OrthographicCamera consistency and usability #6201

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,9 +719,9 @@ fn load_node(
let projection = match camera.projection() {
gltf::camera::Projection::Orthographic(orthographic) => {
let xmag = orthographic.xmag();
let orthographic_projection: OrthographicProjection = OrthographicProjection {
far: orthographic.zfar(),
let orthographic_projection = OrthographicProjection {
near: orthographic.znear(),
far: orthographic.zfar(),
scaling_mode: ScalingMode::FixedHorizontal(1.0),
scale: xmag,
..Default::default()
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_render/src/camera/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ impl Plugin for CameraPlugin {
app.register_type::<Camera>()
.register_type::<Viewport>()
.register_type::<Option<Viewport>>()
.register_type::<WindowOrigin>()
.register_type::<ScalingMode>()
.register_type::<CameraRenderGraph>()
.register_type::<RenderTarget>()
Expand Down
138 changes: 77 additions & 61 deletions crates/bevy_render/src/camera/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::marker::PhantomData;

use bevy_app::{App, CoreSchedule, CoreSet, Plugin, StartupSet};
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::Mat4;
use bevy_math::{Mat4, Rect, Vec2};
use bevy_reflect::{
std_traits::ReflectDefault, FromReflect, GetTypeRegistration, Reflect, ReflectDeserialize,
ReflectSerialize,
Expand Down Expand Up @@ -169,57 +169,92 @@ impl Default for PerspectiveProjection {
}
}

// TODO: make this a component instead of a property
#[derive(Debug, Clone, Reflect, FromReflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub enum WindowOrigin {
Center,
BottomLeft,
}

#[derive(Debug, Clone, Reflect, FromReflect, Serialize, Deserialize)]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Debug, Clone, Reflect, FromReflect, Serialize, Deserialize)]
/// Specifies how the size of a projection is calculated. The projection is the cross-sectional area
/// of the view frustum which intersects with the window.
#[derive(Debug, Clone, Reflect, FromReflect, Serialize, Deserialize)]

#[reflect(Serialize, Deserialize)]
pub enum ScalingMode {
/// Manually specify left/right/top/bottom values.
/// Ignore window resizing; the image will stretch.
None,
/// Match the window size. 1 world unit = 1 pixel.
WindowSize,
/// 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 vertical axis constant; resize horizontal with aspect ratio.
/// The argument is the desired height of the viewport in world units.
/// 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 horizontal axis constant; resize vertical with aspect ratio.
/// The argument is the desired width of the viewport in world units.
/// 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, FromReflect)]
#[reflect(Component, Default)]
pub struct OrthographicProjection {
pub left: f32,
pub right: f32,
pub bottom: f32,
pub top: f32,
/// The distance of the near clipping plane in world units.
xgbwei marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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,
pub window_origin: WindowOrigin,
/// 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::WindowScale(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.left * self.scale,
self.right * self.scale,
self.bottom * self.scale,
self.top * self.scale,
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,
Expand All @@ -228,8 +263,8 @@ impl CameraProjection for OrthographicProjection {
}

fn update(&mut self, width: f32, height: f32) {
let (viewport_width, viewport_height) = match self.scaling_mode {
ScalingMode::WindowSize => (width, height),
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,
Expand Down Expand Up @@ -260,34 +295,18 @@ impl CameraProjection for OrthographicProjection {
ScalingMode::FixedHorizontal(viewport_width) => {
(viewport_width, height * viewport_width / width)
}
ScalingMode::None => return,
ScalingMode::Fixed { width, height } => (width, height),
};

match self.window_origin {
WindowOrigin::Center => {
let half_width = viewport_width / 2.0;
let half_height = viewport_height / 2.0;
self.left = -half_width;
self.bottom = -half_height;
self.right = half_width;
self.top = half_height;
let origin_x = projection_width * self.viewport_origin.x;
let origin_y = projection_height * self.viewport_origin.y;

if let ScalingMode::WindowSize = self.scaling_mode {
if self.scale == 1.0 {
self.left = self.left.floor();
self.bottom = self.bottom.floor();
self.right = self.right.floor();
self.top = self.top.floor();
}
}
}
WindowOrigin::BottomLeft => {
self.left = 0.0;
self.bottom = 0.0;
self.right = viewport_width;
self.top = viewport_height;
}
}
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 {
Expand All @@ -298,15 +317,12 @@ impl CameraProjection for OrthographicProjection {
impl Default for OrthographicProjection {
fn default() -> Self {
OrthographicProjection {
left: -1.0,
right: 1.0,
bottom: -1.0,
top: 1.0,
scale: 1.0,
near: 0.0,
far: 1000.0,
window_origin: WindowOrigin::Center,
scaling_mode: ScalingMode::WindowSize,
scale: 1.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),
}
}
}