//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "c_ai_basenpc.h" #include "engine/ivmodelinfo.h" #include "rope_physics.h" #include "materialsystem/imaterialsystem.h" #include "fx_line.h" #include "engine/ivdebugoverlay.h" #include "bone_setup.h" #include "model_types.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define BARNACLE_TONGUE_POINTS 7 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class C_NPC_Barnacle : public C_AI_BaseNPC { public: DECLARE_CLASS( C_NPC_Barnacle, C_AI_BaseNPC ); DECLARE_CLIENTCLASS(); C_NPC_Barnacle( void ); virtual void GetRenderBounds( Vector &theMins, Vector &theMaxs ) { BaseClass::GetRenderBounds( theMins, theMaxs ); // Extend our bounding box downwards the length of the tongue theMins -= Vector( 0, 0, m_flAltitude ); } // Purpose: Initialize absmin & absmax to the appropriate box virtual void ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) { // Extend our bounding box downwards the length of the tongue CollisionProp()->WorldSpaceAABB( pVecWorldMins, pVecWorldMaxs ); // We really care about the tongue tip. The altitude is not really relevant. VectorMin( *pVecWorldMins, m_vecTip, *pVecWorldMins ); VectorMax( *pVecWorldMaxs, m_vecTip, *pVecWorldMaxs ); // pVecWorldMins->z -= m_flAltitude; } void OnDataChanged( DataUpdateType_t updateType ); void InitTonguePhysics( void ); void ClientThink( void ); void StandardBlendingRules( CStudioHdr *pStudioHdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ); void SetVecTip( const float *pPosition ); void SetAltitude( float flAltitude ); // Purpose: void ComputeVisualTipPoint( Vector *pTip ); protected: Vector m_vecTipPrevious; Vector m_vecRoot; Vector m_vecTip; Vector m_vecTipDrawOffset; private: // Tongue points float m_flAltitude; Vector m_vecTonguePoints[BARNACLE_TONGUE_POINTS]; CRopePhysics m_TonguePhysics; // Tongue physics delegate class CBarnaclePhysicsDelegate : public CSimplePhysics::IHelper { public: virtual void GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ); virtual void ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ); C_NPC_Barnacle *m_pBarnacle; }; friend class CBarnaclePhysicsDelegate; CBarnaclePhysicsDelegate m_PhysicsDelegate; private: C_NPC_Barnacle( const C_NPC_Barnacle & ); // not defined, not accessible }; static void RecvProxy_VecTip( const CRecvProxyData *pData, void *pStruct, void *pOut ) { ((C_NPC_Barnacle*)pStruct)->SetVecTip( pData->m_Value.m_Vector ); } IMPLEMENT_CLIENTCLASS_DT( C_NPC_Barnacle, DT_Barnacle, CNPC_Barnacle ) RecvPropFloat( RECVINFO( m_flAltitude ) ), RecvPropVector( RECVINFO( m_vecRoot ) ), RecvPropVector( RECVINFO( m_vecTip ), 0, RecvProxy_VecTip ), RecvPropVector( RECVINFO( m_vecTipDrawOffset ) ), END_RECV_TABLE() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_NPC_Barnacle::C_NPC_Barnacle( void ) { m_PhysicsDelegate.m_pBarnacle = this; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); if ( updateType == DATA_UPDATE_CREATED ) { InitTonguePhysics(); // We want to think every frame. SetNextClientThink( CLIENT_THINK_ALWAYS ); return; } } //----------------------------------------------------------------------------- // Sets the tongue altitude //----------------------------------------------------------------------------- void C_NPC_Barnacle::SetAltitude( float flAltitude ) { m_flAltitude = flAltitude; } void C_NPC_Barnacle::SetVecTip( const float *pPosition ) { Vector vecNewTip; vecNewTip.Init( pPosition[0], pPosition[1], pPosition[2] ); if ( vecNewTip != m_vecTip ) { m_vecTip = vecNewTip; CollisionProp()->MarkSurroundingBoundsDirty(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::InitTonguePhysics( void ) { // Init tongue spline // First point is at the top m_TonguePhysics.SetupSimulation( m_flAltitude / (BARNACLE_TONGUE_POINTS-1), &m_PhysicsDelegate ); m_TonguePhysics.Restart(); // Initialize the positions of the nodes. m_TonguePhysics.GetFirstNode()->m_vPos = m_vecRoot; m_TonguePhysics.GetFirstNode()->m_vPrevPos = m_TonguePhysics.GetFirstNode()->m_vPos; float flAltitude = m_flAltitude; for( int i = 1; i < m_TonguePhysics.NumNodes(); i++ ) { flAltitude *= 0.5; CSimplePhysics::CNode *pNode = m_TonguePhysics.GetNode( i ); pNode->m_vPos = m_TonguePhysics.GetNode(i-1)->m_vPos - Vector(0,0,flAltitude); pNode->m_vPrevPos = pNode->m_vPos; // Set the length of the node's spring //m_TonguePhysics.ResetNodeSpringLength( i-1, flAltitude ); } m_vecTipPrevious = m_vecTip; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::ClientThink( void ) { m_TonguePhysics.Simulate( gpGlobals->frametime ); // Set the spring's length to that of the tongue's extension m_TonguePhysics.ResetSpringLength( m_flAltitude / (BARNACLE_TONGUE_POINTS-1) ); // Necessary because ComputeVisualTipPoint depends on m_vecTipPrevious Vector vecTemp; ComputeVisualTipPoint( &vecTemp ); m_vecTipPrevious = vecTemp; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) { BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask ); if ( !hdr ) return; int firstBone = Studio_BoneIndexByName( hdr, "Barnacle.tongue1" ); Vector vecPrevRight; GetVectors( NULL, &vecPrevRight, NULL ); Vector vecPrev = pos[Studio_BoneIndexByName( hdr, "Barnacle.base" )]; Vector vecCurr = vec3_origin; Vector vecForward; for ( int i = 0; i <= BARNACLE_TONGUE_POINTS; i++ ) { // We double up the bones at the last node. if ( i == BARNACLE_TONGUE_POINTS ) { vecCurr = m_TonguePhysics.GetLastNode()->m_vPos; } else { vecCurr = m_TonguePhysics.GetNode(i)->m_vPos; } //debugoverlay->AddBoxOverlay( vecCurr, -Vector(2,2,2), Vector(2,2,2), vec3_angle, 0,255,0, 128, 0.1 ); // Fill out the positions in local space VectorITransform( vecCurr, EntityToWorldTransform(), pos[firstBone+i] ); vecCurr = pos[firstBone+i]; // Disallow twist in the tongue visually // Forward vector has to follow the tongue, right + up have to minimize twist from // the previous bone // Fill out the angles if ( i != BARNACLE_TONGUE_POINTS ) { vecForward = (vecCurr - vecPrev); if ( VectorNormalize( vecForward ) < 1e-3 ) { vecForward.Init( 0, 0, 1 ); } } // Project the previous vecRight into a plane perpendicular to vecForward // that's the vector closest to what we want... Vector vecRight, vecUp; VectorMA( vecPrevRight, -DotProduct( vecPrevRight, vecForward ), vecForward, vecRight ); VectorNormalize( vecRight ); CrossProduct( vecForward, vecRight, vecUp ); BasisToQuaternion( vecForward, vecRight, vecUp, q[firstBone+i] ); vecPrev = vecCurr; vecPrevRight = vecRight; } } //=============================================================================================================================== // BARNACLE TONGUE PHYSICS //=============================================================================================================================== #define TONGUE_GRAVITY 0, 0, -1000 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::CBarnaclePhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNodes, int iNode, Vector *pAccel ) { // Gravity. pAccel->Init( TONGUE_GRAVITY ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #define TIP_SNAP_FACTOR 200 // Todo: this really ought to be SIMD. void C_NPC_Barnacle::ComputeVisualTipPoint( Vector *pTip ) { float flTipMove = TIP_SNAP_FACTOR * gpGlobals->frametime; Vector tipIdeal; VectorAdd(m_vecTip, m_vecTipDrawOffset, tipIdeal); if ( tipIdeal.DistToSqr( m_vecTipPrevious ) > (flTipMove * flTipMove) ) { // Inch the visual tip toward the actual tip VectorSubtract( tipIdeal, m_vecTipPrevious, *pTip ); VectorNormalize( *pTip ); *pTip *= flTipMove; *pTip += m_vecTipPrevious; } else { *pTip = tipIdeal; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_NPC_Barnacle::CBarnaclePhysicsDelegate::ApplyConstraints( CSimplePhysics::CNode *pNodes, int nNodes ) { // Startpoint always stays at the root pNodes[0].m_vPos = m_pBarnacle->m_vecRoot; // Endpoint always stays at the tip m_pBarnacle->ComputeVisualTipPoint( &pNodes[nNodes-1].m_vPos ); }