/*
 * Physics.java
 * by Eric Dietz, 22 Apr 2007
 * 2-dimensional physics simulator
 * v2.00
 */

// package

// imports
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.io.*;
import java.util.*;
import java.math.*;


// class definition...
public class Physics extends Applet
	implements Runnable, ActionListener, ItemListener, MouseListener,
	MouseMotionListener, WindowListener
{
	// members
	public static final String version = "2.00";
	public static final double UGC = 6.672E-11;
	public static final double Inf = 1.0E+10;
	public static int WINDOW_WIDTH = 800;
	public static int WINDOW_HEIGHT = 600;
	public static int CONTROL_HEIGHT = 20;
	public static int CONTROL_WIDTH = 100;
	public static int IMAGE_HEIGHT;
	public static int IMAGE_WIDTH;
	private boolean space_wrap_around;
	private boolean satellites_affectedby_gravity;
	private boolean collision_bad;
	private boolean show_stats;
	private boolean paused;
	private boolean helping;
	private boolean simulating;
	private boolean customize;
	private boolean rem_pause;
	private Satellite [] satellite;
	private double GRAVITATION;
	private double def_xmin;
	private double def_xmax;
	private double def_ymin;
	private double def_ymax;
	private double xmin;
	private double xmax;
	private double ymin;
	private double ymax;
	private double posx;
	private double posy;
	private double velx;
	private double vely;
	private double accx;
	private double accy;
	private double mass;
	private double net_accx;
	private double net_accy;
	private double mass_max;
	private double mass_min;
	private double mass_cat;
	private double rad_min;
	private double rad_max;
	private int rad;
	private int numsats;
	private int numcols;
	private int ctrl_accel_x;
	private int ctrl_accel_y;
	private int ctrl_accel_rad;
	private double ctrl_accel_range;
	private int ctrl_drag_x;
	private int ctrl_drag_y;
	private boolean ctrl_dragging;
	private int crash_counter;
	private int crash_counter_val;
	private int custom_selected;
	private Color [] cols;
	private Button but_help;
	private Button but_reset;
	private Button but_custom;
	private Button but_objless;
	private Button but_objmore;
	private Checkbox chk_wrap;
	private Checkbox chk_gravity;
	private Checkbox chk_crash;
	private Checkbox chk_stats;
	private Checkbox chk_pause;
	private Checkbox chk_custom;
	private TextField txt_objects;
	private TextField txt_mass;
	private TextField txt_radius;
	private TextField txt_posx;
	private TextField txt_posy;
	private TextArea console;
	private Font myfont;
	private Graphics gfx_applet;
	private Image img_applet;
	private Thread render_thread;
	private boolean running;

	// constructor
	public Physics ()
	{
	}

	// if you want to run the applet standalone...
	public static void main (String[] args)
	{
		Physics app = new Physics();
		Frame fr = new Frame("Physics Applet");
		app.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
		fr.add(app, BorderLayout.CENTER);
		fr.setSize(WINDOW_WIDTH+10, WINDOW_HEIGHT+40);
		//fr.pack();
		fr.addWindowListener(app);
		fr.setResizable(true);
		fr.setVisible(true);
		app.init();
		app.repaint();
	}

	// Applet.init override
	public void init ()
	{
		try
		{
			WINDOW_WIDTH = Integer.parseInt(getParameter("width"));
		}
		catch (Exception e)
		{
		}
		try
		{
			WINDOW_HEIGHT = Integer.parseInt(getParameter("height"));
		}
		catch (Exception e)
		{
		}
		setLayout(null);
		setBackground(Color.black);
		//setForeground(Color.white);
		GRAVITATION = UGC;
		IMAGE_WIDTH = WINDOW_WIDTH - CONTROL_WIDTH;
		IMAGE_HEIGHT = WINDOW_HEIGHT - CONTROL_HEIGHT;
		space_wrap_around = false;
		satellites_affectedby_gravity = false;
		collision_bad = true;
		show_stats = true;
		paused = false;
		helping = true;
		simulating = false;
		customize = false;
		rem_pause = false;
		def_xmin = -400.0;
		def_xmax = 400.0;
		def_ymin = -300.0;
		def_ymax = 300.0;
		xmin = def_xmin;
		xmax = def_xmax;
		ymin = def_ymin;
		ymax = def_ymax;
		posx = 0.0;
		posy = 0.0;
		velx = 0.0;
		vely = 0.0;
		accx = 0.0;
		accy = 0.0;
		mass = 1000.0;
		net_accx = 0.0;
		net_accy = 0.0;
		numcols = 200;
		mass_max = 1.0E+15;
		mass_min = 1.0E+3;
		mass_cat = (mass_max - mass_min) / numcols;
		rad_min = 5.0;
		rad_max = 20.0;
		rad = 5;
		ctrl_accel_x = IMAGE_WIDTH - 45;
		ctrl_accel_y = IMAGE_HEIGHT - 45;
		ctrl_accel_rad = 40;
		ctrl_accel_range = 30.0;
		ctrl_drag_x = 0;
		ctrl_drag_y = 0;
		ctrl_dragging = false;
		crash_counter = 0;
		crash_counter_val = 10;
		custom_selected = -1;
		myfont = new Font("Arial", Font.PLAIN, 12);
		img_applet = createImage(IMAGE_WIDTH, IMAGE_HEIGHT);
		gfx_applet = img_applet.getGraphics();
		cols = new Color [numcols];
		for (int i = 0; i < numcols; i++)
			cols[i] = new Color(55+i, 0, 0);
		but_help = new Button("START");
		but_help.setBounds(0, IMAGE_HEIGHT, 50, CONTROL_HEIGHT);
		but_help.setEnabled(true);
		but_help.addActionListener(this);
		add(but_help);
		but_reset = new Button("Reset");
		but_reset.setBounds(50, IMAGE_HEIGHT, 50, CONTROL_HEIGHT);
		but_reset.setEnabled(true);
		but_reset.addActionListener(this);
		add(but_reset);
		but_custom = new Button("Apply");
		but_custom.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT-CONTROL_HEIGHT, CONTROL_WIDTH, CONTROL_HEIGHT);
		but_custom.setEnabled(true);
		but_custom.addActionListener(this);
		//add(but_custom);
		but_objless = new Button("-");
		but_objless.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT-CONTROL_HEIGHT*5, 20, CONTROL_HEIGHT);
		but_objless.setEnabled(true);
		but_objless.addActionListener(this);
		//add(but_objless);
		but_objmore = new Button("+");
		but_objmore.setBounds(IMAGE_WIDTH+CONTROL_WIDTH-20, IMAGE_HEIGHT-CONTROL_HEIGHT*5, 20, CONTROL_HEIGHT);
		but_objmore.setEnabled(true);
		but_objmore.addActionListener(this);
		//add(but_objmore);
		chk_wrap = new Checkbox("wrap space", space_wrap_around);
		chk_wrap.setBounds(100, IMAGE_HEIGHT, 80, CONTROL_HEIGHT);
		chk_wrap.setEnabled(true);
		chk_wrap.addItemListener(this);
		chk_wrap.setForeground(Color.white);
		add(chk_wrap);
		chk_gravity = new Checkbox("apply gravity to satellites", satellites_affectedby_gravity);
		chk_gravity.setBounds(180, IMAGE_HEIGHT, 150, CONTROL_HEIGHT);
		chk_gravity.setEnabled(true);
		chk_gravity.addItemListener(this);
		chk_gravity.setForeground(Color.white);
		add(chk_gravity);
		chk_crash = new Checkbox("detect collisions", collision_bad);
		chk_crash.setBounds(330, IMAGE_HEIGHT, 110, CONTROL_HEIGHT);
		chk_crash.setEnabled(true);
		chk_crash.addItemListener(this);
		chk_crash.setForeground(Color.white);
		add(chk_crash);
		chk_stats = new Checkbox("show vectors", show_stats);
		chk_stats.setBounds(440, IMAGE_HEIGHT, 100, CONTROL_HEIGHT);
		chk_stats.setEnabled(true);
		chk_stats.addItemListener(this);
		chk_stats.setForeground(Color.white);
		add(chk_stats);
		chk_pause = new Checkbox("pause", paused);
		chk_pause.setBounds(540, IMAGE_HEIGHT, 50, CONTROL_HEIGHT);
		chk_pause.setEnabled(true);
		chk_pause.addItemListener(this);
		chk_pause.setForeground(Color.white);
		add(chk_pause);
		chk_custom = new Checkbox("customize", customize);
		chk_custom.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT, CONTROL_WIDTH, CONTROL_HEIGHT);
		chk_custom.setEnabled(true);
		chk_custom.addItemListener(this);
		chk_custom.setForeground(Color.white);
		txt_objects = new TextField("objects", 32);
		txt_objects.setBounds(IMAGE_WIDTH+20, IMAGE_HEIGHT-CONTROL_HEIGHT*5, CONTROL_WIDTH-40, CONTROL_HEIGHT);
		txt_objects.setEditable(true);
		//add(txt_objects);
		txt_mass = new TextField("mass", 32);
		txt_mass.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT-CONTROL_HEIGHT*4, CONTROL_WIDTH, CONTROL_HEIGHT);
		txt_mass.setEditable(true);
		//add(txt_mass);
		txt_radius = new TextField("radius", 32);
		txt_radius.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT-CONTROL_HEIGHT*3, CONTROL_WIDTH, CONTROL_HEIGHT);
		txt_radius.setEditable(true);
		//add(txt_radius);
		txt_posx = new TextField("X", 32);
		txt_posx.setBounds(IMAGE_WIDTH, IMAGE_HEIGHT-CONTROL_HEIGHT*2, CONTROL_WIDTH/2, CONTROL_HEIGHT);
		txt_posx.setEditable(true);
		//add(txt_posx);
		txt_posy = new TextField("Y", 32);
		txt_posy.setBounds(IMAGE_WIDTH+CONTROL_WIDTH/2, IMAGE_HEIGHT-CONTROL_HEIGHT*2, CONTROL_WIDTH/2, CONTROL_HEIGHT);
		txt_posy.setEditable(true);
		//add(txt_posy);
		add(chk_custom);
		console = new TextArea("", 0, 0, TextArea.SCROLLBARS_NONE);
		console.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
		console.setEditable(false);
		add(console);
		addMouseListener(this);
		addMouseMotionListener(this);
		initialize_satellites();
		running = true;
		show_help();
		render_thread = new Thread(this);
		render_thread.start();
	}

	// Applet.destroy override
	public void destroy ()
	{
	}

	// Applet.start override
	public void start ()
	{
	}

	// Applet.stop override
	public void stop ()
	{
	}

	// Applet.getAppletInfo override
	public String getAppletInfo ()
	{
		return "Physics.java by Eric Dietz";
	}

	// Container.paint override
	public void paint (Graphics g)
	{
		render(gfx_applet);
		g.drawImage(img_applet, 0, 0, this);
	}

	// Container.update override
	public void update (Graphics g)
	{
		paint(g);
	}

	// Runnable.run implementation
	public void run ()
	{
		Object o = Thread.currentThread();
		if (o == render_thread)
			render_loop();
	}

	// ActionListener.actionPerformed implementation
	public void actionPerformed (ActionEvent ae)
	{
		Object o = ae.getSource();
		if (o == but_help)
			do_help();
		else if (o == but_reset)
			do_reset();
		else if (o == but_custom)
			do_applycustom();
		else if (o == but_objmore)
			do_objmore();
		else if (o == but_objless)
			do_objless();
	}

	// ItemListener.itemStateChanged implementation
	public void itemStateChanged (ItemEvent evt)
	{
		Object o = evt.getSource();
		if (o == chk_wrap)
			do_wrap();
		else if (o == chk_gravity)
			do_gravity();
		else if (o == chk_crash)
			do_crash();
		else if (o == chk_stats)
			do_stats();
		else if (o == chk_pause)
			do_pause();
		else if (o == chk_custom)
			do_custom();
	}

	// MouseListener.mouseEntered implementation
	public void mouseEntered (MouseEvent me)
	{
	}

	// MouseListener.mouseExited implementation
	public void mouseExited (MouseEvent me)
	{
	}

	// MouseListener.mouseClicked implementation
	public void mouseClicked (MouseEvent me)
	{
	}

	// MouseListener.mousePressed implementation
	public void mousePressed (MouseEvent me)
	{
		if (customize)
		{
			double mx, my, dx, dy, dist;
			mx = xpixel(me.getX());
			my = ypixel(me.getY());
			for (int i = 0; i < numsats; i++)
			{
				if (satellite[i].rad == 0.0)
					continue;
				dx = satellite[i].posx - mx;
				dy = satellite[i].posy - my;
				dist = Math.sqrt(dx * dx + dy * dy);
				if (dist <= satellite[i].rad)
				{
					custom_selected = i;
					txt_mass.setText(""+satellite[i].mass);
					txt_radius.setText(""+satellite[i].rad);
					txt_posx.setText(""+satellite[i].posx);
					txt_posy.setText(""+satellite[i].posy);
					repaint();
					return;
				}
			}
			if (custom_selected >= 0)
			{
				custom_unselect();
				repaint();
			}
		}
		else if (simulating)
		{
			int mx, my, dx, dy, dist;
			mx = me.getX();
			my = me.getY();
			dx = ctrl_accel_x - mx;
			dy = ctrl_accel_y - my;
			dist = (int)Math.sqrt(dx * dx + dy * dy);
			if (dist <= ctrl_accel_rad)
			{
				ctrl_drag_x = mx;
				ctrl_drag_y = my;
			}
			else
			{
				dx = (dx * ctrl_accel_rad) / dist;
				dy = (dy * ctrl_accel_rad) / dist;
				ctrl_drag_x = ctrl_accel_x - dx;
				ctrl_drag_y = ctrl_accel_y - dy;
			}
			ctrl_dragging = true;
		}
	}

	// MouseListener.mouseReleased implementation
	public void mouseReleased (MouseEvent me)
	{
		ctrl_dragging = false;
	}

	// MouseMotionListener.mouseMoved implementation
	public void mouseMoved (MouseEvent me)
	{
	}

	// MouseMotionListener.mouseDragged implementation
	public void mouseDragged (MouseEvent me)
	{
		if (customize)
		{
			if (custom_selected >= 0)
			{
				double mx, my;
				mx = xpixel(me.getX());
				my = ypixel(me.getY());
				satellite[custom_selected].posx = mx;
				satellite[custom_selected].posy = my;
				txt_posx.setText(""+mx);
				txt_posy.setText(""+my);
				repaint();
			}
		}
		else if (ctrl_dragging)
		{
			int mx, my, dx, dy, dist;
			mx = me.getX();
			my = me.getY();
			dx = ctrl_accel_x - mx;
			dy = ctrl_accel_y - my;
			dist = (int)Math.sqrt(dx * dx + dy * dy);
			if (dist <= ctrl_accel_rad)
			{
				ctrl_drag_x = mx;
				ctrl_drag_y = my;
			}
			else
			{
				dx = (dx * ctrl_accel_rad) / dist;
				dy = (dy * ctrl_accel_rad) / dist;
				ctrl_drag_x = ctrl_accel_x - dx;
				ctrl_drag_y = ctrl_accel_y - dy;
			}
		}
	}

    // WindowListener.windowActivated implementation
    public void windowActivated (WindowEvent we)
    {
    }

    // WindowListener.windowClosed implementation
    public void windowClosed (WindowEvent we)
    {
    }

    // WindowListener.windowClosing implementation
    public void windowClosing (WindowEvent we)
    {
        //((Frame)evt.getSource()).dispose();
        do_quit();
    }

    // WindowListener.windowDeactivated implementation
    public void windowDeactivated (WindowEvent we)
    {
    }

    // WindowListener.windowDeiconified implementation
    public void windowDeiconified (WindowEvent we)
    {
    }

    // WindowListener.windowIconified implementation
    public void windowIconified (WindowEvent we)
    {
    }

    // WindowListener.windowOpened implementation
    public void windowOpened (WindowEvent we)
    {
    }


	// quit the applet (:()
	public void do_quit ()
	{
		running = false;
		System.exit(0);
	}

	// display text on the console window.
	public void printf (String txt)
	{
		console.append(txt);
	}

	// display help text on console
	private void show_help()
	{
		printf(
			"Physics.java (c) 2007 Eric Dietz v"+version+"\n"+
			"This program demonstrates 2-dimensional Newtonian "+
			"Physics using Universal Gravitation.  Your ship is "+
			"wandering through 2D space and it is affected by the "+
			"gravity of the objects around it.  The brighter the "+
			"object, the greater its mass, and thus the more "+
			"strongly attracted you will be to it.\n"+
			"You can control your ship by clicking in the circle "+
			"at the lower right.  You can only control your "+
			"acceleration (not velocity directly).  The BLUE DOT "+
			"is your ship, the Yellow Line is your Velocity "+
			"vector, and the Purple Line is your Acceleration "+
			"vector.  If you collide with an object you will "+
			"'explode'.\n"+
			"You can toggle various settings at the bottom of the "+
			"screen.  Pressing the HELP button will show or hide "+
			"this message (CLICK START TO CONTINUE).\n"
		);
	}

	// toggle hide/display help
	private void do_help ()
	{
		helping = !helping;
		if (helping)
		{
			add(console);
			but_help.setLabel("Start");
			simulating = false;
		}
		else
		{
			remove(console);
			but_help.setLabel("Help");
			if (!paused)
				simulating = true;
		}
	}

	// reset the simulation
	private void do_reset ()
	{
		xmin = def_xmin;
		xmax = def_xmax;
		ymin = def_ymin;
		ymax = def_ymax;
		posx = 0.0;
		posy = 0.0;
		velx = 0.0;
		vely = 0.0;
		accx = 0.0;
		accy = 0.0;
		crash_counter = 0;
		initialize_satellites();
		custom_unselect();
		repaint();
	}

	// toggle whether to wrap space
	private void do_wrap ()
	{
		space_wrap_around = !space_wrap_around;
	}

	// toggle whether gravity affects everything
	private void do_gravity ()
	{
		satellites_affectedby_gravity = !satellites_affectedby_gravity;
	}

	// toggle whether to do collision detection
	private void do_crash ()
	{
		collision_bad = !collision_bad;
	}

	// toggle whether to show stats vectors
	private void do_stats ()
	{
		show_stats = !show_stats;
	}

	// toggle pause
	private void do_pause ()
	{
		paused = !paused;
		chk_pause.setState(paused);
		if (paused)
			simulating = false;
		else if (!helping)
			simulating = true;
	}

	// toggle customize mode
	private void do_custom ()
	{
		if (customize)
		{
			custom_selected = -1;
			remove(but_custom);
			remove(but_objmore);
			remove(but_objless);
			remove(txt_objects);
			remove(txt_mass);
			remove(txt_radius);
			remove(txt_posx);
			remove(txt_posy);
			customize = false;
			if (!rem_pause && paused)
				do_pause();
		}
		else
		{
			custom_unselect();
			add(but_custom);
			add(but_objmore);
			add(but_objless);
			add(txt_objects);
			add(txt_mass);
			add(txt_radius);
			add(txt_posx);
			add(txt_posy);
			rem_pause = paused;
			if (!paused)
				do_pause();
			customize = true;
		}
	}

	// apply custom settings
	private void do_applycustom ()
	{
		boolean changed;
		int n;
		double d;
		changed = false;
		if (custom_selected >= 0)
		{
			try
			{
				d = Double.parseDouble(txt_mass.getText());
				if (d != satellite[custom_selected].mass)
				{
					int c = (int)((d - mass_min) / mass_cat);
					if (c < 0)
						c = 0;
					if (c >= numcols)
						c = numcols - 1;
					satellite[custom_selected].mass = d;
					satellite[custom_selected].col = c;
					changed = true;
				}
			}
			catch (Exception e)
			{
			}
			try
			{
				d = Double.parseDouble(txt_radius.getText());
				if (d != satellite[custom_selected].rad && d >= 0.0)
				{
					satellite[custom_selected].rad = d;
					changed = true;
				}
			}
			catch (Exception e)
			{
			}
			try
			{
				d = Double.parseDouble(txt_posx.getText());
				if (d != satellite[custom_selected].posx)
				{
					satellite[custom_selected].posx = d;
					changed = true;
				}
			}
			catch (Exception e)
			{
			}
			try
			{
				d = Double.parseDouble(txt_posy.getText());
				if (d != satellite[custom_selected].posy)
				{
					satellite[custom_selected].posy = d;
					changed = true;
				}
			}
			catch (Exception e)
			{
			}
		}
		try
		{
			n = Integer.parseInt(txt_objects.getText());
			if (n != numsats)
			{
				if (changed)
				{
					txt_objects.setText(""+numsats);
				}
				else
				{
					numsats = n;
					initialize_satellites();
					custom_unselect();
					changed = true;
				}
			}
		}
		catch (Exception e)
		{
		}
		repaint();
	}

	// increase number of satellites
	private void do_objmore ()
	{
		if (numsats < Inf)
		{
			numsats++;
			initialize_satellites();
			custom_unselect();
			repaint();
		}
	}

	// decrease number of satellites
	private void do_objless ()
	{
		if (numsats > 0)
		{
			numsats--;
			initialize_satellites();
			custom_unselect();
			repaint();
		}
	}

	// set default textfield text
	private void custom_unselect ()
	{
		txt_objects.setText(""+numsats);
		txt_mass.setText("mass");
		txt_radius.setText("radius");
		txt_posx.setText("X");
		txt_posy.setText("Y");
		custom_selected = -1;
	}

	// for viewing purposes: translate the x & y -abstract- coordinates (e.g.
	// xmin<=x<=xmax,ymin<=y<=ymax) to physical x & y integer coordinates
	// for the screen
	private int xcoord (double xval)
	{
		return (int)((xval - xmin) * IMAGE_WIDTH / (xmax - xmin));
	}
	private int ycoord (double yval)
	{
		return (int)(IMAGE_HEIGHT - (yval - ymin) * IMAGE_HEIGHT / (ymax - ymin));
	}
	private int xscl (double xval)
	{
		return (int)(xval * IMAGE_WIDTH / (xmax - xmin));
	}
	private int yscl (double yval)
	{
		return (int)(yval * IMAGE_HEIGHT / (ymax - ymin));
	}

	// turn a pixel coordinate into abstract coordinates
	private double xpixel (int xval)
	{
		return (double)(xval * (xmax - xmin) / IMAGE_WIDTH + xmin);
	}
	private double ypixel (int yval)
	{
		return (double)((IMAGE_HEIGHT - yval) * (ymax - ymin) / IMAGE_HEIGHT + ymin);
	}

	// initialize satellite values (randomly)
	private void initialize_satellites ()
	{
		double x, y, m, d;
		int satsmin, satsmax, c;
		Random rand = new Random();
		// initialize satellites
		satsmin = 0;
		satsmax = 40;
		// generate random number of satellites
		if (!customize)
			numsats = rand.nextInt(satsmax - satsmin + 1) + satsmin;
		satellite = new Satellite [numsats];
		for (int i = 0; i < numsats; i++)
		{
			// random position
			x = 0.0;
			y = 0.0;
			while (x == 0.0 && y == 0.0)
			{
				x = rand.nextDouble() * (xmax - xmin) + xmin;
				y = rand.nextDouble() * (ymax - ymin) + ymin;
			}
			if (customize)
			{
				m = mass_max;
				d = rad_max;
				c = numcols - 1;
			}
			else
			{
				// random mass
				m = rand.nextDouble() * (mass_max - mass_min) + mass_min;
				// random radius
				d = rand.nextDouble() * (rad_max - rad_min) + rad_min;
				// color (based on mass)
				c = (int)((m - mass_min) / mass_cat);
			}
			// create the satellite object
			satellite[i] = new Satellite(x, y, m, d, c);
		}
	}

	// render loop - runs in its own thread
	private void render_loop ()
	{
		double delay = 0.1;
		while (running)
		{
			if (simulating)
			{
				if (crash_counter > 0)
				{
					crash_counter--;
					if (crash_counter == 0)
					{
						posx = 0.0;
						posy = 0.0;
						velx = 0.0;
						vely = 0.0;
						accx = 0.0;
						accy = 0.0;
					}
				}
				else
				{
					process_rockets();
					net_accx = accx;
					net_accy = accy;
					process_gravity();
					if (crash_counter == 0)
					{
						xmin = def_xmin;
						xmax = def_xmax;
						ymin = def_ymin;
						ymax = def_ymax;
						velx += net_accx * delay;
						vely += net_accy * delay;
						posx += velx * delay;
						posy += vely * delay;
						if (satellites_affectedby_gravity)
							process_satellite_gravity(delay);
						if (space_wrap_around)
						{
							for (int i = 0; i < 10 && posx > xmax; i++)
								posx -= (xmax - xmin);
							for (int i = 0; i < 10 && posx < xmin; i++)
								posx += (xmax - xmin);
							for (int i = 0; i < 10 && posy > ymax; i++)
								posy -= (ymax - ymin);
							for (int i = 0; i < 10 && posy < ymin; i++)
								posy += (ymax - ymin);
						}
						else
						{
							if (posx < xmin)
								xmin = posx;
							if (posx > xmax)
								xmax = posx;
							if (posy < ymin)
								ymin = posy;
							if (posy > ymax)
								ymax = posy;
						}
					}
				}
				repaint();
				try { Thread.sleep((long)(1000.0 * delay)); }
				catch (Exception e) { }
			}
			else
			{
				try { Thread.sleep(500); }
				catch (Exception e) { }
			}
		}
	}

	// display data...
	private String display_data ()
	{
		return
			(crash_counter > 0 ? "COLLISION " : "") +
			"position = <" +
			String.format("%.3g", posx) +
			", " +
			String.format("%.3g", posy) +
			">, velocity = <" +
			String.format("%.3g", velx) +
			", " +
			String.format("%.3g", vely) +
			">, rocket acceleration = <" +
			String.format("%.3g", accx) +
			", " +
			String.format("%.3g", accy) +
			">,";
	}
	// display more data
	private String display_data2 ()
	{
		return
			"total acceleration = <" +
			String.format("%.3g", net_accx) +
			", " +
			String.format("%.3g", net_accy) +
			">.";
	}

	// display the space ship on the screen
	private void render (Graphics g)
	{
		int x, y, w, h, bw, bh;
		g.setFont(myfont);
		g.setColor(Color.black);
		g.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
		// first draw the satellites
		bw = bh = 0;
		for (int i = 0; i < numsats; i++)
		{
			w = 0;
			try
			{
				w = xscl(satellite[i].rad);
			}
			catch (Exception e)
			{
				System.out.println("Exception! w="+xscl(satellite[i].rad));
				//e.printStackTrace();
			}
			h = yscl(satellite[i].rad);
			g.setColor(cols[satellite[i].col]);
			g.fillOval(
				xcoord(satellite[i].posx) - w,
				ycoord(satellite[i].posy) - h,
				w * 2,
				h * 2
			);
			if (custom_selected == i)
			{
				bw = w;
				bh = h;
			}
		}
		// now draw the object and its vectors
		x = xcoord(posx);
		y = ycoord(posy);
		g.setColor(Color.blue);
		g.fillOval(x - rad, y - rad, rad * 2, rad * 2);
		if (crash_counter > 0)
		{
			g.setColor(Color.yellow);
			g.fillRect(x-crash_counter, y-crash_counter,
				crash_counter*2, crash_counter*2 );
		}
		else
		{
			g.setColor(Color.yellow);
			g.drawLine(x, y, xcoord(posx + velx), ycoord(posy + vely));
			g.setColor(Color.magenta);
			g.drawLine(x, y, xcoord(posx + net_accx), ycoord(posy + net_accy));
		}
		g.setColor(Color.blue);
		g.drawOval(
			ctrl_accel_x - ctrl_accel_rad,
			ctrl_accel_y - ctrl_accel_rad,
			ctrl_accel_rad * 2,
			ctrl_accel_rad * 2
		);
		g.fillOval(ctrl_accel_x - 3, ctrl_accel_y - 3, 6, 6);
		if (ctrl_dragging)
			g.drawLine(ctrl_accel_x, ctrl_accel_y, ctrl_drag_x, ctrl_drag_y);
		if (custom_selected >= 0)
		{
			g.setColor(Color.yellow);
			g.drawRect(
				xcoord(satellite[custom_selected].posx) - bw,
				ycoord(satellite[custom_selected].posy) - bh,
				bw * 2,
				bh * 2
			);
		}
		if (show_stats)
		{
			g.setColor(Color.white);
			g.drawString(display_data(), 0, myfont.getSize());
			g.drawString(display_data2(), 0, myfont.getSize() * 2);
		}
	}

	// process the acceleration due to rocketing around
	private void process_rockets ()
	{
		if (ctrl_dragging)
		{
			accx =
				(double)(ctrl_drag_x - ctrl_accel_x) *
				ctrl_accel_range / ctrl_accel_rad;
			accy =
				(double)(ctrl_accel_y - ctrl_drag_y) *
				ctrl_accel_range / ctrl_accel_rad;
		}
		else
		{
			accx = 0.0;
			accy = 0.0;
		}
	}

	// process the gravity of various objects on the ship
	// taken from Newton's Universal Law of Gravitation
	// *****
	// the principle here is:
	// F = G * m1 * m2 / r^2
	// where F is the Force of gravity,
	// G is the Universal Gravitational Constant
	// m1 is the mass of object 1,
	// m2 is the mass of object 2,
	// r^2 is the square of the radius (distance) between the objects
	// Substituting this equation for F = m*a,
	// we get a = acceleration due to gravity = F / m.
	// This yields the equation
	// A = G * m1 * m2 / m * r^2,
	// or
	// A1 = G * m2 / r^2
	// and A2 = G * m1 / r^2
	// and A1 + A2 = G * (m1 + m2) / r^2
	// THE RELEVANT EQUATION here is:
	// A1 = G * m2 / r^2.
	// *****
	private void process_gravity ()
	{
		double grav, dist, dx, dy;
		// for each satellite on the map:
		for (int i = 0; i < numsats; i++)
		{
			if (satellite[i].rad == 0.0)
				continue;
			dx = satellite[i].posx - posx;		// <dx,dy> is the vector from the ship to the satellite
			dy = satellite[i].posy - posy;
			dist = Math.sqrt(dx * dx + dy * dy);	// dist is the distance (r) from the ship to the satellite
			if (dist < satellite[i].rad && collision_bad)
				crash_counter = crash_counter_val;	// detect a collission if there is one.
			if (dist == 0)
			{
				dx = Inf;	// avoid a division by zero
				dy = Inf;
			}
			else
			{
				dx /= dist;	// <dx, dy> is now the unit vector pointing towards the satellite
				dy /= dist;
			}
			grav = GRAVITATION * satellite[i].mass;	// first part of the equation: A1 = G * m2
			dist *= dist;		// square the distance
			if (dist == 0)
				grav = Inf;	// again avoid a division by zero
			else
				grav /= dist;	// apply the final part of the equation, A1 = G * m2 / r^2
			dx *= grav;	// <dx, dy> will now be the directional acceleration vector of gravity.
			dy *= grav;
			net_accx += dx;		// add this vector to our NET acceleration due to gravity
			net_accy += dy;
		}
	}

	// Calculate satellites gravitational effect on eachother
	public void process_satellite_gravity (double delay)
	{
		double grav, dist, dx, dy, netx, nety, wmin, wmax, hmin, hmax;
		if (numsats == 0)
			return;
		wmin = xmin;
		wmax = xmax;
		hmin = ymin;
		hmax = ymax;
		for (int i = 0; i < numsats; i++)
		{
			if (satellite[i].rad == 0.0)
				continue;
			netx = 0.0;
			nety = 0.0;
			dx = posx - satellite[i].posx;
			dy = posy - satellite[i].posy;
			dist = Math.sqrt(dx * dx + dy * dy);
			if (dist == 0)
			{
				dx = Inf;
				dy = Inf;
			}
			else
			{
				dx /= dist;
				dy /= dist;
			}
			grav = GRAVITATION * mass;
			dist *= dist;
			if (dist == 0)
				grav = Inf;
			else
				grav /= dist;
			dx *= grav;
			dy *= grav;
			netx += dx;
			nety += dy;
			for (int j = 0; j < numsats; j++)
			{
				if (i == j)
					continue;
				if (satellite[j].mass == 0.0)
					continue;
				dx = satellite[j].posx - satellite[i].posx;
				dy = satellite[j].posy - satellite[i].posy;
				dist = Math.sqrt(dx * dx + dy * dy);
				if (dist < satellite[i].rad + satellite[j].rad)
				{
					if (satellite[i].rad > satellite[j].rad)
						satellite[i].rad += 5.0;
					else
						satellite[i].rad = satellite[j].rad + 5.0;
					satellite[i].mass += satellite[j].mass;
					satellite[i].posx += dx / 2.0;
					satellite[i].posy += dy / 2.0;
					satellite[i].velx =
						(satellite[i].velx * satellite[i].mass +
						satellite[j].velx * satellite[j].mass) /
						(satellite[i].mass + satellite[j].mass);
					satellite[i].vely =
						(satellite[i].vely * satellite[i].mass +
						satellite[j].vely * satellite[j].mass) /
						(satellite[i].mass + satellite[j].mass);
					satellite[i].col += satellite[j].col;
					if (satellite[i].col >= numcols)
						satellite[i].col = numcols - 1;
					satellite[j].rad = 0.0;
					satellite[j].mass = 0.0;
					satellite[j].posx = 0.0;
					satellite[j].posy = 0.0;
					satellite[j].velx = 0.0;
					satellite[j].vely = 0.0;
					satellite[j].col = 0;
					//continue;
				}
				if (dist == 0)
				{
					dx = Inf;
					dy = Inf;
				}
				else
				{
					dx /= dist;
					dy /= dist;
				}
				grav = GRAVITATION * satellite[j].mass;
				dist *= dist;
				if (dist == 0)
					grav = Inf;
				else
					grav /= dist;
				dx *= grav;
				dy *= grav;
				netx += dx;
				nety += dy;
			}
			satellite[i].velx += netx * delay;
			satellite[i].vely += nety * delay;
			satellite[i].posx += satellite[i].velx * delay;
			satellite[i].posy += satellite[i].vely * delay;
			if (space_wrap_around)
			{
				for (int j = 0; j < 10 && satellite[i].posx > xmax; j++)
					satellite[i].posx -= (xmax - xmin);
				for (int j = 0; j < 10 && satellite[i].posx < xmin; j++)
					satellite[i].posx += (xmax - xmin);
				for (int j = 0; j < 10 && satellite[i].posy > ymax; j++)
					satellite[i].posy -= (ymax - ymin);
				for (int j = 0; j < 10 && satellite[i].posy < ymin; j++)
					satellite[i].posy += (ymax - ymin);
			}
			else
			{
				if (satellite[i].posx < wmin)
					wmin = satellite[i].posx;
				if (satellite[i].posx > wmax)
					wmax = satellite[i].posx;
				if (satellite[i].posy < hmin)
					hmin = satellite[i].posy;
				if (satellite[i].posy > hmax)
					hmax = satellite[i].posy;
			}
		}
		if (!space_wrap_around)
		{
			if (wmax > xmax)
				xmax = wmax;
			if (wmin < xmin)
				xmin = wmin;
			if (hmax > ymax)
				ymax = hmax;
			if (hmin < ymin)
				ymin = hmin;
		}
	}

	// Satellite subclass
	// stores info about a satellite
	private class Satellite
	{
		// Satellite elements:
		private double posx;
		private double posy;
		private double velx;
		private double vely;
		private double mass;
		private double rad;
		private int col;

		// Satellite constructor
		public Satellite (double x, double y, double m, double r, int c)
		{
			posx = x;
			posy = y;
			mass = m;
			rad = r;
			col = c;
			velx = 0.0;
			vely = 0.0;
		}
	}
	// end of Satellite subclass
}
// end of Physics class

// end of file
