############################################################################
#
#	File:     clipping.icn
#
#	Subject:  Procedures for clipping lines
#
#	Authors:  William S. Evans and Gregg M. Townsend
#
#	Date:     June 16, 2000
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	ClipLine(W, L, x, y, w, h) clips the multisegment line specified
#	by coordinates in L to the region (x, y, w, h), which defaults
#	to the clipping region of the window W.  ClipLine() returns a
#	list of coordinates suitable for calling DrawSegment().  If no
#	segments remain after clipping, ClipLine() fails.
#
#	Coalesce(L) connects adjoining segments from a DrawSegment()
#	argument list such as is produced by ClipLine().  Coalesce()
#	returns a list of DrawLine() lists.
#
#	DrawClipped(W, x1, y1, x2, y2, ...) draws a line using ClipLine()
#	with the clipping region of the window W.  DrawClipped() is
#	superior to DrawLine() only when lines with extremely large
#	coordinate values (beyond +/-32767) are involved.
#
############################################################################

 
#  DrawClipped(W, x1, y1, x2, y2, ...) -- draw line using ClipLine()

procedure DrawClipped(a[])		#: draw line with clipping
   local win

   if type(a[1]) == "window" then
      win := pop(a)
   else
      win := &window

   DrawSegment ! push(ClipLine(win, a), win)
   return win
end


#  ClipLine(W, L, x, y, w, h) -- clip polyline to region, returning segments.
#
#  Cyrus-Beck parametric line clipping with Liang-Barsky
#  optimizations for axis-aligned rectangular clipping regions.

procedure ClipLine(win, L, x, y, w, h)	#: clip line for DrawSegment
   local i, ret, tin, tout, delx, dely, x0, x1, xmax, y0, y1, ymax

   if (type(win) == "list") then		# window param is optional
      return ClipLine(&window, win, L, x, y, w)

   /x := WAttrib(win, "clipx") - WAttrib(win, "dx")
   /y := WAttrib(win, "clipy") - WAttrib(win, "dy")
   /w := WAttrib(win, "clipw")
   /h := WAttrib(win, "cliph")
   if w < 0 then
      x -:= (w := -w)
   if h < 0 then
      y -:= (h := -h)
   xmax := x + w
   ymax := y + h

   ret := []
   x1 := L[1]
   y1 := L[2]

   every i := 3 to *L by 2 do {
      x0 := x1
      y0 := y1
      x1 := L[i]
      y1 := L[i + 1]
      tin := 0.0
      tout := 1.0

      delx := real(x1 - x0)
      if delx < 0.0 then {
         tin <:= (xmax - x0) / delx
         tout >:= (x - x0) / delx
         }
      else if delx > 0.0 then {
         tin <:= (x - x0) / delx
         tout >:= (xmax - x0) / delx
         }
      else
         x <= x0 <= xmax | next
      if tout < tin then next
   
      dely := real(y1 - y0)
      if dely < 0.0 then {
         tin <:= (ymax - y0) / dely
         tout >:= (y - y0) / dely
         }
      else if dely > 0.0 then {
         tin <:= (y - y0) / dely
         tout >:= (ymax - y0) / dely
         }
      else
         y <= y0 <= ymax | next
      if tout < tin then next

      put(ret, x0 + tin*delx, y0 + tin*dely, x0 + tout*delx, y0 + tout*dely)
      }

   if *ret > 0 then
      return ret
   else
      fail
end


#  Coalesce(L) -- connect adjoining segments

procedure Coalesce(L)			#: connect adjoining segments
   local i, all, seg, x1, y1, x2, y2

   all := []
   every i := 1 to *L by 4 do {
      x1 := L[i]
      y1 := L[i + 1]
      if x1 ~=== x2 | y1 ~=== y2 then
         put(all, seg := [x1, y1])
      put(seg, x2 := L[i + 2], y2 := L[i + 3])
      }

   return all
end
