/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 *
 * ET: Legacy
 * Copyright (C) 2012-2024 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ET: Legacy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL Source Code is also
 * subject to certain additional terms. You should have received a copy
 * of these additional terms immediately following the terms and conditions
 * of the GNU General Public License which accompanied the source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file rendererGLES/tr_sky.c
 */

#include "tr_local.h"

#define SKY_SUBDIVISIONS        8
#define HALF_SKY_SUBDIVISIONS   (SKY_SUBDIVISIONS / 2)

static float s_cloudTexCoords[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2];
static float s_cloudTexP[6][SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1];

/*
===================================================================================
POLYGON TO BOX SIDE PROJECTION
===================================================================================
*/

static vec3_t sky_clip[6] =
{
	{ 1,  1,  0 },
	{ 1,  -1, 0 },
	{ 0,  -1, 1 },
	{ 0,  1,  1 },
	{ 1,  0,  1 },
	{ -1, 0,  1 }
};

static float sky_mins[2][6], sky_maxs[2][6];
static float sky_min, sky_max;

/**
 * @brief AddSkyPolygon
 * @param[in] nump
 * @param[in] vecs
 */
static void AddSkyPolygon(int nump, vec3_t vecs)
{
	int    i, j;
	vec3_t v, av;
	float  s, t, dv;
	int    axis;
	float  *vp;

	static int vec_to_st[6][3] =
	{
		{ -2, 3,  1  },
		{ 2,  3,  -1 },

		{ 1,  3,  2  },
		{ -1, 3,  -2 },

		{ -2, -1, 3  },
		{ -2, 1,  -3 }
	};

	// decide which face it maps to
	VectorCopy(vec3_origin, v);
	for (i = 0, vp = vecs; i < nump; i++, vp += 3)
	{
		VectorAdd(vp, v, v);
	}
	av[0] = Q_fabs(v[0]);
	av[1] = Q_fabs(v[1]);
	av[2] = Q_fabs(v[2]);
	if (av[0] > av[1] && av[0] > av[2])
	{
		if (v[0] < 0)
		{
			axis = 1;
		}
		else
		{
			axis = 0;
		}
	}
	else if (av[1] > av[2] && av[1] > av[0])
	{
		if (v[1] < 0)
		{
			axis = 3;
		}
		else
		{
			axis = 2;
		}
	}
	else
	{
		if (v[2] < 0)
		{
			axis = 5;
		}
		else
		{
			axis = 4;
		}
	}

	// project new texture coords
	for (i = 0; i < nump; i++, vecs += 3)
	{
		j = vec_to_st[axis][2];
		if (j > 0)
		{
			dv = vecs[j - 1];
		}
		else
		{
			dv = -vecs[-j - 1];
		}
		if (dv < 0.001f)
		{
			continue;   // don't divide by zero
		}
		j = vec_to_st[axis][0];
		if (j < 0)
		{
			s = -vecs[-j - 1] / dv;
		}
		else
		{
			s = vecs[j - 1] / dv;
		}
		j = vec_to_st[axis][1];
		if (j < 0)
		{
			t = -vecs[-j - 1] / dv;
		}
		else
		{
			t = vecs[j - 1] / dv;
		}

		if (s < sky_mins[0][axis])
		{
			sky_mins[0][axis] = s;
		}
		if (t < sky_mins[1][axis])
		{
			sky_mins[1][axis] = t;
		}
		if (s > sky_maxs[0][axis])
		{
			sky_maxs[0][axis] = s;
		}
		if (t > sky_maxs[1][axis])
		{
			sky_maxs[1][axis] = t;
		}
	}
}

#define ON_EPSILON      0.1f    ///< point on plane side epsilon
#define MAX_CLIP_VERTS  64

/**
 * @brief ClipSkyPolygon
 * @param[in] nump
 * @param[in] vecs
 * @param[in] stage
 */
static void ClipSkyPolygon(int nump, vec3_t vecs, int stage)
{
	float    *norm;
	float    *v;
	qboolean front, back;
	float    d, e;
	float    dists[MAX_CLIP_VERTS];
	int      sides[MAX_CLIP_VERTS];
	vec3_t   newv[2][MAX_CLIP_VERTS];
	int      newc[2];
	int      i, j;

	if (nump > MAX_CLIP_VERTS - 2)
	{
		Ren_Drop("ClipSkyPolygon: MAX_CLIP_VERTS");
	}
	if (stage == 6)     // fully clipped, so draw it
	{
		AddSkyPolygon(nump, vecs);
		return;
	}

	front = back = qfalse;
	norm  = sky_clip[stage];
	for (i = 0, v = vecs; i < nump; i++, v += 3)
	{
		d = DotProduct(v, norm);
		if (d > ON_EPSILON)
		{
			front    = qtrue;
			sides[i] = SIDE_FRONT;
		}
		else if (d < -ON_EPSILON)
		{
			back     = qtrue;
			sides[i] = SIDE_BACK;
		}
		else
		{
			sides[i] = SIDE_ON;
		}
		dists[i] = d;
	}

	if (!front || !back)     // not clipped
	{
		ClipSkyPolygon(nump, vecs, stage + 1);
		return;
	}

	// clip it
	sides[i] = sides[0];
	dists[i] = dists[0];
	VectorCopy(vecs, (vecs + (i * 3)));
	newc[0] = newc[1] = 0;

	for (i = 0, v = vecs; i < nump; i++, v += 3)
	{
		switch (sides[i])
		{
		case SIDE_FRONT:
			VectorCopy(v, newv[0][newc[0]]);
			newc[0]++;
			break;
		case SIDE_BACK:
			VectorCopy(v, newv[1][newc[1]]);
			newc[1]++;
			break;
		case SIDE_ON:
			VectorCopy(v, newv[0][newc[0]]);
			newc[0]++;
			VectorCopy(v, newv[1][newc[1]]);
			newc[1]++;
			break;
		}

		if (sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
		{
			continue;
		}

		d = dists[i] / (dists[i] - dists[i + 1]);
		for (j = 0; j < 3; j++)
		{
			e                   = v[j] + d * (v[j + 3] - v[j]);
			newv[0][newc[0]][j] = e;
			newv[1][newc[1]][j] = e;
		}
		newc[0]++;
		newc[1]++;
	}

	// continue
	ClipSkyPolygon(newc[0], newv[0][0], stage + 1);
	ClipSkyPolygon(newc[1], newv[1][0], stage + 1);
}

/**
 * @brief ClearSkyBox
 */
static void ClearSkyBox(void)
{
	int i;

	for (i = 0; i < 6; i++)
	{
		sky_mins[0][i] = sky_mins[1][i] = 9999;
		sky_maxs[0][i] = sky_maxs[1][i] = -9999;
	}
}

/**
 * @brief RB_ClipSkyPolygons
 * @param[in] input
 */
void RB_ClipSkyPolygons(shaderCommands_t *input)
{
	vec3_t p[5];        // need one extra point for clipping
	int    i, j;

	ClearSkyBox();

	for (i = 0; i < input->numIndexes; i += 3)
	{
		for (j = 0 ; j < 3 ; j++)
		{
			VectorSubtract(input->xyz[input->indexes[i + j]],
			               backEnd.viewParms.orientation.origin,
			               p[j]);
		}
		ClipSkyPolygon(3, p[0], 0);
	}
}

/*
===================================================================================
CLOUD VERTEX GENERATION
===================================================================================
*/

/**
 * @brief MakeSkyVec
 * @param[in] s range from -1 to 1
 * @param[in] t range from -1 to 1
 * @param[in] axis
 * @param[out] outSt
 * @param[out] outXYZ
 */
static void MakeSkyVec(float s, float t, int axis, float outSt[2], vec3_t outXYZ)
{
	// 1 = s, 2 = t, 3 = 2048
	static int st_to_vec[6][3] =
	{
		{ 3,  -1, 2 },
		{ -3, 1,  2 },

		{ 1,  3,  2 },
		{ -1, -3, 2 },

		{ -2, -1, 3 }, // 0 degrees yaw, look straight up
		{ 2,  -1, -3}   // look straight down
	};
	vec3_t b;
	int    j, k;
	float  boxSize;

	// JPW NERVE swiped from Sherman SP fix
	//  if(glfogNum > FOG_NONE && glfogsettings[FOG_CURRENT].mode == GL_EXP) {
	if (glfogsettings[FOG_SKY].registered)         // (SA) trying this...
	{
		// boxSize = backEnd.viewParms.zFar / 1.75;        // div sqrt(3)
		// boxSize = glfogsettings[FOG_CURRENT].end / 1.75;
		boxSize = glfogsettings[FOG_SKY].end;       // (SA) trying this...

	}
	else
	{
		boxSize = backEnd.viewParms.zFar / 1.75f;        // div sqrt(3)

	}
	// JPW NERVE swiped from Sherman
	// make sure the sky is not near clipped
	if (boxSize < r_zNear->value * 2.0f)
	{
		boxSize = r_zNear->value * 2.0f;
	}

	b[0] = s * boxSize;
	b[1] = t * boxSize;
	b[2] = boxSize;

	for (j = 0 ; j < 3 ; j++)
	{
		k = st_to_vec[axis][j];
		if (k < 0)
		{
			outXYZ[j] = -b[-k - 1];
		}
		else
		{
			outXYZ[j] = b[k - 1];
		}
	}

	// avoid bilerp seam
	s = (s + 1) * 0.5f;
	t = (t + 1) * 0.5f;
	if (s < sky_min)
	{
		s = sky_min;
	}
	else if (s > sky_max)
	{
		s = sky_max;
	}

	if (t < sky_min)
	{
		t = sky_min;
	}
	else if (t > sky_max)
	{
		t = sky_max;
	}

	t = 1.0f - t;


	if (outSt)
	{
		outSt[0] = s;
		outSt[1] = t;
	}
}

static int    sky_texorder[6] = { 0, 2, 1, 3, 4, 5 };
static vec3_t s_skyPoints[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1];
static float  s_skyTexCoords[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2];

/**
 * @brief DrawSkySide
 * @param[in] image
 * @param[in] mins
 * @param[in] maxs
 */
static void DrawSkySide(struct image_s *image, const int mins[2], const int maxs[2])
{
	int s, t;

	// OpenGLES implementation
	GL_Bind(image);
	GLfloat vtx[3 * 1024];    // arbitrary sized
	GLfloat tex[2 * 1024];
	int     idx;

	GLboolean text  = glIsEnabled(GL_TEXTURE_COORD_ARRAY);
	GLboolean glcol = glIsEnabled(GL_COLOR_ARRAY);
	if (glcol)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	if (!text)
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	for (t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++)
	{
		idx = 0;

		for (s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++)
		{
			Com_Memcpy(tex + idx * 2, s_skyTexCoords[t][s], sizeof(GLfloat) * 2);
			Com_Memcpy(vtx + idx * 3, s_skyPoints[t][s], sizeof(GLfloat) * 3);
			idx++;
			Com_Memcpy(tex + idx * 2, s_skyTexCoords[t + 1][s], sizeof(GLfloat) * 2);
			Com_Memcpy(vtx + idx * 3, s_skyPoints[t + 1][s], sizeof(GLfloat) * 3);
			idx++;
		}

		glVertexPointer(3, GL_FLOAT, 0, vtx);
		glTexCoordPointer(2, GL_FLOAT, 0, tex);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, idx);
	}
}

/**
 * @brief DrawSkySideInner
 * @param[in] image
 * @param[in] mins
 * @param[in] maxs
 */
static void DrawSkySideInner(struct image_s *image, const int mins[2], const int maxs[2])
{
	int s, t;

	// OpenGLES implementation
	GL_Bind(image);
	GLfloat vtx[3 * 1024];    // arbitrary sized
	GLfloat tex[2 * 1024];
	int     idx;

	GLboolean text  = glIsEnabled(GL_TEXTURE_COORD_ARRAY);
	GLboolean glcol = glIsEnabled(GL_COLOR_ARRAY);
	if (glcol)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	if (!text)
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	//glDisable (GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	GL_TexEnv(GL_MODULATE);

	for (t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++)
	{
		idx = 0;

		for (s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++)
		{
			Com_Memcpy(tex + idx * 2, s_skyTexCoords[t][s], sizeof(GLfloat) * 2);
			Com_Memcpy(vtx + idx * 3, s_skyPoints[t][s], sizeof(GLfloat) * 3);
			idx++;
			Com_Memcpy(tex + idx * 2, s_skyTexCoords[t + 1][s], sizeof(GLfloat) * 2);
			Com_Memcpy(vtx + idx * 3, s_skyPoints[t + 1][s], sizeof(GLfloat) * 3);
			idx++;
		}

		glVertexPointer(3, GL_FLOAT, 0, vtx);
		glTexCoordPointer(2, GL_FLOAT, 0, tex);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, idx);
	}

	glDisable(GL_BLEND);
}

/**
 * @brief DrawSkyBox
 * @param[in] shader
 */
static void DrawSkyBox(shader_t *shader)
{
	int i;
	int sky_mins_subd[2], sky_maxs_subd[2];
	int s, t;

	Com_Memset(s_skyTexCoords, 0, sizeof(s_skyTexCoords));

	sky_min = 0;
	sky_max = 1;

	for (i = 0 ; i < 6 ; i++)
	{
		sky_mins[0][i] = floor(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_mins[1][i] = floor(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[0][i] = ceil(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[1][i] = ceil(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;

		if ((sky_mins[0][i] >= sky_maxs[0][i]) ||
		    (sky_mins[1][i] >= sky_maxs[1][i]))
		{
			continue;
		}

		sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS;

		if (sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		if (sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		// iterate through the subdivisions
		for (t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++)
		{
			for (s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++)
			{
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           i,
				           s_skyTexCoords[t][s],
				           s_skyPoints[t][s]);
			}
		}

		DrawSkySide(shader->sky.outerbox[sky_texorder[i]],
		            sky_mins_subd,
		            sky_maxs_subd);
	}
}

/**
 * @brief DrawSkyBoxInner
 * @param[in] shader
 */
static void DrawSkyBoxInner(shader_t *shader)
{
	int i;
	int sky_mins_subd[2], sky_maxs_subd[2];
	int s, t;

	Com_Memset(s_skyTexCoords, 0, sizeof(s_skyTexCoords));

	for (i = 0 ; i < 6 ; i++)
	{
		sky_mins[0][i] = floor(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_mins[1][i] = floor(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[0][i] = ceil(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[1][i] = ceil(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;

		if ((sky_mins[0][i] >= sky_maxs[0][i]) ||
		    (sky_mins[1][i] >= sky_maxs[1][i]))
		{
			continue;
		}

		sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS;
		sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS;

		if (sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		if (sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		// iterate through the subdivisions
		for (t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++)
		{
			for (s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++)
			{
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / ( float ) HALF_SKY_SUBDIVISIONS,
				           (t - HALF_SKY_SUBDIVISIONS) / ( float ) HALF_SKY_SUBDIVISIONS,
				           i,
				           s_skyTexCoords[t][s],
				           s_skyPoints[t][s]);
			}
		}

		DrawSkySideInner(shader->sky.innerbox[sky_texorder[i]],
		                 sky_mins_subd,
		                 sky_maxs_subd);
	}
}

/**
 * @brief FillCloudySkySide
 * @param[in] mins
 * @param[in] maxs
 * @param[in] addIndexes
 */
static void FillCloudySkySide(const int mins[2], const int maxs[2], qboolean addIndexes)
{
	int s, t;
	int vertexStart = tess.numVertexes;
	int tHeight     = maxs[1] - mins[1] + 1;
	int sWidth      = maxs[0] - mins[0] + 1;

	// overflow check
	RB_CHECKOVERFLOW((maxs[0] - mins[0]) * (maxs[1] - mins[1]), (sWidth - 1) * (tHeight - 1) * 6);

	for (t = mins[1] + HALF_SKY_SUBDIVISIONS; t <= maxs[1] + HALF_SKY_SUBDIVISIONS; t++)
	{
		for (s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++)
		{
			VectorAdd(s_skyPoints[t][s], backEnd.viewParms.orientation.origin, tess.xyz[tess.numVertexes]);
			tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0];
			tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1];

			tess.numVertexes++;

			if (tess.numVertexes >= SHADER_MAX_VERTEXES)
			{
				Ren_Drop("SHADER_MAX_VERTEXES(%i) hit in FillCloudySkySide()\n", SHADER_MAX_VERTEXES);
			}
		}
	}

	// only add indexes for one pass, otherwise it would draw multiple times for each pass
	if (addIndexes)
	{
		for (t = 0; t < tHeight - 1; t++)
		{
			for (s = 0; s < sWidth - 1; s++)
			{
				tess.indexes[tess.numIndexes] = vertexStart + s + t * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth);
				tess.numIndexes++;

				tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + (t + 1) * (sWidth);
				tess.numIndexes++;
				tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth);
				tess.numIndexes++;
			}
		}
	}
}

/**
 * @brief FillCloudBox
 * @param shader - unused
 * @param[in] stage
 */
static void FillCloudBox(const shader_t *shader, int stage)
{
	int i;

	for (i = 0; i < 6; i++)
	{
		int   sky_mins_subd[2], sky_maxs_subd[2];
		int   s, t;
		float MIN_T;

		if (1)     // FIXME? shader->sky.fullClouds )
		{
			MIN_T = -HALF_SKY_SUBDIVISIONS;

			// still don't want to draw the bottom, even if fullClouds
			if (i == 5)
			{
				continue;
			}
		}
		else
		{
			switch (i)
			{
			case 0:
			case 1:
			case 2:
			case 3:
				MIN_T = -1;
				break;
			case 5:
				// don't draw clouds beneath you
				continue;
			case 4:     // top
			default:
				MIN_T = -HALF_SKY_SUBDIVISIONS;
				break;
			}
		}

		sky_mins[0][i] = floor(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_mins[1][i] = floor(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[0][i] = ceil(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;
		sky_maxs[1][i] = ceil(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS) / HALF_SKY_SUBDIVISIONS;

		if ((sky_mins[0][i] >= sky_maxs[0][i]) || (sky_mins[1][i] >= sky_maxs[1][i]))
		{
			continue;
		}

		sky_mins_subd[0] = (int)(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS);
		sky_mins_subd[1] = (int)(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS);
		sky_maxs_subd[0] = (int)(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS);
		sky_maxs_subd[1] = (int)(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS);

		if (sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_mins_subd[1] < MIN_T)
		{
			sky_mins_subd[1] = MIN_T;
		}
		else if (sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		if (sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
		}
		else if (sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
		}
		if (sky_maxs_subd[1] < MIN_T)
		{
			sky_maxs_subd[1] = MIN_T;
		}
		else if (sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS)
		{
			sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
		}

		// iterate through the subdivisions
		for (t = sky_mins_subd[1] + HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1] + HALF_SKY_SUBDIVISIONS; t++)
		{
			for (s = sky_mins_subd[0] + HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0] + HALF_SKY_SUBDIVISIONS; s++)
			{
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           i,
				           NULL,
				           s_skyPoints[t][s]);

				s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0];
				s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1];
			}
		}

		// only add indexes for first stage
		FillCloudySkySide(sky_mins_subd, sky_maxs_subd, (qboolean) (stage == 0));
	}
}

/**
 * @brief R_BuildCloudData
 * @param[in] input
 */
void R_BuildCloudData(shaderCommands_t *input)
{
	shader_t *shader = input->shader;

	etl_assert(shader->isSky);

	sky_min = 1.0f / 256.0f;     // FIXME: not correct?
	sky_max = 255.0f / 256.0f;

	// set up for drawing
	tess.numIndexes  = 0;
	tess.numVertexes = 0;

	if (shader->sky.cloudHeight != 0.f)
	{
		// FIXME: ok, this is really wierd. it's iterating through shader stages here,
		// which is unecessary for a multi-stage sky shader, as far as i can tell
		// nuking this
#if 0
		// int          i;

		for (i = 0; i < MAX_SHADER_STAGES; i++)
		{
			if (!tess.xstages[i])
			{
				break;
			}
			FillCloudBox(input->shader, i);
		}
#else
		FillCloudBox(shader, 0);
#endif
	}
}

/**
 * @brief Called when a sky shader is parsed
 * @param[in] heightCloud
 */
void R_InitSkyTexCoords(float heightCloud)
{
	int    i, s, t;
	float  radiusWorld = 4096;
	float  p;
	float  sRad, tRad;
	vec3_t skyVec;
	vec3_t v;

	// init zfar so MakeSkyVec works even though
	// a world hasn't been bounded
	backEnd.viewParms.zFar = 1024;

	for (i = 0; i < 6; i++)
	{
		for (t = 0; t <= SKY_SUBDIVISIONS; t++)
		{
			for (s = 0; s <= SKY_SUBDIVISIONS; s++)
			{
				// compute vector from view origin to sky side integral point
				MakeSkyVec((s - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           (t - HALF_SKY_SUBDIVISIONS) / (float)HALF_SKY_SUBDIVISIONS,
				           i,
				           NULL,
				           skyVec);

				// compute parametric value 'p' that intersects with cloud layer
				p = (1.0f / (2 * DotProduct(skyVec, skyVec))) *
				    (-2 * skyVec[2] * radiusWorld +
				     2 * sqrt(Square(skyVec[2]) * Square(radiusWorld) +
				              2 * Square(skyVec[0]) * radiusWorld * heightCloud +
				              Square(skyVec[0]) * Square(heightCloud) +
				              2 * Square(skyVec[1]) * radiusWorld * heightCloud +
				              Square(skyVec[1]) * Square(heightCloud) +
				              2 * Square(skyVec[2]) * radiusWorld * heightCloud +
				              Square(skyVec[2]) * Square(heightCloud)));

				s_cloudTexP[i][t][s] = p;

				// compute intersection point based on p
				VectorScale(skyVec, p, v);
				v[2] += radiusWorld;

				// compute vector from world origin to intersection point 'v'
				VectorNormalize(v);

				sRad = Q_acos(v[0]);
				tRad = Q_acos(v[1]);

				s_cloudTexCoords[i][t][s][0] = sRad;
				s_cloudTexCoords[i][t][s][1] = tRad;
			}
		}
	}
}

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

/**
 * @brief RB_DrawSun
 *
 * @todo FIXME: sun should render behind clouds, so passing dark areas cover it up
 */
void RB_DrawSun(void)
{
	float  size;
	float  dist;
	vec3_t origin, vec1, vec2;
	byte   color[4];

	if (!tr.sunShader)
	{
		return;
	}

	if (!backEnd.skyRenderedThisView)
	{
		return;
	}
	if (!r_drawSun->integer)
	{
		return;
	}
	glPushMatrix();
	glLoadMatrixf(backEnd.viewParms.world.modelMatrix);
	glTranslatef(backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]);

	dist = backEnd.viewParms.zFar / 1.75f;       // div sqrt(3)

	// shrunk the size of the sun
	size = dist * 0.2f;

	VectorScale(tr.sunDirection, dist, origin);
	PerpendicularVector(vec1, tr.sunDirection);
	CrossProduct(tr.sunDirection, vec1, vec2);

	VectorScale(vec1, size, vec1);
	VectorScale(vec2, size, vec2);

	// farthest depth range
	glDepthRangef(1.0, 1.0);

	color[0] = color[1] = color[2] = color[3] = 255;

	// simpler sun drawing
	RB_BeginSurface(tr.sunShader, tess.fogNum);

	RB_AddQuadStamp(origin, vec1, vec2, color);
	/*
	        // vec3_t temp; init moved down
	        VectorCopy( origin, temp );
	        VectorSubtract( temp, vec1, temp );
	        VectorSubtract( temp, vec2, temp );
	        VectorCopy( temp, tess.xyz[tess.numVertexes] );
	        tess.texCoords0[tess.numVertexes][0] = 0;
	        tess.texCoords0[tess.numVertexes][1] = 0;
	        tess.vertexColors[tess.numVertexes][0] = 255;
	        tess.vertexColors[tess.numVertexes][1] = 255;
	        tess.vertexColors[tess.numVertexes][2] = 255;
	        tess.numVertexes++;

	        VectorCopy( origin, temp );
	        VectorAdd( temp, vec1, temp );
	        VectorSubtract( temp, vec2, temp );
	        VectorCopy( temp, tess.xyz[tess.numVertexes] );
	        tess.texCoords0[tess.numVertexes][0] = 0;
	        tess.texCoords0[tess.numVertexes][1] = 1;
	        tess.vertexColors[tess.numVertexes][0] = 255;
	        tess.vertexColors[tess.numVertexes][1] = 255;
	        tess.vertexColors[tess.numVertexes][2] = 255;
	        tess.numVertexes++;

	        VectorCopy( origin, temp );
	        VectorAdd( temp, vec1, temp );
	        VectorAdd( temp, vec2, temp );
	        VectorCopy( temp, tess.xyz[tess.numVertexes] );
	        tess.texCoords0[tess.numVertexes][0] = 1;
	        tess.texCoords0[tess.numVertexes][1] = 1;
	        tess.vertexColors[tess.numVertexes][0] = 255;
	        tess.vertexColors[tess.numVertexes][1] = 255;
	        tess.vertexColors[tess.numVertexes][2] = 255;
	        tess.numVertexes++;

	        VectorCopy( origin, temp );
	        VectorSubtract( temp, vec1, temp );
	        VectorAdd( temp, vec2, temp );
	        VectorCopy( temp, tess.xyz[tess.numVertexes] );
	        tess.texCoords0[tess.numVertexes][0] = 1;
	        tess.texCoords0[tess.numVertexes][1] = 0;
	        tess.vertexColors[tess.numVertexes][0] = 255;
	        tess.vertexColors[tess.numVertexes][1] = 255;
	        tess.vertexColors[tess.numVertexes][2] = 255;
	        tess.numVertexes++;

	        tess.indexes[tess.numIndexes++] = 0;
	        tess.indexes[tess.numIndexes++] = 1;
	        tess.indexes[tess.numIndexes++] = 2;
	        tess.indexes[tess.numIndexes++] = 0;
	        tess.indexes[tess.numIndexes++] = 2;
	        tess.indexes[tess.numIndexes++] = 3;
	*/
	RB_EndSurface();

	if (r_drawSun->integer > 1)     // draw flare effect
	{
		vec3_t temp;
		// FYI: This is cheezy and was only a test so far.
		//      If we decide to use the flare business I will /definatly/ improve all this

		// get a point a little closer
		dist = dist * 0.7f;
		VectorScale(tr.sunDirection, dist, origin);

		// and make the flare a little smaller
		VectorScale(vec1, 0.5f, vec1);
		VectorScale(vec2, 0.5f, vec2);

		// add the vectors to give an 'off angle' result
		VectorAdd(tr.sunDirection, backEnd.viewParms.orientation.axis[0], temp);
		VectorNormalize(temp);

		// amplify the result
		origin[0] += temp[0] * 500.0f;
		origin[1] += temp[1] * 500.0f;
		origin[2] += temp[2] * 500.0f;

		// FIXME: todo: flare effect should render last (on top of everything else) and only when sun is in view (sun moving out of camera past degree n should start to cause flare dimming until view angle to sun is off by angle n + x.

		// draw the flare
		RB_BeginSurface(tr.sunflareShader[0], tess.fogNum);
		RB_AddQuadStamp(origin, vec1, vec2, color);
		RB_EndSurface();
	}

	// back to normal depth range
	glDepthRangef(0.0, 1.0);
	glPopMatrix();
}

/**
 * @brief All of the visible sky triangles are in tess
 *
 * Other things could be stuck in here, like birds in the sky, etc
 */
void RB_StageIteratorSky(void)
{
	if (r_fastSky->integer)
	{
		return;
	}

	// when portal sky exists, only render skybox for the portal sky scene
	if (skyboxportal && !(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL))
	{
		return;
	}

	// does the current fog require fastsky?
	if (backEnd.viewParms.glFog.registered)
	{
		if (!backEnd.viewParms.glFog.drawsky)
		{
			return;
		}
	}
	else if (glfogNum > FOG_NONE)
	{
		if (!glfogsettings[FOG_CURRENT].drawsky)
		{
			return;
		}
	}

	backEnd.refdef.rdflags |= RDF_DRAWINGSKY;

	// go through all the polygons and project them onto
	// the sky box to see which blocks on each side need
	// to be drawn
	RB_ClipSkyPolygons(&tess);

	// r_showsky will let all the sky blocks be drawn in
	// front of everything to allow developers to see how
	// much sky is getting sucked in
	if (r_showSky->integer)
	{
		glDepthRangef(0.0, 0.0);
	}
	else
	{
		glDepthRangef(1.0, 1.0);
	}

	GL_Cull(CT_TWO_SIDED);

	// draw the outer skybox
	if (tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage)
	{
		glColor4f(tr.identityLight, tr.identityLight, tr.identityLight, 1.0f);

		glPushMatrix();
		GL_State(0);
		glTranslatef(backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]);

		DrawSkyBox(tess.shader);

		glPopMatrix();
	}

	// generate the vertexes for all the clouds, which will be drawn
	// by the generic shader routine
	R_BuildCloudData(&tess);

	RB_StageIteratorGeneric();

	// draw the inner skybox
	if (tess.shader->sky.innerbox[0] && tess.shader->sky.innerbox[0] != tr.defaultImage)
	{
		glColor4f(tr.identityLight, tr.identityLight, tr.identityLight, 1.0f);

		glPushMatrix();
		GL_State(0);
		glTranslatef(backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]);

		DrawSkyBoxInner(tess.shader);

		glPopMatrix();
	}

	// back to normal depth range
	glDepthRangef(0.0, 1.0);

	backEnd.refdef.rdflags &= ~RDF_DRAWINGSKY;

	// note that sky was drawn so we will draw a sun later
	backEnd.skyRenderedThisView = qtrue;
}
