#include "meshutil.hpp"
#include "surface.hpp"

MeshModel* MeshUtil::createCube(const Brush& b)
{
	static Vector norms[]      = {Vector(0, 0, -1), Vector(1, 0, 0), Vector(0, 0, 1),
                             Vector(-1, 0, 0), Vector(0, 1, 0), Vector(0, -1, 0)};
	static Vector tex_coords[] = {Vector(0, 0, 1), Vector(1, 0, 1), Vector(1, 1, 1), Vector(0, 1, 1)};
	static int    verts[]      = {2, 3, 1, 0, 3, 7, 5, 1, 7, 6, 4, 5, 6, 2, 0, 4, 6, 7, 3, 2, 0, 1, 5, 4};
	static Box    box(Vector(-1, -1, -1), Vector(1, 1, 1));

	MeshModel*        m = new MeshModel();
	Surface*          s = m->createSurface(b);
	Surface::Vertex   v;
	Surface::Triangle t;
	for (int k = 0; k < 24; k += 4) {
		const Vector& normal = norms[k / 4];
		for (int j = 0; j < 4; ++j) {
			v.coords           = box.corner(verts[k + j]);
			v.normal           = normal;
			v.tex_coords[0][0] = v.tex_coords[1][0] = tex_coords[j].x;
			v.tex_coords[0][1] = v.tex_coords[1][1] = tex_coords[j].y;
			s->addVertex(v);
		}
		t.verts[0] = k;
		t.verts[1] = k + 1;
		t.verts[2] = k + 2;
		s->addTriangle(t);
		t.verts[1] = k + 2;
		t.verts[2] = k + 3;
		s->addTriangle(t);
	}
	return m;
}

MeshModel* MeshUtil::createSphere(const Brush& b, int segs)
{
	int h_segs = segs * 2, v_segs = segs;

	MeshModel* m = new MeshModel();
	Surface*   s = m->createSurface(b);

	Surface::Vertex   v;
	Surface::Triangle t;

	v.coords = v.normal = Vector(0, 1, 0);
	int k;
	for (k = 0; k < h_segs; ++k) {
		v.tex_coords[0][0] = v.tex_coords[1][0] = (k + .5f) / h_segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 0;
		s->addVertex(v);
	}
	for (k = 1; k < v_segs; ++k) {
		float pitch = k * PI / v_segs - HALFPI;
		for (int j = 0; j <= h_segs; ++j) {
			float yaw = (j % h_segs) * TWOPI / h_segs;
			v.coords = v.normal = rotationMatrix(pitch, yaw, 0).k;
			v.tex_coords[0][0] = v.tex_coords[1][0] = float(j) / float(h_segs);
			v.tex_coords[0][1] = v.tex_coords[1][1] = float(k) / float(v_segs);
			s->addVertex(v);
		}
	}
	v.coords = v.normal = Vector(0, -1, 0);
	for (k = 0; k < h_segs; ++k) {
		v.tex_coords[0][0] = v.tex_coords[1][0] = (k + .5f) / h_segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 1;
		s->addVertex(v);
	}
	for (k = 0; k < h_segs; ++k) {
		t.verts[0] = k;
		t.verts[1] = t.verts[0] + h_segs + 1;
		t.verts[2] = t.verts[1] - 1;
		s->addTriangle(t);
	}
	for (k = 1; k < v_segs - 1; ++k) {
		for (int j = 0; j < h_segs; ++j) {
			t.verts[0] = k * (h_segs + 1) + j - 1;
			t.verts[1] = t.verts[0] + 1;
			t.verts[2] = t.verts[1] + h_segs + 1;
			s->addTriangle(t);
			t.verts[1] = t.verts[2];
			t.verts[2] = t.verts[1] - 1;
			s->addTriangle(t);
		}
	}
	for (k = 0; k < h_segs; ++k) {
		t.verts[0] = (h_segs + 1) * (v_segs - 1) + k - 1;
		t.verts[1] = t.verts[0] + 1;
		t.verts[2] = t.verts[1] + h_segs;
		s->addTriangle(t);
	}

	return m;
}

MeshModel* MeshUtil::createCylinder(const Brush& b, int segs, bool solid)
{
	MeshModel*        m = new MeshModel();
	Surface::Vertex   v;
	Surface::Triangle t;

	Surface* s = m->createSurface(b);
	int      k;
	for (k = 0; k <= segs; ++k) {
		float yaw          = (k % segs) * TWOPI / segs;
		v.coords           = rotationMatrix(0, yaw, 0).k;
		v.coords.y         = 1;
		v.normal           = Vector(v.coords.x, 0, v.coords.z);
		v.tex_coords[0][0] = v.tex_coords[1][0] = float(k) / segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 0;
		s->addVertex(v);
		v.coords.y         = -1;
		v.tex_coords[0][0] = v.tex_coords[1][0] = float(k) / segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 1;
		s->addVertex(v);
	}
	for (k = 0; k < segs; ++k) {
		t.verts[0] = k * 2;
		t.verts[1] = t.verts[0] + 2;
		t.verts[2] = t.verts[1] + 1;
		s->addTriangle(t);
		t.verts[1] = t.verts[2];
		t.verts[2] = t.verts[1] - 2;
		s->addTriangle(t);
	}

	if (!solid)
		return m;

	s = m->createSurface(b);

	for (k = 0; k < segs; ++k) {
		float yaw          = k * TWOPI / segs;
		v.coords           = rotationMatrix(0, yaw, 0).k;
		v.coords.y         = 1;
		v.normal           = Vector(0, 1, 0);
		v.tex_coords[0][0] = v.tex_coords[1][0] = v.coords.x * .5f + .5f;
		v.tex_coords[0][1] = v.tex_coords[1][1] = v.coords.z * .5f + .5f;
		s->addVertex(v);
		v.coords.y = -1;
		v.normal   = Vector(0, -1, 0);
		s->addVertex(v);
	}
	for (k = 2; k < segs; ++k) {
		t.verts[0] = 0;
		t.verts[1] = k * 2;
		t.verts[2] = (k - 1) * 2;
		s->addTriangle(t);
		t.verts[0] = 1;
		t.verts[1] = (k - 1) * 2 + 1;
		t.verts[2] = k * 2 + 1;
		s->addTriangle(t);
	}

	return m;
}

MeshModel* MeshUtil::createCone(const Brush& b, int segs, bool solid)
{
	MeshModel*        m = new MeshModel();
	Surface::Vertex   v;
	Surface::Triangle t;

	Surface* s;
	s = m->createSurface(b);
	int k;
	v.coords = v.normal = Vector(0, 1, 0);
	for (k = 0; k < segs; ++k) {
		v.tex_coords[0][0] = v.tex_coords[1][0] = (k + .5f) / segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 0;
		s->addVertex(v);
	}
	for (k = 0; k <= segs; ++k) {
		float yaw          = (k % segs) * TWOPI / segs;
		v.coords           = yawMatrix(yaw).k;
		v.coords.y         = -1;
		v.normal           = Vector(v.coords.x, 0, v.coords.z);
		v.tex_coords[0][0] = v.tex_coords[1][0] = float(k) / segs;
		v.tex_coords[0][1] = v.tex_coords[1][1] = 1;
		s->addVertex(v);
	}
	for (k = 0; k < segs; ++k) {
		t.verts[0] = k;
		t.verts[1] = k + segs + 1;
		t.verts[2] = k + segs;
		s->addTriangle(t);
	}
	if (!solid)
		return m;
	s = m->createSurface(b);
	for (k = 0; k < segs; ++k) {
		float yaw          = k * TWOPI / segs;
		v.coords           = yawMatrix(yaw).k;
		v.coords.y         = -1;
		v.normal           = Vector(v.coords.x, 0, v.coords.z);
		v.tex_coords[0][0] = v.tex_coords[1][0] = v.coords.x * .5f + .5f;
		v.tex_coords[0][1] = v.tex_coords[1][1] = v.coords.z * .5f + .5f;
		s->addVertex(v);
	}
	t.verts[0] = 0;
	for (k = 2; k < segs; ++k) {
		t.verts[1] = k - 1;
		t.verts[2] = k;
		s->addTriangle(t);
	}
	return m;
}

void MeshUtil::lightMesh(MeshModel* m, const Vector& pos, const Vector& rgb, float range)
{
	if (range) {
		float                         att   = 1.0f / range;
		const MeshModel::SurfaceList& surfs = m->getSurfaces();
		for (int k = 0; k < surfs.size(); ++k) {
			Surface* s = surfs[k];
			for (int j = 0; j < s->numVertices(); ++j) {
				const Surface::Vertex& v  = s->getVertex(j);
				Vector                 lv = pos - v.coords;
				float                  dp = v.normal.normalized().dot(lv);
				if (dp <= 0)
					continue;
				float d = lv.length();
				float i = 1 / (d * att) * (dp / d);
				s->setColor(j, s->getColor(j) + rgb * i);
			}
		}
	} else {
		const MeshModel::SurfaceList& surfs = m->getSurfaces();
		for (int k = 0; k < surfs.size(); ++k) {
			Surface* s = surfs[k];
			for (int j = 0; j < s->numVertices(); ++j) {
				const Surface::Vertex& v = s->getVertex(j);
				s->setColor(j, s->getColor(j) + rgb);
			}
		}
	}
}
