@@ -127,88 +127,54 @@ DEFN_RENDER(circle)
127127
128128DEFN_RENDER (line )
129129{
130- const struct color color = line -> c ;
131-
132- // I think this is essentially Bresenham's line algorithm, as in, that
133- // is where the ideas came from, but with the caveat that I didn't want
134- // a separate vertical and horizontal drawing function.
135- //
136- // ## Terms
137- //
138- // Since this is octant-agnostic, we generalize the notion of a step
139- // between pixels.
130+ // This approach is guided by https://zingl.github.io/Bresenham.pdf
131+ // (A Rasterizing Algorithm for Drawing Curves, by Alois Zingl).
132+ // The math is based on the implicit function for a line,
140133 //
141- // Considering the vector (dx, dy), one component will have greater or
142- // equal magnitude than the other, so at every iteration, the pixel
143- // position will be incremented along that component. I refer to this
144- // increment vector as the step.
134+ // > f(x, y) = (y - y0)(x1 - x0) - (x - x0)(y1 - y0)
145135 //
146- // The pixel position may also be incremented along the component with
147- // lesser magnitude, but this happens a fraction of the time (a fraction
148- // in the range [0, 1]). I refer to this as the lesser step.
136+ // which for all points on the line equals zero. Since the slope of a
137+ // line remains constant, we can test the "error" (evaluating the
138+ // function) at the next possible points and use a cheap comparison to
139+ // decide which to move to with the least error.
149140 //
150- // ## Process for deriving
141+ // For lines, the errors for directly adjacent pixels can be expressed
142+ // in terms of the error for the diagonal pixel and the total distances
143+ // `dy` and `dx`. This means we only need one error variable.
151144 //
152- // 1. Determine whether to step in the lesser direction.
153- // - Given the two possible future points (either cur + step or cur +
154- // step + lstep), and the point on the line which lies between
155- // them, which future point is closer to the line? This calculation
156- // is made with real numbers, approximated with floats.
157- // 2. Scale the two distances such that the comparison is still
158- // meaningful, while eliminating floating point division. Now, we can
159- // use integers!
160- // 3. Avoid recomputing the distances from scratch each iteration.
161- // - Determine the initial value, and the difference between the
162- // (i+1)th and ith values for each branch.
163-
164- const int32_t dx = (int32_t )line -> x1 - (int32_t )line -> x0 ;
165- const int32_t dy = (int32_t )line -> y1 - (int32_t )line -> y0 ;
166-
167- const uint16_t steps = (uint16_t )max (abs (dx ), abs (dy ));
168-
169- if (steps == 0 ) {
170- draw_point (c , line -> x0 , line -> y0 , color );
171- return ;
172- }
173-
174- // step_x and step_y are both in the range [-1, 1], and
175- // at least one is -1 or 1
176- const int32_t step_x = dx / steps ;
177- const int32_t step_y = dy / steps ;
145+ // The next optimization is to track the initial value and difference
146+ // per iteration, rather than recomputing the error each time.
178147
179- const int32_t lstep_x = step_x == 0 ? (dx != 0 ? dx / abs (dx ) : 0 ) : 0 ;
180- const int32_t lstep_y = step_y == 0 ? (dy != 0 ? dy / abs (dy ) : 0 ) : 0 ;
148+ const int32_t dx = abs (line -> x1 - line -> x0 );
149+ const int32_t dy = abs (line -> y1 - line -> y0 );
150+ const int32_t sx = line -> x0 < line -> x1 ? 1 : -1 ;
151+ const int32_t sy = line -> y0 < line -> y1 ? 1 : -1 ;
181152
182- uint16_t x = line -> x0 ;
183- uint16_t y = line -> y0 ;
153+ int32_t x = line -> x0 ;
154+ int32_t y = line -> y0 ;
184155
185- int32_t d0_x = steps * step_x ;
186- int32_t d0_y = steps * step_y ;
187- int32_t d1_x = - steps * ( step_x + lstep_x );
188- int32_t d1_y = - steps * ( step_y + lstep_y );
189-
190- for ( int32_t i = 0 ; i <= steps ; i ++ ) {
191- draw_point ( c , x , y , color );
192-
193- const int32_t px = abs ( d0_x ) - abs ( d1_x );
194- const int32_t py = abs ( d0_y ) - abs ( d1_y );
195-
196- x += step_x ;
197- d0_x += steps * step_x - dx ;
198- d1_x -= steps * step_x - dx ;
199- if ( px >= 0 ) {
200- x += lstep_x ;
201- d0_x += steps * lstep_x ;
202- d1_x -= steps * lstep_x ;
156+ // We track the error for the next diagonal pixel (x + sx, y + sy).
157+ //
158+ // This relies on the assumption that the slope is positive, but we took
159+ // the absolute value for `dy` and `dx`, and this lie is self contained
160+ // in the mathy state and accounted for by `sx` and `sy`.
161+ int32_t e = dx - dy ;
162+
163+ while (true) {
164+ draw_point ( c , x , y , line -> c );
165+
166+ // Check if we reached the other end.
167+ if ( x == line -> x1 && y == line -> y1 )
168+ break ;
169+
170+ const int32_t test = 2 * e ;
171+ if ( test > - dy ) {
172+ x += sx ;
173+ e -= dy ;
203174 }
204-
205- y += step_y ;
206- d0_y += steps * step_y - dy ;
207- d1_y -= steps * step_y - dy ;
208- if (py >= 0 ) {
209- y += lstep_y ;
210- d0_y += steps * lstep_y ;
211- d1_y -= steps * lstep_y ;
175+ if (test < dx ) {
176+ y += sy ;
177+ e += dx ;
212178 }
213179 }
214180}
0 commit comments