Improve OrthographicCamera
consistency and usability (#6201)
# Objective - Terminology used in field names and docs aren't accurate - `window_origin` doesn't have any effect when `scaling_mode` is `ScalingMode::None` - `left`, `right`, `bottom`, and `top` are set automatically unless `scaling_mode` is `None`. Fields that only sometimes give feedback are confusing. - `ScalingMode::WindowSize` has no arguments, which is inconsistent with other `ScalingMode`s. 1 pixel = 1 world unit is also typically way too wide. - `OrthographicProjection` feels generally less streamlined than its `PerspectiveProjection` counterpart - Fixes #5818 - Fixes #6190 ## Solution - Improve consistency in `OrthographicProjection`'s public fields (they should either always give feedback or never give feedback). - Improve consistency in `ScalingMode`'s arguments - General usability improvements - Improve accuracy of terminology: - "Window" should refer to the physical window on the desktop - "Viewport" should refer to the component in the window that images are drawn on (typically all of it) - "View frustum" should refer to the volume captured by the projection --- ## Changelog ### Added - Added argument to `ScalingMode::WindowSize` that specifies the number of pixels that equals one world unit. - Added documentation for fields and enums ### Changed - Renamed `window_origin` to `viewport_origin`, which now: - Affects all `ScalingMode`s - Takes a fraction of the viewport's width and height instead of an enum - Removed `WindowOrigin` enum as it's obsolete - Renamed `ScalingMode::None` to `ScalingMode::Fixed`, which now: - Takes arguments to specify the projection size - Replaced `left`, `right`, `bottom`, and `top` fields with a single `area: Rect` - `scale` is now applied before updating `area`. Reading from it will take `scale` into account. - Documentation changes to make terminology more accurate and consistent ## Migration Guide - Change `window_origin` to `viewport_origin`; replace `WindowOrigin::Center` with `Vec2::new(0.5, 0.5)` and `WindowOrigin::BottomLeft` with `Vec2::new(0.0, 0.0)` - For shadow projections and such, replace `left`, `right`, `bottom`, and `top` with `area: Rect::new(left, bottom, right, top)` - For camera projections, remove l/r/b/t values from `OrthographicProjection` instantiations, as they no longer have any effect in any `ScalingMode` - Change `ScalingMode::None` to `ScalingMode::Fixed` - Replace manual changes of l/r/b/t with: - Arguments in `ScalingMode::Fixed` to specify size - `viewport_origin` to specify offset - Change `ScalingMode::WindowSize` to `ScalingMode::WindowSize(1.0)`
This commit is contained in:
parent
26e00f9069
commit
09cb590c57
@ -719,9 +719,9 @@ fn load_node(
|
|||||||
let projection = match camera.projection() {
|
let projection = match camera.projection() {
|
||||||
gltf::camera::Projection::Orthographic(orthographic) => {
|
gltf::camera::Projection::Orthographic(orthographic) => {
|
||||||
let xmag = orthographic.xmag();
|
let xmag = orthographic.xmag();
|
||||||
let orthographic_projection: OrthographicProjection = OrthographicProjection {
|
let orthographic_projection = OrthographicProjection {
|
||||||
far: orthographic.zfar(),
|
|
||||||
near: orthographic.znear(),
|
near: orthographic.znear(),
|
||||||
|
far: orthographic.zfar(),
|
||||||
scaling_mode: ScalingMode::FixedHorizontal(1.0),
|
scaling_mode: ScalingMode::FixedHorizontal(1.0),
|
||||||
scale: xmag,
|
scale: xmag,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -18,7 +18,6 @@ impl Plugin for CameraPlugin {
|
|||||||
app.register_type::<Camera>()
|
app.register_type::<Camera>()
|
||||||
.register_type::<Viewport>()
|
.register_type::<Viewport>()
|
||||||
.register_type::<Option<Viewport>>()
|
.register_type::<Option<Viewport>>()
|
||||||
.register_type::<WindowOrigin>()
|
|
||||||
.register_type::<ScalingMode>()
|
.register_type::<ScalingMode>()
|
||||||
.register_type::<CameraRenderGraph>()
|
.register_type::<CameraRenderGraph>()
|
||||||
.register_type::<RenderTarget>()
|
.register_type::<RenderTarget>()
|
||||||
|
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use bevy_app::{App, CoreSchedule, CoreSet, Plugin, StartupSet};
|
use bevy_app::{App, CoreSchedule, CoreSet, Plugin, StartupSet};
|
||||||
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
||||||
use bevy_math::Mat4;
|
use bevy_math::{Mat4, Rect, Vec2};
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{
|
||||||
std_traits::ReflectDefault, FromReflect, GetTypeRegistration, Reflect, ReflectDeserialize,
|
std_traits::ReflectDefault, FromReflect, GetTypeRegistration, Reflect, ReflectDeserialize,
|
||||||
ReflectSerialize,
|
ReflectSerialize,
|
||||||
@ -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)]
|
#[derive(Debug, Clone, Reflect, FromReflect, Serialize, Deserialize)]
|
||||||
#[reflect(Serialize, Deserialize)]
|
#[reflect(Serialize, Deserialize)]
|
||||||
pub enum ScalingMode {
|
pub enum ScalingMode {
|
||||||
/// Manually specify left/right/top/bottom values.
|
/// Manually specify the projection's size, ignoring window resizing. The image will stretch.
|
||||||
/// Ignore window resizing; the image will stretch.
|
/// Arguments are in world units.
|
||||||
None,
|
Fixed { width: f32, height: f32 },
|
||||||
/// Match the window size. 1 world unit = 1 pixel.
|
/// Match the viewport size.
|
||||||
WindowSize,
|
/// 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.
|
/// Keeping the aspect ratio while the axes can't be smaller than given minimum.
|
||||||
/// Arguments are in world units.
|
/// Arguments are in world units.
|
||||||
AutoMin { min_width: f32, min_height: f32 },
|
AutoMin { min_width: f32, min_height: f32 },
|
||||||
/// Keeping the aspect ratio while the axes can't be bigger than given maximum.
|
/// Keeping the aspect ratio while the axes can't be bigger than given maximum.
|
||||||
/// Arguments are in world units.
|
/// Arguments are in world units.
|
||||||
AutoMax { max_width: f32, max_height: f32 },
|
AutoMax { max_width: f32, max_height: f32 },
|
||||||
/// Keep vertical axis constant; resize horizontal with aspect ratio.
|
/// Keep the projection's height constant; width will be adjusted to match aspect ratio.
|
||||||
/// The argument is the desired height of the viewport in world units.
|
/// The argument is the desired height of the projection in world units.
|
||||||
FixedVertical(f32),
|
FixedVertical(f32),
|
||||||
/// Keep horizontal axis constant; resize vertical with aspect ratio.
|
/// Keep the projection's width constant; height will be adjusted to match aspect ratio.
|
||||||
/// The argument is the desired width of the viewport in world units.
|
/// The argument is the desired width of the projection in world units.
|
||||||
FixedHorizontal(f32),
|
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)]
|
#[derive(Component, Debug, Clone, Reflect, FromReflect)]
|
||||||
#[reflect(Component, Default)]
|
#[reflect(Component, Default)]
|
||||||
pub struct OrthographicProjection {
|
pub struct OrthographicProjection {
|
||||||
pub left: f32,
|
/// The distance of the near clipping plane in world units.
|
||||||
pub right: f32,
|
///
|
||||||
pub bottom: f32,
|
/// Objects closer than this will not be rendered.
|
||||||
pub top: f32,
|
///
|
||||||
|
/// Defaults to `0.0`
|
||||||
pub near: f32,
|
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 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,
|
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,
|
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 {
|
impl CameraProjection for OrthographicProjection {
|
||||||
fn get_projection_matrix(&self) -> Mat4 {
|
fn get_projection_matrix(&self) -> Mat4 {
|
||||||
Mat4::orthographic_rh(
|
Mat4::orthographic_rh(
|
||||||
self.left * self.scale,
|
self.area.min.x,
|
||||||
self.right * self.scale,
|
self.area.max.x,
|
||||||
self.bottom * self.scale,
|
self.area.min.y,
|
||||||
self.top * self.scale,
|
self.area.max.y,
|
||||||
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
|
// 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.
|
// This is for interoperability with pipelines using infinite reverse perspective projections.
|
||||||
self.far,
|
self.far,
|
||||||
@ -228,8 +263,8 @@ impl CameraProjection for OrthographicProjection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, width: f32, height: f32) {
|
fn update(&mut self, width: f32, height: f32) {
|
||||||
let (viewport_width, viewport_height) = match self.scaling_mode {
|
let (projection_width, projection_height) = match self.scaling_mode {
|
||||||
ScalingMode::WindowSize => (width, height),
|
ScalingMode::WindowSize(pixel_scale) => (width / pixel_scale, height / pixel_scale),
|
||||||
ScalingMode::AutoMin {
|
ScalingMode::AutoMin {
|
||||||
min_width,
|
min_width,
|
||||||
min_height,
|
min_height,
|
||||||
@ -260,34 +295,18 @@ impl CameraProjection for OrthographicProjection {
|
|||||||
ScalingMode::FixedHorizontal(viewport_width) => {
|
ScalingMode::FixedHorizontal(viewport_width) => {
|
||||||
(viewport_width, height * viewport_width / width)
|
(viewport_width, height * viewport_width / width)
|
||||||
}
|
}
|
||||||
ScalingMode::None => return,
|
ScalingMode::Fixed { width, height } => (width, height),
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.window_origin {
|
let origin_x = projection_width * self.viewport_origin.x;
|
||||||
WindowOrigin::Center => {
|
let origin_y = projection_height * self.viewport_origin.y;
|
||||||
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;
|
|
||||||
|
|
||||||
if let ScalingMode::WindowSize = self.scaling_mode {
|
self.area = Rect::new(
|
||||||
if self.scale == 1.0 {
|
self.scale * -origin_x,
|
||||||
self.left = self.left.floor();
|
self.scale * -origin_y,
|
||||||
self.bottom = self.bottom.floor();
|
self.scale * (projection_width - origin_x),
|
||||||
self.right = self.right.floor();
|
self.scale * (projection_height - origin_y),
|
||||||
self.top = self.top.floor();
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowOrigin::BottomLeft => {
|
|
||||||
self.left = 0.0;
|
|
||||||
self.bottom = 0.0;
|
|
||||||
self.right = viewport_width;
|
|
||||||
self.top = viewport_height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn far(&self) -> f32 {
|
fn far(&self) -> f32 {
|
||||||
@ -298,15 +317,12 @@ impl CameraProjection for OrthographicProjection {
|
|||||||
impl Default for OrthographicProjection {
|
impl Default for OrthographicProjection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
OrthographicProjection {
|
OrthographicProjection {
|
||||||
left: -1.0,
|
scale: 1.0,
|
||||||
right: 1.0,
|
|
||||||
bottom: -1.0,
|
|
||||||
top: 1.0,
|
|
||||||
near: 0.0,
|
near: 0.0,
|
||||||
far: 1000.0,
|
far: 1000.0,
|
||||||
window_origin: WindowOrigin::Center,
|
viewport_origin: Vec2::new(0.5, 0.5),
|
||||||
scaling_mode: ScalingMode::WindowSize,
|
scaling_mode: ScalingMode::WindowSize(1.0),
|
||||||
scale: 1.0,
|
area: Rect::new(-1.0, -1.0, 1.0, 1.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user