Skip to content

Commit 47772ea

Browse files
committed
Extend viewport conversion example with dynamic viewport
1 parent 4c519dd commit 47772ea

File tree

1 file changed

+129
-7
lines changed

1 file changed

+129
-7
lines changed

examples/2d/2d_viewport_to_world.rs

+129-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method.
22
3-
use bevy::{color::palettes::basic::WHITE, prelude::*};
3+
use bevy::render::camera::Viewport;
4+
use bevy::{
5+
color::palettes::{basic::WHITE, css::GREEN},
6+
prelude::*,
7+
};
48

59
fn main() {
610
App::new()
711
.add_plugins(DefaultPlugins)
812
.add_systems(Startup, setup)
9-
.add_systems(Update, draw_cursor)
13+
.add_systems(Update, (draw_cursor, controls, viewport_border_gizmos))
1014
.run();
1115
}
1216

@@ -26,24 +30,142 @@ fn draw_cursor(
2630
};
2731

2832
// Calculate a world position based on the cursor's position.
29-
let Ok(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
33+
let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
3034
return;
3135
};
3236

33-
gizmos.circle_2d(point, 10., WHITE);
37+
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
38+
let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
39+
else {
40+
return;
41+
};
42+
let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy()) else {
43+
return;
44+
};
45+
46+
gizmos.circle_2d(world_pos, 10., WHITE);
47+
// Should be the same as world_pos
48+
gizmos.circle_2d(world_check, 8., GREEN);
49+
}
50+
51+
fn viewport_border_gizmos(camera_query: Single<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
52+
let (camera, camera_transform) = *camera_query;
53+
let Some(viewport) = camera.logical_viewport_rect() else {
54+
return;
55+
};
56+
57+
let Ok(min) = camera.viewport_to_world_2d(camera_transform, viewport.min) else {
58+
return;
59+
};
60+
let Ok(max) = camera.viewport_to_world_2d(camera_transform, viewport.max) else {
61+
return;
62+
};
63+
64+
gizmos.line_2d(Vec2::new(min.x, min.y), Vec2::new(max.x, min.y), WHITE);
65+
gizmos.line_2d(Vec2::new(max.x, min.y), Vec2::new(max.x, max.y), WHITE);
66+
gizmos.line_2d(Vec2::new(max.x, max.y), Vec2::new(min.x, max.y), WHITE);
67+
gizmos.line_2d(Vec2::new(min.x, max.y), Vec2::new(min.x, min.y), WHITE);
3468
}
3569

36-
fn setup(mut commands: Commands) {
37-
commands.spawn(Camera2d);
70+
fn controls(
71+
mut camera_query: Query<(&mut Camera, &mut Transform)>,
72+
input: Res<ButtonInput<KeyCode>>,
73+
) {
74+
for (mut camera, mut transform) in camera_query.iter_mut() {
75+
if input.pressed(KeyCode::ArrowUp) {
76+
transform.translation.y += 3.0;
77+
}
78+
if input.pressed(KeyCode::ArrowDown) {
79+
transform.translation.y -= 3.0;
80+
}
81+
if input.pressed(KeyCode::ArrowLeft) {
82+
transform.translation.x -= 3.0;
83+
}
84+
if input.pressed(KeyCode::ArrowRight) {
85+
transform.translation.x += 3.0;
86+
}
87+
88+
if input.pressed(KeyCode::Comma) {
89+
transform.scale *= 0.95;
90+
}
91+
92+
if input.pressed(KeyCode::Period) {
93+
transform.scale *= 1.05;
94+
}
95+
96+
if let Some(viewport) = camera.viewport.as_mut() {
97+
if input.pressed(KeyCode::KeyW) {
98+
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(3);
99+
}
100+
if input.pressed(KeyCode::KeyS) {
101+
viewport.physical_position.y =
102+
(viewport.physical_position.y + 3).min(720 - viewport.physical_size.y);
103+
}
104+
if input.pressed(KeyCode::KeyA) {
105+
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(3);
106+
}
107+
if input.pressed(KeyCode::KeyD) {
108+
viewport.physical_position.x =
109+
(viewport.physical_position.x + 3).min(1280 - viewport.physical_size.x);
110+
}
111+
112+
if input.pressed(KeyCode::KeyI) {
113+
viewport.physical_size.y =
114+
viewport.physical_size.y.checked_sub(3).unwrap_or(1).max(1);
115+
}
116+
if input.pressed(KeyCode::KeyK) {
117+
viewport.physical_size.y = (viewport.physical_size.y + 3).min(720);
118+
}
119+
if input.pressed(KeyCode::KeyJ) {
120+
viewport.physical_size.x =
121+
viewport.physical_size.x.checked_sub(3).unwrap_or(1).max(1);
122+
}
123+
if input.pressed(KeyCode::KeyL) {
124+
viewport.physical_size.x = (viewport.physical_size.x + 3).min(1280);
125+
}
126+
127+
let max_viewport_size = UVec2::new(1280, 720) - viewport.physical_position;
128+
viewport.physical_size = viewport.physical_size.min(max_viewport_size);
129+
}
130+
}
131+
}
132+
133+
fn setup(
134+
mut commands: Commands,
135+
mut meshes: ResMut<Assets<Mesh>>,
136+
mut materials: ResMut<Assets<ColorMaterial>>,
137+
) {
138+
commands.spawn((
139+
Camera2d,
140+
Camera {
141+
viewport: Some(Viewport {
142+
physical_position: UVec2::new(320, 180),
143+
physical_size: UVec2::new(640, 360),
144+
..default()
145+
}),
146+
..default()
147+
},
148+
));
38149

39150
// Create a minimal UI explaining how to interact with the example
40151
commands.spawn((
41-
Text::new("Move the mouse to see the circle follow your cursor."),
152+
Text::new(
153+
"Move the mouse to see the circle follow your cursor.\n\
154+
Use the arrow keys to move the camera.\n\
155+
Use the comma and period keys to zoom in and out.\n\
156+
Use the WASD keys to move the viewport.\n\
157+
Use the IJKL keys to resize the viewport.",
158+
),
42159
Node {
43160
position_type: PositionType::Absolute,
44161
top: Val::Px(12.0),
45162
left: Val::Px(12.0),
46163
..default()
47164
},
48165
));
166+
167+
commands.spawn((
168+
Mesh2d(meshes.add(Rectangle::new(10.0, 20.0))),
169+
MeshMaterial2d(materials.add(Color::from(GREEN))),
170+
));
49171
}

0 commit comments

Comments
 (0)