//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//===========================================================================//

// wrapper for the material system for the engine.

#include "render_pch.h"
#include "view.h"
#include "zone.h"
#include <float.h>
#include "sys_dll.h"
#include "materialsystem/imesh.h"
#include "gl_water.h"
#include "utlrbtree.h"
#include "istudiorender.h"
#include "tier0/dbg.h"
#include "KeyValues.h"
#include "vstdlib/random.h"
#include "lightcache.h"
#include "sysexternal.h"
#include "cmd.h"
#include "modelloader.h"
#include "tier0/icommandline.h"
#include "materialsystem/imaterial.h"
#include "toolframework/itoolframework.h"
#include "toolframework/itoolsystem.h"
#include "tier2/p4helpers.h"
#include "p4lib/ip4.h"
#include "vgui/ISystem.h"
#include <vgui_controls/Controls.h>


extern ConVar developer;
#ifdef _WIN32
#include <crtdbg.h>
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#ifndef SWDS
extern IMaterialSystem *materials;

IMaterial*	g_materialWireframe;
IMaterial*	g_materialTranslucentSingleColor;
IMaterial*	g_materialTranslucentVertexColor;
IMaterial*	g_materialWorldWireframe;
IMaterial*	g_materialWorldWireframeZBuffer;
IMaterial*	g_materialBrushWireframe;
IMaterial*	g_materialDecalWireframe;
IMaterial*	g_materialDebugLightmap;
IMaterial*	g_materialDebugLightmapZBuffer;
IMaterial*	g_materialDebugLuxels;
IMaterial*	g_materialLeafVisWireframe;
IMaterial*	g_pMaterialWireframeVertexColor;
IMaterial*  g_pMaterialWireframeVertexColorIgnoreZ;
IMaterial*  g_pMaterialLightSprite;
IMaterial*  g_pMaterialShadowBuild;
IMaterial*  g_pMaterialMRMWireframe;
IMaterial*  g_pMaterialWriteZ;
IMaterial*  g_pMaterialWaterDuDv;
IMaterial*  g_pMaterialWaterFirstPass;
IMaterial*  g_pMaterialWaterSecondPass;
IMaterial*	g_pMaterialAmbientCube;
IMaterial*	g_pMaterialDebugFlat;
IMaterial*	g_pMaterialDepthWrite[2][2];
IMaterial*	g_pMaterialSSAODepthWrite[2][2];

#ifdef NEWMESH
CUtlVector<IVertexBuffer *> g_WorldStaticMeshes;  // fixme - rename to g_WorldStaticVertexBuffers
#else
CUtlVector<IMesh *> g_WorldStaticMeshes;
#endif

void WorldStaticMeshCreate( void );
void WorldStaticMeshDestroy( void );


bool TangentSpaceSurfaceSetup( SurfaceHandle_t surfID, Vector &tVect );
void TangentSpaceComputeBasis( Vector& tangent, Vector& binormal, const Vector& normal, const Vector& tVect, bool negateTangent );


//-----------------------------------------------------------------------------
// A console command edit a particular material
//-----------------------------------------------------------------------------
CON_COMMAND_F( mat_edit, "Bring up the material under the crosshair in the editor", FCVAR_CHEAT )
{
	if ( !toolframework->InToolMode() )
		return;

	IMaterial* pMaterial = NULL;
	if ( args.ArgC() < 2 )
	{
		pMaterial = GetMaterialAtCrossHair();
	}
	else
	{
		const char *pMaterialName = args[ 1 ];
		pMaterial = materials->FindMaterial( pMaterialName, "edited materials", false );
	}

	if ( !pMaterial )
	{
		ConMsg( "no/bad material\n" );
	}
	else
	{
		IToolSystem *pToolSystem = toolframework->SwitchToTool( "Material Editor" );
		if ( pToolSystem )
		{
			ConMsg( "editing material \"%s\"\n", pMaterial->GetName() );

			KeyValues *pKeyValues = new KeyValues( "EditMaterial" );
			pKeyValues->SetString( "material", pMaterial->GetName() );
			pToolSystem->PostMessage( 0, pKeyValues );
			pKeyValues->deleteThis();
		}
	}
}


//-----------------------------------------------------------------------------
// A console command to spew out the material under the crosshair
//-----------------------------------------------------------------------------
CON_COMMAND_F( mat_crosshair, "Display the name of the material under the crosshair", FCVAR_CHEAT )
{
	IMaterial* pMaterial = GetMaterialAtCrossHair();
	if (!pMaterial)
		ConMsg ("no/bad material\n");
	else
		ConMsg ("hit material \"%s\"\n", pMaterial->GetName());
}

//-----------------------------------------------------------------------------
// A console command to open the material under the crosshair in the associated editor.
//-----------------------------------------------------------------------------
CON_COMMAND_F( mat_crosshair_edit, "open the material under the crosshair in the editor defined by mat_crosshair_edit_editor", FCVAR_CHEAT )
{
	IMaterial* pMaterial = GetMaterialAtCrossHair();
	if (!pMaterial)
	{
		ConMsg ("no/bad material\n");
	}
	else
	{
		char chResolveName[ 256 ] = {0}, chResolveNameArg[ 256 ] = {0};
		Q_snprintf( chResolveNameArg, sizeof( chResolveNameArg ) - 1, "materials/%s.vmt", pMaterial->GetName() );
		char const *szResolvedName = g_pFileSystem->RelativePathToFullPath( chResolveNameArg, "game", chResolveName, sizeof( chResolveName ) - 1 );
		if ( p4 )
		{
			CP4AutoEditAddFile autop4( szResolvedName );
		}
		else
		{
			Warning( "run with -p4 to get p4 operations upon mat_crosshair_edit\n" );
		}
		vgui::system()->ShellExecute( "open", szResolvedName );
	}
}

//-----------------------------------------------------------------------------
// A console command to open the material under the crosshair in the associated editor.
//-----------------------------------------------------------------------------
CON_COMMAND_F( mat_crosshair_explorer, "open the material under the crosshair in explorer and highlight the vmt file", FCVAR_CHEAT )
{
	IMaterial* pMaterial = GetMaterialAtCrossHair();
	if (!pMaterial)
	{
		ConMsg ("no/bad material\n");
	}
	else
	{
		char chResolveName[ 256 ] = {0}, chResolveNameArg[ 256 ] = {0};
		Q_snprintf( chResolveNameArg, sizeof( chResolveNameArg ) - 1, "materials/%s.vmt", pMaterial->GetName() );
		char const *szResolvedName = g_pFileSystem->RelativePathToFullPath( chResolveNameArg, "game", chResolveName, sizeof( chResolveName ) - 1 );
		char params[256];
		Q_snprintf( params, sizeof( params ) - 1, "/E,/SELECT,%s", szResolvedName );
		vgui::system()->ShellExecuteEx( "open", "explorer.exe", params );
	}
}

//-----------------------------------------------------------------------------
// A console command to open the material under the crosshair in the associated editor.
//-----------------------------------------------------------------------------
CON_COMMAND_F( mat_crosshair_reloadmaterial, "reload the material under the crosshair", FCVAR_CHEAT )
{
	IMaterial* pMaterial = GetMaterialAtCrossHair();
	if (!pMaterial)
	{
		ConMsg ("no/bad material\n");
	}
	else
	{
		materials->ReloadMaterials( pMaterial->GetName() );
	}
}

CON_COMMAND_F( mat_crosshair_printmaterial, "print the material under the crosshair", FCVAR_CHEAT )
{
	IMaterial* pMaterial = GetMaterialAtCrossHair();
	if (!pMaterial)
	{
		ConMsg ("no/bad material\n");
	}
	else
	{
		materials->DebugPrintUsedMaterials( pMaterial->GetName(), true );
	}
}

static void RegisterLightmappedSurface( SurfaceHandle_t surfID )
{
	int lightmapSize[2];
	int allocationWidth, allocationHeight;
	bool bNeedsBumpmap;
	
	// fixme: lightmapSize needs to be in msurface_t once we
	// switch over to having lightmap size untied to base texture
	// size
	lightmapSize[0] = ( MSurf_LightmapExtents( surfID )[0] ) + 1;
	lightmapSize[1] = ( MSurf_LightmapExtents( surfID )[1] ) + 1;
	
	// Allocate all bumped lightmaps next to each other so that we can just 
	// increment the s texcoord by pSurf->bumpSTexCoordOffset to render the next
	// of the three lightmaps
	bNeedsBumpmap = SurfNeedsBumpedLightmaps( surfID );
	if( bNeedsBumpmap )
	{
		MSurf_Flags( surfID ) |= SURFDRAW_BUMPLIGHT;
		allocationWidth = lightmapSize[0] * ( NUM_BUMP_VECTS+1 );
	}
	else
	{
		MSurf_Flags( surfID ) &= ~SURFDRAW_BUMPLIGHT;
		allocationWidth = lightmapSize[0];
	}
	allocationHeight = lightmapSize[1];

	// register this surface's lightmap
	int offsetIntoLightmapPage[2];
	MSurf_MaterialSortID( surfID ) = materials->AllocateLightmap( 
		allocationWidth, 
		allocationHeight,
		offsetIntoLightmapPage,
		MSurf_TexInfo( surfID )->material );

	MSurf_OffsetIntoLightmapPage( surfID )[0] = offsetIntoLightmapPage[0];
	MSurf_OffsetIntoLightmapPage( surfID )[1] = offsetIntoLightmapPage[1];
}

static void RegisterUnlightmappedSurface( SurfaceHandle_t surfID )
{
	MSurf_MaterialSortID( surfID ) = materials->AllocateWhiteLightmap( MSurf_TexInfo( surfID )->material );
	MSurf_OffsetIntoLightmapPage( surfID )[0] = 0;
	MSurf_OffsetIntoLightmapPage( surfID )[1] = 0;
}

static bool LightmapLess( const SurfaceHandle_t& surfID1, const SurfaceHandle_t& surfID2 )
{
	// FIXME: This really should be in the material system,
	// as it completely depends on the behavior of the lightmap packer
	bool hasLightmap1 = (MSurf_Flags( surfID1 ) & SURFDRAW_NOLIGHT) == 0;
	bool hasLightmap2 = (MSurf_Flags( surfID2 ) & SURFDRAW_NOLIGHT) == 0;

	// We want lightmapped surfaces to show up first
	if (hasLightmap1 != hasLightmap2)
		return hasLightmap1 > hasLightmap2;
	
	// The sort by enumeration ID
	IMaterial* pMaterial1 = MSurf_TexInfo( surfID1 )->material;
	IMaterial* pMaterial2 = MSurf_TexInfo( surfID2 )->material;
	int enum1 = pMaterial1->GetEnumerationID();
	int enum2 = pMaterial2->GetEnumerationID();
	if (enum1 != enum2)
		return enum1 < enum2;

	bool hasLightstyle1 = (MSurf_Flags( surfID1 ) & SURFDRAW_HASLIGHTSYTLES) == 0;
	bool hasLightstyle2 = (MSurf_Flags( surfID2 ) & SURFDRAW_HASLIGHTSYTLES) == 0;

	// We want Lightstyled surfaces to show up first
	if (hasLightstyle1 != hasLightstyle2)
		return hasLightstyle1 > hasLightstyle2;

	// Then sort by lightmap area for better packing... (big areas first)
	// NOTE: Don't care about bumpmap increasing area here because it is a linear factor 
	// (all surfs with the same material have the same bumpmapping cost)
#if 1
	int area1 = MSurf_LightmapExtents( surfID1 )[0] * MSurf_LightmapExtents( surfID1 )[1];
	int area2 = MSurf_LightmapExtents( surfID2 )[0] * MSurf_LightmapExtents( surfID2 )[1];
	return area2 < area1;
#else
	// Previous algorithm: pack minimum height first
	// NOTE: In d1_trainstation_05, greatest area results in fewer material splits
	//		so I've switched over to that heuristic
	return MSurf_LightmapExtents( surfID1 )[1] < MSurf_LightmapExtents( surfID2 )[1];
#endif
}

void MaterialSystem_RegisterLightmapSurfaces( void )
{
	SurfaceHandle_t surfID = SURFACE_HANDLE_INVALID;

	materials->BeginLightmapAllocation();

	// Add all the surfaces to a list, sorted by lightmapped
	// then by material enumeration then by area
	CUtlRBTree< SurfaceHandle_t, int >	surfaces( 0, host_state.worldbrush->numsurfaces, LightmapLess );
	for( int surfaceIndex = 0; surfaceIndex < host_state.worldbrush->numsurfaces; surfaceIndex++ )
	{
		surfID = SurfaceHandleFromIndex( surfaceIndex );
		if( ( MSurf_TexInfo( surfID )->flags & SURF_NOLIGHT ) || 
			( MSurf_Flags( surfID ) & SURFDRAW_NOLIGHT) )
		{
			MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT;
		}
		else
		{
			MSurf_Flags( surfID ) &= ~SURFDRAW_NOLIGHT;
		}

		surfaces.Insert(surfID);
	}

	// iterate sorted surfaces
	surfID = SURFACE_HANDLE_INVALID;
	for (int i = surfaces.FirstInorder(); i != surfaces.InvalidIndex(); i = surfaces.NextInorder(i) )
	{
		surfID = surfaces[i];

		bool hasLightmap = ( MSurf_Flags( surfID ) & SURFDRAW_NOLIGHT) == 0;
		if ( hasLightmap )
		{
			RegisterLightmappedSurface( surfID );
		}
		else
		{
			RegisterUnlightmappedSurface( surfID );
		}
	}
	materials->EndLightmapAllocation();
}

static void TestBumpSanity( SurfaceHandle_t surfID )
{
	ASSERT_SURF_VALID( surfID );
	// use the last one to check if we need a bumped lightmap, but don't have it so that we can warn.
	bool needsBumpmap = SurfNeedsBumpedLightmaps( surfID );
	bool hasBumpmap = SurfHasBumpedLightmaps( surfID );
	
	if ( needsBumpmap && !hasBumpmap && MSurf_Samples( surfID ) )
	{
		Warning( "Need to rebuild map to get bumped lighting on material %s\n", 
			materialSortInfoArray[MSurf_MaterialSortID( surfID )].material->GetName() );
	}
}

void MaterialSytsem_DoBumpWarnings( void )
{
	int sortID;
	IMaterial *pPrevMaterial = NULL;

	for( sortID = 0; sortID < g_WorldStaticMeshes.Count(); sortID++ )
	{
		if( pPrevMaterial == materialSortInfoArray[sortID].material )
		{
			continue;
		}
		// Find one surface in each material sort info type
		for ( int surfaceIndex = 0; surfaceIndex < host_state.worldbrush->numsurfaces; surfaceIndex++ )
		{
			SurfaceHandle_t surfID = SurfaceHandleFromIndex( surfaceIndex );

			if( MSurf_MaterialSortID( surfID ) == sortID )
			{
				TestBumpSanity( surfID );
				break;
			}
		}
		pPrevMaterial = materialSortInfoArray[sortID].material;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
static void GenerateTexCoordsForPrimVerts( void )
{
	int j, k, l;
	for ( int surfaceIndex = 0; surfaceIndex < host_state.worldbrush->numsurfaces; surfaceIndex++ )
	{
		SurfaceHandle_t surfID = SurfaceHandleFromIndex( surfaceIndex );
/*
		if( pSurf->numPrims > 0 )
		{
			ConMsg( "pSurf %d has %d prims (normal: %f %f %f dist: %f)\n", 
				( int )i, ( int )pSurf->numPrims, 
				pSurf->plane->normal[0], pSurf->plane->normal[1], pSurf->plane->normal[2], 
				pSurf->plane->dist );
			ConMsg( "\tfirst primID: %d\n", ( int )pSurf->firstPrimID );
		}
*/
		for( j = 0; j < MSurf_NumPrims( surfID ); j++ )
		{
			mprimitive_t *pPrim;
			assert( MSurf_FirstPrimID( surfID ) + j < host_state.worldbrush->numprimitives );
			pPrim = &host_state.worldbrush->primitives[MSurf_FirstPrimID( surfID ) + j];
			for( k = 0; k < pPrim->vertCount; k++ )
			{
				int lightmapSize[2];
				int lightmapPageSize[2];
				float sOffset, sScale, tOffset, tScale;
				
				materials->GetLightmapPageSize( 
					SortInfoToLightmapPage( MSurf_MaterialSortID( surfID ) ), 
					&lightmapPageSize[0], &lightmapPageSize[1] );
				lightmapSize[0] = ( MSurf_LightmapExtents( surfID )[0] ) + 1;
				lightmapSize[1] = ( MSurf_LightmapExtents( surfID )[1] ) + 1;

				sScale = 1.0f / ( float )lightmapPageSize[0];
				sOffset = ( float )MSurf_OffsetIntoLightmapPage( surfID )[0] * sScale;
				sScale = MSurf_LightmapExtents( surfID )[0] * sScale;

				tScale = 1.0f / ( float )lightmapPageSize[1];
				tOffset = ( float )MSurf_OffsetIntoLightmapPage( surfID )[1] * tScale;
				tScale = MSurf_LightmapExtents( surfID )[1] * tScale;

				for ( l = 0; l < pPrim->vertCount; l++ )
				{
					// world-space vertex
					assert( l+pPrim->firstVert < host_state.worldbrush->numprimverts );
					mprimvert_t &vert = host_state.worldbrush->primverts[l+pPrim->firstVert];
					Vector& vec = vert.pos;

					// base texture coordinate
					vert.texCoord[0] = DotProduct (vec, MSurf_TexInfo( surfID )->textureVecsTexelsPerWorldUnits[0].AsVector3D()) + 
						MSurf_TexInfo( surfID )->textureVecsTexelsPerWorldUnits[0][3];
					vert.texCoord[0] /= MSurf_TexInfo( surfID )->material->GetMappingWidth();

					vert.texCoord[1] = DotProduct (vec, MSurf_TexInfo( surfID )->textureVecsTexelsPerWorldUnits[1].AsVector3D()) + 
						MSurf_TexInfo( surfID )->textureVecsTexelsPerWorldUnits[1][3];
					vert.texCoord[1] /= MSurf_TexInfo( surfID )->material->GetMappingHeight();

					if ( (MSurf_Flags( surfID ) & SURFDRAW_NOLIGHT) )
					{
						vert.lightCoord[0] = 0.5f;
						vert.lightCoord[1] = 0.5f;
					}
					else if ( MSurf_LightmapExtents( surfID )[0] == 0 )
					{
						vert.lightCoord[0] = sOffset;
						vert.lightCoord[1] = tOffset;
					}
					else
					{
						vert.lightCoord[0] = DotProduct (vec, MSurf_TexInfo( surfID )->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D()) + 
							MSurf_TexInfo( surfID )->lightmapVecsLuxelsPerWorldUnits[0][3];
						vert.lightCoord[0] -= MSurf_LightmapMins( surfID )[0];
						vert.lightCoord[0] += 0.5f;
						vert.lightCoord[0] /= ( float )MSurf_LightmapExtents( surfID )[0]; //pSurf->texinfo->texture->width;

						vert.lightCoord[1] = DotProduct (vec, MSurf_TexInfo( surfID )->lightmapVecsLuxelsPerWorldUnits[1].AsVector3D()) + 
							MSurf_TexInfo( surfID )->lightmapVecsLuxelsPerWorldUnits[1][3];
						vert.lightCoord[1] -= MSurf_LightmapMins( surfID )[1];
						vert.lightCoord[1] += 0.5f;
						vert.lightCoord[1] /= ( float )MSurf_LightmapExtents( surfID )[1]; //pSurf->texinfo->texture->height;
						
						vert.lightCoord[0] = sOffset + vert.lightCoord[0] * sScale;
						vert.lightCoord[1] = tOffset + vert.lightCoord[1] * tScale;
					}
				}
			}			
		}
	}
}

struct sortmap_t
{
	MaterialSystem_SortInfo_t info;
	int index;
};

IMatRenderContext *pSortMapRenderContext;

int __cdecl SortMapCompareFunc( const void *pElem0, const void *pElem1 )
{
	const sortmap_t *pMap0 = (const sortmap_t *)pElem0;
	const sortmap_t *pMap1 = (const sortmap_t *)pElem1;
	return pSortMapRenderContext->CompareMaterialCombos( pMap0->info.material, pMap1->info.material, pMap0->info.lightmapPageID, pMap1->info.lightmapPageID );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void MaterialSystem_CreateSortinfo( void )
{
	Assert( !materialSortInfoArray );

	int nSortIDs = materials->GetNumSortIDs();
	materialSortInfoArray = ( MaterialSystem_SortInfo_t * )new MaterialSystem_SortInfo_t[ nSortIDs ];
	Assert( materialSortInfoArray );
	materials->GetSortInfo( materialSortInfoArray );

	int i = 0;
	sortmap_t *pMap = (sortmap_t *)_alloca( sizeof(sortmap_t) * nSortIDs );
	for ( i = 0; i < nSortIDs; i++ )
	{
		pMap[i].info = materialSortInfoArray[i];
		pMap[i].index = i;
	}

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pSortMapRenderContext = pRenderContext;

	qsort( pMap, nSortIDs, sizeof( sortmap_t ), SortMapCompareFunc );

	int *pSortIDRemap = (int *)_alloca( sizeof(int) * nSortIDs );
	for ( i = 0; i < nSortIDs; i++ )
	{
		materialSortInfoArray[i] = pMap[i].info;
		pSortIDRemap[pMap[i].index] = i;
		//Msg("Material %s, lightmap %d ", materialSortInfoArray[i].material->GetName(), materialSortInfoArray[i].lightmapPageID );
	}

	for ( int surfaceIndex = 0; surfaceIndex < host_state.worldbrush->numsurfaces; surfaceIndex++ )
	{
		SurfaceHandle_t surfID = SurfaceHandleFromIndex( surfaceIndex );
		int sortID = MSurf_MaterialSortID( surfID );
#if _DEBUG
		IMaterial *pMaterial = MSurf_TexInfo( surfID )->material;

		if ( !HushAsserts() )
		{
			Assert ( materialSortInfoArray[pSortIDRemap[sortID]].material == pMaterial );
		}
#endif
		MSurf_MaterialSortID( surfID ) = pSortIDRemap[sortID];
	}

	// Create texcoords for subdivided surfaces
	GenerateTexCoordsForPrimVerts();
	// Create the hardware vertex buffers for each face
	WorldStaticMeshCreate();
	if ( developer.GetInt() )
	{
		MaterialSytsem_DoBumpWarnings();
	}
}

bool SurfHasBumpedLightmaps( SurfaceHandle_t surfID )
{
	ASSERT_SURF_VALID( surfID );
	bool hasBumpmap = false;
	if( ( MSurf_TexInfo( surfID )->flags & SURF_BUMPLIGHT ) && 
		( !( MSurf_TexInfo( surfID )->flags & SURF_NOLIGHT ) ) &&
		( host_state.worldbrush->lightdata ) &&
		( MSurf_Samples( surfID ) ) )
	{
		hasBumpmap = true;
	}
	return hasBumpmap;
}

bool SurfNeedsBumpedLightmaps( SurfaceHandle_t surfID )
{
	ASSERT_SURF_VALID( surfID );
	assert( MSurf_TexInfo( surfID ) );
	assert( MSurf_TexInfo( surfID )->material );
	return MSurf_TexInfo( surfID )->material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS );
}

bool SurfHasLightmap( SurfaceHandle_t surfID )
{
	ASSERT_SURF_VALID( surfID );

	bool hasLightmap = false;
	if( ( !( MSurf_TexInfo( surfID )->flags & SURF_NOLIGHT ) ) &&
		( host_state.worldbrush->lightdata ) &&
		( MSurf_Samples( surfID ) ) )
	{
		hasLightmap = true;
	}
	return hasLightmap;
}

bool SurfNeedsLightmap( SurfaceHandle_t surfID )
{
	ASSERT_SURF_VALID( surfID );
	assert( MSurf_TexInfo( surfID ) );
	assert( MSurf_TexInfo( surfID )->material );
	if (MSurf_TexInfo( surfID )->flags & SURF_NOLIGHT)
		return false;

	return MSurf_TexInfo( surfID )->material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP );
}



//-----------------------------------------------------------------------------
// Purpose: This builds the surface info for a terrain face
//-----------------------------------------------------------------------------

void BuildMSurfaceVerts( const worldbrushdata_t *pBrushData, SurfaceHandle_t surfID, Vector *verts,
							Vector2D *texCoords, Vector2D lightCoords[][4] )
{ 
	SurfaceCtx_t ctx;
	SurfSetupSurfaceContext( ctx, surfID );

	int vertCount = MSurf_VertCount( surfID );
	int vertFirstIndex = MSurf_FirstVertIndex( surfID );
	for ( int i = 0; i < vertCount; i++ )
	{
		int vertIndex = pBrushData->vertindices[vertFirstIndex + i];

		// world-space vertex
		Vector& vec = pBrushData->vertexes[vertIndex].position;

		// output to mesh
		if ( verts )
		{
			VectorCopy( vec, verts[i] );
		}

		if ( texCoords )
		{
			SurfComputeTextureCoordinate( ctx, surfID, vec, texCoords[i] );
		}

		//
		// garymct: normalized (within space of surface) lightmap texture coordinates
		//
		if ( lightCoords )
		{
			SurfComputeLightmapCoordinate( ctx, surfID, vec, lightCoords[i][0] );

			if ( MSurf_Flags( surfID ) & SURFDRAW_BUMPLIGHT )
			{
				// bump maps appear left to right in lightmap page memory, calculate the offset for the
				// width of a single map
				for ( int bumpID = 1; bumpID <= NUM_BUMP_VECTS; bumpID++ )
				{
					lightCoords[i][bumpID][0] = lightCoords[i][0][0] + (bumpID * ctx.m_BumpSTexCoordOffset);
					lightCoords[i][bumpID][1] = lightCoords[i][0][1];
				}
			}
		}
	}
}



void BuildMSurfacePrimVerts( worldbrushdata_t *pBrushData, mprimitive_t *prim, CMeshBuilder &builder, SurfaceHandle_t surfID )
{
	Vector tVect;
	bool negate = false;
// FIXME: For some reason, normals are screwed up on water surfaces.  Revisit this once we have normals started in primverts.
	if ( MSurf_Flags( surfID ) & SURFDRAW_TANGENTSPACE )
	{
		negate = TangentSpaceSurfaceSetup( surfID, tVect );
	}
//	Vector& normal = pBrushData->vertnormals[ pBrushData->vertnormalindices[MSurf_FirstVertNormal( surfID )] ];

	for ( int i = 0; i < prim->vertCount; i++ )
	{
		mprimvert_t &primVert = pBrushData->primverts[prim->firstVert + i];
		builder.Position3fv( primVert.pos.Base() );
		builder.Normal3fv( MSurf_Plane( surfID ).normal.Base() );
//		builder.Normal3fv( normal.Base() );
		builder.TexCoord2fv( 0, primVert.texCoord );
		builder.TexCoord2fv( 1, primVert.lightCoord );
		if ( MSurf_Flags( surfID ) & SURFDRAW_TANGENTSPACE )
		{
			Vector tangentS, tangentT;
			TangentSpaceComputeBasis( tangentS, tangentT, MSurf_Plane( surfID ).normal, tVect, false );
			builder.TangentS3fv( tangentS.Base() );
			builder.TangentT3fv( tangentT.Base() );
		}
		builder.AdvanceVertex();
	}
}

void BuildMSurfacePrimIndices( worldbrushdata_t *pBrushData, mprimitive_t *prim, CMeshBuilder &builder )
{
	for ( int i = 0; i < prim->indexCount; i++ )
	{
		unsigned short primIndex = pBrushData->primindices[prim->firstIndex + i];
		builder.Index( primIndex - prim->firstVert );
		builder.AdvanceIndex();
	}
}

//-----------------------------------------------------------------------------
// Here's a version of the mesh builder used to allow for client DLL to draw brush models
//-----------------------------------------------------------------------------

void BuildBrushModelVertexArray(worldbrushdata_t *pBrushData, SurfaceHandle_t surfID, BrushVertex_t* pVerts )
{
	SurfaceCtx_t ctx;
	SurfSetupSurfaceContext( ctx, surfID );

	Vector tVect;
	bool negate = false;
	if ( MSurf_Flags( surfID ) & SURFDRAW_TANGENTSPACE )
	{
		negate = TangentSpaceSurfaceSetup( surfID, tVect );
	}

	for ( int i = 0; i < MSurf_VertCount( surfID ); i++ )
	{
		int vertIndex = pBrushData->vertindices[MSurf_FirstVertIndex( surfID ) + i];

		// world-space vertex
		Vector& vec = pBrushData->vertexes[vertIndex].position;

		// output to mesh
		VectorCopy( vec, pVerts[i].m_Pos );

		Vector2D uv;
		SurfComputeTextureCoordinate( ctx, surfID, vec, pVerts[i].m_TexCoord );

		// garymct: normalized (within space of surface) lightmap texture coordinates
		SurfComputeLightmapCoordinate( ctx, surfID, vec, pVerts[i].m_LightmapCoord );

// Activate this if necessary
//		if ( surf->flags & SURFDRAW_BUMPLIGHT )
//		{
//			// bump maps appear left to right in lightmap page memory, calculate 
//			// the offset for the width of a single map. The pixel shader will use 
//			// this to compute the actual texture coordinates
//			builder.TexCoord2f( 2, ctx.m_BumpSTexCoordOffset, 0.0f );
//		}

		Vector& normal = pBrushData->vertnormals[ pBrushData->vertnormalindices[MSurf_FirstVertNormal( surfID ) + i] ];
		VectorCopy( normal, pVerts[i].m_Normal );

		if ( MSurf_Flags( surfID ) & SURFDRAW_TANGENTSPACE )
		{
			Vector tangentS, tangentT;
			TangentSpaceComputeBasis( tangentS, tangentT, normal, tVect, negate );
			VectorCopy( tangentS, pVerts[i].m_TangentS );
			VectorCopy( tangentT, pVerts[i].m_TangentT );
		}
	}
}
#endif // SWDS

void CMSurfaceSortList::Init( int maxSortIDs, int minMaterialLists )
{
	m_list.RemoveAll();
	m_list.EnsureCapacity(minMaterialLists);
	m_maxSortIDs = maxSortIDs;
	int groupMax = maxSortIDs*MAX_MAT_SORT_GROUPS;
	m_groups.RemoveAll();
	m_groups.EnsureCount(groupMax);
	int groupBytes = (groupMax+7)>>3;
	m_groupUsed.EnsureCount(groupBytes);
	Q_memset(m_groupUsed.Base(), 0, groupBytes);

	for ( int i = 0; i < MAX_MAT_SORT_GROUPS; i++ )
	{
		m_sortGroupLists[i].RemoveAll();
		int cap = (i==0) ? 128 : 16;
		m_sortGroupLists[i].EnsureCapacity(cap);
		groupOffset[i] = m_maxSortIDs * i;
	}
	InitGroup(&m_emptyGroup);
}

void CMSurfaceSortList::InitGroup( surfacesortgroup_t *pGroup )
{
	pGroup->listHead = -1;
	pGroup->listTail = -1;
	pGroup->vertexCount = 0;
	pGroup->groupListIndex = -1;
	pGroup->vertexCountNoDetail = 0;
	pGroup->indexCountNoDetail = 0;
	pGroup->triangleCount = 0;
	pGroup->surfaceCount = 0;
}

void CMSurfaceSortList::Shutdown()
{
}

void CMSurfaceSortList::Reset()
{
	Init( m_maxSortIDs, m_list.NumAllocated() );
}

// this resizes the groups and groupUsed arrays
void CMSurfaceSortList::EnsureMaxSortIDs( int newMaxSortIDs )
{
	if ( newMaxSortIDs > m_maxSortIDs )
	{
		int oldMax = m_maxSortIDs;
		// compute new size, expand by minimum of 256
		newMaxSortIDs += 255;
		newMaxSortIDs -= (newMaxSortIDs&255);
		int groupMax = newMaxSortIDs * MAX_MAT_SORT_GROUPS;
		int groupBytes = (groupMax+7)>>3;
		// resize the arrays
		m_groups.EnsureCount(groupMax);
		m_groupUsed.EnsureCount(groupBytes);
		// now loop through the list backwards and move the old data over
		for ( int i = MAX_MAT_SORT_GROUPS; --i >= 0; )
		{
			for ( int j = newMaxSortIDs; --j >= 0; )
			{
				int newIndex = (i * newMaxSortIDs) + j;
				if ( j < oldMax )
				{
					// when i == 0, the group indices overlap so they don't need to be remapped
					if ( i != 0 )
					{
						int oldIndex = (i * oldMax) + j;
						MarkGroupNotUsed(newIndex);
						if ( IsGroupUsed(oldIndex) )
						{
							MarkGroupNotUsed(oldIndex);
							MarkGroupUsed(newIndex);
							m_groups[newIndex] = m_groups[oldIndex];
							InitGroup( &m_groups[oldIndex] );
						}
					}
					if ( IsGroupUsed(newIndex) && m_groups[newIndex].groupListIndex >= 0 )
					{
						m_sortGroupLists[i][m_groups[newIndex].groupListIndex] = &m_groups[newIndex];
					}
				}
				else
				{
					MarkGroupNotUsed(newIndex);
				}
			}
			groupOffset[i] = i*newMaxSortIDs;
		}
		m_maxSortIDs = newMaxSortIDs;
	}
}


void CMSurfaceSortList::AddSurfaceToTail( msurface2_t *pSurface, int sortGroup, int sortID )
{
	Assert(sortGroup<MAX_MAT_SORT_GROUPS);
	int index = groupOffset[sortGroup] + sortID;
	surfacesortgroup_t *pGroup = &m_groups[index];
	if ( !IsGroupUsed(index) )
	{
		MarkGroupUsed(index);
		InitGroup(pGroup);
	}
	materiallist_t *pList = NULL;
	short prevIndex = -1;
	int vertCount = MSurf_VertCount(pSurface);
	int triangleCount = vertCount - 2;
	pGroup->triangleCount += triangleCount;
	pGroup->surfaceCount++;
	pGroup->vertexCount += vertCount;
	if (MSurf_Flags(pSurface) & SURFDRAW_NODE)
	{
		pGroup->vertexCountNoDetail += vertCount;
		pGroup->indexCountNoDetail += triangleCount * 3;
	}
	if ( pGroup->listTail != m_list.InvalidIndex() )
	{
		// existing block
		pList = &m_list[pGroup->listTail];
		if ( pList->count >= ARRAYSIZE(pList->pSurfaces) )
		{
			prevIndex = pGroup->listTail;
			// no space in existing block
			pList = NULL;
		}
	}
	// use existing block?
	if ( pList )
	{
		pList->pSurfaces[pList->count] = pSurface;
		pList->count++;
	}
	else
	{
		// allocate a new block
		short nextBlock = m_list.AddToTail();
		if ( prevIndex >= 0 )
		{
			m_list[prevIndex].nextBlock = nextBlock;
		}
		pGroup->listTail = nextBlock;
		// handle the first use case
		if ( pGroup->listHead == m_list.InvalidIndex() )
		{
			// UNDONE: This should really be sorted by sortID would help reduce state changes
			// NOTE: Doesn't seem to help much in benchmarks to sort this vector
			index = m_sortGroupLists[sortGroup].AddToTail(pGroup);
			pGroup->groupListIndex = index;
			pGroup->listHead = nextBlock;
		}
		pList = &m_list[nextBlock];
		pList->nextBlock = m_list.InvalidIndex();
		pList->count = 1;
		pList->pSurfaces[0] = pSurface;
	}
}

msurface2_t *CMSurfaceSortList::GetSurfaceAtHead( const surfacesortgroup_t &group ) const
{
	if ( group.listHead == m_list.InvalidIndex() )
		return NULL;
	Assert(m_list[group.listHead].count>0);
	return m_list[group.listHead].pSurfaces[0];
}

void CMSurfaceSortList::GetSurfaceListForGroup( CUtlVector<msurface2_t *> &list, const surfacesortgroup_t &group ) const
{
	MSL_FOREACH_SURFACE_IN_GROUP_BEGIN( *this, group, surfID )
	{
		list.AddToTail(surfID);
	}
	MSL_FOREACH_SURFACE_IN_GROUP_END()
}

#ifndef SWDS
IMaterial *GetMaterialAtCrossHair( void )
{
	Vector endPoint;
	Vector lightmapColor;

	// max_range * sqrt(3)
	VectorMA( MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint );
	
	SurfaceHandle_t hitSurfID = R_LightVec( MainViewOrigin(), endPoint, false, lightmapColor );
	if( IS_SURF_VALID( hitSurfID ) )
	{
		return MSurf_TexInfo( hitSurfID )->material;
	}
	else
	{
		return NULL;
	}
}

// hack
extern void DrawLightmapPage( int lightmapPageID );

static float textureS, textureT;
static SurfaceHandle_t s_CrossHairSurfID;;
static Vector crossHairDiffuseLightColor;
static Vector crossHairBaseColor;
static float lightmapCoords[2];

void SaveSurfAtCrossHair()
{
	Vector endPoint;
	Vector lightmapColor;

	// max_range * sqrt(3)
	VectorMA(  MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint );
	
	s_CrossHairSurfID = R_LightVec( MainViewOrigin(), endPoint, false, lightmapColor, 
		&textureS, &textureT, &lightmapCoords[0], &lightmapCoords[1] );
}


void DebugDrawLightmapAtCrossHair() // Lol
{
#if 0
	IMaterial *pMaterial;
	int lightmapPageSize[2];

	if( !s_CrossHairSurfID )
		return;
	materials->GetLightmapPageSize( materialSortInfoArray[MSurf_MaterialSortID( s_CrossHairSurfID )].lightmapPageID, 
		&lightmapPageSize[0], &lightmapPageSize[1] );
	pMaterial = MSurf_TexInfo( s_CrossHairSurfID )->material;
//	pMaterial->GetLowResColorSample( textureS, textureT, baseColor );
	DrawLightmapPage( materialSortInfoArray[MSurf_MaterialSortID( s_CrossHairSurfID )].lightmapPageID );

	int i;
	for( i = 0; i < 2; i++ )
	{
		xy[i] = 
			( ( float )pCrossHairSurf->offsetIntoLightmapPage[i] / ( float )lightmapPageSize[i] ) +
			lightmapCoord[i] * ( pCrossHairSurf->lightmapExtents[i] / ( float )lightmapPageSize[i] );
	}

	materials->Bind( g_materialWireframe );
	IMesh* pMesh = materials->GetDynamicMesh( g_materialWireframe );
	
	CMeshBuilder meshBuilder;
	meshBuilder.Begin( pMesh, MATERIAL_QUAD, 1 );

	meshBuilder.Position3f( 
	meshBuilder.AdvanceVertex();

	meshBuilder.End();
	pMesh->Draw();
#endif
}

void ReleaseMaterialSystemObjects();
void RestoreMaterialSystemObjects( int nChangeFlags );

void ForceMatSysRestore()
{
	ReleaseMaterialSystemObjects();
	RestoreMaterialSystemObjects( 0 );
}

#endif