//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// //NOTE: Mirrors with models require an attachment named "MirrorSurface_Attach" with x facing out of the mirror plane. //They also require that the mirror surface be in a bodygroup by itself named "MirrorSurface" with the first index being the mirror, second being empty. //Lastly, they require that all non-mirror geometry be in bodygroups that have the second entry as empty. //It's a good idea to put a cubemap on the mirror surface material because they're not infinitely recursive #include "cbase.h" #include "baseanimating.h" #include "pvs_extender.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //#define TEST_ANIMATION //uncomment to run "testanim" in a loop class CProp_Mirror : public CBaseAnimating, CPVS_Extender { public: DECLARE_CLASS( CProp_Mirror, CBaseAnimating ); DECLARE_SERVERCLASS(); DECLARE_DATADESC(); CProp_Mirror( void ); virtual void Precache( void ); virtual void Spawn( void ); virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); } virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | (m_bPhysicsEnabled ? FCAP_IMPULSE_USE : 0); }; virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void UpdateReflectionPlane( void ); void UpdateReflectionPolygon( void ); virtual CServerNetworkProperty *GetExtenderNetworkProp( void ) { return NetworkProp(); } virtual const edict_t *GetExtenderEdict( void ) const { return edict(); } virtual Vector GetExtensionPVSOrigin( void ) { return GetAbsOrigin(); } virtual bool IsExtenderValid( void ) { return true; } int ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes ); //This portal is decidedly visible, recursively extend the visibility problem virtual void ComputeSubVisibility( CPVS_Extender **pExtenders, int iExtenderCount, unsigned char *outputPVS, int pvssize, const Vector &vVisOrigin, const VPlane *pVisFrustum, int iVisFrustumPlanes, VisExtensionChain_t *pVisChain, int iAreasNetworked[MAX_MAP_AREAS], int iMaxRecursionsLeft ); #if defined( TEST_ANIMATION ) virtual void Think( void ); #endif Vector m_LocalSpaceReflectionPolygonVerts[10]; //best guess at the reflection polygon by intersecting the reflection plane with the local space OBB int m_LocalSpaceReflectionPolygonVertCount; struct ReflectPlaneCachedData_t { Vector vAttachmentOrigin; QAngle qAttachmentAngle; bool bModel; Vector vLocalSpaceAttachmentOrigin; QAngle qLocalSpaceAttachmentAngles; Vector vLocalOBB_Mins; Vector vLocalOBB_Maxs; }; ReflectPlaneCachedData_t m_CachedReflectedData; VMatrix m_matReflection; CNetworkVar( float, m_fWidth ); CNetworkVar( float, m_fHeight ); int m_iMirrorFaceAttachment; bool m_bModel; bool m_bPhysicsEnabled; }; BEGIN_DATADESC( CProp_Mirror ) DEFINE_KEYFIELD( m_fWidth, FIELD_FLOAT, "Width" ), DEFINE_KEYFIELD( m_fHeight, FIELD_FLOAT, "Height" ), DEFINE_FIELD( m_iMirrorFaceAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_bModel, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bPhysicsEnabled, FIELD_BOOLEAN, "PhysicsEnabled" ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CProp_Mirror, DT_Prop_Mirror ) SendPropFloat( SENDINFO(m_fWidth) ), SendPropFloat( SENDINFO(m_fHeight) ), END_SEND_TABLE() LINK_ENTITY_TO_CLASS( prop_mirror, CProp_Mirror ); CProp_Mirror::CProp_Mirror( void ) { m_matReflection.m[3][0] = 0.0f; m_matReflection.m[3][1] = 0.0f; m_matReflection.m[3][2] = 0.0f; m_matReflection.m[3][3] = 1.0f; m_CachedReflectedData.vAttachmentOrigin.Invalidate(); m_CachedReflectedData.qAttachmentAngle.Invalidate(); m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate(); m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate(); m_CachedReflectedData.vLocalOBB_Maxs.Invalidate(); m_CachedReflectedData.vLocalOBB_Mins.Invalidate(); } void CProp_Mirror::Precache( void ) { BaseClass::Precache(); if( (m_ModelName.ToCStr() != NULL) && (m_ModelName.ToCStr()[0] != '\0') ) { PrecacheModel( m_ModelName.ToCStr() ); } } void CProp_Mirror::Spawn( void ) { Precache(); BaseClass::Spawn(); if( m_ModelName.ToCStr() != NULL && m_ModelName.ToCStr()[0] != '\0' ) { SetModel( m_ModelName.ToCStr() ); SetSolid( SOLID_VPHYSICS ); SetCollisionGroup( COLLISION_GROUP_INTERACTIVE ); if( m_bPhysicsEnabled ) { SetMoveType( MOVETYPE_VPHYSICS ); VPhysicsInitNormal( GetSolid(), GetSolidFlags(), false ); } else { SetMoveType( MOVETYPE_NONE ); } #if defined( TEST_ANIMATION ) ResetSequence( LookupSequence( "testanim" ) ); ResetSequenceInfo(); SetPlaybackRate( 0.1f ); SetNextThink( gpGlobals->curtime + 1.0f ); #endif m_iMirrorFaceAttachment = LookupAttachment( "MirrorSurface_Attach" ); m_bModel = ( m_iMirrorFaceAttachment > 0 ); //0 is an invalid attachment index according to LookupAttachment() } else { Vector vExtent( 2.0f, m_fWidth/2.0f, m_fHeight/2.0f ); SetSize( -vExtent, vExtent ); m_bModel = false; } } void CProp_Mirror::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if( m_bPhysicsEnabled ) { CBasePlayer *pPlayer = ToBasePlayer( pActivator ); if ( pPlayer ) { pPlayer->PickupObject( this ); } } else { BaseClass::Use( pActivator, pCaller, useType, value ); } } void CProp_Mirror::UpdateReflectionPlane( void ) { Vector vMirrorAttachmentOrigin; QAngle qMirrorAttachmentAngles; if( m_bModel ) { GetAttachment( m_iMirrorFaceAttachment, vMirrorAttachmentOrigin, qMirrorAttachmentAngles ); } else { vMirrorAttachmentOrigin = GetAbsOrigin(); qMirrorAttachmentAngles = GetAbsAngles(); } if( (m_CachedReflectedData.vAttachmentOrigin != vMirrorAttachmentOrigin) || (m_CachedReflectedData.qAttachmentAngle != qMirrorAttachmentAngles) ) { m_CachedReflectedData.vAttachmentOrigin = vMirrorAttachmentOrigin; m_CachedReflectedData.qAttachmentAngle = qMirrorAttachmentAngles; Vector vOrigin = m_CachedReflectedData.vAttachmentOrigin; Vector vForward, vRight, vUp; AngleVectors( qMirrorAttachmentAngles, &vForward, &vRight, &vUp ); Vector vToOrigin( vOrigin.Dot( vForward ), vOrigin.Dot( vRight ), -vOrigin.Dot( vUp ) ); //generate mirroring matrix. Move mirror to origin using base vectors, flip on forward axis, move back to position and orientation { m_matReflection.m[0][0] = (-vForward.x * vForward.x) + (vRight.x * vRight.x) + (vUp.x * vUp.x); m_matReflection.m[0][1] = (-vForward.x * vForward.y) + (vRight.x * vRight.y) + (vUp.x * vUp.y); m_matReflection.m[0][2] = (-vForward.x * vForward.z) + (vRight.x * vRight.z) + (vUp.x * vUp.z); m_matReflection.m[0][3] = (vToOrigin.x * vForward.x) - (vToOrigin.y * vRight.x) + (vToOrigin.z * vUp.x) + vOrigin.x; m_matReflection.m[1][0] = m_matReflection.m[0][1]; //rotation portion of the matrix is equal to it's own transpose m_matReflection.m[1][1] = (-vForward.y * vForward.y) + (vRight.y * vRight.y) + (vUp.y * vUp.y); m_matReflection.m[1][2] = (-vForward.y * vForward.z) + (vRight.y * vRight.z) + (vUp.y * vUp.z); m_matReflection.m[1][3] = (vToOrigin.x * vForward.y) - (vToOrigin.y * vRight.y) + (vToOrigin.z * vUp.y) + vOrigin.y; m_matReflection.m[2][0] = m_matReflection.m[0][2]; //rotation portion of the matrix is equal to it's own transpose m_matReflection.m[2][1] = m_matReflection.m[1][2]; //rotation portion of the matrix is equal to it's own transpose m_matReflection.m[2][2] = (-vForward.z * vForward.z) + (vRight.z * vRight.z) + (vUp.z * vUp.z); m_matReflection.m[2][3] = (vToOrigin.x * vForward.z) - (vToOrigin.y * vRight.z) + (vToOrigin.z * vUp.z) + vOrigin.z; } UpdateReflectionPolygon(); } } void CProp_Mirror::UpdateReflectionPolygon( void ) { if( m_bModel != m_CachedReflectedData.bModel ) { m_CachedReflectedData.qAttachmentAngle.Invalidate(); m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate(); m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate(); m_CachedReflectedData.vLocalOBB_Maxs.Invalidate(); m_CachedReflectedData.bModel = m_bModel; } if( m_bModel ) { Vector vMins, vMaxs; vMins = WorldAlignMins(); vMaxs = WorldAlignMaxs(); Vector vLocalAttachmentOrigin; QAngle qLocalAttachmentAngles; GetAttachmentLocal( m_iMirrorFaceAttachment, vLocalAttachmentOrigin, qLocalAttachmentAngles ); if( (vMins == m_CachedReflectedData.vLocalOBB_Mins) && (vMaxs == m_CachedReflectedData.vLocalOBB_Maxs) && (vLocalAttachmentOrigin == m_CachedReflectedData.vLocalSpaceAttachmentOrigin) && (qLocalAttachmentAngles == m_CachedReflectedData.qLocalSpaceAttachmentAngles) ) { return; //nothing to update } m_CachedReflectedData.vLocalOBB_Mins = vMins; m_CachedReflectedData.vLocalOBB_Maxs = vMaxs; m_CachedReflectedData.vLocalSpaceAttachmentOrigin = vLocalAttachmentOrigin; m_CachedReflectedData.qLocalSpaceAttachmentAngles = qLocalAttachmentAngles; Vector vAttachmentVectors[3]; AngleVectors( qLocalAttachmentAngles, &vAttachmentVectors[0], &vAttachmentVectors[1], &vAttachmentVectors[2] ); float fLargestOBBDiff = vMaxs.x - vMins.x; for( int i = 1; i != 3; ++i ) { float fDiff = vMaxs[i] - vMins[i]; if( fDiff > fLargestOBBDiff ) { fLargestOBBDiff = fDiff; } } fLargestOBBDiff *= 4.0f; //to easily cover diagonal intersection and then some Vector vClipBuffers[2][10]; //4 starting points, possible to create 1 extra point per cut, 6 cuts vClipBuffers[0][0] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][1] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][2] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][3] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff); int iVertCount = 4; VPlane vClipPlanes[6]; vClipPlanes[0].Init( Vector( 1.0f, 0.0f, 0.0f ), vMins.x ); vClipPlanes[1].Init( Vector( -1.0f, 0.0f, 0.0f ), -vMaxs.x ); vClipPlanes[2].Init( Vector( 0.0f, 1.0f, 0.0f ), vMins.y ); vClipPlanes[3].Init( Vector( 0.0f, -1.0f, 0.0f ), -vMaxs.y ); vClipPlanes[4].Init( Vector( 0.0f, 0.0f, 1.0f ), vMins.z ); vClipPlanes[5].Init( Vector( 0.0f, 0.0f, -1.0f ), -vMaxs.z ); for( int i = 0; i != 6; ++i ) { iVertCount = ClipPolyToPlane( vClipBuffers[i & 1], iVertCount, vClipBuffers[(i & 1) ^ 1], vClipPlanes[i].m_Normal, vClipPlanes[i].m_Dist, 0.01f ); } Assert( iVertCount >= 3 ); m_LocalSpaceReflectionPolygonVertCount = iVertCount; memcpy( m_LocalSpaceReflectionPolygonVerts, vClipBuffers[0], sizeof( Vector ) * iVertCount ); } else { if( (m_CachedReflectedData.vLocalOBB_Maxs.x == m_fWidth) && (m_CachedReflectedData.vLocalOBB_Maxs.y == m_fHeight) ) return; m_LocalSpaceReflectionPolygonVertCount = 4; float fHalfWidth = m_fWidth / 2.0f; float fHalfHeight = m_fHeight / 2.0f; m_LocalSpaceReflectionPolygonVerts[0].Init( 0.0f, fHalfWidth, fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[1].Init( 0.0f, -fHalfWidth, fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[2].Init( 0.0f, -fHalfWidth, -fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[3].Init( 0.0f, fHalfWidth, -fHalfHeight ); } } #if defined( TEST_ANIMATION ) void CProp_Mirror::Think( void ) { StudioFrameAdvance(); DispatchAnimEvents( this ); if (IsSequenceFinished() && !SequenceLoops()) { // ResetSequenceInfo(); // hack to avoid reloading model every frame m_flAnimTime = gpGlobals->curtime; m_flPlaybackRate = 0.1f; m_bSequenceFinished = false; m_flLastEventCheck = 0; m_flCycle = 0; } SetNextThink( gpGlobals->curtime + 0.1f ); } #endif int CProp_Mirror::ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes ) { Vector vTransformedPolyVerts[10]; const matrix3x4_t &matLocalToWorld = CollisionProp()->CollisionToWorldTransform(); for( int i = 0; i != m_LocalSpaceReflectionPolygonVertCount; ++i ) { VectorTransform( &m_LocalSpaceReflectionPolygonVerts[i].x, matLocalToWorld, &vTransformedPolyVerts[i].x ); } int iReturnedPlanes = UTIL_CalcFrustumThroughConvexPolygon( vTransformedPolyVerts, m_LocalSpaceReflectionPolygonVertCount, vVisOrigin, pInputFrustum, iInputFrustumPlanes, pOutputFrustum, iOutputFrustumMaxPlanes, 0 ); if( (iReturnedPlanes < iOutputFrustumMaxPlanes) && (iReturnedPlanes != 0) ) { Vector vForward; AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward ); vForward = -vForward; //add the reflection plane as a near plane pOutputFrustum[iReturnedPlanes].Init( vForward, vForward.Dot( m_CachedReflectedData.vAttachmentOrigin ) ); ++iReturnedPlanes; } return iReturnedPlanes; } void CProp_Mirror::ComputeSubVisibility( CPVS_Extender **pExtenders, int iExtenderCount, unsigned char *outputPVS, int pvssize, const Vector &vVisOrigin, const VPlane *pVisFrustum, int iVisFrustumPlanes, VisExtensionChain_t *pVisChain, int iAreasNetworked[MAX_MAP_AREAS], int iMaxRecursionsLeft ) { if( iAreasNetworked[MAX_MAP_AREAS - 1] != -1 ) //early out, can't add any more data if we wanted to return; UpdateReflectionPlane(); Vector vForward; AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward ); if( vForward.Dot( vVisOrigin ) < vForward.Dot( m_CachedReflectedData.vAttachmentOrigin ) ) return; //vis origin is behind the reflection plane //both test if the portal is within the view frustum, and calculate the new one at the same time int iFrustumPlanesMax = (iVisFrustumPlanes + m_LocalSpaceReflectionPolygonVertCount + 1); VPlane *pNewFrustum = (VPlane *)stackalloc( sizeof( VPlane ) * iFrustumPlanesMax ); int iNewFrustumPlanes = ComputeFrustumThroughPolygon( vVisOrigin, pVisFrustum, iVisFrustumPlanes, pNewFrustum, iFrustumPlanesMax ); if( iNewFrustumPlanes == 0 ) { return; } //NDebugOverlay::EntityBounds( this, 0, 255, 0, 100, 0.0f ); int iArea = NetworkProp()->AreaNum(); unsigned char *pPVS = m_pExtenderData->iPVSBits; if( !m_pExtenderData->bAddedToPVSAlready ) { bool bFound = false; for( int i = 0; i != MAX_MAP_AREAS; ++i ) { if( iAreasNetworked[i] == iArea ) { bFound = true; break; } if( iAreasNetworked[i] == -1 ) { bFound = true; //we found it by adding it iAreasNetworked[i] = iArea; int iOutputPVSIntSize = pvssize / sizeof( unsigned int ); for( int j = 0; j != iOutputPVSIntSize; ++j ) { ((unsigned int *)outputPVS)[j] |= ((unsigned int *)pPVS)[j]; } for( int j = iOutputPVSIntSize * sizeof( unsigned int ); j != pvssize; ++j ) { outputPVS[j] |= pPVS[j]; } break; } } Vector vForward; AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward, NULL, NULL ); engine->AddOriginToPVS( m_CachedReflectedData.vAttachmentOrigin + vForward ); m_pExtenderData->bAddedToPVSAlready = true; } --iMaxRecursionsLeft; if( iMaxRecursionsLeft == 0 ) return; edict_t *myEdict = edict(); VisExtensionChain_t chainNode; chainNode.m_nArea = iArea; chainNode.pParentChain = pVisChain; //transform vis origin to linked space Vector vTransformedVisOrigin = m_matReflection * vVisOrigin; Vector vTranslation = m_matReflection.GetTranslation(); //transform the planes into the linked portal space for( int i = 0; i != iNewFrustumPlanes; ++i ) { pNewFrustum[i].m_Normal = m_matReflection.ApplyRotation( pNewFrustum[i].m_Normal ); pNewFrustum[i].m_Dist += pNewFrustum[i].m_Normal.Dot( vTranslation ); } Assert( pPVS != NULL ); //extend the vis by what the linked portal can see for( int i = 0; i != iExtenderCount; ++i ) { CPVS_Extender *pExtender = pExtenders[i]; if ( pExtender->GetExtenderEdict() == myEdict ) continue; if ( pExtender->GetExtenderNetworkProp()->IsInPVS( myEdict, pPVS, (MAX_MAP_LEAFS/8) ) ) //test against linked portal PVS, not aggregate PVS { chainNode.pExtender = pExtender; pExtender->ComputeSubVisibility( pExtenders, iExtenderCount, outputPVS, pvssize, vTransformedVisOrigin, pNewFrustum, iNewFrustumPlanes, &chainNode, iAreasNetworked, iMaxRecursionsLeft ); } } }