374 lines
11 KiB
C++
374 lines
11 KiB
C++
/*
|
|
gl_frustum.cpp - frustum test implementation class
|
|
Copyright (C) 2014 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "hud.h"
|
|
#include "cl_util.h"
|
|
#include "const.h"
|
|
#include "ref_params.h"
|
|
#include "gl_local.h"
|
|
#include <mathlib.h>
|
|
#include <stringlib.h>
|
|
|
|
void CFrustum :: ClearFrustum( void )
|
|
{
|
|
memset( planes, 0, sizeof( planes ));
|
|
clipFlags = 0;
|
|
}
|
|
|
|
void CFrustum :: EnablePlane( int side )
|
|
{
|
|
ASSERT( side >= 0 && side < FRUSTUM_PLANES );
|
|
|
|
// make sure what plane is ready
|
|
if( planes[side].normal != g_vecZero )
|
|
SetBits( clipFlags, BIT( side ));
|
|
}
|
|
|
|
void CFrustum :: DisablePlane( int side )
|
|
{
|
|
ASSERT( side >= 0 && side < FRUSTUM_PLANES );
|
|
ClearBits( clipFlags, BIT( side ));
|
|
}
|
|
|
|
void CFrustum :: InitProjection( const matrix4x4 &view, float flZNear, float flZFar, float flFovX, float flFovY )
|
|
{
|
|
float xs, xc;
|
|
Vector normal;
|
|
|
|
// horizontal fov used for left and right planes
|
|
SinCos( DEG2RAD( flFovX ) * 0.5f, &xs, &xc );
|
|
|
|
// setup left plane
|
|
normal = view.GetForward() * xs + view.GetRight() * -xc;
|
|
SetPlane( FRUSTUM_LEFT, normal, DotProduct( view.GetOrigin(), normal ));
|
|
|
|
// setup right plane
|
|
normal = view.GetForward() * xs + view.GetRight() * xc;
|
|
SetPlane( FRUSTUM_RIGHT, normal, DotProduct( view.GetOrigin(), normal ));
|
|
|
|
// vertical fov used for top and bottom planes
|
|
SinCos( DEG2RAD( flFovY ) * 0.5f, &xs, &xc );
|
|
|
|
// setup bottom plane
|
|
normal = view.GetForward() * xs + view.GetUp() * -xc;
|
|
SetPlane( FRUSTUM_BOTTOM, normal, DotProduct( view.GetOrigin(), normal ));
|
|
|
|
// setup top plane
|
|
normal = view.GetForward() * xs + view.GetUp() * xc;
|
|
SetPlane( FRUSTUM_TOP, normal, DotProduct( view.GetOrigin(), normal ));
|
|
|
|
// setup far plane
|
|
SetPlane( FRUSTUM_FAR, -view.GetForward(), DotProduct( -view.GetForward(), ( view.GetOrigin() + view.GetForward() * flZFar )));
|
|
|
|
// no need to setup backplane for general view. It's only used for portals and mirrors
|
|
if( flZNear == 0.0f ) return;
|
|
|
|
// setup near plane
|
|
SetPlane( FRUSTUM_NEAR, view.GetForward(), DotProduct( view.GetForward(), ( view.GetOrigin() + view.GetForward() * flZNear )));
|
|
}
|
|
|
|
void CFrustum :: InitOrthogonal( const matrix4x4 &view, float xLeft, float xRight, float yBottom, float yTop, float flZNear, float flZFar )
|
|
{
|
|
// setup the near and far planes
|
|
float orgOffset = DotProduct( view.GetOrigin(), view.GetForward() );
|
|
|
|
SetPlane( FRUSTUM_FAR, -view.GetForward(), -flZNear - orgOffset );
|
|
SetPlane( FRUSTUM_NEAR, view.GetForward(), flZFar + orgOffset );
|
|
|
|
// setup left and right planes
|
|
orgOffset = DotProduct( view.GetOrigin(), view.GetRight() );
|
|
|
|
SetPlane( FRUSTUM_LEFT, view.GetRight(), xLeft + orgOffset );
|
|
SetPlane( FRUSTUM_RIGHT, -view.GetRight(), -xRight - orgOffset );
|
|
|
|
// setup top and buttom planes
|
|
orgOffset = DotProduct( view.GetOrigin(), view.GetUp() );
|
|
|
|
SetPlane( FRUSTUM_TOP, view.GetUp(), yTop + orgOffset );
|
|
SetPlane( FRUSTUM_BOTTOM, -view.GetUp(), -yBottom - orgOffset );
|
|
}
|
|
|
|
void CFrustum :: InitBoxFrustum( const Vector &org, float radius )
|
|
{
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
// setup normal for each direction
|
|
Vector normal = g_vecZero;
|
|
normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f;
|
|
SetPlane( i, normal, DotProduct( org, normal ) - radius );
|
|
}
|
|
}
|
|
|
|
void CFrustum :: InitProjectionFromMatrix( const matrix4x4 &projection )
|
|
{
|
|
// left
|
|
planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0];
|
|
planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0];
|
|
planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0];
|
|
planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]);
|
|
|
|
// right
|
|
planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0];
|
|
planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0];
|
|
planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0];
|
|
planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]);
|
|
|
|
// bottom
|
|
planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1];
|
|
planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1];
|
|
planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1];
|
|
planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]);
|
|
|
|
// top
|
|
planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1];
|
|
planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1];
|
|
planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1];
|
|
planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]);
|
|
|
|
// near
|
|
planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2];
|
|
planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2];
|
|
planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2];
|
|
planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]);
|
|
|
|
// far
|
|
planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2];
|
|
planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2];
|
|
planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2];
|
|
planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]);
|
|
|
|
for( int i = 0; i < FRUSTUM_PLANES; i++ )
|
|
{
|
|
NormalizePlane( i );
|
|
}
|
|
}
|
|
|
|
void CFrustum :: SetPlane( int side, const Vector &vecNormal, float flDist )
|
|
{
|
|
ASSERT( side >= 0 && side < FRUSTUM_PLANES );
|
|
|
|
planes[side].type = PlaneTypeForNormal( vecNormal );
|
|
planes[side].signbits = SignbitsForPlane( vecNormal );
|
|
planes[side].normal = vecNormal;
|
|
planes[side].dist = flDist;
|
|
|
|
clipFlags |= BIT( side );
|
|
}
|
|
|
|
void CFrustum :: NormalizePlane( int side )
|
|
{
|
|
ASSERT( side >= 0 && side < FRUSTUM_PLANES );
|
|
|
|
// normalize
|
|
float length = planes[side].normal.Length();
|
|
|
|
if( length )
|
|
{
|
|
float ilength = (1.0f / length);
|
|
planes[side].normal.x *= ilength;
|
|
planes[side].normal.y *= ilength;
|
|
planes[side].normal.z *= ilength;
|
|
planes[side].dist *= ilength;
|
|
}
|
|
|
|
planes[side].type = PlaneTypeForNormal( planes[side].normal );
|
|
planes[side].signbits = SignbitsForPlane( planes[side].normal );
|
|
|
|
clipFlags |= BIT( side );
|
|
}
|
|
|
|
void CFrustum :: ComputeFrustumCorners( Vector corners[8] )
|
|
{
|
|
memset( corners, 0, sizeof( Vector ) * 8 );
|
|
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_FAR], corners[0] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_FAR], corners[1] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_FAR], corners[2] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_FAR], corners[3] );
|
|
|
|
if( FBitSet( clipFlags, BIT( FRUSTUM_NEAR )))
|
|
{
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_NEAR], corners[4] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_NEAR], corners[5] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_NEAR], corners[6] );
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_NEAR], corners[7] );
|
|
}
|
|
else
|
|
{
|
|
PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], corners[4] );
|
|
corners[7] = corners[6] = corners[5] = corners[4];
|
|
}
|
|
}
|
|
|
|
void CFrustum :: ComputeFrustumBounds( Vector &mins, Vector &maxs )
|
|
{
|
|
Vector corners[8];
|
|
|
|
ComputeFrustumCorners( corners );
|
|
|
|
ClearBounds( mins, maxs );
|
|
|
|
for( int i = 0; i < 8; i++ )
|
|
AddPointToBounds( corners[i], mins, maxs );
|
|
}
|
|
|
|
void CFrustum :: DrawFrustumDebug( void )
|
|
{
|
|
Vector bbox[8];
|
|
|
|
ComputeFrustumCorners( bbox );
|
|
|
|
// g-cont. frustum must be yellow :-)
|
|
pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f );
|
|
GL_BindTexture( GL_TEXTURE0, tr.whiteTexture );
|
|
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
pglBegin( GL_LINES );
|
|
|
|
for( int i = 0; i < 2; i += 1 )
|
|
{
|
|
pglVertex3fv( bbox[i+0] );
|
|
pglVertex3fv( bbox[i+2] );
|
|
pglVertex3fv( bbox[i+4] );
|
|
pglVertex3fv( bbox[i+6] );
|
|
pglVertex3fv( bbox[i+0] );
|
|
pglVertex3fv( bbox[i+4] );
|
|
pglVertex3fv( bbox[i+2] );
|
|
pglVertex3fv( bbox[i+6] );
|
|
pglVertex3fv( bbox[i*2+0] );
|
|
pglVertex3fv( bbox[i*2+1] );
|
|
pglVertex3fv( bbox[i*2+4] );
|
|
pglVertex3fv( bbox[i*2+5] );
|
|
}
|
|
|
|
pglEnd();
|
|
}
|
|
|
|
bool CFrustum :: CullBox( const Vector &mins, const Vector &maxs, int userClipFlags )
|
|
{
|
|
int iClipFlags;
|
|
|
|
if( CVAR_TO_BOOL( r_nocull ))
|
|
return false;
|
|
|
|
if( userClipFlags != 0 )
|
|
iClipFlags = userClipFlags;
|
|
else iClipFlags = clipFlags;
|
|
|
|
for( int i = 0; i < FRUSTUM_PLANES; i++ )
|
|
{
|
|
if( !FBitSet( iClipFlags, BIT( i )))
|
|
continue;
|
|
|
|
const mplane_t *p = &planes[i];
|
|
|
|
switch( p->signbits )
|
|
{
|
|
case 0:
|
|
if( p->normal.x * maxs.x + p->normal.y * maxs.y + p->normal.z * maxs.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 1:
|
|
if( p->normal.x * mins.x + p->normal.y * maxs.y + p->normal.z * maxs.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 2:
|
|
if( p->normal.x * maxs.x + p->normal.y * mins.y + p->normal.z * maxs.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 3:
|
|
if( p->normal.x * mins.x + p->normal.y * mins.y + p->normal.z * maxs.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 4:
|
|
if( p->normal.x * maxs.x + p->normal.y * maxs.y + p->normal.z * mins.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 5:
|
|
if( p->normal.x * mins.x + p->normal.y * maxs.y + p->normal.z * mins.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 6:
|
|
if( p->normal.x * maxs.x + p->normal.y * mins.y + p->normal.z * mins.z < p->dist )
|
|
return true;
|
|
break;
|
|
case 7:
|
|
if( p->normal.x * mins.x + p->normal.y * mins.y + p->normal.z * mins.z < p->dist )
|
|
return true;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CFrustum :: CullSphere( const Vector ¢re, float radius, int userClipFlags )
|
|
{
|
|
int iClipFlags;
|
|
|
|
if( CVAR_TO_BOOL( r_nocull ))
|
|
return false;
|
|
|
|
if( userClipFlags != 0 )
|
|
iClipFlags = userClipFlags;
|
|
else iClipFlags = clipFlags;
|
|
|
|
for( int i = 0; i < FRUSTUM_PLANES; i++ )
|
|
{
|
|
if( !FBitSet( iClipFlags, BIT( i )))
|
|
continue;
|
|
|
|
const mplane_t *p = &planes[i];
|
|
|
|
if( DotProduct( centre, p->normal ) - p->dist <= -radius )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// FIXME: could be optimize?
|
|
bool CFrustum :: CullFrustum( CFrustum *frustum, int userClipFlags )
|
|
{
|
|
int iClipFlags;
|
|
Vector bbox[8];
|
|
|
|
if( CVAR_TO_BOOL( r_nocull ))
|
|
return false;
|
|
|
|
if( userClipFlags != 0 )
|
|
iClipFlags = userClipFlags;
|
|
else iClipFlags = clipFlags;
|
|
|
|
frustum->ComputeFrustumCorners( bbox );
|
|
|
|
for( int i = 0; i < FRUSTUM_PLANES; i++ )
|
|
{
|
|
if( !FBitSet( iClipFlags, BIT( i )))
|
|
continue;
|
|
|
|
const mplane_t *p = &planes[i];
|
|
|
|
for( int j = 0; j < 8; j++ )
|
|
{
|
|
// at least one point of other frustum intersect with our frustum
|
|
if( DotProduct( bbox[j], p->normal ) - p->dist >= ON_EPSILON )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} |