[comp.lang.scheme] programming with user-level continuations

shivers@BRONTO.SOAR.CS.CMU.EDU (Olin Shivers) (03/22/90)

Aamod Sane wrote:
    I would also like examples/references on the Continuation
    Passing style (just building of lambdas, not the call/cc variety). 
    I know of examples such as gcd of a list where you can escape
    if a 1 is encountered without doing any computation, by building lambdas
    and using them only if 1 is not found.

I think the Sutherland-Hodgmann polygon clipping algorithm is a lovely
example. Here is one I wrote in T in 1983 for part of an object-oriented
graphics system in T. The clipper itself is in GEN-POLYGON-CLIPPER.
The rest of the code is auxiliaries and clients. MAKE-CAMERA-CLIPPER
shows how to build up a pipeline of clippers.

Dialect/ideolect specifics:
    fl+, fl-, and friends are T's floating-point specific math functions.
    ? means COND; := means SET or SET! in my personal ideolect.
    I assume T is a constant bound to a true value and NIL is a constant
        bound to the the empty list/false value. This code was written in
        1983. There was no #T, or #F; () was the false value.
    FOR is the UCI Lisp FOR macro, ported to T.
	-Olin

===============================================================================
(herald planeint (env t (graphics points) (tlib hacks) (tutil for)))
;(require hacks  (tlib hacks))
;(require points (graphics points))
;;; This file contains functions for doing operations on points and
;;; planes.


;;; plane-sign tells which side of a plane the point is on. It is negative
;;; if the pt is on the negative side, 0 if it is in the plane, and positive
;;; if the pt is on the positive side.
(define-integrable (plane-sign plane pt)
  (fl+ (plane:d plane) (dot-prod plane pt)))

;;; crosses-plane? returns true <==> pt0 and pt1 are on opposite sides of
;;; the plane. (minus? (* (plane-sign plane pt0) (plane-sign plane pt1)))
(define (crosses-plane? plane pt0 pt1)
   (fl> 0.0 (fl* (plane-sign plane pt0) (plane-sign plane pt1))))


;;; plane-intersect gives the point which is the intersection of plane and
;;; the line running through pt0 and pt1. If the plane is (P; d) and
;;; the points are A and B, the intersection parameter s is
;;;      P.A - d
;;; s =  -------
;;;      P.(A-B)

(define (plane-intersect plane pt0 pt1)
  (let ((delta (pt- pt1 pt0)))
    (if (pt-zero? delta)
      (error "plane-intersect: degenerate line segment (~s,~s)~%" pt0 pt1)
      (let ((denom (fl- 0.0 (dot-prod plane delta))))
	(if (fl= 0.0 denom)
	  (error "plane-intersect: line (~s,~s) is parallel to plane ~s~%"
		 pt0 pt1 plane)
	  (let ((s (fl/ (fl- (dot-prod plane pt0) (plane:d plane)) denom)))
	    (pt+ pt0 (pt* delta s))))))))


;;; gen-polygon-clipper takes a plane and a continuation as args. It
;;; returns a closure that, when called on successive points in a polygon,
;;; clips them against the plane and sends them on to the continuation.
;;; The end of the polygon is signalled by calling the clipper on T or nil.
;;; If the closure is called on T, the polygon is closed, i.e. the previous
;;; point is connected to the first point of the polygon. In either case,
;;; the terminal T/nil is passed along, in case the continuation is another
;;; clipping stage.

(define (gen-polygon-clipper plane cont)
  (let ((first-pt nil) (pt0 nil))

    (lambda (x)
      (? ((eq? x t) ;x=t means time to close the polygon
	   (if (and first-pt (crosses-plane? plane pt0 first-pt))
	       (cont (plane-intersect plane pt0 first-pt)))
	   (set first-pt nil)
	   (cont t)) ;pass along the close signal

	 (x ;x is the next point in the polygon
	  (if (not first-pt) (set first-pt x)
	      (if (crosses-plane? plane pt0 x)              ;edge crosses ==>
		  (cont (plane-intersect plane x pt0))))    ;output intersection
	  (set pt0 x)
	  (if (fl<= 0.0 (plane-sign plane pt0)) (cont pt0)))

	 (t ;null x means the stream is done, but do not close back to first-pt
	  (set first-pt nil)
	  (cont nil) ;pass along the close signal
	  )))))

(define (gen-polygon-splitter plane cont1 cont2)
  (let ((first-pt nil) (pt0 nil))
    (lambda (x)
      (? ((eq? x t) ;x=t means time to close the split polys
	  (if (crosses-plane? plane pt0 first-pt)
	    (let ((i (plane-intersect plane pt0 first)))
	      (cont1 i)
	      (cont2 i)))
	  (set first-pt nil)
	  (cont1 t)             ;pass along the close signal
	  (cont2 t))

	 (x
	  (if (null? first-pt) (set first-pt x)
	    (if (crosses-plane? plane pt0 x)
	      (let ((i (plane-intersect plane pt0 x)))
		(cont1 i)
		(cont2 i))))
	  (set pt0 x)
	  (let ((s (plane-sign plane pt0)))
	    (if (fl<= 0.0 s) (cont1 pt0))
	    (if (fl>= 0.0 s) (cont2 pt0))))

	 (t ;null x means the stream is done, but do not close back to first-pt
	  (set first-pt nil)
	  (cont1 nil)           ;pass along the done signal
	  (cont2 nil)
	  )))))

(define (clip-polygon polygon plane)
  (let ((pts (disclose-pts polygon))
	(newpts nil))
    (let ((clipper (gen-polygon-clipper plane (lambda (x) (push newpts x)))))
      ;;feed the points to the polygon clipper
      (for (x in pts)
	   (do (clipper x)))
      ;;close the polygon
      (clipper t)
      ;;return the polygon whose points are newpts
      (polygon:new (reverse! (cdr newpts))))))

(define-integrable (make-camera-clipper top bot left right)
  (let* ( (clipped-pts nil)
	  (clipper (gen-polygon-clipper top
		       (gen-polygon-clipper bot
			   (gen-polygon-clipper left
			       (gen-polygon-clipper right
				   (gen-polygon-clipper *hither-plane*
				       (gen-polygon-clipper *yon-plane*
					   (lambda (p)
					       (push clipped-pts p))))))))) )
    (lambda (pts close?)
      (:= clipped-pts nil)
      (for (p in pts) (do (clipper p)))
      (clipper close?)
      (reverse! (cdr clipped-pts))) ))