#!/usr/bin/wish -f ## ## SCRIPT: twoSinusoidalWavesMerging_twoPointSources.tk ## ## PURPOSE: This Tk GUI script generates an animation of 2 circular ## wave patterns interfering (merging). ## ## The animation is shown in a rectangular Tk 'photo' image ## on a rectangular Tk 'canvas' widget. ## ## The resultant wave amplitude at any point in the image ## is the algebraic sum of the two amplitudes of the 2 waves ## at the point. ## ## The resultant amplitude at any point in the rectangular ## image area is plotted as a pixel color. ## ############################################ ## METHOD - MATH MODELLING OF THE AMPLITUDES: ## ## The two waves are modelled as coming from two 'sources' ## or 'slits' at the bottom of the rectangular image. ## ## The pixels of the image area (in integer coordintes --- i,j) ## are mapped to 'real', 'floating-point' 'world coordinates' ## --- (x,y). ## ## We imagine the two waves traveling at a common velocity ## ## v = f * L ## ## where f is frequency (say, cycles per second) and L is ## wave length (say, inches or meters per cycle). ## ## So f = v / L and L = v / f. ## ## Actually, we will allow our two merging waves to have ## their own frequency and wave length --- f(i) and L(i) ## where i = 1 and 2. But we think of velocity v being ## the same for both waves, since we are simulating 2 waves ## traveling in the same medium (water, vacuum, granite, whatever). ## ## The amplitude of wave 'i', where i = 1 or 2, is given by ## a sinusoidal equation representing the amplitude of a 'traveling wave' ## ## A(i,x,y,t) = A(i) * sin ( (2 * pi * f(i) * t) - ( 2 * pi* r(i,x,y) / L(i) ) + ph(i) ) ## ## term-1 (time-dependent) term-2 (distance-dependent) term-3 (phase) ## ## where there are 3 terms in the sin function and where ## A(i) and ph(i) are constants for wave i --- 'A' being ## an amplitude factor and 'ph' being a phase angle. ## ## (We allow for two different 'initial' phase angles for the 2 waves. ## In other words, we allow for simulating 2 waves that are NOT ## in sync at their sources, even when their two frequencies are equal.) ## ## f(i) and L(i) are the frequency and wave-length of wave i. ## ## The term ( 2 * pi * f(i) * t ) represents the sinusoidal variation ## with time, t, of the wave amplitude at any point (x,y) --- converted ## to radians. ## ## The term ( 2 * pi * r(i,x,y) / L(i) ) represents the number of ## wave-lengths from the source-point to the point (x,y) --- converted ## to radians. r(i,x,y) represents the distance from the source-point ## of wave i. Let us say (x1,y1) and (x2,y2) are the 2 source points. ## Then ## r(1,x,y) = sqrt( (x-x1)^2 + (y-y1)^2 ) ## and ## r(2,x,y) = sqrt( (x-x2)^2 + (y-y2)^2 ). ## ## We will let the 2 source points be on the bottom of the image rectangle ## at y=0. We will let the origin (0,0) be in the middle of the bottom ## of the rectangle. We let the two source points be a distance 'd' ## on either side of the origin. ## ## The resultant amplitude of the 2 merged waves at time t and at ## point (x,y) is given by A(1,x,y,t) + A(2.x,y,t). ## ## The Tk GUI allows the user to specify positive constants for the ## numbers f(i), L(i), A(i), ph(i), and 2d (the distance between the ## two point sources). ## ## (Actually, we let the user specify wave-velocity, v, rather ## than L(1) and L(2). We calculate L(i) = v / f(i).) ## ########################################################### ## METHOD - MATH CONVERSION OF RESULTANT AMPLITUDE TO COLOR: ## ## The amplitude of the waves are depicted with colors interpolated ## between a 'max-color' and a 'min-color'. In fact, we allow for ## specifying a 3rd 'zero-color'. For positive amplitudes, we interpolate ## between 'max-color' and 'zero-color'. For negative amplitudes, ## we interpolate between 'min-color' and 'zero-color'. ## ## So the Tk GUI has 3 buttons by which to specify the ## 1) max-color ## 2) zero-color ## 3) min-color ## ## We use the sum of the 2 max-amplitudes Asum = A(1) + A(2) ## to do the interpolation. ## ## When resultant amplitude RA = A(1,x,y,t) + A(2.x,y,t) is ## positive, we get the color for RA from ## ## RA-color = (RA/Asum) * max-color + (1 - RA/Asum) * zero-color ## ## where RA/Asum and (1 - RA/Asum) are between 0 and 1 ## ## and when RA is negative, we get the color for RA from ## ## RA-color = (abs(RA)/Asum) * min-color + (1 - abs(RA)/Asum) * zero-color ## ## where abs(RA)/Asum and (1 - abs(RA)/Asum) are between 0 and 1 ## ## Let r = abs(RA)/Asum ## ## Actually, we use 3 interpolations to get RGB colors for RA. ## For example: ## ## RA-R = ( r * max/min-color-R ) + ( (1 - r) * zero-color-R ) ## RA-G = ( r * max/min-color-G ) + ( (1 - r) * zero-color-G ) ## RA-B = ( r * max/min-color-B ) + ( (1 - r) * zero-color-B ) ## ## We use max or min color depending on whether RA is positive or negative. ## ## The RGB values for max-color, zero-color and min-color are ## integers between 0 and 255. The 3 RGB values for RA are also ## between 0 and 255. ## ##+####################################################### ## METHOD - PUTTING THE PIXEL COLORS ON EACH OF THE IMAGES: ## ## ('create image' on a Tk canvas widget) ## ## This script makes a sequence of color-shaded images by making a ## sequence of images in a Tk 'photo' image 'structure' placed on ## a Tk canvas widget. ## ## The in-memory image 'structure' is put on the Tk canvas via a ## Tk canvas 'image create' command --- and each image in the ## animation sequence is generated via 'put -to $i $j' ## commands on the image. ## ## Actually, to reduce the number of 'put' commands, we build ## each entire image as a list-of-lists-of-hexcolors and ## use one 'put' to put the list-of-lists in the image structure ## on the canvas. ## ############################# ## PERFORMANCE CONSIDERATIONS: ## ## Since each 'image-draw' has a lot of pixels to color, especially when ## a large image size is requested, a draw of each image of the ## animation may take several seconds. ## ## To make it possible to generate a pretty smooth animation, ## we allow the user to specify the size of the image (in pixels) ## in the x and y directions --- by means of a couple of widgets ## on the GUI. ## ##+############## ## THE GUI LAYOUT: ## ## Recall that the Tk GUI should allow the user to specify ## positive constants for the numbers f(i), L(i), A(i), ph(i), and d. ## And it should provide 3 buttons by which to specify 3 colors. ## And it should allow the user to set the image size. ## ## In place of L(i) or f(i), we could specify v, the wave velocity. ## Then L(i) = v / f(i) ---- OR f(i) = v / L(i). ## ## One way the user can specify these parameters is indicated by ## the following 'sketch' of the GUI: ## ## FRAMEnames ## VVVVVVVVVV ## ------------------------------------------------------------------------------------------ ## Two Waves Propagating from 2 point sources - an animation ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} Animate: O Start O Stop {Reset {Color for max {Color for zero {Color for min ## parms} amplitude} amplitude} amplitude} ## ## .fRwave1 Wave1 - Amplitude: 10.0__ Frequency (cycles/sec): 4.0___ Phase angle (degrees): 0.0___ ## ## .fRwave2 Wave2 - Amplitude: 10.0__ Frequency (cycles/sec): 4.0___ Phase angle (degrees): 0.0___ ## ## .fRdistance Distance between 2 sources (inches): 4.0___ Wave Velocity (inches/sec): 10.0____ ## ## .fRimgsize Xpixels: <---------O--------> Ypixels: <--------O-------> ## ## .fRmsg [ .......... Messages go here .........................................] ## ## .fRcanvas |------------------------------------------------------------------------A ## | | ## | [This scrollable canvas contains the 'image-structure'.] | ## | | ## | | ## | | ## | | ## | | ## |<---------------------------------------------------------------------->V ## ## ------------------------------------------------------------------- ## ## 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) outline a 'listbox' widget. ## 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 ## ## - 6 button widgets ## - 11 label widgets ## - 8 entry widgets ## - 2 scale widgets (but may use scale widgets in place of some entry widgets) ## - 1 canvas widget with 2 scrollbars ## - 2 radiobutton widgets in 1 group ## - 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' ## '.fRwave1' ## '.fRwave2' ## '.fRdistance' ## '.fRimgsize' ## '.fRmsg' ## '.fRcanvas' ## No sub-frames. ## ## 1b) Pack ALL frames. ## ## 2) Define all widgets in the frames (and pack them): ## ## - In '.fRbuttons': ## 3 button widgets ('Exit', 'Help' , 'StartAnimation') ## and ## 3 buttons (for setting 3 colors). ## ## - In '.fRwave1': ## 3 pairs of 'label' and 'entry' (or 'scale') widgets ## ## - In '.fRwave2': ## 3 pairs of 'label' and 'entry' (or 'scale') widgets ## ## - In '.fRdistance': ## 2 pairs of 'label' and 'entry' (or 'scale') widgets ## ## - In '.fRimgsize': ## 1 'label' and 1 'scale' widget each for XmaxPx, YmaxPx ## ## - In '.fRmsg': ## 1 label widget to display messages to the user, such as ## elapsed execution time, as well as a 'calculation 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: ## ## - 'animate' - called by the 'StartAnimation' button ## ## - 'advise_user' - called by the 'animate' proc ## ## - 'setMappingVars_for_px2wc' - called by proc 'animate' ## ## - 'Xpx2wc' - called by proc 'animate' ## - 'Ypx2wc' - called by proc 'animate' ## ## - 'set_scale_Ymax_equal_Xmax' - called by button1-release on the X-scale ## ## - 'set_max_color1' - called by the max-color button ## ## - 'set_min_color2' - called by the min-color button ## ## - 'set_zero_color0' - called by the zero-color button ## ## - 'update_color_button' - sets background & foreground color of any ## of the 3 color buttons ## ## - 'reset_parms' - called by the 'Reset' button ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button ## ## 5) Additional GUI initialization: Set some inital values of parameters ## like amplitudes, frequencies, ## wave velocity, and 3 colors. ## ##+######################################################################## ## 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 2016jan28 ## Changed by: Blaise Montandon 2016feb05 Changed 'StartAnimation' button ## to a label and two start/stop ## radiobuttons. Provided the ## internals of the 'animate' proc. ## Changed by: Blaise Montandon 2016feb06 Reduce the number of arithmetic ## operations in the image-making ## multiple loops over image pixels. ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "Two Waves Propagating from 2 point sources - an animation" wm iconname . "TwoWaves" 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 scaleBKGD "#f0f0f0" set radbuttBKGD "#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" ## 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 xyrangevarEntryWidthChars 7 ## SCALE geom parameters: set BDwidthPx_scale 2 # set initScaleLengthPx 100 set scaleThicknessPx 10 set scaleRepeatDelayMillisecs 800 ## CANVAS geom parameters: set initCanWidthPx 300 set initCanHeightPx 200 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ##+################################################################### ## Set a MINSIZE of the window. ## ## For width, allow for the minwidth of the '.fRbuttons' frame: ## about 6 buttons (Exit,Help,StartAnimation,Color,Color,Color). ## We want to at least be able to see the Exit button. ## ## For height, allow ## 2 chars high for the '.fRbuttons' frame, ## 1 char high for the '.fRwave1' frame, ## 1 char high for the '.fRwave2' frame, ## 1 char high for the '.fRdistance' frame, ## 2 chars high for the '.fRimgsize' frame, ## 1 char high for the '.fRmsg' frame, ## 24 pixels high for the '.fRcanvas' frame. ##+################################################################### ## MIN WIDTH: set minWinWidthPx [font measure fontTEMP_varwidth \ "Exit Help StartAnimation Color for max Color for zero Color for min"] ## Add some pixels to account for right-left-side window decoration ## (about 8 pixels), about 6 x 4 pixels/widget for borders/padding for ## 6 widgets --- 6 buttons. set minWinWidthPx [expr {32 + $minWinWidthPx}] ## MIN HEIGHT --- allow ## 2 chars high for 'fRbuttons' ## 1 char high for 'fRwave1' ## 1 char high for 'fRwave2' ## 1 char high for 'fRdistance' ## 2 chars high for 'fRimgsize' ## 1 char high for 'fRmsg' ## 24 pixels high for 'fRcanvas' set CharHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {24 + (8 * $CharHeightPx)}] ## Add about 28 pixels for top-bottom window decoration, ## about 7x4 pixels for each of the 7 stacked frames and their ## widgets (their borders/padding). set minWinHeightPx [expr {$minWinHeightPx + 56}] ## 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(labelSTARTSTOP) "Animation:" set aRtext(radbuttSTART) "Start" set aRtext(radbuttSTOP) "Stop" set aRtext(buttonRESET) "ResetParms" set aRtext(buttonCOLOR1) "Color for max amplitude" set aRtext(buttonCOLOR2) "Color for min amplitude" set aRtext(buttonCOLOR0) "Color for zero amplitude" ## For widgets in 'fRwave1' frame: set aRtext(labelAMP1) "WAVE-1 Amplitude:" set aRtext(labelFREQ1) "Frequency (cycles/sec):" set aRtext(labelPHASE1) "Phase angle (degrees):" ## For widgets in 'fRwave2' frame: set aRtext(labelAMP2) "WAVE-2 Amplitude:" set aRtext(labelFREQ2) "Frequency (cycles/sec):" set aRtext(labelPHASE2) "Phase angle (degrees):" ## For widgets in 'fRdistance' frame: set aRtext(labelDIST) "Distance between 2 sources (inches):" set aRtext(labelVELOCITY) "Wave Velocity (inches/sec):" ## For widgets in 'fRimgsize' frame: set aRtext(labelXmaxPx) "Image size Xpixels:" set aRtext(labelYmaxPx) " Ypixels:" ## For some calls to the 'advise_user' proc: set aRtext(STARTmsg) "** Click 'Start' to start the animation **" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRwave1' '.fRwave2' ## '.fRdistance' '.fRimgsize' '.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 .fRwave1 -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRwave1 -relief raised -borderwidth 2 # frame .fRwave2 -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRwave2 -relief raised -borderwidth 2 frame .fRdistance -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRimgsize -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 \ .fRwave1 \ .fRwave2 \ .fRdistance \ .fRimgsize \ .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-and-PACK ## - an exit-button, a help-button ## - a label and 2 start/stop radiobuttons ## - a reset-parms button ## and ## - 3 buttons ( to specify colors). ##+##################################################################### 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} label .fRbuttons.labelSTARTSTOP \ -text "$aRtext(labelSTARTSTOP)" \ -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 \ -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.buttRESET \ -text "$aRtext(buttonRESET)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command {reset_parms} button .fRbuttons.buttCOLOR1 \ -text "$aRtext(buttonCOLOR1)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_max_color1" button .fRbuttons.buttCOLOR2 \ -text "$aRtext(buttonCOLOR2)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_min_color2" button .fRbuttons.buttCOLOR0 \ -text "$aRtext(buttonCOLOR0)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_zero_color0" ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.labelSTARTSTOP \ .fRbuttons.radbuttSTART \ .fRbuttons.radbuttSTOP \ .fRbuttons.buttRESET \ .fRbuttons.buttCOLOR1 \ .fRbuttons.buttCOLOR0 \ .fRbuttons.buttCOLOR2 \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRwave1' FRAME ---- DEFINE 3 pairs of ## LABEL-and-SCALE widgets. PACK them. ##+################################################################## ################################### ## DEFINE SCALE for WAVE1 AMPLITUDE. ################################### label .fRwave1.labelAMP1 \ -text "$aRtext(labelAMP1)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARamp1 0.5 scale .fRwave1.scaleAMP1 \ -from 0.0 -to 1.0 \ -resolution 0.01 \ -bigincrement 0.1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARamp1 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave1.labelAMP1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave1.scaleAMP1 \ -side left \ -anchor w \ -fill x \ -expand 0 ######################################## ## DEFINE SCALE for WAVE1 FREQUENCY. ######################################## label .fRwave1.labelFREQ1 \ -text "$aRtext(labelFREQ1)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARfreq1 4.0 scale .fRwave1.scaleFREQ1 \ -from 1.0 -to 100.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARfreq1 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave1.labelFREQ1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave1.scaleFREQ1 \ -side left \ -anchor w \ -fill x \ -expand 0 ######################################## ## DEFINE SCALE for WAVE1 PHASE ANGLE. ######################################## label .fRwave1.labelPHASE1 \ -text "$aRtext(labelPHASE1)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARphase1 0.0 scale .fRwave1.scalePHASE1 \ -from 0.0 -to 360.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARphase1 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave1.labelPHASE1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave1.scalePHASE1 \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRwave2' FRAME ---- DEFINE-and-PACK 3 pairs of ## LABEL-and-SCALE widgets. ##+################################################################## ################################### ## DEFINE SCALE for WAVE2 AMPLITUDE. ################################### label .fRwave2.labelAMP2 \ -text "$aRtext(labelAMP2)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARamp2 0.5 scale .fRwave2.scaleAMP2 \ -from 0.0 -to 1.0 \ -resolution 0.01 \ -bigincrement 0.1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARamp2 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave2.labelAMP2 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave2.scaleAMP2 \ -side left \ -anchor w \ -fill x \ -expand 0 ######################################## ## DEFINE SCALE for WAVE2 FREQUENCY. ######################################## label .fRwave2.labelFREQ2 \ -text "$aRtext(labelFREQ2)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARfreq2 4.0 scale .fRwave2.scaleFREQ2 \ -from 1.0 -to 100.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARfreq2 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave2.labelFREQ2 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave2.scaleFREQ2 \ -side left \ -anchor w \ -fill x \ -expand 0 ######################################## ## DEFINE SCALE for WAVE2 PHASE ANGLE. ######################################## label .fRwave2.labelPHASE2 \ -text "$aRtext(labelPHASE2)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARphase2 0.0 scale .fRwave2.scalePHASE2 \ -from 0.0 -to 360.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 120 \ -font fontTEMP_varwidth \ -variable VARphase2 \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRwave2.labelPHASE2 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRwave2.scalePHASE2 \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRdistance' FRAME ---- DEFINE-and-PACK 2 pairs of ## LABEL and SCALE widgets. PACK them. ##+################################################################## ######################################################################## ## DEFINE LABEL-and-SCALE for X-DISTANCE between 2 point sources. ######################################################################## label .fRdistance.labelDIST \ -text "$aRtext(labelDIST)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARdistance 0.5 scale .fRdistance.scaleDIST \ -from 0.0 -to 2.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 60 \ -font fontTEMP_varwidth \ -variable VARdistance \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRdistance.labelDIST \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRdistance.scaleDIST \ -side left \ -anchor w \ -fill x \ -expand 1 ################################################ ## DEFINE LABEL-and-SCALE pair for WAVE-VELOCITY. ################################################ label .fRdistance.labelVELOCITY \ -text "$aRtext(labelVELOCITY)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set VARvelocity 10.0 scale .fRdistance.scaleVELOCITY \ -from 0.0 -to 20.0 \ -resolution 0.1 \ -bigincrement 1.0 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 60 \ -font fontTEMP_varwidth \ -variable VARvelocity \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRdistance.labelVELOCITY \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRdistance.scaleVELOCITY \ -side left \ -anchor w \ -fill x \ -expand 1 ##+################################################################## ## In the '.fRimgsize' FRAME ---- DEFINE-and-PACK ## 1 LABEL and 1 SCALE widget for Xmax and ## 1 LABEL and 1 SCALE widget for Ymax. ##+################################################################### label .fRimgsize.labelXmaxPx \ -text "$aRtext(labelXmaxPx)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set XmaxPx 500 scale .fRimgsize.scaleXmaxPx \ -from 10 -to 1000 \ -resolution 1 \ -bigincrement 1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 100 \ -font fontTEMP_varwidth \ -variable XmaxPx \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRimgsize.labelXmaxPx \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRimgsize.scaleXmaxPx \ -side left \ -anchor w \ -fill x \ -expand 1 ## DEFINE LABEL-and-SCALE pair for YmaxPx. label .fRimgsize.labelYmaxPx \ -text "$aRtext(labelYmaxPx)" \ -font fontTEMP_SMALL_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## Set this widget var in the GUI initialization section ## at the bottom of this script. # set YmaxPx 400 scale .fRimgsize.scaleYmaxPx \ -from 10 -to 1000 \ -resolution 1 \ -bigincrement 1 \ -repeatdelay $scaleRepeatDelayMillisecs \ -length 100 \ -font fontTEMP_varwidth \ -variable YmaxPx \ -showvalue true \ -orient horizontal \ -bd $BDwidthPx_scale \ -width $scaleThicknessPx pack .fRimgsize.labelYmaxPx \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRimgsize.scaleYmaxPx \ -side left \ -anchor w \ -fill x \ -expand 1 ##+################################################################## ## In the '.fRmsg' FRAME ---- DEFINE-and-PACK 1 LABEL widget. ##+################################################################## label .fRmsg.labelINFO \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bg "#ff9999" \ -bd $BDwidthPx_button pack .fRmsg.labelINFO \ -side left \ -anchor w \ -fill x \ -expand 1 ##+###################################################### ## In the '.fRcanvas' FRAME - ## DEFINE-and-PACK the 'canvas' widget ##+###################################################### ## 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'. ## ## 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. pack .fRcanvas.can \ -side top \ -anchor nw \ -fill both \ -expand 1 ##+######################################## ## END OF the DEFINITION OF THE GUI WIDGETS ##+######################################## ##+############################### ## BINDINGS SECTION: ##+############################### ##+####################################################### ## Start the animation with a button1-release on the ## 'Start' radiobutton. ##+####################################################### bind .fRbuttons.radbuttSTART {animate} ##+####################################################### ## Make ymax-scale move to position of xmax-scale, ## to make it easy to ask for a square image. ##+####################################################### bind .fRimgsize.scaleXmaxPx \ {set_scale_Ymax_equal_Xmax ; advise_user "$aRtext(STARTmsg) with new image size." } ##+##################################################################### ## Remind user to click 'Start' after changing ymax scale value. ##+##################################################################### bind .fRimgsize.scaleYmaxPx \ {advise_user "$aRtext(STARTmsg) with new y-image size."} ##+################################################################### ## Remind user to click 'Start' after changing an amplitude ## --- VARamp1 or VARamp2. ##+################################################################### bind .fRwave1.scaleAMP1 \ {advise_user "$aRtext(STARTmsg) with new amplitude-1."} bind .fRwave2.scaleAMP2 \ {advise_user "$aRtext(STARTmsg) with new amplitude-2."} ##+########################################################## ## Remind user to click 'Start' after changing a frequency ## --- VARfreq1 or VARfreq2. ##+########################################################## bind .fRwave1.scaleFREQ1 \ {advise_user "$aRtext(STARTmsg) with new frequency-1."} bind .fRwave2.scaleFREQ2 \ {advise_user "$aRtext(STARTmsg) with new frequency-2."} ##+########################################################## ## Remind user to click 'Start' after changing a phase --- ## VARphase1 or VARphase2. ##+########################################################## bind .fRwave1.scalePHASE1 \ {advise_user "$aRtext(STARTmsg) with new phase-1."} bind .fRwave2.scalePHASE2 \ {advise_user "$aRtext(STARTmsg) with new phase-2."} ##+####################################################### ## Remind user to click 'Start' after changing the ## distance between the 2 wave-source-points. ##+########################################################## bind .fRdistance.scaleDIST \ {advise_user "$aRtext(STARTmsg) with new distance between sources."} ##+####################################################### ## Remind user to click 'Start' after changing ## the wave-velocity. ##+####################################################### bind .fRdistance.scaleVELOCITY \ {advise_user "$aRtext(STARTmsg) with new wave-velocity (and wave-length)."} ##+###################################################################### ## PROCS SECTION: ## ## - animate - called by a button1-release binding ## on the 'Start' radiobutton. ## ## - setMappingVars_for_px2wc - called by proc 'animate' ## ## - Xpx2wc - called by proc 'animate' ## - Ypx2wc - called by proc 'animate' ## ## - set_scale_Ymax_equal_Xmax - called by button1-release on the X-scale ## ## - set_max_color1 - called by color1 (max) button '-command' ## ## - set_min_color2 - called by color2 (min) button '-command' ## ## - set_zero_color0 - called by color0 (zero) button '-command' ## ## - update_color_button - called by the 'set_*_color*' procs and once ## in the 'Additional GUI Initialization section. ## ## - reset_parms - called by 'Reset' button and in the ## 'Additional GUI Initialization' section, ## to initialize the parms. ## ## - popup_msgVarWithScroll - called by 'Help' button ## ##+####################################################################### ##+##################################################################### ## PROC animate ## ## PURPOSE: ## Starts drawing and showing the sequence of 2-waves-merging images. ## ## METHOD: ## For each calculated time step, t-new = t-prev + DELTAsecs, ## where DELTAsecs is a fixed time step, ## we scan horizontally over the rectangular image area and ## we calculate a hex-color at each pixel. ## ## We build a row-list of hexcolors and add each row-list to a list, ## thus making a list-of-lists. ## Then we put that list-of-lists on the 'image structure' with a call like: ## imgID put $allrowsLISThexcolors -to 0 0 ## Then we go to the next time step. ## ## CALLED BY: By clicking on the 'Start' animation radiobutton --- and, possibly, ## in the 'Additional GUI Initialization' section at the bottom of ## this script, to start with an animation going when the GUI ## first starts up. ## ##+##################################################################### ## Recall that: ## ## We use the sum of the 2 max-amplitudes Asum = A(1) + A(2) ## as a divisor (normalizer) in the color-interpolation. ## ## When resultant amplitude RA = A(1,x,y,t) + A(2,x,y,t) is ## positive, we get the color for RA from ## ## RA-color = (RA/Asum) * max-color + (1 - RA/Asum) * zero-color ## ## where RA/Asum and (1 - RA/Asum) are between 0 and 1 ## ## and when RA is negative, we get the color for RA from ## ## RA-color = (abs(RA)/Asum) * min-color + (1 - abs(RA)/Asum) * zero-color ## ## where abs(RA)/Asum and (1 - abs(RA)/Asum) are between 0 and 1 ## ## Let r = abs(RA)/Asum ## ## Actually, we use 3 interpolations to get RGB colors for RA. ## For example: ## ## RA-R = ( r * max/min-color-R ) + ( (1 - r) * zero-color-R ) ## RA-G = ( r * max/min-color-G ) + ( (1 - r) * zero-color-G ) ## RA-B = ( r * max/min-color-B ) + ( (1 - r) * zero-color-B ) ## ## We use max or min color depending on whether RA is positive or negative. ## ## The RGB values for max-color, zero-color and min-color are ## integers between 0 and 255. The 3 RGB values for RA are also ## kept between 0 and 255. ## ## More details are given in comments near the top of this Tk script ## and in comments in this proc. ##+##################################################################### proc animate {} { ## FOR TESTING: (dummy out this proc) # return global VARanimate0or1 VARamp1 VARfreq1 VARphase1 VARamp2 VARfreq2 VARphase2 \ pi twopi radsPERdeg XmaxPx YmaxPx VARdistance VARvelocity \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex \ COLOR0r COLOR0g COLOR0b COLOR0hex ## Set the current time, for determining elapsed ## time for building each 'photo' image. set t0 [clock milliseconds] ## Indicate that drawing calculations are starting. advise_user "** PERFORMING CALCULATIONS for first image **" ## Delete the current image structure. ## We especially need to do this when the canvas has been re-sized, ## so that we can redraw the image according to the new canvas size. catch {image delete imgID} ######################################################### ## Initialize the width & height of the image that we are ## going to create --- from two scale widget variables. ######################################################### set imgWidthPx $XmaxPx set imgHeightPx $YmaxPx ######################################################### ## Make the new image structure. ## (We need to do this after the 'delete imgID' above.) ######################################################### image create photo imgID -width $imgWidthPx -height $imgHeightPx ######################################################## ## Put the new image 'structure' on the canvas. ## (We need to do this after the 'delete imgID' above.) ######################################################## .fRcanvas.can create image 0 0 -anchor nw -image imgID ######################################################## ## Set the 'scrollregion' of the canvas according to the ## size of the image. (A simple 'update' does not work.) ######################################################## .fRcanvas.can configure -scrollregion "0 0 $imgWidthPx $imgHeightPx" ######################################################## ## Make the canvas area as big as we can to accomodate ## a large image. ## (We try some 'wm' commands to get the window to resize ## according to the canvas resize --- even after the ## user has manually resized the top window.) ######################################################## # wm geometry . {} ;# from wiki.tcl.tk/10720 and wiki.tcl.tk/44 ; WORKS! .fRcanvas.can configure -width $imgWidthPx -height $imgHeightPx wm geometry . {} ;# WORKS here too. ################################################################ ## Calculate the aspect-ratio of the image area, for use in ## setting world-coordinate limits below. ################################################################ set ASPECTratio [expr { $imgHeightPx / double($imgWidthPx) }] ################################################################ ## In world-coordinates, we imagine the base of the image area ## (on which the 2 point wave-sources lie) extending from ## -1.0 to +1.0 for a total width of 2.0. ## We get the image-area height (in world-coordinate units) ## by applying the aspect-ratio to the image-width 2.0. ################################################################ set imgHeightWC [expr { $ASPECTratio * 2.0}] ################################################################ ## Set the variables for converting pixels to world-coords. ## This is in case the user changed the image dimensions. ################################################################ ## Recall input vars for proc 'setMappingVars_for_px2wc' are: ## xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY ## ## In order to allow some margin: ## ## We map world-coord X-limits -1.0 and 1.0 ## TO pixel-coord X-limits 0 and imgWidthPx ## ## AND ## ## We map world-coord Y-limits imgHeightWC and 0.0 (top and bottom) ## TO pixel-coord Y-limits 0 and imgHeightPx (top and bottom) ## ## See code in 'setMappingVars_for_px2wc' for details. ######################################################################## set XwcUL -1.0 set YwcUL $imgHeightWC set XwcLR 1.0 set YwcLR 0.0 setMappingVars_for_px2wc xy $XwcUL $YwcUL 0 0 $XwcLR $YwcLR $imgWidthPx $imgHeightPx ########################################################################## ## Set the coordinates of the 2 point-sources in world-coords: ## (x1,y1) and (x2,y2) where y1 and y2 are zero. ########################################################################## set d [expr {$VARdistance / 2.0}] set x1 [expr {-$d}] set y1 0.0 set x2 $d set y2 0.0 ########################################################################## ## Mark where the two source points are, with two short lines ## at the bottom of the canvas. ########################################################################## set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] .fRcanvas.can create line $x1Px $y1Px \ $x1Px [expr {$y1Px - 5}] \ -tags TAGsourcepoint -fill "#ffffff" set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] .fRcanvas.can create line $x2Px $y2Px \ $x2Px [expr {$y2Px - 5}] \ -tags TAGsourcepoint -fill "#ffffff" ########################################################################## ## Get the sum of the 2 user-selected amplitudes, ## for use in calculations in the loops below. ########################################################################## set Asum [expr {$VARamp1 + $VARamp2}] ########################################################################## ## Get the wave-lengths, L1 and L2, from the wave-velocity and ## the 2 wave frequencies specified on the GUI. ########################################################################## set L1 [expr {$VARvelocity / double($VARfreq1)}] set L2 [expr {$VARvelocity / double($VARfreq2)}] ########################################################################## ## Calculate some factors to reduce the number of arithmetic ## operations in the loops below. ########################################################################## set twopif1 [expr {$twopi * $VARfreq1}] set twopif2 [expr {$twopi * $VARfreq2}] set twopiOverL1 [expr {$twopi / $L1}] set twopiOverL2 [expr {$twopi / $L2}] set RADSphase1 [expr {$radsPERdeg * $VARphase1}] set RADSphase2 [expr {$radsPERdeg * $VARphase2}] ########################################################################## ## SET THE TIME-STEP according to the smallest period (highest ## frequency) of the two waves. ## ## Note that 1 / frequency gives the period of a sinusoidal wave. ## Since it takes about 20 time points to give a pretty smooth plot ## of one period of a sine curve, we use 0.05 of the smaller period ## as our time step. ########################################################################## set maxf $VARfreq1 if {$VARfreq2 > $VARfreq1} {set maxf $VARfreq2} set DELTAsecs [expr {0.05 / $maxf}] ########################################################################## ## START LOOPING OVER TIME --- in a 'while' loop. ## This while loop stops when VARanimate0or1 is set to 0. ## At the bottom of each pass through the loop, variable 'time' ## is augmented by DELTAsecs, ## where DELTAsecs is determined from the width of the image area and ## the user-selected wave-velocity. ########################################################################## set time 0.0 set imgCNT 0 while {$VARanimate0or1 == 1} { incr imgCNT ######################################################################### ## HERE IS THE 'GUTS' of generating each image: ## In a loop over yPx and xPx, where yPx and xPx are measured from the ## top left of the canvas/image rectangle, we calculate the hex-color ## for each pixel, using x,y where x and y are 'world coordinates' ## relative to the origin at the bottom-center of the image area. ## ## To determine the hexcolor of a pixel: ## After converting xPx,yPx to world-coordinates x,y: ## 1) We calculate the amplitude of waves 1 and 2 from the ## two sinudsoidal formulas involving amplitude, frequency, ## phase, and velocity values from the GUI: ## VARamp1 VARfreq1 VARphase1 VARamp2 VARfreq2 VARphase2 VARvelocity ## 2) We add the two amplitudes together to get the ## resultant-amplitude, RA. ## 3) We calculate the 'coloring-ratio' r = abs(RA)/Asum ## 4) We use the scalar 'coloring-ratio' 'r' to calculate the ## color of the pixel at (xPx,yPx) using RGB color1, color2, ## and color0 via formulas like ## RA-R = r * max/min-color-R + (1 -r) * zero-color-R ## RA-G = r * max/min-color-G + (1 -r) * zero-color-G ## RA-B = r * max/min-color-B + (1 -r) * zero-color-B ## where min-color is used when RA is negative and ## max-color is used when RA is positive. ####################################################################### ## Get the half width and height of the image rectangle --- ## which is the same as the pixel-coordinates of the 'origin' ## in the middle of the image area. ## ** For use in 'FOR TESTING' message-sections below. ** set xmidPx [expr {$imgWidthPx / 2}] set ymidPx [expr {$imgHeightPx / 2}] ## Get 2 vars to hold image dimensions minus one. ## ** For use in 'FOR TESTING' message-sections below. ** set imgWidthPx_1 [expr {$imgWidthPx - 1}] set imgHeightPx_1 [expr {$imgHeightPx - 1}] ####################################################### ## Remove var 'allrowsLISTcolors' so that it can be ## re-created by a first 'lappend' command below. ## ## "You can call 'lappend' with the name of an undefined ## variable and the variable will be created." from ## page 66 of Chapter 5 'Tcl Lists' of the book ## 'Practical Programming in Tcl and Tk' (4th edition) ## by Brent Welch, Ken Jones, Jeffrey Hobbs. ####################################################### catch {unset allrowsLISTcolors} ################################################### ## We start 'scanning' at the top row of pixels. ################################################### for {set yPx 0} {$yPx < $imgHeightPx} {incr yPx} { ####################################################### ## At the start of building each row of pixels, ## let us check if the user signaled to stop the animation. ## (If we wait until an entire image is finished, ## and if the image requested is very large, ## the user might have to wait many seconds for ## the stop signal to take effect.) ## 'return' exits this 'animate' proc. ####################################################### if {$VARanimate0or1 == 0} {return} ####################################################### ## Remove var 'onerowLISTcolors' so that it can be ## re-created by a first 'lappend' command below. ## ## "You can call 'lappend' with the name of an undefined ## variable and the variable will be created." from ## page 66 of Chapter 5 'Tcl Lists' of the book ## 'Practical Programming in Tcl and Tk' (4th edition) ## by Brent Welch, Ken Jones, Jeffrey Hobbs. ####################################################### catch {unset onerowLISTcolors} ################################################ ## We 'scan' along each row of pixels. ################################################ for {set xPx 0} {$xPx < $imgWidthPx} {incr xPx} { ################################################ ## Convert (xPx,yPx) to world-coordinates (x,y). ################################################ set x [Xpx2wc $xPx] set y [Ypx2wc $yPx] ## FOR TESTING: (show pixel AND world coords.) ## (Change 'if {0}' to 'if {1}' to activate.) if {0} { if {$yPx == 0 || $yPx == $ymidPx || $yPx == $imgHeightPx_1} { if {$xPx == 0 || $xPx == $xmidPx || $xPx == $imgWidthPx_1} { puts "" puts "xPx = $xPx yPx = $yPx x = $x y = $y" } } } ############################################################## ## Calculate Amplitude of wave1 at x,y,t. Recall that for i=1,2 ## A(i,x,y,t) = A(i)*sin((2*pi*f(i)*t) - (2*pi*r(i,x,y)/L(i)) + ph(i)) ## and we have vars 'time' and (x1,y1) and (x2,y2) and ## VARamp1 VARfreq1 VARphase1 VARamp2 VARfreq2 VARphase2 L1 L2 ############################################################## set TERM1 [expr {$twopif1 * $time}] # set TEMPr [expr {sqrt( (($x - $x1)*($x - $x1)) + (($y - $y1)*($y - $y1)) )}] set TEMPr [expr {hypot($x - $x1,$y - $y1)}] set TERM2 [expr {$twopiOverL1 * $TEMPr}] set A1 [expr {$VARamp1 * sin($TERM1 - $TERM2 + $RADSphase1)}] ############################################################## ## Calculate Amplitude of wave2 at x,y,t. Recall that for i=1,2 ## A(i,x,y,t) = A(i)*sin((2*pi*f(i)*t) - (2*pi*r(i,x,y)/L(i)) + ph(i)) ## and we have vars 'time' and (x1,y1) and (x2,y2) and ## VARamp1 VARfreq1 VARphase1 VARamp2 VARfreq2 VARphase2 L1 L2 ############################################################## set TERM1 [expr {$twopif2 * $time}] # set TEMPr [expr {sqrt( (($x - $x2)*($x - $x2)) + (($y - $y2)*($y - $y2)) )}] set TEMPr [expr {hypot($x - $x2,$y - $y2)}] set TERM2 [expr {$twopiOverL2 * $TEMPr}] set A2 [expr {$VARamp1 * sin($TERM1 - $TERM2 + $RADSphase2)}] ################################################ ## Calculate the resultant-amplitude, RA, and ## r = abs(RA)/Asum ############################################### set RA [expr {$A1 +$A2}] set r [expr {abs($RA)/$Asum}] set oneMINUSr [expr {1.0 - $r}] ################################################################# ## According to the value of r, and the sign of RA, ## set the pixel color that we will put at $xPx, $yPx. ## ## If RA = 0.0, we set the pixel to the zero-color. ################################################################# if {$RA == 0.0} { lappend onerowLISTcolors $COLOR0hex } elseif {$RA > 0.0 } { set Red [expr {int(($r * $COLOR1r) + ($oneMINUSr * $COLOR0r))}] set Grn [expr {int(($r * $COLOR1g) + ($oneMINUSr * $COLOR0g))}] set Blu [expr {int(($r * $COLOR1b) + ($oneMINUSr * $COLOR0b))}] if {$Red > 255} {set Red 255} if {$Grn > 255} {set Grn 255} if {$Blu > 255} {set Blu 255} if {$Red < 0} {set Red 0} if {$Grn < 0} {set Grn 0} if {$Blu < 0} {set Blu 0} set hexcolor [format "#%02X%02X%02X" $Red $Grn $Blu] ## Put the color at $xPx $yPx. lappend onerowLISTcolors $hexcolor } elseif {$RA < 0.0 } { set Red [expr {int(($r * $COLOR2r) + ($oneMINUSr * $COLOR0r))}] set Grn [expr {int(($r * $COLOR2g) + ($oneMINUSr * $COLOR0g))}] set Blu [expr {int(($r * $COLOR2b) + ($oneMINUSr * $COLOR0b))}] if {$Red > 255} {set Red 255} if {$Grn > 255} {set Grn 255} if {$Blu > 255} {set Blu 255} if {$Red < 0} {set Red 0} if {$Grn < 0} {set Grn 0} if {$Blu < 0} {set Blu 0} set hexcolor [format "#%02X%02X%02X" $Red $Grn $Blu] ## Put the color at $xPx $yPx. lappend onerowLISTcolors $hexcolor } ## END OF if {$RA zero/pos/neg} } ## END OF the x-loop --- for {set xPx 0} {$xPx < $imgWidthPx} {incr xPx} ################################################################## ## Add the completed row-list to the all-rows-list. ################################################################## lappend allrowsLISTcolors $onerowLISTcolors } ## END OF the y-loop --- for {set yPx 0} {$yPx < $imgHeightPx} {incr yPx} ################################################################## ## Put the list-of-lists of row colors (the complete image) ## into the 'image structure'. ################################################################## imgID put $allrowsLISTcolors -to 0 0 ## Reset the cursor from a 'watch' cursor. # . config -cursor {} ############################################### ## Show the user the draw-time for this redraw. ############################################### set drawtime [expr {[clock milliseconds] - $t0}] set simtime [format "%.4f" $time] advise_user "\ ** ANIMATE TIME: $drawtime millisecs elapsed ; SIMULATED TIME: $simtime secs ; IMG# $imgCNT **" ######################################################################## ## Make sure the image is shown before going on to build the next image. ######################################################################## update ########################################################## ## BOTTOM of the loop over time. ## Augment 'time' by DELTAsecs. ########################################################## set time [expr {$time + $DELTAsecs}] } ## END OF LOOP while {$VARanimate0or1 == 1} } ## END OF PROC 'animate' ##+######################################################################## ## 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: ## PXperWC 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 variable 'PXperWC' ## ## (This will generally include 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 $canvasWidthPx $canvasHeightPx ## ## The first argument can be either 'x' or 'y' or 'xy'. This determines whether ## global variable 'PXperWC' is detemined by just the X-numbers, just the Y-numbers, ## or both. In this script, we 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 PXperWC 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_scale_Ymax_equal_Xmax ## ## PURPOSE: Make YmaxPx equal XmaxPx when the scale for ## XmaxPx is used. ## ## CALLED BY: a buttoVARfreq1-release binding on .fRimgsize.scaleXmaxPx. ##+########################################################## proc set_scale_Ymax_equal_Xmax {} { global XmaxPx YmaxPx if {$YmaxPx != $XmaxPx} {set YmaxPx $XmaxPx} } ## END OF proc 'set_scale_Ymax_equal_Xmax' ##+##################################################################### ## proc 'set_max_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 'max amplitude' color. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLOR1 button ##+##################################################################### proc set_max_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_max_color1' ##+##################################################################### ## proc 'set_min_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 'min amplitude' color. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLOR2 button ##+##################################################################### proc set_min_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_min_color2' ##+##################################################################### ## proc 'set_zero_color0' ##+##################################################################### ## 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 the 'zero amplitude' color. ## ## Arguments: none ## ## CALLED BY: .fRbuttons.buttCOLOR0 button ##+##################################################################### proc set_zero_color0 {} { global COLOR0r COLOR0g COLOR0b COLOR0hex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLOR0r: $COLOR0r" # puts "COLOR0g: $COLOR0b" # puts "COLOR0b: $COLOR0b" set TEMPrgb [ exec $ColorSelectorScript $COLOR0r $COLOR0g $COLOR0b] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR0hex "#$hexRGB" set COLOR0r $r255 set COLOR0g $g255 set COLOR0b $b255 ## Set background-and-foreground color of the indicated color button. update_color_button color0 advise_user "$aRtext(STARTmsg) to use new color." } ## END OF proc 'set_zero_color0' ##+##################################################################### ## 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 three '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 COLOR0r COLOR0g COLOR0b COLOR0hex 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" } } elseif {"$colorID" == "color0"} { .fRbuttons.buttCOLOR0 configure -bg $COLOR0hex set sumCOLOR1 [expr {$COLOR0r + $COLOR0g + $COLOR0b}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLOR0 configure -fg "#000000" } else { .fRbuttons.buttCOLOR0 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.labelINFO 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 (and initialize) the scale widgets on the GUI ## --- and one checkbutton. ## ## CALLED BY: 'Reset' button and in the 'Additional GUI Initialization' ## section at the bottom of this script. ##+######################################################################## proc reset_parms {} { global VARamp1 VARfreq1 VARphase1 VARamp2 VARfreq2 VARphase2 \ VARdistance VARvelocity XmaxPx YmaxPx ##+########################################################## ## Set initial value of scale widget variables --- for ## VARamp1, VARfreq1, VARphase1, VARamp2, VARfreq2, VARphase2. ##+########################################################## set VARamp1 0.5 set VARfreq1 40.0 set VARphase1 0.0 set VARamp2 0.5 set VARfreq2 40.0 set VARphase2 0.0 set VARdistance 1.0 set VARvelocity 6.0 ##+################################################# ## Set initial image size. ##+################################################ if {1} { ## Draw goes much faster with small image size --- ## less than 0.5 second for a 200x200 image on a ## moderately powerful CPU. set XmaxPx 200 set YmaxPx 200 } else { ## A 300x300 image may take about 1 second. set XmaxPx 300 set YmaxPx 300 } } ## END OF PROC 'reset_parms' ##+######################################################################## ## 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_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" 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_varwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 } 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 } ##################################### ## 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 Two Waves Merging Animation - a simulation ** This Tk GUI script generates an animation of 2 circular wave patterns interfering (merging). The animation is shown in a rectangular Tk 'photo' image on a rectangular Tk 'canvas' widget. We imagine 2 point-sources of the waves. The sources are located on the bottom edge of the image area. We imagine that the two point-sources are located on the left and right of the center-point of the bottom edge --- equi-distant from the center-point. We imagine that all distances and velocities of this simulation are provided in a common unit of distance measure, say inches. And we imagine times to be given in seconds. (If you prefer metric units, think of the distance units as centimeters or meters, say.) The width of the bottom edge of the image is imagined to be 2 inches. The center-point of the bottom edge is imagined to be the origin (0,0) of our 'world-coordinates' system. So the lower-left point is (-1,0) and the lower-right is (+1,0). The user can specify the image area (in pixels) via two 'scale' widgets on the GUI. The height of the image area, in world-coordinates (inches), is set according to the aspect-ratio of the image area chosen by the user. For example, if the image area is 400x400 pixels, the height of the image area in world-coordinates (inches) is to be the same as the image width --- 2.0 units (inches). Second example: If the image area is chosen to be 600x300 pixels, the height of the image is imagined to be 1.0 world-units (inches), while the width remains at 2.0 world-units (inches). The world- coordinates of the two upper corners of the image are then imagined to be at (-1,1) and (+1,1). The world-coordinates of the two bottom corners of the image are always (-1,0) and (+1,0). The resultant wave amplitude at any point in the image is the sum of the two amplitudes of the 2 waves at the point. The resultant amplitude at any point in the rectangular image area is plotted as a pixel color. ***************************************** METHOD - MATH MODELLING OF THE AMPLITUDES: The two waves are modelled as coming from two 'sources' or 'slits' at the bottom of the rectangular image. The pixels of the image area (in integer coordintes --- i,j) are mapped to 'real', 'floating-point' 'world coordinates' --- (x,y). We imagine the two waves traveling at a common velocity v = f * L where f is frequency (say, cycles per second) and L is wave length (say, inches or meters per cycle). So f = v / L and L = v / f. To allow for a wide variety of experiments with the simulation, we will allow our two merging waves to have their own frequency and wave length --- f(i) and L(i) where i = 1 and 2. But we think of velocity v being the same for both waves, since we are simulating 2 waves traveling in the same medium (water, vacuum, granite, whatever). The amplitude of wave 'i', where i = 1 or 2, is given by a sinusoidal equation representing the amplitude of a 'traveling wave' A(i,x,y,t) = A(i) * sin ( (2 * pi * f(i) * t) - ( 2 * pi * r(i,x,y) / L(i) ) + ph(i) ) term-1 (time-dependent) term-2 (distance-dependent) term-3 (phase) where there are 3 terms in the sin function and where A(i) and ph(i) are constants for wave i --- 'A' being an amplitude factor and 'ph' being a phase angle. (We allow for two different 'initial' phase angles for the 2 waves. In other words, we allow for simulating 2 waves that are NOT in sync at their sources, even when their two frequencies are equal.) f(i) and L(i) are the frequency and wave-length of wave i. The term ( 2 * pi * f(i) * t ) represents the sinusoidal variation with time, t, of the wave amplitude at any point (x,y) --- converted to radians. The term ( 2 * pi * r(i,x,y) / L(i) ) represents the number of wave-lengths from the source-point to the point (x,y) --- converted to radians. r(i,x,y) represents the distance from the source-point of wave i. Let us say (x1,y1) and (x2,y2) are the 2 source points. Then r(1,x,y) = sqrt( (x-x1)^2 + (y-y1)^2 ) and r(2,x,y) = sqrt( (x-x2)^2 + (y-y2)^2 ). We will let the 2 source points be on the bottom of the image rectangle at y=0. We will let the origin (0,0) be in the middle of the bottom of the rectangle. We let the two source points be a distance 'd' on either side of the origin. The resultant amplitude of the 2 merged waves at time t and at point (x,y) is given by A(1,x,y,t) + A(2,x,y,t). The Tk GUI allows the user to specify positive constants for the numbers f(i), L(i), A(i), ph(i), and d. (Actually, the GUI allows the user to specify the velocity, v. Then L(i) is calculated from the quotient v / f(i).) (Also, the GUI allows the user to specify the distance, D, between the 2 point-sources. Then d is calculated from the quotient D / 2.) ******************************************************** METHOD - MATH CONVERSION OF RESULTANT AMPLITUDE TO COLOR: The amplitude of the waves are depicted with colors interpolated between a 'max-color' and a 'min-color'. In fact, we allow for specifying a 3rd 'zero-color'. For positive amplitudes, we interpolate between 'max-color' and 'zero-color'. For negative amplitudes, we interpolate between 'min-color' and 'zero-color'. So the Tk GUI has 3 buttons by which to specify the 1) max-color 2) zero-color 3) min-color We use the sum of the 2 max-amplitudes Asum = A(1) + A(2) to do the interpolation. When resultant amplitude RA = A(1,x,y,t) + A(2.x,y,t) is positive, we get the color for RA from RA-color = (RA/Asum) * max-color + (1 - RA/Asum) * zero-color where RA/Asum and (1 - RA/Asum) are between 0 and 1 and when RA is negative, we get the color for RA from RA-color = (abs(RA)/Asum) * min-color + (1 - abs(RA)/Asum) * zero-color where abs(RA)/Asum and (1 - abs(RA)/Asum) are between 0 and 1 Let r = abs(RA)/Asum Actually, we use 3 interpolations to get RGB colors for RA. For example: RA-R = ( r * max/min-color-R ) + ( (1 - r) * zero-color-R ) RA-G = ( r * max/min-color-G ) + ( (1 - r) * zero-color-G ) RA-B = ( r * max/min-color-B ) + ( (1 - r) * zero-color-B ) We use max or min color depending on whether RA is positive or negative. The RGB values for max-color, zero-color and min-color are integers between 0 and 255. The 3 RGB values for RA are also kept between 0 and 255. ******************************************************* METHOD - PUTTING THE PIXEL COLORS ON EACH OF THE IMAGES: ('create image' on a Tk canvas widget) This script makes a sequence of color-shaded images by making a sequence of images in a Tk 'photo' image 'structure' placed on a Tk canvas widget. The in-memory image 'structure' is put on the Tk canvas via a Tk canvas 'image create' command --- and each image in the animation sequence is generated via 'put -to \$i \$j' commands on the image. Actually, to reduce the number of 'put' commands, we build each entire image as a list-of-row-lists-of-hexcolors and use one 'put' to put the list-of-lists in the image structure on the canvas. ************************** PERFORMANCE CONSIDERATIONS: Since each 'image-draw' has a lot of pixels to color, especially when a large image size is requested, a draw of each image of the animation may take several seconds. To make it possible to generate a pretty smooth animation (several images per second), we allow the user to specify the size of the image (in pixels) in the x and y directions --- by means of a couple of widgets on the GUI. Image sizes less than 200x200 pixels should draw quickly, even on low-powered computers. " ##+##################################################### ##+##################################################### ## 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 max-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative max-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 min-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative min-color.) ##+############################################# if {1} { ## Magenta: set COLOR2r 255 set COLOR2g 0 set COLOR2b 255 } 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 zero-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative zero-color.) ##+############################################## if {1} { ## Black: set COLOR0r 0 set COLOR0g 0 set COLOR0b 0 } else { ## Medium Gray: set COLOR0r 150 set COLOR0g 150 set COLOR0b 150 } set COLOR0hex [format "#%02X%02X%02X" $COLOR0r $COLOR0g $COLOR0b] update_color_button "color0" ##+################################################# ## Set constants to use for angle calculations --- ## in radians (and degrees). ##+################################################ set pi [expr {4.0 * atan(1.0)}] set twopi [expr {2.0 * $pi}] set radsPERdeg [expr {$pi/180.0}] #+####################################################### ## Initialize the scale widgets on the GUI --- and ## one checkbutton. ##+###################################################### reset_parms ##+##################################################### ## Advise the user how to start. ##+##################################################### advise_user "** Click Animation 'Start' radiobutton to start animation with current settings. **" ##+##################################################### ## Alternatively, go ahead and start the animation ## as the GUI starts up, using the initial settings. ##+##################################################### if {0} { set VARanimate0or1 1 animate advise_user "** Click Animation 'Stop' radiobutton to stop animation. **" }