import java.awt.*;
import java.applet.*;


/*

This version
2005-01-03


ood2 is now pre-multiplied into dirx and diry

*/



public class Galaxy2 extends Applet implements Runnable
{
  Thread runThread = null;
  Image im = null;
  Graphics img = null;

  int imw, imh;

  //---- Galaxy data

  static int gw, gh;

  double[][] m0;  //  Mass double buffer 0
  double[][] m1;  //  Mass double buffer 1
  double[][] dx0, dx1;
  double[][] dy0, dy1;

  double[][] dist_lookup;
  double[][] ood2_lookup;
  double[][] dirx_lookup;
  double[][] diry_lookup;

  int frame = 0;
  int time = 0;

  double[][] potx, poty;

  Color[] greyscale;



  public void init()
  {
    int i, j;

    imw = size().width;
    imh = size().height;
    gw = imw/2;
    gh = imh/2;

    m0 = new double[gh][gw];   //  mass
    dx0 = new double[gh][gw];  //  x speed
    dy0 = new double[gh][gw];  //  y speed
    
    m1 = new double[gh][gw];
    dx1 = new double[gh][gw];
    dy1 = new double[gh][gw];

    //  Initial empty mass and speed buffers

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      m0[j][i] = 0;
      dx0[j][i] = 0;
      dy0[j][i] = 0;

      m1[j][i] = 0;
      dx1[j][i] = 0;
      dy1[j][i] = 0;
    }
    
    //- Gravity precalc

	  int x, y;
	
	  //  calculate the distance from the centre point (SIZE,SIZE) in (SIZE*2,SIZE*2) array
	
	  double dx, dy, d2, d;

	  dist_lookup = new double[gh*2][gw*2];
	  ood2_lookup = new double[gh*2][gw*2];
	  dirx_lookup = new double[gh*2][gw*2];
	  diry_lookup = new double[gh*2][gw*2];

	  for( y=0; y<gh*2; y++ )
	  {
	    for( x=0; x<gw*2; x++ )
	    {
	      if( x != gw && y != gh )
	      {
	        dx = (x-gw);
	        dy = (y-gh);
	        
	        d2 = dx*dx + dy*dy;
	
	        dist_lookup[ y ][ x ] = (d = Math.sqrt( d2 ));  //  distance
	        ood2_lookup[ y ][ x ] = 1.f / d2;   //  one over distance squared, for gravitational force
	        dirx_lookup[ y ][ x ] = dx / d / d2;     //  Unit direction vector
	        diry_lookup[ y ][ x ] = dy / d / d2;
	      }
	      else  //  Special case for centre point
	      {
	        dist_lookup[ y ][ x ] = 0;
	        ood2_lookup[ y ][ x ] = 0;
	        dirx_lookup[ y ][ x ] = 0;
	        diry_lookup[ y ][ x ] = 0;
	      }
	    }
	  }
    
    //- Set up colour palette

    greyscale = new Color[256];
    for(i=0; i<256; i++) greyscale[i] = new Color(i, i, i);
  }



  //---- paint

	public void paint(Graphics g)
	{
    if(img != null)
    {
      //- Draw image

      int mc;

	    double[][] current_m, new_m;  //  Current and new mass
	    double[][] current_dx, new_dx;  //  Current and new x speed
	    double[][] current_dy, new_dy;  //  Current and new y speed
	
	    current_dx = dx0; new_dx = dx1;
	    current_dy = dy0; new_dy = dy1;
	
	    if(frame == 0)
	    {
	      current_m  =  m0; new_m  =  m1;
	      current_dx = dx0; new_dx = dx1;
	      current_dy = dy0; new_dy = dy1;
	    }
	    else
	    {
	      current_m  =  m1; new_m  =  m0;
	      current_dx = dx1; new_dx = dx0;
	      current_dy = dy1; new_dy = dy0;
	    }
	    
	    //- Render
    
      for(int j=0; j<gh; j++) for(int i=0; i<gw; i++)
      {
        mc = (int)(current_m[j][i]*32.0); if(mc > 255) mc = 255; if( mc < 0 ) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i, j, 1, 1);

        mc = (int)(128.0+current_dx[j][i]*256.0); if(mc > 255) mc = 255; if(mc < 0) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i, j+gh, 1, 1);

        mc = (int)(128.0+current_dy[j][i]*256.0); if(mc > 255) mc = 255; if(mc < 0) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i+gw, j+gh, 1, 1);
      }

      g.drawImage(im, 0, 0, this);
    }
    else
    {
      g.drawString("Loading...", 5, 15);
    }
	}





  public void recalc()
  {
    int i, j;

    int ii, jj;
    double newi, newj;
    double mm;
    double px, py;
    double  dx, dy;
    
    //- Decide which buffer is primary and which is secondary

    double[][] current_m, new_m;  //  Current and new mass
    double[][] current_dx, new_dx;  //  Current and new x speed
    double[][] current_dy, new_dy;  //  Current and new y speed

    if(frame == 0)
    {
      current_m  =  m0; new_m  =  m1;
      current_dx = dx0; new_dx = dx1;
      current_dy = dy0; new_dy = dy1;
    }
    else
    {
      current_m  =  m1; new_m  =  m0;
      current_dx = dx1; new_dx = dx0;
      current_dy = dy1; new_dy = dy0;
    }

    //- Add mouse generated matter

    if( mouse_drag )
    {
      int x, y;
      x = mouse_x;
      y = mouse_y;
      for(j=y-2; j<=y+2; j++) for(i=x-2; i<=x+2; i++)
        if(i >= 0 && i < gw && j >= 0 && j < gh)
        {
          current_m[j][i] += 10;
          current_dx[j][i] = mouse_dx * 0.0245;// + (j-y)*0.0121;
          current_dy[j][i] = mouse_dy * 0.0245;// + (x-i)*0.0121;
        }
    }
    
    //- Calculate new speeds
    
    int x, y;

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
	    px=0;
	    py=0;
	    
	    //  Calculate force for (i,j) by calculating the influence of every pixel (x,y)
	
      for( y=0; y<gh; y++ )
      {
        for( x=0; x<gw; x++ )
        {
          px += dirx_lookup[gh+y-j][gw+x-i] * current_m[y][x];
          py += diry_lookup[gh+y-j][gw+x-i] * current_m[y][x];
        }
      }

      px *= 0.01;
      py *= 0.01;
      
      //  Update V - acceleration of pixel is force (px,py) / mass

      if( current_m[j][i] > 0 )
      {
        current_dx[j][i] += px / current_m[j][i];
        current_dy[j][i] += py / current_m[j][i];
      }
    }

    //- Clear destination arrays

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      new_m[j][i] = 0;
      new_dx[j][i] = 0;
      new_dy[j][i] = 0;
    }

    //- Move matter and velocities by sub-pixel correct forward projection

    double m00, m01, m10, m11;

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      mm = current_m [j][i];   //  Mass of this pixel last time
      if( mm == 0 ) continue;  //  No point doing anything if there is no stuff

      //  New position

      newi = (double)(i) + current_dx[j][i];
      newj = (double)(j) + current_dy[j][i];

      //  Simply discard if it falls out of the array
      
      if( newi < 0    ) continue;
      if( newj < 0    ) continue;
      if( newi > gw-1 ) continue;
      if( newj > gh-1 ) continue;

      //  Integer positions with (un-necessary) wrap-around

      ii = (int)newi;
      jj = (int)newj;
      if( ii < 0 ) ii = gw - ((-ii) % gw); else ii %= gw;
      if( jj < 0 ) jj = gh - ((-jj) % gh); else jj %= gh;
      
      px = newi - (int)newi;  //  Fractional parts of position
      py = newj - (int)newj;
      if( px < 0 ) px += 1;
      if( py < 0 ) py += 1;

      //  Move mass
            
      new_m [ (jj  )%gh ][ (ii  )%gw ] += m11 = mm * (1-px) * (1-py);
      new_m [ (jj  )%gh ][ (ii+1)%gw ] += m01 = mm * (  px) * (1-py);
      new_m [ (jj+1)%gh ][ (ii  )%gw ] += m10 = mm * (1-px) * (  py);
      new_m [ (jj+1)%gh ][ (ii+1)%gw ] += m00 = mm * (  px) * (  py);
      
      //  Move speed

      dx = current_dx[j][i];
      dy = current_dy[j][i];

      new_dx[ (jj  )%gh ][ (ii  )%gw ] += dx * m11;  //  Speed is weighted by the portion of the
      new_dx[ (jj  )%gh ][ (ii+1)%gw ] += dx * m01;  //  mass it is bringing into each square
      new_dx[ (jj+1)%gh ][ (ii  )%gw ] += dx * m10;
      new_dx[ (jj+1)%gh ][ (ii+1)%gw ] += dx * m00;

      new_dy[ (jj  )%gh ][ (ii  )%gw ] += dy * m11;
      new_dy[ (jj  )%gh ][ (ii+1)%gw ] += dy * m01;
      new_dy[ (jj+1)%gh ][ (ii  )%gw ] += dy * m10;
      new_dy[ (jj+1)%gh ][ (ii+1)%gw ] += dy * m00;
    }
    
    //  Scale down the velocities (the new velocity is a weighted average of the velocities passed in)
    
    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      if( new_m[j][i] > 0 )
      {
        new_dx[j][i] /= new_m[j][i];
        new_dy[j][i] /= new_m[j][i];
      }
    }    

    //- Move on

    frame ^= 1;
    time ++;
  }



  //---- GRAVITY CALCULATIONS
  
  //---- MOUSE STUFF

  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  int mouse_dx = 0, mouse_dy = 0;

  boolean mouse_drag = false;

  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;
    
    //.. Mouse button pressed

    return true;
  }
  
  public boolean mouseDrag(Event e, int x, int y)
  {
    mouse_dx = x - mouse_x;
    mouse_dy = y - mouse_y;

    mouse_x = x;
    mouse_y = y;

    //.. Mouse is dragging

    return true;
  }

  public boolean mouseUp(Event evt, int x, int y)
  {
    mouse_drag = false;

    if(mouse_oldx == mouse_x && mouse_oldy == mouse_y)
    {
      //.. The mouse has been released on the same spot (might have moved while dragging tho)
    }
    else
    {
      //.. Mouse released in new position
    }

    return true;
  }
  

  
  //---- THREAD & APPLET GUBBINS
  
  //---- start
  
  public void start()
  {
    if(runThread == null)
    {
      runThread = new Thread(this);
      runThread.start();
    }
  }

  //---- stop
  
  public void stop()
  {
    if(runThread != null)
    {
      runThread.stop();
      runThread = null;
    }
  }

  //---- run
  
  public void run()
  {
    im = createImage(imw, imh);
    img = im.getGraphics();

    while(true)
    {
      recalc();
      repaint();
      try { runThread.sleep(20); } catch (InterruptedException e) { return; }
    }

  }

  //---- update
 
  public void update(Graphics g)
  {
    paint(g);
  }

}	






