import java.applet.*;

import java.awt.*;

import java.io.*;

import java.net.*;

import java.util.*;



/***************************************************

*

*   An instructional Ray-Tracing Renderer written

*   for MIT 6.837  Fall '98 by Leonard McMillan.

*

*   A fairly primitive Ray-Tracing program written

*   on a Sunday afternoon before Monday's class.

*   Everything is contained in a single file. The

*   structure should be fairly easy to extend, with

*   new primitives, features and other such stuff.

*

*   I tend to write things bottom up (old K&R C

*   habits die slowly). If you want the big picture

*   scroll to the applet code at the end and work

*   your way back here.

*

****************************************************/


//    The following Applet demonstrates a simple ray tracer with a

//    mouse-based painting interface for the impatient and Mac owners

public class RayTrace extends Applet implements Runnable {

    final static int CHUNKSIZE = 100;

    Image screen;

    Graphics gc;

    Vector objectList;

    Vector lightList;

    Surface currentSurface;



    Vector3D eye, lookat, up;

    Vector3D Du, Dv, Vp;

    float fov;



    Color background;



    int width, height;



    public void init( ) {

        // initialize the off-screen rendering buffer

        width = size().width;

        height = size().height;

        screen = createImage(width, height);

        gc = screen.getGraphics();

        gc.setColor(getBackground());

        gc.fillRect(0, 0, width, height);



        fov = 30;               // default horizonal field of view



        // Initialize various lists

        objectList = new Vector(CHUNKSIZE, CHUNKSIZE);

        lightList = new Vector(CHUNKSIZE, CHUNKSIZE);

        currentSurface = new Surface(0.8f, 0.2f, 0.9f, 0.2f, 0.4f, 0.4f, 10.0f, 0f, 0f, 1f);



        // Parse the scene file

        String filename = getParameter("datafile");

        showStatus("Parsing " + filename);

        InputStream is = null;

        try {

            is = new URL(getDocumentBase(), filename).openStream();

            ReadInput(is);

            is.close();

        } catch (IOException e) {

            showStatus("Error reading "+filename);

            System.err.println("Error reading "+filename);

            System.exit(-1);

        }



        // Initialize more defaults if they weren't specified

        if (eye == null) eye = new Vector3D(0, 0, 10);

        if (lookat == null) lookat = new Vector3D(0, 0, 0);

        if (up  == null) up = new Vector3D(0, 1, 0);

        if (background == null) background = new Color(0,0,0);



        // Compute viewing matrix that maps a

        // screen coordinate to a ray direction

        Vector3D look = new Vector3D(lookat.x - eye.x, lookat.y - eye.y, lookat.z - eye.z);

        Du = Vector3D.normalize(look.cross(up));

        Dv = Vector3D.normalize(look.cross(Du));

        float fl = (float)(width / (2*Math.tan((0.5*fov)*Math.PI/180)));

        Vp = Vector3D.normalize(look);

        Vp.x = Vp.x*fl - 0.5f*(width*Du.x + height*Dv.x);

        Vp.y = Vp.y*fl - 0.5f*(width*Du.y + height*Dv.y);

        Vp.z = Vp.z*fl - 0.5f*(width*Du.z + height*Dv.z);

    }





    double getNumber(StreamTokenizer st) throws IOException {

        if (st.nextToken() != StreamTokenizer.TT_NUMBER) {

            System.err.println("ERROR: number expected in line "+st.lineno());

            throw new IOException(st.toString());

        }

        return st.nval;

    }



    void ReadInput(InputStream is) throws IOException {

	    StreamTokenizer st = new StreamTokenizer(is);

    	st.commentChar('#');

        scan: while (true) {

	        switch (st.nextToken()) {

	          default:

		        break scan;

	          case StreamTokenizer.TT_WORD:

	            if (st.sval.equals("sphere")) {

                    Vector3D v = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

		            float r = (float) getNumber(st);

		            objectList.addElement(new Sphere(currentSurface, v, r));

			    } else

				if (st.sval.equals("hemisphere")) {

					Vector3D v1 = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));
					
					Vector3D v2 = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

					objectList.addElement(new Hemisphere(currentSurface, v1, v2));

				} else

			    if (st.sval.equals("eye")) {

		            eye = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

			    } else

			    if (st.sval.equals("lookat")) {

		            lookat = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

			    } else

			    if (st.sval.equals("up")) {

		            up = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

			    } else

			    if (st.sval.equals("fov")) {

                    fov = (float) getNumber(st);

			    } else

			    if (st.sval.equals("background")) {

                    background = new Color((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

			    } else

			    if (st.sval.equals("light")) {

			        float r = (float) getNumber(st);

			        float g = (float) getNumber(st);

			        float b = (float) getNumber(st);

		            if (st.nextToken() != StreamTokenizer.TT_WORD) {

                        throw new IOException(st.toString());

                    }

		            if (st.sval.equals("ambient")) {

		                lightList.addElement(new Light(Light.AMBIENT, null, r, g, b));

		            } else

		            if (st.sval.equals("directional")) {

		                Vector3D v = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

		                lightList.addElement(new Light(Light.DIRECTIONAL, v, r, g, b));

		            } else

		            if (st.sval.equals("point")) {

		                Vector3D v = new Vector3D((float) getNumber(st), (float) getNumber(st), (float) getNumber(st));

		                lightList.addElement(new Light(Light.POINT, v, r, g, b));

		            } else {

		                System.err.println("ERROR: in line "+st.lineno()+" at "+st.sval);

		                throw new IOException(st.toString());

		            }

			    } else

			    if (st.sval.equals("surface")) {

			        float r = (float) getNumber(st);

			        float g = (float) getNumber(st);

			        float b = (float) getNumber(st);

		            float ka = (float) getNumber(st);

		            float kd = (float) getNumber(st);

		            float ks = (float) getNumber(st);

		            float ns = (float) getNumber(st);

		            float kr = (float) getNumber(st);

		            float kt = (float) getNumber(st);

		            float index = (float) getNumber(st);

		            currentSurface = new Surface(r, g, b, ka, kd, ks, ns, kr, kt, index);

			    }

			    break;

	        }

	    }

        is.close();

	    if (st.ttype != StreamTokenizer.TT_EOF)

	        throw new IOException(st.toString());

	}

    

    boolean finished = false;

    

    public void paint(Graphics g) {

        if (finished)

            g.drawImage(screen, 0, 0, this);

    }



    // this overide avoid the unnecessary clear on each paint()

    public void update(Graphics g) {

        paint(g);

    }





    Thread raytracer;



    public void start() {

        if (raytracer == null) {

            raytracer = new Thread(this);

            raytracer.start();

        } else {

            raytracer.resume();

        }

    }



    public void stop() {

        if (raytracer != null) {

            raytracer.suspend();

        }

    }



    private void renderPixel(int i, int j) {

        Vector3D dir = new Vector3D(

                                i*Du.x + j*Dv.x + Vp.x,

                                i*Du.y + j*Dv.y + Vp.y,

                                i*Du.z + j*Dv.z + Vp.z);

        Ray ray = new Ray(eye, dir);

        if (ray.trace(objectList)) {

            gc.setColor(ray.Shade(lightList, objectList, background));

        } else {

            gc.setColor(background);

        }

        gc.drawLine(i, j, i, j);        // oh well, it works.

    }



    public void run() {

        Graphics g = getGraphics();

        long time = System.currentTimeMillis();

        for (int j = 0; j < height; j++) {

            showStatus("Scanline "+j);

            for (int i = 0; i < width; i++) {

                renderPixel(i, j);

            }

            g.drawImage(screen, 0, 0, this);        // doing this less often speed things up a bit

        }

        g.drawImage(screen, 0, 0, this);

        time = System.currentTimeMillis() - time;

        showStatus("Rendered in "+(time/60000)+":"+((time%60000)*0.001));

        finished = true;

    }





    public boolean mouseDown(Event e, int x, int y) {

        renderPixel(x, y);

        repaint();

        return true;

    }



    public boolean mouseDrag(Event e, int x, int y) {

        renderPixel(x, y);

        repaint();

        return true;

    }



    public boolean mouseUp(Event e, int x, int y) {

        renderPixel(x, y);

        repaint();

        return true;

    }

}