// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills
// the vertex array for different cube surfaces.

#include "cube.h"

#import "Command.h"
#import "Variable.h"

static struct vertex *verts = NULL;
int curvert;
static int curmaxverts = 10000;

void
setarraypointers()
{
	glVertexPointer(3, GL_FLOAT, sizeof(struct vertex), &verts[0].x);
	glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(struct vertex), &verts[0].r);
	glTexCoordPointer(2, GL_FLOAT, sizeof(struct vertex), &verts[0].u);
}

void
reallocv()
{
	verts =
	    OFResizeMemory(verts, (curmaxverts *= 2), sizeof(struct vertex));
	curmaxverts -= 10;
	setarraypointers();
}

// generating the actual vertices is done dynamically every frame and sits at
// the leaves of all these functions, and are part of the cpu bottleneck on
// really slow machines, hence the macros.

#define vertcheck()                         \
	{                                   \
		if (curvert >= curmaxverts) \
			reallocv();         \
	}

#define vertf(v1, v2, v3, ls, t1, t2)                 \
	{                                             \
		struct vertex *v = &verts[curvert++]; \
		v->u = t1;                            \
		v->v = t2;                            \
		v->x = v1;                            \
		v->y = v2;                            \
		v->z = v3;                            \
		v->r = ls->r;                         \
		v->g = ls->g;                         \
		v->b = ls->b;                         \
		v->a = 255;                           \
	}

#define vert(v1, v2, v3, ls, t1, t2)                                      \
	{                                                                 \
		vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); \
	}

int nquads;
const float TEXTURESCALE = 32.0f;
bool floorstrip = false, deltastrip = false;
int oh, oy, ox, ogltex; // the o* vars are used by the stripification
int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b;
int firstindex;
bool showm = false;

COMMAND(showmip, ARG_NONE, ^ {
	showm = !showm;
})

void
mipstats(int a, int b, int c)
{
	if (showm)
		conoutf(@"1x1/2x2/4x4: %d / %d / %d", a, b, c);
}

#define stripend()                                                          \
	{                                                                   \
		if (floorstrip || deltastrip) {                             \
			addstrip(ogltex, firstindex, curvert - firstindex); \
			floorstrip = deltastrip = false;                    \
		}                                                           \
	}
void
finishstrips()
{
	stripend();
}

static struct sqr sbright, sdark;
VAR(lighterror, 1, 8, 100);

// floor/ceil quads
void
render_flat(int wtex, int x, int y, int size, int h, struct sqr *l1,
    struct sqr *l2, struct sqr *l3, struct sqr *l4, bool isceil)
{
	vertcheck();
	if (showm) {
		l3 = l1 = &sbright;
		l4 = l2 = &sdark;
	}

	int sx, sy;
	int gltex = lookuptexture(wtex, &sx, &sy);
	float xf = TEXTURESCALE / sx;
	float yf = TEXTURESCALE / sy;
	float xs = size * xf;
	float ys = size * yf;
	float xo = xf * x;
	float yo = yf * y;

	bool first = !floorstrip || y != oy + size || ogltex != gltex ||
	    h != oh || x != ox;

	if (first) // start strip here
	{
		stripend();
		firstindex = curvert;
		ogltex = gltex;
		oh = h;
		ox = x;
		floorstrip = true;
		if (isceil) {
			vert(x + size, h, y, l2, xo + xs, yo);
			vert(x, h, y, l1, xo, yo);
		} else {
			vert(x, h, y, l1, xo, yo);
			vert(x + size, h, y, l2, xo + xs, yo);
		}
		ol3r = l1->r;
		ol3g = l1->g;
		ol3b = l1->b;
		ol4r = l2->r;
		ol4g = l2->g;
		ol4b = l2->b;
	} else // continue strip
	{
		int lighterr = lighterror * 2;
		if ((abs(ol3r - l3->r) < lighterr &&
		        abs(ol4r - l4->r) < lighterr // skip vertices if light
		                                     // values are close enough
		        && abs(ol3g - l3->g) < lighterr &&
		        abs(ol4g - l4->g) < lighterr &&
		        abs(ol3b - l3->b) < lighterr &&
		        abs(ol4b - l4->b) < lighterr) ||
		    !wtex) {
			curvert -= 2;
			nquads--;
		} else {
			unsigned char *p3 =
			    (unsigned char *)(&verts[curvert - 1].r);
			ol3r = p3[0];
			ol3g = p3[1];
			ol3b = p3[2];
			unsigned char *p4 =
			    (unsigned char *)(&verts[curvert - 2].r);
			ol4r = p4[0];
			ol4g = p4[1];
			ol4b = p4[2];
		}
	}

	if (isceil) {
		vert(x + size, h, y + size, l3, xo + xs, yo + ys);
		vert(x, h, y + size, l4, xo, yo + ys);
	} else {
		vert(x, h, y + size, l4, xo, yo + ys);
		vert(x + size, h, y + size, l3, xo + xs, yo + ys);
	}

	oy = y;
	nquads++;
}

// floor/ceil quads on a slope
void
render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3,
    float h4, struct sqr *l1, struct sqr *l2, struct sqr *l3, struct sqr *l4,
    bool isceil)
{
	vertcheck();
	if (showm) {
		l3 = l1 = &sbright;
		l4 = l2 = &sdark;
	}

	int sx, sy;
	int gltex = lookuptexture(wtex, &sx, &sy);
	float xf = TEXTURESCALE / sx;
	float yf = TEXTURESCALE / sy;
	float xs = size * xf;
	float ys = size * yf;
	float xo = xf * x;
	float yo = yf * y;

	bool first =
	    !deltastrip || y != oy + size || ogltex != gltex || x != ox;

	if (first) {
		stripend();
		firstindex = curvert;
		ogltex = gltex;
		ox = x;
		deltastrip = true;
		if (isceil) {
			vertf((float)x + size, h2, (float)y, l2, xo + xs, yo);
			vertf((float)x, h1, (float)y, l1, xo, yo);
		} else {
			vertf((float)x, h1, (float)y, l1, xo, yo);
			vertf((float)x + size, h2, (float)y, l2, xo + xs, yo);
		}
		ol3r = l1->r;
		ol3g = l1->g;
		ol3b = l1->b;
		ol4r = l2->r;
		ol4g = l2->g;
		ol4b = l2->b;
	}

	if (isceil) {
		vertf(
		    (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys);
		vertf((float)x, h4, (float)y + size, l4, xo, yo + ys);
	} else {
		vertf((float)x, h4, (float)y + size, l4, xo, yo + ys);
		vertf(
		    (float)x + size, h3, (float)y + size, l3, xo + xs, yo + ys);
	}

	oy = y;
	nquads++;
}

// floor/ceil tris on a corner cube
void
render_2tris(struct sqr *h, struct sqr *s, int x1, int y1, int x2, int y2,
    int x3, int y3, struct sqr *l1, struct sqr *l2, struct sqr *l3)
{
	stripend();
	vertcheck();

	int sx, sy;
	int gltex = lookuptexture(h->ftex, &sx, &sy);
	float xf = TEXTURESCALE / sx;
	float yf = TEXTURESCALE / sy;

	vertf((float)x1, h->floor, (float)y1, l1, xf * x1, yf * y1);
	vertf((float)x2, h->floor, (float)y2, l2, xf * x2, yf * y2);
	vertf((float)x3, h->floor, (float)y3, l3, xf * x3, yf * y3);
	addstrip(gltex, curvert - 3, 3);

	gltex = lookuptexture(h->ctex, &sx, &sy);
	xf = TEXTURESCALE / sx;
	yf = TEXTURESCALE / sy;

	vertf((float)x3, h->ceil, (float)y3, l3, xf * x3, yf * y3);
	vertf((float)x2, h->ceil, (float)y2, l2, xf * x2, yf * y2);
	vertf((float)x1, h->ceil, (float)y1, l1, xf * x1, yf * y1);
	addstrip(gltex, curvert - 3, 3);
	nquads++;
}

void
render_tris(int x, int y, int size, bool topleft, struct sqr *h1,
    struct sqr *h2, struct sqr *s, struct sqr *t, struct sqr *u, struct sqr *v)
{
	if (topleft) {
		if (h1)
			render_2tris(h1, s, x + size, y + size, x, y + size, x,
			    y, u, v, s);
		if (h2)
			render_2tris(h2, s, x, y, x + size, y, x + size,
			    y + size, s, t, v);
	} else {
		if (h1)
			render_2tris(
			    h1, s, x, y, x + size, y, x, y + size, s, t, u);
		if (h2)
			render_2tris(h2, s, x + size, y, x + size, y + size, x,
			    y + size, t, u, v);
	}
}

void
render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2,
    int x1, int y1, int x2, int y2, int size, struct sqr *l1, struct sqr *l2,
    bool flip) // wall quads
{
	stripend();
	vertcheck();
	if (showm) {
		l1 = &sbright;
		l2 = &sdark;
	}

	int sx, sy;
	int gltex = lookuptexture(wtex, &sx, &sy);
	float xf = TEXTURESCALE / sx;
	float yf = TEXTURESCALE / sy;
	float xs = size * xf;
	float xo = xf * (x1 == x2 ? min(y1, y2) : min(x1, x2));

	if (!flip) {
		vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2);
		vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1);
		vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf);
		vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf);
	} else {
		vertf((float)x1, ceil1, (float)y1, l1, xo, -yf * ceil1);
		vertf((float)x2, ceil2, (float)y2, l2, xo + xs, -yf * ceil2);
		vertf((float)x1, floor1, (float)y1, l1, xo, -floor1 * yf);
		vertf((float)x2, floor2, (float)y2, l2, xo + xs, -floor2 * yf);
	}

	nquads++;
	addstrip(gltex, curvert - 4, 4);
}

int wx1, wy1, wx2, wy2;

VAR(watersubdiv, 1, 4, 64);
VARF(waterlevel, -128, -128, 127,
    if (!noteditmode()) hdr.waterlevel = waterlevel);

static inline void
vertw(int v1, float v2, int v3, struct sqr *c, float t1, float t2, float t)
{
	vertcheck();
	vertf((float)v1, v2 - (float)sin(v1 * v3 * 0.1 + t) * 0.2f, (float)v3,
	    c, t1, t2);
}

inline float
dx(float x)
{
	return x + (float)sin(x * 2 + lastmillis / 1000.0f) * 0.04f;
}
inline float
dy(float x)
{
	return x + (float)sin(x * 2 + lastmillis / 900.0f + PI / 5) * 0.05f;
}

// renders water for bounding rect area that contains water... simple but very
// inefficient

int
renderwater(float hf)
{
	if (wx1 < 0)
		return nquads;

	glDepthMask(GL_FALSE);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_SRC_COLOR);
	int sx, sy;
	glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, &sx, &sy));

	wx1 &= ~(watersubdiv - 1);
	wy1 &= ~(watersubdiv - 1);

	float xf = TEXTURESCALE / sx;
	float yf = TEXTURESCALE / sy;
	float xs = watersubdiv * xf;
	float ys = watersubdiv * yf;
	float t1 = lastmillis / 300.0f;
	float t2 = lastmillis / 4000.0f;

	struct sqr dl;
	dl.r = dl.g = dl.b = 255;

	for (int xx = wx1; xx < wx2; xx += watersubdiv) {
		for (int yy = wy1; yy < wy2; yy += watersubdiv) {
			float xo = xf * (xx + t2);
			float yo = yf * (yy + t2);
			if (yy == wy1) {
				vertw(xx, hf, yy, &dl, dx(xo), dy(yo), t1);
				vertw(xx + watersubdiv, hf, yy, &dl,
				    dx(xo + xs), dy(yo), t1);
			}
			vertw(xx, hf, yy + watersubdiv, &dl, dx(xo),
			    dy(yo + ys), t1);
			vertw(xx + watersubdiv, hf, yy + watersubdiv, &dl,
			    dx(xo + xs), dy(yo + ys), t1);
		}
		int n = (wy2 - wy1 - 1) / watersubdiv;
		nquads += n;
		n = (n + 2) * 2;
		glDrawArrays(GL_TRIANGLE_STRIP, curvert -= n, n);
	}

	glDisable(GL_BLEND);
	glDepthMask(GL_TRUE);

	return nquads;
}

void
addwaterquad(int x, int y, int size) // update bounding rect that contains water
{
	int x2 = x + size;
	int y2 = y + size;
	if (wx1 < 0) {
		wx1 = x;
		wy1 = y;
		wx2 = x2;
		wy2 = y2;
	} else {
		if (x < wx1)
			wx1 = x;
		if (y < wy1)
			wy1 = y;
		if (x2 > wx2)
			wx2 = x2;
		if (y2 > wy2)
			wy2 = y2;
	}
}

void
resetcubes()
{
	if (!verts)
		reallocv();
	floorstrip = deltastrip = false;
	wx1 = -1;
	nquads = 0;
	sbright.r = sbright.g = sbright.b = 255;
	sdark.r = sdark.g = sdark.b = 0;
}
