/* -------------------------------------------------------------------------------

   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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 2 of the License, or
   (at your option) any later version.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

   ----------------------------------------------------------------------------------

   This code has been altered significantly from its original form, to support
   several games based on the Quake III Arena engine, in the form of "Q3Map2."

   ------------------------------------------------------------------------------- */



/* dependencies */
#include "q3map2.h"


/* must be identical to bspDrawVert_t except for float color! */
struct radVert_t
{
	Vector3 xyz;
	Vector2 st;
	Array4<Vector2> lightmap;
	Vector3 normal;
	Array4<Color4f> color;

	radVert_t() = default;
	radVert_t( const bspDrawVert_t& dv )
	:	xyz( dv.xyz ),
		st( dv.st ),
		lightmap( dv.lightmap ),
		normal( dv.normal )
	{}
};


struct radWinding_t
{
	int numVerts;
	radVert_t verts[ MAX_POINTS_ON_WINDING ];
};


/* functions */


/*
   RadClipWindingEpsilon()
   clips a rad winding by a plane
   based off the regular clip winding code
 */

static void RadClipWindingEpsilon( const radWinding_t& in, const Vector3& normal, float dist,
                                   float epsilon, radWinding_t& front, radWinding_t& back ){
	float dists[ MAX_POINTS_ON_WINDING + 4 ];
	EPlaneSide sides[ MAX_POINTS_ON_WINDING + 4 ];
	int counts[ 3 ] = { 0 };


	/* determine sides for each point */
	for ( int i = 0; i < in.numVerts; ++i )
	{
		dists[ i ] = vector3_dot( in.verts[ i ].xyz, normal ) - dist;
		if ( dists[ i ] > epsilon ) {
			sides[ i ] = eSideFront;
		}
		else if ( dists[ i ] < -epsilon ) {
			sides[ i ] = eSideBack;
		}
		else{
			sides[ i ] = eSideOn;
		}
		counts[ sides[ i ] ]++;
	}
	sides[ in.numVerts ] = sides[ 0 ];
	dists[ in.numVerts ] = dists[ 0 ];

	/* clear front and back */
	front.numVerts = back.numVerts = 0;

	/* handle all on one side cases */
	if ( counts[ 0 ] == 0 ) {
		back = in;
		return;
	}
	if ( counts[ 1 ] == 0 ) {
		front = in;
		return;
	}

	/* setup windings */
	const int maxPoints = in.numVerts + 4;

	/* do individual verts */
	for ( int i = 0; i < in.numVerts; ++i )
	{
		/* do simple vertex copies first */
		const radVert_t& v1 = in.verts[ i ];

		if ( sides[ i ] == eSideOn ) {
			front.verts[ front.numVerts++ ] = v1;
			back.verts[ back.numVerts++ ] = v1;
			continue;
		}

		if ( sides[ i ] == eSideFront ) {
			front.verts[ front.numVerts++ ] = v1;
		}

		if ( sides[ i ] == eSideBack ) {
			back.verts[ back.numVerts++ ] = v1;
		}

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

		/* generate a split vertex */
		const radVert_t& v2 = in.verts[ ( i + 1 ) % in.numVerts ];

		const float dot = dists[ i ] / ( dists[ i ] - dists[ i + 1 ] );

		/* average vertex values */
		radVert_t mid;
		/* color */
		for ( int k = 0; k < MAX_LIGHTMAPS; ++k ){
			mid.color[ k ] = v1.color[ k ] + ( v2.color[ k ] - v1.color[ k ] ) * dot;
		}
		/* xyz, normal */
		mid.xyz = v1.xyz + ( v2.xyz - v1.xyz ) * dot;
		mid.normal = v1.normal + ( v2.normal - v1.normal ) * dot;
		/* st, lightmap */
		mid.st = v1.st + ( v2.st - v1.st ) * dot;
		for ( int k = 0; k < MAX_LIGHTMAPS; ++k )
			mid.lightmap[ k ] = v1.lightmap[ k ] + ( v2.lightmap[ k ] - v1.lightmap[ k ] ) * dot;

		/* normalize the averaged normal */
		VectorNormalize( mid.normal );

		/* copy the midpoint to both windings */
		front.verts[ front.numVerts++ ] = mid;
		back.verts[ back.numVerts++ ] = mid;
	}

	/* error check */
	if ( front.numVerts > maxPoints ) {
		Error( "RadClipWindingEpsilon: points exceeded estimate" );
	}
	if ( front.numVerts > MAX_POINTS_ON_WINDING ) {
		Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" );
	}
}



inline float Modulo1IfNegative( float f ){
	return f < 0 ? f - floor( f ) : f;
}


/*
   RadSampleImage()
   samples a texture image for a given color
   returns false if pixels are bad
 */

bool RadSampleImage( const byte *pixels, int width, int height, const Vector2& st, Color4f& color ){
	int x, y;

	/* clear color first */
	color.set( 255 );

	/* dummy check */
	if ( pixels == nullptr || width < 1 || height < 1 ) {
		return false;
	}

	/* get offsets */
	x = width * Modulo1IfNegative( st[ 0 ] ) + 0.5f;
	x %= width;
	y = height * Modulo1IfNegative( st[ 1 ] ) + 0.5f;
	y %= height;

	/* get pixel */
	pixels += ( y * width * 4 ) + ( x * 4 );
	color = Color4f( pixels[ 0 ], pixels[ 1 ], pixels[ 2 ], pixels[ 3 ] );

	if ( texturesRGB ) {
		color[0] = Image_LinearFloatFromsRGBFloat( color[0] * ( 1.0 / 255.0 ) ) * 255.0;
		color[1] = Image_LinearFloatFromsRGBFloat( color[1] * ( 1.0 / 255.0 ) ) * 255.0;
		color[2] = Image_LinearFloatFromsRGBFloat( color[2] * ( 1.0 / 255.0 ) ) * 255.0;
	}

	return true;
}



/*
   RadSample()
   samples a fragment's lightmap or vertex color and returns an
   average color and a color gradient for the sample
 */

#define MAX_SAMPLES         150
#define SAMPLE_GRANULARITY  6

static void RadSample( int lightmapNum, const bspDrawSurface_t& ds, const rawLightmap_t *lm, const shaderInfo_t& si, const radWinding_t& rw, Vector3& average, Vector3& gradient, int *style ){
	int i, j, k, l, v, samples;
	Vector3 color;
	MinMax minmax;
	Color4f textureColor;
	float alpha, alphaI;

	if ( !bouncing )
		Sys_Printf( "BUG: RadSample: !bouncing shouldn't happen\n" );

	/* initial setup */
	average.set( 0 );
	gradient.set( 0 );
	alpha = 0;

	/* dummy check */
	if ( rw.numVerts < 3 ) {
		return;
	}

	/* start sampling */
	samples = 0;

	/* sample vertex colors if no lightmap or this is the initial pass */
	if ( lm == nullptr || lm->radLuxels[ lightmapNum ] == nullptr || !bouncing ) {
		for ( samples = 0; samples < rw.numVerts; ++samples )
		{
			/* multiply by texture color */
			if ( !RadSampleImage( si.lightImage->pixels, si.lightImage->width, si.lightImage->height, rw.verts[ samples ].st, textureColor ) ) {
				textureColor.rgb() = si.averageColor.rgb();
				textureColor.alpha() = 255.0f;
			}
			const float avgcolor = ( textureColor[ 0 ] + textureColor[ 1 ] + textureColor[ 2 ] ) / 3;
			color = ( ( textureColor.rgb() * bounceColorRatio + Vector3( avgcolor * ( 1 - bounceColorRatio ) ) ) / 255 ) * ( rw.verts[ samples ].color[ lightmapNum ].rgb() / 255.0f );
//			color = ( textureColor.rgb / 255 ) * ( rw.verts[ samples ].color[ lightmapNum ].rgb / 255.0f );

			minmax.extend( color );
			average += color;

			/* get alpha */
			alpha += ( textureColor.alpha() / 255.0f ) * ( rw.verts[ samples ].color[ lightmapNum ].alpha() / 255.0f );
		}

		/* set style */
		*style = ds.vertexStyles[ lightmapNum ];
	}

	/* sample lightmap */
	else
	{
		/* fracture the winding into a fan (including degenerate tris) */
		for ( v = 1; v < ( rw.numVerts - 1 ) && samples < MAX_SAMPLES; ++v )
		{
			/* get a triangle */
			const radVert_t *rv[ 3 ]{ &rw.verts[ 0 ],
			                          &rw.verts[ v ],
			                          &rw.verts[ v + 1 ] };

			/* this code is embarrassing (really should just rasterize the triangle) */
			for ( i = 1; i < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; ++i )
			{
				for ( j = 1; j < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; ++j )
				{
					for ( k = 1; k < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; ++k )
					{
						/* create a blend vector (barycentric coordinates) */
						DoubleVector3 blend( i, j, k );
						blend *= 1.0 / ( blend[ 0 ] + blend[ 1 ] + blend[ 2 ] );

						/* create a blended sample */
						Vector2 st( 0 );
						Vector2 lightmap( 0 );
						alphaI = 0;
						for ( l = 0; l < 3; ++l )
						{
							st += rv[ l ]->st * blend[ l ];
							lightmap += rv[ l ]->lightmap[ 0 ] * blend[ l ];
							alphaI += rv[ l ]->color[ lightmapNum ].alpha() * blend[ l ];
						}

						/* get lightmap xy coords */
						const int x = std::clamp( int( lightmap[ 0 ] / superSample ), 0, lm->w - 1 );
						const int y = std::clamp( int( lightmap[ 1 ] / superSample ), 0, lm->h - 1 );

						/* get radiosity luxel */
						const Vector3& radLuxel = lm->getRadLuxel( lightmapNum, x, y );

						/* ignore unlit/unused luxels */
						if ( radLuxel[ 0 ] < 0 ) {
							continue;
						}

						/* inc samples */
						samples++;

						/* multiply by texture color */
						if ( !RadSampleImage( si.lightImage->pixels, si.lightImage->width, si.lightImage->height, st, textureColor ) ) {
							textureColor.rgb() = si.averageColor.rgb();
							textureColor.alpha() = 255;
						}
						const float avgcolor = ( textureColor[ 0 ] + textureColor[ 1 ] + textureColor[ 2 ] ) / 3;
						color = ( ( textureColor.rgb() * bounceColorRatio + Vector3( avgcolor * ( 1 - bounceColorRatio ) ) ) / 255 ) * ( radLuxel / 255 );
						//Sys_Printf( "%i %i %i %i %i \n", (int) textureColor.rgb[ 0 ], (int) textureColor.rgb[ 1 ], (int) textureColor.rgb[ 2 ], (int) avgcolor, (int) color[ i ] );
						minmax.extend( color );
						average += color;

						/* get alpha */
						alpha += ( textureColor.alpha() / 255 ) * ( alphaI / 255 );
					}
				}
			}
		}

		/* set style */
		*style = ds.lightmapStyles[ lightmapNum ];
	}

	/* any samples? */
	if ( samples <= 0 ) {
		return;
	}

	/* average the color */
	average *= ( 1.0 / samples );

	/* create the color gradient */
	//%	VectorSubtract( minmax.maxs, minmax.mins, delta );

	/* new: color gradient will always be 0-1.0, expressed as the range of light relative to overall light */
	//%	gradient[ 0 ] = minmax.maxs[ 0 ] > 0.0f ? ( minmax.maxs[ 0 ] - minmax.mins[ 0 ] ) / minmax.maxs[ 0 ] : 0.0f;
	//%	gradient[ 1 ] = minmax.maxs[ 1 ] > 0.0f ? ( minmax.maxs[ 1 ] - minmax.mins[ 1 ] ) / minmax.maxs[ 1 ] : 0.0f;
	//%	gradient[ 2 ] = minmax.maxs[ 2 ] > 0.0f ? ( minmax.maxs[ 2 ] - minmax.mins[ 2 ] ) / minmax.maxs[ 2 ] : 0.0f;

	/* newer: another contrast function */
	gradient = ( minmax.maxs - minmax.mins ) * minmax.maxs;
}



/*
   RadSubdivideDiffuseLight()
   subdivides a radiosity winding until it is smaller than subdivide, then generates an area light
 */

#define RADIOSITY_MAX_GRADIENT      0.75f   //%	0.25f
#define RADIOSITY_VALUE             500.0f
#define RADIOSITY_MIN               0.0001f
#define RADIOSITY_CLIP_EPSILON      0.125f



static void RadSubdivideDiffuseLight( int lightmapNum, const bspDrawSurface_t& ds, const rawLightmap_t *lm, const shaderInfo_t& si,
                                      float scale, float subdivide, const radWinding_t& rw ){
	int style = 0;
	float area, value;
	Vector3 normal, color, gradient;


	/* dummy check */
	if ( rw.numVerts < 3 ) {
		return;
	}

	/* get bounds for winding */
	MinMax minmax;
	for ( const radVert_t& vert : Span( rw.verts, rw.numVerts ) )
		minmax.extend( vert.xyz );

	/* subdivide if necessary */
	for ( int i = 0; i < 3; ++i )
	{
		if ( minmax.maxs[ i ] - minmax.mins[ i ] > subdivide ) {
			auto front = std::make_unique<radWinding_t>(); // prevent stack overflow
			auto back = std::make_unique<radWinding_t>();

			/* make axial plane */
			const float dist = ( minmax.maxs[ i ] + minmax.mins[ i ] ) * 0.5f;

			/* clip the winding */
			RadClipWindingEpsilon( rw, g_vector3_axes[i], dist, RADIOSITY_CLIP_EPSILON, *front, *back );

			/* recurse */
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, *front );
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, *back );
			return;
		}
	}

	/* check area */
	area = 0;
	for ( int i = 2; i < rw.numVerts; ++i )
	{
		area += 0.5f * vector3_length( vector3_cross( rw.verts[ i - 1 ].xyz - rw.verts[ 0 ].xyz, rw.verts[ i ].xyz - rw.verts[ 0 ].xyz ) );
	}
	if ( area < 1 || area > 20000000.0f ) {
		return;
	}

	/* more subdivision may be necessary */
	if ( bouncing ) {
		/* get color sample for the surface fragment */
		RadSample( lightmapNum, ds, lm, si, rw, color, gradient, &style );

		/* if color gradient is too high, subdivide again */
		if ( subdivide > minDiffuseSubdivide &&
		     ( gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT ) ) {
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, ( subdivide / 2.0f ), rw );
			return;
		}
	}

	/* create an average normal */
	normal.set( 0 );
	for ( const radVert_t& vert : Span( rw.verts, rw.numVerts ) )
	{
		normal += vert.normal;
	}
	normal /= rw.numVerts;
	if ( VectorNormalize( normal ) == 0 ) {
		return;
	}

	/* early out? */
	if ( bouncing && vector3_length( color ) < RADIOSITY_MIN ) {
		return;
	}

	/* debug code */
	//%	Sys_Printf( "Size: %d %d %d\n", (int)( minmax.maxs[ 0 ] - minmax.mins[ 0 ] ), (int)( minmax.maxs[ 1 ] - minmax.mins[ 1 ] ), (int)( minmax.maxs[ 2 ] - minmax.mins[ 2 ] ) );
	//%	Sys_Printf( "Grad: %f %f %f\n", gradient[ 0 ], gradient[ 1 ], gradient[ 2 ] );

	/* increment counts */
	numDiffuseLights++;
	switch ( ds.surfaceType )
	{
	case MST_PLANAR:
		numBrushDiffuseLights++;
		break;

	case MST_TRIANGLE_SOUP:
		numTriangleDiffuseLights++;
		break;

	case MST_PATCH:
		numPatchDiffuseLights++;
		break;

	default:
		break;
	}


	/* create a light */
	ThreadLock();
	light_t& light = lights.emplace_front();
	ThreadUnlock();

	/* initialize the light */
	light.flags = LightFlags::DefaultArea;
	light.type = ELightType::Area;
	light.si = &si;
	light.fade = 1;
	/* create a regular winding */
	light.w = AllocWinding( rw.numVerts );
	for ( const radVert_t& vert : Span( rw.verts, rw.numVerts ) )
	{
		light.w.push_back( vert.xyz );
	}

	/* set falloff threshold */
	light.falloffTolerance = falloffTolerance;

	/* bouncing light? */
	if ( !bouncing ) {
		/* This is weird. This actually handles surfacelight and not
		 * bounces. */

		/* handle first-pass lights in normal q3a style */
		value = si.value;
		light.photons = value * area * areaScale;
		light.add = value * formFactorValueScale * areaScale;
		light.color = si.color;
		light.style = noStyles || !style_is_valid( si.lightStyle )? LS_NORMAL : si.lightStyle;

		/* set origin */
		light.origin = minmax.origin();

		/* nudge it off the plane a bit */
		light.normal = normal;
		light.origin += light.normal;
		light.dist = vector3_dot( light.origin, normal );

#if 0
		/* optionally create a point backsplash light */
		if ( si.backsplashFraction > 0 ) {
			/* allocate a new point light */
			light_t& splash = lights.emplace_front();

			/* set it up */
			splash.flags = LightFlags::DefaultQ3A;
			splash.type = ELightType::Point;
			splash.photons = light.photons * si.backsplashFraction;

			splash.fade = 1;
			splash.si = si;
			splash.origin = normal * si.backsplashDistance + light.origin;
			splash.color = si.color;

			splash.falloffTolerance = falloffTolerance;
			splash.style = noStyles ? LS_NORMAL : light.style;

			/* add to counts */
			numPointLights++;
		}
#endif

#if 1
		/* optionally create area backsplash light */
		//if ( original && si.backsplashFraction > 0 ) {
		if ( si.backsplashFraction > 0 && !( si.compileFlags & C_SKY ) ) {
			/* allocate a new area light */
			ThreadLock();
			light_t& splash = lights.emplace_front();
			ThreadUnlock();

			/* set it up */
			splash.flags = LightFlags::DefaultArea;
			splash.type = ELightType::Area;
			splash.photons = light.photons * 7.0f * si.backsplashFraction;
			splash.add = light.add * 7.0f * si.backsplashFraction;
			splash.fade = 1;
			splash.si = &si;
			splash.color = si.color;
			splash.falloffTolerance = falloffTolerance;
			splash.style = noStyles || !style_is_valid( si.lightStyle )? LS_NORMAL : si.lightStyle;

			/* create a regular winding */
			splash.w = AllocWinding( rw.numVerts );
			for ( int i = rw.numVerts; i-- != 0; )
				splash.w.push_back( rw.verts[i].xyz + normal * si.backsplashDistance );

			splash.origin = normal * si.backsplashDistance + light.origin;
			splash.normal = -normal;
			splash.dist = vector3_dot( splash.origin, splash.normal );

//			splash.flags |= LightFlags::Twosided;
		}
#endif
	}
	else
	{
		/* handle bounced light (radiosity) a little differently */
		value = RADIOSITY_VALUE * si.bounceScale * 0.375f;
		light.photons = value * area * bounceScale;
		light.add = value * formFactorValueScale * bounceScale;
		light.color = color;
		light.style = noStyles || !style_is_valid( style )? LS_NORMAL : style;

		/* set origin */
		light.origin = WindingCenter( light.w );

		/* nudge it off the plane a bit */
		light.normal = normal;
		light.origin += light.normal;
		light.dist = vector3_dot( light.origin, normal );
	}

	if ( light.photons < 0 || light.add < 0 || light.color[0] < 0 || light.color[1] < 0 || light.color[2] < 0 )
		Sys_Printf( "BUG: RadSubdivideDiffuseLight created a darkbulb\n" );

	/* emit light from both sides? */
	if ( si.compileFlags & C_FOG || si.twoSided ) {
		light.flags |= LightFlags::Twosided;
	}

	//%	Sys_Printf( "\nAL: C: (%6f, %6f, %6f) [%6f] N: (%6f, %6f, %6f) %s\n",
	//%		light.color[ 0 ], light.color[ 1 ], light.color[ 2 ], light.add,
	//%		light.normal[ 0 ], light.normal[ 1 ], light.normal[ 2 ],
	//%		light.si.shader );
}


/*
   RadLightForTriangles()
   creates unbounced diffuse lights for triangle soup (misc_models, etc)
 */

void RadLightForTriangles( int num, int lightmapNum, const rawLightmap_t *lm, const shaderInfo_t& si, float scale, float subdivide ){
	radWinding_t rw;


	/* get surface */
	const bspDrawSurface_t& ds = bspDrawSurfaces[ num ];

	/* each triangle is a potential emitter */
	rw.numVerts = 3;
	for ( int i = 0; i < ds.numIndexes; i += 3 )
	{
		/* copy each vert */
		for ( int j = 0; j < 3; ++j )
		{
			/* get vertex index and rad vertex luxel */
			const int v = ds.firstVert + bspDrawIndexes[ ds.firstIndex + i + j ];

			/* get most everything */
			rw.verts[ j ] = yDrawVerts[ v ];

			/* fix colors */
			for ( int k = 0; k < MAX_LIGHTMAPS; ++k )
			{
				rw.verts[ j ].color[ k ].rgb() = getRadVertexLuxel( k, ds.firstVert + bspDrawIndexes[ ds.firstIndex + i + j ] );
				rw.verts[ j ].color[ k ].alpha() = yDrawVerts[ v ].color[ k ].alpha();
			}
		}

		/* subdivide into area lights */
		RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, rw );
	}
}



/*
   RadLightForPatch()
   creates unbounced diffuse lights for patches
 */

#define PLANAR_EPSILON  0.1f

void RadLightForPatch( int num, int lightmapNum, const rawLightmap_t *lm, const shaderInfo_t& si, float scale, float subdivide ){
	/* get surface */
	const bspDrawSurface_t& ds = bspDrawSurfaces[ num ];
	const surfaceInfo_t& info = surfaceInfos[ num ];

	/* construct a bogus vert list with color index stuffed into color[ 0 ] */
	mesh_t bogus( ds.patchWidth, ds.patchHeight );
	std::copy_n( &yDrawVerts[ ds.firstVert ], ds.numVerts, bogus.verts() );
	for ( int i = 0; i < ds.numVerts; ++i )
		bogus.verts()[ i ].color[ 0 ][ 0 ] = i;

	/* build a subdivided mesh identical to shadow facets for this patch */
	/* this MUST MATCH FacetsForPatch() identically! */
	mesh_t mesh = TessellatedMesh( bogus, info.patchIterations );

	/* FIXME: build interpolation table into color[ 1 ] */

	/* fix up color indexes */
	for ( bspDrawVert_t& vert : mesh )
	{
		if ( vert.color[ 0 ][ 0 ] >= ds.numVerts ) {
			vert.color[ 0 ][ 0 ] = ds.numVerts - 1;
		}
	}

	/* iterate through the mesh quads */
	for( MeshQuadIterator it( mesh ); it; ++it )
	{
		const QuadRef quad( it.quad() );
		/* planar? */
		Plane3f plane;
		const bool planar = PlaneFromPoints( plane, quad[ 0 ]->xyz, quad[ 1 ]->xyz, quad[ 2 ]->xyz )
		                    && std::fabs( plane3_distance_to_point( plane, quad[ 3 ]->xyz ) ) < PLANAR_EPSILON;
		/* generate a quad */
		if ( planar ) {
			radWinding_t rw;
			rw.numVerts = 4;
			for ( int v = 0; v < 4; ++v )
			{
				/* get most everything */
				rw.verts[ v ] = *quad[ v ];

				/* fix colors */
				for ( int i = 0; i < MAX_LIGHTMAPS; ++i )
				{
					rw.verts[ v ].color[ i ].rgb() = getRadVertexLuxel( i, ds.firstVert + quad[ v ]->color[ 0 ][ 0 ] );
					rw.verts[ v ].color[ i ].alpha() = quad[ v ]->color[ i ].alpha();
				}
			}

			/* subdivide into area lights */
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, rw );
		}

		/* generate 2 tris */
		else
		{
			radWinding_t rw;
			rw.numVerts = 3;
			for ( const TriRef& tri : it.tris() )
			{
				for ( int v = 0; v < 3; ++v )
				{
					/* get most everything */
					rw.verts[ v ] = *tri[ v ];

					/* fix colors */
					for ( int i = 0; i < MAX_LIGHTMAPS; ++i )
					{
						rw.verts[ v ].color[ i ].rgb() = getRadVertexLuxel( i, ds.firstVert + tri[ v ]->color[ 0 ][ 0 ] );
						rw.verts[ v ].color[ i ].alpha() = tri[ v ]->color[ i ].alpha();
					}
				}

				/* subdivide into area lights */
				RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, rw );
			}
		}
	}
}




/*
   RadLight()
   creates unbounced diffuse lights for a given surface
 */

static void RadLight( int num ){
	/* get drawsurface, lightmap, and shader info */
	const bspDrawSurface_t& ds = bspDrawSurfaces[ num ];
	const surfaceInfo_t& info = surfaceInfos[ num ];
	const rawLightmap_t *lm = info.lm;
	const shaderInfo_t& si = *info.si;
	const float scale = si.bounceScale;

	/* find nodraw bit */
	int contentFlags = 0, surfaceFlags = 0, compileFlags = 0;
	ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, &compileFlags );

	// jal : avoid bouncing on trans surfaces
	ApplySurfaceParm( "trans", &contentFlags, &surfaceFlags, &compileFlags );

	/* early outs? */
	if ( scale <= 0 || ( si.compileFlags & C_SKY ) || si.autosprite ||
	     ( bspShaders[ ds.shaderNum ].contentFlags & contentFlags ) || ( bspShaders[ ds.shaderNum ].surfaceFlags & surfaceFlags ) ||
	     ( si.compileFlags & compileFlags ) ) {
		return;
	}

	/* determine how much we need to chop up the surface */
	const float subdivide = si.lightSubdivide? si.lightSubdivide : diffuseSubdivide;

	/* inc counts */
	numDiffuseSurfaces++;

	/* iterate through styles (this could be more efficient, yes) */
	for ( int lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; ++lightmapNum )
	{
		/* switch on type */
		if ( ds.lightmapStyles[ lightmapNum ] != LS_NONE && ds.lightmapStyles[ lightmapNum ] != LS_UNUSED ) {
			switch ( ds.surfaceType )
			{
			case MST_PLANAR:
			case MST_TRIANGLE_SOUP:
				RadLightForTriangles( num, lightmapNum, lm, si, scale, subdivide );
				break;

			case MST_PATCH:
				RadLightForPatch( num, lightmapNum, lm, si, scale, subdivide );
				break;

			default:
				break;
			}
		}
	}
}



/*
   RadCreateDiffuseLights()
   creates lights for unbounced light on surfaces in the bsp
 */

void RadCreateDiffuseLights(){
	/* startup */
	Sys_FPrintf( SYS_VRB, "--- RadCreateDiffuseLights ---\n" );
	numDiffuseSurfaces = 0;
	numDiffuseLights = 0;
	numBrushDiffuseLights = 0;
	numTriangleDiffuseLights = 0;
	numPatchDiffuseLights = 0;
	static int iterations = 0;

	/* hit every surface (threaded) */
	RunThreadsOnIndividual( bspDrawSurfaces.size(), true, RadLight );

	/* dump the lights generated to a file */
	if ( dump && !lights.empty() ) {
		char dumpName[ 1024 ], ext[ 64 ];

		strcpy( dumpName, source );
		sprintf( ext, "_bounce_%03d.map", iterations );
		path_set_extension( dumpName, ext );
		FILE *file = fopen( dumpName, "wb" );
		Sys_Printf( "Writing %s...\n", dumpName );
		if ( file ) {
			for ( const light_t& light : lights )
			{
				fprintf( file,
				         "{\n"
				         "\"classname\" \"light\"\n"
				         "\"light\" \"%d\"\n"
				         "\"origin\" \"%.0f %.0f %.0f\"\n"
				         "\"_color\" \"%.3f %.3f %.3f\"\n"
				         "}\n",

				         (int) light.add,

				         light.origin[ 0 ],
				         light.origin[ 1 ],
				         light.origin[ 2 ],

				         light.color[ 0 ],
				         light.color[ 1 ],
				         light.color[ 2 ] );
			}
			fclose( file );
		}
	}

	/* increment */
	iterations++;

	/* print counts */
	Sys_Printf( "%8d diffuse surfaces\n", numDiffuseSurfaces );
	Sys_FPrintf( SYS_VRB, "%8d total diffuse lights\n", numDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d brush diffuse lights\n", numBrushDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d patch diffuse lights\n", numPatchDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d triangle diffuse lights\n", numTriangleDiffuseLights );
}
