From c22b434ce585bf568ccbdaba0316fa579e0e150b Mon Sep 17 00:00:00 2001 From: Andre Popovitch Date: Fri, 2 Apr 2021 11:53:48 -0400 Subject: [PATCH] Added function for converting between screen and world space --- Cargo.toml | 8 + crates/bevy_geometry/Cargo.toml | 15 + crates/bevy_geometry/src/lib.rs | 383 ++++++++++++++++++++++++ crates/bevy_internal/Cargo.toml | 7 +- crates/bevy_internal/src/lib.rs | 5 + crates/bevy_render/Cargo.toml | 1 + crates/bevy_render/src/camera/camera.rs | 62 ++++ examples/2d/mouse_tracking.rs | 46 +++ examples/3d/3d_scene.rs | 2 + examples/3d/screen_to_world.rs | 71 +++++ examples/README.md | 204 +++++++------ rustfmt.toml | 2 + tools/publish.sh | 1 + 13 files changed, 711 insertions(+), 96 deletions(-) create mode 100644 crates/bevy_geometry/Cargo.toml create mode 100644 crates/bevy_geometry/src/lib.rs create mode 100644 examples/2d/mouse_tracking.rs create mode 100644 examples/3d/screen_to_world.rs diff --git a/Cargo.toml b/Cargo.toml index 043341a63732e7..05bad0febbc03d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,11 +121,19 @@ path = "examples/2d/text2d.rs" name = "texture_atlas" path = "examples/2d/texture_atlas.rs" +[[example]] +name = "mouse_tracking" +path = "examples/2d/mouse_tracking.rs" + # 3D Rendering [[example]] name = "3d_scene" path = "examples/3d/3d_scene.rs" +[[example]] +name = "screen_to_world" +path = "examples/3d/screen_to_world.rs" + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" diff --git a/crates/bevy_geometry/Cargo.toml b/crates/bevy_geometry/Cargo.toml new file mode 100644 index 00000000000000..fe6d31bd2b12b7 --- /dev/null +++ b/crates/bevy_geometry/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bevy_geometry" +version = "0.4.0" +authors = [ + "Bevy Contributors ", + "Aevyrie Roessler ", +] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs new file mode 100644 index 00000000000000..3473eaa33eea33 --- /dev/null +++ b/crates/bevy_geometry/src/lib.rs @@ -0,0 +1,383 @@ +use bevy_math::*; +use bevy_reflect::Reflect; +use std::error::Error; +use std::fmt; + +pub trait Primitive3d { + /// Returns true if this primitive is entirely on the outside (in the normal direction) of the + /// supplied plane. + fn outside_plane(&self, plane: Plane) -> bool; +} + +#[derive(Debug, Clone)] +pub enum PrimitiveError { + MinGreaterThanMax, + NonPositiveExtents, +} +impl Error for PrimitiveError {} +impl fmt::Display for PrimitiveError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PrimitiveError::MinGreaterThanMax => { + write!( + f, + "AxisAlignedBox minimums must be smaller or equal to the maximums" + ) + } + PrimitiveError::NonPositiveExtents => { + write!(f, "AxisAlignedBox extents must be greater than zero") + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +pub struct Sphere { + origin: Vec3, + radius: f32, +} + +impl Sphere { + /// Get the sphere's origin. + pub fn origin(&self) -> Vec3 { + self.origin + } + + /// Get the sphere's radius. + pub fn radius(&self) -> f32 { + self.radius + } + + /// Set the sphere's origin. + pub fn set_origin(&mut self, origin: Vec3) { + self.origin = origin; + } + + /// Set the sphere's radius. + pub fn set_radius(&mut self, radius: f32) { + self.radius = radius; + } +} +impl Primitive3d for Sphere { + /// Use the sphere's position and radius to determin eif it is entirely on the outside of the + /// the supplied plane. + fn outside_plane(&self, plane: Plane) -> bool { + plane.distance_to_point(self.origin) > self.radius + } +} + +/// An oriented box, unlike an axis aligned box, can be rotated and is not constrained to match the +/// orientation of the coordinate system it is defined in. Internally, this is represented as an +/// axis aligned box with some rotation ([Quat]) applied. +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +pub struct OBB { + aab: AABB, + transform: Mat4, +} +impl Primitive3d for OBB { + fn outside_plane(&self, plane: Plane) -> bool { + for vertex in self.vertices().iter() { + if plane.distance_to_point(*vertex) <= 0.0 { + return false; + } + } + true + } +} +impl OBB { + /// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox]. + /// ```none + /// (5)------(1) + /// | \ | \ + /// | (4)------(0) + /// | | | | + /// (7)--|---(3) | + /// \ | \ | + /// (6)------(2) + /// ``` + pub fn vertices(&self) -> [Vec3; 8] { + let mut vertices = [Vec3::ZERO; 8]; + let aab_vertices = self.aab.vertices(); + for i in 0..vertices.len() { + vertices[i] = self.transform.project_point3(aab_vertices[i]) + } + vertices + } + + /// Set the oriented box's aab. + pub fn set_aabb(&mut self, aab: AABB) { + self.aab = aab; + } + + /// Set the oriented box's transform. + pub fn set_transform(&mut self, transform: Mat4) { + self.transform = transform; + } + pub fn fast_aabb(&self) -> AABB { + let vertices = self.vertices(); + let mut max = Vec3::splat(f32::MIN); + let mut min = Vec3::splat(f32::MAX); + for vertex in vertices.iter() { + max = vertex.max(max); + min = vertex.min(min); + } + // Unwrap is okay here because min < max + AABB::from_min_max(min, max).unwrap() + } +} + +/// An axis aligned box is a box whose axes lie in the x/y/z directions of the coordinate system +/// the box is defined in. +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +pub struct AABB { + min: Vec3, + max: Vec3, +} +impl Primitive3d for AABB { + fn outside_plane(&self, plane: Plane) -> bool { + for vertex in self.vertices().iter() { + if plane.distance_to_point(*vertex) <= 0.0 { + return false; + } + } + true + } +} +impl AABB { + /// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox]. + /// ```none + /// (5)------(1) Y + /// | \ | \ | + /// | (4)------(0) MAX o---X + /// | | | | \ + /// MIN (7)--|---(3) | Z + /// \ | \ | + /// (6)------(2) + /// ``` + pub fn vertices(&self) -> [Vec3; 8] { + let min = self.min; + let max = self.max; + [ + Vec3::new(max.x, max.y, max.z), + Vec3::new(max.x, max.y, min.z), + Vec3::new(max.x, min.y, max.z), + Vec3::new(max.x, min.y, min.z), + Vec3::new(min.x, max.y, max.z), + Vec3::new(min.x, max.y, min.z), + Vec3::new(min.x, min.y, max.z), + Vec3::new(min.x, min.y, min.z), + ] + } + /// Construct an [AxisAlignedBox] given the coordinates of the minimum and maximum corners. + pub fn from_min_max(min: Vec3, max: Vec3) -> Result { + if (max - min).min_element() >= 0.0 { + Ok(AABB { min, max }) + } else { + Err(PrimitiveError::MinGreaterThanMax) + } + } + /// Construct an [AxisALignedBox] from the origin at the minimum corner, and the extents - the + /// dimensions of the box in each axis. + pub fn from_extents_origin(extents: Vec3, origin: Vec3) -> Result { + if extents.min_element() > 0.0 { + Ok(AABB { + min: origin, + max: extents + origin, + }) + } else { + Err(PrimitiveError::NonPositiveExtents) + } + } + /// Computes the AAB that + pub fn from_points(points: &[Vec3]) -> AABB { + let mut max = Vec3::splat(f32::MIN); + let mut min = Vec3::splat(f32::MAX); + for &point in points.iter() { + max = point.max(max); + min = point.min(min); + } + // Unwrap is okay here because min < max + AABB::from_min_max(min, max).unwrap() + } +} + +/// A frustum is a truncated pyramid that is used to represent the volume of world space that is +/// visible to the camera. +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +#[reflect_value(PartialEq)] +pub struct Frustum { + planes: [Plane; 6], + vertices: [Vec3; 8], +} +impl Primitive3d for Frustum { + fn outside_plane(&self, plane: Plane) -> bool { + for vertex in self.vertices().iter() { + if plane.distance_to_point(*vertex) <= 0.0 { + return false; + } + } + true + } +} +impl Frustum { + fn compute_vertices(camera_position: &Mat4, projection_matrix: &Mat4) -> [Vec3; 8] { + let ndc_to_world: Mat4 = *camera_position * projection_matrix.inverse(); + [ + ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, -1.0)), + ndc_to_world.project_point3(Vec3::new(1.0, -1.0, -1.0)), + ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, -1.0)), + ndc_to_world.project_point3(Vec3::new(1.0, 1.0, -1.0)), + ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, 1.0)), + ndc_to_world.project_point3(Vec3::new(1.0, -1.0, 1.0)), + ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, 1.0)), + ndc_to_world.project_point3(Vec3::new(1.0, 1.0, 1.0)), + ] + } + + pub fn from_camera_properties(camera_position: &Mat4, projection_matrix: &Mat4) -> Frustum { + let vertices = Frustum::compute_vertices(camera_position, projection_matrix); + let [nbl_world, nbr_world, ntl_world, ntr_world, fbl_world, fbr_world, ftl_world, ftr_world] = + vertices; + + let near_normal = (nbr_world - nbl_world) + .cross(ntl_world - nbl_world) + .normalize(); + let far_normal = (fbr_world - ftr_world) + .cross(ftl_world - ftr_world) + .normalize(); + let top_normal = (ftl_world - ftr_world) + .cross(ntr_world - ftr_world) + .normalize(); + let bottom_normal = (fbl_world - nbl_world) + .cross(nbr_world - nbl_world) + .normalize(); + let right_normal = (ntr_world - ftr_world) + .cross(fbr_world - ftr_world) + .normalize(); + let left_normal = (ntl_world - nbl_world) + .cross(fbl_world - nbl_world) + .normalize(); + + let left = Plane { + point: nbl_world, + normal: left_normal, + }; + let right = Plane { + point: ftr_world, + normal: right_normal, + }; + let bottom = Plane { + point: nbl_world, + normal: bottom_normal, + }; + let top = Plane { + point: ftr_world, + normal: top_normal, + }; + let near = Plane { + point: nbl_world, + normal: near_normal, + }; + let far = Plane { + point: ftr_world, + normal: far_normal, + }; + + let planes = [left, right, top, bottom, near, far]; + + Frustum { planes, vertices } + } + + /// Get a reference to the frustum's vertices. These are given as an ordered list of vertices + /// that form the 8 corners of a [Frustum]. + /// ```none + /// (6)--------------(7) + /// | \ TOP / | + /// | (2)------(3) | + /// | L | | R | + /// (4) | NEAR | (5) + /// \ | | / + /// (0)------(1) + /// ``` + pub fn vertices(&self) -> &[Vec3; 8] { + &self.vertices + } + + /// Get a reference to the frustum's planes. + pub fn planes(&self) -> &[Plane; 6] { + &self.planes + } +} + +/// A plane is defined by a point in space and a normal vector at that point. +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Plane { + point: Vec3, + normal: Vec3, +} +impl Primitive3d for Plane { + fn outside_plane(&self, plane: Plane) -> bool { + self.normal == plane.normal && self.distance_to_point(plane.point()) > 0.0 + } +} +impl Plane { + /// Generate a plane from three points that lie on the plane. + pub fn from_points(points: [Vec3; 3]) -> Plane { + let point = points[1]; + let arm_1 = points[0] - point; + let arm_2 = points[2] - point; + let normal = arm_1.cross(arm_2).normalize(); + Plane { point, normal } + } + /// Generate a plane from a point on that plane and the normal direction of the plane. The + /// normal vector does not need to be normalized (length can be != 1). + pub fn from_point_normal(point: Vec3, normal: Vec3) -> Plane { + Plane { + point, + normal: normal.normalize(), + } + } + /// Returns the nearest distance from the supplied point to this plane. Positive values are in + /// the direction of the plane's normal (outside), negative values are opposite the direction + /// of the planes normal (inside). + pub fn distance_to_point(&self, point: Vec3) -> f32 { + self.normal.dot(point) + -self.normal.dot(self.point) + } + + /// Get the plane's point. + pub fn point(&self) -> Vec3 { + self.point + } + + /// Get the plane's normal. + pub fn normal(&self) -> Vec3 { + self.normal + } + + /// Compute the intersection of the plane and a line. + /// Returns None if the plane and line are parallel. + pub fn intersection_line(&self, line: &Line) -> Option { + let d = line.direction.dot(self.normal); + if d == 0. { + // Should probably check if they're approximately equal, not strictly equal + None + } else { + let diff = line.point - self.point; + let p = diff.dot(self.normal); + let dist = p / d; + Some(line.point - line.direction * dist) + } + } +} + +// Replace this with whatever @aevyrie comes up with in the geometry primitives RFC :p +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Line { + point: Vec3, + direction: Vec3, +} +impl Line { + pub fn from_point_direction(point: Vec3, direction: Vec3) -> Self { + Line { point, direction } + } +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d4556746d35085..88a329f1ded780 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -15,8 +15,8 @@ categories = ["game-engines", "graphics", "gui", "rendering"] [features] wgpu_trace = ["bevy_wgpu/trace"] -trace = [ "bevy_app/trace", "bevy_ecs/trace" ] -trace_chrome = [ "bevy_log/tracing-chrome" ] +trace = ["bevy_app/trace", "bevy_ecs/trace"] +trace_chrome = ["bevy_log/tracing-chrome"] # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_render/hdr"] @@ -49,6 +49,7 @@ bevy_core = { path = "../bevy_core", version = "0.4.0" } bevy_derive = { path = "../bevy_derive", version = "0.4.0" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.4.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_geometry = { path = "../bevy_geometry", version = "0.4.0" } bevy_input = { path = "../bevy_input", version = "0.4.0" } bevy_log = { path = "../bevy_log", version = "0.4.0" } bevy_math = { path = "../bevy_math", version = "0.4.0" } @@ -72,4 +73,4 @@ bevy_winit = { path = "../bevy_winit", optional = true, version = "0.4.0" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.4.0" } [target.'cfg(target_os = "android")'.dependencies] -ndk-glue = {version = "0.2", features = ["logger"]} +ndk-glue = { version = "0.2", features = ["logger"] } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 62fb7cf270b565..8032858b1932d9 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -29,6 +29,11 @@ pub mod ecs { pub use bevy_ecs::*; } +pub mod geometry { + //! Geometric primitives + pub use bevy_geometry::*; +} + pub mod input { //! Resources and events for inputs, e.g. mouse/keyboard, touch, gamepads, etc. pub use bevy_input::*; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index d53a4b54e9358e..549aab8c8425b1 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -19,6 +19,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.4.0" } bevy_core = { path = "../bevy_core", version = "0.4.0" } bevy_derive = { path = "../bevy_derive", version = "0.4.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_geometry = { path = "../bevy_geometry", version = "0.4.0" } bevy_math = { path = "../bevy_math", version = "0.4.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } bevy_transform = { path = "../bevy_transform", version = "0.4.0" } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d1b983e69f63e8..cc42d6070a1e9a 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -7,6 +7,7 @@ use bevy_ecs::{ reflect::ReflectComponent, system::{Query, QuerySet, Res}, }; +use bevy_geometry::{Line, Plane}; use bevy_math::{Mat4, Vec2, Vec3}; use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_transform::components::GlobalTransform; @@ -61,6 +62,67 @@ impl Camera { let screen_space_coords = (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * window_size; Some(screen_space_coords) } + + /// Given a position in screen space, compute the world-space line that corresponds to it. + pub fn screen_to_world_line( + pos_screen: Vec2, + windows: &Res, + camera: &Camera, + camera_transform: &GlobalTransform, + ) -> Line { + let camera_position = camera_transform.compute_matrix(); + let window = windows + .get(camera.window) + .unwrap_or_else(|| panic!("WindowId {} does not exist", camera.window)); + let screen_size = Vec2::from([window.width() as f32, window.height() as f32]); + let projection_matrix = camera.projection_matrix; + + // Normalized device coordinate cursor position from (-1, -1, -1) to (1, 1, 1) + let cursor_ndc = (pos_screen / screen_size) * 2.0 - Vec2::from([1.0, 1.0]); + let cursor_pos_ndc_near: Vec3 = cursor_ndc.extend(-1.0); + let cursor_pos_ndc_far: Vec3 = cursor_ndc.extend(1.0); + + // Use near and far ndc points to generate a ray in world space + // This method is more robust than using the location of the camera as the start of + // the ray, because ortho cameras have a focal point at infinity! + let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse(); + let cursor_pos_near: Vec3 = ndc_to_world.project_point3(cursor_pos_ndc_near); + let cursor_pos_far: Vec3 = ndc_to_world.project_point3(cursor_pos_ndc_far); + let ray_direction = cursor_pos_far - cursor_pos_near; + Line::from_point_direction(cursor_pos_near, ray_direction) + //Ray3d::new(cursor_pos_near, ray_direction) + } + + /// 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. + /// In 2D, use `screen_to_point_2d`. + pub fn screen_to_point_on_plane( + pos_screen: Vec2, + plane: Plane, + windows: &Res, + camera: &Camera, + camera_transform: &GlobalTransform, + ) -> Option { + let world_line = Self::screen_to_world_line(pos_screen, windows, camera, camera_transform); + plane.intersection_line(&world_line) + } + + /// Computes the world position for a given screen position. + /// 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. + /// For more flexibility in 3D, consider `screen_to_point_on_plane`. + pub fn screen_to_point_2d( + pos_screen: Vec2, + windows: &Res, + camera: &Camera, + camera_transform: &GlobalTransform, + ) -> Option { + Self::screen_to_point_on_plane( + pos_screen, + Plane::from_point_normal(Vec3::new(0., 0., 0.), Vec3::new(0., 0., 1.)), + windows, + camera, + camera_transform, + ) + } } pub fn camera_system( diff --git a/examples/2d/mouse_tracking.rs b/examples/2d/mouse_tracking.rs new file mode 100644 index 00000000000000..480e0fa3355570 --- /dev/null +++ b/examples/2d/mouse_tracking.rs @@ -0,0 +1,46 @@ +use bevy::prelude::*; +use bevy::render::camera::Camera; + +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_startup_system(setup.system()) + .add_system(follow.system()) + .run(); +} + +struct Follow; + +fn setup( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, +) { + let texture_handle = asset_server.load("branding/icon.png"); + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands + .spawn_bundle(SpriteBundle { + material: materials.add(texture_handle.into()), + ..Default::default() + }) + .insert(Follow); +} + +fn follow( + mut q: Query<&mut Transform, With>, + q_camera: Query<(&Camera, &GlobalTransform)>, + windows: Res, + mut evr_cursor: EventReader, +) { + if let Ok((camera, camera_transform)) = q_camera.single() { + if let Some(cursor) = evr_cursor.iter().next() { + for mut transform in q.iter_mut() { + let point: Option = + Camera::screen_to_point_2d(cursor.position, &windows, camera, camera_transform); + if let Some(point) = point { + transform.translation = point; + } + } + } + } +} diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index d6aebe9f12498b..fdce750e58c65a 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -1,3 +1,4 @@ +use bevy::geometry::Plane; use bevy::prelude::*; fn main() { @@ -5,6 +6,7 @@ fn main() { .insert_resource(Msaa { samples: 4 }) .add_plugins(DefaultPlugins) .add_startup_system(setup.system()) + .add_system(follow.system()) .run(); } diff --git a/examples/3d/screen_to_world.rs b/examples/3d/screen_to_world.rs new file mode 100644 index 00000000000000..27697ad8f37f0e --- /dev/null +++ b/examples/3d/screen_to_world.rs @@ -0,0 +1,71 @@ +use bevy::geometry::Plane; +use bevy::prelude::*; +use bevy::render::camera::Camera; + +fn main() { + App::build() + .insert_resource(Msaa { samples: 4 }) + .add_plugins(DefaultPlugins) + .add_startup_system(setup.system()) + .add_system(follow.system()) + .run(); +} + +struct Follow; + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // plane + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), + material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..Default::default() + }); + // cube + commands + .spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..Default::default() + }) + .insert(Follow); + // light + commands.spawn_bundle(LightBundle { + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..Default::default() + }); + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +fn follow( + mut q: Query<&mut Transform, With>, + q_camera: Query<(&Camera, &GlobalTransform)>, + windows: Res, + mut evr_cursor: EventReader, +) { + // Assumes there is at least one camera + let (camera, camera_transform) = q_camera.iter().next().unwrap(); + if let Some(cursor) = evr_cursor.iter().next() { + for mut transform in q.iter_mut() { + let point: Option = Camera::screen_to_point_on_plane( + cursor.position, + Plane::from_point_normal(Vec3::new(0., 0., 0.), Vec3::new(0., 1., 0.)), + &windows, + camera, + camera_transform, + ); + if let Some(point) = point { + transform.translation = point + Vec3::new(0., 0.5, 0.); + } + } + } +} diff --git a/examples/README.md b/examples/README.md index 29979f5498e385..eca5ad8710052e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,8 +16,8 @@ There are often large differences and incompatible API changes between the lates If you are using a released version of bevy, you need to make sure you are viewing the correct version of the examples! - - Latest release: [https://github.com/bevyengine/bevy/tree/latest/examples](https://github.com/bevyengine/bevy/tree/latest/examples) - - Specific version, such as `0.4`: [https://github.com/bevyengine/bevy/tree/v0.4.0/examples](https://github.com/bevyengine/bevy/tree/v0.4.0/examples) +- Latest release: [https://github.com/bevyengine/bevy/tree/latest/examples](https://github.com/bevyengine/bevy/tree/latest/examples) +- Specific version, such as `0.4`: [https://github.com/bevyengine/bevy/tree/v0.4.0/examples](https://github.com/bevyengine/bevy/tree/v0.4.0/examples) When you clone the repo locally to run the examples, use `git checkout` to get the correct version: @@ -59,11 +59,12 @@ git checkout v0.4.0 + ## Hello, World! -Example | Main | Description ---- | --- | --- -`hello_world` | [`hello_world.rs`](./hello_world.rs) | Runs a minimal example that outputs "hello world" +| Example | Main | Description | +| ------------- | ------------------------------------ | ------------------------------------------------- | +| `hello_world` | [`hello_world.rs`](./hello_world.rs) | Runs a minimal example that outputs "hello world" | # Cross-Platform Examples @@ -78,6 +79,7 @@ Example | Main | Description `text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d `sprite_flipping` | [`2d/sprite_flipping.rs`](./2d/sprite_flipping.rs) | Renders a sprite flipped along an axis `texture_atlas` | [`2d/texture_atlas.rs`](./2d/texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites +`mouse_tracking` | [`2d/mouse_tracking.rs`](./2d/mouse_tracking.rs) | Creates a sprite that follows your mouse | ## 3D Rendering @@ -93,71 +95,85 @@ Example | File | Description `texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials `update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene `wireframe` | [`3d/wireframe.rs`](./3d/wireframe.rs) | Showcases wireframe rendering -`z_sort_debug` | [`3d/z_sort_debug.rs`](./3d/z_sort_debug.rs) | Visualizes camera Z-ordering +`z_sort_debug` | [`3d/z_sort_debug.rs`](./3d/z_sort_debug.rs) | Visualizes camera Z-ordering| +`screen_to_world` | [`3d/screen_to_world.rs`](./3d/screen_to_world.rs) | Converts screen-space coordinates to world coordinates to make an object follow the mouse | ## Application -Example | File | Description ---- | --- | --- -`custom_loop` | [`app/custom_loop.rs`](./app/custom_loop.rs) | Demonstrates how to create a custom runner (to update an app manually). -`drag_and_drop` | [`app/drag_and_drop.rs`](./app/drag_and_drop.rs) | An example that shows how to handle drag and drop in an app. -`empty` | [`app/empty.rs`](./app/empty.rs) | An empty application (does nothing) -`empty_defaults` | [`app/empty_defaults.rs`](./app/empty_defaults.rs) | An empty application with default plugins -`headless` | [`app/headless.rs`](./app/headless.rs) | An application that runs without default plugins -`logs` | [`app/logs.rs`](./app/logs.rs) | Illustrate how to use generate log output -`plugin` | [`app/plugin.rs`](./app/plugin.rs) | Demonstrates the creation and registration of a custom plugin -`plugin_group` | [`app/plugin_group.rs`](./app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group -`return_after_run` | [`app/return_after_run.rs`](./app/return_after_run.rs) | Show how to return to main after the Bevy app has exited -`thread_pool_resources` | [`app/thread_pool_resources.rs`](./app/thread_pool_resources.rs) | Creates and customizes the internal thread pool +| Example | File | Description | +| ----------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `custom_loop` | [`app/custom_loop.rs`](./app/custom_loop.rs) | Demonstrates how to create a custom runner (to update an app manually). | +| `drag_and_drop` | [`app/drag_and_drop.rs`](./app/drag_and_drop.rs) | An example that shows how to handle drag and drop in an app. | +| `empty` | [`app/empty.rs`](./app/empty.rs) | An empty application (does nothing) | +| `empty_defaults` | [`app/empty_defaults.rs`](./app/empty_defaults.rs) | An empty application with default plugins | +| `headless` | [`app/headless.rs`](./app/headless.rs) | An application that runs without default plugins | +| `logs` | [`app/logs.rs`](./app/logs.rs) | Illustrate how to use generate log output | +| `plugin` | [`app/plugin.rs`](./app/plugin.rs) | Demonstrates the creation and registration of a custom plugin | +| `plugin_group` | [`app/plugin_group.rs`](./app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group | +| `return_after_run` | [`app/return_after_run.rs`](./app/return_after_run.rs) | Show how to return to main after the Bevy app has exited | +| `thread_pool_resources` | [`app/thread_pool_resources.rs`](./app/thread_pool_resources.rs) | Creates and customizes the internal thread pool | ## Assets -Example | File | Description ---- | --- | --- -`asset_loading` | [`asset/asset_loading.rs`](./asset/asset_loading.rs) | Demonstrates various methods to load assets -`custom_asset` | [`asset/custom_asset.rs`](./asset/custom_asset.rs) | Implements a custom asset loader -`custom_asset_io` | [`asset/custom_asset_io.rs`](./asset/custom_asset_io.rs) | Implements a custom asset io loader -`hot_asset_reloading` | [`asset/hot_asset_reloading.rs`](./asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk +| Example | File | Description | +| --------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | +| `asset_loading` | [`asset/asset_loading.rs`](./asset/asset_loading.rs) | Demonstrates various methods to load assets | +| `custom_asset` | [`asset/custom_asset.rs`](./asset/custom_asset.rs) | Implements a custom asset loader | +| `custom_asset_io` | [`asset/custom_asset_io.rs`](./asset/custom_asset_io.rs) | Implements a custom asset io loader | +| `hot_asset_reloading` | [`asset/hot_asset_reloading.rs`](./asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk | ## Audio -Example | File | Description ---- | --- | --- -`audio` | [`audio/audio.rs`](./audio/audio.rs) | Shows how to load and play an audio file +| Example | File | Description | +| ------- | ------------------------------------ | ---------------------------------------- | +| `audio` | [`audio/audio.rs`](./audio/audio.rs) | Shows how to load and play an audio file | ## Diagnostics -Example | File | Description ---- | --- | --- -`log_diagnostics` | [`diagnostics/log_diagnostics.rs`](./diagnostics/log_diagnostics.rs) | Add a plugin that logs diagnostics to the console -`custom_diagnostic` | [`diagnostics/custom_diagnostic.rs`](./diagnostics/custom_diagnostic.rs) | Shows how to create a custom diagnostic +| Example | File | Description | +| ------------------- | ------------------------------------------------------------------------ | ------------------------------------------------- | +| `log_diagnostics` | [`diagnostics/log_diagnostics.rs`](./diagnostics/log_diagnostics.rs) | Add a plugin that logs diagnostics to the console | +| `custom_diagnostic` | [`diagnostics/custom_diagnostic.rs`](./diagnostics/custom_diagnostic.rs) | Shows how to create a custom diagnostic | ## ECS (Entity Component System) -Example | File | Description ---- | --- | --- -`ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS -`change_detection` | [`ecs/change_detection.rs`](./ecs/change_detection.rs) | Change detection on components -`event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception -`fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick -`hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities -`parallel_query` | [`ecs/parallel_query.rs`](./ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` -`removal_detection` | [`ecs/removal_detection.rs`](./ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame. -`startup_system` | [`ecs/startup_system.rs`](./ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) -`state` | [`ecs/state.rs`](./ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state -`system_chaining` | [`ecs/system_chaining.rs`](./ecs/system_chaining.rs) | Chain two systems together, specifying a return type in a system (such as `Result`) -`system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam` -`timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state +| Example | File | Description | +| ------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS | +| `change_detection` | [`ecs/change_detection.rs`](./ecs/change_detection.rs) | Change detection on components | +| `event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception | +| `fixed_timestep` | [`ecs/fixed_timestep.rs`](./ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick | +| `hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities | +| `parallel_query` | [`ecs/parallel_query.rs`](./ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` | +| `removal_detection` | [`ecs/removal_detection.rs`](./ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame. | +| `startup_system` | [`ecs/startup_system.rs`](./ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) | +| `state` | [`ecs/state.rs`](./ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state | +| `system_chaining` | [`ecs/system_chaining.rs`](./ecs/system_chaining.rs) | Chain two systems together, specifying a return type in a system (such as `Result`) | +| `system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam` | +| `timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state | ## Games -Example | File | Description ---- | --- | --- -`alien_cake_addict` | [`game/alien_cake_addict.rs`](./game/alien_cake_addict.rs) | Eat the cakes. Eat them all. An example 3D game -`breakout` | [`game/breakout.rs`](./game/breakout.rs) | An implementation of the classic game "Breakout" +| Example | File | Description | +| ------------------- | ---------------------------------------------------------- | ------------------------------------------------ | +| `alien_cake_addict` | [`game/alien_cake_addict.rs`](./game/alien_cake_addict.rs) | Eat the cakes. Eat them all. An example 3D game | +| `breakout` | [`game/breakout.rs`](./game/breakout.rs) | An implementation of the classic game "Breakout" | ## Input +<<<<<<< HEAD +| Example | File | Description | +| ----------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `char_input_events` | [`input/char_input_events.rs`](./input/char_input_events.rs) | Prints out all chars as they are inputted. | +| `gamepad_input` | [`input/gamepad_input.rs`](./input/gamepad_input.rs) | Shows handling of gamepad input, connections, and disconnections | +| `gamepad_input_events` | [`input/gamepad_input_events.rs`](./input/gamepad_input_events.rs) | Iterates and prints gamepad input and connection events | +| `keyboard_input` | [`input/keyboard_input.rs`](./input/keyboard_input.rs) | Demonstrates handling a key press/release | +| `keyboard_input_events` | [`input/keyboard_input_events.rs`](./input/keyboard_input_events.rs) | Prints out all keyboard events | +| `mouse_input` | [`input/mouse_input.rs`](./input/mouse_input.rs) | Demonstrates handling a mouse button press/release | +| `mouse_input_events` | [`input/mouse_input_events.rs`](./input/mouse_input_events.rs) | Prints out all mouse events (buttons, movement, etc.) | +| `touch_input` | [`input/touch_input.rs`](./input/touch_input.rs) | Displays touch presses, releases, and cancels | +| `touch_input_events` | [`input/touch_input_events.rs`](./input/touch_input_input_events.rs) | Prints out all touch inputs | +======= Example | File | Description --- | --- | --- `char_input_events` | [`input/char_input_events.rs`](./input/char_input_events.rs) | Prints out all chars as they are inputted. @@ -171,55 +187,57 @@ Example | File | Description `touch_input` | [`input/touch_input.rs`](./input/touch_input.rs) | Displays touch presses, releases, and cancels `touch_input_events` | [`input/touch_input_events.rs`](./input/touch_input_input_events.rs) | Prints out all touch inputs +> > > > > > > upstream/main + ## Reflection -Example | File | Description ---- | --- | --- -`reflection` | [`reflection/reflection.rs`](reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types -`generic_reflection` | [`reflection/generic_reflection.rs`](reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection -`reflection_types` | [`reflection/reflection_types.rs`](reflection/reflection_types.rs) | Illustrates the various reflection types available -`trait_reflection` | [`reflection/trait_reflection.rs`](reflection/trait_reflection.rs) | Allows reflection with trait objects +| Example | File | Description | +| -------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `reflection` | [`reflection/reflection.rs`](reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types | +| `generic_reflection` | [`reflection/generic_reflection.rs`](reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection | +| `reflection_types` | [`reflection/reflection_types.rs`](reflection/reflection_types.rs) | Illustrates the various reflection types available | +| `trait_reflection` | [`reflection/trait_reflection.rs`](reflection/trait_reflection.rs) | Allows reflection with trait objects | ## Scene -Example | File | Description ---- | --- | --- -`scene` | [`scene/scene.rs`](./scene/scene.rs) | Demonstrates loading from and saving scenes to files +| Example | File | Description | +| ------- | ------------------------------------ | ---------------------------------------------------- | +| `scene` | [`scene/scene.rs`](./scene/scene.rs) | Demonstrates loading from and saving scenes to files | ## Shaders -Example | File | Description ---- | --- | --- -`array_texture` | [`shader/array_texture.rs`](./shader/array_texture.rs) | Illustrates how to create a texture for use with a texture2DArray shader uniform variable -`hot_shader_reloading` | [`shader/hot_shader_reloading.rs`](./shader/hot_shader_reloading.rs) | Illustrates how to load shaders such that they can be edited while the example is still running -`mesh_custom_attribute` | [`shader/mesh_custom_attribute.rs`](./shader/mesh_custom_attribute.rs) | Illustrates how to add a custom attribute to a mesh and use it in a custom shader -`shader_custom_material` | [`shader/shader_custom_material.rs`](./shader/shader_custom_material.rs) | Illustrates creating a custom material and a shader that uses it -`shader_defs` | [`shader/shader_defs.rs`](./shader/shader_defs.rs) | Demonstrates creating a custom material that uses "shaders defs" (a tool to selectively toggle parts of a shader) +| Example | File | Description | +| ------------------------ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | +| `array_texture` | [`shader/array_texture.rs`](./shader/array_texture.rs) | Illustrates how to create a texture for use with a texture2DArray shader uniform variable | +| `hot_shader_reloading` | [`shader/hot_shader_reloading.rs`](./shader/hot_shader_reloading.rs) | Illustrates how to load shaders such that they can be edited while the example is still running | +| `mesh_custom_attribute` | [`shader/mesh_custom_attribute.rs`](./shader/mesh_custom_attribute.rs) | Illustrates how to add a custom attribute to a mesh and use it in a custom shader | +| `shader_custom_material` | [`shader/shader_custom_material.rs`](./shader/shader_custom_material.rs) | Illustrates creating a custom material and a shader that uses it | +| `shader_defs` | [`shader/shader_defs.rs`](./shader/shader_defs.rs) | Demonstrates creating a custom material that uses "shaders defs" (a tool to selectively toggle parts of a shader) | ## Tools -Example | File | Description ---- | --- | --- -`bevymark` | [`tools/bevymark.rs`](./tools/bevymark.rs) | A heavy workload to benchmark your system with Bevy +| Example | File | Description | +| ---------- | ------------------------------------------ | --------------------------------------------------- | +| `bevymark` | [`tools/bevymark.rs`](./tools/bevymark.rs) | A heavy workload to benchmark your system with Bevy | ## UI (User Interface) -Example | File | Description ---- | --- | --- -`button` | [`ui/button.rs`](./ui/button.rs) | Illustrates creating and updating a button -`font_atlas_debug` | [`ui/font_atlas_debug.rs`](./ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally) -`text` | [`ui/text.rs`](./ui/text.rs) | Illustrates creating and updating text -`text_debug` | [`ui/text_debug.rs`](./ui/text_debug.rs) | An example for debugging text layout -`ui` | [`ui/ui.rs`](./ui/ui.rs) | Illustrates various features of Bevy UI +| Example | File | Description | +| ------------------ | ---------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `button` | [`ui/button.rs`](./ui/button.rs) | Illustrates creating and updating a button | +| `font_atlas_debug` | [`ui/font_atlas_debug.rs`](./ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally) | +| `text` | [`ui/text.rs`](./ui/text.rs) | Illustrates creating and updating text | +| `text_debug` | [`ui/text_debug.rs`](./ui/text_debug.rs) | An example for debugging text layout | +| `ui` | [`ui/ui.rs`](./ui/ui.rs) | Illustrates various features of Bevy UI | ## Window -Example | File | Description ---- | --- | --- -`clear_color` | [`window/clear_color.rs`](./window/clear_color.rs) | Creates a solid color window -`multiple_windows` | [`window/multiple_windows.rs`](./window/multiple_windows.rs) | Creates two windows and cameras viewing the same mesh -`scale_factor_override` | [`window/scale_factor_override.rs`](./window/scale_factor_override.rs) | Illustrates how to customize the default window settings -`window_settings` | [`window/window_settings.rs`](./window/window_settings.rs) | Demonstrates customizing default window settings +| Example | File | Description | +| ----------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------- | +| `clear_color` | [`window/clear_color.rs`](./window/clear_color.rs) | Creates a solid color window | +| `multiple_windows` | [`window/multiple_windows.rs`](./window/multiple_windows.rs) | Creates two windows and cameras viewing the same mesh | +| `scale_factor_override` | [`window/scale_factor_override.rs`](./window/scale_factor_override.rs) | Illustrates how to customize the default window settings | +| `window_settings` | [`window/window_settings.rs`](./window/window_settings.rs) | Demonstrates customizing default window settings | # Platform-Specific Examples @@ -269,9 +287,9 @@ target_sdk_version = >>API<< min_sdk_version = >>API or less<< ``` -Example | File | Description ---- | --- | --- -`android` | [`android/android.rs`](./android/android.rs) | The `3d/3d_scene.rs` example for Android +| Example | File | Description | +| --------- | -------------------------------------------- | ---------------------------------------- | +| `android` | [`android/android.rs`](./android/android.rs) | The `3d/3d_scene.rs` example for Android | ## iOS @@ -317,9 +335,9 @@ variable in the "`cargo_ios` target" to be either `x86_64-apple-ios` or Note: if you update this variable in Xcode, it will also change the default used for the `Makefile`. -Example | File | Description ---- | --- | --- -`ios` | [`ios/src/lib.rs`](./ios/src/lib.rs) | The `3d/3d_scene.rs` example for iOS +| Example | File | Description | +| ------- | ------------------------------------ | ------------------------------------ | +| `ios` | [`ios/src/lib.rs`](./ios/src/lib.rs) | The `3d/3d_scene.rs` example for iOS | ## WASM @@ -347,9 +365,9 @@ Then serve `examples/wasm` dir to browser. i.e. basic-http-server examples/wasm ``` -Example | File | Description ---- | --- | --- -`hello_wasm` | [`wasm/hello_wasm.rs`](./wasm/hello_wasm.rs) | Runs a minimal example that logs "hello world" to the browser's console -`assets_wasm` | [`wasm/assets_wasm.rs`](./wasm/assets_wasm.rs) | Demonstrates how to load assets from wasm -`headless_wasm` | [`wasm/headless_wasm.rs`](./wasm/headless_wasm.rs) | Sets up a schedule runner and continually logs a counter to the browser's console -`winit_wasm` | [`wasm/winit_wasm.rs`](./wasm/winit_wasm.rs) | Logs user input to the browser's console. Requires the `bevy_winit` features +| Example | File | Description | +| --------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- | +| `hello_wasm` | [`wasm/hello_wasm.rs`](./wasm/hello_wasm.rs) | Runs a minimal example that logs "hello world" to the browser's console | +| `assets_wasm` | [`wasm/assets_wasm.rs`](./wasm/assets_wasm.rs) | Demonstrates how to load assets from wasm | +| `headless_wasm` | [`wasm/headless_wasm.rs`](./wasm/headless_wasm.rs) | Sets up a schedule runner and continually logs a counter to the browser's console | +| `winit_wasm` | [`wasm/winit_wasm.rs`](./wasm/winit_wasm.rs) | Logs user input to the browser's console. Requires the `bevy_winit` features | diff --git a/rustfmt.toml b/rustfmt.toml index 39a9ccb5081331..3c2da76ddeb29b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,5 @@ +unstable_features = true +imports_granularity = "Crate" use_field_init_shorthand = true newline_style = "Unix" diff --git a/tools/publish.sh b/tools/publish.sh index 6d827feaf62149..188d04b0e25d15 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -3,6 +3,7 @@ crates=( bevy_utils bevy_derive bevy_math + bevy_geometry bevy_tasks bevy_ecs/macros bevy_ecs