// Modification history
// Nov. 3, impletemt double buffering
//-----------------------------------------------------
// written by Fu-Kwun Hwang
// Last modified date: Oct. 30, 1996
// I hope that you enjoy this applet
// Suggestions? Send E-mail to  hwang@phy03.phy.ntnu.edu.tw
//-----------------------------------------------------
import java.awt.*;

public class startButton extends java.applet.Applet{
        String buttonText="start";
        String windowTitle="Thin Lens demonstration by Fu-Kwun Hwang";
        int windowWidth = 600;
        int windowHeight = 300;
        MainWindow m;
        int windowCount=0;

        public void init() {
                String str;
                // get parameters
                if((str=getParameter("buttonText"))!=null)
                                buttonText=str;
                if((str=getParameter("windowTitle"))!=null)
                                windowTitle=str;
                if((str=getParameter("windowWidth"))!=null)
                        windowWidth=Integer.parseInt(str);
                if((str=getParameter("windowHeight"))!=null)
                        windowHeight=Integer.parseInt(str);
                if((str=getParameter("autoStart"))!=null){
                        m = new MainWindow(windowTitle, true);
                        go();
                }
                add(new Button("Start"));
        }

        private void go(){
                m.resize(windowWidth,windowHeight);
                m.show();
                m.start();
        }

        public boolean action(Event e,Object arg) {
                if (e.target instanceof Button &&
                                        ((String)arg).equals(buttonText)) {
                        m = new MainWindow(String.valueOf(++windowCount)+":"+
                                windowTitle, true);
                }
                go();
                return true;
        }
        
        //These methods allow the applet to
        //      also run as an application.
        public static void main(String args[]) {
                new startButton().begin();
        }

        private void begin() {
                m = new MainWindow(windowTitle, false);
                go();
        }
}

 
class MainWindow extends Frame {
        boolean is_applet;
        Animation anim;
        MainWindow(String title, boolean isapp){
                super(title);
                is_applet=isapp;
                anim=new Animation(this);
                add("Center",anim);
                setBackground(Color.white);
        }
        public void start(){
                anim.reset();
                repaint();
        }

        public boolean handleEvent(Event e) {
                if (e.id == Event.WINDOW_DESTROY) {
                        hide();
                        removeAll();
                        dispose();
                        if(!is_applet) System.exit(0);
                }   
                return super.handleEvent(e);
        }

        public void paint(Graphics g){
                anim.reset();
                anim.repaint();
        }
}

class control extends Panel{
        TextField textP,textQ,textF,textM,textR;
        Button type;
        Animation anim;
        control(Animation an){
                anim=an;
                add(textR=new TextField("mouse X,Y",8));
                add(new Label("p="));
                add(textP=new TextField("40.",5));
                add(new Label("q="));
                add(textQ=new TextField("40.",5));
                add(new Label("f="));
                add(type=new Button("+"));
                add(textF=new TextField("20.",5));
                add(new Label("M="));
                add(textM=new TextField("1.",5));
        }

        public boolean action(Event ev, Object arg) {
                String label = (String)arg;
                if (ev.target instanceof Button) {
                        if(label.equals("+"))
                                type.setLabel("-");
                        else if(label.equals("-"))
                                type.setLabel("+");
                        anim.l.f*=-1.;
                        anim.repaint();
                        return true;
                }else if( ev.target instanceof TextField){
                        double value;
                        value=Double.valueOf(label).doubleValue();
                        if(ev.target==textP)
                                anim.textInput(1,value);
                        else if(ev.target==textQ)
                                anim.textInput(2,value);
                        else if(ev.target==textF)
                                anim.textInput(3,value);
                        else if(ev.target==textM)
                                anim.textInput(4,value);
                        anim.repaint();
                        return true;
                }
                return false;
        }
}

class Animation extends Canvas {
        Thread animatorThread;
        Dimension area;
        control p;
        lens l;
        int xc,yc;
        double scale=10.;

        Animation(MainWindow m){
                p=new control(this);
                m.add("North",p);
        }
        public void reset() {
                area=size();
                xc=area.width/2;
                yc=area.height/2;
                if(l==null){
                        double ff=10.*scale;
                        l=new lens(xc,yc,(int)(0.75*yc),ff);
                        p.textF.setText(Double.toString(ff));
                }else l.move(xc,yc);
                xc-=(int)(2.*l.f); // initial object position
                yc=yc*3/4;
        }

        boolean objectmove=false;
        public boolean mouseDown(Event e, int x, int y){
                if(!l.mouseDown(x,y)){
                        if(Math.abs(x-xc)<5 ){
                                xc=x;
                                yc=y;
                                repaint();
                                objectmove=true;
                        }
                }
                return true;
        }
        
        public boolean mouseDrag(Event e, int x, int y){
                if(l.mouseDrag(x,y)) repaint(); // change lens
                else if(objectmove){ // move object
                        xc=x;
                        yc=y;
                        writeXY(x,y);
                        repaint();
                } 
                return true;
        }       

        public boolean mouseUp(Event e, int x, int y){
                l.mouseUp(x,y);
                objectmove=false;
                repaint();
                return true;
        }

        public boolean mouseMove(Event  evt, int  x, int  y){
                if(!objectmove) writeXY(x,y);
                return true;
        }

        private void writeXY(int x, int y){
                p.textR.setText(
                        Double.toString((double)(l.xc-x)/scale)+
                        ",  "+
                        Double.toString((double)(l.yc-y)/scale));
        }

        public boolean mouseExit(Event  evt, int  x, int  y){
                p.textR.setText("mouse X,Y");
                return true;
        }

        public void textInput(int type,double value){
                switch (type) {
                case 1: // P
                        xc=l.xc-(int)(value*scale);
                        break;
                case 2: // Q
                        xc=l.xc-(int)(1./(1./l.f-1./(scale*value)));
                        break;
                case 3: // F
                        l.f=value*scale;
                        break;
                case 4: // M
                        xc=l.xc-(int)(Math.abs((1.-1./value)*l.f));
                        break;
                }
        }

        Dimension offDimension;
        Image offImage;
        Graphics g;

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

        public void update(Graphics gs){
                if ( (g == null)
                                || (area.width != offDimension.width)
                                || (area.height != offDimension.height) ) {
                        offDimension = area;
                        offImage = createImage(area.width, area.height);
                        g = offImage.getGraphics();
                        l.setGraphics(g);
                }
                //Erase the previous image.
                g.setColor(getBackground());
                g.fillRect(0, 0, area.width, area.height);
                g.setColor(Color.black);

                //public        void paint(Graphics g){
                g.drawLine(0,area.height/2,area.width,area.height/2);
                drawGrid(g);
                drawRay(g);
                l.show();
                gs.drawImage(offImage, 0, 0, this);
        }

        private void drawGrid(Graphics g){
                g.setColor(Color.gray);
                int i,j;
                for(i=l.xc,j=0;i>0;j++,i=l.xc-(int)(j*scale))
                        g.drawLine(i,l.yc-2,i,l.yc+2);
                for(i=l.xc,j=0;i<area.width;j++,i=l.xc+(int)(j*scale))
                        g.drawLine(i,l.yc-2,i,l.yc+2);
                g.setColor(Color.black);
        }

        private void writeText(TextField obj,double value){
                value=(double)((int)(10.*value))/(10.*scale);
                //formated output, keep same digits outputs after .
                obj.setText(Double.toString(value));
        }

        public void drawRay(Graphics g){
                int ox,oy,side;         // object position
                double ix,iy;                   // image position
                double magnify;

                if(l.xc>xc){
                        ox=l.xc-xc;
                        side=1;
                }else{
                        ox=xc-l.xc;
                        side=-1;
                }
                oy=l.yc-yc;
                ix=1./(1./l.f-1./ox);
                magnify=-ix/ox;
                iy=magnify*oy;
                // write P,Q,F
                writeText(p.textP,Math.abs(l.xc-xc));
                writeText(p.textQ,ix);
                writeText(p.textF,Math.abs(l.f));
                writeText(p.textM,magnify*scale);               
                int x1,y1,x2,y2; // ray paths
                x1=l.xc-side*ox;
                y1=l.yc-oy;
                x2=l.xc+side*(int)ix;
                y2=l.yc-(int)iy;
                //draw object
                drawit(g,x1,l.yc,oy);
                g.setColor(Color.blue);
                g.drawLine(x1,y1,l.xc,y1);              //1-1
                if(magnify<0){
                        g.drawLine(l.xc,y1,x2,y2);      //1-2
                        g.drawLine(x1,y1,x2,y2);                //2
                        g.drawLine(x1,y1,l.xc,y2);      //3-1
                        g.drawLine(l.xc,y2,x2,y2);      //3-2
                        g.setColor(Color.blue);
                }else{
                        if(l.f>0){              // converging lens
                                g.drawLine(l.xc,y1,l.xc+2*side*(int)l.f,l.yc+oy);       //1-2
                                g.drawLine(x1,y1,l.xc-side*(int)ix,l.yc+(int)iy);       //2-1
                                g.drawLine(x1,y1,l.xc,l.yc-(int)iy);                                                    //3-1
                                g.drawLine(l.xc,y2,l.xc+2*side*(int)l.f,y2);                    //3-2
                                g.setColor(Color.green);
                                g.drawLine(x1,y1,x2,y2);                                                                //2-2
                                g.drawLine(l.xc,y1,x2,y2);                                                      //1-3
                                g.drawLine(l.xc,y2,x2,y2);      
                        }else{                          // diverging lens
                                g.drawLine(l.xc,y1,l.xc-2*side*(int)l.f,l.yc-3*oy); //1-2
                                g.drawLine(x1,y1,l.xc+side*ox,l.yc+(int)oy);    //2
                                g.drawLine(x1,y1,l.xc,y2);                                                                              //3-1
                                g.drawLine(l.xc,y2,l.xc-2*side*(int)l.f,y2);    //3-2
                                g.setColor(Color.green);
                                g.drawLine(l.xc,y1,l.xc+side*(int)l.f,l.yc);    //1-3
                                g.drawLine(l.xc,y2,l.xc+side*(int)ix,y2);                       //3-3
                        }
                }
                drawit(g,x2,l.yc,(int)iy); // draw image
        }

        private void drawit(Graphics g,int x,int y,int height){
                // for object and image 
                int x1,y1,width=4,sign=1;
                x1=x-width/2;
                if(height<0){ 
                        sign=-1;
                        y1=y;
                }else y1=y-height;
                g.fillRect(x1,y1,width,sign*height);
                y1=y-height;
                width=2*width;
                sign*=2;
                // draw hats
                g.drawLine(x,y1,x+width,y1+sign*width);
                g.drawLine(x,y1,x-width,y1+sign*width);
        }
}

class lens{
        int xc,yc,h;    // (xc,yc):center position, h:height of lens
        double r,f;             // focusLength*2;
        int width;              // half/quater width of lens
        Graphics g;

        lens(int x,int y,int hi, double fi){
                //g=gs;
                init(x,y,hi,fi);
        }

        private void init(int x, int y,int hi,double fi){
                xc=x; yc=y;
                r=Math.abs(2.*fi);
                f=fi;
                if(hi<10)h=10; // minimum height of lens
                        else h=hi;
        }

        private void set(int x, int y,int hi,double fi){
                init(x,y,hi,fi);
                show();
        }

        public void setGraphics(Graphics gs) {g=gs;}
        // for netscape 3.0 only, fixed clip-region problem
        // when window enlarged

        boolean moving=false;
        boolean sizing=false;
        boolean mouseDown(int x,int y){
                if(Math.abs(x-xc)<width/2.){
                        if(Math.abs(y-yc)<h/2.)moving=true; // move lens
                        if( (Math.abs(y-yc-h)<h/2.) || (Math.abs(y-yc+h)<h/2.)){
                                sizing=true; // change lens size/shape
                        }else sizing=false;
                        return true;
                }else return false;             
        }

        boolean mouseUp(int x,int y){
                sizing=false;// back to normal
                moving=false;
                return true;
        }

        boolean mouseDrag(int x,int y){
                boolean done = true;
                if(sizing){ // change lens size/shape
                        double dx,dy;
                        double sign=1.;
                        if(f<0)sign=-1;
                        dx=Math.abs(x-xc);
                        dy=Math.abs(y-yc);
                        set(xc,yc,(int)dy,sign*(dy*dy/dx+dx)/4.);
                        show();
                }else if(moving)move(x,yc); // move lens
                else done=false;
                return done;
        }

        void move(int x, int y){
                xc=x;
                yc=y;
                show();
        }

        void show(){
                double angle;
                int angle2;
                int d,delta;
                if(!sizing)r=(3.+h*h/3.)/4; // 3. is the half width of lens
                angle=Math.asin((double)h/r);
                angle2=(int)(180.*angle/Math.PI+0.5);
                // Why angle has to be integer in Java?
                // This cause errors for large radius arc

                d=(int)(2.*r);
                width=(int)(2.*r*(1.-Math.cos(angle)));
                g.setColor(Color.black);
                // draw lens
                if(f<0){ // diverging lens, top and bottom
                        delta=width;
                        g.drawLine(xc-delta,yc-h,xc+delta,yc-h);
                        g.drawLine(xc-delta,yc+h,xc+delta,yc+h);
                }else  delta=0;
//              g.drawArc(xc-(int)(r*(1.-Math.cos(angle)))+delta,yc-(int)r,d,d,
//                                      180-angle2,2*angle2);
                g.drawArc(xc-width/2+delta,yc-(int)r,d,d,
                                        180-angle2,2*angle2);
                g.drawArc(xc-(int)(r*(1.+Math.cos(angle)))-delta,yc-(int)r,d,d,
                                        -angle2,2*angle2);
                g.setColor(Color.red);
                g.drawLine(xc,yc-5,xc,yc+5);
                // draw at +/- f, +/- 2f, 
                delta=(int)f;
                g.drawLine(xc-delta,yc-5,xc-delta,yc+5);
                g.drawLine(xc+delta,yc-5,xc+delta,yc+5);
                delta=(int)(2.*f);
                g.drawLine(xc-delta,yc-5,xc-delta,yc+5);
                g.drawLine(xc+delta,yc-5,xc+delta,yc+5);
        }
}

