Skip to content

Instantly share code, notes, and snippets.

@ideasman42
Created December 3, 2016 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ideasman42/de10701fcaed66c2cf6a448b937fdf24 to your computer and use it in GitHub Desktop.
Save ideasman42/de10701fcaed66c2cf6a448b937fdf24 to your computer and use it in GitHub Desktop.
/// Plot a line,
///
/// Bresenham's line algorithm,
/// See: https://en.wikipedia.org/wiki/Bresenham's_line_algorithm
/// StackOverflow: http://stackoverflow.com/a/40902741/432509
fn plot_line_v2v2i<F>(
p1: &[i32; 2],
p2: &[i32; 2],
mut clip_xmin: i32,
mut clip_ymin: i32,
mut clip_xmax: i32,
mut clip_ymax: i32,
callback: &mut F)
where
F: FnMut(i32, i32),
{
use ::std::mem::swap;
use ::std::cmp::{min, max};
let mut x1 = p1[0];
let mut y1 = p1[1];
let mut x2 = p2[0];
let mut y2 = p2[1];
// Vertical line
if x1 == x2 {
if x1 < clip_xmin || x1 > clip_xmax {
return;
}
if y1 <= y2 {
if y2 < clip_ymin || y1 > clip_ymax {
return;
}
y1 = max(y1, clip_ymin);
y2 = min(y2, clip_ymax);
for y in y1..(y2 + 1) {
callback(x1, y);
}
} else {
if y1 < clip_ymin || y2 > clip_ymax {
return;
}
y1 = min(y1, clip_ymax);
y2 = max(y2, clip_ymin);
for y in (y2..(y1 + 1)).rev() {
callback(x1, y);
}
}
return;
}
// Horizontal line
if y1 == y2 {
if y1 < clip_ymin || y1 > clip_ymax {
return;
}
if x1 <= x2 {
if x2 < clip_xmin || x1 > clip_xmax {
return;
}
x1 = max(x1, clip_xmin);
x2 = min(x2, clip_xmax);
for x in x1..(x2 + 1) {
callback(x, y1);
}
} else {
if x1 < clip_xmin || x2 > clip_xmax {
return;
}
x1 = min(x1, clip_xmax);
x2 = max(x2, clip_xmin);
for x in x2..(x1 + 1) {
callback(x, y1);
}
}
return;
}
// Now simple cases are handled, perform clipping checks.
let sign_x;
let sign_y;
if x1 < x2 {
if x1 > clip_xmax || x2 < clip_xmin {
return;
}
sign_x = 1;
} else {
if x2 > clip_xmax || x1 < clip_xmin {
return;
}
sign_x = -1;
// Invert sign, invert again right before plotting.
x1 = -x1;
x2 = -x2;
clip_xmin = -clip_xmin;
clip_xmax = -clip_xmax;
swap(&mut clip_xmin, &mut clip_xmax);
}
if y1 < y2 {
if y1 > clip_ymax || y2 < clip_ymin {
return;
}
sign_y = 1;
} else {
if y2 > clip_ymax || y1 < clip_ymin {
return;
}
sign_y = -1;
// Invert sign, invert again right before plotting.
y1 = -y1;
y2 = -y2;
clip_ymin = -clip_ymin;
clip_ymax = -clip_ymax;
swap(&mut clip_ymin, &mut clip_ymax);
}
let delta_x = x2 - x1;
let delta_y = y2 - y1;
let mut delta_x_step = 2 * delta_x;
let mut delta_y_step = 2 * delta_y;
// Plotting values
let mut x_pos = x1;
let mut y_pos = y1;
if delta_x >= delta_y {
let mut error = delta_y_step - delta_x;
let mut set_exit = false;
// Line starts below the clip window.
if y1 < clip_ymin {
let temp = (2 * (clip_ymin - y1) - 1) * delta_x;
let msd = temp / delta_y_step;
x_pos += msd;
// Line misses the clip window entirely.
if x_pos > clip_xmax {
return;
}
// Line starts.
if x_pos >= clip_xmin {
let rem = temp - msd * delta_y_step;
y_pos = clip_ymin;
error -= rem + delta_x;
if rem > 0 {
x_pos += 1;
error += delta_y_step
}
set_exit = true;
}
}
// Line starts left of the clip window.
if !set_exit && x1 < clip_xmin {
let temp = delta_y_step * (clip_xmin - x1);
let msd = temp / delta_x_step;
y_pos += msd;
let rem = temp % delta_x_step;
// Line misses clip window entirely.
if y_pos > clip_ymax || (y_pos == clip_ymax && rem >= delta_x) {
return;
}
x_pos = clip_xmin;
error += rem;
if rem >= delta_x {
y_pos += 1;
error -= delta_x_step;
}
}
let mut x_pos_end = x2;
if y2 > clip_ymax {
let temp = delta_x_step * (clip_ymax - y1) + delta_x;
let msd = temp / delta_y_step;
x_pos_end = x1 + msd;
if (temp - msd * delta_y_step) == 0 {
x_pos_end -= 1;
}
}
x_pos_end = min(x_pos_end, clip_xmax) + 1;
if sign_y == -1 {
y_pos = -y_pos
}
if sign_x == -1 {
x_pos = -x_pos;
x_pos_end = -x_pos_end;
}
delta_x_step -= delta_y_step;
while x_pos != x_pos_end {
callback(x_pos, y_pos);
if error >= 0 {
y_pos += sign_y;
error -= delta_x_step;
} else {
error += delta_y_step;
}
x_pos += sign_x;
}
} else {
// Line is steep '/' (delta_x < delta_y).
// Same as previous block of code with swapped x/y axis.
let mut error = delta_x_step - delta_y;
let mut set_exit = false;
// Line starts left of the clip window.
if x1 < clip_xmin {
let temp = (2 * (clip_xmin - x1) - 1) * delta_y;
let msd = temp / delta_x_step;
y_pos += msd;
// Line misses the clip window entirely.
if y_pos > clip_ymax {
return;
}
// Line starts.
if y_pos >= clip_ymin {
let rem = temp - msd * delta_x_step;
x_pos = clip_xmin;
error -= rem + delta_y;
if rem > 0 {
y_pos += 1;
error += delta_x_step;
}
set_exit = true;
}
}
// Line starts below the clip window.
if !set_exit && y1 < clip_ymin {
let temp = delta_x_step * (clip_ymin - y1);
let msd = temp / delta_y_step;
x_pos += msd;
let rem = temp % delta_y_step;
// Line misses clip window entirely.
if x_pos > clip_xmax || (x_pos == clip_xmax && rem >= delta_y) {
return;
}
y_pos = clip_ymin;
error += rem;
if rem >= delta_y {
x_pos += 1;
error -= delta_y_step;
}
}
let mut y_pos_end = y2;
if x2 > clip_xmax {
let temp = delta_y_step * (clip_xmax - x1) + delta_y;
let msd = temp / delta_x_step;
y_pos_end = y1 + msd;
if (temp - msd * delta_x_step) == 0 {
y_pos_end -= 1;
}
}
y_pos_end = min(y_pos_end, clip_ymax) + 1;
if sign_x == -1 {
x_pos = -x_pos;
}
if sign_y == -1 {
y_pos = -y_pos;
y_pos_end = -y_pos_end;
}
delta_y_step -= delta_x_step;
while y_pos != y_pos_end {
callback(x_pos, y_pos);
if error >= 0 {
x_pos += sign_x;
error -= delta_y_step;
} else {
error += delta_x_step;
}
y_pos += sign_y;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment