You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
967 lines
29 KiB
967 lines
29 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose: Places "detail" objects which are client-only renderable things
|
||
|
//
|
||
|
// $Revision: $
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include "vbsp.h"
|
||
|
#include "bsplib.h"
|
||
|
#include "KeyValues.h"
|
||
|
#include "utlsymbol.h"
|
||
|
#include "utlvector.h"
|
||
|
#include <io.h>
|
||
|
#include "bspfile.h"
|
||
|
#include "utilmatlib.h"
|
||
|
#include "gamebspfile.h"
|
||
|
#include "mathlib/VMatrix.h"
|
||
|
#include "materialpatch.h"
|
||
|
#include "pacifier.h"
|
||
|
#include "vstdlib/random.h"
|
||
|
#include "builddisp.h"
|
||
|
#include "disp_vbsp.h"
|
||
|
#include "UtlBuffer.h"
|
||
|
#include "CollisionUtils.h"
|
||
|
#include <float.h>
|
||
|
#include "UtlLinkedList.h"
|
||
|
#include "byteswap.h"
|
||
|
#include "writebsp.h"
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Information about particular detail object types
|
||
|
//-----------------------------------------------------------------------------
|
||
|
enum
|
||
|
{
|
||
|
MODELFLAG_UPRIGHT = 0x1,
|
||
|
};
|
||
|
|
||
|
struct DetailModel_t
|
||
|
{
|
||
|
CUtlSymbol m_ModelName;
|
||
|
float m_Amount;
|
||
|
float m_MinCosAngle;
|
||
|
float m_MaxCosAngle;
|
||
|
int m_Flags;
|
||
|
int m_Orientation;
|
||
|
int m_Type;
|
||
|
Vector2D m_Pos[2];
|
||
|
Vector2D m_Tex[2];
|
||
|
float m_flRandomScaleStdDev;
|
||
|
unsigned char m_ShapeSize;
|
||
|
unsigned char m_ShapeAngle;
|
||
|
unsigned char m_SwayAmount;
|
||
|
};
|
||
|
|
||
|
struct DetailObjectGroup_t
|
||
|
{
|
||
|
float m_Alpha;
|
||
|
CUtlVector< DetailModel_t > m_Models;
|
||
|
};
|
||
|
|
||
|
struct DetailObject_t
|
||
|
{
|
||
|
CUtlSymbol m_Name;
|
||
|
float m_Density;
|
||
|
CUtlVector< DetailObjectGroup_t > m_Groups;
|
||
|
|
||
|
bool operator==(const DetailObject_t& src ) const
|
||
|
{
|
||
|
return src.m_Name == m_Name;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static CUtlVector<DetailObject_t> s_DetailObjectDict;
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Error checking.. make sure the model is valid + is a static prop
|
||
|
//-----------------------------------------------------------------------------
|
||
|
struct StaticPropLookup_t
|
||
|
{
|
||
|
CUtlSymbol m_ModelName;
|
||
|
bool m_IsValid;
|
||
|
};
|
||
|
|
||
|
static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 )
|
||
|
{
|
||
|
return src1.m_ModelName < src2.m_ModelName;
|
||
|
}
|
||
|
|
||
|
static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// These puppies are used to construct the game lumps
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static CUtlVector<DetailObjectDictLump_t> s_DetailObjectDictLump;
|
||
|
static CUtlVector<DetailObjectLump_t> s_DetailObjectLump;
|
||
|
static CUtlVector<DetailSpriteDictLump_t> s_DetailSpriteDictLump;
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Parses the key-value pairs in the detail.rad file
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues )
|
||
|
{
|
||
|
// Sort the group by alpha
|
||
|
float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f );
|
||
|
|
||
|
int i = s_DetailObjectDict[detailId].m_Groups.Count();
|
||
|
while ( --i >= 0 )
|
||
|
{
|
||
|
if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Insert after the first guy who's more transparent that we are!
|
||
|
i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i);
|
||
|
DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i];
|
||
|
|
||
|
group.m_Alpha = alpha;
|
||
|
|
||
|
// Add in all the model groups
|
||
|
KeyValues* pIter = pGroupKeyValues->GetFirstSubKey();
|
||
|
float totalAmount = 0.0f;
|
||
|
while( pIter )
|
||
|
{
|
||
|
if (pIter->GetFirstSubKey())
|
||
|
{
|
||
|
int i = group.m_Models.AddToTail();
|
||
|
|
||
|
DetailModel_t &model = group.m_Models[i];
|
||
|
|
||
|
model.m_ModelName = pIter->GetString( "model", 0 );
|
||
|
if (model.m_ModelName != UTL_INVAL_SYMBOL)
|
||
|
{
|
||
|
model.m_Type = DETAIL_PROP_TYPE_MODEL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const char *pSpriteData = pIter->GetString( "sprite", 0 );
|
||
|
if (pSpriteData)
|
||
|
{
|
||
|
const char *pProcModelType = pIter->GetString( "sprite_shape", 0 );
|
||
|
|
||
|
if ( pProcModelType )
|
||
|
{
|
||
|
if ( !Q_stricmp( pProcModelType, "cross" ) )
|
||
|
{
|
||
|
model.m_Type = DETAIL_PROP_TYPE_SHAPE_CROSS;
|
||
|
}
|
||
|
else if ( !Q_stricmp( pProcModelType, "tri" ) )
|
||
|
{
|
||
|
model.m_Type = DETAIL_PROP_TYPE_SHAPE_TRI;
|
||
|
}
|
||
|
else
|
||
|
model.m_Type = DETAIL_PROP_TYPE_SPRITE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// card sprite
|
||
|
model.m_Type = DETAIL_PROP_TYPE_SPRITE;
|
||
|
}
|
||
|
|
||
|
model.m_Tex[0].Init();
|
||
|
model.m_Tex[1].Init();
|
||
|
|
||
|
float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512;
|
||
|
int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize );
|
||
|
if ( (nValid != 5) || (flTextureSize == 0) )
|
||
|
{
|
||
|
Error( "Invalid arguments to \"sprite\" in detail.vbsp (model %s)!\n", model.m_ModelName.String() );
|
||
|
}
|
||
|
|
||
|
model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize;
|
||
|
model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize;
|
||
|
model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize;
|
||
|
model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize;
|
||
|
|
||
|
model.m_Pos[0].Init( -10, 20 );
|
||
|
model.m_Pos[1].Init( 10, 0 );
|
||
|
|
||
|
pSpriteData = pIter->GetString( "spritesize", 0 );
|
||
|
if (pSpriteData)
|
||
|
{
|
||
|
sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight );
|
||
|
|
||
|
float ox = flWidth * x;
|
||
|
float oy = flHeight * y;
|
||
|
|
||
|
model.m_Pos[0].x = -ox;
|
||
|
model.m_Pos[0].y = flHeight - oy;
|
||
|
model.m_Pos[1].x = flWidth - ox;
|
||
|
model.m_Pos[1].y = -oy;
|
||
|
}
|
||
|
|
||
|
model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f );
|
||
|
|
||
|
// sway is a percent of max sway, cl_detail_max_sway
|
||
|
float flSway = clamp( pIter->GetFloat( "sway", 0.0f ), 0.0, 1.0 );
|
||
|
model.m_SwayAmount = (unsigned char)( 255.0 * flSway );
|
||
|
|
||
|
// shape angle
|
||
|
// for the tri shape, this is the angle each side is fanned out
|
||
|
model.m_ShapeAngle = pIter->GetInt( "shape_angle", 0 );
|
||
|
|
||
|
// shape size
|
||
|
// for the tri shape, this is the distance from the origin to the center of a side
|
||
|
float flShapeSize = clamp( pIter->GetFloat( "shape_size", 0.0f ), 0.0, 1.0 );
|
||
|
model.m_ShapeSize = (unsigned char)( 255.0 * flShapeSize );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount;
|
||
|
totalAmount = model.m_Amount;
|
||
|
|
||
|
model.m_Flags = 0;
|
||
|
if (pIter->GetInt( "upright", 0 ))
|
||
|
{
|
||
|
model.m_Flags |= MODELFLAG_UPRIGHT;
|
||
|
}
|
||
|
|
||
|
// These are used to prevent emission on steep surfaces
|
||
|
float minAngle = pIter->GetFloat( "minAngle", 180 );
|
||
|
float maxAngle = pIter->GetFloat( "maxAngle", 180 );
|
||
|
model.m_MinCosAngle = cos(minAngle * M_PI / 180.f);
|
||
|
model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f);
|
||
|
model.m_Orientation = pIter->GetInt( "detailOrientation", 0 );
|
||
|
|
||
|
// Make sure minAngle < maxAngle
|
||
|
if ( model.m_MinCosAngle < model.m_MaxCosAngle)
|
||
|
{
|
||
|
model.m_MinCosAngle = model.m_MaxCosAngle;
|
||
|
}
|
||
|
}
|
||
|
pIter = pIter->GetNextKey();
|
||
|
}
|
||
|
|
||
|
// renormalize the amount if the total > 1
|
||
|
if (totalAmount > 1.0f)
|
||
|
{
|
||
|
for (i = 0; i < group.m_Models.Count(); ++i)
|
||
|
{
|
||
|
group.m_Models[i].m_Amount /= totalAmount;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Parses the key-value pairs in the detail.vbsp file
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void ParseDetailObjectFile( KeyValues& keyValues )
|
||
|
{
|
||
|
// Iterate over all detail object groups...
|
||
|
KeyValues* pIter;
|
||
|
for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() )
|
||
|
{
|
||
|
if (!pIter->GetFirstSubKey())
|
||
|
continue;
|
||
|
|
||
|
int i = s_DetailObjectDict.AddToTail( );
|
||
|
s_DetailObjectDict[i].m_Name = pIter->GetName() ;
|
||
|
s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f );
|
||
|
|
||
|
// Iterate over all detail object groups...
|
||
|
KeyValues* pIterGroups = pIter->GetFirstSubKey();
|
||
|
while( pIterGroups )
|
||
|
{
|
||
|
if (pIterGroups->GetFirstSubKey())
|
||
|
{
|
||
|
ParseDetailGroup( i, pIterGroups );
|
||
|
}
|
||
|
pIterGroups = pIterGroups->GetNextKey();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Finds the name of the detail.vbsp file to use
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static const char *FindDetailVBSPName( void )
|
||
|
{
|
||
|
for( int i = 0; i < num_entities; i++ )
|
||
|
{
|
||
|
char* pEntity = ValueForKey( &entities[i], "classname" );
|
||
|
if ( !strcmp( pEntity, "worldspawn" ) )
|
||
|
{
|
||
|
const char *pDetailVBSP = ValueForKey( &entities[i], "detailvbsp" );
|
||
|
if ( !pDetailVBSP || !pDetailVBSP[0] )
|
||
|
{
|
||
|
pDetailVBSP = "detail.vbsp";
|
||
|
}
|
||
|
return pDetailVBSP;
|
||
|
}
|
||
|
}
|
||
|
return "detail.vbsp";
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Loads up the detail object dictionary
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void LoadEmitDetailObjectDictionary( const char* pGameDir )
|
||
|
{
|
||
|
// Set the required global lights filename and try looking in qproject
|
||
|
const char *pDetailVBSP = FindDetailVBSPName();
|
||
|
KeyValues * values = new KeyValues( pDetailVBSP );
|
||
|
if ( values->LoadFromFile( g_pFileSystem, pDetailVBSP ) )
|
||
|
{
|
||
|
ParseDetailObjectFile( *values );
|
||
|
}
|
||
|
values->deleteThis();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Selects a detail group
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int SelectGroup( const DetailObject_t& detail, float alpha )
|
||
|
{
|
||
|
// Find the two groups whose alpha we're between...
|
||
|
int start, end;
|
||
|
for ( start = 0; start < detail.m_Groups.Count() - 1; ++start )
|
||
|
{
|
||
|
if (alpha < detail.m_Groups[start+1].m_Alpha)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
end = start + 1;
|
||
|
if (end >= detail.m_Groups.Count())
|
||
|
--end;
|
||
|
|
||
|
if (start == end)
|
||
|
return start;
|
||
|
|
||
|
// Figure out how far we are between start and end...
|
||
|
float dist = 0.0f;
|
||
|
float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha);
|
||
|
if (dAlpha != 0.0f)
|
||
|
{
|
||
|
dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha;
|
||
|
}
|
||
|
|
||
|
// Pick a number, any number...
|
||
|
float r = rand() / (float)VALVE_RAND_MAX;
|
||
|
|
||
|
// When dist == 0, we *always* want start.
|
||
|
// When dist == 1, we *always* want end
|
||
|
// That's why this logic looks a little reversed
|
||
|
return (r > dist) ? start : end;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Selects a detail object
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int SelectDetail( DetailObjectGroup_t const& group )
|
||
|
{
|
||
|
// Pick a number, any number...
|
||
|
float r = rand() / (float)VALVE_RAND_MAX;
|
||
|
|
||
|
// Look through the list of models + pick the one associated with this number
|
||
|
for ( int i = 0; i < group.m_Models.Count(); ++i )
|
||
|
{
|
||
|
if (r <= group.m_Models[i].m_Amount)
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Adds a detail dictionary element (expected to oftentimes be shared)
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int AddDetailDictLump( const char* pModelName )
|
||
|
{
|
||
|
DetailObjectDictLump_t dictLump;
|
||
|
strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH );
|
||
|
|
||
|
for (int i = s_DetailObjectDictLump.Count(); --i >= 0; )
|
||
|
{
|
||
|
if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) ))
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
return s_DetailObjectDictLump.AddToTail( dictLump );
|
||
|
}
|
||
|
|
||
|
static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex )
|
||
|
{
|
||
|
DetailSpriteDictLump_t dictLump;
|
||
|
dictLump.m_UL = pPos[0];
|
||
|
dictLump.m_LR = pPos[1];
|
||
|
dictLump.m_TexUL = pTex[0];
|
||
|
dictLump.m_TexLR = pTex[1];
|
||
|
|
||
|
for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; )
|
||
|
{
|
||
|
if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) ))
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
return s_DetailSpriteDictLump.AddToTail( dictLump );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Computes the leaf that the detail lies in
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int ComputeDetailLeaf( const Vector& pt )
|
||
|
{
|
||
|
int node = 0;
|
||
|
while( node >= 0 )
|
||
|
{
|
||
|
dnode_t* pNode = &dnodes[node];
|
||
|
dplane_t* pPlane = &dplanes[pNode->planenum];
|
||
|
|
||
|
if (DotProduct(pt, pPlane->normal) < pPlane->dist)
|
||
|
node = pNode->children[1];
|
||
|
else
|
||
|
node = pNode->children[0];
|
||
|
}
|
||
|
|
||
|
return - node - 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Make sure the details are compiled with static prop
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static bool IsModelValid( const char* pModelName )
|
||
|
{
|
||
|
StaticPropLookup_t lookup;
|
||
|
lookup.m_ModelName = pModelName;
|
||
|
|
||
|
int i = s_StaticPropLookup.Find( lookup );
|
||
|
if (i != s_StaticPropLookup.InvalidIndex() )
|
||
|
return s_StaticPropLookup[i].m_IsValid;
|
||
|
|
||
|
CUtlBuffer buf;
|
||
|
lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf );
|
||
|
if (!lookup.m_IsValid)
|
||
|
{
|
||
|
Warning("Error loading studio model \"%s\"!\n", pModelName );
|
||
|
}
|
||
|
|
||
|
s_StaticPropLookup.Insert( lookup );
|
||
|
return lookup.m_IsValid;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Add a detail to the lump.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int s_nDetailOverflow = 0;
|
||
|
static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation )
|
||
|
{
|
||
|
Assert( pt.IsValid() && angles.IsValid() );
|
||
|
|
||
|
// Make sure the model is valid...
|
||
|
if (!IsModelValid(pModelName))
|
||
|
return;
|
||
|
|
||
|
if (s_DetailObjectLump.Count() == 65535)
|
||
|
{
|
||
|
++s_nDetailOverflow;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Insert an element into the object dictionary if it aint there...
|
||
|
int i = s_DetailObjectLump.AddToTail( );
|
||
|
|
||
|
DetailObjectLump_t& objectLump = s_DetailObjectLump[i];
|
||
|
objectLump.m_DetailModel = AddDetailDictLump( pModelName );
|
||
|
VectorCopy( angles, objectLump.m_Angles );
|
||
|
VectorCopy( pt, objectLump.m_Origin );
|
||
|
objectLump.m_Leaf = ComputeDetailLeaf(pt);
|
||
|
objectLump.m_Lighting.r = 255;
|
||
|
objectLump.m_Lighting.g = 255;
|
||
|
objectLump.m_Lighting.b = 255;
|
||
|
objectLump.m_Lighting.exponent = 0;
|
||
|
objectLump.m_LightStyles = 0;
|
||
|
objectLump.m_LightStyleCount = 0;
|
||
|
objectLump.m_Orientation = nOrientation;
|
||
|
objectLump.m_Type = DETAIL_PROP_TYPE_MODEL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Add a detail sprite to the lump.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation,
|
||
|
const Vector2D *pPos, const Vector2D *pTex, float flScale, int iType,
|
||
|
int iShapeAngle = 0, int iShapeSize = 0, int iSwayAmount = 0 )
|
||
|
{
|
||
|
// Insert an element into the object dictionary if it aint there...
|
||
|
int i = s_DetailObjectLump.AddToTail( );
|
||
|
|
||
|
if (i >= 65535)
|
||
|
{
|
||
|
Error( "Error! Too many detail props emitted on this map! (64K max!)n" );
|
||
|
}
|
||
|
|
||
|
DetailObjectLump_t& objectLump = s_DetailObjectLump[i];
|
||
|
objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex );
|
||
|
VectorCopy( vecAngles, objectLump.m_Angles );
|
||
|
VectorCopy( vecOrigin, objectLump.m_Origin );
|
||
|
objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin);
|
||
|
objectLump.m_Lighting.r = 255;
|
||
|
objectLump.m_Lighting.g = 255;
|
||
|
objectLump.m_Lighting.b = 255;
|
||
|
objectLump.m_Lighting.exponent = 0;
|
||
|
objectLump.m_LightStyles = 0;
|
||
|
objectLump.m_LightStyleCount = 0;
|
||
|
objectLump.m_Orientation = nOrientation;
|
||
|
objectLump.m_Type = iType;
|
||
|
objectLump.m_flScale = flScale;
|
||
|
objectLump.m_ShapeAngle = iShapeAngle;
|
||
|
objectLump.m_ShapeSize = iShapeSize;
|
||
|
objectLump.m_SwayAmount = iSwayAmount;
|
||
|
}
|
||
|
|
||
|
static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale )
|
||
|
{
|
||
|
AddDetailSpriteToLump( vecOrigin,
|
||
|
vecAngles,
|
||
|
model.m_Orientation,
|
||
|
model.m_Pos,
|
||
|
model.m_Tex,
|
||
|
flScale,
|
||
|
model.m_Type,
|
||
|
model.m_ShapeAngle,
|
||
|
model.m_ShapeSize,
|
||
|
model.m_SwayAmount );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Got a detail! Place it on the surface...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function
|
||
|
// (only when not in the debugger?)
|
||
|
// Printing the values of normal at the bottom of the function fixes it as does
|
||
|
// disabling global optimizations.
|
||
|
static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal )
|
||
|
{
|
||
|
// But only place it on the surface if it meets the angle constraints...
|
||
|
float cosAngle = normal.z;
|
||
|
|
||
|
// Never emit if the angle's too steep
|
||
|
if (cosAngle < model.m_MaxCosAngle)
|
||
|
return;
|
||
|
|
||
|
// If it's between min + max, flip a coin...
|
||
|
if (cosAngle < model.m_MinCosAngle)
|
||
|
{
|
||
|
float probability = (cosAngle - model.m_MaxCosAngle) /
|
||
|
(model.m_MinCosAngle - model.m_MaxCosAngle);
|
||
|
|
||
|
float t = rand() / (float)VALVE_RAND_MAX;
|
||
|
if (t > probability)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Compute the orientation of the detail
|
||
|
QAngle angles;
|
||
|
if (model.m_Flags & MODELFLAG_UPRIGHT)
|
||
|
{
|
||
|
// If it's upright, we just select a random yaw
|
||
|
angles.Init( 0, 360.0f * rand() / (float)VALVE_RAND_MAX, 0.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// It's not upright, so it must conform to the ground. Choose
|
||
|
// a random orientation based on the surface normal
|
||
|
|
||
|
Vector zaxis;
|
||
|
VectorCopy( normal, zaxis );
|
||
|
VectorNormalize( zaxis );
|
||
|
|
||
|
// Choose any two arbitrary axes which are perpendicular to the normal
|
||
|
Vector xaxis( 1, 0, 0 );
|
||
|
if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3)
|
||
|
xaxis.Init( 0, 1, 0 );
|
||
|
Vector yaxis;
|
||
|
CrossProduct( zaxis, xaxis, yaxis );
|
||
|
VectorNormalize( yaxis );
|
||
|
CrossProduct( yaxis, zaxis, xaxis );
|
||
|
VectorNormalize( xaxis );
|
||
|
VMatrix matrix;
|
||
|
matrix.SetBasisVectors( xaxis, yaxis, zaxis );
|
||
|
matrix.SetTranslation( vec3_origin );
|
||
|
|
||
|
float rotAngle = 360.0f * rand() / (float)VALVE_RAND_MAX;
|
||
|
VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle );
|
||
|
matrix = matrix * rot;
|
||
|
|
||
|
MatrixToAngles( matrix, angles );
|
||
|
}
|
||
|
|
||
|
// FIXME: We may also want a purely random rotation too
|
||
|
|
||
|
// Insert an element into the object dictionary if it aint there...
|
||
|
switch ( model.m_Type )
|
||
|
{
|
||
|
case DETAIL_PROP_TYPE_MODEL:
|
||
|
AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation );
|
||
|
break;
|
||
|
|
||
|
// Sprites and procedural models made from sprites
|
||
|
case DETAIL_PROP_TYPE_SPRITE:
|
||
|
default:
|
||
|
{
|
||
|
float flScale = 1.0f;
|
||
|
if ( model.m_flRandomScaleStdDev != 0.0f )
|
||
|
{
|
||
|
flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) );
|
||
|
}
|
||
|
|
||
|
AddDetailSpriteToLump( pt, angles, model, flScale );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects on a face
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail )
|
||
|
{
|
||
|
if (pFace->numedges < 3)
|
||
|
return;
|
||
|
|
||
|
// We're going to pick a bunch of random points, and then probabilistically
|
||
|
// decide whether or not to plant a detail object there.
|
||
|
|
||
|
// Turn the face into a bunch of polygons, and compute the area of each
|
||
|
int* pSurfEdges = &dsurfedges[pFace->firstedge];
|
||
|
int vertexIdx = (pSurfEdges[0] < 0);
|
||
|
int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx];
|
||
|
dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex];
|
||
|
for (int i = 1; i < pFace->numedges - 1; ++i )
|
||
|
{
|
||
|
int vertexIdx = (pSurfEdges[i] < 0);
|
||
|
dedge_t* pEdge = &dedges[abs(pSurfEdges[i])];
|
||
|
|
||
|
// Compute two triangle edges
|
||
|
Vector e1, e2;
|
||
|
VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 );
|
||
|
VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 );
|
||
|
|
||
|
// Compute the triangle area
|
||
|
Vector areaVec;
|
||
|
CrossProduct( e1, e2, areaVec );
|
||
|
float normalLength = areaVec.Length();
|
||
|
float area = 0.5f * normalLength;
|
||
|
|
||
|
// Compute the number of samples to take
|
||
|
int numSamples = area * detail.m_Density * 0.000001;
|
||
|
|
||
|
// Now take a sample, and randomly place an object there
|
||
|
for (int i = 0; i < numSamples; ++i )
|
||
|
{
|
||
|
// Create a random sample...
|
||
|
float u = rand() / (float)VALVE_RAND_MAX;
|
||
|
float v = rand() / (float)VALVE_RAND_MAX;
|
||
|
if (v > 1.0f - u)
|
||
|
{
|
||
|
u = 1.0f - u;
|
||
|
v = 1.0f - v;
|
||
|
assert( u + v <= 1.0f );
|
||
|
}
|
||
|
|
||
|
// Compute alpha
|
||
|
float alpha = 1.0f;
|
||
|
|
||
|
// Select a group based on the alpha value
|
||
|
int group = SelectGroup( detail, alpha );
|
||
|
|
||
|
// Now that we've got a group, choose a detail
|
||
|
int model = SelectDetail( detail.m_Groups[group] );
|
||
|
if (model < 0)
|
||
|
continue;
|
||
|
|
||
|
// Got a detail! Place it on the surface...
|
||
|
Vector pt, normal;
|
||
|
VectorMA( pFirstVertex->point, u, e1, pt );
|
||
|
VectorMA( pt, v, e2, pt );
|
||
|
VectorDivide( areaVec, -normalLength, normal );
|
||
|
|
||
|
PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects on a face
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static float ComputeDisplacementFaceArea( dface_t* pFace )
|
||
|
{
|
||
|
float area = 0.0f;
|
||
|
|
||
|
// Compute the area of the base face
|
||
|
int* pSurfEdges = &dsurfedges[pFace->firstedge];
|
||
|
int vertexIdx = (pSurfEdges[0] < 0);
|
||
|
int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx];
|
||
|
dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex];
|
||
|
for (int i = 1; i <= 2; ++i )
|
||
|
{
|
||
|
int vertexIdx = (pSurfEdges[i] < 0);
|
||
|
dedge_t* pEdge = &dedges[abs(pSurfEdges[i])];
|
||
|
|
||
|
// Compute two triangle edges
|
||
|
Vector e1, e2;
|
||
|
VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 );
|
||
|
VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 );
|
||
|
|
||
|
// Compute the triangle area
|
||
|
Vector areaVec;
|
||
|
CrossProduct( e1, e2, areaVec );
|
||
|
float normalLength = areaVec.Length();
|
||
|
area += 0.5f * normalLength;
|
||
|
}
|
||
|
|
||
|
return area;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects on a face
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace,
|
||
|
DetailObject_t& detail, CCoreDispInfo& coreDispInfo )
|
||
|
{
|
||
|
assert(pFace->numedges == 4);
|
||
|
|
||
|
// We're going to pick a bunch of random points, and then probabilistically
|
||
|
// decide whether or not to plant a detail object there.
|
||
|
|
||
|
// Compute the area of the base face
|
||
|
float area = ComputeDisplacementFaceArea( pFace );
|
||
|
|
||
|
// Compute the number of samples to take
|
||
|
int numSamples = area * detail.m_Density * 0.000001;
|
||
|
|
||
|
// Now take a sample, and randomly place an object there
|
||
|
for (int i = 0; i < numSamples; ++i )
|
||
|
{
|
||
|
// Create a random sample...
|
||
|
float u = rand() / (float)VALVE_RAND_MAX;
|
||
|
float v = rand() / (float)VALVE_RAND_MAX;
|
||
|
|
||
|
// Compute alpha
|
||
|
float alpha;
|
||
|
Vector pt, normal;
|
||
|
coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha );
|
||
|
alpha /= 255.0f;
|
||
|
|
||
|
// Select a group based on the alpha value
|
||
|
int group = SelectGroup( detail, alpha );
|
||
|
|
||
|
// Now that we've got a group, choose a detail
|
||
|
int model = SelectDetail( detail.m_Groups[group] );
|
||
|
if (model < 0)
|
||
|
continue;
|
||
|
|
||
|
// Got a detail! Place it on the surface...
|
||
|
PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Sort detail objects by leaf
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int SortFunc( const void *arg1, const void *arg2 )
|
||
|
{
|
||
|
int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf;
|
||
|
if ( nDelta < 0 )
|
||
|
return -1;
|
||
|
if ( nDelta > 0 )
|
||
|
return 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects in the lump
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void SetLumpData( )
|
||
|
{
|
||
|
// Sort detail props by leaf
|
||
|
qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc );
|
||
|
|
||
|
GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_DETAIL_PROPS);
|
||
|
if (handle != g_GameLumps.InvalidGameLump())
|
||
|
{
|
||
|
g_GameLumps.DestroyGameLump(handle);
|
||
|
}
|
||
|
int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t);
|
||
|
int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t);
|
||
|
int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t);
|
||
|
int nSize = nDictSize + nSpriteDictSize + nObjSize + (3 * sizeof(int));
|
||
|
|
||
|
handle = g_GameLumps.CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION );
|
||
|
|
||
|
// Serialize the data
|
||
|
CUtlBuffer buf( g_GameLumps.GetGameLump(handle), nSize );
|
||
|
buf.PutInt( s_DetailObjectDictLump.Count() );
|
||
|
if (nDictSize)
|
||
|
{
|
||
|
buf.Put( s_DetailObjectDictLump.Base(), nDictSize );
|
||
|
}
|
||
|
buf.PutInt( s_DetailSpriteDictLump.Count() );
|
||
|
if (nSpriteDictSize)
|
||
|
{
|
||
|
buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize );
|
||
|
}
|
||
|
buf.PutInt( s_DetailObjectLump.Count() );
|
||
|
if (nObjSize)
|
||
|
{
|
||
|
buf.Put( s_DetailObjectLump.Base(), nObjSize );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects in the level
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void EmitDetailModels()
|
||
|
{
|
||
|
StartPacifier("Placing detail props : ");
|
||
|
|
||
|
// Place stuff on each face
|
||
|
dface_t* pFace = dfaces;
|
||
|
for (int j = 0; j < numfaces; ++j)
|
||
|
{
|
||
|
UpdatePacifier( (float)j / (float)numfaces );
|
||
|
|
||
|
// Get at the material associated with this face
|
||
|
texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo];
|
||
|
dtexdata_t* pTexData = GetTexData( pTexInfo->texdata );
|
||
|
|
||
|
// Try to get at the material
|
||
|
bool found;
|
||
|
MaterialSystemMaterial_t handle =
|
||
|
FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ),
|
||
|
&found, false );
|
||
|
if (!found)
|
||
|
continue;
|
||
|
|
||
|
// See if its got any detail objects on it
|
||
|
const char* pDetailType = GetMaterialVar( handle, "%detailtype" );
|
||
|
if (!pDetailType)
|
||
|
continue;
|
||
|
|
||
|
// Get the detail type...
|
||
|
DetailObject_t search;
|
||
|
search.m_Name = pDetailType;
|
||
|
int objectType = s_DetailObjectDict.Find(search);
|
||
|
if (objectType < 0)
|
||
|
{
|
||
|
Warning("Material %s uses unknown detail object type %s!\n",
|
||
|
TexDataStringTable_GetString( pTexData->nameStringTableID ),
|
||
|
pDetailType);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Emit objects on a particular face
|
||
|
DetailObject_t& detail = s_DetailObjectDict[objectType];
|
||
|
|
||
|
// Initialize the Random Number generators for detail prop placement based on the hammer Face num.
|
||
|
int detailpropseed = dfaceids[j].hammerfaceid;
|
||
|
#ifdef WARNSEEDNUMBER
|
||
|
Warning( "[%d]\n",detailpropseed );
|
||
|
#endif
|
||
|
srand( detailpropseed );
|
||
|
RandomSeed( detailpropseed );
|
||
|
|
||
|
if (pFace[j].dispinfo < 0)
|
||
|
{
|
||
|
EmitDetailObjectsOnFace( &pFace[j], detail );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates.
|
||
|
mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo];
|
||
|
CCoreDispInfo coreDispInfo;
|
||
|
DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL );
|
||
|
|
||
|
EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Emit specifically specified detail props
|
||
|
Vector origin;
|
||
|
QAngle angles;
|
||
|
Vector2D pos[2];
|
||
|
Vector2D tex[2];
|
||
|
for (int i = 0; i < num_entities; ++i)
|
||
|
{
|
||
|
char* pEntity = ValueForKey(&entities[i], "classname");
|
||
|
if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail"))
|
||
|
{
|
||
|
GetVectorForKey( &entities[i], "origin", origin );
|
||
|
GetAnglesForKey( &entities[i], "angles", angles );
|
||
|
char* pModelName = ValueForKey( &entities[i], "model" );
|
||
|
int nOrientation = IntForKey( &entities[i], "detailOrientation" );
|
||
|
|
||
|
AddDetailToLump( pModelName, origin, angles, nOrientation );
|
||
|
|
||
|
// strip this ent from the .bsp file
|
||
|
entities[i].epairs = 0;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(pEntity, "prop_detail_sprite"))
|
||
|
{
|
||
|
GetVectorForKey( &entities[i], "origin", origin );
|
||
|
GetAnglesForKey( &entities[i], "angles", angles );
|
||
|
int nOrientation = IntForKey( &entities[i], "detailOrientation" );
|
||
|
GetVector2DForKey( &entities[i], "position_ul", pos[0] );
|
||
|
GetVector2DForKey( &entities[i], "position_lr", pos[1] );
|
||
|
GetVector2DForKey( &entities[i], "tex_ul", tex[0] );
|
||
|
GetVector2DForKey( &entities[i], "tex_size", tex[1] );
|
||
|
float flTextureSize = FloatForKey( &entities[i], "tex_total_size" );
|
||
|
|
||
|
tex[1].x += tex[0].x - 0.5f;
|
||
|
tex[1].y += tex[0].y - 0.5f;
|
||
|
tex[0].x += 0.5f;
|
||
|
tex[0].y += 0.5f;
|
||
|
tex[0] /= flTextureSize;
|
||
|
tex[1] /= flTextureSize;
|
||
|
|
||
|
AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f, DETAIL_PROP_TYPE_SPRITE );
|
||
|
|
||
|
// strip this ent from the .bsp file
|
||
|
entities[i].epairs = 0;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndPacifier( true );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Places Detail Objects in the level
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void EmitDetailObjects()
|
||
|
{
|
||
|
EmitDetailModels();
|
||
|
|
||
|
// Done! Now lets add the lumps (destroy previous ones)
|
||
|
SetLumpData( );
|
||
|
|
||
|
if ( s_nDetailOverflow != 0 )
|
||
|
{
|
||
|
Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow );
|
||
|
}
|
||
|
}
|