Skip to content

Commit 7311885

Browse files
authored
fix: raycast traversal (#647)
`Ray::voxel_traversal` and `get_direction_from_rotation` were implemented wrong.
1 parent 169375c commit 7311885

File tree

4 files changed

+52
-26
lines changed

4 files changed

+52
-26
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
[build]
22
rustflags = ["--cfg", "tokio_unstable", "-Ctarget-cpu=native"]
3+
4+
[env]
5+
RUST_LOG="debug"

crates/geometry/src/ray.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,20 @@ impl Ray {
4242
self.origin + self.direction * t
4343
}
4444

45-
/// Efficiently traverse through grid cells that the ray intersects.
45+
/// Efficiently traverse through grid cells that the ray intersects using an optimized DDA algorithm.
4646
/// Returns an iterator over the grid cells ([`IVec3`]) that the ray passes through.
4747
pub fn voxel_traversal(&self, bounds_min: IVec3, bounds_max: IVec3) -> VoxelTraversal {
48-
// Convert ray origin to grid coordinates
49-
let current_pos = self.origin.floor().as_ivec3();
48+
// Convert ray origin to grid coordinates and handle negative coordinates correctly
49+
#[allow(clippy::cast_possible_truncation)]
50+
let current_pos = if self.origin.x < 0.0 || self.origin.y < 0.0 || self.origin.z < 0.0 {
51+
IVec3::new(
52+
self.origin.x.floor() as i32,
53+
self.origin.y.floor() as i32,
54+
self.origin.z.floor() as i32,
55+
)
56+
} else {
57+
self.origin.as_ivec3()
58+
};
5059

5160
// Calculate step direction for each axis
5261
let step = IVec3::new(
@@ -73,42 +82,56 @@ impl Ray {
7382
},
7483
);
7584

76-
// Calculate initial t_max values (distance to next voxel boundary for each axis)
77-
let next_voxel = current_pos + step;
85+
// Calculate t_max - distance to next voxel boundary for each axis
7886
let t_max = Vec3::new(
7987
if self.direction.x == 0.0 {
8088
f32::INFINITY
8189
} else {
82-
((next_voxel.x as f32) - self.origin.x) * self.inv_direction.x
90+
let next_x = if self.direction.x > 0.0 {
91+
current_pos.x as f32 + 1.0 - self.origin.x
92+
} else {
93+
self.origin.x - current_pos.x as f32
94+
};
95+
next_x * self.inv_direction.x.abs()
8396
},
8497
if self.direction.y == 0.0 {
8598
f32::INFINITY
8699
} else {
87-
((next_voxel.y as f32) - self.origin.y) * self.inv_direction.y
100+
let next_y = if self.direction.y > 0.0 {
101+
current_pos.y as f32 + 1.0 - self.origin.y
102+
} else {
103+
self.origin.y - current_pos.y as f32
104+
};
105+
next_y * self.inv_direction.y.abs()
88106
},
89107
if self.direction.z == 0.0 {
90108
f32::INFINITY
91109
} else {
92-
((next_voxel.z as f32) - self.origin.z) * self.inv_direction.z
110+
let next_z = if self.direction.z > 0.0 {
111+
current_pos.z as f32 + 1.0 - self.origin.z
112+
} else {
113+
self.origin.z - current_pos.z as f32
114+
};
115+
next_z * self.inv_direction.z.abs()
93116
},
94117
);
95118

96-
// Calculate t_delta values (distance between voxel boundaries)
119+
// Calculate t_delta - distance between voxel boundaries
97120
let t_delta = Vec3::new(
98121
if self.direction.x == 0.0 {
99122
f32::INFINITY
100123
} else {
101-
step.x as f32 * self.inv_direction.x
124+
self.inv_direction.x.abs()
102125
},
103126
if self.direction.y == 0.0 {
104127
f32::INFINITY
105128
} else {
106-
step.y as f32 * self.inv_direction.y
129+
self.inv_direction.y.abs()
107130
},
108131
if self.direction.z == 0.0 {
109132
f32::INFINITY
110133
} else {
111-
step.z as f32 * self.inv_direction.z
134+
self.inv_direction.z.abs()
112135
},
113136
);
114137

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,18 @@ impl Blocks {
9999

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

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

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

111-
let traversal = ray.voxel_traversal(min, max);
112+
// Set up voxel traversal through the blocks
113+
let traversal = ray.voxel_traversal(min_block, max_block);
112114

113115
let mut min: Option<RayCollision> = None;
114116

events/tag/src/command/raycast.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,14 @@ pub struct RaycastCommand;
3030
/// A normalized Vec3 representing the look direction
3131
pub fn get_direction_from_rotation(yaw: f32, pitch: f32) -> Vec3 {
3232
// Convert angles from degrees to radians
33-
let yaw_rad = (yaw + 90.0).to_radians(); // Add 90° to match Minecraft's coordinate system
34-
let pitch_rad = (-pitch).to_radians(); // Negate pitch because Minecraft's pitch is inverted
33+
let yaw_rad = yaw.to_radians();
34+
let pitch_rad = pitch.to_radians();
3535

36-
// Calculate direction vector components
3736
Vec3::new(
38-
pitch_rad.cos() * yaw_rad.cos(), // X component
39-
pitch_rad.sin(), // Y component
40-
pitch_rad.cos() * yaw_rad.sin(), // Z component
37+
-pitch_rad.cos() * yaw_rad.sin(), // x = -cos(pitch) * sin(yaw)
38+
-pitch_rad.sin(), // y = -sin(pitch)
39+
pitch_rad.cos() * yaw_rad.cos(), // z = cos(pitch) * cos(yaw)
4140
)
42-
.normalize()
4341
}
4442

4543
impl MinecraftCommand for RaycastCommand {

0 commit comments

Comments
 (0)