summaryrefslogtreecommitdiff
path: root/hammer/detailobjects.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/detailobjects.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'hammer/detailobjects.cpp')
-rw-r--r--hammer/detailobjects.cpp781
1 files changed, 781 insertions, 0 deletions
diff --git a/hammer/detailobjects.cpp b/hammer/detailobjects.cpp
new file mode 100644
index 0000000..d1cb5b7
--- /dev/null
+++ b/hammer/detailobjects.cpp
@@ -0,0 +1,781 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Places "detail" objects which are client-only renderable things
+//
+// $Revision: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "stdafx.h"
+
+#include "collisionutils.h"
+#include "const.h"
+#include "interface.h"
+
+#include "KeyValues.h"
+#include "utlsymbol.h"
+#include "utlvector.h"
+#include "utilmatlib.h"
+#include "mathlib/VMatrix.h"
+#include "vstdlib/random.h"
+#include "builddisp.h"
+#include "tier1/utlbuffer.h"
+#include "IEditorTexture.h"
+#include "materialsystem/imaterialvar.h"
+#include "materialsystem/imaterial.h"
+#include "mapface.h"
+#include "camera.h"
+#include "options.h"
+
+#include "hammer.h"
+
+// Actually, this is the max per map, but for now this is better than no limit at all.
+#define MAX_DETAIL_SPRITES_PER_FACE 65535
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//IMPLEMENT_MAPCLASS(DetailObjects)
+
+bool DetailObjects::s_bBuildDetailObjects = true;
+
+// Defaults to match the parsing defaults in ParseDetailGroup -- code path defaults may/may not execute
+DetailObjects::~DetailObjects()
+{
+ m_DetailModels.PurgeAndDeleteElements();
+ m_DetailSprites.PurgeAndDeleteElements();
+}
+
+DetailObjects::DetailModel_t::DetailModel_t() : m_ModelName()
+{
+ m_Amount = 0.0;
+ m_MinCosAngle = -1.0;
+ m_MaxCosAngle = -1.0;
+ m_Flags = 0;
+ m_Orientation = 0;
+ m_Type = DETAIL_PROP_TYPE_SPRITE;
+ m_Pos[0] = Vector2D(-10, 20);
+ m_Pos[1] = Vector2D(10, 0);
+ m_Tex[0] = Vector2D(0.5/512, 0.5/512);
+ m_Tex[1] = Vector2D(63.5/512, 63.5/512);
+ m_flRandomScaleStdDev = 0.0;
+ m_ShapeSize = 0;
+ m_ShapeAngle = 0;
+ m_SwayAmount = 0;
+}
+
+CUtlVector<DetailObjects::DetailObject_t> DetailObjects::s_DetailObjectDict; // static members?
+
+//-----------------------------------------------------------------------------
+// Parses the key-value pairs in the detail.rad file
+//-----------------------------------------------------------------------------
+void DetailObjects::ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues )
+{
+ // Sort the group by alpha
+ float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f );
+
+ int iGroup = s_DetailObjectDict[detailId].m_Groups.Count();
+ while ( --iGroup >= 0 )
+ {
+ if (alpha > s_DetailObjectDict[detailId].m_Groups[iGroup].m_Alpha)
+ break;
+ }
+
+ // Insert after the first guy who's more transparent that we are!
+ iGroup = s_DetailObjectDict[detailId].m_Groups.InsertAfter( iGroup );
+ DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[iGroup];
+
+ 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 (int 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
+//-----------------------------------------------------------------------------
+void DetailObjects::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
+//-----------------------------------------------------------------------------
+const char *DetailObjects::FindDetailVBSPName( void )
+{
+#if 0
+ 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;
+ }
+ }
+#endif
+ return "detail.vbsp";
+}
+
+#include "tier0\memdbgoff.h"
+
+//-----------------------------------------------------------------------------
+// Loads up the detail object dictionary
+//-----------------------------------------------------------------------------
+void DetailObjects::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
+//-----------------------------------------------------------------------------
+int DetailObjects::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 flR = 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 (flR > dist) ? start : end;
+}
+
+
+//-----------------------------------------------------------------------------
+// Selects a detail object
+//-----------------------------------------------------------------------------
+int DetailObjects::SelectDetail( DetailObjectGroup_t const& group )
+{
+ // Pick a number, any number...
+ float flR = 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 ( flR <= group.m_Models[i].m_Amount)
+ return i;
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Add a detail to the lump.
+//-----------------------------------------------------------------------------
+
+void DetailObjects::AddDetailModelToFace( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation )
+{
+ StudioModel *pStudioModel = new StudioModel();
+ m_DetailModels.AddToTail( pStudioModel );
+ pStudioModel->LoadModel( pModelName );
+ pStudioModel->SetOrigin( pt.x, pt.y, pt.z );
+ QAngle modelangle = angles;
+ pStudioModel->SetAngles( modelangle );
+}
+
+//-----------------------------------------------------------------------------
+// Add a detail sprite to the lump.
+//-----------------------------------------------------------------------------
+
+void DetailObjects::AddDetailSpriteToFace( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale )
+{
+ CSpriteModel *pSpriteModel = new CSpriteModel;
+ m_DetailSprites.AddToTail(pSpriteModel);
+
+ const char szSpriteName[_MAX_PATH] = "detail/detailsprites";
+
+ pSpriteModel->LoadSprite( szSpriteName );
+
+ pSpriteModel->SetRenderMode( kRenderNormal );
+ pSpriteModel->SetMaterialPrimitiveType( MATERIAL_POLYGON );
+
+ pSpriteModel->SetOrigin( vecOrigin );
+ pSpriteModel->SetAngles( vecAngles );
+ pSpriteModel->SetScale( flScale );
+ pSpriteModel->SetInvert( true );
+
+ pSpriteModel->SetExtent( model.m_Pos[0], model.m_Pos[1] );
+ pSpriteModel->SetTextureExtent( model.m_Tex[0], model.m_Tex[1] );
+}
+
+//-----------------------------------------------------------------------------
+// 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.
+void DetailObjects::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:
+ AddDetailModelToFace( 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 ) );
+ }
+
+ AddDetailSpriteToFace( pt, angles, model, flScale );
+ }
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Places Detail Objects on a face
+//-----------------------------------------------------------------------------
+void DetailObjects::EmitDetailObjectsOnFace( CMapFace *pMapFace, DetailObject_t& detail )
+{
+ // See how many points define this particular face
+ int nPoints = pMapFace->GetPointCount();
+
+ // Faces with detail props need at least 3 point to form a plane
+ if (nPoints < 3)
+ return;
+
+ // Get the first point of the face
+ Vector p0;
+ pMapFace->GetPoint(p0,0);
+
+ // Get next points on the face in pairs -- ie get points necessary to tesselate the face into triangles
+ for (int i = 1; i < nPoints-1; i++ )
+ {
+ // Get the next two points of the face
+ Vector p1, p2;
+ pMapFace->GetPoint(p1,i);
+ pMapFace->GetPoint(p2,i+1);
+
+ // For the edges of the current triangle tesselating a portion of the face
+ Vector e1, e2;
+ VectorSubtract( p1, p0, e1 );
+ VectorSubtract( p2, p0, e2 );
+
+ // Calculate the area of the tesselated triange using half the crossproduct of the edges
+ Vector areaVec;
+ CrossProduct( e1, e2, areaVec );
+ float normalLength = areaVec.Length();
+ float area = 0.5 * normalLength;
+
+ // Calculate the detail prop density based on the expected density and the tesselated triangle area
+ int numSamples = clamp( area * detail.m_Density * 0.000001, 0, MAX_DETAIL_SPRITES_PER_FACE );
+
+ // For each possible sample, attempt to randomly place a detail object there
+ for (int j = 0; j < numSamples; ++j )
+ {
+ // Create a random sample location...
+ float u = rand() / (float)VALVE_RAND_MAX;
+ float v = rand() / (float)VALVE_RAND_MAX;
+
+ // Make sure the u,v coordinate stay within the triangle boundaries (ie they NOT in the far half of the parallelogram)
+ if (v > 1.0f - u)
+ {
+ // Triangle is out of bounds, flip the coordinates so they are in the near half of the parallelogram
+ u = 1.0f - u;
+ v = 1.0f - v;
+ assert( u + v <= 1.0f );
+ }
+
+ // Compute alpha - assumed to be 1.0 across entire face for non-displacement map faces, since there is no alpha channel
+ 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( p0, 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
+//-----------------------------------------------------------------------------
+float DetailObjects::ComputeDisplacementFaceArea( CMapFace *pMapFace )
+{
+ float area = 0.0f;
+
+ // Compute the area of the base face
+ // Displacement base faces must be quads.
+ Vector edge[4];
+ for( int i=0; i<4; i++ )
+ {
+ Vector p0, p1;
+ pMapFace->GetPoint( p0, i );
+ pMapFace->GetPoint( p1, (i+1)%4 );
+ VectorSubtract( p1, p0, edge[i] );
+ }
+ Vector area_01, area_23;
+ CrossProduct( edge[0], edge[1], area_01 );
+ CrossProduct( edge[2], edge[3], area_23 );
+ area = ( area_01.Length() + area_23.Length() ) * 0.5f;
+
+ return area;
+}
+
+
+//-----------------------------------------------------------------------------
+// Places Detail Objects on a face
+//-----------------------------------------------------------------------------
+void DetailObjects::EmitDetailObjectsOnDisplacementFace( CMapFace *pMapFace,
+ DetailObject_t& detail )
+{
+ assert(pMapFace->GetPointCount() == 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( pMapFace );
+
+ // Compute the number of samples to take
+ int numSamples = area * detail.m_Density * 0.000001;
+
+ EditDispHandle_t editdisphandle = pMapFace->GetDisp();
+ CMapDisp *pMapDisp = EditDispMgr()->GetDisp(editdisphandle);
+ CCoreDispInfo *pCoreDispInfo = pMapDisp->GetCoreDispInfo();
+
+ // 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;
+ pCoreDispInfo->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 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Builds Detail Objects for a particular face
+//-----------------------------------------------------------------------------
+
+bool DetailObjects::ShouldRenderLast(void)
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Builds Detail Objects for a particular face
+//-----------------------------------------------------------------------------
+void DetailObjects::BuildAnyDetailObjects(CMapFace *pMapFace)
+{
+ // Ignore this call while loading the VMF or else we'll generate a lot of redundant ones.
+ if ( !s_bBuildDetailObjects )
+ return;
+
+ if ( pMapFace->IsCordonFace() )
+ return;
+
+ // Try to get at the material
+ bool found;
+
+ IEditorTexture *pEditorTexture = pMapFace->GetTexture();
+ if ( !pEditorTexture )
+ return;
+
+ IMaterial *pMaterial = pEditorTexture->GetMaterial();
+ if ( !pMaterial )
+ return;
+
+ IMaterialVar *pMaterialVar = pMaterial->FindVar("%detailtype", &found, false );
+ if ( !found || !pMaterialVar )
+ return;
+
+ const char* pDetailType = pMaterialVar->GetStringValue();
+ if ( !pDetailType )
+ return;
+
+ // Get the detail type...
+ DetailObject_t search;
+ search.m_Name = pDetailType;
+
+ DetailObjects *pDetails = pMapFace->m_pDetailObjects;
+ if ( pMapFace->m_pDetailObjects )
+ {
+ pDetails->m_DetailModels.PurgeAndDeleteElements();
+ pDetails->m_DetailSprites.PurgeAndDeleteElements();
+ }
+ else
+ {
+ pMapFace->m_pDetailObjects = pDetails = new DetailObjects;
+ }
+
+ if ( pDetails )
+ {
+ // Set the center the "detailobjects" to be the average of the face points
+ int nPoints = pMapFace->GetPointCount();
+ Vector faceCenter, faceCorner;
+ faceCenter.Init();
+ for ( int point=0; point < nPoints; point++ )
+ {
+ pMapFace->GetPoint(faceCorner,point);
+ faceCenter += faceCorner;
+ }
+ faceCenter /= nPoints;
+
+ pDetails->SetOrigin( faceCenter );
+
+ int objectType = s_DetailObjectDict.Find(search);
+ if (objectType < 0)
+ {
+ char szTextureName[MAX_PATH];
+ pMapFace->GetTextureName(szTextureName);
+ Warning("Material %s uses unknown detail object type %s!\n", szTextureName, pDetailType);
+ return;
+ }
+
+ // Emit objects on a particular face
+ DetailObject_t& detail = s_DetailObjectDict[objectType];
+
+ // Initialize the Random Number generators for detail prop placement based on the origFace num.
+ int detailpropseed = pMapFace->GetFaceID();
+#ifdef WARNSEEDNUMBER
+ Warning("[%d]\n",detailpropseed);
+#endif
+ srand( detailpropseed );
+ RandomSeed( detailpropseed );
+
+ if ( pMapFace->HasDisp() )
+ {
+ pDetails->EmitDetailObjectsOnDisplacementFace( pMapFace, detail );
+ }
+ else
+ {
+ pDetails->EmitDetailObjectsOnFace( pMapFace, detail );
+ }
+ }
+ else
+ {
+ Warning("Could not allocate DetailObject for CMapFace!\n");
+ }
+}
+
+void DetailObjects::EnableBuildDetailObjects( bool bEnable )
+{
+ s_bBuildDetailObjects = bEnable;
+}
+
+void DetailObjects::Render3D(CRender3D *pRender)
+{
+ Vector Mins, Maxs;
+ float fDetailDistance = Options.view3d.nDetailDistance;
+ Vector viewPoint; pRender->GetCamera()->GetViewPoint( viewPoint );
+
+ int models = m_DetailModels.Count();
+ if ( models )
+ {
+ pRender->PushRenderMode( RENDER_MODE_DEFAULT );
+ for ( int i = 0; i < models; i++ )
+ {
+ StudioModel *pModel = m_DetailModels[i];
+ pModel->GetOrigin(Mins);
+ pModel->GetOrigin(Maxs);
+ for( int j=0; j<3; j++ )
+ {
+ Mins[j] -= fDetailDistance;
+ Maxs[j] += fDetailDistance;
+ }
+ if ( IsPointInBox( viewPoint, Mins, Maxs ) )
+ pModel->DrawModel3D( pRender, 1, false );
+ }
+ pRender->PopRenderMode();
+
+ }
+
+ int sprites = m_DetailSprites.Count();
+ if ( sprites )
+ {
+ unsigned char color[3] = { 255, 255, 255 };
+ pRender->PushRenderMode( RENDER_MODE_DEFAULT );
+ for ( int i = 0; i < sprites; i++ )
+ {
+ CSpriteModel *pSprite = m_DetailSprites[i];
+ pSprite->GetOrigin(Mins);
+ pSprite->GetOrigin(Maxs);
+ for( int j=0; j<3; j++ )
+ {
+ Mins[j] -= fDetailDistance;
+ Maxs[j] += fDetailDistance;
+ }
+ if ( IsPointInBox( viewPoint, Mins, Maxs ) )
+ pSprite->DrawSprite3D( pRender, color );
+ }
+ pRender->PopRenderMode();
+ }
+}
+
+// EOF \ No newline at end of file