You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
8.5 KiB
354 lines
8.5 KiB
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
#include "cbase.h" |
|
#include "simple_physics.h" |
|
#include "mathlib/vmatrix.h" |
|
#include "beamdraw.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
class C_Hairball : public C_BaseEntity |
|
{ |
|
DECLARE_CLASS( C_Hairball, C_BaseEntity ); |
|
private: |
|
|
|
class CHairballDelegate : public CSimplePhysics::IHelper |
|
{ |
|
public: |
|
virtual void GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ); |
|
virtual void ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ); |
|
|
|
C_Hairball *m_pParent; |
|
}; |
|
|
|
|
|
public: |
|
|
|
C_Hairball(); |
|
|
|
void Init(); |
|
|
|
|
|
// IClientThinkable. |
|
public: |
|
|
|
virtual void ClientThink(); |
|
|
|
|
|
// IClientRenderable. |
|
public: |
|
|
|
virtual int DrawModel( int flags, const RenderableInstance_t &instance ); |
|
virtual RenderableTranslucencyType_t ComputeTranslucencyType(); |
|
|
|
|
|
public: |
|
|
|
float m_flSphereRadius; |
|
|
|
int m_nHairs; |
|
int m_nNodesPerHair; |
|
float m_flSpringDist; // = hair length / (m_nNodesPerHair-1) |
|
|
|
CUtlVector<CSimplePhysics::CNode> m_Nodes; // This is m_nHairs * m_nNodesPerHair large. |
|
CUtlVector<Vector> m_HairPositions; // Untransformed base hair positions, distributed on the sphere. |
|
CUtlVector<Vector> m_TransformedHairPositions; // Transformed base hair positions, distributed on the sphere. |
|
|
|
CHairballDelegate m_Delegate; |
|
CSimplePhysics m_Physics; |
|
|
|
IMaterial *m_pMaterial; |
|
|
|
|
|
// Super sophisticated AI. |
|
float m_flSitStillTime; |
|
Vector m_vMoveDir; |
|
|
|
float m_flSpinDuration; |
|
float m_flCurSpinTime; |
|
float m_flSpinRateX, m_flSpinRateY; |
|
|
|
bool m_bFirstThink; |
|
}; |
|
|
|
|
|
void C_Hairball::CHairballDelegate::GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ) |
|
{ |
|
pAccel->Init( 0, 0, -1500 ); |
|
} |
|
|
|
|
|
void C_Hairball::CHairballDelegate::ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ) |
|
{ |
|
int nSegments = m_pParent->m_nNodesPerHair - 1; |
|
float flSpringDistSqr = m_pParent->m_flSpringDist * m_pParent->m_flSpringDist; |
|
|
|
static int nIterations = 1; |
|
for( int iIteration=0; iIteration < nIterations; iIteration++ ) |
|
{ |
|
for ( int iHair=0; iHair < m_pParent->m_nHairs; iHair++ ) |
|
{ |
|
CSimplePhysics::CNode *pBase = &pNodes[iHair * m_pParent->m_nNodesPerHair]; |
|
|
|
for( int i=0; i < nSegments; i++ ) |
|
{ |
|
Vector &vNode1 = pBase[i].m_vPos; |
|
Vector &vNode2 = pBase[i+1].m_vPos; |
|
Vector vTo = vNode1 - vNode2; |
|
|
|
float flDistSqr = vTo.LengthSqr(); |
|
if( flDistSqr > flSpringDistSqr ) |
|
{ |
|
float flDist = (float)sqrt( flDistSqr ); |
|
vTo *= 1 - (m_pParent->m_flSpringDist / flDist); |
|
|
|
vNode1 -= vTo * 0.5f; |
|
vNode2 += vTo * 0.5f; |
|
} |
|
} |
|
|
|
// Lock the base of each hair to the right spot. |
|
pBase->m_vPos = m_pParent->m_TransformedHairPositions[iHair]; |
|
} |
|
} |
|
} |
|
|
|
|
|
C_Hairball::C_Hairball() |
|
{ |
|
m_nHairs = 100; |
|
m_nNodesPerHair = 3; |
|
|
|
float flHairLength = 20; |
|
m_flSpringDist = flHairLength / (m_nNodesPerHair - 1); |
|
|
|
m_Nodes.SetSize( m_nHairs * m_nNodesPerHair ); |
|
m_HairPositions.SetSize( m_nHairs ); |
|
m_TransformedHairPositions.SetSize( m_nHairs ); |
|
|
|
m_flSphereRadius = 20; |
|
m_vMoveDir.Init(); |
|
|
|
m_flSpinDuration = 1; |
|
m_flCurSpinTime = 0; |
|
m_flSpinRateX = m_flSpinRateY = 0; |
|
|
|
// Distribute on the sphere (need a better random distribution for the sphere). |
|
for ( int i=0; i < m_HairPositions.Count(); i++ ) |
|
{ |
|
float theta = RandomFloat( -M_PI, M_PI ); |
|
float phi = RandomFloat( -M_PI/2, M_PI/2 ); |
|
|
|
float cosPhi = cos( phi ); |
|
|
|
m_HairPositions[i].Init( |
|
cos(theta) * cosPhi * m_flSphereRadius, |
|
sin(theta) * cosPhi * m_flSphereRadius, |
|
sin(phi) * m_flSphereRadius ); |
|
} |
|
|
|
m_Delegate.m_pParent = this; |
|
|
|
m_Physics.Init( 1.0 / 20 ); // NOTE: PLAY WITH THIS FOR EFFICIENCY |
|
m_pMaterial = NULL; |
|
|
|
m_bFirstThink = true; |
|
} |
|
|
|
|
|
void C_Hairball::Init() |
|
{ |
|
ClientEntityList().AddNonNetworkableEntity( this ); |
|
ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS ); |
|
|
|
AddToLeafSystem( false ); |
|
|
|
m_pMaterial = materials->FindMaterial( "cable/cable", TEXTURE_GROUP_OTHER ); |
|
m_flSitStillTime = 5; |
|
} |
|
|
|
|
|
void C_Hairball::ClientThink() |
|
{ |
|
// Do some AI-type stuff.. move the entity around. |
|
//C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
//m_vecAngles = SetAbsAngles( pPlayer->GetAbsAngles() ); // copy player angles. |
|
|
|
Assert( !GetMoveParent() ); |
|
|
|
// Sophisticated AI. |
|
m_flCurSpinTime += gpGlobals->frametime; |
|
if ( m_flCurSpinTime < m_flSpinDuration ) |
|
{ |
|
float div = m_flCurSpinTime / m_flSpinDuration; |
|
|
|
QAngle angles = GetLocalAngles(); |
|
|
|
angles.x += m_flSpinRateX * SmoothCurve( div ); |
|
angles.y += m_flSpinRateY * SmoothCurve( div ); |
|
|
|
SetLocalAngles( angles ); |
|
} |
|
else |
|
{ |
|
// Flip between stopped and starting. |
|
if ( fabs( m_flSpinRateX ) > 0.01f ) |
|
{ |
|
m_flSpinRateX = m_flSpinRateY = 0; |
|
|
|
m_flSpinDuration = RandomFloat( 1, 2 ); |
|
} |
|
else |
|
{ |
|
static float flXSpeed = 3; |
|
static float flYSpeed = flXSpeed * 0.1f; |
|
m_flSpinRateX = RandomFloat( -M_PI*flXSpeed, M_PI*flXSpeed ); |
|
m_flSpinRateY = RandomFloat( -M_PI*flYSpeed, M_PI*flYSpeed ); |
|
|
|
m_flSpinDuration = RandomFloat( 1, 4 ); |
|
} |
|
|
|
m_flCurSpinTime = 0; |
|
} |
|
|
|
|
|
if ( m_flSitStillTime > 0 ) |
|
{ |
|
m_flSitStillTime -= gpGlobals->frametime; |
|
|
|
if ( m_flSitStillTime <= 0 ) |
|
{ |
|
// Shoot out some random lines and find the longest one. |
|
m_vMoveDir.Init( 1, 0, 0 ); |
|
float flLongestFraction = 0; |
|
for ( int i=0; i < 15; i++ ) |
|
{ |
|
Vector vDir( RandomFloat( -1, 1 ), RandomFloat( -1, 1 ), RandomFloat( -1, 1 ) ); |
|
VectorNormalize( vDir ); |
|
|
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vDir * 10000, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); |
|
|
|
if ( trace.fraction != 1.0 ) |
|
{ |
|
if ( trace.fraction > flLongestFraction ) |
|
{ |
|
flLongestFraction = trace.fraction; |
|
m_vMoveDir = vDir; |
|
} |
|
} |
|
} |
|
|
|
m_vMoveDir *= 650; // set speed. |
|
m_flSitStillTime = -1; // Move in this direction.. |
|
} |
|
} |
|
else |
|
{ |
|
// Move in the specified direction. |
|
Vector vEnd = GetAbsOrigin() + m_vMoveDir * gpGlobals->frametime; |
|
|
|
trace_t trace; |
|
UTIL_TraceLine( GetAbsOrigin(), vEnd, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); |
|
|
|
if ( trace.fraction < 1 ) |
|
{ |
|
// Ok, stop moving. |
|
m_flSitStillTime = RandomFloat( 1, 3 ); |
|
} |
|
else |
|
{ |
|
SetLocalOrigin( GetLocalOrigin() + m_vMoveDir * gpGlobals->frametime ); |
|
} |
|
} |
|
|
|
|
|
// Transform the base hair positions so we can lock them down. |
|
VMatrix mTransform; |
|
mTransform.SetupMatrixOrgAngles( GetLocalOrigin(), GetLocalAngles() ); |
|
|
|
for ( int i=0; i < m_HairPositions.Count(); i++ ) |
|
{ |
|
Vector3DMultiplyPosition( mTransform, m_HairPositions[i], m_TransformedHairPositions[i] ); |
|
} |
|
|
|
if ( m_bFirstThink ) |
|
{ |
|
m_bFirstThink = false; |
|
for ( int i=0; i < m_HairPositions.Count(); i++ ) |
|
{ |
|
for ( int j=0; j < m_nNodesPerHair; j++ ) |
|
{ |
|
m_Nodes[i*m_nNodesPerHair+j].Init( m_TransformedHairPositions[i] ); |
|
} |
|
} |
|
} |
|
|
|
// Simulate the physics and apply constraints. |
|
m_Physics.Simulate( m_Nodes.Base(), m_Nodes.Count(), &m_Delegate, gpGlobals->frametime, 0.98 ); |
|
} |
|
|
|
|
|
RenderableTranslucencyType_t C_Hairball::ComputeTranslucencyType() |
|
{ |
|
return ( m_pMaterial && m_pMaterial->IsTranslucent() ) ? RENDERABLE_IS_TRANSLUCENT : RENDERABLE_IS_OPAQUE; |
|
} |
|
|
|
int C_Hairball::DrawModel( int flags, const RenderableInstance_t &instance ) |
|
{ |
|
if ( !m_pMaterial ) |
|
return 0; |
|
|
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
for ( int iHair=0; iHair < m_nHairs; iHair++ ) |
|
{ |
|
CSimplePhysics::CNode *pBase = &m_Nodes[iHair * m_nNodesPerHair]; |
|
|
|
CBeamSegDraw beamDraw; |
|
beamDraw.Start( pRenderContext, m_nNodesPerHair-1, m_pMaterial ); |
|
|
|
for ( int i=0; i < m_nNodesPerHair; i++ ) |
|
{ |
|
BeamSeg_t seg; |
|
seg.m_vPos = pBase[i].m_vPredicted; |
|
seg.m_color.r = seg.m_color.g = seg.m_color.b = seg.m_color.a = 0; |
|
seg.m_flTexCoord = 0; |
|
static float flHairWidth = 1; |
|
seg.m_flWidth = flHairWidth; |
|
|
|
beamDraw.NextSeg( &seg ); |
|
} |
|
|
|
beamDraw.End(); |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
void CreateHairballCallback() |
|
{ |
|
for ( int i=0; i < 20; i++ ) |
|
{ |
|
C_Hairball *pHairball = new C_Hairball; |
|
pHairball->Init(); |
|
|
|
// Put it a short distance in front of the player. |
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
if ( !pPlayer ) |
|
return; |
|
|
|
Vector vForward; |
|
AngleVectors( pPlayer->GetAbsAngles(), &vForward ); |
|
pHairball->SetLocalOrigin( pPlayer->GetAbsOrigin() + vForward * 300 + RandomVector( 0, 100 ) ); |
|
} |
|
} |
|
|
|
ConCommand cc_CreateHairball( "CreateHairball", CreateHairballCallback, 0, FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT ); |
|
|
|
|