package spheretilings;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;

public class SpherePanel extends JPanel {
    static double phi = (1+Math.sqrt(5))/2;
    static double phim = -phi;
    static double phibar = (1-Math.sqrt(5))/2;
    static double phibarm =  -phibar;
    static final int OCTAHEDRON=0;
    static final int CUBE= 1;
    static final int ICOSAHEDRON=2;
    static final int TETRAHEDRON=3;
    static final int DODECAHEDRON=4;
    
    static double[][] cube = { 	{1, 1, 1}, 
                                { 1, 1, -1},
                                { 1, -1, 1 },
                                { 1, -1, -1 },
                                { -1, 1, 1 },
                                { -1, 1, -1 },
                                { -1, -1, 1 },
                                { -1, -1, -1 } };
    static int[][] cubefaces = { {1, 0, 2, 3},
                                 {4, 5, 7, 6},
                                 {0, 1, 5, 4},
                                 {3, 2, 6, 7},
                                 {2, 0, 4, 6},
                                 {1, 3, 7, 5}};

    static double[][] dodecahedron = {	{ 1,1,1 },
                                        { 1,1,-1},
                                        { 1,-1, 1},
                                        { 1,-1, -1},
                                        { -1, 1, 1 },
                                        { -1, 1, -1 },
                                        { -1, -1, 1 },
                                        { -1, -1, -1 },
                                        { phibarm, phi, 0 },
                                        { phibar, phi, 0 },
                                        { phibarm, phim, 0 },
                                        { phibar, phim, 0 },
                                        { phi, 0, phibarm },
                                        { phi, 0, phibar },
                                        { phim, 0, phibarm },
                                        { phim, 0, phibar },
                                        { 0, phibarm, phi },
                                        { 0, phibar, phi },
                                        { 0, phibarm, phim },
                                        { 0, phibar, phim } };

    static int[][] dodecfaces = { { 1,  8, 0, 12, 13 },
                                  { 4,  9, 5, 15, 14 },
                                  { 2, 10, 3, 13, 12 },
                                  { 7, 11, 6, 14, 15 },
                                  { 2, 12, 0, 16, 17 },
                                  { 1, 13, 3, 19, 18 },
                                  { 4, 14, 6, 17, 16 },
                                  { 7, 15, 5, 18, 19 },
                                  { 4, 16, 0,  8,  9 },
                                  { 2, 17, 6, 11, 10 },
                                  { 1, 18, 5,  9,  8 },
                                  { 7, 19, 3, 10, 11 } };

    static double[][] octahedron = { { 1, 0, 0 },
                                     { -1, 0, 0 },
                                     { 0, 1, 0 },
                                     { 0, -1, 0 },
                                     { 0, 0, 1 },
                                     { 0, 0, -1} };

    static int[][] octfaces = { { 0, 2, 4}, 
                                { 2, 0, 5 },
                                { 3, 0, 4 },
                                { 0, 3, 5 },
                                { 2, 1, 4 },
                                { 1, 2, 5 },
                                { 1, 3, 4 },
                                { 3, 1, 5 } };

    static double[][] tetrahedron = { { 1, 1, 1 },
                                      { 1, -1, -1 },
                                      { -1, 1, -1 },
                                      { -1, -1, 1 } };
    static int[][] tetfaces = { { 3, 2, 1},
                                { 2, 3, 0 },
                                { 1, 0, 3 },
                                { 0, 1, 2 } };

    static double[][] icosahedron = { { phi, 1, 0 },
                                      { phim, 1, 0 },
                                      { phi, -1, 0 },
                                      { phim, -1, 0 },
                                      { 1, 0, phi },
                                      { 1, 0, phim },
                                      {-1, 0, phi },
                                      {-1, 0, phim },
                                      {0, phi, 1 },
                                      {0, phim, 1},
                                      {0, phi, -1 },
                                      {0, phim, -1} };
    static int[][] icosfaces = { { 0, 8, 4 },
                                 { 0, 5, 10 },
                                 { 2, 4, 9 },
                                 { 2, 11, 5 },
                                 { 1, 6, 8 },
                                 { 1, 10, 7 },
                                 { 3, 9, 6 },
                                 { 3, 7, 11 },
                                 { 0, 10, 8 },
                                 { 1, 8, 10 },
                                 { 2, 9, 11 },
                                 { 3, 11, 9 },
                                 { 4, 2, 0 },
                                 { 5, 0, 2 },
                                 { 6, 1, 3 },
                                 { 7, 3, 1 },
                                 { 8, 6, 4 },
                                 { 9, 4, 6 },
                                 { 10, 5, 7 },
                                 { 11, 7, 5 } };
    
    Vector frontPoints, backPoints;
    Vector frontFaces, backFaces;
    Vector frontMarkedFace, backMarkedFace;
    double[][] stdPoints;
    double[][] points;
    int[][] faces;
    Coordinates coordinates;
    int width, height;
    double r = 2;
    double r2 = 2*r;
    static AffineTransform transform, inverse;
    Ellipse2D.Double border;
    double[] vdown;
    float backAlpha = 0.2f;
    float frontAlpha = 0.5f;
    Color backFillColor = new Color(0.4f, 0.4f, 1.0f, backAlpha);
    Color backFillColor0 = backFillColor;
    Color backFillColor1 = //new Color(0.3f, 0.3f, 0.9f, backAlpha);
	new Color(0f, 0f, 0f, backAlpha);
    Color frontFillColor = new Color(0.4f, 0.4f, 1.0f, frontAlpha);
    Color frontFillColor0 = frontFillColor;
    Color frontFillColor1 = //new Color(0.3f, 0.3f, 0.9f, frontAlpha);
	new Color(0f, 0f, 0f, frontAlpha);
    Color backMarkedFillColor = new Color(1f, 0.2f, 0.2f, backAlpha);
    Color backMarkedFillColor0 = backMarkedFillColor;
    Color backMarkedFillColor1 = //new Color(0.9f, 0.15f, 0.15f, backAlpha);
	new Color(0f, 0f, 0f, backAlpha);
    Color frontMarkedFillColor = new Color(1f, 0.2f, 0.2f, frontAlpha);
    Color frontMarkedFillColor0 = frontMarkedFillColor;
    Color frontMarkedFillColor1 = //new Color(0.9f, 0.15f, 0.15f, frontAlpha);
	new Color(0f, 0f, 0f, frontAlpha);
    Color backStrokeColor = new Color(0f, 0f, 0f, backAlpha);
    Color frontStrokeColor = new Color(0f, 0f, 0f, frontAlpha);
    int markedFace = 4;
    double[] source;
    IlluminatedPaint backFillPaint, backMarkedFillPaint,
	frontFillPaint, frontMarkedFillPaint;
    boolean illuminated = false;

    public SpherePanel(boolean b) {
	setBackground(Color.white);
	illuminated = b;
	border = new Ellipse2D.Double(-1, -1, 2, 2);
	points = dodecahedron;
	faces  = dodecfaces;
	/*
	points = icosahedron;
	faces  = icosfaces;
	*/
	for (int i = 0; i < points.length; i++) 
	    points[i] = normalize(points[i]);
	stdPoints = new double[points.length][];
	coordinates = new Coordinates();
	configure();

	source = new double[] {-2, 1, 2};
	double l = Math.sqrt(source[0]*source[0] + source[1]*source[1]
			     + source[2]*source[2]);
	source[0] /= l;  source[1] /= l;  source[2] /= l;
	
	backFillPaint = new IlluminatedPaint(source, -1, 
				 backFillColor0, backFillColor1);
	frontFillPaint = new IlluminatedPaint(source, 1,
				 frontFillColor0, frontFillColor1);
	backMarkedFillPaint = new IlluminatedPaint(source, -1, 
				 backMarkedFillColor0, backMarkedFillColor1);
	frontMarkedFillPaint = new IlluminatedPaint(source, 1,
				 frontMarkedFillColor0, frontMarkedFillColor1);
	
	addMouseListener(new MouseAdapter() {
		public void mousePressed(MouseEvent me) {
		    vdown = coordinates.untransform(fromPix(me.getX(),
							    me.getY()));
		}
		public void mouseReleased(MouseEvent me) {
		    double[] vdrag = 
			coordinates.untransformLast(fromPix(me.getX(),
							    me.getY()));
		    coordinates.rotate(vdown, vdrag);
		    repaint();
		}

	    });
	addMouseMotionListener(new MouseMotionAdapter() {
		public void mouseDragged(MouseEvent me) {
		    double[] vdrag = 
			coordinates.untransformLast(fromPix(me.getX(),
							    me.getY()));
		    coordinates.rotateLast(vdown, vdrag);
		    repaint();
		}
	    });
    }

    public double[] fromPix(int x, int y) {
	Point2D.Double point = 
	    new Point2D.Double(x, y);
	inverse.transform(point, point);
	double[] r = new double[3];
	r[0] = point.x;  r[1] = point.y;
	double lp = r[0]*r[0] + r[1]*r[1];
	if (lp > 1) {
	    lp = Math.sqrt(lp);
	    r[0] /= lp;
	    r[1] /= lp;
	    r[2] = 0;
	} else r[2] = Math.sqrt(1-lp);
	return r;
    }

    public double[] normalize(double[] x) {
	double[] r = new double[3];
	double l = Math.sqrt(x[0]*x[0] + x[1]*x[1] + x[2]*x[2]);
	for (int i = 0; i < 3; i++) r[i] = x[i] / l;
	return r;
    }

    public void paintComponent(Graphics gfx) {
        super.paintComponent(gfx);
        Graphics2D g = (Graphics2D) gfx;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);
	Rectangle rect = getBounds();
	if (transform == null || 
	    width != rect.width || height != rect.height) {
	    width = rect.width;  height = rect.height;
	    transform = new AffineTransform();
	    int minDim = (int) Math.min(width, height);
	    double mathWidth = 2.2;
            float ratio = (float) (minDim / mathWidth );
            transform = new AffineTransform();
            transform.scale(ratio, -ratio);
            transform.translate(mathWidth / 2, -mathWidth / 2);
	    inverse = new AffineTransform();
	    try {
		inverse = transform.createInverse();
	    } catch(NoninvertibleTransformException ex) {
		System.err.println("error");
	    }
	}
	configure();
	
	for (int i = 0; i < backFaces.size(); i++) {
	    GeneralPath path = (GeneralPath) backFaces.elementAt(i);
	    if (illuminated) g.setPaint(backFillPaint);
	    else g.setPaint(backFillColor);
	    g.fill(transform.createTransformedShape(path));
	    g.setPaint(backStrokeColor);
	    g.draw(transform.createTransformedShape(path));
	}
	if (backMarkedFace.size() > 0) {
	    GeneralPath path = (GeneralPath) backMarkedFace.elementAt(0);
	    if (illuminated) g.setPaint(backMarkedFillPaint);
	    else g.setPaint(backMarkedFillColor);
	    g.fill(transform.createTransformedShape(path));
	    g.setPaint(backStrokeColor);
	    g.draw(transform.createTransformedShape(path));
	}
	    
	for (int i = 0; i < frontFaces.size(); i++) {
	    GeneralPath path = (GeneralPath) frontFaces.elementAt(i);
	    if (illuminated) g.setPaint(frontFillPaint);
	    else g.setPaint(frontFillColor);
	    g.fill(transform.createTransformedShape(path));
	    g.setPaint(frontStrokeColor);
	    g.draw(transform.createTransformedShape(path));
	}
	if (frontMarkedFace.size() > 0) {
	    GeneralPath path = (GeneralPath) frontMarkedFace.elementAt(0);
	    if (illuminated) g.setPaint(frontMarkedFillPaint);
	    else g.setPaint(frontMarkedFillColor);
	    g.fill(transform.createTransformedShape(path));
	    g.setPaint(backStrokeColor);
	    g.draw(transform.createTransformedShape(path));
	}

	g.draw(transform.createTransformedShape(border));
    }

    public void configure() {
	frontPoints = new Vector();  backPoints = new Vector();
	frontFaces  = new Vector();  backFaces  = new Vector();
	frontMarkedFace = new Vector();  backMarkedFace = new Vector();
	for (int i = 0; i < stdPoints.length; i++) {
	    stdPoints[i] = coordinates.transform(points[i]);
	    if (stdPoints[i][2] < 0) 
		backPoints.addElement(new Point2D.Double(stdPoints[i][0],
							 stdPoints[i][1]));
	    else frontPoints.addElement(new Point2D.Double(stdPoints[i][0],
							   stdPoints[i][1]));
	}
	//	for (int i = 0; i < faces.length; i++) {
	for (int i = 0; i < faces.length; i++) 
	    if (i != markedFace) processFace(frontFaces, backFaces, i);
	processFace(frontMarkedFace, backMarkedFace, markedFace);
    }
    
    public void processFace(Vector v1, Vector v2, int i) {
	int[] face = faces[i];
	Vector front = new Vector();
	Vector back  = new Vector();
	for (int j = 0; j < face.length; j++) {
	    double[] p0 = stdPoints[face[j]];
	    double[] p1 = stdPoints[face[mod((j+1), face.length)]];
	    if (p0[2] > 0) {
		front.addElement(p0);
		if (p1[2] < 0) addSplitPoint(front, back, p0, p1);
	    }
	    else if (p0[2] < 0) {
		back.addElement(p0);
		if (p1[2] > 0) addSplitPoint(front, back, p0, p1);
	    }
	    else {
		double prev = stdPoints[face[mod((j-1), face.length)]][2];
		double next = p1[2];
		if (prev * next >= 0) {
		    if (prev + next > 0) front.add(p0);
		    else back.add(p0);
		} else {
		    front.add(p0);
		    back.add(p0);
		}
	    }
	}
	if (front.size() > 0) {
	    GeneralPath faceFront = new GeneralPath();
	    double[] p0 = (double[]) front.elementAt(0);
	    faceFront.moveTo((float) p0[0], (float) p0[1]);
	    for (int k = 0; k < front.size(); k++) {
		p0 = (double[]) front.elementAt(k);
		double[] p1 = 
		    (double[]) front.elementAt(mod((k+1),front.size()));
		faceFront.append(getArc(p0, p1), true);
	    }
	    faceFront.closePath();
	    v1.addElement(faceFront);
	}
	
	if (back.size() > 0) {
	    GeneralPath faceBack = new GeneralPath();
	    double[] p0 = (double[]) back.elementAt(0);
	    faceBack.moveTo((float) p0[0], (float) p0[1]);
	    for (int k = 0; k < back.size(); k++) {
		p0 = (double[]) back.elementAt(k);
		double[] p1 = 
		    (double[]) back.elementAt(mod((k+1),back.size()));
		faceBack.append(getArc(p0, p1), true);
	    }
	    faceBack.closePath();
	    v2.addElement(faceBack);
	}
    }

    public int mod(int a, int n) {
	if (n == 0) return a;
	while(a < 0) a += n;
	while(a >= n) a -= n;
	return a;
    }

    public void addSplitPoint(Vector v1, Vector v2, double[] p, double[] q) {
	double[] normal = Coordinates.cross(p, q);
	double[] r = new double[3];
	r[0] = normal[1];  r[1] = -normal[0];  r[2] = 0;
	r = normalize(r);
	if (Coordinates.dot(r , p) < 0 || Coordinates.dot(r, q) < 0) 
	    for (int i = 0; i < 3; i++) r[i] *= -1;
	v1.addElement(r);  v2.addElement(r); 
    } 

    public Shape getArc(double[] p0, double[] p1) {
	double[] normal = Coordinates.cross(p0, p1);
	double[] v = normalize(Coordinates.cross(normal, p0));
	double x = Coordinates.dot(p1, p0);
	double y = Coordinates.dot(p1, v);
	double angle = -Math.toDegrees(Math.atan2(y, x));
	Arc2D.Double arc = new Arc2D.Double(-1, -1, 2, 2, 0, angle, Arc2D.OPEN);
	AffineTransform tform = 
	    new AffineTransform(p0[0], p0[1], v[0], v[1], 0, 0);
	return tform.createTransformedShape(arc);
    }


    public static void main(String[] args) {
	//        JPEGDrawFrame frame = new JPEGDrawFrame("SpherePanel");
	PSDrawFrame frame = new PSDrawFrame("SpherePanel");
	boolean illuminated = false;
	try {
	    if (args[0].equals("illuminated")) illuminated = true;
	} catch(Exception ex) { }
        SpherePanel panel = new SpherePanel(illuminated);
        panel.setPreferredSize(new Dimension(400, 400));
        frame.getContentPane().add(panel, BorderLayout.CENTER);

        frame.pack();
        frame.show();
    }

}
	    
	
    
