From f87cc5ccc3112efad2aed215b9d434100c4d2d95 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 11 Mar 2021 10:40:59 -0800 Subject: [PATCH 01/13] Add geometric primitives --- crates/bevy_geometry/Cargo.toml | 10 ++++++++++ crates/bevy_geometry/src/lib.rs | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 crates/bevy_geometry/Cargo.toml create mode 100644 crates/bevy_geometry/src/lib.rs diff --git a/crates/bevy_geometry/Cargo.toml b/crates/bevy_geometry/Cargo.toml new file mode 100644 index 0000000000000..47bff5f999aa9 --- /dev/null +++ b/crates/bevy_geometry/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bevy_geometry" +version = "0.1.0" +authors = ["Aevyrie Roessler "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy_math = { path = "../bevy_math", version = "0.4.0" } diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs new file mode 100644 index 0000000000000..4906b2cea2400 --- /dev/null +++ b/crates/bevy_geometry/src/lib.rs @@ -0,0 +1,24 @@ +use bevy_math::{Quat, Vec3}; + +pub trait Primitive3d {} + +pub struct Sphere { + pub origin: Vec3, + pub radius: f32, +} + +pub struct Box { + pub maximums: Vec3, + pub minimums: Vec3, + pub orientation: Quat, +} + +pub struct AxisAlignedBox { + pub maximums: Vec3, + pub minimums: Vec3, +} + +pub struct Plane { + pub point: Vec3, + pub normal: Vec3, +} From 5e1e472afe1ca734199b3b49358a6dea42a20ab0 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Thu, 11 Mar 2021 10:49:57 -0800 Subject: [PATCH 02/13] Update crates/bevy_geometry/Cargo.toml Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> --- crates/bevy_geometry/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_geometry/Cargo.toml b/crates/bevy_geometry/Cargo.toml index 47bff5f999aa9..24cc9f781670c 100644 --- a/crates/bevy_geometry/Cargo.toml +++ b/crates/bevy_geometry/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "bevy_geometry" version = "0.1.0" -authors = ["Aevyrie Roessler "] +authors = [ + "Bevy Contributors ", + "Aevyrie Roessler ", +] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 59170411f0d51893dfa90e13ce02d153d31b3301 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 11 Mar 2021 11:10:59 -0800 Subject: [PATCH 03/13] re-exports --- crates/bevy_geometry/Cargo.toml | 7 ++++--- crates/bevy_internal/Cargo.toml | 7 ++++--- crates/bevy_internal/src/lib.rs | 5 +++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/bevy_geometry/Cargo.toml b/crates/bevy_geometry/Cargo.toml index 24cc9f781670c..62aab1c1d29e1 100644 --- a/crates/bevy_geometry/Cargo.toml +++ b/crates/bevy_geometry/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "bevy_geometry" -version = "0.1.0" +version = "0.4.0" authors = [ - "Bevy Contributors ", - "Aevyrie Roessler ", + "Bevy Contributors ", + "Aevyrie Roessler ", ] edition = "2018" @@ -11,3 +11,4 @@ edition = "2018" [dependencies] bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0" } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d4556746d3508..88a329f1ded78 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 331a5e90ab256..0a1e8ea6e2c44 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::*; From 557c31a510580c6fc824fbe4fb874666428611dd Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 11 Mar 2021 11:11:09 -0800 Subject: [PATCH 04/13] crate publishing --- tools/publish.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/publish.sh b/tools/publish.sh index 6d827feaf6214..f6c3266a9b492 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -30,6 +30,7 @@ crates=( bevy_wgpu bevy_internal bevy_dylib + bevy_geometry ) cd crates From e2fd3d2739d474ecb8f8059969e31c25b308a0e1 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 11 Mar 2021 11:11:18 -0800 Subject: [PATCH 05/13] derives --- crates/bevy_geometry/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index 4906b2cea2400..03271d01832ac 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -2,22 +2,26 @@ use bevy_math::{Quat, Vec3}; pub trait Primitive3d {} +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Sphere { pub origin: Vec3, pub radius: f32, } +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Box { pub maximums: Vec3, pub minimums: Vec3, pub orientation: Quat, } +#[derive(Copy, Clone, PartialEq, Debug)] pub struct AxisAlignedBox { pub maximums: Vec3, pub minimums: Vec3, } +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Plane { pub point: Vec3, pub normal: Vec3, From a9b50729e99148633e3eee29c1f4c71e1d3af5de Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 11 Mar 2021 12:43:07 -0800 Subject: [PATCH 06/13] correct publish order --- tools/publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/publish.sh b/tools/publish.sh index f6c3266a9b492..188d04b0e25d1 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 @@ -30,7 +31,6 @@ crates=( bevy_wgpu bevy_internal bevy_dylib - bevy_geometry ) cd crates From 432138fce00c401f20a6957b74da0b9d97861009 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Fri, 12 Mar 2021 00:21:22 -0800 Subject: [PATCH 07/13] add all the things --- crates/bevy_geometry/Cargo.toml | 3 +- crates/bevy_geometry/src/lib.rs | 179 +++++++++++++++++++++++++++++--- tools/publish.sh | 2 +- 3 files changed, 170 insertions(+), 14 deletions(-) diff --git a/crates/bevy_geometry/Cargo.toml b/crates/bevy_geometry/Cargo.toml index 62aab1c1d29e1..fe6d31bd2b12b 100644 --- a/crates/bevy_geometry/Cargo.toml +++ b/crates/bevy_geometry/Cargo.toml @@ -10,5 +10,6 @@ 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" } +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 index 03271d01832ac..83c6db4950267 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -1,28 +1,183 @@ -use bevy_math::{Quat, Vec3}; +use bevy_math::*; +use bevy_reflect::Reflect; +//use bevy_transform::components::GlobalTransform; +use std::error::Error; +use std::fmt; -pub trait Primitive3d {} +pub trait Primitive3d { + /* + /// Returns true if this primitive is on the outside (normal direction) of the supplied + fn outside_plane( + &self, + primitive_transform: GlobalTransform, + plane: Plane, + plane_transform: GlobalTransform, + ) -> bool;*/ +} -#[derive(Copy, Clone, PartialEq, Debug)] +#[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 than maximums") + } + PrimitiveError::NonPositiveExtents => { + write!(f, "AxisAlignedBox extents must be greater than zero") + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] pub struct Sphere { pub origin: Vec3, pub radius: f32, } +impl Primitive3d for Sphere {} -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct Box { - pub maximums: Vec3, - pub minimums: Vec3, +/// 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 OrientedBox { + pub aab: AxisAlignedBox, pub orientation: Quat, } +impl Primitive3d for OrientedBox {} -#[derive(Copy, Clone, PartialEq, Debug)] +/// 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 AxisAlignedBox { - pub maximums: Vec3, - pub minimums: Vec3, + minimums: Vec3, + maximums: Vec3, } +impl Primitive3d for AxisAlignedBox {} +impl AxisAlignedBox { + pub fn from_min_max(minimums: Vec3, maximums: Vec3) -> Result { + if (maximums - minimums).min_element() > 0.0 { + Ok(AxisAlignedBox { minimums, maximums }) + } else { + Err(PrimitiveError::MinGreaterThanMax) + } + } + pub fn from_extents_origin( + extents: Vec3, + origin: Vec3, + ) -> Result { + if extents.min_element() > 0.0 { + Ok(AxisAlignedBox { + minimums: origin, + maximums: extents + origin, + }) + } else { + Err(PrimitiveError::NonPositiveExtents) + } + } +} + +/// 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], +} +impl Primitive3d for Frustum {} +impl Frustum { + pub fn from_camera_properties( + &self, + camera_position: Mat4, + projection_matrix: Mat4, + ) -> Frustum { + let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse(); + // Near/Far, Top/Bottom, Left/Right + let nbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, -1.0)); + let nbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, -1.0)); + let ntl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, -1.0)); + let fbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, 1.0)); + let ftr_world = ndc_to_world.project_point3(Vec3::new(1.0, 1.0, 1.0)); + let ftl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, 1.0)); + let fbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, 1.0)); + let ntr_world = ndc_to_world.project_point3(Vec3::new(1.0, 1.0, -1.0)); + + 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, + }; + Frustum { + planes: [left, right, top, bottom, near, far], + } + } +} + +/// A plane is defined by a point in space and a normal vector at that point. #[derive(Copy, Clone, PartialEq, Debug)] pub struct Plane { - pub point: Vec3, - pub normal: Vec3, + point: Vec3, + normal: Vec3, +} +impl Primitive3d for Plane {} +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(), + } + } } diff --git a/tools/publish.sh b/tools/publish.sh index 188d04b0e25d1..a760824cf45f5 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -3,7 +3,6 @@ crates=( bevy_utils bevy_derive bevy_math - bevy_geometry bevy_tasks bevy_ecs/macros bevy_ecs @@ -17,6 +16,7 @@ crates=( bevy_core bevy_diagnostic bevy_transform + bevy_geometry bevy_window bevy_render bevy_input From 45ba723ea483a286725f098ac09c10dc2a4ea4ac Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Sat, 13 Mar 2021 09:59:11 -0800 Subject: [PATCH 08/13] Adding helper methods --- crates/bevy_geometry/src/lib.rs | 264 ++++++++++++++++++++++++++------ tools/publish.sh | 2 +- 2 files changed, 221 insertions(+), 45 deletions(-) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index 83c6db4950267..1dcd10406c67b 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -1,18 +1,12 @@ use bevy_math::*; use bevy_reflect::Reflect; -//use bevy_transform::components::GlobalTransform; use std::error::Error; use std::fmt; pub trait Primitive3d { - /* - /// Returns true if this primitive is on the outside (normal direction) of the supplied - fn outside_plane( - &self, - primitive_transform: GlobalTransform, - plane: Plane, - plane_transform: GlobalTransform, - ) -> bool;*/ + /// 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)] @@ -25,7 +19,10 @@ 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 than maximums") + write!( + f, + "AxisAlignedBox minimums must be smaller or equal to the maximums" + ) } PrimitiveError::NonPositiveExtents => { write!(f, "AxisAlignedBox extents must be greater than zero") @@ -36,76 +33,214 @@ impl fmt::Display for PrimitiveError { #[derive(Copy, Clone, PartialEq, Debug, Reflect)] pub struct Sphere { - pub origin: Vec3, - pub radius: f32, + origin: Vec3, + radius: f32, +} + +impl Sphere { + /// Get a reference to the sphere's origin. + pub fn origin(&self) -> &Vec3 { + &self.origin + } + + /// Get a reference to 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 + } } -impl Primitive3d for Sphere {} /// 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 OrientedBox { - pub aab: AxisAlignedBox, - pub orientation: Quat, + aab: AxisAlignedBox, + transform: Mat4, +} +impl Primitive3d for OrientedBox { + 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 OrientedBox { + /// 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_aab(&mut self, aab: AxisAlignedBox) { + 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) -> AxisAlignedBox { + 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 + AxisAlignedBox::from_min_max(min, max).unwrap() + } } -impl Primitive3d for OrientedBox {} /// 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 AxisAlignedBox { - minimums: Vec3, - maximums: Vec3, + min: Vec3, + max: Vec3, +} +impl Primitive3d for AxisAlignedBox { + 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 Primitive3d for AxisAlignedBox {} impl AxisAlignedBox { - pub fn from_min_max(minimums: Vec3, maximums: Vec3) -> Result { - if (maximums - minimums).min_element() > 0.0 { - Ok(AxisAlignedBox { minimums, maximums }) + /// 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(AxisAlignedBox { 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(AxisAlignedBox { - minimums: origin, - maximums: extents + origin, + min: origin, + max: extents + origin, }) } else { Err(PrimitiveError::NonPositiveExtents) } } + /// Computes the AAB that + pub fn from_points(points: Vec) -> AxisAlignedBox { + 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 + AxisAlignedBox::from_min_max(min, max).unwrap() + } } -/// A frustum is a truncated pyramid that is used to represent the "volume" of world space that is +/// 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 Primitive3d for Frustum {} impl Frustum { - pub fn from_camera_properties( - &self, - camera_position: Mat4, - projection_matrix: Mat4, - ) -> Frustum { + fn compute_vertices(camera_position: Mat4, projection_matrix: Mat4) -> [Vec3; 8] { let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse(); - // Near/Far, Top/Bottom, Left/Right - let nbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, -1.0)); - let nbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, -1.0)); - let ntl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, -1.0)); - let fbl_world = ndc_to_world.project_point3(Vec3::new(-1.0, -1.0, 1.0)); - let ftr_world = ndc_to_world.project_point3(Vec3::new(1.0, 1.0, 1.0)); - let ftl_world = ndc_to_world.project_point3(Vec3::new(-1.0, 1.0, 1.0)); - let fbr_world = ndc_to_world.project_point3(Vec3::new(1.0, -1.0, 1.0)); - let ntr_world = 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)), + 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) @@ -150,9 +285,30 @@ impl Frustum { point: ftr_world, normal: far_normal, }; - Frustum { - planes: [left, right, top, bottom, near, far], - } + + 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 } } @@ -162,7 +318,11 @@ pub struct Plane { point: Vec3, normal: Vec3, } -impl Primitive3d for Plane {} +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 { @@ -180,4 +340,20 @@ impl Plane { 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 a reference to the plane's point. + pub fn point(&self) -> &Vec3 { + &self.point + } + + /// Get a reference to the plane's normal. + pub fn normal(&self) -> &Vec3 { + &self.normal + } } diff --git a/tools/publish.sh b/tools/publish.sh index a760824cf45f5..188d04b0e25d1 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 @@ -16,7 +17,6 @@ crates=( bevy_core bevy_diagnostic bevy_transform - bevy_geometry bevy_window bevy_render bevy_input From feab0c8a5c5e36d1c1c3b9f166620c33f3ed9a94 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 18 Mar 2021 23:49:27 -0700 Subject: [PATCH 09/13] Suggested review changes --- crates/bevy_geometry/src/lib.rs | 57 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index 1dcd10406c67b..f7b5cf45310f7 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -39,13 +39,13 @@ pub struct Sphere { impl Sphere { /// Get a reference to the sphere's origin. - pub fn origin(&self) -> &Vec3 { - &self.origin + pub fn origin(&self) -> Vec3 { + self.origin } /// Get a reference to the sphere's radius. - pub fn radius(&self) -> &f32 { - &self.radius + pub fn radius(&self) -> f32 { + self.radius } /// Set the sphere's origin. @@ -62,7 +62,7 @@ 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 + plane.distance_to_point(self.origin) > self.radius } } @@ -70,21 +70,21 @@ impl Primitive3d for Sphere { /// 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 OrientedBox { - aab: AxisAlignedBox, +pub struct OBB { + aab: AABB, transform: Mat4, } -impl Primitive3d for OrientedBox { +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 { + if plane.distance_to_point(*vertex) <= 0.0 { return false; } } true } } -impl OrientedBox { +impl OBB { /// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox]. /// ```none /// (5)------(1) @@ -105,7 +105,7 @@ impl OrientedBox { } /// Set the oriented box's aab. - pub fn set_aab(&mut self, aab: AxisAlignedBox) { + pub fn set_aabb(&mut self, aab: AABB) { self.aab = aab; } @@ -113,7 +113,7 @@ impl OrientedBox { pub fn set_transform(&mut self, transform: Mat4) { self.transform = transform; } - pub fn fast_aabb(&self) -> AxisAlignedBox { + pub fn fast_aabb(&self) -> AABB { let vertices = self.vertices(); let mut max = Vec3::splat(f32::MIN); let mut min = Vec3::splat(f32::MAX); @@ -122,18 +122,18 @@ impl OrientedBox { min = vertex.min(min); } // Unwrap is okay here because min < max - AxisAlignedBox::from_min_max(min, max).unwrap() + 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 AxisAlignedBox { +pub struct AABB { min: Vec3, max: Vec3, } -impl Primitive3d for AxisAlignedBox { +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 { @@ -143,7 +143,7 @@ impl Primitive3d for AxisAlignedBox { true } } -impl AxisAlignedBox { +impl AABB { /// An ordered list of the vertices that form the 8 corners of the [AxisAlignedBox]. /// ```none /// (5)------(1) Y @@ -169,21 +169,18 @@ impl AxisAlignedBox { ] } /// Construct an [AxisAlignedBox] given the coordinates of the minimum and maximum corners. - pub fn from_min_max(min: Vec3, max: Vec3) -> Result { + pub fn from_min_max(min: Vec3, max: Vec3) -> Result { if (max - min).min_element() >= 0.0 { - Ok(AxisAlignedBox { min, max }) + 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 { + pub fn from_extents_origin(extents: Vec3, origin: Vec3) -> Result { if extents.min_element() > 0.0 { - Ok(AxisAlignedBox { + Ok(AABB { min: origin, max: extents + origin, }) @@ -192,7 +189,7 @@ impl AxisAlignedBox { } } /// Computes the AAB that - pub fn from_points(points: Vec) -> AxisAlignedBox { + 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() { @@ -200,7 +197,7 @@ impl AxisAlignedBox { min = point.min(min); } // Unwrap is okay here because min < max - AxisAlignedBox::from_min_max(min, max).unwrap() + AABB::from_min_max(min, max).unwrap() } } @@ -223,8 +220,8 @@ impl Primitive3d for Frustum { } } impl Frustum { - fn compute_vertices(camera_position: Mat4, projection_matrix: Mat4) -> [Vec3; 8] { - let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse(); + 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)), @@ -237,7 +234,7 @@ impl Frustum { ] } - pub fn from_camera_properties(camera_position: Mat4, projection_matrix: Mat4) -> Frustum { + 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; @@ -343,8 +340,8 @@ impl Plane { /// 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) + pub fn distance_to_point(&self, point: Vec3) -> f32 { + self.normal.dot(point) + -self.normal.dot(self.point) } /// Get a reference to the plane's point. From f369c50912162d66a606b39c1616b474c9fb6af7 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 18 Mar 2021 23:50:50 -0700 Subject: [PATCH 10/13] Fix errors and comments --- crates/bevy_geometry/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index f7b5cf45310f7..410f852c9b878 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -38,12 +38,12 @@ pub struct Sphere { } impl Sphere { - /// Get a reference to the sphere's origin. + /// Get the sphere's origin. pub fn origin(&self) -> Vec3 { self.origin } - /// Get a reference to the sphere's radius. + /// Get the sphere's radius. pub fn radius(&self) -> f32 { self.radius } @@ -136,7 +136,7 @@ pub struct AABB { 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 { + if plane.distance_to_point(*vertex) <= 0.0 { return false; } } @@ -212,7 +212,7 @@ pub struct Frustum { 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 { + if plane.distance_to_point(*vertex) <= 0.0 { return false; } } @@ -344,13 +344,13 @@ impl Plane { self.normal.dot(point) + -self.normal.dot(self.point) } - /// Get a reference to the plane's point. - pub fn point(&self) -> &Vec3 { - &self.point + /// Get the plane's point. + pub fn point(&self) -> Vec3 { + self.point } - /// Get a reference to the plane's normal. - pub fn normal(&self) -> &Vec3 { - &self.normal + /// Get the plane's normal. + pub fn normal(&self) -> Vec3 { + self.normal } } From 924cc5a81db3bb678879ae56f24e4499d52a123d Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Thu, 8 Apr 2021 13:07:20 -0700 Subject: [PATCH 11/13] Update crates/bevy_geometry/src/lib.rs Co-authored-by: Grindv1k --- crates/bevy_geometry/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index 410f852c9b878..693531e7577d6 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -176,7 +176,7 @@ impl AABB { Err(PrimitiveError::MinGreaterThanMax) } } - /// Construct an [AxisALignedBox] from the origin at the minimum corner, and the extents - the + /// 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 { From e967a83bd7eb3e4f558d6d0005258e7a8fb51ac8 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Thu, 8 Apr 2021 13:07:59 -0700 Subject: [PATCH 12/13] Apply suggestions from code review Co-authored-by: Grindv1k --- crates/bevy_geometry/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs index 693531e7577d6..8980d5c12a3f7 100644 --- a/crates/bevy_geometry/src/lib.rs +++ b/crates/bevy_geometry/src/lib.rs @@ -59,7 +59,7 @@ impl Sphere { } } impl Primitive3d for Sphere { - /// Use the sphere's position and radius to determin eif it is entirely on the outside of the + /// Use the sphere's position and radius to determine if 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 @@ -188,7 +188,7 @@ impl AABB { Err(PrimitiveError::NonPositiveExtents) } } - /// Computes the AAB that + /// Computes the [AxisAlignedBox] whose extents are determined by the minimum and maximum of the points given. pub fn from_points(points: &[Vec3]) -> AABB { let mut max = Vec3::splat(f32::MIN); let mut min = Vec3::splat(f32::MAX); From 5b263ffe82658cace1e305dd8711f72e0b89794f Mon Sep 17 00:00:00 2001 From: Andre Popovitch Date: Tue, 18 May 2021 10:58:54 -0700 Subject: [PATCH 13/13] Add a function for computing a world point from a screen point --- Cargo.toml | 8 + crates/bevy_geometry/Cargo.toml | 15 + crates/bevy_geometry/src/lib.rs | 382 ++++++++++++++++++++++++ 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 | 63 ++++ examples/2d/mouse_tracking.rs | 45 +++ examples/3d/3d_scene.rs | 2 +- examples/3d/screen_to_world.rs | 69 +++++ examples/README.md | 160 ++++++---- tools/publish.sh | 1 + 12 files changed, 688 insertions(+), 70 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 898ab0bbc3b8f..f13415c38aece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,11 +125,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 0000000000000..1b38a6361717e --- /dev/null +++ b/crates/bevy_geometry/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bevy_geometry" +version = "0.5.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.5.0" } +bevy_math = { path = "../bevy_math", version = "0.5.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } diff --git a/crates/bevy_geometry/src/lib.rs b/crates/bevy_geometry/src/lib.rs new file mode 100644 index 0000000000000..d9a78d30c47f6 --- /dev/null +++ b/crates/bevy_geometry/src/lib.rs @@ -0,0 +1,382 @@ +use bevy_math::*; +use bevy_reflect::Reflect; +use std::{error::Error, 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 9d557aff5d167..c8cdb052e6f4f 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"] @@ -52,6 +52,7 @@ bevy_core = { path = "../bevy_core", version = "0.5.0" } bevy_derive = { path = "../bevy_derive", version = "0.5.0" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.5.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } +bevy_geometry = { path = "../bevy_geometry", version = "0.5.0" } bevy_input = { path = "../bevy_input", version = "0.5.0" } bevy_log = { path = "../bevy_log", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } @@ -75,4 +76,4 @@ bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.5.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 62fb7cf270b56..8032858b1932d 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 6dbc5cbf0407b..8288ec8c67bba 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -19,6 +19,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.5.0" } bevy_core = { path = "../bevy_core", version = "0.5.0" } bevy_derive = { path = "../bevy_derive", version = "0.5.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } +bevy_geometry = { path = "../bevy_geometry", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } bevy_transform = { path = "../bevy_transform", version = "0.5.0" } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index df4074001ce8a..39b27def19bfc 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,68 @@ 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: W, + camera: &Camera, + camera_transform: &GlobalTransform, + ) -> Line { + let camera_position = camera_transform.compute_matrix(); + let window = windows + .as_ref() + .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: W, + 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: W, + 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, + ) + } } #[allow(clippy::type_complexity)] diff --git a/examples/2d/mouse_tracking.rs b/examples/2d/mouse_tracking.rs new file mode 100644 index 0000000000000..063291f44a9a5 --- /dev/null +++ b/examples/2d/mouse_tracking.rs @@ -0,0 +1,45 @@ +use bevy::{prelude::*, 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 3aec65a2e82b9..3dded32ee274b 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{geometry::Plane, prelude::*}; fn main() { App::build() diff --git a/examples/3d/screen_to_world.rs b/examples/3d/screen_to_world.rs new file mode 100644 index 0000000000000..f72140e67b15c --- /dev/null +++ b/examples/3d/screen_to_world.rs @@ -0,0 +1,69 @@ +use bevy::{geometry::Plane, prelude::*, 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(PointLightBundle { + 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 13b791835ba65..882d56c845983 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,11 +69,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 @@ -89,6 +90,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 @@ -105,37 +107,38 @@ 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 @@ -165,13 +168,26 @@ Example | File | Description ## 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. @@ -185,23 +201,34 @@ 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_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 +<<<<<<< HEAD +| 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 --- | --- | --- `animate_shader` | [`shader/animate_shader.rs`](./shader/animate_shader.rs) | Shows how to animate a shader by accessing a time uniform variable @@ -210,6 +237,7 @@ Example | File | Description `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) +>>>>>>> upstream/main ## Tests @@ -219,28 +247,28 @@ Example | File | Description ## 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 @@ -292,9 +320,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 @@ -340,9 +368,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 @@ -370,9 +398,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/tools/publish.sh b/tools/publish.sh index 6d827feaf6214..188d04b0e25d1 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