//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
// identifier was truncated to '255' characters in the debug information
#pragma warning(disable: 4786)

#include "ProxyEntity.h"
#include "materialsystem/IMaterialVar.h"
#include "materialsystem/ITexture.h"
#include "TGALoader.h"
#include "view.h"
#include "materialsystem/IMaterial.h"
#include "cache_user.h"
#include "vtf/vtf.h"


class CCamoMaterialProxy;

class CCamoTextureRegen : public ITextureRegenerator
{
public:
	CCamoTextureRegen( CCamoMaterialProxy *pProxy ) : m_pProxy(pProxy) {} 
	virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect );
	virtual void Release() {}

private:
	CCamoMaterialProxy *m_pProxy;
};

class CCamoMaterialProxy : public CEntityMaterialProxy
{
public:
	CCamoMaterialProxy();
	virtual ~CCamoMaterialProxy();
	virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
	virtual void OnBind(C_BaseEntity *pC_BaseEntity );

	// Procedurally generates the camo texture...
	void GenerateCamoTexture( ITexture* pTexture, IVTFTexture *pVTFTexture );

protected:
#if 0
	virtual void SetInstanceDataSize( int size );
	virtual void *FindInstanceData( C_BaseEntity *pEntity );
	virtual void *AllocateInstanceData( C_BaseEntity *pEntity );
#endif

private:
	void LoadCamoPattern( void );
	void GenerateRandomPointsInNormalizedCube( void );
	void GetColors( Vector &lighting, Vector &base, int index, 
						  const Vector &boxMin, const Vector &boxExtents, 
						  const Vector &forward, const Vector &right, const Vector &up,
						  const Vector& entityPosition );
	// this needs to go in a base class

private:
#if 0
	// stuff that needs to be in a base class.
	struct InstanceData_t
	{
		C_BaseEntity *pEntity;
		void *data;
		struct InstanceData_s *next;
	};
	
	struct CamoInstanceData_t
	{
		int dummy;
	};
#endif
	
	unsigned char *m_pCamoPatternImage;

#if 0
	int m_InstanceDataSize;
	InstanceData_t *m_InstanceDataListHead;
#endif

	IMaterial *m_pMaterial;
	IMaterialVar *m_pCamoTextureVar;
	IMaterialVar *m_pCamoPatternTextureVar;
	Vector *m_pointsInNormalizedBox; // [m_CamoPatternNumColors]

	int m_CamoPatternNumColors;
	int m_CamoPatternWidth;
	int m_CamoPatternHeight;
	cache_user_t m_camoImageDataCache;
	unsigned char m_CamoPalette[256][3];
	// these represent that part of the entitiy's bounding box that we 
	// want to cast rays through to get colors for the camo
	Vector m_SubBoundingBoxMin; // normalized
	Vector m_SubBoundingBoxMax; // normalized

	CCamoTextureRegen m_TextureRegen;
	C_BaseEntity *m_pEnt;
};


void CCamoTextureRegen::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect )
{
	m_pProxy->GenerateCamoTexture( pTexture, pVTFTexture );
}


#pragma warning (disable:4355)

CCamoMaterialProxy::CCamoMaterialProxy() : m_TextureRegen(this)
{
#if 0
	m_InstanceDataSize = 0;
#endif
	memset( &m_camoImageDataCache, 0,sizeof( m_camoImageDataCache ) );
	m_pointsInNormalizedBox = NULL;
#if 0
	m_InstanceDataListHead = NULL;
#endif
	m_pCamoPatternImage = NULL;
	m_pMaterial = NULL;
	m_pCamoTextureVar = NULL;
	m_pCamoPatternTextureVar = NULL;
	m_pointsInNormalizedBox = NULL;
	m_pEnt = NULL;
}

#pragma warning (default:4355)

CCamoMaterialProxy::~CCamoMaterialProxy()
{
#if 0
	InstanceData_t *curr = m_InstanceDataListHead;
	while( curr )
	{
		InstanceData_t *next;
		next = curr->next;
		delete curr;
		curr = next;
	}
	m_InstanceDataListHead = NULL;
#endif

	// Disconnect the texture regenerator...
	if (m_pCamoTextureVar)
	{
		ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue();
		if (pCamoTexture)
			pCamoTexture->SetTextureRegenerator( NULL );
	}

	delete m_pCamoPatternImage;
	delete m_pointsInNormalizedBox;
}


#if 0
void CCamoMaterialProxy::SetInstanceDataSize( int size )
{
	m_InstanceDataSize = size;
}
#endif

#if 0
void *CCamoMaterialProxy::FindInstanceData( C_BaseEntity *pEntity )
{
	InstanceData_t *curr = m_InstanceDataListHead;
	while( curr )
	{
		if( pEntity == curr->pEntity )
		{
			return curr->data;
		}
		curr = curr->next;
	}
	return NULL;
}
#endif

#if 0
void *CCamoMaterialProxy::AllocateInstanceData( C_BaseEntity *pEntity )
{
	InstanceData_t *newData = new InstanceData_t;
	newData->pEntity = pEntity;
	newData->next = m_InstanceDataListHead;
	m_InstanceDataListHead = newData;
	newData->data = new unsigned char[m_InstanceDataSize];
	return newData->data;
}
#endif

bool CCamoMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
	return false; // hack!  Need to make sure that the TGA loader has a valid filesystem before trying
			// to load the camo pattern.

#if 0
	// set how big our instance data is.
	SetInstanceDataSize( sizeof( CamoInstanceData_t ) );
#endif
	// remember what material we belong to.
	m_pMaterial = pMaterial;
	// get pointers to material vars.
	bool found;
	m_pCamoTextureVar = m_pMaterial->FindVar( "$baseTexture", &found );
	if( !found )
	{
		m_pCamoTextureVar = NULL;
		return false;
	}
	ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue();
	if (pCamoTexture)
		pCamoTexture->SetTextureRegenerator( &m_TextureRegen );
	
	// Need to get the palettized texture to create the procedural texture from
	// somewhere.
	m_pCamoPatternTextureVar = m_pMaterial->FindVar( "$camoPatternTexture", &found );
	if( !found )
	{
		m_pCamoTextureVar = NULL;
		return false;
	}
	
	IMaterialVar *subBoundingBoxMinVar, *subBoundingBoxMaxVar;

	subBoundingBoxMinVar = m_pMaterial->FindVar( "$camoBoundingBoxMin", &found, false );
	if( !found )
	{
		m_SubBoundingBoxMin = Vector( 0.0f, 0.0f, 0.0f );
	}
	else
	{
		subBoundingBoxMinVar->GetVecValue( m_SubBoundingBoxMin.Base(), 3 );
	}

	subBoundingBoxMaxVar = m_pMaterial->FindVar( "$camoBoundingBoxMax", &found, false );
	if( !found )
	{
		m_SubBoundingBoxMax = Vector( 1.0f, 1.0f, 1.0f );
	}
	else
	{
		subBoundingBoxMaxVar->GetVecValue( m_SubBoundingBoxMax.Base(), 3 );
	}
	
	LoadCamoPattern();
	GenerateRandomPointsInNormalizedCube();

	return true;
}

void CCamoMaterialProxy::GetColors( Vector &diffuseColor, Vector &baseColor, int index, 
										  const Vector &boxMin, const Vector &boxExtents, 
										  const Vector &forward, const Vector &right, const Vector &up,
										  const Vector& entityPosition )
{
	Vector position, transformedPosition;
	
	// hack
//	m_pointsInNormalizedBox[index] = Vector( 0.5f, 0.5f, 1.0f );
	
	position[0] = m_pointsInNormalizedBox[index][0] * boxExtents[0] + boxMin[0];
	position[1] = m_pointsInNormalizedBox[index][1] * boxExtents[1] + boxMin[1];
	position[2] = m_pointsInNormalizedBox[index][2] * boxExtents[2] + boxMin[2];
	transformedPosition[0] = right[0] * position[0] + forward[0] * position[1] + up[0] * position[2];
	transformedPosition[1] = right[1] * position[0] + forward[1] * position[1] + up[1] * position[2];
	transformedPosition[2] = right[2] * position[0] + forward[2] * position[1] + up[2] * position[2];
	transformedPosition = transformedPosition + entityPosition;
	Vector direction = transformedPosition - CurrentViewOrigin();
	VectorNormalize( direction );
	direction = direction * ( COORD_EXTENT * 1.74f );
	Vector endPoint = position + direction;
	
	// baseColor is already in gamma space
//	engine->TraceLineMaterialAndLighting( g_vecInstantaneousRenderOrigin, endPoint, diffuseColor, baseColor );
	engine->TraceLineMaterialAndLighting( transformedPosition, endPoint, diffuseColor, baseColor );

	// hack - optimize! - convert from linear to gamma space - this should be hidden
	diffuseColor[0] = pow( diffuseColor[0], 1.0f / 2.2f );
	diffuseColor[1] = pow( diffuseColor[1], 1.0f / 2.2f );
	diffuseColor[2] = pow( diffuseColor[2], 1.0f / 2.2f );

#if 0
	Msg( "%f %f %f\n", 
		diffuseColor[0], 
		diffuseColor[1], 
		diffuseColor[2] );
#endif
	
#if 0
	float max;
	max = diffuseColor[0];
	if( diffuseColor[1] > max )
	{
		max = diffuseColor[1];
	}
	if( diffuseColor[2] > max )
	{
		max = diffuseColor[2];
	}
	if( max > 1.0f )
	{
		max = 1.0f / max;
		diffuseColor = diffuseColor * max;
	}
#else
	if( diffuseColor[0] > 1.0f )
	{
		diffuseColor[0] = 1.0f;
	}
	if( diffuseColor[1] > 1.0f )
	{
		diffuseColor[1] = 1.0f;
	}
	if( diffuseColor[2] > 1.0f )
	{
		diffuseColor[2] = 1.0f;
	}
#endif
	// hack
	//baseColor = Vector( 1.0f, 1.0f, 1.0f );
	//diffuseColor = Vector( 1.0f, 1.0f, 1.0f );
}


//-----------------------------------------------------------------------------
// Procedurally generates the camo texture...
//-----------------------------------------------------------------------------
void CCamoMaterialProxy::GenerateCamoTexture( ITexture* pTexture, IVTFTexture *pVTFTexture )
{
	if (!m_pEnt)
		return;

#if 0
	CamoInstanceData_t *pInstanceData;
	pInstanceData = ( CamoInstanceData_t * )FindInstanceData( pEnt );
	if( !pInstanceData )
	{
		pInstanceData = ( CamoInstanceData_t * )AllocateInstanceData( pEnt );
		if( !pInstanceData )
		{
			return;
		}
		// init the instance data
	}
#endif

	Vector entityPosition;
	entityPosition = m_pEnt->GetAbsOrigin();

	QAngle entityAngles;
	entityAngles = m_pEnt->GetAbsAngles();

	// Get the bounding box for the entity
	Vector mins, maxs;
	mins = m_pEnt->WorldAlignMins();
	maxs = m_pEnt->WorldAlignMaxs();
	
	Vector traceDirection;
	Vector traceEnd;
	trace_t	traceResult;
	
	Vector forward, right, up;
	AngleVectors( entityAngles, &forward, &right, &up );
	
	Vector position, transformedPosition;
	Vector maxsMinusMins = maxs - mins;

	Vector diffuseColor[256];
	Vector baseColor;

	unsigned char camoPalette[256][3];
	// Calculate the camo palette
	//Msg( "start of loop\n" );
	int i;
	for( i = 0; i < m_CamoPatternNumColors; i++ )
	{
		GetColors( diffuseColor[i], baseColor, i,
			mins, maxsMinusMins, forward, right, up, entityPosition );
#if 1
		camoPalette[i][0] = diffuseColor[i][0] * baseColor[0] * 255.0f;
		camoPalette[i][1] = diffuseColor[i][1] * baseColor[1] * 255.0f;
		camoPalette[i][2] = diffuseColor[i][2] * baseColor[2] * 255.0f;
#endif
#if 0
		camoPalette[i][0] = baseColor[0] * 255.0f;
		camoPalette[i][1] = baseColor[1] * 255.0f;
		camoPalette[i][2] = baseColor[2] * 255.0f;
#endif
#if 0
		camoPalette[i][0] = diffuseColor[i][0] * 255.0f;
		camoPalette[i][1] = diffuseColor[i][1] * 255.0f;
		camoPalette[i][2] = diffuseColor[i][2] * 255.0f;
#endif
	}
	
	int width = pVTFTexture->Width();
	int height = pVTFTexture->Height();
	if( width != m_CamoPatternWidth || height != m_CamoPatternHeight )
	{
		return;
	}
	
	unsigned char *imageData = pVTFTexture->ImageData( 0, 0, 0 );
	enum ImageFormat imageFormat = pVTFTexture->Format();
	if( imageFormat != IMAGE_FORMAT_RGB888 )
	{
		return;
	}
	// optimize
#if 1
	int x, y;
	for( y = 0; y < height; y++ )
	{
		for( x = 0; x < width; x++ )
		{
			int offset = 3 * ( x + y * width );
			assert( offset < width * height * 3 );
			int paletteID = m_pCamoPatternImage[x + y * width];
			assert( paletteID < 256 );
#if 1
			imageData[offset + 0] = camoPalette[paletteID][0];
			imageData[offset + 1] = camoPalette[paletteID][1];
			imageData[offset + 2] = camoPalette[paletteID][2];
#else
			imageData[offset] = 255;
			imageData[offset + 1] = 0;
			imageData[offset + 2] = 0;
#endif
		}
	}
#endif
}


//-----------------------------------------------------------------------------
// Called when the texture is bound...
//-----------------------------------------------------------------------------
void CCamoMaterialProxy::OnBind( C_BaseEntity *pEntity )
{
	if( !m_pCamoTextureVar )
	{
		return;
	}
	
	m_pEnt = pEntity;	
	ITexture *pCamoTexture = m_pCamoTextureVar->GetTextureValue();
	pCamoTexture->Download();

	// Mark it so it doesn't get regenerated on task switch
	m_pEnt = NULL;
}

void CCamoMaterialProxy::LoadCamoPattern( void )
{
#if 0
	// hack - need to figure out a name to attach that isn't too long.
	m_pCamoPatternImage = 
		( unsigned char * )engineCache->FindByName( &m_camoImageDataCache, "camopattern" );
	
	if( m_pCamoPatternImage )
	{
		// is already in the cache.
		return m_pCamoPatternImage;
	}
#endif
	
	enum ImageFormat indexImageFormat;
	int indexImageSize;
	float dummyGamma;
	if( !TGALoader::GetInfo( m_pCamoPatternTextureVar->GetStringValue(), 
		&m_CamoPatternWidth, &m_CamoPatternHeight, &indexImageFormat, &dummyGamma ) )
	{
		//Warning( "Can't get tga info for hl2/materials/models/combine_elite/camo7paletted.tga for camo material\n" );
		m_pCamoTextureVar = NULL;
		return;
	}
	
	if( indexImageFormat != IMAGE_FORMAT_I8 )
	{
		//	Warning( "Camo material texture hl2/materials/models/combine_elite/camo7paletted.tga must be 8-bit greyscale\n" );
		m_pCamoTextureVar = NULL;
		return;
	}
	
	indexImageSize = ImageLoader::GetMemRequired( m_CamoPatternWidth, m_CamoPatternHeight, indexImageFormat, false );
#if 0
	m_pCamoPatternImage = ( unsigned char * )
		engineCache->Alloc( &m_camoImageDataCache, indexImageSize, "camopattern" );
#endif
	m_pCamoPatternImage = ( unsigned char * )new unsigned char[indexImageSize];
	if( !m_pCamoPatternImage )
	{
		m_pCamoTextureVar = NULL;
		return;
	}
	
	if( !TGALoader::Load( m_pCamoPatternImage, m_pCamoPatternTextureVar->GetStringValue(),
		m_CamoPatternWidth, m_CamoPatternHeight, IMAGE_FORMAT_I8, dummyGamma, false ) )
	{
		//			Warning( "camo texture hl2/materials/models/combine_elite/camo7paletted.tga must be grey-scale" );
		m_pCamoTextureVar = NULL;
		return;
	}
	
	bool colorUsed[256];
	int colorRemap[256];
	// count the number of colors used in the image.
	int i;
	for( i = 0; i < 256; i++ )
	{
		colorUsed[i] = false;
	}
	for( i = 0; i < indexImageSize; i++ )
	{
		colorUsed[m_pCamoPatternImage[i]] = true;
	}
	m_CamoPatternNumColors = 0;
	for( i = 0; i < 256; i++ )
	{
		if( colorUsed[i] )
		{
			colorRemap[i] = m_CamoPatternNumColors;
			m_CamoPatternNumColors++;
		}
	}
	// remap the color to the beginning of the palette.
	for( i = 0; i < indexImageSize; i++ )
	{
		m_pCamoPatternImage[i] = colorRemap[m_pCamoPatternImage[i]];
		// hack
//		m_pCamoPatternImage[i] = 0;
	}
}

void CCamoMaterialProxy::GenerateRandomPointsInNormalizedCube( void )
{
	m_pointsInNormalizedBox = new Vector[m_CamoPatternNumColors];
	if( !m_pointsInNormalizedBox )
	{
		m_pCamoTextureVar = NULL;
		return;
	}
	
	int i;
	for( i = 0; i < m_CamoPatternNumColors; i++ )
	{
		m_pointsInNormalizedBox[i][0] = random->RandomFloat( m_SubBoundingBoxMin[0], m_SubBoundingBoxMax[0] );
		m_pointsInNormalizedBox[i][1] = random->RandomFloat( m_SubBoundingBoxMin[1], m_SubBoundingBoxMax[1] );
		m_pointsInNormalizedBox[i][2] = random->RandomFloat( m_SubBoundingBoxMin[2], m_SubBoundingBoxMax[2] );
	}
}

EXPOSE_INTERFACE( CCamoMaterialProxy, IMaterialProxy, "Camo" IMATERIAL_PROXY_INTERFACE_VERSION );
