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 $methoduniquename 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.
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.
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.