//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "ai_spotlight.h" #include "ai_basenpc.h" #include "spotlightend.h" #include "beam_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Parameters for how the scanner relates to citizens. //----------------------------------------------------------------------------- #define SPOTLIGHT_WIDTH 96 //----------------------------------------------------------------------------- // Save/load //----------------------------------------------------------------------------- BEGIN_SIMPLE_DATADESC( CAI_Spotlight ) // Robin: Don't save, recreated after restore/transition. //DEFINE_FIELD( m_hSpotlight, FIELD_EHANDLE ), //DEFINE_FIELD( m_hSpotlightTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_vSpotlightTargetPos, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vSpotlightDir, FIELD_VECTOR ), DEFINE_FIELD( m_flSpotlightCurLength, FIELD_FLOAT ), DEFINE_FIELD( m_flSpotlightMaxLength, FIELD_FLOAT ), DEFINE_FIELD( m_flConstraintAngle, FIELD_FLOAT ), DEFINE_FIELD( m_nHaloSprite, FIELD_MODELINDEX ), DEFINE_FIELD( m_nSpotlightAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_nFlags, FIELD_INTEGER ), DEFINE_FIELD( m_vAngularVelocity, FIELD_QUATERNION ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CAI_Spotlight::CAI_Spotlight() { #ifdef _DEBUG m_vSpotlightTargetPos.Init(); m_vSpotlightDir.Init(); #endif } CAI_Spotlight::~CAI_Spotlight() { SpotlightDestroy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAI_Spotlight::Precache(void) { // Sprites m_nHaloSprite = GetOuter()->PrecacheModel("sprites/light_glow03.vmt"); GetOuter()->PrecacheModel( "sprites/glow_test02.vmt" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAI_Spotlight::Init( CAI_BaseNPC *pOuter, int nFlags, float flConstraintAngle, float flMaxLength ) { SetOuter( pOuter ); m_nFlags = nFlags; m_flConstraintAngle = flConstraintAngle; m_flSpotlightMaxLength = flMaxLength; // Check for user error if (m_flSpotlightMaxLength <= 0) { DevMsg("ERROR: Invalid spotlight length <= 0, setting to 500\n"); m_flSpotlightMaxLength = 500; } Precache(); m_vSpotlightTargetPos = vec3_origin; m_hSpotlight = NULL; m_hSpotlightTarget = NULL; AngleVectors( GetAbsAngles(), &m_vSpotlightDir ); m_vAngularVelocity.Init( 0, 0, 0, 1 ); m_flSpotlightCurLength = m_flSpotlightMaxLength; } //------------------------------------------------------------------------------ // Computes the spotlight endpoint //------------------------------------------------------------------------------ void CAI_Spotlight::ComputeEndpoint( const Vector &vecStartPoint, Vector *pEndPoint ) { // Create the endpoint trace_t tr; AI_TraceLine( vecStartPoint, vecStartPoint + m_vSpotlightDir * 2 * m_flSpotlightMaxLength, MASK_OPAQUE, GetOuter(), COLLISION_GROUP_NONE, &tr ); *pEndPoint = tr.endpos; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CAI_Spotlight::SpotlightCreate( int nAttachment, const Vector &vecInitialDir ) { // Make sure we don't already have one if ( m_hSpotlight != NULL ) return; m_vSpotlightDir = vecInitialDir; VectorNormalize( m_vSpotlightDir ); m_nSpotlightAttachment = nAttachment; CreateSpotlightEntities(); } //----------------------------------------------------------------------------- // Purpose: Create the beam & spotlight end for this spotlight. // Will be re-called after restore/transition //----------------------------------------------------------------------------- void CAI_Spotlight::CreateSpotlightEntities( void ) { m_vAngularVelocity.Init( 0, 0, 0, 1 ); // Create the endpoint // Get the initial position... Vector vecStartPoint; if ( m_nSpotlightAttachment == 0 ) { vecStartPoint = GetOuter()->GetAbsOrigin(); } else { GetOuter()->GetAttachment( m_nSpotlightAttachment, vecStartPoint ); } Vector vecEndPoint; ComputeEndpoint( vecStartPoint, &vecEndPoint ); m_hSpotlightTarget = (CSpotlightEnd*)CreateEntityByName( "spotlight_end" ); m_hSpotlightTarget->Spawn(); m_hSpotlightTarget->SetAbsOrigin( vecEndPoint ); m_hSpotlightTarget->SetOwnerEntity( GetOuter() ); m_hSpotlightTarget->SetRenderColor( 255, 255, 255 ); m_hSpotlightTarget->m_Radius = m_flSpotlightMaxLength; if ( FBitSet (m_nFlags, AI_SPOTLIGHT_NO_DLIGHTS) ) { m_hSpotlightTarget->m_flLightScale = 0.0; } else { m_hSpotlightTarget->m_flLightScale = SPOTLIGHT_WIDTH; } // Create the beam m_hSpotlight = CBeam::BeamCreate( "sprites/glow_test02.vmt", SPOTLIGHT_WIDTH ); // Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore) m_hSpotlight->AddSpawnFlags( SF_BEAM_TEMPORARY ); m_hSpotlight->SetColor( 255, 255, 255 ); m_hSpotlight->SetHaloTexture( m_nHaloSprite ); m_hSpotlight->SetHaloScale( 32 ); m_hSpotlight->SetEndWidth( m_hSpotlight->GetWidth() ); m_hSpotlight->SetBeamFlags( (FBEAM_SHADEOUT|FBEAM_NOTILE) ); m_hSpotlight->SetBrightness( 32 ); m_hSpotlight->SetNoise( 0 ); m_hSpotlight->EntsInit( GetOuter(), m_hSpotlightTarget ); m_hSpotlight->SetStartAttachment( m_nSpotlightAttachment ); } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CAI_Spotlight::SpotlightDestroy(void) { if ( m_hSpotlight ) { UTIL_Remove(m_hSpotlight); m_hSpotlight = NULL; UTIL_Remove(m_hSpotlightTarget); m_hSpotlightTarget = NULL; } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CAI_Spotlight::SetSpotlightTargetPos( const Vector &vSpotlightTargetPos ) { m_vSpotlightTargetPos = vSpotlightTargetPos; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CAI_Spotlight::SetSpotlightTargetDirection( const Vector &vSpotlightTargetDir ) { if ( !m_hSpotlight ) { CreateSpotlightEntities(); } VectorMA( m_hSpotlight->GetAbsStartPos(), 1000.0f, vSpotlightTargetDir, m_vSpotlightTargetPos ); } //------------------------------------------------------------------------------ // Constrain to cone //------------------------------------------------------------------------------ bool CAI_Spotlight::ConstrainToCone( Vector *pDirection ) { Vector vecOrigin, vecForward; if ( m_nSpotlightAttachment == 0 ) { QAngle vecAngles; vecAngles = GetOuter()->GetAbsAngles(); AngleVectors( vecAngles, &vecForward ); } else { GetOuter()->GetAttachment( m_nSpotlightAttachment, vecOrigin, &vecForward ); } if ( m_flConstraintAngle == 0.0f ) { *pDirection = vecForward; return true; } float flDot = DotProduct( vecForward, *pDirection ); if ( flDot >= cos( DEG2RAD( m_flConstraintAngle ) ) ) return false; Vector vecAxis; CrossProduct( *pDirection, vecForward, vecAxis ); VectorNormalize( vecAxis ); Quaternion q; AxisAngleQuaternion( vecAxis, -m_flConstraintAngle, q ); Vector vecResult; matrix3x4_t rot; QuaternionMatrix( q, rot ); VectorRotate( vecForward, rot, vecResult ); VectorNormalize( vecResult ); *pDirection = vecResult; return true; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ #define QUAT_BLEND_FACTOR 0.4f void CAI_Spotlight::UpdateSpotlightDirection( void ) { if ( !m_hSpotlight ) { CreateSpotlightEntities(); } // Compute the current beam direction Vector vTargetDir; VectorSubtract( m_vSpotlightTargetPos, m_hSpotlight->GetAbsStartPos(), vTargetDir ); VectorNormalize(vTargetDir); ConstrainToCone( &vTargetDir ); // Compute the amount to rotate float flDot = DotProduct( vTargetDir, m_vSpotlightDir ); flDot = clamp( flDot, -1.0f, 1.0f ); float flAngle = AngleNormalize( RAD2DEG( acos( flDot ) ) ); float flClampedAngle = clamp( flAngle, 0.0f, 45.0f ); float flBeamTurnRate = SimpleSplineRemapVal( flClampedAngle, 0.0f, 45.0f, 10.0f, 45.0f ); if ( fabs(flAngle) > flBeamTurnRate * gpGlobals->frametime ) { flAngle = flBeamTurnRate * gpGlobals->frametime; } // Compute the rotation axis Vector vecRotationAxis; CrossProduct( m_vSpotlightDir, vTargetDir, vecRotationAxis ); if ( VectorNormalize( vecRotationAxis ) < 1e-3 ) { vecRotationAxis.Init( 0, 0, 1 ); } // Compute the actual rotation amount, using quat slerp blending Quaternion desiredQuat, resultQuat; AxisAngleQuaternion( vecRotationAxis, flAngle, desiredQuat ); QuaternionSlerp( m_vAngularVelocity, desiredQuat, QUAT_BLEND_FACTOR, resultQuat ); m_vAngularVelocity = resultQuat; // If we're really close, and we're not moving very quickly, slam. float flActualRotation = AngleNormalize( RAD2DEG(2 * acos(m_vAngularVelocity.w)) ); if (( flActualRotation < 1e-3 ) && (flAngle < 1e-3 )) { m_vSpotlightDir = vTargetDir; m_vAngularVelocity.Init( 0, 0, 0, 1 ); return; } // Update the desired direction matrix3x4_t rot; Vector vecNewDir; QuaternionMatrix( m_vAngularVelocity, rot ); VectorRotate( m_vSpotlightDir, rot, vecNewDir ); m_vSpotlightDir = vecNewDir; VectorNormalize(m_vSpotlightDir); } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CAI_Spotlight::UpdateSpotlightEndpoint( void ) { if ( !m_hSpotlight ) { CreateSpotlightEntities(); } Vector vecStartPoint, vecEndPoint; vecStartPoint = m_hSpotlight->GetAbsStartPos(); ComputeEndpoint( vecStartPoint, &vecEndPoint ); // If I'm not facing the spotlight turn it off Vector vecSpotDir; VectorSubtract( vecEndPoint, vecStartPoint, vecSpotDir ); float flBeamLength = VectorNormalize(vecSpotDir); m_hSpotlightTarget->SetAbsOrigin( vecEndPoint ); m_hSpotlightTarget->SetAbsVelocity( vec3_origin ); m_hSpotlightTarget->m_vSpotlightOrg = vecStartPoint; m_hSpotlightTarget->m_vSpotlightDir = vecSpotDir; // Avoid sudden change in where beam fades out when cross disconinuities m_flSpotlightCurLength = Lerp( 0.20f, m_flSpotlightCurLength, flBeamLength ); // Fade out spotlight end if past max length. if (m_flSpotlightCurLength > 2*m_flSpotlightMaxLength) { m_hSpotlightTarget->SetRenderAlpha( 0 ); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else if (m_flSpotlightCurLength > m_flSpotlightMaxLength) { m_hSpotlightTarget->SetRenderAlpha( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) ); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else { m_hSpotlightTarget->SetRenderAlpha( 1.0 ); m_hSpotlight->SetFadeLength(m_flSpotlightCurLength); } // Adjust end width to keep beam width constant float flNewWidth = SPOTLIGHT_WIDTH * ( flBeamLength / m_flSpotlightMaxLength ); flNewWidth = MIN( 100, flNewWidth ); m_hSpotlight->SetWidth(flNewWidth); m_hSpotlight->SetEndWidth(flNewWidth); // Adjust width of light on the end. if ( FBitSet (m_nFlags, AI_SPOTLIGHT_NO_DLIGHTS) ) { m_hSpotlightTarget->m_flLightScale = 0.0; } else { m_hSpotlightTarget->m_flLightScale = flNewWidth; } } //------------------------------------------------------------------------------ // Purpose: Update the direction and position of my spotlight (if it's active) //------------------------------------------------------------------------------ void CAI_Spotlight::Update(void) { if ( !m_hSpotlight ) { CreateSpotlightEntities(); } // Update the beam direction UpdateSpotlightDirection(); UpdateSpotlightEndpoint(); }