mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-23 21:02:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1165 lines
30 KiB
C++
1165 lines
30 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code 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.
|
|
|
|
Doom 3 Source Code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#ifdef __ppc__
|
|
#include <vecLib/vecLib.h>
|
|
#endif
|
|
#if defined(__GNUC__) && defined(__SSE2__)
|
|
#include <xmmintrin.h>
|
|
#endif
|
|
|
|
#include "sys/platform.h"
|
|
#include "framework/Session.h"
|
|
#include "renderer/RenderWorld_local.h"
|
|
|
|
#include "renderer/tr_local.h"
|
|
|
|
//====================================================================
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::Clear
|
|
======================
|
|
*/
|
|
void idScreenRect::Clear() {
|
|
x1 = y1 = 32000;
|
|
x2 = y2 = -32000;
|
|
zmin = 0.0f; zmax = 1.0f;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::AddPoint
|
|
======================
|
|
*/
|
|
void idScreenRect::AddPoint( float x, float y ) {
|
|
int ix = idMath::FtoiFast( x );
|
|
int iy = idMath::FtoiFast( y );
|
|
|
|
if ( ix < x1 ) {
|
|
x1 = ix;
|
|
}
|
|
if ( ix > x2 ) {
|
|
x2 = ix;
|
|
}
|
|
if ( iy < y1 ) {
|
|
y1 = iy;
|
|
}
|
|
if ( iy > y2 ) {
|
|
y2 = iy;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::Expand
|
|
======================
|
|
*/
|
|
void idScreenRect::Expand() {
|
|
x1--;
|
|
y1--;
|
|
x2++;
|
|
y2++;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::Intersect
|
|
======================
|
|
*/
|
|
void idScreenRect::Intersect( const idScreenRect &rect ) {
|
|
if ( rect.x1 > x1 ) {
|
|
x1 = rect.x1;
|
|
}
|
|
if ( rect.x2 < x2 ) {
|
|
x2 = rect.x2;
|
|
}
|
|
if ( rect.y1 > y1 ) {
|
|
y1 = rect.y1;
|
|
}
|
|
if ( rect.y2 < y2 ) {
|
|
y2 = rect.y2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::Union
|
|
======================
|
|
*/
|
|
void idScreenRect::Union( const idScreenRect &rect ) {
|
|
if ( rect.x1 < x1 ) {
|
|
x1 = rect.x1;
|
|
}
|
|
if ( rect.x2 > x2 ) {
|
|
x2 = rect.x2;
|
|
}
|
|
if ( rect.y1 < y1 ) {
|
|
y1 = rect.y1;
|
|
}
|
|
if ( rect.y2 > y2 ) {
|
|
y2 = rect.y2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::Equals
|
|
======================
|
|
*/
|
|
bool idScreenRect::Equals( const idScreenRect &rect ) const {
|
|
return ( x1 == rect.x1 && x2 == rect.x2 && y1 == rect.y1 && y2 == rect.y2 );
|
|
}
|
|
|
|
/*
|
|
======================
|
|
idScreenRect::IsEmpty
|
|
======================
|
|
*/
|
|
bool idScreenRect::IsEmpty() const {
|
|
return ( x1 > x2 || y1 > y2 );
|
|
}
|
|
|
|
/*
|
|
======================
|
|
R_ScreenRectFromViewFrustumBounds
|
|
======================
|
|
*/
|
|
idScreenRect R_ScreenRectFromViewFrustumBounds( const idBounds &bounds ) {
|
|
idScreenRect screenRect;
|
|
|
|
screenRect.x1 = idMath::FtoiFast( 0.5f * ( 1.0f - bounds[1].y ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) );
|
|
screenRect.x2 = idMath::FtoiFast( 0.5f * ( 1.0f - bounds[0].y ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) );
|
|
screenRect.y1 = idMath::FtoiFast( 0.5f * ( 1.0f + bounds[0].z ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) );
|
|
screenRect.y2 = idMath::FtoiFast( 0.5f * ( 1.0f + bounds[1].z ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) );
|
|
|
|
if ( r_useDepthBoundsTest.GetInteger() ) {
|
|
R_TransformEyeZToWin( -bounds[0].x, tr.viewDef->projectionMatrix, screenRect.zmin );
|
|
R_TransformEyeZToWin( -bounds[1].x, tr.viewDef->projectionMatrix, screenRect.zmax );
|
|
}
|
|
|
|
return screenRect;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
R_ShowColoredScreenRect
|
|
======================
|
|
*/
|
|
void R_ShowColoredScreenRect( const idScreenRect &rect, int colorIndex ) {
|
|
if ( !rect.IsEmpty() ) {
|
|
static idVec4 colors[] = { colorRed, colorGreen, colorBlue, colorYellow, colorMagenta, colorCyan, colorWhite, colorPurple };
|
|
tr.viewDef->renderWorld->DebugScreenRect( colors[colorIndex & 7], rect, tr.viewDef );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
R_ToggleSmpFrame
|
|
====================
|
|
*/
|
|
void R_ToggleSmpFrame( void ) {
|
|
if ( r_lockSurfaces.GetBool() ) {
|
|
return;
|
|
}
|
|
R_FreeDeferredTriSurfs( frameData );
|
|
|
|
// clear frame-temporary data
|
|
frameData_t *frame;
|
|
frameMemoryBlock_t *block;
|
|
|
|
// update the highwater mark
|
|
R_CountFrameData();
|
|
|
|
frame = frameData;
|
|
|
|
// reset the memory allocation to the first block
|
|
frame->alloc = frame->memory;
|
|
|
|
// clear all the blocks
|
|
for ( block = frame->memory ; block ; block = block->next ) {
|
|
block->used = 0;
|
|
}
|
|
|
|
R_ClearCommandChain();
|
|
}
|
|
|
|
|
|
//=====================================================
|
|
|
|
#define MEMORY_BLOCK_SIZE 0x100000
|
|
|
|
/*
|
|
=====================
|
|
R_ShutdownFrameData
|
|
=====================
|
|
*/
|
|
void R_ShutdownFrameData( void ) {
|
|
frameData_t *frame;
|
|
frameMemoryBlock_t *block;
|
|
|
|
// free any current data
|
|
frame = frameData;
|
|
if ( !frame ) {
|
|
return;
|
|
}
|
|
|
|
R_FreeDeferredTriSurfs( frame );
|
|
|
|
frameMemoryBlock_t *nextBlock;
|
|
for ( block = frame->memory ; block ; block = nextBlock ) {
|
|
nextBlock = block->next;
|
|
Mem_Free( block );
|
|
}
|
|
Mem_Free( frame );
|
|
frameData = NULL;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
R_InitFrameData
|
|
=====================
|
|
*/
|
|
void R_InitFrameData( void ) {
|
|
int size;
|
|
frameData_t *frame;
|
|
frameMemoryBlock_t *block;
|
|
|
|
R_ShutdownFrameData();
|
|
|
|
frameData = (frameData_t *)Mem_ClearedAlloc( sizeof( *frameData ));
|
|
frame = frameData;
|
|
size = MEMORY_BLOCK_SIZE;
|
|
block = (frameMemoryBlock_t *)Mem_Alloc( size + sizeof( *block ) );
|
|
if ( !block ) {
|
|
common->FatalError( "R_InitFrameData: Mem_Alloc() failed" );
|
|
}
|
|
block->size = size;
|
|
block->used = 0;
|
|
block->next = NULL;
|
|
frame->memory = block;
|
|
frame->memoryHighwater = 0;
|
|
|
|
R_ToggleSmpFrame();
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_CountFrameData
|
|
================
|
|
*/
|
|
int R_CountFrameData( void ) {
|
|
frameData_t *frame;
|
|
frameMemoryBlock_t *block;
|
|
int count;
|
|
|
|
count = 0;
|
|
frame = frameData;
|
|
for ( block = frame->memory ; block ; block=block->next ) {
|
|
count += block->used;
|
|
if ( block == frame->alloc ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// note if this is a new highwater mark
|
|
if ( count > frame->memoryHighwater ) {
|
|
frame->memoryHighwater = count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_StaticAlloc
|
|
=================
|
|
*/
|
|
void *R_StaticAlloc( int bytes ) {
|
|
void *buf;
|
|
|
|
tr.pc.c_alloc++;
|
|
|
|
tr.staticAllocCount += bytes;
|
|
|
|
buf = Mem_Alloc( bytes );
|
|
|
|
// don't exit on failure on zero length allocations since the old code didn't
|
|
if ( !buf && ( bytes != 0 ) ) {
|
|
common->FatalError( "R_StaticAlloc failed on %i bytes", bytes );
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_ClearedStaticAlloc
|
|
=================
|
|
*/
|
|
void *R_ClearedStaticAlloc( int bytes ) {
|
|
void *buf;
|
|
|
|
buf = R_StaticAlloc( bytes );
|
|
SIMDProcessor->Memset( buf, 0, bytes );
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_StaticFree
|
|
=================
|
|
*/
|
|
void R_StaticFree( void *data ) {
|
|
tr.pc.c_free++;
|
|
Mem_Free( data );
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_FrameAlloc
|
|
|
|
This data will be automatically freed when the
|
|
current frame's back end completes.
|
|
|
|
This should only be called by the front end. The
|
|
back end shouldn't need to allocate memory.
|
|
|
|
If we passed smpFrame in, the back end could
|
|
alloc memory, because it will always be a
|
|
different frameData than the front end is using.
|
|
|
|
All temporary data, like dynamic tesselations
|
|
and local spaces are allocated here.
|
|
|
|
The memory will not move, but it may not be
|
|
contiguous with previous allocations even
|
|
from this frame.
|
|
|
|
The memory is NOT zero filled.
|
|
Should part of this be inlined in a macro?
|
|
================
|
|
*/
|
|
void *R_FrameAlloc( int bytes ) {
|
|
frameData_t *frame;
|
|
frameMemoryBlock_t *block;
|
|
void *buf;
|
|
|
|
bytes = (bytes+16)&~15;
|
|
// see if it can be satisfied in the current block
|
|
frame = frameData;
|
|
block = frame->alloc;
|
|
|
|
if ( block->size - block->used >= bytes ) {
|
|
buf = block->base + block->used;
|
|
block->used += bytes;
|
|
return buf;
|
|
}
|
|
|
|
// advance to the next memory block if available
|
|
block = block->next;
|
|
// create a new block if we are at the end of
|
|
// the chain
|
|
if ( !block ) {
|
|
int size;
|
|
|
|
size = MEMORY_BLOCK_SIZE;
|
|
block = (frameMemoryBlock_t *)Mem_Alloc( size + sizeof( *block ) );
|
|
if ( !block ) {
|
|
common->FatalError( "R_FrameAlloc: Mem_Alloc() failed" );
|
|
}
|
|
block->size = size;
|
|
block->used = 0;
|
|
block->next = NULL;
|
|
frame->alloc->next = block;
|
|
}
|
|
|
|
// we could fix this if we needed to...
|
|
if ( bytes > block->size ) {
|
|
common->FatalError( "R_FrameAlloc of %i exceeded MEMORY_BLOCK_SIZE",
|
|
bytes );
|
|
}
|
|
|
|
frame->alloc = block;
|
|
|
|
block->used = bytes;
|
|
|
|
return block->base;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_ClearedFrameAlloc
|
|
==================
|
|
*/
|
|
void *R_ClearedFrameAlloc( int bytes ) {
|
|
void *r;
|
|
|
|
r = R_FrameAlloc( bytes );
|
|
SIMDProcessor->Memset( r, 0, bytes );
|
|
return r;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
R_FrameFree
|
|
|
|
This does nothing at all, as the frame data is reused every frame
|
|
and can only be stack allocated.
|
|
|
|
The only reason for it's existance is so functions that can
|
|
use either static or frame memory can set function pointers
|
|
to both alloc and free.
|
|
==================
|
|
*/
|
|
void R_FrameFree( void *data ) {
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
void R_AxisToModelMatrix( const idMat3 &axis, const idVec3 &origin, float modelMatrix[16] ) {
|
|
modelMatrix[0] = axis[0][0];
|
|
modelMatrix[4] = axis[1][0];
|
|
modelMatrix[8] = axis[2][0];
|
|
modelMatrix[12] = origin[0];
|
|
|
|
modelMatrix[1] = axis[0][1];
|
|
modelMatrix[5] = axis[1][1];
|
|
modelMatrix[9] = axis[2][1];
|
|
modelMatrix[13] = origin[1];
|
|
|
|
modelMatrix[2] = axis[0][2];
|
|
modelMatrix[6] = axis[1][2];
|
|
modelMatrix[10] = axis[2][2];
|
|
modelMatrix[14] = origin[2];
|
|
|
|
modelMatrix[3] = 0;
|
|
modelMatrix[7] = 0;
|
|
modelMatrix[11] = 0;
|
|
modelMatrix[15] = 1;
|
|
}
|
|
|
|
|
|
// FIXME: these assume no skewing or scaling transforms
|
|
|
|
void R_LocalPointToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) {
|
|
#if defined(__GNUC__) && defined(__SSE2__)
|
|
__m128 m0, m1, m2, m3;
|
|
__m128 in0, in1, in2;
|
|
float i0,i1,i2;
|
|
i0 = in[0];
|
|
i1 = in[1];
|
|
i2 = in[2];
|
|
|
|
m0 = _mm_loadu_ps(&modelMatrix[0]);
|
|
m1 = _mm_loadu_ps(&modelMatrix[4]);
|
|
m2 = _mm_loadu_ps(&modelMatrix[8]);
|
|
m3 = _mm_loadu_ps(&modelMatrix[12]);
|
|
|
|
in0 = _mm_load1_ps(&i0);
|
|
in1 = _mm_load1_ps(&i1);
|
|
in2 = _mm_load1_ps(&i2);
|
|
|
|
m0 = _mm_mul_ps(m0, in0);
|
|
m1 = _mm_mul_ps(m1, in1);
|
|
m2 = _mm_mul_ps(m2, in2);
|
|
|
|
m0 = _mm_add_ps(m0, m1);
|
|
m0 = _mm_add_ps(m0, m2);
|
|
m0 = _mm_add_ps(m0, m3);
|
|
|
|
_mm_store_ss(&out[0], m0);
|
|
m1 = (__m128) _mm_shuffle_epi32((__m128i)m0, 0x55);
|
|
_mm_store_ss(&out[1], m1);
|
|
m2 = _mm_movehl_ps(m2, m0);
|
|
_mm_store_ss(&out[2], m2);
|
|
#else
|
|
out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4]
|
|
+ in[2] * modelMatrix[8] + modelMatrix[12];
|
|
out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5]
|
|
+ in[2] * modelMatrix[9] + modelMatrix[13];
|
|
out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6]
|
|
+ in[2] * modelMatrix[10] + modelMatrix[14];
|
|
#endif
|
|
}
|
|
|
|
void R_PointTimesMatrix( const float modelMatrix[16], const idVec4 &in, idVec4 &out ) {
|
|
out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4]
|
|
+ in[2] * modelMatrix[8] + modelMatrix[12];
|
|
out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5]
|
|
+ in[2] * modelMatrix[9] + modelMatrix[13];
|
|
out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6]
|
|
+ in[2] * modelMatrix[10] + modelMatrix[14];
|
|
out[3] = in[0] * modelMatrix[3] + in[1] * modelMatrix[7]
|
|
+ in[2] * modelMatrix[11] + modelMatrix[15];
|
|
}
|
|
|
|
void R_GlobalPointToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) {
|
|
idVec3 temp;
|
|
|
|
VectorSubtract( in, &modelMatrix[12], temp );
|
|
|
|
out[0] = DotProduct( temp, &modelMatrix[0] );
|
|
out[1] = DotProduct( temp, &modelMatrix[4] );
|
|
out[2] = DotProduct( temp, &modelMatrix[8] );
|
|
}
|
|
|
|
void R_LocalVectorToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) {
|
|
out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4]
|
|
+ in[2] * modelMatrix[8];
|
|
out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5]
|
|
+ in[2] * modelMatrix[9];
|
|
out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6]
|
|
+ in[2] * modelMatrix[10];
|
|
}
|
|
|
|
void R_GlobalVectorToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) {
|
|
out[0] = DotProduct( in, &modelMatrix[0] );
|
|
out[1] = DotProduct( in, &modelMatrix[4] );
|
|
out[2] = DotProduct( in, &modelMatrix[8] );
|
|
}
|
|
|
|
void R_GlobalPlaneToLocal( const float modelMatrix[16], const idPlane &in, idPlane &out ) {
|
|
out[0] = DotProduct( in, &modelMatrix[0] );
|
|
out[1] = DotProduct( in, &modelMatrix[4] );
|
|
out[2] = DotProduct( in, &modelMatrix[8] );
|
|
out[3] = in[3] + modelMatrix[12] * in[0] + modelMatrix[13] * in[1] + modelMatrix[14] * in[2];
|
|
}
|
|
|
|
void R_LocalPlaneToGlobal( const float modelMatrix[16], const idPlane &in, idPlane &out ) {
|
|
float offset;
|
|
|
|
R_LocalVectorToGlobal( modelMatrix, in.Normal(), out.Normal() );
|
|
|
|
offset = modelMatrix[12] * out[0] + modelMatrix[13] * out[1] + modelMatrix[14] * out[2];
|
|
out[3] = in[3] - offset;
|
|
}
|
|
|
|
// transform Z in eye coordinates to window coordinates
|
|
void R_TransformEyeZToWin( float src_z, const float *projectionMatrix, float &dst_z ) {
|
|
float clip_z, clip_w;
|
|
|
|
// projection
|
|
clip_z = src_z * projectionMatrix[ 2 + 2 * 4 ] + projectionMatrix[ 2 + 3 * 4 ];
|
|
clip_w = src_z * projectionMatrix[ 3 + 2 * 4 ] + projectionMatrix[ 3 + 3 * 4 ];
|
|
|
|
if ( clip_w <= 0.0f ) {
|
|
dst_z = 0.0f; // clamp to near plane
|
|
} else {
|
|
dst_z = clip_z / clip_w;
|
|
dst_z = dst_z * 0.5f + 0.5f; // convert to window coords
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_RadiusCullLocalBox
|
|
|
|
A fast, conservative center-to-corner culling test
|
|
Returns true if the box is outside the given global frustum, (positive sides are out)
|
|
=================
|
|
*/
|
|
bool R_RadiusCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) {
|
|
int i;
|
|
float d;
|
|
idVec3 worldOrigin;
|
|
float worldRadius;
|
|
const idPlane *frust;
|
|
|
|
if ( r_useCulling.GetInteger() == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
// transform the surface bounds into world space
|
|
idVec3 localOrigin = ( bounds[0] + bounds[1] ) * 0.5;
|
|
|
|
R_LocalPointToGlobal( modelMatrix, localOrigin, worldOrigin );
|
|
|
|
worldRadius = (bounds[0] - localOrigin).Length(); // FIXME: won't be correct for scaled objects
|
|
|
|
for ( i = 0 ; i < numPlanes ; i++ ) {
|
|
frust = planes + i;
|
|
d = frust->Distance( worldOrigin );
|
|
if ( d > worldRadius ) {
|
|
return true; // culled
|
|
}
|
|
}
|
|
|
|
return false; // no culled
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_CornerCullLocalBox
|
|
|
|
Tests all corners against the frustum.
|
|
Can still generate a few false positives when the box is outside a corner.
|
|
Returns true if the box is outside the given global frustum, (positive sides are out)
|
|
=================
|
|
*/
|
|
bool R_CornerCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) {
|
|
int i, j;
|
|
idVec3 transformed[8];
|
|
float dists[8];
|
|
idVec3 v;
|
|
const idPlane *frust;
|
|
|
|
// we can disable box culling for experimental timing purposes
|
|
if ( r_useCulling.GetInteger() < 2 ) {
|
|
return false;
|
|
}
|
|
|
|
// transform into world space
|
|
for ( i = 0 ; i < 8 ; i++ ) {
|
|
v[0] = bounds[i&1][0];
|
|
v[1] = bounds[(i>>1)&1][1];
|
|
v[2] = bounds[(i>>2)&1][2];
|
|
|
|
R_LocalPointToGlobal( modelMatrix, v, transformed[i] );
|
|
}
|
|
|
|
// check against frustum planes
|
|
for ( i = 0 ; i < numPlanes ; i++ ) {
|
|
frust = planes + i;
|
|
for ( j = 0 ; j < 8 ; j++ ) {
|
|
dists[j] = frust->Distance( transformed[j] );
|
|
if ( dists[j] < 0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j == 8 ) {
|
|
// all points were behind one of the planes
|
|
tr.pc.c_box_cull_out++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
tr.pc.c_box_cull_in++;
|
|
|
|
return false; // not culled
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_CullLocalBox
|
|
|
|
Performs quick test before expensive test
|
|
Returns true if the box is outside the given global frustum, (positive sides are out)
|
|
=================
|
|
*/
|
|
bool R_CullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) {
|
|
if ( R_RadiusCullLocalBox( bounds, modelMatrix, numPlanes, planes ) ) {
|
|
return true;
|
|
}
|
|
return R_CornerCullLocalBox( bounds, modelMatrix, numPlanes, planes );
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
R_TransformModelToClip
|
|
==========================
|
|
*/
|
|
void R_TransformModelToClip( const idVec3 &src, const float *modelMatrix, const float *projectionMatrix, idPlane &eye, idPlane &dst ) {
|
|
int i;
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
eye[i] =
|
|
src[0] * modelMatrix[ i + 0 * 4 ] +
|
|
src[1] * modelMatrix[ i + 1 * 4 ] +
|
|
src[2] * modelMatrix[ i + 2 * 4 ] +
|
|
1 * modelMatrix[ i + 3 * 4 ];
|
|
}
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
dst[i] =
|
|
eye[0] * projectionMatrix[ i + 0 * 4 ] +
|
|
eye[1] * projectionMatrix[ i + 1 * 4 ] +
|
|
eye[2] * projectionMatrix[ i + 2 * 4 ] +
|
|
eye[3] * projectionMatrix[ i + 3 * 4 ];
|
|
}
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
R_GlobalToNormalizedDeviceCoordinates
|
|
|
|
-1 to 1 range in x, y, and z
|
|
==========================
|
|
*/
|
|
void R_GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) {
|
|
int i;
|
|
idPlane view;
|
|
idPlane clip;
|
|
|
|
// _D3XP added work on primaryView when no viewDef
|
|
if ( !tr.viewDef ) {
|
|
|
|
for ( i = 0 ; i < 4 ; i ++ ) {
|
|
view[i] =
|
|
global[0] * tr.primaryView->worldSpace.modelViewMatrix[ i + 0 * 4 ] +
|
|
global[1] * tr.primaryView->worldSpace.modelViewMatrix[ i + 1 * 4 ] +
|
|
global[2] * tr.primaryView->worldSpace.modelViewMatrix[ i + 2 * 4 ] +
|
|
tr.primaryView->worldSpace.modelViewMatrix[ i + 3 * 4 ];
|
|
}
|
|
|
|
for ( i = 0 ; i < 4 ; i ++ ) {
|
|
clip[i] =
|
|
view[0] * tr.primaryView->projectionMatrix[ i + 0 * 4 ] +
|
|
view[1] * tr.primaryView->projectionMatrix[ i + 1 * 4 ] +
|
|
view[2] * tr.primaryView->projectionMatrix[ i + 2 * 4 ] +
|
|
view[3] * tr.primaryView->projectionMatrix[ i + 3 * 4 ];
|
|
}
|
|
|
|
} else {
|
|
|
|
for ( i = 0 ; i < 4 ; i ++ ) {
|
|
view[i] =
|
|
global[0] * tr.viewDef->worldSpace.modelViewMatrix[ i + 0 * 4 ] +
|
|
global[1] * tr.viewDef->worldSpace.modelViewMatrix[ i + 1 * 4 ] +
|
|
global[2] * tr.viewDef->worldSpace.modelViewMatrix[ i + 2 * 4 ] +
|
|
tr.viewDef->worldSpace.modelViewMatrix[ i + 3 * 4 ];
|
|
}
|
|
|
|
|
|
for ( i = 0 ; i < 4 ; i ++ ) {
|
|
clip[i] =
|
|
view[0] * tr.viewDef->projectionMatrix[ i + 0 * 4 ] +
|
|
view[1] * tr.viewDef->projectionMatrix[ i + 1 * 4 ] +
|
|
view[2] * tr.viewDef->projectionMatrix[ i + 2 * 4 ] +
|
|
view[3] * tr.viewDef->projectionMatrix[ i + 3 * 4 ];
|
|
}
|
|
|
|
}
|
|
|
|
ndc[0] = clip[0] / clip[3];
|
|
ndc[1] = clip[1] / clip[3];
|
|
ndc[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] );
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
R_TransformClipToDevice
|
|
|
|
Clip to normalized device coordinates
|
|
==========================
|
|
*/
|
|
void R_TransformClipToDevice( const idPlane &clip, const viewDef_t *view, idVec3 &normalized ) {
|
|
normalized[0] = clip[0] / clip[3];
|
|
normalized[1] = clip[1] / clip[3];
|
|
normalized[2] = clip[2] / clip[3];
|
|
}
|
|
|
|
|
|
/*
|
|
==========================
|
|
myGlMultMatrix
|
|
==========================
|
|
*/
|
|
void myGlMultMatrix( const float a[16], const float b[16], float out[16] ) {
|
|
#if 0
|
|
int i, j;
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
for ( j = 0 ; j < 4 ; j++ ) {
|
|
out[ i * 4 + j ] =
|
|
a [ i * 4 + 0 ] * b [ 0 * 4 + j ]
|
|
+ a [ i * 4 + 1 ] * b [ 1 * 4 + j ]
|
|
+ a [ i * 4 + 2 ] * b [ 2 * 4 + j ]
|
|
+ a [ i * 4 + 3 ] * b [ 3 * 4 + j ];
|
|
}
|
|
}
|
|
#else
|
|
out[0*4+0] = a[0*4+0]*b[0*4+0] + a[0*4+1]*b[1*4+0] + a[0*4+2]*b[2*4+0] + a[0*4+3]*b[3*4+0];
|
|
out[0*4+1] = a[0*4+0]*b[0*4+1] + a[0*4+1]*b[1*4+1] + a[0*4+2]*b[2*4+1] + a[0*4+3]*b[3*4+1];
|
|
out[0*4+2] = a[0*4+0]*b[0*4+2] + a[0*4+1]*b[1*4+2] + a[0*4+2]*b[2*4+2] + a[0*4+3]*b[3*4+2];
|
|
out[0*4+3] = a[0*4+0]*b[0*4+3] + a[0*4+1]*b[1*4+3] + a[0*4+2]*b[2*4+3] + a[0*4+3]*b[3*4+3];
|
|
out[1*4+0] = a[1*4+0]*b[0*4+0] + a[1*4+1]*b[1*4+0] + a[1*4+2]*b[2*4+0] + a[1*4+3]*b[3*4+0];
|
|
out[1*4+1] = a[1*4+0]*b[0*4+1] + a[1*4+1]*b[1*4+1] + a[1*4+2]*b[2*4+1] + a[1*4+3]*b[3*4+1];
|
|
out[1*4+2] = a[1*4+0]*b[0*4+2] + a[1*4+1]*b[1*4+2] + a[1*4+2]*b[2*4+2] + a[1*4+3]*b[3*4+2];
|
|
out[1*4+3] = a[1*4+0]*b[0*4+3] + a[1*4+1]*b[1*4+3] + a[1*4+2]*b[2*4+3] + a[1*4+3]*b[3*4+3];
|
|
out[2*4+0] = a[2*4+0]*b[0*4+0] + a[2*4+1]*b[1*4+0] + a[2*4+2]*b[2*4+0] + a[2*4+3]*b[3*4+0];
|
|
out[2*4+1] = a[2*4+0]*b[0*4+1] + a[2*4+1]*b[1*4+1] + a[2*4+2]*b[2*4+1] + a[2*4+3]*b[3*4+1];
|
|
out[2*4+2] = a[2*4+0]*b[0*4+2] + a[2*4+1]*b[1*4+2] + a[2*4+2]*b[2*4+2] + a[2*4+3]*b[3*4+2];
|
|
out[2*4+3] = a[2*4+0]*b[0*4+3] + a[2*4+1]*b[1*4+3] + a[2*4+2]*b[2*4+3] + a[2*4+3]*b[3*4+3];
|
|
out[3*4+0] = a[3*4+0]*b[0*4+0] + a[3*4+1]*b[1*4+0] + a[3*4+2]*b[2*4+0] + a[3*4+3]*b[3*4+0];
|
|
out[3*4+1] = a[3*4+0]*b[0*4+1] + a[3*4+1]*b[1*4+1] + a[3*4+2]*b[2*4+1] + a[3*4+3]*b[3*4+1];
|
|
out[3*4+2] = a[3*4+0]*b[0*4+2] + a[3*4+1]*b[1*4+2] + a[3*4+2]*b[2*4+2] + a[3*4+3]*b[3*4+2];
|
|
out[3*4+3] = a[3*4+0]*b[0*4+3] + a[3*4+1]*b[1*4+3] + a[3*4+2]*b[2*4+3] + a[3*4+3]*b[3*4+3];
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_TransposeGLMatrix
|
|
================
|
|
*/
|
|
void R_TransposeGLMatrix( const float in[16], float out[16] ) {
|
|
int i, j;
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
for ( j = 0 ; j < 4 ; j++ ) {
|
|
out[i*4+j] = in[j*4+i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_SetViewMatrix
|
|
|
|
Sets up the world to view matrix for a given viewParm
|
|
=================
|
|
*/
|
|
void R_SetViewMatrix( viewDef_t *viewDef ) {
|
|
idVec3 origin;
|
|
viewEntity_t *world;
|
|
float viewerMatrix[16];
|
|
static float s_flipMatrix[16] = {
|
|
// convert from our coordinate system (looking down X)
|
|
// to OpenGL's coordinate system (looking down -Z)
|
|
0, 0, -1, 0,
|
|
-1, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
0, 0, 0, 1
|
|
};
|
|
|
|
world = &viewDef->worldSpace;
|
|
|
|
memset( world, 0, sizeof(*world) );
|
|
|
|
// the model matrix is an identity
|
|
world->modelMatrix[0*4+0] = 1;
|
|
world->modelMatrix[1*4+1] = 1;
|
|
world->modelMatrix[2*4+2] = 1;
|
|
|
|
// transform by the camera placement
|
|
origin = viewDef->renderView.vieworg;
|
|
|
|
viewerMatrix[0] = viewDef->renderView.viewaxis[0][0];
|
|
viewerMatrix[4] = viewDef->renderView.viewaxis[0][1];
|
|
viewerMatrix[8] = viewDef->renderView.viewaxis[0][2];
|
|
viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
|
|
|
|
viewerMatrix[1] = viewDef->renderView.viewaxis[1][0];
|
|
viewerMatrix[5] = viewDef->renderView.viewaxis[1][1];
|
|
viewerMatrix[9] = viewDef->renderView.viewaxis[1][2];
|
|
viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
|
|
|
|
viewerMatrix[2] = viewDef->renderView.viewaxis[2][0];
|
|
viewerMatrix[6] = viewDef->renderView.viewaxis[2][1];
|
|
viewerMatrix[10] = viewDef->renderView.viewaxis[2][2];
|
|
viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10];
|
|
|
|
viewerMatrix[3] = 0;
|
|
viewerMatrix[7] = 0;
|
|
viewerMatrix[11] = 0;
|
|
viewerMatrix[15] = 1;
|
|
|
|
// convert from our coordinate system (looking down X)
|
|
// to OpenGL's coordinate system (looking down -Z)
|
|
myGlMultMatrix( viewerMatrix, s_flipMatrix, world->modelViewMatrix );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_SetupProjection
|
|
|
|
This uses the "infinite far z" trick
|
|
===============
|
|
*/
|
|
void R_SetupProjection( void ) {
|
|
float xmin, xmax, ymin, ymax;
|
|
float width, height;
|
|
float zNear;
|
|
float jitterx, jittery;
|
|
static idRandom random;
|
|
|
|
// random jittering is usefull when multiple
|
|
// frames are going to be blended together
|
|
// for motion blurred anti-aliasing
|
|
if ( r_jitter.GetBool() ) {
|
|
jitterx = random.RandomFloat();
|
|
jittery = random.RandomFloat();
|
|
} else {
|
|
jitterx = jittery = 0;
|
|
}
|
|
|
|
//
|
|
// set up projection matrix
|
|
//
|
|
zNear = r_znear.GetFloat();
|
|
if ( tr.viewDef->renderView.cramZNear ) {
|
|
zNear *= 0.25;
|
|
}
|
|
|
|
ymax = zNear * tan( tr.viewDef->renderView.fov_y * idMath::PI / 360.0f );
|
|
ymin = -ymax;
|
|
|
|
xmax = zNear * tan( tr.viewDef->renderView.fov_x * idMath::PI / 360.0f );
|
|
xmin = -xmax;
|
|
|
|
width = xmax - xmin;
|
|
height = ymax - ymin;
|
|
|
|
jitterx = jitterx * width / ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 + 1 );
|
|
xmin += jitterx;
|
|
xmax += jitterx;
|
|
jittery = jittery * height / ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 + 1 );
|
|
ymin += jittery;
|
|
ymax += jittery;
|
|
|
|
tr.viewDef->projectionMatrix[0] = 2 * zNear / width;
|
|
tr.viewDef->projectionMatrix[4] = 0;
|
|
tr.viewDef->projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0
|
|
tr.viewDef->projectionMatrix[12] = 0;
|
|
|
|
tr.viewDef->projectionMatrix[1] = 0;
|
|
tr.viewDef->projectionMatrix[5] = 2 * zNear / height;
|
|
tr.viewDef->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0
|
|
tr.viewDef->projectionMatrix[13] = 0;
|
|
|
|
// this is the far-plane-at-infinity formulation, and
|
|
// crunches the Z range slightly so w=0 vertexes do not
|
|
// rasterize right at the wraparound point
|
|
tr.viewDef->projectionMatrix[2] = 0;
|
|
tr.viewDef->projectionMatrix[6] = 0;
|
|
tr.viewDef->projectionMatrix[10] = -0.999f;
|
|
tr.viewDef->projectionMatrix[14] = -2.0f * zNear;
|
|
|
|
tr.viewDef->projectionMatrix[3] = 0;
|
|
tr.viewDef->projectionMatrix[7] = 0;
|
|
tr.viewDef->projectionMatrix[11] = -1;
|
|
tr.viewDef->projectionMatrix[15] = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_SetupViewFrustum
|
|
|
|
Setup that culling frustum planes for the current view
|
|
FIXME: derive from modelview matrix times projection matrix
|
|
=================
|
|
*/
|
|
static void R_SetupViewFrustum( void ) {
|
|
int i;
|
|
float xs, xc;
|
|
float ang;
|
|
|
|
ang = DEG2RAD( tr.viewDef->renderView.fov_x ) * 0.5f;
|
|
idMath::SinCos( ang, xs, xc );
|
|
|
|
tr.viewDef->frustum[0] = xs * tr.viewDef->renderView.viewaxis[0] + xc * tr.viewDef->renderView.viewaxis[1];
|
|
tr.viewDef->frustum[1] = xs * tr.viewDef->renderView.viewaxis[0] - xc * tr.viewDef->renderView.viewaxis[1];
|
|
|
|
ang = DEG2RAD( tr.viewDef->renderView.fov_y ) * 0.5f;
|
|
idMath::SinCos( ang, xs, xc );
|
|
|
|
tr.viewDef->frustum[2] = xs * tr.viewDef->renderView.viewaxis[0] + xc * tr.viewDef->renderView.viewaxis[2];
|
|
tr.viewDef->frustum[3] = xs * tr.viewDef->renderView.viewaxis[0] - xc * tr.viewDef->renderView.viewaxis[2];
|
|
|
|
// plane four is the front clipping plane
|
|
tr.viewDef->frustum[4] = /* vec3_origin - */ tr.viewDef->renderView.viewaxis[0];
|
|
|
|
for ( i = 0; i < 5; i++ ) {
|
|
// flip direction so positive side faces out (FIXME: globally unify this)
|
|
tr.viewDef->frustum[i] = -tr.viewDef->frustum[i].Normal();
|
|
tr.viewDef->frustum[i][3] = -( tr.viewDef->renderView.vieworg * tr.viewDef->frustum[i].Normal() );
|
|
}
|
|
|
|
// eventually, plane five will be the rear clipping plane for fog
|
|
|
|
float dNear, dFar, dLeft, dUp;
|
|
|
|
dNear = r_znear.GetFloat();
|
|
if ( tr.viewDef->renderView.cramZNear ) {
|
|
dNear *= 0.25f;
|
|
}
|
|
|
|
dFar = MAX_WORLD_SIZE;
|
|
dLeft = dFar * tan( DEG2RAD( tr.viewDef->renderView.fov_x * 0.5f ) );
|
|
dUp = dFar * tan( DEG2RAD( tr.viewDef->renderView.fov_y * 0.5f ) );
|
|
tr.viewDef->viewFrustum.SetOrigin( tr.viewDef->renderView.vieworg );
|
|
tr.viewDef->viewFrustum.SetAxis( tr.viewDef->renderView.viewaxis );
|
|
tr.viewDef->viewFrustum.SetSize( dNear, dFar, dLeft, dUp );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ConstrainViewFrustum
|
|
===================
|
|
*/
|
|
static void R_ConstrainViewFrustum( void ) {
|
|
idBounds bounds;
|
|
|
|
// constrain the view frustum to the total bounds of all visible lights and visible entities
|
|
bounds.Clear();
|
|
for ( viewLight_t *vLight = tr.viewDef->viewLights; vLight; vLight = vLight->next ) {
|
|
bounds.AddBounds( vLight->lightDef->frustumTris->bounds );
|
|
}
|
|
for ( viewEntity_t *vEntity = tr.viewDef->viewEntitys; vEntity; vEntity = vEntity->next ) {
|
|
bounds.AddBounds( vEntity->entityDef->referenceBounds );
|
|
}
|
|
tr.viewDef->viewFrustum.ConstrainToBounds( bounds );
|
|
|
|
if ( r_useFrustumFarDistance.GetFloat() > 0.0f ) {
|
|
tr.viewDef->viewFrustum.MoveFarDistance( r_useFrustumFarDistance.GetFloat() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==========================================================================================
|
|
|
|
DRAWSURF SORTING
|
|
|
|
==========================================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
=======================
|
|
R_QsortSurfaces
|
|
|
|
=======================
|
|
*/
|
|
static int R_QsortSurfaces( const void *a, const void *b ) {
|
|
const drawSurf_t *ea, *eb;
|
|
|
|
ea = *(drawSurf_t **)a;
|
|
eb = *(drawSurf_t **)b;
|
|
|
|
if ( ea->sort < eb->sort ) {
|
|
return -1;
|
|
}
|
|
if ( ea->sort > eb->sort ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_SortDrawSurfs
|
|
=================
|
|
*/
|
|
static void R_SortDrawSurfs( void ) {
|
|
// sort the drawsurfs by sort type, then orientation, then shader
|
|
qsort( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs, sizeof( tr.viewDef->drawSurfs[0] ),
|
|
R_QsortSurfaces );
|
|
}
|
|
|
|
|
|
|
|
//========================================================================
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
R_RenderView
|
|
|
|
A view may be either the actual camera view,
|
|
a mirror / remote location, or a 3D view on a gui surface.
|
|
|
|
Parms will typically be allocated with R_FrameAlloc
|
|
================
|
|
*/
|
|
void R_RenderView( viewDef_t *parms ) {
|
|
viewDef_t *oldView;
|
|
|
|
if ( parms->renderView.width <= 0 || parms->renderView.height <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
tr.viewCount++;
|
|
|
|
// save view in case we are a subview
|
|
oldView = tr.viewDef;
|
|
|
|
tr.viewDef = parms;
|
|
|
|
tr.sortOffset = 0;
|
|
|
|
// set the matrix for world space to eye space
|
|
R_SetViewMatrix( tr.viewDef );
|
|
|
|
// the four sides of the view frustum are needed
|
|
// for culling and portal visibility
|
|
R_SetupViewFrustum();
|
|
|
|
// we need to set the projection matrix before doing
|
|
// portal-to-screen scissor box calculations
|
|
R_SetupProjection();
|
|
|
|
// identify all the visible portalAreas, and the entityDefs and
|
|
// lightDefs that are in them and pass culling.
|
|
static_cast<idRenderWorldLocal *>(parms->renderWorld)->FindViewLightsAndEntities();
|
|
|
|
// constrain the view frustum to the view lights and entities
|
|
R_ConstrainViewFrustum();
|
|
|
|
// make sure that interactions exist for all light / entity combinations
|
|
// that are visible
|
|
// add any pre-generated light shadows, and calculate the light shader values
|
|
R_AddLightSurfaces();
|
|
|
|
// adds ambient surfaces and create any necessary interaction surfaces to add to the light
|
|
// lists
|
|
R_AddModelSurfaces();
|
|
|
|
// any viewLight that didn't have visible surfaces can have it's shadows removed
|
|
R_RemoveUnecessaryViewLights();
|
|
|
|
// sort all the ambient surfaces for translucency ordering
|
|
R_SortDrawSurfs();
|
|
|
|
// generate any subviews (mirrors, cameras, etc) before adding this view
|
|
if ( R_GenerateSubViews() ) {
|
|
// if we are debugging subviews, allow the skipping of the
|
|
// main view draw
|
|
if ( r_subviewOnly.GetBool() ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// write everything needed to the demo file
|
|
if ( session->writeDemo ) {
|
|
static_cast<idRenderWorldLocal *>(parms->renderWorld)->WriteVisibleDefs( tr.viewDef );
|
|
}
|
|
|
|
// add the rendering commands for this viewDef
|
|
R_AddDrawViewCmd( parms );
|
|
|
|
// restore view in case we are a subview
|
|
tr.viewDef = oldView;
|
|
}
|