Version 1 of Creating an animated display, part 2

Updated 2002-10-24 07:27:42

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.


 # 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

[ Arts and cratfs of Tcl-Tk programming

Category graphics ]