From 73118852fa13e136d9787d9d408e88edf3c341cd Mon Sep 17 00:00:00 2001 From: Tebarem <104076087+Tebarem@users.noreply.github.com> Date: Thu, 5 Dec 2024 05:13:34 +0100 Subject: [PATCH] fix: raycast traversal (#647) `Ray::voxel_traversal` and `get_direction_from_rotation` were implemented wrong. --- .cargo/config.toml | 3 ++ crates/geometry/src/ray.rs | 47 +++++++++++++++----- crates/hyperion/src/simulation/blocks/mod.rs | 16 ++++--- events/tag/src/command/raycast.rs | 12 +++-- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index dc966825..555e3728 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ [build] rustflags = ["--cfg", "tokio_unstable", "-Ctarget-cpu=native"] + +[env] +RUST_LOG="debug" \ No newline at end of file diff --git a/crates/geometry/src/ray.rs b/crates/geometry/src/ray.rs index c186c1a9..1dc9b326 100644 --- a/crates/geometry/src/ray.rs +++ b/crates/geometry/src/ray.rs @@ -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( @@ -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() }, ); diff --git a/crates/hyperion/src/simulation/blocks/mod.rs b/crates/hyperion/src/simulation/blocks/mod.rs index e2e93fc7..49e3ade3 100644 --- a/crates/hyperion/src/simulation/blocks/mod.rs +++ b/crates/hyperion/src/simulation/blocks/mod.rs @@ -99,16 +99,18 @@ impl Blocks { #[must_use] pub fn first_collision(&self, ray: Ray, distance_limit: f32) -> Option { - 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 = None; diff --git a/events/tag/src/command/raycast.rs b/events/tag/src/command/raycast.rs index 769397ed..c425b9c9 100644 --- a/events/tag/src/command/raycast.rs +++ b/events/tag/src/command/raycast.rs @@ -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 {