

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

import kinetoscope.awt.*;



/**
 *
 *
 * This is a Java port of hypercube by jwz@jwz.org, which is included in 
 * xscreensaver.
 * <p>
 * This is the original copyright notice in jwz's C source:
 * <pre>
 * xscreensaver, Copyright (c) 1992, 1995, 1996, 1998
 *  Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 *
 * This code derived from TI Explorer Lisp code by Joe Keane, Fritz Mueller,
 * and Jamie Zawinski.
 * </pre>
 *
 *
 */
public class HyperCube extends DoubleBufferedFrame implements Runnable
{
	public static long kRefreshInterval = 30;

	private Thread refreshThread;

	int width, height;

	boolean firstPaint = false;


	/* These are the global variables and other vars from hyper() function in hypercube.c */

	double observer_z;
	double x_offset, y_offset;
	double unit_pixels;


	double cos_xy;
	double cos_xz;
	double cos_yz;
	double cos_xw;
	double cos_yw;
	double cos_zw;
	double sin_xy;
	double sin_xz;
	double sin_yz;
	double sin_xw;
	double sin_yw;
	double sin_zw;

	double xy;
	double xz;
	double yz;
	double xw;
	double yw;
	double zw;

	double ax = 1.0, ay = 0.0, az = 0.0, aw = 0.0;
	double bx = 0.0, by = 1.0, bz = 0.0, bw = 0.0;
	double cx = 0.0, cy = 0.0, cz = 1.0, cw = 0.0;
	double dx = 0.0, dy = 0.0, dz = 0.0, dw = 1.0;

	Color color0;
	Color color1;
	Color color2;
	Color color3;
	Color color4;
	Color color5;
	Color color6;
	Color color7;


	PointState[] points;


	boolean mono_p;


	/* This is essentially the screenhack() function from hypercube.c */
	public HyperCube(String[] args)
	{
		super("COLOR FRAME");
		
		setSize(512, 384);

		initFrame();

		Options options = new Options(args);
		
		mono_p = options.mono;
		kRefreshInterval = options.delay;
		observer_z = options.observer_z;

		this.xy = options.xy;
		this.xz = options.xz;
		this.yz = options.yz;
		this.xw = options.xw;
		this.yw = options.yw;
		this.zw = options.zw;

		cos_xy = cos(this.xy);
		cos_xz = cos(this.xz);
		cos_yz = cos(this.yz);
		cos_xw = cos(this.xw);
		cos_yw = cos(this.yw);
		cos_zw = cos(this.zw);
		sin_xy = sin(this.xy);
		sin_xz = sin(this.xz);
		sin_yz = sin(this.yz);
		sin_xw = sin(this.xw);
		sin_yw = sin(this.yw);
		sin_zw = sin(this.zw);

		points = new PointState[16];

		if(mono_p) {
			color0 = Color.white;
			color1 = Color.white;
			color2 = Color.white;
			color3 = Color.white;
			color4 = Color.white;
			color5 = Color.white;
			color6 = Color.white;
			color7 = Color.white;
		}
		else {
			color0 = Color.white;
			color1 = Color.cyan;
			color2 = Color.red;
			color3 = Color.green;
			color4 = Color.blue;
			color5 = Color.magenta;
			color6 = Color.yellow;
			color7 = Color.pink;
		}


		for(int i=0;i<16;i++) {
			points[i] = new PointState();
		}

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

		this.show();
	}



	/* This is the hyper() function from hypercube.c which does the calculations
		and draws the lines */
	synchronized void hyper(Graphics g) 
	{
      compute (-1, -1, -1, -1, points[0]);
      compute (-1, -1, -1,  1, points[1]);
      compute (-1, -1,  1, -1, points[2]);
      compute (-1, -1,  1,  1, points[3]);
      compute (-1,  1, -1, -1, points[4]);
      compute (-1,  1, -1,  1, points[5]);
      compute (-1,  1,  1, -1, points[6]);
      compute (-1,  1,  1,  1, points[7]);
      compute ( 1, -1, -1, -1, points[8]);
      compute ( 1, -1, -1,  1, points[9]);
      compute ( 1, -1,  1, -1, points[10]);
      compute ( 1, -1,  1,  1, points[11]);
      compute ( 1,  1, -1, -1, points[12]);
      compute ( 1,  1, -1,  1, points[13]);
      compute ( 1,  1,  1, -1, points[14]);
      compute ( 1,  1,  1,  1, points[15]);

      move_line (points[0], points[1], color0, g);
      move_line (points[0], points[2], color0, g);
      move_line (points[2], points[3], color0, g);
      move_line (points[1], points[3], color0, g);
      
      move_line (points[8], points[9], color1, g);
      move_line (points[8], points[10], color1, g);
      move_line (points[10], points[11], color1, g);
      move_line (points[9], points[11], color1, g);
      
      move_line (points[4], points[5], color2, g);
      move_line (points[4], points[6], color2, g);
      move_line (points[6], points[7], color2, g);
      move_line (points[5], points[7], color2, g);
      
      move_line (points[3], points[7], color3, g);
      move_line (points[3], points[11], color3, g);
      move_line (points[11], points[15], color3, g);
      move_line (points[7], points[15], color3, g);
      
      move_line (points[0], points[4], color4, g);
      move_line (points[0], points[8], color4, g);
      move_line (points[4], points[12], color4, g);
      move_line (points[8], points[12], color4, g);
      
      move_line (points[1], points[5], color5, g);
      move_line (points[1], points[9], color5, g);
      move_line (points[9], points[13], color5, g);
      move_line (points[5], points[13], color5, g);
      
      move_line (points[2], points[6], color6, g);
      move_line (points[2], points[10], color6, g);
      move_line (points[10], points[14], color6, g);
      move_line (points[6], points[14], color6, g);
      
      move_line (points[12], points[13], color7, g);
      move_line (points[12], points[14], color7, g);
      move_line (points[14], points[15], color7, g);
      move_line (points[13], points[15], color7, g);

		rotateXY();
		rotateXZ();
		rotateYZ();
		rotateXW();
		rotateYW();
		rotateZW();

	}

	/* This mimics move_line() in hypercube.c */
	void move_line(PointState state0, PointState state1, Color gc, Graphics X)
	{
		if(state0.same_p && state1.same_p) {
			return;
		}
		if(mono_p) {
			X.setColor(new Color(0,0,0));
			X.drawLine(state0.old_x, state0.old_y, state1.old_x, state1.old_y);
			X.setColor(gc);
			X.drawLine(state0.new_x, state0.new_y, state1.new_x, state1.new_y);
		}
		else {
			X.setColor(gc);
			//			X.drawLine(state0.old_x, state0.old_y, state1.old_x, state1.old_y);
			X.drawLine(state0.new_x, state0.new_y, state1.new_x, state1.new_y);
		}
	}			


	/* This accomplishes the same task as the compute() #define in hypercube.c */
	void compute(double a, double b, double c, double d, PointState point_state)
	{
		double temp_mult = ((unit_pixels+0.0) / (((a*az) + (b*bz) + (c*cz) + (d*dz) +	
														(a*aw) + (b*bw) + (c*cw) + (d*dw)) 
													  - observer_z));
		point_state.old_x = point_state.new_x;				  
		point_state.old_y = point_state.new_y;
		point_state.new_x = (int)Math.round((double)((((a*ax) + (b*bx) + (c*cx) + (d*dx)) * temp_mult)
									 + x_offset));
		point_state.new_y = (int)Math.round((double)((((a*ay) + (b*by) + (c*cy) + (d*dy)) * temp_mult)
									 + y_offset));
		point_state.same_p = (point_state.old_x == point_state.new_x &&
									  point_state.old_y == point_state.new_y);
	}


	/* This attempts to mimic the rotate() and rotates() #defines in hypercube.c
		without concatenating variable names "##" */
	double rotate0(double dim0, double dim1, double cos, double sin)
	{
		return((dim0 * cos) + (dim1 * sin));
	}

	double rotate1(double dim0, double dim1, double cos, double sin)
	{
		return((dim1 * cos) - (dim0 * sin));
	}

	/* this is the enumeration of rotates() #define */
	void rotateXY()
	{
		double tmp;

		if(sin_xy != 0) {
			tmp = rotate0(ax, ay, cos_xy, sin_xy);
			ay = rotate1(ax, ay, cos_xy, sin_xy);
			ax = tmp;
			tmp = rotate0(bx, by, cos_xy, sin_xy);
			by = rotate1(bx, by, cos_xy, sin_xy);
			bx = tmp;
			tmp = rotate0(cx, cy, cos_xy, sin_xy);
			cy = rotate1(cx, cy, cos_xy, sin_xy);
			cx = tmp;
			tmp = rotate0(dx, dy, cos_xy, sin_xy);
			dy = rotate1(dx, dy, cos_xy, sin_xy);
			dx = tmp;
		}
	}
	void rotateXZ()
	{
		double tmp;

		if(sin_xz != 0) {
			tmp = rotate0(ax, az, cos_xz, sin_xz);
			az = rotate1(ax, az, cos_xz, sin_xz);
			ax = tmp;
			tmp = rotate0(bx, bz, cos_xz, sin_xz);
			bz = rotate1(bx, bz, cos_xz, sin_xz);
			bx = tmp;
			tmp = rotate0(cx, cz, cos_xz, sin_xz);
			cz = rotate1(cx, cz, cos_xz, sin_xz);
			cx = tmp;
			tmp = rotate0(dx, dz, cos_xz, sin_xz);
			dz = rotate1(dx, dz, cos_xz, sin_xz);
			dx = tmp;
		}		
	}
	void rotateYZ()
	{
		double tmp;

		if(sin_yz != 0) {
			tmp = rotate0(ay, az, cos_yz, sin_yz);
			az = rotate1(ay, az, cos_yz, sin_yz);
			ay = tmp;
			tmp = rotate0(by, bz, cos_yz, sin_yz);
			bz = rotate1(by, bz, cos_yz, sin_yz);
			by = tmp;
			tmp = rotate0(cy, cz, cos_yz, sin_yz);
			cz = rotate1(cy, cz, cos_yz, sin_yz);
			cy = tmp;
			tmp = rotate0(dy, dz, cos_yz, sin_yz);
			dz = rotate1(dy, dz, cos_yz, sin_yz);
			dy = tmp;
		}
	}
	void rotateXW()
	{
		double tmp;

		if(sin_xw != 0) {
			tmp = rotate0(ax, aw, cos_xw, sin_xw);
			aw = rotate1(ax, aw, cos_xw, sin_xw);
			ax = tmp;
			tmp = rotate0(bx, bw, cos_xw, sin_xw);
			bw = rotate1(bx, bw, cos_xw, sin_xw);
			bx = tmp;
			tmp = rotate0(cx, cw, cos_xw, sin_xw);
			cw = rotate1(cx, cw, cos_xw, sin_xw);
			cx = tmp;
			tmp = rotate0(dx, dw, cos_xw, sin_xw);
			dw = rotate1(dx, dw, cos_xw, sin_xw);
			dx = tmp;
		}
	}
	void rotateYW()
	{
		double tmp;

		if(sin_yw != 0) {
			tmp = rotate0(ay, aw, cos_yw, sin_yw);
			aw = rotate1(ay, aw, cos_yw, sin_yw);
			ay = tmp;
			tmp = rotate0(by, bw, cos_yw, sin_yw);
			bw = rotate1(by, bw, cos_yw, sin_yw);
			by = tmp;
			tmp = rotate0(cy, cw, cos_yw, sin_yw);
			cw = rotate1(cy, cw, cos_yw, sin_yw);
			cy = tmp;
			tmp = rotate0(dy, dw, cos_yw, sin_yw);
			dw = rotate1(dy, dw, cos_yw, sin_yw);
			dy = tmp;
		}
	}
	void rotateZW()
	{
		double tmp;

		if(sin_zw != 0) {
			tmp = rotate0(az, aw, cos_zw, sin_zw);
			aw = rotate1(az, aw, cos_zw, sin_zw);
			az = tmp;
			tmp = rotate0(bz, bw, cos_zw, sin_zw);
			bw = rotate1(bz, bw, cos_zw, sin_zw);
			bz = tmp;
			tmp = rotate0(cz, cw, cos_zw, sin_zw);
			cw = rotate1(cz, cw, cos_zw, sin_zw);
			cz = tmp;
			tmp = rotate0(dz, dw, cos_zw, sin_zw);
			dw = rotate1(dz, dw, cos_zw, sin_zw);
			dz = tmp;
		}
	}


	/* This is the point_state struct in hypercube.c */
	public class PointState
	{
		public int old_x;
		public int old_y;
		public int new_x;
		public int new_y;

		public boolean same_p;

		PointState()
		{
			old_x=0;
			old_y=0;
			new_x=0;
			new_y=0;
			same_p=true;
		}
	}














	/*** These are shortcuts used for accessing static methods in java.lang.Math **/
	double cos(double x) {return(Math.cos(x));}
	double sin(double x) {return(Math.sin(x));}

	  








	/*** These methods are DoubleBufferedFrame methods ***/


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

		setBackground(new Color(0,0,0));
	}

	public void paintContent(Graphics g, int width, int height)
	{
		if((this.width!=width || this.height!=height) || firstPaint) {
			x_offset = (width / 2);
			y_offset = (height / 2);
			unit_pixels = (width < height) ? width : height;
			this.width = width;
			this.height = height;
			firstPaint = false;
		}

		hyper(g);
	}

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

		}
	}
	





	/*** This is the command line part */


	public class Options
	{
		double xy = .005;
		double xz = .005;
		double yz = .005;
		double xw = .005;
		double yw = .005;
		double zw = .005;

		double observer_z = 5;
		int delay = 30;

		boolean mono = false;

		Options(String[] args)
		{
			try {
				for(int x=0;x<args.length;x++) {
					if(args[x].equals("-xy")) {
						xy = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-xz")) {
						xz = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-yz")) {
						yz = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-xw")) {
						xw = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-yw")) {
						yw = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-zw")) {
						zw = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-observer_z")) {
						observer_z = Double.valueOf(args[x+1]).doubleValue();
					}
					else if(args[x].equals("-delay")) {
						delay = Integer.parseInt(args[x+1]);
					}
					else if(args[x].equals("-mono")) {
						mono = true;
					}
				}
			}
			catch(Exception e) {
				System.out.println("Usage: java HyperCube [options]");
				System.out.println("       -xy <float>");
				System.out.println("       -xz <float>");
				System.out.println("       -yz <float>");
				System.out.println("       -xw <float>");
				System.out.println("       -yw <float>");
				System.out.println("       -zw <float>");
				System.out.println("       -delay <int>");
				System.out.println("       -observer_z <float>");
				System.out.println("       -mono");
				System.exit(-1);
			}
		}
	}



	public static void main(String[] argv)
	{
		HyperCube frame = new HyperCube(argv);
	}


}

