├── .gitignore ├── CMakeLists.txt ├── Grid3D.h ├── LICENSE ├── README.md ├── Ray.h ├── Vec3.h ├── amanatidesWooAlgorithm.cpp ├── amanatidesWooAlgorithm.h └── overview ├── .DS_Store ├── FastVoxelTraversalOverview.md └── images ├── 2d_grid.png ├── ray_dir.png ├── ray_dir2.png ├── ray_intersection.png ├── tDelta.png └── tMax.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /cmake-build-debug/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(fast_voxel_traversal_algorithm) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | add_executable(fast_voxel_traversal_algorithm amanatidesWooAlgorithm.cpp amanatidesWooAlgorithm.h Ray.h Vec3.h Grid3D.h) -------------------------------------------------------------------------------- /Grid3D.h: -------------------------------------------------------------------------------- 1 | #ifndef FAST_VOXEL_TRAVERSAL_ALGORITHM_GRID3D_H 2 | #define FAST_VOXEL_TRAVERSAL_ALGORITHM_GRID3D_H 3 | 4 | #include 5 | #include "Vec3.h" 6 | 7 | // Represents a 3 dimensional grid sub-divided with voxels. Provides necessary information to perform 8 | // traversal over the grid system. 9 | // 10 | // Requires: 11 | // min_bound < max_bound 12 | // num_x_voxels > 0 13 | // num_y_voxels > 0 14 | // num_z_voxels > 0 15 | struct Grid3D { 16 | public: 17 | [[nodiscard]] constexpr Grid3D(const BoundVec3& min_bound, const BoundVec3& max_bound, size_t num_x_voxels, 18 | size_t num_y_voxels, size_t num_z_voxels) : 19 | min_bound_{min_bound}, 20 | max_bound_{max_bound}, 21 | grid_size_{max_bound - min_bound}, 22 | num_x_voxels_{num_x_voxels}, 23 | num_y_voxels_{num_y_voxels}, 24 | num_z_voxels_{num_z_voxels}, 25 | voxel_size_x_{grid_size_.x() / num_x_voxels}, 26 | voxel_size_y_{grid_size_.y() / num_y_voxels}, 27 | voxel_size_z_{grid_size_.z() / num_z_voxels} {} 28 | 29 | [[nodiscard]] constexpr inline size_t numberOfXVoxels() const { return num_x_voxels_; } 30 | [[nodiscard]] constexpr inline size_t numberOfYVoxels() const { return num_y_voxels_; } 31 | [[nodiscard]] constexpr inline size_t numberOfZVoxels() const { return num_z_voxels_; } 32 | [[nodiscard]] constexpr inline BoundVec3 minBound() const { return min_bound_; } 33 | [[nodiscard]] constexpr inline BoundVec3 maxBound() const { return max_bound_; } 34 | [[nodiscard]] constexpr inline FreeVec3 gridSize() const { return grid_size_; } 35 | [[nodiscard]] constexpr inline value_type voxelSizeX() const { return voxel_size_x_; } 36 | [[nodiscard]] constexpr inline value_type voxelSizeY() const { return voxel_size_y_; } 37 | [[nodiscard]] constexpr inline value_type voxelSizeZ() const { return voxel_size_z_; } 38 | 39 | private: 40 | // The minimum bound vector of the voxel grid. 41 | const BoundVec3 min_bound_; 42 | // The maximum bound vector of the voxel grid. 43 | const BoundVec3 max_bound_; 44 | // The grid size, determined by (max_bound_ - min_bound_). 45 | const FreeVec3 grid_size_; 46 | // The number of voxels in each of the x, y, z directions. 47 | const size_t num_x_voxels_, num_y_voxels_, num_z_voxels_; 48 | // The size of the voxel's x dimension. 49 | const value_type voxel_size_x_; 50 | // The size of the voxel's y dimension. 51 | const value_type voxel_size_y_; 52 | // The size of the voxel's z dimension. 53 | const value_type voxel_size_z_; 54 | }; 55 | 56 | #endif //FAST_VOXEL_TRAVERSAL_ALGORITHM_GRID3D_H 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chris Gyurgyik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### About 2 | A prototype for the implementation of Amanatides & Woo's "A Fast Voxel Traversal Algorithm" in C++. Note, this has not been tested, and is not guaranteed to be bug-free. An overview of the algorithm can be found [here](https://github.com/cgyurgyik/fast-voxel-traversal-algorithm/blob/master/overview/FastVoxelTraversalOverview.md). 3 | 4 | ### Notes 5 | - Instead of using ```double``` or ```float```, I've decided to use ```value_type``` which can be set to a user-specified type in ```Vec3.h```. 6 | - An optional enhancement that can be done is calculating the ray's inverse direction upon construction of the ray. This will ensure inverse direction is calculated only once per ray. 7 | 8 | ### References 9 | - ["Amanatides & Woo "A Fast Voxel Traversal Algorithm"](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.3443&rep=rep1&type=pdf) 10 | - [Williams, Barrus, et. al "An Efficient and Robust Ray–Box Intersection Algorithm"](http://www.cs.utah.edu/~awilliam/box/box.pdf) 11 | -------------------------------------------------------------------------------- /Ray.h: -------------------------------------------------------------------------------- 1 | #ifndef FAST_VOXEL_TRAVERSAL_ALGORITHM_RAY_H 2 | #define FAST_VOXEL_TRAVERSAL_ALGORITHM_RAY_H 3 | 4 | #include "Vec3.h" 5 | 6 | // Encapsulates the functionality of a ray. 7 | // This consists of two components, the origin of the ray, 8 | // and the direction of the ray. 9 | struct Ray final { 10 | [[nodiscard]] constexpr Ray(const BoundVec3& origin, const UnitVec3& direction) 11 | : origin_{origin}, direction_{direction} {} 12 | 13 | // Represents the function p(t) = origin + t * direction, 14 | // where p is a 3-dimensional position, and t is a scalar. 15 | [[nodiscard]] inline constexpr BoundVec3 point_at_parameter(const value_type t) const { 16 | return this->origin_ + (this->direction_ * t); 17 | } 18 | 19 | [[nodiscard]] inline constexpr BoundVec3 origin() const { return this->origin_; } 20 | [[nodiscard]] inline constexpr UnitVec3 direction() const { return this->direction_; } 21 | private: 22 | // The origin of the ray. 23 | const BoundVec3 origin_; 24 | // The normalized direction of the ray. 25 | const UnitVec3 direction_; 26 | }; 27 | 28 | #endif //FAST_VOXEL_TRAVERSAL_ALGORITHM_RAY_H 29 | -------------------------------------------------------------------------------- /Vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef FAST_VOXEL_TRAVERSAL_ALGORITHM_VEC3_H 2 | #define FAST_VOXEL_TRAVERSAL_ALGORITHM_VEC3_H 3 | 4 | #include 5 | 6 | // The type used for the 3-dimensional vectors. 7 | // In most cases, this will be either double or float. 8 | using value_type = double; 9 | 10 | // Represents a Euclidean vector in 3-dimensional space. 11 | // Assumes vectors take the form of: 12 | // [x] 13 | // [y] 14 | // [z] 15 | struct Vec3 { 16 | public: 17 | constexpr explicit Vec3(const value_type x, const value_type y, const value_type z) 18 | : x_{x}, y_{y}, z_{z} {} 19 | 20 | [[nodiscard]] inline constexpr value_type x() const { return this->x_; } 21 | [[nodiscard]] inline constexpr value_type y() const { return this->y_; } 22 | [[nodiscard]] inline constexpr value_type z() const { return this->z_; } 23 | 24 | [[nodiscard]] inline constexpr value_type& x() { return this->x_; } 25 | [[nodiscard]] inline constexpr value_type& y() { return this->y_; } 26 | [[nodiscard]] inline constexpr value_type& z() { return this->z_; } 27 | 28 | [[nodiscard]] inline value_type length() const { 29 | return std::hypot(this->x(), this->y(), this->z()); 30 | } 31 | 32 | [[nodiscard]] inline value_type squared_length() const { 33 | return x() * x() + y() * y() + z() * z(); 34 | } 35 | 36 | private: 37 | // Represents the x-dimension value of the vector. 38 | value_type x_; 39 | // Represents the y-dimension value of the vector. 40 | value_type y_; 41 | // Represents the z-dimension value of the vector. 42 | value_type z_; 43 | }; 44 | 45 | // A 3-dimensional free vector, which has no initial point. It has two main criteria: 46 | // (1) direction, and (2) magnitude. 47 | struct FreeVec3 final : Vec3 { 48 | using Vec3::Vec3; 49 | 50 | [[nodiscard]] constexpr explicit FreeVec3(const Vec3& vec3) : Vec3::Vec3{vec3} {} 51 | 52 | [[nodiscard]] inline constexpr value_type dot(const Vec3& other) const { 53 | return this->x() * other.x() + this->y() * other.y() + this->z() * other.z(); 54 | } 55 | 56 | [[nodiscard]] inline constexpr FreeVec3 cross(const Vec3& other) const { 57 | return FreeVec3{this->y() * other.z() - this->z() * other.y(), 58 | this->z() * other.x() - this->x() * other.z(), 59 | this->x() * other.y() - this->y() * other.x()}; 60 | } 61 | 62 | [[nodiscard]] inline constexpr FreeVec3& operator+=(const FreeVec3& other) { 63 | this->x() += other.x(); 64 | this->y() += other.y(); 65 | this->z() += other.z(); 66 | return *this; 67 | } 68 | 69 | [[nodiscard]] inline constexpr FreeVec3& operator-=(const FreeVec3& other) { 70 | this->x() -= other.x(); 71 | this->y() -= other.y(); 72 | this->z() -= other.z(); 73 | return *this; 74 | } 75 | 76 | [[nodiscard]] inline constexpr FreeVec3& operator*=(const value_type scalar) { 77 | this->x() *= scalar; 78 | this->y() *= scalar; 79 | this->z() *= scalar; 80 | return *this; 81 | } 82 | 83 | [[nodiscard]] inline constexpr FreeVec3& operator/=(const value_type scalar) { 84 | this->x() /= scalar; 85 | this->y() /= scalar; 86 | this->z() /= scalar; 87 | return *this; 88 | } 89 | }; 90 | 91 | [[nodiscard]] inline constexpr FreeVec3 operator+(const FreeVec3& v) { return v; } 92 | 93 | [[nodiscard]] inline constexpr FreeVec3 operator-(const FreeVec3& v) { 94 | return FreeVec3{-v.x(), -v.y(), -v.z()}; 95 | } 96 | 97 | [[nodiscard]] inline constexpr FreeVec3 operator+(FreeVec3 v1, const FreeVec3& v2) { 98 | return v1 += v2; 99 | } 100 | 101 | [[nodiscard]] inline constexpr FreeVec3 operator-(FreeVec3 v1, const FreeVec3& v2) { 102 | return v1 -= v2; 103 | } 104 | 105 | [[nodiscard]] inline constexpr FreeVec3 operator*(FreeVec3 v, const value_type scalar) { 106 | return v *= scalar; 107 | } 108 | 109 | [[nodiscard]] inline constexpr FreeVec3 operator/(FreeVec3 v, const value_type scalar) { 110 | return v /= scalar; 111 | } 112 | 113 | // A 3-dimensional bounded vector has a fixed start and end point. It represents a fixed point 114 | // in space, relative to some frame of reference. 115 | struct BoundVec3 final : Vec3 { 116 | [[nodiscard]]constexpr explicit BoundVec3(const Vec3& vec3) : Vec3::Vec3{vec3} {} 117 | 118 | [[nodiscard]] inline constexpr value_type dot(const Vec3& other) const { 119 | return this->x() * other.x() + this->y() * other.y() + this->z() * other.z(); 120 | } 121 | 122 | [[nodiscard]] inline constexpr BoundVec3& operator+=(const FreeVec3& other) { 123 | this->x() += other.x(); 124 | this->y() += other.y(); 125 | this->z() += other.z(); 126 | return *this; 127 | } 128 | 129 | [[nodiscard]] inline constexpr BoundVec3& operator-=(const FreeVec3& other) { 130 | return *this += (-other); 131 | } 132 | }; 133 | 134 | [[nodiscard]] inline constexpr FreeVec3 operator-(const BoundVec3& v1, const BoundVec3& v2) { 135 | return FreeVec3{v1.x() - v2.x(), v1.y() - v2.y(), v1.z() - v2.z()}; 136 | } 137 | 138 | [[nodiscard]] inline constexpr BoundVec3 operator+(BoundVec3 v1, const FreeVec3& v2) { 139 | return v1 += v2; 140 | } 141 | 142 | [[nodiscard]] inline constexpr BoundVec3 operator-(BoundVec3 v1, const FreeVec3& v2) { 143 | return v1 -= v2; 144 | } 145 | 146 | // Represents a 3-dimensional unit vector, an abstraction over free vectors that guarantees 147 | // a length of 1. To prevent its length from changing, UnitVec3 does not allow 148 | // for mutations. 149 | struct UnitVec3 final { 150 | UnitVec3(value_type x, value_type y, value_type z) 151 | : UnitVec3{FreeVec3{x, y, z}} {} 152 | [[nodiscard]] constexpr explicit UnitVec3(const Vec3& vec3) : UnitVec3{FreeVec3{vec3}} {} 153 | [[nodiscard]] constexpr explicit UnitVec3(const FreeVec3& free_vec3) : 154 | inner_{free_vec3 / free_vec3.length()} {} 155 | 156 | [[nodiscard]] inline constexpr value_type x() const { return this->to_free().x(); } 157 | [[nodiscard]] inline constexpr value_type y() const { return this->to_free().y(); } 158 | [[nodiscard]] inline constexpr value_type z() const { return this->to_free().z(); } 159 | [[nodiscard]] inline constexpr const FreeVec3& to_free() const { return inner_; } 160 | private: 161 | const FreeVec3 inner_; 162 | }; 163 | 164 | [[nodiscard]] inline constexpr FreeVec3 operator*(const UnitVec3& v, const value_type scalar) { 165 | return v.to_free() * scalar; 166 | } 167 | 168 | [[nodiscard]] inline constexpr FreeVec3 operator/(const UnitVec3& v, const value_type scalar) { 169 | return v.to_free() / scalar; 170 | } 171 | 172 | #endif //FAST_VOXEL_TRAVERSAL_ALGORITHM_VEC3_H 173 | -------------------------------------------------------------------------------- /amanatidesWooAlgorithm.cpp: -------------------------------------------------------------------------------- 1 | #include "amanatidesWooAlgorithm.h" 2 | #include 3 | 4 | // Macro defined to avoid unnecessary checks with NaNs when using std::max 5 | #define MAX(a,b) ((a > b ? a : b)) 6 | 7 | // Uses the improved version of Smit's algorithm to determine if the given ray will intersect 8 | // the grid between tMin and tMax. This version causes an additional efficiency penalty, 9 | // but takes into account the negative zero case. 10 | // tMin and tMax are then updated to incorporate the new intersection values. 11 | // Returns true if the ray intersects the grid, and false otherwise. 12 | // See: http://www.cs.utah.edu/~awilliam/box/box.pdf 13 | [[no discard]] bool rayBoxIntersection(const Ray& ray, const Grid3D& grid, value_type& tMin, value_type& tMax, 14 | value_type t0, value_type t1) noexcept { 15 | value_type tYMin, tYMax, tZMin, tZMax; 16 | const value_type x_inv_dir = 1 / ray.direction().x(); 17 | if (x_inv_dir >= 0) { 18 | tMin = (grid.minBound().x() - ray.origin().x()) * x_inv_dir; 19 | tMax = (grid.maxBound().x() - ray.origin().x()) * x_inv_dir; 20 | } else { 21 | tMin = (grid.maxBound().x() - ray.origin().x()) * x_inv_dir; 22 | tMax = (grid.minBound().x() - ray.origin().x()) * x_inv_dir; 23 | } 24 | 25 | const value_type y_inv_dir = 1 / ray.direction().y(); 26 | if (y_inv_dir >= 0) { 27 | tYMin = (grid.minBound().y() - ray.origin().y()) * y_inv_dir; 28 | tYMax = (grid.maxBound().y() - ray.origin().y()) * y_inv_dir; 29 | } else { 30 | tYMin = (grid.maxBound().y() - ray.origin().y()) * y_inv_dir; 31 | tYMax = (grid.minBound().y() - ray.origin().y()) * y_inv_dir; 32 | } 33 | 34 | if (tMin > tYMax || tYMin > tMax) return false; 35 | if (tYMin > tMin) tMin = tYMin; 36 | if (tYMax < tMax) tMax = tYMax; 37 | 38 | const value_type z_inv_dir = 1 / ray.direction().z(); 39 | if (z_inv_dir >= 0) { 40 | tZMin = (grid.minBound().z() - ray.origin().z()) * z_inv_dir; 41 | tZMax = (grid.maxBound().z() - ray.origin().z()) * z_inv_dir; 42 | } else { 43 | tZMin = (grid.maxBound().z() - ray.origin().z()) * z_inv_dir; 44 | tZMax = (grid.minBound().z() - ray.origin().z()) * z_inv_dir; 45 | } 46 | 47 | if (tMin > tZMax || tZMin > tMax) return false; 48 | if (tZMin > tMin) tMin = tZMin; 49 | if (tZMax < tMax) tMax = tZMax; 50 | return (tMin < t1 && tMax > t0); 51 | } 52 | 53 | void amanatidesWooAlgorithm(const Ray& ray, const Grid3D& grid, value_type t0, value_type t1) noexcept { 54 | value_type tMin; 55 | value_type tMax; 56 | const bool ray_intersects_grid = rayBoxIntersection(ray, grid, tMin, tMax, t0, t1); 57 | if (!ray_intersects_grid) return; 58 | 59 | tMin = MAX(tMin, t0); 60 | tMax = MAX(tMax, t1); 61 | const BoundVec3 ray_start = ray.origin() + ray.direction() * tMin; 62 | const BoundVec3 ray_end = ray.origin() + ray.direction() * tMax; 63 | 64 | size_t current_X_index = MAX(1, std::ceil(ray_start.x() - grid.minBound().x() / grid.voxelSizeX())); 65 | const size_t end_X_index = MAX(1, std::ceil(ray_end.x() - grid.minBound().x() / grid.voxelSizeX())); 66 | int stepX; 67 | value_type tDeltaX; 68 | value_type tMaxX; 69 | if (ray.direction().x() > 0.0) { 70 | stepX = 1; 71 | tDeltaX = grid.voxelSizeX() / ray.direction().x(); 72 | tMaxX = tMin + (grid.minBound().x() + current_X_index * grid.voxelSizeX() 73 | - ray_start.x()) / ray.direction().x(); 74 | } else if (ray.direction().x() < 0.0) { 75 | stepX = -1; 76 | tDeltaX = grid.voxelSizeX() / -ray.direction().x(); 77 | const size_t previous_X_index = current_X_index - 1; 78 | tMaxX = tMin + (grid.minBound().x() + previous_X_index * grid.voxelSizeX() 79 | - ray_start.x()) / ray.direction().x(); 80 | } else { 81 | stepX = 0; 82 | tDeltaX = tMax; 83 | tMaxX = tMax; 84 | } 85 | 86 | size_t current_Y_index = MAX(1, std::ceil(ray_start.y() - grid.minBound().y() / grid.voxelSizeY())); 87 | const size_t end_Y_index = MAX(1, std::ceil(ray_end.y() - grid.minBound().y() / grid.voxelSizeY())); 88 | int stepY; 89 | value_type tDeltaY; 90 | value_type tMaxY; 91 | if (ray.direction().y() > 0.0) { 92 | stepY = 1; 93 | tDeltaY = grid.voxelSizeY() / ray.direction().y(); 94 | tMaxY = tMin + (grid.minBound().y() + current_Y_index * grid.voxelSizeY() 95 | - ray_start.y()) / ray.direction().y(); 96 | } else if (ray.direction().y() < 0.0) { 97 | stepY= -1; 98 | tDeltaY = grid.voxelSizeY() / -ray.direction().y(); 99 | const size_t previous_Y_index = current_Y_index - 1; 100 | tMaxY = tMin + (grid.minBound().y() + previous_Y_index * grid.voxelSizeY() 101 | - ray_start.y()) / ray.direction().y(); 102 | } else { 103 | stepY = 0; 104 | tDeltaY = tMax; 105 | tMaxY = tMax; 106 | } 107 | 108 | size_t current_Z_index = MAX(1, std::ceil(ray_start.z() - grid.minBound().z() / grid.voxelSizeZ())); 109 | const size_t end_Z_index = MAX(1, std::ceil(ray_end.z() - grid.minBound().z() / grid.voxelSizeZ())); 110 | int stepZ; 111 | value_type tDeltaZ; 112 | value_type tMaxZ; 113 | if (ray.direction().z() > 0.0) { 114 | stepZ = 1; 115 | tDeltaZ = grid.voxelSizeZ() / ray.direction().z(); 116 | tMaxZ = tMin + (grid.minBound().z() + current_Z_index * grid.voxelSizeZ() 117 | - ray_start.z()) / ray.direction().z(); 118 | } else if (ray.direction().z() < 0.0) { 119 | stepZ = -1; 120 | tDeltaZ = grid.voxelSizeZ() / -ray.direction().z(); 121 | const size_t previous_Z_index = current_Z_index - 1; 122 | tMaxZ = tMin + (grid.minBound().z() + previous_Z_index * grid.voxelSizeZ() 123 | - ray_start.z()) / ray.direction().z(); 124 | } else { 125 | stepZ = 0; 126 | tDeltaZ = tMax; 127 | tMaxZ = tMax; 128 | } 129 | 130 | while (current_X_index != end_X_index || current_Y_index != end_Y_index || current_Z_index != end_Z_index) { 131 | if (tMaxX < tMaxY && tMaxX < tMaxZ) { 132 | // X-axis traversal. 133 | current_X_index += stepX; 134 | tMaxX += tDeltaX; 135 | } else if (tMaxY < tMaxZ) { 136 | // Y-axis traversal. 137 | current_Y_index += stepY; 138 | tMaxY += tDeltaY; 139 | } else { 140 | // Z-axis traversal. 141 | current_Z_index += stepZ; 142 | tMaxZ += tDeltaZ; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /amanatidesWooAlgorithm.h: -------------------------------------------------------------------------------- 1 | #ifndef FAST_VOXEL_TRAVERSAL_ALGORITHM_AMANATIDESWOOALGORITHM_H 2 | #define FAST_VOXEL_TRAVERSAL_ALGORITHM_AMANATIDESWOOALGORITHM_H 3 | 4 | #include "Ray.h" 5 | #include "Grid3D.h" 6 | 7 | // Implements the algorithm presented in Amanatides & Woo's "A Fast Voxel Traversal Algorithm for Ray Tracing." 8 | // See: https://www.researchgate.net/publication/2611491_A_Fast_Voxel_Traversal_Algorithm_for_Ray_Tracing 9 | // If the ray origin is outside the voxel grid, uses a safer version of Smit's ray box intersection algorithm 10 | // to determine intersection. The bounds [t0, t1] determine the begin and end parameter for which the ray travels. 11 | // The algorithm occurs in two phases, initialization and traversal. 12 | // Requires: 13 | // t1 > t0 14 | // 0.0 <= t0 <= 1.0 15 | // 0.0 <= t1 <= 1.0 16 | // To encapsulate entire ray traversal, set t0 = 0.0, t1 = 1.0 17 | // 'grid' encapsulates a valid voxel grid system. 18 | // 19 | // Notes: 20 | // Assumes that indices for voxel coordinates begin at 1. 21 | void amanatidesWooAlgorithm(const Ray& ray, const Grid3D& grid, value_type t0, value_type t1) noexcept; 22 | 23 | #endif //FAST_VOXEL_TRAVERSAL_ALGORITHM_AMANATIDESWOOALGORITHM_H 24 | -------------------------------------------------------------------------------- /overview/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/.DS_Store -------------------------------------------------------------------------------- /overview/FastVoxelTraversalOverview.md: -------------------------------------------------------------------------------- 1 | # An Overview of the Fast Voxel Traversal Algorithm 2 | 3 | This document serves as an overview of the research paper [Amanatides & Woo “A Fast Voxel Traversal Algorithm For Ray Tracing”](https://www.researchgate.net/publication/2611491_A_Fast_Voxel_Traversal_Algorithm_for_Ray_Tracing). While the paper provides pseudocode and some understanding of its inner-workings, we discovered it lacked depth. Hopefully, this will provide a deeper comprehension of how the algorithm works, as well as how it can be implemented. 4 | 5 | ## Authors 6 | - Gyurgyik C (cpg49@cornell.edu) 7 | - Kellison A (ak2485@cornell.edu) 8 | 9 | 10 | ## Pre-requisites 11 | At this point, we assume that you have read the paper, and you’re familiar with [voxels](https://en.wikipedia.org/wiki/Voxel). For simplicity, we are also assuming each voxel in that grid has unit size. 12 | 13 | ## 2-dimensional case 14 | We’ll begin with the 2-dimensional grid case displayed in Figure 1 of the paper: 15 | ![2D grid](images/2d_grid.png) 16 | 17 | Here, we have our ray which is a line described as ```r = u + t * v, t >= 0```. This boundary condition makes sense, since ```t``` is time here. ```u``` is a bounded vector usually referred to as the origin, and ```v``` is the free vector representing the direction of the ray. The goal of this algorithm is to traverse the voxels with minimal floating point operations. The paper breaks it down into two phases: “initialization” and “incremental traversal.” 18 | 19 | ### Initialization 20 | 21 | The first step of the initialization phase is to find the voxel that contains the ray origin, u. There is no requirement that the ray origin is within our grid though. Let’s say then, that we have the following case: 22 | 23 | ![ray intersection](images/ray_intersection.png) 24 | 25 | Here, the ray origin is outside our grid. We can clearly see that the ray’s first entrance is into ```Voxel(0,0)```. 26 | In other words, the ray origin is below ```Voxel(0,0)``` which one might name as ```Voxel(0,-1)```. As discussed in the paper, we can simply take the adjacent voxel which in our case 27 | is ```Voxel(0,0)```. To calculate entrance, one can use Smit’s algorithm discussed [here](http://www.cse.chalmers.se/edu/course/TDA362/EfficiencyIssuesForRayTracing.pdf). 28 | A “safer” implementation of this algorithm can be found in the paper [here](http://www.cs.utah.edu/~awilliam/box/box.pdf). 29 | 30 | #### Example: 31 | The ray in the image above might be represented as ```r = u + tv``` where ```u = <0,-3/4>``` and ```v = <1, 8/9>```, i.e. ```r``` follows the line ```y = (-3/4)+ (9/8)x```. Thus, ```ray.origin.x = 0``` and ```ray.origin.y = -3/4```. Based on grid (given at initialization) we know that 32 | ```c 33 | grid.corners = [(0,0),(0,2), 34 | (2,2),(2,0)]; 35 | ``` 36 | 37 | Thus, we can determine the boundary at which ```r``` intersects the grid. In particular, we see the ```y = 0``` boundary of the grid is represented by the vector ```r2 = <0,0> + <1,0>t```; solving for ```r2 = r``` yields ```t = 27/32```. Thus, at the annoying value of ```t = 27/32```, the ray ```r``` will intersect the boundary of the grid. Note that this value of ```t``` is the least such value of ```t``` for all boundary crossings. 38 | 39 | From here, based on grid we know that 40 | ```c 41 | grid.voxel_list = [(0,0),(1,0), 42 | (1,1),(0,1)] 43 | ``` 44 | and we can determine that the voxel we are located in is ```grid.voxel_list[0]```, or ```Voxel(0,0)```. 45 | 46 | ### Incremental Traversal 47 | This brings us to our first algorithm, which we’ll uncover one pseudovariable at a time. 48 | ```c 49 | loop { 50 | if (tMaxX < tMaxY) { 51 | tMaxX= tMaxX + tDeltaX; 52 | X= X + stepX; 53 | } else { 54 | tMaxY= tMaxY + tDeltaY; 55 | Y= Y + stepY; 56 | } 57 | NextVoxel(X,Y); 58 | } 59 | ``` 60 | 61 | The process by which this algorithm works is an iterative loop as one might expect. 62 | We’ve already calculated ```X``` and ```Y``` in the initialization phase; 63 | at the beginning of the loop, these correspond to the first voxel location of the ray 64 | (i.e. ```x_location```, ```y_location``` as found above). 65 | This leaves us with three variable sets to initialize: ```Step```, ```tMax```, and ```tDelta```. 66 | 67 | #### Step 68 | ```Step``` is initialized before the loop begins. 69 | The initialization of ```Step``` requires two considerations: 70 | the (relative) lengths of each dimension of a voxel (e.g. ```grid.x_step```) 71 | and the slope of the ray relative to the cartesian grid (call this ```slope = ray.direction.y/ray.direction.x```). 72 | 73 | Another way to think about step is to ask the question, 74 | “while following the ray from the origin, which direction does it go?” 75 | In fact, some users also call this variable ```Dir```, short for direction. Let’s look at an example: 76 | 77 | ![ray direction 1](images/ray_dir.png) 78 | 79 | Here, (```ray.direction.y > 0``` and ```ray.direction.x > 0``` and ```grid.x_step = grid.y_step = 1```). So in this case, 80 | ```c 81 | StepX = 1 * grid.x_step = 1; 82 | StepY = 1 * grid.y_step = 1; 83 | ``` 84 | 85 | ![ray direction 2](images/ray_dir2.png) 86 | 87 | If the ray’s slope was 0, we’d have instead the following: 88 | ```c 89 | StepX = 1 * grid.x_step = 1; 90 | StepY = 0 * grid.y_step = 0; 91 | ``` 92 | 93 | The case for negative ray slope is similar, but now ```Step = -1```. 94 | 95 | Note that, in addition to the slope of the ray, initializing ```Step``` relies on consideration of the size of the voxel, and is therefore not a multiple. If the voxels were not unit size, you would change the X value for a positive ray.direction.x to 96 | 97 | ```StepX = (voxel_size);``` 98 | This gives us the following generalized function for initializing StepX with unit sized voxel: 99 | 100 | ```c 101 | // StepX will be 0, 1, -1 depending on the ray's x direction. 102 | InitializeStepX(Ray r) { 103 | if (r.direction.x > 0) StepX = 1; 104 | else if (r.direction.x < 0) StepX = -1; 105 | else StepX = 0; 106 | } 107 | ``` 108 | Calculating ```StepY``` is identical. 109 | 110 | #### tMax 111 | The value of ```tMax``` is determined with each iteration. It is the maximum direction the ray travels before hitting a voxel boundary in that direction. This can be illustrated as such: 112 | 113 | ![tMax](images/tMax.png) 114 | 115 | Here, ```tMaxX``` represents how far the ray can travel before hitting the first ```X``` boundary; ```tMaxY``` represents how far the ray can travel before hitting the first ```Y``` boundary. Clearly, for the example above, ```tMaxY``` is smaller than ```tMaxX```; this means we will enter the voxel associated with ```tMaxY``` first. This is exactly the first step of our loop: 116 | 117 | ```c 118 | if (tMaxX < tMaxY) { 119 | traverse in the x-direction. 120 | } else { 121 | traverse in the y-direction. 122 | } 123 | ``` 124 | 125 | Example code to calculate ```tMaxX``` might be the following: 126 | First, let’s calculate the current X index that the ray enters at initialization: 127 | 128 | ```c 129 | // Here, we see this is determined by taking the maximum of the 1 and 130 | // the ray's origin and the minimum bound. 131 | // If the ray started outside of the grid, then this would default to 1. 132 | current_X_index = max(1, ceiling(ray_origin.x - grid.minBound.x)); 133 | ``` 134 | We can then calculate ```tMaxX```: 135 | 136 | ```c 137 | // grid.minBound.x is the lower left corner of the grid. 138 | // current_X_index is the current X index where the ray begins. If it starts outside, this is 1. 139 | // ray_origin.x is the x-coordinate of where the ray originates. 140 | // ray.direction.x is the x-direction of the ray’s travel path. 141 | tMax = (grid.minBound.x + current_X_index - ray_origin.x) / ray.direction.x); 142 | ``` 143 | 144 | Not accounted for in the above pseudocode is: 145 | - ```tMin```: Calculated during the initialization phase, this determines the minimum time needed to cross into the grid. This would be added to ```tMaxX``` in the initialization phase. 146 | - ```voxel_size```: When not of unit size, we can divide by the voxel size in the x-direction when calculating the ```current_X_index```, and then multiply when calculating ```tMaxX``` 147 | 148 | #### tDelta 149 | Lastly, ```tDelta``` is calculated before the loop begins. as the paper mentions, “tDeltaX determines how far along the ray we must move (in units of t) for the horizontal component of such a movement to equal the width of a voxel.” In other words, it is the parametric step length between grid planes. 150 | We can show that here: 151 | 152 | ![tDelta](images/tDelta.png) 153 | 154 | From this image, one can infer that ```tDeltaY = 1 / ray.direction.y;``` 155 | 156 | Similarly, ```tDeltaX = 1 / ray.direction.x;``` 157 | 158 | If one were to use a voxel size other than 1, we’d simply multiply by the ```voxel_size```. 159 | 160 | ### 2-dimensional incremental phase algorithm 161 | Now that we’ve established all of our variables, we can now refer to the algorithm. Here is the entire thing: 162 | ```c 163 | loop { 164 | if (tMaxX < tMaxY) { 165 | tMaxX= tMaxX + tDeltaX; 166 | X= X + stepX; 167 | } else { 168 | tMaxY= tMaxY + tDeltaY; 169 | Y= Y + stepY; 170 | } 171 | NextVoxel(X,Y); 172 | } 173 | ``` 174 | 175 | - ``` loop { ... } ``` 176 | The first portion is the loop invariant. In most use cases, the loop will continue until we’ve hit one of two cases: 177 | 1. We’ve hit the end of our grid space, i.e. ```X == x_out_of_bounds``` 178 | 2. We’ve hit a non-empty object list. This means the ray has hit an object. ```ObjectList[X][Y] != NIL``` 179 | 180 | - ```if (tMaxX < tMaxY) { ``` 181 | As discussed above, we want to determine the closest voxel the ray passes through first. In this example, we’ll assume this is true. 182 | 183 | - ```tMaxX = tMaxX + tDeltaX; ``` 184 | We are now moving along the ray. tDeltaX tells us how much we’ll move along until we’ve hit the next voxel boundary in the x-direction. 185 | 186 | - ```NextVoxel(X,Y)``` 187 | We’ve now updated either our X or Y component. This means we’ll move on to the next cell, located at (X,Y). 188 | 189 | With checks for out of bounds and whether we’ve hit an object list, this gives us our final algorithm: 190 | ```c 191 | loop { 192 | if (tMaxX < tMaxY) { 193 | tMaxX= tMaxX + tDeltaX; 194 | X= X + stepX; 195 | if (X == x_out_of_bounds) return(NIL); // Check max x-coordinate. 196 | } else { 197 | tMaxY= tMaxY + tDeltaY; 198 | Y= Y + stepY; 199 | if (Y == y_out_of_bounds) return(NIL); // Check max y-coordinate. 200 | } 201 | list= ObjectList[X][Y]; // Check voxel to see if 202 | if (list != NIL) return(list); // it contains objects. 203 | NextVoxel(X,Y); 204 | } 205 | ``` 206 | 207 | ## 3-dimensional case 208 | In our 3-dimensional case, not much changes. We’ve now added a third dimension, 209 | and need to determine the ```tMax``` for each dimension before stepping. 210 | The paper also uses a ```do...while``` loop, which ends in the two cases we mentioned above: 211 | an ```ObjectList``` has been acquired, i.e. there is an object in the voxel, or we’ve gone out of bounds. 212 | Lastly, they name their variable ```justOutX``` instead of ```x_out_of_bounds```. The semantics for both remains the same. 213 | 214 | ```c 215 | list= NIL; 216 | do { 217 | if(tMaxX < tMaxY) { 218 | if(tMaxX < tMaxZ) { 219 | X= X + stepX; 220 | if (X == justOutX) return(NIL); /* outside grid */ 221 | tMaxX= tMaxX + tDeltaX; 222 | } else { 223 | Z= Z + stepZ; 224 | if (Z == justOutZ) return(NIL); 225 | tMaxZ= tMaxZ + tDeltaZ; 226 | } 227 | } else { 228 | if(tMaxY < tMaxZ) { 229 | Y= Y + stepY; 230 | if (Y == justOutY) return(NIL); 231 | tMaxY= tMaxY + tDeltaY; 232 | } else { 233 | Z= Z + stepZ; 234 | if (Z == justOutZ) return(NIL); 235 | tMaxZ= tMaxZ + tDeltaZ; 236 | } 237 | } list= ObjectList[X][Y][Z]; 238 | } while(list == NIL); 239 | return(list); 240 | ``` 241 | 242 | This concludes an overview of the fast voxel traversal algorithm, as described by Amanatides and Woo. A prototype implementation in C++ can be found [here](https://github.com/cgyurgyik/fast-voxel-traversal-algorithm). 243 | Another implementation in MATLAB can be found [here](https://www.mathworks.com/matlabcentral/fileexchange/26852-a-fast-voxel-traversal-algorithm-for-ray-tracing), with visualization. 244 | 245 | ## References 246 | - [Amanatides & Woo “A Fast Voxel Traversal Algorithm For Ray Tracing”](https://www.researchgate.net/publication/2611491_A_Fast_Voxel_Traversal_Algorithm_for_Ray_Tracing) 247 | - [Jacco Bikker "Spatial Subdivisions"](https://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_4_Spatial_Subdivisions.shtml) 248 | - [SketchpunkLabs "Voxel Ray Intersection"](https://www.youtube.com/watch?v=lJdEX3w0xaY) 249 | - [Smit "Efficiency Issues for Ray Tracing"](http://www.cse.chalmers.se/edu/course/TDA362/EfficiencyIssuesForRayTracing.pdf) 250 | - [Williams et. al. "An Efficient and Robust Ray–Box Intersection Algorithm"](http://www.cs.utah.edu/~awilliam/box/box.pdf) 251 | -------------------------------------------------------------------------------- /overview/images/2d_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/2d_grid.png -------------------------------------------------------------------------------- /overview/images/ray_dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/ray_dir.png -------------------------------------------------------------------------------- /overview/images/ray_dir2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/ray_dir2.png -------------------------------------------------------------------------------- /overview/images/ray_intersection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/ray_intersection.png -------------------------------------------------------------------------------- /overview/images/tDelta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/tDelta.png -------------------------------------------------------------------------------- /overview/images/tMax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgyurgyik/fast-voxel-traversal-algorithm/399dcd9d10b87eacf6f375fd51aed8ee3de9ae1e/overview/images/tMax.png --------------------------------------------------------------------------------