Skip to content

Commit

Permalink
feat: add raycasting
Browse files Browse the repository at this point in the history
Closes #563
  • Loading branch information
andrewgazelka committed Nov 26, 2024
1 parent 846808b commit 8e363c9
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions crates/geometry/src/aabb.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::{fmt::Display, ops::Add};

use glam::Vec3;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -32,6 +32,17 @@ impl Display for Aabb {
}
}

impl Add<Vec3> for Aabb {
type Output = Self;

fn add(self, rhs: Vec3) -> Self::Output {
Self {
min: self.min + rhs,
max: self.max + rhs,
}
}
}

#[derive(Copy, Clone, Eq, PartialEq, Debug, Ord, PartialOrd, Hash)]
pub struct CheckableAabb {
pub min: [ordered_float::NotNan<f32>; 3],
Expand Down Expand Up @@ -200,7 +211,7 @@ impl Aabb {
///
/// Returns Some(t) with the distance to intersection if hit, None if no hit
#[must_use]
pub fn intersect_ray(&self, ray: &Ray) -> Option<f32> {
pub fn intersect_ray(&self, ray: &Ray) -> Option<ordered_float::NotNan<f32>> {
// Calculate t0 and t1 for all three axes simultaneously using SIMD
let t0 = (self.min - ray.origin()) * ray.inv_direction();
let t1 = (self.max - ray.origin()) * ray.inv_direction();
Expand All @@ -217,7 +228,11 @@ impl Aabb {
return None;
}

Some(if t_min > 0.0 { t_min } else { t_max })
Some(if t_min > 0.0 {
ordered_float::NotNan::new(t_min).unwrap()
} else {
ordered_float::NotNan::new(t_max).unwrap()
})
}

#[must_use]
Expand Down
131 changes: 130 additions & 1 deletion crates/geometry/src/ray.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use glam::Vec3;
use glam::{IVec3, Vec3};

#[derive(Debug, Clone, Copy)]
pub struct Ray {
Expand Down Expand Up @@ -41,4 +41,133 @@ impl Ray {
pub fn at(&self, t: f32) -> Vec3 {
self.origin + self.direction * t
}

/// Efficiently traverse through grid cells that the ray intersects.
/// Returns an iterator over the grid cells ([`IVec3`]) that the ray passes through.
pub fn voxel_traversal(&self, bounds_min: IVec3, bounds_max: IVec3) -> VoxelTraversal {
// Convert ray origin to grid coordinates
let current_pos = self.origin.floor().as_ivec3();

// Calculate step direction for each axis
let step = IVec3::new(
if self.direction.x > 0.0 {
1
} else if self.direction.x < 0.0 {
-1
} else {
0
},
if self.direction.y > 0.0 {
1
} else if self.direction.y < 0.0 {
-1
} else {
0
},
if self.direction.z > 0.0 {
1
} else if self.direction.z < 0.0 {
-1
} else {
0
},
);

// Calculate initial t_max values (distance to next voxel boundary for each axis)
let next_voxel = current_pos + step;
let t_max = Vec3::new(
if self.direction.x == 0.0 {
f32::INFINITY
} else {
((next_voxel.x as f32) - self.origin.x) * self.inv_direction.x
},
if self.direction.y == 0.0 {
f32::INFINITY
} else {
((next_voxel.y as f32) - self.origin.y) * self.inv_direction.y
},
if self.direction.z == 0.0 {
f32::INFINITY
} else {
((next_voxel.z as f32) - self.origin.z) * self.inv_direction.z
},
);

// Calculate t_delta values (distance between voxel boundaries)
let t_delta = Vec3::new(
if self.direction.x == 0.0 {
f32::INFINITY
} else {
step.x as f32 * self.inv_direction.x
},
if self.direction.y == 0.0 {
f32::INFINITY
} else {
step.y as f32 * self.inv_direction.y
},
if self.direction.z == 0.0 {
f32::INFINITY
} else {
step.z as f32 * self.inv_direction.z
},
);

VoxelTraversal {
current_pos,
step,
t_max,
t_delta,
bounds_min,
bounds_max,
}
}
}

#[derive(Debug)]
#[must_use]
pub struct VoxelTraversal {
current_pos: IVec3,
step: IVec3,
t_max: Vec3,
t_delta: Vec3,
bounds_min: IVec3,
bounds_max: IVec3,
}

impl Iterator for VoxelTraversal {
type Item = IVec3;

fn next(&mut self) -> Option<Self::Item> {
// Check if current position is within bounds
if self.current_pos.x < self.bounds_min.x
|| self.current_pos.x > self.bounds_max.x
|| self.current_pos.y < self.bounds_min.y
|| self.current_pos.y > self.bounds_max.y
|| self.current_pos.z < self.bounds_min.z
|| self.current_pos.z > self.bounds_max.z
{
return None;
}

let current = self.current_pos;

// Determine which axis to step along (the one with minimum t_max)
if self.t_max.x < self.t_max.y {
if self.t_max.x < self.t_max.z {
self.current_pos.x += self.step.x;
self.t_max.x += self.t_delta.x;
} else {
self.current_pos.z += self.step.z;
self.t_max.z += self.t_delta.z;
}
} else if self.t_max.y < self.t_max.z {
self.current_pos.y += self.step.y;
self.t_max.y += self.t_delta.y;
} else {
self.current_pos.z += self.step.z;
self.t_max.z += self.t_delta.z;
}

Some(current)
}
}
2 changes: 1 addition & 1 deletion crates/hyperion-clap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ publish = false
[dependencies]
clap ={ workspace = true }
flecs_ecs = { workspace = true }
hyperion = { workspace = true }
hyperion-command = { workspace = true }
tracing = { workspace = true }
hyperion = { workspace = true }
valence_protocol = { workspace = true }

[lints]
Expand Down
70 changes: 69 additions & 1 deletion crates/hyperion/src/simulation/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use flecs_ecs::{
core::{Entity, World, WorldGet},
macros::Component,
};
use glam::{IVec2, IVec3};
use geometry::ray::Ray;
use glam::{IVec2, IVec3, Vec3};
use indexmap::IndexMap;
use loader::{ChunkLoaderHandle, launch_manager};
use rayon::iter::ParallelIterator;
Expand Down Expand Up @@ -51,6 +52,13 @@ pub enum TrySetBlockDeltaError {
ChunkNotLoaded,
}

#[derive(Debug)]
pub struct RayCollision {
pub distance: f32,
pub location: IVec3,
pub block: BlockState,
}

/// Accessor of blocks.
#[derive(Component)]
pub struct Blocks {
Expand Down Expand Up @@ -87,6 +95,66 @@ impl Blocks {
})
}

#[must_use]
pub fn first_collision(&self, ray: Ray, distance_limit: f32) -> Option<RayCollision> {
let a = ray.origin();
let b = ray.origin() + ray.direction() * distance_limit;

let min = a.min(b);
let max = a.max(b);

let min = min.floor().as_ivec3();
let max = max.ceil().as_ivec3();

let traversal = ray.voxel_traversal(min, max);
println!("traversal = {traversal:?}");

let mut min: Option<RayCollision> = None;

for cell in traversal {
println!("cell = {cell:?}");

// if there is no block at this cell, return None
let block = self.get_block(cell)?;

let origin = Vec3::new(cell.x as f32, cell.y as f32, cell.z as f32);

let min_dist = block
.collision_shapes()
.map(|shape| {
geometry::aabb::Aabb::new(shape.min().as_vec3(), shape.max().as_vec3())
})
.map(|shape| shape + origin)
.filter_map(|shape| shape.intersect_ray(&ray))
.min();

let Some(min_dist) = min_dist else {
continue;
};

match &min {
Some(current_min) => {
if min_dist.into_inner() < current_min.distance {
min = Some(RayCollision {
distance: min_dist.into_inner(),
location: cell,
block,
});
}
}
None => {
min = Some(RayCollision {
distance: min_dist.into_inner(),
location: cell,
block,
});
}
}
}

min
}

#[must_use]
pub fn par_scan_for(&self, block: BlockState) -> impl ParallelIterator<Item = IVec3> + '_ {
use rayon::prelude::*;
Expand Down
3 changes: 3 additions & 0 deletions crates/hyperion/src/simulation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ pub struct Name(Arc<str>);
#[derive(Component, Deref, DerefMut, From, Debug, Default)]
pub struct IgnMap(DeferredMap<Arc<str>, Entity>);

#[derive(Component, Debug, Default)]
pub struct RaycastTravel;

/// A component that represents a Player. In the future, this should be broken up into multiple components.
///
/// Why should it be broken up? The more things are broken up, the more we can take advantage of Rust borrowing rules.
Expand Down
1 change: 1 addition & 0 deletions events/tag/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tracing = { workspace = true }
rayon = { workspace = true }
gxhash = { workspace = true }
derive_more = { workspace = true }
geometry = { workspace = true }

[dev-dependencies]
tracing = {workspace = true, features = ["release_max_level_info"]}
Expand Down
6 changes: 4 additions & 2 deletions events/tag/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use flecs_ecs::core::World;
use hyperion_clap::{MinecraftCommand, hyperion_command::CommandRegistry};

use crate::command::{
fly::FlyCommand, rank::ClassCommand, replace::ReplaceCommand, speed::SpeedCommand,
xp::XpCommand,
dirt::DirtCommand, fly::FlyCommand, rank::ClassCommand, replace::ReplaceCommand,
speed::SpeedCommand, xp::XpCommand,
};

mod dirt;
mod fly;
mod rank;
mod replace;
Expand All @@ -18,4 +19,5 @@ pub fn register(registry: &mut CommandRegistry, world: &World) {
ClassCommand::register(registry, world);
XpCommand::register(registry, world);
ReplaceCommand::register(registry, world);
DirtCommand::register(registry, world);
}
Loading

0 comments on commit 8e363c9

Please sign in to comment.