Posted to tcl by kbk at Wed Mar 10 23:00:23 GMT 2010view raw
- # Find intersection of an arbitrary polygon with a convex one.
- # cw --
- #
- # Does the path (x0,y0)->(x1,y1)->(x2,y2) turn clockwise
- # or counterclockwise?
- #
- # Parameters:
- # x0, y0 - First point
- # x1, y1 - Second point
- # x2, y2 - Third point
- #
- # Results:
- # Returns 1 if point 2 is to the right of a line joining points 0
- # and 1, and -1 if it is to the left of the line.
- #
- # If the three points are collinear, returns 1 if the third point is
- # the middle point, -1 if point 0 is the middle point, 0 if
- # point 1 is the middle point
- proc cw {x0 y0 x1 y1 x2 y2} {
- set dx1 [expr {$x1 - $x0}]; set dy1 [expr {$y1 - $y0}]
- set dx2 [expr {$x2 - $x0}]; set dy2 [expr {$y2 - $y0}]
- # (0,0,$dx1*$dy2 - $dx2*$dy1) is the crossproduct of
- # ($x1-$x0,$y1-$y0,0) and ($x2-$x0,$y2-$y0,0).
- # Its z-component is positive if the turn
- # is clockwise, negative if the turn is counterclockwise.
- set pr1 [expr {$dx1 * $dy2}]
- set pr2 [expr {$dx2 * $dy1}]
- if {$pr1 > $pr2} {
- # Clockwise
- return 1
- } elseif {$pr1 < $pr2} {
- # Counter-clockwise
- return -1
- } elseif {$dx1*$dx2 < 0 || $dy1*$dy2 < 0} {
- # point 0 is the middle point
- return 0
- } elseif {($dx1*$dx1 + $dy1*$dy1) < ($dx2*$dx2 + $dy2+$dy2)} {
- # point 1 is the middle point
- return 0
- } else {
- # point 2 lies on the segment joining points 0 and 1
- return 1
- }
- }
- # intersect --
- #
- # Calculate the point of intersection of two lines
- # containing the line segments (x1,y1)-(x2,y2) and (x3,y3)-(x4,y4)
- #
- # Parameters:
- # x1,y1 x2,y2 - Endpoints of the first line segment
- # x3,y3 x4,y4 - Endpoints of the second line segment
- #
- # Results:
- # Returns a two-element list containing the point of intersection.
- # Returns an empty list if the line segments are parallel
- # (including the case where the segments are concurrent).
- proc intersect {x1 y1 x2 y2 x3 y3 x4 y4} {
- set d [expr {($y4 - $y3) * ($x2 - $x1)
- - ($x4 - $x3) * ($y2 - $y1)}]
- set na [expr {($x4 - $x3) * ($y1 - $y3)
- - ($y4 - $y3) * ($x1 - $x3)}]
- if {$d == 0} {
- return {}
- }
- set r [list \
- [expr {$x1 + $na * ($x2 - $x1) / $d}] \
- [expr {$y1 + $na * ($y2 - $y1) / $d}]]
- return $r
- }
- # pairs --
- #
- # Coroutine that yields the elements of a list in pairs
- #
- # Parameters:
- # list - List to decompose
- #
- # Immediate result:
- # Returns the name of the coroutine
- #
- # Further results:
- # Returns two-element ranges from the given list, one at a time.
- # Returns {} at the end of the iteration.
- proc pairs {list} {
- yield [info coroutine]
- foreach {x y} $list {
- yield [list $x $y]
- }
- return {}
- }
- # clipsegment --
- #
- # Clips one segment of a polygon against a line.
- #
- # Parameters:
- # inside0 - Flag = 1 if sx0,sy0 is to the right of the clipping line
- # cx0,cy1 cx1,cy1 - Two points determining the clipping line
- # sx0,sy0 sx1,sy1 - Two points determining the subject line
- #
- # Results:
- # Returns 1 if sx1,sy1 is to the right of the clipping line, 0 otherwise
- #
- # Yields:
- # Yields, in order:
- #
- # The intersection point of the segment and the line, if
- # the segment intersects the line.
- #
- # The endpoint of the segment, if the segment ends to the
- # right of the line.
- proc clipsegment {inside0 cx0 cy0 cx1 cy1 sx0 sy0 sx1 sy1} {
- set inside1 [expr {[cw $cx0 $cy0 $cx1 $cy1 $sx1 $sy1] > 0}]
- if {$inside1} {
- if {!$inside0} {
- set int [intersect $cx0 $cy0 $cx1 $cy1 \
- $sx0 $sy0 $sx1 $sy1]
- if {[llength $int] >= 0} {
- yield $int
- }
- }
- yield [list $sx1 $sy1]
- } else {
- if {$inside0} {
- set int [intersect $cx0 $cy0 $cx1 $cy1 \
- $sx0 $sy0 $sx1 $sy1]
- if {[llength $int] >= 0} {
- yield $int
- }
- }
- }
- return $inside1
- }
- # clipstep --
- #
- # Coroutine to perform one step of Sutherland-Hodgman polygon clipping
- #
- # Parameters:
- # source - Name of a coroutine that will return the vertices of a
- # subject polygon to clip, and return {} at the end of the
- # iteration.
- # cx0,cy0 cx1,cy1 - Endpoints of an edge of the clip polygon
- #
- # Immediate result:
- # Returns the name of the coroutine
- #
- # Further results:
- # Returns the vertices of the clipped polygon
- proc clipstep {source cx0 cy0 cx1 cy1} {
- yield [info coroutine]
- set pt0 [{*}$source]
- if {[llength $pt0] == 0} {
- return
- }
- lassign $pt0 sx0 sy0
- set inside0 [expr {[cw $cx0 $cy0 $cx1 $cy1 $sx0 $sy0] > 0}]
- set finished 0
- while {!$finished} {
- set thispt [{*}$source]
- if {[llength $thispt] == 0} {
- set thispt $pt0
- set finished 1
- }
- lassign $thispt sx1 sy1
- set inside0 [clipsegment $inside0 \
- $cx0 $cy0 $cx1 $cy1 $sx0 $sy0 $sx1 $sy1]
- set sx0 $sx1
- set sy0 $sy1
- }
- return {}
- }
- # clippoly --
- #
- # Perform Sutherland-Hodgman polygon clipping
- #
- # Parameters:
- # cpoly - Coordinates of the clip polygon, listed in clockwise order.
- # spoly - Coordinates of the subject polygon, listed in clockwise order.
- #
- # Results:
- # Returns the coordinates of the clipped polygon, listed in clockwise
- # order.
- #
- # The clip polygon must be convex. The subject polygon may be any polygon,
- # including degenerate and self-intersecting ones.
- proc clippoly {cpoly spoly} {
- variable clipindx
- set source [coroutine clipper[incr clipindx] \
- pairs $spoly]
- set cx0 [lindex $cpoly end-1]
- set cy0 [lindex $cpoly end]
- foreach {cx1 cy1} $cpoly {
- set source [coroutine clipper[incr clipindx] \
- clipstep $source $cx0 $cy0 $cx1 $cy1]
- set cx0 $cx1; set cy0 $cy1
- }
- set result {}
- while {[llength [set pt [{*}$source]]] > 0} {
- lappend result {*}$pt
- }
- return $result
- }
- if {![info exists ::argv0] || [string compare $::argv0 [info script]]} {
- return
- }
- package require Tk
- grid [canvas .c -width 400 -height 400 -background \#ffffff]
- proc demonstrate {cpoly spoly} {
- .c create polygon {*}$cpoly -outline \#ff9999 -fill {} \
- -width 5
- .c create polygon {*}$spoly -outline \#9999ff -fill {} \
- -width 3
- .c create polygon {*}[clippoly $cpoly $spoly] \
- -fill \#99ff99 -outline black -width 1
- }
- demonstrate {100 100 300 100 300 300 100 300} \
- {50 150 200 50 350 150 350 300 250 300 200 250 150 350 100 250 100 200}