/*
Copyright (C) 2003-2006 Andrey Nazarov

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/*
 * gl_surf.c -- surface post-processing code
 * 
 */
#include "gl_local.h"

lightmapBuilder_t lm;

static cvar_t *gl_coloredlightmaps;
static cvar_t *gl_brightness;

cvar_t *gl_modulate_hack;

/*
=============================================================================

LIGHTMAP TEXTURES BUILDING

=============================================================================
*/

static float colorScale, brightness;

static qboolean LM_AllocBlock( int w, int h, int *s, int *t ) {
    int i, j;
    int x, y, maxInuse, minInuse;

	x = 0; y = LM_BLOCK_HEIGHT;
    minInuse = LM_BLOCK_HEIGHT;
    for( i = 0; i < LM_BLOCK_WIDTH - w; i++ ) {
        maxInuse = 0;
        for( j = 0; j < w; j++ ) {
            if( lm.inuse[ i + j ] >= minInuse ) {
                break;
            }
            if( maxInuse < lm.inuse[ i + j ] ) {
                maxInuse = lm.inuse[ i + j ];
            }
        }
        if( j == w ) {
            x = i;
            y = minInuse = maxInuse;
        }
    }

    if( y + h > LM_BLOCK_HEIGHT ) {
        return qfalse;
    }
    
    for( i = 0; i < w; i++ ) {
        lm.inuse[ x + i ] = y + h;
    }

    *s = x;
    *t = y;
    return qtrue;
}

static void LM_InitBlock( void ) {
    int i;
    
    for( i = 0; i < LM_BLOCK_WIDTH; i++ ) {
        lm.inuse[i] = 0;
    }
}

static void LM_UploadBlock( void ) {
    /* lightmap images would be automatically freed
	 * by R_FreeUnusedImages on next level load */
    GL_SelectTMU( 1 );
    lm.lightmaps[lm.numMaps] = R_CreateImage( va( "*lightmap%d", lm.numMaps ),
            lm.buffer, LM_BLOCK_WIDTH, LM_BLOCK_HEIGHT,
                it_lightmap, if_auto );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    lm.numMaps++;
    GL_SelectTMU( 0 );
}

static int LM_BuildSurfaceLightmap( bspSurface_t *surf ) {
    byte *ptr, *dst, *src;
    int i, j;
    bspPoly_t *poly;
    vec_t *vert;
    int smax, tmax, s, t;
    
    smax = ( surf->extents[0] >> 4 ) + 1;
    tmax = ( surf->extents[1] >> 4 ) + 1;

    if( !LM_AllocBlock( smax, tmax, &s, &t ) ) {
        LM_UploadBlock();
        if( lm.numMaps == LM_MAX_LIGHTMAPS ) {
            Com_EPrintf( "LM_MAX_LIGHTMAPS exceeded\n" );
            return -1;
        }
        LM_InitBlock();
        if( !LM_AllocBlock( smax, tmax, &s, &t ) ) {
            Com_EPrintf( "LM_AllocBlock( %d, %d ) failed\n",
                    smax, tmax );
            return -1;
        }
    }
    
    src = surf->lightmap;
    dst = &lm.buffer[ ( t * LM_BLOCK_WIDTH + s ) << 2 ];

    for( i = 0; i < tmax; i++ ) {
        ptr = dst;
        for( j = 0; j < smax; j++ ) {
			float r, g, b, min, max, mid;

			r = src[0];
			g = src[1];
			b = src[2];

			if( colorScale != 1.0f ) {
			min = max = r;
				if ( g < min ) min = g;
				if ( b < min ) min = b;
				if ( g > max ) max = g;
				if ( b > max ) max = b;
				mid = 0.5 * ( min + max );
				r = mid + ( r - mid ) * colorScale;
				g = mid + ( g - mid ) * colorScale;
				b = mid + ( b - mid ) * colorScale;
			}

			if( !gl_modulate_hack->integer ) {
				r *= gl_modulate->value;
				g *= gl_modulate->value;
				b *= gl_modulate->value;
			} 

			max = g;
			if( r > max ) {
				max = r;
			}
			if( b > max ) {
				max = b;
			}

			if( max > 255 ) {
				r *= 255.0f / max;
				g *= 255.0f / max;
				b *= 255.0f / max;
			}

			//atu brightness adjustments
			brightness = 255.0f * gl_brightness->value;
			r += brightness;
			g += brightness;
			b += brightness;
			if ( r > 255.0f ) r = 255.0f;
			else if ( r < 0.0f ) r = 0.0f;
			if ( g > 255.0f ) g = 255.0f;
			else if ( g < 0.0f ) g = 0.0f;
			if ( b > 255.0f ) b = 255.0f;
			else if ( b < 0.0f ) b = 0.0f;

			src[0] = r;
			src[1] = g;
			src[2] = b;

			ptr[0] = src[0];
            ptr[1] = src[1];
            ptr[2] = src[2];
            ptr[3] = 255;

            src += 3; ptr += 4;
        }
        dst += LM_BLOCK_WIDTH * 4;
    }
    
    surf->lightmapnum = lm.numMaps + 1;

    s = ( s << 4 ) + 8;
    t = ( t << 4 ) + 8;

    poly = surf->polys;
    vert = poly->vertices;
    for( i = 0; i < poly->numVerts; i++ ) {
        vert[5] = vert[3] - surf->texturemins[0] + s;
        vert[6] = vert[4] - surf->texturemins[1] + t;
        vert[5] /= LM_BLOCK_WIDTH * 16;
        vert[6] /= LM_BLOCK_HEIGHT * 16;
        vert += VERTEX_SIZE;
    }

    return 0;
}

/*
=============================================================================

POLYGONS BUILDING

=============================================================================
*/

static void GL_CalcSurfaceExtents( bspSurface_t *surf ) {
	vec2_t mins, maxs;
    int bmins[2], bmaxs[2];
	int i;
    bspPoly_t *poly = surf->polys;
    vec_t *vert;

	mins[0] = mins[1] = 99999;
	maxs[0] = maxs[1] = -99999;

    vert = poly->vertices;
	for( i = 0; i < poly->numVerts; i++ ) {
		if( mins[0] > vert[3] ) mins[0] = vert[3];
		if( maxs[0] < vert[3] ) maxs[0] = vert[3];
		
		if( mins[1] > vert[4] ) mins[1] = vert[4];
		if( maxs[1] < vert[4] ) maxs[1] = vert[4];

		vert += VERTEX_SIZE;
	}

    bmins[0] = floor( mins[0] / 16 );
    bmins[1] = floor( mins[1] / 16 );
    bmaxs[0] = ceil( maxs[0] / 16 );
    bmaxs[1] = ceil( maxs[1] / 16 );

	surf->texturemins[0] = bmins[0] << 4;
	surf->texturemins[1] = bmins[1] << 4;

	surf->extents[0] = ( bmaxs[0] - bmins[0] ) << 4;
	surf->extents[1] = ( bmaxs[1] - bmins[1] ) << 4;
    
}

#define NOLIGHT_MASK \
    (SURF_SKY|SURF_WARP|SURF_TRANS33|SURF_TRANS66)

static int GL_BuildSurfacePoly( bspSurface_t *surf ) {
	int *src_surfedge;
	dvertex_t *src_vert;
	dedge_t *src_edge;
	vec_t *dst_vert;
	bspTexinfo_t *texinfo;
	bspPoly_t *poly;
	int i;
	int index, vertIndex;
    int numEdges = surf->numSurfEdges;
	
	poly = sys.HunkAlloc( &r_world.pool, sizeof( *poly ) +
            sizeof( vec_t ) * VERTEX_SIZE * ( numEdges - 1 ) );
	poly->next = NULL;
	poly->numVerts = numEdges;
	poly->numIndices = ( numEdges - 2 ) * 3;
    surf->polys = poly;

	texinfo = surf->texinfo;
	if( !surf->lightmap || ( texinfo->flags & NOLIGHT_MASK ) ||
            gl_fullbright->integer )
    {
		surf->type = DSURF_NOLM;
	} else {
		surf->type = DSURF_POLY;
	}
    
	src_surfedge = surf->firstSurfEdge;
	dst_vert = poly->vertices;
	for( i = 0; i < numEdges; i++ ) {
		index = *src_surfedge++;
		
		vertIndex = 0;
		if( index < 0 ) {
			index = -index;
			vertIndex = 1;
		}

		if( index >= r_world.numEdges ) {
			printf( "LoadFace: bad edge index" );
			return -1;
		}

		src_edge = r_world.edges + index;
		src_vert = r_world.vertices + src_edge->v[vertIndex];

		VectorCopy( src_vert->point, dst_vert );
		
		/* texture coordinates */
		dst_vert[3] = DotProduct( dst_vert, texinfo->axis[0] ) +
            texinfo->offset[0];
		dst_vert[4] = DotProduct( dst_vert, texinfo->axis[1] ) +
            texinfo->offset[1];

        /* lightmap coordinates */
        dst_vert[5] = 0;
        dst_vert[6] = 0;

		dst_vert += VERTEX_SIZE;
	}

    return 0;

}

#define SUBDIVIDE_SIZE  64
#define SUBDIVIDE_VERTS 64

static bspSurface_t *warpsurf;

static void BoundPolygon( int numVerts, const vec_t *verts,
        vec3_t mins, vec3_t maxs )
{
    ClearBounds( mins, maxs );

    while( numVerts-- ) {
        AddPointToBounds( verts, mins, maxs );
        verts += 3;
    }
}


static void SubdividePolygon_r( int numVerts, vec_t *verts ) {
    int i, j;
    vec3_t front[SUBDIVIDE_VERTS];
    vec3_t back[SUBDIVIDE_VERTS];
    vec_t dist[SUBDIVIDE_VERTS];
    vec3_t mins, maxs;
    int f, b;
    vec_t mid, frac, scale, *v;
    bspPoly_t *poly;
    vec3_t total;
    vec_t total_s, total_t;
    bspTexinfo_t *texinfo;
    
    if( numVerts > SUBDIVIDE_VERTS - 4 ) {
        Com_Error( ERR_DROP, "SubdividePolygon_r: numVerts = %d", numVerts );
    }

    BoundPolygon( numVerts, verts, mins, maxs );

    for( i = 0; i < 3; i++ ) {
        mid = ( mins[i] + maxs[i] ) * 0.5f;
        mid = SUBDIVIDE_SIZE * floor( mid / SUBDIVIDE_SIZE + 0.5f );
        if( mid - mins[i] < 8 ) {
            continue;
        }
        if( maxs[i] - mid < 8 ) {
            continue;
        }

        v = verts + i;
        for( j = 0; j < numVerts; j++, v += 3 ) {
            dist[j] = *v - mid;
        }

        dist[j] = dist[0];
        VectorCopy( verts, v - i );

        f = b = 0;
        v = verts;
        for( j = 0; j < numVerts; j++, v += 3 ) {
            if( dist[j] >= 0 ) {
                VectorCopy( v, front[f] ); f++;
            }
            if( dist[j] <= 0 ) {
                VectorCopy( v, back[b] ); b++;
            }
            if( dist[j] == 0 || dist[j+1] == 0 ) {
                continue;
            }
            if( ( dist[j] > 0 ) != ( dist[j+1] > 0 ) ) {
                frac = dist[j] / ( dist[j] - dist[j+1] );
                front[f][0] = back[b][0] = v[0] + frac * ( v[3+0] - v[0] );
                front[f][1] = back[b][1] = v[1] + frac * ( v[3+1] - v[1] );
                front[f][2] = back[b][2] = v[2] + frac * ( v[3+2] - v[2] );
                f++; b++;
            }
        }

        SubdividePolygon_r( f, front[0] );
        SubdividePolygon_r( b, back[0] );
        return;
    }

    poly = sys.HunkAlloc( &r_world.pool, sizeof( *poly ) +
            sizeof( vec_t ) * VERTEX_SIZE * numVerts );
    poly->next = warpsurf->polys;
    poly->numVerts = numVerts + 1;
    poly->numIndices = numVerts * 3;
    warpsurf->polys = poly;
    
    texinfo = warpsurf->texinfo;
    VectorClear( total );
    total_s = total_t = 0;
    v = poly->vertices + VERTEX_SIZE;
    for( i = 0; i < numVerts; i++ ) {
        VectorCopy( verts, v );
        v[3] = DotProduct( verts, texinfo->axis[0] );
        v[4] = DotProduct( verts, texinfo->axis[1] );
        total_s += v[3];
        total_t += v[4];
        VectorAdd( total, verts, total );
        verts += 3; v += VERTEX_SIZE;
    }

    /* middle point */
    v = poly->vertices;
    scale = 1.0f / numVerts;
    VectorScale( total, scale, v );
    v[3] = total_s * scale;
    v[4] = total_t * scale;

}

static int GL_BuildSurfaceWarpPolys( bspSurface_t *surf ) {
	int *src_surfedge;
	dvertex_t *src_vert;
	dedge_t *src_edge;
	vec_t *dst_vert;
	int i;
	int index, vertIndex;
    int numEdges = surf->numSurfEdges;
    vec3_t verts[SUBDIVIDE_VERTS];
	
    surf->polys = NULL;
    surf->type = DSURF_WARP;
    warpsurf = surf;
    
	src_surfedge = surf->firstSurfEdge;
    dst_vert = verts[0];
	for( i = 0; i < numEdges; i++ ) {
		index = *src_surfedge++;
		
		vertIndex = 0;
		if( index < 0 ) {
			index = -index;
			vertIndex = 1;
		}

		if( index >= r_world.numEdges ) {
			printf( "LoadFace: bad edge index" );
			return -1;
		}

		src_edge = r_world.edges + index;
		src_vert = r_world.vertices + src_edge->v[vertIndex];

		VectorCopy( src_vert->point, dst_vert );
		
		dst_vert += 3;
	}

    SubdividePolygon_r( numEdges, verts[0] );

    return 0;

}

static void GL_NormalizeSurfaceTexcoords( bspSurface_t *surf ) {
    bspTexinfo_t *texinfo = surf->texinfo;
    bspPoly_t *poly = surf->polys;
    vec_t *vert;
    tcoord_t *tc;
    int i;
    vec2_t scale;

    surf->normalizedTC = sys.HunkAlloc( &r_world.pool, sizeof( tcoord_t ) * poly->numVerts );

    scale[0] = 1.0f / texinfo->image->width;
    scale[1] = 1.0f / texinfo->image->height;

    tc = surf->normalizedTC;
    vert = poly->vertices;
    for( i = 0; i < poly->numVerts; i++ ) {
        vert[3] *= scale[0];
        vert[4] *= scale[1];
        tc->st[0] = DotProduct( vert, texinfo->normalizedAxis[0] );
        tc->st[1] = DotProduct( vert, texinfo->normalizedAxis[1] );
        
        vert += VERTEX_SIZE; tc++;
    }
}

int GL_PostProcessSurface( bspSurface_t *surf ) {
	bspTexinfo_t *texinfo = surf->texinfo;
    
    if( texinfo->flags & SURF_SKY ) {
    }
    if( ( texinfo->flags & SURF_WARP ) && gl_subdivide->integer ) {
        if( GL_BuildSurfaceWarpPolys( surf ) ) {
            return -1;
        }
    } else {
        if( GL_BuildSurfacePoly( surf ) ) {
            return -1;
        }
        GL_CalcSurfaceExtents( surf );
    }

    if( surf->type == DSURF_POLY ) {
        if( LM_BuildSurfaceLightmap( surf ) ) {
            return -1;
        }
    }
    
    if( !( texinfo->flags & (SURF_SKY|SURF_WARP) ) ) {
        GL_NormalizeSurfaceTexcoords( surf );
    }
    
    if( ( texinfo->flags & SURF_WARP ) && !gl_subdivide->integer ) {
        GL_NormalizeSurfaceTexcoords( surf );
    }

    return 0;
}

void GL_BeginPostProcessing( void ) {
    lm.numMaps = 0;
    LM_InitBlock();

	gl_coloredlightmaps = cvar.Get( "gl_coloredlightmaps", "1",
        CVAR_ARCHIVE|CVAR_LATCHED );
	gl_brightness = cvar.Get( "gl_brightness", "0",
        CVAR_ARCHIVE|CVAR_LATCHED );
	gl_modulate_hack = cvar.Get( "gl_modulate_hack", "0", CVAR_LATCHED );

	if( gl_coloredlightmaps->value < 0 ) {
		cvar.Set( "gl_coloredlightmaps", "0" );
	} else if( gl_coloredlightmaps->value > 1 ) {
		cvar.Set( "gl_coloredlightmaps", "1" );
	}

	if( gl_brightness->value < -1 ) {
		cvar.Set( "gl_brightness", "-1" );
	} else if( gl_brightness->value > 1 ) {
		cvar.Set( "gl_brightness", "1" );
	}

	brightness = gl_brightness->value;
	colorScale = gl_coloredlightmaps->value;
}
    
void GL_EndPostProcessing( void ) {
    int i;
    
    for( i = 0; i < LM_BLOCK_WIDTH; i++ ) {
        if( lm.inuse[i] ) {
            LM_UploadBlock();
            break;
        }
    }

    Com_DPrintf( "GL_EndPostProcessing: %d lightmaps built\n", lm.numMaps );
}


