//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "C_PortalGhostRenderable.h" #include "PortalRender.h" #include "c_portal_player.h" #include "model_types.h" #include "c_basecombatweapon.h" #include "c_combatweaponworldclone.h" #include "toolframework_client.h" ConVar portal_ghosts_disable( "portal_ghosts_disable", "0", 0, "Disables rendering of ghosted objects in portal environments" ); inline float GhostedRenderableOriginTime( C_BaseEntity *pGhostedRenderable, float fCurTime ) { return pGhostedRenderable->GetOriginInterpolator().GetInterpolatedTime( pGhostedRenderable->GetEffectiveInterpolationCurTime( fCurTime ) ); } #define GHOST_RENDERABLE_TEN_TON_HAMMER 0 #if (GHOST_RENDERABLE_TEN_TON_HAMMER == 1) void GhostResetEverything( C_PortalGhostRenderable *pGhost ) { C_BaseEntity *pGhostedRenderable = pGhost->m_hGhostedRenderable; if( pGhostedRenderable ) { pGhostedRenderable->MarkRenderHandleDirty(); if( pGhost->m_bSourceIsBaseAnimating ) { //((C_BaseAnimating *)pGhostedRenderable)->MarkForThreadedBoneSetup(); ((C_BaseAnimating *)pGhostedRenderable)->InvalidateBoneCache(); } pGhostedRenderable->CollisionProp()->MarkPartitionHandleDirty(); pGhostedRenderable->CollisionProp()->MarkSurroundingBoundsDirty(); pGhostedRenderable->AddEFlags( EFL_DIRTY_ABSTRANSFORM | EFL_DIRTY_SPATIAL_PARTITION ); g_pClientLeafSystem->DisableCachedRenderBounds( pGhostedRenderable->RenderHandle(), true ); g_pClientLeafSystem->RenderableChanged( pGhostedRenderable->RenderHandle() ); if( C_BaseEntity::IsAbsQueriesValid() ) { pGhostedRenderable->CollisionProp()->UpdatePartition(); //pGhostedRenderable->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); } } pGhost->MarkRenderHandleDirty(); //pGhost->MarkForThreadedBoneSetup(); pGhost->InvalidateBoneCache(); pGhost->CollisionProp()->MarkPartitionHandleDirty(); pGhost->CollisionProp()->MarkSurroundingBoundsDirty(); pGhost->AddEFlags( EFL_DIRTY_ABSTRANSFORM | EFL_DIRTY_SPATIAL_PARTITION ); g_pClientLeafSystem->DisableCachedRenderBounds( pGhost->RenderHandle(), true ); g_pClientLeafSystem->RenderableChanged( pGhost->RenderHandle() ); if( C_BaseEntity::IsAbsQueriesValid() ) { pGhost->CollisionProp()->UpdatePartition(); //pGhost->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); } } #endif //#if (GHOST_RENDERABLE_TEN_TON_HAMMER == 1) C_PortalGhostRenderable::C_PortalGhostRenderable( C_Portal_Base2D *pOwningPortal, C_BaseEntity *pGhostSource, const VMatrix &matGhostTransform, float *pSharedRenderClipPlane, C_BasePlayer *pPlayer ) : m_hGhostedRenderable( pGhostSource ), m_matGhostTransform( matGhostTransform ), m_pSharedRenderClipPlane( pSharedRenderClipPlane ), m_pPortalExitRenderClipPlane( NULL ), m_hHoldingPlayer( pPlayer ), m_pOwningPortal( pOwningPortal ) { m_fRenderableRange[0] = -FLT_MAX; m_fRenderableRange[1] = FLT_MAX; m_fNoTransformBeforeTime = -FLT_MAX; m_fDisablePositionChecksUntilTime = -FLT_MAX; #if( DEBUG_GHOSTRENDERABLES == 1 ) if( pOwningPortal->m_bIsPortal2 ) { m_iDebugColor[0] = 255; m_iDebugColor[1] = 0; m_iDebugColor[2] = 0; } else { m_iDebugColor[0] = 0; m_iDebugColor[1] = 0; m_iDebugColor[2] = 255; } m_iDebugColor[3] = 16; #endif m_bSourceIsBaseAnimating = (dynamic_cast(pGhostSource) != NULL); RenderWithViewModels( pGhostSource->IsRenderingWithViewModels() ); SetModelName( m_hGhostedRenderable->GetModelName() ); m_bCombatWeapon = (dynamic_cast(pGhostSource) != NULL); SetModelIndex( m_bCombatWeapon ? ((C_BaseCombatWeapon *)pGhostSource)->GetWorldModelIndex() : pGhostSource->GetModelIndex() ); m_bCombatWeaponWorldClone = ( dynamic_cast< C_CombatWeaponClone* >( pGhostSource ) != NULL ); m_bPlayerHeldClone = ( dynamic_cast< C_PlayerHeldObjectClone* >( pGhostSource ) != NULL ); SetSize( pGhostSource->CollisionProp()->OBBMins(), pGhostSource->CollisionProp()->OBBMaxs() ); Assert( m_hGhostedRenderable.Get() != NULL ); } C_PortalGhostRenderable::~C_PortalGhostRenderable( void ) { m_hGhostedRenderable = NULL; } void C_PortalGhostRenderable::UpdateOnRemove( void ) { m_hGhostedRenderable = NULL; BaseClass::UpdateOnRemove(); } void C_PortalGhostRenderable::PerFrameUpdate( void ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( !pGhostedRenderable ) return; SetModelName( pGhostedRenderable->GetModelName() ); SetModelIndex( m_bCombatWeapon ? ((C_BaseCombatWeapon *)pGhostedRenderable)->GetWorldModelIndex() : pGhostedRenderable->GetModelIndex() ); SetEffects( pGhostedRenderable->GetEffects() | EF_NOINTERP ); m_flAnimTime = pGhostedRenderable->m_flAnimTime; if( m_bSourceIsBaseAnimating && !m_bCombatWeapon ) { C_BaseAnimating *pSource = (C_BaseAnimating *)pGhostedRenderable; SetCycle( pSource->GetCycle() ); SetSequence( pSource->GetSequence() ); SetBody( pSource->GetBody() ); SetSkin( pSource->GetSkin() ); } SetSize( pGhostedRenderable->CollisionProp()->OBBMins(), pGhostedRenderable->CollisionProp()->OBBMaxs() ); // Set position and angles relative to the object it's ghosting Vector ptNewOrigin; QAngle qNewAngles; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) >= m_fNoTransformBeforeTime ) { ptNewOrigin = m_matGhostTransform * pGhostedRenderable->GetNetworkOrigin(); qNewAngles = TransformAnglesToWorldSpace( pGhostedRenderable->GetNetworkAngles(), m_matGhostTransform.As3x4() ); } else { ptNewOrigin = pGhostedRenderable->GetNetworkOrigin(); qNewAngles = pGhostedRenderable->GetNetworkAngles(); } SetNetworkOrigin( ptNewOrigin ); SetLocalOrigin( ptNewOrigin ); SetAbsOrigin( ptNewOrigin ); SetNetworkAngles( qNewAngles ); SetLocalAngles( qNewAngles ); SetAbsAngles( qNewAngles ); g_pClientLeafSystem->RenderableChanged( RenderHandle() ); #if (GHOST_RENDERABLE_TEN_TON_HAMMER == 1) GhostResetEverything( this ); #endif } Vector const& C_PortalGhostRenderable::GetRenderOrigin( void ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return m_ReferencedReturns.vRenderOrigin; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) { m_ReferencedReturns.vRenderOrigin = pGhostedRenderable->GetRenderOrigin(); } else { m_ReferencedReturns.vRenderOrigin = m_matGhostTransform * pGhostedRenderable->GetRenderOrigin(); } return m_ReferencedReturns.vRenderOrigin; } QAngle const& C_PortalGhostRenderable::GetRenderAngles( void ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return m_ReferencedReturns.qRenderAngle; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) { m_ReferencedReturns.qRenderAngle = pGhostedRenderable->GetRenderAngles(); } else { m_ReferencedReturns.qRenderAngle = TransformAnglesToWorldSpace( pGhostedRenderable->GetRenderAngles(), m_matGhostTransform.As3x4() ); } return m_ReferencedReturns.qRenderAngle; } bool C_PortalGhostRenderable::SetupBones( matrix3x4a_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return false; int iOldModelIndex = 0, iWorldModelIndex = 0; bool bChangeModelIndex = m_bCombatWeapon && ((iOldModelIndex = ((C_BaseCombatWeapon *)pGhostedRenderable)->GetModelIndex()) != (iWorldModelIndex = ((C_BaseCombatWeapon *)pGhostedRenderable)->GetWorldModelIndex())); if( bChangeModelIndex ) { ((C_BaseCombatWeapon *)pGhostedRenderable)->SetModelIndex( iWorldModelIndex ); } if( pGhostedRenderable->SetupBones( pBoneToWorldOut, nMaxBones, boneMask, currentTime ) ) { if( pBoneToWorldOut && (GhostedRenderableOriginTime( pGhostedRenderable, currentTime ) >= m_fNoTransformBeforeTime) ) { matrix3x4a_t matGhostTransform; matGhostTransform = m_matGhostTransform.As3x4(); CStudioHdr *hdr = GetModelPtr(); int nBoneCount = hdr->numbones(); nBoneCount = MIN( nMaxBones, nBoneCount ); for( int i = 0; i != nBoneCount; ++i ) { ConcatTransforms_Aligned( matGhostTransform, pBoneToWorldOut[i], pBoneToWorldOut[i] ); } } if( bChangeModelIndex ) { ((C_BaseCombatWeapon *)pGhostedRenderable)->SetModelIndex( iOldModelIndex ); } return true; } if( bChangeModelIndex ) { ((C_BaseCombatWeapon *)pGhostedRenderable)->SetModelIndex( iOldModelIndex ); } return false; } C_BaseAnimating *C_PortalGhostRenderable::GetBoneSetupDependancy( void ) { return m_bSourceIsBaseAnimating ? (C_BaseAnimating *)(m_hGhostedRenderable.Get()) : NULL; } void C_PortalGhostRenderable::GetRenderBounds( Vector& mins, Vector& maxs ) { if( m_hGhostedRenderable == NULL ) { mins = maxs = vec3_origin; return; } m_hGhostedRenderable->GetRenderBounds( mins, maxs ); } void C_PortalGhostRenderable::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) { mins = maxs = vec3_origin; return; } if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetRenderBoundsWorldspace( mins, maxs ); Vector vTempMins, vTempMaxs; pGhostedRenderable->GetRenderBoundsWorldspace( vTempMins, vTempMaxs ); TransformAABB( m_matGhostTransform.As3x4(), vTempMins, vTempMaxs, mins, maxs ); } bool C_PortalGhostRenderable::ShouldReceiveProjectedTextures( int flags ) { return false; } void C_PortalGhostRenderable::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) { mins = maxs = vec3_origin; return; } if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetShadowRenderBounds( mins, maxs, shadowType ); Vector vTempMins, vTempMaxs; pGhostedRenderable->GetShadowRenderBounds( vTempMins, vTempMaxs, shadowType ); TransformAABB( m_matGhostTransform.As3x4(), vTempMins, vTempMaxs, mins, maxs ); } /*bool C_PortalGhostRenderable::GetShadowCastDistance( float *pDist, ShadowType_t shadowType ) const { if( m_hGhostedRenderable == NULL ) return false; return m_hGhostedRenderable->GetShadowCastDistance( pDist, shadowType ); } bool C_PortalGhostRenderable::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const { if( m_hGhostedRenderable == NULL ) return false; if( m_hGhostedRenderable->GetShadowCastDirection( pDirection, shadowType ) ) { if( pDirection ) *pDirection = m_matGhostTransform.ApplyRotation( *pDirection ); return true; } return false; }*/ const matrix3x4_t & C_PortalGhostRenderable::RenderableToWorldTransform() { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return m_ReferencedReturns.matRenderableToWorldTransform; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) { m_ReferencedReturns.matRenderableToWorldTransform = pGhostedRenderable->RenderableToWorldTransform(); } else { ConcatTransforms( m_matGhostTransform.As3x4(), pGhostedRenderable->RenderableToWorldTransform(), m_ReferencedReturns.matRenderableToWorldTransform ); } return m_ReferencedReturns.matRenderableToWorldTransform; } bool C_PortalGhostRenderable::GetAttachment( int number, Vector &origin, QAngle &angles ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return false; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetAttachment( number, origin, angles ); if( pGhostedRenderable->GetAttachment( number, origin, angles ) ) { origin = m_matGhostTransform * origin; angles = TransformAnglesToWorldSpace( angles, m_matGhostTransform.As3x4() ); return true; } return false; } bool C_PortalGhostRenderable::GetAttachment( int number, matrix3x4_t &matrix ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return false; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetAttachment( number, matrix ); if( pGhostedRenderable->GetAttachment( number, matrix ) ) { ConcatTransforms( m_matGhostTransform.As3x4(), matrix, matrix ); return true; } return false; } bool C_PortalGhostRenderable::GetAttachment( int number, Vector &origin ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return false; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetAttachment( number, origin ); if( pGhostedRenderable->GetAttachment( number, origin ) ) { origin = m_matGhostTransform * origin; return true; } return false; } bool C_PortalGhostRenderable::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ) { C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable; if( pGhostedRenderable == NULL ) return false; if( GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ) < m_fNoTransformBeforeTime ) return pGhostedRenderable->GetAttachmentVelocity( number, originVel, angleVel ); Vector ghostVel; if( pGhostedRenderable->GetAttachmentVelocity( number, ghostVel, angleVel ) ) { Vector3DMultiply( m_matGhostTransform, ghostVel, originVel ); Vector3DMultiply( m_matGhostTransform, *(Vector*)( &angleVel ), *(Vector*)( &angleVel ) ); return true; } return false; } bool C_PortalGhostRenderable::ShouldDrawForThisView( void ) { if ( portal_ghosts_disable.GetBool() ) return false; C_BaseEntity *pGhostedRenderable = m_hGhostedRenderable.Get(); if( pGhostedRenderable == NULL ) { return false; } float fInterpTime = GhostedRenderableOriginTime( pGhostedRenderable, gpGlobals->curtime ); if( (fInterpTime < m_fRenderableRange[0]) || (fInterpTime >= m_fRenderableRange[1]) ) return false; if ( m_bSourceIsBaseAnimating ) { C_Portal_Player *pViewPlayer = ToPortalPlayer( GetSplitScreenViewPlayer() ); C_Portal_Player *pHoldingPlayer = ToPortalPlayer( m_hHoldingPlayer.Get() ); if ( pHoldingPlayer && (pHoldingPlayer == pViewPlayer) && !pViewPlayer->ShouldDrawLocalPlayer() ) { if ( !pHoldingPlayer->IsAlive() ) { // Dead player uses a ragdoll to draw, so don't ghost the dead entity return false; } else if( g_pPortalRender->GetViewRecursionLevel() == 0 ) { if ( pHoldingPlayer->m_bEyePositionIsTransformedByPortal ) return false; C_PlayerHeldObjectClone *pClone = NULL; if ( m_bPlayerHeldClone ) { pClone = assert_cast< C_PlayerHeldObjectClone* >( m_hGhostedRenderable.Get() ); if( pClone && pClone->m_bOnOppositeSideOfPortal ) return false; } } else if ( g_pPortalRender->GetViewRecursionLevel() == 1 ) { C_PlayerHeldObjectClone *pClone = NULL; if ( m_bPlayerHeldClone ) { pClone = assert_cast< C_PlayerHeldObjectClone* >( m_hGhostedRenderable.Get() ); } if ( (!pHoldingPlayer->m_bEyePositionIsTransformedByPortal && (g_pPortalRender->GetCurrentViewEntryPortal() == m_pOwningPortal)) && !( pClone && pClone->m_bOnOppositeSideOfPortal ) ) return false; } } } return true; } int C_PortalGhostRenderable::DrawModel( int flags, const RenderableInstance_t &instance ) { if( !ShouldDrawForThisView() ) return 0; #if( DEBUG_GHOSTRENDERABLES == 1 ) if( m_iDebugColor[3] != 0 ) { //NDebugOverlay::BoxAngles( GetRenderOrigin(), m_hGhostedRenderable->CollisionProp()->OBBMins(), m_hGhostedRenderable->CollisionProp()->OBBMaxs(), GetRenderAngles(), m_iDebugColor[0], m_iDebugColor[1], m_iDebugColor[2], m_iDebugColor[3], 0.0f ); NDebugOverlay::Sphere( GetNetworkOrigin(), 5.0f, m_iDebugColor[0], m_iDebugColor[1], m_iDebugColor[2], true, 0.0f ); } #endif if ( m_bSourceIsBaseAnimating ) { return C_BaseAnimating::DrawModel( flags, instance ); } else { DrawBrushModelMode_t mode = DBM_DRAW_ALL; if ( flags & STUDIO_TWOPASS ) { mode = ( flags & STUDIO_TRANSPARENCY ) ? DBM_DRAW_TRANSLUCENT_ONLY : DBM_DRAW_OPAQUE_ONLY; } render->DrawBrushModelEx( m_hGhostedRenderable, (model_t *)m_hGhostedRenderable->GetModel(), GetRenderOrigin(), GetRenderAngles(), mode ); return 1; } return 0; } ModelInstanceHandle_t C_PortalGhostRenderable::GetModelInstance() { // HACK: This causes problems if the ghosted renderable is // has it's model instance destructed mid-render... this is currently // the case for the portalgun view model due to model switching // so we're not going to use the ghost's model instance for combat weapon ents. // This will only mean the decal state is wrong, the worldmodel doesn't get decals anyway. // Real fix would be to 'sync' the decal state between this and the ghosted ent, but // this will fix it for now. if ( m_hGhostedRenderable && (m_bCombatWeapon == false) && (m_bCombatWeaponWorldClone == false) ) return m_hGhostedRenderable->GetModelInstance(); return BaseClass::GetModelInstance(); } RenderableTranslucencyType_t C_PortalGhostRenderable::ComputeTranslucencyType( void ) { if ( m_hGhostedRenderable == NULL ) return RENDERABLE_IS_OPAQUE; return m_hGhostedRenderable->ComputeTranslucencyType(); } int C_PortalGhostRenderable::GetRenderFlags() { if( m_hGhostedRenderable == NULL ) return false; return m_hGhostedRenderable->GetRenderFlags(); } /*const model_t* C_PortalGhostRenderable::GetModel( ) const { if( m_hGhostedRenderable == NULL ) return NULL; return m_hGhostedRenderable->GetModel(); } int C_PortalGhostRenderable::GetBody() { if( m_hGhostedRenderable == NULL ) return 0; return m_hGhostedRenderable->GetBody(); }*/ void C_PortalGhostRenderable::GetColorModulation( float* color ) { if( m_hGhostedRenderable == NULL ) return; #if(DEBUG_GHOSTRENDERABLES == 1) if( color[3] != 0 ) { color[0] = m_iDebugColor[0] / 255.0f; color[1] = m_iDebugColor[1] / 255.0f; color[2] = m_iDebugColor[2] / 255.0f; return; } #endif return m_hGhostedRenderable->GetColorModulation( color ); } /*ShadowType_t C_PortalGhostRenderable::ShadowCastType() { if( m_hGhostedRenderable == NULL ) return SHADOWS_NONE; return m_hGhostedRenderable->ShadowCastType(); }*/ int C_PortalGhostRenderable::LookupAttachment( const char *pAttachmentName ) { if( m_hGhostedRenderable == NULL ) return -1; return m_hGhostedRenderable->LookupAttachment( pAttachmentName ); } /* int C_PortalGhostRenderable::GetSkin() { if( m_hGhostedRenderable == NULL ) return -1; return m_hGhostedRenderable->GetSkin(); } */ float *C_PortalGhostRenderable::GetRenderClipPlane( void ) { if( !ShouldDrawForThisView() ) //Fix for systems that do not support clip planes. The occluding depth box should be avoided if it's not going to be useful return NULL; if( m_pPortalExitRenderClipPlane && //have an exit portal special clip plane g_pPortalRender->IsRenderingPortal() && //rendering through a portal (g_pPortalRender->GetCurrentViewEntryPortal() == m_pOwningPortal) ) //we're being drawn on the exit side { return m_pPortalExitRenderClipPlane; } return m_pSharedRenderClipPlane; } //----------------------------------------------------------------------------- // Handle recording for the SFM //----------------------------------------------------------------------------- void C_PortalGhostRenderable::GetToolRecordingState( KeyValues *msg ) { VPROF_BUDGET( "C_PortalGhostRenderable::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); BaseClass::GetToolRecordingState( msg ); C_Portal_Player *pViewPlayer = ToPortalPlayer( GetSplitScreenViewPlayer() ); if ( m_hHoldingPlayer.Get() && m_hHoldingPlayer.Get() == pViewPlayer ) { msg->SetInt( "worldmodel", 2 ); //world model that should only draw in third person } } bool C_PortalGhostRenderable::ShouldCloneEntity( C_BaseEntity *pEntity, C_Portal_Base2D *pPortal, bool bUsePositionChecks ) { if( pEntity->IsEffectActive( EF_NODRAW ) ) return false; bool bIsMovable = false; C_BaseEntity *pMoveEntity = pEntity; MoveType_t moveType = MOVETYPE_NONE; //unmoveables and doors can never get halfway in the portal while ( pMoveEntity ) { moveType = pMoveEntity->GetMoveType(); if ( !( moveType == MOVETYPE_NONE || moveType == MOVETYPE_PUSH ) ) { bIsMovable = true; pMoveEntity = NULL; } else pMoveEntity = pMoveEntity->GetMoveParent(); } if ( !bIsMovable ) return false; if( (moveType == MOVETYPE_NOCLIP) && dynamic_cast(pEntity) != NULL ) //potentially "mobile" portals in p2 can use MOVETYPE_NOCLIP return false; Assert( dynamic_cast(pEntity) == NULL ); //should have been killed with (pEntity->GetMoveType() == MOVETYPE_NONE) check. Infinite recursion is infinitely bad. if( ToBaseViewModel(pEntity) ) return false; //avoid ghosting view models if( pEntity->IsRenderingWithViewModels() ) return false; //avoid ghosting anything that draws with viewmodels bool bActivePlayerWeapon = false; C_BaseCombatWeapon *pWeapon = ToBaseCombatWeapon( pEntity ); if ( pWeapon ) { C_Portal_Player *pPortalPlayer = ToPortalPlayer( pWeapon->GetOwner() ); if ( pPortalPlayer ) { if ( pWeapon->m_iState != WEAPON_IS_ACTIVE ) { return false; // don't ghost player owned non active weapons } else { bActivePlayerWeapon = true; } } } // Weapon is not teleportable, and we don't want to make that function use a dynamic cast to test for it, // so do the simple thing and just ignore this check for player-held weapons if( !bActivePlayerWeapon && !CPortal_Base2D_Shared::IsEntityTeleportable( pEntity ) ) return false; if( bUsePositionChecks ) { Vector ptEntCenter = pEntity->WorldSpaceCenter(); float fEntCenterDist = (pPortal->m_plane_Origin.normal.Dot( ptEntCenter ) - pPortal->m_plane_Origin.dist); if( fEntCenterDist < -5.0f ) { //view model held objects don't actually teleport to the other side of a portal when their center crosses the plane. //Alternate test is to ensure that a line drawn from the player center to the clone intersects the portal C_Portal_Player *pHoldingPlayer = (C_Portal_Player *)GetPlayerHoldingEntity( pEntity ); if( (pHoldingPlayer == NULL) || //not held !pHoldingPlayer->IsUsingVMGrab() || //not in ViewModel grab mode dynamic_cast(pEntity) ) //clones actually handle the teleportation correctly on the client. { return false; //entity is behind the portal, most likely behind the wall the portal is placed on } else { Vector vPlayerCenter = pHoldingPlayer->WorldSpaceCenter(); float fPlayerCenterDist = (pPortal->m_plane_Origin.normal.Dot( vPlayerCenter ) - pPortal->m_plane_Origin.dist); if( fPlayerCenterDist < 0.0f ) return false; float fTotalDist = fPlayerCenterDist - fEntCenterDist; if( fTotalDist == 0.0f ) //should be >= 5.0 at all times, but I've been bitten too many times by impossibly 0 cases return false; Vector vPlaneIntersect = (ptEntCenter * (fPlayerCenterDist/fTotalDist)) - (vPlayerCenter * (fEntCenterDist/fTotalDist)); Vector vPortalCenterToIntersect = vPlaneIntersect - pPortal->m_ptOrigin; if( (fabs( vPortalCenterToIntersect.Dot( pPortal->m_vRight ) ) > pPortal->GetHalfWidth()) || (fabs( vPortalCenterToIntersect.Dot( pPortal->m_vUp ) ) > pPortal->GetHalfHeight()) ) { return false; } //else intersects on the portal quad and the test passes } } if ( bActivePlayerWeapon ) { // Both the player AND the weapon must be in the portal hole. // // We test the player's collision AABB against the portal hole because it's guaranteed not to intersect // multiple adjacent portal holes at the same time in a degenerate case. // // We test the player's weapon hitbox against the portal hole because it doesn't have a good collision AABB // and because we'll only bother to even do this test if the player's AABB is acutally inside the portal hole. // // If we only test the weapon, we get false positives (phantom renderables) when its bounds span multiple adjacent // portal holes. if( !pPortal->m_PortalSimulator.EntityHitBoxExtentIsInPortalHole( pWeapon->GetOwner(), true /* bUseCollisionAABB */ ) || !pPortal->m_PortalSimulator.EntityHitBoxExtentIsInPortalHole( (C_BaseAnimating*)pEntity, false /* bUseCollisionAABB */ ) ) return false; } else if( pEntity->IsPlayer() ) { if( !pPortal->m_PortalSimulator.EntityHitBoxExtentIsInPortalHole( (C_BaseAnimating*)pEntity, true /* bUseCollisionAABB */ ) ) return false; } else { if( !pPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) ) return false; } } return true; } C_PortalGhostRenderable *C_PortalGhostRenderable::CreateGhostRenderable( C_BaseEntity *pEntity, C_Portal_Base2D *pPortal ) { Assert( ShouldCloneEntity( pEntity, pPortal, false ) ); C_BasePlayer *pPlayerOwner = NULL; bool bRenderableIsPlayer = pEntity->IsPlayer(); if ( bRenderableIsPlayer ) { pPlayerOwner = ToBasePlayer( pEntity ); } else { C_BaseCombatWeapon *pWeapon = ToBaseCombatWeapon( pEntity ); if ( pWeapon ) { C_Portal_Player *pOwningPlayer = ToPortalPlayer( pWeapon->GetOwner() ); if ( pOwningPlayer ) { pPlayerOwner = pOwningPlayer; } } else { C_PlayerHeldObjectClone *pClone = dynamic_cast< C_PlayerHeldObjectClone* >( pEntity ); if ( pClone ) { C_Portal_Player *pOwningPlayer = ToPortalPlayer( pClone->m_hPlayer ); if ( pOwningPlayer ) { pPlayerOwner = pOwningPlayer; } } } } #if( DEBUG_GHOSTRENDERABLES == 1 ) { Vector vTransformedOrigin; QAngle qTransformedAngles; VectorTransform( pEntity->GetRenderOrigin(), pPortal->m_matrixThisToLinked.As3x4(), vTransformedOrigin ); qTransformedAngles = TransformAnglesToWorldSpace( pEntity->GetRenderAngles(), pPortal->m_matrixThisToLinked.As3x4() ); NDebugOverlay::BoxAngles( vTransformedOrigin, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), qTransformedAngles, 0, 255, 0, 16, 0.0f ); } #endif C_PortalGhostRenderable *pNewGhost = new C_PortalGhostRenderable( pPortal, pEntity, pPortal->m_matrixThisToLinked, bRenderableIsPlayer ? pPortal->m_fGhostRenderablesClipForPlayer : pPortal->m_fGhostRenderablesClip, pPlayerOwner ); if( !pNewGhost->InitializeAsClientEntity( pEntity->GetModelName(), false ) ) { pNewGhost->Release(); return NULL; } Assert( pNewGhost ); if( pPlayerOwner ) { pNewGhost->SetOwnerEntity( pPlayerOwner ); } if( pNewGhost->m_pSharedRenderClipPlane == pPortal->m_fGhostRenderablesClipForPlayer ) { pNewGhost->m_pPortalExitRenderClipPlane = pPortal->m_fGhostRenderablesClip; } pPortal->m_GhostRenderables.AddToTail( pNewGhost ); { // HACK - I just copied the CClientTools::OnEntityCreated code here, // since the ghosts aren't really entities - they don't have an entindex, // they're not in the entitylist, and they get created during Simulate(), // which isn't valid for real entities, since it changes the simulate list // -jd if ( ToolsEnabled() && clienttools->IsInRecordingMode() ) { // Send deletion message to tool interface KeyValues *kv = new KeyValues( "created" ); HTOOLHANDLE h = clienttools->AttachToEntity( pNewGhost ); ToolFramework_PostToolMessage( h, kv ); kv->deleteThis(); } } g_pClientLeafSystem->DisableCachedRenderBounds( pNewGhost->RenderHandle(), true ); pNewGhost->PerFrameUpdate(); return pNewGhost; } C_PortalGhostRenderable *C_PortalGhostRenderable::CreateInversion( C_PortalGhostRenderable *pSrc, C_Portal_Base2D *pSourcePortal, float fTime ) { if( !(pSourcePortal && pSrc) ) return NULL; C_Portal_Base2D *pRemotePortal = pSourcePortal->m_hLinkedPortal.Get(); if( !pRemotePortal ) return NULL; C_BaseEntity *pRootEntity = pSrc->m_hGhostedRenderable; if( !pRootEntity ) return NULL; C_PortalGhostRenderable *pNewGhost = NULL; //use existing if we already have one for( int i = 0; i != pRemotePortal->m_GhostRenderables.Count(); ++i ) { if( pRemotePortal->m_GhostRenderables[i]->m_hGhostedRenderable == pRootEntity ) { pNewGhost = pRemotePortal->m_GhostRenderables[i]; //reset time based variables pNewGhost->m_fRenderableRange[0] = -FLT_MAX; pNewGhost->m_fRenderableRange[1] = FLT_MAX; pNewGhost->m_fNoTransformBeforeTime = -FLT_MAX; break; } } if( pNewGhost == NULL ) { pNewGhost = new C_PortalGhostRenderable( pRemotePortal, pSrc->m_hGhostedRenderable, pRemotePortal->m_matrixThisToLinked, pSrc->m_pSharedRenderClipPlane == pSourcePortal->m_fGhostRenderablesClipForPlayer ? pRemotePortal->m_fGhostRenderablesClipForPlayer : pRemotePortal->m_fGhostRenderablesClip, pSrc->m_hHoldingPlayer ); if( !pNewGhost->InitializeAsClientEntity( pRootEntity->GetModelName(), false ) ) { pNewGhost->Release(); return NULL; } if( pSrc->m_hHoldingPlayer ) { pNewGhost->SetOwnerEntity( pSrc->m_hHoldingPlayer ); } if( pSrc->m_pPortalExitRenderClipPlane != NULL ) { if( pSrc->m_pPortalExitRenderClipPlane == pSourcePortal->m_fGhostRenderablesClipForPlayer ) { pNewGhost->m_pPortalExitRenderClipPlane = pRemotePortal->m_fGhostRenderablesClipForPlayer; } else if( pSrc->m_pPortalExitRenderClipPlane == pSourcePortal->m_fGhostRenderablesClip ) { pNewGhost->m_pPortalExitRenderClipPlane = pRemotePortal->m_fGhostRenderablesClip; } else { Assert( false ); } } pRemotePortal->m_GhostRenderables.AddToTail( pNewGhost ); #if( DEBUG_GHOSTRENDERABLES == 1 ) { Vector vTransformedOrigin; QAngle qTransformedAngles; VectorTransform( pSrc->m_hGhostedRenderable->GetNetworkOrigin(), pRemotePortal->m_matrixThisToLinked.As3x4(), vTransformedOrigin ); qTransformedAngles = TransformAnglesToWorldSpace( pSrc->m_hGhostedRenderable->GetNetworkAngles(), pRemotePortal->m_matrixThisToLinked.As3x4() ); NDebugOverlay::BoxAngles( vTransformedOrigin, pSrc->m_hGhostedRenderable->CollisionProp()->OBBMins(), pSrc->m_hGhostedRenderable->CollisionProp()->OBBMaxs(), qTransformedAngles, 0, 255, 0, 16, 0.0f ); } #endif } //these times are in effective origin interpolator time m_hGhostedRenderable->GetOriginInterpolator().GetInterpolatedTime( m_hGhostedRenderable->GetEffectiveInterpolationCurTime() ) pNewGhost->m_fRenderableRange[0] = fTime; pNewGhost->m_fRenderableRange[1] = FLT_MAX; pSrc->m_fRenderableRange[0] = -FLT_MAX; pSrc->m_fRenderableRange[1] = fTime; pNewGhost->m_fNoTransformBeforeTime = fTime; pSrc->m_fDisablePositionChecksUntilTime = fTime + (TICK_INTERVAL * 2.0f) + pSrc->m_hGhostedRenderable->GetOriginInterpolator().GetInterpolationAmount(); pNewGhost->m_fDisablePositionChecksUntilTime = pSrc->m_fDisablePositionChecksUntilTime; g_pClientLeafSystem->DisableCachedRenderBounds( pNewGhost->RenderHandle(), true ); pNewGhost->PerFrameUpdate(); return pNewGhost; }