-
Vincent Nivoliers authoredb9a9e194
hull_almost_exact.cpp 7.09 KiB
#include "svg.hpp"
#include <boost/numeric/interval.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>
#include <algorithm>
#include <random>
#include <vector>
#include <sstream>
#include <cmath>
//{{{ struct vec
//generic vector
template<typename number>
struct generic_vec {
generic_vec() {} ;
generic_vec(number x, number y) : x(x), y(y) {}
number x ;
number y ;
generic_vec operator-(const generic_vec& v) const {
return generic_vec(x - v.x, y - v.y) ;
}
template<typename other_number>
generic_vec& operator=(const generic_vec<other_number>& v) {
x = v.x ;
y = v.y ;
return *this ;
}
generic_vec operator+(const generic_vec& v) const {
return generic_vec(x + v.x, y + v.y) ;
}
generic_vec operator*(number a) const {
return generic_vec(a*x, a*y) ;
}
number length2() const {
return x*x + y*y ;
}
number length() const {
return sqrt(length2()) ;
}
bool lexicoinf(const generic_vec& v) const {
if(x == v.x) return y < v.y ;
return x < v.x ;
}
} ;
//redefine the previous vector with floats
using vec = generic_vec<float> ;
//exact vector on rationals
using rational = boost::multiprecision::cpp_rational ;
using exact_vec = generic_vec<rational> ;
//}}}
template<typename number>
int sign(number x) {
if(x > 0) return 1 ;
if(x < 0) return -1 ;
return 0 ;
}
int orient(const vec& v0_in, const vec& v1_in) {
//sort the inputs to resist permutation and use exactness
exact_vec v0, v1 ;
int signature = 1 ;
if(v0_in.lexicoinf(v1_in)) {
v0 = v0_in ;
v1 = v1_in ;
} else {
v1 = v0_in ;
v0 = v1_in ;
signature = -1 ;
}
//determinant
rational d = v0.x*v1.y - v0.y*v1.x ;
if(d != 0) return signature*sign(d) ;
//enter perturbation with perturbation matrix
// [ e e^2 ]
// [ e^2 e^2 ]
if(v1.y != 0) return signature*sign(v1.y) ;
rational a = v0.x - v0.y - v1.x ;
if(a != 0) return signature*sign(a) ;
return signature ;
//other perturbations work, for instance
// [ e 0 ]
// [ 0 e^2 ]
if(v1.y != 0) return signature*sign(v1.y) ;
if(v0.x != 0) return signature*sign(v0.x) ;
return signature ;
}
struct vec_compare {
//functor given leftmost point for sort
vec_compare(const vec& origin) : origin(origin) {}
//comparison
bool operator()(const vec& p0, const vec& p1) {
int o = orient(p0-origin, p1-origin) ;
return o < 0 ;
}
const vec& origin ;
} ;
//{{{leftmost
size_t leftmost(const std::vector<vec>& points) {
size_t min = 0 ;
//iterato on all the points
for(size_t i = 1; i < points.size(); ++i) {
if(points[i].x < points[min].x) {
//update the leftmost
min = i ;
}
}
return min ;
}
//}}}
void compute_hull(std::vector<vec>& points, std::vector<vec>& hull) {
//leftmost point ahead
size_t l = leftmost(points) ;
std::swap(*points.begin(), *(points.begin() + l)) ;
//sort the rest by angle
std::sort(points.begin() + 1, points.end(), vec_compare(points[0])) ;
//add the first two points to the hull
hull.push_back(points[0]) ;
hull.push_back(points[1]) ;
//sweep the remaining points
for(size_t i = 2; i < points.size(); ++i) {
//check whether the last segment creates a wrong turn
const vec& p = points[i] ;
vec v1 = p - *(hull.end() - 2) ;
vec v2 = *(hull.end() - 1) - *(hull.end() - 2) ;
while(hull.size() > 1 && orient(v1, v2) < 0) {
//in case of wrong turn, rease the last point of the hull and iterate
hull.erase(hull.end() - 1) ;
v1 = p - *(hull.end() - 2) ;
v2 = *(hull.end() - 1) - *(hull.end() - 2) ;
}
hull.push_back(p) ;
}
}
//{{{ generate svg
void svg_hull(
const std::string& filename,
const std::vector<vec>& points,
const std::vector<vec>& hull,
float salt = 0
)
{
//open file for export
std::ofstream file(filename) ;
SvgPad svg(file, 1024, 1024) ;
svg.open() ;
//plot data points
for(const vec& p : points) {
svg.solid_point(p.x, p.y) ;
}
//randomness in case salt is added for hull debugging
std::mt19937 mt ;
std::uniform_real_distribution<float> rf(-salt, salt) ;
//plot hull
size_t hs = hull.size() ;
for(size_t i = 0; i < hs; ++i) {
//salt to perturb the line endpoints and avoid overlaps
vec salti(0,0) ;
vec saltip(0,0) ;
if(salt > 0) {
salti.x = rf(mt) ;
salti.y = rf(mt) ;
saltip.x = rf(mt) ;
saltip.y = rf(mt) ;
}
//hull points
svg.contour_point(hull[i].x, hull[i].y) ;
//hull sides with salt
svg.line(
hull[i].x + salti.x,
hull[i].y + salti.y,
hull[(i+1)%hs].x + saltip.x,
hull[(i+1)%hs].y + saltip.y
) ;
}
//close file
svg.close() ;
}
//utility to illustrate angle sorting
void svg_sort(
const std::string& filename,
const std::vector<vec>& points)
{
//open svg file for export
std::ofstream file(filename) ;
SvgPad svg(file, 1024, 1024) ;
svg.open() ;
//colors for the linear gradient of lines
float col0[3] = { 1., 0.9, 0.3 } ;
float col1[3] = { 0.35, 0.2, 0.4 } ;
//iterate on every point except the leftmost at 0
for(size_t i = 1; i < points.size(); ++i) {
//hue
float b = ((float) i-1) / (points.size() - 1) ;
float a = 1-b ;
svg.stroke(a*col0[0] + b*col1[0], a*col0[1] + b*col1[1], a*col0[2] + b*col1[2]) ;
//line defining the sorting angle
svg.line(points[0].x, points[0].y, points[i].x, points[i].y) ;
}
svg.close() ;
}
//}}}
int main() {
unsigned int size = 100 ;
//{{{ random test
{
//random numbers
std::random_device rd ;
std::mt19937 alea(rd()) ;
std::uniform_real_distribution<float> uniform_float(0, 1) ;
std::normal_distribution<float> normal_float(0, 1) ;
std::vector<vec> points ;
for(unsigned int i = 0; i < size; ++i) {
//generate a uniform point in a ball
vec p(normal_float(alea), normal_float(alea)) ;
float n = sqrt(uniform_float(alea)) / 2 ;
p = p * (n / p.length()) + vec(0.5,0.5);
points.push_back(p) ;
}
//hull
std::vector<vec> hull ;
compute_hull(points, hull) ;
svg_hull("/tmp/hull_test.svg", points, hull) ;
svg_sort("/tmp/hull_lines.svg", points) ;
}
//}}}
//{{{ wicked test
{
std::vector<vec> aligned_points ;
//aligned points
aligned_points.emplace_back(0, 1) ;
aligned_points.emplace_back(0, 0) ;
aligned_points.emplace_back(0, 0.8) ;
aligned_points.emplace_back(0, 0.5) ;
//test every permutation of the input
int indices[4] = {0, 1, 2, 3} ;
int permutation_index = 0 ;
do {
std::vector<vec> points ;
for(unsigned int i = 0; i < 4; ++i) {
points.push_back(aligned_points[indices[i]]) ;
}
//make the hull non flat
points.emplace_back(1, 0.5) ;
//hull
std::vector<vec> hull ;
compute_hull(points, hull) ;
//export with salt for readability
std::stringstream ss ;
ss << "/tmp/hull_aligned_" << permutation_index << ".svg" ;
svg_hull(ss.str(), points, hull, 0.02) ;
//move to next permutation
++ permutation_index ;
}while(std::next_permutation(indices, indices + 4)) ;
}
//}}}
return 0 ;
}