'a simple star motion simulation 1/8/13 WTN 'QBasic-style source for FreeBASIC (http://www.freebasic.net/) 'For Linux compile using the command: fbc -ex -lang qb stars.bas 'For Windows: "c:\program files\freebasic\fbc" -ex -lang qb -s gui stars.bas 'This is public-domain code, no warranty, do whatever you want with it. ' 'This program simulates the dynamics of a star cluster. 'Sort of. Not intended to be an accurate physics simulation. ' defdbl a-z 'use double-precision FP variables screen 12 __Windowtitle "STARS" 'freebasic-specific randomize 'TIMER 'uncomment TIMER part for standard QBasic 'this program can be made to run under real QBasic but it's too slow 'to be practical. Standard QBasic elements included in comments for 'better compatibility with compilers that require true QBasic syntax. defaultinifile$ = "stars_parameters.ini" 'name of default settings file iniext$ = ".ini" 'user-entered filenames must end in this or will be added xmin% = 10 'screen viewport ymin% = 20 xmax% = 630 ymax% = 470 xsize% = xmax% - xmin% ysize% = ymax% - ymin% xofs% = xsize%/2 + xmin% 'view offset yofs% = ysize%/2 + ymin% txmin% = -(xsize%/2) 'min/max for clipping/wrap txmax% = xsize%/2 tymin% = -(ysize%/2) tymax% = ysize%/2 restoredefaults: inifile$ = defaultinifile$ nstars% = 1000 'number of stars delta = val("0.005") 'controls the time scale gravity = val("800") 'gravitational constant initialsizescale = val("0.3") 'size of initial random distribution initialvelocityscale = val("180") 'initial random velocities rbias = val("140") 'initial x/y rotational bias (sort of) mode% = 1 '0=no adjustment, 1=center, 2=wrap zsize% = 400 'used for initial distribution and wrap mode closethreshold = val("2") 'when stars are closer than this then force diminishes ' to help compensate for discrete sim inaccuracies closefactor = val("0.7") 'force adjustment factor - 0=no effect, 1=linear, >1 larger effect ' force=force*((distance^closefactor)/(closethreshold^closefactor)) 'the following variables aren't exposed in the parameters menu 'but can be changed by editing the ini file mindist = val("0.000001") 'below this considered a colision and ignored mode2slowdown = val("0.6") 'avoid runaway accelleration in wrap mode centerthreshold% = 75 'used for center mode averagingfield = val("5") 'stars out-of-view +/- xsize/ysize times this are ' not averaged when in auto-center mode gosub loadsettingsfile if inicontainsbaddata goto badinidata restart: on error goto programerror 'check parameters if nstars% < 2 goto badparms if mindist <= 0 goto badparms if zsize% < 1 goto badparms if initialsizescale <= 0 goto badparms if delta <= 0 goto badparms if gravity <= 0 goto badparms if averagingfield < 1 goto badparms goto parmsok badparms: cls print print " Incorrect parameters, can't run simulation." print " Press a key for parameter menu..." sleep: a$ = inkey$ inhibitcontinue = 1 goto menuloop parmsok: 'dimension/redimension arrays redim mass(nstars%) 'mass of each star redim oldxpos(nstars%) 'X/Y/Z positions for each star redim oldypos(nstars%) redim oldzpos(nstars%) redim newxpos(nstars%) 'new positions while computing redim newypos(nstars%) redim newzpos(nstars%) redim oldxvel(nstars%) 'X/Y/Z velocities for each star redim oldyvel(nstars%) redim oldzvel(nstars%) redim newxvel(nstars%) 'new velocities while computing redim newyvel(nstars%) redim newzvel(nstars%) redim lastx%(nstars%) 'last pixel position for each star redim lasty%(nstars%) 'so it can be erased from screen for i% = 1 to nstars% 'randomize positions, velocities and masses oldxpos(i%) = (rnd(1)-0.5)*xsize%*initialsizescale oldypos(i%) = (rnd(1)-0.5)*ysize%*initialsizescale oldzpos(i%) = (rnd(1)-0.5)*zsize%*initialsizescale oldxvel(i%) = (rnd(1)-0.5)*initialvelocityscale oldyvel(i%) = (rnd(1)-0.5)*initialvelocityscale oldzvel(i%) = (rnd(1)-0.5)*initialvelocityscale mass(i%) = 1.01 + rnd(1)*14.9 'used to colorize lastx%(i%) = 0 lasty%(i%) = 0 next i% 'apply a quadrant-based rotation bias 'not right... tends to break stars into 4 clumps 'but kind of cool. Doing a proper rotation bias is tricky... 'have to figure out how to motion based on angle (atn,sin,cos) 'have to figure out what to do with the Z dimension 'velocity needs to decrease the further out but not linearly 'for now clumps will do for i% = 1 to nstars% oldxvel(i%) = oldxvel(i%) + sgn(oldypos(i%))*rbias oldyvel(i%) = oldyvel(i%) - sgn(oldxpos(i%))*rbias next i% 'replace random init with star data from settings file if inicontainsstardata then gosub loadstardata if inicontainsbaddata goto badinidata 'any star not specified in settings file will remain random avx% = 0 'used by center mode avy% = 0 vscale = 1 'view/wrap scale continue: on error goto programerror cls color 15 locate 1,3 print "*** STARS *** Esc) parms <>) zoom P) pause Q) quit Space) restart" line (xmin%-2,ymin%-2)-(xmax%+2,ymin%-2) line (xmax%+2,ymin%-2)-(xmax%+2,ymax%+2) line (xmax%+2,ymax%+2)-(xmin%-2,ymax%+2) line (xmin%-2,ymax%+2)-(xmin%-2,ymin%-2) tzmin% = -(zsize%/2) 'Z min/max used just for wrap tzmax% = zsize%/2 gosub indicatescale mainloop: key$ = "" while key$ = "" gosub updatepositions key$ = inkey$ if ucase$(key$) = "P" then sleep: a$ = inkey$: key$ = "" if key$ = "," then vscale = vscale / 2: key$ = "": gosub indicatescale if key$ = "." then vscale = vscale * 2: key$ = "": gosub indicatescale gosub plotstars wend if ucase$(key$) = "Q" goto exitprogram if key$ = chr$(0)+"k" goto exitprogram 'X clicked if key$ = chr$(27) goto parametermenu if key$ = " " goto restart goto mainloop 'sub to colorize <> characters to indicate zoom level indicatescale: if vscale < 0.7 then color 12: locate 1,30: print "<"; else if vscale > 1.5 then color 12: locate 1,31: print ">"; else locate 1,30: print "<>"; end if end if color 15 return 'display menu for changing parameters and other operations parametermenu: prevnstars% = nstars% inhibitcontinue = 0 menuloop: 'jump here to redisplay or inhibit continue after error on error goto programerror cls color 15 'print mass colors for reference... locate 2,62: print "Masses" locate 3,62: print "======" xp% = 493 for m% = 1 to 15 yp% = 38 + m% * 16 locate 3+m%,64: print "=";m% pset (xp%,yp%),m% pset (xp%+1,yp%),m% pset (xp%,yp%+1),m% pset (xp%+1,yp%+1),m% next m% locate 2,1 print " Parameters" print " ==========" print print " 1) Number of stars ";nstars% print " 2) Delta ";delta print " 3) Gravity constant ";gravity print " 4) Initial velocities ";initialvelocityscale print " 5) Rotation bias ";rbias print " 6) Initial size ";initialsizescale print " 7) Z-dimension pixels ";zsize% print " 8) Close threshold ";closethreshold print " 9) Close factor ";closefactor print " 0) Adjustment mode "; if mode% = 0 then print "none"; if mode% = 1 then print "keep centered"; if mode% = 2 then print "wrap"; print:print print " F) Settings file: ";inifile$ print " L) Load settings file" print " S) Save settings file" print " P) Save settings with star data" print " D) Reload/Restore defaults and run" print " R) Run simulation with current parms" print " C) Continue simulation" print " Q) Quit" print print " Press key: "; sleep key$ = inkey$ if key$ = chr$(0)+"k" goto exitprogram 'X clicked key$ = ucase$(key$) if key$ = "R" goto restart if key$ = "Q" goto exitprogram if key$ = "C" then print "C" if inhibitcontinue = 0 goto continue print " Can't continue the current simulation." sleep 2: a$ = inkey$ end if if key$ = "F" then print "F" print " Enter settings file name: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then if lcase$(right$(a$,4)) <> iniext$ then a$ = a$ + iniext$ print " (adding ";iniext$;" to filename)" sleep 1 end if inifile$ = a$ inicontainsstardata = 0 end if end if if key$ = "1" then print "1" print " Enter number of stars: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then nstars% = val(a$) if nstars% <> prevnstars% then inhibitcontinue = 1 end if end if if key$ = "2" then print "2" print " Enter delta: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then delta = val(a$) end if if key$ = "3" then print "3" print " Enter gravity constant: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then gravity = val(a$) end if if key$ = "4" then print "4" print " Enter initial velocities: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then initialvelocityscale = val(a$) end if if key$ = "5" then print "5" print " Enter rotation bias: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then rbias = val(a$) end if if key$ = "6" then print "6" print " Enter initial size: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then initialsizescale = val(a$) end if if key$ = "7" then print "7" print " Enter Z-dimension pixels: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then zsize% = val(a$) end if if key$ = "8" then print "8" print " Enter close threshold: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then closethreshold = val(a$) end if if key$ = "9" then print "9" print " Enter close factor: "; line input a$ : a$ = ltrim$(rtrim$(a$)) if a$ <> "" then closefactor = val(a$) end if if key$ = "0" then mode% = mode% + 1 if mode% > 2 then mode% = 0 end if if key$ = "S" then print "S" print " Saving settings to ";inifile$ open inifile$ for output as #1 gosub printsettingstofile close #1 sleep 1: a$ = inkey$ end if if key$ = "P" then print "P" confirmed = 1 if inifile$ = defaultinifile$ then confirmed = 0 print " Saving star data to default settings will cause the simulation" print " to restart with this configuration. Is that OK? (Y to confirm): "; sleep: a$ = ucase$(inkey$): print a$ if a$ = "Y" then confirmed = 1 end if if confirmed then print " Saving settings and star data to ";inifile$ open inifile$ for output as #1 gosub printsettingstofile print #1,"# star data.. star n mass xpos ypos zpos xvel yvel zvel" print #1,"massmultiplier = 1";tab(40);";multiply all masses by this" print #1,"positionmultiplier = 1";tab(40);";multiply all positions by this" print #1,"velocitymultiplier = 1";tab(40);";multiply all velocities by this" for i% = 1 to nstars% print #1, "star";i%; print #1, mass(i%); print #1, " ";oldxpos(i%); print #1, " ";oldypos(i%); print #1, " ";oldzpos(i%); print #1, " ";oldxvel(i%); print #1, " ";oldyvel(i%); print #1, " ";oldzvel(i%) next i% close #1 inicontainsstardata = 1 sleep 1: a$ = inkey$ end if end if if key$ = "L" then print "L" print " Loading settings from ";inifile$ gosub loadsettingsfile if inicontainsbaddata goto badinidata if settingsfileexists then if nstars% <> prevnstars% then inhibitcontinue = 1 else print " Settings file doesn't exist." end if sleep 1: a$ = inkey$ end if if key$ = "D" then print "D" print " Restore default settings? (press Y to confirm) "; sleep: a$ = ucase$(inkey$): print a$ if a$ = "Y" then inifile$ = defaultinifile$ 'check to see if settings file exists on error goto restorenosettings open inifile$ for input as #1 close #1 print " Also remove ";inifile$;" (press Y to confirm) "; sleep: a$ = ucase$(inkey$): print a$ if a$ = "Y" then kill inifile$ else print " Loading settings from default settings file." sleep 1 end if goto restoredefaults end if end if goto menuloop exitprogram: screen 0 system printsettingstofile: 'file must already be open print #1, "; settings file for STARS star motion simulator" print #1, "nstars =";nstars%;tab(39);" ;number of stars" print #1, "delta =";delta;tab(39);" ;time factor" print #1, "gravity =";gravity;tab(39);" ;gravity constant" print #1, "initialsizescale =";initialsizescale;tab(39);" ;initial start size" print #1, "initialvelocityscale =";initialvelocityscale;tab(39);" ;initial random velocities" print #1, "rbias =";rbias;tab(39);" ;initial rotation bias" print #1, "mode =";mode%;tab(39);" ;view adjust 0=none, 1=centered, 2=wrap stars" print #1, "zsize =";zsize%;tab(39);" ;for random start and wrap mode" print #1, "closethreshold =";closethreshold;tab(39);" ;reduce force for distances below this" print #1, "closefactor =";closefactor;tab(39);" ;adjust factor.. 0=none,1=linear,2=square etc" print #1, "; additional settings not on the parameters menu..." print #1, "centerthreshold =";centerthreshold%;tab(39);" ;for mode 1, how often to recenter" print #1, "averagingfield =";averagingfield;tab(39);" ;for mode 1, how big of field to average" print #1, "mode2slowdown =";mode2slowdown;tab(39);" ;for mode 2, slow when crossing" print #1, "mindist =";mindist;tab(39);" ;distances below this are collisions, ignored" return 'on error target to detect if default INI exists when restoring restorenosettings: 'resume rns1 'uncomment the resume lines for standard QBasic rns1: close goto restoredefaults 'on error target for general computation errors programerror: 'resume pe1 pe1: close cls print print " A program error occurred, check settings." print " Press a key to continue..." sleep: a$ = inkey$ inhibitcontinue = 1 goto menuloop 'jump here if settings file contains bad data 'probably can't happen with FreeBASIC? but QBasic will fail 'if an integer% variable is assigned an out-of-range value badinidata: on error goto programerror cls print print " Settings file contains bad data." print " Press a key for parameter menu..." sleep: a$ = inkey$ inhibitcontinue = 1 goto menuloop 'load settings file if it exists 'settings file format is very simple... 'variable = value optional comments 'anything not recognized is ignored loadsettingsfile: settingsfileexists = 1 inicontainsstardata = 0 inicontainsbaddata = 0 on error goto nosettingsfile open inifile$ for input as #1 on error goto baddataerror while not eof(1) line input #1, a$ if left$(ltrim$(a$),5) = "star " then inicontainsstardata = 1 z = instr(a$,"=") if z > 2 and len(a$) > z then variable$ = ltrim$(rtrim$(left$(a$,z-1))) if instr(variable$," ") = 0 then 'only if variable is one word parm$ = ltrim$(rtrim$(mid$(a$,z+1))) z = instr(parm$," ") if z > 0 then parm$ = left$(parm$,z-1) 'use first word after = for parm if variable$ = "nstars" then nstars% = val(parm$) if variable$ = "delta" then delta = val(parm$) if variable$ = "gravity" then gravity = val(parm$) if variable$ = "initialsizescale" then initialsizescale = val(parm$) if variable$ = "initialvelocityscale" then initialvelocityscale = val(parm$) if variable$ = "rbias" then rbias = val(parm$) if variable$ = "mode" then mode% = val(parm$) if variable$ = "mode2slowdown" then mode2slowdown = val(parm$) if variable$ = "centerthreshold" then centerthreshold% = val(parm$) if variable$ = "zsize" then zsize% = val(parm$) if variable$ = "averagingfield" then averagingfield = val(parm$) if variable$ = "mindist" then mindist = val(parm$) if variable$ = "closethreshold" then closethreshold = val(parm$) if variable$ = "closefactor" then closefactor = val(parm$) end if end if wend close #1 return 'load star data from settings file 'format... 'star n mass xpos ypos zpos xvel yvel zvel comments loadstardata: settingsfileexists = 1 inicontainsbaddata = 0 on error goto nosettingsfile open inifile$ for input as #1 on error goto baddataerror massmultiplier = val("1") positionmultiplier = val("1") velocitymultiplier = val("1") while not eof(1) line input #1, a$ a$ = ltrim$(a$) gosub getnextparm tag$ = b$ if tag$ = "massmultiplier" then gosub getnextparm if b$ = "=" then gosub getnextparm massmultiplier = val(b$) end if end if if tag$ = "positionmultiplier" then gosub getnextparm if b$ = "=" then gosub getnextparm positionmultiplier = val(b$) end if end if if tag$ = "velocitymultiplier" then gosub getnextparm if b$ = "=" then gosub getnextparm velocitymultiplier = val(b$) end if end if if tag$ = "star" then gosub getnextparm star% = val(b$) if star% >= 1 and star% <= nstars% then gosub getnextparm mass(star%) = val(b$) * massmultiplier gosub getnextparm oldxpos(star%) = val(b$) * positionmultiplier gosub getnextparm oldypos(star%) = val(b$) * positionmultiplier gosub getnextparm oldzpos(star%) = val(b$) * positionmultiplier gosub getnextparm oldxvel(star%) = val(b$) * velocitymultiplier gosub getnextparm oldyvel(star%) = val(b$) * velocitymultiplier gosub getnextparm oldzvel(star%) = val(b$) * velocitymultiplier end if end if wend close #1 return 'sub used to parse settings lines for star data getnextparm: z = instr(ltrim$(a$+" ")," ") b$ = left$(a$,z-1) a$ = ltrim$(mid$(a$,z+1)) return 'on error target if settings file doesn't exist 'after calling loadsettingsfile or loadstardata subs 'reset the on error target before any other possible error nosettingsfile: 'resume nsf1 nsf1: close settingsfileexists = 0 return 'on error target if settings file contains bad values '(integer% vars have limited range) baddataerror: 'resume bde1 bde1: close inicontainsbaddata = 1 return 'compute new positions and velocities of all stars 'for each star... ' position = oldposition(star) 'position/velocity variables/arrays ' velocity = oldvelocity(star) 'each have separate X/Y/Z dimensions ' for each other star ' distance = absolute distance between star and other star ' if distance > 0 plus a bit then 'ignore collisions ' force = gravity * (mass(other star) / distance^2) ' if distance < closethreshold then 'reduce force when very close ' force = force * (distance^closefactor/closethreshold^closefactor) ' endif ' velocity = velocity + ((force * dim.distance) / distance) * delta ' 'dim.distance is (position(other star)-position) for each dimension ' endif ' next other star ' newposition(star) = position + velocity * delta ' newvelocity(star) = velocity 'next star 'copy new positions/velocities to old positions/velocities ' updatepositions: for i% = 1 to nstars% xpos = oldxpos(i%) ypos = oldypos(i%) zpos = oldzpos(i%) xvel = oldxvel(i%) yvel = oldyvel(i%) zvel = oldzvel(i%) for j% = 1 to nstars% if i% <> j% then tx = oldxpos(j%)-xpos ty = oldypos(j%)-ypos tz = oldzpos(j%)-zpos d2 = tx*tx + ty*ty + tz*tz dist = sqr(d2) if dist > mindist then 'ignore collisions force = gravity*(mass(j%)/d2) if dist < closethreshold then 'diminish force of very close objects force = force *((dist^closefactor)/(closethreshold^closefactor)) end if xvel = xvel + ((force*tx)/dist) * delta yvel = yvel + ((force*ty)/dist) * delta zvel = zvel + ((force*tz)/dist) * delta end if end if next j% newxpos(i%) = xpos + xvel * delta newypos(i%) = ypos + yvel * delta newzpos(i%) = zpos + zvel * delta newxvel(i%) = xvel newyvel(i%) = yvel newzvel(i%) = zvel next i% for i% = 1 to nstars% oldxpos(i%) = newxpos(i%) oldypos(i%) = newypos(i%) oldzpos(i%) = newzpos(i%) oldxvel(i%) = newxvel(i%) oldyvel(i%) = newyvel(i%) oldzvel(i%) = newzvel(i%) next i% return 'plot stars plotstars: navx = 0 navy = 0 for i% = 1 to nstars% x = oldxpos(i%)*vscale y = oldypos(i%)*vscale xp% = x + xofs% - avx% yp% = y + yofs% - avy% lxp% = lastx%(i%) lyp% = lasty%(i%) if xp% <> lxp% or yp% <> lyp% then pset (lxp%,lyp%),0 pset (lxp%+1,lyp%),0 pset (lxp%,lyp%+1),0 pset (lxp%+1,lyp%+1),0 end if if xp% >= xmin% and xp% < xmax% and yp% >= ymin% and yp% < ymax% then m% = int(mass(i%)) if m% < 1 then m% = 1 if m% > 15 then m% = 15 pset (xp%,yp%),m% pset (xp%+1,yp%),m% pset (xp%,yp%+1),m% pset (xp%+1,yp%+1),m% lastx%(i%) = xp% lasty%(i%) = yp% else lastx%(i%) = 0 lasty%(i%) = 0 end if if mode% = 1 then if abs(x-avx%) < xsize%*averagingfield then navx = navx + x if abs(y-avy%) < ysize%*averagingfield then navy = navy + y end if next i% if mode% = 1 then 'keep centered on average position xp% = navx / nstars% yp% = navy / nstars% if abs(avx% - xp%) > centerthreshold% or abs(avy% - yp%) > centerthreshold% then avx% = xp% avy% = yp% end if end if if mode% = 2 then 'this is so wrong! but keeps from shedding stars for i% = 1 to nstars% 'wrap x-x y-y z-z x = oldxpos(i%) 'slow down a bit at interface to avoid runaway effect y = oldypos(i%) z = oldzpos(i%) if x*vscale < txmin% then x = x+xsize%/vscale : oldxpos(i%) = x : oldxvel(i%) = oldxvel(i%)*mode2slowdown if x*vscale >= txmax% then x = x-xsize%/vscale : oldxpos(i%) = x : oldxvel(i%) = oldxvel(i%)*mode2slowdown if y*vscale < tymin% then y = y+ysize%/vscale : oldypos(i%) = y : oldyvel(i%) = oldyvel(i%)*mode2slowdown if y*vscale >= tymax% then y = y-ysize%/vscale : oldypos(i%) = y : oldyvel(i%) = oldyvel(i%)*mode2slowdown if z*vscale < tzmin% then z = z+zsize%/vscale : oldzpos(i%) = z : oldzvel(i%) = oldzvel(i%)*mode2slowdown if z*vscale >= tzmax% then z = z-zsize%/vscale : oldzpos(i%) = z : oldzvel(i%) = oldzvel(i%)*mode2slowdown next i% end if return 'end of stars.bas source code