package mobius;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import graphics3d.*;
import java.util.Vector;

public class StereographicProjection extends JPanel implements ActionListener {
    BufferedImage background;
    int width = 0, height = 0;
    Graphics3d g3d;
    double time = 0;
    double dtime = 2*Math.PI/100;
    Vector planePoints, spherePoints;
    javax.swing.Timer timer;
    double circleX, circleY, circleR;
    public StereographicProjection(double cx, double cy, double cr) {
	super();
	setBackground(Color.white);
	circleX = cx;  circleY = cy;  circleR = cr;

	timer = new javax.swing.Timer(30, this);
	reset();

	g3d = new Graphics3d();
	g3d.setPixCenter(290, 200);
	g3d.setPixScale(150, 150);
	g3d.setEye(new double[] {0, 0, 9, 1});
	//	g3d.translate(0, 1, 0);
	g3d.rotateX(-90);
	g3d.rotateZ(-80);
	g3d.rotateY(-20);
    }

    public void paintComponent(Graphics gfx) {
	super.paintComponent(gfx);
	Graphics2D g = (Graphics2D) gfx;
	Dimension d = getSize();
	if (background == null || width != d.width || height != d.height) {
	    width = d.width;  height = d.height;
	    background = new BufferedImage(width, height,
					   BufferedImage.TYPE_4BYTE_ABGR);
	    setUpBackground();
	}

	g.drawImage(background, 0, 0, this);
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			   RenderingHints.VALUE_ANTIALIAS_ON);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
			   RenderingHints.VALUE_STROKE_PURE);
	g3d.setGraphics2D(g);
	
	//	g3d.setPaint(new Color(0.2f, 0.2f,0.2f));
	g3d.setPaint(new Color(0.2f, 0.2f, 0.2f));
	g3d.setStroke(new BasicStroke(1f));
	double[] ppoint = null;
	g3d.newpath();
	for (int i = 0; i < planePoints.size(); i++) {
	    ppoint = (double[]) planePoints.elementAt(i);
	    if (i == 0) g3d.moveto(ppoint[0], ppoint[1], 0);
	    else g3d.lineto(ppoint[0], ppoint[1], 0);
	}
	g3d.draw();

	g.setPaint(new Color(0.7f, 0.7f, 0.9f));
	g3d.newpath();
	double[] spoint = 
	    (double[]) spherePoints.elementAt(spherePoints.size()-1);
	g3d.moveto(spoint[0], spoint[1], spoint[2]);
	g3d.lineto(0, 0, 1);
	g3d.draw();
	

	g3d.setPaint(new Color(0.2f, 0.2f, 0.2f));
	g3d.newpath();
	for (int i = 0; i < spherePoints.size(); i++) {
	    spoint = (double[]) spherePoints.elementAt(i);
	    if (i == 0) g3d.moveto(spoint[0], spoint[1], spoint[2]);
	    else g3d.lineto(spoint[0], spoint[1], spoint[2]);
	}
	g3d.draw();

	g3d.setPaint(Color.black);
	g3d.newpath();
	g3d.moveto(ppoint[0], ppoint[1],0);
	g3d.lineto(spoint[0], spoint[1], spoint[2]);
	g3d.draw();
	
	
    }

    public double[] circle(double t) {
	return new double[] {circleX + circleR*Math.cos(t),
			     circleY + circleR*Math.sin(t) };
    }

    public void actionPerformed(ActionEvent ae) {
	time += dtime;
	if (time > 2*Math.PI) {
	    timer.stop();
	    return;
	}
	double[] p = circle(time);
	planePoints.addElement(p);
	spherePoints.addElement(p(p[0], p[1]));
	repaint();
    }

    public void setUpBackground() {
	Graphics2D g2 = (Graphics2D) background.getGraphics();
	g3d.setGraphics2D(g2);
	//	g2.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_SQUARE,
	//				     BasicStroke.JOIN_MITER, 2));
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			   RenderingHints.VALUE_ANTIALIAS_ON);
	g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
			   RenderingHints.VALUE_STROKE_PURE);

	// draw grid
	float s = 4;
	float ds = 0.25f;
	g3d.setColor(Color.lightGray);
	g3d.newpath();
	for (float i = -s; i <= s; i+=ds) {
	    g3d.moveto(i, -s, 0);
	    g3d.lineto(i, s, 0);
	    g3d.moveto(-s, i, 0);
	    g3d.lineto(s, i, 0);
	}
	g3d.draw();

	g3d.setColor(Color.gray);
	g3d.newpath();
	ds = 1;
	for (float i = -s; i <= s; i+=ds) {
	    g3d.moveto(i, -s, 0);
	    g3d.lineto(i, s, 0);
	    g3d.moveto(-s, i, 0);
	    g3d.lineto(s, i, 0);
	}
	g3d.draw();

	// draw x-axis
	g3d.setColor(Color.red);
	g3d.newpath();
	g3d.moveto(0, 0, 0);
	g3d.lineto(4.2, 0, 0);
	g3d.draw();

	// draw y-axis
	g3d.setColor(Color.green);
	g3d.newpath();
	g3d.moveto(0, 0, 0);
	g3d.lineto(0, 4.2, 0);
	g3d.draw();

	g3d.translate(0, 0, 0.5);
	g3d.setFillColor(new Color(0.5f, 0.5f, 1f));
	g3d.setLightDirection(new double[] {-1, 2, 1.5, 0});
	g3d.setShadingFunction(new double[] {0, 0.5, 0.5, 1});
	g3d.setStroke(new BasicStroke(1.4f, BasicStroke.CAP_SQUARE,
				    BasicStroke.JOIN_MITER, 1f));
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			    RenderingHints.VALUE_ANTIALIAS_OFF);
	g3d.drawSurfaceWithGouraudShading(Parametrization.SPHERE, 
					new double[] {0.5},
			    0, -Math.PI/2, 2*Math.PI, Math.PI/2, 20, 20);

	//	g3d.setPaint(Color.gray);
	g3d.setStroke(new BasicStroke(1f));
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			    RenderingHints.VALUE_ANTIALIAS_ON);
	g3d.drawSurfaceOutline(Parametrization.SPHERE, new double[] {0.5},
			       0, -Math.PI/2, 2*Math.PI, Math.PI/2, 20, 20);
	//, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 1f));
	g3d.translate(0, 0, -0.5);

    }

    public void start() {
	if (!timer.isRunning()) timer.start();
    }

    public void reset() {
	if (timer.isRunning()) timer.stop();
	time = 0;
	planePoints = new Vector();
	double[] p = circle(time);
	planePoints.addElement(p);
	spherePoints = new Vector();
	spherePoints.addElement(p(p[0], p[1]));
	repaint();
    }

    public double[] p(double x, double y) {
	double l = 1+x*x+y*y;
	return new double[] { x/l, y/l, (l-1)/l };
    }

    static StereographicProjection sp;
    public static void main(String[] args) {
	double circleX = 1.25;
	double circleY = -0.6;
	double circleR = 0.75;
	sp = new StereographicProjection(circleX, circleY, circleR);
	sp.setPreferredSize(new Dimension(400, 400));

	JButton start = new JButton("Start");
	JButton reset = new JButton("Reset");
	//	start.addActionListener(rotate);

	start.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    sp.start();
		}
	    });

	reset.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    sp.reset();
		}
	    });

	JPanel bpanel = new JPanel();
	bpanel.add(start);
	bpanel.add(reset);

	JFrame frame = new JFrame("StereographicProjection");
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.getContentPane().add(sp, BorderLayout.CENTER);
	frame.getContentPane().add(bpanel, BorderLayout.SOUTH);
	frame.pack();
	frame.setVisible(true);
    }

}
	
