#!/usr/bin/wish -f ## ## SCRIPT: tkAnimateCollisonOfTwoRigidMasses_1D.tk ## ## PURPOSE: This Tk GUI script simulates the 'direct' collision of ## 2 masses that are moving at constant velocities and ## that impact each other and 'rebound' along a straight line. ## ## In other words, this script simulates the approach, ## impact, and continuation of movement of two masses where ## the centers of the 2 masses move along a single ## 1-dimensional straight-line path. Furthermore, ## the 2 masses travel at constant velocities ## before and after the impact. ## ## By 'simulate', we mean that for user-specified values ## of the 2 masses and for user-specified values of their ## 2 before-impact velocities, the 2 after-impact velocities ## are computed. ## ## We allow the 2 before-impact velocities to be postive ## or negative, so the masses can be initially travelling in ## the same direction (both velocities positive or both negative) ## or toward each other (one positive velocity and one ## negative velocity). ## ## Furthermore, the GUI provides a Tk 'canvas' widget ## on which the approach and 'rebound' of the 2 masses ## is animated. In the animation, the 2 masses are ## represented by 2 circles. The centers of the circles ## are moved horizontally along a straight line. ## ## The animation is performed by using 'create oval' ## and 'delete' commands on the Tk 'canvas' widget. ## ##+###################### ## NOTATION AND EQUATIONS: ## ## The following notation is typically used in many physics ## documents: ## m1 denotes mass 1 ## m2 denotes mass 2 ## u1 denotes velocity of mass1 before impact ## u2 denotes velocity of mass2 before impact ## v1 denotes velocity of mass1 after impact ## v2 denotes velocity of mass2 after impact ## ## The GUI allows the user to specify m1, m2, u1, u2. ## This script computes algebraic expressions for v1 and v2. ## See below. ## ## The velocities u1, u2, v1, v2 are used to animate ## the motions of the two masses, before and after ## the collision. ## ## We assume a 'conservative system'. By that we mean that ## the motion and collision of the 2 masses are modeled ## under the following assumptions. ## ## - There is no energy loss during motion, such as frictional ## losses due to contact with a surface or losses due to ## air/fluid resistance. ## ## - There are no energy losses at impact, due to energy ## transfer into the masses, such as deformation and ## heat generation. (In traditional physics and engineering ## terminology, the collision is assumed to be 'elastic'.) ## ## - There is no energy loss due to sound generation (energy ## transfer to vibrating air molecules) due to the impact. ## ## The following 2 equations give the values of v1 and v2. ## ## v1 = ((m1-m2)*u1 + (2*m2)*u2) / (m1 + m2) ## v2 = ((m2-m1)*u2 + (2*m1)*u1) / (m1 + m2) ## ## These 2 equations can be derived from the 2 equations ## for conservation of momentum and conservation of energy: ## ## m1*u1 + m2*u2 = m1*v1 + m2*v2 ## ## m1*u1^2 + m2*u2^2 = m1*v1^2 + m2*v2^2 ## ## Some References: ## https://en.wikipedia.org/wiki/Linear_momenta ## (presents equations without derivation) ## http://www.themcclungs.net/physics/download/H/Momentum/ElasticCollisions.pdf ## (presents a nice, compact derivation -- nice notation) ## http://www.real-world-physics-problems.com/elastic-collision.html ## (presents equations without derivation - nice notation) ## http://hyperphysics.phy-astr.gsu.edu/hbase/elacol2.html ## (presents equations for case of 'target' at rest) ## https://en.wikipedia.org/wiki/Restitution_coefficient ## (presents equations for case of 'inelastic', non-conservative collision) ## http://physics.stackexchange.com/questions/81959/perfect-elastic-collision-and-velocity-transfer ## (presents the equations with some discussion of a typical question from 'newbies') ## http://www.euclideanspace.com/physics/dynamics/collision/twod/index.htm ## (presents the equations for 'off-center' collsions as well as 'in-line' collisions) ## http://www.tutelman.com/golf/swing/golfSwingPhysics1.php ## (shows animations of balls with 'target' at rest) ## ##+################################################################### ## SOME DIFFERENT NOTATION: ## ## The above symbolism for the velocities can be confusing. ## It helps to remember that the subscripts 1 and 2 are ## indicative of masses 1 and 2. So the u1 and v1 velocities ## are for mass1 and the u2 and v2 velocities are for mass2. ## ## To make this a little clearer, we could use ## V1 to denote velocities of mass1 ## and V2 to denote velocities of mass2. ## ## Then we could use ## u1 = V1(tb) = V1b, a constant ## u2 = V2(tb) = V2b, a constant ## v1 = V1(ta) = V1a, a constant ## v2 = V2(ta) = V1a, a constant ## where tb denotes times before the impact ## and ta denotes times after the impact. ## ## In other words, ## u is an abbreviation for V_b ## v is an abbreviation for V_a ## where the underscore represents 1 or 2. ## ## The equations above for the 'after-velocities' become ## ## V1a = ((m1-m2)*V1b + (2*m2)*V2b) / (m1 + m2) ## V2a = ((m2-m1)*V2b + (2*m1)*V1b) / (m1 + m2) ## ## Some derivations use this type of notation but use ## 'i' in place of 'b' and 'f' in place of 'a' --- ## where 'i' represents 'initial' and 'f' represents 'final'. ## ##+########################### ## FEATURES AND USE OF THE GUI: ## ## The GUI allows the user to enter various values for ## - m1 ## - m2 ## - V1b ## - V2b ## - D, the initial distance between m1 and m2. ## ## In addition, the GUI allows the user to specify the width ## of the image area in pixels, say ImgWidthPx. ## ## The GUI provides 2 buttons by which the user can specify ## the 2 colors for: ## - the canvas background ## - the 2 masses (circles) and the line along which ## they travel. ## ## When the mass and velocity and D input parameters are ready, ## the GUI provides a 'Solve' button by which the user can ## trigger the calculation of the after-impact velocities, V1a and V2a. ## ## After V1a and V2a are calculated and the user has readied ## the 2 colors, the user can click on a 'Start' radiobutton ## to start the animation. ## ## The user can click on a 'Stop' radiobutton to stop ## the animation, if it is still going. ## ## The GUI is to include a 'Help' button by which the user ## can get information on how the GUI can be used. ## ## And the GUI can include a 'ResetParms' button --- ## by which the user can reset the values of ## m1, m2, V1b, V2b, and some other parameters to their ## initially-displayed values. ## ##+################################ ## DETAILS OF THE ANIMATION PROCESS: ## ## The details of doing the drawing of the animation gets ## rather complicated because the user can specify ## positive or negative velocities. If the 'before' velocities ## are both positive or both negative, it may take a long ## time for the impact to occur. ## ## To model the before-impact and after-impact movement within ## the image area, the time and place of the impact need to ## be computed. ## ## Then limits, Xmin and Xmax, in 'world-coordinates' ## need to be computed so that the animation can be drawn ## in such a way that the before-impact and after-impact ## motion can be drawn within the specified image width. ## ## The limits, Xmin and Xmax, in world-coordinates need to ## be mapped to the left and right pixel-coordinates, ## 0 and ImgWidthPx. This will allow the before-impact, ## impact, and after-impact motion of the 2 masses to ## proceed within the image area. ## ## A time-step size, h, may be automatically computed ## for the user in order that the animation proceeds ## smoothly. ## ## The initial distance, D, between the 2 masses may need ## to be adjusted to avoid an animation that proceeds too ## slowly. ## ## In addition, we use the value of the 2 masses, m1 and m2, ## to determine the radii of the 2 circles that are drawn ## to indicate the magnitude of the 2 masses. The way that ## these 2 radii are determined, in world-coordinates, can be ## rather involved. ## ## See the comments in the 'animate' proc for details on ## the various aspects of implementing the animation. ## ##+############## ## THE GUI LAYOUT: ## ## From the discussion above, we see that the Tk GUI should ## allow the user to specify ## m1, m2, V1b, V2b ## from which V1a and V2a can be calculated. ## ## There is to be a 'Solve' button --- to calculate V1a and V2a. ## ##------------------- ## ## For the animation, some other parameters may be required. ## For example: ## ## The GUI can let the user specify the width of the image area ## in pixels, say ImgWidthPx. ## ## The GUI can provide 2 buttons by which the user can specify 2 colors. ## ## And the GUI may allow the user to specify D ---- an initial distance ## between m1 and m2. ## ## Then, on the GUI, there can be 'Start' and 'Stop' radiobuttons ## to start and stop an animation run. ## ## The time-step and speed of animation: ## ## This script may use the velocities --- V1b,V2b,V1a,V2a ---- to ## calculate a time-step, 'h', that gives smooth animation. (Thus we ## avoid the need to supply a widget on the GUI for time-step.) ## ## The time-step, 'h', may be used to control the real-time ## speed of the animation. ## ## To allow the user to speed-up or slow-down the animation, ## there could be a Tk widget ('entry' or 'scale') by which to specify ## a wait-time (in millisecs) between computing and displaying ## the new positions of the 2 circles. This would be an alternative to ## using a wait-time value calculated from the user-selected ## time-step, h. ## ## For now, we may simply calculate the animation wait-time ## based on the time-step, h. ## ## We may also accumulate the animation data --- t(i), x1(t(i), and x2(t(i) ## --- the location of the two masses during the animation --- ## in array variables. A 'ShowList' button may be used to show ## a table of these values. This data could conceivably be used ## in other applications ## ## ## One way the user can specify these parameters is indicated by ## the following 'sketch' of the GUI: ## ## FRAMEnames ## VVVVVVVVVV ## ------------------------------------------------------------------------------------------ ## Animate the Collision of Two Rigid Masses --- 'direct', 'in-line' impact ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} {Solve} {Reset Animate: O Start O Stop {Masses {Background {Show [a label here ## Parms} Color} Color} List} for V1a,V2a] ## ## .fRguide [ ... A description of the equation(s) or solution technique goes here, in a label widget. ... ] ## ## .fRmasses Mass1: 10.0___ Mass2: 1.0__ D (initial distance between masses): 10___ Image square (pixels): 300__ ## ## .fRinit - V1b: : 2.0__ V2b: 0.0__ <-- Initial (before-impact) Velocities, in distance-units/time-unit ## ## .fRmsg [ .......... Messages go here, in a label widget .......................... ] ## ## .fRcanvas |-----------------------------------------------------------------------------| ## | | ## | [This area is to contain a non-scrollable canvas widget in which | ## | the animation is to be drawn. | ## | | ## | The canvas widget is centered at the top of this area.] | ## | | ## | | ## | | ## | | ## |-----------------------------------------------------------------------------| ## ## ------------------------------------------------------------------- ## ## In the above sketch of the GUI: ## ## SQUARE BRACKETS indicate a comment (not to be placed on the GUI). ## BRACES indicate a Tk 'button' widget. ## UNDERSCORES indicate a Tk 'entry' widget. ## A COLON indicates that the text before the colon is on a 'label' widget. ## CAPITAL-O indicates a Tk 'radiobutton' widget. ## CAPITAL-X indicates a Tk 'checkbutton' widget (if any). ## ## VERTICAL-BARS and horizontal HYPHENS are used to outline a 'canvas' or ## 'listbox' or 'text' widget. ## ## If there are scrollbars: ## Less-than and greater-than signs indicate the left and right ends of a horizontal 'scrollbar'. ## Capital-V and Capital-A letters indicate the bottom and top ends of a vertical 'scrollbar'. ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of about ## ## - 7 button widgets ## - 11 label widgets ## - 6 entry widgets ## - 1 canvas widget with no scrollbars ## - 2 radiobutton widgets in 1 group ## - 0 scale widgets (but may use scale widgets in place of some entry widgets) ## - 0 checkbutton widgets ## - 0 listbox widgets ## - 0 text widgets ## ##+######################################################################## ## 'CANONICAL' STRUCTURE OF THIS TK CODE: ## ## 0) Set general window & widget parms (win-name, win-position, ## win-color-scheme, fonts, widget-geometry-parms, win-size-control). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack ALL frames and sub-frames. ## ## 2) Define all widgets in the frames. Pack them. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen action ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or two of ## the procs), if needed. ## ## ## Some detail about the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : '.fRbuttons' ## '.fRguide' ## '.fRmasses' ## '.fRinit' ## '.fRmsg' ## '.fRcanvas' ## No sub-frames. ## ## 1b) Pack ALL frames. ## ## 2) Define all widgets in the frames (and pack them): ## ## - In '.fRbuttons': ## 5 button widgets ('Exit','Help','Solve','Reset','ShowList') ## and ## 1 label and 2 radiobuttons ('Animate:', 'Start', 'Stop') ## and ## 2 buttons (for setting 2 colors) ## and ## 1 label widget in which to show V1a,V2a. ## ## - In '.fRguide': ## 1 label widget ## ## - In '.fRmasses': ## 4 pairs of 'label' and 'entry' widgets ## ## - In '.fRinit': ## 2 pairs of 'label' and 'entry' widgets, and 1 more label widget ## ## - In '.fRmsg': ## 1 label widget to display messages to the user, such as ## elapsed execution time, as well as an 'in progress' ## msg or other msgs. ## ## - In '.fRcanvas': 1 'canvas' widget ## ## 3) Define BINDINGS: see the BINDINGS section for bindings, if any ## ## 4) Define procs: ## ## - 'solve' - called by the 'Solve' button ## to compute V1a,V2a ## ## - 'animate' - called by a click on the 'Start' animation ## radiobutton ## ## - 'setMappingVars_for_px2wc' - called by proc 'animate' ## ## - 'Xwc2px' - called by proc 'animate' ## - 'Ywc2px' - called by proc 'animate' ## ## - 'show_list' - called by the 'ShowList' button ## ## - 'set_mass_color1' - called by the 'MassesColor' button ## ## - 'set_background_color2' - called by the 'BackgroundColor' button ## ## - 'update_color_button' - sets background & foreground color of ## either of the 2 color buttons ## ## - 'advise_user' - called by the 'solve' and 'animate' procs ## ## - 'reset_parms' - called by the 'ResetParms' button ## ## - 'edit_inputs' - called by the 'solve' and 'animate' procs ## ## - 'decimal_check' - called by the 'edit_inputs' proc ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button ## ## 5) Additional GUI initialization: ## Set some inital values of parameters ## such as the 2 colors and use a call to ## proc 'reset_parms' to set starting values ## for the masses and before-impact velocities. ## ##+######################################################################## ## DEVELOPED WITH: ## Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala'). ## ## $ wish ## % puts "$tcl_version $tk_version" ## showed 8.5 8.5 on Ubuntu 9.10 ## after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts. ##+####################################################################### ## MAINTENANCE HISTORY: ## Created by: Blaise Montandon 2016jul18 ## Changed by: Blaise Montandon 2016jul19 Final changes to prepare for ## release at www.freedomenv.com. ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "Animate the Collision of Two Rigid Masses --- 'direct', 'in-line' impact" wm iconname . "Collision2masses" wm geometry . +15+30 ##+###################################################### ## Set the color scheme for the window and set the ## background color for the 'trough' in some widgets. ##+###################################################### tk_setPalette "#e0e0e0" set entryBKGD "#f0f0f0" set radbuttBKGD "#f0f0f0" # set scaleBKGD "#f0f0f0" # set chkbuttBKGD "#f0f0f0" # set listboxBKGD "#f0f0f0" ##+######################################################## ## Use a VARIABLE-WIDTH FONT for label and button widgets. ## ## Use a FIXED-WIDTH FONT for listboxes (and ## entry fields, if any). ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## BUTTON geom parameters: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 set RELIEF_button "raised" ## LABEL geom parameters: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 # set RELIEF_label "ridge" # set RELIEF_label "raised" set RELIEF_label "flat" ## RADIOBUTTON widget geom settings: set BDwidthPx_radbutt 2 # set RELIEF_radbutt "ridge" set RELIEF_radbutt "raised" ## ENTRY widget geom settings: set BDwidthPx_entry 2 set ParmEntryWidthChars 7 ## SCALE geom parameters: ## (de-activated, for now) if {0} { set BDwidthPx_scale 2 # set initScaleLengthPx 100 set scaleThicknessPx 10 set scaleRepeatDelayMillisecs 800 } ## CANVAS geom parameters: set initCanWidthPx 300 set initCanHeightPx 300 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ##+################################################################### ## Set a MINSIZE of the window. ## ## For width, allow for the minwidth of the '.fRbuttons' frame: ## about 6 buttons, 1 label, 2 radiobuttons ## (Exit,Help,Solve,Reset,Animate,Start,Stop,Color,Color). ## We want to at least be able to see the Exit button. ## ## For height, allow ## 2 chars high for the '.fRbuttons' frame, ## 4 chars high for the '.fRguide' frame, ## 1 char high for the '.fRmasses' frame, ## 1 char high for the '.fRinit' frame, ## 1 chars high for the '.fRmsg' frame, ## 24 pixels high for the '.fRcanvas' frame. ##+################################################################### ## MIN WIDTH: set minWinWidthPx [font measure fontTEMP_varwidth \ "Exit Help Solve ResetParms Animate: Start Stop Color Color ShowList"] ## Add some pixels to account for right-left-side window decoration ## (about 8 pixels), about 10 x 4 pixels/widget for borders/padding for ## 10 widgets. set minWinWidthPx [expr {48 + $minWinWidthPx}] ## MIN HEIGHT --- allow ## 2 chars high for '.fRbuttons' ## 4 chars high for '.fRguide' ## 1 char high for '.fRmasses' ## 1 char high for '.fRinit' ## 1 chars high for '.fRmsg' ## 24 pixels high for '.fRcanvas' set CharHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {24 + (9 * $CharHeightPx)}] ## Add about 28 pixels for top-bottom window decoration, ## about 6x4 pixels for each of the 6 stacked frames and their ## widgets (their borders/padding). set minWinHeightPx [expr {$minWinHeightPx + 52}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We allow the window to be resizable and we pack the canvas with ## '-fill both -expand 1' so that the canvas can be enlarged by enlarging ## the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+#################################################################### ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+#################################################################### ## if { "$VARlocale" == "en"} ## For widgets in 'fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonSOLVE) "Solve" set aRtext(buttonRESET) "ResetParms" set aRtext(labelANIMATE) "Animate:" set aRtext(radbuttSTART) "Start" set aRtext(radbuttSTOP) "Stop" set aRtext(buttonSHOW) "ShowList" set aRtext(buttonCOLOR1) "Masses Color" set aRtext(buttonCOLOR2) "Background Color" ## For widgets in 'fReqns' frame: set aRtext(labelEQNS) \ "Use the 'Solve' button to compute two after-impact velocities, V1a and V2a. Use 'Start' to do an animation. For animation, mass1 is started on the left of mass2. Always set the signs of the two initial, before-impact velocities so that impact is possible. You will be notified if impact is not possible. See 'Help' for more information." ## For widgets in .fRmasses' frame: set aRtext(labelM1) "Mass1:" set aRtext(labelM2) " Mass2:" set aRtext(labelD) " D (initial distance between masses):" set aRtext(labelIMGSIZE) " Image width (pixels):" ## For widgets in 'fRinit' frame: set aRtext(labelVEL1) " V1b:" set aRtext(labelVEL2) " V2b:" set aRtext(labelVELINFO) "<-- Initial (before-impact) Velocities, in distance-units/time-unit." ## For some calls to the 'advise_user' proc: set aRtext(SOLVEmsg) "*** Click 'Solve' when ready to do the solve ***" set aRtext(STARTmsg) "*** Click 'Start' when ready to start the animation ***" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRguide' '.fRmasses' ## '.fRinit' '.fRmsg' '.fRcanvas' ##+################################################################ ## FOR TESTING change 0 to 1: ## (Example1: To see appearance of frames when borders are drawn.) ## (Example2: To see sizes of frames for various '-fill' options.) ## (Example3: To see how frames expand as window is resized.) if {0} { set RELIEF_frame raised set BDwidthPx_frame 2 } else { set RELIEF_frame flat set BDwidthPx_frame 0 } frame .fRbuttons -relief $RELIEF_frame -borderwidth $BDwidthPx_frame # frame .fRguide -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRguide -relief raised -borderwidth 2 frame .fRmasses -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRinit -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRmsg -relief raised -borderwidth 2 # frame .fRcanvas -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRcanvas -relief raised -borderwidth 2 ##+############################## ## PACK the top-level FRAMES. ##+############################## pack .fRbuttons \ .fRguide \ .fRmasses \ .fRinit \ .fRmsg \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvas \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+######################################################### ## OK. Now we are ready to define the widgets in the frames. ##+######################################################### ##+##################################################################### ## In the '.fRbuttons' FRAME --- DEFINE ## - 'Exit','Help','Solve','ResetParms','ShowList' buttons ## - a label and 2 start/stop radiobuttons ## and ## - 2 buttons (to specify colors). ## - 1 label widget for display of V1a,V2a ## Then PACK all these widgets. ##+##################################################################### button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext" +10+10} button .fRbuttons.buttSOLVE \ -text "$aRtext(buttonSOLVE)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {solve} button .fRbuttons.buttRESET \ -text "$aRtext(buttonRESET)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {reset_parms} label .fRbuttons.labelANIMATE \ -text "$aRtext(labelANIMATE)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## 'VARanimate0or1' is the var for these 2 radiobuttons. set VARanimate0or1 0 radiobutton .fRbuttons.radbuttSTART \ -text "$aRtext(radbuttSTART)" \ -font fontTEMP_varwidth \ -anchor w \ -variable VARanimate0or1 \ -value 1 \ -selectcolor "$radbuttBKGD" \ -relief $RELIEF_radbutt \ -state disabled \ -bd $BDwidthPx_radbutt radiobutton .fRbuttons.radbuttSTOP \ -text "$aRtext(radbuttSTOP)" \ -font fontTEMP_varwidth \ -anchor w \ -variable VARanimate0or1 \ -value 0 \ -selectcolor "$radbuttBKGD" \ -relief $RELIEF_radbutt \ -bd $BDwidthPx_radbutt button .fRbuttons.buttCOLOR1 \ -text "$aRtext(buttonCOLOR1)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_mass_color1" button .fRbuttons.buttCOLOR2 \ -text "$aRtext(buttonCOLOR2)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_background_color2" button .fRbuttons.buttSHOW \ -text "$aRtext(buttonSHOW)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -state disabled \ -command {show_list} label .fRbuttons.labelINFO \ -text "" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttSOLVE \ .fRbuttons.buttRESET \ .fRbuttons.labelANIMATE \ .fRbuttons.radbuttSTART \ .fRbuttons.radbuttSTOP \ .fRbuttons.buttCOLOR1 \ .fRbuttons.buttCOLOR2 \ .fRbuttons.buttSHOW \ .fRbuttons.labelINFO \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRguide' FRAME ---- DEFINE 1 LABEL widget. ## Then PACK all these widgets. ##+################################################################## label .fRguide.labelEQNS \ -text "$aRtext(labelEQNS)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bg "#66ff66" \ -bd $BDwidthPx_label pack .fRguide.labelEQNS \ -side left \ -anchor w \ -fill x \ -expand 1 ##+################################################################## ## In the '.fRmasses' FRAME ---- DEFINE 4 pairs of ## LABEL-and-ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## ## FOR MASS1: label .fRmasses.labelM1 \ -text "$aRtext(labelM1)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYmass1 "10.0" entry .fRmasses.entryM1 \ -textvariable ENTRYmass1 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR MASS2: label .fRmasses.labelM2 \ -text "$aRtext(labelM2)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYmass2 "10.0" entry .fRmasses.entryM2 \ -textvariable ENTRYmass2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR D (distance between masses): label .fRmasses.labelD \ -text "$aRtext(labelD)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYdist "50.0" entry .fRmasses.entryD \ -textvariable ENTRYdist \ -bg $entryBKGD \ -font fontTEMP_SMALL_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR IMAGE SIZE: label .fRmasses.labelIMGSIZE \ -text "$aRtext(labelIMGSIZE)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYimgsize "600" entry .fRmasses.entryIMGSIZE \ -textvariable ENTRYimgsize \ -bg $entryBKGD \ -font fontTEMP_SMALL_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ##+##################################### ## PACK the widgets in frame '.fRmasses'. ##+##################################### pack .fRmasses.labelM1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRmasses.entryM1 \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRmasses.labelM2 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRmasses.entryM2 \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRmasses.labelD \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRmasses.entryD \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRmasses.entryIMGSIZE \ -side right \ -anchor e \ -fill x \ -expand 0 pack .fRmasses.labelIMGSIZE \ -side right \ -anchor e \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRinit' FRAME ---- DEFINE 2 pairs of ## LABEL and ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## ## FOR BEFORE-IMPACT VELOCITY of MASS1: label .fRinit.labelVEL1 \ -text "$aRtext(labelVEL1)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYvel1 "20.0" entry .fRinit.entryVEL1 \ -textvariable ENTRYvel1 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR BEFORE-IMPACT VELOCITY of MASS2: label .fRinit.labelVEL2 \ -text "$aRtext(labelVEL2)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYvel2 "-20.0" entry .fRinit.entryVEL2 \ -textvariable ENTRYvel2 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## VELOCITY INFO: label .fRinit.labelVELINFO \ -text "$aRtext(labelVELINFO)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ##+##################################### ## PACK the widgets in frame '.fRinit'. ##+##################################### pack .fRinit.labelVEL1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRinit.entryVEL1 \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRinit.labelVEL2 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRinit.entryVEL2 \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRinit.labelVELINFO \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRmsg' FRAME ---- DEFINE-and-PACK 1 LABEL widget. ##+################################################################## label .fRmsg.labelMSG \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bg "#ff6666" \ -bd $BDwidthPx_button pack .fRmsg.labelMSG \ -side left \ -anchor w \ -fill x \ -expand 1 ##+###################################################### ## In the '.fRcanvas' FRAME - DEFINE the 'canvas' widget ## --- no scrollbars, for now. ## Then PACK the widget(s). ##+###################################################### ## We set highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+###################################################### canvas .fRcanvas.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 # -yscrollcommand ".fRcanvas.scrolly set" \ # -xscrollcommand ".fRcanvas.scrollx set" # scrollbar .fRcanvas.scrolly \ # -orient vertical \ # -command ".fRcanvas.can yview" # scrollbar .fRcanvas.scrollx \ # -orient horizontal \ # -command ".fRcanvas.can xview" ##+####################################################### ## PACK the widgets in frame '.fRcanvas'. ## (Skip the scrollbar pack statements, for now.) ##+####################################################### if {0} { ##+######################################################## ## NOTE: ## NEED TO PACK THE SCROLLBARS BEFORE THE CANVAS WIDGET. ## OTHERWISE THE CANVAS WIDGET TAKES ALL THE FRAME SPACE. ##+####################################################### pack .fRcanvas.scrolly \ -side right \ -anchor e \ -fill y \ -expand 0 pack .fRcanvas.scrollx \ -side bottom \ -anchor s \ -fill x \ -expand 0 ##+############################################################## ## !!!NEED TO USE '-expand 0' FOR THE X AND Y SCROLLBARS, so that ## the canvas is allowed to fill the remaining frame-space nicely ## --- without a gap between the canvas and its scrollbars. ##+############################################################## } ## END OF if {0} pack .fRcanvas.can \ -side top \ -anchor n \ -fill none \ -expand 0 ## We don't let the canvas expand, because we want ## to make it a square with colored background --- ## centered at the bottom of the GUI. # -fill both \ # -expand 1 ##+######################################## ## END OF the DEFINITION OF THE GUI WIDGETS ##+######################################## ##+############################### ## BINDINGS SECTION: ##+############################### ##+################################################################### ## Remind user to click 'Solve' after changing a mass or ## initial velocity parameter. ##+################################################################### bind .fRmasses.entryM1 \ {advise_user "$aRtext(SOLVEmsg) with new mass1 value."} bind .fRmasses.entryM1 \ {advise_user "$aRtext(SOLVEmsg) with new mass1 value."} bind .fRmasses.entryM2 \ {advise_user "$aRtext(SOLVEmsg) with new mass2 value."} bind .fRmasses.entryM2 \ {advise_user "$aRtext(SOLVEmsg) with new mass2 value."} bind .fRinit.entryVEL1 \ {advise_user "$aRtext(SOLVEmsg) with new mass1-velocity value."} bind .fRinit.entryVEL1 \ {advise_user "$aRtext(SOLVEmsg) with new mass1-velocity value."} bind .fRinit.entryVEL2 \ {advise_user "$aRtext(SOLVEmsg) with new mass2-velocity value."} bind .fRinit.entryVEL2 \ {advise_user "$aRtext(SOLVEmsg) with new mass2-velocity value."} ##+################################################################ ## Remind user to click animate 'Start' button after changing ## an animation-related parameter. ##+################################################################ bind .fRmasses.entryD \ {advise_user "$aRtext(STARTmsg) with new distance D value."} bind .fRmasses.entryD \ {advise_user "$aRtext(STARTmsg) with new distance D value."} bind .fRmasses.entryIMGSIZE \ {advise_user "$aRtext(STARTmsg) with new image size value."} bind .fRmasses.entryIMGSIZE \ {advise_user "$aRtext(STARTmsg) with new image size value."} bind .fRbuttons.buttCOLOR1 \ {advise_user "$aRtext(STARTmsg) with new masses (circles) color."} bind .fRbuttons.buttCOLOR2 \ {advise_user "$aRtext(STARTmsg) with new background color."} ##+####################################################### ## Start the animation with a button1-release on the ## 'Start' radiobutton. ##+####################################################### bind .fRbuttons.radbuttSTART {animate} ##+###################################################################### ## PROCS SECTION: ## ## - 'solve' - called by the 'Solve' button. ## ## - 'animate' - called by a click on the 'Start' animation radiobutton. ## ## - 'show_list' - called by the 'ShowList' button ## ## - 'setMappingVars_for_px2wc' - called by proc 'animate'. ## ## - 'Xwc2px' - called by proc 'animate'. ## - 'Ywc2px' - called by proc 'animate'. ## ## - 'set_mass_color1' - called by the 'MassesColor' button. ## ## - 'set_background_color2' - called by the 'BackgroundColor' button. ## ## - 'update_color_button' - sets background & foreground color of ## either of the 2 color buttons. ## ## - 'advise_user' - called by the 'solver' and 'animate' procs. ## ## - 'reset_parms' - called by the 'ResetParms' button and in the ## 'Additional GUI Initialization' section, ## to initialize the parms. ## ## - 'edit_inputs' - called by 'solve' and 'animate' procs ## ## - 'decimal_check' - called by 'edit_inputs' proc ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button. ## ##+####################################################################### ##+##################################################################### ## PROC: solve ## ## PURPOSE: ## Returns value for V1a and V2a using the two equations ## ## V1a = (((m1 - m2)*V1b) + (2*m2*V2b)) / (m1 + m2) ## V2a = (((m2 - m1)*V2b) + (2*m1*V1b)) / (m1 + m2) ## ## The output is saved in global variables V1a and V2a. ## ## CALLED BY: a click on the 'Solve' button. ##+#################################################################### proc solve {} { ## FOR TESTING: (dummy out this proc) # return global ENTRYmass1 ENTRYmass2 ENTRYvel1 ENTRYvel2 \ m1 m2 V1b V2b V1a V2a EDITcode ################################################ ## Disable the the Animate 'Start' radiobutton ## during a solve. ## Disable the 'ShowList' button during a solve. ################################################ .fRbuttons.radbuttSTART configure -state disabled .fRbuttons.buttSHOW configure -state disabled ######################################################## ## Check the entry field inputs. ######################################################## edit_inputs if {$EDITcode > 0} {return} ################################################ ## Set the current time, for determining elapsed ## time for doing this solve. ## (The solve proceeds so fast that the ## elapsed time will typically be 0 millisecs.) ################################################ set t0 [clock milliseconds] ################################################ ## Compute V1a and V2a. ################################################ set m1 [expr {double($ENTRYmass1)}] set m2 [expr {double($ENTRYmass2)}] set V1b [expr {double($ENTRYvel1)}] set V2b [expr {double($ENTRYvel2)}] set m1PLUSm2 [expr { $m1 + $m2}] set m1MINUSm2 [expr { $m1 - $m2}] set V1a [expr { (($m1MINUSm2 * $V1b) + (2.0 * $m2 * $V2b)) / $m1PLUSm2 }] set V2a [expr { (($m1MINUSm2 * $V2b) + (2.0 * $m1 * $V1b)) / $m1PLUSm2 }] ######################################################## ## Show the user the elapsed-time for this solution run. ######################################################## set solvetime [expr {[clock milliseconds] - $t0}] set V1aFMTED [format "%6.2f" $V1a] set V2aFMTED [format "%6.2f" $V2a] advise_user "\ ** SOLVE DONE: $solvetime millisecs elapsed ; COMPUTED: V1a = $V1aFMTED V2a = $V2aFMTED **" .fRbuttons.labelINFO configure -text \ "Currently V1a = $V1aFMTED and V2a = $V2aFMTED" ################################################################## ## Activate the disabled 'Start' radiobutton, since the ## velocities V1a,V2a are now available for the 'animate' proc. ## (This solve proceeds so fast the user will probably ## not notice that the 'Start' radiobutton was ## temporarily deactivated while V1a,V2a are being changed.) ## ## The 'ShowList' button will be activated at the bottom of ## the 'animate' proc --- after V1a,v2a are used to calculate ## new values in arrays aRtime,aRx1,aRx2. ################################################################## .fRbuttons.radbuttSTART configure -state normal } ## END OF PROC 'solve' ##+##################################################################### ## PROC: animate ## ## PURPOSE: ## Starts drawing and showing the sequence of positions of the ## 2 masses --- with 'create oval' statements. ## ## Also use 'create line' to draw a horizontal line to represent ## the path that the centers of the 2 masses follow. ## ## OVERVIEW OF THE METHOD: ## ## 0) Clear the canvas. ## Set the image area (dimensions of the canvas) ## according to the ENTRYimgsize variable. ## Set the canvas background color. ## ## 1) A check is done to make sure the 2 velocities V1b,V2b ## will result in an impact point. If no impact, ## post a message to the user and quit. ## ## 2) Set appropriate radii (r1,r2) of the 2 masses in world-coordinates. ## (The sum of r1 and r2 should be less than D. We could set ## the radius of the bigger mass, M, to be about 0.1*D, say, and ## then set the radius of the smaller mass, m, according to the ## ratio m/M.) ## ## 3) The time (tI) and mass locations (x1I,x2I) at the impact are computed ## on the basis of V1b,V2b and D,r1,r2 --- assuming m1 is initally ## located at x=0.0 and m2 located at x=D. ## ## 4) Based on x1I,x2I and D, calculate an Xmin and Xmax to be ## appropriate limits within which the animation can be ## done to allow some before-impact and after-impact motion. ## ## 5) Map world-coordinates Xmin,Xmax to the image area ## horizontal left and right limits, 0 to ImgWidthPx, in pixels. ## This sets parameter values that can be used to convert ## world-coordinate values x1,x2 to pixel-coordinate values ## using 2 procs Xwc2px and Ywc2px. ## ## 6) Calculate an appropriate time-step, h, to allow for ## smooth motion of the masses, based on the max velocity ## among the before and after velocities: V1b,V2b,V1a,V2a. ## ## 7) Draw a horizontal line along which the centers of ## the two masses move. ## ## 8) In a loop, start animating the motion of the two masses ## by using the 'create oval' and 'delete' commands on the ## Tk canvas. ## ## Stop when the center of both (or one) of the masses ## go off the left or right of the image area. ## Or stop if the user clicks the 'Stop' radiobutton. ## ## As the animation proceeds store values in ## time(i), x1(i), x2(i). These can be used in the ## 'show_list' proc. ## ##+##################################################################### ## CALLED BY: A click on the 'Start' animation radiobutton. ##+##################################################################### proc animate {} { ## FOR TESTING: (dummy out this proc) # return ######################################################### ## Declare the global variables for inputs and outputs. ## INPUTS: ## - Make available the m1 m2 V1b V2b V1a V2a variables ## that were set in the 'solve' proc. ## - Make available the COLOR variables for use in ## coloring the canvas background and for use in ## 'create oval' and 'create line' commands. ## - Make available ENTRYimgsize to set the width of ## the image area. ## - Make available the PXperWCx PXperWCy variables to ## convert the radii of the 2 masses to pixel values. ## OUTPUTS: ## - Use the variables aRtime aRx1 aRx2 Nstep to store ## values that may be used in the 'show_list' proc. ######################################################## global VARanimate0or1 m1 m2 V1b V2b V1a V2a \ ENTRYdist D ENTRYimgsize EDITcode aRtext \ PXperWCx PXperWCy aRtime aRx1 aRx2 Nstep h \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex ######################################################## ## Check the entry field inputs --- esp. ENTRYimgsize. ######################################################## edit_inputs if {$EDITcode > 0} {return} ############################################################ ## Clear the message area --- replace with an animation msg. ############################################################ advise_user "* Animation IN PROGRESS. Click 'Stop' radiobutton to halt. *" ############################################################ ## Clear the canvas, including the circles of the 2 masses ## and the horizontal line on which the centers of the ## two masses move left over from a previous animation. ############################################################ .fRcanvas.can delete all ############################################################## ## Configure the canvas dimensions into the image area that ## we are going to use --- using the user-specified image width ## in variable ENTRYimgsize. ## ## We want the canvas background color to delineate a wide ## rectangular area on which the sequence of positons of ## the 2 masses will be drawn. ############################################################## set ImgWidthPx $ENTRYimgsize set imgAspectYtoX 0.2 set ImgHeightPx [expr {int($imgAspectYtoX * $ENTRYimgsize)}] .fRcanvas.can configure -width $ImgWidthPx .fRcanvas.can configure -height $ImgHeightPx ############################################################ ## Set the background color of the canvas area from the ## current background color variable. ############################################################ .fRcanvas.can config -bg $COLOR2hex ############################################################# ## We use a 'wm geometry' command to get the window (and the ## canvas widget) to resize appropriately --- even after the ## user has manually resized the top window. ## ## Reference: wiki.tcl.tk/10720 and wiki.tcl.tk/44 ## and page 237 of Ousterhout's book 'Tcl and the Tk Toolkit': ## "If you would like to restore a window to its natural ## size, you can invoke 'wm geometry' with an empty ## geometry string." ############################################################# wm geometry . {} ####################################################### ## Check to make sure the 2 velocities V1b,V2b ## will result in an impact point. If no impact, ## post a message to the user and quit. ####################################################### ## NINE CASES OF Postive/Negative/Zero VALUES OF THE ## 2 'before' VELOCITIES --- with 2 sub-cases: ## ## 1 +,-) If V1b > 0.0 and V2b < 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 2 +,0) If V1b > 0.0 and V2b = 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 3 0,-) If V1b = 0.0 and V2b < 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 4 +,+) If V1b > 0.0 and V2b > 0.0, the two masses are moving ## to the right. In this case, when V1b > V2b, there ## will eventually be an impact --- otherwise, no impact. ## ## 4b In other words, if V1b <= V2b, ## advise the user to use a different velocity combination. ## ## 5 -,-) If V1b < 0.0 and V2b < 0.0, the two masses are moving ## to the left. In this case, when abs(V2b) > abs(V1b) ## --- that is, when V2b < V1b --- there will eventually ## be an impact --- otherwise, no impact. ## ## 5b In other words, if V1b <= V2b, ## advise the user to use a different velocity combination. ## ## 6 -,+) If V1b < 0.0 and V2b > 0.0, there will be no impact, so ## advise the user to use a different velocity combination. ## ## 7 -,0) If V1b < 0.0 and V2b = 0.0, there will be no impact, so ## advise the user to use a different velocity combination. ## ## 8 0,+) If V1b = 0.0 and V2b > 0.0, there will be no impact, so ## advise the user to use a different velocity combination. ## ## 9 0,0) If V1b = 0.0 and V2b = 0.0, there will be no impact, so ## advise the user to use a different velocity combination. ################################################################ set aRtext(NOIMPACTmsg) " there will be no impact. Try a different velocity combo." ## 6 and 7: if {$V1b < 0.0 && $V2b >= 0.0} { advise_user "With V1b < 0.0 nd V2b >= 0.0, $aRtext(NOIMPACTmsg)" return } ## 8 and 9: if {$V1b == 0.0 && $V2b >= 0.0} { advise_user "With V1b = 0.0 and $V2b >= 0.0, $aRtext(NOIMPACTmsg)" return } ## 4b if {$V1b > 0.0 && $V2b > 0.0 && $V1b <= $V2b} { advise_user "With V1b and V2b > 0.0 and with V1b <= V2b, $aRtext(NOIMPACTmsg)" return } ## 5b if {$V1b < 0.0 && $V2b < 0.0 && $V1b <= $V2b} { advise_user "With V1b and V2b < 0.0 and with V1b <= V2b, $aRtext(NOIMPACTmsg)" return } ################################################################ ## SET MASS RADII r1,r2: ## If we get to this point, there is an impact. We now compute ## the radii r1 and r2 of the 2 masses, because we need them ## to compute the time (tI) and points (x1I,x2I) of impact. ################################################################ ## We set appropriate radii (r1,r2) of the 2 masses in ## world-coordinates. (Later we convert to pixel coordinates.) ## ## The sum of r1 and r2 should be less than the user-specified D, ## which is in world-coordinates. ## ## We set the radius, R, of the bigger mass, M, to be about ## 0.1*D, say, and then set the radius, r, of the smaller mass, ## m, according to the ratio m/M --- as follows. ################################################################ ## SETTING THE RADIUS OF THE SMALLER MASS: ## ## We want to indicate the magnitude of the two masses by setting ## the radii of the 2 circles according to the user-specified ## mass values. ## ## To determine the radii, we assume that mass m(i) is proportional ## to pi*r(i)^2 (pi times radius squared), for i = 1, 2. ## ## Let M and R be the mass and radius of the larger mass. ## Let m and r be the mass and radius of the smaller mass. ## ## Let us assume that the mass-density of the 2 masses ## (in units of mass per distance-units squared) is the same, ## say k. ## ## Then M = k * (area of M's circle) = k * pi * R^2 ## And m = k * (area of m's circle) = k * pi * r^2 ## ## So (r/R)^2 = m/M. Thus r/R = sqrt(m/M). ## Thus r = R * sqrt(m/M). ## ## So, in summary, we compute R and r with the 2 equations ## R = 0.1*D ## r = R * sqrt(m/M) ################################################################ set D [expr {double($ENTRYdist)}] set M $m1 set idxM 1 set m $m2 if {$m2 > $m1} { set M $m2 set idxM 2 set $m1 } set R [expr {0.1 * $D}] set r [expr {$R * sqrt($m/$M)}] if {$idxM == 1} { set r1 $R set r2 $r } else { set r1 $r set r2 $R } ###################################################################### ## If we get to this point, there is an impact. ## We now want to compute the time (tI) and points (x1I,x2I) of impact. ###################################################################### ## 5 of the 9 CASES (above) OF Postive/Negative/Zero Values ## OF THE 2 'before' VELOCITIES result in an impact: ## ## 1 +,-) If V1b > 0.0 and V2b < 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 2 +,0) If V1b > 0.0 and V2b = 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 3 0,-) If V1b = 0.0 and V2b < 0.0, the two masses ## approach each other. This is OK. No need to exit. ## ## 4 +,+) If V1b > 0.0 and V2b > 0.0, the two masses are moving ## to the right. In this case, when V1b > V2b, there ## will eventually be an impact. In other words, ## V1b > 0.0 and V2b > 0.0 and V1b > V2b ## (i.e. mass1 is moving to the right faster than mass2) ## yields an impact. ## ## 5 -,-) If V1b < 0.0 and V2b < 0.0, the two masses are moving ## to the left. In this case, when abs(V2b) > abs(V1b) ## --- that is, when V2b < V1b, there will eventually be ## an impact. In other words, ## V1b < 0.0 and V2b < 0.0 and V2b < V1b ## (i.e. mass2 is moving to the left faster than mass1) ## yields an impact. ## ## We could combine the first 3 cases into 2 checks: ## ## + , - OR 0) V1b > 0.0 and V2b <= 0.0 ## ## + OR 0 , -) V1b >= 0.0 and V2b < 0.0 ## ## This eliminates the case when both masses have zero velocity ## (i.e. are at rest) and thus cannot impact each other. ## But this includes the two cases when one of the masses is resting ## at zero velocity, but the other mass is traveling toward the ## mass at rest. ## ## Hopefully we can use one algebraic expression to determine ## tI for all of these 'impact cases'. #################################################################### ## The time (tI) and locations (x1I,x2I) of the impact is computed ## on the basis of V1b,V2b and D --- assuming m1 is located ## at x=0.0 and m2 located at x=D. ## ## Some details of calculating tI and x1I,x2I follow. #################################################################### ## HANDLING THE TIME OF IMPACT and POINT OF IMPACT: ## ## Note that the position of the 2 masses at any time t ## are given by ## x1(t) = 0.0 + V1b*t ## x2(t) = D + V2b*t ## ## The impact is determined when x1(t) + r1 = x2(t) - r2 ## where x1 and r1 are the location and radius of mass1 (on the left) ## and x2 and r2 are the location and radius of mass2 (on the right). ## ## So at the time of impact, tI, the following equation holds. ## V1b*tI + r1 = D + V2b*tI - r2 ## So solving for tI gives ## tI = (D - (r1 + r2))/(V1b - V2b) ## ## Then tI can be used to calculate x1I and x2I ## x1I = V1b*tI ## x2I = D + V2b*tI ##################################################################### set tI [expr { ($D - ($r1 + $r2)) / ($V1b - $V2b) }] set x1I [expr { $V1b * $tI }] set x2I [expr { $D + ($V2b * $tI) }] ############################################################# ## Based on x1I,x2I (the impact points) ## and on 0.0,D (the initial positions of mass1,mass2) ## we calculate an Xmin and Xmax to be appropriate limits ## within which the animation can be done to allow ## some before-impact and after-impact motion. ############################################################# ## We set ## Xmin = min(0.0,x1I,x2I) ## Xmax = max(D,x1I,x2I) ## and we appy a margin on the left and right to allow ## some room to move the masses after impact. ## ## We may use D (or some multiple of D, such as 2*D) ## as the margin. ############################################################# set Xmin [expr { min(0.0,$x1I,$x2I) }] set Xmax [expr { max($D,$x1I,$x2I) }] set Xmin [expr { $Xmin - $D }] set Xmax [expr { $Xmax + $D }] ############################################################ ## Now we want to set parameter values that can be used to ## convert world-coordinate values to pixel-coordinate values ## Those parameters are to be used later in 2 procs Xwc2px and Ywc2px. ## ## We now map world-coordinates Xmin,Xmax to the image area ## horizontal limits --- which are, in pixels, 0 to ImgWidthPx. ## ## The size of the image area was set above with statements ## ## set ImgWidthPx $ENTRYimgsize ## set imgAspectYtoX 0.2 ## set ImgHeightPx [expr {int($imgAspectYtoX * $ENTRYimgsize)}] ## ## We want the height of the image-area in world-coordinates ## to reflect the same aspect ratio. So we set the height ## of the image area in world-coordinates as ## ImgWidthWC = Xmax - Xmin ## ImgHeightWC = imgAspectYtoX * ImgWidthWC ## ## We let 0.0 be the height of a line across the middle ## of the image area --- so the world-coordinate for the ## TOP of the image-area is +ImgHeightWC/2 ## and the world-coordinate for the ## BOTTOM of the image-area is -ImgHeightWC/2. ################################################################ ## Recall input vars for proc 'setMappingVars_for_px2wc' are: ## xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY ## where UL = upper-left and LR = lower-right ## The 4 UL variables are for the upper-left corner point ## in world-coordinates (wc) and in pixel-coordinates (px). ## The 4 LR variables are for the lower-right corner point ## in world-coordinates (wc) and in pixel-coordinates (px). ## ## We map the upper-left of the image area: ## world-coordinates (Xmin , ImgHeightWC/2) ## TO pixel-coordinated ( 0 , 0 ) ## ## AND ## ## We map the lower-left of the image area: ## world-coordinates (Xmax , -ImgHeightWC/2) ## TO pixel-coordinates (ImgWidthPx,ImgHeightPx) ## ## See code in 'setMappingVars_for_px2wc' for details. ######################################################################## set ImgWidthWC [expr {$Xmax - $Xmin}] set ImgHeightWC [expr {$imgAspectYtoX * $ImgWidthWC}] set Hhalf [expr {$ImgHeightWC / 2.0}] set XwcUL $Xmin set YwcUL $Hhalf set XwcLR $Xmax set YwcLR [expr { -$Hhalf}] setMappingVars_for_px2wc xy $XwcUL $YwcUL 0 0 $XwcLR $YwcLR $ImgWidthPx $ImgHeightPx ############################################################# ## Calculate an appropriate time-step, h, to allow for ## smooth motion of the masses, based on the max velocity ## among the before and after velocities: V1b,V2b,V1a,V2a. ############################################################# ## To determine time-step h: ## ## First we note that we want the motion of the 2 circles to be ## fairly smooth. ## ## Since screens nowadays have a width of about 1000 to 2000 pixels, ## the image area can be about that wide --- so ImgWidthPx may be ## around 1000 pixels. ## ## For smooth motion on such an image area, we would want to ## move less than 1 percent of the width per time step --- ## that is, less than 10 pixels. Let us aim for about 2 pixels ## per time step. ## ## Let Vmax = max of the absolute values of V1b,V2b,V1a,V2a, ## the before and after velocities of the 2 masses. ## ## The for a time step, h, h*Vmax is the largest movement ## of the 2 masses per time step, in world coordinates. ## ## Then PXperWCx*h*Vmax is that distance in pixels, where ## PXperWCx is a conversion factor that was determined by ## the world-coordinates and pixel-coordinates mapping above. ## ## Since we want PXperWCx*h*Vmax < 3 pixels, this implies ## h should be less than 3/(PXperWCx*Vmax) --- ## say h = 2.0/(PXperWCx*Vmax) in world-coordinates. ################################################################ set Vmax [expr {max(abs($V1b),abs($V2b),abs($V1a),abs($V2a))}] set h [expr {2.0 / ($PXperWCx * $Vmax)}] ########################################################### ## Compute a WAITmillisecs value for a pause at the bottom ## of the loop --- after a draw of the 2 masses and before ## the next draw in the next pass thru the loop. ## (This is a preliminary setting. ## We would like to make the drawing proceed at ## a realistic rate. This may mean that we compute ## WAITmillisecs based on 'h', but we may need to use ## a different method involving, Vmax, Xmin,Xmax, say.) ########################################################## set WAITmillisecs [expr {int(1000 * $h)}] ################################################################# ## Set the (constant) y-coordinate of the 2 masses ## in pixel-coordinates. ## ## This avoids repeating this little bit of processing ## over and over again in the loop below. ## And we can use this to draw a horizontal line across ## the middle of the image area. ################################################################# set yPx [expr {int($ImgHeightPx/2.0)}] ######################################################### ## Draw a horizontal line along which the centers of ## the two masses move. Uses 'create line'. ######################################################### ## Set the pixel coordinates of the left and right ## end-points of the line. ######################################################### set XminPx [Xwc2px $Xmin] set XmaxPx [Xwc2px $Xmax] .fRcanvas.can create line $XminPx $yPx $XmaxPx $yPx \ -tags TAGline -fill "$COLOR1hex" ######################################################### ## Draw short vertical lines at 0.0 and +D at the height ## of the line along which the centers of the two masses ## move. Uses 'create line'. ######################################################### set yPx2 [expr {$yPx - 4}] set ZEROpx [Xwc2px 0.0] set Dpx [Xwc2px $D] .fRcanvas.can create line $ZEROpx $yPx $ZEROpx $yPx2 \ -tags TAGline -fill "$COLOR1hex" .fRcanvas.can create line $Dpx $yPx $Dpx $yPx2 \ -tags TAGline -fill "$COLOR1hex" .fRcanvas.can create text $ZEROpx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "0.0" -anchor s set DpxFMTED [format "%6.2f" $D] .fRcanvas.can create text $Dpx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "$DpxFMTED" -anchor s ######################################################### ## Draw text (distances) at the left and right end-points ## of the horizontal line representing the flat surface. ## This gives the user an idea of the distance ## (and x-coordinate values) across the image area. ## Uses 'create text'. ## (If too confusing, this can be de-activated ## by changing 'if {1}' to 'if {0}'.) ######################################################### if {1} { set HhalfFMTED [format "%6.2f" $Hhalf] .fRcanvas.can create text $XminPx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "$Xmin" -anchor sw .fRcanvas.can create text $XmaxPx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "$Xmax" -anchor se } ################################################################# ## Set the radii in pixels --- for use in the loop below. ## ## This avoids repeating this little bit of processing ## over and over again. ## ## We use the radii in world-coordinates (r1,r2) that were set above. ## And we use the global variable PXperWCx which was set in the ## 'setMappingVars_for_px2wc' proc. ################################################################# set radius1Px [expr {$PXperWCx * $r1}] set radius2Px [expr {$PXperWCx * $r2}] ############################################################# ## THE LOOP: ## In a loop, start animating the motion of the two masses ## by using the 'create oval' and 'delete' commands on the ## Tk canvas. ## ## Stop when the center of one of the masses goes off the ## left or right of the image area. ## Or stop if the user clicks the 'Stop' radiobutton. ## ## As the animation proceeds store values in ## t(i), x1(i), x2(i). These can be used in the ## 'show_list' proc. ################################################################### ## SOME DETAILS OF ANIMATING THE MOVEMENT OF THE 2 MASSES, ## before and after the impact: ## ## We start advancing circles representing mass1 and mass2 ## by using the 'before' velocities V1b and V2b which are ## considered to be in distance-units per time-unit. ## ## The time-step h is considered to be in the same time-units ## as the velocity --- seconds, hours, whatever. ## ## The motion of mass1 is given by h*V1b and ## the motion of mass2 is given by h*V2b ## for each time step. ## ## Either velocity can be negative. Positive velocity ## generates movement to the right, negative to the left. ## ## Mass1 is considered to start at position 0.0 and ## mass2 is considered to start at position +D. #################################################################### ## CALCULATING THE POSITIONS OF THE 2 MASSES: ## ## In a loop advancing time by h time-units per step: ## - the center of mass1 is advanced V1b * h distance units ## to give position x1 ## - the center of mass2 is advanced V2b * h distance units ## to give position x2. ## After impact: ## - the center of mass1 is advanced V1a * h distance units ## to give position x1 ## - the center of mass2 is advanced V2a * h distance units ## to give position x2. ## ## The impact is considered to occur when ## x1 + r1 exceeds x2 - r2 ## ## Since the time-step at impact will generally take x1 + r1 ## past x2 - r2, we can 'back off' the values of x1 and x2 --- ## according to V1b and V2b so that the two circles just touch ## at impact. Then V1a*h and V2a*h are used to advance x1 and x2 ## --- from those adjusted values of x1 and x2 --- in subsequent ## passes through the loop. ## ########################################### ## Handling 'OVERSHOOT' of the impact point: ## ## Since the time that the two circles just touch will generally ## not occur exactly at a time-step, 'h', increment, we will adjust ## (i.e. 'back off') the values of x1 and x2 so that the circles ## just touch --- and 'back off' the time to emulate an exact ## time of impact, 'tI'. ## ## If x1(tI) and x2(tI) represent the locations of the centers of ## the 2 circles at the time of impact, we continue the animation using ## the values of 'after velocities', V1a and V2a, to advance ## the values of x1 and x2 --- from x1(tI) and x2(tI), which differ ## by r1 + r2 --- for each time-step, 'h', starting from the ## impact time, 'tI'. ## ## We save the values of t(i), x1(t(i)), x2(t(i)) in 3 arrays ## to allow for showing a list of the values when a user ## clicks on a 'ShowList' button on the GUI. ################################################################ ## We initialize ## time = 0.0 ## x1 = 0.0 ## x2 = D ## idx = 0 ## Then in the loop, we calculate ## idx = idx + 1 ## time = time + h ## x1 = time*V1b ## x2 = D + time*V2b ## At each time step in the loop, ## we store the values in arrays ## aRtime(idx) = time ## aRx1(idx) = x1 ## aRx2(idx) = x2 ## Nstep = idx ############################################################### ## This while loop stops when VARanimate0or1 is set to 0 ## --- by the user clicking on the 'Stop' radiobutton. ## ## If the centers of both (or one) of the masses ## become greater than Xmax or less than Xmin, ## then we 'break' out of this animation loop. ############################################################### ###################################### ## Initialize the variables and arrays. ###################################### set idx 0 set time 0.0 set aRtime(0) $time set x1 0.0 set aRx1(0) $x1 set x2 $D set aRx2(0) $x2 ## We use V1 and V2 to hold the current velocity of the ## 2 masses. After impact, we reset V1,V2 to V1a,V2a. set V1 $V1b set V2 $V2b ################# ## Start the loop. ################# while {$VARanimate0or1 == 1} { incr idx ####################################################### ## Delete the circles of the 2 masses ## from a previous draw iteration. ####################################################### .fRcanvas.can delete -tags TAGcircle ###################################################################### ## Set the next values of time using h. ## Set the next values of x1, x2 using V1,V2. ###################################################################### set time [expr {$time + $h}] set x1 [expr {$x1 + ($V1*$h)}] set x2 [expr {$x2 + ($V2*$h)}] ###################################################################### ## Check if there is impact --- x1+r1 > x2-r2. ## If so, adjust the time variable proportional to the 'overreach'. ## --- and calculate the values of x1,x2 at exact impact. ## Also change V1,V2 to V1a,V2a. ###################################################################### set chk1 [expr {$x1 + $r1}] set chk2 [expr {$x2 - $r2}] if {$chk1 > $chk2} { set V1 $V1a set V2 $V2a ## To be accurate, we should put some code here to ## adjust time,x1,x2 to compensate for the 'overshoot' ## which is measured by (chk1 - ch2). } ###################################################################### ## Load the 3 arrays -- aRtime, aRx1, aRx2 --- and Nstep. ###################################################################### set aRtime($idx) $time set aRx1($idx) $x1 set aRx2($idx) $x2 set Nstep $idx ###################################################################### ## Set the x-coordinate of the location of the center of the ## mass1, in pixel-coords. ###################################################################### set xPx [Xwc2px $x1] ########################################################################## ## Draw the mass1 --- with 'create oval'. ## Set the upper-left and lower-right corners of the oval (a circle ## in this application). ########################################################################## set massX1Px [expr {$xPx - $radius1Px}] set massY1Px [expr {$yPx - $radius1Px}] set massX2Px [expr {$xPx + $radius1Px}] set massY2Px [expr {$yPx + $radius1Px}] .fRcanvas.can create oval $massX1Px $massY1Px $massX2Px $massY2Px \ -tags TAGcircle -fill "$COLOR1hex" ###################################################################### ## Set the x-coordinate of the location of the center of the ## mass2, in pixel-coords. ###################################################################### set xPx [Xwc2px $x2] ########################################################################## ## Draw the mass2 --- with 'create oval'. ## Set the upper-left and lower-right corners of the oval (a circle ## in this application). ########################################################################## set massX1Px [expr {$xPx - $radius2Px}] set massY1Px [expr {$yPx - $radius2Px}] set massX2Px [expr {$xPx + $radius2Px}] set massY2Px [expr {$yPx + $radius2Px}] .fRcanvas.can create oval $massX1Px $massY1Px $massX2Px $massY2Px \ -tags TAGcircle -fill "$COLOR1hex" ########################################################## ## Make sure the image is shown before going on to build ## the next image (the next positions of the 2 masses). ########################################################## update ########################################################### ## We check if one of the masses is exiting the image area, ## and 'break' out of this loop in that case. ## ## If x1 becomes less than Xmin OR greater than Xmax ## AND ## if x2 becomes less than Xmin OR greater than Xmax, ## ## then we 'break' out of this animation loop. ## ## If V1a or V1b is zero, this does not work. ## One mass never goes off the image area. ## So if V1a=0, then check if x2 goes off image. ## And if V2a=0, then check if x1 goes off image. ########################################################### if {( $x1 < $Xmin || $x1 > $Xmax) && ($x2 < $Xmin || $x2 > $Xmax)} {break} if {$V1a == 0.0 && ($x2 < $Xmin || $x2 > $Xmax)} {break} if {$V2a == 0.0 && ($x1 < $Xmin || $x1 > $Xmax)} {break} ########################################################## ## Wait a few millisecs before proceeding to erase the ## current 2 mass circles and before drawing ## the next 2 mass circles in position. ## (To get a better wait-time to make the oscillation ## times more accurate, we could get the draw time using ## a couple of calls to 'clock milliseconds' before and ## after each draw and subtract the indicated draw-time ## from WAITmillsecs.) ########################################################## after $WAITmillisecs } ## END OF the animation loop (over the index of array aRu1) ############################################################ ## Clear the message area --- replace with an animation msg. ############################################################ advise_user "\ ** ANIMATION ENDED. NumberOfTimeStepsAvailable: $Nstep **" set VARanimate0or1 0 ################################################################## ## Activate the disabled 'ShowList' button, since the arrays ## aRtime,aRx1,aRx2 are available now for the 'show_list' proc. ################################################################## .fRbuttons.buttSHOW configure -state normal } ## END OF PROC 'animate' ##+##################################################################### ## PROC: show_list ## ## PURPOSE: ## Creates a columnar list of results of the form: ## t x1 x2 ## where ## t is the time in secs, from 0.0 to $aRtime($Nstep) ## x1 the location of the center of mass1 ## x2 the location of the center of mass2 ## ## Puts the list in a text variable, VARlist, and ## shows the list in a popup window by using the ## 'popup_msgVarWithScroll' proc. ## ## METHOD: Uses the 3 arrays created by the 'animate' proc. ## ## CALLED BY: a click on the 'ShowList' button. ##+#################################################################### proc show_list {} { ## FOR TESTING: (dummy out this proc) # return ########################################################### ## Use the global variables --- m1,m2,V1b,V2b,D --- ## that were created in the 'animate' proc --- rather than ## using the ENTRY variables which might have been ## changed by the user before they click on 'ShowList'. ########################################################## global m1 m2 V1b V2b D h aRtime aRx1 aRx2 Nstep ################################## ## Create the heading for the list. ################################## set VARlist \ "Simulation of Two Colliding Rigid Masses Mass1 = $m1 Mass2 = $m2 'before-impact' Velocity of Mass1: $V1b (distance-units/time-units) 'before-impact' Velocity of Mass2: $V2b (distance-units/time-units) Initial distance between the 2 masses: $D (distance-units) Animation time step, h (time-units): $h Number of time steps: $Nstep Start time (secs): 0.0 End time (secs): [format "%-10.4f" $aRtime($Nstep)] Time in Position of Mass1 Position of Mass2 Step time-units (distance-units) (distance-units) Number ---------- -------------------- -------------------- --------- " ######################################################## ## In a loop over the integer-index of the 3 'aR' arrays, ## put the 3 numbers in each row of the list. ######################################################## set idx 0 while {$idx <= $Nstep} { set timeFMTED [format "%10.4f" $aRtime($idx)] set x1FMTED [format "%20.4f" $aRx1($idx)] set x2FMTED [format "%20.4f" $aRx2($idx)] set nFMTED [format "%9.1d" $idx] append VARlist "$timeFMTED $x1FMTED $x2FMTED $nFMTED \n" incr idx } ## END OF while loop ####################################### ## Add usage info to bottom of the list. ####################################### append VARlist " --- With a mouse, you can highlight the lines above and paste them into a text editor window and save that text into a file. Then you can use that text file in a plot utility, such as 'gnuplot', to plot the locations against time. " ############################## ## Show the list. ############################## popup_msgVarWithScroll .topList "$VARlist" +20+30 } ## END OF PROC 'show_list' ##+######################################################################## ## PROC: 'setMappingVars_for_px2wc' ##+######################################################################## ## PURPOSE: Sets up 'constants' to be used in converting between x,y ## 'world coordinates' and 'pixel coordinates' on a Tk canvas. ## ## Puts the constants in global variables: ## PXperWCx PXperWCy BASEwcX BASEwcY BASEpxX BASEpxY ## ## These variables are for use by 'Xwc2px' and 'Ywc2px' procs ## and 'Xpx2wc' and 'Ypx2wc' procs. ## ## The 'BASE' variables are coordinates of the upper-left point ## of the 'plotting rectangle' --- in world coordinates and ## in pixel coordinates. ## ## METHOD: This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to be used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit, ## in global variables 'PXperWCx' and 'PXperWCy' ## ## (These will generally be a fractional amount, ## i.e. it is not necessarily an integer.) ## ## INPUTS: ## ## At least eight numbers are input to this proc, as indicated by: ## ## ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: (for a plot area with x between -1.2 and +1.2 ## and with y between -0.2 and +1.2) ## setMappingVars_for_px2wc xy -1.2 1.2 0 0 1.2 -0.2 $ImgWidthPx $ImgHeightPx ## ## The first argument can be either 'x' or 'y' or 'xy'. This determines whether ## global variables 'PXperWCx' and 'PXperWCx' are determined by just the ## X-numbers, just the Y-numbers, or both. In most scripts, we may use 'xy' (both). ## ## An 'adjustYpx' global variable can be used to adjust if the pixels ## on a user's monitor are not square. ## ## OUTPUTS: global variables PXperWCx PXperWCy BASEwcX BASEwcY BASEpxX BASEpxY ## ## CALLED BY: by the animate' proc. ##+######################################################################## proc setMappingVars_for_px2wc {xORy ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY} { global PXperWCx PXperWCy BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Calculate PXperWCx and PXperWCy ## (pixels-per-world-coordinate-unit) --- the ratio ## of pixels-per-world-coordinate in the x and y directions, ## for the given UL and LR values. ############################################################ set PXperWCx [expr {abs(($LRpxX - $ULpxX) / ($LRwcX - $ULwcX))}] set PXperWCy [expr {abs(($LRpxY - $ULpxY) / ($LRwcY - $ULwcY))}] ## FOR TESTING: if {0} { puts "proc 'animate':" puts "LRwcY: $LRwcY ULwcY: $ULwcY LRwcX: $LRwcX ULwcX: $ULwcX" puts "PXperWCx: $PXperWCx" puts "LRpxY: $LRpxY ULpxY: $ULpxY LRpxX: $LRpxX ULpxX: $ULpxX" puts "PXperWCy: $PXperWCy" } ############################################################# ## Reset PXperWCx and PXperWCy according to whether input ## variable 'xORy' is 'x' or 'y' or 'min' or 'xy'. ## ## For 'x', we set PXperWCy equal to PCperWCx. ## ## For 'y', we set PXperWCx equal to PCperWCy. ## ## For 'min', we set PXperWCx and PXperWCy to the smaller ## of PXperWCx and PXperWCy. ## ## For 'xy', we will leave PXperWCx and PXperWCy unchanged. ############################################################# if {$xORy == "x"} { set PXperWCy $PXperWCx } elseif {$xORy == "y"} { set PXperWCx $PXperWCy } elseif {$xORy == "min"} { if {$PXperWCx > $PXperWCy} { set PXperWCx $PXperWCy } else { set PXperWCy $PXperWCx } } ## END OF if {$xORy == "x"} ############################################################ ## In case the pixels are not square, provide a factor ## that can be used to adjust in the Y direction. ############################################################ set adjustYpx 1.0 ############################################################ ## Set BASEwcX, BASEwcY, BASEpxX and BASEpxY. ############################################################ set BASEwcX $ULwcX set BASEwcY $ULwcY set BASEpxX $ULpxX set BASEpxY $ULpxY ## FOR TESTING: if {0} { puts "proc 'setMappingVars_for_px2wc':" puts "PXperWCx: $PXperWCx PXperWCy: $PXperWCy" puts "BASEwcX: $BASEwcX BASEwcY: $BASEwcY" puts "BASEpxX: $BASEpxX BASEpxY: $BASEpxY" } } ## END OF PROC 'setMappingVars_for_px2wc' ##+######################################################################## ## PROC: 'Xwc2px' ##+######################################################################## ## PURPOSE: Converts an x world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Xwc2px {x} { global PXperWCx BASEwcX BASEpxX set px [expr {($x - $BASEwcX) * $PXperWCx + $BASEpxX}] return $px } ## END OF PROC 'Xwc2px' ##+######################################################################## ## PROC: 'Xpx2wc' ##+######################################################################## ## PURPOSE: Converts an x-pixel unit to an x-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Xpx2wc {px} { global PXperWCx BASEwcX BASEpxX set x [expr {( ($px - $BASEpxX) / $PXperWCx ) + $BASEwcX }] return $x } ## END OF PROC 'Xpx2wc' ##+######################################################################## ## PROC: 'Ywc2px' ##+######################################################################## ## PURPOSE: Converts an y world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Ywc2px {y} { global PXperWCy BASEwcY BASEpxY adjustYpx set px [expr {($BASEwcY - $y) * $PXperWCy * $adjustYpx + $BASEpxY}] return $px } ## END OF PROC 'Ywc2px' ##+######################################################################## ## PROC: 'Ypx2wc' ##+######################################################################## ## PURPOSE: Converts a y-pixel unit to a y-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Ypx2wc {px} { global PXperWCy BASEwcY BASEpxY adjustYpx set y [expr { $BASEwcY - ( ($px - $BASEpxY) / ( $PXperWCy * $adjustYpx ) ) }] return $y } ## END OF PROC 'Ypx2wc' ##+##################################################################### ## PROC: 'set_mass_color1' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a 'mass-circles' color. ## ## Arguments: global variables ## ## CALLED BY: .fRbuttons.buttCOLOR1 button ##+##################################################################### proc set_mass_color1 {} { global COLOR1r COLOR1g COLOR1b COLOR1hex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLOR1r: $COLOR1r" # puts "COLOR1g: $COLOR1g" # puts "COLOR1b: $COLOR1b" set TEMPrgb [ exec $ColorSelectorScript $COLOR1r $COLOR1g $COLOR1b] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR1hex "#$hexRGB" set COLOR1r $r255 set COLOR1g $g255 set COLOR1b $b255 ## Set background-and-foreground colors of the indicated color button. update_color_button color1 advise_user "$aRtext(STARTmsg) to use a new color" } ## END OF proc 'set_mass_color1' ##+##################################################################### ## PROC: 'set_background_color2' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a 'background' color. ## ## Arguments: global variables ## ## CALLED BY: .fRbuttons.buttCOLOR2 button ##+##################################################################### proc set_background_color2 {} { global COLOR2r COLOR2g COLOR2b COLOR2hex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLOR2r: $COLOR2r" # puts "COLOR2g: $COLOR2g" # puts "COLOR2b: $COLOR2b" set TEMPrgb [ exec $ColorSelectorScript $COLOR2r $COLOR2g $COLOR2b] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR2hex "#$hexRGB" set COLOR2r $r255 set COLOR2g $g255 set COLOR2b $b255 ## Set background-and-foreground colors of the indicated color button. update_color_button color2 advise_user "$aRtext(STARTmsg) to use new color." } ## END OF proc 'set_background_color2' ##+##################################################################### ## PROC: 'update_color_button' ##+##################################################################### ## PURPOSE: ## This procedure is invoked to set the background color of the ## color button, indicated by the 'colorID' string, ## to its currently set 'colorID' color --- and sets ## foreground color, for text on the button, to a suitable black or ## white color, so that the label text is readable. ## ## Arguments: global color vars ## ## CALLED BY: in two 'set_*_color' procs ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc update_color_button {colorID} { global COLOR1r COLOR1g COLOR1b COLOR1hex global COLOR2r COLOR2g COLOR2b COLOR2hex # set colorBREAK 300 set colorBREAK 350 if {"$colorID" == "color1"} { .fRbuttons.buttCOLOR1 configure -bg $COLOR1hex set sumCOLOR1 [expr {$COLOR1r + $COLOR1g + $COLOR1b}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLOR1 configure -fg "#000000" } else { .fRbuttons.buttCOLOR1 configure -fg "#ffffff" } } elseif {"$colorID" == "color2"} { .fRbuttons.buttCOLOR2 configure -bg $COLOR2hex set sumCOLOR1 [expr {$COLOR2r + $COLOR2g + $COLOR2b}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLOR2 configure -fg "#000000" } else { .fRbuttons.buttCOLOR2 configure -fg "#ffffff" } } else { ## Seems to be an invalid colorID. return } } ## END OF PROC 'update_color_button' ##+##################################################################### ## PROC: 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in three 'set_*_color*' procs, ## in some 'bind' statements in the BIND section above, ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc advise_user {text} { .fRmsg.labelMSG configure -text "$text" ## Make sure the text is displayed on the GUI. update ## Alternatively, we could put the message in the title-bar ## of the GUI window. (But it is easy for the user to ## fail to see the message there. Besides, we have more ## options in displaying the message by putting it on a ## Tk widget in the GUI.) ## # wm title . "$text" } ## END OF PROC 'advise_user' ##+######################################################################## ## PROC: 'reset_parms' ##+######################################################################## ## PURPOSE: To reset the data 'entry' widgets on the GUI. ## ## CALLED BY: 'ResetParms' button and in the 'Additional GUI Initialization' ## section at the bottom of this script. ##+######################################################################## proc reset_parms {} { global ENTRYmass1 ENTRYmass2 ENTRYvel1 ENTRYvel2 ENTRYdist ####################################### ## Set parameters mass1 and mass2. ####################################### set ENTRYmass1 10.0 # set ENTRYmass1 15.0 set ENTRYmass2 10.0 # set ENTRYmass2 5.0 ######################################## ## Set initial-velocities of the masses. ## (for example, in meters/sec) ######################################## # set ENTRYvel1 10.0 set ENTRYvel1 20.0 # set ENTRYvel2 0.0 set ENTRYvel2 -20.0 #################################################### ## Set initial distance between the masses. ## (This should probably be set 'realistically' ## relative to the initial-velocity values, above.) #################################################### # set ENTRYdist 100.0 set ENTRYdist 30.0 } ## END OF PROC 'reset_parms' ##+#################################################################### ## PROC: 'edit_inputs' ##+##################################################################### ## PURPOSE: Checks entry widgets entries and pops up an error message ## if the data is invalid. ## ## CALLED BY: the 'solve' and 'animate' procs ##+##################################################################### proc edit_inputs {} { ## Decimal/floating-point variables. global ENTRYmass1 ENTRYmass2 ENTRYvel1 ENTRYvel2 ENTRYdist ## Integer variables. global ENTRYimgsize ## Error indicator. global EDITcode ## We could do without the following EDITcode variable, by using ## a code with the 'return' statement herein. But using this ## code variable is a little more self-documenting. global EDITcode set EDITcode 0 ####################################################### ## Remove trailing and leading blanks (if any) from the ## user entries in the 'entry' widgets. ####################################################### set ENTRYmass1 [string trim $ENTRYmass1] set ENTRYmass2 [string trim $ENTRYmass2] set ENTRYvel1 [string trim $ENTRYvel1] set ENTRYvel2 [string trim $ENTRYvel2] set ENTRYdist [string trim $ENTRYdist] set ENTRYimgsize [string trim $ENTRYimgsize] ######################################################################### ## Check that these entry fields are NOT blank. ######################################################################### set MSGblank "is blank. Must NOT be blank." if {"$ENTRYmass1" == ""} { popup_msgVarWithScroll .topErr "The mass1 $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYmass2" == ""} { popup_msgVarWithScroll .topErr "The mass2 $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYvel1" == ""} { popup_msgVarWithScroll .topErr "The 'before' velocity of mass1 $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYvel2" == ""} { popup_msgVarWithScroll .topErr "The 'before' velocity of mass2 $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYdist" == ""} { popup_msgVarWithScroll .topErr "The distance between masses $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYimgsize" == ""} { popup_msgVarWithScroll .topErr "The image width (pixels) $MSGblank" +10+10 set EDITcode 1 return } ########################################################## ## Check that ENTRYimgsize is an integer. ########################################################## set MSGnotInteger " is NOT INTEGER." if {![string is integer -strict "$ENTRYimgsize"]} { popup_msgVarWithScroll .topErr "The image width (pixels) $MSGnotInteger" +10+10 set EDITcode 1 return } ######################################################################### ## Check that ENTRYmass1 ENTRYmass2 ENTRYvel1 ENTRYvel2 ENTRYdist ## are decimal numbers (positive or negative) ## --- such as 1.234 or -3 or -3.0 or -.4 or .5 or 7 ######################################################################### ## Implemented using the 'decimal_check' proc below. ######################################################################### set NUMERICmsg "should be a decimal number. Examples: 1.234 or 0.56 or -.789" if {![decimal_check "$ENTRYmass1"]} { popup_msgVarWithScroll .topErr "The mass1 $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYmass2"]} { popup_msgVarWithScroll .topErr "The mass2 $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYvel1"]} { popup_msgVarWithScroll .topErr "The 'before' velocity of mass1 $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYvel2"]} { popup_msgVarWithScroll .topErr "The 'before' velocity of mass2 $NUMERICmsg" +10+10 set EDITcode 1 return } ####################################################################### ## Check that ENTRYmass1 ENTRYmass2 ENTRYdist ENTRYimgsize ## are not negative. ####################################################################### set POSITIVEmsg "should be a POSITIVE number. Examples: 1.234 or 0.56" if {$ENTRYmass1 <= 0.0} { popup_msgVarWithScroll .topErr "The mass1 $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYmass2 <= 0.0} { popup_msgVarWithScroll .topErr "The mass2 $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYdist <= 0.0} { popup_msgVarWithScroll .topErr "The distance between masses $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYimgsize <= 0} { popup_msgVarWithScroll .topErr "The image width (pixels) $POSITIVEmsg" +10+10 set EDITcode 1 return } ########################################################################## ## Check that ENTRY??? is less than or equal to 1.0 (not greater than 1.0). ########################################################################## # if {$ENTRY??? > 1.0} { # popup_msgVarWithScroll .topErr "The ???? should not be greater than 1.0" +10+10 # set EDITcode 1 # return # } } ## END of proc 'edit_inputs' ##+######################################################################## ## PROC: 'decimal_check' ##+######################################################################## ## PURPOSE: Returns 1 or 0 if the input string looks like a decimal number ## --- positive or negative. Example numbers that are OK: ## 1.234 12.34 0.234 .234 6 ## -1.234 -12.34 -0.234 -.234 -6 ##+######################################################################## ## References (lots of one-liners): ## http://stackoverflow.com/questions/2072222/regular-expression-for-positive-and-a-negative-decimal-value-in-java ## http://stackoverflow.com/questions/308122/simple-regular-expression-for-a-decimal-with-a-precision-of-2 ## http://stackoverflow.com/questions/4246077/matching-numbers-with-regular-expressions-only-digits-and-commas/4247184#4247184 ## ## More specific to Tcl-Tk (including multi-liners): ## http://wiki.tcl.tk/989 'Regular Expression Examples' ## http://wiki.tcl.tk/768 'Entry Validation' - See "Integer forbidding leading zero:" ## http://wiki.tcl.tk/10166 'string is' ## http://wiki.tcl.tk/40710 'significant digits rounding' - uses regexp to split a number - ## Splits using: if {[regexp {^([+,-]?)([0-9]+)(\.?[0-9]*)?([eE][+-]?[0-9]+)?$} $num -> s i d e]} ## Removes leading zero with: regexp {^(0*)([1-9][0-9]*)$} $i -> NULL DIG ## http://wiki.tcl.tk/530 'Unit converter' has a regexp to parse numbers: ## set RE {(?ix) # Ignore case, extended syntax ## ([-+]?) # Optional leading sign ## ([0-9]*) # Integer part ## \.? # Optional decimal point ## ([0-9]*) # Fractional part ## (e?[0-9]*) # Optional exponent ## } ## ##+######################################################################## ## I do not mind incurring a little (minute amount of) processing ## with a multiple-line implementation. Probably easier to fix if ## a string gets through --- such as ".0.3" (two decimal points). ## ## CALLED BY: proc 'edit_inputs' ##+######################################################################## proc decimal_check {string} { set PosDecimalOK [regexp {^([0-9]*)\.?([0-9]*)$} "$string"] set NegDecimalOK [regexp {^\-([0-9]*)\.?([0-9]*)$} "$string"] set PosNakedDecimalOK [regexp {^\.?([0-9]*)$} "$string"] set NegNakedDecimalOK [regexp {^\-\.?([0-9]*)$} "$string"] set IntegerOK [string is integer $string] set retCODE [expr { \ $PosDecimalOK || $NegDecimalOK || \ $PosNakedDecimalOK || $NegNakedDecimalOK || \ $IntegerOK }] ## FOR TESTING: if {0} { puts "" puts "decimal_check:" puts "string: $string" puts "PosDecimalOK: $PosDecimalOK" puts "NegDecimalOK: $NegDecimalOK" puts "PosNakedDecimalOK: $PosNakedDecimalOK" puts "NegNakedDecimalOK: $NegNakedDecimalOK" puts "IntegerOK: $IntegerOK" puts "retCODE: $retCODE" } return $retCODE } ## END of proc 'decimal_check' ##+######################################################################## ## PROC: 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext ULloc} { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 # wm geometry $toplevName +100+50 wm geometry $toplevName $ULloc wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" ## -font fontTEMP_varwidth scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 ## -font fontTEMP_varwidth } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ################################################ ## Set some 'event' bindings to allow for ## easy scrolling through huge listings. ## is a press of the Page-Down key. ## is a press of the Page-Up key. ## is a press of the Home key ## to go to the top of the listing. ## is a press of the End key ## to go to the bottom of the listing. ## is a press of the Up-arrow key. ## is a press of the Down-arrow key. ################################################ bind $toplevName "$toplevName.text yview scroll +1 page" bind $toplevName "$toplevName.text yview scroll -1 page" bind $toplevName "$toplevName.text see 1.0" bind $toplevName "$toplevName.text see end" bind $toplevName "$toplevName.text yview scroll -1 unit" bind $toplevName "$toplevName.text yview scroll +1 unit" ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################################################## ## Set the 'HELPtext' var. ##+######################################################## set HELPtext \ " ** HELP for this 'Animate 2 Colliding Masses' ** * software application * This Tk GUI script simulates the 'direct' collision of 2 masses that are moving at constant velocities and that impact each other and 'rebound' along a straight line. In other words, this script simulates the approach, impact, and continuation of movement of two masses where the centers of the 2 masses move along a single 1-dimensional straight-line path. Furthermore, the 2 masses travel at constant velocities before and after the impact. By 'simulate', we mean that for user-specified values of the 2 masses and for user-specified values of their 2 'before-impact' velocities, the 2 'after-impact' velocities are computed. Then the 4 'before' and 'after' velocities can be used to generate an animation of the movement of the 2 masses. We allow the 2 'before-impact' velocities to be postive or negative, so the masses can be initially travelling in the same direction (both velocities positive or both negative) or toward each other (one positive velocity and one negative velocity). Furthermore, the GUI provides a Tk 'canvas' widget on which the approach and 'rebound' of the 2 masses is animated. In the animation, the 2 masses are represented by 2 circles. The centers of the circles are moved horizontally along a straight line. The animation is performed by using 'create oval' and 'delete' commands on the Tk 'canvas' widget. ********************** NOTATION AND EQUATIONS: ********************** The following notation is typically used in many physics documents: m1 denotes mass 1 m2 denotes mass 2 u1 denotes velocity of mass1 before impact u2 denotes velocity of mass2 before impact v1 denotes velocity of mass1 after impact v2 denotes velocity of mass2 after impact The GUI allows the user to specify m1, m2, u1, u2. This script computes v1 and v2 from 2 algebraic expressions that are presented below. The velocities u1, u2, v1, v2 are used to animate the motions of the two masses, before and after the collision. We assume a 'conservative system'. By that we mean that the motion and collision of the 2 masses are modeled under the following assumptions. - There is no energy loss during motion, such as frictional losses due to contact with a surface or losses due to air/fluid resistance. - There are no energy losses at impact, due to energy transfer into the masses, such as deformation and heat generation. (In traditional physics and engineering terminology, the collision is said to be 'elastic'.) - There is no energy loss due to sound generation (energy transfer to vibrating air molecules) due to the impact. - Although any two masses experience a gravitational attraction to each other, we assume that the velocity increments that would arise from such gravitational attraction are extremely small increments compared to the user-specified velocities. The following 2 equations give the values of v1 and v2. v1 = ((m1 - m2)*u1 + (2*m2*u2)) / (m1 + m2) v2 = ((m2 - m1)*u2 + (2*m1*u1)) / (m1 + m2) These 2 equations can be derived from the 2 equations for conservation of momentum and conservation of energy: m1*u1 + m2*u2 = m1*v1 + m2*v2 m1*u1^2 + m2*u2^2 = m1*v1^2 + m2*v2^2 Some References: https://en.wikipedia.org/wiki/Linear_momenta (presents equations without derivation) http://www.themcclungs.net/physics/download/H/Momentum/ElasticCollisions.pdf (presents a nice, compact derivation -- nice notation) http://www.real-world-physics-problems.com/elastic-collision.html (presents equations without derivation - nice notation) http://hyperphysics.phy-astr.gsu.edu/hbase/elacol2.html (presents equations for case of 'target' at rest) https://en.wikipedia.org/wiki/Restitution_coefficient (presents equations for case of 'inelastic', non-conservative collision) http://physics.stackexchange.com/questions/81959/perfect-elastic-collision-and-velocity-transfer (presents the equations with some discussion of a typical question from 'newbies') http://www.euclideanspace.com/physics/dynamics/collision/twod/index.htm (presents the equations for 'off-center' collsions as well as 'in-line' collisions) http://www.tutelman.com/golf/swing/golfSwingPhysics1.php (shows animations of balls with 'target' at rest) *********************** SOME DIFFERENT NOTATION: *********************** The above symbolism for the velocities can be confusing. It helps to remember that the subscripts 1 and 2 are indicative of masses 1 and 2. So the u1 and v1 velocities are for mass1 and the u2 and v2 velocities are for mass2. To make this a little clearer, we could use V1 to denote velocities of mass1 and V2 to denote velocities of mass2. Then we could use u1 = V1(tb) = V1b, a constant u2 = V2(tb) = V2b, a constant v1 = V1(ta) = V1a, a constant v2 = V2(ta) = V1a, a constant where tb denotes times before the impact and ta denotes times after the impact. In other words, u is an abbreviation for V_b v is an abbreviation for V_a where the underscore represents 1 or 2. The equations above for the 'after-velocities' become V1a = ((m1 - m2)*V1b + (2*m2*V2b)) / (m1 + m2) V2a = ((m2 - m1)*V2b + (2*m1*V1b)) / (m1 + m2) Some derivations use this type of notation but use 'i' in place of 'b' and 'f' in place of 'a' --- where 'i' represents 'initial' and 'f' represents 'final'. *************************** FEATURES AND USE OF THE GUI: *************************** The GUI allows the user to enter various values for - m1 - m2 - V1b - V2b When the mass and velocity parameters are ready, the GUI provides a 'Solve' button by which the user can trigger the calculation of the 'after-impact' velocities, V1a and V2a. --- The other parameters on the GUI are for generating an animation on the Tk 'canvas' widget. The GUI allows the user to specify the width of the image area in pixels, say ImgWidthPx. The GUI provides 2 buttons by which the user can specify the 2 colors for: 1) the canvas background 2) the 2 masses (circles) and the line along which they travel. The GUI allows the user to specify D, an initial distance between m1 and m2. The simulation starts by assuming mass1 is located at x-coordinate 0.0 and mass2 is located on the right at x-coordinate +D. When the user determines that all parameters are ready, the user can click on the 'Start' radiobutton to start the animation. The user can click on the 'Stop' radiobutton to stop the animation, if it is still going. Otherwise, the animation will stop when the center of one (or both) of the two masses exceeds the left or right limits of the image area. The GUI includes a 'ResetParms' button --- by which the user can reset the values of m1, m2, V1b, V2b, and D to their initially-displayed values. **************************************************** EXPERIMENTING WITH THE MASSES AND INITIAL VELOCITIES: **************************************************** The INITIAL VELOCITIES are initially set (positive and negative) so that the left mass (mass1) is moving to the right and the right mass (mass2) is moving to the left. However, the velocities can be set so that they are both positive or both negative. The user will be warned if these velocities will not result in a collision. Example: If both velocities are positive and the mass1 velocity is less than the mass2 velocity, there will be no collision. --- In addition to changing the velocities, the user can change the MASSES so that one mass is much larger than the other. For example, change mass1 from 10.0 to 30.0 --- then 50.0. For the animation, the radii of the circles representing the 2 masses will be adjusted so that the area of the 2 circles are proportional to their masses. --- The UNITS-OF-MEASURE of the initial velocities can be whatever you like --- meters/sec, kilometers/hour, astonomical-units/year, miles/hour, inches/sec, centimeters/sec. Then think of specifying the initial distance, D, between the 2 masses in the same distance units. If the width of your computer monitor is about 20 centimeters, then you may find it helpful to think of setting the initial velocities in terms of centimeters/sec (for example, V1b = 10 cm/sec and V2b = -10 cm/sec) and set the initial distance D in corresponding distance-units and at a meaningful magnitude, say 10 cm, where 10 cm represents about half the distance across the computer's screen. --- You can keep clicking the 'Start' radiobutton to repeat the same animation over and over. ********************************* OVERVIEW OF THE ANIMATION PROCESS: ********************************* 0) - Any previously drawn masses (circles) are deleted from the canvas. - The size of the image area (the dimensions of the canvas) are set according to the user-specified image-width (pixels). - The canvas background color is (re)set. 1) A check is done to make sure the 2 velocities V1b,V2b will result in an impact point. If no impact, a message is posted to the user and the animation is abandoned. 2) The time (tI) and mass locations (x1I,x2I) at the impact are computed on the basis of V1b,V2b and D --- assuming mass1 is located at x=0.0 and mass2 located at x=D. 3) Based on x1I,x2I and D, Xmin and Xmax are calculated --- for appropriate limits within which the animation can be done, allowing for some before-impact and after-impact motion. 4) The world-coordinates Xmin,Xmax are mapped to the image area horizontal left and right limits, 0 to ImgWidthPx , in pixels. 5) An appropriate time-step, h, is calculated to allow for smooth motion of the masses, based on the max velocity among the before and after velocities: V1b,V2b,V1a,V2a. 6) Start animating the motion of the two masses. A loop is used in which the time is advanced by 'h' units at each step. For each step, move mass1 by V1b*h and move mass2 by V2b*h --- until impact. After impact, move mass1 by V1a*h and move mass2 by V2a*h. Stop when the center of both (or one) of the masses go off the left or right of the image area. Or stop if the user clicks the 'Stop' radiobutton. As the animation proceeds store values in arrays t(i), x1(i), x2(i). These can be used by the 'ShowList' option. " ##+##################################################### ##+##################################################### ## ADDITIONAL GUI INITIALIZATION, if needed (or wanted). ##+##################################################### ##+##################################################### ##+##################################################### ## Set the full-name of the RGB color-selector Tk script ## that is used in several procs above. ##+##################################################### ## FOR TESTING: # puts "argv0: $argv0" set DIRthisScript "[file dirname $argv0]" ## For ease of testing in a Linux/Unix terminal and located at the ## directory containing this Tk script. Set the full directory name. if {"$DIRthisScript" == "."} { set DIRthisScript "[pwd]" } set DIRupOne "[file dirname "$DIRthisScript"]" set DIRupTwo "[file dirname "$DIRupOne"]" set ColorSelectorScript "$DIRupTwo/SELECTORtools/tkRGBselector/sho_colorvals_via_sliders3rgb.tk" ## Alternatively: Put the RGB color-selector Tk script in the ## same directory as this Tk script and uncomment the following. # set ColorSelectorScript "$DIRthisScript/sho_colorvals_via_sliders3rgb.tk" ##+############################################# ## Initialize the color for the masses --- and ## their line of travel. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative color.) ##+############################################# if {1} { ## Yellow: set COLOR1r 255 set COLOR1g 255 set COLOR1b 0 } else { ## White: set COLOR1r 255 set COLOR1g 255 set COLOR1b 255 } set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b] update_color_button "color1" ##+############################################# ## Initialize the background-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative min-color.) ##+############################################# if {1} { ## Dark Blue: set COLOR2r 0 set COLOR2g 0 set COLOR2b 170 } else { ## Red: set COLOR2r 255 set COLOR2g 0 set COLOR2b 0 } set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b] update_color_button "color2" #+####################################################### ## Initialize the entry widgets on the GUI. ##+###################################################### reset_parms ##+############################################################### ## Set the wait-millisecs between each draw of the masses ## in the 'animate' proc. ## (To be used if we activate the entry widget for ## ENTRYmillisecs.) ##+############################################################### # set ENTRYmillisecs 100 ##+############################################################### ## Initialize the image-size entry field. ##+############################################################### # set ENTRYimgsize 640 set ENTRYimgsize 600 ##+##################################################### ## Advise the user how to start. ##+##################################################### advise_user "$aRtext(SOLVEmsg)"