// rendermd2.cpp: loader code adapted from a nehe tutorial

#include "cube.h"

struct md2_header {
	int magic;
	int version;
	int skinWidth, skinHeight;
	int frameSize;
	int numSkins, numVertices, numTexcoords;
	int numTriangles, numGlCommands, numFrames;
	int offsetSkins, offsetTexcoords, offsetTriangles;
	int offsetFrames, offsetGlCommands, offsetEnd;
};

struct md2_vertex {
	uchar vertex[3], lightNormalIndex;
};

struct md2_frame {
	float scale[3];
	float translate[3];
	char name[16];
	md2_vertex vertices[1];
};

struct md2 {
	int numGlCommands;
	int *glCommands;
	int numTriangles;
	int frameSize;
	int numFrames;
	int numVerts;
	char *frames;
	vec **mverts;
	int displaylist;
	int displaylistverts;

	mapmodelinfo mmi;
	char *loadname;
	int mdlnum;
	bool loaded;

	bool load(char *filename);
	void render(vec &light, int numFrame, int range, float x, float y,
	    float z, float yaw, float pitch, float scale, float speed, int snap,
	    int basetime);
	void scale(int frame, float scale, int sn);

	md2()
	    : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0),
	      loaded(false) {};

	~md2()
	{
		if (glCommands)
			delete[] glCommands;
		if (frames)
			delete[] frames;
	}
};

bool
md2::load(char *filename)
{
	FILE *file;
	md2_header header;

	if ((file = fopen(filename, "rb")) == NULL)
		return false;

	fread(&header, sizeof(md2_header), 1, file);
	endianswap(&header, sizeof(int), sizeof(md2_header) / sizeof(int));

	if (header.magic != 844121161 || header.version != 8)
		return false;

	frames = new char[header.frameSize * header.numFrames];
	if (frames == NULL)
		return false;

	fseek(file, header.offsetFrames, SEEK_SET);
	fread(frames, header.frameSize * header.numFrames, 1, file);

	for (int i = 0; i < header.numFrames; ++i) {
		endianswap(frames + i * header.frameSize, sizeof(float), 6);
	}

	glCommands = new int[header.numGlCommands];
	if (glCommands == NULL)
		return false;

	fseek(file, header.offsetGlCommands, SEEK_SET);
	fread(glCommands, header.numGlCommands * sizeof(int), 1, file);

	endianswap(glCommands, sizeof(int), header.numGlCommands);

	numFrames = header.numFrames;
	numGlCommands = header.numGlCommands;
	frameSize = header.frameSize;
	numTriangles = header.numTriangles;
	numVerts = header.numVertices;

	fclose(file);

	mverts = new vec *[numFrames];
	loopj(numFrames) mverts[j] = NULL;

	return true;
};

float
snap(int sn, float f)
{
	return sn ? (float)(((int)(f + sn * 0.5f)) & (~(sn - 1))) : f;
};

void
md2::scale(int frame, float scale, int sn)
{
	mverts[frame] = new vec[numVerts];
	md2_frame *cf = (md2_frame *)((char *)frames + frameSize * frame);
	float sc = 16.0f / scale;
	loop(vi, numVerts)
	{
		uchar *cv = (uchar *)&cf->vertices[vi].vertex;
		vec *v = &(mverts[frame])[vi];
		v->x = (snap(sn, cv[0] * cf->scale[0]) + cf->translate[0]) / sc;
		v->y =
		    -(snap(sn, cv[1] * cf->scale[1]) + cf->translate[1]) / sc;
		v->z = (snap(sn, cv[2] * cf->scale[2]) + cf->translate[2]) / sc;
	};
};

void
md2::render(vec &light, int frame, int range, float x, float y, float z,
    float yaw, float pitch, float sc, float speed, int snap, int basetime)
{
	loopi(range) if (!mverts[frame + i]) scale(frame + i, sc, snap);

	glPushMatrix();
	glTranslatef(x, y, z);
	glRotatef(yaw + 180, 0, -1, 0);
	glRotatef(pitch, 0, 0, 1);

	glColor3fv((float *)&light);

	if (displaylist && frame == 0 && range == 1) {
		glCallList(displaylist);
		xtraverts += displaylistverts;
	} else {
		if (frame == 0 && range == 1) {
			static int displaylistn = 10;
			glNewList(displaylist = displaylistn++, GL_COMPILE);
			displaylistverts = xtraverts;
		};

		int time = lastmillis - basetime;
		int fr1 = (int)(time / speed);
		float frac1 = (time - fr1 * speed) / speed;
		float frac2 = 1 - frac1;
		fr1 = fr1 % range + frame;
		int fr2 = fr1 + 1;
		if (fr2 >= frame + range)
			fr2 = frame;
		vec *verts1 = mverts[fr1];
		vec *verts2 = mverts[fr2];

		for (int *command = glCommands; (*command) != 0;) {
			int numVertex = *command++;
			if (numVertex > 0) {
				glBegin(GL_TRIANGLE_STRIP);
			} else {
				glBegin(GL_TRIANGLE_FAN);
				numVertex = -numVertex;
			};

			loopi(numVertex)
			{
				float tu = *((float *)command++);
				float tv = *((float *)command++);
				glTexCoord2f(tu, tv);
				int vn = *command++;
				vec &v1 = verts1[vn];
				vec &v2 = verts2[vn];
#define ip(c) v1.c *frac2 + v2.c *frac1
				glVertex3f(ip(x), ip(z), ip(y));
			};

			xtraverts += numVertex;

			glEnd();
		};

		if (displaylist) {
			glEndList();
			displaylistverts = xtraverts - displaylistverts;
		};
	};

	glPopMatrix();
}

hashtable<md2 *> *mdllookup = NULL;
vector<md2 *> mapmodels;
const int FIRSTMDL = 20;

void
delayedload(md2 *m)
{
	if (!m->loaded) {
		sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname);
		if (!m->load(path(name1)))
			fatal("loadmodel: ", name1);
		sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname);
		int xs, ys;
		installtex(FIRSTMDL + m->mdlnum, path(name2), xs, ys);
		m->loaded = true;
	};
};

int modelnum = 0;

md2 *
loadmodel(char *name)
{
	if (!mdllookup)
		mdllookup = new hashtable<md2 *>;
	md2 **mm = mdllookup->access(name);
	if (mm)
		return *mm;
	md2 *m = new md2();
	m->mdlnum = modelnum++;
	mapmodelinfo mmi = {2, 2, 0, 0, ""};
	m->mmi = mmi;
	m->loadname = newstring(name);
	mdllookup->access(m->loadname, &m);
	return m;
};

void
mapmodel(char *rad, char *h, char *zoff, char *snap, char *name)
{
	md2 *m = loadmodel(name);
	mapmodelinfo mmi = {
	    atoi(rad), atoi(h), atoi(zoff), atoi(snap), m->loadname};
	m->mmi = mmi;
	mapmodels.add(m);
};

void
mapmodelreset()
{
	mapmodels.setsize(0);
};

mapmodelinfo *
getmminfo(int i)
{
	return i < mapmodels.length() ? &mapmodels[i]->mmi : NULL;
};

COMMAND(mapmodel, ARG_5STR);
COMMAND(mapmodelreset, ARG_NONE);

void
rendermodel(char *mdl, int frame, int range, int tex, float rad, float x,
    float y, float z, float yaw, float pitch, bool teammate, float scale,
    float speed, int snap, int basetime)
{
	md2 *m = loadmodel(mdl);

	if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2))
		return;

	delayedload(m);

	int xs, ys;
	glBindTexture(GL_TEXTURE_2D,
	    tex ? lookuptexture(tex, xs, ys) : FIRSTMDL + m->mdlnum);

	int ix = (int)x;
	int iy = (int)z;
	vec light = {1.0f, 1.0f, 1.0f};

	if (!OUTBORD(ix, iy)) {
		sqr *s = S(ix, iy);
		float ll = 256.0f; // 0.96f;
		float of = 0.0f;   // 0.1f;
		light.x = s->r / ll + of;
		light.y = s->g / ll + of;
		light.z = s->b / ll + of;
	};

	if (teammate) {
		light.x *= 0.6f;
		light.y *= 0.7f;
		light.z *= 1.2f;
	};

	m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap,
	    basetime);
};
