packages feed

alms-0.6.5: examples/session-types-polygons.alms

-- Sutherland-Hodgman (1974) re-entrant polygon clipping

#load "libthread"
#load "libsessiontype"

open SessionType

(*
We first build a tiny 3-D geometry library
*)
module type GEOMETRY = sig
  (* Points, planes, and line segments in R³ *)
  type point   = { x, y, z : float }
  type plane   = { a, b, c, d : float }
  type segment = point × point

  (*
    The plane { a, b, c, d } represents the open half-space
    { (x, y, z) | ax + by + cz + d > 0 }
  *)

  val string_of_point : point → string
  val string_of_plane : plane → string

  val point_of_string : string → point
  val plane_of_string : string → plane

  (* Is the point above the plane?  (i.e., in the semi-space) *)
  val isPointAbovePlane         : point → plane → bool

  (* Does the line segment between the two points intersect the plane,
     and if so, where? *)
  val intersect       : segment → plane → point option
end

module Geometry : GEOMETRY = struct
  type point   = { x, y, z : float }
  type plane   = { a, b, c, d : float }
  type segment = point × point

  let string_of_point p =
      "(" ^ string_of p.x ^ ", " ^ string_of p.y ^ ", " ^ string_of p.z ^ ")"

  let string_of_plane {a, b, c, d} =
      string_of a ^ "x + " ^ string_of b ^ "y + " ^
      string_of c ^ "z + " ^ string_of d ^ " > 0"

  (* Some of this should be in the library! *)
  let splitWhile pred =
    let rec loop acc xs =
              match xs with
              | []      → (rev acc, [])
              | x ∷ xs' → if pred x
                            then loop (x ∷ acc) xs'
                            else (rev acc, xs)
     in loop []

  let notp = compose not

  let isSpace = function
    | ' '  → true
    | '\t' → true
    | '\n' → true
    | '\r' → true
    | _    → false

  let dropSpace = compose snd (splitWhile isSpace)

  let point_of_string s =
    let foil = compose float_of_string implode in
      let cs = explode s in
      let ('(' ∷ cs) = dropSpace cs in
      let (x, (_ ∷ cs)) = splitWhile (notp ((==) ',')) (dropSpace cs) in
      let (y, (_ ∷ cs)) = splitWhile (notp ((==) ',')) (dropSpace cs) in
      let (z, (_ ∷ cs)) = splitWhile (notp ((==) ')')) (dropSpace cs) in
        { x = foil x, y = foil y, z = foil z }

  let plane_of_string s =
    let foil = compose float_of_string implode in
      let cs = explode s in
      let (a, (_ ∷ cs)) = splitWhile (notp ((==) 'x')) (dropSpace cs) in
      let ('+' ∷ cs)    = dropSpace cs in
      let (b, (_ ∷ cs)) = splitWhile (notp ((==) 'y')) (dropSpace cs) in
      let ('+' ∷ cs)    = dropSpace cs in
      let (c, (_ ∷ cs)) = splitWhile (notp ((==) 'z')) (dropSpace cs) in
      let ('+' ∷ cs)    = dropSpace cs in
      let (d, (_ ∷ cs)) = splitWhile (notp ((==) '>')) (dropSpace cs) in
      let ('0' ∷ cs)    = dropSpace cs in
        { a = foil a, b = foil b, c = foil c, d = foil d }

  let isPointAbovePlane { x, y, z } { a, b, c, d } =
    a *. x +. b *. y +. c *. z +. d >. 0.0

  let intersect (p1, p2) ({ a, b, c, d } as plane) =
   if isPointAbovePlane p1 plane == isPointAbovePlane p2 plane
     then None
     else let t = (a *. p1.x +. b *. p1.y +. c *. p1.z +. d) /.
                  (a *. (p1.x -. p2.x) +.
                   b *. (p1.y -. p2.y) +.
                   c *. (p1.z -. p2.z)) in
          let x = p1.x +. (p2.x -. p1.x) *. t in
          let y = p1.y +. (p2.y -. p1.y) *. t in
          let z = p1.z +. (p2.z -. p1.z) *. t in
            Some { x, y, z }
end

open Geometry

(*
Our protocol is to send an unbounded sequence of points:
*)
type `a stream = mu 's. 1 |&| ?`a; 's

(*
Each transducer takes a plane to clip by, and two rendezvous objects,
the first on which it expects to receive points, and the second on
which it will send points.
*)

let clipper (plane: plane)
            (ic: point stream channel)
            (oc: point stream dual channel) =
       let finish (oc: point stream dual channel) =
             sel1 oc; () in
       let put pt (oc: point stream dual channel) =
             send pt (sel2 oc) in
       let putCross p1 p2 (oc: point stream dual channel) =
             match intersect (p1, p2) plane with
             | Some pt → put pt oc
             | None    → oc in
       let putVisible pt (oc: point stream dual channel) =
             if isPointAbovePlane pt plane
               then put pt oc
               else oc in
         match follow ic with
         | Left _   → finish oc
         | Right ic →
             let (pt0, ic) = recv ic in
             let rec loop pt
                          (ic: point stream channel)
                          (oc: point stream dual channel) =
                       let oc = putVisible pt oc in
                         match follow ic with
                         | Left _   → let oc = putCross pt pt0 oc in
                                         finish oc
                         | Right ic → let (pt', ic) = recv ic in
                                       let oc = putCross pt pt' oc in
                                         loop pt' ic oc
               in loop pt0 ic oc

let rec printer ic =
  match follow ic with
  | Left _   → ()
  | Right ic → let (pt, ic) = recv ic in
      putStrLn (string_of_point pt);
      printer ic

-- The main protocol for the program, which lets us split our parser
-- from our main loop.
type main_prot = mu 's. point stream |&| ?plane; 's

let parser =
  let rec plane_loop (oc: main_prot dual channel) =
            match getLine () with
            | "" → point_loop (sel1 oc)
            | s  → let plane = plane_of_string s in
                    let oc    = send plane (sel2 oc) in
                      plane_loop oc
      and point_loop (oc: point stream dual channel) =
            match getLine () with
            | "" → sel1 oc; ()
            | s  → let point = point_of_string s in
                    let oc    = send point (sel2 oc) in
                      point_loop oc
   in plane_loop

let main () =
  let rec get_planes (acc : plane list) (ic: main_prot channel) =
            match follow ic with
            | Left ic  → (rev acc, ic)
            | Right ic → let (plane, ic) = recv ic in
                            get_planes (plane ∷ acc) ic in
  let rec connect planes (ic : point stream channel) =
            match planes with
            | []           → ic
            | plane ∷ rest →
                let outrv = newRendezvous () in
                  AThread.fork (λ _ → clipper plane ic (accept outrv));
                  connect rest (request outrv) in
  let rv           = newRendezvous () in
  AThread.fork (λ _ → parser (accept rv));
  let (planes, ic) = get_planes [] (request rv) in
    printer (connect planes ic)

in
  main ()