Team Fortress 2 Source Code as on 22/4/2020
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.

1337 lines
45 KiB

  1. //========= Copyright 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 "func_noportal_volume.h"
  11. #include "BasePropDoor.h"
  12. #include "collisionutils.h"
  13. #include "decals.h"
  14. #include "physicsshadowclone.h"
  15. #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
  16. struct CPortalCornerFitData
  17. {
  18. trace_t trCornerTrace;
  19. Vector ptIntersectionPoint;
  20. Vector vIntersectionDirection;
  21. Vector vBumpDirection;
  22. bool bCornerIntersection;
  23. bool bSoftBump;
  24. };
  25. CUtlVector<CBaseEntity *> g_FuncBumpingEntityList;
  26. bool g_bBumpedByLinkedPortal;
  27. ConVar sv_portal_placement_debug ("sv_portal_placement_debug", "0", FCVAR_REPLICATED );
  28. ConVar sv_portal_placement_never_bump ("sv_portal_placement_never_bump", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
  29. bool IsMaterialInList( const csurface_t &surface, char *g_ppszMaterials[] )
  30. {
  31. char szLowerName[ 256 ];
  32. Q_strcpy( szLowerName, surface.name );
  33. Q_strlower( szLowerName );
  34. int iMaterial = 0;
  35. while ( g_ppszMaterials[ iMaterial ] )
  36. {
  37. if ( Q_strstr( szLowerName, g_ppszMaterials[ iMaterial ] ) )
  38. return true;
  39. ++iMaterial;
  40. }
  41. return false;
  42. }
  43. bool IsNoPortalMaterial( const csurface_t &surface )
  44. {
  45. if ( surface.flags & SURF_NOPORTAL )
  46. return true;
  47. const surfacedata_t *pdata = physprops->GetSurfaceData( surface.surfaceProps );
  48. if ( pdata->game.material == CHAR_TEX_GLASS )
  49. return true;
  50. // Skipping all studio models
  51. if ( StringHasPrefix( surface.name, "**studio**" ) )
  52. return true;
  53. return false;
  54. }
  55. bool IsPassThroughMaterial( const csurface_t &surface )
  56. {
  57. if ( surface.flags & SURF_SKY )
  58. return true;
  59. if ( IsMaterialInList( surface, g_ppszPortalPassThroughMaterials ) )
  60. return true;
  61. return false;
  62. }
  63. void TracePortals( const CProp_Portal *pIgnorePortal, const Vector &vForward, const Vector &vStart, const Vector &vEnd, trace_t &tr )
  64. {
  65. UTIL_ClearTrace( tr );
  66. Ray_t ray;
  67. ray.Init( vStart, vEnd );
  68. trace_t trTemp;
  69. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  70. if( iPortalCount != 0 )
  71. {
  72. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  73. for( int i = 0; i != iPortalCount; ++i )
  74. {
  75. CProp_Portal *pTempPortal = pPortals[i];
  76. if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
  77. {
  78. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  79. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  80. Vector vLinkedForward;
  81. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  82. // If they're not on the same face then don't worry about overlap
  83. if ( vForward.Dot( vLinkedForward ) < 0.95f )
  84. continue;
  85. UTIL_IntersectRayWithPortalOBBAsAABB( pTempPortal, ray, &trTemp );
  86. if ( trTemp.fraction < 1.0f && trTemp.fraction < tr.fraction )
  87. {
  88. tr = trTemp;
  89. }
  90. }
  91. }
  92. }
  93. }
  94. bool TraceBumpingEntities( const Vector &vStart, const Vector &vEnd, trace_t &tr )
  95. {
  96. UTIL_ClearTrace( tr );
  97. // We use this so portal bumpers can't squeeze a portal into not fitting
  98. bool bClosestIsSoftBumper = false;
  99. // Trace to the surface to see if there's a rotating door in the way
  100. CBaseEntity *list[1024];
  101. Ray_t ray;
  102. ray.Init( vStart, vEnd );
  103. int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, 0 );
  104. for ( int i = 0; i < nCount; i++ )
  105. {
  106. trace_t trTemp;
  107. UTIL_ClearTrace( trTemp );
  108. bool bSoftBumper = false;
  109. if ( FClassnameIs( list[i], "func_portal_bumper" ) )
  110. {
  111. bSoftBumper = true;
  112. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  113. if ( trTemp.startsolid )
  114. {
  115. trTemp.fraction = 1.0f;
  116. }
  117. }
  118. else if ( FClassnameIs( list[i], "trigger_portal_cleanser" ) )
  119. {
  120. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  121. if ( trTemp.startsolid )
  122. {
  123. trTemp.fraction = 1.0f;
  124. }
  125. }
  126. else if ( FClassnameIs( list[i], "func_noportal_volume" ) )
  127. {
  128. if ( static_cast<CFuncNoPortalVolume*>( list[i] )->IsActive() )
  129. {
  130. enginetrace->ClipRayToEntity( ray, MASK_ALL, list[i], &trTemp );
  131. // Bump by an extra 2 units so that the portal isn't touching the no portal volume
  132. Vector vDelta = trTemp.endpos - trTemp.startpos;
  133. float fLength = VectorNormalize( vDelta ) - 2.0f;
  134. if ( fLength < 0.0f )
  135. fLength = 0.0f;
  136. trTemp.fraction = fLength / ray.m_Delta.Length();
  137. trTemp.endpos = trTemp.startpos + vDelta * fLength;
  138. }
  139. else
  140. trTemp.fraction = 1.0f;
  141. }
  142. else if( FClassnameIs( list[i], "prop_door_rotating" ) )
  143. {
  144. // Check more precise door collision
  145. CBasePropDoor *pRotatingDoor = static_cast<CBasePropDoor *>( list[i] );
  146. pRotatingDoor->TestCollision( ray, 0, trTemp );
  147. }
  148. // If this is the closest and has only bumped once (for soft bumpers)
  149. if ( trTemp.fraction < tr.fraction && ( !bSoftBumper || !g_FuncBumpingEntityList.HasElement( list[i] ) ) )
  150. {
  151. tr = trTemp;
  152. bClosestIsSoftBumper = bSoftBumper;
  153. }
  154. }
  155. return bClosestIsSoftBumper;
  156. }
  157. bool TracePortalCorner( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const Vector &vCorner, const Vector &vForward, int iPlacedBy, ITraceFilter *pTraceFilterPortalShot, trace_t &tr, bool &bSoftBump )
  158. {
  159. Vector vOriginToCorner = vCorner - vOrigin;
  160. // Check for surface edge
  161. trace_t trSurfaceEdge;
  162. UTIL_TraceLine( vOrigin - vForward, vCorner - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
  163. if ( trSurfaceEdge.startsolid )
  164. {
  165. float fTotalFraction = trSurfaceEdge.fractionleftsolid;
  166. while ( trSurfaceEdge.startsolid && trSurfaceEdge.fractionleftsolid > 0.0f && fTotalFraction < 1.0f )
  167. {
  168. UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vCorner + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
  169. if ( trSurfaceEdge.startsolid )
  170. {
  171. fTotalFraction += trSurfaceEdge.fractionleftsolid + 0.05f;
  172. }
  173. }
  174. if ( fTotalFraction < 1.0f )
  175. {
  176. UTIL_TraceLine( vOrigin + vOriginToCorner * ( fTotalFraction + 0.05f ) - vForward, vOrigin - vForward, MASK_SHOT_PORTAL, pTraceFilterPortalShot, &trSurfaceEdge );
  177. if ( trSurfaceEdge.startsolid )
  178. {
  179. trSurfaceEdge.fraction = 1.0f;
  180. }
  181. else
  182. {
  183. trSurfaceEdge.fraction = fTotalFraction;
  184. trSurfaceEdge.plane.normal = -trSurfaceEdge.plane.normal;
  185. }
  186. }
  187. else
  188. {
  189. trSurfaceEdge.fraction = 1.0f;
  190. }
  191. }
  192. else
  193. {
  194. trSurfaceEdge.fraction = 1.0f;
  195. }
  196. // Check for enclosing wall
  197. trace_t trEnclosingWall;
  198. UTIL_TraceLine( vOrigin + vForward, vCorner + vForward, MASK_SOLID_BRUSHONLY|CONTENTS_MONSTER, pTraceFilterPortalShot, &trEnclosingWall );
  199. if ( trSurfaceEdge.fraction < trEnclosingWall.fraction )
  200. {
  201. trEnclosingWall.fraction = trSurfaceEdge.fraction;
  202. trEnclosingWall.plane.normal = trSurfaceEdge.plane.normal;
  203. }
  204. trace_t trPortal;
  205. trace_t trBumpingEntity;
  206. if ( iPlacedBy != PORTAL_PLACED_BY_FIXED )
  207. TracePortals( pIgnorePortal, vForward, vOrigin + vForward, vCorner + vForward, trPortal );
  208. else
  209. UTIL_ClearTrace( trPortal );
  210. bool bSoftBumper = TraceBumpingEntities( vOrigin + vForward, vCorner + vForward, trBumpingEntity );
  211. if ( trEnclosingWall.fraction >= 1.0f && trPortal.fraction >= 1.0f && trBumpingEntity.fraction >= 1.0f )
  212. {
  213. UTIL_ClearTrace( tr );
  214. return false;
  215. }
  216. if ( trEnclosingWall.fraction <= trPortal.fraction && trEnclosingWall.fraction <= trBumpingEntity.fraction )
  217. {
  218. tr = trEnclosingWall;
  219. bSoftBump = false;
  220. }
  221. else if ( trPortal.fraction <= trEnclosingWall.fraction && trPortal.fraction <= trBumpingEntity.fraction )
  222. {
  223. tr = trPortal;
  224. g_bBumpedByLinkedPortal = true;
  225. bSoftBump = false;
  226. }
  227. else if ( !trBumpingEntity.startsolid && trBumpingEntity.fraction <= trEnclosingWall.fraction && trBumpingEntity.fraction <= trPortal.fraction )
  228. {
  229. tr = trBumpingEntity;
  230. bSoftBump = bSoftBumper;
  231. }
  232. else
  233. {
  234. UTIL_ClearTrace( tr );
  235. return false;
  236. }
  237. return true;
  238. }
  239. 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 )
  240. {
  241. Vector ptClosestSegment1, ptClosestSegment2;
  242. float fT1, fT2;
  243. CalcLineToLineIntersectionSegment( ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1,
  244. ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2,
  245. &ptClosestSegment1, &ptClosestSegment2, &fT1, &fT2 );
  246. Vector ptLineIntersection = ( ptClosestSegment1 + ptClosestSegment2 ) * 0.5f;
  247. // The 2 corner trace intersections and the intersection of those lines makes a triangle.
  248. // We want to make a similar triangle where the base is large enough to fit the edge of the portal
  249. // Get the the small triangle's legs and leg lengths
  250. Vector vShortLeg = ptIntersectionPoint1 - ptLineIntersection;
  251. Vector vShortLeg2 = ptIntersectionPoint2 - ptLineIntersection;
  252. float fShortLegLength = vShortLeg.Length();
  253. float fShortLeg2Length = vShortLeg2.Length();
  254. if ( fShortLegLength == 0.0f || fShortLeg2Length == 0.0f )
  255. {
  256. // FIXME: Our triangle is actually a point or a line, so there's nothing we can do
  257. return vec3_origin;
  258. }
  259. // Normalized legs
  260. vShortLeg /= fShortLegLength;
  261. vShortLeg2 /= fShortLeg2Length;
  262. // Check if corners are aligned with one of the legs
  263. Vector vCornerToCornerNorm = ptCorner2 - ptCorner1;
  264. VectorNormalize( vCornerToCornerNorm );
  265. float fPortalEdgeDotLeg = vCornerToCornerNorm.Dot( vShortLeg );
  266. float fPortalEdgeDotLeg2 = vCornerToCornerNorm.Dot( vShortLeg2 );
  267. if ( fPortalEdgeDotLeg < -0.9999f || fPortalEdgeDotLeg > 0.9999f || fPortalEdgeDotLeg2 < -0.9999f || fPortalEdgeDotLeg2 > 0.9999f )
  268. {
  269. // Do a one corner bump with corner 1
  270. float fBumpDistance1 = CalcDistanceToLine( ptCorner1, ptIntersectionPoint1, ptIntersectionPoint1 + vIntersectionDirection1 );
  271. fBumpDistance1 += PORTAL_BUMP_FORGIVENESS;
  272. // Do a one corner bump with corner 2
  273. float fBumpDistance2 = CalcDistanceToLine( ptCorner2, ptIntersectionPoint2, ptIntersectionPoint2 + vIntersectionDirection2 );
  274. fBumpDistance2 += PORTAL_BUMP_FORGIVENESS;
  275. return vIntersectionBumpDirection1 * fBumpDistance1 + vIntersectionBumpDirection2 * fBumpDistance2;
  276. }
  277. float fLegsDot = vShortLeg.Dot( vShortLeg2 );
  278. // Need to know if the triangle is pointing toward the portal or away from the portal
  279. /*bool bPointingTowardPortal = true;
  280. Vector vLineIntersectionToCornerNorm = ptCorner1 - ptLineIntersection;
  281. VectorNormalize( vLineIntersectionToCornerNorm );
  282. if ( vLineIntersectionToCornerNorm.Dot( vShortLeg2 ) < fLegsDot )
  283. {
  284. bPointingTowardPortal = false;
  285. }
  286. if ( !bPointingTowardPortal )*/
  287. {
  288. // Get the small triangle's base length
  289. float fLongBaseLength = ptCorner1.DistTo( ptCorner2 );
  290. // Get the large triangle's base length
  291. float fShortLeg2Angle = acosf( vCornerToCornerNorm.Dot( -vShortLeg ) );
  292. float fShortBaseAngle = acosf( fLegsDot );
  293. float fShortLegAngle = M_PI_F - fShortBaseAngle - fShortLeg2Angle;
  294. if ( sinf( fShortLegAngle ) == 0.0f )
  295. {
  296. return Vector( 1000.0f, 1000.0f, 1000.0f );
  297. }
  298. float fShortBaseLength = sinf( fShortBaseAngle ) * ( fShortLegLength / sinf( fShortLegAngle ) );
  299. // Avoid divide by zero
  300. if ( fShortBaseLength == 0.0f )
  301. {
  302. return Vector( 0.0f, 0.0f, 0.0f );
  303. }
  304. // Use ratio to get the big triangles leg length
  305. float fLongLegLength = fLongBaseLength * ( fShortLegLength / fShortBaseLength );
  306. // Get the relative point on the large triangle
  307. Vector ptNewCornerPos = ptLineIntersection + vShortLeg * fLongLegLength;
  308. // Bump by the same amount the corner has to move to fit
  309. return ptNewCornerPos - ptCorner1;
  310. }
  311. /*else
  312. {
  313. return Vector( 0.0f, 0.0f, 0.0f );
  314. }*/
  315. }
  316. bool FitPortalOnSurface( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight,
  317. const Vector &vTopEdge, const Vector &vBottomEdge, const Vector &vRightEdge, const Vector &vLeftEdge,
  318. int iPlacedBy, ITraceFilter *pTraceFilterPortalShot,
  319. int iRecursions /*= 0*/, const CPortalCornerFitData *pPortalCornerFitData /*= 0*/, const int *p_piIntersectionIndex /*= 0*/, const int *piIntersectionCount /*= 0*/ )
  320. {
  321. // Don't infinitely recurse
  322. if ( iRecursions >= 6 )
  323. {
  324. return false;
  325. }
  326. Vector pptCorner[ 4 ];
  327. // Get corner points
  328. pptCorner[ 0 ] = vOrigin + vTopEdge + vLeftEdge;
  329. pptCorner[ 1 ] = vOrigin + vTopEdge + vRightEdge;
  330. pptCorner[ 2 ] = vOrigin + vBottomEdge + vLeftEdge;
  331. pptCorner[ 3 ] = vOrigin + vBottomEdge + vRightEdge;
  332. // Corner data
  333. CPortalCornerFitData sFitData[ 4 ];
  334. int piIntersectionIndex[ 4 ];
  335. int iIntersectionCount = 0;
  336. // Gather data we already know
  337. if ( pPortalCornerFitData )
  338. {
  339. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  340. {
  341. sFitData[ iIntersection ] = pPortalCornerFitData[ iIntersection ];
  342. }
  343. }
  344. else
  345. {
  346. memset( sFitData, 0, sizeof( sFitData ) );
  347. }
  348. if ( p_piIntersectionIndex )
  349. {
  350. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  351. {
  352. piIntersectionIndex[ iIntersection ] = p_piIntersectionIndex[ iIntersection ];
  353. }
  354. }
  355. else
  356. {
  357. memset( piIntersectionIndex, 0, sizeof( piIntersectionIndex ) );
  358. }
  359. if ( piIntersectionCount )
  360. {
  361. iIntersectionCount = *piIntersectionCount;
  362. }
  363. int iOldIntersectionCount = iIntersectionCount;
  364. // Find intersections from center to each corner
  365. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  366. {
  367. // HACK: In weird cases intersection count can go over 3 and index outside of our arrays. Don't let this happen!
  368. if ( iIntersectionCount < 4 )
  369. {
  370. // Don't recompute intersection data that we already have
  371. if ( !sFitData[ iIntersection ].bCornerIntersection )
  372. {
  373. // Test intersection of the current corner
  374. sFitData[ iIntersection ].bCornerIntersection = TracePortalCorner( pIgnorePortal, vOrigin, pptCorner[ iIntersection ], vForward, iPlacedBy, pTraceFilterPortalShot, sFitData[ iIntersection ].trCornerTrace, sFitData[ iIntersection ].bSoftBump );
  375. // If the intersection has no normal, ignore it
  376. if ( sFitData[ iIntersection ].trCornerTrace.plane.normal.IsZero() )
  377. sFitData[ iIntersection ].bCornerIntersection = false;
  378. // If it intersected
  379. if ( sFitData[ iIntersection ].bCornerIntersection )
  380. {
  381. sFitData[ iIntersection ].ptIntersectionPoint = vOrigin + ( pptCorner[ iIntersection ] - vOrigin ) * sFitData[ iIntersection ].trCornerTrace.fraction;
  382. VectorNormalize( sFitData[ iIntersection ].trCornerTrace.plane.normal );
  383. sFitData[ iIntersection ].vIntersectionDirection = sFitData[ iIntersection ].trCornerTrace.plane.normal.Cross( vForward );
  384. VectorNormalize( sFitData[ iIntersection ].vIntersectionDirection );
  385. sFitData[ iIntersection ].vBumpDirection = vForward.Cross( sFitData[ iIntersection ].vIntersectionDirection );
  386. VectorNormalize( sFitData[ iIntersection ].vBumpDirection );
  387. piIntersectionIndex[ iIntersectionCount ] = iIntersection;
  388. if ( sv_portal_placement_debug.GetBool() )
  389. {
  390. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  391. {
  392. NDebugOverlay::Line( sFitData[ iIntersection ].ptIntersectionPoint - sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
  393. sFitData[ iIntersection ].ptIntersectionPoint + sFitData[ iIntersection ].vIntersectionDirection * 32.0f,
  394. 0, 0, 255, true, 0.5f );
  395. }
  396. }
  397. ++iIntersectionCount;
  398. }
  399. }
  400. else
  401. {
  402. // We shouldn't be intersecting with any old corners
  403. sFitData[ iIntersection ].trCornerTrace.fraction = 1.0f;
  404. }
  405. }
  406. }
  407. for ( int iIntersection = 0; iIntersection < 4; ++iIntersection )
  408. {
  409. // Remember soft bumpers so we don't bump with it twice
  410. if ( sFitData[ iIntersection ].bSoftBump )
  411. {
  412. g_FuncBumpingEntityList.AddToTail( sFitData[ iIntersection ].trCornerTrace.m_pEnt );
  413. }
  414. }
  415. // If no new intersections were found then it already fits
  416. if ( iOldIntersectionCount == iIntersectionCount )
  417. {
  418. return true;
  419. }
  420. switch ( iIntersectionCount )
  421. {
  422. case 0:
  423. {
  424. // If no corners intersect it already fits
  425. return true;
  426. }
  427. break;
  428. case 1:
  429. {
  430. float fBumpDistance = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ 0 ] ],
  431. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint,
  432. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection );
  433. fBumpDistance += PORTAL_BUMP_FORGIVENESS;
  434. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection * fBumpDistance;
  435. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  436. }
  437. break;
  438. case 2:
  439. {
  440. if ( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint == sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint )
  441. {
  442. return false;
  443. }
  444. float fDot = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  445. // If there are parallel intersections try scooting it away from a near wall
  446. if ( fDot < -0.9f )
  447. {
  448. // Check if perpendicular wall is near
  449. trace_t trPerpWall1;
  450. bool bSoftBump1;
  451. bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
  452. trace_t trPerpWall2;
  453. bool bSoftBump2;
  454. bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
  455. // No fit if there's blocking walls on both sides it can't fit
  456. if ( bDir1 && bDir2 )
  457. {
  458. if ( bSoftBump1 )
  459. bDir1 = false;
  460. else if ( bSoftBump2 )
  461. bDir1 = true;
  462. else
  463. return false;
  464. }
  465. // If there's no assumption to make, just pick a direction.
  466. if ( !bDir1 && !bDir2 )
  467. {
  468. bDir1 = true;
  469. }
  470. // Bump the portal
  471. if ( bDir1 )
  472. {
  473. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
  474. }
  475. else
  476. {
  477. vOrigin += sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
  478. }
  479. // Prepare data for recursion
  480. iIntersectionCount = 0;
  481. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  482. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  483. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  484. }
  485. // If they are the same there's an easy way
  486. if ( fDot > 0.9f )
  487. {
  488. // Get the closest intersection to the portal's center
  489. int iClosestIntersection = ( ( vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint ) < vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint ) ) ? ( 0 ) : ( 1 ) );
  490. // Find the largest amount that the portal needs to bump for the corner to pass the intersection
  491. float pfBumpDistance[ 2 ];
  492. for ( int iIntersection = 0; iIntersection < 2; ++iIntersection )
  493. {
  494. pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
  495. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
  496. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
  497. pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
  498. }
  499. int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
  500. // Bump the portal
  501. vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
  502. // If they were parallel to the intersection line don't invalidate both before recursion
  503. if ( pfBumpDistance[ 0 ] == pfBumpDistance[ 1 ] )
  504. {
  505. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  506. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  507. iIntersectionCount = 0;
  508. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  509. }
  510. else
  511. {
  512. // Prepare data for recursion
  513. if ( iLargestBump != iClosestIntersection )
  514. {
  515. sFitData[ piIntersectionIndex[ iLargestBump ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
  516. }
  517. sFitData[ piIntersectionIndex[ ( ( iLargestBump == 0 ) ? ( 1 ) : ( 0 ) ) ] ].bCornerIntersection = false;
  518. piIntersectionIndex[ 0 ] = piIntersectionIndex[ iLargestBump ];
  519. iIntersectionCount = 1;
  520. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  521. }
  522. }
  523. // Intersections are angled, bump based on math using the corner
  524. vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ 0 ] ], pptCorner[ piIntersectionIndex[ 1 ] ],
  525. sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint,
  526. sFitData[ piIntersectionIndex[ 0 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ 1 ] ].vIntersectionDirection,
  527. sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  528. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  529. }
  530. break;
  531. case 3:
  532. {
  533. // Get the relationships of the intersections
  534. float fDot[ 3 ];
  535. fDot[ 0 ] = sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection );
  536. fDot[ 1 ] = sFitData[ piIntersectionIndex[ 1 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection );
  537. fDot[ 2 ] = sFitData[ piIntersectionIndex[ 2 ] ].vBumpDirection.Dot( sFitData[ piIntersectionIndex[ 0 ] ].vBumpDirection );
  538. int iSimilarWalls = 0;
  539. for ( int iDot = 0; iDot < 3; ++iDot )
  540. {
  541. // If there are parallel intersections try scooting it away from a near wall
  542. if ( fDot[ iDot ] < -0.99f )
  543. {
  544. // Check if perpendicular wall is near
  545. trace_t trPerpWall1;
  546. bool bSoftBump1;
  547. bool bDir1 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall1, bSoftBump1 );
  548. trace_t trPerpWall2;
  549. bool bSoftBump2;
  550. bool bDir2 = TracePortalCorner( pIgnorePortal, vOrigin, vOrigin + sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH * 2.0f, vForward, iPlacedBy, pTraceFilterPortalShot, trPerpWall2, bSoftBump2 );
  551. // No fit if there's blocking walls on both sides it can't fit
  552. if ( bDir1 && bDir2 )
  553. {
  554. if ( bSoftBump1 )
  555. bDir1 = false;
  556. else if ( bSoftBump2 )
  557. bDir1 = true;
  558. else
  559. return false;
  560. }
  561. // If there's no assumption to make, just pick a direction.
  562. if ( !bDir1 && !bDir2 )
  563. {
  564. bDir1 = true;
  565. }
  566. // Bump the portal
  567. if ( bDir1 )
  568. {
  569. vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * -PORTAL_HALF_WIDTH;
  570. }
  571. else
  572. {
  573. vOrigin += sFitData[ piIntersectionIndex[ iDot ] ].vIntersectionDirection * PORTAL_HALF_WIDTH;
  574. }
  575. // Prepare data for recursion
  576. iIntersectionCount = 0;
  577. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  578. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  579. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  580. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  581. }
  582. // Count similar intersections
  583. else if ( fDot[ iDot ] > 0.99f )
  584. {
  585. ++iSimilarWalls;
  586. }
  587. }
  588. // If no intersections are similar
  589. if ( iSimilarWalls == 0 )
  590. {
  591. // Total the angles between the intersections
  592. float fAngleTotal = 0.0f;
  593. for ( int iDot = 0; iDot < 3; ++iDot )
  594. {
  595. fAngleTotal += acosf( fDot[ iDot ] );
  596. }
  597. // If it's in a triangle, it can't be fit
  598. if ( M_PI_F - 0.01f < fAngleTotal && fAngleTotal < M_PI_F + 0.01f )
  599. {
  600. // If any of the bumps are soft, give it another try
  601. if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump )
  602. {
  603. // Prepare data for recursion
  604. iIntersectionCount = 0;
  605. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  606. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  607. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  608. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  609. }
  610. else
  611. {
  612. return false;
  613. }
  614. }
  615. }
  616. // If the intersections are all similar there's an easy way
  617. if ( iSimilarWalls == 3 )
  618. {
  619. // Get the closest intersection to the portal's center
  620. int iClosestIntersection = 0;
  621. float fClosestDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 0 ] ].ptIntersectionPoint );
  622. float fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 1 ] ].ptIntersectionPoint );
  623. if ( fClosestDistance > fDistance )
  624. {
  625. iClosestIntersection = 1;
  626. fClosestDistance = fDistance;
  627. }
  628. fDistance = vOrigin.DistTo( sFitData[ piIntersectionIndex[ 2 ] ].ptIntersectionPoint );
  629. if ( fClosestDistance > fDistance )
  630. {
  631. iClosestIntersection = 2;
  632. fClosestDistance = fDistance;
  633. }
  634. // Find the largest amount that the portal needs to bump for the corner to pass the intersection
  635. float pfBumpDistance[ 3 ];
  636. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  637. {
  638. pfBumpDistance[ iIntersection ] = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIntersection ] ],
  639. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint,
  640. sFitData[ piIntersectionIndex[ iClosestIntersection ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vIntersectionDirection );
  641. pfBumpDistance[ iIntersection ] += PORTAL_BUMP_FORGIVENESS;
  642. }
  643. int iLargestBump = ( ( pfBumpDistance[ 0 ] > pfBumpDistance[ 1 ] ) ? ( 0 ) : ( 1 ) );
  644. iLargestBump = ( ( pfBumpDistance[ iLargestBump ] > pfBumpDistance[ 2 ] ) ? ( iLargestBump ) : ( 2 ) );
  645. // Bump the portal
  646. vOrigin += sFitData[ piIntersectionIndex[ iClosestIntersection ] ].vBumpDirection * pfBumpDistance[ iLargestBump ];
  647. // Prepare data for recursion
  648. int iStillIntersecting = 0;
  649. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  650. {
  651. // Invalidate corners that were closer to the intersection line
  652. if ( pfBumpDistance[ iIntersection ] != pfBumpDistance[ iLargestBump ] )
  653. {
  654. sFitData[ piIntersectionIndex[ iIntersection ] ].bCornerIntersection = false;
  655. --iIntersectionCount;
  656. }
  657. else
  658. {
  659. sFitData[ piIntersectionIndex[ iIntersection ] ] = sFitData[ piIntersectionIndex[ iClosestIntersection ] ];
  660. piIntersectionIndex[ iStillIntersecting ] = piIntersectionIndex[ iIntersection ];
  661. ++iStillIntersecting;
  662. }
  663. }
  664. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  665. }
  666. // Get info for which corners are diagonal from each other
  667. float fLongestDist = 0.0f;
  668. int iLongestDist = 0;
  669. for ( int iIntersection = 0; iIntersection < 3; ++iIntersection )
  670. {
  671. float fDist = pptCorner[ piIntersectionIndex[ iIntersection ] ].DistTo( pptCorner[ piIntersectionIndex[ ( iIntersection + 1 ) % 3 ] ] );
  672. if ( fLongestDist < fDist )
  673. {
  674. fLongestDist = fDist;
  675. iLongestDist = iIntersection;
  676. }
  677. }
  678. int iIndex1, iIndex2, iIndex3;
  679. switch ( iLongestDist )
  680. {
  681. case 0:
  682. iIndex1 = 0;
  683. iIndex2 = 1;
  684. iIndex3 = 2;
  685. break;
  686. case 1:
  687. iIndex1 = 1;
  688. iIndex2 = 2;
  689. iIndex3 = 0;
  690. break;
  691. default:
  692. iIndex1 = 2;
  693. iIndex2 = 0;
  694. iIndex3 = 1;
  695. break;
  696. }
  697. // If corner is 90 degrees there my be an easy way
  698. float fCornerDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
  699. if ( fCornerDot < 0.0001f && fCornerDot > -0.0001f )
  700. {
  701. // Check if portal is aligned perfectly with intersection normals
  702. float fPortalDot = sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection.Dot( vRight );
  703. if ( ( fPortalDot < 0.0001f && fPortalDot > -0.0001f ) || fPortalDot > 0.9999f || fPortalDot < -0.9999f )
  704. {
  705. float fBump1 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex1 ] ],
  706. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint,
  707. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection );
  708. fBump1 += PORTAL_BUMP_FORGIVENESS;
  709. float fBump2 = CalcDistanceToLine( pptCorner[ piIntersectionIndex[ iIndex2 ] ],
  710. sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
  711. sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint + sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection );
  712. fBump2 += PORTAL_BUMP_FORGIVENESS;
  713. // Bump portal
  714. vOrigin += sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection * fBump1;
  715. vOrigin += sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection * fBump2;
  716. // Prepare recursion data
  717. iIntersectionCount = 0;
  718. sFitData[ piIntersectionIndex[ iIndex1 ] ].bCornerIntersection = false;
  719. sFitData[ piIntersectionIndex[ iIndex2 ] ].bCornerIntersection = false;
  720. sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
  721. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  722. }
  723. }
  724. vOrigin += FindBumpVectorInCorner( pptCorner[ piIntersectionIndex[ iIndex1 ] ], pptCorner[ piIntersectionIndex[ iIndex2 ] ],
  725. sFitData[ piIntersectionIndex[ iIndex1 ] ].ptIntersectionPoint, sFitData[ piIntersectionIndex[ iIndex2 ] ].ptIntersectionPoint,
  726. sFitData[ piIntersectionIndex[ iIndex1 ] ].vIntersectionDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vIntersectionDirection,
  727. sFitData[ piIntersectionIndex[ iIndex1 ] ].vBumpDirection, sFitData[ piIntersectionIndex[ iIndex2 ] ].vBumpDirection );
  728. // Prepare data for recursion
  729. iIntersectionCount = 0;
  730. sFitData[ piIntersectionIndex[ iIndex3 ] ].bCornerIntersection = false;
  731. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  732. }
  733. break;
  734. default:
  735. {
  736. if ( sFitData[ piIntersectionIndex[ 0 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 1 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 2 ] ].bSoftBump || sFitData[ piIntersectionIndex[ 3 ] ].bSoftBump )
  737. {
  738. // Prepare data for recursion
  739. iIntersectionCount = 0;
  740. sFitData[ piIntersectionIndex[ 0 ] ].bCornerIntersection = false;
  741. sFitData[ piIntersectionIndex[ 1 ] ].bCornerIntersection = false;
  742. sFitData[ piIntersectionIndex[ 2 ] ].bCornerIntersection = false;
  743. sFitData[ piIntersectionIndex[ 3 ] ].bCornerIntersection = false;
  744. return FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, pTraceFilterPortalShot, iRecursions + 1, sFitData, piIntersectionIndex, &iIntersectionCount );
  745. }
  746. else
  747. {
  748. // All corners intersect with no soft bumps, so it can't be fit
  749. return false;
  750. }
  751. }
  752. break;
  753. }
  754. return true;
  755. }
  756. void FitPortalAroundOtherPortals( const CProp_Portal *pIgnorePortal, Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp )
  757. {
  758. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  759. if( iPortalCount != 0 )
  760. {
  761. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  762. for( int i = 0; i != iPortalCount; ++i )
  763. {
  764. CProp_Portal *pTempPortal = pPortals[i];
  765. if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
  766. {
  767. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  768. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  769. Vector vLinkedForward;
  770. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  771. // If they're not on the same face then don't worry about overlap
  772. if ( vForward.Dot( vLinkedForward ) < 0.95f )
  773. continue;
  774. Vector vDiff = vOrigin - pTempPortal->GetLocalOrigin();
  775. Vector vDiffProjRight = vDiff.Dot( vRight ) * vRight;
  776. Vector vDiffProjUp = vDiff.Dot( vUp ) * vUp;
  777. float fProjRightLength = VectorNormalize( vDiffProjRight );
  778. float fProjUpLength = VectorNormalize( vDiffProjUp );
  779. if ( fProjRightLength < 1.0f )
  780. {
  781. vDiffProjRight = vRight;
  782. }
  783. if ( fProjUpLength < PORTAL_HALF_HEIGHT && fProjRightLength < PORTAL_HALF_WIDTH )
  784. {
  785. vOrigin += vDiffProjRight * ( PORTAL_HALF_WIDTH - fProjRightLength + 1.0f );
  786. }
  787. }
  788. }
  789. }
  790. }
  791. bool IsPortalIntersectingNoPortalVolume( const Vector &vOrigin, const QAngle &qAngles, const Vector &vForward )
  792. {
  793. // Walk the no portal volume list, check each with box-box intersection
  794. for ( CFuncNoPortalVolume *pNoPortalEnt = GetNoPortalVolumeList(); pNoPortalEnt != NULL; pNoPortalEnt = pNoPortalEnt->m_pNext )
  795. {
  796. // Skip inactive no portal zones
  797. if ( !pNoPortalEnt->IsActive() )
  798. {
  799. continue;
  800. }
  801. Vector vMin;
  802. Vector vMax;
  803. pNoPortalEnt->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
  804. Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
  805. Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
  806. // Take bump forgiveness into account on non major axies
  807. vBoxExtents += Vector( ( ( vForward.x > 0.5f || vForward.x < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
  808. ( ( vForward.y > 0.5f || vForward.y < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ),
  809. ( ( vForward.z > 0.5f || vForward.z < -0.5f ) ? ( 0.0f ) : ( -PORTAL_BUMP_FORGIVENESS ) ) );
  810. if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, vOrigin, qAngles ) )
  811. {
  812. if ( sv_portal_placement_debug.GetBool() )
  813. {
  814. NDebugOverlay::Box( Vector( 0.0f, 0.0f, 0.0f ), vMin, vMax, 0, 255, 0, 128, 0.5f );
  815. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  816. DevMsg( "Portal placed in no portal volume.\n" );
  817. }
  818. return true;
  819. }
  820. }
  821. // Passed the list, so we didn't hit any func_noportal_volumes
  822. return false;
  823. }
  824. bool IsPortalOverlappingOtherPortals( const CProp_Portal *pIgnorePortal, const Vector &vOrigin, const QAngle &qAngles, bool bFizzle /*= false*/ )
  825. {
  826. bool bOverlappedOtherPortal = false;
  827. Vector vForward;
  828. AngleVectors( qAngles, &vForward, NULL, NULL );
  829. Vector vPortalOBBMin = CProp_Portal_Shared::vLocalMins + Vector( 1.0f, 1.0f, 1.0f );
  830. Vector vPortalOBBMax = CProp_Portal_Shared::vLocalMaxs - Vector( 1.0f, 1.0f, 1.0f );
  831. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  832. if( iPortalCount != 0 )
  833. {
  834. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  835. for( int i = 0; i != iPortalCount; ++i )
  836. {
  837. CProp_Portal *pTempPortal = pPortals[i];
  838. if( pTempPortal != pIgnorePortal && pTempPortal->m_bActivated )
  839. {
  840. Vector vOtherOrigin = pTempPortal->GetAbsOrigin();
  841. QAngle qOtherAngles = pTempPortal->GetAbsAngles();
  842. Vector vLinkedForward;
  843. AngleVectors( qOtherAngles, &vLinkedForward, NULL, NULL );
  844. // If they're not on the same face then don't worry about overlap
  845. if ( vForward.Dot( vLinkedForward ) < 0.95f )
  846. continue;
  847. if ( IsOBBIntersectingOBB( vOrigin, qAngles, vPortalOBBMin, vPortalOBBMax,
  848. vOtherOrigin, qOtherAngles, vPortalOBBMin, vPortalOBBMax, 0.0f ) )
  849. {
  850. if ( sv_portal_placement_debug.GetBool() )
  851. {
  852. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  853. UTIL_Portal_NDebugOverlay( pTempPortal, 255, 0, 0, 128, false, 0.5f );
  854. DevMsg( "Portal overlapped another portal.\n" );
  855. }
  856. if ( bFizzle )
  857. {
  858. pTempPortal->DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  859. pTempPortal->Fizzle();
  860. bOverlappedOtherPortal = true;
  861. }
  862. else
  863. {
  864. return true;
  865. }
  866. }
  867. }
  868. }
  869. }
  870. return bOverlappedOtherPortal;
  871. }
  872. bool IsPortalOnValidSurface( const Vector &vOrigin, const Vector &vForward, const Vector &vRight, const Vector &vUp, ITraceFilter *traceFilterPortalShot )
  873. {
  874. trace_t tr;
  875. // Check if corners are on a no portal material
  876. for ( int iCorner = 0; iCorner < 5; ++iCorner )
  877. {
  878. Vector ptCorner = vOrigin;
  879. if ( iCorner < 4 )
  880. {
  881. if ( iCorner / 2 == 0 )
  882. ptCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //top
  883. else
  884. ptCorner += vUp * -( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f ); //bottom
  885. if ( iCorner % 2 == 0 )
  886. ptCorner += vRight * -( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //left
  887. else
  888. ptCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f ); //right
  889. }
  890. Ray_t ray;
  891. ray.Init( ptCorner + vForward, ptCorner - vForward );
  892. enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, traceFilterPortalShot, &tr );
  893. if ( tr.startsolid )
  894. {
  895. // Portal center/corner in solid
  896. if ( sv_portal_placement_debug.GetBool() )
  897. {
  898. DevMsg( "Portal center or corner placed inside solid.\n" );
  899. }
  900. return false;
  901. }
  902. if ( tr.fraction == 1.0f )
  903. {
  904. // Check if there's a portal bumper to act as a surface
  905. TraceBumpingEntities( ptCorner + vForward, ptCorner - vForward, tr );
  906. if ( tr.fraction == 1.0f )
  907. {
  908. // No surface behind the portal
  909. if ( sv_portal_placement_debug.GetBool() )
  910. {
  911. DevMsg( "Portal corner has no surface behind it.\n" );
  912. }
  913. return false;
  914. }
  915. }
  916. if ( tr.m_pEnt && FClassnameIs( tr.m_pEnt, "func_door" ) )
  917. {
  918. if ( sv_portal_placement_debug.GetBool() )
  919. {
  920. DevMsg( "Portal placed on func_door.\n" );
  921. }
  922. return false;
  923. }
  924. if ( IsPassThroughMaterial( tr.surface ) )
  925. {
  926. if ( sv_portal_placement_debug.GetBool() )
  927. {
  928. DevMsg( "Portal placed on a pass through material.\n" );
  929. }
  930. return false;
  931. }
  932. if ( IsNoPortalMaterial( tr.surface ) )
  933. {
  934. if ( sv_portal_placement_debug.GetBool() )
  935. {
  936. DevMsg( "Portal placed on a no portal material.\n" );
  937. }
  938. return false;
  939. }
  940. }
  941. return true;
  942. }
  943. float VerifyPortalPlacement( const CProp_Portal *pIgnorePortal, Vector &vOrigin, QAngle &qAngles, int iPlacedBy, bool bTest /*= false*/ )
  944. {
  945. Vector vOriginalOrigin = vOrigin;
  946. Vector vForward, vRight, vUp;
  947. AngleVectors( qAngles, &vForward, &vRight, &vUp );
  948. VectorNormalize( vForward );
  949. VectorNormalize( vRight );
  950. VectorNormalize( vUp );
  951. trace_t tr;
  952. CTraceFilterSimpleClassnameList baseFilter( pIgnorePortal, COLLISION_GROUP_NONE );
  953. UTIL_Portal_Trace_Filter( &baseFilter );
  954. baseFilter.AddClassnameToIgnore( "prop_portal" );
  955. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  956. // Check if center is on a surface
  957. Ray_t ray;
  958. ray.Init( vOrigin + vForward, vOrigin - vForward );
  959. enginetrace->TraceRay( ray, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
  960. if ( tr.fraction == 1.0f )
  961. {
  962. if ( sv_portal_placement_debug.GetBool() )
  963. {
  964. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  965. DevMsg( "Portal center has no surface behind it.\n" );
  966. }
  967. return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
  968. }
  969. // Check if the surface is moving
  970. Vector vVelocityCheck;
  971. AngularImpulse vAngularImpulseCheck;
  972. IPhysicsObject *pPhysicsObject = tr.m_pEnt->VPhysicsGetObject();
  973. if ( pPhysicsObject )
  974. {
  975. pPhysicsObject->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  976. }
  977. else
  978. {
  979. tr.m_pEnt->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  980. }
  981. if ( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
  982. {
  983. if ( sv_portal_placement_debug.GetBool() )
  984. {
  985. DevMsg( "Portal was on moving surface.\n" );
  986. }
  987. return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
  988. }
  989. // Check for invalid materials
  990. if ( IsPassThroughMaterial( tr.surface ) )
  991. {
  992. if ( sv_portal_placement_debug.GetBool() )
  993. {
  994. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  995. DevMsg( "Portal placed on a pass through material.\n" );
  996. }
  997. return PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE;
  998. }
  999. if ( IsNoPortalMaterial( tr.surface ) )
  1000. {
  1001. if ( sv_portal_placement_debug.GetBool() )
  1002. {
  1003. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  1004. DevMsg( "Portal placed on a no portal material.\n" );
  1005. }
  1006. return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
  1007. }
  1008. // Get pointer to liked portal if it might be in the way
  1009. g_bBumpedByLinkedPortal = false;
  1010. if ( iPlacedBy == PORTAL_PLACED_BY_PLAYER && !sv_portal_placement_never_bump.GetBool() )
  1011. {
  1012. // Bump away from linked portal so it can be fit next to it
  1013. FitPortalAroundOtherPortals( pIgnorePortal, vOrigin, vForward, vRight, vUp );
  1014. }
  1015. float fBumpDistance = 0.0f;
  1016. if ( !sv_portal_placement_never_bump.GetBool() )
  1017. {
  1018. // Fit onto surface and auto bump
  1019. g_FuncBumpingEntityList.RemoveAll();
  1020. Vector vTopEdge = vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS );
  1021. Vector vBottomEdge = -vTopEdge;
  1022. Vector vRightEdge = vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS );
  1023. Vector vLeftEdge = -vRightEdge;
  1024. if ( !FitPortalOnSurface( pIgnorePortal, vOrigin, vForward, vRight, vTopEdge, vBottomEdge, vRightEdge, vLeftEdge, iPlacedBy, &traceFilterPortalShot ) )
  1025. {
  1026. if ( g_bBumpedByLinkedPortal )
  1027. {
  1028. return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
  1029. }
  1030. if ( sv_portal_placement_debug.GetBool() )
  1031. {
  1032. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  1033. DevMsg( "Portal was unable to fit on surface.\n" );
  1034. }
  1035. return PORTAL_ANALOG_SUCCESS_CANT_FIT;
  1036. }
  1037. // Check if it's moved too far from it's original location
  1038. fBumpDistance = vOrigin.DistToSqr( vOriginalOrigin );
  1039. if ( fBumpDistance > MAXIMUM_BUMP_DISTANCE )
  1040. {
  1041. if ( sv_portal_placement_debug.GetBool() )
  1042. {
  1043. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  1044. DevMsg( "Portal adjusted too far from it's original location.\n" );
  1045. }
  1046. return PORTAL_ANALOG_SUCCESS_CANT_FIT;
  1047. }
  1048. //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
  1049. if( vUp.z > 0.7f )
  1050. {
  1051. Vector vSmallForward = vForward * 0.05f;
  1052. trace_t FloorTrace;
  1053. UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT + 1.5f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace );
  1054. if( FloorTrace.fraction < 1.0f )
  1055. {
  1056. //we hit floor in that 1 extra unit, now doublecheck to make sure we didn't hit something else
  1057. trace_t FloorTrace_Verify;
  1058. UTIL_TraceLine( vOrigin + vSmallForward, vOrigin + vSmallForward - (vUp * (PORTAL_HALF_HEIGHT - 0.1f)), MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &FloorTrace_Verify );
  1059. if( FloorTrace_Verify.fraction == 1.0f )
  1060. {
  1061. //if we're in here, we're definitely in a floor matching configuration, bump down to match the floor better
  1062. vOrigin = FloorTrace.endpos + (vUp * PORTAL_HALF_HEIGHT) - vSmallForward;// - vUp * PORTAL_WALL_MIN_THICKNESS;
  1063. }
  1064. }
  1065. }
  1066. }
  1067. // Fail if it's in a no portal volume
  1068. if ( IsPortalIntersectingNoPortalVolume( vOrigin, qAngles, vForward ) )
  1069. {
  1070. return PORTAL_ANALOG_SUCCESS_INVALID_VOLUME;
  1071. }
  1072. // Fail if it's overlapping the linked portal
  1073. if ( bTest && IsPortalOverlappingOtherPortals( pIgnorePortal, vOrigin, qAngles ) )
  1074. {
  1075. return PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED;
  1076. }
  1077. // Fail if it's on a flagged surface material
  1078. if ( !IsPortalOnValidSurface( vOrigin, vForward, vRight, vUp, &traceFilterPortalShot ) )
  1079. {
  1080. if ( sv_portal_placement_debug.GetBool() )
  1081. {
  1082. UTIL_Portal_NDebugOverlay( vOrigin, qAngles, 0, 0, 255, 128, false, 0.5f );
  1083. }
  1084. return PORTAL_ANALOG_SUCCESS_INVALID_SURFACE;
  1085. }
  1086. float fAnalogSuccessMultiplier = 1.0f - ( fBumpDistance / MAXIMUM_BUMP_DISTANCE );
  1087. fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
  1088. fAnalogSuccessMultiplier *= fAnalogSuccessMultiplier;
  1089. return fAnalogSuccessMultiplier * ( PORTAL_ANALOG_SUCCESS_NO_BUMP - PORTAL_ANALOG_SUCCESS_BUMPED ) + PORTAL_ANALOG_SUCCESS_BUMPED;
  1090. }