
/**
 * Copyright (c) 2006 Carl Brannen.  No Rights Reserved.
 *
 * This source file produces a Java applet intended to simulate
 * gravitational trajectories for small masses in the grip of
 * a central "black hole".
 */

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class GravSim extends Applet implements Runnable{
	private static final long serialVersionUID = 1L;

	
	int PixelAL;
	int Pixels[];
	int MenuDo = 0;

	// Source of pixel display
	int PictureWidth = 2000;
	int PictureHeight = 2000;

	// Colors for types of gravities
	Color NewtonColor   = new Color(193,0,0);
	Color SchwarzColor  = new Color(0,103,0);
	Color PainleveColor = new Color(0,0,143);

	int delay = 40; // Frame rate for animation
	// Source of pixel display
	//***
	Thread animator;
	private GCanvas C_Space = new GCanvas();
	int PictureXC = C_Space.PictureWidth/2 - 1;
	int PictureYC = C_Space.PictureHeight/2 - 1;
	// These keep track of where the test masses are so as to allow them
	// to be distinguished from the orbits.
	int[][] NewtonIXIY   = new int[2][16];
	int[][] SchwarzIXIY  = new int[2][16];
	int[][] PainleveIXIY = new int[2][16];

	// These real variables are maintained as inputs to the run logic
	private double Size = 8.0;
	double DeltaT = 0.001;
	double CoordTime = 0.0;
	double MaxTime = 1.0e+10;

	DemoInitStruct[] DemoInit;

	// Keep track of orbit information
	// Initial Conditions
	private PhaseSpace InitCond  = new PhaseSpace();
	private PhaseSpace DinitCond = new PhaseSpace();
	private PhaseSpace[] NewtonPS   = new PhaseSpace[16];
	private PhaseSpace[] SchwarzPS  = new PhaseSpace[16];
	private PhaseSpace[] PainlevePS = new PhaseSpace[16];

	private int NumTestBodies = 5;
	// Buttons are made true by pressing, should be cleared upon execution
	// Checkboxes are states that stay true or false
	private boolean Newton = true;
	private boolean Schwarz = true;
	private boolean Painleve = true;
	// Various AWI variables
	private GridBagLayout Gblo = new GridBagLayout();
	// Labels
	private Label L_Spacer = new Label("      by Carl Brannen");
	int MenuMax = 0;
	String[] MenuItems = new String[20];
	private Label L_Demo   = new Label(" 0) Conservation of Angular Momentum");
	// Menu logic
	//private int MenuDo = 0;
	//private int MenuMax = 1;
	//private String[] MenuList;
	//private String MenuString = " 0)  "; // Default to first menu item.
	//private PopupMenu Pumu_Goto = new PopupMenu();
	//
	public String getAppletInfo() {
		return "Name: SwGrav\r\n" +
		"Author: Carl Brannen";
	}

	public void start() {
		if (animator == null) {
			animator = new Thread(this);
			animator.start();
		}
	}
	public void stop() {
		animator = null;
		//offCImage = null;
		//offCGraphics = null;
	}



	public void InitializePhaseSpaces() {
		CoordTime = 0.0;
		double Delta = 1.0;
		if (NumTestBodies > 1) Delta = 1.0/(NumTestBodies-1.0);
		for (int I = 0; I < NumTestBodies; I++) {
			NewtonPS[I].X  = InitCond.X  + Delta * (2*I - NumTestBodies+1) * DinitCond.X;
			NewtonPS[I].Y  = InitCond.Y  + Delta * (2*I - NumTestBodies+1) * DinitCond.Y;
			NewtonPS[I].Vx = InitCond.Vx + Delta * (2*I - NumTestBodies+1) * DinitCond.Vx;
			NewtonPS[I].Vy = InitCond.Vy + Delta * (2*I - NumTestBodies+1) * DinitCond.Vy;
			SchwarzPS[I].InitializePS(NewtonPS[I]);
			PainlevePS[I].InitializePS(NewtonPS[I]);
		}
	}
	public void InitializeDemoSpaces() {
		int M = MenuDo;
		CoordTime     = DemoInit[M].Init_CoordTime;
		DeltaT        = DemoInit[M].Init_DeltaT;
		Newton        = DemoInit[M].Init_Newton;
		Schwarz       = DemoInit[M].Init_Schwarz;
		Painleve      = DemoInit[M].Init_Painleve;
		NumTestBodies = DemoInit[M].Init_NumTestBodies;
		Size          = DemoInit[M].Init_Size;
		MaxTime       = DemoInit[M].Init_MaxTime;
		for (int I = 0; I < NumTestBodies; I++) {
			NewtonPS[I].X  = DemoInit[M].Init_Phase[0][I].X;
			NewtonPS[I].Vx = DemoInit[M].Init_Phase[0][I].Vx;
			NewtonPS[I].Y  = DemoInit[M].Init_Phase[0][I].Y;
			NewtonPS[I].Vy = DemoInit[M].Init_Phase[0][I].Vy;
			SchwarzPS[I].X  = DemoInit[M].Init_Phase[1][I].X;
			SchwarzPS[I].Vx = DemoInit[M].Init_Phase[1][I].Vx;
			SchwarzPS[I].Y  = DemoInit[M].Init_Phase[1][I].Y;
			SchwarzPS[I].Vy = DemoInit[M].Init_Phase[1][I].Vy;
			PainlevePS[I].X  = DemoInit[M].Init_Phase[2][I].X;
			PainlevePS[I].Vx = DemoInit[M].Init_Phase[2][I].Vx;
			PainlevePS[I].Y  = DemoInit[M].Init_Phase[2][I].Y;
			PainlevePS[I].Vy = DemoInit[M].Init_Phase[2][I].Vy;
		}
		InitCond.X  = (NewtonPS[0].X + NewtonPS[NumTestBodies-1].X)*0.5;
		InitCond.Y  = (NewtonPS[0].Y + NewtonPS[NumTestBodies-1].Y)*0.5;
		InitCond.Vx = (NewtonPS[0].Vx + NewtonPS[NumTestBodies-1].Vx)*0.5;
		InitCond.Vy = (NewtonPS[0].Vy + NewtonPS[NumTestBodies-1].Vy)*0.5;
		DinitCond.X  = (-NewtonPS[0].X + NewtonPS[NumTestBodies-1].X)*0.5;
		DinitCond.Y  = (-NewtonPS[0].Y + NewtonPS[NumTestBodies-1].Y)*0.5;
		DinitCond.Vx = (-NewtonPS[0].Vx + NewtonPS[NumTestBodies-1].Vx)*0.5;
		DinitCond.Vy = (-NewtonPS[0].Vy + NewtonPS[NumTestBodies-1].Vy)*0.5;
		//SetTFs();
	}

	

	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************
	//
	//  Here is where the physics is
	//
	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************


	private DeltaV Newton(PhaseSpace PS) {
		DeltaV DV = new DeltaV();
		double R = Math.sqrt(PS.X*PS.X+PS.Y*PS.Y);
		double R3 = R*R*R;
		DV.DVX = -PS.X/R3;
		DV.DVY = -PS.Y/R3;
		return DV;
	}
	private DeltaV Painleve(PhaseSpace PS) {
		DeltaV DV = new DeltaV();
		//    - 1*x/r^3 (Newton)
		//    - 2*vy*(x*vy-y*vx)/r^3
		//    + 2*x/r^4 (Schwarzschild)
		//    + 3*x*(x*vx+y*vy)^2/r^5
		// Painleve coordinates:
		//    - 1.0*sqrt(2)*vx*(vx^2+vy^2)/r^{1.5}
		//    + 3.0*sqrt(2)*vx/r^{2.5}
		//    + 1.5*sqrt(2)*vx*(x*vx+y*vy)^2/r^{3.5}
		//    + 2.0*sqrt(2)*y*(x*vy-y*vx)/r^{4.5}
		// Avoid division by zero: halt particle near singularity:
		double x = PS.X; double y = PS.Y;
		double vx = PS.Vx; double vy = PS.Vy;
		double R2 = x*x + y*y;
		double R1 = Math.sqrt(R2);
		double d2xdtdt = -PS.Vx / DeltaT;
		double d2ydtdt = -PS.Vy / DeltaT;
		if( R1 > 1.0e-10) {
			double R3 = R1 * R2;
			double R4 = R2 * R2;
			double R5 = R4 * R1;
			double R15 = R1 * Math.sqrt(R1/2.0);
			double R25 = R15 * R1;
			double R35 = R15 * R2;
			double R45 = R35 * R1;
			double xvy = x*vy - y*vx;
			double xvx = x*vx + y*vy;
			double xvx2 = xvx*xvx;
			double vx2 = vx*vx + vy*vy;
			double xxxn = - x / R3; // Newtonian approximation
			double yyyn = - y / R3;
			xxxn += -2.0*vy*xvy/R3;
			yyyn += +2.0*vx*xvy/R3;
			xxxn += 2.0*x/R4;
			yyyn += 2.0*y/R4;
			xxxn += 3.0*x*xvx2/R5;
			yyyn += 3.0*y*xvx2/R5;
			xxxn += -1.0*vx*vx2/R15;
			yyyn += -1.0*vy*vx2/R15;
			xxxn += 3.0*vx/R25;
			yyyn += 3.0*vy/R25;
			xxxn += 1.5*vx*xvx2/R35;
			yyyn += 1.5*vy*xvx2/R35;
			xxxn += +2.0*y*xvy/R45;
			yyyn += -2.0*x*xvy/R45;
			d2xdtdt = xxxn;
			d2ydtdt = yyyn;
		}
		DV.DVX = d2xdtdt;
		DV.DVY = d2ydtdt;
		return DV;
	}
	private DeltaV Schwarz(PhaseSpace PS) {
		DeltaV DV = new DeltaV();
		// Equations are:
		// s = r-2.0;
		// xxxn = x*(3*r*(y*yy+x*xx)^2-r*s^2) + y*4*(y*xx-x*yy)*(y*yy+x*xx) + yy*(y*xx-x*yy)*2*r^2*s
		// yyyn = y*(3*r*(y*yy+x*xx)^2-r*s^2) - x*4*(y*xx-x*yy)*(y*yy+x*xx) - xx*(y*xx-x*yy)*2*r^2*s
		// denom = r^5*s
		double RR = PS.X*PS.X+PS.Y*PS.Y;
		double R = Math.sqrt(RR);
		double S = R - 2.0;
		// Zero velocity by setting negative accelerations:
		DV.DVX = -PS.Vx/DeltaT;
		DV.DVY = -PS.Vy/DeltaT;
		// If S and R are sufficiently far from zero, calculate accelerations:
		if (((S >= 1.0E-10)||(S<=-1.0E-10))&&(R >= 1.0e-10)) {
			double xVx = PS.X*PS.Vx+PS.Y*PS.Vy;
			double yVx = PS.Y*PS.Vx-PS.X*PS.Vy;
			double amul = R*(3.0*xVx*xVx - S*S);
			double bmul = 4.0*yVx*xVx;
			double cmul = 2.0*yVx*RR*S;
			double denom = RR*RR*R*S;
			DV.DVX = (PS.X*amul + PS.Y*bmul + PS.Vy*cmul)/denom;
			DV.DVY = (PS.Y*amul - PS.X*bmul - PS.Vx*cmul)/denom;
		}
		return DV;
	}
	// *****************************************************************************
	//  End of physics, well, except for initial conditions
	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************
	// *****************************************************************************



	// *****************************************************************************
	// *****************************************************************************
	// Classical 4th order Runge-Kutta calculations.
	// k_1 = f( z_n )
	// k_2 = f( z_n + k_1 * DeltaT/2.0 )
	// k_3 = f( z_n + k_2 * DeltaT/2.0 )
	// k_4 = f( z_n + k_3 * DeltaT )
	//
	// z_{n+1} = z_n + (k_1 + 2k_2 + 2k_3 + k_4) * DeltaT / 6.0;
	//
	private PhaseSpace RK4_Newton(PhaseSpace PS, double DeltaT) {
		double DTOv2 = DeltaT * 0.5;
		double DTOv6 = DeltaT / 6.0;
		DeltaV DV1 = new DeltaV();
		DV1 = Newton(PS);
		RKn K1 = new RKn(DV1, PS);
		PhaseSpace PS2 = new PhaseSpace(PS,K1,DTOv2);
		DeltaV DV2 = new DeltaV();
		DV2 = Newton(PS2);
		RKn K2 = new RKn(DV2, PS2);
		PhaseSpace PS3 = new PhaseSpace(PS,K2,DTOv2);
		DeltaV DV3 = new DeltaV();
		DV3 = Newton(PS3);
		RKn K3 = new RKn(DV3, PS3);
		PhaseSpace PS4 = new PhaseSpace(PS,K3,DeltaT);
		DeltaV DV4 = new DeltaV();
		DV4 = Newton(PS4);
		RKn K4 = new RKn(DV4, PS4);
		//DeltaV DVr = new DeltaV();
		PhaseSpace PSRet = new PhaseSpace();
		PSRet.X  = PS.X  + (K1.Kx  + 2.0*(K2.Kx +K3.Kx ) + K4.Kx  )*DTOv6;
		PSRet.Y  = PS.Y  + (K1.Ky  + 2.0*(K2.Ky +K3.Ky ) + K4.Ky  )*DTOv6;
		PSRet.Vx = PS.Vx + (K1.Kdx + 2.0*(K2.Kdx+K3.Kdx) + K4.Kdx )*DTOv6;
		PSRet.Vy = PS.Vy + (K1.Kdy + 2.0*(K2.Kdy+K3.Kdy) + K4.Kdy )*DTOv6;
		return PSRet;
	}
	private PhaseSpace RK4_Schwarz(PhaseSpace PS, double DeltaT) {
		double DTOv2 = DeltaT * 0.5;
		double DTOv6 = DeltaT / 6.0;
		DeltaV DV1 = new DeltaV();
		DV1 = Schwarz(PS);
		RKn K1 = new RKn(DV1, PS);
		PhaseSpace PS2 = new PhaseSpace(PS,K1,DTOv2);
		DeltaV DV2 = new DeltaV();
		DV2 = Schwarz(PS2);
		RKn K2 = new RKn(DV2, PS2);
		PhaseSpace PS3 = new PhaseSpace(PS,K2,DTOv2);
		DeltaV DV3 = new DeltaV();
		DV3 = Schwarz(PS3);
		RKn K3 = new RKn(DV3, PS3);
		PhaseSpace PS4 = new PhaseSpace(PS,K3,DeltaT);
		DeltaV DV4 = new DeltaV();
		DV4 = Schwarz(PS4);
		RKn K4 = new RKn(DV4, PS4);
		//DeltaV DVr = new DeltaV();
		PhaseSpace PSRet = new PhaseSpace();
		PSRet.X  = PS.X  + (K1.Kx  + 2.0*(K2.Kx +K3.Kx ) + K4.Kx  )*DTOv6;
		PSRet.Y  = PS.Y  + (K1.Ky  + 2.0*(K2.Ky +K3.Ky ) + K4.Ky  )*DTOv6;
		PSRet.Vx = PS.Vx + (K1.Kdx + 2.0*(K2.Kdx+K3.Kdx) + K4.Kdx )*DTOv6;
		PSRet.Vy = PS.Vy + (K1.Kdy + 2.0*(K2.Kdy+K3.Kdy) + K4.Kdy )*DTOv6;
		return PSRet;
	}
	private PhaseSpace RK4_Painleve(PhaseSpace PS, double DeltaT) {
		double DTOv2 = DeltaT * 0.5;
		double DTOv6 = DeltaT / 6.0;
		DeltaV DV1 = new DeltaV();
		DV1 = Painleve(PS);
		RKn K1 = new RKn(DV1, PS);
		PhaseSpace PS2 = new PhaseSpace(PS,K1,DTOv2);
		DeltaV DV2 = new DeltaV();
		DV2 = Painleve(PS2);
		RKn K2 = new RKn(DV2, PS2);
		PhaseSpace PS3 = new PhaseSpace(PS,K2,DTOv2);
		DeltaV DV3 = new DeltaV();
		DV3 = Painleve(PS3);
		RKn K3 = new RKn(DV3, PS3);
		PhaseSpace PS4 = new PhaseSpace(PS,K3,DeltaT);
		DeltaV DV4 = new DeltaV();
		DV4 = Painleve(PS4);
		RKn K4 = new RKn(DV4, PS4);
		//DeltaV DVr = new DeltaV();
		PhaseSpace PSRet = new PhaseSpace();
		PSRet.X  = PS.X  + (K1.Kx  + 2.0*(K2.Kx +K3.Kx ) + K4.Kx  )*DTOv6;
		PSRet.Y  = PS.Y  + (K1.Ky  + 2.0*(K2.Ky +K3.Ky ) + K4.Ky  )*DTOv6;
		PSRet.Vx = PS.Vx + (K1.Kdx + 2.0*(K2.Kdx+K3.Kdx) + K4.Kdx )*DTOv6;
		PSRet.Vy = PS.Vy + (K1.Kdy + 2.0*(K2.Kdy+K3.Kdy) + K4.Kdy )*DTOv6;
		return PSRet;
	}


	private void DrawPicture() {
		// Compute the advancement in true time for this frame
		int IterPerFrame = 3000;
		CoordTime = CoordTime + ((double) IterPerFrame) * DeltaT;
		// Newton
		if (Newton) {
			for (int I = 0; I < IterPerFrame; I++) {
				for (int J = 0; J < NumTestBodies; J++) {
					NewtonPS[J] = RK4_Newton(NewtonPS[J],DeltaT);
				}
				if ((I % 100) == 99) {
					for (int J = 0; J<NumTestBodies; J++) {
						double DX = (double) PictureXC + Size * NewtonPS[J].X;
						double DY = (double) PictureYC - Size * NewtonPS[J].Y;
						if ((DX >= 0.0) & (DY >= 0.0) &
								(DX < (double) PictureWidth) &
								(DY < (double) PictureHeight)) {
							C_Space.Picture[  NewtonIXIY[0][J]][  NewtonIXIY[1][J]] = NewtonColor;
							NewtonIXIY[0][J] = (int) DX;
							NewtonIXIY[1][J] = (int) DY;
							C_Space.Picture[NewtonIXIY[0][J]][NewtonIXIY[1][J]] =
								NewtonColor;
						}
					}
				}
			}
		}
		// Schwarz Schwarzschild metric
		if (Schwarz) {
			for (int I = 0; I < IterPerFrame; I++) {
				for (int J = 0; J < NumTestBodies; J++) {
					SchwarzPS[J] = RK4_Schwarz(SchwarzPS[J],DeltaT);
				}
				if ((I % 100) == 99) {
					for (int J = 0; J<NumTestBodies; J++) {
						double DX = (double) PictureXC + Size * SchwarzPS[J].X;
						double DY = (double) PictureYC - Size * SchwarzPS[J].Y;
						if ((DX >= 0.0) & (DY >= 0.0) &
								(DX < (double) PictureWidth) &
								(DY < (double) PictureHeight)) {
							C_Space.Picture[SchwarzIXIY[0][J]][SchwarzIXIY[1][J]] = SchwarzColor;
							SchwarzIXIY[0][J] = (int) DX;
							SchwarzIXIY[1][J] = (int) DY;
							C_Space.Picture[SchwarzIXIY[0][J]][SchwarzIXIY[1][J]] =
								SchwarzColor;
						}
						//	                    double AX = (double) PictureXC + Size*10000.0 * SchwarzDV.DVX;
						//	                    double AY = (double) PictureYC - Size*10000.0 * SchwarzDV.DVY;
						//	                    int IX = (int) AX;
						//	                    int IY = (int) AY;
						//	                    Picture[IX-30][IY] = SchwarzColor;
					}
				}
			}
		}
		// Schwarz Painleve (gauge) metric
		if (Painleve) {
			for (int I = 0; I < IterPerFrame; I++) {
				for (int J = 0; J < NumTestBodies; J++) {
					PainlevePS[J] = RK4_Painleve(PainlevePS[J],DeltaT);
				}
				if ((I % 100) == 99) {
					for (int J = 0; J<NumTestBodies; J++) {
						double DX = (double) PictureXC + Size * PainlevePS[J].X;
						double DY = (double) PictureYC - Size * PainlevePS[J].Y;
						if ((DX >= 0.0) & (DY >= 0.0) &
								(DX < (double) PictureWidth) &
								(DY < (double) PictureHeight)) {
							C_Space.Picture[   PainleveIXIY[0][J]][   PainleveIXIY[1][J]] = PainleveColor;
							PainleveIXIY[0][J] = (int) DX;
							PainleveIXIY[1][J] = (int) DY;
							C_Space.Picture[PainleveIXIY[0][J]][PainleveIXIY[1][J]] =
								PainleveColor;
						}
						//	                   double AX = (double) PictureXC + Size*10000.0 * PainleveDV.DVX;
						//	                   double AY = (double) PictureYC - Size*10000.0 * PainleveDV.DVY;
						//	                   int IX = (int) AX;
						//	                    int IY = (int) AY;
						//	                    Picture[IX-30][IY] = PainleveColor;
					}
				}
			}
		}
		// Make test bodies white
		for (int J=0; J<NumTestBodies; J++) {
			if (Newton)   C_Space.Picture[  NewtonIXIY[0][J]][  NewtonIXIY[1][J]] = Color.white;
			if (Schwarz)  C_Space.Picture[ SchwarzIXIY[0][J]][ SchwarzIXIY[1][J]] = Color.white;
			if (Painleve) C_Space.Picture[PainleveIXIY[0][J]][PainleveIXIY[1][J]] = Color.white;
		}
	}

	public void InitIXIYs() {
		// This puts the saved test bodies to (0,0), which is off screen at bottom left
		for (int J=0; J<16; J++) {
			NewtonIXIY[0][J] = 0; SchwarzIXIY[0][J] = 0; PainleveIXIY[0][J] = 0;
			NewtonIXIY[1][J] = 0; SchwarzIXIY[1][J] = 0; PainleveIXIY[1][J] = 0;
		}
	}

	public void InitializePicture() {
		CoordTime = 0.0;
		InitializePhaseSpaces();
		InitializeDemoSpaces();
		InitIXIYs();
		for (int X=0; X<PictureWidth; X++) for (int Y=0; Y<PictureHeight; Y++)
			C_Space.Picture[X][Y] = Color.black;
		// Paint Sun at size of view.
		double RadiusSun2 = 4.0*Size*Size;
		double MinX = Math.abs(2.0*Size);
		if (Size >  PictureWidth/2.0) MinX =  PictureWidth/2.0;
		if (Size > PictureHeight/2.0) MinX = PictureHeight/2.0;
		int MX = (int) MinX;
		for (int X=-MX; X<= MX; X++) for (int Y=-MX; Y<= MX; Y++) {
			double R2 = ((double) X)*((double) X) + ((double) Y)*((double) Y);
			if (R2 < RadiusSun2) C_Space.Picture[X + PictureXC][Y + PictureYC] = Color.darkGray;
		}
		C_Space.Picture[PictureXC][PictureYC] = Color.white;
	}

	public void AddMI(String S) {
		MenuItems[MenuMax] = S;
		MenuMax++;
	}
	public void AddMV(int I, double Sz, double Dt, boolean N, boolean S, boolean P,
			double CT, double MT, int NP,
			double x,  double dx,   double y,  double dy,
			double vx, double dvx,  double vy, double dvy) {
		DemoInit[I] = new DemoInitStruct();
		DemoInit[I].Init_Size = Sz;
		DemoInit[I].Init_DeltaT = Dt;
		DemoInit[I].Init_Newton = N;
		DemoInit[I].Init_Schwarz = S;
		DemoInit[I].Init_Painleve = P;
		DemoInit[I].Init_CoordTime = CT;
		DemoInit[I].Init_MaxTime = MT;
		DemoInit[I].Init_NumTestBodies = NP;
		DemoInit[I].Init_Phase = new PhaseSpace[3][16];
		double Delta = 1.0;
		if (NP > 1) Delta = 1.0/(NP-1.0);
		for (int J=0; J<NP; J++) {
			DemoInit[I].Init_Phase[0][J] = new PhaseSpace();
			DemoInit[I].Init_Phase[0][J].X  =  x + Delta * (2 * J - NP + 1) * dx;
			DemoInit[I].Init_Phase[0][J].Y  =  y + Delta * (2 * J - NP + 1) * dy;
			DemoInit[I].Init_Phase[0][J].Vx = vx + Delta * (2 * J - NP + 1) * dvx;
			DemoInit[I].Init_Phase[0][J].Vy = vy + Delta * (2 * J - NP + 1) * dvy;
			DemoInit[I].Init_Phase[1][J] = new PhaseSpace();
			DemoInit[I].Init_Phase[1][J].X  =  x + Delta * (2 * J - NP + 1) * dx;
			DemoInit[I].Init_Phase[1][J].Y  =  y + Delta * (2 * J - NP + 1) * dy;
			DemoInit[I].Init_Phase[1][J].Vx = vx + Delta * (2 * J - NP + 1) * dvx;
			DemoInit[I].Init_Phase[1][J].Vy = vy + Delta * (2 * J - NP + 1) * dvy;
			DemoInit[I].Init_Phase[2][J] = new PhaseSpace();
			DemoInit[I].Init_Phase[2][J].X  =  x + Delta * (2 * J - NP + 1) * dx;
			DemoInit[I].Init_Phase[2][J].Y  =  y + Delta * (2 * J - NP + 1) * dy;
			DemoInit[I].Init_Phase[2][J].Vx = vx + Delta * (2 * J - NP + 1) * dvx;
			DemoInit[I].Init_Phase[2][J].Vy = vy + Delta * (2 * J - NP + 1) * dvy;
		}
	}

	public void CreateDemos() {
		// List of Menu choices:
			DemoInit = new DemoInitStruct[20];
	//     I Size DeltaT   N     S     P  CoordT  MaxT    NP  x   Dx   y  Dy   Vx   dVx   Vy  dVy
	AddMV( 0,8.0, 0.001 ,true ,false,false, 0.0 ,1000.  ,16, 0.0,0.0,20.,0.0,-0.2,0.0 ,0.0,0.14);
	AddMV( 1,2.0, 0.050 ,true ,true ,false, 0.0 ,150000., 1,100.,0.0,0.0,0.0,-0.01,0.0,0.08,0.0);
	AddMV( 2,1.0, 0.0005,true ,true ,false, 0.0 ,1000.  , 1,360.,0.0,60.,0.0,-1.0 ,0.0,0.0,0.0);
	AddMV( 3,8.0, 0.001 ,false,true ,true,  0.0 ,2200.0 , 8,30.0,0.0,0.0,0.0,0.0  ,0.0,0.1207615,1.0E-8);
	AddMV( 4,1.0, 0.001 ,true ,true ,false, 0.0 ,500.0  ,16,200.,0.0,0.0,10.,-1.0 ,0.0,0.0,0.0);
	AddMV( 5,50., 0.0001,false,true ,true , 0.0 ,58.0   , 8, 0.0,0.0,4.0,0.0,-0.4,0.094,0.0,0.0);
	AddMV( 6,35., 0.0002,false,true ,true , 0.0 ,280.0  , 6,6.01,0.02,0.0,0.0,0.0,0.0,0.4079,0.0);
	AddMV( 7,35., 0.0002,false,false,true , 0.0 ,180.0  ,15,5.5 ,0.0 ,0.0,0.0,0.0,0.0,0.427,0.002);
	AddMV( 8,12., 0.0002,false,false,true , 0.0 ,144.0  ,15,3.5 ,0.0 ,0.0,0.0,0.0,0.0,0.5344,0.002);
	MenuMax = 0;
	AddMI(" 0) Conservation of Angular Momentum");
	AddMI(" 1) Relativistic Precession");
	AddMI(" 2) Deflection of Light by gravity");
	AddMI(" 3) Knife-edge Black hole orbits");
	AddMI(" 4) Absorption Cross Section");
	AddMI(" 5) Event Horizon, Painleve coord.");
	AddMI(" 6) r=6 inner edge of accretion disk");
	AddMI(" 7) 4<r<6 no stable circular orbits");
	AddMI(" 8) r<4 unstable orbits can escape");
	}

	public void run() {
		//	    System.out.println("...running");
		// Remember the starting time
		long tm = System.currentTimeMillis();
		while (Thread.currentThread() == animator) {
			// Display the next frame of animation.
			C_Space.repaint();
			repaint();
			DrawPicture();
			// Delay depending on how far we are behind.
			try {
				tm += delay;
				Thread.sleep(Math.max(0, tm - System.currentTimeMillis()));
			} catch (InterruptedException e) {
				break;
			}
			if (CoordTime > MaxTime) {
				MenuDo++;
				if (MenuDo >= MenuMax) MenuDo = 0;
				InitializeDemoSpaces();
				L_Demo.setText(MenuItems[MenuDo]);
				InitializePicture();
			}
		}
	}


	public void init() {
		MenuDo = 0;
		InitIXIYs();
		CreateDemos();
		//	    System.out.print("GA Applet initializing... ");
		for (int I=0; I<16; I++) {
			NewtonPS[I]   = new PhaseSpace();
			SchwarzPS[I]  = new PhaseSpace();
			PainlevePS[I] = new PhaseSpace();
		}
		// Initialize the picture
		InitializePicture();
		// Create the user interface for this applet
		this.setLayout(Gblo);
		// Metric and Geometry Buttons
		GridBagConstraints Gbc = new GridBagConstraints();
		Gbc.gridheight = 1;
		Gbc.gridwidth = 1;
		Gbc.anchor = GridBagConstraints.CENTER;
		Gbc.weighty = 0;
		Gbc.fill = GridBagConstraints.HORIZONTAL;
		Gbc.weightx = 0;
		Gbc.gridx = 0;
		// Gridwork
		Gbc.gridx = 0;
		Gbc.gridx = 10;
		Gbc.gridy = 0; Gbc.gridwidth = 2; Gblo.setConstraints(L_Spacer, Gbc);
		Gbc.gridy = 2; Gbc.gridwidth = 2; Gblo.setConstraints(L_Demo,   Gbc);
		Gbc.gridheight = 1;
		Gbc.gridwidth = 12;
		Gbc.gridx = 0;
		C_Space.setSize(700,600);
		C_Space.setBackground(java.awt.Color.BLACK);
		C_Space.setForeground(java.awt.Color.WHITE);
		Gbc.gridy = 3; Gblo.setConstraints(C_Space, Gbc);
		// Addition
		this.add(L_Spacer);
		this.add(L_Demo);
		this.add(C_Space);
		this.setBackground(Color.lightGray);
		L_Spacer.setBackground(Color.lightGray);
		C_Space.PixelWidth = C_Space.getSize().width;
		C_Space.PixelHeight = C_Space.getSize().height;
		PixelAL = C_Space.PixelWidth * C_Space.PixelHeight;
		Pixels = new int [PixelAL];
	}
}

class GCanvas extends Canvas {
	// Source of pixel display
	int PixelWidth;
	int PixelHeight;
	int PictureWidth = 2000;
	int PictureHeight = 2000;
	Color[][] Picture = new Color[PictureWidth][PictureHeight];
	private static final long serialVersionUID = 1L;
	Image offCImage;
	Dimension offCDimension;
	public Graphics offCGraphics;
	public void update(Graphics g) {
		Dimension d = this.getSize();
		if ((offCGraphics == null)
				|| (d.width != offCDimension.width)
				|| (d.height != offCDimension.height)) {
			offCDimension = d;
			offCImage = createImage(d.width, d.height);
			offCGraphics = offCImage.getGraphics();
		}
		offCGraphics.setColor(getBackground());
		offCGraphics.fillRect(0, 0, d.width, d.height);
		offCGraphics.setColor(Color.yellow);
		paintCanvas(offCGraphics);
		g.drawImage(offCImage, 0, 0, null);
	}
	public void paint(Graphics g) {
		if (offCImage != null) {
			g.drawImage(offCImage, 0, 0, null);
		}
	}
	private void paintCanvas(Graphics g) {
		Dimension d = this.getSize();
		g.setColor(Color.red);
		// Compute transformation region
		int IX = 0; int MaxX = 0; int OffX = 0;
		if (d.width < PictureWidth) {
			IX = 0;
			MaxX = IX + d.width;
			OffX = (PictureWidth - d.width)/2;
		}
		else {
			IX = (d.width - PictureWidth)/2;
			MaxX = IX + PictureWidth;
			OffX = (PictureWidth - d.width)/2;
		}
		int IY = 0; int MaxY = 0; int OffY = 0;
		if (d.height < PictureHeight) {
			IY = 0;
			MaxY = IY + d.height;
			OffY = (PictureHeight - d.height)/2;
		}
		else {
			IY = (d.height - PictureHeight)/2;
			MaxY = IY + PictureHeight;
			OffY = (PictureHeight - d.height)/2;
		}
		for (int x = IX; x < MaxX; x++)for (int y = IY; y < MaxY; y++)
			if (Picture[x+OffX][y+OffY] != Color.black) {
				g.setColor(Picture[x + OffX][y + OffY]);
				g.drawLine(x, y, x, y);
			}
	}
}

// Phase Space variables
class PhaseSpace{
	public double X;
	public double Y;
	public double Vx;
	public double Vy;
	public PhaseSpace() {
		X=0.0; Y=0.0; Vx=0.0; Vy=0.0; }
	public PhaseSpace(PhaseSpace PS, RKn Kn, double H) {
		this.X = PS.X + Kn.Kx * H;
		this.Y = PS.Y + Kn.Ky * H;
		this.Vx = PS.Vx + Kn.Kdx * H;
		this.Vy = PS.Vy + Kn.Kdy * H;
	}
	public void InitializePS(PhaseSpace PS) {
		this.X = PS.X;
		this.Y = PS.Y;
		this.Vx = PS.Vx;
		this.Vy = PS.Vy;
	}
}

class RKn{
	double Kx; double Ky; double Kdx; double Kdy;
	public RKn(DeltaV DV, PhaseSpace PS) {
		Kx = PS.Vx;
		Ky = PS.Vy;
		Kdx = DV.DVX;
		Kdy = DV.DVY;
	}
}

class DeltaV{
	double DVX; double DVY;
	public DeltaV() {
		DVX = 0.0;
		DVY = 0.0;
	}
	public DeltaV(double dvx, double dvy) {
		DVX = dvx;
		DVY = dvy;
	}
}

// Initialization Structure
class DemoInitStruct{
	public double Init_MaxTime;
	public double Init_CoordTime;
	public double Init_DeltaT;
	public double Init_Size;
	public boolean Init_Newton;
	public boolean Init_Schwarz;
	public boolean Init_Painleve;
	public int Init_NumTestBodies;
	public PhaseSpace[][] Init_Phase;
}