Add a script to clean up after Tcl/Tk installation
[git/jnareb-git.git] / mingw / lib / tk8.5 / demos / pendulum.tcl
blobb70cbf12a9e956dbe4c71bb901120f9f7103d4d7
1 # pendulum.tcl --
3 # This demonstration illustrates how Tcl/Tk can be used to construct
4 # simulations of physical systems.
6 # RCS: @(#) $Id: pendulum.tcl,v 1.3 2006/10/17 05:52:40 das Exp $
8 if {![info exists widgetDemo]} {
9 error "This script should be run from the \"widget\" demo."
12 package require Tk
14 set w .pendulum
15 catch {destroy $w}
16 toplevel $w
17 wm title $w "Pendulum Animation Demonstration"
18 wm iconname $w "pendulum"
19 positionWindow $w
21 label $w.msg -font $font -wraplength 4i -justify left -text "This demonstration shows how Tcl/Tk can be used to carry out animations that are linked to simulations of physical systems. In the left canvas is a graphical representation of the physical system itself, a simple pendulum, and in the right canvas is a graph of the phase space of the system, which is a plot of the angle (relative to the vertical) against the angular velocity. The pendulum bob may be repositioned by clicking and dragging anywhere on the left canvas."
22 pack $w.msg
24 ## See Code / Dismiss buttons
25 set btns [addSeeDismiss $w.buttons $w]
26 pack $btns -side bottom -fill x
28 # Create some structural widgets
29 pack [panedwindow $w.p] -fill both -expand 1
30 $w.p add [labelframe $w.p.l1 -text "Pendulum Simulation"]
31 $w.p add [labelframe $w.p.l2 -text "Phase Space"]
33 # Create the canvas containing the graphical representation of the
34 # simulated system.
35 canvas $w.c -width 320 -height 200 -background white -bd 2 -relief sunken
36 $w.c create text 5 5 -anchor nw -text "Click to Adjust Bob Start Position"
37 # Coordinates of these items don't matter; they will be set properly below
38 $w.c create line 0 25 320 25 -width 2 -fill grey50 -tags plate
39 $w.c create line 1 1 1 1 -tags pendulumRod -width 3 -fill black
40 $w.c create oval 1 1 2 2 -tags pendulumBob -fill yellow -outline black
41 $w.c create oval 155 20 165 30 -fill grey50 -outline {}
42 pack $w.c -in $w.p.l1 -fill both -expand true
44 # Create the canvas containing the phase space graph; this consists of
45 # a line that gets gradually paler as it ages, which is an extremely
46 # effective visual trick.
47 canvas $w.k -width 320 -height 200 -background white -bd 2 -relief sunken
48 $w.k create line 160 200 160 0 -fill grey75 -arrow last -tags y_axis
49 $w.k create line 0 100 320 100 -fill grey75 -arrow last -tags x_axis
50 for {set i 90} {$i>=0} {incr i -10} {
51 # Coordinates of these items don't matter; they will be set properly below
52 $w.k create line 0 0 1 1 -smooth true -tags graph$i -fill grey$i
54 # FIXME: UNICODE labels
55 $w.k create text 0 0 -anchor ne -text "q" -font {Symbol 8} -tags label_theta
56 $w.k create text 0 0 -anchor ne -text "dq" -font {Symbol 8} -tags label_dtheta
57 pack $w.k -in $w.p.l2 -fill both -expand true
59 # Initialize some variables
60 set points {}
61 set Theta 45.0
62 set dTheta 0.0
63 set pi 3.1415926535897933
64 set length 150
66 # This procedure makes the pendulum appear at the correct place on the
67 # canvas. If the additional arguments "at $x $y" are passed (the 'at'
68 # is really just syntactic sugar) instead of computing the position of
69 # the pendulum from the length of the pendulum rod and its angle, the
70 # length and angle are computed in reverse from the given location
71 # (which is taken to be the centre of the pendulum bob.)
72 proc showPendulum {canvas {at {}} {x {}} {y {}}} {
73 global Theta dTheta pi length
74 if {$at eq "at" && ($x!=160 || $y!=25)} {
75 set dTheta 0.0
76 set x2 [expr {$x-160}]
77 set y2 [expr {$y-25}]
78 set length [expr {hypot($x2,$y2)}]
79 set Theta [expr {atan2($x2,$y2)*180/$pi}]
80 } else {
81 set angle [expr {$Theta * $pi/180}]
82 set x [expr {160+$length*sin($angle)}]
83 set y [expr {25+$length*cos($angle)}]
85 $canvas coords pendulumRod 160 25 $x $y
86 $canvas coords pendulumBob \
87 [expr {$x-15}] [expr {$y-15}] [expr {$x+15}] [expr {$y+15}]
89 showPendulum $w.c
91 # Update the phase-space graph according to the current angle and the
92 # rate at which the angle is changing (the first derivative with
93 # respect to time.)
94 proc showPhase {canvas} {
95 global Theta dTheta points psw psh
96 lappend points [expr {$Theta+$psw}] [expr {-20*$dTheta+$psh}]
97 if {[llength $points] > 100} {
98 set points [lrange $points end-99 end]
100 for {set i 0} {$i<100} {incr i 10} {
101 set list [lrange $points end-[expr {$i-1}] end-[expr {$i-12}]]
102 if {[llength $list] >= 4} {
103 $canvas coords graph$i $list
108 # Set up some bindings on the canvases. Note that when the user
109 # clicks we stop the animation until they release the mouse
110 # button. Also note that both canvases are sensitive to <Configure>
111 # events, which allows them to find out when they have been resized by
112 # the user.
113 bind $w.c <Destroy> {
114 after cancel $animationCallbacks(pendulum)
115 unset animationCallbacks(pendulum)
117 bind $w.c <1> {
118 after cancel $animationCallbacks(pendulum)
119 showPendulum %W at %x %y
121 bind $w.c <B1-Motion> {
122 showPendulum %W at %x %y
124 bind $w.c <ButtonRelease-1> {
125 showPendulum %W at %x %y
126 set animationCallbacks(pendulum) [after 15 repeat [winfo toplevel %W]]
128 bind $w.c <Configure> {
129 %W coords plate 0 25 %w 25
131 bind $w.k <Configure> {
132 set psh [expr %h/2]
133 set psw [expr %w/2]
134 %W coords x_axis 2 $psh [expr %w-2] $psh
135 %W coords y_axis $psw [expr %h-2] $psw 2
136 %W coords label_dtheta [expr $psw-4] 6
137 %W coords label_theta [expr %w-6] [expr $psh+4]
140 # This procedure is the "business" part of the simulation that does
141 # simple numerical integration of the formula for a simple rotational
142 # pendulum.
143 proc recomputeAngle {} {
144 global Theta dTheta pi length
145 set scaling [expr {3000.0/$length/$length}]
147 # To estimate the integration accurately, we really need to
148 # compute the end-point of our time-step. But to do *that*, we
149 # need to estimate the integration accurately! So we try this
150 # technique, which is inaccurate, but better than doing it in a
151 # single step. What we really want is bound up in the
152 # differential equation:
153 # .. - sin theta
154 # theta + theta = -----------
155 # length
156 # But my math skills are not good enough to solve this!
158 # first estimate
159 set firstDDTheta [expr {-sin($Theta * $pi/180)*$scaling}]
160 set midDTheta [expr {$dTheta + $firstDDTheta}]
161 set midTheta [expr {$Theta + ($dTheta + $midDTheta)/2}]
162 # second estimate
163 set midDDTheta [expr {-sin($midTheta * $pi/180)*$scaling}]
164 set midDTheta [expr {$dTheta + ($firstDDTheta + $midDDTheta)/2}]
165 set midTheta [expr {$Theta + ($dTheta + $midDTheta)/2}]
166 # Now we do a double-estimate approach for getting the final value
167 # first estimate
168 set midDDTheta [expr {-sin($midTheta * $pi/180)*$scaling}]
169 set lastDTheta [expr {$midDTheta + $midDDTheta}]
170 set lastTheta [expr {$midTheta + ($midDTheta + $lastDTheta)/2}]
171 # second estimate
172 set lastDDTheta [expr {-sin($lastTheta * $pi/180)*$scaling}]
173 set lastDTheta [expr {$midDTheta + ($midDDTheta + $lastDDTheta)/2}]
174 set lastTheta [expr {$midTheta + ($midDTheta + $lastDTheta)/2}]
175 # Now put the values back in our globals
176 set dTheta $lastDTheta
177 set Theta $lastTheta
180 # This method ties together the simulation engine and the graphical
181 # display code that visualizes it.
182 proc repeat w {
183 global animationCallbacks
185 # Simulate
186 recomputeAngle
188 # Update the display
189 showPendulum $w.c
190 showPhase $w.k
192 # Reschedule ourselves
193 set animationCallbacks(pendulum) [after 15 [list repeat $w]]
195 # Start the simulation after a short pause
196 set animationCallbacks(pendulum) [after 500 [list repeat $w]]