@@ -16,10 +16,10 @@ def voxel_traversal_2d(
16
16
end_vertex : Tuple [int , int ],
17
17
debug : bool = False , # if true, prints diagnostic info for the algorithm
18
18
) -> List [Tuple [int , int ]]:
19
- """Returns all pixels between two points as inspired by Amanatides & Woo's “A Fast Voxel Traversal Algorithm For Ray Tracing”"""
20
-
21
- # epsilon is a constant for correcting near-misses in voxel traversal
22
- EPSILON = 1e-14
19
+ """
20
+ Returns all pixels between two points as inspired by Amanatides & Woo's “A Fast Voxel Traversal Algorithm For Ray Tracing”
21
+ Implementation adapted from https://www.redblobgames.com/grids/line-drawing/
22
+ """
23
23
24
24
# Setup initial conditions
25
25
x1 , y1 = start_vertex
@@ -45,31 +45,21 @@ def voxel_traversal_2d(
45
45
46
46
dy = y2 - y1
47
47
dx = x2 - x1
48
- slope = dy / dx
49
- inv_slope = dx / dy
50
-
51
- # reverse order if negative slope to preserve symmetry in floating point calculations
52
- if slope < 0 :
53
- x1 , y1 = end_vertex
54
- x2 , y2 = start_vertex
55
-
56
- direction_x = 1 if x2 > x1 else - 1
57
- direction_y = 1 if y2 > y1 else - 1
58
-
59
- slope_multiplier = np .sqrt (1 + slope ** 2 )
60
- inv_slope_multiplier = np .sqrt (1 + inv_slope ** 2 )
61
48
62
49
pixel_x , pixel_y = x1 , y1
63
- ray_x , ray_y = pixel_x , pixel_y
64
50
pixels = [(pixel_x , pixel_y )]
65
51
66
52
is_finished = False
67
53
68
54
if debug :
69
55
print (f"\n Traversing from ({ x1 } ,{ y1 } ) to ({ x2 } ,{ y2 } )" )
70
56
71
- # number of steps should not exceed the perimeter of the rectangle enclosing the line segment
72
- max_steps = 2 * (abs (dx ) + abs (dy ))
57
+ ix = 0
58
+ iy = 0
59
+
60
+ nx = abs (dx )
61
+ ny = abs (dy )
62
+ max_steps = nx + ny
73
63
n_steps = 0
74
64
while not is_finished :
75
65
# this prevents infinite loops
@@ -79,71 +69,27 @@ def voxel_traversal_2d(
79
69
f"Traversal has exceeded steps limit { max_steps :,} . Please recheck inputs"
80
70
)
81
71
82
- # get the next x or y integer that the next ray would hit
83
- if direction_x == 1 :
84
- next_ray_x = np .floor (ray_x ) + 1
85
- elif direction_x == - 1 :
86
- next_ray_x = np .ceil (ray_x ) - 1
87
-
88
- if direction_y == 1 :
89
- next_ray_y = np .floor (ray_y ) + 1
90
- elif direction_y == - 1 :
91
- next_ray_y = np .ceil (ray_y ) - 1
92
-
93
- # get distance between the 2 candidates and check which one is closer
94
- # there is an epsilon to account near-misses due to floating point differences
95
-
96
- # y coordinate line formula is next_ray_y = ray_y + slope*(next_ray_x-ray_x)
97
- # squred distance is (next_ray_x - ray_x)**2 + (slope*(next_ray_x-ray_x))**2
98
- # distance simplifies to abs(next_ray_x - ray_x)* sqrt(1+slope**2)
99
-
100
- ray_candidate_1 = (
101
- next_ray_x ,
102
- ray_y + slope * (next_ray_x - ray_x ) + direction_y * EPSILON ,
103
- )
104
- # unsimplified square distance
105
- # dist_1 = (ray_candidate_1[0] - ray_x)**2 + (ray_candidate_1[1] - ray_y)**2
106
- # simplified distance
107
- dist_1 = abs (next_ray_x - ray_x ) * slope_multiplier
108
-
109
- # x coordinate line formula is next_ray_x = ray_x + inv_slope*(next_ray_y-y)
110
- # squared distance is (inv_slope*(next_ray_y-ray_y))**2 + (next_ray_y-ray_y)**2
111
- # distance simplifies to abs(next_ray_y-ray_y)* sqrt(1 + inv_slope**2)
112
-
113
- ray_candidate_2 = (
114
- ray_x + inv_slope * (next_ray_y - ray_y ) + direction_x * EPSILON ,
115
- next_ray_y ,
116
- )
117
- # unsimplified square distance
118
- # dist_2 = (ray_candidate_2[0] - ray_x)**2 + (ray_candidate_2[1] - ray_y)**2
119
- # simplified distance
120
- dist_2 = abs (next_ray_y - ray_y ) * inv_slope_multiplier
121
-
122
- # candidate 1 is closer
123
- if dist_1 < dist_2 :
72
+ decision = (1 + 2 * ix ) * ny - (1 + 2 * iy ) * nx
73
+ if decision == 0 :
74
+ # diagonal step
124
75
pixel_x += direction_x
125
- ray_x , ray_y = ray_candidate_1
126
-
127
- # candidate 2 is closer
128
- elif dist_1 > dist_2 :
129
76
pixel_y += direction_y
130
- ray_x , ray_y = ray_candidate_2
131
-
132
- # line passes exactly on the corner
133
- elif dist_1 == dist_2 :
77
+ ix += 1
78
+ iy += 1
79
+ elif decision < 0 :
80
+ # horizontal step
134
81
pixel_x += direction_x
135
- pixel_y += direction_y
136
- ray_x , ray_y = pixel_x , pixel_y
82
+ ix += 1
137
83
else :
138
- raise ValueError (f"Erroneous distances { dist_1 } , { dist_2 } " )
139
-
140
- if debug :
141
- print (
142
- f"Next ray coords are ({ ray_x } , { ray_y } ) and tile coords are ({ pixel_x } , { pixel_y } )"
143
- )
84
+ # vetical step
85
+ pixel_y += direction_y
86
+ iy += 1
144
87
145
88
pixels .append ((pixel_x , pixel_y ))
146
89
90
+ if debug :
91
+ print (f"Next tile coords are ({ pixel_x } , { pixel_y } )" )
92
+
147
93
# checks to see if the loop is finished
148
94
if direction_x == 1 :
149
95
is_x_finished = pixel_x >= x2
0 commit comments