//************************************************************************
// You are free to copy this source and use it in any way you deem fit
//************************************************************************
// 3D globe class. to create 2D games on a 3 D grid
//
// Program: Globe class
// Programmer: H K Gupta 		Date: Someday in March 1998
//************************************************************************

import java.awt.*;
import java.lang.Math;

public	class HridayeshGlobe extends Canvas {

	int globeLevel = -1;

	// Icon File related variables
	private Image[]	icons;
	private Graphics iconG;
	private boolean iconsCopied = false;
	private int	noOfIcons = 16;
		MediaTracker 	tracker;
	private Image		iconImages;

	public HridayeshGlobe(int level, Image iconImages) {
		super();

		this.iconImages = iconImages;
		tracker = new MediaTracker(this);
		tracker.addImage(this.iconImages, 0);

		pntX = new double[maxPoints];
		pntY = new double[maxPoints];
		pntZ = new double[maxPoints];
		dblX = new double[maxPoints];
		dblY = new double[maxPoints];
		dblZ = new double[maxPoints];
		intX = new int[maxPoints];
		intY = new int[maxPoints];
		intZ = new int[maxPoints];
		pntNeighbour = new int[maxPoints][6];
		globeInit(maxPoints);
		setGlobeLevel(level);
	}

	void	globeInit(int maxPoints) { // method to be overridden by subclass
	}

	void	globeCreate() { // method to be overridden by subclass
	}

	void	globeInitDisplayPoint(Graphics o, int point) { // method to be overridden by subclass
	}

	void	globeDisplayPoint(Graphics o, int point) { // method to be overridden by subclass
	}

	void	globePointSelected(int point, int mask) { // method to be overridden by subclass
	}

	void	globeFinalUpdate(Graphics o, int xCenter, int yCenter) { // method to be overridden by subclass
	}

	void	globeJustRotated() { // method to be overridden by subclass
	}

	boolean	globeMouseAllowed() { // method to be overridden by subclass
		return true;
	}

	private	void iconsCopy() {
		icons = new Image[noOfIcons];
		for(int i = 0; i < noOfIcons; i++) {
			icons[i] = createImage(16, 16);
			iconG = icons[i].getGraphics();
			iconG.drawImage(iconImages, - ((16 + 2) * i), 0, this);
		}
	}

	public void setGlobeLevel(int level) { // restrict no. of point to 4 <> 1026
		drawBackground = true;
		int temp;
		temp = level;
		if(temp < 1) temp = 1;
		if(temp > 4) temp = 4;
		if(globeLevel != -1) {
			globeLevel = temp;
			createGrid();
			repaint();
		}
		globeLevel = temp;
	} 

	boolean repaintInProgress = false;

	public 	void update(Graphics g) {
		if(repaintInProgress) return;
		if(!iconsCopied) {
			if(!tracker.checkAll()) {
				return; // wait for image to load
			}
			iconsCopy();
			iconsCopied = true;
		}

		repaintInProgress = true;
		if(!bufferSet) {
			setBuffers();
			createGrid();
		}
		if(drawBackground) {
			o.drawImage(backGroundImage, 0, 0, this);
			drawBackground = false;
		}
		drawGrid();
		globeFinalUpdate(o, xCenter, yCenter);
		g.drawImage(offImage, 0, 0, this);
		repaintInProgress = false;
	}

	private boolean drawBackground = true;
        private Image backGroundImage; // for backGround
	private Image offImage;
        private Graphics o;
	private boolean bufferSet = false;
	private int	xOff, yOff, diameter, radius, xCenter, yCenter;

	private void setBuffers() {
        	Graphics b;
		Dimension d = size();
		offImage = createImage(d.width, d.height);
       		o = offImage.getGraphics();
		backGroundImage = createImage(d.width, d.height);
       		b = backGroundImage.getGraphics();
		calculateOffsets();
		b.setColor(Color.black);
		b.fillRect(0, 0, d.width, d.height);
		b.setColor(Color.white);
		b.fillOval(xOff - 5, yOff - 5, diameter, diameter);
		drawArrows(b);
		bufferSet = true;
	}

		//        Left    LTop    LBot    Right   RTop    RBot    Top     Bottom
private int[] arrType = { 1,  2,  1,  2,  1,  2,  1,  2,  1,  2,  1,  2,  1,  2,  1,  2};
private int[] arrXPos = { 1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1,  0,  0,  0,  0};
private int[] arrYPos = { 0,  0,  1,  1, -1, -1,  0,  0,  1,  1, -1, -1,  1,  1, -1, -1}; 
private int[] arrXOff = { 1,  1, -2, -1, -2, -1, -1, -1,  2,  1,  2,  1, -6,  6, -6,  6}; 
private int[] arrYOff = { 2, -2, -2, -1,  2,  1,  2, -2, -2, -1,  2,  1, -1, -1,  1,  1};
private int[] arrXRot = { 1,  1,  1,  1,  1,  1, -1, -1, -1, -1, -1, -1,  0,  0,  0,  0};
private int[] arrYRot = { 0,  0,  1,  1, -1, -1,  0,  0,  1,  1, -1, -1,  1,  1, -1, -1};
	private	int arrGap = 20;

	private void drawArrows(Graphics b) {
		int	x, y;
		for(int i = 0; i < 16; i++) {
			x = xCenter + arrXPos[i] * radius + arrXOff[i] * arrGap;
			y = yCenter - (arrYPos[i] * radius + arrYOff[i] * arrGap);
			b.drawImage(icons[i], x - 8, y - 8, this);
		}
	}

	public boolean mouseDown(Event e, int xIn, int yIn) {
		if(!globeMouseAllowed()) return true;
		int	x, y;
		for(int i = 0; i < 16; i++) {
			x = xCenter + arrXPos[i] * radius + arrXOff[i] * arrGap;
			y = yCenter - (arrYPos[i] * radius + arrYOff[i] * arrGap);
			if(((xIn - x) * (xIn - x) + (yIn - y) * (yIn - y)) < 16) {
				if(repaintInProgress) return true;
				repaintInProgress = true;
				currAngleOne = incrAngle(currAngleOne, arrXRot[i] * arrType[i] * 10);
				currAngleTwo = incrAngle(currAngleTwo, arrYRot[i] * arrType[i] * 10);
				transformGrid();
				globeJustRotated();
				drawBackground = true;
				repaintInProgress = false;
				repaint();
				return true;
			}
		}

		for(int i = 0; i < nPoints; i++) { // look for grid point selection
			if(intZ[i] >= 0) {
				x = xIn - intX[i];
				y = yIn - intY[i];
				if((x * x + y * y) < 16) {
					globePointSelected(i, e.modifiers & Event.META_MASK);
					return true;
				}
			}
		}	
		return false;
	}

	private void calculateOffsets() {
		Dimension d = size();
		if(d.width > d.height) {
			xOff = 1 + (d.width - d.height) / 2;
			yOff = 1;
		}
		else {
			xOff = 1;
			yOff = 1 + (d.height - d.width) / 2;
		}
		diameter = d.width - 2 * xOff;	
		radius = diameter / 2;
		xCenter = xOff + radius;
		yCenter = yOff + radius;
		xOff	+= 5;
		yOff 	+= 5;
		radius 	-= 5;
	}

	public void paint(Graphics g) {
		update(g);
	}

	public void rotate() {
		if(!iconsCopied) {
			repaint();
			return;
		}
		if(repaintInProgress) return;
		repaintInProgress = true;
		currAngleOne = incrAngle(currAngleOne, 10);
		currAngleTwo = incrAngle(currAngleTwo, 10);
		transformGrid();
		globeJustRotated();
		drawBackground = true;
		repaintInProgress = false;
		repaint();
	}

	private int incrAngle(int a, int incr) {
		if((a + incr) >= 360) return a + incr - 360; 
		if((a + incr) < -360) return a + incr + 360; 
		return a + incr;
	}

	private void drawGrid() {
		int k;
		for(int i = 0; i < nPoints; i++) {
			if(intZ[i] >= 0) globeInitDisplayPoint(o, i);
		}
		for(int i = 0; i < nPoints; i++) {
			if(intZ[i] >= 0) {
				for(int j = 0; j < 6; j++) {
					k = pntNeighbour[i][j];
					if(k > i) {
						if(intZ[k] >= 0) {
							globeDrawLine(o, i, k);
						}
					}
				}
			}
		}
		for(int i = 0; i < nPoints; i++) {
			if(intZ[i] >= 0) globeDisplayPoint(o, i);
		}
	}

	void globeDrawLine(Graphics o, int i, int j) {
		o.setColor(Color.red);
		if((intZ[i] >= 0) && (intZ[j] >=0)) 
			o.drawLine(intX[i], intY[i], intX[j], intY[j]);
	}

	int currAngleOne = 0;
	int currAngleTwo = 0;

	private void transformGrid() {

		double	x, y, z;
		double	r, a; // radius, final angle
		for(int i = 0; i < nPoints; i++) {
			x = dblX[i]; y = dblY[i]; z = dblZ[i];
			r = Math.sqrt((double)(x * x + z * z));
			a = getNewAngle(x, r, z, currAngleOne);
			x = r * Math.cos(a);
			z = r * Math.sin(a);

			r = Math.sqrt((double)(y * y + z * z));
			a = getNewAngle(y, r, z, currAngleTwo);

			dblX[i] = x;
			dblY[i] = r * Math.cos(a);
			dblZ[i] = r * Math.sin(a);

			intX[i] = xCenter + (int)dblX[i];
			intY[i] = yCenter - (int)dblY[i];
			intZ[i] = (int)dblZ[i];
		}
		currAngleOne = 0;
		currAngleTwo = 0;
	}

	private double getNewAngle(double point, double r, double dir, int delta) {
		double t;
		if(r == 0.0) { // avoid zero divide
			t = 0.0;
		}
		else {
			t = Math.acos((double)point / r);
		}
		if(dir < 0) t = - t;
		return angle(t, delta);
	}

	private double angle(double a, int rot) { // add angle in radian to angle in degrees
		if(rot == 0) return a;
		double retValue = a + ((double)(rot)) * Math.PI / 180.0;
		if(retValue > Math.PI) return retValue - 2 * Math.PI;
		return retValue;
	}
		
	int	nPoints = 6;
	private int[][]	pointInit = {
		//      X        Y      Z    
		{	100,	0,	0},
		{	-100,	0,	0},
		{	0,	100,	0},
		{	0,	-100,	0},
		{	0,	0,	100},
		{	0,	0,	-100}
	};

	// for calculating the grid
	private double[] 	pntX;
	private double[] 	pntY;
	private double[] 	pntZ;
	// for calculating the rotation
	private double[] 	dblX;
	private double[] 	dblY;
	private double[] 	dblZ;
	// final position after rotation
	int[]		intX; 
	int[]		intY;
	int[]		intZ;
	int[][] 	pntNeighbour;
	private int 		maxPoints = 1026;

	private int oldCalculatedLevel = -2; // I am sick and tired of setting variables to -1

	private void createGrid() {
		if(globeLevel != oldCalculatedLevel) {
			nPoints = 6;
			currAngleOne = 0;
			currAngleTwo = 0;
			for(int i = 0; i < nPoints; i++) {
				pntX[i] = pointInit[i][0] * radius / 100;
				pntY[i] = pointInit[i][1] * radius / 100;
				pntZ[i] = pointInit[i][2] * radius / 100;
			} 
			findNeighbours();
			calculateAllPoints();
		}
		oldCalculatedLevel = globeLevel;
		for(int i = 0; i < nPoints; i++) {
			dblX[i] = pntX[i];
			dblY[i] = pntY[i];
			dblZ[i] = pntZ[i];
		}
		for(int i = 0; i < nPoints; i++) { // move to integer co-ordinates
			intX[i] = xCenter + (int)dblX[i];
			intY[i] = yCenter - (int)dblY[i];
			intZ[i] = (int)dblZ[i];
		}
		globeCreate();
	}

	private void calculateAllPoints() {
		int	currPoint;
		for(int i = 0; i < globeLevel; i++) {
			currPoint = nPoints;
			for(int j = 0; j < nPoints; j++) {
				for(int k = 0; k < 6; k++) {
					if(pntNeighbour[j][k] > j) {
						calculateMidPoint(j, pntNeighbour[j][k], currPoint);
						currPoint++;
					}
				}
			}
			nPoints = currPoint;
			findNeighbours();
		}
	}

	private void calculateMidPoint(int i, int j, int index) {
		double xC = (pntX[i] + pntX[j]) / 2;
		double yC = (pntY[i] + pntY[j]) / 2;
		double zC = (pntZ[i] + pntZ[j]) / 2;
		double rCenter = Math.sqrt((double)(xC * xC + yC * yC + zC * zC));
		double beta = Math.asin(zC / rCenter); // angle from x-y plane
		// next line contains a range to make sure that the argument remains <= 1
		// Why is that required. Well to cut a long story short it is happening because
		// of round off errors
		double cosBeta = Math.cos(beta); // trying to speed up the things
		double temp = xC / ( rCenter * Math.abs(cosBeta));
		if (temp < - 1.0) temp = -1.0;
		if (temp > 1.0) temp = 1.0;
		double alpha = Math.acos(temp);
		if (yC < 0) alpha = - alpha;
		pntX[index] = radius * Math.cos(alpha) * cosBeta;
		pntY[index] = radius * Math.sin(alpha) * cosBeta;
		pntZ[index] = radius * Math.sin(beta);
	}

	private void findNeighbours() {
		double	x, y, z;
		double[] sortArr = new double[nPoints]; 
		double highVal = 1000.0 * 1000.0; // should be greated than radius * radius 
		for(int i = 0; i < nPoints; i++) {
			for(int j = 0; j < nPoints; j++) {
				if (i != j) { // avoid being neighbour of yourself. you dummy!
					x = pntX[i] - pntX[j];
					y = pntY[i] - pntY[j];
					z = pntZ[i] - pntZ[j];
					sortArr[j] = x * x + y * y + z * z;
				}
				else {
					sortArr[j] = highVal; // high value
				}
			}
			for(int k = 0; k < 6; k++) {
				int selIndex = 0;
				for(int n = 1; n < nPoints; n++) {
					if(sortArr[n] < sortArr[selIndex]) selIndex = n;
				}
				pntNeighbour[i][k] = selIndex;
				sortArr[selIndex] = highVal; // move high value
			}
			if(i < 6) { // first six points can have only four neighbours
				pntNeighbour[i][4] = -1;
				pntNeighbour[i][5] = -1;
			}
		}
	}

}

