//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "baseprojectedentity_shared.h" #if defined( GAME_DLL ) # include "baseprojector.h" # include "info_placement_helper.h" # include "portal_gamestats.h" # include "weapon_portalgun_shared.h" #else typedef C_BaseProjectedEntity CBaseProjectedEntity; #include "prediction.h" #include "c_baseprojector.h" #endif // offset away from the projector, so the trace doesn't start in solid #define PROJECTEDENTITY_TRACE_OFFSET 25.f void UTil_ProjectedEntity_Trace_Filter( CTraceFilterSimpleClassnameList *traceFilter ) { traceFilter->AddClassnameToIgnore( "prop_physics" ); traceFilter->AddClassnameToIgnore( "func_physbox" ); traceFilter->AddClassnameToIgnore( "simple_physics_brush" ); /*traceFilter->AddClassnameToIgnore( "prop_weighted_cube" ); traceFilter->AddClassnameToIgnore( "npc_portal_turret_floor" ); traceFilter->AddClassnameToIgnore( "prop_energy_ball" ); traceFilter->AddClassnameToIgnore( "npc_security_camera" ); traceFilter->AddClassnameToIgnore( "simple_physics_prop" ); traceFilter->AddClassnameToIgnore( "prop_ragdoll" ); traceFilter->AddClassnameToIgnore( "prop_glados_core" ); traceFilter->AddClassnameToIgnore( "player" ); traceFilter->AddClassnameToIgnore( "projected_wall_entity" ); traceFilter->AddClassnameToIgnore( "prop_paint_bomb" ); traceFilter->AddClassnameToIgnore( "prop_exploding_futbol" ); traceFilter->AddClassnameToIgnore( "prop_wall_projector" ); traceFilter->AddClassnameToIgnore( "projected_wall_entity" ); traceFilter->AddClassnameToIgnore( "projected_tractor_beam_entity" ); traceFilter->AddClassnameToIgnore( "trigger_tractorbeam" ); traceFilter->AddClassnameToIgnore( "physicsshadowclone" ); traceFilter->AddClassnameToIgnore( "prop_floor_button" );*/ } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- bool CBaseProjectedEntity::IsHittingPortal( Vector* pOutOrigin, QAngle* pOutAngles, CPortal_Base2D** pOutPortal ) { #if defined( GAME_DLL ) QAngle qAngles = GetLocalAngles(); #else QAngle qAngles = GetNetworkAngles(); #endif // Get current orientation Vector vForward, vecRight, vecUp; AngleVectors( qAngles, &vForward ); Vector mins, maxs; GetProjectionExtents( mins, maxs ); #if defined( GAME_DLL ) Vector vStart = GetLocalOrigin(); #else Vector vStart = GetNetworkOrigin(); #endif Ray_t ray; // Projected ents keep themselves off the walls slightly, so move this up double that ammount to make // sure we hit any potential portal. Vector rayPos = vStart + PROJECTEDENTITY_TRACE_OFFSET * vForward;// GetEndPoint() - 5.f * vForward; // back up the start pos of the ray a bit to make sure that we miss anything at the endpoint ray.Init( rayPos, rayPos + ( vForward * MAX_TRACE_LENGTH ), mins, maxs ); float flPortalTraceFraction = 1.0f; trace_t worldTrace; CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE ); UTil_ProjectedEntity_Trace_Filter( &traceFilter ); UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &worldTrace ); CPortal_Base2D* pHitPortal = UTIL_Portal_FirstAlongRay( ray, flPortalTraceFraction ); if ( pOutOrigin ) { *pOutOrigin = worldTrace.endpos; } if ( pOutAngles ) { *pOutAngles = qAngles; } // trace hit world brush before hitting portal // we need the threshold because the difference of the two fractions is very small in a valid case float flTraceThreshold = 0.0001f; if ( flPortalTraceFraction - worldTrace.fraction > flTraceThreshold ) { return false; } if ( pOutPortal ) { *pOutPortal = pHitPortal; } if ( !pHitPortal ) { return false; } // We only care about portals that are currently linked (we don't project through anything else) if ( pHitPortal->IsActivedAndLinked() == false ) return false; float flIntersectionFraction = UTIL_IntersectRayWithPortal( ray, pHitPortal ); Vector vHitPoint = ray.m_Start + ray.m_Delta*flIntersectionFraction; // Wall hit a portal, reorient and project on the other side VMatrix matToLinked = pHitPortal->MatrixThisToLinked(); Vector vNewWallOrigin; UTIL_Portal_PointTransform( matToLinked, vHitPoint, vNewWallOrigin ); QAngle vNewAngles; UTIL_Portal_AngleTransform( matToLinked, qAngles, vNewAngles ); Vector vNewForward; AngleVectors( vNewAngles, &vNewForward, NULL, NULL ); // Move far enough in front of the portal not to be co-planar // (will cause traces to start solid and have z fighting issues on the renderable) vNewWallOrigin += vNewForward * PROJECTION_END_POINT_EPSILON; if ( pOutAngles ) { *pOutAngles = vNewAngles; } if ( pOutOrigin ) { *pOutOrigin = vNewWallOrigin; } return true; } #if defined( CLIENT_DLL ) ConVar cl_predict_projected_entities( "cl_predict_projected_entities", "1" ); #endif //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::RecursiveProjection( bool bShouldSpawn, CBaseProjector *pParentProjector, CPortal_Base2D *pExitPortal, const Vector &vProjectOrigin, const QAngle &qProjectAngles, int iRemainingProjections, bool bDisablePlacementHelper ) { #if defined( CLIENT_DLL ) if( !prediction->InPrediction() || !GetPredictable() ) return; #endif AddEffects( EF_NOINTERP ); #if 0 Vector vFlooredPosition; //HACKHACK: the inputs vary just ever so slightly from client/server. Hopefully flooring them will keep them in sync vFlooredPosition.x = floor( vProjectOrigin.x * 512.0f ) / 512.0f; vFlooredPosition.y = floor( vProjectOrigin.y * 512.0f ) / 512.0f; vFlooredPosition.z = floor( vProjectOrigin.z * 512.0f ) / 512.0f; #else Vector vFlooredPosition = vProjectOrigin; #endif #if defined( GAME_DLL ) OnPreProjected(); #endif Vector vOldOrigin = GetAbsOrigin(); QAngle qModAngles; //SendProxy_Angles will perform this operation on the angles, making them differ by either extremely small values, or 360 degrees (-90 == 270 angularly, but not from a precision standpoint) qModAngles.x = anglemod( qProjectAngles.x ); qModAngles.y = anglemod( qProjectAngles.y ); qModAngles.z = anglemod( qProjectAngles.z ); #if defined( CLIENT_DLL ) Assert( bShouldSpawn == false ); SetNetworkOrigin( vFlooredPosition ); SetNetworkAngles( qModAngles ); #else SetOwnerEntity( pParentProjector ); SetLocalOrigin( vFlooredPosition ); SetLocalAngles( qModAngles ); #endif //EASY_DIFFPRINT( this, "CBaseProjectedEntity::RecursiveProjection() %f %f %f\n", XYZ( vFlooredPosition ) ); m_iMaxRemainingRecursions = iRemainingProjections; #if defined( GAME_DLL ) if( bShouldSpawn ) { DispatchSpawn( this ); } else #endif { FindProjectedEndpoints(); } if( pExitPortal ) { SetSourcePortal( pExitPortal ); #if defined( GAME_DLL ) && !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) CWeaponPortalgun *pPortalGun = dynamic_cast( pExitPortal->m_hPlacedBy.Get() ); if ( pPortalGun != NULL ) { Vector vecForward, vecRight, vecUp; AngleVectors( qModAngles, &vecForward, &vecRight, &vecUp ); g_PortalGameStats.Event_TractorBeam_Project( pExitPortal->m_ptOrigin, vecForward , ToPortalPlayer( pPortalGun->GetOwner() ) ); } #endif } OnProjected(); #if defined( CLIENT_DLL ) if( cl_predict_projected_entities.GetBool() == false ) return; #endif // If this hits a portal, reorient through it. // We create a new ent to do this. if ( iRemainingProjections > 1 ) { // If there is a portal within a small distance of our end point, reorient CPortal_Base2D* pHitPortal = NULL; Vector vNewProjectedEntityOrigin; QAngle qNewProjectedEntityAngles; bool bIsHittingPortal = IsHittingPortal( &vNewProjectedEntityOrigin, &qNewProjectedEntityAngles, &pHitPortal ); SetHitPortal( pHitPortal ); if ( bIsHittingPortal && pHitPortal && pHitPortal->IsActivedAndLinked() ) { CPortal_Base2D *pNewExitPortal = pHitPortal->m_hLinkedPortal.Get(); bool bCreateNew = (m_hChildSegment.Get() == NULL); #if defined( GAME_DLL ) //TODO: Set dormant on the client if( bCreateNew ) { m_hChildSegment = CreateNewProjectedEntity(); } #else if( !bCreateNew ) #endif { m_hChildSegment.Get()->RecursiveProjection( bCreateNew, pParentProjector, pNewExitPortal, vNewProjectedEntityOrigin, qNewProjectedEntityAngles, iRemainingProjections - 1, bDisablePlacementHelper ); } } // FIXME: Bring this back for DLC2 //else if ( engine->HasPaintmap() ) //{ // //TestForReflectPaint(); //} else if( m_hChildSegment.Get() != NULL ) { #if defined( GAME_DLL ) //TODO: Set dormant on the client UTIL_Remove( m_hChildSegment.Get() ); m_hChildSegment = NULL; #endif } } #if defined( GAME_DLL ) if( !bDisablePlacementHelper ) { m_bCreatePlacementHelper = true; bool bCreatePlacement = m_hPlacementHelper.Get() == NULL; if( bCreatePlacement ) { m_hPlacementHelper = (CInfoPlacementHelper *) CreateEntityByName( "info_placement_helper" ); } PlacePlacementHelper( m_hPlacementHelper ); if( bCreatePlacement ) { DispatchSpawn( m_hPlacementHelper ); } } else { m_bCreatePlacementHelper = false; } PhysicsTouchTriggers( NULL ); #endif // Projected entities should probably reflect in water because they are noticeable AddEffects( EF_MARKED_FOR_FAST_REFLECTION ); } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::TestForProjectionChanges( void ) { #if defined( CLIENT_DLL ) if( cl_predict_projected_entities.GetBool() == false ) return; #endif Vector vNewPosition; QAngle qNewAngles; CPortal_Base2D* pHitPortal = NULL; bool bIsHittingPortal = IsHittingPortal( &vNewPosition, &qNewAngles, &pHitPortal ); CBaseProjectedEntity *pChild = m_hChildSegment.Get(); //if( pChild ) //{ // EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::TestForProjectionChanges() %i %s %i", entindex(), bIsHittingPortal ? "true" : "false", pHitPortal ? pHitPortal->entindex() : -1 ); //} // Lost the portal we were hitting: Fizzle all children if ( !bIsHittingPortal || (pHitPortal && !pHitPortal->IsActivedAndLinked()) ) { SetHitPortal( NULL ); #if defined( GAME_DLL ) //TODO: Set dormant on the client float flDistSqr = GetEndPoint().DistToSqr( vNewPosition ); if ( flDistSqr > 0.1f ) { FindProjectedEndpoints(); OnProjected(); } #endif // FIXME: Bring this back for DLC2 // check for reflect paint /*if ( engine->HasPaintmap() ) { TestForReflectPaint(); }*/ #if defined( GAME_DLL ) /*else*/ if( pChild ) { UTIL_Remove( pChild ); m_hChildSegment = NULL; } #endif } #if defined( GAME_DLL ) else if( pHitPortal->IsActivedAndLinked() && (DidRedirectionPortalMove( pHitPortal ) || ((pChild == NULL) && (m_iMaxRemainingRecursions > 0))) ) #else else if( pHitPortal->IsActivedAndLinked() && DidRedirectionPortalMove( pHitPortal ) && (pChild != NULL) ) #endif { #if defined( CLIENT_DLL ) if( GetPredictable() ) #endif { Vector vPrevStart = GetStartPoint(); Vector vPrevEnd = GetEndPoint(); FindProjectedEndpoints(); if( (vPrevStart != GetStartPoint()) || (vPrevEnd != GetEndPoint()) ) { OnProjected(); } SetHitPortal( pHitPortal ); } //reproject child portal bool bCreateNew = (pChild == NULL); #if defined( GAME_DLL ) if( bCreateNew ) { m_hChildSegment = pChild = CreateNewProjectedEntity(); } #else if( !bCreateNew && pChild->GetPredictable() ) #endif { pChild->RecursiveProjection( bCreateNew, (CBaseProjector *)GetOwnerEntity(), pHitPortal->m_hLinkedPortal.Get(), vNewPosition, qNewAngles, m_iMaxRemainingRecursions - 1, m_bCreatePlacementHelper ); } } #if defined( GAME_DLL ) //server propogates down the chain. Client evaluates each entity separately since it's not guaranteed to know about them all else if( pChild ) { pChild->TestForProjectionChanges(); } #endif } //-------------------------------------------------------------------------------------------------- // Test for perturbation of the portal this projected entity is redirecting through // if true, this entity's owning projector will rebuild all projected ents after this one //-------------------------------------------------------------------------------------------------- bool CBaseProjectedEntity::DidRedirectionPortalMove( CPortal_Base2D* pPortal ) { if ( !pPortal ) return true; // remote portal must exist to project through if ( pPortal->IsActivedAndLinked() == false ) return true; if ( pPortal != m_hHitPortal.Get() ) return true; CBaseProjectedEntity *pChild = m_hChildSegment; if( !pChild ) return true; // close portal moved if ( VectorsAreEqual( pChild->m_vecSourcePortalRemoteCenter, pPortal->m_ptOrigin ) == false ) { //EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::DidRedirectionPortalMove() hit portal moved" ); return true; } // close portal rotated if ( QAnglesAreEqual( pChild->m_vecSourcePortalRemoteAngle, pPortal->m_qAbsAngle ) == false ) { return true; } //EASY_DIFFPRINT( pChild, "%f %f %f %f %f %f", XYZ( (Vector)pChild->m_vecSourcePortalCenter ), XYZ( pPortal->m_hLinkedPortal->m_ptOrigin ) ); // remote portal moved if ( VectorsAreEqual( pChild->m_vecSourcePortalCenter, pPortal->m_hLinkedPortal->m_ptOrigin ) == false ) { //EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::DidRedirectionPortalMove() remote portal moved" ); return true; } // remote portal rotated if ( QAnglesAreEqual( pChild->m_vecSourcePortalAngle, pPortal->m_hLinkedPortal->m_qAbsAngle ) == false ) { return true; } return false; } //-------------------------------------------------------------------------------------------------- // Project from origin to solid in the direction of our forward //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::FindProjectedEndpoints( void ) { #if defined( GAME_DLL ) QAngle qAngles = GetLocalAngles(); #else QAngle qAngles = GetNetworkAngles(); #endif // Get current orientation Vector vecForward, vecRight, vecUp; AngleVectors( qAngles, &vecForward, &vecRight, &vecUp ); Vector mins, maxs; GetProjectionExtents( mins, maxs ); #if defined( GAME_DLL ) Vector vStart = GetLocalOrigin(); #else Vector vStart = GetNetworkOrigin(); #endif Vector vRayPos = vStart + PROJECTEDENTITY_TRACE_OFFSET * vecForward; Ray_t ray; ray.Init( vRayPos, vRayPos + vecForward*PROJECTOR_MAX_LENGTH, mins, maxs ); trace_t tr; CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE ); UTil_ProjectedEntity_Trace_Filter( &traceFilter ); UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); // Should up the max trace dist if this hits Assert ( tr.DidHit() ); m_vecStartPoint = vStart; m_vecEndPoint = tr.endpos + tr.plane.normal * (PROJECTION_END_POINT_EPSILON); // Move a tiny bit off the hit surface so there's no physical overlap } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::SetHitPortal( CPortal_Base2D* pPortal ) { m_hHitPortal = pPortal; if ( pPortal ) { Assert( pPortal->IsActivedAndLinked() ); if ( pPortal->IsActivedAndLinked() ) { #if defined( GAME_DLL ) // Listen for this portal to move //pPortal->AddPortalEventListener( this ); #endif } } } CPortal_Base2D* CBaseProjectedEntity::GetHitPortal( void ) { return m_hHitPortal.Get(); } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::SetSourcePortal( CPortal_Base2D* pPortal ) { Assert( pPortal && pPortal->IsActivedAndLinked() ); m_hSourcePortal.Set( pPortal ); #if defined( CLIENT_DLL ) SetPredictionEligible( pPortal != NULL ); #endif if( pPortal ) { m_vecSourcePortalCenter = pPortal->m_ptOrigin; m_vecSourcePortalRemoteCenter = pPortal->m_hLinkedPortal->m_ptOrigin; m_vecSourcePortalAngle = pPortal->m_qAbsAngle; m_vecSourcePortalRemoteAngle = pPortal->m_hLinkedPortal->m_qAbsAngle; } else { m_vecSourcePortalCenter = vec3_origin; m_vecSourcePortalRemoteCenter = vec3_origin; m_vecSourcePortalAngle = vec3_angle; m_vecSourcePortalRemoteAngle = vec3_angle; } if( pPortal && pPortal->GetSimulatingPlayer() ) { SetPlayerSimulated( pPortal->GetSimulatingPlayer() ); } else { UnsetPlayerSimulated(); } } CPortal_Base2D* CBaseProjectedEntity::GetSourcePortal( void ) { return m_hSourcePortal.Get(); } //-------------------------------------------------------------------------------------------------- // Specify the extents to use for the projection trace //-------------------------------------------------------------------------------------------------- void CBaseProjectedEntity::GetProjectionExtents( Vector &outMins, Vector &outMaxs ) { outMins = outMaxs = vec3_origin; } void CBaseProjectedEntity::OnProjected( void ) { AddEffects( EF_NOINTERP ); #if defined( CLIENT_DLL ) SetNetworkOrigin( GetStartPoint() ); PreDataChanged.vStartPoint = GetStartPoint(); PreDataChanged.vEndPoint = GetEndPoint(); PreDataChanged.qAngles = GetNetworkAngles(); #endif } void CBaseProjectedEntity::TestForReflectPaint( void ) { Ray_t ray; // make ray twice longer than the projected length, so the trace will actually hit something ray.Init( GetStartPoint(), GetStartPoint() + 2.f * ( GetEndPoint() - GetStartPoint() ) ); CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE ); UTil_ProjectedEntity_Trace_Filter( &traceFilter ); trace_t tr; UTIL_ClearTrace( tr ); UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); Vector vDir, vNewProjectedEntityOrigin; if ( UTIL_Paint_Reflect( tr, vNewProjectedEntityOrigin, vDir ) ) { // rotate psuedo up vector Vector vOldDir, vOldUp; GetVectors( &vOldDir, NULL, &vOldUp ); Vector vRotAxis = CrossProduct( vOldDir, vDir ); float flAngleBetween = RAD2DEG( acos( clamp( DotProduct( vOldDir, vDir ), -1.f, 1.f ) ) ); matrix3x4_t matRotation; MatrixBuildRotationAboutAxis( vRotAxis, flAngleBetween, matRotation ); Vector vNewUp; VectorRotate( vOldUp, matRotation, vNewUp ); QAngle qNewProjectedEntityAngles; VectorAngles( vDir, vNewUp, qNewProjectedEntityAngles ); //reproject child portal bool bCreateNew = (m_hChildSegment.Get() == NULL); #if defined( GAME_DLL ) if( bCreateNew ) { m_hChildSegment = CreateNewProjectedEntity(); } #else if( !bCreateNew ) #endif { m_hChildSegment.Get()->RecursiveProjection( bCreateNew, (CBaseProjector *)GetOwnerEntity(), NULL, vNewProjectedEntityOrigin, qNewProjectedEntityAngles, m_iMaxRemainingRecursions - 1, m_bCreatePlacementHelper ); } } #if defined( GAME_DLL ) else if ( m_hChildSegment.Get() != NULL ) { UTIL_Remove( m_hChildSegment.Get() ); m_hChildSegment = NULL; } #endif }