I've implemented @templatetypedef's solution in C++
, for a closed polygonal chain, described by two x,y
vectors. I walk the polygon, and if a point is collinear with the previous and the next point, I delete it:
template<class T> void del_collinear_vanilla(std::vector<T> &x,
std::vector<T> &y) {
assert(x.size() == y.size());
size_t i = x.size();
size_t im1, ip1 = 0;
do {
i--;
im1 = i ? i - 1 : x.size() - 1;
if (are_collinear(x[ip1], y[ip1], x[i], y[i], x[im1], y[im1])) {
x.erase(x.begin() + i);
y.erase(y.begin() + i);
}
ip1 = i;
} while (i != 0);
}
where the implementation depends on a macro/template are_collinear(x0,y0,x1,y1,x2,y2)
.
However, in some cases I still had some collinear points in the output. This is a sample input with which the algorithm fails:

In the example, P5 coincides with P0 and P4 has the same ordinate of P0 and P1; I changed a little their coordinates to show all the segments. The algorithm should return only a rectangle with vertices P1,P2,P3,P4.
Above, P6 is collinear with P5 and P0. Then, once P6 is eliminated, P5 and P0 coincide, and they are both collinear with P4 and P1.
It turns out that a simple loop over each point, deleting a point if it is collinear with the previous and the next point, does not provide the correct result.
(In the example, let's say you start with P0, and you find that it is not collinear with the point before P6 and the point after P1. Then you move to P1,P2,... until you reach P6. P6 is collinear, you delete it, and the loop is finished. But now P0 is collinear with P4 and P1, and it should have been deleted!)
The same flaw exists for an open path. The algorithm works fine as long as the input path has not collapsed on itself, in a way.
The solution is to take a step back every time you delete a point, to verify if the previous point has now become collinear:
template<class T> void del_collinear(std::vector<T> &x, std::vector<T> &y) {
assert(x.size() == y.size());
size_t target = x.size() - 1;
size_t i = x.size() - 1;
do {
size_t im1 = i ? i - 1 : x.size() - 1;
size_t ip1 = (i == x.size() - 1) ? 0 : i + 1;
if (are_collinear(x[ip1], y[ip1], x[i], y[i], x[im1], y[im1])) {
x.erase(x.begin() + i);
y.erase(y.begin() + i);
// I do not decrease i in this case, as the the previous (alread
// processed) point may now be a collinear point that must be
// deleted. I mod it because i may now exceed x.size()
i = i % x.size();
//Increment the target as well.
target = (i + 1 + x.size()) % x.size();
} else
//go for the next point.
i = i ? i - 1 : x.size() - 1;
} while (i != target);
}