Creating an animated display, part 2

Arjen Markus (23 october 2002) As a sequel to the page Creating an animated display here is a script that incorporates two separate displays working in the same framework. I added a little user-interface to make it work smoother.


Jeff Smith 2019-08-30 : Below is an online demo using CloudTk


 package require Tk

 # animdemo.tcl --
 #   A few simple animated displays
 #
 #
 # Animation --
 #    Namespace to hold all (specific) information
 #
 namespace eval ::Animation:: {
    variable xmouse 0   ;# Make sure they have a value
    variable ymouse 0
 }

 # StoreMousePosition --
 #    Store the coordinates of the mouse pointer for later use
 #
 # Arguments:
 #    xp       X-coordinate of the mouse
 #    yp       Y-coordinate of the mouse
 #
 # Result:
 #    None
 #
 proc ::Animation::StoreMousePosition {xp yp} {
    variable xmouse
    variable ymouse

    set xmouse $xp
    set ymouse $yp
 }

 # drawBall --
 #    Draw a red circle at a certain height above the green ground
 #
 # Arguments:
 #    time     Time parameter, used to calculate the actual height
 #
 # Result:
 #    None
 #
 # Note:
 #    Assume a perfectly elastic collision. The time parameter must
 #    be reduced to the time since the last collision.
 #
 # Technique used: redraw the entire picture
 #
 proc ::Animation::drawBall {time} {
    global accel
    global velo0
    global cnv_height
    global cnv_width

    set period [expr {2.0*$velo0/$accel}]
    set time2  [expr {$time - $period * int($time/$period)}]

    set grass_height 20
    set radius        7
    set ball_height   [expr {$velo0*$time2-0.5*$accel*$time2*$time2}]
    set pix_height [expr {
                  $cnv_height-$grass_height - $radius - $ball_height}]

    set xl     [expr {0.5*$cnv_width-$radius}]
    set xr     [expr {0.5*$cnv_width+$radius}]
    set yb     [expr {int($pix_height)-$radius}]
    set yt     [expr {int($pix_height)+$radius}]

    .cnv delete all
    .cnv create rectangle 0 $cnv_height $cnv_width \
       [expr {$cnv_height-$grass_height}] -fill green -outline green
    .cnv create oval      $xl $yb $xr $yt -fill red -outline black
 }

 # drawCompassNeedles --
 #    Draw a set of compass needles that orient themselves to the
 #    current mouse position
 #
 # Arguments:
 #    time     Time parameter, ignored
 #
 # Result:
 #    None
 #
 # Note:
 #    The mouse position is stored via the binding to the mouse event
 #    motion. We use only this information to create a new display.
 #
 # Technique used: redraw the picture
 proc ::Animation::drawCompassNeedles {time} {
    variable xmouse
    variable ymouse

    set hlength 14
    set hwidth   7

    .cnv delete all

    foreach y {10 50 90 130 170 210 250 290} {
       foreach x {10 50 90 130 170 210 250 290 330 370} {
          set dx    [expr {$xmouse-$x}]
          set dy    [expr {$ymouse-$y}]
          if { $dx != 0 || $dy != 0 } {
             set angle [expr {atan2($dy,$dx)}]
          } else {
             set angle 0
          }
          set cosa  [expr {cos($angle)}]
          set sina  [expr {sin($angle)}]
          set x1    [expr {$x+$hlength*$cosa}]
          set y1    [expr {$y+$hlength*$sina}]
          set x2    [expr {$x+$hwidth*$sina}]
          set y2    [expr {$y-$hwidth*$cosa}]
          set x3    [expr {$x-$hlength*$cosa}]
          set y3    [expr {$y-$hlength*$sina}]
          set x4    [expr {$x-$hwidth*$sina}]
          set y4    [expr {$y+$hwidth*$cosa}]

          .cnv create polygon $x1 $y1 $x2 $y2 $x4 $y4 \
              -fill red  -outline black
          .cnv create polygon $x3 $y3 $x2 $y2 $x4 $y4 \
              -fill blue -outline black
       }
    }
 }

 # nextPicture --
 #    Prepare to call the next picture, stop after some predefined
 #    number of steps.
 #
 # Arguments:
 #    step     Step number (converted to time)
 #
 # Result:
 #    None
 #
 proc nextPicture {step method} {
    global time_delay
    global max_steps
    global stop_anim

    #
    # Draw the picture
    #
    $method [expr {0.1*$step}]

    #
    # Set up the next picture via the [after] command
    #
    if { $step < $max_steps && $stop_anim != 1} {
       incr step
       after $time_delay [list nextPicture $step $method]
    }
 }

 # stopPicture --
 #    Stop the animation
 #
 # Arguments:
 #    None
 #
 # Result:
 #    None
 #
 # Side effect:
 #    Sets the variable "stop_anim" to gracefully stop the animation
 #
 proc stopPicture {} {
    global stop_anim

    set stop_anim 1
 }

 # main --
 #   Set up the canvas, start the loop
 #
 global cnv_width
 global cnv_height
 global velo0
 global accel

 #
 # Canvas size
 #
 set cnv_width   400
 set cnv_height  300

 #
 # Time delay and maximum duration (steps)
 #
 set time_delay  100   ;# Time in ms between pictures
 set max_steps  1000   ;# Maximum number of steps

 #
 # Private variable to stop the animation if wanted
 #
 set stop_anim     0

 #
 # Needed for drawBall
 #
 set velo0        70.0  ;# m/s
 set accel        10.0  ;# m/s2
                        ;# pixels become m that way :)

 #
 # Set up the canvas and the buttons
 #
 canvas .cnv -width $cnv_width -height $cnv_height -background white

 frame  .frm1
 radiobutton .frm1.ball    -text "Bouncing ball" \
    -variable method -value "::Animation::drawBall" \
    -command {stopPicture}
 radiobutton .frm1.compass -text "Compass needles (following mouse)" \
    -variable method -value "::Animation::drawCompassNeedles" \
    -command {stopPicture}
 pack .frm1.ball .frm1.compass -side left

 frame  .frm2
 button .frm2.start   -text "Start" \
    -command {set stop_anim 0; nextPicture 0 $method}
 button .frm2.stop    -text "Stop"  -command {stopPicture}
 pack .frm2.start .frm2.stop -side left

 pack   .frm2 -side bottom -fill x
 pack   .frm1 -side bottom -fill x
 pack   .cnv -fill both


 bind   .cnv <Motion> {::Animation::StoreMousePosition %x %y}

 set method ::Animation::drawBall

 $method 0

 #
 # If you want the animation to start rightaway ...
 #
 #nextPicture 0 $method

uniquename 2014jan27

For those who do not have the facilities or time to implement the code above, here is an image showing the GUI when either the bouncing-ball or the compass-needles radiobutton is chosen.

bouncingBall_animation_wiki4412_405x376.jpg

compassNeedles_pointToMouseLoc_wiki4412_406x374.jpg

I found that, to make the ball bounce faster (more like a real ball), I had to change the 'time_delay' variable's value from 100 to 10 milliseconds.

It is nice that ALL of those compass needles respond immediately to any movement of the mouse.


I am new to the wiki... so sorry if I have put my comments in the wrong place... First of all, a big thanks to Mr. Arjen Markus for the above code.. I wanted a break exactly like this one..

Also I have gone ahead a made one myself.. with some gravity effect and trails.. here is the code..

(Also sorry for the poor or non-existant commenting....)

 package require Tk

#canvas ...
 namespace eval can {
        variable width           600
         variable height          600
        variable gravity        1
        variable loss                 3        
        variable delay                60
 }

#ball........
 namespace eval ball {
         variable vely                 10   ;# Velociy Y Dir
         variable velx                 25   ;# Velociy X Dir
         variable rad                 10   ;# Ball Radius (Smallest)
         variable dRad                 1.05 ;# % increase in ball dia
         variable n_trail         15   ;# Number of trails..
         
         variable col_id                5    ;# grey index of the last one
         
         variable px                  ;# Array of X Coordinate of ceneters
         variable py                  ;# Array of Y Coordinate of ceneters
         
         set px(0) 200
         set py(0) 50
         
         for {set i 1} {$i<$n_trail} {incr i} {
                 set px($i) -100
                 set py($i) -100
         }
         
        variable top
        variable bottom
        variable left
        variable right

        set top                 $rad
        set left                 $rad
        set bottom                 [expr {$can::height - $rad} ]
        set right                 [expr {$can::width  - $rad} ]
         
}
 
#Push the center of ball to the next one..
#Add the new x, y  to the first one..
proc ball::push {x y} {

        variable px
        variable py
        variable n_trail

        set n [expr {$n_trail -1}]

        for {set i $n} {$i > -1} {incr i -1} {
                
                set i_         [expr {$i -1}]
                if { $i != 0 } {
                        set px($i) $px($i_)
                        set py($i) $py($i_)
                } else {
                        set px($i) $x
                        set py($i) $y
                }

        }
}
 
 
 
proc drawBall {} {
        
        #Effect of Gravity
        incr ball::vely $can::gravity

        #Exiting condition
        if {($ball::vely == 0) && ( [expr {$ball::bottom - $ball::py(0)}] < 20 ) } {destroy .}

        #Add Velocity
        set px_ [expr {$ball::px(0) + $ball::velx}]
        set py_ [expr {$ball::py(0) + $ball::vely}]

        #When bouncing from the bottom, reduce velocity 
        if {$py_ > $ball::bottom } {
                set ball::vely [expr {$ball::vely - $can::loss}]
        }   

        #When reached the top or bottom, flip the direction of Velocity-Y
        if { ($py_ > $ball::bottom) || ($py_ < $ball::top) } {
                #after 250
                set ball::vely [expr {-1 * $ball::vely}]
                set py_ [expr {$ball::py(0) + $ball::vely}]
        }

        #when reached left or right, flip the direction Velocity-X
        if { ($px_ > $ball::right) || ($px_ < $ball::left) } {
                #after 250
                set ball::velx [expr {-1 * $ball::velx}]
                set px_ [expr {$ball::px(0) + $ball::velx}]
        }

        #Remove All
        .cnv delete all
                
        #Call Push with new center
        ball::push $px_ $py_

        #Last ball radius
        set r $ball::rad

        #Draw all balls
        for {set i [expr {$ball::n_trail -1}]} {$i > -1} {incr i -1} {
        
                set x1 [expr {$ball::px($i) - $r}]
                set x2 [expr {$ball::px($i) + $r}]

                set y1 [expr {$ball::py($i) - $r}]
                set y2 [expr {$ball::py($i) + $r}]

                #find the ball rad
                set r [expr {$r * $ball::dRad}]

                #find the Grey Index
                set greyn [expr {$ball::col_id + ( (100 - $ball::col_id)/$ball::n_trail*$i) }]
                
                #First ball in Red, Other Balls in Grey with calculated index
                if {$i == 0 } {
                        .cnv create oval      $x1 $y1 $x2 $y2 -fill red -outline black
                } else {
                        .cnv create oval      $x1 $y1 $x2 $y2 -fill gray$greyn -outline gray$greyn
                }
        
        }
}

proc nextPicture {} { 
        drawBall
        after $can::delay [list nextPicture]   
}

canvas .cnv -width $can::width -height $can::height -background white
pack   .cnv -fill both

drawBall 

tkwait visibility .
nextPicture


#try...

Joe Varghese


uniquename 2014jan27

For those who do not have the facilities or time to implement the Varghese code above, here is an image showing the 'fading trail' on the bouncing-ball.

bouncingBall_withFadingTrail_energyLoss_wiki4412_603x625.jpg

Unlike Arjen's animation, this animation does not continue indefinitely. Varghese has simulated a ball that is losing energy. The ball starts bouncing high up on the walls and finally ends up barely bouncing off the floor --- until the window suddenly closes.