Keith Vetter 2002-11-25 - This is from an old graphics class I took back in 1994. It shows a person juggling from 3 to 20 balls in either the cascade or even ball juggling pattern.
I added the juggling man just last night--with the design borrowed from Flag signalling.
The goal of this program was not just to show a perfect juggler--although if you turn on the perfect button it will do that. Rather, the goal was to add errors into each throw and see how the juggler responds. I've remove much of that code, but still each throw is unique, based on how fast the ball must be released before the next ball lands.
One undocumented feature is that you can use the up and down arrows to zoom in and out.
KBK Lovely! Now if we can get Vince Darley to add site swap notation (http://www.juggling.org/help/siteswap/ ), we'll be all set.
Vince Ok, you've tempted me ;-)
AM If I remember correctly, the physics behind juggling is not at all trivial - you need a certain rhythmic motion which is difficult to learn. I do not remember the details, unfortunately.
##+################################################################ # # tkjuggler.tcl -- an interactive juggling program. # by Keith P. Vetter # # Revisions: # KPV Nov, 1994 - initial revision for UCB CS285, Fall 1994 # KPV Nov 25, 2002 - removed 3d YART support; added juggler # package require Tk ##+################################################################ # # Juggle -- controls the animation. Probable should rewrite using after to # avoid the update call, perhaps later. # proc juggle {{delta 1}} { global ss while {1} { ;# Go until button press incr ss(t) $delta ;# Another clock tick for {set j 0} {$j < $ss(num)} {incr j} {;# For each ball move_ball $ss(t) $j ;# Move it } move_hands update ;# Show it on the screen if {$ss(stop) || $delta == 0} return ;# Should we stop } } ##+############################################################### # # Init - Initializes all non-varying state variables # proc init {} { global ss set ss(pattern) cascade ;# Juggling pattern set ss(perfect) 0 ;# Add randomness set ss(stop) 1 ;# Animation on/off set ss(h) 300 ;# Height set ss(flight) 64 set ss(num) 5 set ss(v,h) $ss(h) ;# Display variants of above set ss(v,flight) $ss(flight) set ss(v,num) $ss(num) set ss(w) 140 set ss(s) 40 ;# Size of the ball set ss(s2) [expr {$ss(s)/2}] ;# Half the size of the ball set ss(startstop) Start set ss(scale) 1 set ss(colors) {magenta2 orange2 MediumPurple2 orchid2 SpringGreen2} lappend ss(colors) lightslateblue PaleVioletRed2 chartreuse DarkOrchid2 lappend ss(colors) lightslateblue PaleVioletRed2 chartreuse DarkOrchid2 lappend ss(colors) purple2 cyan2 goldenrod2 plum2 HotPink2 deepskyblue lappend ss(colors) firebrick2 slateblue1 maroon2 DarkGoldenrod2 lappend ss(colors) coral2 thistle2 skyblue2 } ##+############################################################### # # Display - Sets up the display with its canvas and sliders # proc display {} { global ss foreach w [winfo child .] { ;# Delete any existing widgets destroy $w } set ss(ch) [expr [winfo screenheight .] - 300];# Canvas height set ss(cw) 664 ;# Canvas width frame .ftop frame .fbot pack .fbot -side bottom -fill x pack .ftop -side top -expand 1 -fill both catch {image create photo ::img::blank -width 1 -height 1} make_canvas frame .fstop -relief sunken -bd 1 button .stop -textvariable ss(startstop) -command startstop -width 5 frame .fqbtn -relief sunken -bd 1 button .qbtn -text { Quit } -command exit button .about -image ::img::blank -command About -highlightthickness 0 pack .fqbtn .fstop -side right -expand yes -in .fbot pack .stop -in .fstop -side left -expand yes -padx 3m -pady 2m pack .qbtn -in .fqbtn -side left -expand yes -padx 3m -pady 2m bind .stop <2> juggle ;# Single step scale .s1 -label Height -orient horizontal -from 1 -to 1000 .s1 config -relief ridge -showvalue 1 -variable ss(v,h) scale .s2 -label "Flight Time" -orient horizontal -from 1 -to 500 .s2 config -relief ridge -showvalue 1 -variable ss(v,flight) scale .s4 -label Balls -orient horizontal -from 3 -to 20 .s4 config -relief ridge -showvalue 1 -variable ss(v,num) pack .s1 .s2 .s4 -side left -in .fbot bind .s1 <ButtonRelease-1> "set_value height" bind .s2 <ButtonRelease-1> "set_value flight" bind .s4 <ButtonRelease-1> "set_value balls" frame .frb ;# Radiobuttons for patterns radiobutton .cascade -text "Cascade" -var ss(pattern) \ -value cascade -command reinit -anchor w radiobutton .shower -text "Shower" -var ss(pattern) \ -value shower -command reinit -anchor w radiobutton .even -text "Even" -var ss(pattern) \ -value even -command reinit -anchor w pack .frb -side left -in .fbot -padx 1 pack .cascade .even -in .frb -side top -expand yes -anchor w -fill x frame .fcb ;# Checkbuttons for options checkbutton .crandom -text "Perfect" -var ss(perfect) -anc w checkbutton .cback -text "Outside" -var ss(back) -command reinit -anc w pack .fcb -side left -in .fbot -padx 1 pack .crandom .cback -in .fcb -side top -expand no -anchor w -fill x place .about -in .fbot -relx 1 -rely 0 -anchor ne wm withdraw . ;# Update to get sizes wm geom . +0+0 wm deiconify . wm title . "Tk Juggler" } ##+##################################################### # # Make_canvas - Creates the canvas on which all output will be done # proc make_canvas {} { global ss scrollbar .vscroll -relief sunken -command ".c yview" set c2 [expr {$ss(cw) / 2}] canvas .c -relief raised -borderwidth 2 -height $ss(ch) -width $ss(cw) \ -bg steelblue3 -highlightthickness 0 .c config -scrollregion [list -$c2 -1200 $c2 500] .c config -yscrollcommand ".vscroll set" -yscrollincrement 1 .c config -highlightcolor [.c cget -bg] .c yview moveto .4 flagman ;# Draws are flagman wink 0 pack .vscroll -in .ftop -side right -fill y pack .c -in .ftop -fill both -expand 1 bind .c <2> ".c scan mark %x %y" bind .c <B2-Motion> ".c scan drag %x %y" bind .c <MouseWheel> {%W yview scroll [expr {- (%D / 120) * 20}] units} bind .c <Configure> {Recenter %W %h %w} bind . <Up> {scaler 1} bind . <Down> {scaler 0} focus .c ;# So mouse wheel works } ##+###################################################### # # Recenter - Called when window gets resized. # proc Recenter {W h w} { set h [expr {$h / 2.0}] ; set w [expr {$w / 2.0}] $W config -scrollregion [list -$w -1200 $w 500] } ##+##################################################### # # Move_ball - Moves ball WHO to parameter value T. It flies in a # parabola going through points (-w,0), (0,height), (w,0). # proc move_ball {t w} { global ball ss switch $ball($w,ss) { "LR" { ;# Left to right toss place_obj ball$w [tossing $t $w] if {$t >= $ball($w,catch)} { ;# ...just got caught catch_ball $w 1 set ball($w,ss) HR } } "HR" { ;# Hold in right hand if {$t >= $ball($w,toss)} { ;# ...just got tossed set ball($w,ss) $ss(HR) toss_ball $w 1 place_obj ball$w [tossing $t $w] } } "RL" { ;# Right to left toss place_obj ball$w [tossing $t $w] if {$t == $ball($w,catch)} { ;# ...just got caught catch_ball $w 0 set ball($w,ss) HL } } "HL" { ;# Hold in left hand if {$t >= $ball($w,toss)} { ;# ...just got tossed set ball($w,ss) $ss(HL) toss_ball $w 0 place_obj ball$w [tossing $t $w] } } "SL" { ;# Start in left hand place_obj ball$w [tossing $ball($w,toss) $w] set ball($w,ss) "HL" } "SR" { ;# Start in right hand place_obj ball$w [tossing $ball($w,toss) $w] set ball($w,ss) "HR" } } } ##+##################################################### # # Tossing - Figures out the path for a ball: x,y # # u = (2t/sqrt(k)*f - 1) # x = w*u # y = kh * (1 - u^2) # proc tossing {time who} { global ball ss set t [expr {$time - $ball($who,toss)}] ;# Time since the toss set f $ball($who,flight) ;# Flight time set u [expr {-1 + 2.0 * $t / $f}] ;# range -1 to 1 set x [expr {$ball($who,x) + ((1 + $u)/2) * $ball($who,w)}] set y [expr {-($ball($who,kh) * (1 - $u * $u))}];# Y is a parabola return [list $x $y] } ##+##################################################### # # Create_hand - Creates a new hand, and put them anywhere on the canvas # proc create_hand {name} { global ss .c delete hand$name .c create arc 0 -$ss(s) $ss(s) $ss(s) -fill orange -outline orange \ -tag "hands hand$name hand_x$name" -start 0 -extent -180 foreach {x1 y1 x2 y2} [.c bbox hand_x$name] break set color [lindex [.c config -bg] 4] ;# Erasure color .c create arc $x1 0 $x2 $ss(s) -fill $color -outline "" \ -tag "hands hand$name hand_y$name" -start 0 -extent -180 .c lower hand$name .c lower flagman place_obj hand_x$name {0 0} -1 place_obj hand_y$name {0 0} -1 } ##+##################################################### # # Create_ball - Creates a new ball # proc create_ball {n} { global ss .c delete ball$n set color [lindex $ss(colors) 0] ;# Take head of the list set ss(colors) "[lrange $ss(colors) 1 end] $color" ;# Put head at end .c create oval -$ss(s2) -$ss(s2) $ss(s2) $ss(s2) -fill $color \ -tag "balls ball$n" #.c create text 0 0 -text $n -tag "balls ball$n" -anchor c } ##+##################################################### # # New_balls - Deletes then recreates the balls # proc new_balls {} { global ss .c delete balls for {set i 0} {$i < $ss(num)} {incr i} { create_ball $i } juggle 0 ;# Update the display } ##+##################################################### # # Startstop - Manipulates the start / stop button # proc startstop {} { global ss if {$ss(startstop) == "Start"} { set ss(startstop) "Stop" set ss(stop) 0 after 1 juggle } else { set ss(startstop) "Start" set ss(stop) 1 } } ##+##################################################### # # Set_value # # Handles changing the values of any juggling parameter. We late-bind # so we only change on button release. # proc set_value who { global ss if {$who == "height"} { set ss(h) $ss(v,h) ;# Just get the height return } elseif {$who == "flight"} { set ss(flight) $ss(v,flight) ;# Get new flight time } elseif {$who == "balls"} { set ss(num) $ss(v,num) ;# New number of balls } adjust reinit ;# Update global values } ##+##################################################### # # Reinit -- Initializes the ss variables for the balls # proc reinit {} { global ss set ss(t) 0 ;# Start at time 0 set ss(h) $ss(v,h) ;# Height set ss(flight) $ss(v,flight) ;# Flight time set ss(num) $ss(v,num) ;# How many balls set ss(w) [expr {round($ss(scale) * 140)}] ;# Width of hands .cback config -state normal set ss(LR) HR ;# State transitions set ss(RL) HL set ss(HR) RL ;# Even does weirdness set ss(HL) LR $ss(pattern) ;# Set up for given pattern } ##+##################################################### # # Best - Sets up the hold time for N balls # # empty (e) = P3 - BALL(n-1) # = (2f+h) - (n-1)(2f+2h)/n # = (2f - h(n-2)) / n # hold (h) = (2f - en) / (n-2) # # Also h + e = time between balls = (2f+2h)/n # # Constraints: at the start the last ball must be in the air # BALL(n-1) < P3 # ==> hold < 2f / (n-2) # ==> empty < 2f / n # alt. hold time less than time between balls # hold < (2f+2h)/n # hold < 2f / (n-2) # # Best: e == h ==> h = f / (n-1) # # To compute: the last ball starts at the exact moment when the first ball # is launched. The hand is empty until the ball lands. # proc best {} { global ss set ss(hold) $ss(flight) if {$ss(num) > 1} { set ss(hold) [expr {round(1.0 * $ss(flight) / ($ss(num) - 1))}] } } ##+##################################################### # # Move_hands - Positions the hands correctly. # proc move_hands {} { global hand ss if {[.c find withtag hands] == ""} return ;# No hands, do nothing .c delete arms foreach h {0 1} { set where [where_hands $ss(t) $h] ;# Where it belongs foreach {x y} $where break set x [expr {$x - 1 - $h}] ;# Fudge factor set y [expr {$y - 1}] ;# Fudge factor place_obj hand$h [list $x $y] -1 ;# Put into place set w [expr {3 * $ss(s) / 4}] set y [expr {$y + $w}] .c create line $ss(elbowx,$h) $ss(elbowy,$h) $x $y -tag arms \ -fill gray95 -width $w if {$hand($h,ss) == "full"} { ;# Does it have a ball in it? set b ball$hand($h,ball) ;# Yep, then move the ball also place_obj $b $where } } .c lower arms hands } ##+##################################################### # # Where_hands - Determines where H hand should be at time T # proc where_hands {t h} { global hand ss set d [expr {$hand($h,duration) - 1}] if {$d <= 0} {set d 1} if {$hand($h,ss) == "full"} { set p [expr {1.0 - (1.0*$hand($h,toss) - $t -1) / $d}] set y [expr {$hand($h,y) - $ss(s) * (4 * ($p * ($p - 1)))}] } else { set p [expr {(1.0 * $hand($h,catch) - $t) / $hand($h,duration)}] set y [expr {$ss(s2) * (4 * ($p * ($p - 1)))}] } set w [expr {$ss(w) + $ss(shift)}] ;# Biggest width if $h { ;# X depends on which hand set x [expr {$w - 2 * $p * $ss(shift)}] } else { set x [expr {-$w + 2 * $p * $ss(shift)}] } set x [expr {round($x)}] set y [expr {round($y)}] return [list $x $y] } ##+##################################################### # # Adjust - Adjust the flight & hold time so that their sum is a # multiple of the number of balls. This way, we get no round off # errors in computing where the balls should start. # proc adjust {} { global ss if {$ss(pattern) != "cascade"} return set n $ss(num) ;# Number of balls set f $ss(flight) ;# Flight time set h $ss(hold) ;# Hold time set r [expr {($f + $h) % $n}] ;# How much we're off by if {$r != 0} { if {$r > $n / 2} { set r [expr {$r - $n}] } set ss(flight) [expr {$ss(flight) - $r}] ;# Adjust flight down set ss(v,flight) $ss(flight) ;# Set the scale } } ##+##################################################### # # Toss_ball - Called when a ball has just been tossed. We need to # update the hand info. # proc toss_ball {who which} { global ball hand ss set next [next_ball $who $which] ;# Next ball to land here set hand($which,ss) empty ;# No longer holding a ball set hand($which,ball) -1 ;# Ball in hand set hand($which,catch) $ball($next,catch) ;# Next ball to land here set hand($which,duration) [expr {$ball($next,catch) - $ss(t)}] } ##+##################################################### # # Catch_ball - Called when ball WHO lands in hand WHICH. Generates a # new toss and updates the hand information. # proc catch_ball {who which} { global ball hand ss set dirs(RL) to_right set dirs(LR) to_left set next [next_ball $who $which] ;# Next ball to land here set when [expr {($ss(t) + $ball($next,catch)) /2.0}];# Time for us to leave set when [expr {round($when)}] if {$when == $ss(t)} { ;# Problem when WHO == NEXT set when [expr {$ss(t) + $ss(hold)}] } if {0 && $which == 0} { puts -nonewline "catch $who: time $ss(t) catch($next) " puts -nonewline "$ball($next,catch) when $when " puts "when: +[expr {$when - $ss(t)}]" } new_toss $who $when $dirs($ball($who,ss)) $which set hand($which,ss) full ;# Holding a ball set hand($which,ball) $who ;# Ball in hand set hand($which,toss) $ball($who,toss);# When we throw it set hand($which,duration) [expr {$ball($who,toss) - $ss(t)}] set u [expr {-1 + 2.0/$ball($who,flight)}] set y [expr {$ball($who,kh) * (1 - $u*$u)}] set hand($which,y) $y } ##+##################################################### # # Next_ball - Returns the next ball after WHO to land in hand WHICH # proc next_ball {w h} { global ss incr w -1 if {$ss(pattern) == "even"} { if {$w == -1} { set w [expr {$ss(n2) - 1}] } elseif {$w == $ss(n2) - 1} { set w [expr {$ss(num) - 1}] } } elseif {$w == -1} { set w [expr {$ss(num) - 1}] } return $w } ##+##################################################### # # New_toss - Sets up ball WHO for being tossed again at time WHEN # in direction DIR. # new height = k * height # new flight = sqrt(k) * flight # proc new_toss {who when dir xhand} { global ball ss set k 1 ;# Scaling factor set f $ss(flight) ;# Total flight time set x 0 ;# Overlap into holding time if {! $ss(perfect)} { ;# Should we add randomness? set x [expr {int(rand() * $ss(hold))}] ;# Use this much of hold time set f [expr {$ss(flight) + $x}] ;# New flight time set k [expr {1.0 * $f / $ss(flight)}] set k [expr {$k * $k}] } if {$dir == "to_right" && $ss(pattern) == "shower"} { set f $ss(flight2) ;# Special low path set k [expr {1.0 * $f / $ss(flight)}] set k [expr {$k * $k}] } set ball($who,k) $k ;# Random height scale factor set ball($who,toss) $when ;# Time of the toss set ball($who,flight) $f ;# New flight time set ball($who,catch) [expr {$when + $f}] ;# Time of catch set ball($who,kh) [expr {$k * $ss(h)}] ;# How high this toss goes set ball($who,w) [expr {2 * $ss(w)}] if {$ss(pattern) == "even"} { set ball($who,w) [expr {-2*$ss(shift)}] } set ball($who,x) [expr {-($ss(w) - $ss(shift))}] if {$xhand == 1} { set ball($who,w) [expr {-$ball($who,w)}] set ball($who,x) [expr {-$ball($who,x)}] } if {$ss(pattern) == "shower" && $ss(back) == 1} { set ball($who,w) [expr {-$ball($who,w)}] set ball($who,x) [expr {-$ball($who,x)}] } } ##+##################################################### # # Dump - Dumps out the ss of a ball or all the balls # proc dump {} { global ball hand ss puts "" for {set i 0} {$i < $ss(num)} {incr i} { set msg "Ball $i: $ball($i,ss)" set msg "$msg toss [format %4d $ball($i,toss)]" set msg "$msg catch[format %4d $ball($i,catch)]" set msg "$msg flight[format %4d $ball($i,flight)]" set msg "$msg x [format %4d $ball($i,x)]" set msg "$msg w [format %4d $ball($i,w)]" set msg "$msg k $ball($i,k)" set msg "$msg kh $ball($i,kh)" puts $msg } for {set i 0} {$i < 2} {incr i} { set msg "Hand $i: [format %5s $hand($i,ss)]" set msg "$msg ball [format %2s $hand($i,ball)]" set msg "$msg toss [format %4d $hand($i,toss)]" set msg "$msg catch [format %4d $hand($i,catch)]" set msg "$msg duration $hand($i,duration)" set msg "$msg y $hand($i,y)" puts $msg } puts "time: $ss(t)" puts "" } ##+##################################################### # # Init_ball - Given the starting position of a ball, it determines the # ss the ball is in and what its toss/catch values should be. # proc init_ball {who time} { global ball ss if {$time < $ss(p1)} { ;# Left to right set ball($who,ss) LR new_toss $who [expr {-$time}] to_right 0 } elseif {$time < $ss(p2)} { ;# Hold right set ball($who,ss) SR set ball($who,ss) HR new_toss $who [expr {$ss(p2) - $time}] to_left 1 } elseif {$time < $ss(p3)} { ;# Right to left set ball($who,ss) RL new_toss $who [expr {$ss(p2) - $time}] to_left 1 } elseif {$time < $ss(p4)} { ;# Hold left set ball($who,ss) SL set ball($who,ss) HL new_toss $who [expr {$ss(p4) - $time}] to_right 0 } else { puts "ERROR: init_ball $who $time: time out of range" } } ##+##################################################### # # Startup - Re-init the balls so that they all start in the hands. # Not fully working yet. # proc startup {} { global ss ball set newss(LR) SL ;# Cheap way to avoid an if set newss(HR) SR set newss(RL) SR set newss(HL) SL set max $ss(t) ;# Find longest in air for {set i 0} {$i < $ss(num)} {incr i} { if {$ball($i,toss) < $max} { set max $ball($i,toss) } } set max [expr {$ss(t) - $max}] for {set i 0} {$i < $ss(num)} {incr i} { ;# Adjust everyone by max set ball($i,toss) [expr {$ball($i,toss) + $max}] set ball($i,catch) [expr {$ball($i,toss) + $ball($i,flight)}] set ball($i,ss) $newss($ball($i,ss)) move_ball 0 $i } } ##+##################################################### # # Init_hands - Initializes where the hands are # proc init_hands {} { global ball ss hand if {$ss(pattern) == "shower"} return set hand(0,y) 20 set hand(1,y) 20 toss_ball 0 0 ;# Just tossed off ball 0 if {[expr {($ss(num) % 2) == 0}]} { toss_ball [expr {$ss(num) / 2}] 1 return } set hand(0,toss) 0 ;# When ball gets tossed set who [expr {$ss(num) / 2}] ;# Ball in the right hand set hand(1,ss) full ;# It has a ball in it set hand(1,ball) $who ;# Which ball set hand(1,toss) $ball($who,toss) ;# When toss will happen set hand(1,catch) 0 ;# When next ball lands set hand(1,duration) $ss(hold) ;# How long we hold ball for } ##+##################################################### # # Cascade - Sets up balls & hands for the cascade pattern # proc cascade {} { global ball ss best ;# Set up HOLD adjust set ss(pattern) cascade ;# Indicate this pattern set ss(t) 0 ;# Start at time 0 set ss(shift) [expr {(1 - 2*$ss(back)) * $ss(s)}] set ss(p1) $ss(flight) ;# Cycle timings set ss(p2) [expr {$ss(p1) + $ss(hold)}] set ss(p3) [expr {$ss(p2) + $ss(flight)}] set ss(p4) [expr {$ss(p3) + $ss(hold)}] set ss(total) $ss(p4) .c delete balls for {set i 0} {$i < $ss(num)} {incr i} { create_ball $i init_ball $i [expr {$ss(total) * $i / $ss(num)}] } #startup ;# Put into start position create_hand 0 create_hand 1 init_hands juggle 0 ;# Put them in position juggle 0 ;# Don't ask, it looks better } ##+##################################################### # # Shower - Sets up for the shower pattern # proc shower {} { global ball ss set ss(pattern) shower ;# Indicate this pattern set ss(t) 0 set ss(shift) 0 ;# Get rid of the shift .c delete hands ## total = f + 2hold + f2 ## f2 = total/n ==> f/(n-2) ## hold = 1/2 * (total / n) ==> f/2(n-2) set ss(flight2) [expr {round($ss(flight) / ($ss(num) - 2.0))}] if {$ss(flight2) <= 1} { set ss(flight2) 2 } if {$ss(flight2) >= 5} { set ss(flight2) 4 } set ss(hold) [expr {round($ss(flight2) / 2.0)}] set ss(p1) $ss(flight2) ;# Cycle timings set ss(p2) [expr {$ss(p1) + $ss(hold)}] set ss(p3) [expr {$ss(p2) + $ss(flight)}] set ss(p4) [expr {$ss(p3) + $ss(hold)}] set ss(total) $ss(p4) .c delete balls for {set i 0} {$i < $ss(num)} {incr i} { create_ball $i init_ball $i [expr {$ss(total) * $i / $ss(num)}] } juggle 0 ;# Put them in position } ##+##################################################### # # Even - Sets up for even ball pattern # proc even {} { global ball hand ss set ss(pattern) even ;# Indicate this pattern set ss(t) 0 set ss(shift) [expr {(1 - 2*$ss(back)) * 2*$ss(s)}] ;# Bigger shift set ss(w) [expr {round($ss(scale) * 110)}] ;# Width of hands set ss(HR) LR ;# Change the transitions set ss(HL) RL set ss(hold) [expr {$ss(flight) / ($ss(num) - 1)}] set ss(total) [expr {$ss(flight) + $ss(hold)}] set n2 [expr {round($ss(num) / 2.0)}] ;# Balls in left hand set n3 [expr {$ss(num) - $n2}] ;# Balls in right hand set ss(n2) $n2 .c delete balls for {set i 0} {$i < $n2} {incr i} { ;# Left hand create_ball $i ;# New ball set t [expr {-$ss(total) * $i / $n2}] ;# When it got tossed new_toss $i $t xxx 0 ;# Put in then toss values set ball($i,ss) RL ;# Reset the ss info if [expr {$t > $ss(flight)}] { set ball($i2,ss) SL set ball($i2,ss) HL } } set offset [expr {$n2 == $n3 ? $ss(hold) : 0}] for {set i $n2} {$i < $ss(num)} {incr i} { ;# Right hand set i2 [expr {$i - $n2}] ;# Ball in other hand create_ball $i set t [expr {-$ss(total) * $i2 / $n3}] ;# When it got tossed set t [expr {-$offset + $t}] ;# Offset it a little new_toss $i $t xxx 1 ;# Put in the toss values set ball($i,ss) LR ;# Reset the ss info if [expr {$t > $ss(flight)}] { set ball($i,ss) SR set ball($i,ss) HR } } create_hand 0 create_hand 1 toss_ball 0 0 toss_ball $n2 1 juggle 0 ;# Put them in position juggle 0 ;# Don't ask, it looks better } proc wink {onoff} { catch {after cancel $::ss(wink)} if {$onoff} { .c lower reye .c raise wink flagman set ::ss(wink) [after 500 {wink 0}] } else { .c lower wink .c raise reye flagman set delay [expr {int(1000 * (10 + 40*rand()))}] set ::ss(wink) [after $delay {wink 1}] } } ##+##################################################### # # Place_obj # # Moves OBJ to absolute coordinates (x,y). If center is 0 then the # top left corner moves to (x,y). If center is 1 then the object is # centered at (x,y). If center is -1, then only centered in x. # proc place_obj {obj xy {center 1}} { global ss foreach {x y} $xy break set bb [.c bbox $obj] ;# Where it is set x [expr {$x - $ss(s2)}] ;# Center at this point if {$center != -1} { set y [expr {$y - $ss(s2)}] } set dx [expr {$x - [lindex $bb 0]}] ;# Delta in X set dy [expr {$y - [lindex $bb 1]}] ;# Delta in Y .c move $obj $dx $dy ;# Move into place } proc About {} { tk_messageBox -icon info -parent . -title "About TkJuggler" \ -message "Tk Juggler\n\nby Keith Vetter\nNovember, 2002" } proc flagman {} { # stolen from https://wiki.tcl-lang.org/3208 .c create rect {-5000 110 5000 5000} -fill grey -outline grey -tag flagman .c create poly {-80 280 -20 280 0 80 20 280 80 280 100 -136 0 \ -160 -100 -136} -fill white -tag flagman .c create oval {-40 -236 40 -140} -fill orange -outline orange -tag flagman .c create line {-16 -200 -16 -188} -tag {flagman reye} .c create line {-8 -194 -24 -194} -tag {flagman wink} .c create line {16 -200 16 -188} -tag flagman .c create arc -24 -216 24 -160 -start 210 -extent 125 -style arc \ -tag flagman .c create rect {-36 -236 36 -216} -fill white -outline white -tag flagman .c create poly {-80 -120 -100 -120 -100 0 -60 0 -60 -120} -fill grey95 \ -tag flagman .c create poly {80 -120 100 -120 100 0 60 0 60 -120} -fill grey95 \ -tag flagman .c lower wink array set ::ss {elbowx,0 -80 elbowy,0 -10 elbowx,1 80 elbowy,1 -10} } proc scaler {bigger} { global ss if {$bigger} { if {$ss(scale) > 2} return set f 1.25 } else { if {$ss(scale) < .15} return set f .8 } .c scale all 0 0 $f $f foreach w {scale s s2 w v,h elbowx,0 elbowx,1} { set ss($w) [expr {$ss($w) * $f}] } set ss(v,flight) [expr {round($ss(v,flight) / $f)}] adjust reinit } ##+############################################################## ################################################################# ################################################################# init ;# One time inits display ;# Set up all the widgets reinit ;# Inits for this pattern startstop
uniquename 2013jul29
This code could use an image to show what it produces. (It seems the images above, at mini.net and juggling.org, have gone dead.)
(Thanks to 'gnome-screenshot', 'mtpaint', and ImageMagick 'convert' on Linux for, respectively, capturing a screen image to a PNG file, cropping the image, and converting the PNG file to a JPEG file that was less than 10% the size of the PNG file. Thanks to FOSS developers everywhere --- including Linux kernel and Gnu developers. I used the 'mv' command and the ImageMagick 'identify' command in a shell script to easily rename the cropped image file to contain the image dimensions in pixels.)
I captured the image above while the juggler was juggling. Hence the 'break up' of the balls into filled partial-circles.
Note the controls along the bottom of the GUI, to set up different juggling patterns, speeds, and heights.