/*

    Trochor
    Fun with trochoids.


   This version © Fergus Crawshay Murray, February 2000.
   Any comments or suggestions to fcm@oolong.co.uk.  */

import java.awt.*;
import java.applet.Applet;

public class Trochor extends java.applet.Applet implements Runnable {

    double t, a, b, tF, aF, bF,
    s=150, r=1,x,y, spin=0, roll=0, rota=100, spinF, rollF, phas;
    float hue, leng, ratio=3, damp, damping, size;
    int count, idx, i, length;
    int drawer=1, c, ox, oy,
    xcentre=200,ycentre=200;
    double f;
    double sineArray[]=new double[1025];
    Color colours[]=new Color[256];
    boolean clearnow=false;
    public int axOne=100,axTwo=64, axbOne, axbTwo, ecOne, ecTwo;
    public Scrollbar dampBar, axOneBar, axTwoBar, ecOneBar, ecTwoBar, ratioBar, rotaBar, phasBar, lengthBar;
    public TextField inputBox;
    public CheckboxGroup drawType, symmGroup;
    public Checkbox wipeBox;
    public Label ratioLabel, lengthLabel;
    String theBox,symmName;
    Image offscreenImg;
    Graphics offscreenGraphics, g, graphDest;

public void init() {

    //  Create lookup tables for colour and trig functions
    offscreenImg=createImage(this.size().width,this.size().height);
    offscreenGraphics=offscreenImg.getGraphics();
    g=getGraphics();
    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("Major Axis 1"));
    axOneBar=new Scrollbar (Scrollbar.HORIZONTAL,100,4,0,200);
    eastPanel.add(axOneBar);
    eastPanel.add(new Label("Eccentricity 1"));
    ecOneBar=new Scrollbar (Scrollbar.HORIZONTAL,0,4,0,104);
    eastPanel.add(ecOneBar);
    eastPanel.add(new Label("Major Axis 2"));
    axTwoBar=new Scrollbar (Scrollbar.HORIZONTAL,75,4,0,200);
    eastPanel.add(axTwoBar);
    eastPanel.add(new Label("Eccentricity 2"));
    ecTwoBar=new Scrollbar (Scrollbar.HORIZONTAL,0,4,0,104);
    eastPanel.add(ecTwoBar);
    ratioLabel=new Label("Ratio");
    eastPanel.add(ratioLabel);
    ratioBar=new Scrollbar (Scrollbar.HORIZONTAL,404,400,0,2040);
    eastPanel.add(ratioBar);
    eastPanel.add(new Label("Step size"));
    rotaBar=new Scrollbar (Scrollbar.HORIZONTAL,100,4,1,2000);
    eastPanel.add(rotaBar);
    eastPanel.add(new Label("Animation speed"));
    phasBar=new Scrollbar (Scrollbar.HORIZONTAL,500,4,0,2000);
    eastPanel.add(phasBar);
    lengthLabel=new Label("Length");
    eastPanel.add(lengthLabel);
    lengthBar=new Scrollbar (Scrollbar.HORIZONTAL,50,4,1,500);
    eastPanel.add(lengthBar);
    eastPanel.add(new Label("Damping"));
    dampBar=new Scrollbar (Scrollbar.HORIZONTAL,20,4,0,100);
    eastPanel.add(dampBar);
    eastPanel.add(new Panel());
    drawType=new CheckboxGroup ();
    eastPanel.add (new Checkbox("Lines", drawType, true));
    eastPanel.add (new Checkbox("Dots", drawType, false));
    eastPanel.add (new Checkbox("2x2 squares", drawType, false));
    eastPanel.add (new Checkbox("3x3 squares", drawType, false));
    eastPanel.add (new Checkbox("4x4 squares", drawType, false));
    eastPanel.add(new Panel());
    wipeBox=new Checkbox("Wipe each frame", null, true);
    eastPanel.add (wipeBox);
    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(){
    axOne = axOneBar.getValue();
    ecOne = ecOneBar.getValue();
    axTwo = axTwoBar.getValue();
    ecTwo = ecTwoBar.getValue();
    if (ecOne==0) axbOne=axOne;
    else axbOne = (int)((double)axOne*Math.sqrt(1-((float)(ecOne*ecOne)/10000)));
    if (ecTwo==0) axbTwo=axTwo;
    else axbTwo = (int)((double)axTwo*Math.sqrt(1-((float)(ecTwo*ecTwo)/10000)));
    ratio = (float)(ratioBar.getValue())/100;
    ratioLabel.setText("Ratio: " + ratio);
    length = lengthBar.getValue();
    leng = (float)(Math.PI*2*(float)length)/10;
    lengthLabel.setText("Turns: " + (float)length/10);
    rota = rotaBar.getValue()*0.0002;
    phas = phasBar.getValue()*0.00005;
    damp = (float)(dampBar.getValue());
    damping = 1-(damp*damp/500000);
    clearnow = true;
}

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(wipeBox.getState()==true) graphDest=offscreenGraphics;
    if(wipeBox.getState()==false) graphDest=g;
}

Thread trochoidThread;
public void start() {
    if (trochoidThread == null) {
        trochoidThread = new Thread(this, "trochoid");
        trochoidThread.start();
        }
}

public void run() {
    while (trochoidThread != null) {
        repaint();
        try {
        trochoidThread.sleep(10);
        } catch (InterruptedException e){        }
    }
}

// The real substance of the program is contained here, in the paint method.

public void paint (Graphics g) {
    if (graphDest==offscreenGraphics){
        offscreenGraphics.setColor(Color.black);
        offscreenGraphics.fillRect(0,0,1024,768);
    }
    if (clearnow==true){
        g.setColor(Color.black);
        g.fillRect(0,0,1024,768);
        clearnow=false;
    }
    c=0;
    f+=phas;
    size=1;
    for (t=0;t<leng;t+=rota){
        x=(int)(xcentre+size*(axbOne*cosine(t)+axTwo*cosine(t*ratio-f)));
        y=(int)(ycentre+size*(axOne*sine(t)+axbTwo*sine(t*ratio+f)));
        if (c==0){ ox=(int)x; oy=(int)y; }
        draw((int)x,(int)y, ox,oy, drawer, graphDest);
        ox=(int)x;
        oy=(int)y;
        c++;
        size*=damping;
    }
    if (graphDest==offscreenGraphics) 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() {
    trochoidThread.stop();
    trochoidThread = null;
}

public void destroy() {
    offscreenGraphics.dispose();
}
}
