/** Represents a three-dimensional flat surface. This surface
 * should be fully opaque. */
public class Polygon extends Surface {
	private class MyIntersection extends AbstractIntersection {
		private Point hit;
		private Vector myNorm = null;

		private MyIntersection(Ray incident, double dist, Point hit) {
			super(incident, dist);
			this.hit = hit;
		}

		public Point getHitPoint() {
			return hit;
		}

		public Vector getNormal() {
			if(myNorm == null) {
				myNorm = getIncidentRay().getDirection().dot(frontNorm) < 0 ? frontNorm : backNorm;
			}
			return myNorm;
		}

		public Material getMaterial() {
			return material;
		}
	}

	private Material material;
	private Point[] vertices;
	private Vector frontNorm;
	private Vector backNorm;
	private double offset; // points P on polygon satisfy: frontNorm . P = offset

	public Polygon(Point[] vertices, Material material) {
		this.vertices = (Point[]) vertices.clone();
		this.material = material;

		// compute normal
		Vector lastEdge = vertices[0].subtract(vertices[vertices.length - 1]);
		Vector prevEdge = lastEdge;
		Vector total = Vector.ZERO;
		for(int i = 1; i < vertices.length; i++) {
			Vector thisEdge = vertices[i].subtract(vertices[i - 1]);
			total = total.add(thisEdge.cross(prevEdge));
			prevEdge = thisEdge;
		}
		total = total.add(lastEdge.cross(prevEdge));
		frontNorm = total.normalize();
		backNorm = frontNorm.scale(-1.0);

		// compute offset
		double totalOffset = 0.0;
		for(int i = 0; i < vertices.length; i++) {
			totalOffset += frontNorm.dotPoint(vertices[i]);
		}
		offset = totalOffset / vertices.length;
		// (I'm perplexed by this... I think offset should be negated, but it works this way.)
	}

	public Intersection getIntersection(Ray query) {
		// see where the query hits the polygon's plane
		double dist = (offset - frontNorm.dotPoint(query.getOrigin())) / frontNorm.dot(query.getDirection());
		Point hit = query.extend(dist);
		if(dist <= 1.0) return Intersection.NONE;

		// now test whether point lies within polygon
		boolean isInside = true;
		boolean isPositive = true;
		for(int i = 0; i < vertices.length; i++) {
			int next_i = (i == vertices.length - 1 ? 0 : i + 1);
			Vector edge = vertices[next_i].subtract(vertices[i]);
			Vector toPoint = hit.subtract(vertices[i]);
			boolean thisPos = edge.cross(toPoint).dot(frontNorm) > 0.0;
			if(i == 0) {
				isPositive = thisPos;
			} else if(isPositive != thisPos) {
				isInside = false;
				break;
			}
		}
		if(!isInside) return Intersection.NONE;

		return new MyIntersection(query, dist, hit);
	}
}
