//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= // // Purpose: // //============================================================================= #include "mathlib/camera.h" #include "tier0/dbg.h" #include "mathlib/vector.h" #include "mathlib/vmatrix.h" #include "tier2/tier2.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define FORWARD_AXIS 0 #define LEFT_AXIS 1 #define UP_AXIS 2 /// matrix to align our Valve coordinate system camera space with a standard viewing coordinate system /// x-axis goes from left to right in view space (right in camera space) /// y-axis goes from bottom to top in view space (up in camera space) /// z-axis goes from far to near in view space (-forward in camera space) /// The constructor takes sequential matrix rows, which are basis vectors in view space (so transposed from the init) #ifdef YUP_ACTIVE #error YUP_ACTIVE is not supported on this branch. #endif /// g_ViewAlignMatrix.Init( Vector(0,-1,0), Vector(0,0,1), Vector(-1,0,0), vec3_origin ); static matrix3x4_t g_ViewAlignMatrix( 0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0 ); VMatrix g_matViewToCameraMatrix( 0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 ); VMatrix g_matCameraToViewMatrix( 0, -1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, 0, 1 ); //-------------------------------------------------------------------------------------------------- // Extract the direction vectors from the a matrix //-------------------------------------------------------------------------------------------------- void ExtractDirectionVectors( Vector *pForward, Vector *pLeft, Vector *pUp, const matrix3x4_t &mMatrix ) { MatrixGetColumn( mMatrix, FORWARD_AXIS, *pForward ); MatrixGetColumn( mMatrix, LEFT_AXIS, *pLeft ); MatrixGetColumn( mMatrix, UP_AXIS, *pUp ); } // Returns points in this order: // 2--3 // | | // 0--1 void CalcFarPlaneCameraRelativePoints( Vector *p4PointsOut, Vector &vForward, Vector &vUp, Vector &vLeft, float flFarPlane, float flFovX, float flAspect, float flClipSpaceBottomLeftX /*= -1.0f*/, float flClipSpaceBottomLeftY /*= -1.0f*/, float flClipSpaceTopRightX /*= 1.0f*/, float flClipSpaceTopRightY /*= 1.0f*/ ) { Vector vFowardShift = flFarPlane * vForward; Vector vUpShift; Vector vRightShift; if ( flFovX == -1 ) { vUpShift = vUp; vRightShift = -vLeft; } else { float flTanX = tanf( DEG2RAD( flFovX * 0.5f ) ); float flTanY = flTanX / flAspect; vUpShift = flFarPlane * flTanY * vUp; vRightShift = flFarPlane * flTanX * -vLeft; } p4PointsOut[0] = vFowardShift + flClipSpaceBottomLeftX * vRightShift + flClipSpaceBottomLeftY * vUpShift; p4PointsOut[1] = vFowardShift + flClipSpaceTopRightX * vRightShift + flClipSpaceBottomLeftY * vUpShift; p4PointsOut[2] = vFowardShift + flClipSpaceBottomLeftX * vRightShift + flClipSpaceTopRightY * vUpShift; p4PointsOut[3] = vFowardShift + flClipSpaceTopRightX * vRightShift + flClipSpaceTopRightY * vUpShift; } //----------------------------------------------------------------------------- // accessors for generated matrices //----------------------------------------------------------------------------- void ComputeViewMatrix( matrix3x4_t *pWorldToView, matrix3x4_t *pCameraToWorld, const Camera_t &camera ) { AngleMatrix( camera.m_angles, camera.m_origin, *pCameraToWorld ); matrix3x4_t tmp; ConcatTransforms( *pCameraToWorld, g_ViewAlignMatrix, tmp ); MatrixInvert( tmp, *pWorldToView ); } void ComputeViewMatrix( matrix3x4_t *pWorldToView, matrix3x4_t *pCameraToWorld, Vector const &vecOrigin, Vector const &vecForward, Vector const &vecLeft, Vector const &vecUp ) { MatrixSetColumn( vecForward, FORWARD_AXIS, *pCameraToWorld ); MatrixSetColumn( vecLeft, LEFT_AXIS, *pCameraToWorld ); MatrixSetColumn( vecUp, UP_AXIS, *pCameraToWorld ); MatrixSetColumn( vecOrigin, ORIGIN, *pCameraToWorld ); matrix3x4_t tmp; ConcatTransforms( *pCameraToWorld, g_ViewAlignMatrix, tmp ); MatrixInvert( tmp, *pWorldToView ); } void ComputeViewMatrix( matrix3x4_t *pWorldToView, const Camera_t &camera ) { matrix3x4_t cameraToWorld; ComputeViewMatrix( pWorldToView, &cameraToWorld, camera ); } void ComputeViewMatrix( VMatrix *pWorldToView, const Camera_t &camera ) { matrix3x4_t transform, invTransform; AngleMatrix( camera.m_angles, camera.m_origin, transform ); VMatrix matRotate( transform ); #ifndef YUP_ACTIVE VMatrix matRotateZ; MatrixBuildRotationAboutAxis( matRotateZ, Vector(0,0,1), -90 ); MatrixMultiply( matRotate, matRotateZ, matRotate ); VMatrix matRotateX; MatrixBuildRotationAboutAxis( matRotateX, Vector(1,0,0), 90 ); MatrixMultiply( matRotate, matRotateX, matRotate ); transform = matRotate.As3x4(); #else VMatrix matRotateUp; MatrixBuildRotationAboutAxis( matRotateUp, Vector(0,1,0), -180 ); transform = matRotate.As3x4(); #endif MatrixInvert( transform, invTransform ); pWorldToView->Init( invTransform ); } void ComputeViewMatrix( VMatrix *pViewMatrix, const Vector &origin, const QAngle &angles ) { static VMatrix baseRotation; static bool bDidInit; if ( !bDidInit ) { MatrixBuildRotationAboutAxis( baseRotation, Vector( 1, 0, 0 ), -90 ); MatrixRotate( baseRotation, Vector( 0, 0, 1 ), 90 ); bDidInit = true; } *pViewMatrix = baseRotation; MatrixRotate( *pViewMatrix, Vector( 1, 0, 0 ), -angles[2] ); MatrixRotate( *pViewMatrix, Vector( 0, 1, 0 ), -angles[0] ); MatrixRotate( *pViewMatrix, Vector( 0, 0, 1 ), -angles[1] ); MatrixTranslate( *pViewMatrix, -origin ); } void ComputeViewMatrix( VMatrix *pViewMatrix, const matrix3x4_t &matGameCustom ) { //translate game coordinates to rendering coordinates. Basically does the same as baseRotation in the other version of ComputeViewMatrix() pViewMatrix->m[0][0] = -matGameCustom.m_flMatVal[1][0]; pViewMatrix->m[0][1] = -matGameCustom.m_flMatVal[1][1]; pViewMatrix->m[0][2] = -matGameCustom.m_flMatVal[1][2]; pViewMatrix->m[0][3] = -matGameCustom.m_flMatVal[1][3]; pViewMatrix->m[1][0] = matGameCustom.m_flMatVal[2][0]; pViewMatrix->m[1][1] = matGameCustom.m_flMatVal[2][1]; pViewMatrix->m[1][2] = matGameCustom.m_flMatVal[2][2]; pViewMatrix->m[1][3] = matGameCustom.m_flMatVal[2][3]; pViewMatrix->m[2][0] = -matGameCustom.m_flMatVal[0][0]; pViewMatrix->m[2][1] = -matGameCustom.m_flMatVal[0][1]; pViewMatrix->m[2][2] = -matGameCustom.m_flMatVal[0][2]; pViewMatrix->m[2][3] = -matGameCustom.m_flMatVal[0][3]; //standard 4th row pViewMatrix->m[3][0] = pViewMatrix->m[3][1] = pViewMatrix->m[3][2] = 0.0f; pViewMatrix->m[3][3] = 1.0f; } void ComputeProjectionMatrix( VMatrix *pCameraToProjection, const Camera_t &camera, int width, int height ) { float flApsectRatio = (float)width / (float)height; ComputeProjectionMatrix( pCameraToProjection, camera.m_flZNear, camera.m_flZFar, camera.m_flFOVX, flApsectRatio ); } void ComputeProjectionMatrix( VMatrix *pCameraToProjection, float flZNear, float flZFar, float flFOVX, float flAspectRatio ) { float halfWidth = tan( flFOVX * M_PI / 360.0 ); float halfHeight = halfWidth / flAspectRatio; memset( pCameraToProjection, 0, sizeof( VMatrix ) ); pCameraToProjection->m[0][0] = 1.0f / halfWidth; pCameraToProjection->m[1][1] = 1.0f / halfHeight; pCameraToProjection->m[2][2] = flZFar / ( flZNear - flZFar ); pCameraToProjection->m[3][2] = -1.0f; pCameraToProjection->m[2][3] = flZNear * flZFar / ( flZNear - flZFar ); } void ComputeProjectionMatrix( VMatrix *pCameraToProjection, float flZNear, float flZFar, float flFOVX, float flAspectRatio, float flClipSpaceBottomLeftX, float flClipSpaceBottomLeftY, float flClipSpaceTopRightX, float flClipSpaceTopRightY ) { Vector pNearPoints[ 4 ]; Vector vForward( 0, 0, 1 ); Vector vUp( 0, 1, 0 ); Vector vLeft( -1, 0, 0 ); CalcFarPlaneCameraRelativePoints( pNearPoints, vForward, vUp, vLeft, flZNear, flFOVX, flAspectRatio, flClipSpaceBottomLeftX, flClipSpaceBottomLeftY, flClipSpaceTopRightX, flClipSpaceTopRightY ); float l = pNearPoints[ 0 ].x; float r = pNearPoints[ 1 ].x; float b = pNearPoints[ 0 ].y; float t = pNearPoints[ 2 ].y; float zn = flZNear; float zf = flZFar; float flWidth = r - l; float flHeight = t - b; float flReverseDepth = zn - zf; memset( pCameraToProjection, 0, sizeof( VMatrix ) ); pCameraToProjection->m[0][0] = ( 2.0f * zn ) / flWidth; pCameraToProjection->m[1][1] = ( 2.0f * zn ) / flHeight; pCameraToProjection->m[2][2] = zf / flReverseDepth; pCameraToProjection->m[0][2] = ( l + r ) / flWidth; pCameraToProjection->m[1][2] = ( t + b ) / flHeight; pCameraToProjection->m[2][3] = ( zn * zf ) / flReverseDepth; pCameraToProjection->m[3][2] = -1.0f; /* // this is the matrix we're going for here 2*zn/(r-l) 0 0 0 0 2*zn/(t-b) 0 0 (l+r)/(r-l) (t+b)/(t-b) zf/(zn-zf) -1 0 0 zn*zf/(zn-zf) 0 */ } //----------------------------------------------------------------------------- // Computes the screen space position given a screen size //----------------------------------------------------------------------------- void ComputeScreenSpacePosition( Vector2D *pScreenPosition, const Vector &vecWorldPosition, const Camera_t &camera, int width, int height ) { VMatrix view, proj, viewproj; ComputeViewMatrix( &view, camera ); ComputeProjectionMatrix( &proj, camera, width, height ); MatrixMultiply( proj, view, viewproj ); Vector vecScreenPos; Vector3DMultiplyPositionProjective( viewproj, vecWorldPosition, vecScreenPos ); pScreenPosition->x = ( vecScreenPos.x + 1.0f ) * width / 2.0f; pScreenPosition->y = ( -vecScreenPos.y + 1.0f ) * height / 2.0f; } VMatrix ViewMatrixRH( Vector &vEye, Vector &vAt, Vector &vUp ) { Vector xAxis, yAxis; Vector zAxis = vEye - vAt; xAxis = CrossProduct( vUp, zAxis ); yAxis = CrossProduct( zAxis, xAxis ); xAxis.NormalizeInPlace(); yAxis.NormalizeInPlace(); zAxis.NormalizeInPlace(); float flDotX = -DotProduct( xAxis, vEye ); float flDotY = -DotProduct( yAxis, vEye ); float flDotZ = -DotProduct( zAxis, vEye ); // YUP_ACTIVE: This is ok VMatrix mRet( xAxis.x, yAxis.x, zAxis.x, 0, xAxis.y, yAxis.y, zAxis.y, 0, xAxis.z, yAxis.z, zAxis.z, 0, flDotX, flDotY, flDotZ, 1 ); return mRet.Transpose(); } // Given populated camera params, generate view and proj matrices. void MatricesFromCamera( VMatrix &mWorldToView, VMatrix &mProjection, const Camera_t &camera, float flClipSpaceBottomLeftX, float flClipSpaceBottomLeftY, float flClipSpaceTopRightX, float flClipSpaceTopRightY ) { matrix3x4_t cameraToWorld; ComputeViewMatrix( &mWorldToView.As3x4(), &cameraToWorld, camera ); if ( camera.IsOrthographic() ) { mProjection = OrthoMatrixRH( camera.m_flWidth, camera.m_flHeight, camera.m_flZNear, camera.m_flZFar ); } else { ComputeProjectionMatrix( &mProjection, camera.m_flZNear, camera.m_flZFar, camera.m_flFOVX, camera.m_flAspect, flClipSpaceBottomLeftX, flClipSpaceBottomLeftY, flClipSpaceTopRightX, flClipSpaceTopRightY ); } } // Generate frustum planes from viewproj matrix void FrustumFromViewProj( Frustum_t *pFrustum, const VMatrix &mViewProj, const Vector &origin, bool bD3DClippingRange ) { VPlane planes[FRUSTUM_NUMPLANES]; ExtractClipPlanesFromNonTransposedMatrix( mViewProj, planes, bD3DClippingRange ); // Subtract the origin. for ( int i = 0; i < FRUSTUM_NUMPLANES; ++i ) { planes[i].m_Dist = planes[i].m_Dist + DotProduct( planes[i].m_Normal, -origin ); } pFrustum->SetPlanes( planes ); } // Generate frustum planes given view and proj matrices void FrustumFromMatrices( Frustum_t *pFrustum, const VMatrix &mWorldToView, const VMatrix &mProjection, const Vector &origin, bool bD3DClippingRange ) { VMatrix viewProj; viewProj = ( mProjection * mWorldToView ); FrustumFromViewProj( pFrustum, viewProj, origin, bD3DClippingRange ); } VMatrix ViewProjFromVectors( const Vector &origin, float flNear, float flFar, float flFOV, float flAspect, Vector const &vecForward, Vector const &vecLeft, Vector const &vecUp ) { matrix3x4_t mCameraToWorld; matrix3x4_t mWorldToView; ComputeViewMatrix( &mWorldToView, &mCameraToWorld, origin, vecForward, vecLeft, vecUp ); VMatrix mProjection; ComputeProjectionMatrix( &mProjection, flNear, flFar, flFOV, flAspect ); VMatrix mViewProj; mViewProj = (mProjection * VMatrix(mWorldToView)); return mViewProj; } int CFrustum::CheckBoxAgainstNearAndFarPlanes( const VectorAligned &minBounds, const VectorAligned &maxBounds ) const { // !!speed!! not super fast. change to use simd, inlining float flNear = 0; float flFar = 0; AABB_t aabb; aabb.m_vMinBounds = minBounds; aabb.m_vMaxBounds = maxBounds; Vector vZero( 0, 0, 0 ); GetNearAndFarPlanesAroundBox( &flNear, &flFar, aabb, vZero ); int nRet = 0; if ( flNear <= m_camera.m_flZNear ) { nRet |= BOXCHECK_FLAGS_OVERLAPS_NEAR; } if ( flFar >= m_camera.m_flZFar ) { nRet |= BOXCHECK_FLAGS_OVERLAPS_FAR; } return nRet; } void CFrustum::GetNearAndFarPlanesAroundBox( float *pNear, float *pFar, AABB_t const &inBox, Vector &vOriginShift ) const { AABB_t box = inBox; box.m_vMinBounds -= vOriginShift; box.m_vMaxBounds -= vOriginShift; Vector vCorners[8]; vCorners[0] = box.m_vMinBounds; vCorners[1] = Vector( box.m_vMinBounds.x, box.m_vMinBounds.y, box.m_vMaxBounds.z ); vCorners[2] = Vector( box.m_vMinBounds.x, box.m_vMaxBounds.y, box.m_vMinBounds.z ); vCorners[3] = Vector( box.m_vMinBounds.x, box.m_vMaxBounds.y, box.m_vMaxBounds.z ); vCorners[4] = Vector( box.m_vMaxBounds.x, box.m_vMinBounds.y, box.m_vMinBounds.z ); vCorners[5] = Vector( box.m_vMaxBounds.x, box.m_vMinBounds.y, box.m_vMaxBounds.z ); vCorners[6] = Vector( box.m_vMaxBounds.x, box.m_vMaxBounds.y, box.m_vMinBounds.z ); vCorners[7] = box.m_vMaxBounds; float flNear = FLT_MAX;//m_camera.m_flZNear; float flFar = -FLT_MAX;//m_camera.m_flZFar; for ( int i=0; i<8; ++i ) { Vector vDelta = vCorners[i] - m_camera.m_origin; float flDist = DotProduct( m_forward, vDelta ); flNear = MIN( flNear, flDist ); flFar = MAX( flFar, flDist ); } *pNear = flNear; *pFar = flFar; } void CFrustum::UpdateFrustumFromCamera() { if ( !m_bDirty ) return; ComputeViewMatrix( &m_worldToView, &m_cameraToWorld, m_camera ); if ( m_camera.IsOrthographic() ) { MatrixBuildOrtho( m_projection, m_camera.m_flWidth * m_flClipSpaceBottomLeftX * 0.5f, m_camera.m_flHeight * m_flClipSpaceTopRightY * 0.5f, m_camera.m_flWidth * m_flClipSpaceTopRightX * 0.5f, m_camera.m_flHeight * m_flClipSpaceBottomLeftY * 0.5f, m_camera.m_flZNear, m_camera.m_flZFar ); } else { // Determine the extents ComputeProjectionMatrix( &m_projection, m_camera.m_flZNear, m_camera.m_flZFar, m_camera.m_flFOVX, m_camera.m_flAspect, m_flClipSpaceBottomLeftX, m_flClipSpaceBottomLeftY, m_flClipSpaceTopRightX, m_flClipSpaceTopRightY ); } CalcViewProj(); ExtractDirectionVectors( &m_forward, &m_left, &m_up, m_cameraToWorld ); FrustumFromViewProj( &m_frustumStruct, m_viewProj, m_camera.m_origin, true ); m_bDirty = false; } void CFrustum::BuildFrustumFromVectors( const Vector &origin, float flNear, float flFar, float flFOV, float flAspect, Vector const &vecForward, Vector const &vecLeft, Vector const &vecUp ) { InitCamera( origin, QAngle( 0, 0, 0 ), flNear, flFar, flFOV, flAspect ); ComputeViewMatrix( &m_worldToView, &m_cameraToWorld, origin, vecForward, vecLeft, vecUp ); ComputeProjectionMatrix( &m_projection, flNear, flFar, flFOV, flAspect ); m_viewProj = (m_projection * VMatrix(m_worldToView)); ExtractDirectionVectors( &m_forward, &m_left, &m_up, m_cameraToWorld ); m_frustumStruct.CreatePerspectiveFrustum( vec3_origin, m_forward, -m_left, m_up, flNear, flFar, flFOV, flAspect ); MatrixInverseGeneral( m_viewProj, m_invViewProj ); MatrixInverseGeneral( m_projection, m_invProjection ); VMatrix worldToView( m_worldToView ); VMatrix viewToWorld; worldToView.InverseGeneral( viewToWorld ); m_cameraToWorld = viewToWorld.As3x4(); viewToWorld.GetTranslation( m_camera.m_origin ); } /// Given only the world->view and an ortho view->proj matrices, this helper method computes /// the implied frustum values needed for orthographic shadow buffer rendering (but /// should work with perspective projections too). This is slow and general, but /// it should guarantee a frustum in a consistent/sane state given any world->view and /// view->proj matrices. void CFrustum::BuildShadowFrustum( VMatrix &newWorldToView, VMatrix &newProj ) { SetView( newWorldToView ); SetProj( newProj ); CalcViewProj(); VMatrix &viewToProj = m_projection; Assert( ( viewToProj.m[3][0] == 0.0f ) && ( viewToProj.m[3][1] == 0.0f ) && ( viewToProj.m[3][2] == 0.0f ) && ( viewToProj[3][3] == 1.0f ) ); VMatrix worldToView( m_worldToView ); VMatrix worldToCamera; MatrixMultiply( g_matViewToCameraMatrix, worldToView, worldToCamera ); VMatrix cameraToWorld; MatrixInverseGeneral( worldToCamera, cameraToWorld ); m_cameraToWorld = cameraToWorld.As3x4(); // Compute camera location in world space. VMatrix viewToWorld; MatrixInverseGeneral( worldToView, viewToWorld ); viewToWorld.GetTranslation( m_camera.m_origin ); cameraToWorld.GetTranslation( m_camera.m_origin ); MatrixToAngles( cameraToWorld, m_camera.m_angles ); // forward/left/up - world relative coordinates, assuming an FPS camera sitting on an XY plane, Z is up MatrixGetRow( worldToCamera, FORWARD_AXIS, &m_forward ); MatrixGetRow( worldToCamera, LEFT_AXIS, &m_left ); MatrixGetRow( worldToCamera, UP_AXIS, &m_up ); // Compute near/far planes, assuming D3D-style clipping range of [0,1] VMatrix projToView; viewToProj.InverseGeneral( projToView ); Vector vNearPoint, vFarPoint; projToView.V3Mul( Vector( 0.0f, 0.0f, 0.0f ), vNearPoint ); projToView.V3Mul( Vector( 0.0f, 0.0f, 1.0f ), vFarPoint ); m_camera.m_flZNear = fabs( vNearPoint.z ); m_camera.m_flZFar = fabs( vFarPoint.z ); m_camera.m_flAspect = 1.0f; m_camera.m_flFOVX = -1.0f; Vector vCornerPoints[2]; // Y's are negated here because MatrixBuildOrtho() flips top/bottom! projToView.V3Mul( Vector( -1.0f, 1.0f, 0.0f ), vCornerPoints[0] ); // left/bottom projToView.V3Mul( Vector( 1.0f, -1.0f, 0.0f ), vCornerPoints[1] ); // right/top m_flClipSpaceBottomLeftX = vCornerPoints[0].x; m_flClipSpaceBottomLeftY = vCornerPoints[0].y; m_flClipSpaceTopRightX = vCornerPoints[1].x; m_flClipSpaceTopRightY = vCornerPoints[1].y; m_camera.m_flWidth = 2.0f; m_camera.m_flHeight = 2.0f; // Now compute the frustum planes used for culling purposes. These planes are computed assuming the camera is already at the origin. VMatrix worldToCamLocalWorld; // This is confusing - vShadowCamPos is not negated here, because we need to compensate for the fact that the // frustum culling code makes the cam pos the origin before culling by subtracting the camera's origin - so undo it. // Calc a viewproj matrix that has no g_ViewAlignMatrix matrix in it. MatrixBuildTranslation( worldToCamLocalWorld, m_camera.m_origin.x, m_camera.m_origin.y, m_camera.m_origin.z ); VMatrix worldToCamLocalWorldToView( worldToView * worldToCamLocalWorld ); VMatrix shadowCamLocalWorldToViewProj( viewToProj * worldToCamLocalWorldToView ); VPlane pSixPlanes[FRUSTUM_NUMPLANES]; #if 0 Vector actualOriginProjSpace( 0.0f, 0.0f, .5f ); Vector actualOriginWorldSpace; VMatrix projToWorld; m_viewProj.InverseGeneral( projToWorld ); projToWorld.V3Mul( actualOriginProjSpace, actualOriginWorldSpace ); ExtractClipPlanesFromNonTransposedMatrix( m_viewProj, pSixPlanes, true ); // Testing float flDots[6]; for (uint i = 0; i < 6; i++) { flDots[i] = pSixPlanes[i].DistTo( actualOriginWorldSpace ); } #endif ExtractClipPlanesFromNonTransposedMatrix( shadowCamLocalWorldToViewProj, pSixPlanes, true ); m_frustumStruct.SetPlanes( pSixPlanes ); MatrixInverseGeneral( m_viewProj, m_invViewProj ); MatrixInverseGeneral( m_projection, m_invProjection ); m_bDirty = false; // This should be a no-op (ignoring FP precision) if all the above stuff was done right. //m_bDirty = true; //UpdateFrustumFromCamera(); } void CFrustum::CalcFarPlaneCameraRelativePoints( Vector *p4PointsOut, float flFarPlane, float flClipSpaceBottomLeftX, float flClipSpaceBottomLeftY, float flClipSpaceTopRightX, float flClipSpaceTopRightY ) const { Vector vForward = CameraForward(); Vector vUp = CameraUp(); Vector vLeft = CameraLeft(); float flFovX = GetCameraFOV(); ::CalcFarPlaneCameraRelativePoints( p4PointsOut, vForward, vUp, vLeft, flFarPlane, flFovX, GetCameraAspect(), flClipSpaceBottomLeftX, flClipSpaceBottomLeftY, flClipSpaceTopRightX, flClipSpaceTopRightY ); } // generates 8 vertices of the frustum void Camera_t::ComputeGeometry( Vector *pVertsOut8, const Vector &vForward, const Vector &vLeft, const Vector &vUp ) const { Vector vNearLeft, vFarLeft; Vector vNearUp, vFarUp; Vector vNear = m_origin + m_flZNear * vForward; Vector vFar = m_origin + m_flZFar * vForward; if ( IsOrthographic() ) { vNearLeft = vLeft * m_flWidth; vNearUp = vUp * m_flHeight; vFarLeft = vNearLeft; vFarUp = vNearUp; } else { float flTanX = tan( DEG2RAD(m_flFOVX) * 0.5f ); float flooAspect = 1.0f / m_flAspect; float flWidth = m_flZNear * flTanX; float flHeight = flWidth * flooAspect; vNearLeft = vLeft * flWidth; vNearUp = vUp * flHeight; float flFarWidth = m_flZFar * flTanX; float flFarHeight = flFarWidth * flooAspect; vFarLeft = vLeft * flFarWidth; vFarUp = vUp * flFarHeight; } pVertsOut8[0] = vNear + vNearLeft - vNearUp; pVertsOut8[1] = vNear - vNearLeft - vNearUp; pVertsOut8[2] = vNear + vNearLeft + vNearUp; pVertsOut8[3] = vNear - vNearLeft + vNearUp; pVertsOut8[4] = vFar + vFarLeft - vFarUp; pVertsOut8[5] = vFar - vFarLeft - vFarUp; pVertsOut8[6] = vFar + vFarLeft + vFarUp; pVertsOut8[7] = vFar - vFarLeft + vFarUp; } void Camera_t::ComputeGeometry( Vector *pVertsOut8 ) const { Vector vForward, vLeft, vUp; AngleVectorsFLU( m_angles, &vForward, &vLeft, &vUp ); ComputeGeometry( pVertsOut8, vForward, vLeft, vUp ); } // generates 8 vertices of the frustum as bounds void CFrustum::ComputeBounds( Vector *pMins, Vector *pMaxs ) const { ClearBounds( *pMins, *pMaxs ); Vector vPts[8]; m_camera.ComputeGeometry( vPts, m_forward, m_left, m_up ); for ( int i = 0; i < 8; i++ ) { AddPointToBounds( vPts[i], *pMins, *pMaxs ); } } static inline void InvertVMatrix( const VMatrix &src, VMatrix &dst ) { src.InverseGeneral( dst ); } void CFrustum::CalcViewProj() { m_viewProj = ( m_projection * VMatrix( m_worldToView ) ); InvertVMatrix( m_viewProj, m_invViewProj ); InvertVMatrix( m_projection, m_invProjection ); } float CFrustum::ComputeScreenSize( Vector vecOrigin, float flRadius ) const { vecOrigin -= GetCameraPosition(); float flDist = vecOrigin.Length(); if ( flDist < flRadius ) { return 1.0; // eye inside sphere } float flSin = sin( DEG2RAD( MIN( GetCameraFOV(), 90.0 ) ) ); return MIN( 1.0, flSin * ( flRadius / flDist ) ); } void CFrustum::ViewToWorld( const Vector2D &vViewMinusOneToOne, Vector *pOutWorld ) { Vector vView3D; vView3D.x = vViewMinusOneToOne.x; vView3D.y = vViewMinusOneToOne.y; vView3D.z = 0; const VMatrix &invViewProjMatrix = GetInvViewProj(); Vector3DMultiplyPositionProjective( invViewProjMatrix, vView3D, *pOutWorld ); } void CFrustum::BuildRay( const Vector2D &vViewMinusOneToOne, Vector *pOutRayStart, Vector *pOutRayDirection ) { Vector vClickPoint; ViewToWorld( vViewMinusOneToOne, &vClickPoint ); if ( !IsOrthographic() ) { Camera_t camera = GetCameraStruct(); Vector vRay = vClickPoint - camera.m_origin; VectorNormalize( vRay ); *pOutRayStart = camera.m_origin; *pOutRayDirection = vRay; } else { *pOutRayStart = vClickPoint; ViewForward( *pOutRayDirection ); } } void CFrustum::BuildFrustumFromParameters( const Vector &origin, const QAngle &angles, float flNear, float flFar, float flFOV, float flAspect, const VMatrix &worldToView, const VMatrix &viewToProj ) { InitCamera( origin, angles, flNear, flFar, flFOV, flAspect ); m_worldToView = worldToView.As3x4(); VMatrix worldToCamera; MatrixMultiply( g_matViewToCameraMatrix, worldToView, worldToCamera ); VMatrix cameraToWorld; MatrixInverseGeneral( worldToCamera, cameraToWorld ); m_cameraToWorld = cameraToWorld.As3x4(); m_projection = viewToProj; CalcViewProj(); // forward/left/up - world relative coordinates, assuming an FPS camera sitting on an XY plane, Z is up MatrixGetRow( worldToCamera, FORWARD_AXIS, &m_forward ); MatrixGetRow( worldToCamera, LEFT_AXIS, &m_left ); MatrixGetRow( worldToCamera, UP_AXIS, &m_up ); // Now compute the frustum planes used for culling purposes. These planes are computed assuming the camera is already at the origin. VMatrix worldToCamLocalWorld; // This is confusing - vShadowCamPos is not negated here, because we need to compensate for the fact that the // frustum culling code makes the cam pos the origin before culling by subtracting the camera's origin - so undo it. MatrixBuildTranslation( worldToCamLocalWorld, m_camera.m_origin.x, m_camera.m_origin.y, m_camera.m_origin.z ); VMatrix worldToCamLocalWorldToView( worldToView * worldToCamLocalWorld ); VMatrix shadowCamLocalWorldToViewProj( viewToProj * worldToCamLocalWorldToView ); VPlane pSixPlanes[FRUSTUM_NUMPLANES]; ExtractClipPlanesFromNonTransposedMatrix( shadowCamLocalWorldToViewProj, pSixPlanes, true ); m_frustumStruct.SetPlanes( pSixPlanes ); m_bDirty = false; // This should be a no-op (ignoring FP precision) if all the above stuff was done right. //m_bDirty = true; //UpdateFrustumFromCamera(); }