Skip to content

Commit

Permalink
Extend viewport conversion example with dynamic viewport
Browse files Browse the repository at this point in the history
  • Loading branch information
Novakasa committed Feb 1, 2025
1 parent 4c519dd commit b7e8a03
Showing 1 changed file with 129 additions and 7 deletions.
136 changes: 129 additions & 7 deletions examples/2d/2d_viewport_to_world.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method.
use bevy::{color::palettes::basic::WHITE, prelude::*};
use bevy::{
color::palettes::{basic::WHITE, css::GREEN},
prelude::*,
};
use bevy_render::camera::Viewport;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, draw_cursor)
.add_systems(Update, (draw_cursor, controls, viewport_border_gizmos))
.run();
}

Expand All @@ -26,24 +30,142 @@ fn draw_cursor(
};

// Calculate a world position based on the cursor's position.
let Ok(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
return;
};

gizmos.circle_2d(point, 10., WHITE);
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
else {
return;
};
let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy()) else {
return;
};

gizmos.circle_2d(world_pos, 10., WHITE);
// Should be the same as world_pos
gizmos.circle_2d(world_check, 8., GREEN);
}

fn viewport_border_gizmos(camera_query: Single<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
let (camera, camera_transform) = *camera_query;
let Some(viewport) = camera.logical_viewport_rect() else {
return;
};

let Ok(min) = camera.viewport_to_world_2d(camera_transform, viewport.min) else {
return;
};
let Ok(max) = camera.viewport_to_world_2d(camera_transform, viewport.max) else {
return;
};

gizmos.line_2d(Vec2::new(min.x, min.y), Vec2::new(max.x, min.y), WHITE);
gizmos.line_2d(Vec2::new(max.x, min.y), Vec2::new(max.x, max.y), WHITE);
gizmos.line_2d(Vec2::new(max.x, max.y), Vec2::new(min.x, max.y), WHITE);
gizmos.line_2d(Vec2::new(min.x, max.y), Vec2::new(min.x, min.y), WHITE);
}

fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
fn controls(
mut camera_query: Query<(&mut Camera, &mut Transform)>,
input: Res<ButtonInput<KeyCode>>,
) {
for (mut camera, mut transform) in camera_query.iter_mut() {
if input.pressed(KeyCode::ArrowUp) {
transform.translation.y += 3.0;
}
if input.pressed(KeyCode::ArrowDown) {
transform.translation.y -= 3.0;
}
if input.pressed(KeyCode::ArrowLeft) {
transform.translation.x -= 3.0;
}
if input.pressed(KeyCode::ArrowRight) {
transform.translation.x += 3.0;
}

if input.pressed(KeyCode::Comma) {
transform.scale *= 0.95;
}

if input.pressed(KeyCode::Period) {
transform.scale *= 1.05;
}

if let Some(viewport) = camera.viewport.as_mut() {
if input.pressed(KeyCode::KeyW) {
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(3);
}
if input.pressed(KeyCode::KeyS) {
viewport.physical_position.y =
(viewport.physical_position.y + 3).min(720 - viewport.physical_size.y);
}
if input.pressed(KeyCode::KeyA) {
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(3);
}
if input.pressed(KeyCode::KeyD) {
viewport.physical_position.x =
(viewport.physical_position.x + 3).min(1280 - viewport.physical_size.x);
}

if input.pressed(KeyCode::KeyI) {
viewport.physical_size.y =
viewport.physical_size.y.checked_sub(3).unwrap_or(1).max(1);
}
if input.pressed(KeyCode::KeyK) {
viewport.physical_size.y = (viewport.physical_size.y + 3).min(720);
}
if input.pressed(KeyCode::KeyJ) {
viewport.physical_size.x =
viewport.physical_size.x.checked_sub(3).unwrap_or(1).max(1);
}
if input.pressed(KeyCode::KeyL) {
viewport.physical_size.x = (viewport.physical_size.x + 3).min(1280);
}

let max_viewport_size = UVec2::new(1280, 720) - viewport.physical_position;
viewport.physical_size = viewport.physical_size.min(max_viewport_size);
}
}
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn((
Camera2d,
Camera {
viewport: Some(Viewport {
physical_position: UVec2::new(320, 180),
physical_size: UVec2::new(640, 360),
..default()
}),
..default()
},
));

// Create a minimal UI explaining how to interact with the example
commands.spawn((
Text::new("Move the mouse to see the circle follow your cursor."),
Text::new(
"Move the mouse to see the circle follow your cursor.\n\
Use the arrow keys to move the camera.\n\
Use the comma and period keys to zoom in and out.\n\
Use the WASD keys to move the viewport.\n\
Use the IJKL keys to resize the viewport.",
),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));

commands.spawn((
Mesh2d(meshes.add(Rectangle::new(10.0, 20.0))),
MeshMaterial2d(materials.add(Color::from(GREEN))),
));
}

0 comments on commit b7e8a03

Please sign in to comment.