import java.util.Vector;
import java.awt.Color;

class Scene3D {

	private Polygon3D[] poly;
	private ZbufferedRaster r;

	private Matrix3D model;
	private Matrix3D lookAt;
	private Matrix3D projection;
	
	private float fov, left, right, bottom, top, near, far;
	private Vector3 eye, look, up;
	private Vector3 eyeHome, lookHome, upHome;
	private boolean perspective; //true for perspective, false for orthographic
	Color background;



	//Constructors//////////////////////////////////////////////////////////

	public Scene3D(Polygon3D[] p, ZbufferedRaster ras) {
		r = ras;
		background = new Color(0,0,0);
		poly = p;
		perspective = true;
		eye = new Vector3(0,0,-10);
		look = new Vector3(0,0,0);
		up = new Vector3(0,1,0);
		calculateLookAt();
		setCameraHome();
		setFOV(30, 0, 200);
		model = new Matrix3D();
	};


	public Scene3D(	Polygon3D[] p,
					ZbufferedRaster ras,
					Vector3 eyePos,
					Vector3 lookPos,
					Vector3 upVector,
					float fovAngle,
					float near,
					float far,
					boolean persp,
					Color bgcolor)
	{
		r = ras;
		background = bgcolor;
		poly = p;
		perspective = persp;
		moveCameraTo(eyePos, lookPos, upVector);
		setCameraHome();
		setFOV(fovAngle, near, far);
		model = new Matrix3D();
	};



	//Methods//////////////////////////////////////////////////////////////


	public void render() {
System.out.println("Rendering...");

System.out.println("clearing background");
		r.fill(background);

System.out.println("doing model transformations");
		Polygon3D[] temp = Pipeline.transformToNew(poly, model);

System.out.println("doing backface culling");
		temp = Pipeline.trivialReject(temp, eye.x, eye.y, eye.z);

System.out.println("doing illumination");
		//Illumination step ??

System.out.println("There are currently "+temp.length+" polygons...");
System.out.println("viewing (lookAt) transformation");
		Pipeline.transform(temp, lookAt);

System.out.println("\nPolygon 0 is at\n"+poly[0]);

System.out.println("s-h clipping to ("+left+", "+right+"), ("+bottom+", "+top+"), ("+near+", "+far+")");
		temp = Pipeline.clip(temp, left, right, bottom, top, near, far);

System.out.println("...Now there are "+temp.length+" polygons...");
System.out.println("projection");
		Pipeline.transform(temp, projection);

System.out.println("converting polygons to triangles");
		Vector triangles = Pipeline.toTriangles(temp);

System.out.println("displaying triangles");
		Pipeline.display(triangles, r);

System.out.println("...Finished Rendering");
	};


	public boolean switchPerspective() {
		return (perspective = !perspective);
	};

	
	public void moveCameraTo(Vector3 ey, Vector3 at, Vector3 u) {
		eye.moveTo(ey);
		look.moveTo(at);
		up.moveTo(u);
		calculateLookAt();
	};


	public void moveCameraBy(Vector3 dx) {
		eye.add(dx);
		calculateLookAt();
	};


	public void moveCameraBy(Vector3 dx, boolean viewpointAlso) {
		eye.plus(dx);
		if (viewpointAlso)
			look.add(dx);
		calculateLookAt();
	};

	
	public void moveCameraTargetBy(Vector3 dx) {
		look.add(dx);
		calculateLookAt();
	};
	
	
	public void moveCameraHome() {
		eye.moveTo(eyeHome);
		look.moveTo(lookHome);
		up.moveTo(upHome);
		calculateLookAt();
	};

	
	public void setCameraHome() {
		eyeHome = new Vector3(eye);
		lookHome = new Vector3(look);
		upHome = new Vector3(up);
	};


	public void modelTransform(Matrix3D m) {
		model.compose(m);
	};


	//Private Methods//////////////////////////////////////////////////////


	private void setFOV(float f, float n, float fa) {
		fov = f;
        right = (float) Math.sin(Math.PI*(fov/2)/180);
		left = -right;
        top = (right*r.height)/r.width;
		bottom = -top;
		near = n;
		far = fa;
		calculateProjectionMatrix();
	};
	
	
	private void calculateProjectionMatrix() {
		if (perspective)
			projection = Matrix3D.perspectiveMatrix(left,right,bottom,top,near,far);
		else
			projection = Matrix3D.orthographicMatrix(left,right,bottom,top,near,far);
	};


	private void calculateLookAt() {
		lookAt = Matrix3D.lookAtMatrix(eye, look, up);
	};

};
