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

OrthographicProjection scaling mode + camera bundle refactoring #400

Merged
merged 18 commits into from
Jan 30, 2021
Merged
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
69 changes: 58 additions & 11 deletions crates/bevy_render/src/camera/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ pub enum WindowOrigin {
BottomLeft,
}

#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
#[reflect_value(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,
/// Keep vertical axis constant; resize horizontal with aspect ratio.
FixedVertical,
/// Keep horizontal axis constant; resize vertical with aspect ratio.
FixedHorizontal,
Copy link
Member

Choose a reason for hiding this comment

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

May as well have ScalingMode::None too, if we're here.

Copy link
Contributor Author

@inodentry inodentry Jan 26, 2021

Choose a reason for hiding this comment

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

I assume you mean this: that nothing should happen on window resize (let the image stretch), just letting the user set the left/right/top/bottom fields manually.

OK, added this.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, exactly that. No point limiting the functionality.

}

#[derive(Debug, Clone, Reflect)]
#[reflect(Component)]
pub struct OrthographicProjection {
Expand All @@ -61,36 +75,67 @@ pub struct OrthographicProjection {
pub near: f32,
pub far: f32,
pub window_origin: WindowOrigin,
pub scaling_mode: ScalingMode,
pub scale: f32,
}

impl CameraProjection for OrthographicProjection {
fn get_projection_matrix(&self) -> Mat4 {
Mat4::orthographic_rh(
self.left,
self.right,
self.bottom,
self.top,
self.left * self.scale,
Copy link
Member

Choose a reason for hiding this comment

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

🔥 I really like this simplification

self.right * self.scale,
self.bottom * self.scale,
self.top * self.scale,
self.near,
self.far,
)
}

fn update(&mut self, width: f32, height: f32) {
match self.window_origin {
WindowOrigin::Center => {
match (&self.scaling_mode, &self.window_origin) {
(ScalingMode::WindowSize, WindowOrigin::Center) => {
let half_width = width / 2.0;
let half_height = height / 2.0;
self.left = -half_width;
self.right = half_width;
self.top = half_height;
self.bottom = -half_height;
}
WindowOrigin::BottomLeft => {
(ScalingMode::WindowSize, WindowOrigin::BottomLeft) => {
self.left = 0.0;
self.right = width;
self.top = height;
self.bottom = 0.0;
}
(ScalingMode::FixedVertical, WindowOrigin::Center) => {
let aspect_ratio = width / height;
self.left = -aspect_ratio;
self.right = aspect_ratio;
self.top = 1.0;
self.bottom = -1.0;
}
(ScalingMode::FixedVertical, WindowOrigin::BottomLeft) => {
let aspect_ratio = width / height;
self.left = 0.0;
self.right = aspect_ratio;
self.top = 1.0;
self.bottom = 0.0;
}
(ScalingMode::FixedHorizontal, WindowOrigin::Center) => {
let aspect_ratio = height / width;
self.left = -1.0;
self.right = 1.0;
self.top = aspect_ratio;
self.bottom = -aspect_ratio;
}
(ScalingMode::FixedHorizontal, WindowOrigin::BottomLeft) => {
let aspect_ratio = height / width;
self.left = 0.0;
self.right = 1.0;
self.top = aspect_ratio;
self.bottom = 0.0;
}
(ScalingMode::None, _) => {}
}
}

Expand All @@ -102,13 +147,15 @@ impl CameraProjection for OrthographicProjection {
impl Default for OrthographicProjection {
fn default() -> Self {
OrthographicProjection {
left: 0.0,
right: 0.0,
bottom: 0.0,
top: 0.0,
left: -1.0,
right: 1.0,
bottom: -1.0,
top: 1.0,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing the defaults here, to allow for this:

let projection = ОrthographicProjection {
    scaling_mode: ScalingMode::None,
    ..Default::default()
};

near: 0.0,
far: 1000.0,
window_origin: WindowOrigin::Center,
scaling_mode: ScalingMode::WindowSize,
scale: 1.0,
Copy link
Member

Choose a reason for hiding this comment

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

I think this scale field shouldn't be here, and instead should be handled by the scale on the Transform of self.

Obviously that would also scale far, which isn't ideal.

Copy link
Contributor Author

@inodentry inodentry Jan 22, 2021

Choose a reason for hiding this comment

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

I strongly disagree. They serve different purposes. I think it is important to have this field, to be able to set the "span" of the projection.

The camera's transform is typically used to perform "zoom in/out" effects in the game.

The "scale" of the projection is if you need to normalize the sizes of things (say, depending on what units you work in, particularly for a 2D game).

Say, for example, you are making a 2D game and you want it to scale based on screen resolution, to always fit the same content on screen.

You might want the projection to go -600.0 to +600.0 (1200 total height), because you designed your assets for a nominal 1200-vertical-pixels screen size. You shouldn't have to complicate your camera math to account for that. The camera scale should stay at 1.0 unless you are doing some "zoom in/out" effects.

This is the same argument I've been giving before w.r.t your suggestion of "just use the window-pixels 2d projection bevy already provides for 2D games and fiddle with the camera scale to make your content fit the screen" ... it's an ugly workaround and not ergonomic.

It really doesn't cost anything to have this field, and I think it opens up some likely-useful extra flexibility for users. Why potentially cripple/limit the functionality, just to avoid that extra field? IMO it makes sense to have it.

Copy link
Member

Choose a reason for hiding this comment

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

My point is that if we did have scale only as the field on transform, then that would be the only thing defining the size (of the fixed axis) of the in-world screen/size of the in-world viewing pane

Whereas at the moment you have to multiply it by this scale field to know that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the projection scale is at 1.0 by default, so what you said applies, unless the user explicitly goes to mess with the projection. I think it is valuable for the users to have the option of doing that.

Which means we shouldn't remove the scale field.

It's not going to surprise or confuse anyone; it's only relevant to those users who know they want to use it.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that also makes sense.

}
}
}
67 changes: 58 additions & 9 deletions crates/bevy_render/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,40 @@ pub struct MeshBundle {
pub global_transform: GlobalTransform,
}

/// A component bundle for "3d camera" entities
/// Component bundle for camera entities with perspective projection
///
/// Use this for 3D rendering.
#[derive(Bundle)]
pub struct Camera3dBundle {
pub struct PerspectiveCameraBundle {
pub camera: Camera,
pub perspective_projection: PerspectiveProjection,
pub visible_entities: VisibleEntities,
pub transform: Transform,
pub global_transform: GlobalTransform,
}

impl Default for Camera3dBundle {
impl PerspectiveCameraBundle {
pub fn new_3d() -> Self {
Default::default()
}

pub fn with_name(name: &str) -> Self {
PerspectiveCameraBundle {
camera: Camera {
name: Some(name.to_string()),
..Default::default()
},
perspective_projection: Default::default(),
visible_entities: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}
}

impl Default for PerspectiveCameraBundle {
fn default() -> Self {
Camera3dBundle {
PerspectiveCameraBundle {
camera: Camera {
name: Some(base::camera::CAMERA_3D.to_string()),
..Default::default()
Expand All @@ -47,22 +68,24 @@ impl Default for Camera3dBundle {
}
}

/// A component bundle for "2d camera" entities
/// Component bundle for camera entities with orthographic projection
///
/// Use this for 2D games, isometric games, CAD-like 3D views.
#[derive(Bundle)]
pub struct Camera2dBundle {
pub struct OrthographicCameraBundle {
pub camera: Camera,
pub orthographic_projection: OrthographicProjection,
pub visible_entities: VisibleEntities,
pub transform: Transform,
pub global_transform: GlobalTransform,
}

impl Default for Camera2dBundle {
fn default() -> Self {
impl OrthographicCameraBundle {
pub fn new_2d() -> Self {
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
// the camera's translation by far and use a right handed coordinate system
let far = 1000.0;
Camera2dBundle {
OrthographicCameraBundle {
camera: Camera {
name: Some(base::camera::CAMERA_2D.to_string()),
..Default::default()
Expand All @@ -76,4 +99,30 @@ impl Default for Camera2dBundle {
global_transform: Default::default(),
}
}

pub fn new_3d() -> Self {
OrthographicCameraBundle {
camera: Camera {
name: Some(base::camera::CAMERA_3D.to_string()),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should the new camera use the CAMERA_3D or CAMERA_2D name? What purpose do these serve / what difference would it make?

..Default::default()
},
orthographic_projection: Default::default(),
visible_entities: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}

pub fn with_name(name: &str) -> Self {
OrthographicCameraBundle {
camera: Camera {
name: Some(name.to_string()),
..Default::default()
},
orthographic_projection: Default::default(),
visible_entities: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}
}
6 changes: 3 additions & 3 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,20 @@ impl Default for ButtonBundle {
}

#[derive(Bundle, Debug)]
pub struct CameraUiBundle {
pub struct UiCameraBundle {
pub camera: Camera,
pub orthographic_projection: OrthographicProjection,
pub visible_entities: VisibleEntities,
pub transform: Transform,
pub global_transform: GlobalTransform,
}

impl Default for CameraUiBundle {
impl Default for UiCameraBundle {
fn default() -> Self {
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
// the camera's translation by far and use a right handed coordinate system
let far = 1000.0;
CameraUiBundle {
UiCameraBundle {
camera: Camera {
name: Some(crate::camera::CAMERA_UI.to_string()),
..Default::default()
Expand Down
4 changes: 2 additions & 2 deletions examples/2d/contributors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ fn setup(
let texture_handle = asset_server.load("branding/icon.png");

commands
.spawn(Camera2dBundle::default())
.spawn(CameraUiBundle::default());
.spawn(OrthographicCameraBundle::new_2d())
.spawn(UiCameraBundle::default());

let mut sel = ContributorSelection {
order: vec![],
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn setup(
) {
let texture_handle = asset_server.load("branding/icon.png");
commands
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(SpriteBundle {
material: materials.add(texture_handle.into()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/sprite_sheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn setup(
let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
commands
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
transform: Transform::from_scale(Vec3::splat(6.0)),
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() {
fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
commands
// 2d camera
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(Text2dBundle {
text: Text::with_section(
"This text is in the 2D scene.",
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fn setup(

// set up a scene to display our texture atlas
commands
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
// draw a sprite from the atlas
.spawn(SpriteSheetBundle {
transform: Transform {
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/3d_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/load_gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
transform: Transform::from_xyz(4.0, 5.0, 4.0),
..Default::default()
})
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.7, 0.7, 1.0)
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/msaa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(-3.0, 3.0, 5.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/parenting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(5.0, 10.0, 10.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 15.0, 150.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, 8.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/update_gltf_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn setup(
transform: Transform::from_xyz(4.0, 5.0, 4.0),
..Default::default()
})
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(1.05, 0.9, 1.5)
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/3d/z_sort_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fn setup(
});
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(5.0, 10.0, 10.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/android/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/asset/asset_loading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn setup(
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 3.0, 10.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion examples/asset/custom_asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn setup(
) {
let texture_handle = asset_server.load("branding/icon.png");
commands
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(SpriteBundle {
material: materials.add(texture_handle.into()),
..Default::default()
Expand Down
Loading