OrthographicProjection scaling mode + camera bundle refactoring (#400)

* add normalized orthographic projection

* custom scale for ScaledOrthographicProjection

* allow choosing base axis for ScaledOrthographicProjection

* cargo fmt

* add general (scaled) orthographic camera bundle

FIXME: does the same "far" trick from Camera2DBundle make any sense here?

* fixes

* camera bundles: rename and new ortho constructors

* unify orthographic projections

* give PerspectiveCameraBundle constructors like those of OrthographicCameraBundle

* update examples with new camera bundle syntax

* rename CameraUiBundle to UiCameraBundle

* update examples

* ScalingMode::None

* remove extra blank lines

* sane default bounds for orthographic projection

* fix alien_cake_addict example

* reorder ScalingMode enum variants

* ios example fix
This commit is contained in:
Jasen Borisov 2021-01-30 11:31:03 +01:00 committed by GitHub
parent af67231567
commit 57f9ac18d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 163 additions and 67 deletions

View file

@ -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,
}
#[derive(Debug, Clone, Reflect)]
#[reflect(Component)]
pub struct OrthographicProjection {
@ -61,23 +75,25 @@ 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,
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;
@ -85,12 +101,41 @@ impl CameraProjection for OrthographicProjection {
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, _) => {}
}
}
@ -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,
near: 0.0,
far: 1000.0,
window_origin: WindowOrigin::Center,
scaling_mode: ScalingMode::WindowSize,
scale: 1.0,
}
}
}

View file

@ -22,9 +22,11 @@ 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,
@ -32,9 +34,28 @@ pub struct Camera3dBundle {
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()
@ -47,9 +68,11 @@ 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,
@ -57,12 +80,12 @@ pub struct Camera2dBundle {
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()
@ -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()),
..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(),
}
}
}

View file

@ -164,7 +164,7 @@ impl Default for ButtonBundle {
}
#[derive(Bundle, Debug)]
pub struct CameraUiBundle {
pub struct UiCameraBundle {
pub camera: Camera,
pub orthographic_projection: OrthographicProjection,
pub visible_entities: VisibleEntities,
@ -172,12 +172,12 @@ pub struct CameraUiBundle {
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()

View file

@ -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![],

View file

@ -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()

View file

@ -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)),

View file

@ -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.",

View file

@ -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 {

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -30,7 +30,7 @@ fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
..Default::default()
})
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(2.0, 2.0, 6.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()

View file

@ -13,7 +13,7 @@ fn setup(
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2dBundle::default());
commands.spawn(OrthographicCameraBundle::new_2d());
let texture = asset_server.load("branding/icon.png");
// Spawn a root entity with no parent

View file

@ -8,7 +8,7 @@ fn spawn_system(
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2dBundle::default());
commands.spawn(OrthographicCameraBundle::new_2d());
let texture_handle = asset_server.load("branding/icon.png");
let material = materials.add(texture_handle.into());
for _ in 0..128 {

View file

@ -33,7 +33,7 @@ fn setup(
let texture = asset_server.load("branding/icon.png");
commands
.spawn(Camera2dBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(SpriteBundle {
material: materials.add(texture.into()),
..Default::default()

View file

@ -35,7 +35,7 @@ fn setup_menu(
) {
commands
// ui camera
.spawn(CameraUiBundle::default())
.spawn(UiCameraBundle::default())
.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),
@ -104,7 +104,7 @@ fn setup_game(
) {
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()

View file

@ -83,7 +83,7 @@ fn setup_cameras(commands: &mut Commands, mut game: ResMut<Game>) {
game.camera_should_focus = Vec3::from(RESET_FOCUS);
game.camera_is_focus = game.camera_should_focus;
commands
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(
-(BOARD_SIZE_I as f32 / 2.0),
2.0 * BOARD_SIZE_J as f32 / 3.0,
@ -92,7 +92,7 @@ fn setup_cameras(commands: &mut Commands, mut game: ResMut<Game>) {
.looking_at(game.camera_is_focus, Vec3::unit_y()),
..Default::default()
})
.spawn(CameraUiBundle::default());
.spawn(UiCameraBundle::default());
}
fn setup(commands: &mut Commands, asset_server: Res<AssetServer>, mut game: ResMut<Game>) {

View file

@ -44,8 +44,8 @@ fn setup(
// Add the game's entities to our world
commands
// cameras
.spawn(Camera2dBundle::default())
.spawn(CameraUiBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(UiCameraBundle::default())
// paddle
.spawn(SpriteBundle {
material: materials.add(Color::rgb(0.5, 0.5, 1.0).into()),

View file

@ -52,7 +52,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()

View file

@ -97,7 +97,7 @@ fn save_scene_system(_world: &mut World, resources: &mut Resources) {
// This is only necessary for the info message in the UI. See examples/ui/text.rs for a standalone text example.
fn infotext_system(commands: &mut Commands, asset_server: Res<AssetServer>) {
commands.spawn(CameraUiBundle::default()).spawn(TextBundle {
commands.spawn(UiCameraBundle::default()).spawn(TextBundle {
style: Style {
align_self: AlignSelf::FlexEnd,
..Default::default()

View file

@ -109,7 +109,7 @@ fn setup(
.add_node_edge("my_array_texture", base::node::MAIN_PASS)
.unwrap();
commands.spawn(Camera3dBundle {
commands.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
});

View file

@ -72,7 +72,7 @@ fn setup(
})
.with(material)
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, -8.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()

View file

@ -137,7 +137,7 @@ fn setup(
})
.with(material)
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, -8.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()

View file

@ -93,7 +93,7 @@ fn setup(
})
.with(material)
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, -8.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()

View file

@ -124,7 +124,7 @@ fn setup(
})
.with(blue_material)
// camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, -8.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()

View file

@ -50,8 +50,8 @@ fn main() {
fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
commands
.spawn(Camera2dBundle::default())
.spawn(CameraUiBundle::default())
.spawn(OrthographicCameraBundle::new_2d())
.spawn(UiCameraBundle::default())
.spawn(TextBundle {
text: Text {
sections: vec![

View file

@ -61,7 +61,7 @@ fn setup(
) {
commands
// ui camera
.spawn(CameraUiBundle::default())
.spawn(UiCameraBundle::default())
.spawn(ButtonBundle {
style: Style {
size: Size::new(Val::Px(150.0), Val::Px(65.0)),

View file

@ -79,7 +79,7 @@ fn text_update_system(mut state: ResMut<State>, time: Res<Time>, mut query: Quer
fn setup(commands: &mut Commands, asset_server: Res<AssetServer>, mut state: ResMut<State>) {
let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf");
state.handle = font_handle.clone();
commands.spawn(CameraUiBundle::default()).spawn(TextBundle {
commands.spawn(UiCameraBundle::default()).spawn(TextBundle {
text: Text::with_section(
"a",
TextStyle {

View file

@ -25,7 +25,7 @@ struct ColorText;
fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
commands
// UI camera
.spawn(CameraUiBundle::default())
.spawn(UiCameraBundle::default())
// Text with one section
.spawn(TextBundle {
style: Style {

View file

@ -21,7 +21,7 @@ struct TextChanges;
fn infotext_system(commands: &mut Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
commands.spawn(CameraUiBundle::default());
commands.spawn(UiCameraBundle::default());
commands.spawn(TextBundle {
style: Style {
align_self: AlignSelf::FlexEnd,

View file

@ -15,7 +15,7 @@ fn setup(
) {
commands
// ui camera
.spawn(CameraUiBundle::default())
.spawn(UiCameraBundle::default())
// root node
.spawn(NodeBundle {
style: Style {

View file

@ -192,13 +192,13 @@ fn setup_pipeline(
..Default::default()
})
// main camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 0.0, 6.0)
.looking_at(Vec3::default(), Vec3::unit_y()),
..Default::default()
})
// second window camera
.spawn(Camera3dBundle {
.spawn(PerspectiveCameraBundle {
camera: Camera {
name: Some("Secondary".to_string()),
window: window_id,

View file

@ -22,7 +22,7 @@ fn setup(
) {
commands
// ui camera
.spawn(CameraUiBundle::default())
.spawn(UiCameraBundle::default())
// root node
.spawn(NodeBundle {
style: Style {