Skip to content

Commit 03a9f7e

Browse files
committed
Adding Screen to world function
1 parent 6b6df6d commit 03a9f7e

File tree

5 files changed

+209
-3
lines changed

5 files changed

+209
-3
lines changed

Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,19 @@ path = "examples/2d/text2d.rs"
165165
name = "texture_atlas"
166166
path = "examples/2d/texture_atlas.rs"
167167

168+
[[example]]
169+
name = "mouse_tracking"
170+
path = "examples/2d/mouse_tracking.rs"
171+
168172
# 3D Rendering
169173
[[example]]
170174
name = "3d_scene"
171175
path = "examples/3d/3d_scene.rs"
172176

177+
[[example]]
178+
name = "screen_to_world"
179+
path = "examples/3d/screen_to_world.rs"
180+
173181
[[example]]
174182
name = "lighting"
175183
path = "examples/3d/lighting.rs"

crates/bevy_render/src/camera/camera.rs

+75-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use crate::{
2-
camera::CameraProjection, prelude::Image, render_asset::RenderAssets,
3-
render_resource::TextureView, view::ExtractedWindows,
2+
camera::CameraProjection,
3+
prelude::Image,
4+
primitives::{Line, Plane},
5+
render_asset::RenderAssets,
6+
render_resource::TextureView,
7+
view::ExtractedWindows,
48
};
59
use bevy_asset::{AssetEvent, Assets, Handle};
610
use bevy_ecs::{
@@ -12,7 +16,7 @@ use bevy_ecs::{
1216
reflect::ReflectComponent,
1317
system::{QuerySet, Res},
1418
};
15-
use bevy_math::{Mat4, UVec2, Vec2, Vec3};
19+
use bevy_math::{Mat4, UVec2, Vec2, Vec3, Vec4};
1620
use bevy_reflect::{Reflect, ReflectDeserialize};
1721
use bevy_transform::components::GlobalTransform;
1822
use bevy_utils::HashSet;
@@ -138,6 +142,74 @@ impl Camera {
138142
None
139143
}
140144
}
145+
146+
/// Given a position in screen space, compute the world-space line that corresponds to it.
147+
pub fn screen_to_world_ray(
148+
&self,
149+
pos_screen: Vec2,
150+
windows: &Windows,
151+
images: &Assets<Image>,
152+
camera_transform: &GlobalTransform,
153+
) -> Line {
154+
let camera_position = camera_transform.compute_matrix();
155+
let window_size = self.target.get_logical_size(windows, images).unwrap();
156+
let projection_matrix = self.projection_matrix;
157+
158+
// Normalized device coordinate cursor position from (-1, -1, -1) to (1, 1, 1)
159+
let cursor_ndc = (pos_screen / window_size) * 2.0 - Vec2::from([1.0, 1.0]);
160+
let cursor_pos_ndc_near: Vec3 = cursor_ndc.extend(-1.0);
161+
let cursor_pos_ndc_far: Vec3 = cursor_ndc.extend(1.0);
162+
163+
// Use near and far ndc points to generate a ray in world space
164+
// This method is more robust than using the location of the camera as the start of
165+
// the ray, because ortho cameras have a focal point at infinity!
166+
let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse();
167+
let cursor_pos_near: Vec3 = ndc_to_world.project_point3(cursor_pos_ndc_near);
168+
let cursor_pos_far: Vec3 = ndc_to_world.project_point3(cursor_pos_ndc_far);
169+
let ray_direction = cursor_pos_far - cursor_pos_near;
170+
Line::from_point_direction(cursor_pos_near, ray_direction)
171+
}
172+
173+
/// Given a position in screen space and a plane in world space, compute what point on the plane the point in screen space corresponds to.
174+
/// In 2D, use `screen_to_point_2d`.
175+
pub fn screen_to_point_on_plane(
176+
&self,
177+
pos_screen: Vec2,
178+
plane: Plane,
179+
windows: &Windows,
180+
images: &Assets<Image>,
181+
camera_transform: &GlobalTransform,
182+
) -> Option<Vec3> {
183+
let world_ray = self.screen_to_world_ray(pos_screen, windows, images, camera_transform);
184+
let d = world_ray.point.dot(plane.normal());
185+
if d == 0. {
186+
None
187+
} else {
188+
let diff = world_ray.point.extend(1.0) - plane.normal_d();
189+
let p = diff.dot(plane.normal_d());
190+
let dist = p / d;
191+
Some(world_ray.point - world_ray.direction * dist)
192+
}
193+
}
194+
195+
/// Computes the world position for a given screen position.
196+
/// The output will always be on the XY plane with Z at zero. It is designed for 2D, but also works with a 3D camera.
197+
/// For more flexibility in 3D, consider `screen_to_point_on_plane`.
198+
pub fn screen_to_point_2d(
199+
&self,
200+
pos_screen: Vec2,
201+
windows: &Windows,
202+
images: &Assets<Image>,
203+
camera_transform: &GlobalTransform,
204+
) -> Option<Vec3> {
205+
self.screen_to_point_on_plane(
206+
pos_screen,
207+
Plane::new(Vec4::new(0., 0., 1., 0.)),
208+
windows,
209+
images,
210+
camera_transform,
211+
)
212+
}
141213
}
142214

143215
#[allow(clippy::type_complexity)]

crates/bevy_render/src/primitives/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,18 @@ impl CubemapFrusta {
192192
}
193193
}
194194

195+
#[derive(Clone, Copy, Debug, Default)]
196+
pub struct Line {
197+
pub point: Vec3,
198+
pub direction: Vec3,
199+
}
200+
201+
impl Line {
202+
pub fn from_point_direction(point: Vec3, direction: Vec3) -> Self {
203+
Self { point, direction }
204+
}
205+
}
206+
195207
#[cfg(test)]
196208
mod tests {
197209
use super::*;

examples/2d/mouse_tracking.rs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use bevy::{prelude::*, render::camera::Camera};
2+
3+
fn main() {
4+
App::new()
5+
.add_plugins(DefaultPlugins)
6+
.add_startup_system(setup)
7+
.add_system(follow)
8+
.run();
9+
}
10+
11+
#[derive(Component)]
12+
struct Follow;
13+
14+
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
15+
let texture_handle = asset_server.load("branding/icon.png");
16+
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
17+
commands
18+
.spawn_bundle(SpriteBundle {
19+
texture: texture_handle,
20+
..Default::default()
21+
})
22+
.insert(Follow);
23+
}
24+
25+
fn follow(
26+
mut q: Query<&mut Transform, With<Follow>>,
27+
q_camera: Query<(&Camera, &GlobalTransform)>,
28+
windows: Res<Windows>,
29+
images: Res<Assets<Image>>,
30+
mut evr_cursor: EventReader<CursorMoved>,
31+
) {
32+
let (camera, camera_transform) = q_camera.single();
33+
if let Some(cursor) = evr_cursor.iter().next() {
34+
for mut transform in q.iter_mut() {
35+
let point: Option<Vec3> =
36+
camera.screen_to_point_2d(cursor.position, &windows, &images, camera_transform);
37+
println!("Point {:?}", point);
38+
if let Some(point) = point {
39+
transform.translation = point;
40+
}
41+
}
42+
}
43+
}

examples/3d/screen_to_world.rs

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use bevy::{prelude::*, render::camera::Camera, render::primitives::Plane};
2+
3+
fn main() {
4+
App::new()
5+
.insert_resource(Msaa { samples: 4 })
6+
.add_plugins(DefaultPlugins)
7+
.add_startup_system(setup)
8+
.add_system(follow)
9+
.run();
10+
}
11+
12+
#[derive(Component)]
13+
struct Follow;
14+
15+
/// set up a simple 3D scene
16+
fn setup(
17+
mut commands: Commands,
18+
mut meshes: ResMut<Assets<Mesh>>,
19+
mut materials: ResMut<Assets<StandardMaterial>>,
20+
) {
21+
// plane
22+
commands.spawn_bundle(PbrBundle {
23+
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
24+
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
25+
..Default::default()
26+
});
27+
// cube
28+
commands
29+
.spawn_bundle(PbrBundle {
30+
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
31+
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
32+
transform: Transform::from_xyz(0.0, 0.5, 0.0),
33+
..Default::default()
34+
})
35+
.insert(Follow);
36+
// light
37+
commands.spawn_bundle(PointLightBundle {
38+
transform: Transform::from_xyz(4.0, 8.0, 4.0),
39+
..Default::default()
40+
});
41+
// camera
42+
commands.spawn_bundle(PerspectiveCameraBundle {
43+
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
44+
..Default::default()
45+
});
46+
}
47+
48+
fn follow(
49+
mut q: Query<&mut Transform, With<Follow>>,
50+
q_camera: Query<(&Camera, &GlobalTransform)>,
51+
windows: Res<Windows>,
52+
images: Res<Assets<Image>>,
53+
mut evr_cursor: EventReader<CursorMoved>,
54+
) {
55+
// Assumes there is at least one camera
56+
let (camera, camera_transform) = q_camera.iter().next().unwrap();
57+
if let Some(cursor) = evr_cursor.iter().next() {
58+
for mut transform in q.iter_mut() {
59+
let point: Option<Vec3> = camera.screen_to_point_on_plane(
60+
cursor.position,
61+
Plane::new(Vec4::new(0., 1., 0., 1.)),
62+
&windows,
63+
&images,
64+
camera_transform,
65+
);
66+
if let Some(point) = point {
67+
transform.translation = point + Vec3::new(0., 0.5, 0.);
68+
}
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)