Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raycasting #615

Closed
wants to merge 13 commits into from
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ path = "examples/3d/texture.rs"
name = "z_sort_debug"
path = "examples/3d/z_sort_debug.rs"

[[example]]
name = "raycast"
path = "examples/physics/raycast.rs"

[[example]]
name = "custom_loop"
path = "examples/app/custom_loop.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ bevy_input = { path = "../bevy_input", version = "0.3.0" }
bevy_log = { path = "../bevy_log", version = "0.3.0" }
bevy_math = { path = "../bevy_math", version = "0.3.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.3.0", features = ["bevy"] }
bevy_physics = { path = "../bevy_physics", version = "0.3.0" }
bevy_scene = { path = "../bevy_scene", version = "0.3.0" }
bevy_transform = { path = "../bevy_transform", version = "0.3.0" }
bevy_utils = { path = "../bevy_utils", version = "0.3.0" }
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub mod math {
pub use bevy_math::*;
}

pub mod physics {
//! colliders and raycasting
pub use bevy_physics::*;
}

pub mod reflect {
// TODO: remove these renames once TypeRegistryArc is no longer required
//! Type reflection used for dynamically interacting with rust types.
Expand Down
23 changes: 23 additions & 0 deletions crates/bevy_physics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "bevy_physics"
version = "0.3.0"
edition = "2018"
authors = [
"Bevy Contributors <bevyengine@gmail.com>",
"Carter Anderson <mcanders1@gmail.com>",
]
description = "Provides physics functionality for Bevy Engine"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT"
keywords = ["bevy"]

[dependencies]
# bevy
bevy_transform = { path = "../bevy_transform", version = "0.3.0" }
bevy_render = { path = "../bevy_render", version = "0.3.0" }
bevy_window = { path = "../bevy_window", version = "0.3.0" }

# linear algebra
glam = { version = "0.11.2", features = ["serde"] }

55 changes: 55 additions & 0 deletions crates/bevy_physics/src/d3/geometry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use glam::Vec3;

pub struct Plane {
center: Vec3,
normal: Vec3,
}

impl Plane {
pub fn new(center: Vec3, normal: Vec3) -> Self {
normal.normalize();
Self { center, normal }
}

pub fn center(&self) -> &Vec3 {
&self.center
}

pub fn center_mut(&mut self) -> &mut Vec3 {
&mut self.center
}

pub fn normal(&self) -> &Vec3 {
&self.normal
}

pub fn set_normal(&mut self, normal: Vec3) {
normal.normalize();
self.normal = normal;
}
}

impl Default for Plane {
fn default() -> Self {
Plane {
center: Vec3::zero(),
normal: Vec3::unit_y(),
}
}
}

pub struct Sphere {
pub center: Vec3,
pub radius: f32,
}

impl Default for Sphere {
fn default() -> Self {
Sphere {
center: Vec3::zero(),
radius: 1.0,
}
}
}

pub struct Triangle(pub Vec3, pub Vec3, pub Vec3);
105 changes: 105 additions & 0 deletions crates/bevy_physics/src/d3/intersectors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use super::{geometry::*, ray::*};
use glam::Vec3;

pub struct RayHit {
distance: f32,
point: Vec3,
}

impl RayHit {
pub fn new(distance: f32, point: Vec3) -> Self {
Self { distance, point }
}

pub fn distance(&self) -> &f32 {
&self.distance
}

pub fn point(&self) -> &Vec3 {
&self.point
}
}

pub trait RayIntersector {
fn intersect_ray(&self, ray: &Ray) -> Option<RayHit>;
}

impl RayIntersector for Plane {
fn intersect_ray(&self, ray: &Ray) -> Option<RayHit> {
let denominator = self.normal().dot(*ray.direction());
if denominator.abs() > f32::EPSILON {
let distance = (*self.center() - *ray.origin()).dot(*self.normal()) / denominator;
if distance > 0.0 {
return Some(RayHit::new(
distance,
*ray.origin() + *ray.direction() * distance,
));
}
}

None
}
}

impl RayIntersector for Sphere {
fn intersect_ray(&self, ray: &Ray) -> Option<RayHit> {
let oc = *ray.origin() - self.center;
let a = ray.direction().length_squared();
let b = 2.0 * oc.dot(*ray.direction());
let c = oc.length_squared() - self.radius.powi(2);

let d = b.powi(2) - 4.0 * a * c;

if d < 0.0 {
None
} else {
let distance = (-b - d.sqrt()) / (2.0 * a);

Some(RayHit::new(
distance,
*ray.origin() + *ray.direction() * distance,
))
}
}
}

impl RayIntersector for Triangle {
// using the Moeller-Trumbore intersection algorithm
// Can anyone think of sensible names for theese?
#[allow(clippy::many_single_char_names)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this allow acceptable?

fn intersect_ray(&self, ray: &Ray) -> Option<RayHit> {
let edges = (self.1 - self.0, self.2 - self.0);
let h = ray.direction().cross(edges.1);
let a = edges.0.dot(h);

if a > -f32::EPSILON && a < f32::EPSILON {
return None;
}

let f = 1.0 / a;
let s = *ray.origin() - self.0;
let u = f * s.dot(h);

if !(0.0..=1.0).contains(&u) {
return None;
}

let q = s.cross(edges.0);
let v = f * ray.direction().dot(q);

if v < 0.0 || u + v > 1.0 {
return None;
}

let distance = f * edges.1.dot(q);

if distance > f32::EPSILON {
Some(RayHit::new(
distance,
*ray.origin() + *ray.direction() * distance,
))
} else {
None
}
}
}
7 changes: 7 additions & 0 deletions crates/bevy_physics/src/d3/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod geometry;
pub mod intersectors;
pub mod ray;

pub mod prelude {
pub use super::{geometry::*, intersectors::*, ray::*};
}
73 changes: 73 additions & 0 deletions crates/bevy_physics/src/d3/ray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use bevy_render::camera::Camera;
use bevy_transform::components::GlobalTransform;
use bevy_window::Window;
use glam::{Vec2, Vec3};

pub struct Ray {
origin: Vec3,
direction: Vec3,
}

impl Ray {
pub fn new(origin: Vec3, direction: Vec3) -> Self {
direction.normalize();
Self { origin, direction }
}

pub fn from_window(
window: &Window,
camera: &Camera,
camera_transform: &GlobalTransform,
) -> Self {
Self::from_mouse_position(
&window.cursor_position().unwrap(),
window,
camera,
camera_transform,
)
}

pub fn from_mouse_position(
mouse_position: &Vec2,
Copy link
Member

@smokku smokku Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Window now has .cursor_position() method. We could add:

pub fn from_window(
    window: &Window,
    camera: &Camera,
    camera_transform: &GlobalTransform,
) -> Self {
    Self::from_mouse_position(window.cursor_position(), camera, camera_transform)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I'll add it.

window: &Window,
camera: &Camera,
camera_transform: &GlobalTransform,
) -> Self {
if window.id() != camera.window {
panic!("Generating Ray from Camera with wrong Window");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if i'm wrong, this can terminate the program?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, you're right. This is by design. Passing a non-matching window and camera to from_mouse_position(..) would result in undefined behavior and should be checked before calling the method. Do you think a Log Error would be more sensible? I didn't familiarize myself very much with the Diagnostics Plugin, so maybe that could help.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I don't know the right answer, I am learning here 👍

}

let x = 2.0 * (mouse_position.x / window.width() as f32) - 1.0;
let y = 2.0 * (mouse_position.y / window.height() as f32) - 1.0;

let camera_inverse_matrix =
camera_transform.compute_matrix() * camera.projection_matrix.inverse();
let near = camera_inverse_matrix * Vec3::new(x, y, 0.0).extend(1.0);
let far = camera_inverse_matrix * Vec3::new(x, y, 1.0).extend(1.0);

let near = near.truncate() / near.w;
let far = far.truncate() / far.w;

let direction: Vec3 = far - near;
let origin: Vec3 = near;

Self { origin, direction }
}

pub fn origin(&self) -> &Vec3 {
&self.origin
}

pub fn origin_mut(&mut self) -> &mut Vec3 {
&mut self.origin
}

pub fn direction(&self) -> &Vec3 {
&self.direction
}

pub fn set_direction(&mut self, direction: Vec3) {
direction.normalize();
self.direction = direction;
}
}
1 change: 1 addition & 0 deletions crates/bevy_physics/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod d3;
Loading