//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// //NOTE: Mirrors with models require an attachment named "MirrorSurface_Attach" with x facing out of the mirror plane. //They also require that the mirror surface be in a bodygroup by itself named "MirrorSurface" with the first index being the mirror, second being empty. //Lastly, they require that all non-mirror geometry be in bodygroups that have the second entry as empty. //It's a good idea to put a cubemap on the mirror surface material because they're not infinitely recursive #include "cbase.h" #include "portalrenderable_flatbasic.h" #include "tier0/vprof.h" #include "model_types.h" #include "debugoverlay_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" class C_Prop_Mirror : public CPortalRenderable_FlatBasic { public: DECLARE_CLASS( C_Prop_Mirror, CPortalRenderable_FlatBasic ); DECLARE_CLIENTCLASS(); C_Prop_Mirror( void ); virtual void Spawn( void ); virtual void UpdateOnRemove( void ); virtual void OnDataChanged( DataUpdateType_t type ); virtual bool ShouldDraw( void ) { return true; } virtual int DrawModel( int flags, const RenderableInstance_t &instance ); virtual CStudioHdr *OnNewModel( void ); virtual void ClientThink( void ); virtual bool ShouldUpdatePortalView_BasedOnView( const CViewSetup ¤tView, const CUtlVector ¤tComplexFrustum ); virtual bool ShouldUpdatePortalView_BasedOnPixelVisibility( float fScreenFilledByStencilMaskLastFrame_Normalized ) { return true; } void UpdateReflectionPlane( void ); void UpdateReflectionPolygon( void ); //call this before accessing m_LocalSpaceReflectionPolygonVerts, it'll detect if an update is necessary and do it virtual void DrawStencilMask( IMatRenderContext *pRenderContext ); virtual void DrawPostStencilFixes( IMatRenderContext *pRenderContext ); virtual void RenderPortalViewToBackBuffer( CViewRender *pViewRender, const CViewSetup &cameraView ); bool CalcFrustumThroughPortal( const Vector &ptCurrentViewOrigin, Frustum OutputFrustum ); static CMaterialReference sm_Mirror_Stencil; float m_fWidth; float m_fHeight; int m_iMirrorSurfaceBodyGroup; int m_iMirrorFaceAttachment; bool m_bModel; Vector m_LocalSpaceReflectionPolygonVerts[10]; //best guess at the reflection polygon by intersecting the reflection plane with the local space rendering OBB int m_LocalSpaceReflectionPolygonVertCount; struct ReflectPlaneCachedData_t { //Vector vAttachmentOrigin; //handled by m_ptOrigin QAngle qAttachmentAngle; bool bModel; Vector vLocalSpaceAttachmentOrigin; QAngle qLocalSpaceAttachmentAngles; Vector vRenderOBB_Mins; Vector vRenderOBB_Maxs; }; ReflectPlaneCachedData_t m_CachedReflectedData; }; CMaterialReference C_Prop_Mirror::sm_Mirror_Stencil; IMPLEMENT_CLIENTCLASS_DT( C_Prop_Mirror, DT_Prop_Mirror, CProp_Mirror ) RecvPropFloat( RECVINFO(m_fWidth) ), RecvPropFloat( RECVINFO(m_fHeight) ), END_RECV_TABLE() C_Prop_Mirror::C_Prop_Mirror( void ) { m_ptOrigin.Invalidate(); m_CachedReflectedData.qAttachmentAngle.Invalidate(); m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate(); m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate(); m_CachedReflectedData.vRenderOBB_Maxs.Invalidate(); m_matrixThisToLinked.m[3][0] = 0.0f; m_matrixThisToLinked.m[3][1] = 0.0f; m_matrixThisToLinked.m[3][2] = 0.0f; m_matrixThisToLinked.m[3][3] = 1.0f; } void C_Prop_Mirror::Spawn( void ) { BaseClass::Spawn(); if( !sm_Mirror_Stencil.IsValid() ) { sm_Mirror_Stencil.Init( "decals/portalstencildecal", TEXTURE_GROUP_CLIENT_EFFECTS ); } m_pLinkedPortal = this; g_pPortalRender->AddPortal( this ); //will know if we're already added and avoid adding twice } void C_Prop_Mirror::UpdateOnRemove( void ) { BaseClass::UpdateOnRemove(); g_pPortalRender->RemovePortal( this ); } CStudioHdr *C_Prop_Mirror::OnNewModel( void ) { CStudioHdr *pRetVal = BaseClass::OnNewModel(); if( (GetModel() != NULL) ) { m_iMirrorSurfaceBodyGroup = FindBodygroupByName( "MirrorSurface" ); m_iMirrorFaceAttachment = LookupAttachment( "MirrorSurface_Attach" ); if( (m_iMirrorSurfaceBodyGroup >= 0) && (m_iMirrorFaceAttachment >= 0) ) { m_bModel = true; SetBodygroup( m_iMirrorSurfaceBodyGroup, 1 ); //by default the mirror surface is hidden } else { Warning( "Prop_Mirror model missing vital data %s\n", (m_iMirrorFaceAttachment < 0) ? ((m_iMirrorSurfaceBodyGroup < 0) ? "MirrorSurface bodygroup and MirrorSurface_Attach attachment point" : "MirrorSurface_Attach attachment point") : "MirrorSurface bodygroup" ); m_bModel = false; } } if( m_bModel ) { SetNextClientThink( CLIENT_THINK_ALWAYS ); } else { SetNextClientThink( CLIENT_THINK_NEVER ); } return pRetVal; } void C_Prop_Mirror::UpdateReflectionPlane( void ) { PortalMoved(); Vector vToOrigin( m_ptOrigin.Dot( m_vForward ), m_ptOrigin.Dot( m_vRight ), -m_ptOrigin.Dot( m_vUp ) ); //generate mirroring matrix. Move mirror to origin using base vectors, flip on forward axis, move back to position and orientation { m_matrixThisToLinked.m[0][0] = (-m_vForward.x * m_vForward.x) + (m_vRight.x * m_vRight.x) + (m_vUp.x * m_vUp.x); m_matrixThisToLinked.m[0][1] = (-m_vForward.x * m_vForward.y) + (m_vRight.x * m_vRight.y) + (m_vUp.x * m_vUp.y); m_matrixThisToLinked.m[0][2] = (-m_vForward.x * m_vForward.z) + (m_vRight.x * m_vRight.z) + (m_vUp.x * m_vUp.z); m_matrixThisToLinked.m[0][3] = (vToOrigin.x * m_vForward.x) - (vToOrigin.y * m_vRight.x) + (vToOrigin.z * m_vUp.x) + m_ptOrigin.x; m_matrixThisToLinked.m[1][0] = m_matrixThisToLinked.m[0][1]; //rotation portion of the matrix is equal to it's own transpose m_matrixThisToLinked.m[1][1] = (-m_vForward.y * m_vForward.y) + (m_vRight.y * m_vRight.y) + (m_vUp.y * m_vUp.y); m_matrixThisToLinked.m[1][2] = (-m_vForward.y * m_vForward.z) + (m_vRight.y * m_vRight.z) + (m_vUp.y * m_vUp.z); m_matrixThisToLinked.m[1][3] = (vToOrigin.x * m_vForward.y) - (vToOrigin.y * m_vRight.y) + (vToOrigin.z * m_vUp.y) + m_ptOrigin.y; m_matrixThisToLinked.m[2][0] = m_matrixThisToLinked.m[0][2]; //rotation portion of the matrix is equal to it's own transpose m_matrixThisToLinked.m[2][1] = m_matrixThisToLinked.m[1][2]; //rotation portion of the matrix is equal to it's own transpose m_matrixThisToLinked.m[2][2] = (-m_vForward.z * m_vForward.z) + (m_vRight.z * m_vRight.z) + (m_vUp.z * m_vUp.z); m_matrixThisToLinked.m[2][3] = (vToOrigin.x * m_vForward.z) - (vToOrigin.y * m_vRight.z) + (vToOrigin.z * m_vUp.z) + m_ptOrigin.z; } UpdateReflectionPolygon(); } void C_Prop_Mirror::UpdateReflectionPolygon( void ) { if( m_bModel != m_CachedReflectedData.bModel ) { m_CachedReflectedData.qAttachmentAngle.Invalidate(); m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate(); m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate(); m_CachedReflectedData.vRenderOBB_Maxs.Invalidate(); m_CachedReflectedData.bModel = m_bModel; } if( m_bModel ) { Vector vMins, vMaxs; GetRenderBounds( vMins, vMaxs ); Vector vLocalAttachmentOrigin; QAngle qLocalAttachmentAngles; GetAttachmentLocal( m_iMirrorFaceAttachment, vLocalAttachmentOrigin, qLocalAttachmentAngles ); if( (vMins == m_CachedReflectedData.vRenderOBB_Mins) && (vMaxs == m_CachedReflectedData.vRenderOBB_Maxs) && (vLocalAttachmentOrigin == m_CachedReflectedData.vLocalSpaceAttachmentOrigin) && (qLocalAttachmentAngles == m_CachedReflectedData.qLocalSpaceAttachmentAngles) ) { return; //nothing to update } m_CachedReflectedData.vRenderOBB_Mins = vMins; m_CachedReflectedData.vRenderOBB_Maxs = vMaxs; m_CachedReflectedData.vLocalSpaceAttachmentOrigin = vLocalAttachmentOrigin; m_CachedReflectedData.qLocalSpaceAttachmentAngles = qLocalAttachmentAngles; Vector vAttachmentVectors[3]; AngleVectors( qLocalAttachmentAngles, &vAttachmentVectors[0], &vAttachmentVectors[1], &vAttachmentVectors[2] ); float fLargestOBBDiff = vMaxs.x - vMins.x; for( int i = 1; i != 3; ++i ) { float fDiff = vMaxs[i] - vMins[i]; if( fDiff > fLargestOBBDiff ) { fLargestOBBDiff = fDiff; } } fLargestOBBDiff *= 4.0f; //to easily cover diagonal intersection and then some Vector vClipBuffers[2][10]; //4 starting points, possible to create 1 extra point per cut, 6 cuts vClipBuffers[0][0] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][1] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][2] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff); vClipBuffers[0][3] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff); int iVertCount = 4; VPlane vClipPlanes[6]; vClipPlanes[0].Init( Vector( 1.0f, 0.0f, 0.0f ), vMins.x ); vClipPlanes[1].Init( Vector( -1.0f, 0.0f, 0.0f ), -vMaxs.x ); vClipPlanes[2].Init( Vector( 0.0f, 1.0f, 0.0f ), vMins.y ); vClipPlanes[3].Init( Vector( 0.0f, -1.0f, 0.0f ), -vMaxs.y ); vClipPlanes[4].Init( Vector( 0.0f, 0.0f, 1.0f ), vMins.z ); vClipPlanes[5].Init( Vector( 0.0f, 0.0f, -1.0f ), -vMaxs.z ); for( int i = 0; i != 6; ++i ) { iVertCount = ClipPolyToPlane( vClipBuffers[i & 1], iVertCount, vClipBuffers[(i & 1) ^ 1], vClipPlanes[i].m_Normal, vClipPlanes[i].m_Dist, 0.01f ); } Assert( iVertCount >= 3 ); m_LocalSpaceReflectionPolygonVertCount = iVertCount; memcpy( m_LocalSpaceReflectionPolygonVerts, vClipBuffers[0], sizeof( Vector ) * iVertCount ); } else { if( (m_CachedReflectedData.vRenderOBB_Maxs.x == m_fWidth) && (m_CachedReflectedData.vRenderOBB_Maxs.y == m_fHeight) ) return; m_LocalSpaceReflectionPolygonVertCount = 4; float fHalfWidth = GetHalfWidth(); float fHalfHeight = GetHalfHeight(); m_LocalSpaceReflectionPolygonVerts[0].Init( 0.0f, fHalfWidth, fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[1].Init( 0.0f, -fHalfWidth, fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[2].Init( 0.0f, -fHalfWidth, -fHalfHeight ); m_LocalSpaceReflectionPolygonVerts[3].Init( 0.0f, fHalfWidth, -fHalfHeight ); } } void C_Prop_Mirror::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); SetHalfSizes( m_fWidth / 2.0f, m_fHeight / 2.0f ); if( m_bModel ) { Vector vMirrorAttachmentOrigin; QAngle qMirrorAttachmentAngles; GetAttachment( m_iMirrorFaceAttachment, vMirrorAttachmentOrigin, qMirrorAttachmentAngles ); if( (m_ptOrigin != vMirrorAttachmentOrigin) || (m_CachedReflectedData.qAttachmentAngle != qMirrorAttachmentAngles) ) { m_ptOrigin = vMirrorAttachmentOrigin; m_CachedReflectedData.qAttachmentAngle = qMirrorAttachmentAngles; AngleVectors( qMirrorAttachmentAngles, &m_vForward, &m_vRight, &m_vUp ); UpdateReflectionPlane(); } } else { if( (m_ptOrigin != GetRenderOrigin()) || (m_CachedReflectedData.qAttachmentAngle != GetRenderAngles()) ) { m_ptOrigin = GetRenderOrigin(); m_CachedReflectedData.qAttachmentAngle = GetRenderAngles(); AngleVectors( GetRenderAngles(), &m_vForward, &m_vRight, &m_vUp ); UpdateReflectionPlane(); } } } void C_Prop_Mirror::ClientThink( void ) { BaseClass::ClientThink(); if( m_bModel ) { Vector vMirrorAttachmentOrigin; QAngle qMirrorAttachmentAngles; GetAttachment( m_iMirrorFaceAttachment, vMirrorAttachmentOrigin, qMirrorAttachmentAngles ); if( (m_ptOrigin != vMirrorAttachmentOrigin) || (m_CachedReflectedData.qAttachmentAngle != qMirrorAttachmentAngles) ) { m_ptOrigin = vMirrorAttachmentOrigin; AngleVectors( qMirrorAttachmentAngles, &m_vForward, &m_vRight, &m_vUp ); UpdateReflectionPlane(); } } } void C_Prop_Mirror::DrawStencilMask( IMatRenderContext *pRenderContext ) { if( m_bModel ) { int iNumBodyGroups = GetNumBodyGroups(); int *pOldBodyGroups = (int *)stackalloc( sizeof( int ) * iNumBodyGroups ); for( int i = 0; i != iNumBodyGroups; ++i ) { pOldBodyGroups[i] = GetBodygroup( i ); SetBodygroup( i, 1 ); } pOldBodyGroups[m_iMirrorSurfaceBodyGroup] = 1; SetBodygroup( m_iMirrorSurfaceBodyGroup, 0 ); RenderableInstance_t tempInstance; tempInstance.m_nAlpha = 255; modelrender->ForcedMaterialOverride( (IMaterial *)(const IMaterial *)g_pPortalRender->m_MaterialsAccess.m_WriteZ_Model ); DrawModel( STUDIO_RENDER, tempInstance ); modelrender->ForcedMaterialOverride( NULL ); for( int i = 0; i != iNumBodyGroups; ++i ) { SetBodygroup( i, pOldBodyGroups[i] ); } } else { DrawSimplePortalMesh( pRenderContext, g_pPortalRender->m_MaterialsAccess.m_WriteZ_Model ); } } void C_Prop_Mirror::DrawPostStencilFixes( IMatRenderContext *pRenderContext ) { if( m_bModel ) { int iNumBodyGroups = GetNumBodyGroups(); int *pOldBodyGroups = (int *)stackalloc( sizeof( int ) * iNumBodyGroups ); for( int i = 0; i != iNumBodyGroups; ++i ) { pOldBodyGroups[i] = GetBodygroup( i ); SetBodygroup( i, 1 ); } pOldBodyGroups[m_iMirrorSurfaceBodyGroup] = 1; SetBodygroup( m_iMirrorSurfaceBodyGroup, 0 ); RenderableInstance_t tempInstance; tempInstance.m_nAlpha = 255; modelrender->ForcedMaterialOverride( (IMaterial *)(const IMaterial *)g_pPortalRender->m_MaterialsAccess.m_WriteZ_Model ); DrawModel( STUDIO_RENDER, tempInstance ); modelrender->ForcedMaterialOverride( NULL ); for( int i = 0; i != iNumBodyGroups; ++i ) { SetBodygroup( i, pOldBodyGroups[i] ); } } else { DrawSimplePortalMesh( pRenderContext, g_pPortalRender->m_MaterialsAccess.m_WriteZ_Model ); } } bool C_Prop_Mirror::ShouldUpdatePortalView_BasedOnView( const CViewSetup ¤tView, const CUtlVector ¤tComplexFrustum ) { if( m_vForward.Dot( currentView.origin ) <= m_vForward.Dot( m_ptOrigin ) ) return false; //camera is behind the mirror plane UpdateReflectionPolygon(); matrix3x4_t matRenderTransform; AngleMatrix( GetRenderAngles(), GetRenderOrigin(), matRenderTransform ); Vector *pClipBuffers[2]; int clipAllocSize = (m_LocalSpaceReflectionPolygonVertCount + currentComplexFrustum.Count() + 2); //possible to add 1 point per cut, m_LocalSpaceReflectionPolygonVertCount starting points, currentComplexFrustum.Count() plane cuts, 2 extra because I'm paranoid pClipBuffers[0] = (Vector *)stackalloc( sizeof( Vector ) * clipAllocSize * 2 ); pClipBuffers[1] = pClipBuffers[0] + clipAllocSize; for( int i = 0; i != m_LocalSpaceReflectionPolygonVertCount; ++i ) { VectorTransform( m_LocalSpaceReflectionPolygonVerts[i], matRenderTransform, pClipBuffers[0][i] ); } const VPlane *currentFrustum = currentComplexFrustum.Base(); int iCurrentFrustumPlanes = currentComplexFrustum.Count(); //clip by first plane and put output into pInVerts int iVertCount = ClipPolyToPlane( pClipBuffers[0], m_LocalSpaceReflectionPolygonVertCount, pClipBuffers[1], currentFrustum[0].m_Normal, currentFrustum[0].m_Dist, 0.01f ); for( int i = 1; i != iCurrentFrustumPlanes; ++i ) { if( iVertCount < 3 ) return false; //nothing left in the frustum iVertCount = ClipPolyToPlane( pClipBuffers[i&1], iVertCount, pClipBuffers[(i&1)^1], currentFrustum[i].m_Normal, currentFrustum[i].m_Dist, 0.01f ); } #if 0 //for visibility culling debugging int iFinalBuffer = (iCurrentFrustumPlanes & 1) ^ 1; if( g_pPortalRender->GetViewRecursionLevel() == 0 ) { NDebugOverlay::Line( pClipBuffers[iFinalBuffer][iVertCount - 1], pClipBuffers[iFinalBuffer][0], 255, 0, 0, false, 0.0f ); for( int j = 0; j != iVertCount - 1; ++j ) { NDebugOverlay::Line( pClipBuffers[iFinalBuffer][j], pClipBuffers[iFinalBuffer][j+1], 255, 0, 0, false, 0.0f ); } } #endif return (iVertCount >= 3); #if 0 if( m_bModel ) { //it would be complicated to get the exact shape of the mirror surface and cut it up, so we'll just use the model's oriented bounding box. //which can provide up to 3 visible quads, if any 1 of them passes the test then the entire thing is visible. if( m_vForward.Dot( currentView.origin ) <= m_vForward.Dot( m_ptOrigin ) ) return false; //camera is behind the mirror plane Vector vMins, vMaxs; GetRenderBounds( vMins, vMaxs ); Vector vRenderOrigin = GetRenderOrigin(); Vector vOBBVectors[3]; #if 1 AngleVectors( GetRenderAngles(), &vOBBVectors[0], &vOBBVectors[1], &vOBBVectors[2] ); vOBBVectors[1] = -vOBBVectors[1]; #else { matrix3x4_t fRotateMatrix; AngleMatrix( GetRenderAngles(), fRotateMatrix ); VectorRotate( Vector( 1.0f, 0.0f, 0.0f ), fRotateMatrix, vOBBVectors[0] ); VectorRotate( Vector( 0.0f, 1.0f, 0.0f ), fRotateMatrix, vOBBVectors[1] ); VectorRotate( Vector( 0.0f, 0.0f, 1.0f ), fRotateMatrix, vOBBVectors[2] ); } #endif Vector vRenderOriginToCamera = currentView.origin - vRenderOrigin; Vector *pClipBuffers[2]; int clipAllocSize = (6 + currentComplexFrustum.Count()); //possible to add 1 point per cut, 4 starting points, N plane cuts, 2 extra because I'm paranoid pClipBuffers[0] = (Vector *)stackalloc( sizeof( Vector ) * clipAllocSize * 2 ); pClipBuffers[1] = pClipBuffers[0] + clipAllocSize; VPlane *currentFrustum = currentComplexFrustum.Base(); int iCurrentFrustumPlanes = currentComplexFrustum.Count(); for( int i = 0; i != 3; ++i ) { Vector vPolyStartPoint; float fDot = vRenderOriginToCamera.Dot( vOBBVectors[i] ); if( fDot > vMaxs[i] ) { //on the positive side of this axis vPolyStartPoint = vRenderOrigin + (vOBBVectors[i] * vMaxs[i]); } else if( fDot < vMins[i] ) { //on the negative side of this axis vPolyStartPoint = vRenderOrigin + (vOBBVectors[i] * vMins[i]); } else { //can't see the outside of the face continue; } int iBasis[2]; iBasis[0] = (i&1) ^ 1; iBasis[1] = (i&2) ^ 2; pClipBuffers[0][0] = vPolyStartPoint + (vOBBVectors[iBasis[0]] * vMins[iBasis[0]]) + (vOBBVectors[iBasis[1]] * vMaxs[iBasis[1]]); pClipBuffers[0][1] = vPolyStartPoint + (vOBBVectors[iBasis[0]] * vMaxs[iBasis[0]]) + (vOBBVectors[iBasis[1]] * vMaxs[iBasis[1]]); pClipBuffers[0][2] = vPolyStartPoint + (vOBBVectors[iBasis[0]] * vMaxs[iBasis[0]]) + (vOBBVectors[iBasis[1]] * vMins[iBasis[1]]); pClipBuffers[0][3] = vPolyStartPoint + (vOBBVectors[iBasis[0]] * vMins[iBasis[0]]) + (vOBBVectors[iBasis[1]] * vMins[iBasis[1]]); //clip by first plane and put output into pInVerts int iVertCount = ClipPolyToPlane( pClipBuffers[0], 4, pClipBuffers[1], currentFrustum[0].m_Normal, currentFrustum[0].m_Dist, 0.01f ); for( int j = 1; j != iCurrentFrustumPlanes; ++j ) { if( iVertCount < 3 ) break; //nothing left in the frustum iVertCount = ClipPolyToPlane( pClipBuffers[j&1], iVertCount, pClipBuffers[(j&1)^1], currentFrustum[j].m_Normal, currentFrustum[j].m_Dist, 0.01f ); } if( iVertCount >= 3 ) { return true; //this polygon is visible, we're done } } return false; //none of the polygons are visible } else { //rely on flatbasic's technique since we're just using a quad return BaseClass::ShouldUpdatePortalView_BasedOnView( currentView, currentComplexFrustum ); } #endif } extern ConVar r_portal_use_complex_frustums; bool C_Prop_Mirror::CalcFrustumThroughPortal( const Vector &ptCurrentViewOrigin, Frustum OutputFrustum ) { if( r_portal_use_complex_frustums.GetBool() == false ) return false; if( m_vForward.Dot( ptCurrentViewOrigin ) <= m_vForward.Dot( m_ptOrigin ) ) return false; //looking at portal backface Vector pTransformedVerts[10]; const matrix3x4_t &matLocalToWorld = EntityToWorldTransform(); for( int i = 0; i != m_LocalSpaceReflectionPolygonVertCount; ++i ) { VectorTransform( &m_LocalSpaceReflectionPolygonVerts[i].x, matLocalToWorld, &pTransformedVerts[i].x ); } return CalcFrustumThroughPolygon( pTransformedVerts, m_LocalSpaceReflectionPolygonVertCount, ptCurrentViewOrigin, OutputFrustum ); } void C_Prop_Mirror::RenderPortalViewToBackBuffer( CViewRender *pViewRender, const CViewSetup &cameraView ) { VPROF( "C_Prop_Mirror::RenderPortalViewToBackBuffer" ); Frustum FrustumBackup; memcpy( FrustumBackup, pViewRender->GetFrustum(), sizeof( Frustum ) ); Frustum seeThroughFrustum; bool bUseSeeThroughFrustum; bUseSeeThroughFrustum = CalcFrustumThroughPortal( cameraView.origin, seeThroughFrustum ); Vector vCameraForward; AngleVectors( cameraView.angles, &vCameraForward, NULL, NULL ); // Setup fog state for the camera. Vector ptPOVOrigin = m_matrixThisToLinked * cameraView.origin; Vector vPOVForward = m_matrixThisToLinked.ApplyRotation( vCameraForward ); CViewSetup portalView = cameraView; if( portalView.zNear < 1.0f ) portalView.zNear = 1.0f; QAngle qPOVAngles = TransformAnglesToWorldSpace( cameraView.angles, m_matrixThisToLinked.As3x4() ); portalView.origin = ptPOVOrigin; portalView.angles = qPOVAngles; VMatrix matCurrentView; if( cameraView.m_bCustomViewMatrix ) { matCurrentView.CopyFrom3x4( cameraView.m_matCustomViewMatrix ); } else { matCurrentView.Identity(); //generate the view matrix for the existing position and angle, then wedge our mirror matrix onto it as a world transformation that prepends the view MatrixRotate( matCurrentView, Vector( 1, 0, 0 ), -cameraView.angles[2] ); MatrixRotate( matCurrentView, Vector( 0, 1, 0 ), -cameraView.angles[0] ); MatrixRotate( matCurrentView, Vector( 0, 0, 1 ), -cameraView.angles[1] ); MatrixTranslate( matCurrentView, -cameraView.origin ); } VMatrix matTemp = matCurrentView * m_matrixThisToLinked; //technically we should be using the inverse of m_matrixThisToLinked, but it's the same matrix! portalView.m_matCustomViewMatrix = matTemp.As3x4(); portalView.m_bCustomViewMatrix = true; CopyToCurrentView( pViewRender, portalView ); CMatRenderContextPtr pRenderContext( materials ); pRenderContext->FlipCullMode(); //mirroring the world requires a flip in cull mode or we'll draw everything bass ackwards //if we look through multiple unique pairs of portals, we have to take care not to clip too much bool bReplaceOldPortalClipPlane = (g_pPortalRender->GetViewRecursionLevel() != 0) && (dynamic_cast(g_pPortalRender->GetCurrentViewExitPortal()) != NULL); { float fCustomClipPlane[4]; fCustomClipPlane[0] = m_vForward.x; fCustomClipPlane[1] = m_vForward.y; fCustomClipPlane[2] = m_vForward.z; fCustomClipPlane[3] = m_vForward.Dot( m_ptOrigin ); if( bReplaceOldPortalClipPlane ) pRenderContext->PopCustomClipPlane(); //HACKHACK: We really only want to remove the clip plane of the current portal view. This assumes we're the only ones leaving clip planes on the stack pRenderContext->PushCustomClipPlane( fCustomClipPlane ); } { ViewCustomVisibility_t customVisibility; m_pLinkedPortal->AddToVisAsExitPortal( &customVisibility ); render->Push3DView( pRenderContext, portalView, 0, NULL, pViewRender->GetFrustum() ); { if( bUseSeeThroughFrustum) memcpy( pViewRender->GetFrustum(), seeThroughFrustum, sizeof( Frustum ) ); render->OverrideViewFrustum( pViewRender->GetFrustum() ); SetViewRecursionLevel( g_pPortalRender->GetViewRecursionLevel() + 1 ); CPortalRenderable *pRenderingViewForPortalBackup = g_pPortalRender->GetCurrentViewEntryPortal(); CPortalRenderable *pRenderingViewExitPortalBackup = g_pPortalRender->GetCurrentViewExitPortal(); SetViewEntranceAndExitPortals( this, m_pLinkedPortal ); //DRAW!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ViewDrawScene_PortalStencil( pViewRender, portalView, &customVisibility ); SetViewEntranceAndExitPortals( pRenderingViewForPortalBackup, pRenderingViewExitPortalBackup ); if( m_InternallyMaintainedData.m_bUsableDepthDoublerConfiguration && (g_pPortalRender->GetRemainingPortalViewDepth() == 1) ) { //save the view matrix for usage with the depth doubler. //It's important that we do this AFTER using the depth doubler this frame to compensate for the fact that the front buffer is 1 frame behind the current view matrix //otherwise we get a lag effect when the player changes their viewing angles pRenderContext->GetMatrix( MATERIAL_VIEW, &m_InternallyMaintainedData.m_DepthDoublerTextureView[GET_ACTIVE_SPLITSCREEN_SLOT()] ); } SetViewRecursionLevel( g_pPortalRender->GetViewRecursionLevel() - 1 ); } render->PopView( pRenderContext, pViewRender->GetFrustum() ); //restore old frustum memcpy( pViewRender->GetFrustum(), FrustumBackup, sizeof( Frustum ) ); render->OverrideViewFrustum( FrustumBackup ); } pRenderContext->FlipCullMode(); //flip the cull mode again to restore it to it's original setting pRenderContext->PopCustomClipPlane(); if( bReplaceOldPortalClipPlane ) { CPortalRenderable_FlatBasic *pCurrentExitPortal = (CPortalRenderable_FlatBasic *)g_pPortalRender->GetCurrentViewExitPortal(); float fCustomClipPlane[4]; fCustomClipPlane[0] = pCurrentExitPortal->m_vForward.x; fCustomClipPlane[1] = pCurrentExitPortal->m_vForward.y; fCustomClipPlane[2] = pCurrentExitPortal->m_vForward.z; fCustomClipPlane[3] = pCurrentExitPortal->m_vForward.Dot( pCurrentExitPortal->m_ptOrigin - (pCurrentExitPortal->m_vForward * 0.5f) ); //moving it back a smidge to eliminate visual artifacts for half-in objects pRenderContext->PushCustomClipPlane( fCustomClipPlane ); } //restore old vis data CopyToCurrentView( pViewRender, cameraView ); } int C_Prop_Mirror::DrawModel( int flags, const RenderableInstance_t &instance ) { if( g_pPortalRender->GetRemainingPortalViewDepth() == 0 ) { SetBodygroup( m_iMirrorSurfaceBodyGroup, 0 ); } int iRetVal = BaseClass::DrawModel( flags, instance ); if( g_pPortalRender->GetRemainingPortalViewDepth() == 0 ) { SetBodygroup( m_iMirrorSurfaceBodyGroup, 1 ); } return iRetVal; }