/* 

    Toroidia
    Adventures in (super)toroidal geometry.
	
	
   This version © Fergus Crawshay Murray, February 2000.
   Any comments or suggestions to toroids@fergusmurray.co.uk.  */

import java.awt.*;
import java.applet.Applet;

public class Toroidia extends java.applet.Applet implements Runnable {

	// Main entry point when running standalone - note that at present this only runs as an applet...
	public static void main(String[] args) {
		Toroidia applet = new Toroidia();
		applet.isStandalone = true;
		Frame frame = new Frame();
		frame.addWindowListener(new java.awt.event.WindowAdapter() {
			public void windowClosing(java.awt.event.WindowEvent e) {
				Frame f = (Frame) e.getSource();
				f.setVisible(false);
				f.dispose();
				System.exit(0);
			}
		});
		frame.setTitle("Applet Frame");
		frame.add( applet, BorderLayout.CENTER );
		applet.init();
		applet.start();
		frame.setSize( 768, 576 );
		Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
		frame.setLocation( (d.width - frame.getSize().width) / 2,
			(d.height - frame.getSize().height) / 2);
		frame.setVisible( true );
	}
	
    
    // t is the convergence angle, a the roll angle, b the spin angle;
    // tF, aF and bF are the angles these are set to at the start of each frame.
    
    boolean isStandalone=false;
    double t, a, b, tF, aF, bF,
    s=150, r=1,x,y, spin=0, roll=0, conv=100, spinF, rollF, convF;
    float hue;
	int count, idx, i, leng;
    int drawer=1, c, ox, oy,
	xcentre=200,ycentre=200;
	double f,realx,realy,d,step=0.0001, symmetry=1; 
	float direction=1;                           
	double sineArray[]=new double[1025];
	Color colours[]=new Color[256];
	boolean clearnow=true;
	public int major=100,minor=64;
	public Scrollbar majorBar, minorBar, lengBar,
	convBar, rollBar, spinBar, convFBar, rollFBar, spinFBar;
	public TextField inputBox;
	public CheckboxGroup drawType, symmGroup;
	public Checkbox reverseBox;
	String theBox,symmName;	
	Image offscreenImg;
	Graphics offscreenGraphics;
	
public void init() {
	
	if(!isStandalone){
	offscreenImg=createImage(this.size().width,this.size().height);
	offscreenGraphics=offscreenImg.getGraphics();}
	//  Create lookup tables for colour and trig functions
	for (i=0; i<colours.length; i++){
		colours[i]=
		Color.getHSBColor(hue,(float)1.0,(float)1.0);
		hue+=0.0039;
	}
	for (i=0; i<sineArray.length;i++){
		sineArray[i]=Math.sin(i*Math.PI/512);
	}
	setBackground (Color.black);
	setLayout(new BorderLayout());
	Panel eastPanel = new Panel();
	add("East", eastPanel);
	eastPanel.setLayout(new GridLayout(28,1));
	eastPanel.setForeground (Color.white);
    eastPanel.setBackground (Color.black);
    eastPanel.add(new Label("Length"));
    lengBar=new Scrollbar (Scrollbar.HORIZONTAL,144,4,1,5555);
    eastPanel.add(lengBar);
	eastPanel.add(new Label("Major Radius"));
	majorBar=new Scrollbar (Scrollbar.HORIZONTAL,100,4,1,150);
	eastPanel.add(majorBar);
	eastPanel.add(new Label("Minor Radius"));
	minorBar=new Scrollbar (Scrollbar.HORIZONTAL,75,4,1,150);
	eastPanel.add(minorBar);
	eastPanel.add(new Panel());
	eastPanel.add(new Label("Convergence per step"));	
	convBar=new Scrollbar (Scrollbar.HORIZONTAL,1000,4,0,10000);
	eastPanel.add(convBar);
	eastPanel.add(new Label("Roll per step"));	
	rollBar=new Scrollbar (Scrollbar.HORIZONTAL,500,4,0,10000);	
	eastPanel.add(rollBar);
	eastPanel.add(new Label("Spin per step"));
	spinBar=new Scrollbar (Scrollbar.HORIZONTAL,5000,4,0,10000);
	eastPanel.add(spinBar);
	eastPanel.add(new Panel());
    eastPanel.add(new Label("Convergence per frame"));	
	convFBar=new Scrollbar (Scrollbar.HORIZONTAL,1500,4,0,10000);
	eastPanel.add(convFBar);
	eastPanel.add(new Label("Roll per frame"));	
	rollFBar=new Scrollbar (Scrollbar.HORIZONTAL,50,4,0,10000);	
	eastPanel.add(rollFBar);
	eastPanel.add(new Label("Spin per frame"));
	spinFBar=new Scrollbar (Scrollbar.HORIZONTAL,5000,4,0,10000);
	eastPanel.add(spinFBar);
	eastPanel.add(new Panel());
	drawType=new CheckboxGroup ();
	eastPanel.add (new Checkbox("Lines", drawType, false));
	eastPanel.add (new Checkbox("Dots", drawType, false));
	eastPanel.add (new Checkbox("2x2 squares", drawType, true));	
	eastPanel.add (new Checkbox("3x3 squares", drawType, false));
	eastPanel.add (new Checkbox("4x4 squares", drawType, false));
	eastPanel.add(new Panel());
	reverseBox=new Checkbox("Reverse", null, false);
	eastPanel.add (reverseBox);
	scrollUpdate();
	boxUpdate();		
	}	

public boolean handleEvent (Event evt) {
	if (evt.target instanceof Scrollbar) {
		scrollUpdate();
		return true;
	}
	else if (evt.id==Event.MOUSE_DOWN||evt.id==Event.MOUSE_DRAG){
		xcentre=evt.x;
		ycentre=evt.y;
		return true;
	}
	else if (evt.target instanceof Checkbox) {
		boxUpdate();
		return true;
	}
	return super.handleEvent(evt);
}

public void scrollUpdate(){
	major = majorBar.getValue();
	minor = minorBar.getValue();
	leng = lengBar.getValue();
	conv = convBar.getValue()*0.00001;
	roll = rollBar.getValue()*0.00001;
	spin = spinBar.getValue()*0.00001;
	convF = convFBar.getValue()*0.00005;
	rollF = rollFBar.getValue()*0.00005;
	spinF = spinFBar.getValue()*0.00005;	
}

public void boxUpdate(){
	theBox=drawType.getCurrent().getLabel();
	if(theBox=="Lines") drawer=0;
	if(theBox=="Dots") drawer=1;
	if(theBox=="2x2 squares") drawer=2;
	if(theBox=="3x3 squares") drawer=3;
	if(theBox=="4x4 squares") drawer=4;
	if(reverseBox.getState()==true) direction=-1;
	if(reverseBox.getState()==false) direction=1;
}	

Thread toroidThread;    	
public void start() {
	if (toroidThread == null) {
	    toroidThread = new Thread(this, "toroid");
	    toroidThread.start();
	    }
}
    
public void run() {
	while (toroidThread != null) {
	    repaint();
            try {
            toroidThread.sleep(10);
            } catch (InterruptedException e){        }
        }
}
    
public void paint (Graphics g) {
    offscreenGraphics.setColor(Color.black);
    offscreenGraphics.fillRect(0,0,1024,768);	
    t=tF;
    a=aF;
    b=bF;
    tF+=convF*direction;
    aF+=rollF*direction;
    bF+=spinF*direction;    
   	c=0;
    for (int count=0;count<leng;count++){
        t+=conv;
        a+=roll;
        b+=spin;
        c++;  
        x=xcentre+ cosine (b)*(major+minor*cosine (t));
        y=ycentre+ sine (b)*(major+minor*cosine (t))*sine(a) + cosine (a)*sine (t)*minor;
        if (count==0){ ox=(int)x; oy=(int)y; }
        draw((int)x,(int)y, ox,oy, drawer, offscreenGraphics);
        ox=(int)x;
        oy=(int)y;
    }
    g.drawImage(offscreenImg,0,0,this);
}

public void update (Graphics g) {
	paint (g);
}  

public void draw (int x,int y,int ox,int oy,int drawer,Graphics gr) {
    gr.setColor(colours[c%255]);
	if (drawer==0) 	gr.drawLine (x,y,ox,oy);
	else			gr.fillRect (x,y,drawer,drawer);
}

public double sine (double angle){
	while (angle<0) angle+=Math.PI*2;
	idx=((int)(angle*512/Math.PI))%1024;
	return sineArray[idx];
}

public double cosine (double angle){
	while (angle<0) angle+=Math.PI*2;
	idx=((int)(768+angle*512/Math.PI))%1024;
	return sineArray[idx];
}

public void stop() {
	toroidThread.stop();
	toroidThread = null;
}

public void destroy() {
    offscreenGraphics.dispose();
}
}

