Skip to content

Commit

Permalink
Consistent screen-space coordinates (#8306)
Browse files Browse the repository at this point in the history
# Objective

Make the coordinate systems of screen-space items (cursor position, UI,
viewports, etc.) consistent.

## Solution

Remove the weird double inversion of the cursor position's Y origin.
Once in bevy_winit to the bottom and then again in bevy_ui back to the
top.
This leaves the origin at the top left like it is in every other popular
app framework.

Update the `world_to_viewport`, `viewport_to_world`, and
`viewport_to_world_2d` methods to flip the Y origin (as they should
since the viewport coordinates were always relative to the top left).

## Migration Guide

`Window::cursor_position` now returns the position of the cursor
relative to the top left instead of the bottom left.
This now matches other screen-space coordinates like
`RelativeCursorPosition`, UI, and viewports.

The `world_to_viewport`, `viewport_to_world`, and `viewport_to_world_2d`
methods on `Camera` now return/take the viewport position relative to
the top left instead of the bottom left.

If you were using `world_to_viewport` to position a UI node the returned
`y` value should now be passed into the `top` field on `Style` instead
of the `bottom` field.
Note that this might shift the position of the UI node as it is now
anchored at the top.

If you were passing `Window::cursor_position` to `viewport_to_world` or
`viewport_to_world_2d` no change is necessary.
  • Loading branch information
tim-blackbird authored Apr 5, 2023
1 parent 1a7f046 commit 585baf0
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 69 deletions.
13 changes: 10 additions & 3 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,10 @@ impl Camera {
}

// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
Some((ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_size)
let mut viewport_position = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_size;
// Flip the Y co-ordinate origin from the bottom to the top.
viewport_position.y = target_size.y - viewport_position.y;
Some(viewport_position)
}

/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
Expand All @@ -247,9 +250,11 @@ impl Camera {
pub fn viewport_to_world(
&self,
camera_transform: &GlobalTransform,
viewport_position: Vec2,
mut viewport_position: Vec2,
) -> Option<Ray> {
let target_size = self.logical_viewport_size()?;
// Flip the Y co-ordinate origin from the top to the bottom.
viewport_position.y = target_size.y - viewport_position.y;
let ndc = viewport_position * 2. / target_size - Vec2::ONE;

let ndc_to_world =
Expand All @@ -273,9 +278,11 @@ impl Camera {
pub fn viewport_to_world_2d(
&self,
camera_transform: &GlobalTransform,
viewport_position: Vec2,
mut viewport_position: Vec2,
) -> Option<Vec2> {
let target_size = self.logical_viewport_size()?;
// Flip the Y co-ordinate origin from the top to the bottom.
viewport_position.y = target_size.y - viewport_position.y;
let ndc = viewport_position * 2. / target_size - Vec2::ONE;

let world_near_plane = self.ndc_to_world(camera_transform, ndc.extend(1.))?;
Expand Down
10 changes: 4 additions & 6 deletions crates/bevy_ui/src/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,10 @@ pub fn ui_focus_system(
}
})
.find_map(|window_ref| {
windows.get(window_ref.entity()).ok().and_then(|window| {
window.cursor_position().map(|mut cursor_pos| {
cursor_pos.y = window.height() - cursor_pos.y;
cursor_pos
})
})
windows
.get(window_ref.entity())
.ok()
.and_then(|window| window.cursor_position())
})
.or_else(|| touches_input.first_pressed_position());

Expand Down
6 changes: 1 addition & 5 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,11 +434,7 @@ pub fn winit_runner(mut app: App) {
.send(converters::convert_keyboard_input(input));
}
WindowEvent::CursorMoved { position, .. } => {
let physical_position = DVec2::new(
position.x,
// Flip the coordinate space from winit's context to our context.
window.resolution.physical_height() as f64 - position.y,
);
let physical_position = DVec2::new(position.x, position.y);

window.set_physical_cursor_position(Some(physical_position));

Expand Down
81 changes: 30 additions & 51 deletions examples/3d/blend_modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,51 +220,34 @@ fn setup(
ExampleDisplay,
));

commands.spawn((
TextBundle::from_section("┌─ Opaque\n\n\n\n│", label_text_style.clone()).with_style(
Style {
position_type: PositionType::Absolute,
..default()
},
),
ExampleLabel { entity: opaque },
));

commands.spawn((
TextBundle::from_section("┌─ Blend\n\n\n│", label_text_style.clone()).with_style(Style {
position_type: PositionType::Absolute,
..default()
}),
ExampleLabel { entity: blend },
));

commands.spawn((
TextBundle::from_section("┌─ Premultiplied\n\n│", label_text_style.clone()).with_style(
Style {
position_type: PositionType::Absolute,
..default()
},
),
ExampleLabel {
entity: premultiplied,
},
));

commands.spawn((
TextBundle::from_section("┌─ Add\n│", label_text_style.clone()).with_style(Style {
position_type: PositionType::Absolute,
..default()
}),
ExampleLabel { entity: add },
));
let mut label = |entity: Entity, label: &str| {
commands
.spawn((
NodeBundle {
style: Style {
position_type: PositionType::Absolute,
..default()
},
..default()
},
ExampleLabel { entity },
))
.with_children(|parent| {
parent.spawn(
TextBundle::from_section(label, label_text_style.clone()).with_style(Style {
position_type: PositionType::Absolute,
bottom: Val::Px(0.),
..default()
}),
);
});
};

commands.spawn((
TextBundle::from_section("┌─ Multiply", label_text_style).with_style(Style {
position_type: PositionType::Absolute,
..default()
}),
ExampleLabel { entity: multiply },
));
label(opaque, "┌─ Opaque\n\n\n\n│");
label(blend, "┌─ Blend\n\n\n│");
label(premultiplied, "┌─ Premultiplied\n\n│");
label(add, "┌─ Add\n│");
label(multiply, "┌─ Multiply");
}

#[derive(Component)]
Expand Down Expand Up @@ -347,20 +330,16 @@ fn example_control_system(
0.0
};

camera_transform.rotate_around(
Vec3::ZERO,
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
);
camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(rotation));

for (mut style, label) in &mut labels {
let world_position =
labelled.get(label.entity).unwrap().translation() + Vec3::new(0.0, 1.0, 0.0);
let world_position = labelled.get(label.entity).unwrap().translation() + Vec3::Y;

let viewport_position = camera
.world_to_viewport(camera_global_transform, world_position)
.unwrap();

style.bottom = Val::Px(viewport_position.y);
style.top = Val::Px(viewport_position.y);
style.left = Val::Px(viewport_position.x);
}

Expand Down
5 changes: 1 addition & 4 deletions examples/input/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@ fn toggle_ime(
if input.just_pressed(MouseButton::Left) {
let mut window = windows.single_mut();

window.ime_position = window
.cursor_position()
.map(|p| Vec2::new(p.x, window.height() - p.y))
.unwrap();
window.ime_position = window.cursor_position().unwrap();
window.ime_enabled = !window.ime_enabled;

let mut text = text.single_mut();
Expand Down

0 comments on commit 585baf0

Please sign in to comment.