From 3425064ebe231d03c6e1fa1ac01b7fa5d00a9674 Mon Sep 17 00:00:00 2001
From: Vincent Nivoliers <vincent.nivoliers@univ-lyon1.fr>
Date: Mon, 18 Nov 2019 19:08:06 +0100
Subject: [PATCH] multiple hull versions

---
 Code/Makefile              |  11 +-
 Code/hull_almost_exact.cpp | 310 +++++++++++++++++++++++++++++++++++++
 Code/hull_permutation.cpp  | 293 +++++++++++++++++++++++++++++++++++
 Code/hull_perturbation.cpp | 277 +++++++++++++++++++++++++++++++++
 4 files changed, 890 insertions(+), 1 deletion(-)
 create mode 100755 Code/hull_almost_exact.cpp
 create mode 100755 Code/hull_permutation.cpp
 create mode 100755 Code/hull_perturbation.cpp

diff --git a/Code/Makefile b/Code/Makefile
index cc59505..1d4ab52 100644
--- a/Code/Makefile
+++ b/Code/Makefile
@@ -1,4 +1,4 @@
-all: precision hull
+all: precision hull hull_perturbation hull_permutation hull_almost_exact
 
 precision: precision.cpp
 	g++ -g -Wall precision.cpp -o precision
@@ -6,5 +6,14 @@ precision: precision.cpp
 hull: hull.cpp svg.cpp
 	g++ -g -Wall hull.cpp svg.cpp -o hull
 
+hull_perturbation: hull_perturbation.cpp svg.cpp
+	g++ -g -Wall hull_perturbation.cpp svg.cpp -o hull_perturbation
+
+hull_permutation: hull_permutation.cpp svg.cpp
+	g++ -g -Wall hull_permutation.cpp svg.cpp -o hull_permutation
+
+hull_almost_exact: hull_almost_exact.cpp svg.cpp
+	g++ -g -Wall hull_almost_exact.cpp svg.cpp -o hull_almost_exact
+
 clean:
 	rm -f precision hull
diff --git a/Code/hull_almost_exact.cpp b/Code/hull_almost_exact.cpp
new file mode 100755
index 0000000..a561335
--- /dev/null
+++ b/Code/hull_almost_exact.cpp
@@ -0,0 +1,310 @@
+#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 ;
+}
diff --git a/Code/hull_permutation.cpp b/Code/hull_permutation.cpp
new file mode 100755
index 0000000..83b0fe5
--- /dev/null
+++ b/Code/hull_permutation.cpp
@@ -0,0 +1,293 @@
+#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
+
+struct vec {
+  vec() {} ;
+
+  vec(float x, float y) : x(x), y(y) {}
+  float x ;
+  float y ;
+
+  vec operator-(const vec& v) const {
+    return vec(x - v.x, y - v.y) ;
+  }
+
+  vec operator+(const vec& v) const {
+    return vec(x + v.x, y + v.y) ;
+  }
+
+  vec operator*(float a) const {
+    return vec(a*x, a*y) ;
+  }
+
+  float length2() const {
+    return x*x + y*y ;
+  }
+  
+  float length() const {
+    return sqrt(length2()) ;
+  }
+
+  bool lexicoinf(const vec& v) const {
+    if(x == v.x) return y < v.y ;
+    return x < v.x ;
+  }
+} ;
+
+//}}}
+
+int sign(float 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
+  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
+  float 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) ;
+  float 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 ;
+}
diff --git a/Code/hull_perturbation.cpp b/Code/hull_perturbation.cpp
new file mode 100755
index 0000000..54ffc51
--- /dev/null
+++ b/Code/hull_perturbation.cpp
@@ -0,0 +1,277 @@
+#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
+
+struct vec {
+  vec() {} ;
+
+  vec(float x, float y) : x(x), y(y) {}
+  float x ;
+  float y ;
+
+  vec operator-(const vec& v) const {
+    return vec(x - v.x, y - v.y) ;
+  }
+
+  vec operator+(const vec& v) const {
+    return vec(x + v.x, y + v.y) ;
+  }
+
+  vec operator*(float a) const {
+    return vec(a*x, a*y) ;
+  }
+
+  float length2() const {
+    return x*x + y*y ;
+  }
+  
+  float length() const {
+    return sqrt(length2()) ;
+  }
+} ;
+
+//}}}
+
+int sign(float x) {
+  if(x > 0) return 1 ;
+  if(x < 0) return -1 ;
+  return 0 ;
+}
+
+int orient(const vec& v0, const vec& v1) {
+  //determinant
+  float d = v0.x*v1.y - v0.y*v1.x ;
+  if(d != 0) return sign(d) ;
+
+  //enter perturbation with perturbation matrix
+  //  [ e   e^2 ]
+  //  [ e^2 e^2 ]
+  if(v1.y != 0) return sign(v1.y) ;
+  float a = v0.x - v0.y - v1.x ;
+  if(a != 0) return sign(a) ;
+  return 1 ;
+
+  //other perturbations work, for instance
+  //  [ e  0   ]
+  //  [ 0  e^2 ]
+  if(v1.y != 0) return sign(v1.y) ;
+  if(v0.x != 0) return sign(v0.x) ;
+  return 1 ;
+}
+
+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 ;
+}
-- 
GitLab