//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "enginesprite.h"
#include "hud.h"
#include "materialsystem/imesh.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "c_sprite.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Sprites are clipped to this rectangle (x,y,width,height) if ScissorTest is enabled
static int scissor_x = 0;
static int scissor_y = 0;
static int scissor_width = 0;
static int scissor_height = 0;
static bool giScissorTest = false;
//-----------------------------------------------------------------------------
// Purpose:
// Set the scissor
// the coordinate system for gl is upsidedown (inverted-y) as compared to software, so the
// specified clipping rect must be flipped
// Input : x -
// y -
// width -
// height -
//-----------------------------------------------------------------------------
void EnableScissorTest( int x, int y, int width, int height )
{
x = clamp( x, 0, ScreenWidth() );
y = clamp( y, 0, ScreenHeight() );
width = clamp( width, 0, ScreenWidth() - x );
height = clamp( height, 0, ScreenHeight() - y );
scissor_x = x;
scissor_width = width;
scissor_y = y;
scissor_height = height;
giScissorTest = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DisableScissorTest( void )
{
scissor_x = 0;
scissor_width = 0;
scissor_y = 0;
scissor_height = 0;
giScissorTest = false;
}
//-----------------------------------------------------------------------------
// Purpose: Verify that this is a valid, properly ordered rectangle.
// Input : *prc -
// Output : int
//-----------------------------------------------------------------------------
static int ValidateWRect(const wrect_t *prc)
{
if (!prc)
return false;
if ((prc->left >= prc->right) || (prc->top >= prc->bottom))
{
//!!!UNDONE Dev only warning msg
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: classic interview question
// Input : *prc1 -
// *prc2 -
// *prc -
// Output : int
//-----------------------------------------------------------------------------
static int IntersectWRect(const wrect_t *prc1, const wrect_t *prc2, wrect_t *prc)
{
wrect_t rc;
if (!prc)
prc = &rc;
prc->left = max(prc1->left, prc2->left);
prc->right = min(prc1->right, prc2->right);
if (prc->left < prc->right)
{
prc->top = max(prc1->top, prc2->top);
prc->bottom = min(prc1->bottom, prc2->bottom);
if (prc->top < prc->bottom)
return 1;
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : x -
// y -
// width -
// height -
// u0 -
// v0 -
// u1 -
// v1 -
// Output : static bool
//-----------------------------------------------------------------------------
static bool Scissor( int& x, int& y, int& width, int& height, float& u0, float& v0, float& u1, float& v1 )
{
// clip sub rect to sprite
if ((width == 0) || (height == 0))
return false;
if ((x + width <= scissor_x) || (x >= scissor_x + scissor_width) ||
(y + height <= scissor_y) || (y >= scissor_y + scissor_height))
return false;
float dudx = (u1-u0) / width;
float dvdy = (v1-v0) / height;
if (x < scissor_x)
{
u0 += (scissor_x - x) * dudx;
width -= scissor_x - x;
x = scissor_x;
}
if (x + width > scissor_x + scissor_width)
{
u1 -= (x + width - (scissor_x + scissor_width)) * dudx;
width = scissor_x + scissor_width - x;
}
if (y < scissor_y)
{
v0 += (scissor_y - y) * dvdy;
height -= scissor_y - y;
y = scissor_y;
}
if (y + height > scissor_y + scissor_height)
{
v1 -= (y + height - (scissor_y + scissor_height)) * dvdy;
height = scissor_y + scissor_height - y;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSprite -
// frame -
// *pfLeft -
// *pfRight -
// *pfTop -
// *pfBottom -
// *pw -
// *ph -
// *prcSubRect -
// Output : static void
//-----------------------------------------------------------------------------
static void AdjustSubRect(CEngineSprite *pSprite, int frame, float *pfLeft, float *pfRight, float *pfTop,
float *pfBottom, int *pw, int *ph, const wrect_t *prcSubRect)
{
wrect_t rc;
float f;
if (!ValidateWRect(prcSubRect))
return;
// clip sub rect to sprite
rc.top = rc.left = 0;
rc.right = *pw;
rc.bottom = *ph;
if (!IntersectWRect(prcSubRect, &rc, &rc))
return;
*pw = rc.right - rc.left;
*ph = rc.bottom - rc.top;
f = 1.0 / (float)pSprite->GetWidth();;
*pfLeft = ((float)rc.left + 0.5) * f;
*pfRight = ((float)rc.right - 0.5) * f;
f = 1.0 / (float)pSprite->GetHeight();
*pfTop = ((float)rc.top + 0.5) * f;
*pfBottom = ((float)rc.bottom - 0.5) * f;
return;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static unsigned int spriteOriginCache = 0;
bool CEngineSprite::Init( const char *pName )
{
m_hAVIMaterial = AVIMATERIAL_INVALID;
m_width = m_height = m_numFrames = 1;
const char *pExt = Q_GetFileExtension( pName );
bool bIsAVI = pExt && !Q_stricmp( pExt, "avi" );
if ( bIsAVI )
{
m_hAVIMaterial = avi->CreateAVIMaterial( pName, pName, "GAME" );
if ( m_hAVIMaterial == AVIMATERIAL_INVALID )
return false;
m_material = avi->GetMaterial( m_hAVIMaterial );
avi->GetFrameSize( m_hAVIMaterial, &m_width, &m_height );
m_numFrames = avi->GetFrameCount( m_hAVIMaterial );
}
else
{
m_material = materials->FindMaterial( pName, TEXTURE_GROUP_CLIENT_EFFECTS );
m_width = m_material->GetMappingWidth();
m_height = m_material->GetMappingHeight();
m_numFrames = (!bIsAVI) ? m_material->GetNumAnimationFrames() : avi->GetFrameCount( m_hAVIMaterial );
}
if ( !m_material )
return false;
m_material->IncrementReferenceCount();
m_orientation = GetOrientation();
IMaterialVar *originVar = m_material->FindVarFast( "$spriteorigin", &spriteOriginCache );
Vector origin, originVarValue;
if( !originVar || ( originVar->GetType() != MATERIAL_VAR_TYPE_VECTOR ) )
{
origin[0] = -m_width * 0.5f;
origin[1] = m_height * 0.5f;
}
else
{
originVar->GetVecValue( &originVarValue[0], 3 );
origin[0] = -m_width * originVarValue[0];
origin[1] = m_height * originVarValue[1];
}
up = origin[1];
down = origin[1] - m_height;
left = origin[0];
right = m_width + origin[0];
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::Shutdown( void )
{
if ( m_hAVIMaterial != AVIMATERIAL_INVALID )
{
avi->DestroyAVIMaterial( m_hAVIMaterial );
m_hAVIMaterial = AVIMATERIAL_INVALID;
}
if ( m_material )
{
m_material->DecrementReferenceCount();
m_material = NULL;
}
}
//-----------------------------------------------------------------------------
// Is the sprite an AVI?
//-----------------------------------------------------------------------------
bool CEngineSprite::IsAVI()
{
return ( m_hAVIMaterial != AVIMATERIAL_INVALID );
}
//-----------------------------------------------------------------------------
// Returns the texture coordinate range used to draw the sprite
//-----------------------------------------------------------------------------
void CEngineSprite::GetTexCoordRange( float *pMinU, float *pMinV, float *pMaxU, float *pMaxV )
{
*pMaxU = 1.0f;
*pMaxV = 1.0f;
if ( IsAVI() )
{
avi->GetTexCoordRange( m_hAVIMaterial, pMaxU, pMaxV );
}
float flOOWidth = ( m_width != 0 ) ? 1.0f / m_width : 1.0f;
float flOOHeight = ( m_height!= 0 ) ? 1.0f / m_height : 1.0f;
*pMinU = 0.5f * flOOWidth;
*pMinV = 0.5f * flOOHeight;
*pMaxU = (*pMaxU) - (*pMinU);
*pMaxV = (*pMaxV) - (*pMinV);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::SetColor( float r, float g, float b )
{
Assert( (r >= 0.0) && (g >= 0.0) && (b >= 0.0) );
Assert( (r <= 1.0) && (g <= 1.0) && (b <= 1.0) );
m_hudSpriteColor[0] = r;
m_hudSpriteColor[1] = g;
m_hudSpriteColor[2] = b;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::GetHUDSpriteColor( float* color )
{
VectorCopy( m_hudSpriteColor, color );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::SetAdditive( bool additive )
{
SetRenderMode( additive ? kRenderTransAdd : kRenderTransTexture );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static unsigned int frameCache = 0;
void CEngineSprite::SetFrame( float frame )
{
if ( !IsAVI() )
{
IMaterialVar* pFrameVar = m_material->FindVarFast( "$frame", &frameCache );
if (pFrameVar)
{
pFrameVar->SetFloatValue( frame );
}
return;
}
avi->SetFrame( m_hAVIMaterial, frame );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static unsigned int spriteRenderModeCache = 0;
void CEngineSprite::SetRenderMode( int renderMode )
{
IMaterialVar* pRenderModeVar = m_material->FindVarFast( "$spriteRenderMode", &spriteRenderModeCache );
if (pRenderModeVar)
{
if ( pRenderModeVar->GetIntValue() != renderMode )
{
pRenderModeVar->SetIntValue( renderMode );
m_material->RecomputeStateSnapshots();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
static unsigned int spriteOrientationCache = 0;
int CEngineSprite::GetOrientation( void )
{
IMaterialVar *orientationVar = m_material->FindVarFast( "$spriteorientation", &spriteOrientationCache );
if( orientationVar )
{
return orientationVar->GetIntValue();
}
else
{
return C_SpriteRenderer::SPR_VP_PARALLEL_UPRIGHT;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::UnloadMaterial( void )
{
if( m_material )
{
m_material->DecrementReferenceCount();
}
m_material = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngineSprite::DrawFrame( int frame, int x, int y, const wrect_t *prcSubRect )
{
DrawFrameOfSize( frame, x, y, GetWidth(), GetHeight(), prcSubRect);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : frame -
// x -
// y -
// *prcSubRect -
//-----------------------------------------------------------------------------
void CEngineSprite::DrawFrameOfSize( int frame, int x, int y, int iWidth, int iHeight, const wrect_t *prcSubRect )
{
// FIXME: If we ever call this with AVIs, need to have it call GetTexCoordRange and make that work
Assert( !IsAVI() );
float fLeft = 0;
float fRight = 1;
float fTop = 0;
float fBottom = 1;
if ( prcSubRect )
{
AdjustSubRect( this, frame, &fLeft, &fRight, &fTop, &fBottom, &iWidth, &iHeight, prcSubRect );
}
if ( giScissorTest && !Scissor( x, y, iWidth, iHeight, fLeft, fTop, fRight, fBottom ) )
return;
SetFrame( frame );
IMesh* pMesh = materials->GetDynamicMesh( true, NULL, NULL, GetMaterial() );
CMeshBuilder meshBuilder;
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
float color[3];
GetHUDSpriteColor( color );
meshBuilder.Color3fv( color );
meshBuilder.TexCoord2f( 0, fLeft, fTop );
meshBuilder.Position3f( x, y, 0.0f );
meshBuilder.AdvanceVertex();
meshBuilder.Color3fv( color );
meshBuilder.TexCoord2f( 0, fRight, fTop );
meshBuilder.Position3f( x + iWidth, y, 0.0f );
meshBuilder.AdvanceVertex();
meshBuilder.Color3fv( color );
meshBuilder.TexCoord2f( 0, fRight, fBottom );
meshBuilder.Position3f( x + iWidth, y + iHeight, 0.0f );
meshBuilder.AdvanceVertex();
meshBuilder.Color3fv( color );
meshBuilder.TexCoord2f( 0, fLeft, fBottom );
meshBuilder.Position3f( x, y + iHeight, 0.0f );
meshBuilder.AdvanceVertex();
meshBuilder.End();
pMesh->Draw();
}