/* Author: David Anson */

import java.applet.Applet;
import java.awt.*;

public final class FigureRotationApplet extends Applet
	{
	private RotationThread Rotator = null;
	private PointsDisplay Points = new PointsDisplay();
	private FigureDisplay Figure = new FigureDisplay();
	private Choice NumberOfPoints = new Choice(), NumberOfSlices = new Choice();
	private Scrollbar Distance = null, Theta = null, Phi = null, Rotation = null;

	public void init()
		{
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets = new Insets(1, 1, 1, 1);
		GridBagLayout gbl = new GridBagLayout();
		setLayout(gbl);
		Label TempLabel;
		Panel TempPanel;
		TempLabel = new Label("Control Points");
		setupConstraints(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(TempLabel, gbc);
		add(TempLabel);
		setupConstraints(gbc, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(Points, gbc);
		add(Points);
		TempLabel = new Label("Figure of Rotation");
		setupConstraints(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(TempLabel, gbc);
		add(TempLabel);
		setupConstraints(gbc, 1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(Figure, gbc);
		add(Figure);
		TempPanel = new Panel();
		TempLabel = new Label("Number of Points:", Label.RIGHT);
		TempPanel.add(TempLabel);
		NumberOfPoints.addItem("2");
		NumberOfPoints.addItem("3");
		NumberOfPoints.addItem("4");
		NumberOfPoints.addItem("5");
		NumberOfPoints.addItem("6");
		NumberOfPoints.addItem("7");
		NumberOfPoints.addItem("8");
		NumberOfPoints.addItem("9");
		NumberOfPoints.addItem("10");
		NumberOfPoints.select(8);
		TempPanel.add(NumberOfPoints);
		setupConstraints(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(TempPanel, gbc);
		add(TempPanel);
		TempPanel = new Panel();
		TempLabel = new Label("Number of Slices:", Label.RIGHT);
		TempPanel.add(TempLabel);
		NumberOfSlices.addItem("3");
		NumberOfSlices.addItem("4");
		NumberOfSlices.addItem("5");
		NumberOfSlices.addItem("6");
		NumberOfSlices.addItem("7");
		NumberOfSlices.addItem("8");
		NumberOfSlices.addItem("9");
		NumberOfSlices.addItem("10");
		NumberOfSlices.addItem("11");
		NumberOfSlices.addItem("12");
		NumberOfSlices.addItem("13");
		NumberOfSlices.addItem("14");
		NumberOfSlices.addItem("15");
		NumberOfSlices.select(4);
		TempPanel.add(NumberOfSlices);
		setupConstraints(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE);
		gbl.setConstraints(TempPanel, gbc);
		add(TempPanel);
		TempPanel = new Panel();
		GridBagLayout pgbl = new GridBagLayout();
		TempPanel.setLayout(pgbl);
		TempLabel = new Label("Distance: ", Label.RIGHT);
		setupConstraints(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE);
		pgbl.setConstraints(TempLabel, gbc);
		TempPanel.add(TempLabel);
		Distance = new Scrollbar(Scrollbar.HORIZONTAL, 8, 1, 5, 20);  // Can not let image go behind the camera (no clipping)
		Distance.setPageIncrement(5);
		setupConstraints(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
		pgbl.setConstraints(Distance, gbc);
		TempPanel.add(Distance);
		TempLabel = new Label("Theta: ", Label.RIGHT);
		setupConstraints(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE);
		pgbl.setConstraints(TempLabel, gbc);
		TempPanel.add(TempLabel);
		Theta = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, -180, 180);
		Theta.setPageIncrement(10);
		setupConstraints(gbc, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
		pgbl.setConstraints(Theta, gbc);
		TempPanel.add(Theta);
		TempLabel = new Label("Phi: ", Label.RIGHT);
		setupConstraints(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE);
		pgbl.setConstraints(TempLabel, gbc);
		TempPanel.add(TempLabel);
		Phi = new Scrollbar(Scrollbar.HORIZONTAL, 30, 1, -90, 90);
		Phi.setPageIncrement(10);
		setupConstraints(gbc, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
		pgbl.setConstraints(Phi, gbc);
		TempPanel.add(Phi);
		TempLabel = new Label("Rotation: ", Label.RIGHT);
		setupConstraints(gbc, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE);
		pgbl.setConstraints(TempLabel, gbc);
		TempPanel.add(TempLabel);
		Rotation = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, -180, 180);
		Rotation.setPageIncrement(10);
		setupConstraints(gbc, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
		pgbl.setConstraints(Rotation, gbc);
		TempPanel.add(Rotation);
		setupConstraints(gbc, 0, 3, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
		gbl.setConstraints(TempPanel, gbc);
		add(TempPanel);
		}

	private void setupConstraints(GridBagConstraints gbc, int x, int y, int width, int height, double weightx, double weighty, int anchor, int fill)
		{
		gbc.gridx = x;
		gbc.gridy = y;
		gbc.gridwidth = width;
		gbc.gridheight = height;
		gbc.weightx = weightx;
		gbc.weighty = weighty;
		gbc.anchor = anchor;
		gbc.fill = fill;
		}

	public void start()
		{
		Figure.createImages();
		Rotator = new RotationThread(this, Points, Figure);
		Points.setRotationThread(Rotator);
		Rotator.start();
		handleEvent(new Event(Distance, Event.SCROLL_ABSOLUTE, null));  // Force update by faking an event
		action(new Event(NumberOfPoints, 0, null), null);
		}
	
	public void stop()
		{
		Points.setRotationThread(null);
		Rotator.stop();
		Rotator = null;
		}

	public boolean action(Event event, Object object)
		{
		if((event.target == NumberOfPoints) || (event.target == NumberOfSlices))
			{
			Rotator.setNumberOfs(NumberOfPoints.getSelectedIndex()+2, NumberOfSlices.getSelectedIndex()+3);
			return true;
			}
		return super.action(event, object);
		}

	public boolean handleEvent(Event event)
		{
		if((event.id == Event.SCROLL_ABSOLUTE) || (event.id == Event.SCROLL_LINE_DOWN) || (event.id == Event.SCROLL_LINE_UP) || (event.id == Event.SCROLL_PAGE_DOWN) || (event.id == Event.SCROLL_PAGE_UP))
			{
			if(event.target instanceof Scrollbar)
				{
				Rotator.setViewParams(((double)Distance.getValue())/8.0, ((double)Theta.getValue())*(Math.PI/180.0), ((double)Phi.getValue())*(Math.PI/180.0), ((double)Rotation.getValue())*(Math.PI/180.0));
				return true;
				}
			}
		return super.handleEvent(event);
		}

	public String getAppletInfo()
		{
		return new String("FigureRotationApplet by David Anson\nCopyright (C) 1997");
		}
	}

final class RotationThread extends Thread
	{
	private static final int X = 0, Y = 1, Z = 2, CLICK_PRECISION = 4, MAX_POINTS = 10, MAX_SLICES = 15;
	private static final double d = 0.5;
	private boolean PointsUpdateNeeded = false, FigureUpdateNeeded = false;
	private double Distance = 2.0, Theta = 0.0, Phi = 0.0, Rotation = 0.0, NewDistance = 2.0, NewTheta = 0.0, NewPhi = 0.0, NewRotation = 0.0;
	private double[] C = {0.0, 0.0, 0.0}, V = {0.0, 0.0, 0.0}, N = {0.0, 0.0, 0.0}, U = {0.0, 0.0, 0.0};
	private FigureRotationApplet Parent = null;
	private PointsDisplay Points = null;
	private FigureDisplay Figure = null;
	private int PointsWidth = -1, PointsHeight = -1, FigureWidth = -1, FigureHeight = -1;
	private int ClickX = -1, ClickY = -1, ClickPoint = -1;
	private int NumberOfPoints = MAX_POINTS, NumberOfSlices = MAX_SLICES;
	private int[][] PointLocations = new int[MAX_POINTS][2];
	// All points must be within 0.5 of [0,0,0] to avoid clipping problems
	// World is right-handed

	RotationThread(FigureRotationApplet Parent, PointsDisplay Points, FigureDisplay Figure)
		{
		this.Parent = Parent;
		this.Points = Points;
		this.Figure = Figure;
		PointsWidth = Points.getDimension().width;
		PointsHeight = Points.getDimension().height;
		FigureWidth = Figure.getDimension().width;
		FigureHeight = Figure.getDimension().height;
		for(int CurPoint = 0 ; CurPoint < NumberOfPoints ; CurPoint++)
			{
			PointLocations[CurPoint][X] = PointsWidth/2;
			PointLocations[CurPoint][Y] = (int)((double)(PointsHeight-2)*((double)CurPoint/(double)(NumberOfPoints-1)))+1;
			}
		}

	public synchronized void setNumberOfs(int NumberOfPoints, int NumberOfSlices)
		{
		this.NumberOfPoints = NumberOfPoints;
		this.NumberOfSlices = NumberOfSlices;
		PointsUpdateNeeded = true;
		FigureUpdateNeeded = true;
		}
	
	public synchronized void setViewParams(double Distance, double Theta, double Phi, double Rotation)
		{
		this.NewDistance = Distance;
		this.NewTheta = Theta;
		this.NewPhi = Phi;
		this.NewRotation = Rotation;
		FigureUpdateNeeded = true;
		}

	private synchronized void copyParams()
		{
		Distance = NewDistance;
		Theta = NewTheta;
		Phi = NewPhi;
		Rotation = NewRotation;
		FigureUpdateNeeded = false;
		}

	public void run()
		{
		while(true)
			{
			if(PointsUpdateNeeded)
				drawPoints();
			if(FigureUpdateNeeded)
				{
				copyParams();
				drawFigure();
				}
			try
				{
				sleep(200);
				}
			catch(InterruptedException IE)
				{
				;
				}
			}
		}

	public void drawPoints()
		{
		drawPoints(Points.getGraphics());
		}

	public void drawPoints(Graphics g)
		{
		// Draw the control points
		g.setColor(Color.white);
		g.fillRect(1, 1, PointsWidth-2, PointsHeight-2);
		g.setColor(Color.black);
		g.drawRect(0, 0, PointsWidth-1, PointsHeight-1);
		g.drawRect(PointLocations[0][X]-3, PointLocations[0][Y]-3, 6, 6);
		for(int CurPoint = 1 ; CurPoint < NumberOfPoints ; CurPoint++)
			{
			g.drawRect(PointLocations[CurPoint][X]-3, PointLocations[CurPoint][Y]-3, 6, 6);
			g.drawLine(PointLocations[CurPoint-1][X], PointLocations[CurPoint-1][Y], PointLocations[CurPoint][X], PointLocations[CurPoint][Y]);
			}
		PointsUpdateNeeded = false;
		}

	private void drawFigure()
		{
		// Update the view parameters - View is left-handed
		double st = Math.sin(Theta);
		double ct = Math.cos(Theta);
		double sp = Math.sin(Phi);
		double cp = Math.cos(Phi);
		double sr = Math.sin(Rotation);
		double cr = Math.cos(Rotation);
		N[X] = -st*cp;
		N[Y] = -sp;
		N[Z] = -ct*cp;
		V[X] = -ct*sr-st*sp*cr;
		V[Y] = cp*cr;
		V[Z] = st*sr-ct*sp*cr;
		U[X] = ct*cr-st*sp*sr;
		U[Y] = cp*sr;
		U[Z] = -st*cr-ct*sp*sr;
		C[X] = -N[X]*Distance;
		C[Y] = -N[Y]*Distance;
		C[Z] = -N[Z]*Distance;

		// Prepare for drawing
		Graphics g = Figure.getBackGraphics();
		g.setColor(Color.black);
		g.fillRect(0, 0, FigureWidth, FigureHeight);
		g.setColor(Color.green);
		int[][][] FigureLocations = new int[MAX_SLICES][MAX_POINTS][2];
		// Draw the points according to the corrent viewpoint
		for(int CurSlice = 0 ; CurSlice < NumberOfSlices; CurSlice++)
			{
			for(int CurPoint = 0 ; CurPoint < NumberOfPoints ; CurPoint++)
				{
				double XDiff = 0.35*Math.cos(((double)CurSlice/(double)NumberOfSlices)*2*Math.PI)*((double)PointLocations[CurPoint][X]/(double)PointsWidth)-C[X];
				double YDiff = 0.7*((1.0-((double)PointLocations[CurPoint][Y]/(double)PointsHeight))-0.5)-C[Y];
				double ZDiff = 0.35*Math.sin(((double)CurSlice/(double)NumberOfSlices)*2*Math.PI)*((double)PointLocations[CurPoint][X]/(double)PointsWidth)-C[Z];
				double ZPos = N[X]*XDiff+N[Y]*YDiff+N[Z]*ZDiff;
				double XPos = ((U[X]*XDiff+U[Y]*YDiff+U[Z]*ZDiff)/(ZPos*d)+1.0)/2.0;
				double YPos = ((V[X]*XDiff+V[Y]*YDiff+V[Z]*ZDiff)/(ZPos*d)+1.0)/2.0;
				int XCoord = (int)(XPos*FigureWidth);
				int YCoord = FigureHeight-(int)(YPos*FigureHeight);
				FigureLocations[CurSlice][CurPoint][X] = XCoord;
				FigureLocations[CurSlice][CurPoint][Y] = YCoord;
				if(CurSlice > 0)
					g.drawLine(XCoord, YCoord, FigureLocations[CurSlice-1][CurPoint][X], FigureLocations[CurSlice-1][CurPoint][Y]);
				if(CurPoint > 0)
					g.drawLine(XCoord, YCoord, FigureLocations[CurSlice][CurPoint-1][X], FigureLocations[CurSlice][CurPoint-1][Y]);
				}
			}
		for(int CurPoint = 0 ; CurPoint < NumberOfPoints ; CurPoint++)
			g.drawLine(FigureLocations[0][CurPoint][X], FigureLocations[0][CurPoint][Y], FigureLocations[NumberOfSlices-1][CurPoint][X], FigureLocations[NumberOfSlices-1][CurPoint][Y]);
		Figure.swapImages();
		}

	public void userClick(int x, int y)
		{
		ClickX = x;
		ClickY = y;
		ClickPoint = -1;
		for(int CurPoint = 0 ; CurPoint < NumberOfPoints ; CurPoint++)
			{
			if((-CLICK_PRECISION <= ClickX-PointLocations[CurPoint][X]) && (ClickX-PointLocations[CurPoint][X] <= CLICK_PRECISION) && (-CLICK_PRECISION <= ClickY-PointLocations[CurPoint][Y]) && (ClickY-PointLocations[CurPoint][Y] <= CLICK_PRECISION))
				ClickPoint = CurPoint;
			}
		}

	public synchronized void userDrag(int x, int y)
		{
		if(ClickPoint != -1)
			{
			PointLocations[ClickPoint][X] = x;
			if(PointLocations[ClickPoint][X] < 0)
				PointLocations[ClickPoint][X] = 0;
			if(PointLocations[ClickPoint][X] > PointsWidth-1)
				PointLocations[ClickPoint][X] = PointsWidth-1;
			PointLocations[ClickPoint][Y] = y;
			if(PointLocations[ClickPoint][Y] < 0)
				PointLocations[ClickPoint][Y] = 0;
			if(PointLocations[ClickPoint][Y] > PointsHeight-1)
				PointLocations[ClickPoint][Y] = PointsHeight-1;
			PointsUpdateNeeded = true;
			FigureUpdateNeeded = true;
			}
		}
	}

final class PointsDisplay extends Canvas
	{
	private static final int DESIRED_SIZE = 250;
	private RotationThread Rotator = null;

	public void setRotationThread(RotationThread Rotator)
		{
		this.Rotator = Rotator;
		}
	
	public Dimension getDimension()
		{
		return size();
		}
	
	public boolean mouseDown(Event evt, int x, int y)
		{
		if(Rotator != null)
			{
			Rotator.userClick(x, y);
			return true;
			}
		else
			return false;
		}

	public boolean mouseDrag(Event evt, int x, int y)
		{
		if(Rotator != null)
			{
			Rotator.userDrag(x, y);
			return true;
			}
		else
			return false;
		}

	public void update(Graphics g)
		{
		paint(g);
		}

	public void paint(Graphics g)
		{
		if(Rotator != null)
			Rotator.drawPoints(g);
		}											  

	public Dimension minimumSize()
		{
		return new Dimension(DESIRED_SIZE, DESIRED_SIZE);
		}

	public Dimension preferredSize()
		{
		return new Dimension(DESIRED_SIZE, DESIRED_SIZE);
		}
	}

final class FigureDisplay extends Canvas
	{
	private static final int DESIRED_SIZE = 250;
	private Image ForeImage = null, BackImage = null;
	
	public void createImages()
		{
		ForeImage = createImage(size().width, size().height);
		BackImage = createImage(size().width, size().height);
		}
	
	public Graphics getBackGraphics()
		{
		return BackImage.getGraphics();
		}

	public void swapImages()
		{
		Image TempImage = ForeImage;
		ForeImage = BackImage;
		BackImage = TempImage;
		repaint();
		}

	public Dimension getDimension()
		{
		return size();
		}
	
	public void update(Graphics g)
		{
		paint(g);
		}

	public void paint(Graphics g)
		{
		if(ForeImage != null)
			g.drawImage(ForeImage, 0, 0, this);
		}											  

	public Dimension minimumSize()
		{
		return new Dimension(DESIRED_SIZE, DESIRED_SIZE);
		}

	public Dimension preferredSize()
		{
		return new Dimension(DESIRED_SIZE, DESIRED_SIZE);
		}
	}