Skip to content

Commit

Permalink
fix: raycast traversal (#647)
Browse files Browse the repository at this point in the history
`Ray::voxel_traversal` and `get_direction_from_rotation` were
implemented wrong.
  • Loading branch information
Tebarem authored Dec 5, 2024
1 parent 169375c commit 7311885
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 26 deletions.
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[build]
rustflags = ["--cfg", "tokio_unstable", "-Ctarget-cpu=native"]

[env]
RUST_LOG="debug"
47 changes: 35 additions & 12 deletions crates/geometry/src/ray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ impl Ray {
self.origin + self.direction * t
}

/// Efficiently traverse through grid cells that the ray intersects.
/// Efficiently traverse through grid cells that the ray intersects using an optimized DDA algorithm.
/// 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();
// Convert ray origin to grid coordinates and handle negative coordinates correctly
#[allow(clippy::cast_possible_truncation)]
let current_pos = if self.origin.x < 0.0 || self.origin.y < 0.0 || self.origin.z < 0.0 {
IVec3::new(
self.origin.x.floor() as i32,
self.origin.y.floor() as i32,
self.origin.z.floor() as i32,
)
} else {
self.origin.as_ivec3()
};

// Calculate step direction for each axis
let step = IVec3::new(
Expand All @@ -73,42 +82,56 @@ impl Ray {
},
);

// Calculate initial t_max values (distance to next voxel boundary for each axis)
let next_voxel = current_pos + step;
// Calculate t_max - distance to next voxel boundary for each axis
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
let next_x = if self.direction.x > 0.0 {
current_pos.x as f32 + 1.0 - self.origin.x
} else {
self.origin.x - current_pos.x as f32
};
next_x * self.inv_direction.x.abs()
},
if self.direction.y == 0.0 {
f32::INFINITY
} else {
((next_voxel.y as f32) - self.origin.y) * self.inv_direction.y
let next_y = if self.direction.y > 0.0 {
current_pos.y as f32 + 1.0 - self.origin.y
} else {
self.origin.y - current_pos.y as f32
};
next_y * self.inv_direction.y.abs()
},
if self.direction.z == 0.0 {
f32::INFINITY
} else {
((next_voxel.z as f32) - self.origin.z) * self.inv_direction.z
let next_z = if self.direction.z > 0.0 {
current_pos.z as f32 + 1.0 - self.origin.z
} else {
self.origin.z - current_pos.z as f32
};
next_z * self.inv_direction.z.abs()
},
);

// Calculate t_delta values (distance between voxel boundaries)
// Calculate t_delta - 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
self.inv_direction.x.abs()
},
if self.direction.y == 0.0 {
f32::INFINITY
} else {
step.y as f32 * self.inv_direction.y
self.inv_direction.y.abs()
},
if self.direction.z == 0.0 {
f32::INFINITY
} else {
step.z as f32 * self.inv_direction.z
self.inv_direction.z.abs()
},
);

Expand Down
16 changes: 9 additions & 7 deletions crates/hyperion/src/simulation/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,18 @@ 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;
// Calculate exact start position (the block we're in)
let start_pos = ray.origin();

let min = a.min(b);
let max = a.max(b);
// Calculate end position with a small offset to handle edge cases
let end_pos = ray.origin() + ray.direction() * (distance_limit + 0.0001);

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

let traversal = ray.voxel_traversal(min, max);
// Set up voxel traversal through the blocks
let traversal = ray.voxel_traversal(min_block, max_block);

let mut min: Option<RayCollision> = None;

Expand Down
12 changes: 5 additions & 7 deletions events/tag/src/command/raycast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,14 @@ pub struct RaycastCommand;
/// A normalized Vec3 representing the look direction
pub fn get_direction_from_rotation(yaw: f32, pitch: f32) -> Vec3 {
// Convert angles from degrees to radians
let yaw_rad = (yaw + 90.0).to_radians(); // Add 90° to match Minecraft's coordinate system
let pitch_rad = (-pitch).to_radians(); // Negate pitch because Minecraft's pitch is inverted
let yaw_rad = yaw.to_radians();
let pitch_rad = pitch.to_radians();

// Calculate direction vector components
Vec3::new(
pitch_rad.cos() * yaw_rad.cos(), // X component
pitch_rad.sin(), // Y component
pitch_rad.cos() * yaw_rad.sin(), // Z component
-pitch_rad.cos() * yaw_rad.sin(), // x = -cos(pitch) * sin(yaw)
-pitch_rad.sin(), // y = -sin(pitch)
pitch_rad.cos() * yaw_rad.cos(), // z = cos(pitch) * cos(yaw)
)
.normalize()
}

impl MinecraftCommand for RaycastCommand {
Expand Down

0 comments on commit 7311885

Please sign in to comment.