Paranoia2/cl_dll/render/gl_subview.cpp

352 lines
9.4 KiB
C++

/*
gl_mirror.cpp - draw reflected surfaces
Copyright (C) 2011 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 "gl_local.h"
#include "gl_world.h"
#include "mathlib.h"
#include <stringlib.h>
#include "gl_occlusion.h"
#define MIRROR_PLANE_EPSILON 0.1f
/*
=============================================================
MIRROR RENDERING
=============================================================
*/
/*
================
R_SetupMirrorView
Prepare view for mirroring
================
*/
int R_SetupMirrorView( msurface_t *surf, ref_viewpass_t *rvp )
{
cl_entity_t *ent = surf->info->parent;
matrix3x3 matAngles;
mplane_t plane;
gl_state_t *glm;
float d;
// setup mirror plane
if( FBitSet( surf->flags, SURF_PLANEBACK ))
SetPlane( &plane, -surf->plane->normal, -surf->plane->dist );
else SetPlane( &plane, surf->plane->normal, surf->plane->dist );
glm = GL_GetCache( ent->hCachedMatrix );
if( ent->hCachedMatrix != WORLD_MATRIX )
glm->transform.TransformPositivePlane( plane, plane );
// reflect view by mirror plane
d = -2.0f * ( DotProduct( GetVieworg(), plane.normal ) - plane.dist );
Vector origin = GetVieworg() + d * plane.normal;
d = -2.0f * DotProduct( GetVForward(), plane.normal );
Vector forward = ( GetVForward() + d * plane.normal ).Normalize();
d = -2.0f * DotProduct( GetVRight(), plane.normal );
Vector right = ( GetVRight() + d * plane.normal ).Normalize();
d = -2.0f * DotProduct( GetVUp(), plane.normal );
Vector up = ( GetVUp() + d * plane.normal ).Normalize();
// compute mirror angles
matAngles.SetForward( forward );
matAngles.SetRight( right );
matAngles.SetUp( up );
Vector angles = matAngles.GetAngles();
plane.dist += ON_EPSILON; // to prevent z-fighting with reflective water
rvp->flags = RP_MIRRORVIEW|RP_CLIPPLANE|RP_MERGE_PVS;
RI->view.frustum.SetPlane( FRUSTUM_NEAR, plane.normal, plane.dist );
RI->clipPlane = plane;
rvp->viewangles[0] = anglemod( angles[0] );
rvp->viewangles[1] = anglemod( angles[1] );
rvp->viewangles[2] = anglemod( angles[2] );
rvp->vieworigin = origin;
rvp->fov_x = RI->view.fov_x;
rvp->fov_y = RI->view.fov_y;
// put pvsorigin before the mirror plane to avoid get recursion with himself
if( ent == GET_ENTITY( 0 )) origin = surf->info->origin + (1.0f * plane.normal);
else origin = glm->transform.VectorTransform( surf->info->origin ) + (1.0f * plane.normal);
material_t *mat = R_TextureAnimation( surf )->material;
RI->view.pvspoint = origin;
rvp->viewport[0] = rvp->viewport[1] = 0;
if( FBitSet( surf->flags, SURF_REFLECT_PUDDLE ))
{
rvp->viewport[2] = 192;
rvp->viewport[3] = 192;
}
else
{
rvp->viewport[2] = RI->view.port[2];
rvp->viewport[3] = RI->view.port[3];
}
if( GL_Support( R_ARB_TEXTURE_NPOT_EXT ))
{
// allow screen size
// rvp->viewport[2] = bound( 96, rvp->viewport[2], 1024 );
// rvp->viewport[3] = bound( 72, rvp->viewport[3], 768 );
}
else
{
rvp->viewport[2] = NearestPOW( rvp->viewport[2], true );
rvp->viewport[3] = NearestPOW( rvp->viewport[3], true );
// rvp->viewport[2] = bound( 128, rvp->viewport[2], 1024 );
// rvp->viewport[3] = bound( 64, rvp->viewport[3], 512 );
}
if( FBitSet( mat->flags, BRUSH_LIQUID ))
SetBits( rvp->flags, RP_WATERPASS );
else if( FBitSet( surf->flags, SURF_REFLECT_PUDDLE ))
SetBits( rvp->flags, RP_NOSHADOWS|RP_WATERPASS|RP_NOGRASS ); // don't draw grass from puddles
return TF_SCREEN;
}
/*
================
R_AllocateSubviewTexture
Allocate the screen texture and make copy
================
*/
int R_AllocateSubviewTexture( int viewport[4], int texFlags )
{
int i;
// first, search for available mirror texture
for( i = 0; i < tr.num_subview_used; i++ )
{
if( tr.subviewTextures[i].texframe == tr.realframecount )
continue; // already used for this frame
if( viewport[2] != RENDER_GET_PARM( PARM_TEX_WIDTH, tr.subviewTextures[i].texturenum ))
continue; // width mismatched
if( viewport[3] != RENDER_GET_PARM( PARM_TEX_HEIGHT, tr.subviewTextures[i].texturenum ))
continue; // height mismatched
// screens don't want textures with 'clamp' modifier
if( FBitSet( texFlags, TF_CLAMP ) != FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, tr.subviewTextures[i].texturenum ), TF_CLAMP ))
continue; // mismatch texture flags
// found a valid spot
tr.subviewTextures[i].texframe = tr.realframecount; // now used
break;
}
if( i == tr.num_subview_used )
{
if( i == MAX_SUBVIEW_TEXTURES )
{
ALERT( at_error, "R_AllocSubviewTexture: texture limit exceeded (per frame)!\n" );
return 0; // disable
}
// create new mirror texture
tr.subviewTextures[i].texturenum = CREATE_TEXTURE( va( "*subview%i", i ), viewport[2], viewport[3], NULL, texFlags );
tr.subviewTextures[i].texframe = tr.realframecount; // now used
if( GL_Support( R_FRAMEBUFFER_OBJECT ))
tr.subviewTextures[i].framebuffer = R_AllocFrameBuffer( viewport );
tr.num_subview_used++; // allocate new one
}
if( GL_Support( R_FRAMEBUFFER_OBJECT ))
{
GL_BindFrameBuffer( tr.subviewTextures[i].framebuffer, tr.subviewTextures[i].texturenum );
}
else
{
GL_BindTexture( GL_TEXTURE0, tr.subviewTextures[i].texturenum );
pglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, viewport[0], viewport[1], viewport[2], viewport[3], 0 );
}
return (i+1);
}
bool R_CheckMirrorClone( msurface_t *surf, msurface_t *check )
{
if( !FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE ))
return false;
if( !FBitSet( check->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE ))
return false;
if( !check->info->subtexture[glState.stack_position-1] )
return false;
if( surf->info->parent != check->info->parent )
return false;
if( FBitSet( surf->flags, SURF_PLANEBACK ) != FBitSet( check->flags, SURF_PLANEBACK ))
return false;
if( surf->plane->normal != check->plane->normal )
return false;
if( Q_sign( surf->plane->dist ) != Q_sign( check->plane->dist ))
return false;
if( fabs( surf->plane->dist - check->plane->dist ) > MIRROR_PLANE_EPSILON )
return false;
// just reuse the handle
surf->info->subtexture[glState.stack_position-1] = check->info->subtexture[glState.stack_position-1];
return true;
}
bool R_CanSkipPass( int end, msurface_t *surf, ref_instance_t *prevRI )
{
// IMPORTANT: limit the recursion depth
if( surf == prevRI->reject_face )
return true;
for( int i = 0; i < end; i++ )
{
if( R_CheckMirrorClone( surf, prevRI->frame.subview_faces[i] ))
return true;
}
// occluded by query
if( FBitSet( surf->flags, SURF_OCCLUDED ))
{
// don't use me
surf->info->subtexture[glState.stack_position-1] = 0;
return true;
}
return false;
}
/*
================
R_CheckOutside
check if main view is outside level
================
*/
bool R_CheckOutside( void )
{
// only main view should be tested
if( !RP_NORMALPASS( ))
return false;
mleaf_t *leaf = Mod_PointInLeaf( RI->view.origin, worldmodel->nodes );
if( RP_OUTSIDE( leaf ))
return true;
return false;
}
/*
================
R_RenderSubview
Draw scene from another points
e.g. for planar reflection,
remote cameras etc
================
*/
void R_RenderSubview( void )
{
ref_instance_t *prevRI;
unsigned int oldFBO;
ref_viewpass_t rvp;
// player is outside world. Don't draw subview for speedup reasons
if( R_CheckOutside( ))
return;
if( glState.stack_position > (unsigned int)r_recursion_depth->value )
return; // too deep...
if( FBitSet( RI->params, RP_DRAW_OVERVIEW ))
return;
if( !RI->frame.num_subview_faces )
return; // nothing to render
R_PushRefState(); // make refinst backup
prevRI = R_GetPrevInstance();
// draw the subviews through the list
for( int i = 0; i < prevRI->frame.num_subview_faces; i++ )
{
msurface_t *surf = prevRI->frame.subview_faces[i];
cl_entity_t *e = RI->currententity = surf->info->parent;
mextrasurf_t *es = surf->info;
int texFlags = 0, subview = 0;
RI->currentmodel = e->model;
if( R_CanSkipPass( i, surf, prevRI ))
continue;
ASSERT( RI->currententity != NULL );
ASSERT( RI->currentmodel != NULL );
// NOTE: we can do additionaly culling here by PVS
if( !e || e->curstate.messagenum != r_currentMessageNum )
continue; // bad camera, ignore
// setup view apropriate by type
if( FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE ))
{
texFlags = R_SetupMirrorView( surf, &rvp );
r_stats.c_subview_passes++;
}
else continue; // ???
oldFBO = glState.frameBuffer;
if( GL_Support( R_FRAMEBUFFER_OBJECT ))
{
if(( subview = R_AllocateSubviewTexture( rvp.viewport, texFlags )) == 0 )
continue;
}
// reset the subinfo
surf->info->subtexture[glState.stack_position-1] = 0;
RI->reject_face = surf;
R_RenderScene( &rvp, rvp.flags );
RI->reject_face = NULL;
if( !GL_Support( R_FRAMEBUFFER_OBJECT ))
subview = R_AllocateSubviewTexture( rvp.viewport, texFlags );
ASSERT( subview > 0 && subview < MAX_SUBVIEW_TEXTURES );
surf->info->subtexture[glState.stack_position-1] = subview; // now it's valid
tr.subviewTextures[subview-1].matrix = RI->view.worldProjectionMatrix;
GL_BindFBO( oldFBO );
R_ResetRefState();
}
R_PopRefState(); // restore ref instance
}