Consistent screen-space coordinates (#8306)
# 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.
This commit is contained in:
parent
1a7f046c4d
commit
585baf0a66
@ -233,7 +233,10 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Once in NDC space, we can discard the z element and rescale x/y to fit the screen
|
// 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`.
|
/// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
|
||||||
@ -247,9 +250,11 @@ impl Camera {
|
|||||||
pub fn viewport_to_world(
|
pub fn viewport_to_world(
|
||||||
&self,
|
&self,
|
||||||
camera_transform: &GlobalTransform,
|
camera_transform: &GlobalTransform,
|
||||||
viewport_position: Vec2,
|
mut viewport_position: Vec2,
|
||||||
) -> Option<Ray> {
|
) -> Option<Ray> {
|
||||||
let target_size = self.logical_viewport_size()?;
|
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 = viewport_position * 2. / target_size - Vec2::ONE;
|
||||||
|
|
||||||
let ndc_to_world =
|
let ndc_to_world =
|
||||||
@ -273,9 +278,11 @@ impl Camera {
|
|||||||
pub fn viewport_to_world_2d(
|
pub fn viewport_to_world_2d(
|
||||||
&self,
|
&self,
|
||||||
camera_transform: &GlobalTransform,
|
camera_transform: &GlobalTransform,
|
||||||
viewport_position: Vec2,
|
mut viewport_position: Vec2,
|
||||||
) -> Option<Vec2> {
|
) -> Option<Vec2> {
|
||||||
let target_size = self.logical_viewport_size()?;
|
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 = viewport_position * 2. / target_size - Vec2::ONE;
|
||||||
|
|
||||||
let world_near_plane = self.ndc_to_world(camera_transform, ndc.extend(1.))?;
|
let world_near_plane = self.ndc_to_world(camera_transform, ndc.extend(1.))?;
|
||||||
|
|||||||
@ -180,12 +180,10 @@ pub fn ui_focus_system(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.find_map(|window_ref| {
|
.find_map(|window_ref| {
|
||||||
windows.get(window_ref.entity()).ok().and_then(|window| {
|
windows
|
||||||
window.cursor_position().map(|mut cursor_pos| {
|
.get(window_ref.entity())
|
||||||
cursor_pos.y = window.height() - cursor_pos.y;
|
.ok()
|
||||||
cursor_pos
|
.and_then(|window| window.cursor_position())
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.or_else(|| touches_input.first_pressed_position());
|
.or_else(|| touches_input.first_pressed_position());
|
||||||
|
|
||||||
|
|||||||
@ -434,11 +434,7 @@ pub fn winit_runner(mut app: App) {
|
|||||||
.send(converters::convert_keyboard_input(input));
|
.send(converters::convert_keyboard_input(input));
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
let physical_position = DVec2::new(
|
let physical_position = DVec2::new(position.x, position.y);
|
||||||
position.x,
|
|
||||||
// Flip the coordinate space from winit's context to our context.
|
|
||||||
window.resolution.physical_height() as f64 - position.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.set_physical_cursor_position(Some(physical_position));
|
window.set_physical_cursor_position(Some(physical_position));
|
||||||
|
|
||||||
|
|||||||
@ -220,51 +220,34 @@ fn setup(
|
|||||||
ExampleDisplay,
|
ExampleDisplay,
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn((
|
let mut label = |entity: Entity, label: &str| {
|
||||||
TextBundle::from_section("┌─ Opaque\n│\n│\n│\n│", label_text_style.clone()).with_style(
|
commands
|
||||||
Style {
|
.spawn((
|
||||||
position_type: PositionType::Absolute,
|
NodeBundle {
|
||||||
..default()
|
style: Style {
|
||||||
},
|
position_type: PositionType::Absolute,
|
||||||
),
|
..default()
|
||||||
ExampleLabel { entity: opaque },
|
},
|
||||||
));
|
..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((
|
label(opaque, "┌─ Opaque\n│\n│\n│\n│");
|
||||||
TextBundle::from_section("┌─ Blend\n│\n│\n│", label_text_style.clone()).with_style(Style {
|
label(blend, "┌─ Blend\n│\n│\n│");
|
||||||
position_type: PositionType::Absolute,
|
label(premultiplied, "┌─ Premultiplied\n│\n│");
|
||||||
..default()
|
label(add, "┌─ Add\n│");
|
||||||
}),
|
label(multiply, "┌─ Multiply");
|
||||||
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 },
|
|
||||||
));
|
|
||||||
|
|
||||||
commands.spawn((
|
|
||||||
TextBundle::from_section("┌─ Multiply", label_text_style).with_style(Style {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
ExampleLabel { entity: multiply },
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@ -347,20 +330,16 @@ fn example_control_system(
|
|||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
camera_transform.rotate_around(
|
camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(rotation));
|
||||||
Vec3::ZERO,
|
|
||||||
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (mut style, label) in &mut labels {
|
for (mut style, label) in &mut labels {
|
||||||
let world_position =
|
let world_position = labelled.get(label.entity).unwrap().translation() + Vec3::Y;
|
||||||
labelled.get(label.entity).unwrap().translation() + Vec3::new(0.0, 1.0, 0.0);
|
|
||||||
|
|
||||||
let viewport_position = camera
|
let viewport_position = camera
|
||||||
.world_to_viewport(camera_global_transform, world_position)
|
.world_to_viewport(camera_global_transform, world_position)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
style.bottom = Val::Px(viewport_position.y);
|
style.top = Val::Px(viewport_position.y);
|
||||||
style.left = Val::Px(viewport_position.x);
|
style.left = Val::Px(viewport_position.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -108,10 +108,7 @@ fn toggle_ime(
|
|||||||
if input.just_pressed(MouseButton::Left) {
|
if input.just_pressed(MouseButton::Left) {
|
||||||
let mut window = windows.single_mut();
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
window.ime_position = window
|
window.ime_position = window.cursor_position().unwrap();
|
||||||
.cursor_position()
|
|
||||||
.map(|p| Vec2::new(p.x, window.height() - p.y))
|
|
||||||
.unwrap();
|
|
||||||
window.ime_enabled = !window.ime_enabled;
|
window.ime_enabled = !window.ime_enabled;
|
||||||
|
|
||||||
let mut text = text.single_mut();
|
let mut text = text.single_mut();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user