

import java.awt.*;
import java.awt.image.*;

import kinetoscope.awt.*;



/**
 *
 *
 * This is a Java port of grav, which is included in 
 * xscreensaver.
 * <p>
 * This is the original copyright notice in the C source:
 * <pre>
 * Copyright (c) 1993 Greg Bowering <greg@smug.student.adelaide.edu.au>
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision history:
 * 10-May-97: jwz@jwz.org: turned into a standalone program.
 * 11-Jul-94: color version
 * 06-Oct-93: by Greg Bowering <greg@smug.student.adelaide.edu.au>
 * </pre>
 *
 *
 */
public class Grav extends DoubleBufferedFrame implements Runnable
{
	public static long kRefreshInterval = 0;

	private Thread refreshThread;

	int width, height;

	ModeInfo myInfo;

	java.util.Random rand = new java.util.Random();


	public final static double GRAV = -0.02;
	public final static double DIST = 16.0;
	public final static double COLLIDE = 0.0001;
	public final static double ALMOST = 15.99;
	public final static double HALF = 0.5;

	public final static double VR = 0.04;
	public final static int DIMENSIONS = 3;
	public final static int X = 0;
	public final static int Y = 1;
	public final static int Z = 2;
	public final static double DAMP = 0.999999;
	public final static double MaxA = 0.1;


	public static double XR;
	public static double YR;
	public static double ZR;

	
	public static boolean decay;
	public static boolean trail;
	public static int count;

	GravStruct[] gravs;



	public Grav()
	{
		super("COLOR FRAME");
		
		setSize(512, 384);

		initFrame();
		

		XR = HALF*ALMOST;
		YR = HALF*ALMOST;
		ZR = HALF*ALMOST;


		refreshThread = new Thread(this);
		refreshThread.setPriority(Thread.MIN_PRIORITY); 
		refreshThread.start();

		this.show();
	}


	void Planet(int x, int y, Graphics g, int ri, GravStruct gp, Color gc)
	{
		if ((x) >= 0 && (y) >= 0 && (x) <= gp.width && (y) <= gp.height) {
			if (ri < 2) {
				g.setColor(gc);
				g.drawLine(x,y,x,y);
			}
			else {
				g.setColor(gc);
				g.fillArc((x) - ri / 2, (y) - ri / 2, ri, ri, 0, 360);
			}
		}
	}

	double FLOATRAND(double min, double max) 
	{
		return((rand.nextDouble()*(max-min))+min);
	}


	void init_planet(ModeInfo mi, PlanetStruct planet)
	{
		Color gc = (mi).xgwa.getColor();
		GravStruct gp = gravs[0];

		if ((mi).npixels > 2)
			planet.colors = mi.pixels[Math.abs((int)rand.nextInt()%(mi).npixels)];
		else
			planet.colors = (mi).white;
		/* Initialize positions */
		planet.P[X] = FLOATRAND(-XR, XR);
		planet.P[Y] = FLOATRAND(-YR, YR);
		planet.P[Z] = FLOATRAND(-ZR, ZR);

		if (planet.P[Z] > -ALMOST) {
			planet.xi = (int)
				((double) gp.width * (HALF + planet.P[X] / (planet.P[Z] + DIST)));
			planet.yi = (int)
				((double) gp.height * (HALF + planet.P[Y] / (planet.P[Z] + DIST)));
		}
		else {
			planet.xi = planet.yi = -1;
		}
		planet.ri = (int)(((float) (gp.height/5))/(planet.P[Z]+DIST));

		if(trail) {
			planet.x1 = planet.xi;
			planet.x2 = planet.xi;
			planet.x3 = planet.xi;
			planet.x4 = planet.xi;
			planet.y1 = planet.xi;
			planet.y2 = planet.xi;
			planet.y3 = planet.xi;
			planet.y4 = planet.xi;
		}

		/* Initialize velocities */
		planet.V[X] = FLOATRAND(-VR, VR);
		planet.V[Y] = FLOATRAND(-VR, VR);
		planet.V[Z] = FLOATRAND(-VR, VR);
		
		/* Draw planets */
		Planet(planet.xi, planet.yi, mi.xgwa, planet.ri, gp, planet.colors);
	}

	
	void draw_planet(ModeInfo mi, PlanetStruct planet)
	{
		Color gc = (mi).xgwa.getColor();
		GravStruct gp = gravs[0];
		double      D;		/* A distance variable to work with */
		int cmpt;

		D = planet.P[X] * planet.P[X] + planet.P[Y] * planet.P[Y] + planet.P[Z] * planet.P[Z];
		if (D < COLLIDE)
			D = COLLIDE;
		D = Math.sqrt(D);
		D = D * D * D;

		for (cmpt = X; cmpt < DIMENSIONS; cmpt++) {
			planet.A[cmpt] = planet.P[cmpt] * GRAV / D;
			if (decay) {
				if (planet.A[cmpt] > MaxA)
					planet.A[cmpt] = MaxA;
				else if (planet.A[cmpt] < -MaxA)
					planet.A[cmpt] = -MaxA;
				planet.V[cmpt] = planet.V[cmpt] + planet.A[cmpt];
				planet.V[cmpt] *= DAMP;
			} 
			else {
				/* update velocity */
				planet.V[cmpt] = planet.V[cmpt] + planet.A[cmpt];
			}
			/* update position */
			planet.P[cmpt] = planet.P[cmpt] + planet.V[cmpt];
		}

		gp.x = planet.xi;
		gp.y = planet.yi;

		if (planet.P[Z] > -ALMOST) {
			planet.xi = (int)
				((double) gp.width * (HALF + planet.P[X] / (planet.P[Z] + DIST)));
			planet.yi = (int)
				((double) gp.height * (HALF + planet.P[Y] / (planet.P[Z] + DIST)));
		} else
			planet.xi = planet.yi = -1;
		
		/* Mask */
		//		Planet(gp.x, gp.y, mi.xgwa, planet.ri, gp, mi.black);
		if (trail) {
			int size = planet.ri;

			mi.xgwa.setColor(planet.colors);
			
			mi.xgwa.fillArc(gp.x-size/2,gp.y-size/2,(size*2)/3,(size*2)/3,0,360);
			mi.xgwa.fillArc(planet.x1-size/3,planet.y1-size/3,size*2/5,size*2/5,0,360);
			mi.xgwa.fillArc(planet.x2-size/4,planet.y2-size/4,size/3,size/3,0,360);
			mi.xgwa.fillArc(planet.x3-size/6,planet.y3-size/6,(size/4)+1,(size/4)+1,0,360);
			mi.xgwa.fillArc(planet.x4-size/8,planet.y4-size/8,(size/6)+1,(size/6)+1,0,360);


			planet.x4 = planet.x3;
			planet.x3 = planet.x2;
			planet.x2 = planet.x1;
			planet.x1 = gp.x;
			planet.y4 = planet.y3;
			planet.y3 = planet.y2;
			planet.y2 = planet.y1;
			planet.y1 = gp.y;

		}
		/* Move */
		gp.x = planet.xi;
		gp.y = planet.yi;
		planet.ri = (int)(((float) (gp.height/5))/(planet.P[Z]+DIST));
		
		/* Redraw */
		Planet(gp.x, gp.y, mi.xgwa, planet.ri, gp, planet.colors);
	}
	
	
	void init_grav(ModeInfo mi)
	{	
		Color          gc = (mi).xgwa.getColor();
		GravStruct gp;
		int ball;
		
		if (gravs == null) {
			gravs = new GravStruct[1];
			gravs[0] = new GravStruct();
		}

		gp = gravs[0];
			
		gp.width = (mi).width;
		gp.height = (mi).height;
			
		gp.sr = (int)(gp.height/(2*DIST));
			
		gp.nplanets = (mi).batchcount;
		if (gp.nplanets < 0) {
			if (gp.planets != null) {
				gp.planets=null;
			}
			gp.nplanets = Math.abs(rand.nextInt()%(-gp.nplanets)) + 1;	/* Add 1 so its not too boring */
		}
		if (gp.planets==null) {
			gp.planets = new PlanetStruct[gp.nplanets];
			for(int i=0;i<gp.nplanets;i++) {
				gp.planets[i] = new PlanetStruct();
			}
		}

		//		XClearWindow(display, MI_WINDOW(mi));
		// JON: FIX THIS!
			
		if ((mi).npixels > 2) {
			gp.starcolor = mi.pixels[Math.abs(rand.nextInt()%((mi).npixels))];
		}
		else {
			gp.starcolor = (mi).white;
		}
		for (ball = 0; ball < gp.nplanets; ball++) {
			init_planet(mi, gp.planets[ball]);
		}

		/* Draw centrepoint */
		mi.xgwa.setColor(gp.starcolor);
		mi.xgwa.drawArc(gp.width / 2 - gp.sr / 2, gp.height / 2 - gp.sr / 2, gp.sr, gp.sr, 0, 23040);
	}


	void draw_grav(ModeInfo mi)
	{
		Color          gc = (mi).xgwa.getColor();
		GravStruct gp = gravs[0];
		int ball;

		/* Mask centrepoint */
		mi.xgwa.setColor(mi.black);
		mi.xgwa.drawArc(gp.width / 2 - gp.sr / 2, gp.height / 2 - gp.sr / 2, gp.sr, gp.sr, 0, 23040);

		/* Resize centrepoint */
		switch (Math.abs(rand.nextInt()%4)) {
		case 0:
			if (gp.sr < (int) (gp.height/(2*DIST)))
				gp.sr++;
			break;
		case 1:
			if (gp.sr > 2)
				gp.sr--;
		}

		/* Draw centrepoint */
		mi.xgwa.setColor(gp.starcolor);
		mi.xgwa.drawArc(gp.width / 2 - gp.sr / 2, gp.height / 2 - gp.sr / 2, gp.sr, gp.sr, 0, 23040);

		for (ball = 0; ball < gp.nplanets; ball++) {
			draw_planet(mi, gp.planets[ball]);
		}
	}

	





	class ModeInfo
	{
		int npixels;
		Color[] pixels;
		Color[] colors;
		Color white;
		Color black;
		Graphics xgwa;
		int batchcount;
		int width;
		int height;
		

		ModeInfo()
		{
			npixels = 10;
			pixels = new Color[10];
			pixels[0] = Color.cyan;
			pixels[1] = Color.magenta;
			pixels[2] = Color.yellow;
			pixels[3] = Color.green;
			pixels[4] = Color.red;
			pixels[5] = Color.pink;
			pixels[6] = Color.orange;
			pixels[7] = Color.white;
			pixels[8] = Color.lightGray;
			pixels[9] = Color.gray;
			
			white = Color.white;
			black = Color.black;
			batchcount = Grav.count;
		}
	
	}

	class PlanetStruct
	{
		double[] P = new double[Grav.DIMENSIONS];
		double[] V = new double[Grav.DIMENSIONS];
		double[] A = new double[Grav.DIMENSIONS];
		int xi,yi,ri;
		Color colors;

		int x1,y1;
		int x2,y2;
		int x3,y3;
		int x4,y4;


		PlanetStruct()
		{
		}
	}

	class GravStruct
	{
		int width = 512, height = 384;
		int x,y,sr,nplanets;
		Color starcolor;
		PlanetStruct[] planets;

		GravStruct()
		{
		}
	}






	public void initFrame()
	{
		width  = getSize().width;
		height = getSize().height;

		setBackground(Color.black);
	}

	public void paintContent(Graphics g, int width, int height)
	{
		if(myInfo == null) {
			myInfo = new ModeInfo();

			myInfo.xgwa = g;
			myInfo.width = width;
			myInfo.height = height;

			this.width = width;
			this.height = height;

			init_grav(myInfo);

		}
		else {
			myInfo.xgwa = g;
			myInfo.width = width;
			myInfo.height = height;

			if(this.width==width && this.height==height) {
				draw_grav(myInfo);
			}
			else {
				this.width = width;
				this.height = height;
				init_grav(myInfo);
			}
		}


	}

	public void run()
	{
		while (true)
		{
			repaint();
			
			try
			{
				Thread.sleep(kRefreshInterval);
			}
			catch (InterruptedException e)
			{
				;
			}

		}
	}
	






	public static void main(String[] argv)
	{
		try {
			Grav.decay = false;
			Grav.trail = false;
			Grav.count = 9;
			for(int x=0;x<argv.length;x++) {
				if(argv[x].equals("-decay")) {
					Grav.decay = true;
				}
				else if(argv[x].equals("-trail")) {
					Grav.trail = true;
				}
				else if(argv[x].equals("-count")) {
					Grav.count = Integer.parseInt(argv[++x]);
				}
				else if(argv[x].equals("-delay")) {
					Grav.kRefreshInterval = Integer.parseInt(argv[++x]);
				}
				else {
					throw(new NullPointerException());
				}
			}
		}
		catch(Exception e) {
			System.out.println(e);
			System.out.println("Usage: java Grav [options]");
			System.out.println("       -decay");
			System.out.println("       -trail");
			System.out.println("       -count <int>");
			System.out.println("       -delay <int>");
			System.exit(-1);
		}
					

			  


		Grav frame = new Grav();
	}


}

