/* Program Tracer.java

 * This is a 1-pass, non-recursive ray tracer.
 * It has a viewer at (15, 0, 2), looking through a pixel grid
 * in the plane X=10, into the scene.  There is only one object,
 * a square in the plane X=0, with 0<=Y<= 4 and 0<=Z<= 4.
 * The square is green on top and red on bottom.  The ray
 * tracer uses background color blue for rays that miss the square.
 */
 
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import javax.media.opengl.awt.GLCanvas;
import com.jogamp.opengl.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.nio.ByteBuffer;

import javax.swing.event.*;

class Ray {
	float [] point = new float[3];
	float [] direction = new float[3];
	
	public Ray(float[] point, float [] direction) { 
		for (int i = 0; i < 3; i++) {
			this.point[i] = point[i];
			this.direction[i] = direction[i];
		}
	}
	
	float [] rayPoint(float t) {
		float p[] = new float[3];
		for (int i = 0; i < 3; i++) {
			p[i] = point[i] + t*direction[i];
		}
		return p;
	}
}

class Intersection {
	float [] point;
	float [] normal;
	int objectNumber;
	float t;
	
	public Intersection(int object, float t) {
		this.objectNumber = object;
		this.t = t;
	}
	
	public Intersection() {
		this(0, 0);
	}
	
	void setPoint(float [] point){
		this.point = new float[3];
		for (int i = 0; i < 3; i++)
			this.point[i] = point[i];
	}
	void setNormal(float [] normal){
		this.normal = new float[3];
		for (int i = 0; i < 3; i++)
			this.normal[i] = normal[i];
	}
	
	void setObjectNumber(int i) {
		this.objectNumber = i;
	}
	
	void setT(int t) {
		this.t= t;
	}
}

public class Tracer implements GLEventListener, ActionListener, ChangeListener {
	
	public static void main(String[] args) {
		new Tracer();
	}
	
	private int INITIAL_WIDTH=600;
	private int INITIAL_HEIGHT=600;
	private float XSCALE=10f;
	private float YSCALE=10f;
	private JButton quitButton;
	private GLCanvas canvas;
	private GL2 gl;
	private GLU glu;
	
	private int N = 200; // rows of the bitmap
	private int M = 400; // columns of bitmap
	
	float viewerPosition[] = {15, 0, 2};
	
	float gridX = 10;
	float gridY = -2; // (gridX, gridY, gridZ) is the upper left
	float gridZ = 3;  // corner of the pixel grid, which lies in
					  // the plane X=10
	float gridWidth = 4; // gridWidth and gridHeight are the
	float gridHeight = 2; // dimensions of the pixel grid
	
	float polyWidth = 4; //polyWidth and polyHeight are the
	float polyHeight = 4; // dimensions of a square with one
							// vertex at the origin that lies in
							// the plane X=0
	private byte buffer[];
	
	public  Tracer() {
			GLProfile glp=GLProfile.getDefault();
			GLCapabilities caps = new GLCapabilities(glp);
			canvas = new GLCanvas(caps);
			canvas.addGLEventListener(this);

			JFrame frame = new JFrame("TRACE");
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE );
			frame.setSize(INITIAL_WIDTH, INITIAL_HEIGHT);
			frame.setLayout(new BorderLayout());
			frame.setVisible(true);
			
			JPanel north = new JPanel( new FlowLayout());
			
			quitButton = new JButton( "Quit");
			quitButton.addActionListener(this);
			north.add(quitButton);

			JPanel center = new JPanel(new GridLayout(1,1));
			center.add(canvas);
		
			frame.add(north,  BorderLayout.NORTH);
			frame.add(center, BorderLayout.CENTER);

			FPSAnimator animator = new FPSAnimator(canvas, 60);
			animator.start(); 	
	}
	
	public void actionPerformed(ActionEvent event) {
			if (event.getSource() == quitButton)
				System.exit(0);
	}
	
	public void stateChanged(ChangeEvent e) {
	}


	public void display(GLAutoDrawable drawable) {
		update();
		render();
	}

	private void update() {
	}
	
	private void render() {
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
		gl.glRasterPos2i((INITIAL_WIDTH-M)/2, (INITIAL_HEIGHT-N)/2);
		gl.glDrawPixels(M,  N,  GL2.GL_RGB, GL2.GL_BYTE, ByteBuffer.wrap(buffer));
	}
	
	void copy3( byte dest[], int row, int col, byte source[]) {
			int index = row*M*3+col*3;
			dest[index]=source[0];
			dest[index+1]=source[1];
			dest[index+2]=source[2];
	}
	
	public void dispose(GLAutoDrawable drawable) {
		// put the cleanup code here
		
	}

	public Ray makeRay(int i, int j) {
		// returns a ray from the viewer through pixel (i, j)
		float direction[] = new float[3];
		float pixel[] = new float[3];
		pixel[0] = gridX;
		pixel[1] = gridY+(gridWidth*j)/M;
		pixel[2] = gridZ - (gridHeight*i)/N;
		for (int k = 0; k < 3; k++) {
			direction[k] = pixel[k]-viewerPosition[k];
		}
		return new Ray(viewerPosition, direction);
	}
	
	Intersection intersect(Ray r) {
		// This returns the Intersection of r with our
		// square in the plane X=0
		float normal[] = {1, 0, 0};
		float t = -r.point[0]/r.direction[0];
		float point[] = r.rayPoint(t);
		float y = point[1];
		float z = point[2];
		if ( (0 <= y) && (y <= polyWidth) && (0 <= z) && (z <= polyHeight)) {
			Intersection I = new Intersection(1, t);
			I.setPoint(point);
			I.setNormal(normal);
			return I;
		}
		else return null;
	}
	
	byte [] trace(Ray r, int level, float weight) {
		// a  baby version of a non-recursive ray tracer
		byte RED[] = {127, 0, 0};
		byte BLUE[] = {0, 0, 127};
		byte GREEN[] = {0, 127, 0};
		
		Intersection I = intersect(r);
		if (I == null)
			return BLUE;
		else if (I.point[2] < 2)
			return RED;
		else
			return GREEN;
	}
	
	public void makePicture() {
		gl.glClearColor(1,  1, 0, 1);
		gl.glPixelStorei(GL2.GL_PACK_ALIGNMENT, 1);
		gl.glPixelStorei(GL2.GL_UNPACK_ALIGNMENT, 1);
		gl.glPixelStorei(GL2.GL_UNPACK_SKIP_PIXELS, 0);
		gl.glPixelStorei(GL2.GL_UNPACK_SKIP_ROWS, 0);

		buffer = new byte[N*M*3];
		// The buffer has N rows and M columns
		// Note that the buffer is installed upside down;
		// this is why we copy pixels from row i in the image to
		// row N-i-1 of the buffer.

		for (int i = 0; i < N; i++) {
			for (int j = 0; j < M; j++) {
				Ray r = makeRay(i, j);
				byte[] color = trace(r, 0, 1);
				copy3(buffer, N-i-1, j, color);
			}
		}
	}
	
	public void init(GLAutoDrawable drawable) {
		gl = drawable.getGL().getGL2();
		glu = new GLU();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluOrtho2D(0f, INITIAL_WIDTH, 0f, INITIAL_HEIGHT);
		
		makePicture();  // colors the buffer
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
		// this is called when the window is resized
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluOrtho2D(0f, width, 0f, height);

	}
	
}
