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

feat(physics): add optional range to raycasts #798

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/editor/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub async fn rpc_select(args: ServerRpcArgs, (method, mode): (SelectMethod, Sele
collider_type: None,
},
ray,
None,
) {
Selection::new([entity])
} else {
Expand Down
76 changes: 56 additions & 20 deletions crates/physics/src/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,23 @@ pub fn get_entities_in_radius(world: &World, center: Vec3, radius: f32) -> Vec<E
.collect_vec()
}

pub fn raycast_first(world: &World, ray: Ray) -> Option<(EntityId, f32)> {
raycast_first_px(world, ray).and_then(|(shape, dist)| {
pub fn raycast_first(
world: &World,
ray: Ray,
max_distance: Option<f32>,
) -> Option<(EntityId, f32)> {
raycast_first_px(world, ray, max_distance).and_then(|(shape, dist)| {
shape
.get_user_data::<PxShapeUserData>()
.map(|ud| (ud.entity, dist))
})
}

fn raycast_first_px(world: &World, ray: Ray) -> Option<(PxShape, f32)> {
fn raycast_first_px(world: &World, ray: Ray, max_distance: Option<f32>) -> Option<(PxShape, f32)> {
(0..3)
.filter_map(|i| raycast_first_collider_type_px(world, ColliderScene::from_usize(i), ray))
.filter_map(|i| {
raycast_first_collider_type_px(world, ColliderScene::from_usize(i), ray, max_distance)
})
.sorted_by_key(|x| OrderedFloat(x.1))
.next()
}
Expand All @@ -48,22 +54,33 @@ pub fn raycast_first_collider_type(
world: &World,
collider_type: ColliderScene,
ray: Ray,
max_distance: Option<f32>,
) -> Option<(EntityId, f32)> {
raycast_first_collider_type_px(world, collider_type, ray).and_then(|(shape, dist)| {
shape
.get_user_data::<PxShapeUserData>()
.map(|ud| (ud.entity, dist))
})
raycast_first_collider_type_px(world, collider_type, ray, max_distance).and_then(
|(shape, dist)| {
shape
.get_user_data::<PxShapeUserData>()
.map(|ud| (ud.entity, dist))
},
)
}
pub fn raycast_first_collider_type_px(
world: &World,
collider_type: ColliderScene,
ray: Ray,
max_distance: Option<f32>,
) -> Option<(PxShape, f32)> {
let mut hit = PxRaycastCallback::new(0);
let scene = collider_type.get_scene(world);
let filter_data = PxQueryFilterData::new();
if scene.raycast(ray.origin, ray.dir, f32::MAX, &mut hit, None, &filter_data) {
if scene.raycast(
ray.origin,
ray.dir,
get_max_distance(max_distance),
&mut hit,
None,
&filter_data,
) {
let block = hit.block().unwrap();
if let Some(shape) = block.shape {
return Some((shape, block.distance));
Expand All @@ -72,8 +89,8 @@ pub fn raycast_first_collider_type_px(
None
}

pub fn raycast(world: &World, ray: Ray) -> Vec<(EntityId, f32)> {
raycast_px(world, ray)
pub fn raycast(world: &World, ray: Ray, max_distance: Option<f32>) -> Vec<(EntityId, f32)> {
raycast_px(world, ray, max_distance)
.into_iter()
.flat_map(|(shape, dist)| {
shape
Expand All @@ -83,10 +100,11 @@ pub fn raycast(world: &World, ray: Ray) -> Vec<(EntityId, f32)> {
.collect_vec()
}

fn raycast_px(world: &World, ray: Ray) -> Vec<(PxShape, f32)> {
fn raycast_px(world: &World, ray: Ray, max_distance: Option<f32>) -> Vec<(PxShape, f32)> {
(0..3)
.flat_map(|i| {
raycast_collider_type_px(world, ColliderScene::from_usize(i), ray).into_iter()
raycast_collider_type_px(world, ColliderScene::from_usize(i), ray, max_distance)
.into_iter()
})
.sorted_by_key(|x| OrderedFloat(x.1))
.collect_vec()
Expand All @@ -96,8 +114,9 @@ pub fn raycast_collider_type(
world: &World,
collider_type: ColliderScene,
ray: Ray,
max_distance: Option<f32>,
) -> Vec<(EntityId, f32)> {
raycast_collider_type_px(world, collider_type, ray)
raycast_collider_type_px(world, collider_type, ray, max_distance)
.into_iter()
.filter_map(|(shape, dist)| {
shape
Expand All @@ -110,11 +129,19 @@ pub fn raycast_collider_type_px(
world: &World,
collider_type: ColliderScene,
ray: Ray,
max_distance: Option<f32>,
) -> Vec<(PxShape, f32)> {
let mut hit = PxRaycastCallback::new(100);
let scene = collider_type.get_scene(world);
let filter_data = PxQueryFilterData::new();
if scene.raycast(ray.origin, ray.dir, f32::MAX, &mut hit, None, &filter_data) {
if scene.raycast(
ray.origin,
ray.dir,
get_max_distance(max_distance),
&mut hit,
None,
&filter_data,
) {
return hit
.touches()
.into_iter()
Expand All @@ -124,6 +151,10 @@ pub fn raycast_collider_type_px(
Vec::new()
}

fn get_max_distance(max_distance: Option<f32>) -> f32 {
max_distance.unwrap_or(f32::MAX)
}

pub fn intersect_frustum(world: &World, frustum_corners: &[Vec3; 8]) -> Vec<EntityId> {
let mut hit_call = PxOverlapCallback::new(1000);
let filter_data = PxQueryFilterData::new();
Expand Down Expand Up @@ -170,14 +201,19 @@ pub async fn rpc_pick(
(ray, filter): (Ray, RaycastFilter),
) -> Option<(EntityId, f32)> {
let state = args.state.lock();
raycast_filtered(state.get_player_world(&args.user_id)?, filter, ray)
raycast_filtered(state.get_player_world(&args.user_id)?, filter, ray, None)
}

pub fn raycast_filtered(world: &World, filter: RaycastFilter, ray: Ray) -> Option<(EntityId, f32)> {
pub fn raycast_filtered(
world: &World,
filter: RaycastFilter,
ray: Ray,
max_distance: Option<f32>,
) -> Option<(EntityId, f32)> {
let hits = if let Some(collider_type) = filter.collider_type {
raycast_collider_type(world, collider_type, ray)
raycast_collider_type(world, collider_type, ray, max_distance)
} else {
raycast(world, ray)
raycast(world, ray, max_distance)
};
if let Some(filter) = &filter.entities {
hits.into_iter()
Expand Down
2 changes: 2 additions & 0 deletions crates/wasm/src/client/implementation/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ impl wit::server_physics::Host for Bindings {
&mut self,
_origin: wit::types::Vec3,
_direction: wit::types::Vec3,
_max_distance: Option<f32>,
) -> anyhow::Result<Option<(wit::types::EntityId, f32)>> {
unsupported()
}
Expand All @@ -106,6 +107,7 @@ impl wit::server_physics::Host for Bindings {
&mut self,
_origin: wit::types::Vec3,
_direction: wit::types::Vec3,
_max_distance: Option<f32>,
) -> anyhow::Result<Vec<(wit::types::EntityId, f32)>> {
unsupported()
}
Expand Down
4 changes: 4 additions & 0 deletions crates/wasm/src/server/implementation/specific/physics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,13 @@ impl shared::wit::server_physics::Host for Bindings {
&mut self,
origin: wit::types::Vec3,
direction: wit::types::Vec3,
max_distance: Option<f32>,
) -> anyhow::Result<Option<(wit::types::EntityId, f32)>> {
let direction = get_raycast_direction(direction)?;
let result = ambient_physics::intersection::raycast_first(
self.world(),
Ray::new(origin.from_bindgen(), direction),
max_distance,
)
.map(|t| (t.0.into_bindgen(), t.1.into_bindgen()));

Expand All @@ -189,11 +191,13 @@ impl shared::wit::server_physics::Host for Bindings {
&mut self,
origin: wit::types::Vec3,
direction: wit::types::Vec3,
max_distance: Option<f32>,
) -> anyhow::Result<Vec<(wit::types::EntityId, f32)>> {
let direction = get_raycast_direction(direction)?;
let result = ambient_physics::intersection::raycast(
self.world(),
Ray::new(origin.from_bindgen(), direction),
max_distance,
)
.into_iter()
.map(|t| (t.0.into_bindgen(), t.1.into_bindgen()))
Expand Down
4 changes: 2 additions & 2 deletions crates/wasm/wit/server-physics.wit
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ interface server-physics {
start-motor: func(entity: entity-id, velocity: float32)
stop-motor: func(entity: entity-id)
create-revolute-joint: func(actor0: entity-id, transform0: mat4, actor1: entity-id, transform1: mat4)
raycast-first: func(origin: vec3, direction: vec3) -> option<tuple<entity-id, float32>>
raycast: func(origin: vec3, direction: vec3) -> list<tuple<entity-id, float32>>
raycast-first: func(origin: vec3, direction: vec3, max-distance: option<float32>) -> option<tuple<entity-id, float32>>
raycast: func(origin: vec3, direction: vec3, max-distance: option<float32>) -> list<tuple<entity-id, float32>>
move-character: func(entity: entity-id, displacement: vec3, min-dist: float32, elapsed-time: float32) -> character-collision
set-character-position: func(entity: entity-id, position: vec3)
set-character-foot-position: func(entity: entity-id, position: vec3)
Expand Down
36 changes: 23 additions & 13 deletions guest/rust/api_core/src/internal/bindings.rs

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions guest/rust/api_core/src/server/physics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,36 @@ pub struct RaycastHit {
/// Casts a ray from `origin` in `direction`, and returns the [RaycastHit]s along the way.
///
/// `direction` must be normalized.
pub fn raycast(origin: Vec3, direction: Vec3) -> Vec<RaycastHit> {
wit::server_physics::raycast(origin.into_bindgen(), direction.into_bindgen())
.into_iter()
.map(|(entity, distance)| raycast_result_to_hit(origin, direction, entity, distance))
.collect()
///
/// If `max_distance` is specified, the ray will stop after travelling that distance. Otherwise, it will
/// travel infinitely.
pub fn raycast(origin: Vec3, direction: Vec3, max_distance: Option<f32>) -> Vec<RaycastHit> {
wit::server_physics::raycast(
origin.into_bindgen(),
direction.into_bindgen(),
max_distance,
)
.into_iter()
.map(|(entity, distance)| raycast_result_to_hit(origin, direction, entity, distance))
.collect()
}
/// Casts a ray from `origin` in `direction`, and returns the first [RaycastHit] if it hits.
///
/// `direction` must be normalized.
pub fn raycast_first(origin: Vec3, direction: Vec3) -> Option<RaycastHit> {
wit::server_physics::raycast_first(origin.into_bindgen(), direction.into_bindgen())
.map(|(entity, distance)| raycast_result_to_hit(origin, direction, entity, distance))
///
/// If `max_distance` is specified, the ray will stop after travelling that distance. Otherwise, it will
/// travel infinitely.
pub fn raycast_first(
origin: Vec3,
direction: Vec3,
max_distance: Option<f32>,
) -> Option<RaycastHit> {
wit::server_physics::raycast_first(
origin.into_bindgen(),
direction.into_bindgen(),
max_distance,
)
.map(|(entity, distance)| raycast_result_to_hit(origin, direction, entity, distance))
}
fn raycast_result_to_hit(
origin: Vec3,
Expand Down
8 changes: 6 additions & 2 deletions guest/rust/examples/basics/physics/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ pub async fn main() {

Collision::subscribe(move |msg| {
println!("Bonk! {:?} collided", msg.ids);
Bonk {emitter: cube, listener: camera}.send_client_broadcast_unreliable();
Bonk {
emitter: cube,
listener: camera,
}
.send_client_broadcast_unreliable();
});

Frame::subscribe(move |_| {
for hit in physics::raycast(Vec3::Z * 20., -Vec3::Z) {
for hit in physics::raycast(Vec3::Z * 20., -Vec3::Z, Some(10.)) {
if hit.entity == cube {
println!("The raycast hit the cube: {hit:?}");
}
Expand Down
2 changes: 1 addition & 1 deletion guest/rust/examples/intermediate/screen_ray/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn main() {
.spawn();

Input::subscribe(move |_ctx, msg| {
if let Some(hit) = physics::raycast_first(msg.ray_origin, msg.ray_dir) {
if let Some(hit) = physics::raycast_first(msg.ray_origin, msg.ray_dir, None) {
// Set position of cube to the raycast hit position
entity::set_component(cube_id, translation(), hit.position);
WorldPosition::new(hit.position).send_client_broadcast_unreliable();
Expand Down
2 changes: 1 addition & 1 deletion guest/rust/packages/games/afps/core/fpsrule/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn main() {
});

Shoot::subscribe(move |_ctx, msg| {
let result = physics::raycast_first(msg.ray_origin, msg.ray_dir);
let result = physics::raycast_first(msg.ray_origin, msg.ray_dir, None);

if let Some(hit) = result {
// Laser gun, not used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use packages::{

pub fn main() {
Spraypaint::subscribe(move |_ctx, msg| {
if let Some(hit) = physics::raycast_first(msg.origin, msg.dir) {
if let Some(hit) = physics::raycast_first(msg.origin, msg.dir, None) {
let player_pos = entity::get_component(msg.source, translation()).unwrap();
let distance = (player_pos - hit.position).length();
if distance > 12. {
Expand Down
2 changes: 1 addition & 1 deletion guest/rust/packages/tools/editor/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn main() {
*translation += new_rotation * vec3(movement.x, 0.0, -movement.y) * movement_speed;
});

if let Some(hit) = physics::raycast_first(msg.ray_origin, msg.ray_direction) {
if let Some(hit) = physics::raycast_first(msg.ray_origin, msg.ray_direction, None) {
entity::add_component(id, mouseover_position(), hit.position);
entity::add_component(id, mouseover_entity(), hit.entity);
} else {
Expand Down
Loading