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

#include "render_pch.h"
#include "modelloader.h"
#include "gl_model_private.h"
#include "gl_lightmap.h"
#include "disp.h"
#include "mathlib/mathlib.h"
#include "gl_rsurf.h"
#include "gl_matsysiface.h"
#include "zone.h"
#include "materialsystem/imesh.h"
#include "materialsystem/ivballoctracker.h"
#include "mathlib/vector.h"
#include "iscratchpad3d.h"
#include "tier0/fasttimer.h"
#include "lowpassstream.h"
#include "con_nprint.h"
#include "tier2/tier2.h"
#include "tier0/dbg.h"

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

void BuildTagData( CCoreDispInfo *pCoreDisp, CDispInfo *pDisp );
void SmoothDispSurfNormals( CCoreDispInfo **ppListBase, int nListSize );

// This makes sure that whoever is creating and deleting CDispInfos frees them all
// (and calls their destructors) before the module is gone.
class CConstructorChecker
{
public:
			CConstructorChecker()	{m_nConstructedObjects = 0;}
			~CConstructorChecker()	{Assert(m_nConstructedObjects == 0);}
	int		m_nConstructedObjects;
} g_ConstructorChecker;
 


//-----------------------------------------------------------------------------
// Static helpers.
//-----------------------------------------------------------------------------

static void BuildDispGetSurfNormals( Vector points[4], Vector normals[4] )
{
	//
	// calculate the displacement surface normal
	//
	Vector tmp[2];
	Vector normal;
	tmp[0] = points[1] - points[0];
	tmp[1] = points[3] - points[0];
	normal = tmp[1].Cross( tmp[0] );
	VectorNormalize( normal );

	for( int i = 0; i < 4; i++ )
	{
		normals[i] = normal;
	}
}


static bool FindExtraDependency( unsigned short *pDependencies, int nDependencies, int iDisp )
{
	for( int i=0; i < nDependencies; i++ )
	{
		if ( pDependencies[i] == iDisp )
			return true;
	}
	return false;
}


static CDispGroup* FindCombo( CUtlVector<CDispGroup*> &combos, int idLMPage, IMaterial *pMaterial )
{
	for( int i=0; i < combos.Size(); i++ )
	{
		if( combos[i]->m_LightmapPageID == idLMPage && combos[i]->m_pMaterial == pMaterial )
			return combos[i];
	}
	return NULL;
}


static CDispGroup* AddCombo( CUtlVector<CDispGroup*> &combos, int idLMPage, IMaterial *pMaterial )
{
	CDispGroup *pCombo = new CDispGroup;
	pCombo->m_LightmapPageID = idLMPage;
	pCombo->m_pMaterial = pMaterial;
	pCombo->m_nVisible = 0;
	combos.AddToTail( pCombo );
	return pCombo;
}


static inline CDispInfo* GetModelDisp( model_t const *pWorld, int i )
{
	return static_cast< CDispInfo* >(
		DispInfo_IndexArray( pWorld->brush.pShared->hDispInfos, i ) );
}			


static void BuildDispSurfInit( 
	model_t *pWorld, 
	CCoreDispInfo *pBuildDisp,
	SurfaceHandle_t worldSurfID )
{
	if( !IS_SURF_VALID( worldSurfID ) )
		return;
	ASSERT_SURF_VALID( worldSurfID );
	
	Vector surfPoints[4];
	Vector surfNormals[4];
	Vector2D surfTexCoords[4];
	Vector2D surfLightCoords[4][4];

	if ( MSurf_VertCount( worldSurfID ) != 4 )
		return;

#ifndef SWDS
	BuildMSurfaceVerts( pWorld->brush.pShared, worldSurfID, surfPoints, surfTexCoords, surfLightCoords );
#endif
	BuildDispGetSurfNormals( surfPoints, surfNormals );

	CCoreDispSurface *pDispSurf = pBuildDisp->GetSurface();

	int surfFlag = pDispSurf->GetFlags();
	int nLMVects = 1;
	if( MSurf_Flags( worldSurfID ) & SURFDRAW_BUMPLIGHT )
	{
		surfFlag |= CCoreDispInfo::SURF_BUMPED;
		nLMVects = NUM_BUMP_VECTS + 1;
	}

	pDispSurf->SetPointCount( 4 );
	for( int i = 0; i < 4; i++ )
	{
		pDispSurf->SetPoint( i, surfPoints[i] );
		pDispSurf->SetPointNormal( i, surfNormals[i] );
		pDispSurf->SetTexCoord( i, surfTexCoords[i] );

		for( int j = 0; j < nLMVects; j++ )
		{
			pDispSurf->SetLuxelCoord( j, i, surfLightCoords[i][j] );
		}
	}

	Vector vecS = MSurf_TexInfo( worldSurfID )->textureVecsTexelsPerWorldUnits[0].AsVector3D();
	Vector vecT = MSurf_TexInfo( worldSurfID )->textureVecsTexelsPerWorldUnits[1].AsVector3D();
	VectorNormalize( vecS );
	VectorNormalize( vecT );
	pDispSurf->SetSAxis( vecS );
	pDispSurf->SetTAxis( vecT );

	pDispSurf->SetFlags( surfFlag );
	pDispSurf->FindSurfPointStartIndex();
	pDispSurf->AdjustSurfPointData();

#ifndef SWDS
	//
	// adjust the lightmap coordinates -- this is currently done redundantly!
	// the will be fixed correctly when the displacement common code is written.
	// This is here to get things running for (GDC, E3)
	//
	SurfaceCtx_t ctx;
	SurfSetupSurfaceContext( ctx, worldSurfID );
	int lightmapWidth = MSurf_LightmapExtents( worldSurfID )[0];
	int lightmapHeight = MSurf_LightmapExtents( worldSurfID )[1];

	Vector2D uv( 0.0f, 0.0f );
	for ( int ndxLuxel = 0; ndxLuxel < 4; ndxLuxel++ )
	{
		switch( ndxLuxel )
		{
		case 0: { uv.Init( 0.0f, 0.0f ); break; }
		case 1: { uv.Init( 0.0f, ( float )lightmapHeight ); break; }
		case 2: { uv.Init( ( float )lightmapWidth, ( float )lightmapHeight ); break; }
		case 3: { uv.Init( ( float )lightmapWidth, 0.0f ); break; }
		}
		
		uv.x += 0.5f;
		uv.y += 0.5f;
		
		uv *= ctx.m_Scale; 
		uv += ctx.m_Offset; 
		
		pDispSurf->SetLuxelCoord( 0, ndxLuxel, uv );
	}
#endif
}

VertexFormat_t ComputeDisplacementStaticMeshVertexFormat( const IMaterial * pMaterial, const CDispGroup *pCombo, const ddispinfo_t *pMapDisps )
{
	VertexFormat_t vertexFormat = pMaterial->GetVertexFormat();

	// FIXME: set VERTEX_FORMAT_COMPRESSED if there are no artifacts and if it saves enough memory (use 'mem_dumpvballocs')
	vertexFormat &= ~VERTEX_FORMAT_COMPRESSED;
	// FIXME: check for and strip unused vertex elements (TANGENT_S/T?)

	return vertexFormat;
}

void AddEmptyMesh( 
	model_t *pWorld,
	CDispGroup *pCombo, 
	const ddispinfo_t *pMapDisps,
	int *pDispInfos,
	int nDisps,
	int nTotalVerts, 
	int nTotalIndices )
{
	CMatRenderContextPtr pRenderContext( materials );

	CGroupMesh *pMesh = new CGroupMesh;
	pCombo->m_Meshes.AddToTail( pMesh );

	VertexFormat_t vertexFormat = ComputeDisplacementStaticMeshVertexFormat( pCombo->m_pMaterial, pCombo, pMapDisps );
	pMesh->m_pMesh = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_DISP );
	pMesh->m_pGroup = pCombo;
	pMesh->m_nVisible = 0;

	CMeshBuilder builder;
	builder.Begin( pMesh->m_pMesh, MATERIAL_TRIANGLES, nTotalVerts, nTotalIndices );

		// Just advance the verts and indices and leave the data blank for now.
		builder.AdvanceIndices( nTotalIndices );
		builder.AdvanceVertices( nTotalVerts );

	builder.End();


	pMesh->m_DispInfos.SetSize( nDisps );
	pMesh->m_Visible.SetSize( nDisps );
	pMesh->m_VisibleDisps.SetSize( nDisps );

	int iVertOffset = 0;
	int iIndexOffset = 0;
	for( int iDisp=0; iDisp < nDisps; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, pDispInfos[iDisp] );
		const ddispinfo_t *pMapDisp = &pMapDisps[ pDispInfos[iDisp] ];

		pDisp->m_pMesh = pMesh;
		pDisp->m_iVertOffset = iVertOffset;
		pDisp->m_iIndexOffset = iIndexOffset;

		int nVerts, nIndices;
		CalcMaxNumVertsAndIndices( pMapDisp->power, &nVerts, &nIndices );
		iVertOffset += nVerts;
		iIndexOffset += nIndices;
		
		pMesh->m_DispInfos[iDisp] = pDisp;
	}

	Assert( iVertOffset == nTotalVerts );
	Assert( iIndexOffset == nTotalIndices );
}


void FillStaticBuffer( 
	CGroupMesh *pMesh,
	CDispInfo *pDisp,
	const CCoreDispInfo *pCoreDisp,
	const CDispVert *pVerts,
	int nLightmaps )
{
#ifndef SWDS
	// Put the verts into the buffer.
	int nVerts, nIndices;
	CalcMaxNumVertsAndIndices( pDisp->GetPower(), &nVerts, &nIndices );
	
	CMeshBuilder builder;
	builder.BeginModify( pMesh->m_pMesh, pDisp->m_iVertOffset, nVerts, 0, 0 );
	
	SurfaceCtx_t ctx;
	SurfSetupSurfaceContext( ctx, pDisp->GetParent() );
	
	for( int i=0; i < nVerts; i++ )
	{
		// NOTE: position comes from our system-memory buffer so when you're restoring
		//       static buffers (from alt+tab), it includes changes from terrain mods.
		const Vector &vPos = pCoreDisp->GetVert( i );
		builder.Position3f( vPos.x, vPos.y, vPos.z );
		
		const Vector &vNormal = pCoreDisp->GetNormal( i );
		builder.Normal3f( vNormal.x, vNormal.y, vNormal.z );
		
		Vector vec;
		pCoreDisp->GetTangentS( i, vec );
		builder.TangentS3f( VectorExpand( vec ) );
		
		pCoreDisp->GetTangentT( i, vec );
		builder.TangentT3f( VectorExpand( vec ) );
		
		Vector2D texCoord;
		pCoreDisp->GetTexCoord( i, texCoord );
		builder.TexCoord2f( 0, texCoord.x, texCoord.y );
		
		Vector2D lightCoord;
		{
			pCoreDisp->GetLuxelCoord( 0, i, lightCoord );
			builder.TexCoord2f( DISP_LMCOORDS_STAGE, lightCoord.x, lightCoord.y );
		}
		
		float flAlpha = ( ( CCoreDispInfo * )pCoreDisp )->GetAlpha( i );
		flAlpha *= ( 1.0f / 255.0f );
		flAlpha = clamp( flAlpha, 0.0f, 1.0f );
		builder.Color4f( 1.0f, 1.0f, 1.0f, flAlpha );
		
		if( nLightmaps > 1 )
		{
			SurfComputeLightmapCoordinate( ctx, pDisp->GetParent(), pDisp->m_Verts[i].m_vPos, lightCoord );
			builder.TexCoord2f( 2, ctx.m_BumpSTexCoordOffset, 0.0f );
		}
		
		builder.AdvanceVertex();
	}
	
	builder.EndModify();
#endif
}


void CDispInfo::CopyMapDispData( const ddispinfo_t *pBuildDisp )
{
	m_iLightmapAlphaStart = pBuildDisp->m_iLightmapAlphaStart;
	m_Power = pBuildDisp->power;

	Assert( m_Power >= 2 && m_Power <= NUM_POWERINFOS );
	m_pPowerInfo = ::GetPowerInfo( m_Power );

	// Max # of indices:
	// Take the number of triangles (2 * (size-1) * (size-1))
	// and multiply by 3!
	// These can be non-null in the case of task switch restore
	int size = GetSideLength();
	m_Indices.SetSize( 6 * (size-1) * (size-1) );

	// Per-node information
	if (m_pNodeInfo)
		delete[] m_pNodeInfo;

	m_pNodeInfo = new DispNodeInfo_t[m_pPowerInfo->m_NodeCount];
}


void DispInfo_CreateMaterialGroups( model_t *pWorld, const MaterialSystem_SortInfo_t *pSortInfos )
{
	for ( int iDisp=0; iDisp < pWorld->brush.pShared->numDispInfos; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );

		int idLMPage = pSortInfos[MSurf_MaterialSortID( pDisp->m_ParentSurfID )].lightmapPageID;
		
		CDispGroup *pCombo = FindCombo( g_DispGroups, idLMPage, MSurf_TexInfo( pDisp->m_ParentSurfID )->material );
		if( !pCombo )
			pCombo = AddCombo( g_DispGroups, idLMPage, MSurf_TexInfo( pDisp->m_ParentSurfID )->material );

		MEM_ALLOC_CREDIT();
		pCombo->m_DispInfos.AddToTail( iDisp );
	}
}


void DispInfo_LinkToParentFaces( model_t *pWorld, const ddispinfo_t *pMapDisps, int nDisplacements )
{
	for ( int iDisp=0; iDisp < nDisplacements; iDisp++ )
	{
		const ddispinfo_t *pMapDisp = &pMapDisps[iDisp];
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );

		// Set its parent.
		SurfaceHandle_t surfID = SurfaceHandleFromIndex( pMapDisp->m_iMapFace );
		Assert( pMapDisp->m_iMapFace >= 0 && pMapDisp->m_iMapFace < pWorld->brush.pShared->numsurfaces );
		Assert( MSurf_Flags( surfID ) & SURFDRAW_HAS_DISP );
		surfID->pDispInfo = pDisp;
		pDisp->SetParent( surfID );
	}
}


void DispInfo_CreateEmptyStaticBuffers( model_t *pWorld, const ddispinfo_t *pMapDisps )
{
	// For each combo, create empty buffers.
	for( int i=0; i < g_DispGroups.Size(); i++ )
	{
		CDispGroup *pCombo = g_DispGroups[i];

		int nTotalVerts=0, nTotalIndices=0;
		int iStart = 0;
		for( int iDisp=0; iDisp < pCombo->m_DispInfos.Size(); iDisp++ )
		{
			const ddispinfo_t *pMapDisp = &pMapDisps[pCombo->m_DispInfos[iDisp]];

			int nVerts, nIndices;
			CalcMaxNumVertsAndIndices( pMapDisp->power, &nVerts, &nIndices );
			
			// If we're going to pass our vertex buffer limit, or we're at the last one,
			// make a static buffer and fill it up.
			if( (nTotalVerts + nVerts) > MAX_STATIC_BUFFER_VERTS || 
				(nTotalIndices + nIndices) > MAX_STATIC_BUFFER_INDICES )
			{
				AddEmptyMesh( pWorld, pCombo, pMapDisps, &pCombo->m_DispInfos[iStart], iDisp-iStart, nTotalVerts, nTotalIndices );
				Assert( nTotalVerts > 0 && nTotalIndices > 0 );

				nTotalVerts = nTotalIndices = 0;
				iStart = iDisp;
				--iDisp;
			}
			else if( iDisp == pCombo->m_DispInfos.Size()-1 )
			{
				AddEmptyMesh( pWorld, pCombo, pMapDisps, &pCombo->m_DispInfos[iStart], iDisp-iStart+1, nTotalVerts+nVerts, nTotalIndices+nIndices );
				break;
			}
			else
			{
				nTotalVerts += nVerts;
				nTotalIndices += nIndices;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pWorld - 
//			iDisp - 
//			*pMapDisp - 
//			*pCoreDisp - 
//			*pVerts - 
//			pWorld - 
//			iDisp - 
// Output : Returns true on success, false on failure.
// Information: Setup the CCoreDispInfo using the ddispinfo_t and have it translate the data
// into a format we'll copy into the rendering structures. This roundaboutness is because
// of legacy code. It should all just be stored in the map file, but it's not a high priority right now.
//-----------------------------------------------------------------------------
bool DispInfo_CreateFromMapDisp( model_t *pWorld, int iDisp, const ddispinfo_t *pMapDisp, CCoreDispInfo *pCoreDisp, const CDispVert *pVerts,
								 const CDispTri *pTris,const MaterialSystem_SortInfo_t *pSortInfos, bool bRestoring )
{
	// Get the matching CDispInfo to fill in.
	CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );

	// Initialize the core disp info with data from the map displacement.
	pCoreDisp->GetSurface()->SetPointStart( pMapDisp->startPosition );
	pCoreDisp->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, pVerts, pTris );
	pCoreDisp->SetNeighborData( pMapDisp->m_EdgeNeighbors, pMapDisp->m_CornerNeighbors );

	// Copy the allowed verts list.
	ErrorIfNot( pCoreDisp->GetAllowedVerts().GetNumDWords() == sizeof( pMapDisp->m_AllowedVerts ) / 4, ( "DispInfo_StoreMapData: size mismatch in 'allowed verts' list" ) );
	for ( int iVert = 0; iVert < pCoreDisp->GetAllowedVerts().GetNumDWords(); ++iVert )
	{
		pCoreDisp->GetAllowedVerts().SetDWord( iVert, pMapDisp->m_AllowedVerts[iVert] );
	}

	// Build the reset of the intermediate data from the initial map displacement data.
	BuildDispSurfInit( pWorld, pCoreDisp, pDisp->GetParent() );	
	if ( !pCoreDisp->Create() )
		return false;

	// Save the point start index - needed for overlays.
	pDisp->m_iPointStart = pCoreDisp->GetSurface()->GetPointStartIndex();

	// Now setup the CDispInfo.
	pDisp->m_Index = static_cast<unsigned short>( iDisp );

	// Store ddispinfo_t data.
	pDisp->CopyMapDispData( pMapDisp );

	// Store CCoreDispInfo data.	
	if( !pDisp->CopyCoreDispData( pWorld, pSortInfos, pCoreDisp, bRestoring ) )
		return false;

	// Initialize all the active and other verts after setting up neighbors.
	pDisp->InitializeActiveVerts();
	pDisp->m_iLightmapSamplePositionStart = pMapDisp->m_iLightmapSamplePositionStart;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pWorld - 
//			iDisp - 
//			*pCoreDisp - 
//			*pVerts - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
void DispInfo_CreateStaticBuffersAndTags( model_t *pWorld, int iDisp, CCoreDispInfo *pCoreDisp, const CDispVert *pVerts )
{
	// Get the matching CDispInfo to fill in.
	CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );

	// Now copy the CCoreDisp's data into the static buffer.
	FillStaticBuffer( pDisp->m_pMesh, pDisp, pCoreDisp, pVerts, pDisp->NumLightMaps() );

	// Now build the tagged data for visualization.
	BuildTagData( pCoreDisp, pDisp );
}

// On the xbox, we lock the meshes ahead of time.
void SetupMeshReaders( model_t *pWorld, int nDisplacements )
{
	for ( int iDisp=0; iDisp < nDisplacements; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
		
		MeshDesc_t desc;
		memset( &desc, 0, sizeof( desc ) );
		
		desc.m_VertexSize_Position = sizeof( CDispRenderVert );
		desc.m_VertexSize_TexCoord[0] = sizeof( CDispRenderVert );
		desc.m_VertexSize_TexCoord[DISP_LMCOORDS_STAGE] = sizeof( CDispRenderVert );
		desc.m_VertexSize_Normal = sizeof( CDispRenderVert );
		desc.m_VertexSize_TangentS = sizeof( CDispRenderVert );
		desc.m_VertexSize_TangentT = sizeof( CDispRenderVert );

		CDispRenderVert *pBaseVert = pDisp->m_Verts.Base();
		desc.m_pPosition = (float*)&pBaseVert->m_vPos;
		desc.m_pTexCoord[0] = (float*)&pBaseVert->m_vTexCoord;
		desc.m_pTexCoord[DISP_LMCOORDS_STAGE] = (float*)&pBaseVert->m_LMCoords;
		desc.m_pNormal = (float*)&pBaseVert->m_vNormal;
		desc.m_pTangentS = (float*)&pBaseVert->m_vSVector;
		desc.m_pTangentT = (float*)&pBaseVert->m_vTVector;

		desc.m_nIndexSize = 1;
		desc.m_pIndices = pDisp->m_Indices.Base();
		
		pDisp->m_MeshReader.BeginRead_Direct( desc, pDisp->NumVerts(), pDisp->m_nIndices );
	}		
}

void UpdateDispBBoxes( model_t *pWorld, int nDisplacements )
{
	for ( int iDisp=0; iDisp < nDisplacements; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
		pDisp->UpdateBoundingBox();
	}
}		


#include "tier0/memdbgoff.h"
bool DispInfo_LoadDisplacements( model_t *pWorld, bool bRestoring )
{
	const MaterialSystem_SortInfo_t *pSortInfos = materialSortInfoArray;

	int nDisplacements = CMapLoadHelper::LumpSize( LUMP_DISPINFO ) / sizeof( ddispinfo_t );	
	int nLuxels = CMapLoadHelper::LumpSize( LUMP_DISP_LIGHTMAP_ALPHAS );
	int nSamplePositionBytes = CMapLoadHelper::LumpSize( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS );

	// Setup the world's list of displacements.
	if ( bRestoring )
	{
		/* Breakpoint-able: */
		if (pWorld->brush.pShared->numDispInfos != nDisplacements)
		{ 
			volatile int a = 0; a = a + 1; 
		}

		if ( !pWorld->brush.pShared->numDispInfos && nDisplacements )
		{
			// Attempting to restore displacements before displacements got loaded
			return false;
		}

		ErrorIfNot( 
			pWorld->brush.pShared->numDispInfos == nDisplacements,
			("DispInfo_LoadDisplacments: dispcounts (%d and %d) don't match.", pWorld->brush.pShared->numDispInfos, nDisplacements)
			);

		ErrorIfNot(
			g_DispLMAlpha.Count() == nLuxels,
			("DispInfo_LoadDisplacements: lightmap alpha counts (%d and %d) don't match.", g_DispLMAlpha.Count(), nLuxels)
			);
	}
	else
	{
		// Create the displacements.
		pWorld->brush.pShared->numDispInfos = nDisplacements;
		pWorld->brush.pShared->hDispInfos = DispInfo_CreateArray( pWorld->brush.pShared->numDispInfos );

		// Load lightmap alphas.
		{
		MEM_ALLOC_CREDIT();
		g_DispLMAlpha.SetSize( nLuxels );
		}
		CMapLoadHelper lhDispLMAlphas( LUMP_DISP_LIGHTMAP_ALPHAS );
		lhDispLMAlphas.LoadLumpData( 0, nLuxels, g_DispLMAlpha.Base() );
	
		// Load lightmap sample positions.
		{
		MEM_ALLOC_CREDIT();
		g_DispLightmapSamplePositions.SetSize( nSamplePositionBytes );
		}
		CMapLoadHelper lhDispLMPositions( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS );
		lhDispLMPositions.LoadLumpData( 0, nSamplePositionBytes, g_DispLightmapSamplePositions.Base() );
	}

	// Free old data.
	DispInfo_ReleaseMaterialSystemObjects( pWorld );
	
	// load the displacement info structures into temporary space
	// using temporary storage that is not the stack for compatibility with console stack
#ifndef _X360
	ddispinfo_t tempDisps[MAX_MAP_DISPINFO];
#else
	CUtlMemory< ddispinfo_t > m_DispInfoBuf( 0, MAX_MAP_DISPINFO );
	ddispinfo_t *tempDisps = m_DispInfoBuf.Base();
#endif
	ErrorIfNot( 
		nDisplacements <= MAX_MAP_DISPINFO,
		("DispInfo_LoadDisplacements: nDisplacements (%d) > MAX_MAP_DISPINFO (%d)", nDisplacements, MAX_MAP_DISPINFO)
		);
	CMapLoadHelper lhDispInfo( LUMP_DISPINFO );
	lhDispInfo.LoadLumpData( 0, nDisplacements * sizeof( ddispinfo_t ), tempDisps );

	// Now hook up the displacements to their parents.
	DispInfo_LinkToParentFaces( pWorld, tempDisps, nDisplacements );

	// First, create "groups" (or "combos") which contain all the displacements that 
	// use the same material and lightmap.
	DispInfo_CreateMaterialGroups( pWorld, pSortInfos );

	// Now make the static buffers for each material/lightmap combo.
	if ( g_VBAllocTracker )
		g_VBAllocTracker->TrackMeshAllocations( "DispInfo_LoadDisplacements" );
	DispInfo_CreateEmptyStaticBuffers( pWorld, tempDisps );
	if ( g_VBAllocTracker )
		g_VBAllocTracker->TrackMeshAllocations( NULL );

	// Now setup each displacement one at a time.
	// using temporary storage that is not the stack for compatibility with console stack
#ifndef _X360
	CDispVert tempVerts[MAX_DISPVERTS];
#else
	CUtlMemory< CDispVert > m_DispVertsBuf( 0, MAX_DISPVERTS );
	CDispVert *tempVerts = m_DispVertsBuf.Base();
#endif

#ifndef _X360
	CDispTri tempTris[MAX_DISPTRIS];
#else
	// using temporary storage that is not the stack for compatibility with console stack
	CUtlMemory< CDispTri > m_DispTrisBuf( 0, MAX_DISPTRIS );
	CDispTri *tempTris = m_DispTrisBuf.Base();
#endif

	int iCurVert = 0;
	int iCurTri = 0;

	// Core displacement list.
	CUtlVector<CCoreDispInfo*> aCoreDisps;
	int iDisp = 0;
	for ( iDisp = 0; iDisp < nDisplacements; ++iDisp )
	{
		CCoreDispInfo *pCoreDisp = new CCoreDispInfo;
		aCoreDisps.AddToTail( pCoreDisp );
	}
	
	CMapLoadHelper lhDispVerts( LUMP_DISP_VERTS );
	CMapLoadHelper lhDispTris( LUMP_DISP_TRIS );

	for ( iDisp = 0; iDisp < nDisplacements; ++iDisp )
	{
		// Get the current map displacement.
		ddispinfo_t *pMapDisp = &tempDisps[iDisp];
		if ( !pMapDisp )
			continue;

		// Load the vertices from the file.
		int nVerts = NUM_DISP_POWER_VERTS( pMapDisp->power );
		ErrorIfNot( nVerts <= MAX_DISPVERTS, ( "DispInfo_LoadDisplacements: invalid vertex count (%d)", nVerts ) );
		lhDispVerts.LoadLumpData( iCurVert * sizeof(CDispVert), nVerts*sizeof(CDispVert), tempVerts );
		iCurVert += nVerts;

		// Load the triangle indices from the file.
		int nTris = NUM_DISP_POWER_TRIS( pMapDisp->power );
		ErrorIfNot( nTris <= MAX_DISPTRIS, ( "DispInfo_LoadDisplacements: invalid tri count (%d)", nTris ) );
		lhDispTris.LoadLumpData( iCurTri * sizeof(CDispTri), nTris*sizeof(CDispTri), tempTris );
		iCurTri += nTris;
	
		// Now create the CoreDispInfo and the base CDispInfo.
		if ( !DispInfo_CreateFromMapDisp( pWorld, iDisp, pMapDisp, aCoreDisps[iDisp], tempVerts, tempTris, pSortInfos, bRestoring ) )
			return false;
	}	

	// Smooth Normals.
	SmoothDispSurfNormals( aCoreDisps.Base(), nDisplacements );

	// Fill in the static buffers.
	for ( iDisp = 0; iDisp < nDisplacements; ++iDisp )
	{
		DispInfo_CreateStaticBuffersAndTags( pWorld, iDisp, aCoreDisps[iDisp], tempVerts );
		
		// Copy over the now blended normals
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
		pDisp->CopyCoreDispVertData( aCoreDisps[iDisp], pDisp->m_BumpSTexCoordOffset );

	}

	// Destroy core displacement list.
	aCoreDisps.PurgeAndDeleteElements();

	// If we're not using LOD, then maximally tesselate all the displacements and
	// make sure they never change.
	for ( iDisp=0; iDisp < nDisplacements; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
		
		pDisp->m_ActiveVerts = pDisp->m_AllowedVerts;
	}

	for ( iDisp=0; iDisp < nDisplacements; iDisp++ )
	{
		CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
		pDisp->TesselateDisplacement();			
	}		

	SetupMeshReaders( pWorld, nDisplacements );

	UpdateDispBBoxes( pWorld, nDisplacements );

	return true;
}
#include "tier0/memdbgon.h"


void DispInfo_ReleaseMaterialSystemObjects( model_t *pWorld )
{
	CMatRenderContextPtr pRenderContext( materials );

	// Free all the static meshes.
	for( int iGroup=0; iGroup < g_DispGroups.Size(); iGroup++ )
	{
		CDispGroup *pGroup = g_DispGroups[iGroup];
	
		for( int iMesh=0; iMesh < pGroup->m_Meshes.Size(); iMesh++ )
		{
			CGroupMesh *pMesh = pGroup->m_Meshes[iMesh];

			pRenderContext->DestroyStaticMesh( pMesh->m_pMesh );
		}

		pGroup->m_Meshes.PurgeAndDeleteElements();
	}

	g_DispGroups.PurgeAndDeleteElements();


	// Clear pointers in the dispinfos.
	if( pWorld )
	{
		for( int iDisp=0; iDisp < pWorld->brush.pShared->numDispInfos; iDisp++ )
		{
			CDispInfo *pDisp = GetModelDisp( pWorld, iDisp );
			if ( !pDisp )
			{
				AssertOnce( 0 );
				continue;
			}
			
			pDisp->m_pMesh = NULL;
			pDisp->m_iVertOffset = pDisp->m_iIndexOffset = 0;
		}
	}
}


//-----------------------------------------------------------------------------
// CDispInfo implementation.
//-----------------------------------------------------------------------------

CDispInfo::CDispInfo()
{
	m_ParentSurfID = SURFACE_HANDLE_INVALID;

    m_bTouched = false;

	++g_ConstructorChecker.m_nConstructedObjects;

	m_BBoxMin.Init();
	m_BBoxMax.Init();

	m_idLMPage = -1;

	m_pPowerInfo = NULL;

	m_ViewerSphereCenter.Init( 1e24, 1e24, 1e24 );
	
	m_bInUse = false;

	m_pNodeInfo = 0;

	m_pMesh = NULL;

	m_Tag = NULL;
	m_pDispArray = NULL;

	m_FirstDecal = DISP_DECAL_HANDLE_INVALID;
	m_FirstShadowDecal = DISP_SHADOW_HANDLE_INVALID;

	for ( int i=0; i < 4; i++ )
	{
		m_EdgeNeighbors[i].SetInvalid();
		m_CornerNeighbors[i].SetInvalid();
	}
}


CDispInfo::~CDispInfo()
{
	if (m_pNodeInfo)
		delete[] m_pNodeInfo;

	delete[] m_pWalkIndices;
	delete[] m_pBuildIndices;

	--g_ConstructorChecker.m_nConstructedObjects;

	// All the decals should have been freed through 
	// CModelLoader::Map_UnloadModel -> R_DecalTerm
	Assert( m_FirstDecal == DISP_DECAL_HANDLE_INVALID );
	Assert( m_FirstShadowDecal == DISP_SHADOW_HANDLE_INVALID );
}


void CDispInfo::CopyCoreDispVertData( const CCoreDispInfo *pCoreDisp, float bumpSTexCoordOffset )
{
#ifndef SWDS
	if( NumLightMaps() <= 1 )
	{
		bumpSTexCoordOffset = 0.0f;
	}
	// Copy vertex positions (for backfacing tests).
	m_Verts.SetSize( m_pPowerInfo->m_MaxVerts );
	m_BumpSTexCoordOffset = bumpSTexCoordOffset;
	for( int i=0; i < NumVerts(); i++ )
	{
		pCoreDisp->GetVert( i, m_Verts[i].m_vPos );

		pCoreDisp->GetTexCoord( i, m_Verts[i].m_vTexCoord );
		pCoreDisp->GetLuxelCoord( 0, i, m_Verts[i].m_LMCoords );

		// mat_normals needs this as well as the dynamic lighting code
		pCoreDisp->GetNormal( i, m_Verts[i].m_vNormal );
		pCoreDisp->GetTangentS( i, m_Verts[i].m_vSVector );
		pCoreDisp->GetTangentT( i, m_Verts[i].m_vTVector );
	} 
#endif
}

bool CDispInfo::CopyCoreDispData( 
	model_t *pWorld,
	const MaterialSystem_SortInfo_t *pSortInfos,
	const CCoreDispInfo *pCoreDisp,
	bool bRestoring )
{
	m_idLMPage = pSortInfos[MSurf_MaterialSortID( GetParent() )].lightmapPageID;

#ifndef SWDS
	SurfaceCtx_t ctx;
	SurfSetupSurfaceContext( ctx, GetParent() );
#endif

	// Restoring is only for alt+tabbing, which can't happen on consoles
	if ( IsPC() && bRestoring )
	{
#ifndef SWDS
		// When restoring, have to recompute lightmap coords
		if( NumLightMaps() > 1 )
		{
			m_BumpSTexCoordOffset = ctx.m_BumpSTexCoordOffset;
		}
		else
		{
			m_BumpSTexCoordOffset = 0.0f;
		}
		for( int i=0; i < NumVerts(); i++ )
		{
			pCoreDisp->GetLuxelCoord( 0, i, m_Verts[i].m_LMCoords );
		}
#endif // SWDS
		return true;
	}

	// When restoring, leave all this data the same.
	const CCoreDispSurface *pSurface = pCoreDisp->GetSurface();
	for( int index=0; index < 4; index++ )
	{
		pSurface->GetTexCoord( index, m_BaseSurfaceTexCoords[index] );
		m_BaseSurfacePositions[index] = pSurface->GetPoint( index );
	}

#ifndef SWDS
	CopyCoreDispVertData( pCoreDisp, ctx.m_BumpSTexCoordOffset );
#endif

	// Copy neighbor info.
	for ( int iEdge=0; iEdge < 4; iEdge++ )
	{
		m_EdgeNeighbors[iEdge] = *pCoreDisp->GetEdgeNeighbor( iEdge );
		m_CornerNeighbors[iEdge] = *pCoreDisp->GetCornerNeighbors( iEdge );
	}

	// Copy allowed verts.
	m_AllowedVerts = pCoreDisp->GetAllowedVerts();

	m_nIndices = 0;
	return true;
}


int CDispInfo::NumLightMaps()
{
	return (MSurf_Flags( m_ParentSurfID ) & SURFDRAW_BUMPLIGHT) ? NUM_BUMP_VECTS+1 : 1;
}

//-----------------------------------------------------------------------------
// Purpose:
// NOTE: You cannot use the builddisp.cpp IsTriWalkable, IsTriBuildable functions
//       because the flags are different having been collapsed in vbsp 
//-----------------------------------------------------------------------------
void BuildTagData( CCoreDispInfo *pCoreDisp, CDispInfo *pDisp )
{
	int nWalkTest = 0;
	int nBuildTest = 0;
	int iTri;
	for ( iTri = 0; iTri < pCoreDisp->GetTriCount(); ++iTri )
	{
		if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_WALKABLE ) )
		{
			nWalkTest++;
		}

		if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_BUILDABLE ) )
		{
			nBuildTest++;
		}
	}

	nWalkTest *= 3;
	nBuildTest *= 3;

	pDisp->m_pWalkIndices = new unsigned short[nWalkTest];
	pDisp->m_pBuildIndices = new unsigned short[nBuildTest];

	int nWalkCount = 0;
	int nBuildCount = 0;
	for ( iTri = 0; iTri < pCoreDisp->GetTriCount(); ++iTri )
	{
		if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_WALKABLE ) )
		{
			pCoreDisp->GetTriIndices( iTri, 
								      pDisp->m_pWalkIndices[nWalkCount],
									  pDisp->m_pWalkIndices[nWalkCount+1],
									  pDisp->m_pWalkIndices[nWalkCount+2] );

			nWalkCount += 3;
		}

		if ( pCoreDisp->IsTriTag( iTri, DISPTRI_TAG_BUILDABLE ) )
		{
			pCoreDisp->GetTriIndices( iTri, 
								      pDisp->m_pBuildIndices[nBuildCount],
									  pDisp->m_pBuildIndices[nBuildCount+1],
									  pDisp->m_pBuildIndices[nBuildCount+2] );

			nBuildCount += 3;
		}
	}

	Assert( nWalkCount == nWalkTest );
	Assert( nBuildCount == nBuildTest );

	pDisp->m_nWalkIndexCount = nWalkCount;
	pDisp->m_nBuildIndexCount = nBuildCount;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pDisp - 
//			&vecPoint - 
// Output : int
//-----------------------------------------------------------------------------
int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vecPoint )
{
	CDispUtilsHelper *pDispHelper = pDisp;

	int iClosest = 0;
	float flClosest = 1e24;
	for ( int iCorner = 0; iCorner < 4; ++iCorner )
	{

		// Has it been touched?
		CVertIndex viCornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner );
		int iCornerVert = pDispHelper->VertIndexToInt( viCornerVert );
		const Vector &vecCornerVert = pDisp->GetVert( iCornerVert );

		float flDist = vecCornerVert.DistTo( vecPoint );
		if ( flDist < flClosest )
		{
			iClosest = iCorner;
			flClosest = flDist;
		}
	}

	if ( flClosest <= 0.1f )
		return iClosest;
	else
		return -1;
}

// sets a new normal/tangentS, recomputes tangent T
void UpdateTangentSpace(CCoreDispInfo *pDisp, int iVert, const Vector &vNormal, const Vector &vTanS)
{
	Vector tanT;
	pDisp->SetNormal( iVert, vNormal );
	CrossProduct( vTanS, vNormal, tanT );
	pDisp->SetTangentS(iVert, vTanS);
	pDisp->SetTangentT(iVert, tanT);
}

void UpdateTangentSpace(CCoreDispInfo *pDisp, const CVertIndex &index, const Vector &vNormal, const Vector &vTanS)
{
	UpdateTangentSpace(pDisp, pDisp->VertIndexToInt(index), vNormal, vTanS);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : **ppListBase - 
//			nListSize - 
//-----------------------------------------------------------------------------
void BlendSubNeighbors( CCoreDispInfo **ppListBase, int nListSize )
{
	// Loop through all of the displacements in the list.
	for ( int iDisp = 0; iDisp < nListSize; ++iDisp )
	{
		// Get the current displacement.
		CCoreDispInfo *pDisp = ppListBase[iDisp];
		if ( !pDisp )
			continue;

		// Loop through all the edges of the displacement.
		for ( int iEdge = 0; iEdge < 4; ++iEdge )
		{
			// Find valid neighbors along the edge.
			CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge );
			if ( !pEdge )
				continue;

			// Check to see if we have sub-neighbors - defines a t-junction in this world.  If not,
			// then the normal blend edges function will catch it all.
			if ( !pEdge->m_SubNeighbors[0].IsValid() || !pEdge->m_SubNeighbors[1].IsValid() )
				continue;

			// Get the mid-point of the current displacement.
			CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge );
			int iMidPoint = pDisp->VertIndexToInt( viMidPoint );

			const Vector &vecMidPoint = pDisp->GetVert( iMidPoint );

			// Get the current sub-neighbors along the edge.
			CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()];
			CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()];

			// Get the current sub-neighbor corners.
			int iCorners[2];
			iCorners[0] = FindNeighborCornerVert( pNeighbor1, vecMidPoint );
			iCorners[1] = FindNeighborCornerVert( pNeighbor2, vecMidPoint );
			if ( iCorners[0] != -1 && iCorners[1] != -1 )
			{
				CVertIndex viCorners[2] = { pNeighbor1->GetCornerPointIndex( iCorners[0] ),pNeighbor2->GetCornerPointIndex( iCorners[1] ) };

				// Accumulate the normals at the mid-point of the primary edge and corners of the sub-neighbors.
				Vector vecAverage = pDisp->GetNormal( iMidPoint );
				vecAverage += pNeighbor1->GetNormal( viCorners[0] );
				vecAverage += pNeighbor2->GetNormal( viCorners[1] );

				// Re-normalize.
				VectorNormalize( vecAverage );
				Vector vAvgTanS = pDisp->GetTangentS(iMidPoint);
				vAvgTanS += pNeighbor1->GetTangentS(viCorners[0]);
				vAvgTanS += pNeighbor2->GetTangentS(viCorners[1]);
				VectorNormalize(vAvgTanS);
				//vecAverage.Init( 0.0f, 0.0f, 1.0f );

				// Set the new normal value back.
				UpdateTangentSpace( pDisp, iMidPoint, vecAverage, vAvgTanS );
				UpdateTangentSpace( pNeighbor1, viCorners[0], vecAverage, vAvgTanS );
				UpdateTangentSpace( pNeighbor2, viCorners[1], vecAverage, vAvgTanS );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pDisp - 
//			iNeighbors[512] - 
// Output : int
//-----------------------------------------------------------------------------
int GetAllNeighbors( const CCoreDispInfo *pDisp, int iNeighbors[512] )
{
	int nNeighbors = 0;

	// Check corner neighbors.
	for ( int iCorner=0; iCorner < 4; iCorner++ )
	{
		const CDispCornerNeighbors *pCorner = pDisp->GetCornerNeighbors( iCorner );

		for ( int i=0; i < pCorner->m_nNeighbors; i++ )
		{
			if ( nNeighbors < 512 )
				iNeighbors[nNeighbors++] = pCorner->m_Neighbors[i];
		}
	}

	for ( int iEdge=0; iEdge < 4; iEdge++ )
	{
		const CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge );

		for ( int i=0; i < 2; i++ )
		{
			if ( pEdge->m_SubNeighbors[i].IsValid() )
				if ( nNeighbors < 512 )
					iNeighbors[nNeighbors++] = pEdge->m_SubNeighbors[i].GetNeighborIndex();
		}
	}

	return nNeighbors;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : **ppListBase - 
//			listSize - 
//-----------------------------------------------------------------------------
void BlendCorners( CCoreDispInfo **ppListBase, int nListSize )
{
	CUtlVector<int> nbCornerVerts;

	for ( int iDisp = 0; iDisp < nListSize; ++iDisp )
	{
		CCoreDispInfo *pDisp = ppListBase[iDisp];

		int iNeighbors[512];
		int nNeighbors = GetAllNeighbors( pDisp, iNeighbors );

		// Make sure we have room for all the neighbors.
		nbCornerVerts.RemoveAll();
		nbCornerVerts.EnsureCapacity( nNeighbors );
		nbCornerVerts.AddMultipleToTail( nNeighbors );
		
		// For each corner.
		for ( int iCorner=0; iCorner < 4; iCorner++ )
		{
			// Has it been touched?
			CVertIndex cornerVert = pDisp->GetCornerPointIndex( iCorner );
			int iCornerVert = pDisp->VertIndexToInt( cornerVert );
			const Vector &vCornerVert = pDisp->GetVert( iCornerVert );

			// For each displacement sharing this corner..
			Vector vAverage = pDisp->GetNormal( iCornerVert );
			Vector vAvgTanS;
			pDisp->GetTangentS( iCornerVert, vAvgTanS );

			for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ )
			{
				int iNBListIndex = iNeighbors[iNeighbor];
				CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex];
				
				// Find out which vert it is on the neighbor.
				int iNBCorner = FindNeighborCornerVert( pNeighbor, vCornerVert );
				if ( iNBCorner == -1 )
				{
					nbCornerVerts[iNeighbor] = -1; // remove this neighbor from the list.
				}
				else
				{
					CVertIndex viNBCornerVert = pNeighbor->GetCornerPointIndex( iNBCorner );
					int iNBVert = pNeighbor->VertIndexToInt( viNBCornerVert );
					nbCornerVerts[iNeighbor] = iNBVert;
					vAverage += pNeighbor->GetNormal( iNBVert );
					vAvgTanS += pNeighbor->GetTangentS( iNBVert );
				}
			}


			// Blend all the neighbor normals with this one.
			VectorNormalize( vAverage );
			VectorNormalize( vAvgTanS );
			UpdateTangentSpace(pDisp, iCornerVert, vAverage, vAvgTanS );

			for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ )
			{
				int iNBListIndex = iNeighbors[iNeighbor];
				if ( nbCornerVerts[iNeighbor] == -1 )
					continue;

				CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex];
				UpdateTangentSpace(pNeighbor, nbCornerVerts[iNeighbor], vAverage, vAvgTanS);
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : **ppListBase - 
//			listSize - 
//-----------------------------------------------------------------------------
void BlendEdges( CCoreDispInfo **ppListBase, int nListSize )
{
	// Loop through all the displacements in the list.
	for ( int iDisp = 0; iDisp < nListSize; ++iDisp )
	{
		// Get the current displacement.
		CCoreDispInfo *pDisp = ppListBase[iDisp];
		if ( !pDisp )
			continue;

		// Loop through all of the edges on a displacement.
		for ( int iEdge = 0; iEdge < 4; ++iEdge )
		{
			// Get the current displacement edge.
			CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge );
			if ( !pEdge )
				continue;

			// Check for sub-edges.
			for ( int iSubEdge = 0; iSubEdge < 2; ++iSubEdge )
			{
				// Get the current sub-edge.
				CDispSubNeighbor *pSubEdge = &pEdge->m_SubNeighbors[iSubEdge];
				if ( !pSubEdge->IsValid() )
					continue;

				// Get the current neighbor.
				CCoreDispInfo *pNeighbor = ppListBase[pSubEdge->GetNeighborIndex()];
				if ( !pNeighbor )
					continue;

				// Get the edge dimension.
				int iEdgeDim = g_EdgeDims[iEdge];

				CDispSubEdgeIterator it;
				it.Start( pDisp, iEdge, iSubEdge, true );

				// Get setup on the first corner vert.
				it.Next();
				CVertIndex viPrevPos = it.GetVertIndex();
				while ( it.Next() )
				{
					// Blend the two.
					if ( !it.IsLastVert() )
					{
						Vector vecAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() );
						Vector vAvgTanS = pDisp->GetTangentS( it.GetVertIndex() ) + pNeighbor->GetTangentS( it.GetNBVertIndex() );
						VectorNormalize( vecAverage );
						VectorNormalize( vAvgTanS );
						UpdateTangentSpace(pDisp, it.GetVertIndex(), vecAverage, vAvgTanS );
						UpdateTangentSpace(pNeighbor, it.GetNBVertIndex(), vecAverage, vAvgTanS );
					}

					// Now blend the in-between verts (if this edge is high-res).
					int iPrevPos = viPrevPos[!iEdgeDim];
					int iCurPos = it.GetVertIndex()[!iEdgeDim];
					for ( int iTween = iPrevPos+1; iTween < iCurPos; iTween++ )
					{
						float flPercent = RemapVal( iTween, iPrevPos, iCurPos, 0, 1 );
						Vector vecNormal;
						VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vecNormal );
						VectorNormalize( vecNormal );
						Vector vAvgTanS;
						VectorLerp( pDisp->GetTangentS( viPrevPos ), pDisp->GetTangentS( it.GetVertIndex() ), flPercent, vAvgTanS );
						VectorNormalize( vAvgTanS );

						CVertIndex viTween;
						viTween[iEdgeDim] = it.GetVertIndex()[iEdgeDim];
						viTween[!iEdgeDim] = iTween;
						UpdateTangentSpace(pDisp, viTween, vecNormal, vAvgTanS);
					}
			
					viPrevPos = it.GetVertIndex();
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : **pListBase - 
//			listSize - 
// NOTE: todo - this is almost the same code as found in vrad, should probably
//              move it up into common code at some point if the feature
//              continues to get used
//-----------------------------------------------------------------------------
void SmoothDispSurfNormals( CCoreDispInfo **ppListBase, int nListSize )
{
	// Setup helper list for iteration.
	for ( int iDisp = 0; iDisp < nListSize; ++iDisp )
	{
		ppListBase[iDisp]->SetDispUtilsHelperInfo( ppListBase, nListSize );
	}

	// Blend normals along t-junctions, corners, and edges.
	BlendSubNeighbors( ppListBase, nListSize );
	BlendCorners( ppListBase, nListSize );
	BlendEdges( ppListBase, nListSize );
}