#pragma once #include "../Common/Casts.hpp" #include "../Math/Ray.hpp" #include "Position.hpp" 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 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 = { Math::sign(ray.direction.x()), Math::sign(ray.direction.y()), Math::sign(ray.direction.z()), }; Position::WorldOffset t_max = { (TO(Real, block_pos.x()) + TO(Real, step.x() > 0) - ray.origin.x()) / ray.direction.x(), (TO(Real, block_pos.y()) + TO(Real, step.y() > 0) - ray.origin.y()) / ray.direction.y(), (TO(Real, block_pos.z()) + TO(Real, step.z() > 0) - ray.origin.z()) / ray.direction.z() }; Position::WorldOffset const t_delta = { TO(Real, step.x()) / ray.direction.x(), TO(Real, step.y()) / ray.direction.y(), 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)) { 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 {true, block_pos, approach, normal}; } }