/**************************************************************************
   galaxy   stellar simulation program

   Free software licensed under GNU General Public License v.2
   source URL:  kornelix.squarespace.com
***************************************************************************/

#include "zfuncs.h"

#define gtitle "galaxy v.1.8"                                              //  version 

#define maxthreads 8                                                       //  max. accel. compute threads  v.1.8
#define minRflat 5                                                         //  min. value for Rflat
#define interval 0.02                                                      //  data update interval, secs.
#define PI 3.14159

int initfunc(void * data);                                                 //  GTK initial function
void gdk_initialize();                                                     //  GDK initializations
double grandom();                                                          //  random numbers, special distribution
void menufunc(GtkWidget *item, const char *menu);                          //  menu/toolbar event function
int  winpaint();                                                           //  window paint, expose event
int  winstars(void *);                                                     //  window paint, update stars

void * init_galaxy();                                                      //  initialize all stars
void * run_galaxy0(void *);                                                //  compute motion for all stars
void * run_galaxy1(void *);                                                //  compute acceleration from stars
void * run_galaxy2(void *);                                                //  compute acceleration from black hole
void * paint_cps(void *);                                                  //  paint calculation rates in corner
void * help_func(void *);                                                  //  display help file

GtkWidget      *mWin, *mVbox, *gdkWin;                                     //  main and drawing windows
GdkGC          *gdkgc = 0;                                                 //  graphics context
GdkColor       black, white;
GdkColor       grey[20];                                                   //  shades of grey, black to white
GdkColormap    *colormap = 0;
GdkPixmap      *starImage3[100];                                           //  3x3 star images for 10 steps in x, y
GdkPixmap      *blackStar3;                                                //  3x3 black star image
GdkPixmap      *starImage2[100];                                           //  2x2 star images for 10 steps in x, y
GdkPixmap      *blackStar2;                                                //  2x2 black star image
PangoLayout    *cpslayout = 0;

struct t_star {                                                            //  star structure
   int         ppx, ppy;                                                   //  last paint position (pixels)
   int         fpaint;                                                     //  flag, paint requested
   double      px, py;                                                     //  current position
   double      vx, vy;                                                     //  velocity vector
   double      ax1, ay1;                                                   //  acceleration vector, other stars
   double      ax2, ay2;                                                   //  acceleration vector, black hole
};
t_star   *star = 0;                                                        //  star array

void star_init(t_star &);                                                  //  initialize star
void star_move(t_star &);                                                  //  update star velocity and position
void star_accell1(t_star &);                                               //  update star acceleration from stars
void star_accell2(t_star &);                                               //  update star acceleration from black hole
void star_paint(t_star &, int force);                                      //  paint star
void star_paint1(t_star &, int force);                                     //  paint 1-pixel star
void star_paint2(t_star &);                                                //  paint 2-pixel star
void star_paint3(t_star &);                                                //  paint 3-pixel star

double   Nthreads = 1;                                                     //  default parameters
double   nstars = 20;
double   pixels = 3;
double   gravity = 2000;
double   Rflat = 5;
double   BHmass = 0;
double   BHcapture = 10;
double   Ivelocity = 0.95;

double   winR = 1000;                                                      //  model window, winR x winR square
int      winX = 800, winY = 600;                                           //  drawing window, pixels
double   winXR = winX / winR, winYR = winY / winR;                         //  scaling ratios, winR to winX, winY

int      trun = 0;                                                         //  thread running count
int      tpause = 0;                                                       //  thread pause flag
int      tkill = 0;                                                        //  thread kill flag
double   cps = 0;                                                          //  calculation cycle time
int      fpaintall = 0;                                                    //  flag, paint all stars


//  main program

int main(int argc, char *argv[])
{
   GtkWidget   *tbar;
   
   gtk_init(&argc,&argv);                                                  //  GTK command line options
   if (! g_thread_supported())                                             //  suddenly required for new gtk  
         g_thread_init(null);                                              //  initz. GTK for threads
   gdk_threads_init();
   zlockInit();

   initz_appfiles("galaxy","parameters*",null);                            //  get app directories, parameter files

   mWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);                             //  main window
   gtk_window_set_title(GTK_WINDOW(mWin),gtitle);
   gtk_window_set_position(GTK_WINDOW(mWin),GTK_WIN_POS_CENTER);
   gtk_window_set_default_size(GTK_WINDOW(mWin),winX,winY);
   
   mVbox = gtk_vbox_new(0,0);                                              //  vertical packing box
   gtk_container_add(GTK_CONTAINER(mWin),mVbox);                           //  add to main window

   tbar = create_toolbar(mVbox);                                           //  create toolbar and buttons

   add_toolbar_button(tbar,"God","create new galaxy","god.png",menufunc);
   add_toolbar_button(tbar,"init","initialize galaxy","gtk-goto-first",menufunc);
   add_toolbar_button(tbar,"run","run galaxy","run.png",menufunc);
   add_toolbar_button(tbar,"pause","pause/resume galaxy","gtk-media-pause",menufunc);
   add_toolbar_button(tbar,"quit","quit galaxy","gtk-quit",menufunc);
   add_toolbar_button(tbar,"help","view help document","gtk-help",menufunc);
   
   gdkWin = gtk_drawing_area_new();                                        //  create drawing window
   gtk_box_pack_start(GTK_BOX(mVbox),gdkWin,1,1,0);                        //  add to main window mVbox

   G_SIGNAL(mWin,"destroy",gtk_main_quit,0)                                //  connect window events
   G_SIGNAL(gdkWin,"expose-event",winpaint,0)

   gtk_init_add((GtkFunction) initfunc,0);                                 //  setup initial call from gtk_main()

   gtk_widget_show_all(mWin);                                              //  show all widgets

   gdk_initialize();                                                       //  my GDK initializations

   gdk_threads_enter();
   gtk_main();                                                             //  process window events
   gdk_threads_leave();

   return 0;
}


//  initial function called from gtk_main() at startup

int initfunc(void * data)
{
   initParmlist(20);                                                       //  setup default parameters
   setParm("thread count",Nthreads);
   setParm("star count",nstars);
   setParm("pixels",pixels);
   setParm("gravity",gravity);
   setParm("R flatband",Rflat);
   setParm("black hole mass",BHmass);
   setParm("black hole capture",BHcapture);
   setParm("initial velocity",Ivelocity);
   
   initz_userParms();                                                      //  initz. or load parms file
   
   g_timeout_add(20,winstars,0);                                           //  start periodic paint function   v.1.8
   
   return 0;
}


//  process toolbar button selection event

void menufunc(GtkWidget *, const char *menu)
{
   int            ii, nt = int(Nthreads);
   static int     vt[maxthreads];
   pthread_t      tid;

   if (strEqu(menu,"God")) {
      ii = editParms(0,0);
      if (ii) menufunc(0,"init");                                          //  initialize after parm edit  v.1.7
   }

   if (strEqu(menu,"pause")) tpause = 1 - tpause;
   if (strEqu(menu,"help")) pthread_create(&tid,0,help_func,0);
   if (strEqu(menu,"quit")) gtk_main_quit();

   if (strEqu(menu,"init")) {                                              //  initialize all stars
      tkill = 1;
      while (trun) zmainloop();
      init_galaxy();
   }

   if (strEqu(menu,"run")) {
      if (! star) return;                                                  //  no initialization yet
      tpause = tkill = 0;                                                  //  run also is unpause  v.1.6
      if (trun) return;                                                    //  already running

      pthread_create(&tid,0,run_galaxy0,0);                                //  thread, star motion and painting
      pthread_create(&tid,0,run_galaxy2,0);                                //  thread, star - black hole acceleration

      for (ii = 0; ii < nt; ii++) {
         vt[ii] = ii;                                                      //  N threads,
         pthread_create(&tid,0,run_galaxy1,(void *) &vt[ii]);              //    star - star acceleration
      }

      trun = 2 + nt;                                                       //  running thread count
   }
   
   return;
}


//  do GDK initializations

void gdk_initialize()
{
   int         ii, px, py, pxy, bitx, bity;
   unsigned    intensity, maxint = 0xffff;
   double      dx, dy, dxy;

   gdkgc = gdk_gc_new(gdkWin->window);                                     //  initz. graphics context

   black.red = black.green = black.blue = 0;                               //  set up RGB colors
   white.red = white.green = white.blue = maxint;
   colormap = gtk_widget_get_colormap(gdkWin);
   gdk_rgb_find_color(colormap,&black);
   gdk_rgb_find_color(colormap,&white);

   gdk_gc_set_background(gdkgc,&black);                                    //  background always black

   for (ii = 0; ii < 20; ii++)                                             //  set up 20 shades of grey
   {
      intensity = int(ii / 19.0 * maxint);
      grey[ii].red = grey[ii].green = grey[ii].blue = intensity;
      gdk_rgb_find_color(colormap,&grey[ii]);
   }
   
//  Generate 2x2 pixel image per star position within one square pixel.
//  Star position is within 1 virtual pixel centered on the 2x2 square.
//  Position steps are 0.1 pixel, so there are 100 images to create.

   for (px = 0; px < 10; px++)
   for (py = 0; py < 10; py++)
   {
      pxy = 10 * px + py;
      starImage2[pxy] = gdk_pixmap_new(gdkWin->window,2,2,-1);
      
      for (bitx = 0; bitx < 2; bitx++)
      for (bity = 0; bity < 2; bity++)
      {
         dx = (px / 10.0) - bitx;                                          //  x distance from bitxy center, 0...0.9
         dy = (py / 10.0) - bity;                                          //  y distance from bitxy center, 0...0.9
         dxy = sqrtf(dx * dx + dy * dy);                                   //  pxy distance from bitxy center, 0...1.273
         ii = int(19.0 * (1.0 - dxy / 1.5));
         gdk_gc_set_foreground(gdkgc,&grey[ii]);
         gdk_draw_point(starImage2[pxy],gdkgc,bitx,bity);
      }
   }
   
   gdk_gc_set_foreground(gdkgc,&black);                                    //  generate 2x2 black star
   blackStar2 = gdk_pixmap_new(gdkWin->window,2,2,-1);

   for (bitx = 0; bitx < 2; bitx++)
   for (bity = 0; bity < 2; bity++)
      gdk_draw_point(blackStar2,gdkgc,bitx,bity);
   
//  Generate 3x3 pixel image per star position within one square pixel.
//  Star position is within the center pixel of the 3x3 square.
//  Position steps are 0.1 pixel, so there are 100 images to create.

   for (px = 0; px < 10; px++)
   for (py = 0; py < 10; py++)
   {
      pxy = 10 * px + py;
      starImage3[pxy] = gdk_pixmap_new(gdkWin->window,3,3,-1);
      
      for (bitx = 0; bitx < 3; bitx++)
      for (bity = 0; bity < 3; bity++)
      {
         dx = (bitx - 0.5) - (px / 10.0);                                  //  -1.4 ... +1.5
         dy = (bity - 0.5) - (py / 10.0);
         dxy = sqrtf(dx * dx + dy * dy);                                   //  0 ... 2.12
         ii = int(19.0 * (1.0 - dxy / 2.12));
         gdk_gc_set_foreground(gdkgc,&grey[ii]);
         gdk_draw_point(starImage3[pxy],gdkgc,bitx,bity);
      }
   }
   
   gdk_gc_set_foreground(gdkgc,&black);                                    //  generate 3x3 black star
   blackStar3 = gdk_pixmap_new(gdkWin->window,3,3,-1);

   for (bitx = 0; bitx < 3; bitx++)
   for (bity = 0; bity < 3; bity++)
      gdk_draw_point(blackStar3,gdkgc,bitx,bity);

   return;
}


//  window created or exposed, set up to repaint all stars

int winpaint()
{
   winX = gdkWin->allocation.width;                                        //  get (new) window size
   winY = gdkWin->allocation.height;                                       //  X, Y = rectangle dimensions
   
   winXR = winX / winR;                                                    //  update scaling ratios
   winYR = winY / winR;
   
   fpaintall++;                                                            //  paint all stars

   return 0;
}


//  periodic function (20 millisecs)
//  paint all stars needing an update

int winstars(void *)                                                       //  v.1.8
{
   static int     cps_counter = 0;

   if (fpaintall) {
      gdk_window_set_background(gdkWin->window,&black);                    //  initz. drawing window
      gdk_window_clear(gdkWin->window);
   }
   
   if (star) {
      for (int ii = 0; ii < nstars; ii++)
         if (star[ii].fpaint || fpaintall)
            star_paint(star[ii],fpaintall);
   }   

   fpaintall = 0;
   
   if (++cps_counter > 50) {                                               //  1 second interval
      paint_cps(0);
      cps_counter = 0;
   }

   return 1;
}


//  paint calculation rate (cycles per second) in lower right corner

void * paint_cps(void *)
{
   static PangoFontDescription   *pfont = null;
   static PangoLayout            *playout = null;
   char                          cpstext[30];
   
   if (! pfont) {
      pfont = pango_font_description_from_string("monospace 8");
      playout = gtk_widget_create_pango_layout(gdkWin,0);
      pango_layout_set_font_description(playout,pfont);
   }

   gdk_gc_set_foreground(gdkgc,&white);
   sprintf(cpstext,"cps: %.2f",cps);
   pango_layout_set_text(playout,cpstext,-1);
   gdk_window_clear_area(gdkWin->window,winX-90,winY-22,90,22);
   gdk_draw_layout(gdkWin->window,gdkgc,winX-90,winY-22,playout);

   return 0;
}


//  initialize all stars in galaxy

void * init_galaxy()
{
   int         ii;
   double      px, py, vx, vy, ax, ay;
   double      R, theta, F, V;
   
   nstars = 0;
   if (star) zfree(star);                                                  //  delete prior star array
   star = 0;

   Nthreads = getParm("thread count");                                     //  get parameters
   nstars = getParm("star count");
   pixels = getParm("pixels");
   gravity = getParm("gravity");
   Rflat = getParm("R flatband");
   BHmass = getParm("black hole mass");
   BHcapture = getParm("black hole capture");
   Ivelocity = getParm("initial velocity");

   if (nstars < 1) nstars = 1;                                             //  enforce reasonable limits
   if (nstars > 100000) nstars = 100000;   
   if (Nthreads > maxthreads) Nthreads = maxthreads;
   if (Rflat < minRflat) Rflat = minRflat;
   if (BHcapture > 100) BHcapture = 100;
   if (Ivelocity > 1.10) Ivelocity = 1.10;
   if (Ivelocity < 0.80) Ivelocity = 0.80;

   int size = int(nstars) * sizeof(t_star);
   star = (t_star *) zmalloc(size);                                        //  allocate new star array
   memset(star,0,size);

   cps = 0;                                                                //  reset calc. rate

   for (ii = 0; ii < nstars; ii++)                                         //  set all star initial positions
   {
      R = winR * grandom();                                                //  random R, theta for star position
      theta = 2 * PI * drand48();
      px = R * sin(theta) + winR / 2;
      py = R * cos(theta) + winR / 2;
      star[ii].px = px;
      star[ii].py = py;
      star[ii].fpaint = 1;                                                 //  set paint required   v.1.8
   }

   for (ii = 0; ii < nstars; ii++)                                         //  set initial velocity for all stars
   {
      star_accell1(star[ii]);                                              //  star - star acceleration
      star_accell2(star[ii]);                                              //  star - black hole acceleration

      ax = star[ii].ax1 + star[ii].ax2;                                    //  add accelerations
      ay = star[ii].ay1 + star[ii].ay2;

      px = star[ii].px - winR / 2;                                         //  compute R from center
      py = star[ii].py - winR / 2;
      R = sqrtf(px*px + py*py);

      F = sqrt(ax*ax + ay*ay);                                             //  set angular velocity to match
      V = sqrt(R * F);                                                     //    acceleration: stars will not
      vx = V * -py / R;                                                    //      initially expand or collapse 
      vy = V * px / R;

      star[ii].vx = Ivelocity * vx;                                        //  user velocity tweak
      star[ii].vy = Ivelocity * vy;
   }

   fpaintall++;                                                            //  v.1.8
   return 0;
}


//  initialize new star created when a star leaves the galaxy

void star_init(t_star &star)
{
   double   px, py, vx, vy, ax, ay;
   double   R, theta, F, V;

   R = winR * grandom();                                                   //  random R, theta for star position
   theta = 2 * PI * drand48();
   px = R * sin(theta) + winR / 2;
   py = R * cos(theta) + winR / 2;
   star.px = px;
   star.py = py;
   star.fpaint = 1;                                                        //  v.1.8

   star_accell1(star);                                                     //  star - star acceleration
   star_accell2(star);                                                     //  star - black hole acceleration

   ax = star.ax1 + star.ax2;                                               //  add accelerations
   ay = star.ay1 + star.ay2;

   px = star.px - winR / 2;                                                //  compute R from center
   py = star.py - winR / 2;
   R = sqrtf(px*px + py*py);
  
   F = sqrt(ax*ax + ay*ay);
   V = sqrt(R * F);
   vx = V * -py / R;
   vy = V * px / R;

   star.vx = Ivelocity * vx;                                               //  user velocity tweak
   star.vy = Ivelocity * vy;

   return;
}


//  thread function
//  compute motion for all stars

void * run_galaxy0(void *)
{
   if (! star) return 0;
   
   while (true)
   {
      while (tpause && !tkill) zsleep(0.1);
      
      for (int ii = 0; ii < nstars; ii++) 
      {
         star_move(star[ii]);
         star[ii].fpaint = 1;                                              //  v.1.8
         if (tkill) break;
      }
      
      if (tkill) break;
      zsleep(interval);
   }

   --trun;
   return 0;
}


//  (multiple) thread function
//  compute acceleration for all stars due to other stars

void * run_galaxy1(void * vth)
{
   timeval           time0;
   double            elaps, cps2;
   int               nth = int(Nthreads);                                  //  thread count
   int               ith = *((int *) vth);                                 //  thread number: 0, 1, 2 ...

   if (! star) return 0;

   while (true)
   {
      while (tpause && !tkill) zsleep(0.1);
      if (tkill) break;

      if (ith == 0) start_timer(&time0);                                   //  start timer if thread 0

      zsleep(interval);                                                    //  wait nominal interval
      
      for (int ii = ith; ii < nstars; ii += nth)                           //  compute acceleration for all stars
      {                                                                    //   (every nth star for this thread)
         star_accell1(star[ii]);
         if (tkill) break;
      }
      
      if (ith == 0) {                                                      //  calculate CPS only if thread 0
         elaps = get_timer(&time0);                                        //  elapsed time
         cps2 = 1.0 / elaps;                                               //  calculation rate, cycles/sec
         if (cps == 0) cps = cps2;
         else cps = 0.75 * cps + 0.25 * cps2;                              //  running weighted average
      }
   }

   --trun;
   return 0;
}


//  thread function
//  compute acceleration for all stars due to black hole

void * run_galaxy2(void *)
{
   if (! star) return 0;
   
   while (true)
   {
      while (tpause && !tkill) zsleep(0.1);
      if (tkill) break;

      zsleep(interval);                                                    //  wait nominal interval
      
      for (int ii = 0; ii < nstars; ii++)                                  //  compute acceleration for all stars
      {
         star_accell2(star[ii]);
         if (tkill) break;
      }
   }

   --trun;
   return 0;
}


//  update star velocity and position

void star_move(t_star &star)
{
   star.vx += (star.ax1 + star.ax2) * interval;                            //  dV = A * dT
   star.vy += (star.ay1 + star.ay2) * interval;

   star.px += star.vx * interval;                                          //  dP = V * dT
   star.py += star.vy * interval;

   if (star.px < 0 || star.px > winR || star.py < 0 || star.py > winR)     //  if off screen, initialize
         star_init(star);
   
   star.fpaint = 1;                                                        //  v.1.8

   return;
}


//  update star acceleration due to attraction of other stars

void star_accell1(t_star &star0)
{
   double      Rx, Ry, R, Rnv, A, ax, ay;
   
   ax = ay = 0;                                                            //  A = 0

   for (int ii = 0; ii < nstars; ii++)                                     //  inspect every star
   {
      if (&star[ii] == &star0) continue;                                   //  skip self

      Rx = star[ii].px - star0.px;                                         //  compute distance R beween stars
      Ry = star[ii].py - star0.py;
      R = sqrtf(Rx * Rx + Ry * Ry);
      Rnv = 1.0 / (R + Rflat);                                             //  weaken pull of nearby stars
      A = gravity * Rnv * Rnv;                                             //  accel. = gravity / R**2
      ax += A * Rx * Rnv;                                                  //  add acceleration contribution
      ay += A * Ry * Rnv;                                                  //    from this star
   }

   star0.ax1 = ax;                                                         //  total star acceleration
   star0.ay1 = ay;
   return;
}


//  update star acceleration due to attraction of black hole

void star_accell2(t_star &star0)
{
   double   Rx, Ry, R, Rnv, A;
   
   if (BHmass == 0) return;
   
   Rx = winR / 2 - star0.px;                                               //  compute distance R to
   Ry = winR / 2 - star0.py;                                               //    black hole at center
   R = sqrtf(Rx * Rx + Ry * Ry);
   Rnv = 1.0 / (R + 2 * Rflat);                                            //  limit pull when very near
   A = BHmass * gravity * Rnv * Rnv;                                       //  accel. = mass * gravity / R**2
   star0.ax2 = A * Rx / R;
   star0.ay2 = A * Ry / R;
   
   if (R < BHcapture) {                                                    //  BH capture distance
      star_init(star0);                                                    //  star is eaten by black hole
      BHmass++;                                                            //    and black hole gains mass
   }

   return;
}


//  paint star on screen
//  force: if 1-pixel stars, force repaint even if position is unchanged

void star_paint(t_star &star, int force)
{
   if (pixels == 1) star_paint1(star,force);
   if (pixels == 2) star_paint2(star);
   if (pixels >= 3) star_paint3(star);
   star.fpaint = 0;                                                        //  v.1.8
   return;
}


//  paint star when movement from last paint is significant
//  paint 1-pixel star (small space but jerky movement)

void star_paint1(t_star &star, int force)
{
   double   px, py;
   int      ipx, ipy, jpx, jpy;

   px = star.px * winXR;                                                   //  current position, scaled to window
   py = star.py * winYR;
   
   ipx = star.ppx;                                                         //  last paint position
   ipy = star.ppy;

   jpx = int(px + 0.5);                                                    //  current paint position
   jpy = int(py + 0.5);

   if (!force && ipx == jpx && ipy == jpy) return;                         //  no change in paint position
   
   gdk_gc_set_foreground(gdkgc,&black);                                    //  erase at old paint position
   gdk_draw_point(gdkWin->window,gdkgc,ipx,ipy);

   gdk_gc_set_foreground(gdkgc,&white);                                    //  draw at new paint position
   gdk_draw_point(gdkWin->window,gdkgc,jpx,jpy);

   star.ppx = jpx;                                                         //  set position of last paint
   star.ppy = jpy;

   return;
}


//  erase star at prior position and paint at new position
//  paint 2x2 pixel star with anti-aliasing for smooth movement

void star_paint2(t_star &star)
{
   double   px, py;
   int      ipx, ipy, jpx, jpy, kpx, kpy, kpxy;
   
   px = star.px * winXR;                                                   //  current position, scaled to window
   py = star.py * winYR;

   ipx = star.ppx;                                                         //  last paint position
   ipy = star.ppy;                                                         //  (origin of 3x3 pixmap)

   jpx = int(px);                                                          //  current paint position
   jpy = int(py);

   kpx = int(10.0 * (px - jpx));                                           //  star position within pixel
   kpy = int(10.0 * (py - jpy));
   kpxy = 10 * kpx + kpy;                                                  //  corresp. 2x2 pixmap

   gdk_draw_drawable(gdkWin->window,gdkgc,                                 //  paint black at old position
                     blackStar2,0,0,ipx,ipy,-1,-1);
   gdk_draw_drawable(gdkWin->window,gdkgc,                                 //  draw pixmap at curr. position
                     starImage2[kpxy],0,0,jpx,jpy,-1,-1);

   star.ppx = jpx;                                                         //  set position of last paint
   star.ppy = jpy;

   return;
}


//  erase star at prior position and paint at new position
//  paint 3x3 pixel star with anti-aliasing for smooth movement

void star_paint3(t_star &star)
{
   double   px, py;
   int      ipx, ipy, jpx, jpy, kpx, kpy, kpxy;
   
   px = star.px * winXR;                                                   //  current position, scaled to window
   py = star.py * winYR;

   ipx = star.ppx;                                                         //  last paint position
   ipy = star.ppy;                                                         //  (origin of 3x3 pixmap)

   jpx = int(px);                                                          //  current paint position
   jpy = int(py);

   kpx = int(10.0 * (px - jpx));                                           //  star position within pixel
   kpy = int(10.0 * (py - jpy));
   kpxy = 10 * kpx + kpy;                                                  //  corresp. 3x3 pixmap

   gdk_draw_drawable(gdkWin->window,gdkgc,                                 //  paint black at old position
                     blackStar3,0,0,ipx,ipy,-1,-1);
   gdk_draw_drawable(gdkWin->window,gdkgc,                                 //  draw pixmap at curr. position
                     starImage3[kpxy],0,0,jpx,jpy,-1,-1);

   star.ppx = jpx;                                                         //  set position of last paint
   star.ppy = jpy;

   return;
}


//  generate random number 0.0 - 1.0 in a distribution peaking at 0

double   grandom()
{
   double         rsum = 0.0;
   timeval        time;

   gettimeofday(&time,0);                                                  //  set seed for random numbers
   srand48(lrand48() + time.tv_sec);

   rsum = drand48() + drand48();
   rsum = 0.5 * rsum - 0.5;
   if (rsum < 0) rsum = -rsum;
   return  rsum;
}


//  thread function to display help file

void * help_func(void *)
{
   showz_helpfile();                                                       //  launch help file in new process
   return 0;
}


/**************************************************************************

//  star image test function (menu function)
//  draw matrix of 100 3x3 and 2x2 stars

void star_test()
{
   int      ipx, ipy, jpx, jpy, kpxy;
   
   for (ipx = 0; ipx < 10; ipx++)
   for (ipy = 0; ipy < 10; ipy++)
   {
      jpx = 4 + 4 * ipx;
      jpy = 4 + 4 * ipy;
      kpxy = 10 * ipx + ipy;
      gdk_draw_drawable(gdkWin->window,gdkgc,starImage3[kpxy],0,0,jpx,jpy,-1,-1);
   }

   for (ipx = 0; ipx < 10; ipx++)
   for (ipy = 0; ipy < 10; ipy++)
   {
      jpx = 4 + 4 * ipx + 100;
      jpy = 4 + 4 * ipy;
      kpxy = 10 * ipx + ipy;
      gdk_draw_drawable(gdkWin->window,gdkgc,starImage2[kpxy],0,0,jpx,jpy,-1,-1);
   }
}

**************************************************************************/


