#include "render.h"

extern vec3_t  xy_viewnormal;

// define    NOLIGHT

vec3_t  r_origin, r_matrix[3];

int         t_width, t_height;
unsigned    *t_data;
int         t_widthmask, t_heightmask, t_widthshift;
float       t_widthadd, t_heightadd;

int         r_width, r_height;
float       *r_zbuffer;
unsigned    *r_picbuffer;

vec5_t  rightside, leftside, rightstep, leftstep;

face_t  *r_face;

BOOL  r_drawflat;

pixel32_t  r_flatcolor;

int  sy[20];

/*
====================
REN_ClearBuffers
====================
*/
void
REN_ClearBuffers (void)
{
	int  size;

	size = r_width * r_height;

	memset (r_zbuffer, 0, size * sizeof (float));
	memset (r_picbuffer, 0, size * sizeof (unsigned));
}

/*
====================
REN_SetTexture
====================
*/
void
REN_SetTexture (face_t * face)
{
	int         i;
	int         t_heightshift;
	qtexture_t  *q;

	if (!face->qtexture)
		face->qtexture = TEX_ForName (face->texture.texture);   // try to load
	q = face->qtexture;

	t_width = q->width;
	t_height = q->height;
	t_data = q->data;
	r_flatcolor = q->flatcolor;

	r_flatcolor.chan[0] *= r_face->light;
	r_flatcolor.chan[1] *= r_face->light;
	r_flatcolor.chan[2] *= r_face->light;

	t_widthadd = t_width * 1024;
	t_heightadd = t_height * 1024;

	t_widthmask = t_width - 1;
	t_heightmask = t_height - 1;

	t_widthshift = 0;
	i = t_width;
	while (i >= 2) {
		t_widthshift++;
		i >>= 1;
	}

	t_heightshift = 0;
	i = t_width;
	while (i >= 2) {
		t_heightshift++;
		i >>= 1;
	}

	if ((1 << t_widthshift) != t_width || (1 << t_heightshift) != t_height)
		t_widthshift = t_heightshift = 0;   // non power of two
}

/*
==================
REN_DrawSpan
==================
*/
void
REN_DrawSpan (int y)
{
	int         x, count;
	int         ofs;
	int         tx, ty;
	int         x1, x2;
	float       ufrac, vfrac, zfrac, lightfrac, ustep, vstep, zstep;
	pixel32_t   *in, *out;
	float       scale;

	if (y < 0 || y >= r_height)
		return;

	x1 = (leftside[0]);
	x2 = (rightside[0]);

	count = x2 - x1;
	if (count < 0)
		return;

	zfrac = leftside[2];
	ufrac = leftside[3];
	vfrac = leftside[4];
	lightfrac = r_face->light;

	if (!count)
		scale = 1;
	else
		scale = 1.0 / count;

	zstep = (rightside[2] - zfrac) * scale;
	ustep = (rightside[3] - ufrac) * scale;
	vstep = (rightside[4] - vfrac) * scale;

	if (x1 < 0) {
		ufrac -= x1 * ustep;
		vfrac -= x1 * vstep;
		zfrac -= x1 * zstep;
		x1 = 0;
	}

	if (x2 > r_width)
		x2 = r_width;
	ofs = y * r_width + x1;

	// this should be specialized for 1.0 / 0.5 / 0.75 light levels
	for (x = x1; x < x2; x++) {
		if (r_zbuffer[ofs] <= zfrac) {
			scale = 1 / zfrac;

			r_zbuffer[ofs] = zfrac;

			if (t_widthshift) {
				tx = (int) ((ufrac * scale)) & t_widthmask;
				ty = (int) ((vfrac * scale)) & t_heightmask;
				in = (pixel32_t *) &t_data[(ty << t_widthshift) + tx];
			} else {
				tx = (int) ((ufrac * scale) + t_widthadd) % t_width;
				ty = (int) ((vfrac * scale) + t_heightadd) % t_height;
				in = (pixel32_t *) &t_data[ty * t_width + tx];
			}

			out = (pixel32_t *) &r_picbuffer[ofs];
#ifdef NOLIGHT
			*out = *in;
#else
			out->chan[0] = in->chan[0] * lightfrac;
			out->chan[1] = in->chan[1] * lightfrac;
			out->chan[2] = in->chan[2] * lightfrac;
			out->chan[3] = 0xff;
#endif
		}
		ufrac += ustep;
		vfrac += vstep;
		zfrac += zstep;
		ofs++;
	}
}

/*
==================
REN_DrawFlatSpan
==================
*/
void
REN_DrawFlatSpan (int y)
{
	int     x, count;
	int     ofs;
	int     x1, x2;
	float   zfrac, zstep;
	int     *out;

	if (y < 0 || y >= r_height)
		return;

	x1 = (leftside[0]);
	x2 = (rightside[0]);

	count = x2 - x1;
	if (count < 0)
		return;

	zfrac = leftside[2];

	zstep = (rightside[2] - zfrac) / count;

	if (x1 < 0) {
		zfrac -= x1 * zstep;
		x1 = 0;
	}

	if (x2 > r_width)
		x2 = r_width;

	ofs = y * r_width + x1;

	// this should be specialized for 1.0 / 0.5 / 0.75 light levels
	for (x = x1; x < x2; x++) {
		if (r_zbuffer[ofs] <= zfrac) {
			r_zbuffer[ofs] = zfrac;
			out = (int *) &r_picbuffer[ofs];
			*out = r_flatcolor.p;
		}
		zfrac += zstep;
		ofs++;
	}
}

/*
=====================
REN_RasterizeFace

=====================
*/
void
REN_RasterizeFace (winding_t * w)
{
	int     y;
	int     i;
	int     top, bot;
	int     leftv, rightv;
	int     count;
	int     numvertex;

	// find top vertex
	numvertex = w->numpoints;
	top = 0x7fffffff;
	bot = 0x80000000;
	leftv = 0;

	for (i = 0; i < numvertex; i++) {
		w->points[i][3] *= w->points[i][2];
		w->points[i][4] *= w->points[i][2];

		sy[i] = (int) w->points[i][1];

		if (sy[i] < top) {
			top = sy[i];
			leftv = i;
		}
		if (sy[i] > bot)
			bot = sy[i];
	}
	rightv = leftv;

	if (top < 0 || bot > r_height || top > bot)
		return;                     // shouldn't have to have this...

	// render a trapezoid
	y = top;

	while (y < bot) {
		if (y >= sy[leftv]) {
			do {
				for (i = 0; i < 5; i++)
					leftside[i] = w->points[leftv][i];

				leftv--;
				if (leftv == -1)
					leftv = numvertex - 1;
			} while (sy[leftv] <= y);
			count = sy[leftv] - y;
			for (i = 0; i < 5; i++)
				leftstep[i] = (w->points[leftv][i] - leftside[i]) / count;
		}
		if (y >= sy[rightv]) {
			do {
				for (i = 0; i < 5; i++)
					rightside[i] = w->points[rightv][i];

				rightv++;
				if (rightv == numvertex)
					rightv = 0;
			} while (sy[rightv] <= y);
			count = sy[rightv] - y;
			for (i = 0; i < 5; i++)
				rightstep[i] = (w->points[rightv][i] - rightside[i]) / count;
		}

		if (r_drawflat)
			REN_DrawFlatSpan (y);
		else
			REN_DrawSpan (y);

		for (i = 0; i < 5; i++) {
			leftside[i] += leftstep[i];
			rightside[i] += rightstep[i];
		}

		y++;
	}
}

// =============================================================================

/*
==================
REN_DrawSpanLinear
==================
*/
void
REN_DrawSpanLinear (int y)
{
	int         x, count;
	int         ofs;
	int         tx, ty;
	int         x1, x2;
	float       ufrac, vfrac, zfrac, ustep, vstep, zstep;
	pixel32_t   *in, *out;
	float       scale;

	if (y < 0 || y >= r_height)
		return;

	x1 = (leftside[0]);
	x2 = (rightside[0]);

	count = x2 - x1;
	if (count < 0)
		return;

	zfrac = leftside[2];
	ufrac = leftside[3];
	vfrac = leftside[4];

	if (!count)
		scale = 1;
	else
		scale = 1.0 / count;

	zstep = (rightside[2] - zfrac) * scale;
	ustep = (rightside[3] - ufrac) * scale;
	vstep = (rightside[4] - vfrac) * scale;

	if (x1 < 0) {
		ufrac -= x1 * ustep;
		vfrac -= x1 * vstep;
		zfrac -= x1 * zstep;
		x1 = 0;
	}

	if (x2 > r_width)
		x2 = r_width;

	ofs = y * r_width + x1;

	for (x = x1; x < x2; x++) {
		if (r_zbuffer[ofs] <= zfrac) {
			r_zbuffer[ofs] = zfrac;

			if (t_widthshift) {
				tx = (int) ufrac & t_widthmask;
				ty = (int) vfrac & t_heightmask;
				in = (pixel32_t *) &t_data[(ty << t_widthshift) + tx];
			} else {
				tx = (int) (ufrac + t_widthadd) % t_width;
				ty = (int) (vfrac + t_heightadd) % t_height;
				in = (pixel32_t *) &t_data[ty * t_width + tx];
			}

			out = (pixel32_t *) &r_picbuffer[ofs];
			*out = *in;
		}
		ufrac += ustep;
		vfrac += vstep;
		zfrac += zstep;
		ofs++;
	}
}

/*
=====================
REN_RasterizeFaceLinear

=====================
*/
void
REN_RasterizeFaceLinear (winding_t * w)
{
	int     y;
	int     i;
	int     top, bot;
	int     leftv, rightv;
	int     count;
	int     numvertex;

	// find top vertex
	numvertex = w->numpoints;
	top = 0x7fffffff;
	bot = 0x80000000;

	leftv = 0;
	for (i = 0; i < numvertex; i++) {
		sy[i] = (int) w->points[i][1];

		if (sy[i] < top) {
			top = sy[i];
			leftv = i;
		}
		if (sy[i] > bot)
			bot = sy[i];
	}
	rightv = leftv;

	if (top < 0 || bot > r_height || top > bot)
		return;                     // shouldn't have to have this...

	// render a trapezoid
	y = top;

	while (y < bot) {
		if (y >= sy[leftv]) {
			do {
				for (i = 0; i < 5; i++)
					leftside[i] = w->points[leftv][i];

				leftv--;
				if (leftv == -1)
					leftv = numvertex - 1;
			} while (sy[leftv] <= y);
			count = sy[leftv] - y;
			for (i = 0; i < 5; i++)
				leftstep[i] = (w->points[leftv][i] - leftside[i]) / count;
		}
		if (y >= sy[rightv]) {
			do {
				for (i = 0; i < 5; i++)
					rightside[i] = w->points[rightv][i];

				rightv++;
				if (rightv == numvertex)
					rightv = 0;
			} while (sy[rightv] <= y);
			count = sy[rightv] - y;
			for (i = 0; i < 5; i++)
				rightstep[i] = (w->points[rightv][i] - rightside[i]) / count;
		}

		REN_DrawSpanLinear (y);

		for (i = 0; i < 5; i++) {
			leftside[i] += leftstep[i];
			rightside[i] += rightstep[i];
		}

		y++;
	}
}

// ============================================================================

/*
==================
REN_BeginCamera
===================
*/
float       r_width_2, r_height_3;
plane_t     rfrustum[5];

void
REN_BeginCamera (void)
{
	r_width_2 = (float) r_width / 2;
	r_height_3 = (float) r_height / 3;

	// clip to right side
	rfrustum[0].normal[0] = -1;
	rfrustum[0].normal[1] = 0;
	rfrustum[0].normal[2] = 1;
	rfrustum[0].dist = 0;

	// clip to left side
	rfrustum[1].normal[0] = 1;
	rfrustum[1].normal[1] = 0;
	rfrustum[1].normal[2] = 1;
	rfrustum[1].dist = 0;

	// clip to top side
	rfrustum[2].normal[0] = 0;
	rfrustum[2].normal[1] = -1;
	rfrustum[2].normal[2] = r_height_3 / r_width_2;
	rfrustum[2].dist = 0;

	// clip to bottom side
	rfrustum[3].normal[0] = 0;
	rfrustum[3].normal[1] = 1;
	rfrustum[3].normal[2] = 2 * r_height_3 / r_width_2;
	rfrustum[3].dist = 0;

	// near Z
	rfrustum[4].normal[0] = 0;
	rfrustum[4].normal[1] = 0;
	rfrustum[4].normal[2] = 1;
	rfrustum[4].dist = 1;
}

void
REN_BeginXY (void)
{
	rfrustum[0].normal[0] = 1;
	rfrustum[0].normal[1] = 0;
	rfrustum[0].normal[2] = 0;
	rfrustum[0].dist = 0;

	rfrustum[1].normal[0] = -1;
	rfrustum[1].normal[1] = 0;
	rfrustum[1].normal[2] = 0;
	rfrustum[1].dist = -r_width;

	rfrustum[2].normal[0] = 0;
	rfrustum[2].normal[1] = 1;
	rfrustum[2].normal[2] = 0;
	rfrustum[2].dist = 0;

	rfrustum[3].normal[0] = 0;
	rfrustum[3].normal[1] = -1;
	rfrustum[3].normal[2] = 0;
	rfrustum[3].dist = -r_height;
}

/*
=====================
REN_DrawCameraFace
=====================
*/
void
REN_DrawCameraFace (face_t * idpol)
{
	int         i;
	float       scale;
	int         numvertex;
	winding_t   *w, *in;
	vec3_t      temp;

	if (!idpol->w)
		return;                     // overconstrained plane

	r_face = idpol;

	// back face cull
	if (DotProduct (r_origin, idpol->plane.normal) <= idpol->plane.dist)
		return;

	// transform in 3D (FIXME: clip first, then transform)
	in = idpol->w;
	numvertex = in->numpoints;

	w = NewWinding (numvertex);
	w->numpoints = numvertex;
	for (i = 0; i < numvertex; i++) {
		VectorSubtract (in->points[i], r_origin, temp);

		w->points[i][0] = DotProduct (temp, r_matrix[0]);
		w->points[i][1] = DotProduct (temp, r_matrix[1]);
		w->points[i][2] = DotProduct (temp, r_matrix[2]);

		w->points[i][3] = in->points[i][3];
		w->points[i][4] = in->points[i][4];
	}

	// 3D clip
	for (i = 0; i < 4; i++) {
		w = ClipWinding (w, &rfrustum[i]);
		if (!w)
			return;
	}

	// project to 2D
	for (i = 0; i < w->numpoints; i++) {
		scale = r_width_2 / w->points[i][2];
		w->points[i][0] = r_width_2 + scale * w->points[i][0];
		w->points[i][1] = r_height_3 - scale * w->points[i][1];
		w->points[i][2] = scale;
	}

	// draw it
	REN_SetTexture (idpol);

	REN_RasterizeFace (w);
	free (w);
}

/*
=====================
REN_DrawXYFace
=====================
*/
void
REN_DrawXYFace (face_t * idpol)
{
	int         i, j, numvertex;
	winding_t   *w, *in;
	float       *dest, *source;
	float       temp;

	if (!idpol->w)
		return;                     // overconstrained plane

	w = idpol->w;
	r_face = idpol;

	// back (and side) face cull
	if (DotProduct (idpol->plane.normal, xy_viewnormal) > -VECTOR_EPSILON)
		return;

	// transform
	in = idpol->w;
	numvertex = in->numpoints;

	w = NewWinding (numvertex);
	w->numpoints = numvertex;

	for (i = 0; i < numvertex; i++) {
		// using Z as a scale for the 2D projection
		w->points[i][0] = (in->points[i][0] - r_origin[0]) * r_origin[2];
		w->points[i][1] =
		    r_height - (in->points[i][1] - r_origin[1]) * r_origin[2];
		w->points[i][2] = in->points[i][2] + 3000;
		w->points[i][3] = in->points[i][3];
		w->points[i][4] = in->points[i][4];
	}

	// clip
	for (i = 0; i < 4; i++) {
		w = ClipWinding (w, &rfrustum[i]);
		if (!w)
			return;
	}

	// project to 2D
	for (i = 0; i < w->numpoints; i++) {
		dest = w->points[i];
		if (dest[0] < 0)
			dest[0] = 0;
		if (dest[0] > r_width)
			dest[0] = r_width;
		if (dest[1] < 0)
			dest[1] = 0;
		if (dest[1] > r_height)
			dest[1] = r_height;
		if (xy_viewnormal[2] > 0)
			dest[2] = 4096 - dest[2];
	}

	if (xy_viewnormal[2] > 0) {     // flip order when upside down
		for (i = 0; i < w->numpoints / 2; i++) {
			dest = w->points[i];
			source = w->points[w->numpoints - 1 - i];
			for (j = 0; j < 5; j++) {
				temp = dest[j];
				dest[j] = source[j];
				source[j] = temp;
			}
		}
	}

	REN_SetTexture (idpol);

	// draw it
	REN_RasterizeFaceLinear (w);
	free (w);
}
