/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.common.util;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nonnull;
import java.util.Arrays;
/**
* Line calculation by Bresenham. This class is used to calculate a line between
* 2 points in 2D space on a tiles map.
*
* @author Martin Karing <nitram@illarion.org>
* @author Nop
*/
public final class Bresenham {
/**
* The maximum length of the line states how many points one line could contain. If a longer line is calculated,
* the calculation is canceled.
*/
private static final int MAX_LINE_LENGTH = 100;
/**
* The length of the line that was created latest.
*/
private int length;
/**
* The list of x-coordinates that were calculated due the last line calculation.
*/
@Nonnull
private final int[] x = new int[MAX_LINE_LENGTH];
/**
* The list of y-coordinates that were calculated due the last line calculation.
*/
@Nonnull
private final int[] y = new int[MAX_LINE_LENGTH];
/**
* Reverse order of points if they do not start with given point.
*
* @param sx x coordinate of the expected starting point
* @param sy y coordinate of the expected starting point
*/
public void adjustStart(int sx, int sy) {
if ((x[0] != sx) || (y[0] != sy)) {
int i = 0;
int j = length - 1;
while (i < j) {
int tmp = x[i];
x[i] = x[j];
x[j] = tmp;
int tmp2 = y[i];
y[i] = y[j];
y[j] = tmp2;
++i;
--j;
}
}
}
/**
* Calculate a line between 2 locations using the Bresenham algorithms. The last line that was calculated is
* overwritten by calling this function. So this line calculation is removed also instantly as the next calculation
* is performed. So ensure to copy the data of this lines in oder to store them and do not just save the
* references to the arrays.
*
* @param x0 the x coordinate of the start location of the line
* @param y0 the y coordinate of the start location of the line
* @param x1 the x coordinate of the target location of the line
* @param y1 the y coordinate of the target location of the line
*/
public void calculate(int x0, int y0, int x1, int y1) {
length = 0;
int currX = x0;
int currY = y0;
int dy = y1 - y0;
int dx = x1 - x0;
int stepX, stepY;
if (dy < 0) {
dy = -dy;
stepY = -1;
} else {
stepY = 1;
}
if (dx < 0) {
dx = -dx;
stepX = -1;
} else {
stepX = 1;
}
dy <<= 1; // dy is now 2*dy
dx <<= 1; // dx is now 2*dx
addPoint(x0, y0);
if (dx > dy) {
int fraction = dy - (dx >> 1); // same as 2*dy - dx
while (currX != x1) {
if (fraction >= 0) {
currY += stepY;
fraction -= dx; // same as fraction -= 2*dx
}
currX += stepX;
fraction += dy; // same as fraction -= 2*dy
addPoint(currX, currY);
}
} else {
int fraction = dx - (dy >> 1);
while (currY != y1) {
if (fraction >= 0) {
currX += stepX;
fraction -= dy;
}
currY += stepY;
fraction += dx;
addPoint(currX, currY);
}
}
}
/**
* Get the length of the line that was calculated at the last run.
*
* @return the length of the line
*/
@Contract(pure = true)
public int getLength() {
return length;
}
/**
* Get the list of x coordinates that were calculated last time.
*
* @return the list of x coordinates
*/
@Nonnull
@Contract(pure = true)
public int[] getX() {
return Arrays.copyOf(x, length);
}
/**
* Get the list of y coordinates that were calculated last time.
*
* @return the list of y coordinates
*/
@Nonnull
@Contract(pure = true)
public int[] getY() {
return Arrays.copyOf(y, length);
}
@Nonnull
@Override
@Contract(pure = true)
public String toString() {
return "Bresenham Line Tracer";
}
/**
* Add a point to the list of points that were calculated. The length of the line in automatically increased by
* one after calling this function.
*
* @param px the x-coordinate of the point that shall be added
* @param py the y-coordinate of the point that shall be added
*/
private void addPoint(int px, int py) {
if (length > (MAX_LINE_LENGTH - 1)) {
throw new IllegalStateException("Bresenham line is getting too long.");
}
x[length] = px;
y[length] = py;
++length;
}
}