summaryrefslogtreecommitdiff
path: root/game/server/portal/portal_placement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/portal/portal_placement.cpp')
-rw-r--r--game/server/portal/portal_placement.cpp1337
1 files changed, 1337 insertions, 0 deletions
diff --git a/game/server/portal/portal_placement.cpp b/game/server/portal/portal_placement.cpp
new file mode 100644
index 0000000..1a5dcef
--- /dev/null
+++ b/game/server/portal/portal_placement.cpp
@@ -0,0 +1,1337 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+#include "portal_placement.h"
+#include "portal_shareddefs.h"
+#include "prop_portal_shared.h"
+#include "func_noportal_volume.h"
+#include "BasePropDoor.h"
+#include "collisionutils.h"
+#include "decals.h"
+#include "physicsshadowclone.h"
+
+
+#define MAXIMUM_BUMP_DISTANCE ( ( PORTAL_HALF_WIDTH * 2.0f ) * ( PORTAL_HALF_WIDTH * 2.0f ) + ( PORTAL_HALF_HEIGHT * 2.0f ) * ( PORTAL_HALF_HEIGHT * 2.0f ) ) / 2.0f
+
+
+struct CPortalCornerFitData
+{
+ trace_t trCornerTrace;
+ Vector ptIntersectionPoint;
+ Vector vIntersectionDirection;
+ Vector vBumpDirection;
+ bool bCornerIntersection;
+ bool bSoftBump;
+};
+
+
+CUtlVector<CBaseEntity *> g_FuncBumpingEntityList;
+bool g_bBumpedByLinkedPortal;
+
+
+ConVar sv_portal_placement_debug ("sv_portal_placement_debug", "0", FCVAR_REPLICATED );
+ConVar sv_portal_placement_never_bump ("sv_portal_placement_never_bump", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+
+bool IsMaterialInList( const csurface_t &surface, char *g_ppszMaterials[] )
+{
+ char szLowerName[ 256 ];
+ Q_strcpy( szLowerName, surface.name );
+ Q_strlower( szLowerName );
+
+ int iMaterial = 0;
+
+ while ( g_ppszMaterials[ iMaterial ] )
+ {
+ if ( Q_strstr( szLowerName, g_ppszMaterials[ iMaterial ] ) )
+ return true;
+
+ ++iMaterial;
+ }
+
+ return false;
+}
+
+bool IsNoPortalMaterial( const csurface_t &surface )
+{
+ if ( surface.flags & SURF_NOPORTAL )
+ return true;
+
+ const surfacedata_t *pdata = physprops->GetSurfaceData( surface.surfaceProps );
+ if ( pdata->game.material == CHAR_TEX_GLASS )
+ return true;
+
+ // Skipping all studio models
+ if ( StringHasPrefix( surface.name, "**studio**" ) )
+ return true;
+
+ return false;
+}
+
+bool IsPassThroughMaterial( const csurface_t &surface )
+{
+ if ( surface.flags & SURF_SKY )
+ return true;
+
+ if ( IsMaterialInList( surface, g_ppszPortalPassThroughMaterials ) )
+ return true;
+
+ return false;
+}
+
+
+void TracePortals( const CProp_Portal *pIgnorePortal, const Vector &vForward, const Vector &vStart, const Vector &vEnd, trace_t &tr )
+{
+ UTIL_ClearTrace( tr );
+
+ Ray_t ray;
+ ray.Init( vStart, vEnd );
+
+ trace_t trTemp;
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ UTIL_IntersectRayWithPortalOBBAsAABB( pTempPortal, ray, &trTemp );
+
+ if ( trTemp.fraction < 1.0f && trTemp.fraction < tr.fraction )
+ {
+ tr = trTemp;
+ }
+ }
+ }
+ }
+}
+
+bool TraceBumpingEntities( const Vector &vStart, const Vector &vEnd, trace_t &tr )
+{
+ UTIL_ClearTrace( tr );
+
+ // We use this so portal bumpers can't squeeze a portal into not fitting
+ bool bClosestIsSoftBumper = false;
+
+ // Trace to the surface to see if there's a rotating door in the way
+ CBaseEntity *list[1024];
+
+ Ray_t ray;
+ ray.Init( vStart, vEnd );
+
+ int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, 0 );
+
+ for ( int i = 0; i < nCount; i++ )
+ {
+ trace_t trTemp;
+ UTIL_ClearTrace( trTemp );
+
+ bool bSoftBumper = false;
+
+ if ( FClassnameIs( list[i], "func_portal_bumper" ) )
+ {
+ bSoftBumper = true;
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+ if ( trTemp.startsolid )
+ {
+ trTemp.fraction = 1.0f;
+ }
+ }
+ else if ( FClassnameIs( list[i], "trigger_portal_cleanser" ) )
+ {
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+ if ( trTemp.startsolid )
+ {
+ trTemp.fraction = 1.0f;
+ }
+ }
+ else if ( FClassnameIs( list[i], "func_noportal_volume" ) )
+ {
+ if ( static_cast<CFuncNoPortalVolume*>( list[i] )->IsActive() )
+ {
+ enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
+
+ // Bump by an extra 2 units so that the portal isn't touching the no portal volume
+ Vector vDelta = trTemp.endpos - trTemp.startpos;
+ float fLength = VectorNormalize( vDelta ) - 2.0f;
+ if ( fLength < 0.0f )
+ fLength = 0.0f;
+ trTemp.fraction = fLength / ray.m_Delta.Length();
+ trTemp.endpos = trTemp.startpos + vDelta * fLength;
+ }
+ else
+ trTemp.fraction = 1.0f;
+ }
+ else if( FClassnameIs( list[i], "prop_door_rotating" ) )
+ {
+ // Check more precise door collision
+ CBasePropDoor *pRotatingDoor = static_cast<CBasePropDoor *>( list[i] );
+
+ pRotatingDoor->TestCollision( ray, 0, trTemp );
+ }
+
+ // If this is the closest and has only bumped once (for soft bumpers)
+ if ( trTemp.fraction < tr.fraction && ( !bSoftBumper || !g_FuncBumpingEntityList.HasElement( list[i] ) ) )
+ {
+ tr = trTemp;
+ bClosestIsSoftBumper = bSoftBumper;
+ }
+ }
+
+ return bClosestIsSoftBumper;
+}
+
+bool TracePortalCorner( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const Vector &vCorner, const Vector &vForward, int iPlacedBy, ITraceFilter *pTraceFilterPortalShot, trace_t &tr, bool &bSoftBump )
+{
+ Vector vOriginToCorner = vCorner - vOrigin;
+
+ // Check for surface edge
+ trace_t trSurfaceEdge;
+ UTIL_TraceLine( vOrigin - vForward, vCorner - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ float fTotalFraction = trSurfaceEdge.fractionleftsolid;
+
+ while ( trSurfaceEdge.startsolid && trSurfaceEdge.fractionleftsolid > 0.0f && fTotalFraction < 1.0f )
+ {
+ UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vCorner + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ fTotalFraction += trSurfaceEdge.fractionleftsolid + 0.05f;
+ }
+ }
+
+ if ( fTotalFraction < 1.0f )
+ {
+ UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vOrigin - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
+
+ if ( trSurfaceEdge.startsolid )
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+ else
+ {
+ trSurfaceEdge.fraction = fTotalFraction;
+ trSurfaceEdge.plane.normal = -trSurfaceEdge.plane.normal;
+ }
+ }
+ else
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+ }
+ else
+ {
+ trSurfaceEdge.fraction = 1.0f;
+ }
+
+ // Check for enclosing wall
+ trace_t trEnclosingWall;
+ UTIL_TraceLine( vOrigin + vForward, vCorner + vForward, MASK_SOLID_BRUSHONLY|CONTENTS_MONSTER, pTraceFilterPortalShot, &trEnclosingWall );
+
+ if ( trSurfaceEdge.fraction < trEnclosingWall.fraction )
+ {
+ trEnclosingWall.fraction = trSurfaceEdge.fraction;
+ trEnclosingWall.plane.normal = trSurfaceEdge.plane.normal;
+ }
+
+ trace_t trPortal;
+ trace_t trBumpingEntity;
+
+ if ( iPlacedBy != PORTAL_PLACED_BY_FIXED )
+ TracePortals( pIgnorePortal, vForward, vOrigin + vForward, vCorner + vForward, trPortal );
+ else
+ UTIL_ClearTrace( trPortal );
+
+ bool bSoftBumper = TraceBumpingEntities( vOrigin + vForward, vCorner + vForward, trBumpingEntity );
+
+ if ( trEnclosingWall.fraction >= 1.0f && trPortal.fraction >= 1.0f && trBumpingEntity.fraction >= 1.0f )
+ {
+ UTIL_ClearTrace( tr );
+ return false;
+ }
+
+ if ( trEnclosingWall.fraction <= trPortal.fraction && trEnclosingWall.fraction <= trBumpingEntity.fraction )
+ {
+ tr = trEnclosingWall;
+ bSoftBump = false;
+ }
+ else if ( trPortal.fraction <= trEnclosingWall.fraction && trPortal.fraction <= trBumpingEntity.fraction )
+ {
+ tr = trPortal;
+ g_bBumpedByLinkedPortal = true;
+ bSoftBump = false;
+ }
+ else if ( !trBumpingEntity.startsolid && trBumpingEntity.fraction <= trEnclosingWall.fraction && trBumpingEntity.fraction <= trPortal.fraction )
+ {
+ tr = trBumpingEntity;
+ bSoftBump = bSoftBumper;
+ }
+ else
+ {
+ UTIL_ClearTrace( tr );
+ return false;
+ }
+
+ return true;
+}
+
+Vector FindBumpVectorInCorner( const Vector &ptCorner1, const Vector &ptCorner2, const Vector &ptIntersectionPoint1, const Vector &ptIntersectionPoint2, const Vector &vIntersectionDirection1, const Vector &vIntersectionDirection2, const Vector &vIntersectionBumpDirection1, const Vector &vIntersectionBumpDirection2 )
+{
+ Vector ptClosestSegment1, ptClosestSegment2;
+ float fT1, fT2;
+
+ CalcLineToLineIntersectionSegment( ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1,
+ ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2,
+ &ptClosestSegment1, &ptClosestSegment2, &fT1, &fT2 );
+
+ Vector ptLineIntersection = ( ptClosestSegment1 + ptClosestSegment2 ) * 0.5f;
+
+ // The 2 corner trace intersections and the intersection of those lines makes a triangle.
+ // We want to make a similar triangle where the base is large enough to fit the edge of the portal
+
+ // Get the the small triangle's legs and leg lengths
+ Vector vShortLeg = ptIntersectionPoint1 - ptLineIntersection;
+ Vector vShortLeg2 = ptIntersectionPoint2 - ptLineIntersection;
+
+ float fShortLegLength = vShortLeg.Length();
+ float fShortLeg2Length = vShortLeg2.Length();
+
+ if ( fShortLegLength == 0.0f || fShortLeg2Length == 0.0f )
+ {
+ // FIXME: Our triangle is actually a point or a line, so there's nothing we can do
+ return vec3_origin;
+ }
+
+ // Normalized legs
+ vShortLeg /= fShortLegLength;
+ vShortLeg2 /= fShortLeg2Length;
+
+ // Check if corners are aligned with one of the legs
+ Vector vCornerToCornerNorm = ptCorner2 - ptCorner1;
+ VectorNormalize( vCornerToCornerNorm );
+
+ float fPortalEdgeDotLeg = vCornerToCornerNorm.Dot( vShortLeg );
+ float fPortalEdgeDotLeg2 = vCornerToCornerNorm.Dot( vShortLeg2 );
+
+ if ( fPortalEdgeDotLeg < -0.9999f || fPortalEdgeDotLeg > 0.9999f || fPortalEdgeDotLeg2 < -0.9999f || fPortalEdgeDotLeg2 > 0.9999f )
+ {
+ // Do a one corner bump with corner 1
+ float fBumpDistance1 = CalcDistanceToLine( ptCorner1, ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1 );
+
+ fBumpDistance1 += PORTAL_BUMP_FORGIVENESS;
+
+ // Do a one corner bump with corner 2
+ float fBumpDistance2 = CalcDistanceToLine( ptCorner2, ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2 );
+
+ fBumpDistance2 += PORTAL_BUMP_FORGIVENESS;
+
+ return vIntersectionBumpDirection1 * fBumpDistance1 + vIntersectionBumpDirection2 * fBumpDistance2;
+ }
+
+ float fLegsDot = vShortLeg.Dot( vShortLeg2 );
+
+ // Need to know if the triangle is pointing toward the portal or away from the portal
+ /*bool bPointingTowardPortal = true;
+
+ Vector vLineIntersectionToCornerNorm = ptCorner1 - ptLineIntersection;
+ VectorNormalize( vLineIntersectionToCornerNorm );
+
+ if ( vLineIntersectionToCornerNorm.Dot( vShortLeg2 ) < fLegsDot )
+ {
+ bPointingTowardPortal = false;
+ }
+
+ if ( !bPointingTowardPortal )*/
+ {
+ // Get the small triangle's base length
+ float fLongBaseLength = ptCorner1.DistTo( ptCorner2 );
+
+ // Get the large triangle's base length
+ float fShortLeg2Angle = acosf( vCornerToCornerNorm.Dot( -vShortLeg ) );
+ float fShortBaseAngle = acosf( fLegsDot );
+ float fShortLegAngle = M_PI_F - fShortBaseAngle - fShortLeg2Angle;
+
+ if ( sinf( fShortLegAngle ) == 0.0f )
+ {
+ return Vector( 1000.0f, 1000.0f, 1000.0f );
+ }
+
+ float fShortBaseLength = sinf( fShortBaseAngle ) * ( fShortLegLength / sinf( fShortLegAngle ) );
+
+ // Avoid divide by zero
+ if ( fShortBaseLength == 0.0f )
+ {
+ return Vector( 0.0f, 0.0f, 0.0f );
+ }
+
+ // Use ratio to get the big triangles leg length
+ float fLongLegLength = fLongBaseLength * ( fShortLegLength / fShortBaseLength );
+
+ // Get the relative point on the large triangle
+ Vector ptNewCornerPos = ptLineIntersection + vShortLeg * fLongLegLength;
+
+ // Bump by the same amount the corner has to move to fit
+ return ptNewCornerPos - ptCorner1;
+ }
+ /*else
+ {
+ return Vector( 0.0f, 0.0f, 0.0f );
+ }*/
+}
+
+
+bool FitPortalOnSurface( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight,
+ const Vector &vTopEdge, const Vector &vBottomEdge, const Vector &vRightEdge, const Vector &vLeftEdge,
+ int iPlacedBy, ITraceFilter *pTraceFilterPortalShot,
+ int iRecursions /*= 0*/, const CPortalCornerFitData *pPortalCornerFitData /*= 0*/, const int *p_piIntersectionIndex /*= 0*/, const int *piIntersectionCount /*= 0*/ )
+{
+ // Don't infinitely recurse
+ if ( iRecursions >= 6 )
+ {
+ return false;
+ }
+
+ Vector pptCorner[ 4 ];
+
+ // Get corner points
+ pptCorner[ 0 ] = vOrigin + vTopEdge + vLeftEdge;
+ pptCorner[ 1 ] = vOrigin + vTopEdge + vRightEdge;
+ pptCorner[ 2 ] = vOrigin + vBottomEdge + vLeftEdge;
+ pptCorner[ 3 ] = vOrigin + vBottomEdge + vRightEdge;
+
+ // Corner data
+ CPortalCornerFitData sFitData[ 4 ];
+ int piIntersectionIndex[ 4 ];
+ int iIntersectionCount = 0;
+
+ // Gather data we already know
+ if ( pPortalCornerFitData )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ sFitData[ iIntersection ] = pPortalCornerFitData[ iIntersection ];
+ }
+ }
+ else
+ {
+ memset( sFitData, 0, sizeof( sFitData ) );
+ }
+
+ if ( p_piIntersectionIndex )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ piIntersectionIndex[ iIntersection ] = p_piIntersectionIndex[ iIntersection ];
+ }
+ }
+ else
+ {
+ memset( piIntersectionIndex, 0, sizeof( piIntersectionIndex ) );
+ }
+
+ if ( piIntersectionCount )
+ {
+ iIntersectionCount = *piIntersectionCount;
+ }
+
+ int iOldIntersectionCount = iIntersectionCount;
+
+ // Find intersections from center to each corner
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ // HACK: In weird cases intersection count can go over 3 and index outside of our arrays. Don't let this happen!
+ if ( iIntersectionCount < 4 )
+ {
+ // Don't recompute intersection data that we already have
+ if ( !sFitData[ iIntersection ].bCornerIntersection )
+ {
+ // Test intersection of the current corner
+ sFitData[ iIntersection ].bCornerIntersection = TracePortalCorner( pIgnorePortal, vOrigin, pptCorner[ iIntersection ], vForward, iPlacedBy, pTraceFilterPortalShot, sFitData[ iIntersection ].trCornerTrace, sFitData[ iIntersection ].bSoftBump );
+
+ // If the intersection has no normal, ignore it
+ if ( sFitData[ iIntersection ].trCornerTrace.plane.normal.IsZero() )
+ sFitData[ iIntersection ].bCornerIntersection = false;
+
+ // If it intersected
+ if ( sFitData[ iIntersection ].bCornerIntersection )
+ {
+ sFitData[ iIntersection ].ptIntersectionPoint = vOrigin + ( pptCorner[ iIntersection ] - vOrigin ) * sFitData[ iIntersection ].trCornerTrace.fraction;
+ VectorNormalize( sFitData[ iIntersection ].trCornerTrace.plane.normal );
+ sFitData[ iIntersection ].vIntersectionDirection = sFitData[ iIntersection ].trCornerTrace.plane.normal.Cross( vForward );
+ VectorNormalize( sFitData[ iIntersection ].vIntersectionDirection );
+ sFitData[ iIntersection ].vBumpDirection = vForward.Cross( sFitData[ iIntersection ].vIntersectionDirection );
+ VectorNormalize( sFitData[ iIntersection ].vBumpDirection );
+
+ piIntersectionIndex[ iIntersectionCount ] = iIntersection;
+
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ NDebugOverlay::Line( sFitData[ iIntersection ].ptIntersectionPoint - sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
+ sFitData[ iIntersection ].ptIntersectionPoint + sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
+ 0, 0, 255, true, 0.5f );
+ }
+ }
+
+ ++iIntersectionCount;
+ }
+ }
+ else
+ {
+ // We shouldn't be intersecting with any old corners
+ sFitData[ iIntersection ].trCornerTrace.fraction = 1.0f;
+ }
+ }
+ }
+
+ for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
+ {
+ // Remember soft bumpers so we don't bump with it twice
+ if ( sFitData[ iIntersection ].bSoftBump )
+ {
+ g_FuncBumpingEntityList.AddToTail( sFitData[ iIntersection ].trCornerTrace.m_pEnt );
+ }
+ }
+
+ // If no new intersections were found then it already fits
+ if ( iOldIntersectionCount == iIntersectionCount )
+ {
+ return true;
+ }
+
+ switch ( iIntersectionCount )
+ {
+ case 0:
+ {
+ // If no corners intersect it already fits
+ return true;
+ }
+ break;
+
+ case 1:
+ {
+ float fBumpDistance = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ 0 ] ],
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection );
+
+ fBumpDistance += PORTAL_BUMP_FORGIVENESS;
+
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection * fBumpDistance;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ case 2:
+ {
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint == sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint )
+ {
+ return false;
+ }
+
+ float fDot = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+
+ // If there are parallel intersections try scooting it away from a near wall
+ if ( fDot < -0.9f )
+ {
+ // Check if perpendicular wall is near
+ trace_t trPerpWall1;
+ bool bSoftBump1;
+ bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
+
+ trace_t trPerpWall2;
+ bool bSoftBump2;
+ bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
+
+ // No fit if there's blocking walls on both sides it can't fit
+ if ( bDir1 && bDir2 )
+ {
+ if ( bSoftBump1 )
+ bDir1 = false;
+ else if ( bSoftBump2 )
+ bDir1 = true;
+ else
+ return false;
+ }
+
+ // If there's no assumption to make, just pick a direction.
+ if ( !bDir1 && !bDir2 )
+ {
+ bDir1 = true;
+ }
+
+ // Bump the portal
+ if ( bDir1 )
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
+ }
+ else
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
+ }
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+
+ // If they are the same there's an easy way
+ if ( fDot > 0.9f )
+ {
+ // Get the closest intersection to the portal's center
+ int iClosestIntersection = ( ( vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint ) < vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint ) ) ? ( 0 ) : ( 1 ) );
+
+ // Find the largest amount that the portal needs to bump for the corner to pass the intersection
+ float pfBumpDistance[ 2 ];
+
+ for ( int iIntersection = 0; iIntersection < 2; ++iIntersection )
+ {
+ pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
+
+ pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
+ }
+
+ int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
+
+ // Bump the portal
+ vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
+
+ // If they were parallel to the intersection line don't invalidate both before recursion
+ if ( pfBumpDistance[ 0 ] == pfBumpDistance[ 1 ] )
+ {
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ iIntersectionCount = 0;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ // Prepare data for recursion
+ if ( iLargestBump != iClosestIntersection )
+ {
+ sFitData[ piIntersectionIndex[ iLargestBump ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
+ }
+ sFitData[ piIntersectionIndex[ ( ( iLargestBump == 0 ) ? ( 1 ) : ( 0 ) ) ] ].bCornerIntersection = false;
+ piIntersectionIndex[ 0 ] = piIntersectionIndex[ iLargestBump ];
+ iIntersectionCount = 1;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ }
+
+ // Intersections are angled, bump based on math using the corner
+ vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ 0 ] ], pptCorner[ piIntersectionIndex[ 1 ] ],
+ sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ 1 ] ].vIntersectionDirection,
+ sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ case 3:
+ {
+ // Get the relationships of the intersections
+ float fDot[ 3 ];
+ fDot[ 0 ] = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
+ fDot[ 1 ] = sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection );
+ fDot[ 2 ] = sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection );
+
+ int iSimilarWalls = 0;
+
+ for ( int iDot = 0; iDot < 3; ++iDot )
+ {
+ // If there are parallel intersections try scooting it away from a near wall
+ if ( fDot[ iDot ] < -0.99f )
+ {
+ // Check if perpendicular wall is near
+ trace_t trPerpWall1;
+ bool bSoftBump1;
+ bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
+
+ trace_t trPerpWall2;
+ bool bSoftBump2;
+ bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
+
+ // No fit if there's blocking walls on both sides it can't fit
+ if ( bDir1 && bDir2 )
+ {
+ if ( bSoftBump1 )
+ bDir1 = false;
+ else if ( bSoftBump2 )
+ bDir1 = true;
+ else
+ return false;
+ }
+
+ // If there's no assumption to make, just pick a direction.
+ if ( !bDir1 && !bDir2 )
+ {
+ bDir1 = true;
+ }
+
+ // Bump the portal
+ if ( bDir1 )
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
+ }
+ else
+ {
+ vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
+ }
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ // Count similar intersections
+ else if ( fDot[ iDot ] > 0.99f )
+ {
+ ++iSimilarWalls;
+ }
+ }
+
+ // If no intersections are similar
+ if ( iSimilarWalls == 0 )
+ {
+ // Total the angles between the intersections
+ float fAngleTotal = 0.0f;
+ for ( int iDot = 0; iDot < 3; ++iDot )
+ {
+ fAngleTotal += acosf( fDot[ iDot ] );
+ }
+
+ // If it's in a triangle, it can't be fit
+ if ( M_PI_F - 0.01f < fAngleTotal && fAngleTotal < M_PI_F + 0.01f )
+ {
+ // If any of the bumps are soft, give it another try
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump )
+ {
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ // If the intersections are all similar there's an easy way
+ if ( iSimilarWalls == 3 )
+ {
+ // Get the closest intersection to the portal's center
+ int iClosestIntersection = 0;
+ float fClosestDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint );
+
+ float fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint );
+ if ( fClosestDistance > fDistance )
+ {
+ iClosestIntersection = 1;
+ fClosestDistance = fDistance;
+ }
+
+ fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 2 ] ].ptIntersectionPoint );
+ if ( fClosestDistance > fDistance )
+ {
+ iClosestIntersection = 2;
+ fClosestDistance = fDistance;
+ }
+
+ // Find the largest amount that the portal needs to bump for the corner to pass the intersection
+ float pfBumpDistance[ 3 ];
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
+ pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
+ }
+
+ int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
+
+ iLargestBump = ( ( pfBumpDistance[ iLargestBump ] > pfBumpDistance[ 2 ] ) ? ( iLargestBump ) : ( 2 ) );
+
+ // Bump the portal
+ vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
+
+ // Prepare data for recursion
+ int iStillIntersecting = 0;
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ // Invalidate corners that were closer to the intersection line
+ if ( pfBumpDistance[ iIntersection ] != pfBumpDistance[ iLargestBump ] )
+ {
+ sFitData[ piIntersectionIndex[ iIntersection ] ].bCornerIntersection = false;
+ --iIntersectionCount;
+ }
+ else
+ {
+ sFitData[ piIntersectionIndex[ iIntersection ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
+ piIntersectionIndex[ iStillIntersecting ] = piIntersectionIndex[ iIntersection ];
+ ++iStillIntersecting;
+ }
+ }
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+
+ // Get info for which corners are diagonal from each other
+ float fLongestDist = 0.0f;
+ int iLongestDist = 0;
+
+ for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
+ {
+ float fDist = pptCorner[ piIntersectionIndex[ iIntersection ] ].DistTo( pptCorner[ piIntersectionIndex[ ( iIntersection + 1 ) % 3 ] ] );
+
+ if ( fLongestDist < fDist )
+ {
+ fLongestDist = fDist;
+ iLongestDist = iIntersection;
+ }
+ }
+
+ int iIndex1, iIndex2, iIndex3;
+
+ switch ( iLongestDist )
+ {
+ case 0:
+ iIndex1 = 0;
+ iIndex2 = 1;
+ iIndex3 = 2;
+ break;
+
+ case 1:
+ iIndex1 = 1;
+ iIndex2 = 2;
+ iIndex3 = 0;
+ break;
+
+ default:
+ iIndex1 = 2;
+ iIndex2 = 0;
+ iIndex3 = 1;
+ break;
+ }
+
+ // If corner is 90 degrees there my be an easy way
+ float fCornerDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
+
+ if ( fCornerDot < 0.0001f && fCornerDot > -0.0001f )
+ {
+ // Check if portal is aligned perfectly with intersection normals
+ float fPortalDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( vRight );
+
+ if ( ( fPortalDot < 0.0001f && fPortalDot > -0.0001f ) || fPortalDot > 0.9999f || fPortalDot < -0.9999f )
+ {
+ float fBump1 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex1 ] ],
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection );
+
+ fBump1 += PORTAL_BUMP_FORGIVENESS;
+
+ float fBump2 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex2 ] ],
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
+
+ fBump2 += PORTAL_BUMP_FORGIVENESS;
+
+ // Bump portal
+ vOrigin += sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection * fBump1;
+ vOrigin += sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection * fBump2;
+
+ // Prepare recursion data
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ iIndex2 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ }
+
+ vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ iIndex1 ] ], pptCorner[ piIntersectionIndex[ iIndex2 ] ],
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection,
+ sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection );
+
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ break;
+
+ default:
+ {
+ if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 3 ] ].bSoftBump )
+ {
+ // Prepare data for recursion
+ iIntersectionCount = 0;
+ sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
+ sFitData[ piIntersectionIndex[ 3 ] ].bCornerIntersection = false;
+
+ return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
+ }
+ else
+ {
+ // All corners intersect with no soft bumps, so it can't be fit
+ return false;
+ }
+ }
+ break;
+ }
+
+ return true;
+}
+
+void FitPortalAroundOtherPortals( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp )
+{
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ Vector vDiff = vOrigin - pTempPortal->GetLocalOrigin();
+
+ Vector vDiffProjRight = vDiff.Dot( vRight ) * vRight;
+ Vector vDiffProjUp = vDiff.Dot( vUp ) * vUp;
+
+ float fProjRightLength = VectorNormalize( vDiffProjRight );
+ float fProjUpLength = VectorNormalize( vDiffProjUp );
+
+ if ( fProjRightLength < 1.0f )
+ {
+ vDiffProjRight = vRight;
+ }
+
+ if ( fProjUpLength < PORTAL_HALF_HEIGHT && fProjRightLength < PORTAL_HALF_WIDTH )
+ {
+ vOrigin += vDiffProjRight * ( PORTAL_HALF_WIDTH - fProjRightLength + 1.0f );
+ }
+ }
+ }
+ }
+}
+
+bool IsPortalIntersectingNoPortalVolume( const Vector &vOrigin, const QAngle &qAngles, const Vector &vForward )
+{
+ // Walk the no portal volume list, check each with box-box intersection
+ for ( CFuncNoPortalVolume *pNoPortalEnt = GetNoPortalVolumeList(); pNoPortalEnt != NULL; pNoPortalEnt = pNoPortalEnt->m_pNext )
+ {
+ // Skip inactive no portal zones
+ if ( !pNoPortalEnt->IsActive() )
+ {
+ continue;
+ }
+
+ Vector vMin;
+ Vector vMax;
+ pNoPortalEnt->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
+
+ Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
+ Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
+
+ // Take bump forgiveness into account on non major axies
+ vBoxExtents += Vector( ( ( vForward.x > 0.5f || vForward.x < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
+ ( ( vForward.y > 0.5f || vForward.y < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
+ ( ( vForward.z > 0.5f || vForward.z < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ) );
+
+ if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, vOrigin, qAngles ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ NDebugOverlay::Box( Vector( 0.0f, 0.0f, 0.0f ), vMin, vMax, 0, 255, 0, 128, 0.5f );
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+
+ DevMsg( "Portal placed in no portal volume.\n" );
+ }
+
+ return true;
+ }
+ }
+
+ // Passed the list, so we didn't hit any func_noportal_volumes
+ return false;
+}
+
+bool IsPortalOverlappingOtherPortals( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const QAngle &qAngles, bool bFizzle /*= false*/ )
+{
+ bool bOverlappedOtherPortal = false;
+
+ Vector vForward;
+ AngleVectors( qAngles, &vForward, NULL, NULL );
+
+ Vector vPortalOBBMin = CProp_Portal_Shared::vLocalMins + Vector( 1.0f, 1.0f, 1.0f );
+ Vector vPortalOBBMax = CProp_Portal_Shared::vLocalMaxs - Vector( 1.0f, 1.0f, 1.0f );
+
+ int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
+ if( iPortalCount != 0 )
+ {
+ CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
+ for( int i = 0; i != iPortalCount; ++i )
+ {
+ CProp_Portal *pTempPortal = pPortals[i];
+ if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
+ {
+ Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
+ QAngle qOtherAngles = pTempPortal->GetAbsAngles();
+
+ Vector vLinkedForward;
+ AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
+
+ // If they're not on the same face then don't worry about overlap
+ if ( vForward.Dot( vLinkedForward ) < 0.95f )
+ continue;
+
+ if ( IsOBBIntersectingOBB( vOrigin, qAngles, vPortalOBBMin, vPortalOBBMax,
+ vOtherOrigin, qOtherAngles, vPortalOBBMin, vPortalOBBMax, 0.0f ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ UTIL_Portal_NDebugOverlay( pTempPortal, 255, 0, 0, 128, false, 0.5f );
+
+ DevMsg( "Portal overlapped another portal.\n" );
+ }
+
+ if ( bFizzle )
+ {
+ pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
+ pTempPortal->Fizzle();
+ bOverlappedOtherPortal = true;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return bOverlappedOtherPortal;
+}
+
+bool IsPortalOnValidSurface( const Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp, ITraceFilter *traceFilterPortalShot )
+{
+ trace_t tr;
+
+ // Check if corners are on a no portal material
+ for ( int iCorner = 0; iCorner < 5; ++iCorner )
+ {
+ Vector ptCorner = vOrigin;
+
+ if ( iCorner < 4 )
+ {
+ if ( iCorner / 2 == 0 )
+ ptCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //top
+ else
+ ptCorner += vUp * -( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //bottom
+
+ if ( iCorner % 2 == 0 )
+ ptCorner += vRight * -( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //left
+ else
+ ptCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //right
+ }
+
+ Ray_t ray;
+ ray.Init( ptCorner + vForward, ptCorner - vForward );
+ enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, traceFilterPortalShot, &tr );
+
+ if ( tr.startsolid )
+ {
+ // Portal center/corner in solid
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal center or corner placed inside solid.\n" );
+ }
+
+ return false;
+ }
+
+ if ( tr.fraction == 1.0f )
+ {
+ // Check if there's a portal bumper to act as a surface
+ TraceBumpingEntities( ptCorner + vForward, ptCorner - vForward, tr );
+
+ if ( tr.fraction == 1.0f )
+ {
+ // No surface behind the portal
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal corner has no surface behind it.\n" );
+ }
+
+ return false;
+ }
+ }
+
+ if ( tr.m_pEnt && FClassnameIs( tr.m_pEnt, "func_door" ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on func_door.\n" );
+ }
+
+ return false;
+ }
+
+ if ( IsPassThroughMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on a pass through material.\n" );
+ }
+
+ return false;
+ }
+
+ if ( IsNoPortalMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal placed on a no portal material.\n" );
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+float VerifyPortalPlacement( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, int iPlacedBy, bool bTest /*= false*/ )
+{
+ Vector vOriginalOrigin = vOrigin;
+
+ Vector vForward, vRight, vUp;
+ AngleVectors( qAngles, &vForward, &vRight, &vUp );
+
+ VectorNormalize( vForward );
+ VectorNormalize( vRight );
+ VectorNormalize( vUp );
+
+ trace_t tr;
+ CTraceFilterSimpleClassnameList baseFilter( pIgnorePortal, COLLISION_GROUP_NONE );
+ UTIL_Portal_Trace_Filter( &baseFilter );
+ baseFilter.AddClassnameToIgnore( "prop_portal" );
+ CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
+
+ // Check if center is on a surface
+ Ray_t ray;
+ ray.Init( vOrigin + vForward, vOrigin - vForward );
+ enginetrace->TraceRay( ray, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
+
+ if ( tr.fraction == 1.0f )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal center has no surface behind it.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Check if the surface is moving
+ Vector vVelocityCheck;
+ AngularImpulse vAngularImpulseCheck;
+
+ IPhysicsObject *pPhysicsObject = tr.m_pEnt->VPhysicsGetObject();
+
+ if ( pPhysicsObject )
+ {
+ pPhysicsObject->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
+ }
+ else
+ {
+ tr.m_pEnt->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
+ }
+
+ if ( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ DevMsg( "Portal was on moving surface.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Check for invalid materials
+ if ( IsPassThroughMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal placed on a pass through material.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE;
+ }
+
+ if ( IsNoPortalMaterial( tr.surface ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal placed on a no portal material.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ // Get pointer to liked portal if it might be in the way
+ g_bBumpedByLinkedPortal = false;
+
+ if ( iPlacedBy == PORTAL_PLACED_BY_PLAYER && !sv_portal_placement_never_bump.GetBool() )
+ {
+ // Bump away from linked portal so it can be fit next to it
+ FitPortalAroundOtherPortals( pIgnorePortal, vOrigin, vForward, vRight, vUp );
+ }
+
+ float fBumpDistance = 0.0f;
+
+ if ( !sv_portal_placement_never_bump.GetBool() )
+ {
+ // Fit onto surface and auto bump
+ g_FuncBumpingEntityList.RemoveAll();
+
+ Vector vTopEdge = vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS );
+ Vector vBottomEdge = -vTopEdge;
+ Vector vRightEdge = vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS );
+ Vector vLeftEdge = -vRightEdge;
+
+ if ( !FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, &traceFilterPortalShot ) )
+ {
+ if ( g_bBumpedByLinkedPortal )
+ {
+ return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
+ }
+
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal was unable to fit on surface.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CANT_FIT;
+ }
+
+ // Check if it's moved too far from it's original location
+ fBumpDistance = vOrigin.DistToSqr( vOriginalOrigin );
+
+ if ( fBumpDistance > MAXIMUM_BUMP_DISTANCE )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ DevMsg( "Portal adjusted too far from it's original location.\n" );
+ }
+
+ return PORTAL_ANALOG_SUCCESS_CANT_FIT;
+ }
+
+ //if we're less than a unit from floor, we're going to bump to match it exactly and help game movement code run smoothly
+ if( vUp.z > 0.7f )
+ {
+ Vector vSmallForward = vForward * 0.05f;
+ trace_t FloorTrace;
+ UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT + 1.5f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace );
+ if( FloorTrace.fraction < 1.0f )
+ {
+ //we hit floor in that 1 extra unit, now doublecheck to make sure we didn't hit something else
+ trace_t FloorTrace_Verify;
+ UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT - 0.1f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace_Verify );
+ if( FloorTrace_Verify.fraction == 1.0f )
+ {
+ //if we're in here, we're definitely in a floor matching configuration, bump down to match the floor better
+ vOrigin = FloorTrace.endpos + (vUp * PORTAL_HALF_HEIGHT) - vSmallForward;// - vUp * PORTAL_WALL_MIN_THICKNESS;
+ }
+ }
+ }
+ }
+
+ // Fail if it's in a no portal volume
+ if ( IsPortalIntersectingNoPortalVolume( vOrigin, qAngles, vForward ) )
+ {
+ return PORTAL_ANALOG_SUCCESS_INVALID_VOLUME;
+ }
+
+ // Fail if it's overlapping the linked portal
+ if ( bTest && IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles ) )
+ {
+ return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
+ }
+
+ // Fail if it's on a flagged surface material
+ if ( !IsPortalOnValidSurface( vOrigin, vForward, vRight, vUp, &traceFilterPortalShot ) )
+ {
+ if ( sv_portal_placement_debug.GetBool() )
+ {
+ UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
+ }
+ return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
+ }
+
+ float fAnalogSuccessMultiplier = 1.0f - ( fBumpDistance / MAXIMUM_BUMP_DISTANCE );
+ fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
+ fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
+
+ return fAnalogSuccessMultiplier * ( PORTAL_ANALOG_SUCCESS_NO_BUMP - PORTAL_ANALOG_SUCCESS_BUMPED ) + PORTAL_ANALOG_SUCCESS_BUMPED;
+}