diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_blob.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_blob.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_blob.cpp | 1379 |
1 files changed, 1379 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_blob.cpp b/mp/src/game/server/hl2/npc_blob.cpp new file mode 100644 index 00000000..759ae1c4 --- /dev/null +++ b/mp/src/game/server/hl2/npc_blob.cpp @@ -0,0 +1,1379 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// npc_blob - experimental, cpu-intensive monster made of lots of smaller elements
+//
+//=============================================================================//
+#include "cbase.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "ai_basenpc.h"
+#include "engine/IEngineSound.h"
+#include "vstdlib/jobthread.h"
+#include "saverestore_utlvector.h"
+#include "eventqueue.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern float MOVE_HEIGHT_EPSILON;
+
+#define BLOB_MAX_AVOID_ORIGINS 3
+
+ConVar blob_mindist( "blob_mindist", "120.0" );
+ConVar blob_element_speed( "blob_element_speed", "187" );
+ConVar npc_blob_idle_speed_factor( "npc_blob_idle_speed_factor", "0.5" );
+
+ConVar blob_numelements( "blob_numelements", "20" );
+ConVar blob_batchpercent( "blob_batchpercent", "100" );
+
+ConVar blob_radius( "blob_radius", "160" );
+
+//ConVar blob_min_element_speed( "blob_min_element_speed", "50" );
+//ConVar blob_max_element_speed( "blob_max_element_speed", "250" );
+
+ConVar npc_blob_use_threading( "npc_blob_use_threading", "1" );
+
+ConVar npc_blob_sin_amplitude( "npc_blob_sin_amplitude", "60.0f" );
+
+ConVar npc_blob_show_centroid( "npc_blob_show_centroid", "0" );
+
+ConVar npc_blob_straggler_dist( "npc_blob_straggler_dist", "240" );
+
+ConVar npc_blob_use_orientation( "npc_blob_use_orientation", "1" );
+ConVar npc_blob_use_model( "npc_blob_use_model", "2" );
+
+ConVar npc_blob_think_interval( "npc_blob_think_interval", "0.025" );
+
+
+#define NPC_BLOB_MODEL "models/headcrab.mdl"
+
+//=========================================================
+// Blob movement rules
+//=========================================================
+enum
+{
+ BLOB_MOVE_SWARM = 0, // Just swarm with the rest of the group
+ BLOB_MOVE_TO_TARGET_LOCATION, // Move to a designated location
+ BLOB_MOVE_TO_TARGET_ENTITY, // Chase the designated entity
+ BLOB_MOVE_DONT_MOVE, // Sit still!!!!
+};
+
+//=========================================================
+//=========================================================
+class CBlobElement : public CBaseAnimating
+{
+public:
+ void Precache();
+ void Spawn();
+ int DrawDebugTextOverlays(void);
+
+ void SetElementVelocity( Vector vecVelocity, bool bPlanarOnly );
+ void AddElementVelocity( Vector vecVelocityAdd, bool bPlanarOnly );
+ void ModifyVelocityForSurface( float flInterval, float flSpeed );
+
+ void SetSinePhase( float flPhase ) { m_flSinePhase = flPhase; }
+ float GetSinePhase() { return m_flSinePhase; }
+
+ float GetSineAmplitude() { return m_flSineAmplitude; }
+ float GetSineFrequency() { return m_flSineFrequency; }
+
+ void SetActiveMovementRule( int moveRule ) { m_iMovementRule = moveRule; }
+ int GetActiveMovementRule() { return m_iMovementRule; }
+
+ void MoveTowardsTargetEntity( float speed );
+ void SetTargetEntity( CBaseEntity *pEntity ) { m_hTargetEntity = pEntity; }
+ CBaseEntity *GetTargetEntity() { return m_hTargetEntity.Get(); }
+
+ void MoveTowardsTargetLocation( float speed );
+ void SetTargetLocation( const Vector &vecLocation ) { m_vecTargetLocation = vecLocation; }
+
+ void ReconfigureRandomParams();
+ void EnforceSpeedLimits( float flMinSpeed, float flMaxSpeed );
+
+ DECLARE_DATADESC();
+
+public:
+ Vector m_vecPrevOrigin; // Only exists for debugging (isolating stuck elements)
+ int m_iStuckCount;
+ bool m_bOnWall;
+ float m_flDistFromCentroidSqr;
+ int m_iElementNumber;
+ Vector m_vecTargetLocation;
+ float m_flRandomEightyPercent;
+
+private:
+ EHANDLE m_hTargetEntity;
+ float m_flSinePhase;
+ float m_flSineAmplitude;
+ float m_flSineFrequency;
+ int m_iMovementRule;
+};
+LINK_ENTITY_TO_CLASS( blob_element, CBlobElement );
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CBlobElement )
+
+DEFINE_FIELD( m_vecPrevOrigin, FIELD_POSITION_VECTOR ),
+DEFINE_FIELD( m_iStuckCount, FIELD_INTEGER ),
+DEFINE_FIELD( m_bOnWall, FIELD_BOOLEAN ),
+DEFINE_FIELD( m_flDistFromCentroidSqr, FIELD_FLOAT ),
+DEFINE_FIELD( m_iElementNumber, FIELD_INTEGER ),
+DEFINE_FIELD( m_vecTargetLocation, FIELD_POSITION_VECTOR ),
+DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ),
+DEFINE_FIELD( m_flSinePhase, FIELD_FLOAT ),
+DEFINE_FIELD( m_flSineAmplitude, FIELD_FLOAT ),
+DEFINE_FIELD( m_flSineFrequency, FIELD_FLOAT ),
+DEFINE_FIELD( m_iMovementRule, FIELD_INTEGER ),
+
+END_DATADESC()
+
+
+const char *pszBlobModels[] =
+{
+ "models/gibs/agibs.mdl",
+ "models/props_junk/watermelon01.mdl",
+ "models/w_squeak.mdl",
+ "models/baby_headcrab.mdl"
+};
+
+const char *GetBlobModelName()
+{
+ int index = npc_blob_use_model.GetInt();
+
+ return pszBlobModels[ index ];
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBlobElement::Precache()
+{
+ PrecacheModel( GetBlobModelName() );
+
+ m_flRandomEightyPercent = random->RandomFloat( 0.8f, 1.0f );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CBlobElement::Spawn()
+{
+ Precache();
+
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_FLY );
+ AddSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_NOT_SOLID );
+
+ SetModel( GetBlobModelName() );
+ UTIL_SetSize( this, vec3_origin, vec3_origin );
+
+ QAngle angles(0,0,0);
+ angles.y = random->RandomFloat( 0, 180 );
+ SetAbsAngles( angles );
+
+ AddEffects( EF_NOSHADOW );
+
+ ReconfigureRandomParams();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CBlobElement::DrawDebugTextOverlays(void)
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ char tempstr[512];
+ Q_snprintf(tempstr,sizeof(tempstr), "Element #:%d", m_iElementNumber );
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+ }
+ return text_offset;
+}
+
+
+//---------------------------------------------------------
+// This is the official way to set velocity for an element
+// Do not call SetAbsVelocity() directly, since we also
+// need to record the last velocity we intended to give the
+// element, so that we can detect changes after game physics
+// runs.
+//---------------------------------------------------------
+void CBlobElement::SetElementVelocity( Vector vecVelocity, bool bPlanarOnly )
+{
+ SetAbsVelocity( vecVelocity );
+}
+
+//---------------------------------------------------------
+// This is the official way to add velocity to an element.
+// See SetElementVelocity() for explanation.
+//---------------------------------------------------------
+void CBlobElement::AddElementVelocity( Vector vecVelocityAdd, bool bPlanarOnly )
+{
+ Vector vecSum = GetAbsVelocity() + vecVelocityAdd;
+ SetAbsVelocity( vecSum );
+}
+
+//---------------------------------------------------------
+// This function seeks to keep the blob element moving along
+// multiple different types of surfaces (climbing walls, etc)
+//---------------------------------------------------------
+#define BLOB_TRACE_HEIGHT 8.0f
+void CBlobElement::ModifyVelocityForSurface( float flInterval, float flSpeed )
+{
+ trace_t tr;
+ Vector vecStart = GetAbsOrigin();
+ Vector up = Vector( 0, 0, BLOB_TRACE_HEIGHT );
+
+ Vector vecWishedGoal = vecStart + (GetAbsVelocity() * flInterval);
+
+ UTIL_TraceLine( vecStart + up, vecWishedGoal + up, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, false, 0.1f );
+
+ m_bOnWall = false;
+
+ if( tr.fraction == 1.0f )
+ {
+ UTIL_TraceLine( vecWishedGoal + up, vecWishedGoal - (up * 2.0f), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 255, 0, false, 0.1f );
+ tr.endpos.z += MOVE_HEIGHT_EPSILON;
+ }
+ else
+ {
+ //NDebugOverlay::Cross3D( GetAbsOrigin(), 16, 255, 255, 0, false, 0.025f );
+
+ m_bOnWall = true;
+
+ if( tr.m_pEnt != NULL && !tr.m_pEnt->IsWorld() )
+ {
+ IPhysicsObject *pPhysics = tr.m_pEnt->VPhysicsGetObject();
+
+ if( pPhysics != NULL )
+ {
+ Vector vecMassCenter;
+ Vector vecMassCenterWorld;
+
+ vecMassCenter = pPhysics->GetMassCenterLocalSpace();
+ pPhysics->LocalToWorld( &vecMassCenterWorld, vecMassCenter );
+
+ if( tr.endpos.z > vecMassCenterWorld.z )
+ {
+ pPhysics->ApplyForceOffset( (-150.0f * m_flRandomEightyPercent) * tr.plane.normal, tr.endpos );
+ }
+ }
+ }
+ }
+
+ Vector vecDir = tr.endpos - vecStart;
+ VectorNormalize( vecDir );
+ SetElementVelocity( vecDir * flSpeed, false );
+}
+
+//---------------------------------------------------------
+// Set velocity that will carry me towards a specified entity
+// Most often used to move along with the npc_blob that
+// is directing me.
+//---------------------------------------------------------
+void CBlobElement::MoveTowardsTargetEntity( float speed )
+{
+ CBaseEntity *pTarget = m_hTargetEntity.Get();
+
+ if( pTarget != NULL )
+ {
+ // Try to attack my target's enemy directly if I can.
+ CBaseEntity *pTargetEnemy = pTarget->GetEnemy();
+
+ if( pTargetEnemy != NULL )
+ {
+ pTarget = pTargetEnemy;
+ }
+
+ Vector vecDir = pTarget->WorldSpaceCenter() - GetAbsOrigin();
+ vecDir.NormalizeInPlace();
+ SetElementVelocity( vecDir * speed, true );
+ }
+ else
+ {
+ SetElementVelocity( vec3_origin, true );
+ }
+}
+
+//---------------------------------------------------------
+// Set velocity that will take me towards a specified location.
+// This is often used to send all blob elements to specific
+// locations, causing the blob to appear as though it has
+// formed a specific shape.
+//---------------------------------------------------------
+void CBlobElement::MoveTowardsTargetLocation( float speed )
+{
+ Vector vecDir = m_vecTargetLocation - GetAbsOrigin();
+ float dist = VectorNormalize( vecDir );
+
+ //!!!HACKHACK - how about a real way to tell if we've reached our goal?
+ if( dist <= 8.0f )
+ {
+ SetActiveMovementRule( BLOB_MOVE_DONT_MOVE );
+ }
+
+ speed = MIN( dist, speed );
+
+ SetElementVelocity( vecDir * speed, true );
+}
+
+//---------------------------------------------------------
+// Pick new random numbers for the parameters that create
+// variations in movement.
+//---------------------------------------------------------
+void CBlobElement::ReconfigureRandomParams()
+{
+ m_flSinePhase = random->RandomFloat( 0.01f, 0.9f );
+ m_flSineFrequency = random->RandomFloat( 10.0f, 20.0f );
+ m_flSineAmplitude = random->RandomFloat( 0.5f, 1.5f );
+}
+
+//---------------------------------------------------------
+// Adjust velocity if this element is moving faster than
+// flMaxSpeed or slower than flMinSpeed
+//---------------------------------------------------------
+void CBlobElement::EnforceSpeedLimits( float flMinSpeed, float flMaxSpeed )
+{
+ Vector vecVelocity = GetAbsVelocity();
+ float flSpeed = VectorNormalize( vecVelocity );
+
+ if( flSpeed > flMaxSpeed )
+ {
+ SetElementVelocity( vecVelocity * flMaxSpeed, true );
+ }
+ else if( flSpeed < flMinSpeed )
+ {
+ SetElementVelocity( vecVelocity * flMinSpeed, true );
+ }
+}
+
+//=========================================================
+// Custom schedules
+//=========================================================
+enum
+{
+ SCHED_MYCUSTOMSCHEDULE = LAST_SHARED_SCHEDULE,
+};
+
+//=========================================================
+// Custom tasks
+//=========================================================
+enum
+{
+ TASK_MYCUSTOMTASK = LAST_SHARED_TASK,
+};
+
+
+//=========================================================
+// Custom Conditions
+//=========================================================
+enum
+{
+ COND_MYCUSTOMCONDITION = LAST_SHARED_CONDITION,
+};
+
+
+//=========================================================
+//=========================================================
+class CNPC_Blob : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Blob, CAI_BaseNPC );
+
+public:
+ CNPC_Blob();
+ void Precache( void );
+ void Spawn( void );
+ Class_T Classify( void );
+ void RunAI();
+ void GatherConditions( void );
+ int SelectSchedule( void );
+ int GetSoundInterests( void ) { return (SOUND_BUGBAIT); }
+
+
+ void ComputeCentroid();
+
+ void DoBlobBatchedAI( int iStart, int iEnd );
+
+ int ComputeBatchSize();
+ void AdvanceBatch();
+ int GetBatchStart();
+ int GetBatchEnd();
+
+ CBlobElement *CreateNewElement();
+ void InitializeElements();
+ void RecomputeIdealElementDist();
+
+ void RemoveAllElementsExcept( int iExempt );
+
+ void RemoveExcessElements( int iNumElements );
+ void AddNewElements( int iNumElements );
+
+ void FormShapeFromPath( string_t iszPathName );
+ void SetRadius( float flRadius );
+
+ DECLARE_DATADESC();
+
+ int m_iNumElements;
+ bool m_bInitialized;
+ int m_iBatchStart;
+ Vector m_vecCentroid;
+ float m_flMinElementDist;
+
+ CUtlVector<CHandle< CBlobElement > >m_Elements;
+
+ DEFINE_CUSTOM_AI;
+
+public:
+ void InputFormPathShape( inputdata_t &inputdata );
+ void InputSetRadius( inputdata_t &inputdata );
+ void InputChaseEntity( inputdata_t &inputdata );
+ void InputIsolateElement( inputdata_t &inputdata );
+ void InputFormHemisphere( inputdata_t &inputdata );
+ void InputFormTwoSpheres( inputdata_t &inputdata );
+
+public:
+ Vector m_vecAvoidOrigin[ BLOB_MAX_AVOID_ORIGINS ];
+ float m_flAvoidRadiusSqr;
+
+private:
+ int m_iReconfigureElement;
+ int m_iNumAvoidOrigins;
+
+ bool m_bEatCombineHack;
+};
+
+LINK_ENTITY_TO_CLASS( npc_blob, CNPC_Blob );
+IMPLEMENT_CUSTOM_AI( npc_blob,CNPC_Blob );
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Blob )
+
+DEFINE_FIELD( m_iNumElements, FIELD_INTEGER ),
+DEFINE_FIELD( m_bInitialized, FIELD_BOOLEAN ),
+DEFINE_FIELD( m_iBatchStart, FIELD_INTEGER ),
+DEFINE_FIELD( m_vecCentroid, FIELD_POSITION_VECTOR ),
+DEFINE_FIELD( m_flMinElementDist, FIELD_FLOAT ),
+DEFINE_FIELD( m_iReconfigureElement, FIELD_INTEGER ),
+DEFINE_UTLVECTOR( m_Elements, FIELD_EHANDLE ),
+
+DEFINE_INPUTFUNC( FIELD_STRING, "FormPathShape", InputFormPathShape ),
+DEFINE_INPUTFUNC( FIELD_FLOAT, "SetRadius", InputSetRadius ),
+DEFINE_INPUTFUNC( FIELD_STRING, "ChaseEntity", InputChaseEntity ),
+DEFINE_INPUTFUNC( FIELD_INTEGER, "IsolateElement", InputIsolateElement ),
+DEFINE_INPUTFUNC( FIELD_VOID, "FormHemisphere", InputFormHemisphere ),
+DEFINE_INPUTFUNC( FIELD_VOID, "FormTwoSpheres", InputFormTwoSpheres ),
+
+END_DATADESC()
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+CNPC_Blob::CNPC_Blob()
+{
+ m_iNumElements = 0;
+ m_bInitialized = false;
+ m_iBatchStart = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize the custom schedules
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InitCustomSchedules(void)
+{
+ INIT_CUSTOM_AI(CNPC_Blob);
+
+ ADD_CUSTOM_TASK(CNPC_Blob, TASK_MYCUSTOMTASK);
+
+ ADD_CUSTOM_SCHEDULE(CNPC_Blob, SCHED_MYCUSTOMSCHEDULE);
+
+ ADD_CUSTOM_CONDITION(CNPC_Blob, COND_MYCUSTOMCONDITION);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Blob::Precache( void )
+{
+ PrecacheModel( NPC_BLOB_MODEL );
+ UTIL_PrecacheOther( "blob_element" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Blob::Spawn( void )
+{
+ Precache();
+
+ SetModel( NPC_BLOB_MODEL );
+
+ SetHullType(HULL_TINY);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_NONE );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetBloodColor( BLOOD_COLOR_RED );
+ m_iHealth = INT_MAX;
+ m_flFieldOfView = -1.0f;
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+
+ m_Elements.RemoveAll();
+
+ NPCInit();
+
+ AddEffects( EF_NODRAW );
+
+ m_flMinElementDist = blob_mindist.GetFloat();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Blob::Classify( void )
+{
+ return CLASS_PLAYER_ALLY;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::RunAI()
+{
+ BaseClass::RunAI();
+
+ if( !m_bInitialized )
+ {
+ // m_bInitialized is set to false in the constructor. So this bit of
+ // code runs one time, the first time I think.
+ Msg("I need to initialize\n");
+ InitializeElements();
+ m_bInitialized = true;
+ return;
+ }
+
+ int iIdealNumElements = blob_numelements.GetInt();
+ if( iIdealNumElements != m_iNumElements )
+ {
+ int delta = iIdealNumElements - m_iNumElements;
+
+ if( delta < 0 )
+ {
+ delta = -delta;
+ delta = MIN(delta, 5 );
+ RemoveExcessElements( delta );
+
+ if( m_iReconfigureElement > m_iNumElements )
+ {
+ // Start this index over at zero, if it is past the new end of the utlvector.
+ m_iReconfigureElement = 0;
+ }
+ }
+ else
+ {
+ delta = MIN(delta, 5 );
+ AddNewElements( delta );
+ }
+
+ RecomputeIdealElementDist();
+ }
+
+ ComputeCentroid();
+
+ if( npc_blob_show_centroid.GetBool() )
+ {
+ NDebugOverlay::Cross3D( m_vecCentroid + Vector( 0, 0, 12 ), 32, 0, 255, 0, false, 0.025f );
+ }
+
+ if( npc_blob_use_threading.GetBool() )
+ {
+ IterRangeParallel( this, &CNPC_Blob::DoBlobBatchedAI, 0, m_Elements.Count() );
+ }
+ else
+ {
+ DoBlobBatchedAI( 0, m_Elements.Count() );
+ }
+
+ if( GetEnemy() != NULL )
+ {
+ float flEnemyDistSqr = m_vecCentroid.DistToSqr( GetEnemy()->GetAbsOrigin() );
+
+ if( flEnemyDistSqr <= Square( 32.0f ) )
+ {
+ if( GetEnemy()->Classify() == CLASS_COMBINE )
+ {
+ if( !m_bEatCombineHack )
+ {
+ variant_t var;
+
+ var.SetFloat( 0 );
+ g_EventQueue.AddEvent( GetEnemy(), "HitByBugBait", 0.0f, this, this );
+ g_EventQueue.AddEvent( GetEnemy(), "SetHealth", var, 3.0f, this, this );
+ m_bEatCombineHack = true;
+
+ blob_radius.SetValue( 48.0f );
+ RecomputeIdealElementDist();
+ }
+ }
+ else
+ {
+ CTakeDamageInfo info;
+
+ info.SetAttacker( this );
+ info.SetInflictor( this );
+ info.SetDamage( 5 );
+ info.SetDamageType( DMG_SLASH );
+ info.SetDamageForce( Vector( 0, 0, 1 ) );
+
+ GetEnemy()->TakeDamage( info );
+ }
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + npc_blob_think_interval.GetFloat() );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::GatherConditions( void )
+{
+ if( m_bEatCombineHack )
+ {
+ // We just ate someone.
+ if( !GetEnemy() || !GetEnemy()->IsAlive() )
+ {
+ m_bEatCombineHack = false;
+ blob_radius.SetValue( 160.0f );
+ RecomputeIdealElementDist();
+ }
+ }
+
+ BaseClass::GatherConditions();
+
+}
+
+//-----------------------------------------------------------------------------
+// Either stand still or chase the enemy, for now.
+//-----------------------------------------------------------------------------
+int CNPC_Blob::SelectSchedule( void )
+{
+ if( GetEnemy() == NULL )
+ return SCHED_IDLE_STAND;
+
+ return SCHED_CHASE_ENEMY;
+}
+
+//-----------------------------------------------------------------------------
+// Average the origin of all elements to get the centroid for the group
+//-----------------------------------------------------------------------------
+void CNPC_Blob::ComputeCentroid()
+{
+ m_vecCentroid = vec3_origin;
+
+ for( int i = 0 ; i < m_Elements.Count() ; i++ )
+ {
+ m_vecCentroid += m_Elements[ i ]->GetAbsOrigin();
+ }
+
+ m_vecCentroid /= m_Elements.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Run all of the AI for elements within the range iStart to iEnd
+//-----------------------------------------------------------------------------
+void CNPC_Blob::DoBlobBatchedAI( int iStart, int iEnd )
+{
+ float flInterval = gpGlobals->curtime - GetLastThink();
+
+ // Local fields for sin-wave movement variance
+ float flMySine;
+ float flAmplitude = npc_blob_sin_amplitude.GetFloat();
+ float flMyAmplitude;
+ Vector vecRight;
+ Vector vecForward;
+
+ // Local fields for attract/repel
+ float minDistSqr = Square( m_flMinElementDist );
+ float flBlobSpeed = blob_element_speed.GetFloat();
+ float flSpeed;
+
+ // Local fields for speed limiting
+ float flMinSpeed = blob_element_speed.GetFloat() * 0.5f;
+ float flMaxSpeed = blob_element_speed.GetFloat() * 1.5f;
+ bool bEnforceSpeedLimit;
+ bool bEnforceRelativePositions;
+ bool bDoMovementVariation;
+ bool bDoOrientation = npc_blob_use_orientation.GetBool();
+ float flIdleSpeedFactor = npc_blob_idle_speed_factor.GetFloat();
+
+ // Group cohesion
+ float flBlobRadiusSqr = Square( blob_radius.GetFloat() + 48.0f ); // Four feet of fudge
+
+ // Build a right-hand vector along which we'll add some sine wave data to give each
+ // element a unique insect-like undulation along an axis perpendicular to their path,
+ // which makes the entire group look far less orderly
+ if( GetEnemy() != NULL )
+ {
+ // If I have an enemy, the right-hand vector is perpendicular to a straight line
+ // from the group's centroid to the enemy's origin.
+ vecForward = GetEnemy()->GetAbsOrigin() - m_vecCentroid;
+ VectorNormalize( vecForward );
+ vecRight.x = vecForward.y;
+ vecRight.y = -vecForward.x;
+ }
+ else
+ {
+ // If there is no enemy, wobble along the axis from the centroid to me.
+ vecForward = GetAbsOrigin() - m_vecCentroid;
+ VectorNormalize( vecForward );
+ vecRight.x = vecForward.y;
+ vecRight.y = -vecForward.x;
+ }
+
+ //--
+ // MAIN LOOP - Run all of the elements in the set iStart to iEnd
+ //--
+ for( int i = iStart ; i < iEnd ; i++ )
+ {
+ CBlobElement *pThisElement = m_Elements[ i ];
+
+ //--
+ // Initial movement
+ //--
+ // Start out with bEnforceSpeedLimit set to false. This is because an element
+ // can't overspeed if it's moving undisturbed towards its target entity or
+ // target location. An element can only under or overspeed when it is repelled
+ // by multiple other elements in the group. See "Relative Positions" below.
+ //
+ // Initialize some 'defaults' that may be changed for each iteration of this loop
+ bEnforceSpeedLimit = false;
+ bEnforceRelativePositions = true;
+ bDoMovementVariation = true;
+ flSpeed = flBlobSpeed;
+
+ switch( pThisElement->GetActiveMovementRule() )
+ {
+ case BLOB_MOVE_DONT_MOVE:
+ {
+ pThisElement->SetElementVelocity( vec3_origin, true );
+
+ trace_t tr;
+ Vector vecOrigin = pThisElement->GetAbsOrigin();
+
+ UTIL_TraceLine( vecOrigin, vecOrigin - Vector( 0, 0, 16), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction < 1.0f )
+ {
+ QAngle angles;
+
+ VectorAngles( tr.plane.normal, angles );
+
+ float flSwap = angles.x;
+
+ angles.x = -angles.y;
+ angles.y = flSwap;
+
+ pThisElement->SetAbsAngles( angles );
+ }
+ }
+ continue;
+ break;
+
+ case BLOB_MOVE_TO_TARGET_LOCATION:
+ {
+ Vector vecDiff = pThisElement->GetAbsOrigin() - pThisElement->m_vecTargetLocation;
+
+ if( vecDiff.Length2DSqr() <= Square(80.0f) )
+ {
+ // Don't shove this guy around any more, let him get to his goal position.
+ flSpeed *= 0.5f;
+ bEnforceRelativePositions = false;
+ bDoMovementVariation = false;
+ }
+
+ pThisElement->MoveTowardsTargetLocation( flSpeed );
+ }
+ break;
+
+ case BLOB_MOVE_TO_TARGET_ENTITY:
+ {
+ if( !IsMoving() && GetEnemy() == NULL )
+ {
+ if( pThisElement->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) <= flBlobRadiusSqr )
+ {
+ flSpeed = (flSpeed * flIdleSpeedFactor) * pThisElement->m_flRandomEightyPercent;
+ }
+ }
+ pThisElement->MoveTowardsTargetEntity( flSpeed );
+ }
+ break;
+
+ default:
+ Msg("ERROR: Blob Element with unspecified Movement Rule\n");
+ break;
+ }
+
+ //---
+ // Relative positions
+ //--
+ // Check this element against ALL other elements. If the two elements are closer
+ // than the allowed minimum distance, repel this element away. (The other element
+ // will repel when its AI runs). A single element can be repelled by many other
+ // elements. This is why bEnforceSpeedLimit is set to true if any of the repelling
+ // code runs for this element. Multiple attempts to repel an element in the same
+ // direction will cause overspeed. Conflicting attempts to repel an element in opposite
+ // directions will cause underspeed.
+ Vector vecDir = Vector( 0, 0, 0 );
+ Vector vecThisElementOrigin = pThisElement->GetAbsOrigin();
+
+ if( bEnforceRelativePositions )
+ {
+ for( int j = 0 ; j < m_Elements.Count() ; j++ )
+ {
+ // This is the innermost loop! We should optimize here, if anywhere.
+
+ // If this element is on the wall, then don't be repelled by anyone. Repelling
+ // elements that are trying to climb a wall usually make them look like they
+ // fall off the wall a few times while climbing.
+ if( pThisElement->m_bOnWall )
+ continue;
+
+ CBlobElement *pThatElement = m_Elements[ j ];
+ if( i != j )
+ {
+ Vector vecThatElementOrigin = pThatElement->GetAbsOrigin();
+ float distSqr = vecThisElementOrigin.DistToSqr( vecThatElementOrigin );
+
+ if( distSqr < minDistSqr )
+ {
+ // Too close to the other element. Move away.
+ float flRepelSpeed;
+ Vector vecRepelDir = ( vecThisElementOrigin - vecThatElementOrigin );
+
+ vecRepelDir.NormalizeInPlace();
+ flRepelSpeed = (flSpeed * ( 1.0f - ( distSqr / minDistSqr ) ) ) * pThatElement->GetSinePhase();
+ pThisElement->AddElementVelocity( vecRepelDir * flRepelSpeed, true );
+
+ // Since we altered this element's velocity after it was initially set, there's a chance
+ // that the sums of multiple vectors will cause the element to over or underspeed, so
+ // mark it for speed limit enforcement
+ bEnforceSpeedLimit = true;
+ }
+ }
+ }
+ }
+
+ //--
+ // Movement variation
+ //--
+ if( bDoMovementVariation )
+ {
+ flMySine = sin( gpGlobals->curtime * pThisElement->GetSineFrequency() );
+ flMyAmplitude = flAmplitude * pThisElement->GetSineAmplitude();
+ pThisElement->AddElementVelocity( vecRight * (flMySine * flMyAmplitude), true );
+ }
+
+ // Avoidance
+ for( int a = 0 ; a < m_iNumAvoidOrigins ; a++ )
+ {
+ Vector vecAvoidDir = pThisElement->GetAbsOrigin() - m_vecAvoidOrigin[ a ];
+
+ if( vecAvoidDir.LengthSqr() <= (m_flAvoidRadiusSqr * pThisElement->m_flRandomEightyPercent) )
+ {
+ VectorNormalize( vecAvoidDir );
+ pThisElement->AddElementVelocity( vecAvoidDir * (flSpeed * 2.0f), true );
+ break;
+ }
+ }
+
+ //--
+ // Speed limits
+ //---
+ if( bEnforceSpeedLimit == true )
+ {
+ pThisElement->EnforceSpeedLimits( flMinSpeed, flMaxSpeed );
+ }
+
+ //--
+ // Wall crawling
+ //--
+ pThisElement->ModifyVelocityForSurface( flInterval, flSpeed );
+
+ // For identifying stuck elements.
+ pThisElement->m_vecPrevOrigin = pThisElement->GetAbsOrigin();
+
+ pThisElement->m_flDistFromCentroidSqr = pThisElement->m_vecPrevOrigin.DistToSqr( m_vecCentroid );
+
+ // Orientation
+ if( bDoOrientation )
+ {
+ QAngle angles;
+ VectorAngles( pThisElement->GetAbsVelocity(), angles );
+ pThisElement->SetAbsAngles( angles );
+ }
+
+/*
+ //--
+ // Stragglers/Group integrity
+ //
+ if( pThisElement->m_flDistFromCentroidSqr > flStragglerDistSqr )
+ {
+ NDebugOverlay::Line( pThisElement->GetAbsOrigin(), m_vecCentroid, 255, 0, 0, false, 0.025f );
+ }
+*/
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Throw out all elements and their entities except for the the specified
+// index into the UTILVector. This is useful for isolating elements that
+// get into a bad state.
+//-----------------------------------------------------------------------------
+void CNPC_Blob::RemoveAllElementsExcept( int iExempt )
+{
+ if( m_Elements.Count() == 1 )
+ return;
+
+ m_Elements[ 0 ].Set( m_Elements[ iExempt ].Get() );
+
+ for( int i = 1 ; i < m_Elements.Count() ; i++ )
+ {
+ if( i != iExempt )
+ {
+ m_Elements[ i ]->SUB_Remove();
+ }
+ }
+
+ m_Elements.RemoveMultiple( 1, m_Elements.Count() - 1 );
+
+ m_iNumElements = 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The blob has too many elements. Locate good candidates and remove
+// this many elements.
+//-----------------------------------------------------------------------------
+void CNPC_Blob::RemoveExcessElements( int iNumElements )
+{
+ // For now we're not assessing candidates, just blindly removing.
+ int i;
+ for( i = 0 ; i < iNumElements ; i++ )
+ {
+ int iLastElement = m_iNumElements - 1;
+
+ // Nuke the associated entity
+ m_Elements[ iLastElement ]->SUB_Remove();
+
+ m_Elements.Remove( iLastElement );
+ m_iNumElements--;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This blob has too few elements. Add this many elements by stacking
+// them on top of existing elements and allowing them to disperse themselves
+// into the blob.
+//-----------------------------------------------------------------------------
+void CNPC_Blob::AddNewElements( int iNumElements )
+{
+ int i;
+
+ // Keep track of how many elements we had when we came into this function.
+ // Since the new elements copy their origins from existing elements, we only want
+ // to copy origins from elements that existed before we came into this function.
+ // Otherwise, the more elements we create while in this function, the more likely it
+ // becomes that several of them will stack on the same origin.
+ int iInitialElements = m_iNumElements;
+
+ for( i = 0 ; i < iNumElements ; i++ )
+ {
+ CBlobElement *pElement = CreateNewElement();
+
+ if( pElement != NULL )
+ {
+ // Copy the origin of some element that is not me. This will make the expansion
+ // of the group easier on the eye, since this element will spawn inside of some
+ // other element, and then be pushed out by the blob's repel rules.
+ int iCopyElement = random->RandomInt( 0, iInitialElements - 1 );
+ pElement->SetAbsOrigin( m_Elements[iCopyElement]->GetAbsOrigin() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+#define BLOB_MAX_VERTS 128
+void CNPC_Blob::FormShapeFromPath( string_t iszPathName )
+{
+ Vector vertex[ BLOB_MAX_VERTS ];
+
+ int i;
+ int iNumVerts = 0;
+
+ for ( i = 0 ; i < BLOB_MAX_VERTS ; i++ )
+ {
+ if( iszPathName == NULL_STRING )
+ {
+ //Msg("Terminal path\n");
+ break;
+ }
+
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, iszPathName );
+
+ if( pEntity != NULL )
+ {
+ bool bClosedPath = false;
+
+ for( int j = 0 ; j < i ; j++ )
+ {
+ // Stop if we reach a vertex that's already in the array (closed path)
+ if( vertex[ j ] == pEntity->GetAbsOrigin() )
+ {
+ //Msg("Closed path!\n");
+ bClosedPath = true;
+ break;
+ }
+ }
+
+ vertex[ i ] = pEntity->GetAbsOrigin();
+ iszPathName = pEntity->m_target;
+ iNumVerts++;
+
+ if( bClosedPath )
+ break;
+ }
+ }
+
+ //Msg("%d verts found in path!\n", iNumVerts);
+
+ float flPathLength = 0.0f;
+ float flDistribution;
+
+ for( i = 0 ; i < iNumVerts - 1 ; i++ )
+ {
+ Vector vecDiff = vertex[ i ] - vertex[ i + 1 ];
+
+ flPathLength += vecDiff.Length();
+ }
+
+ flDistribution = flPathLength / m_iNumElements;
+ Msg("Path length is %f, distribution is %f\n", flPathLength, flDistribution );
+
+ int element = 0;
+ for( i = 0 ; i < iNumVerts - 1 ; i++ )
+ {
+ //NDebugOverlay::Line( vertex[ i ], vertex[ i + 1 ], 0, 255, 0, false, 10.0f );
+ Vector vecDiff = vertex[ i + 1 ] - vertex[ i ];
+ Vector vecStart = vertex[ i ];
+
+ float flSegmentLength = VectorNormalize( vecDiff );
+
+ float flStep;
+
+ for( flStep = 0.0f ; flStep < flSegmentLength ; flStep += flDistribution )
+ {
+ //NDebugOverlay::Cross3D( vecStart + vecDiff * flStep, 16, 255, 255, 255, false, 10.0f );
+ m_Elements[ element ]->SetTargetLocation( vecStart + vecDiff * flStep );
+ m_Elements[ element ]->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION );
+ element++;
+
+ if( element == m_iNumElements )
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::SetRadius( float flRadius )
+{
+ blob_radius.SetValue( flRadius );
+ RecomputeIdealElementDist();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputFormPathShape( inputdata_t &inputdata )
+{
+ string_t shape = inputdata.value.StringID();
+
+ if( shape == NULL_STRING )
+ return;
+
+ //Msg("I'm supposed to form some shape called:%s\n", shape );
+
+ FormShapeFromPath( shape );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputSetRadius( inputdata_t &inputdata )
+{
+ float flNewRadius = inputdata.value.Float();
+
+ SetRadius( flNewRadius );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputChaseEntity( inputdata_t &inputdata )
+{
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID(), NULL, inputdata.pActivator, inputdata.pCaller );
+
+ if ( pEntity )
+ {
+ for( int i = 0 ; i < m_Elements.Count() ; i++ )
+ {
+ CBlobElement *pElement = m_Elements[ i ];
+
+ pElement->SetTargetEntity( pEntity );
+ pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_ENTITY );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputIsolateElement( inputdata_t &inputdata )
+{
+ int iElement = inputdata.value.Int();
+
+ RemoveAllElementsExcept( iElement );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputFormHemisphere( inputdata_t &inputdata )
+{
+ Vector center = GetAbsOrigin();
+ const float flRadius = 240.0f;
+
+ Vector vecDir;
+
+ for( int i = 0 ; i < m_Elements.Count() ; i++ )
+ {
+ CBlobElement *pElement = m_Elements[ i ];
+
+ // Compute a point around my center
+ vecDir.x = random->RandomFloat( -1, 1 );
+ vecDir.y = random->RandomFloat( -1, 1 );
+ vecDir.z = random->RandomFloat( 0, 1 );
+
+ VectorNormalize( vecDir );
+
+ pElement->SetTargetLocation( center + vecDir * flRadius );
+ pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InputFormTwoSpheres( inputdata_t &inputdata )
+{
+ Vector center = GetAbsOrigin();
+ Vector sphere1 = GetAbsOrigin() + Vector( 120.0f, 0, 120.0f );
+ Vector sphere2 = GetAbsOrigin() + Vector( -120.0f, 0, 120.0f );
+ const float flRadius = 100.0f;
+
+ Vector vecDir;
+
+ int batchSize = m_Elements.Count() / 2;
+
+ for( int i = 0 ; i < batchSize ; i++ )
+ {
+ CBlobElement *pElement = m_Elements[ i ];
+
+ // Compute a point around my center
+ vecDir.x = random->RandomFloat( -1, 1 );
+ vecDir.y = random->RandomFloat( -1, 1 );
+ vecDir.z = random->RandomFloat( -1, 1 );
+
+ VectorNormalize( vecDir );
+
+ pElement->SetTargetLocation( sphere1 + vecDir * flRadius );
+ pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION );
+ }
+
+ for( int i = batchSize ; i < m_Elements.Count() ; i++ )
+ {
+ CBlobElement *pElement = m_Elements[ i ];
+
+ // Compute a point around my center
+ vecDir.x = random->RandomFloat( -1, 1 );
+ vecDir.y = random->RandomFloat( -1, 1 );
+ vecDir.z = random->RandomFloat( -1, 1 );
+
+ VectorNormalize( vecDir );
+
+ pElement->SetTargetLocation( sphere2 + vecDir * flRadius );
+ pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_LOCATION );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Get the index of the element to start processing with for this batch.
+//-----------------------------------------------------------------------------
+int CNPC_Blob::GetBatchStart()
+{
+ return m_iBatchStart;
+}
+
+//-----------------------------------------------------------------------------
+// Get the index of the element to stop processing with for this batch.
+//-----------------------------------------------------------------------------
+int CNPC_Blob::GetBatchEnd()
+{
+ int batchDone = m_iBatchStart + ComputeBatchSize();
+ batchDone = MIN( batchDone, m_Elements.Count() );
+
+ return batchDone;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CNPC_Blob::ComputeBatchSize()
+{
+ int batchSize = m_Elements.Count() / ( 100 / blob_batchpercent.GetInt() );
+ return batchSize;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+void CNPC_Blob::AdvanceBatch()
+{
+ m_iBatchStart += ComputeBatchSize();
+
+ if( m_iBatchStart >= m_Elements.Count() )
+ m_iBatchStart = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a new blob element from scratch and adds it to the blob
+//-----------------------------------------------------------------------------
+CBlobElement *CNPC_Blob::CreateNewElement()
+{
+ CBlobElement *pElement = static_cast<CBlobElement*>(CreateEntityByName( "blob_element" ));
+
+ if( pElement != NULL )
+ {
+ pElement->SetOwnerEntity( this );
+ pElement->SetSinePhase( fabs( sin(((float)m_iNumElements)/10.0f) ) );
+ pElement->SetActiveMovementRule( BLOB_MOVE_TO_TARGET_ENTITY );
+ pElement->SetTargetEntity( this );
+
+ pElement->m_iElementNumber = m_iNumElements;
+ m_iNumElements++;
+ pElement->Spawn();
+ m_Elements.AddToTail( pElement );
+ return pElement;
+ }
+
+ Warning("Blob could not spawn new element!\n");
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Create, initialize, and distribute all blob elements
+//-----------------------------------------------------------------------------
+void CNPC_Blob::InitializeElements()
+{
+ // Squirt all of the elements out into a circle
+ int i;
+ QAngle angDistributor( 0, 0, 0 );
+
+ int iNumElements = blob_numelements.GetInt();
+
+ float step = 360.0f / ((float)iNumElements);
+ for( i = 0 ; i < iNumElements ; i++ )
+ {
+ Vector vecDir;
+ Vector vecDest;
+ AngleVectors( angDistributor, &vecDir, NULL, NULL );
+ vecDest = WorldSpaceCenter() + vecDir * 64.0f;
+
+ CBlobElement *pElement = CreateNewElement();
+
+ if( !pElement )
+ {
+ Msg("Blob could not create all elements!!\n");
+ return;
+ }
+
+ trace_t tr;
+ UTIL_TraceLine( vecDest, vecDest + Vector (0, 0, MIN_COORD_FLOAT), MASK_SHOT, pElement, COLLISION_GROUP_NONE, &tr );
+
+ pElement->SetAbsOrigin( tr.endpos + Vector( 0, 0, 1 ) );
+
+ angDistributor.y += step;
+ }
+
+ CBaseEntity *pEntity = gEntList.FindEntityByClassname( NULL, "info_target" );
+ for( i = 0 ; i < BLOB_MAX_AVOID_ORIGINS ; i++ )
+ {
+ if( pEntity )
+ {
+ if( pEntity->NameMatches("avoid") )
+ {
+ m_vecAvoidOrigin[ i ] = pEntity->GetAbsOrigin();
+ m_flAvoidRadiusSqr = Square( 120.0f );
+ m_iNumAvoidOrigins++;
+ }
+
+ pEntity = gEntList.FindEntityByClassname( pEntity, "info_target" );
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ Msg("%d avoid origins\n", m_iNumAvoidOrigins );
+
+ RecomputeIdealElementDist();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Blob::RecomputeIdealElementDist()
+{
+ float radius = blob_radius.GetFloat();
+ float area = M_PI * Square(radius);
+
+ //Msg("Area of blob is: %f\n", area );
+
+ //m_flMinElementDist = 2.75f * sqrt( area / m_iNumElements );
+ m_flMinElementDist = M_PI * sqrt( area / m_iNumElements );
+
+ //Msg("New element dist: %f\n", m_flMinElementDist );
+}
+
|