alms-0.6.5: examples/session-types-polygons2.alms
(* Sutherland-Hodgman (1974) re-entrant polygon clipping *)
#load "libsessiontype2"
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 is_point_above_plane : 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 is_point_above_plane { x, y, z } { a, b, c, d } =
a *. x +. b *. y +. c *. z +. d >. 0.0
let intersect (p₁, p₂) ({ a, b, c, d } as plane) =
if is_point_above_plane p₁ plane == is_point_above_plane p₂ plane
then None
else let t = (a *. p₁.x +. b *. p₁.y +. c *. p₁.z +. d) /.
(a *. (p₁.x -. p₂.x) +.
b *. (p₁.y -. p₂.y) +.
c *. (p₁.z -. p₂.z)) in
let x = p₁.x +. (p₂.x -. p₁.x) *. t in
let y = p₁.y +. (p₂.y -. p₁.y) *. t in
let z = p₁.z +. (p₂.z -. p₁.z) *. t in
Some { x, y, z }
end
open Geometry
(* The protocol *)
type `a stream = ?->(`a step)
and `a step = Done of 1 channel
| Next of (?`a; `a stream) channel
(*
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
!(ic: point stream channel, oc: point stream dual channel) =
let finish () = choose Done oc in
let put p = choose Next oc; send p oc in
let putCross p₁ p₂ =
match intersect (p₁, p₂) plane with
| Some p → put p
| None → () in
let putVisible p =
if is_point_above_plane p plane
then put p
else ()
in follow ic;
match ic with
| Done ic → finish ()
| Next ic →
let p₀ = recv ic in
let rec loop p =
putVisible p;
follow ic;
match ic with
| Done ic →
putCross p p₀;
finish ()
| Next ic →
let p′ = recv ic in
putCross p p′;
loop p′
in loop p₀
let rec printer !(ic: point stream channel) =
follow ic;
match ic with
| Done ic → ()
| Next ic → putStrLn (string_of_point (recv ic));
printer ic
-- The main protocol for the program, which lets us split our parser
-- from our main loop.
type main_prot = ?->main2
and main2 = Planes of (?plane; main_prot) channel
| Points of point stream channel
let parser !(oc: main_prot dual channel) =
let rec plane_loop () =
match getLine () with
| "" → choose Points oc;
point_loop ()
| s → choose Planes oc;
send (plane_of_string s) oc;
plane_loop ()
and point_loop () =
match getLine () with
| "" → choose Done oc
| s → choose Next oc;
send (point_of_string s) oc;
point_loop ()
in plane_loop ()
let main () =
let rec get_planes (acc: plane list) !(ic: main_prot channel) =
follow ic;
match ic with
| Points ic → rev acc
| Planes ic → get_planes (recv ic ∷ acc) ic in
let connect plane (ic : point stream channel) =
let outrv = newRendezvous () in
Thread.fork (λ_ → clipper plane (ic, accept outrv); ());
request outrv in
let rv = newRendezvous () in
let _ = Thread.fork (λ_ → parser (accept rv); ()) in
let (planes, ic) = get_planes [] (request rv) in
let ic = foldl connect ic planes
in
printer ic
in main ()