Skip to content

Commit d2ac22e

Browse files
feat(core): Entity To Entity Raycasting. (#720)
Co-authored-by: Andrew Gazelka <andrew.gazelka@gmail.com>
1 parent 62a5290 commit d2ac22e

File tree

7 files changed

+89
-24
lines changed

7 files changed

+89
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/geometry/src/ray.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::Mul;
2+
13
use glam::{IVec3, Vec3};
24

35
#[derive(Debug, Clone, Copy)]
@@ -7,6 +9,14 @@ pub struct Ray {
79
inv_direction: Vec3,
810
}
911

12+
impl Mul<f32> for Ray {
13+
type Output = Self;
14+
15+
fn mul(self, rhs: f32) -> Self::Output {
16+
Self::new(self.origin, self.direction * rhs)
17+
}
18+
}
19+
1020
impl Ray {
1121
#[must_use]
1222
pub const fn origin(&self) -> Vec3 {

crates/hyperion/src/egress/sync_entity_state.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,7 @@ impl Module for EntityStateSyncModule {
428428

429429
#[allow(clippy::excessive_nesting)]
430430
world.get::<&mut Blocks>(|blocks| {
431-
// calculate distance limit based on velocity
432-
let distance_limit = velocity.0.length();
433-
let Some(collision) = blocks.first_collision(ray, distance_limit) else {
431+
let Some(collision) = blocks.first_collision(ray) else {
434432
// Drag (0.99 / 20.0)
435433
// 1.0 - (0.99 / 20.0) * 0.05
436434
velocity.0 *= 0.997_525;
@@ -442,7 +440,6 @@ impl Module for EntityStateSyncModule {
442440
velocity.0 = velocity.0.clamp_length(0.0, 100.0);
443441
return;
444442
};
445-
debug!("distance_limit = {}", distance_limit);
446443

447444
debug!("collision = {collision:?}");
448445

crates/hyperion/src/simulation/blocks/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum TrySetBlockDeltaError {
5454
ChunkNotLoaded,
5555
}
5656

57-
#[derive(Debug)]
57+
#[derive(Debug, Copy, Clone)]
5858
pub struct RayCollision {
5959
pub distance: f32,
6060
pub location: IVec3,
@@ -99,12 +99,12 @@ impl Blocks {
9999
}
100100

101101
#[must_use]
102-
pub fn first_collision(&self, ray: Ray, distance_limit: f32) -> Option<RayCollision> {
102+
pub fn first_collision(&self, ray: Ray) -> Option<RayCollision> {
103103
// Calculate exact start position (the block we're in)
104104
let start_pos = ray.origin();
105105

106106
// Calculate end position with a small offset to handle edge cases
107-
let end_pos = ray.origin() + ray.direction() * (distance_limit + 0.0001);
107+
let end_pos = ray.origin() + ray.direction();
108108

109109
// Convert to block coordinates, expanding bounds to ensure we catch all blocks
110110
let min_block = start_pos.min(end_pos).floor().as_ivec3();

crates/spatial/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ publish = false
99
[dependencies]
1010
bvh-region.workspace = true
1111
flecs_ecs.workspace = true
12-
hyperion.workspace = true
1312
geometry.workspace = true
13+
hyperion.workspace = true
14+
ordered-float.workspace = true
1415
rayon.workspace = true
1516

1617
[lints]

crates/spatial/src/lib.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ use flecs_ecs::{
66
macros::{Component, system},
77
prelude::Module,
88
};
9-
use geometry::aabb::Aabb;
9+
use geometry::{aabb::Aabb, ray::Ray};
1010
use hyperion::{
1111
egress::player_join::RayonWorldStages,
1212
glam::Vec3,
13-
simulation::{EntitySize, Position, aabb},
13+
simulation::{
14+
EntitySize, Position, aabb,
15+
blocks::{Blocks, RayCollision},
16+
},
1417
};
18+
use ordered_float::NotNan;
19+
use rayon::iter::Either;
1520

1621
#[derive(Component)]
1722
pub struct SpatialModule;
@@ -22,6 +27,39 @@ pub struct SpatialIndex {
2227
query: bvh_region::Bvh<Entity>,
2328
}
2429

30+
#[must_use]
31+
pub fn get_first_collision(
32+
ray: Ray,
33+
world: &World,
34+
) -> Option<Either<EntityView<'_>, RayCollision>> {
35+
// Check for collisions with entities
36+
let entity = world.get::<&SpatialIndex>(|index| index.closest_to_ray(ray, world));
37+
let block = world.get::<&Blocks>(|blocks| blocks.first_collision(ray));
38+
39+
// check which one is closest to the Ray don't forget to account for entity size
40+
entity.map_or(block.map(Either::Right), |(entity, _)| {
41+
let entity_data = entity.get::<(&Position, &EntitySize)>(|(position, size)| {
42+
let entity_aabb = aabb(**position, *size);
43+
44+
#[allow(clippy::redundant_closure_for_method_calls)]
45+
let distance_to_entity = entity_aabb
46+
.intersect_ray(&ray)
47+
.map_or(f32::MAX, |distance| distance.into_inner());
48+
49+
(entity, distance_to_entity)
50+
});
51+
52+
let (entity, distance_to_entity) = entity_data;
53+
block.map_or(Some(Either::Left(entity)), |block_collision| {
54+
if distance_to_entity < block_collision.distance {
55+
Some(Either::Left(entity))
56+
} else {
57+
Some(Either::Right(block_collision))
58+
}
59+
})
60+
})
61+
}
62+
2563
fn get_aabb_func<'a>(world: &'a World) -> impl Fn(&Entity) -> Aabb + Send + Sync {
2664
let stages: &'a RayonWorldStages = world.get::<&RayonWorldStages>(|stages| {
2765
// we can properly extend lifetimes here
@@ -61,6 +99,18 @@ impl SpatialIndex {
6199
let (target, _) = self.query.get_closest(point, &get_aabb)?;
62100
Some(world.entity_from_id(*target))
63101
}
102+
103+
#[must_use]
104+
pub fn closest_to_ray<'a>(
105+
&self,
106+
ray: Ray,
107+
world: &'a World,
108+
) -> Option<(EntityView<'a>, NotNan<f32>)> {
109+
let get_aabb = get_aabb_func(world);
110+
let (entity, distance) = self.query.get_closest_ray(ray, get_aabb)?;
111+
let entity = world.entity_from_id(*entity);
112+
Some((entity, distance))
113+
}
64114
}
65115

66116
/// If we want the entity to be spatially indexed, we need to add this component.

events/tag/src/command/raycast.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use clap::Parser;
2-
use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldGet, WorldProvider};
2+
use flecs_ecs::core::{Entity, EntityView, EntityViewGet, WorldProvider};
33
use hyperion::{
4-
BlockState,
54
glam::Vec3,
6-
simulation::{Pitch, Position, Yaw, blocks::Blocks},
5+
simulation::{Pitch, Position, Yaw, entity_kind::EntityKind},
76
};
87
use hyperion_clap::{CommandPermission, MinecraftCommand};
8+
use rayon::iter::Either;
9+
use spatial::get_first_collision;
910
use tracing::debug;
1011

1112
#[derive(Parser, CommandPermission, Debug)]
@@ -43,6 +44,7 @@ pub fn get_direction_from_rotation(yaw: f32, pitch: f32) -> Vec3 {
4344
impl MinecraftCommand for RaycastCommand {
4445
fn execute(self, system: EntityView<'_>, caller: Entity) {
4546
const EYE_HEIGHT: f32 = 1.62;
47+
const DISTANCE: f32 = 10.0;
4648

4749
let world = system.world();
4850

@@ -55,21 +57,25 @@ impl MinecraftCommand for RaycastCommand {
5557
let eye = center + Vec3::new(0.0, EYE_HEIGHT, 0.0);
5658
let direction = get_direction_from_rotation(**yaw, **pitch);
5759

58-
geometry::ray::Ray::new(eye, direction)
60+
geometry::ray::Ray::new(eye, direction) * DISTANCE
5961
});
6062

6163
debug!("ray = {ray:?}");
6264

63-
world.get::<&mut Blocks>(|blocks| {
64-
let Some(collision) = blocks.first_collision(ray, 10.0) else {
65-
return;
66-
};
65+
let result = get_first_collision(ray, &world);
6766

68-
debug!("collision = {collision:?}");
69-
70-
blocks
71-
.set_block(collision.location, BlockState::DIRT)
72-
.unwrap();
73-
});
67+
match result {
68+
Some(Either::Left(entity)) => {
69+
entity
70+
.entity_view(world)
71+
.get::<(&Position, &EntityKind)>(|(position, kind)| {
72+
let position = **position;
73+
debug!("kind: {kind:?}");
74+
debug!("position: {position:?}");
75+
});
76+
}
77+
Some(Either::Right(ray_collision)) => debug!("ray_collision: {ray_collision:?}"),
78+
None => debug!("no collision found"),
79+
}
7480
}
7581
}

0 commit comments

Comments
 (0)