Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1663 lines
59 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "portal_placement.h"
  8. #include "portal_shareddefs.h"
  9. #include "prop_portal_shared.h"
  10. #include "collisionutils.h"
  11. #include "decals.h"
  12. #include "debugoverlay_shared.h"
  13. #include "portal_mp_gamerules.h"
  14. #if defined( GAME_DLL )
  15. #include "func_noportal_volume.h"
  16. #include "triggers.h"
  17. #include "func_portal_bumper.h"
  18. #include "physicsshadowclone.h"
  19. #include "trigger_portal_cleanser.h"
  20. #else
  21. #include "c_triggers.h"
  22. #include "c_func_noportal_volume.h"
  23. #include "c_func_portal_bumper.h"
  24. #include "c_trigger_portal_cleanser.h"
  25. #endif
  26. #include "cegclientwrapper.h"
  27. #include "paint_color_manager.h"
  28. ConVar sv_portal_placement_on_paint("sv_portal_placement_on_paint", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "Enable/Disable placing portal on painted surfaces");
  29. ConVar sv_portal_placement_never_fail("sv_portal_placement_never_fail", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
  30. extern ConVar sv_allow_mobile_portals;
  31. #define MAXIMUM_BUMP_DISTANCE ( ( fHalfWidth * 2.0f ) * ( fHalfWidth * 2.0f ) + ( fHalfHeight * 2.0f ) * ( fHalfHeight * 2.0f ) ) / 2.0f
  32. struct CPortalCornerFitData
  33. {
  34. trace_t trCornerTrace;
  35. Vector ptIntersectionPoint;
  36. Vector vIntersectionDirection;
  37. Vector vBumpDirection;
  38. bool bCornerIntersection;
  39. bool bSoftBump;
  40. };
  41. CUtlVector<CBaseEntity *> g_FuncBumpingEntityList;
  42. bool g_bBumpedByLinkedPortal;
  43. ConVar sv_portal_placement_debug("sv_portal_placement_debug", "0", FCVAR_REPLICATED );
  44. ConVar sv_portal_placement_never_bump("sv_portal_placement_never_bump", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
  45. bool IsMaterialInList( const csurface_t &surface, char *g_ppszMaterials[] )
  46. {
  47. char szLowerName[ 256 ];
  48. Q_strcpy( szLowerName, surface.name );
  49. Q_strlower( szLowerName );
  50. int iMaterial = 0;
  51. while ( g_ppszMaterials[ iMaterial ] )
  52. {
  53. if ( Q_strstr( szLowerName, g_ppszMaterials[ iMaterial ] ) )
  54. return true;
  55. ++iMaterial;
  56. }
  57. return false;
  58. }
  59. // exposed here as non-constant so CEG can populate the value at DLL init time
  60. static int CEG_PORTAL_POWER = 0xffffffff; // no paint power until correctly initialized
  61. CEG_NOINLINE void InitPortalPaintPowerValue()
  62. {
  63. CEG_GCV_PRE();
  64. CEG_PORTAL_POWER = CEG_GET_CONSTANT_VALUE( PaintPortalPower );
  65. CEG_GCV_POST();
  66. }
  67. CEG_NOINLINE bool IsOnPortalPaint( const trace_t &tr )
  68. {
  69. if ( sv_portal_placement_on_paint.GetBool() && tr.m_pEnt)
  70. {
  71. if ( !UTIL_IsPaintableSurface( tr.surface ) )
  72. return false;
  73. PaintPowerType paintPower = NO_POWER;
  74. //Trace for paint on the surface if it is the world
  75. if( tr.m_pEnt->IsBSPModel() )
  76. {
  77. // enable portal placement on painted surface
  78. paintPower = UTIL_Paint_TracePower( tr.m_pEnt, tr.endpos, tr.plane.normal );
  79. }
  80. else //For entities
  81. {
  82. if( FClassnameIs( tr.m_pEnt, "func_brush" ) )
  83. {
  84. paintPower = MapColorToPower( tr.m_pEnt->GetRenderColor() );
  85. }
  86. }
  87. if( paintPower == CEG_PORTAL_POWER )
  88. {
  89. return true;
  90. }
  91. }
  92. return false;
  93. }
  94. // exposed here as non-constant so CEG can populate the value at DLL init time
  95. static unsigned short CEG_SURF_NO_PORTAL_FLAG = 0xffff; // portals can't be placed until correctly initialized
  96. CEG_NOINLINE void InitSurfNoPortalFlag()
  97. {
  98. CEG_GCV_PRE();
  99. CEG_SURF_NO_PORTAL_FLAG = CEG_GET_CONSTANT_VALUE( SurfNoPortalFlag );
  100. CEG_GCV_POST();
  101. }
  102. CEG_NOINLINE PortalSurfaceType_t PortalSurfaceType( const trace_t& tr )
  103. {
  104. //Note: this is for placing portal on paint
  105. if ( IsOnPortalPaint( tr ) )
  106. return PORTAL_SURFACE_PAINT;
  107. if ( tr.surface.flags & CEG_SURF_NO_PORTAL_FLAG )
  108. return PORTAL_SURFACE_INVALID;
  109. const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps );
  110. if ( pdata->game.material == CHAR_TEX_GLASS )
  111. return PORTAL_SURFACE_INVALID;
  112. // Skipping all studio models
  113. if ( StringHasPrefix( tr.surface.name, "**studio**" ) )
  114. return PORTAL_SURFACE_INVALID;
  115. return PORTAL_SURFACE_VALID;
  116. }
  117. bool IsNoPortalMaterial( const trace_t &tr )
  118. {
  119. return PortalSurfaceType( tr ) == PORTAL_SURFACE_INVALID;
  120. }
  121. bool IsPassThroughMaterial( const csurface_t &surface )
  122. {
  123. if ( surface.flags & SURF_SKY )
  124. return true;
  125. if ( IsMaterialInList( surface, g_ppszPortalPassThroughMaterials ) )
  126. return true;
  127. return false;
  128. }
  129. void TracePortals( const CProp_Portal *pIgnorePortal, const Vector &vForward, const Vector &vStart, const Vector &vEnd, trace_t &tr )
  130. {
  131. UTIL_ClearTrace( tr );
  132. Ray_t ray;
  133. ray.Init( vStart, vEnd );
  134. trace_t trTemp;
  135. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  136. if( iPortalCount != 0 )
  137. {
  138. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  139. for( int i = 0; i != iPortalCount; ++i )
  140. {
  141. CProp_Portal *pTempPortal = pPortals[i];
  142. if( pTempPortal != pIgnorePortal && pTempPortal->IsActive() )
  143. {
  144. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  145. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  146. Vector vLinkedForward;
  147. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  148. // If they're not on the same face then don't worry about overlap
  149. if ( vForward.Dot( vLinkedForward ) < 0.95f )
  150. continue;
  151. UTIL_IntersectRayWithPortalOBBAsAABB( pTempPortal, ray, &trTemp );
  152. if ( trTemp.fraction < 1.0f && trTemp.fraction < tr.fraction )
  153. {
  154. tr = trTemp;
  155. tr.m_pEnt = pTempPortal;
  156. }
  157. }
  158. }
  159. }
  160. }
  161. int AllEdictsAlongRay( CBaseEntity **pList, int listMax, const Ray_t &ray, int flagMask )
  162. {
  163. CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask );
  164. #if defined( GAME_DLL )
  165. partition->EnumerateElementsAlongRay( PARTITION_ENGINE_NON_STATIC_EDICTS, ray, false, &rayEnum );
  166. #else
  167. partition->EnumerateElementsAlongRay( PARTITION_ALL_CLIENT_EDICTS, ray, false, &rayEnum );
  168. #endif
  169. return rayEnum.GetCount();
  170. }
  171. bool TraceBumpingEntities( const Vector &vStart, const Vector &vEnd, trace_t &tr )
  172. {
  173. UTIL_ClearTrace( tr );
  174. // We use this so portal bumpers can't squeeze a portal into not fitting
  175. bool bClosestIsSoftBumper = false;
  176. // Trace to the surface to see if there's a rotating door in the way
  177. CBaseEntity *list[1024];
  178. Ray_t ray;
  179. ray.Init( vStart, vEnd );
  180. int nCount = AllEdictsAlongRay( list, 1024, ray, 0 );
  181. for ( int i = 0; i < nCount; i++ )
  182. {
  183. #if 0
  184. #if defined( GAME_DLL )
  185. Warning( "TraceBumpingEntities(server) : %s\n", list[i]->m_iClassname );
  186. #else
  187. Warning( "TraceBumpingEntities(client) : %s\n", list[i]->GetClassname() );
  188. #endif
  189. #endif
  190. trace_t trTemp;
  191. UTIL_ClearTrace( trTemp );
  192. bool bSoftBumper = false;
  193. if ( dynamic_cast<CFuncPortalBumper*>( list[i] ) != NULL )
  194. {
  195. if( ((CFuncPortalBumper *)list[i])->IsActive() )
  196. {
  197. bSoftBumper = true;
  198. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  199. if ( trTemp.startsolid )
  200. {
  201. trTemp.fraction = 1.0f;
  202. }
  203. }
  204. }
  205. else if ( dynamic_cast<CTriggerPortalCleanser*>( list[i] ) != NULL )
  206. {
  207. if( ((CTriggerPortalCleanser *)list[i])->IsEnabled() )
  208. {
  209. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  210. if ( trTemp.startsolid )
  211. {
  212. trTemp.fraction = 1.0f;
  213. }
  214. }
  215. }
  216. else if ( dynamic_cast<CFuncNoPortalVolume*>( list[i] ) != NULL )
  217. {
  218. trTemp.fraction = 1.0f;
  219. if ( static_cast<CFuncNoPortalVolume*>( list[i] )->IsActive() )
  220. {
  221. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  222. // Bump by an extra 2 units so that the portal isn't touching the no portal volume
  223. Vector vDelta = trTemp.endpos - trTemp.startpos;
  224. float fLength = VectorNormalize( vDelta ) - 2.0f;
  225. if ( fLength < 0.0f )
  226. fLength = 0.0f;
  227. trTemp.fraction = fLength / ray.m_Delta.Length();
  228. trTemp.endpos = trTemp.startpos + vDelta * fLength;
  229. }
  230. }
  231. #if defined( GAME_DLL )
  232. else if ( FClassnameIs( list[i], "prop_door_rotating" ) )
  233. #else
  234. else if ( FClassnameIs( list[i], "class C_PropDoorRotating" ) )
  235. #endif
  236. {
  237. // Check more precise door collision
  238. //CBasePropDoor *pRotatingDoor = static_cast<CBasePropDoor *>( list[i] );
  239. CBaseEntity *pRotatingDoor = list[i];
  240. pRotatingDoor->TestCollision( ray, 0, trTemp );
  241. }
  242. // If this is the closest and has only bumped once (for soft bumpers)
  243. if ( trTemp.fraction < tr.fraction && ( !bSoftBumper || !g_FuncBumpingEntityList.HasElement( list[i] ) ) )
  244. {
  245. tr = trTemp;
  246. bClosestIsSoftBumper = bSoftBumper;
  247. }
  248. }
  249. return bClosestIsSoftBumper;
  250. }
  251. bool TracePortalCorner( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const Vector &vCorner, const Vector &vForward, PortalPlacedBy_t ePlacedBy, ITraceFilter *pTraceFilterPortalShot, trace_t &tr, bool &bSoftBump )
  252. {
  253. Vector vOriginToCorner = vCorner - vOrigin;
  254. // Check for surface edge
  255. trace_t trSurfaceEdge;
  256. UTIL_TraceLine( vOrigin - vForward, vCorner - vForward, MASK_SHOT_PORTAL|CONTENTS_WATER|CONTENTS_SLIME, pTraceFilterPortalShot, &trSurfaceEdge );
  257. if ( trSurfaceEdge.startsolid )
  258. {
  259. float fTotalFraction = trSurfaceEdge.fractionleftsolid;
  260. while ( trSurfaceEdge.startsolid && trSurfaceEdge.fractionleftsolid > 0.0f && fTotalFraction < 1.0f )
  261. {
  262. UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vCorner + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
  263. if ( trSurfaceEdge.startsolid )
  264. {
  265. fTotalFraction += trSurfaceEdge.fractionleftsolid + 0.05f;
  266. }
  267. }
  268. if ( fTotalFraction < 1.0f )
  269. {
  270. UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vOrigin - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
  271. if ( trSurfaceEdge.startsolid )
  272. {
  273. trSurfaceEdge.fraction = 1.0f;
  274. }
  275. else
  276. {
  277. trSurfaceEdge.fraction = fTotalFraction;
  278. trSurfaceEdge.plane.normal = -trSurfaceEdge.plane.normal;
  279. }
  280. }
  281. else
  282. {
  283. trSurfaceEdge.fraction = 1.0f;
  284. }
  285. }
  286. else
  287. {
  288. trSurfaceEdge.fraction = 1.0f;
  289. }
  290. // Check for enclosing wall
  291. trace_t trEnclosingWall;
  292. UTIL_TraceLine( vOrigin + vForward, vCorner + vForward, MASK_SOLID_BRUSHONLY|CONTENTS_MONSTER|CONTENTS_WATER|CONTENTS_SLIME, pTraceFilterPortalShot, &trEnclosingWall );
  293. if ( trSurfaceEdge.fraction < trEnclosingWall.fraction )
  294. {
  295. trEnclosingWall.fraction = trSurfaceEdge.fraction;
  296. trEnclosingWall.plane.normal = trSurfaceEdge.plane.normal;
  297. }
  298. trace_t trPortal;
  299. trace_t trBumpingEntity;
  300. if ( ePlacedBy != PORTAL_PLACED_BY_FIXED )
  301. TracePortals( pIgnorePortal, vForward, vOrigin + vForward, vCorner + vForward, trPortal );
  302. else
  303. UTIL_ClearTrace( trPortal );
  304. bool bSoftBumper = TraceBumpingEntities( vOrigin + vForward, vCorner + vForward, trBumpingEntity );
  305. if ( trEnclosingWall.fraction >= 1.0f && trPortal.fraction >= 1.0f && trBumpingEntity.fraction >= 1.0f )
  306. {
  307. //check for a surface change between center and corner so we can bump when we partially overlap a non-portal surface
  308. trace_t cornerTrace;
  309. UTIL_TraceLine( vCorner, vCorner - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &cornerTrace );
  310. if( cornerTrace.DidHit() && IsNoPortalMaterial( cornerTrace ) )
  311. {
  312. //a bump is in order, try to determine where we transition to the no portal material with a binary search
  313. float fFullLength = vOriginToCorner.Length();
  314. float fBadLength = fFullLength;
  315. float fGoodLength = 0.0f;
  316. Vector vOriginToCornerNormalized = vOriginToCorner.Normalized();
  317. int iSearchCount = 0; //overwatch is soon. And I think this loop might have locked up once or twice without an absolute loop limit. Being safe for now.
  318. const float kMaxDelta = 0.01f;
  319. while( ((fBadLength - fGoodLength) >= kMaxDelta) && (iSearchCount < 100) )
  320. {
  321. AssertOnce( iSearchCount < 50 );
  322. float fTestLength = (fBadLength + fGoodLength) * 0.5f;
  323. Vector vTestSpot = vOrigin + (vOriginToCornerNormalized * fTestLength);
  324. UTIL_TraceLine( vTestSpot, vTestSpot - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &cornerTrace );
  325. if( cornerTrace.DidHit() && !IsNoPortalMaterial( cornerTrace ) )
  326. {
  327. fGoodLength = fTestLength;
  328. }
  329. else
  330. {
  331. fBadLength = fTestLength;
  332. }
  333. ++iSearchCount;
  334. }
  335. Vector vGoodSpot = vOrigin + (vOriginToCornerNormalized * fGoodLength);
  336. Vector vBadSpot = vOrigin + (vOriginToCornerNormalized * fBadLength);
  337. iSearchCount = 0;
  338. Vector vImpactNormal( 0.0f, 0.0f, 0.0f );
  339. //try spots at 4x the delta in a circular pattern to find the normal of impact
  340. {
  341. Vector vGoodDirection = vForward.Cross( vOriginToCornerNormalized );
  342. Vector vTestSpot = vGoodSpot + (vGoodDirection * (kMaxDelta * 4.0f));
  343. UTIL_TraceLine( vTestSpot, vTestSpot - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &cornerTrace );
  344. if( !(cornerTrace.DidHit() && !IsNoPortalMaterial( cornerTrace )) )
  345. {
  346. vGoodDirection = -vGoodDirection;
  347. }
  348. vTestSpot = vGoodSpot + (vGoodDirection * (kMaxDelta * 4.0f));
  349. UTIL_TraceLine( vTestSpot, vTestSpot - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &cornerTrace );
  350. if( cornerTrace.DidHit() && !IsNoPortalMaterial( cornerTrace ) )
  351. {
  352. float fBadAngle = 0.0f;
  353. float fGoodAngle = 90.0f;
  354. for( int i = 0; i != 10; ++i ) //we'd like a delta of less than 0.1 degrees. And the deltas are predictable. 90, 45, 22.5, 11.25, 5.625, 2.8125, 1.40625, 0.703125, 0.3515625, 0.17578125, 0.087890625
  355. {
  356. float fTestAngle = (fBadAngle + fGoodAngle) * 0.5f;
  357. Vector vTestDirection = (cosf( fTestAngle ) * vOriginToCornerNormalized) + (sinf( fTestAngle ) * vGoodDirection);
  358. vTestSpot = vGoodSpot + (vTestDirection * (kMaxDelta * 4.0f));
  359. UTIL_TraceLine( vTestSpot, vTestSpot - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &cornerTrace );
  360. if( cornerTrace.DidHit() && !IsNoPortalMaterial( cornerTrace ) )
  361. {
  362. fGoodAngle = fTestAngle;
  363. }
  364. else
  365. {
  366. fBadAngle = fTestAngle;
  367. }
  368. }
  369. vGoodDirection = (cosf( fGoodAngle ) * vOriginToCornerNormalized) + (sinf( fGoodAngle ) * vGoodDirection);
  370. vImpactNormal = vForward.Cross( vGoodDirection );
  371. if( vImpactNormal.Dot( vOriginToCornerNormalized ) > 0.0f )
  372. vImpactNormal = -vImpactNormal;
  373. }
  374. }
  375. tr = cornerTrace;
  376. tr.startpos = vOrigin;
  377. tr.endpos = vOrigin + (vOriginToCornerNormalized * fGoodLength);
  378. tr.fraction = fGoodLength / fFullLength;
  379. tr.fractionleftsolid = 1.0f;
  380. tr.plane.normal = vImpactNormal; //.Init();// = -vOriginToCorner.Normalized();
  381. tr.plane.dist = tr.plane.normal.Dot( tr.endpos );
  382. return true;
  383. }
  384. UTIL_ClearTrace( tr );
  385. return false;
  386. }
  387. if ( trEnclosingWall.fraction <= trPortal.fraction && trEnclosingWall.fraction <= trBumpingEntity.fraction )
  388. {
  389. tr = trEnclosingWall;
  390. bSoftBump = false;
  391. }
  392. else if ( trPortal.fraction <= trEnclosingWall.fraction && trPortal.fraction <= trBumpingEntity.fraction && !g_FuncBumpingEntityList.HasElement( trPortal.m_pEnt ) )
  393. {
  394. tr = trPortal;
  395. CProp_Portal *pBumpPortal = static_cast< CProp_Portal * >( trPortal.m_pEnt );
  396. bool bBumpedByOwnPortal = pBumpPortal->GetFiredByPlayer() == pIgnorePortal->GetFiredByPlayer();
  397. if ( bBumpedByOwnPortal )
  398. {
  399. g_bBumpedByLinkedPortal = true;
  400. }
  401. bSoftBump = !bBumpedByOwnPortal;
  402. }
  403. else if ( !trBumpingEntity.startsolid && trBumpingEntity.fraction <= trEnclosingWall.fraction && trBumpingEntity.fraction <= trPortal.fraction )
  404. {
  405. tr = trBumpingEntity;
  406. bSoftBump = bSoftBumper;
  407. }
  408. else
  409. {
  410. UTIL_ClearTrace( tr );
  411. return false;
  412. }
  413. return true;
  414. }
  415. 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 )
  416. {
  417. Vector ptClosestSegment1, ptClosestSegment2;
  418. float fT1, fT2;
  419. CalcLineToLineIntersectionSegment( ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1,
  420. ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2,
  421. &ptClosestSegment1, &ptClosestSegment2, &fT1, &fT2 );
  422. Vector ptLineIntersection = ( ptClosestSegment1 + ptClosestSegment2 ) * 0.5f;
  423. // The 2 corner trace intersections and the intersection of those lines makes a triangle.
  424. // We want to make a similar triangle where the base is large enough to fit the edge of the portal
  425. // Get the the small triangle's legs and leg lengths
  426. Vector vShortLeg = ptIntersectionPoint1 - ptLineIntersection;
  427. Vector vShortLeg2 = ptIntersectionPoint2 - ptLineIntersection;
  428. float fShortLegLength = vShortLeg.Length();
  429. float fShortLeg2Length = vShortLeg2.Length();
  430. if ( fShortLegLength == 0.0f || fShortLeg2Length == 0.0f )
  431. {
  432. // FIXME: Our triangle is actually a point or a line, so there's nothing we can do
  433. return vec3_origin;
  434. }
  435. // Normalized legs
  436. vShortLeg /= fShortLegLength;
  437. vShortLeg2 /= fShortLeg2Length;
  438. // Check if corners are aligned with one of the legs
  439. Vector vCornerToCornerNorm = ptCorner2 - ptCorner1;
  440. VectorNormalize( vCornerToCornerNorm );
  441. float fPortalEdgeDotLeg = vCornerToCornerNorm.Dot( vShortLeg );
  442. float fPortalEdgeDotLeg2 = vCornerToCornerNorm.Dot( vShortLeg2 );
  443. if ( fPortalEdgeDotLeg < -0.9999f || fPortalEdgeDotLeg > 0.9999f || fPortalEdgeDotLeg2 < -0.9999f || fPortalEdgeDotLeg2 > 0.9999f )
  444. {
  445. // Do a one corner bump with corner 1
  446. float fBumpDistance1 = CalcDistanceToLine( ptCorner1, ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1 );
  447. fBumpDistance1 += PORTAL_BUMP_FORGIVENESS;
  448. // Do a one corner bump with corner 2
  449. float fBumpDistance2 = CalcDistanceToLine( ptCorner2, ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2 );
  450. fBumpDistance2 += PORTAL_BUMP_FORGIVENESS;
  451. return vIntersectionBumpDirection1 * fBumpDistance1 + vIntersectionBumpDirection2 * fBumpDistance2;
  452. }
  453. float fLegsDot = vShortLeg.Dot( vShortLeg2 );
  454. // Need to know if the triangle is pointing toward the portal or away from the portal
  455. /*bool bPointingTowardPortal = true;
  456. Vector vLineIntersectionToCornerNorm = ptCorner1 - ptLineIntersection;
  457. VectorNormalize( vLineIntersectionToCornerNorm );
  458. if ( vLineIntersectionToCornerNorm.Dot( vShortLeg2 ) < fLegsDot )
  459. {
  460. bPointingTowardPortal = false;
  461. }
  462. if ( !bPointingTowardPortal )*/
  463. {
  464. // Get the small triangle's base length
  465. float fLongBaseLength = ptCorner1.DistTo( ptCorner2 );
  466. // Get the large triangle's base length
  467. float fShortLeg2Angle = acosf( vCornerToCornerNorm.Dot( -vShortLeg ) );
  468. float fShortBaseAngle = acosf( fLegsDot );
  469. float fShortLegAngle = M_PI_F - fShortBaseAngle - fShortLeg2Angle;
  470. if ( sinf( fShortLegAngle ) == 0.0f )
  471. {
  472. return Vector( 1000.0f, 1000.0f, 1000.0f );
  473. }
  474. float fShortBaseLength = sinf( fShortBaseAngle ) * ( fShortLegLength / sinf( fShortLegAngle ) );
  475. // Avoid divide by zero
  476. if ( fShortBaseLength == 0.0f )
  477. {
  478. return Vector( 0.0f, 0.0f, 0.0f );
  479. }
  480. // Use ratio to get the big triangles leg length
  481. float fLongLegLength = fLongBaseLength * ( fShortLegLength / fShortBaseLength );
  482. // Get the relative point on the large triangle
  483. Vector ptNewCornerPos = ptLineIntersection + vShortLeg * fLongLegLength;
  484. // Bump by the same amount the corner has to move to fit
  485. return ptNewCornerPos - ptCorner1;
  486. }
  487. /*else
  488. {
  489. return Vector( 0.0f, 0.0f, 0.0f );
  490. }*/
  491. }
  492. bool FitPortalOnSurface( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight,
  493. const Vector &vTopEdge, const Vector &vBottomEdge, const Vector &vRightEdge, const Vector &vLeftEdge,
  494. PortalPlacedBy_t ePlacedBy, ITraceFilter *pTraceFilterPortalShot,
  495. float fHalfWidth, float fHalfHeight,
  496. int iRecursions /*= 0*/, const CPortalCornerFitData *pPortalCornerFitData /*= 0*/, const int *p_piIntersectionIndex /*= 0*/, const int *piIntersectionCount /*= 0*/ )
  497. {
  498. // Don't infinitely recurse
  499. if ( iRecursions >= 6 )
  500. {
  501. return false;
  502. }
  503. Vector pptCorner[ 4 ];
  504. // Get corner points
  505. pptCorner[ 0 ] = vOrigin + vTopEdge + vLeftEdge;
  506. pptCorner[ 1 ] = vOrigin + vTopEdge + vRightEdge;
  507. pptCorner[ 2 ] = vOrigin + vBottomEdge + vLeftEdge;
  508. pptCorner[ 3 ] = vOrigin + vBottomEdge + vRightEdge;
  509. // Corner data
  510. CPortalCornerFitData sFitData[ 4 ];
  511. int piIntersectionIndex[ 4 ];
  512. int iIntersectionCount = 0;
  513. // Gather data we already know
  514. if ( pPortalCornerFitData )
  515. {
  516. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  517. {
  518. sFitData[ iIntersection ] = pPortalCornerFitData[ iIntersection ];
  519. }
  520. }
  521. else
  522. {
  523. memset( sFitData, 0, sizeof( sFitData ) );
  524. }
  525. if ( p_piIntersectionIndex )
  526. {
  527. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  528. {
  529. piIntersectionIndex[ iIntersection ] = p_piIntersectionIndex[ iIntersection ];
  530. }
  531. }
  532. else
  533. {
  534. memset( piIntersectionIndex, 0, sizeof( piIntersectionIndex ) );
  535. }
  536. if ( piIntersectionCount )
  537. {
  538. iIntersectionCount = *piIntersectionCount;
  539. }
  540. int iOldIntersectionCount = iIntersectionCount;
  541. Vector vNoNormalMin( 0, 0, 0 ), vNoNormalMax( 0, 0, 0 ), vNoNormalAdditive( 0, 0, 0 );
  542. bool bNewIntersection[4] = { false };
  543. // Find intersections from center to each corner
  544. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  545. {
  546. // HACK: In weird cases intersection count can go over 3 and index outside of our arrays. Don't let this happen!
  547. if ( iIntersectionCount < 4 )
  548. {
  549. // Don't recompute intersection data that we already have
  550. if ( !sFitData[ iIntersection ].bCornerIntersection )
  551. {
  552. // Test intersection of the current corner
  553. sFitData[ iIntersection ].bCornerIntersection = TracePortalCorner( pIgnorePortal, vOrigin, pptCorner[ iIntersection ], vForward, ePlacedBy, pTraceFilterPortalShot, sFitData[ iIntersection ].trCornerTrace, sFitData[ iIntersection ].bSoftBump );
  554. // If it intersected
  555. if ( sFitData[ iIntersection ].bCornerIntersection )
  556. {
  557. sFitData[ iIntersection ].ptIntersectionPoint = vOrigin + ( pptCorner[ iIntersection ] - vOrigin ) * sFitData[ iIntersection ].trCornerTrace.fraction;
  558. if ( sFitData[ iIntersection ].trCornerTrace.plane.normal.IsZero() )
  559. {
  560. Vector vPush = sFitData[ iIntersection ].ptIntersectionPoint - pptCorner[ iIntersection ];
  561. vNoNormalMin = vNoNormalMin.Min( vPush );
  562. vNoNormalMax = vNoNormalMax.Max( vPush );
  563. vNoNormalAdditive += vPush;
  564. }
  565. bNewIntersection[iIntersection] = true;
  566. piIntersectionIndex[ iIntersectionCount ] = iIntersection;
  567. ++iIntersectionCount;
  568. }
  569. }
  570. else
  571. {
  572. // We shouldn't be intersecting with any old corners
  573. sFitData[ iIntersection ].trCornerTrace.fraction = 1.0f;
  574. }
  575. }
  576. }
  577. //clip the additive vector of pushes from intersections with no normal. We're going to give them all a shared normal that agrees
  578. vNoNormalAdditive = vNoNormalAdditive.Min( vNoNormalMax );
  579. vNoNormalAdditive = vNoNormalAdditive.Max( vNoNormalMin );
  580. vNoNormalAdditive.NormalizeInPlace();
  581. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  582. {
  583. if ( bNewIntersection[iIntersection] )
  584. {
  585. if ( sFitData[ iIntersection ].trCornerTrace.plane.normal.IsZero() )
  586. {
  587. sFitData[ iIntersection ].trCornerTrace.plane.normal = vNoNormalAdditive;
  588. sFitData[ iIntersection ].trCornerTrace.plane.dist = vNoNormalAdditive.Dot( sFitData[ iIntersection ].ptIntersectionPoint );
  589. }
  590. VectorNormalize( sFitData[ iIntersection ].trCornerTrace.plane.normal );
  591. sFitData[ iIntersection ].vIntersectionDirection = sFitData[ iIntersection ].trCornerTrace.plane.normal.Cross( vForward );
  592. VectorNormalize( sFitData[ iIntersection ].vIntersectionDirection );
  593. sFitData[ iIntersection ].vBumpDirection = vForward.Cross( sFitData[ iIntersection ].vIntersectionDirection );
  594. VectorNormalize( sFitData[ iIntersection ].vBumpDirection );
  595. if ( sv_portal_placement_debug.GetBool() )
  596. {
  597. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  598. {
  599. NDebugOverlay::Line( sFitData[ iIntersection ].ptIntersectionPoint - sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
  600. sFitData[ iIntersection ].ptIntersectionPoint + sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
  601. 0, 0, 255, true, 0.5f );
  602. }
  603. }
  604. }
  605. }
  606. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  607. {
  608. // Remember soft bumpers so we don't bump with it twice
  609. if ( sFitData[ iIntersection ].bSoftBump )
  610. {
  611. g_FuncBumpingEntityList.AddToTail( sFitData[ iIntersection ].trCornerTrace.m_pEnt );
  612. }
  613. }
  614. // If no new intersections were found then it already fits
  615. if ( iOldIntersectionCount == iIntersectionCount )
  616. {
  617. return true;
  618. }
  619. switch ( iIntersectionCount )
  620. {
  621. case 0:
  622. {
  623. // If no corners intersect it already fits
  624. return true;
  625. }
  626. break;
  627. case 1:
  628. {
  629. float fBumpDistance = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ 0 ] ],
  630. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint,
  631. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection );
  632. fBumpDistance += PORTAL_BUMP_FORGIVENESS;
  633. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection * fBumpDistance;
  634. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  635. }
  636. break;
  637. case 2:
  638. {
  639. if ( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint == sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint )
  640. {
  641. return false;
  642. }
  643. float fDot = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  644. // If there are parallel intersections try scooting it away from a near wall
  645. if ( fDot < -0.9f )
  646. {
  647. // Check if perpendicular wall is near
  648. trace_t trPerpWall1;
  649. bool bSoftBump1;
  650. bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * fHalfWidth * 2.0f, vForward, ePlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
  651. trace_t trPerpWall2;
  652. bool bSoftBump2;
  653. bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -fHalfWidth * 2.0f, vForward, ePlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
  654. // No fit if there's blocking walls on both sides it can't fit
  655. if ( bDir1 && bDir2 )
  656. {
  657. if ( bSoftBump1 )
  658. bDir1 = false;
  659. else if ( bSoftBump2 )
  660. bDir1 = true;
  661. else
  662. return false;
  663. }
  664. // If there's no assumption to make, just pick a direction.
  665. if ( !bDir1 && !bDir2 )
  666. {
  667. bDir1 = true;
  668. }
  669. // Bump the portal
  670. if ( bDir1 )
  671. {
  672. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -fHalfWidth;
  673. }
  674. else
  675. {
  676. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * fHalfWidth;
  677. }
  678. // Prepare data for recursion
  679. iIntersectionCount = 0;
  680. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  681. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  682. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  683. }
  684. // If they are the same there's an easy way
  685. if ( fDot > 0.9f )
  686. {
  687. // Get the closest intersection to the portal's center
  688. int iClosestIntersection = ( ( vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint ) < vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint ) ) ? ( 0 ) : ( 1 ) );
  689. // Find the largest amount that the portal needs to bump for the corner to pass the intersection
  690. float pfBumpDistance[ 2 ];
  691. for ( int iIntersection = 0; iIntersection < 2; ++iIntersection )
  692. {
  693. pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
  694. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
  695. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
  696. pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
  697. }
  698. int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
  699. // Bump the portal
  700. vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
  701. // If they were parallel to the intersection line don't invalidate both before recursion
  702. if ( pfBumpDistance[ 0 ] == pfBumpDistance[ 1 ] )
  703. {
  704. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  705. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  706. iIntersectionCount = 0;
  707. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  708. }
  709. else
  710. {
  711. // Prepare data for recursion
  712. if ( iLargestBump != iClosestIntersection )
  713. {
  714. sFitData[ piIntersectionIndex[ iLargestBump ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
  715. }
  716. sFitData[ piIntersectionIndex[ ( ( iLargestBump == 0 ) ? ( 1 ) : ( 0 ) ) ] ].bCornerIntersection = false;
  717. piIntersectionIndex[ 0 ] = piIntersectionIndex[ iLargestBump ];
  718. iIntersectionCount = 1;
  719. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  720. }
  721. }
  722. // Intersections are angled, bump based on math using the corner
  723. vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ 0 ] ], pptCorner[ piIntersectionIndex[ 1 ] ],
  724. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint,
  725. sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ 1 ] ].vIntersectionDirection,
  726. sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  727. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  728. }
  729. break;
  730. case 3:
  731. {
  732. // Get the relationships of the intersections
  733. float fDot[ 3 ];
  734. fDot[ 0 ] = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  735. fDot[ 1 ] = sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection );
  736. fDot[ 2 ] = sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection );
  737. int iSimilarWalls = 0;
  738. for ( int iDot = 0; iDot < 3; ++iDot )
  739. {
  740. // If there are parallel intersections try scooting it away from a near wall
  741. if ( fDot[ iDot ] < -0.99f )
  742. {
  743. // Check if perpendicular wall is near
  744. trace_t trPerpWall1;
  745. bool bSoftBump1;
  746. bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * fHalfWidth * 2.0f, vForward, ePlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
  747. trace_t trPerpWall2;
  748. bool bSoftBump2;
  749. bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -fHalfWidth * 2.0f, vForward, ePlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
  750. // No fit if there's blocking walls on both sides it can't fit
  751. if ( bDir1 && bDir2 )
  752. {
  753. if ( bSoftBump1 )
  754. bDir1 = false;
  755. else if ( bSoftBump2 )
  756. bDir1 = true;
  757. else
  758. return false;
  759. }
  760. // If there's no assumption to make, just pick a direction.
  761. if ( !bDir1 && !bDir2 )
  762. {
  763. bDir1 = true;
  764. }
  765. // Bump the portal
  766. if ( bDir1 )
  767. {
  768. vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -fHalfWidth;
  769. }
  770. else
  771. {
  772. vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * fHalfWidth;
  773. }
  774. // Prepare data for recursion
  775. iIntersectionCount = 0;
  776. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  777. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  778. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  779. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  780. }
  781. // Count similar intersections
  782. else if ( fDot[ iDot ] > 0.99f )
  783. {
  784. ++iSimilarWalls;
  785. }
  786. }
  787. // If no intersections are similar
  788. if ( iSimilarWalls == 0 )
  789. {
  790. // Total the angles between the intersections
  791. float fAngleTotal = 0.0f;
  792. for ( int iDot = 0; iDot < 3; ++iDot )
  793. {
  794. fAngleTotal += acosf( fDot[ iDot ] );
  795. }
  796. // If it's in a triangle, it can't be fit
  797. if ( M_PI_F - 0.01f < fAngleTotal && fAngleTotal < M_PI_F + 0.01f )
  798. {
  799. // If any of the bumps are soft, give it another try
  800. if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump )
  801. {
  802. // Prepare data for recursion
  803. iIntersectionCount = 0;
  804. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  805. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  806. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  807. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  808. }
  809. else
  810. {
  811. return false;
  812. }
  813. }
  814. }
  815. // If the intersections are all similar there's an easy way
  816. if ( iSimilarWalls == 3 )
  817. {
  818. // Get the closest intersection to the portal's center
  819. int iClosestIntersection = 0;
  820. float fClosestDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint );
  821. float fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint );
  822. if ( fClosestDistance > fDistance )
  823. {
  824. iClosestIntersection = 1;
  825. fClosestDistance = fDistance;
  826. }
  827. fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 2 ] ].ptIntersectionPoint );
  828. if ( fClosestDistance > fDistance )
  829. {
  830. iClosestIntersection = 2;
  831. fClosestDistance = fDistance;
  832. }
  833. // Find the largest amount that the portal needs to bump for the corner to pass the intersection
  834. float pfBumpDistance[ 3 ];
  835. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  836. {
  837. pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
  838. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
  839. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
  840. pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
  841. }
  842. int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
  843. iLargestBump = ( ( pfBumpDistance[ iLargestBump ] > pfBumpDistance[ 2 ] ) ? ( iLargestBump ) : ( 2 ) );
  844. // Bump the portal
  845. vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
  846. // Prepare data for recursion
  847. int iStillIntersecting = 0;
  848. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  849. {
  850. // Invalidate corners that were closer to the intersection line
  851. if ( pfBumpDistance[ iIntersection ] != pfBumpDistance[ iLargestBump ] )
  852. {
  853. sFitData[ piIntersectionIndex[ iIntersection ] ].bCornerIntersection = false;
  854. --iIntersectionCount;
  855. }
  856. else
  857. {
  858. sFitData[ piIntersectionIndex[ iIntersection ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
  859. piIntersectionIndex[ iStillIntersecting ] = piIntersectionIndex[ iIntersection ];
  860. ++iStillIntersecting;
  861. }
  862. }
  863. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  864. }
  865. // Get info for which corners are diagonal from each other
  866. float fLongestDist = 0.0f;
  867. int iLongestDist = 0;
  868. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  869. {
  870. float fDist = pptCorner[ piIntersectionIndex[ iIntersection ] ].DistTo( pptCorner[ piIntersectionIndex[ ( iIntersection + 1 ) % 3 ] ] );
  871. if ( fLongestDist < fDist )
  872. {
  873. fLongestDist = fDist;
  874. iLongestDist = iIntersection;
  875. }
  876. }
  877. int iIndex1, iIndex2, iIndex3;
  878. switch ( iLongestDist )
  879. {
  880. case 0:
  881. iIndex1 = 0;
  882. iIndex2 = 1;
  883. iIndex3 = 2;
  884. break;
  885. case 1:
  886. iIndex1 = 1;
  887. iIndex2 = 2;
  888. iIndex3 = 0;
  889. break;
  890. default:
  891. iIndex1 = 2;
  892. iIndex2 = 0;
  893. iIndex3 = 1;
  894. break;
  895. }
  896. // If corner is 90 degrees there my be an easy way
  897. float fCornerDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
  898. if ( fCornerDot < 0.0001f && fCornerDot > -0.0001f )
  899. {
  900. // Check if portal is aligned perfectly with intersection normals
  901. float fPortalDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( vRight );
  902. if ( fPortalDot < 0.0001f && fPortalDot > -0.0001f || fPortalDot > 0.9999f || fPortalDot < -0.9999f )
  903. {
  904. float fBump1 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex1 ] ],
  905. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint,
  906. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection );
  907. fBump1 += PORTAL_BUMP_FORGIVENESS;
  908. float fBump2 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex2 ] ],
  909. sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
  910. sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
  911. fBump2 += PORTAL_BUMP_FORGIVENESS;
  912. // Bump portal
  913. vOrigin += sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection * fBump1;
  914. vOrigin += sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection * fBump2;
  915. // Prepare recursion data
  916. iIntersectionCount = 0;
  917. sFitData[ piIntersectionIndex[ iIndex1 ] ].bCornerIntersection = false;
  918. sFitData[ piIntersectionIndex[ iIndex2 ] ].bCornerIntersection = false;
  919. sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
  920. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  921. }
  922. }
  923. vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ iIndex1 ] ], pptCorner[ piIntersectionIndex[ iIndex2 ] ],
  924. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
  925. sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection,
  926. sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection );
  927. // Prepare data for recursion
  928. iIntersectionCount = 0;
  929. sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
  930. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  931. }
  932. break;
  933. default:
  934. {
  935. if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 3 ] ].bSoftBump )
  936. {
  937. // Prepare data for recursion
  938. iIntersectionCount = 0;
  939. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  940. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  941. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  942. sFitData[ piIntersectionIndex[ 3 ] ].bCornerIntersection = false;
  943. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, pTraceFilterPortalShot, fHalfWidth, fHalfHeight, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  944. }
  945. else
  946. {
  947. // All corners intersect with no soft bumps, so it can't be fit
  948. return false;
  949. }
  950. }
  951. break;
  952. }
  953. return true;
  954. }
  955. void FitPortalAroundOtherPortals( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp, float fHalfWidth, float fHalfHeight )
  956. {
  957. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  958. if( iPortalCount != 0 )
  959. {
  960. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  961. for( int i = 0; i != iPortalCount; ++i )
  962. {
  963. CProp_Portal *pTempPortal = pPortals[i];
  964. if( pTempPortal != pIgnorePortal && pTempPortal->IsActive() )
  965. {
  966. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  967. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  968. Vector vLinkedForward;
  969. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  970. // If they're not on the same face then don't worry about overlap
  971. if ( (vForward.Dot( vLinkedForward ) < 0.95f) ||
  972. (fabs( vOrigin.Dot( vForward ) - vOtherOrigin.Dot( vLinkedForward ) ) > 1.0f) )
  973. continue;
  974. Vector vDiff = vOrigin - vOtherOrigin;
  975. Vector vDiffProjRight = vDiff.Dot( vRight ) * vRight;
  976. Vector vDiffProjUp = vDiff.Dot( vUp ) * vUp;
  977. float fProjRightLength = VectorNormalize( vDiffProjRight );
  978. float fProjUpLength = VectorNormalize( vDiffProjUp );
  979. if ( fProjRightLength < 1.0f )
  980. {
  981. vDiffProjRight = vRight;
  982. }
  983. if ( fProjUpLength < (fHalfHeight * 2.0f) && fProjRightLength < (fHalfWidth * 2.0f) )
  984. {
  985. vOrigin += vDiffProjRight * ( (fHalfWidth * 2.0f) - fProjRightLength + 1.0f );
  986. }
  987. }
  988. }
  989. }
  990. }
  991. bool IsPortalIntersectingNoPortalVolume( const Vector &vOrigin, const QAngle &qAngles, const Vector &vForward, float fHalfWidth, float fHalfHeight )
  992. {
  993. // Walk the no portal volume list, check each with box-box intersection
  994. for ( CFuncNoPortalVolume *pNoPortalEnt = GetNoPortalVolumeList(); pNoPortalEnt != NULL; pNoPortalEnt = pNoPortalEnt->m_pNext )
  995. {
  996. // Skip inactive no portal zones
  997. if ( !pNoPortalEnt->IsActive() )
  998. {
  999. continue;
  1000. }
  1001. Vector vMin;
  1002. Vector vMax;
  1003. pNoPortalEnt->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
  1004. Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
  1005. Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
  1006. // Take bump forgiveness into account on non major axies
  1007. vBoxExtents += Vector( ( ( vForward.x > 0.5f || vForward.x < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
  1008. ( ( vForward.y > 0.5f || vForward.y < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
  1009. ( ( vForward.z > 0.5f || vForward.z < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ) );
  1010. if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, vOrigin, qAngles, fHalfWidth, fHalfHeight ) )
  1011. {
  1012. if ( sv_portal_placement_debug.GetBool() )
  1013. {
  1014. NDebugOverlay::Box( Vector( 0.0f, 0.0f, 0.0f ), vMin, vMax, 0, 255, 0, 128, 0.5f );
  1015. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1016. DevMsg( "Portal placed in no portal volume.\n" );
  1017. }
  1018. return true;
  1019. }
  1020. }
  1021. // Passed the list, so we didn't hit any func_noportal_volumes
  1022. return false;
  1023. }
  1024. PortalPlacementResult_t IsPortalOverlappingOtherPortals( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const QAngle &qAngles, float fHalfWidth, float fHalfHeight,
  1025. bool bFizzleAll /*= false*/, bool bFizzlePartnerPortals /*= false*/ )
  1026. {
  1027. bool bOverlappedLinkedPortal = false;
  1028. bool bOverlappedPartnerPortal = false;
  1029. Vector vForward;
  1030. AngleVectors( qAngles, &vForward, NULL, NULL );
  1031. Vector vPortalOBBMin = Vector( 0.0f, -fHalfWidth, -fHalfHeight );
  1032. Vector vPortalOBBMax = Vector( 1.0f, fHalfWidth, fHalfHeight );
  1033. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  1034. if( iPortalCount != 0 )
  1035. {
  1036. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  1037. for( int i = 0; i != iPortalCount; ++i )
  1038. {
  1039. CProp_Portal *pTempPortal = pPortals[i];
  1040. if( pTempPortal != pIgnorePortal && pTempPortal->IsActive() )
  1041. {
  1042. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  1043. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  1044. Vector vOtherOBBMins = pTempPortal->GetLocalMins();
  1045. Vector vOtherOBBMaxs = pTempPortal->GetLocalMaxs();
  1046. Vector vLinkedForward;
  1047. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  1048. // If they're not on the same face then don't worry about overlap
  1049. if ( vForward.Dot( vLinkedForward ) < 0.95f )
  1050. continue;
  1051. if ( IsOBBIntersectingOBB( vOrigin, qAngles, vPortalOBBMin, vPortalOBBMax,
  1052. vOtherOrigin, qOtherAngles, vOtherOBBMins, vOtherOBBMaxs, 0.0f ) )
  1053. {
  1054. if ( sv_portal_placement_debug.GetBool() )
  1055. {
  1056. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1057. UTIL_Portal_NDebugOverlay( pTempPortal, 255, 0, 0, 128, false, 0.5f );
  1058. DevMsg( "Portal overlapped another portal.\n" );
  1059. }
  1060. bool bLinkedPortal = !GameRules()->IsMultiplayer() || (pTempPortal->GetFiredByPlayer() == pIgnorePortal->GetFiredByPlayer());
  1061. if ( bFizzleAll || ( !bLinkedPortal && bFizzlePartnerPortals ) )
  1062. {
  1063. pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  1064. pTempPortal->Fizzle();
  1065. if ( bLinkedPortal )
  1066. {
  1067. bOverlappedLinkedPortal = true;
  1068. }
  1069. else
  1070. {
  1071. bOverlappedPartnerPortal = true;
  1072. }
  1073. }
  1074. else
  1075. {
  1076. if ( bLinkedPortal )
  1077. {
  1078. return PORTAL_PLACEMENT_OVERLAP_LINKED;
  1079. }
  1080. else
  1081. {
  1082. bOverlappedPartnerPortal = true;
  1083. }
  1084. }
  1085. }
  1086. }
  1087. }
  1088. }
  1089. if ( bOverlappedLinkedPortal )
  1090. return PORTAL_PLACEMENT_OVERLAP_LINKED;
  1091. if ( bOverlappedPartnerPortal )
  1092. return PORTAL_PLACEMENT_OVERLAP_PARTNER_PORTAL;
  1093. return PORTAL_PLACEMENT_SUCCESS;
  1094. }
  1095. bool IsPortalOnValidSurface( const Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp, float fHalfWidth, float fHalfHeight, ITraceFilter *traceFilterPortalShot )
  1096. {
  1097. trace_t tr;
  1098. // Check if corners are on a no portal material
  1099. for ( int iCorner = 0; iCorner < 5; ++iCorner )
  1100. {
  1101. Vector ptCorner = vOrigin;
  1102. if ( iCorner < 4 )
  1103. {
  1104. if ( iCorner / 2 == 0 )
  1105. ptCorner += vUp * ( fHalfHeight - PORTAL_BUMP_FORGIVENESS * 1.1f ); //top
  1106. else
  1107. ptCorner += vUp * -( fHalfHeight - PORTAL_BUMP_FORGIVENESS * 1.1f ); //bottom
  1108. if ( iCorner % 2 == 0 )
  1109. ptCorner += vRight * -( fHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f ); //left
  1110. else
  1111. ptCorner += vRight * ( fHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f ); //right
  1112. }
  1113. Ray_t ray;
  1114. ray.Init( ptCorner + vForward, ptCorner - vForward );
  1115. enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, traceFilterPortalShot, &tr );
  1116. if ( tr.startsolid )
  1117. {
  1118. // Portal center/corner in solid
  1119. if ( sv_portal_placement_debug.GetBool() )
  1120. {
  1121. DevMsg( "Portal center or corner placed inside solid.\n" );
  1122. }
  1123. return false;
  1124. }
  1125. if ( tr.fraction == 1.0f )
  1126. {
  1127. // Check if there's a portal bumper to act as a surface
  1128. TraceBumpingEntities( ptCorner + vForward, ptCorner - vForward, tr );
  1129. if ( tr.fraction == 1.0f )
  1130. {
  1131. // No surface behind the portal
  1132. if ( sv_portal_placement_debug.GetBool() )
  1133. {
  1134. DevMsg( "Portal corner has no surface behind it.\n" );
  1135. }
  1136. return false;
  1137. }
  1138. }
  1139. if ( tr.m_pEnt && FClassnameIs( tr.m_pEnt, "func_door" ) )
  1140. {
  1141. if ( sv_portal_placement_debug.GetBool() )
  1142. {
  1143. DevMsg( "Portal placed on func_door.\n" );
  1144. }
  1145. return false;
  1146. }
  1147. if ( IsPassThroughMaterial( tr.surface ) )
  1148. {
  1149. if ( sv_portal_placement_debug.GetBool() )
  1150. {
  1151. DevMsg( "Portal placed on a pass through material.\n" );
  1152. }
  1153. return false;
  1154. }
  1155. if ( IsNoPortalMaterial( tr ) )
  1156. {
  1157. if ( sv_portal_placement_debug.GetBool() )
  1158. {
  1159. DevMsg( "Portal placed on a no portal material.\n" );
  1160. }
  1161. return false;
  1162. }
  1163. }
  1164. return true;
  1165. }
  1166. PortalPlacementResult_t VerifyPortalPlacement( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, float fHalfWidth, float fHalfHeight, PortalPlacedBy_t ePlacedBy )
  1167. {
  1168. Vector vOriginalOrigin = vOrigin;
  1169. Vector vForward, vRight, vUp;
  1170. AngleVectors( qAngles, &vForward, &vRight, &vUp );
  1171. VectorNormalize( vForward );
  1172. VectorNormalize( vRight );
  1173. VectorNormalize( vUp );
  1174. trace_t tr;
  1175. #if defined( GAME_DLL )
  1176. CTraceFilterSimpleClassnameList baseFilter( pIgnorePortal, COLLISION_GROUP_NONE );
  1177. UTIL_Portal_Trace_Filter( &baseFilter );
  1178. baseFilter.AddClassnameToIgnore( "prop_portal" );
  1179. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  1180. #else
  1181. CTraceFilterSimpleClassnameList traceFilterPortalShot( pIgnorePortal, COLLISION_GROUP_NONE );
  1182. UTIL_Portal_Trace_Filter( &traceFilterPortalShot );
  1183. traceFilterPortalShot.AddClassnameToIgnore( "prop_portal" );
  1184. #endif
  1185. // Check if center is on a surface
  1186. Ray_t ray;
  1187. ray.Init( vOrigin + vForward, vOrigin - vForward );
  1188. enginetrace->TraceRay( ray, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
  1189. if ( tr.fraction == 1.0f )
  1190. {
  1191. if ( sv_portal_placement_debug.GetBool() )
  1192. {
  1193. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1194. DevMsg( "Portal center has no surface behind it.\n" );
  1195. }
  1196. return PORTAL_PLACEMENT_INVALID_SURFACE;
  1197. }
  1198. // Check if the surface is moving
  1199. /*Vector vVelocityCheck;
  1200. AngularImpulse vAngularImpulseCheck;
  1201. IPhysicsObject *pPhysicsObject = tr.m_pEnt->VPhysicsGetObject();
  1202. if ( pPhysicsObject )
  1203. {
  1204. pPhysicsObject->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  1205. }
  1206. else
  1207. {
  1208. #if defined( GAME_DLL )
  1209. tr.m_pEnt->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  1210. #else
  1211. vVelocityCheck = tr.m_pEnt->GetAbsVelocity();
  1212. vAngularImpulseCheck = vec3_origin; //TODO: Find client equivalent of server code above for angular impulse
  1213. #endif
  1214. }*/
  1215. if ( !sv_allow_mobile_portals.GetBool() && UTIL_IsEntityMovingOrRotating( tr.m_pEnt )/*(vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin)*/ )
  1216. {
  1217. if ( sv_portal_placement_debug.GetBool() )
  1218. {
  1219. DevMsg( "Portal was on moving surface.\n" );
  1220. }
  1221. return PORTAL_PLACEMENT_INVALID_SURFACE;
  1222. }
  1223. // Check for invalid materials
  1224. if ( IsPassThroughMaterial( tr.surface ) )
  1225. {
  1226. if ( sv_portal_placement_debug.GetBool() )
  1227. {
  1228. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1229. DevMsg( "Portal placed on a pass through material.\n" );
  1230. }
  1231. return PORTAL_PLACEMENT_PASSTHROUGH_SURFACE;
  1232. }
  1233. if ( IsNoPortalMaterial( tr ) )
  1234. {
  1235. if ( sv_portal_placement_debug.GetBool() )
  1236. {
  1237. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1238. DevMsg( "Portal placed on a no portal material.\n" );
  1239. }
  1240. return PORTAL_PLACEMENT_INVALID_SURFACE;
  1241. }
  1242. // Get pointer to liked portal if it might be in the way
  1243. g_bBumpedByLinkedPortal = false;
  1244. if ( ePlacedBy == PORTAL_PLACED_BY_PLAYER && !sv_portal_placement_never_bump.GetBool() )
  1245. {
  1246. // Bump away from linked portal so it can be fit next to it
  1247. FitPortalAroundOtherPortals( pIgnorePortal, vOrigin, vForward, vRight, vUp, fHalfWidth, fHalfHeight );
  1248. }
  1249. float fBumpDistance = 0.0f;
  1250. if ( !sv_portal_placement_never_bump.GetBool() )
  1251. {
  1252. // Fit onto surface and auto bump
  1253. g_FuncBumpingEntityList.RemoveAll();
  1254. Vector vTopEdge = vUp * ( fHalfHeight - PORTAL_BUMP_FORGIVENESS );
  1255. Vector vBottomEdge = -vTopEdge;
  1256. Vector vRightEdge = vRight * ( fHalfWidth - PORTAL_BUMP_FORGIVENESS );
  1257. Vector vLeftEdge = -vRightEdge;
  1258. if ( !FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, ePlacedBy, &traceFilterPortalShot, fHalfWidth, fHalfHeight ) )
  1259. {
  1260. if ( g_bBumpedByLinkedPortal )
  1261. {
  1262. return PORTAL_PLACEMENT_OVERLAP_LINKED;
  1263. }
  1264. if ( sv_portal_placement_debug.GetBool() )
  1265. {
  1266. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1267. DevMsg( "Portal was unable to fit on surface.\n" );
  1268. }
  1269. return PORTAL_PLACEMENT_CANT_FIT;
  1270. }
  1271. // Check if it's moved too far from it's original location
  1272. fBumpDistance = vOrigin.DistToSqr( vOriginalOrigin );
  1273. if ( fBumpDistance > MAXIMUM_BUMP_DISTANCE )
  1274. {
  1275. if ( sv_portal_placement_debug.GetBool() )
  1276. {
  1277. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1278. DevMsg( "Portal adjusted too far from it's original location.\n" );
  1279. }
  1280. return PORTAL_PLACEMENT_CANT_FIT;
  1281. }
  1282. //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
  1283. if( vUp.z > 0.7f )
  1284. {
  1285. Vector vSmallForward = vForward * 0.05f;
  1286. trace_t FloorTrace;
  1287. UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (fHalfHeight + 1.5f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace );
  1288. if( FloorTrace.fraction < 1.0f )
  1289. {
  1290. //we hit floor in that 1 extra unit, now doublecheck to make sure we didn't hit something else
  1291. trace_t FloorTrace_Verify;
  1292. UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (fHalfHeight - 0.1f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace_Verify );
  1293. if( FloorTrace_Verify.fraction == 1.0f )
  1294. {
  1295. //if we're in here, we're definitely in a floor matching configuration, bump down to match the floor better
  1296. vOrigin = FloorTrace.endpos + (vUp * fHalfHeight) - vSmallForward;// - vUp * PORTAL_WALL_MIN_THICKNESS;
  1297. }
  1298. }
  1299. }
  1300. }
  1301. // Fail if it's in a no portal volume
  1302. if ( IsPortalIntersectingNoPortalVolume( vOrigin, qAngles, vForward, fHalfWidth, fHalfHeight ) )
  1303. return PORTAL_PLACEMENT_INVALID_VOLUME;
  1304. // Fail if it's overlapping the linked portal
  1305. PortalPlacementResult_t ePortalOverlapResult = IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight );
  1306. if ( ePortalOverlapResult != PORTAL_PLACEMENT_SUCCESS )
  1307. return ePortalOverlapResult;
  1308. // Fail if it's on a flagged surface material
  1309. if ( !IsPortalOnValidSurface( vOrigin, vForward, vRight, vUp, fHalfWidth, fHalfHeight, &traceFilterPortalShot ) )
  1310. {
  1311. if ( sv_portal_placement_debug.GetBool() )
  1312. {
  1313. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, fHalfWidth, fHalfHeight, 0, 0, 255, 128, false, 0.5f );
  1314. }
  1315. return PORTAL_PLACEMENT_INVALID_SURFACE;
  1316. }
  1317. // Is a floor being moved to another floor
  1318. if ( pIgnorePortal->IsFloorPortal() && vForward.z > 0.8f )
  1319. {
  1320. // Is it being moved close by
  1321. if ( pIgnorePortal->GetAbsOrigin().DistToSqr( vOrigin ) < ( fHalfWidth * fHalfWidth + fHalfHeight * fHalfHeight ) )
  1322. {
  1323. // Are there any players intersecting it?
  1324. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  1325. {
  1326. CBasePlayer *pIntersectingPlayer = UTIL_PlayerByIndex( i );
  1327. if ( pIntersectingPlayer )
  1328. {
  1329. if ( pIgnorePortal->m_PortalSimulator.IsReadyToSimulate() )
  1330. {
  1331. if ( UTIL_Portal_EntityIsInPortalHole( pIgnorePortal, pIntersectingPlayer ) )
  1332. {
  1333. // Fail! This created the portal vert hop exploit
  1334. return PORTAL_PLACEMENT_OVERLAP_LINKED;
  1335. }
  1336. }
  1337. }
  1338. }
  1339. }
  1340. }
  1341. return PORTAL_PLACEMENT_BUMPED;
  1342. }
  1343. PortalPlacementResult_t VerifyPortalPlacementAndFizzleBlockingPortals( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, float fHalfWidth, float fHalfHeight, PortalPlacedBy_t ePlacedBy )
  1344. {
  1345. PortalPlacementResult_t placementResult = VerifyPortalPlacement( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight, ePlacedBy );
  1346. if ( ePlacedBy == PORTAL_PLACED_BY_FIXED && placementResult == PORTAL_PLACEMENT_OVERLAP_LINKED )
  1347. {
  1348. // overlapping another portal. Fizzle them and try again.
  1349. IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight, true, false );
  1350. placementResult = VerifyPortalPlacement( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight, ePlacedBy );
  1351. }
  1352. else if ( ePlacedBy == PORTAL_PLACED_BY_PLAYER && placementResult == PORTAL_PLACEMENT_OVERLAP_PARTNER_PORTAL )
  1353. {
  1354. CPortalMPGameRules *pRules = PortalMPGameRules();
  1355. if( pRules && pRules->IsVS() )
  1356. {
  1357. return placementResult;
  1358. }
  1359. // overlapping partner's portal. Fizzle them and try again.
  1360. IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight, false, true );
  1361. placementResult = VerifyPortalPlacement( pIgnorePortal, vOrigin, qAngles, fHalfWidth, fHalfHeight, ePlacedBy );
  1362. }
  1363. return placementResult;
  1364. }
  1365. //-----------------------------------------------------------------------------
  1366. // Purpose: Breaks the granular placement result down into a binary "succeed/fail" state
  1367. //-----------------------------------------------------------------------------
  1368. bool PortalPlacementSucceeded( PortalPlacementResult_t eResult )
  1369. {
  1370. switch ( eResult )
  1371. {
  1372. case PORTAL_PLACEMENT_SUCCESS:
  1373. case PORTAL_PLACEMENT_BUMPED:
  1374. case PORTAL_PLACEMENT_USED_HELPER:
  1375. return true;
  1376. default:
  1377. return false;
  1378. }
  1379. }