Updated 2013-07-30 07:58:23 by uniquename

Keith Vetter 2003-03-24 : as I was writing TkGoldberg, I found it difficult dealing with complex canvas objects that were composed of arcs and polygons. These objects were hard to fill with a color, especially if the arc is a concave part of the object. Also, arcs don't rotate well (see Canvas Rotation).

I realize now that if I could have decomposed the complex object into one polygon then both of my problems would have been solved. But to do that I would need to convert arc into a vertex list.

The code below does just that. It is a generalization of Regular polygons in that it takes "-start", "-extent" and "-sides" as optional addition parameters.

escargo 24 Mar 2003 - There's a little glitch that appears on a seven-sided figure. If start is set to zero, the glitch appears on the far right-hand side....

KPV oops, step size should have been a double, not an int.

escargo 24 Mar 2003 - Is there any reason that the minimum number of sides should not be 3? I don't find the 1 and 2 case to be very interesting. (You wanted to show a circle and treat 0 the same as an infinite number of sides?)

KPV I was following the convention in Regular Polygons where the number of sides equal to zero is a circle. I really want a scale that goes 0, 3, 4, ... but since this is just throw away demo code I let it stand as is.
 proc rp2 {x0 y0 x1 y1 args} {
    
    array set V {-sides 0 -start 90 -extent 360} ;# Default values
    foreach {a value} $args {
        if {! [info exists V($a)]} {error "unknown option $a"}
        if {$value == {}} {error "value of \"$a\" missing"}
        set V($a) $value
    }
    if {$V(-extent) == 0} {return {}}
    
    set xm [expr {($x0+$x1)/2.}]
    set ym [expr {($y0+$y1)/2.}]
    set rx [expr {$xm-$x0}]
    set ry [expr {$ym-$y0}]
 
    set n $V(-sides)
    if {$n == 0} {                              ;# 0 sides => circle
        set n [expr {round(($rx+$ry)*0.5)}]
        if {$n < 2} {set n 4}
    }
    
    set dir [expr {$V(-extent) < 0 ? -1 : 1}]   ;# Extent can be negative
    if {abs($V(-extent)) > 360} {
        set V(-extent) [expr {$dir * (abs($V(-extent)) % 360)}]
    }
    set step [expr {$dir * 360.0 / $n}]
    set numsteps [expr {1 + double($V(-extent)) / $step}]
                              
    set xy {}
    set DEG2RAD [expr {4*atan(1)*2/360}]
                              
    for {set i 0} {$i < int($numsteps)} {incr i} {
        set rad [expr {($V(-start) - $i * $step) * $DEG2RAD}]
        set x [expr {$rx*cos($rad)}]
        set y [expr {$ry*sin($rad)}]
        lappend xy [expr {$xm + $x}] [expr {$ym - $y}]
    }
 
    # Figure out where last segment should end
    if {$numsteps != int($numsteps)} {
        # Vecter V1 is last drawn vertext (x,y) from above
        # Vector V2 is the edge of the polygon
        set rad2 [expr {($V(-start) - int($numsteps) * $step) * $DEG2RAD}]
        set x2 [expr {$rx*cos($rad2) - $x}]
        set y2 [expr {$ry*sin($rad2) - $y}]
 
        # Vector V3 is unit vector in direction we end at
        set rad3 [expr {($V(-start) - $V(-extent)) * $DEG2RAD}]
        set x3 [expr {cos($rad3)}]
        set y3 [expr {sin($rad3)}]
 
        # Find where V3 crosses V1+V2 => find j s.t.  V1 + kV2 = jV3
        set j [expr {($x*$y2 - $x2*$y) / ($x3*$y2 - $x2*$y3)}]
 
        lappend xy [expr {$xm + $j * $x3}] [expr {$ym - $j * $y3}]
    }
    return $xy
 }
 
 # Now for code to demonstrate and test it
 proc DrawIt {args} {
    global S
    
    foreach {x0 y0 x1 y1} [.c cget -scrollregion] break
    set bbox [list 20 20 [incr x1 -20] [incr y1 -20]]
    
    .c delete poly
    set xy [eval rp2 $bbox -sides $S(sides) -start $S(start) -extent 360]
    .c create poly $xy -fill {} -outline black -width 2 -dash - -tag poly
    set xy [eval rp2 $bbox -sides $S(sides) -start $S(start) -extent $S(extent)]
    .c create poly $xy -fill red -outline {} -tag poly
    .c create line $xy -fill red -fill black -width 3 -tag poly
 }
 
 pack [frame .bottom] -side bottom -fill x
 pack [canvas .c -width 500 -height 500 -bd 2 -relief raised] -fill both -expand 1
 bind .c <Configure> {%W config -scrollregion [list 0 0 %w %h] ; DrawIt}
 
 scale .sides -variable S(sides) -orient h -from 0 -to 10 -label Sides -relief ridge
 scale .start -variable S(start) -orient h -from 0 -to 360 -label Start -relief ridge
 scale .extent -variable S(extent) -orient h -from -360 -to 360 -label Extent -relief ridge
 
 pack .sides .start .extent -side left -in .bottom
 array set S {extent 135 sides 4 start 0}
 trace variable S w DrawIt

uniquename 2013jul29

This code could use an image to show what it produces:

(Thanks to 'gnome-screenshot', 'mtpaint', and ImageMagick 'convert' for, respectively, image capture to PNG file, cropping the image, and converting the image to a JPEG file that was at least 6 times smaller than the PNG.)

In the GUI, the 'Sides' slider can be used to change the number of sides of the polygon from its initial default of 4. The 'Start' slider is used to rotate the polygon. The 'Extent' slider is used to increase/decrease the amount of red 'fill' in the polygon.