Skip to content

Commit 82f32bf

Browse files
test: add spatial query (hyperion-mc#754)
1 parent c41f34b commit 82f32bf

File tree

9 files changed

+100
-10
lines changed

9 files changed

+100
-10
lines changed

Cargo.lock

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

crates/bvh-region/src/query/ray.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ impl<T: Debug> Bvh<T> {
1010
///
1111
/// If no element is hit, returns `None`.
1212
#[allow(clippy::excessive_nesting)]
13-
pub fn get_closest_ray(
13+
pub fn first_ray_collision(
1414
&self,
1515
ray: Ray,
1616
get_aabb: impl Fn(&T) -> Aabb,

crates/bvh-region/tests/simple.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn test_bvh_vs_brute_known_case() {
3737

3838
// BVH:
3939
let bvh = Bvh::build(elements.clone(), |x| *x);
40-
let bvh_closest = bvh.get_closest_ray(ray, |x| *x);
40+
let bvh_closest = bvh.first_ray_collision(ray, |x| *x);
4141

4242
assert_eq!(brute_closest.map(|x| x.1), bvh_closest.map(|x| x.1));
4343
}
@@ -53,7 +53,7 @@ fn test_multiple_aabbs_along_ray() {
5353
let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
5454
let bvh = Bvh::build(elements.clone(), |x| *x);
5555
let (closest, dist) = bvh
56-
.get_closest_ray(ray, |x| *x)
56+
.first_ray_collision(ray, |x| *x)
5757
.expect("Should find intersection");
5858
assert_eq!(
5959
closest, &elements[0],
@@ -82,7 +82,7 @@ proptest! {
8282
let ray = Ray::new(Vec3::new(origin.0, origin.1, origin.2), Vec3::new(direction.0, direction.1, direction.2));
8383

8484
let bvh = Bvh::build(elements.clone(), |x| *x);
85-
let bvh_closest = bvh.get_closest_ray(ray, |x| *x);
85+
let bvh_closest = bvh.first_ray_collision(ray, |x| *x);
8686
let brute_closest = brute_force_closest_ray(&elements, ray);
8787

8888
match (bvh_closest, brute_closest) {
@@ -287,7 +287,7 @@ proptest! {
287287
);
288288

289289
let bvh = Bvh::build(elements.clone(), copied);
290-
let bvh_closest = bvh.get_closest_ray(ray, copied);
290+
let bvh_closest = bvh.first_ray_collision(ray, copied);
291291
let brute_closest = brute_force_closest_ray(&elements, ray);
292292

293293
match (bvh_closest, brute_closest) {

crates/geometry/src/ray.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ impl Ray {
4444
}
4545
}
4646

47+
#[must_use]
48+
pub fn from_points(origin: Vec3, end: Vec3) -> Self {
49+
let direction = end - origin;
50+
Self::new(origin, direction)
51+
}
52+
4753
/// Get the point along the ray at distance t
4854
#[must_use]
4955
pub fn at(&self, t: f32) -> Vec3 {

crates/hyperion/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ valence_server = { workspace = true }
7373
valence_text = { workspace = true }
7474

7575
[dev-dependencies]
76+
approx = { workspace = true }
7677
divan = { workspace = true }
7778
fastrand = { workspace = true }
79+
spatial = { workspace = true }
7880

7981
[lints]
8082
workspace = true

crates/hyperion/src/simulation/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ pub struct Pitch {
440440
const PLAYER_WIDTH: f32 = 0.6;
441441
const PLAYER_HEIGHT: f32 = 1.8;
442442

443-
#[derive(Component, Copy, Clone, Debug)]
443+
#[derive(Component, Copy, Clone, Debug, Constructor, PartialEq)]
444444
#[meta]
445445
pub struct EntitySize {
446446
pub half_width: f32,

crates/hyperion/tests/entity.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fn arrow() {
4040
.set(Velocity::new(0.0, 1.0, 0.0))
4141
.set(Position::new(0.0, 20.0, 0.0));
4242

43-
println!("arrow = {arrow:?}\n");
43+
println!("arrow = {arrow:?}");
4444

4545
world.progress();
4646

crates/hyperion/tests/spatial.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#![feature(assert_matches)]
2+
#![allow(
3+
clippy::print_stdout,
4+
reason = "the purpose of not having printing to stdout is so that tracing is used properly \
5+
for the core libraries. These are tests, so it doesn't matter"
6+
)]
7+
8+
use std::{assert_matches::assert_matches, collections::HashSet};
9+
10+
use approx::assert_relative_eq;
11+
use flecs_ecs::core::{QueryBuilderImpl, SystemAPI, World, WorldGet, flecs};
12+
use geometry::{aabb::Aabb, ray::Ray};
13+
use glam::Vec3;
14+
use hyperion::{
15+
HyperionCore,
16+
simulation::{EntitySize, Position, entity_kind::EntityKind},
17+
};
18+
use spatial::{Spatial, SpatialIndex, SpatialModule};
19+
20+
#[test]
21+
fn spatial() {
22+
let world = World::new();
23+
world.import::<HyperionCore>();
24+
world.import::<SpatialModule>();
25+
26+
// Make all entities have Spatial component so they are spatially indexed
27+
world
28+
.observer::<flecs::OnAdd, ()>()
29+
.with_enum_wildcard::<EntityKind>()
30+
.each_entity(|entity, ()| {
31+
entity.add::<Spatial>();
32+
});
33+
34+
let zombie = world
35+
.entity_named("test_zombie")
36+
.add_enum(EntityKind::Zombie)
37+
.set(EntitySize::default())
38+
.set(Position::new(0.0, 0.0, 0.0));
39+
40+
let player = world
41+
.entity_named("test_player")
42+
.add_enum(EntityKind::Player)
43+
.set(EntitySize::default())
44+
.set(Position::new(10.0, 0.0, 0.0));
45+
46+
// progress one tick to ensure that the index is updated
47+
world.progress();
48+
49+
world.get::<&SpatialIndex>(|spatial| {
50+
let closest = spatial
51+
.closest_to(Vec3::new(1.0, 2.0, 0.0), &world)
52+
.expect("there to be a closest entity");
53+
assert_eq!(closest, zombie);
54+
55+
let closest = spatial
56+
.closest_to(Vec3::new(11.0, 2.0, 0.0), &world)
57+
.expect("there to be a closest entity");
58+
assert_eq!(closest, player);
59+
60+
let big_aabb = Aabb::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(100.0, 100.0, 100.0));
61+
62+
let collisions: HashSet<_> = spatial.get_collisions(big_aabb, &world).collect();
63+
assert!(
64+
collisions.contains(&zombie),
65+
"zombie should be in collisions"
66+
);
67+
assert!(
68+
collisions.contains(&player),
69+
"player should be in collisions"
70+
);
71+
72+
let ray = Ray::from_points(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0));
73+
let (first, distance) = spatial.first_ray_collision(ray, &world).unwrap();
74+
assert_eq!(first, zombie);
75+
assert_relative_eq!(distance.into_inner(), 0.0);
76+
77+
let ray = Ray::from_points(Vec3::new(12.0, 0.0, 0.0), Vec3::new(13.0, 1.0, 1.0));
78+
assert_matches!(spatial.first_ray_collision(ray, &world), None);
79+
});
80+
}

crates/spatial/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub fn get_first_collision(
3333
world: &World,
3434
) -> Option<Either<EntityView<'_>, RayCollision>> {
3535
// Check for collisions with entities
36-
let entity = world.get::<&SpatialIndex>(|index| index.closest_to_ray(ray, world));
36+
let entity = world.get::<&SpatialIndex>(|index| index.first_ray_collision(ray, world));
3737
let block = world.get::<&Blocks>(|blocks| blocks.first_collision(ray));
3838

3939
// check which one is closest to the Ray don't forget to account for entity size
@@ -101,13 +101,13 @@ impl SpatialIndex {
101101
}
102102

103103
#[must_use]
104-
pub fn closest_to_ray<'a>(
104+
pub fn first_ray_collision<'a>(
105105
&self,
106106
ray: Ray,
107107
world: &'a World,
108108
) -> Option<(EntityView<'a>, NotNan<f32>)> {
109109
let get_aabb = get_aabb_func(world);
110-
let (entity, distance) = self.query.get_closest_ray(ray, get_aabb)?;
110+
let (entity, distance) = self.query.first_ray_collision(ray, get_aabb)?;
111111
let entity = world.entity_from_id(*entity);
112112
Some((entity, distance))
113113
}

0 commit comments

Comments
 (0)