summary refs log tree commit diff
path: root/src/World/VoxelTraversal.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/World/VoxelTraversal.hpp')
-rw-r--r--src/World/VoxelTraversal.hpp37
1 files changed, 33 insertions, 4 deletions
diff --git a/src/World/VoxelTraversal.hpp b/src/World/VoxelTraversal.hpp
index 1638901..08a53eb 100644
--- a/src/World/VoxelTraversal.hpp
+++ b/src/World/VoxelTraversal.hpp
@@ -6,11 +6,22 @@
 
 namespace MC::World::VoxelTraversal {
 
+struct TraversalResult {
+    // Whether the ray hit a block.
+    Bool hit{};
+    // The block that was hit.
+    Position::BlockWorld block;
+    // The vector from the ray origin to where the block that was hit.
+    Position::WorldOffset approach;
+    // The normal of the collision.
+    Position::BlockWorldOffset normal;
+};
+
 // Amanatides, John, and Andrew Woo. 1987.
 // "A Fast Voxel Traversal Algorithm for Ray Tracing."
 // https://doi.org/10.2312/EGTP.19871000.
 template <typename P>
-Position::BlockWorld traverse(Ray ray, Real max_distance, P&& predicate) {
+TraversalResult traverse(Ray ray, P&& predicate, Real max_distance = 0) {
     // Find the voxel grid cell containing the origin of the ray.
     Position::BlockWorld block_pos = Position::World(ray.origin).round_to_block();
     Position::BlockWorldOffset const step = {
@@ -31,30 +42,48 @@ Position::BlockWorld traverse(Ray ray, Real max_distance, P&& predicate) {
         TO(Real, step.z()) / ray.direction.z()
     };
 
+    // The original algorithm does not mention calculating the normal of the
+    // block that was hit. When we increment t_max to a collision on one of the axes
+    // of the voxel grid, the axis gives us the normal of the current block.
+    Position::BlockWorldOffset normal = {};
+
+    // Also not mentioned in the original algorithm, but we need to keep track of
+    // how far along we have traversed the voxel grid.
+    // This just means we add the direction of the ray at the axis we have progressed along.
+    Position::WorldOffset approach = {};
+
     while (!predicate(block_pos)) {
-        // TODO: Calculate distance exactly.
-        if (ray.origin.distance(Vec3(block_pos)) > max_distance) return {};
+        if (max_distance > 0 && approach.magnitude() > max_distance)
+            return {};
 
         if (t_max.x() < t_max.y()) {
             if (t_max.x() < t_max.z()) {
                 block_pos.x() += step.x();
                 t_max.x() += t_delta.x();
+                normal = {-step.x(), 0, 0};
+                approach.x() += ray.direction.x();
             } else {
                 block_pos.z() += step.z();
                 t_max.z() += t_delta.z();
+                normal = {0, 0, -step.z()};
+                approach.z() += ray.direction.z();
             }
         } else {
             if (t_max.y() < t_max.z()) {
                 block_pos.y() += step.y();
                 t_max.y() += t_delta.y();
+                normal = {0, -step.y(), 0};
+                approach.y() += ray.direction.y();
             } else {
                 block_pos.z() += step.z();
                 t_max.z() += t_delta.z();
+                normal = {0, 0, -step.z()};
+                approach.z() += ray.direction.z();
             }
         }
     }
 
-    return block_pos;
+    return {true, block_pos, approach, normal};
 }
 
 }