//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "render_pch.h" #include "client.h" #include "debug_leafvis.h" #include "con_nprint.h" #include "tier0/fasttimer.h" #include "r_areaportal.h" #include "cmodel_engine.h" #include "con_nprint.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar r_ClipAreaPortals( "r_ClipAreaPortals", "1", FCVAR_CHEAT ); ConVar r_DrawPortals( "r_DrawPortals", "0", FCVAR_CHEAT ); CUtlVector g_PortalRects; static bool g_bViewerInSolidSpace = false; // ------------------------------------------------------------------------------------ // // Classes. // ------------------------------------------------------------------------------------ // #define MAX_PORTAL_VERTS 32 class CAreaCullInfo { public: CAreaCullInfo() { m_GlobalCounter = 0; memset( &m_Frustum, 0, sizeof( m_Frustum ) ); memset( &m_Rect, 0, sizeof( m_Rect ) ); } Frustum_t m_Frustum; CPortalRect m_Rect; unsigned short m_GlobalCounter; // Used to tell if it's been touched yet this frame. }; // ------------------------------------------------------------------------------------ // // Globals. // ------------------------------------------------------------------------------------ // // Visible areas from the client DLL + occluded areas using area portals. unsigned char g_RenderAreaBits[32]; // Used to prevent it from coming back into portals while flowing through them. static unsigned char g_AreaStack[32]; // Frustums for each area for the current frame. Used to cull out leaves. static CUtlVector g_AreaCullInfo; // List of areas marked visible this frame. static unsigned short g_VisibleAreas[MAX_MAP_AREAS]; static int g_nVisibleAreas; // Tied to CAreaCullInfo::m_GlobalCounter. static unsigned short g_GlobalCounter = 1; // A copy of the current view setup, but possibly nobbled a bit. static CViewSetup g_viewSetup; static CPortalRect g_viewWindow; // Maps from world space to normalised (-1,1) screen coords. static VMatrix g_ScreenFromWorldProjection; // ------------------------------------------------------------------------------------ // // Functions. // ------------------------------------------------------------------------------------ // void R_Areaportal_LevelInit() { g_AreaCullInfo.SetCount( host_state.worldbrush->m_nAreas ); } void R_Areaportal_LevelShutdown() { g_AreaCullInfo.Purge(); g_PortalRects.Purge(); } static inline void R_SetBit( unsigned char *pBits, int bit ) { pBits[bit>>3] |= (1 << (bit&7)); } static inline void R_ClearBit( unsigned char *pBits, int bit ) { pBits[bit>>3] &= ~(1 << (bit&7)); } static inline unsigned char R_TestBit( unsigned char *pBits, int bit ) { return pBits[bit>>3] & (1 << (bit&7)); } struct portalclip_t { portalclip_t() { lists[0] = v0; lists[1] = v1; } Vector v0[MAX_PORTAL_VERTS]; Vector v1[MAX_PORTAL_VERTS]; Vector *lists[2]; }; // Transforms and clips the portal's verts to the view frustum. Returns false // if the verts lie outside the frustum. static inline bool GetPortalScreenExtents( dareaportal_t *pPortal, portalclip_t * RESTRICT clip, CPortalRect &portalRect , float *pReflectionWaterHeight ) { portalRect.left = portalRect.bottom = 1e24; portalRect.right = portalRect.top = -1e24; bool bValidExtents = false; worldbrushdata_t *pBrushData = host_state.worldbrush; int nStartVerts = min( (int)pPortal->m_nClipPortalVerts, MAX_PORTAL_VERTS ); // NOTE: We need two passes to deal with reflection. We need to compute // the screen extents for both the reflected + non-reflected area portals // and make bounds that surrounds them both. int nPassCount = ( pReflectionWaterHeight != NULL ) ? 2 : 1; for ( int j = 0; j < nPassCount; ++j ) { int i; for( i=0; i < nStartVerts; i++ ) { clip->v0[i] = pBrushData->m_pClipPortalVerts[pPortal->m_FirstClipPortalVert+i]; // 2nd pass is to compute the reflected areaportal position if ( j == 1 ) { clip->v0[i].z = 2.0f * ( *pReflectionWaterHeight ) - clip->v0[i].z; } } int iCurList = 0; bool bAllClipped = false; for( int iPlane=0; iPlane < 4; iPlane++ ) { const cplane_t *pPlane = g_Frustum.GetPlane(iPlane); Vector *pIn = clip->lists[iCurList]; Vector *pOut = clip->lists[!iCurList]; int nOutVerts = 0; int iPrev = nStartVerts - 1; float flPrevDot = pPlane->normal.Dot( pIn[iPrev] ) - pPlane->dist; for( int iCur=0; iCur < nStartVerts; iCur++ ) { float flCurDot = pPlane->normal.Dot( pIn[iCur] ) - pPlane->dist; if( (flCurDot > 0) != (flPrevDot > 0) ) { if( nOutVerts < MAX_PORTAL_VERTS ) { // Add the vert at the intersection. float t = flPrevDot / (flPrevDot - flCurDot); VectorLerp( pIn[iPrev], pIn[iCur], t, pOut[nOutVerts] ); ++nOutVerts; } } // Add this vert? if( flCurDot > 0 ) { if( nOutVerts < MAX_PORTAL_VERTS ) { pOut[nOutVerts] = pIn[iCur]; ++nOutVerts; } } flPrevDot = flCurDot; iPrev = iCur; } if( nOutVerts == 0 ) { // If they're all behind, then this portal is clipped out. bAllClipped = true; break; } nStartVerts = nOutVerts; iCurList = !iCurList; } if ( bAllClipped ) continue; // Project all the verts and figure out the screen extents. Vector screenPos; Assert( iCurList == 0 ); for( i=0; i < nStartVerts; i++ ) { Vector &point = clip->v0[i]; g_EngineRenderer->ClipTransformWithProjection ( g_ScreenFromWorldProjection, point, &screenPos ); portalRect.left = fpmin( screenPos.x, portalRect.left ); portalRect.bottom = fpmin( screenPos.y, portalRect.bottom ); portalRect.top = fpmax( screenPos.y, portalRect.top ); portalRect.right = fpmax( screenPos.x, portalRect.right ); } bValidExtents = true; } if ( !bValidExtents ) { portalRect.left = portalRect.bottom = 0; portalRect.right = portalRect.top = 0; } return bValidExtents; } // Fill in the intersection between the two rectangles. inline bool GetRectIntersection( CPortalRect const *pRect1, CPortalRect const *pRect2, CPortalRect *pOut ) { pOut->left = fpmax( pRect1->left, pRect2->left ); pOut->right = fpmin( pRect1->right, pRect2->right ); if( pOut->left >= pOut->right ) return false; pOut->bottom = fpmax( pRect1->bottom, pRect2->bottom ); pOut->top = fpmin( pRect1->top, pRect2->top ); if( pOut->bottom >= pOut->top ) return false; return true; } static void R_FlowThroughArea( int area, const Vector &vecVisOrigin, const CPortalRect *pClipRect, const VisOverrideData_t* pVisData, float *pReflectionWaterHeight ) { #ifndef SWDS // Update this area's frustum. if( g_AreaCullInfo[area].m_GlobalCounter != g_GlobalCounter ) { g_VisibleAreas[g_nVisibleAreas] = area; ++g_nVisibleAreas; g_AreaCullInfo[area].m_GlobalCounter = g_GlobalCounter; g_AreaCullInfo[area].m_Rect = *pClipRect; } else { // Expand the areaportal's rectangle to include the new cliprect. CPortalRect *pFrustumRect = &g_AreaCullInfo[area].m_Rect; pFrustumRect->left = fpmin( pFrustumRect->left, pClipRect->left ); pFrustumRect->bottom = fpmin( pFrustumRect->bottom, pClipRect->bottom ); pFrustumRect->top = fpmax( pFrustumRect->top, pClipRect->top ); pFrustumRect->right = fpmax( pFrustumRect->right, pClipRect->right ); } // Mark this area as visible. R_SetBit( g_RenderAreaBits, area ); // Set that we're in this area on the stack. R_SetBit( g_AreaStack, area ); worldbrushdata_t *pBrushData = host_state.worldbrush; Assert( area < host_state.worldbrush->m_nAreas ); darea_t *pArea = &host_state.worldbrush->m_pAreas[area]; // temp buffer for clipping portalclip_t clipTmp; // Check all areas that connect to this area. for( int iAreaPortal=0; iAreaPortal < pArea->numareaportals; iAreaPortal++ ) { Assert( pArea->firstareaportal + iAreaPortal < pBrushData->m_nAreaPortals ); dareaportal_t *pAreaPortal = &pBrushData->m_pAreaPortals[ pArea->firstareaportal + iAreaPortal ]; // Don't flow back into a portal on the stack. if( R_TestBit( g_AreaStack, pAreaPortal->otherarea ) ) continue; // If this portal is closed, don't go through it. if ( !R_TestBit( cl.m_chAreaPortalBits, pAreaPortal->m_PortalKey ) ) continue; // Make sure the viewer is on the right side of the portal to see through it. cplane_t *pPlane = &pBrushData->planes[ pAreaPortal->planenum ]; // Use the specified vis origin to test backface culling, or the main view if none was specified float flDist = pPlane->normal.Dot( vecVisOrigin ) - pPlane->dist; if( flDist < -0.1f ) continue; // If the client doesn't want this area visible, don't try to flow into it. if( !R_TestBit( cl.m_chAreaBits, pAreaPortal->otherarea ) ) continue; CPortalRect portalRect; bool portalVis = true; // don't try to clip portals if the viewer is practically in the plane float fDistTolerance = (pVisData)?(pVisData->m_fDistToAreaPortalTolerance):(0.1f); if ( flDist > fDistTolerance ) { portalVis = GetPortalScreenExtents( pAreaPortal, &clipTmp, portalRect, pReflectionWaterHeight ); } else { portalRect.left = -1; portalRect.top = 1; portalRect.right = 1; portalRect.bottom = -1; // note top/bottom reversed! //portalVis=true - not needed, default } if( portalVis ) { CPortalRect intersection; if( GetRectIntersection( &portalRect, pClipRect, &intersection ) ) { #ifdef USE_CONVARS if( r_DrawPortals.GetInt() ) { g_PortalRects.AddToTail( intersection ); } #endif // Ok, we can see into this area. R_FlowThroughArea( pAreaPortal->otherarea, vecVisOrigin, &intersection, pVisData, pReflectionWaterHeight ); } } } // Mark that we're leaving this area. R_ClearBit( g_AreaStack, area ); #endif } static void IncrementGlobalCounter() { if( g_GlobalCounter == 0xFFFF ) { for( int i=0; i < g_AreaCullInfo.Count(); i++ ) g_AreaCullInfo[i].m_GlobalCounter = 0; g_GlobalCounter = 1; } else { g_GlobalCounter++; } } static void R_SetupGlobalFrustum() { #ifndef SWDS // Copy the current view away so that we can play with it if needed. g_viewSetup = g_EngineRenderer->ViewGetCurrent(); if( g_viewSetup.m_bOrtho ) { g_viewWindow.right = g_viewSetup.m_OrthoRight; g_viewWindow.left = g_viewSetup.m_OrthoLeft; g_viewWindow.top = g_viewSetup.m_OrthoTop; g_viewWindow.bottom = g_viewSetup.m_OrthoBottom; } else { // Assuming a view plane distance of 1, figure out the boundaries of a window // the view would project into given the FOV. float xFOV = g_EngineRenderer->GetFov() * 0.5f; float yFOV = g_EngineRenderer->GetFovY() * 0.5f; g_viewWindow.right = tan( DEG2RAD( xFOV ) ); g_viewWindow.left = -g_viewWindow.right; g_viewWindow.top = tan( DEG2RAD( yFOV ) ); g_viewWindow.bottom = -g_viewWindow.top; if ( g_viewSetup.m_bOffCenter ) { // How did this ever work? Assert ( !"test m_bOffCenter frustums with area portals" ); } else if ( g_viewSetup.m_bViewToProjectionOverride ) { // ...this has been tested and works! } // Rather than try to deal with crazy projection matrices (shear, trapezoid, etc), take whatever FOV we're given, // assume it's conservative, and then construct a matching projection matrix for it. Then use that consistent // hallucination throughout, rather than refer back to the engine's actual proj. matrix for anything. VMatrix matrixView; VMatrix matrixProjection; VMatrix matrixWorldToScreen; g_viewSetup.m_bViewToProjectionOverride = false; ComputeViewMatrices ( &matrixView, &matrixProjection, &matrixWorldToScreen, g_viewSetup ); g_ScreenFromWorldProjection = matrixWorldToScreen; } #endif //#ifndef SWDS } ConVar r_snapportal( "r_snapportal", "-1" ); extern void CSGFrustum( Frustum_t &frustum ); static void R_SetupVisibleAreaFrustums() { #ifndef SWDS // Now scale the portals as specified in the normalized view frustum (-1,-1,1,1) // into our view window and generate planes out of that. for( int i=0; i < g_nVisibleAreas; i++ ) { CAreaCullInfo *pInfo = &g_AreaCullInfo[ g_VisibleAreas[i] ]; CPortalRect portalWindow; portalWindow.left = RemapVal( pInfo->m_Rect.left, -1, 1, g_viewWindow.left, g_viewWindow.right ); portalWindow.right = RemapVal( pInfo->m_Rect.right, -1, 1, g_viewWindow.left, g_viewWindow.right ); portalWindow.top = RemapVal( pInfo->m_Rect.top, -1, 1, g_viewWindow.bottom, g_viewWindow.top ); portalWindow.bottom = RemapVal( pInfo->m_Rect.bottom, -1, 1, g_viewWindow.bottom, g_viewWindow.top ); if( g_viewSetup.m_bOrtho ) { // Left and right planes... float orgOffset = DotProduct(CurrentViewOrigin(), CurrentViewRight()); pInfo->m_Frustum.SetPlane( FRUSTUM_LEFT, PLANE_ANYZ, CurrentViewRight(), portalWindow.left + orgOffset ); pInfo->m_Frustum.SetPlane( FRUSTUM_RIGHT, PLANE_ANYZ, -CurrentViewRight(), -portalWindow.right - orgOffset ); // Top and bottom planes... orgOffset = DotProduct(CurrentViewOrigin(), CurrentViewUp()); pInfo->m_Frustum.SetPlane( FRUSTUM_TOP, PLANE_ANYZ, CurrentViewUp(), portalWindow.top + orgOffset ); pInfo->m_Frustum.SetPlane( FRUSTUM_BOTTOM, PLANE_ANYZ, -CurrentViewUp(), -portalWindow.bottom - orgOffset ); } else { if ( g_viewSetup.m_bOffCenter ) { // How did this ever work? Assert ( !"test m_bOffCenter frustums with area portals" ); } else if ( g_viewSetup.m_bViewToProjectionOverride ) { // ...this has been tested and works! } Vector normal; const cplane_t *pTest; // right side normal = portalWindow.right * CurrentViewForward() - CurrentViewRight(); VectorNormalize(normal); // OPTIMIZE: This is unnecessary for culling pTest = pInfo->m_Frustum.GetPlane( FRUSTUM_RIGHT ); pInfo->m_Frustum.SetPlane( FRUSTUM_RIGHT, PLANE_ANYZ, normal, DotProduct(normal,CurrentViewOrigin()) ); // left side normal = CurrentViewRight() - portalWindow.left * CurrentViewForward(); VectorNormalize(normal); // OPTIMIZE: This is unnecessary for culling pTest = pInfo->m_Frustum.GetPlane( FRUSTUM_LEFT ); pInfo->m_Frustum.SetPlane( FRUSTUM_LEFT, PLANE_ANYZ, normal, DotProduct(normal,CurrentViewOrigin()) ); // top normal = portalWindow.top * CurrentViewForward() - CurrentViewUp(); VectorNormalize(normal); // OPTIMIZE: This is unnecessary for culling pTest = pInfo->m_Frustum.GetPlane( FRUSTUM_TOP ); pInfo->m_Frustum.SetPlane( FRUSTUM_TOP, PLANE_ANYZ, normal, DotProduct(normal,CurrentViewOrigin()) ); // bottom normal = CurrentViewUp() - portalWindow.bottom * CurrentViewForward(); VectorNormalize(normal); // OPTIMIZE: This is unnecessary for culling pTest = pInfo->m_Frustum.GetPlane( FRUSTUM_BOTTOM ); pInfo->m_Frustum.SetPlane( FRUSTUM_BOTTOM, PLANE_ANYZ, normal, DotProduct(normal,CurrentViewOrigin()) ); // farz pInfo->m_Frustum.SetPlane( FRUSTUM_FARZ, PLANE_ANYZ, -CurrentViewForward(), DotProduct(-CurrentViewForward(), CurrentViewOrigin() + CurrentViewForward()*g_viewSetup.zFar) ); } // DEBUG: Code to visualize the areaportal frustums in 3D // Useful for debugging extern void CSGFrustum( Frustum_t &frustum ); if ( r_snapportal.GetInt() >= 0 ) { if ( g_VisibleAreas[i] == r_snapportal.GetInt() ) { pInfo->m_Frustum.SetPlane( FRUSTUM_NEARZ, PLANE_ANYZ, CurrentViewForward(), DotProduct(CurrentViewForward(), CurrentViewOrigin()) ); pInfo->m_Frustum.SetPlane( FRUSTUM_FARZ, PLANE_ANYZ, -CurrentViewForward(), DotProduct(-CurrentViewForward(), CurrentViewOrigin() + CurrentViewForward()*500) ); r_snapportal.SetValue( -1 ); CSGFrustum( pInfo->m_Frustum ); } } } #endif } // Intersection of AABB and a frustum. The frustum may contain 0-32 planes // (active planes are defined by inClipMask). Returns boolean value indicating // whether AABB intersects the view frustum or not. // If AABB intersects the frustum, an output clip mask is returned as well // (indicating which planes are crossed by the AABB). This information // can be used to optimize testing of child nodes or objects inside the // nodes (pass value as 'inClipMask'). inline bool R_CullNodeInternal( mnode_t *pNode, int &nClipMask, const Frustum_t& frustum ) { int nOutClipMask = nClipMask & FRUSTUM_CLIP_IN_AREA; // init outclip mask float flCenterDotNormal, flHalfDiagDotAbsNormal; if (nClipMask & FRUSTUM_CLIP_RIGHT) { const cplane_t *pPlane = frustum.GetPlane(FRUSTUM_RIGHT); flCenterDotNormal = DotProduct( pNode->m_vecCenter, pPlane->normal ) - pPlane->dist; flHalfDiagDotAbsNormal = DotProduct( pNode->m_vecHalfDiagonal, frustum.GetAbsNormal(FRUSTUM_RIGHT) ); if (flCenterDotNormal + flHalfDiagDotAbsNormal < 0.0f) return true; if (flCenterDotNormal - flHalfDiagDotAbsNormal < 0.0f) nOutClipMask |= FRUSTUM_CLIP_RIGHT; } if (nClipMask & FRUSTUM_CLIP_LEFT) { const cplane_t *pPlane = frustum.GetPlane(FRUSTUM_LEFT); flCenterDotNormal = DotProduct( pNode->m_vecCenter, pPlane->normal ) - pPlane->dist; flHalfDiagDotAbsNormal = DotProduct( pNode->m_vecHalfDiagonal, frustum.GetAbsNormal(FRUSTUM_LEFT) ); if (flCenterDotNormal + flHalfDiagDotAbsNormal < 0.0f) return true; if (flCenterDotNormal - flHalfDiagDotAbsNormal < 0.0f) nOutClipMask |= FRUSTUM_CLIP_LEFT; } if (nClipMask & FRUSTUM_CLIP_TOP) { const cplane_t *pPlane = frustum.GetPlane(FRUSTUM_TOP); flCenterDotNormal = DotProduct( pNode->m_vecCenter, pPlane->normal ) - pPlane->dist; flHalfDiagDotAbsNormal = DotProduct( pNode->m_vecHalfDiagonal, frustum.GetAbsNormal(FRUSTUM_TOP) ); if (flCenterDotNormal + flHalfDiagDotAbsNormal < 0.0f) return true; if (flCenterDotNormal - flHalfDiagDotAbsNormal < 0.0f) nOutClipMask |= FRUSTUM_CLIP_TOP; } if (nClipMask & FRUSTUM_CLIP_BOTTOM) { const cplane_t *pPlane = frustum.GetPlane(FRUSTUM_BOTTOM); flCenterDotNormal = DotProduct( pNode->m_vecCenter, pPlane->normal ) - pPlane->dist; flHalfDiagDotAbsNormal = DotProduct( pNode->m_vecHalfDiagonal, frustum.GetAbsNormal(FRUSTUM_BOTTOM) ); if (flCenterDotNormal + flHalfDiagDotAbsNormal < 0.0f) return true; if (flCenterDotNormal - flHalfDiagDotAbsNormal < 0.0f) nOutClipMask |= FRUSTUM_CLIP_BOTTOM; } nClipMask = nOutClipMask; return false; // AABB intersects frustum } bool R_CullNode( Frustum_t *pAreaFrustum, mnode_t *pNode, int& nClipMask ) { // Now cull to this area's frustum. if (( !g_bViewerInSolidSpace ) && ( pNode->area > 0 )) { // First make sure its whole area is even visible. if( !R_IsAreaVisible( pNode->area ) ) return true; // This ConVar access causes a huge perf hit in some cases. // If you want to put it back in please cache it's value. //#ifdef USE_CONVARS // if( r_ClipAreaPortals.GetInt() ) //#else if( 1 ) //#endif { if ((nClipMask & FRUSTUM_CLIP_IN_AREA) == 0) { // In this case, we've never hit this area before and // we need to test all planes again because we just changed the frustum nClipMask = FRUSTUM_CLIP_IN_AREA | FRUSTUM_CLIP_ALL; } pAreaFrustum = &g_AreaCullInfo[pNode->area].m_Frustum; } } return R_CullNodeInternal( pNode, nClipMask, *pAreaFrustum ); } static ConVar r_portalscloseall( "r_portalscloseall", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); static ConVar r_portalsopenall( "r_portalsopenall", "0", FCVAR_CHEAT, "Open all portals" ); static ConVar r_ShowViewerArea( "r_ShowViewerArea", "0" ); void R_SetupAreaBits( int iForceViewLeaf /* = -1 */, const VisOverrideData_t* pVisData /* = NULL */, float *pWaterReflectionHeight /* = NULL */ ) { IncrementGlobalCounter(); g_bViewerInSolidSpace = false; // Clear the visible area bits. memset( g_RenderAreaBits, 0, sizeof( g_RenderAreaBits ) ); memset( g_AreaStack, 0, sizeof( g_AreaStack ) ); // Our initial clip rect is the whole screen. CPortalRect rect; rect.left = rect.bottom = -1; rect.top = rect.right = 1; // Flow through areas starting at the one we're in. int leaf = iForceViewLeaf; // If view point override wasn't specified, use the current view origin if ( iForceViewLeaf == -1 ) { leaf = CM_PointLeafnum( g_EngineRenderer->ViewOrigin() ); } if( r_portalscloseall.GetBool() ) { if ( cl.m_bAreaBitsValid ) { // Clear the visible area bits. memset( g_RenderAreaBits, 0, sizeof( g_RenderAreaBits ) ); int area = host_state.worldbrush->leafs[leaf].area; R_SetBit( g_RenderAreaBits, area ); g_VisibleAreas[0] = area; g_nVisibleAreas = 1; g_AreaCullInfo[area].m_GlobalCounter = g_GlobalCounter; g_AreaCullInfo[area].m_Rect = rect; R_SetupVisibleAreaFrustums(); } else { g_bViewerInSolidSpace = true; } return; } #if defined( REPLAY_ENABLED ) if ( host_state.worldbrush->leafs[leaf].contents & CONTENTS_SOLID || cl.ishltv || cl.isreplay || !cl.m_bAreaBitsValid || r_portalsopenall.GetBool() ) #else if ( host_state.worldbrush->leafs[leaf].contents & CONTENTS_SOLID || cl.ishltv || !cl.m_bAreaBitsValid || r_portalsopenall.GetBool() ) #endif { // Draw everything if we're in solid space or if r_portalsopenall is true (used for building cubemaps) g_bViewerInSolidSpace = true; if ( r_ShowViewerArea.GetInt() ) Con_NPrintf( 3, "Viewer area: (solid space)" ); } else { int area = host_state.worldbrush->leafs[leaf].area; if ( r_ShowViewerArea.GetInt() ) Con_NPrintf( 3, "Viewer area: %d", area ); g_nVisibleAreas = 0; Vector vecVisOrigin = (pVisData)?(pVisData->m_vecVisOrigin):(g_EngineRenderer->ViewOrigin()); R_SetupGlobalFrustum(); R_FlowThroughArea ( area, vecVisOrigin, &rect, pVisData, pWaterReflectionHeight ); R_SetupVisibleAreaFrustums(); } } const Frustum_t* GetAreaFrustum( int area ) { if ( g_AreaCullInfo[area].m_GlobalCounter == g_GlobalCounter ) return &g_AreaCullInfo[area].m_Frustum; else return &g_Frustum; }