Find the points which form a convex hull from a set of arbitrary two dimensional points.

For example, given the points (16,3), (12,17), (0,6), (-4,-6), (16,6), (16,-7), (16,-3), (17,-4), (5,19), (19,-8), (3,16), (12,13), (3,-4), (17,5), (-3,15), (-3,-9), (0,11), (-9,-3), (-4,-2) and (12,10) the convex hull would be (-9,-3), (-3,-9), (19,-8), (17,5), (12,17), (5,19) and (-3,15).




See also
 Convex Hull (youtube)
 http://www.geeksforgeeks.org/convex-hull-set-2-graham-scan/


(*
 * Convex hulls by Andrew's monotone chain algorithm.
 *
 * For a description of the algorithm, see
 * https://en.wikibooks.org/w/index.php?title=Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain&stableid=40169
 *)
 
(*------------------------------------------------------------------*)
(* Just enough plane geometry for our purpose.  *)
 
module Plane_point =
  struct
    type t = float * float
 
    let make (xy : t) = xy
    let to_tuple ((x, y) : t) = (x, y)
 
    let x ((x, _) : t) = x
    let y ((_, y) : t) = y
 
    let equal (u : t) (v : t) = (x u = x v && y u = y v)
 
    (* Impose a total order on points, making it one that will work
       for Andrew's monotone chain algorithm. *)
    let order (u : t) (v : t) =
      (x u < x v) || (x u = x v && y u < y v)
 
    (* The Array module's sort routines expect a "cmp" function. *)
    let cmp u v =
      if order u v then
        (-1)
      else if order v u then
        (1)
      else
        (0)
 
    (* Subtraction is really a vector or multivector operation. *)
    let sub (u : t) (v : t) = make (x u -. x v, y u -. y v)
 
    (* Cross product is really a multivector operation. *)
    let cross (u : t) (v : t) = (x u *. y v) -. (y u *. x v)
 
    let to_string ((x, y) : t) =
      "(" ^ string_of_float x ^ " " ^ string_of_float y ^ ")"
  end
;;
 
(*------------------------------------------------------------------*)
(* We want something akin to array_delete_neighbor_dups of Scheme's
   SRFI-132. *)
 
let array_delete_neighbor_dups equal arr =
  (* Returns a Seq.t rather than an array. *)
  let rec loop i lst =
    (* Cons a list of non-duplicates, going backwards through the
       array so the list will be in forwards order. *)
    if i = 0 then
      arr.(i) :: lst
    else if equal arr.(i - 1) arr.(i) then
      loop (i - 1) lst
    else
      loop (i - 1) (arr.(i) :: lst)
  in
  let n = Array.length arr in
  List.to_seq (if n = 0 then [] else loop (n - 1) [])
;;
 
(*------------------------------------------------------------------*)
(* The convex hull algorithm. *)
 
let cross_test pt_i hull j =
  let hull_j = hull.(j)
  and hull_j1 = hull.(j - 1) in
  0.0 < Plane_point.(cross (sub hull_j hull_j1)
                       (sub pt_i hull_j1))
 
let construct_lower_hull n pt =
  let hull = Array.make n (Plane_point.make (0.0, 0.0)) in
  let () = hull.(0) <- pt.(0)
  and () = hull.(1) <- pt.(1) in
  let rec outer_loop i j =
    if i = n then
      j + 1
    else
      let pt_i = pt.(i) in
      let rec inner_loop j =
        if j = 0 || cross_test pt_i hull j then
          begin
            hull.(j + 1) <- pt_i;
            j + 1
          end
        else
          inner_loop (j - 1)
      in
      outer_loop (i + 1) (inner_loop j)
  in
  let hull_size = outer_loop 2 1 in
  (hull_size, hull)
 
let construct_upper_hull n pt =
  let hull = Array.make n (Plane_point.make (0.0, 0.0)) in
  let () = hull.(0) <- pt.(n - 1)
  and () = hull.(1) <- pt.(n - 2) in
  let rec outer_loop i j =
    if i = (-1) then
      j + 1
    else
      let pt_i = pt.(i) in
      let rec inner_loop j =
        if j = 0 || cross_test pt_i hull j then
          begin
            hull.(j + 1) <- pt_i;
            j + 1
          end
        else
          inner_loop (j - 1)
      in
      outer_loop (i - 1) (inner_loop j)
  in
  let hull_size = outer_loop (n - 3) 1 in
  (hull_size, hull)
 
let construct_hull n pt =
 
  (* Side note: Construction of the lower and upper hulls can be done
     in parallel. *)
  let (lower_hull_size, lower_hull) = construct_lower_hull n pt
  and (upper_hull_size, upper_hull) = construct_upper_hull n pt in
 
  let hull_size = lower_hull_size + upper_hull_size - 2 in
  let hull = Array.make hull_size (Plane_point.make (0.0, 0.0)) in
 
  begin
    Array.blit lower_hull 0 hull 0 (lower_hull_size - 1);
    Array.blit upper_hull 0 hull (lower_hull_size - 1)
      (upper_hull_size - 1);
    hull
  end
 
let plane_convex_hull points =
  (* Takes an arbitrary sequence of points, which may be in any order
     and may contain duplicates. Returns an ordered array of points
     that make up the convex hull. If the sequence of points is empty,
     the returned array is empty. *)
  let pt = Array.of_seq points in
  let () = Array.fast_sort Plane_point.cmp pt in
  let pt = Array.of_seq
             (array_delete_neighbor_dups Plane_point.equal pt) in
  let n = Array.length pt in
  if n <= 2 then
    pt
  else
    construct_hull n pt
;;
 
(*------------------------------------------------------------------*)
 
let example_points =
  [Plane_point.make (16.0, 3.0);
   Plane_point.make (12.0, 17.0);
   Plane_point.make (0.0, 6.0);
   Plane_point.make (-4.0, -6.0);
   Plane_point.make (16.0, 6.0);
   Plane_point.make (16.0, -7.0);
   Plane_point.make (16.0, -3.0);
   Plane_point.make (17.0, -4.0);
   Plane_point.make (5.0, 19.0);
   Plane_point.make (19.0, -8.0);
   Plane_point.make (3.0, 16.0);
   Plane_point.make (12.0, 13.0);
   Plane_point.make (3.0, -4.0);
   Plane_point.make (17.0, 5.0);
   Plane_point.make (-3.0, 15.0);
   Plane_point.make (-3.0, -9.0);
   Plane_point.make (0.0, 11.0);
   Plane_point.make (-9.0, -3.0);
   Plane_point.make (-4.0, -2.0);
   Plane_point.make (12.0, 10.0)]
;;
 
let hull = plane_convex_hull (List.to_seq example_points)
;;
 
Array.iter
  (fun p -> (print_string (Plane_point.to_string p);
             print_string " "))
  hull;
print_newline ()
;;
 
(*------------------------------------------------------------------*)