#!/usr/bin/wish -f ## ## SCRIPT: tkAnimateCollisonOfTwoStickingMasses_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 then stick to each other ## and continue moving 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 stick together after impact and then ## travel at a constant velocities 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 single after-impact ## velocity is 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 of the 2 masses is animated, ## and, after impact, the 2 masses are represented as ## one mass traveling at the 'after' velocity. ## ## In the animation, the 2 masses are represented by ## 2 circles --- and the combined mass after impact is ## represented by a single, larger circle. ## ## The centers of these 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 similar to that used in many ## physics documents: ## m1 denotes mass 1 ## m2 denotes mass 2 ## V1b denotes velocity of mass1 before impact ## V2b denotes velocity of mass2 before impact ## Va denotes velocity of mass1+mass2 after impact ## ## The GUI allows the user to specify m1, m2, V1b, V2b. ## This script uses a single algebraic expressions to ## calculate Va. See below. ## ## The 3 velocities V1b, V2b, Va 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. ## ## - 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 equation gives the values of Va. ## ## Va = (m1*V1b + m2*V2b) / (m1 + m2) ## ## This equation can be derived from the single equation ## for conservation of momentum: ## ## m1*V1b + m2*V2b = (m1 + m2)*Va ## ## Some References: ## https://en.wikipedia.org/wiki/Inelastic_collision#Perfectly_inelastic_collision ## (presents the equation for final velocity along with its derivation) ## https://en.wikipedia.org/wiki/Collision#Perfectly_inelastic_collision ## (also presents the equation for final velocity along with its derivation) ## http://hyperphysics.phy-astr.gsu.edu/hbase/inecol.html ## (example in which one of the 2 masses is at rest before impact) ## https://en.wikipedia.org/wiki/Coefficient_of_restitution ## (presents a discussion of the case when the masses do not stick together ## but energy is dissipated in the collision) ## https://en.wikipedia.org/wiki/Impact_event ## and https://en.wikipedia.org/wiki/Giant_impact_theory ## (the Earth-Moon system may have been formed from an inelastic collision ## in which 2 huge masses did not stick together, but gravity held ## one resulting mass in orbit around the other) ## ##+########################### ## FEATURES AND USE OF THE GUI: ## ## The GUI allows the user to enter various values for ## - m1 ## - m2 ## - V1b ## - V2b ## from which the 'after' velocity, Va, is calculated. ## ## The GUI provides a 'Solve' button by which the user can ## trigger the calculation of the after-impact velocity, Va. ## ## --- ## ## Then, for the purpose of animation, the following ## additional parameters can be specified on the GUI. ## ## 1) D, the initial distance between m1 and m2, via ## an 'entry' widget on the GUI, ## ## 2) the width of the image area in pixels, say ImgWidthPx, ## via an 'entry' widget on the GUI, ## ## 3) 2 colors for: ## a) the canvas background, ## b) the 2 masses (circles) and the line along which ## they travel, ## via 2 button widgets on the GUI, that provide a ## separate color-selector GUI. ## ## After Va is calculated and the user has readied D and ## 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 D 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. ## ## --- ## ## 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 relative magnitude of the 2 masses. And we use the sum ## m1+m2 to determine a radius of a circle to represent the ## merged masses that result from the impact. ## ## The way that these 3 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 Va can be calculated. ## ## There is to be a 'Solve' button --- to calculate Va. ## ##------------------- ## ## For the animation, some other parameters are required --- ## D, ImgWidthPx, and 2 colors. ## ## 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,Va ---- 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 circles. This would be an alternative to ## using a wait-time value calculated from the 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 in arrays --- ## such as t(i), x1(i), x2(i), x12(i) ## where x1,x2 denote the location of the 2 masses during ## the animation, before the impact, ## and x12 denotes the location of the combined mass ## during the animation, after the impact. ## ## 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 Masses That Stick Together --- 'direct', 'in-line' impact ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} {Solve} {Reset Animate: O Start O Stop {Masses {Background {Show ## Parms} Color} Color} List} ## ## .fRguide [ ... A description of the equation(s) or solution technique goes here, in a label widget. ... ] ## ## .fRmasses Mass1: 10.0___ Mass2: 10.0___ D (distance between masses): 10.0__ Image width (pixels): 600__ ## ## .fRinit V1b: 20.0___ V2b: -20.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 ## - 10 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 in which to show the calculated after-velocity, Va ## ## - 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 calculate after-velocity Va ## ## - '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 2016jul19 ## Changed by: Blaise Montandon 2016jul20 Prepare script for first release ## at www.freedomenv.com. ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "Animate Collision of 2 Masses That Stick Together --- 'direct', 'in-line' impact" wm iconname . "StickyCollision" 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 7 buttons, 1 label, 2 radiobuttons ## (Exit,Help,Solve,Reset,Animate,Start,Stop,Color,Color,ShowList). ## 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(labelGUIDE) \ "Use the 'Solve' button to compute an after-impact velocity, Va, for the merged 2 masses. 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). ## 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.labelGUIDE \ -text "$aRtext(labelGUIDE)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bg "#66ff66" \ -bd $BDwidthPx_label pack .fRguide.labelGUIDE \ -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 "20.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 "20.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 "10.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, and one more LABEL widget. ## 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 "10.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 "-10.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 bouncing ball 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 Va using the equation ## ## Va = (((m1*V1b) + (m2*V2b)) / (m1 + m2) ## ## The output is saved in global variable Va. ## ## CALLED BY: a click on the 'Solve' button. ##+#################################################################### proc solve {} { ## FOR TESTING: (dummy out this proc) # return ################################################### ## The following m1 m2 V1b V2b Va variables will be ## used in the 'animate' and 'show_list' procs. ################################################### global ENTRYmass1 ENTRYmass2 ENTRYvel1 ENTRYvel2 \ m1 m2 V1b V2b Va 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 the solve process. ################################################ set t0 [clock milliseconds] ################################################ ## Compute Va. ################################################ 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 Va [expr { (($m1 * $V1b) + ($m2 * $V2b)) / $m1PLUSm2 }] ######################################################## ## Show the user the elapsed-time for this solution run. ## ## The solve proceeds so quickly that the elapsed time ## shown is typically only 0 millisecs. ######################################################## set solvetime [expr {[clock milliseconds] - $t0}] set VaFMTED [format "%6.2f" $Va] advise_user "\ ** SOLVE DONE: $solvetime millisecs elapsed ; COMPUTED: Va = $VaFMTED **" .fRbuttons.labelINFO configure -text \ "Currently Va = $VaFMTED" ################################################################## ## Activate the disabled 'Start' radiobutton, since the ## velocity Va is now available for the 'animate' proc. ## ## The 'ShowList' button will be reactivated at the bottom ## of the 'animate' proc. ################################################################## .fRbuttons.radbuttSTART configure -state normal } ## END OF PROC 'solve' ##+##################################################################### ## PROC: animate ## ## PURPOSE: ## Starts drawing and showing the sequence of positions of the ## 2 masses (as circles) BEFORE the impact. ## ## Then shows the position of the combined mass (as a new, ## larger circle) AFTER the impact. ## ## Uses 'create oval' commands on the Tk 'canvas' to draw ## the circles. ## ## Also use 'create line' to draw a horizontal line to represent ## the path that the centers of the masses (circles) 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 the location of mass centers (x1I,x2I) at 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 a pixels-to-world-coordinate-units ratio ## that can be used to convert world-coordinate values ## 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,Va. ## ## 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 ## (and the merged mass after impact) by using the ## 'create oval' and 'delete' commands on the Tk canvas. ## ## Stop when the center of the merged 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 arrays ## time(i), x1(i), x2(i), x12(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 Va 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 masses to pixel values. ## OUTPUTS: ## - Use the variables aRtime aRx1 aRx2 aRx12 to store ## values that may be used in the 'show_list' proc. ######################################################## global VARanimate0or1 m1 m2 V1b V2b Va \ ENTRYdist D ENTRYimgsize EDITcode aRtext \ PXperWCx PXperWCy aRtime aRx1 aRx2 aRx12 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 masses ## and the horizontal line on which the centers of the masses ## move --- which may be left over from a previous animation. ############################################################ .fRcanvas.can delete all ############################################################## ## Set the canvas dimensions and use them to make the image area ## that we are going to use. Use 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 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 } ################################################################ ## RADII OF THE 'before impact' 2 MASSES --- 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 locations (x1I,x2I) of impact --- ## the centers of the 2 masses at 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 and m, the bigger and smaller masses. set M $m1 set idxM 1 set m $m2 if {$m2 > $m1} { set M $m2 set idxM 2 set $m1 } ## Calculate R and r. set R [expr {0.1 * $D}] set r [expr {$R * sqrt($m/$M)}] ## Put the values of R and r into the proper r1,r2 variables. if {$idxM == 1} { set r1 $R set r2 $r } else { set r1 $r set r2 $R } ################################################################ ## RADIUS, R12, OF THE 'after impact' MERGED MASS (m1+m2 = M+m): ## ## Like above, let k be the common mass-density of the masses. ## ## Then M = k * (area of M's circle) = k * pi * R^2 ## And M12 = k * (area of M+m's circle) = k * pi * R12^2 ## ## So (R12/R)^2 = (M+m)/M. Thus R12/R = sqrt((M+m)/M). ## Thus R12 = R * sqrt((M+m)/M). ################################################################ set R12 [expr {$R * sqrt(($M + $m)/$M)}] ###################################################################### ## 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 are 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 MASS LOCATIONS 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 center-location and radius of mass1 (on the left) ## and ## x2 and r2 are the center-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 a ratio that can be used to convert ## world-coordinate values to pixel-coordinate values. ## That ratio is to be used later in 2 procs Xwc2px and Ywc2px. ## ## We now map world-coordinates Xmin,Xmax to the image area ## horizontal left and right limits, which are 0 and ImgWidthPx, ## in pixels. ## ## 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-are 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-coordinates ( 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,Va. ############################################################# ## 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,Va --- ## 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($Va))}] 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 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 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}] set radius12Px [expr {$PXperWCx * $R12}] ############################################################# ## In a loop, start animating the motion of the masses ## by using the 'create oval' and 'delete' commands on the ## Tk canvas. ## ## Stop when the center of one of the merged mass 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), x12(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+mass2 is advanced Va * h distance units ## to give position x12. ## ## The impact is considered to occur when ## x1 + r1 exceeds x2 - r2 ## ## If we want to handle the impact time and point exactly: ## ## 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 Va*h is used to advance the merged mass ## --- from a 'midpoint' of the 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 can 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 can continue the animation ## using the values of 'after velocity', Va, to advance the ## values of x12 --- from a 'midpoint' of adjusted 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(i), x2(i), x12(i) in 4 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 ## x12 = "N/A" ## idx = 0 ## NSTEP = 0 ## ## Then in the loop, BEFORE the impact, at each time step, ## we calculate ## idx = idx + 1 ## time = time + h ## x1 = x1 + h*V1b ## x2 = x2 + h*V2b ## x12 = "N/A" ## and we store these values in arrays ## aRtime(idx) = time ## aRx1(idx) = x1 ## aRx2(idx) = x2 ## aRx12(idx) = x12 ## NSTEP = idx ## ## We check if x1 + r1 >= x2 - r2 (impact occurs) and we set ## an indicator IMPACTcheck = "after". ## Also we set x12 = x1 + r1, the (approx) point of impact. ## ## In the loop, AFTER the impact, at each time step, ## we calculate ## idx = idx + 1 ## time = time + h ## x1 = "N/A" ## x2 = "N/A" ## x12 = x12 + h*Va ## and we store these values in arrays ## aRtime(idx) = time ## aRx1(idx) = x1 ## aRx2(idx) = x2 ## aRx12(idx) = x12 ## ## When the animation loop is stopped, ## NSTEP will contain the maximum index of the arrays. ############################################################### ## This while loop stops when VARanimate0or1 is set to 0 ## --- by the user clicking on the 'Stop' radiobutton. ## -OR- ## If x12 becomes greater than Xmax ## or ## if x12 becomes less than Xmin, ## then we 'break' out of this animation loop. ############################################################### ###################################### ## Initialize the variables and arrays. ###################################### set idx 0 set NSTEP 0 set time 0.0 set aRtime($idx) $time set x1 0.0 set aRx1($idx) $x1 set x2 $D set aRx2($idx) $x2 set x12 "N/A" set aRx12($idx) $x12 ## Set an indicator to be used to determine ## whether the impact has occurred yet. set IMPACTchk "before" ################# ## 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 V1b,V2b if before impact ## or set the next value of x12 using Va if after impact. ###################################################################### set time [expr {$time + $h}] set aRtime($idx) $time set NSTEP $idx if {"$IMPACTchk" == "before"} { set x1 [expr {$x1 + ($V1b*$h)}] set x2 [expr {$x2 + ($V2b*$h)}] set x12 "N/A" } else { set x1 "N/A" set x2 "N/A" set x12 [expr {$x12 + ($Va*$h)}] } set aRx1($idx) $x1 set aRx2($idx) $x2 set aRx12($idx) $x12 ###################################################################### ## Check if there is impact --- x1+r1 >= x2-r2. ## ## If so, ## we COULD adjust the time variable proportional to the 'overreach'. ## --- and adjust the values of centers x1,x2 to set the circles ## to 'touch exactly' at the computed/adjusted impact time, tI. ## Then the center of the new merged mass, m1+m2, could be set ## to x1+r1. ## ## However, for now, we allow a little overlap of the 2 circles ## and a little 'overshoot' of the 'time' variable past the impact time. ###################################################################### if {"$IMPACTchk" == "before"} { set chk1 [expr {$x1 + $r1}] set chk2 [expr {$x2 - $r2}] if {$chk1 > $chk2} { set x12 [expr {$x1 + $r1}] set IMPACTchk "after" } } ## END OF if {"$IMPACTchk" == "before"} ####################################### ## If before impact, plot the 2 masses. ####################################### if {"$IMPACTchk" == "before"} { ###################################################################### ## 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" } ## END OF if {"$IMPACTchk" == "before"} (plotting m1 and 2) ########################################## ## If AFTER impact, plot the merged mass. ########################################## if {"$IMPACTchk" == "after"} { ###################################################################### ## Set the x-coordinate of the location of the center of the ## mass1, in pixel-coords. ###################################################################### set xPx [Xwc2px $x12] ########################################################################## ## Draw the merged mass --- with 'create oval'. ## Set the upper-left and lower-right corners of the oval (a circle ## in this application). ########################################################################## set massX1Px [expr {$xPx - $radius12Px}] set massY1Px [expr {$yPx - $radius12Px}] set massX2Px [expr {$xPx + $radius12Px}] set massY2Px [expr {$yPx + $radius12Px}] .fRcanvas.can create oval $massX1Px $massY1Px $massX2Px $massY2Px \ -tags TAGcircle -fill "$COLOR1hex" } ## END OF if {"$IMPACTchk" == "after"} (plotting the merged mass) ########################################################## ## 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 the merged mass is exiting the image area, ## and 'break' out of this loop in that case. ## ## If x12 becomes less than Xmin OR greater than Xmax ## then we 'break' out of this animation loop. ########################################################### if {"$IMPACTchk" == "after"} { if { $x12 < $Xmin || $x12 > $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 x12 ## where ## t is the time in secs, from 0.0 to $aRtime($NSTEP) ## x1 the location of mass1 ## x2 the location of mass2 ## x12 the location of the merged mass ## ## 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: By clicking on the 'ShowList' button. ##+#################################################################### proc show_list {} { ## FOR TESTING: (dummy out this proc) # return #################################################################### ## Use the global variables that were set in the last animation run. #################################################################### global m1 m2 V1b V2b D h aRtime aRx1 aRx2 aRx12 NSTEP ################################## ## Create the heading for the list. ################################## set VARlist \ "Simulation of Two Colliding Masses that Stick Together 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 Total 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 Position of m1+m2 Step time-units (distance-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 nFMTED [format "%9.1d" $idx] if {"$aRx1($idx)" != "N/A"} { set x1FMTED [format "%20.4f" $aRx1($idx)] set x2FMTED [format "%20.4f" $aRx2($idx)] set x12FMTED [format "%20s" $aRx12($idx)] } else { set x1FMTED [format "%20s" $aRx1($idx)] set x2FMTED [format "%20s" $aRx2($idx)] set x12FMTED [format "%20.4f" $aRx12($idx)] } append VARlist "$timeFMTED $x1FMTED $x2FMTED $x12FMTED $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 the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit, ## in global variables 'PXperWCx' and 'PXperWCy' ## ## (This 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 'bouncing ball' 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 'entry' widgets on the GUI ## --- math expression parameters, initial value parameters, ## and two time parameters (endtime and stepsize). ## ## 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 10.0 ################################################# ## Set initial distance between the masses. ## (This should probably be set 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 Collision of 2 Sticking 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 then stick to each other and continue moving 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 stick together after impact and then travel at a constant velocities 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 single after-impact velocity is 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 of the 2 masses is animated, and, after impact, the 2 masses are represented as one mass traveling at the 'after' velocity. In the animation, the 2 masses are represented by 2 circles --- and the combined mass after impact is represented by a single, larger circle. The centers of these 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 similar to that used in many physics documents: m1 denotes mass 1 m2 denotes mass 2 V1b denotes velocity of mass1 before impact V2b denotes velocity of mass2 before impact Va denotes velocity of mass1+mass2 after impact The GUI allows the user to specify m1, m2, V1b, V2b. This script uses a single algebraic expressions to calculate Va. See below. The 3 velocities V1b, V2b, Va 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. - 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 equation gives the values of Va. Va = (m1*V1b + m2*V2b) / (m1 + m2) This equation can be derived from the single equation for conservation of momentum: m1*V1b + m2*V2b = (m1 + m2)*Va Some References: https://en.wikipedia.org/wiki/Inelastic_collision#Perfectly_inelastic_collision (presents the equation for final velocity along with its derivation) https://en.wikipedia.org/wiki/Collision#Perfectly_inelastic_collision (also presents the equation for final velocity along with its derivation) http://hyperphysics.phy-astr.gsu.edu/hbase/inecol.html (example in which one of the 2 masses is at rest before impact) https://en.wikipedia.org/wiki/Coefficient_of_restitution (presents a discussion of the case when the masses do not stick together but energy is dissipated in the collision) https://en.wikipedia.org/wiki/Impact_event and https://en.wikipedia.org/wiki/Giant_impact_theory (the Earth-Moon system may have been formed from an inelastic collision in which 2 huge masses did not stick together, but gravity held one resulting mass in orbit around the other) *************************** FEATURES AND USE OF THE GUI: *************************** The GUI allows the user to enter various values for - m1 - m2 - V1b - V2b from which the 'after' velocity, Va, is calculated. The GUI provides a 'Solve' button by which the user can trigger the calculation of the after-impact velocity, Va. --- Then, for the purpose of animation, the following additional parameters can be specified on the GUI. 1) D, the initial distance between m1 and m2, via an 'entry' widget on the GUI, 2) the width of the image area in pixels, say ImgWidthPx, via an 'entry' widget on the GUI, 3) 2 colors for: a) the canvas background, b) the 2 masses (circles) and the line along which they travel, via 2 button widgets on the GUI, that provide a separate color-selector GUI. After Va is calculated and the user has readied D and 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 D to their initially-displayed values. **************************************************** EXPERIMENTING WITH THE INITIAL MASSES AND 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. And the circle representing the merged masses will also be set so that the area of the circle is an indication of the magnitude of the sum of the 2 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: ********************************* 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) 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. 3) 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. 4) Map world-coordinates Xmin,Xmax to the image area horizontal left and right limits, 0 to ImgWidthPx, in pixels. 5) 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. 6) Start animating the motion of the masses. Use a loop in which the time is advanced by 'h' units at each step. For each step, BEFORE the impact, move mass1 by V1b*h and move mass2 by V2b*h --- and AFTER impact, move the merged mass (1 + m2) by Va*h. Stop when the center of the merged mass 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 arrays t(i), x1(i), x2(i), x12(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 masses and the ## path line and text labels on the line. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative bouncing ball-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 bouncing ball ## 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)"