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 ()