csgo-2018-source/mathlib/camera.cpp
2021-07-24 21:11:47 -07:00

734 lines
25 KiB
C++

//====== 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();
}