aboutsummaryrefslogtreecommitdiff
path: root/mp/src/utils/vbsp/detailobjects.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/utils/vbsp/detailobjects.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/utils/vbsp/detailobjects.cpp')
-rw-r--r--mp/src/utils/vbsp/detailobjects.cpp1932
1 files changed, 966 insertions, 966 deletions
diff --git a/mp/src/utils/vbsp/detailobjects.cpp b/mp/src/utils/vbsp/detailobjects.cpp
index b110534c..e7475d94 100644
--- a/mp/src/utils/vbsp/detailobjects.cpp
+++ b/mp/src/utils/vbsp/detailobjects.cpp
@@ -1,966 +1,966 @@
-//========= 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;
- Q_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 );
- }
-}
+//========= 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;
+ Q_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 );
+ }
+}