diff --git a/Cargo.toml b/Cargo.toml index b0f2d6f610..b6967a880d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -290,6 +290,16 @@ description = "Changes the transform of a sprite" category = "2D Rendering" wasm = true +[[example]] +name = "2d_viewport_to_world" +path = "examples/2d/2d_viewport_to_world.rs" + +[package.metadata.example.2d_viewport_to_world] +name = "2D Viewport To World" +description = "Demonstrates how to use the `Camera::viewport_to_world_2d` method" +category = "2D Rendering" +wasm = true + [[example]] name = "rotation" path = "examples/2d/rotation.rs" @@ -467,6 +477,17 @@ description = "A scene showcasing the built-in 3D shapes" category = "3D Rendering" wasm = true +[[example]] +name = "3d_viewport_to_world" +path = "examples/3d/3d_viewport_to_world.rs" +doc-scrape-examples = true + +[package.metadata.example.3d_viewport_to_world] +name = "3D Viewport To World" +description = "Demonstrates how to use the `Camera::viewport_to_world` method" +category = "3D Rendering" +wasm = true + [[example]] name = "generate_custom_mesh" path = "examples/3d/generate_custom_mesh.rs" diff --git a/examples/2d/2d_viewport_to_world.rs b/examples/2d/2d_viewport_to_world.rs new file mode 100644 index 0000000000..8ed4f881f9 --- /dev/null +++ b/examples/2d/2d_viewport_to_world.rs @@ -0,0 +1,34 @@ +//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, draw_cursor) + .run(); +} + +fn draw_cursor( + camera_query: Query<(&Camera, &GlobalTransform)>, + windows: Query<&Window>, + mut gizmos: Gizmos, +) { + let (camera, camera_transform) = camera_query.single(); + + let Some(cursor_position) = windows.single().cursor_position() else { + return; + }; + + // Calculate a world position based on the cursor's position. + let Some(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else { + return; + }; + + gizmos.circle_2d(point, 10., Color::WHITE); +} + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); +} diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs new file mode 100644 index 0000000000..d2c175197f --- /dev/null +++ b/examples/3d/3d_viewport_to_world.rs @@ -0,0 +1,70 @@ +//! This example demonstrates how to use the `Camera::viewport_to_world` method. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, draw_cursor) + .run(); +} + +fn draw_cursor( + camera_query: Query<(&Camera, &GlobalTransform)>, + ground_query: Query<&GlobalTransform, With>, + windows: Query<&Window>, + mut gizmos: Gizmos, +) { + let (camera, camera_transform) = camera_query.single(); + let ground = ground_query.single(); + + let Some(cursor_position) = windows.single().cursor_position() else { + return; + }; + + // Calculate a ray pointing from the camera into the world based on the cursor's position. + let Some(ray) = camera.viewport_to_world(camera_transform, cursor_position) else { + return; + }; + + // Calculate if and where the ray is hitting the ground plane. + let Some(distance) = ray.intersect_plane(ground.translation(), ground.up()) else { + return; + }; + let point = ray.get_point(distance); + + // Draw a circle just above the ground plane at that position. + gizmos.circle(point + ground.up() * 0.01, ground.up(), 0.2, Color::WHITE); +} + +#[derive(Component)] +struct Ground; + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // plane + commands.spawn(( + PbrBundle { + mesh: meshes.add(shape::Plane::from_size(20.).into()), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..default() + }, + Ground, + )); + + // light + commands.spawn(DirectionalLightBundle { + transform: Transform::from_translation(Vec3::ONE).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // camera + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(15.0, 5.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} diff --git a/examples/README.md b/examples/README.md index d48c6646ae..d345cd5707 100644 --- a/examples/README.md +++ b/examples/README.md @@ -95,6 +95,7 @@ Example | Description [2D Gizmos](../examples/2d/2d_gizmos.rs) | A scene showcasing 2D gizmos [2D Rotation](../examples/2d/rotation.rs) | Demonstrates rotating entities in 2D with quaternions [2D Shapes](../examples/2d/2d_shapes.rs) | Renders a rectangle, circle, and hexagon +[2D Viewport To World](../examples/2d/2d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world_2d` method [Custom glTF vertex attribute 2D](../examples/2d/custom_gltf_vertex_attribute.rs) | Renders a glTF mesh in 2D with a custom vertex attribute [Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis [Mesh 2D](../examples/2d/mesh2d.rs) | Renders a 2d mesh @@ -116,6 +117,7 @@ Example | Description [3D Gizmos](../examples/3d/3d_gizmos.rs) | A scene showcasing 3D gizmos [3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting [3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes +[3D Viewport To World](../examples/3d/3d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world` method [Anti-aliasing](../examples/3d/anti_aliasing.rs) | Compares different anti-aliasing methods [Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect [Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes