
#include <cfloat>
#include "std.hpp"
#include "world.hpp"

static World* w;

struct Face {
	Vector verts[4];

	Face(const Vector& v0, const Vector& v1, const Vector& v2, const Vector& v3)
	{
		verts[0] = v0;
		verts[1] = v1;
		verts[2] = v2;
		verts[3] = v3;
	}
};

static int face_verts[][4] = {2, 3, 1, 0, 3, 7, 5, 1, 7, 6, 4, 5, 6, 2, 0, 4, 6, 7, 3, 2, 0, 1, 5, 4};

struct Coll {
	int obj, surf, tri;
	Coll(const ObjCollision& t) : obj((int)t.with), surf((int)t.collision.surface), tri(t.collision.index) {}
};

struct CollCmp {
	bool operator()(const Coll& a, const Coll& b) const
	{
		if (a.obj < b.obj)
			return true;
		if (b.obj < a.obj)
			return false;
		if (a.surf < b.surf)
			return true;
		if (b.surf < a.surf)
			return false;
		if (a.tri < b.tri)
			return true;
		return false;
	}
};

typedef set<Coll, CollCmp> CollSet;

//returns: 1 for visible, 0 for hidden, -1 for don't know
static int faceVis(const Face& src, const Face& dest)
{
	static CollSet all;
	static CollSet colls[16];

	all.clear();

	for (int k = 0; k < 4; ++k) {
		for (int j = 0; j < 4; ++j) {
			int n = k * 4 + j;
			colls[n].clear();

			Vector sv  = src.verts[k];
			Vector dv  = dest.verts[j];
			Vector adj = (dv - sv).normalized() * .01f;
			dv -= adj;

			for (;;) {
				sv += adj;
				Line line(sv, dv - sv);

				ObjCollision c;
				if (!w->traceRay(line, EPSILON, &c))
					break;

				Coll t(c);
				all.insert(t);
				colls[n].insert(t);

				sv = c.coords;
			}
			if (!colls[n].size())
				return 1;
		}
	}
	CollSet::const_iterator it;
	for (it = all.begin(); it != all.end(); ++it) {
		int k = 0;
		for (; k < 16; ++k) {
			if (!colls[k].count(*it))
				break;
		}
		if (k == 16)
			return 0; //definitely hidden!
	}
	return -1;
}

static void subdivide(list<Face>& lst)
{
	int n = lst.size();
	while (n--) {
		const Face& f = lst.front();
		Vector      a((f.verts[0] + f.verts[1]) / 2);
		Vector      b((f.verts[1] + f.verts[2]) / 2);
		Vector      c((f.verts[2] + f.verts[3]) / 2);
		Vector      d((f.verts[3] + f.verts[0]) / 2);
		Vector      e((f.verts[0] + f.verts[1] + f.verts[2] + f.verts[3]) / 4);
		lst.push_back(Face(f.verts[0], a, e, d));
		lst.push_back(Face(a, f.verts[1], b, e));
		lst.push_back(Face(e, b, f.verts[2], c));
		lst.push_back(Face(d, e, c, f.verts[3]));
		lst.erase(lst.begin());
	}
}

static int faceVis(const Face& src, const Face& dest, int recurs_limit)
{
	static list<Face> src_faces, dest_faces;

	src_faces.clear();
	dest_faces.clear();

	src_faces.push_back(src);
	dest_faces.push_back(dest);

	while (recurs_limit--) {
		list<Face>::iterator src_it, dest_it;
		for (src_it = src_faces.begin(); src_it != src_faces.end(); ++src_it) {
			int cnt = 0;
			for (dest_it = dest_faces.begin(); dest_it != dest_faces.end(); ++dest_it) {
				int n = faceVis(*src_it, *dest_it);
				if (n == 1)
					return 1;
				if (!n)
					++cnt;
			}
			if (cnt == dest_faces.size()) {
				//source can't see ANY dest faces
				src_it = src_faces.erase(src_it);
				--src_it;
			}
		}
		if (!src_faces.size())
			return 0;
		//ok, subdivide!
		subdivide(src_faces);
		subdivide(dest_faces);
	}
	return -1;
}

bool World::boxVis(const Box& src, const Box& dest, int recurs_limit)
{
	w = this;

	Box big;

	big.update(src);
	big.update(dest);

	Plane planes[6];

	for (int n = 0; n < 6; ++n) {
		planes[n] = Plane(big.corner(face_verts[n][0]), big.corner(face_verts[n][1]), big.corner(face_verts[n][2]));
	}

	for (int k = 0; k < 6; ++k) {
		Vector v0 = src.corner(face_verts[k][0]);
		Vector v1 = src.corner(face_verts[k][1]);
		Vector v2 = src.corner(face_verts[k][2]);
		Vector v3 = src.corner(face_verts[k][3]);

		int n;
		for (n = 0; n < 6; ++n) {
			const Plane& p = planes[n];
			if (fabs(p.distance(v0)) <= EPSILON && fabs(p.distance(v1)) <= EPSILON && fabs(p.distance(v2)) <= EPSILON
				&& fabs(p.distance(v3)) <= EPSILON)
				break;
		}
		if (n < 6)
			continue;

		Face src_face(v0, v1, v2, v3);

		for (int j = 0; j < 6; ++j) {
			Vector v0 = dest.corner(face_verts[j][0]);
			Vector v1 = dest.corner(face_verts[j][1]);
			Vector v2 = dest.corner(face_verts[j][2]);
			Vector v3 = dest.corner(face_verts[j][3]);

			int n;
			for (n = 0; n < 6; ++n) {
				const Plane& p = planes[n];
				if (fabs(p.distance(v0)) <= EPSILON && fabs(p.distance(v1)) <= EPSILON
					&& fabs(p.distance(v2)) <= EPSILON && fabs(p.distance(v3)) <= EPSILON)
					break;
			}
			if (n < 6)
				continue;

			Face dest_face(v0, v1, v2, v3);

			int t = faceVis(src_face, dest_face, recurs_limit);

			if (t)
				return true;
		}
	}
	return false;
}
