mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 21:21:27 +00:00
2398 lines
58 KiB
C++
2398 lines
58 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition 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 BFG Edition 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
#include "tr_local.h"
|
|
|
|
/*
|
|
===================
|
|
R_ListRenderLightDefs_f
|
|
===================
|
|
*/
|
|
void R_ListRenderLightDefs_f( const idCmdArgs& args )
|
|
{
|
|
int i;
|
|
idRenderLightLocal* ldef;
|
|
|
|
if( !tr.primaryWorld )
|
|
{
|
|
return;
|
|
}
|
|
int active = 0;
|
|
int totalRef = 0;
|
|
int totalIntr = 0;
|
|
|
|
for( i = 0; i < tr.primaryWorld->lightDefs.Num(); i++ )
|
|
{
|
|
ldef = tr.primaryWorld->lightDefs[i];
|
|
if( !ldef )
|
|
{
|
|
common->Printf( "%4i: FREED\n", i );
|
|
continue;
|
|
}
|
|
|
|
// count up the interactions
|
|
int iCount = 0;
|
|
for( idInteraction* inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext )
|
|
{
|
|
iCount++;
|
|
}
|
|
totalIntr += iCount;
|
|
|
|
// count up the references
|
|
int rCount = 0;
|
|
for( areaReference_t* ref = ldef->references; ref; ref = ref->ownerNext )
|
|
{
|
|
rCount++;
|
|
}
|
|
totalRef += rCount;
|
|
|
|
common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, ldef->lightShader->GetName() );
|
|
active++;
|
|
}
|
|
|
|
common->Printf( "%i lightDefs, %i interactions, %i areaRefs\n", active, totalIntr, totalRef );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
R_ListRenderEntityDefs_f
|
|
===================
|
|
*/
|
|
void R_ListRenderEntityDefs_f( const idCmdArgs& args )
|
|
{
|
|
int i;
|
|
idRenderEntityLocal* mdef;
|
|
|
|
if( !tr.primaryWorld )
|
|
{
|
|
return;
|
|
}
|
|
int active = 0;
|
|
int totalRef = 0;
|
|
int totalIntr = 0;
|
|
|
|
for( i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ )
|
|
{
|
|
mdef = tr.primaryWorld->entityDefs[i];
|
|
if( !mdef )
|
|
{
|
|
common->Printf( "%4i: FREED\n", i );
|
|
continue;
|
|
}
|
|
|
|
// count up the interactions
|
|
int iCount = 0;
|
|
for( idInteraction* inter = mdef->firstInteraction; inter != NULL; inter = inter->entityNext )
|
|
{
|
|
iCount++;
|
|
}
|
|
totalIntr += iCount;
|
|
|
|
// count up the references
|
|
int rCount = 0;
|
|
for( areaReference_t* ref = mdef->entityRefs; ref; ref = ref->ownerNext )
|
|
{
|
|
rCount++;
|
|
}
|
|
totalRef += rCount;
|
|
|
|
common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, mdef->parms.hModel->Name() );
|
|
active++;
|
|
}
|
|
|
|
common->Printf( "total active: %i\n", active );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::idRenderWorldLocal
|
|
===================
|
|
*/
|
|
idRenderWorldLocal::idRenderWorldLocal()
|
|
{
|
|
mapName.Clear();
|
|
mapTimeStamp = FILE_NOT_FOUND_TIMESTAMP;
|
|
|
|
generateAllInteractionsCalled = false;
|
|
|
|
areaNodes = NULL;
|
|
numAreaNodes = 0;
|
|
|
|
portalAreas = NULL;
|
|
numPortalAreas = 0;
|
|
|
|
doublePortals = NULL;
|
|
numInterAreaPortals = 0;
|
|
|
|
interactionTable = 0;
|
|
interactionTableWidth = 0;
|
|
interactionTableHeight = 0;
|
|
|
|
for( int i = 0; i < decals.Num(); i++ )
|
|
{
|
|
decals[i].entityHandle = -1;
|
|
decals[i].lastStartTime = 0;
|
|
decals[i].decals = new( TAG_MODEL ) idRenderModelDecal();
|
|
}
|
|
|
|
for( int i = 0; i < overlays.Num(); i++ )
|
|
{
|
|
overlays[i].entityHandle = -1;
|
|
overlays[i].lastStartTime = 0;
|
|
overlays[i].overlays = new( TAG_MODEL ) idRenderModelOverlay();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::~idRenderWorldLocal
|
|
===================
|
|
*/
|
|
idRenderWorldLocal::~idRenderWorldLocal()
|
|
{
|
|
// free all the entityDefs, lightDefs, portals, etc
|
|
FreeWorld();
|
|
|
|
for( int i = 0; i < decals.Num(); i++ )
|
|
{
|
|
delete decals[i].decals;
|
|
}
|
|
|
|
for( int i = 0; i < overlays.Num(); i++ )
|
|
{
|
|
delete overlays[i].overlays;
|
|
}
|
|
|
|
// free up the debug lines, polys, and text
|
|
RB_ClearDebugPolygons( 0 );
|
|
RB_ClearDebugLines( 0 );
|
|
RB_ClearDebugText( 0 );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
ResizeInteractionTable
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::ResizeInteractionTable()
|
|
{
|
|
// we overflowed the interaction table, so make it larger
|
|
common->Printf( "idRenderWorldLocal::ResizeInteractionTable: overflowed interactionTable, resizing\n" );
|
|
|
|
const int oldInteractionTableWidth = interactionTableWidth;
|
|
const int oldIinteractionTableHeight = interactionTableHeight;
|
|
idInteraction** oldInteractionTable = interactionTable;
|
|
|
|
// build the interaction table
|
|
// this will be dynamically resized if the entity / light counts grow too much
|
|
interactionTableWidth = entityDefs.Num() + 100;
|
|
interactionTableHeight = lightDefs.Num() + 100;
|
|
const int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable );
|
|
interactionTable = ( idInteraction** )R_ClearedStaticAlloc( size );
|
|
for( int l = 0; l < oldIinteractionTableHeight; l++ )
|
|
{
|
|
for( int e = 0; e < oldInteractionTableWidth; e++ )
|
|
{
|
|
interactionTable[ l * interactionTableWidth + e ] = oldInteractionTable[ l * oldInteractionTableWidth + e ];
|
|
}
|
|
}
|
|
|
|
R_StaticFree( oldInteractionTable );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
AddEntityDef
|
|
===================
|
|
*/
|
|
qhandle_t idRenderWorldLocal::AddEntityDef( const renderEntity_t* re )
|
|
{
|
|
// try and reuse a free spot
|
|
int entityHandle = entityDefs.FindNull();
|
|
if( entityHandle == -1 )
|
|
{
|
|
entityHandle = entityDefs.Append( NULL );
|
|
|
|
if( interactionTable && entityDefs.Num() > interactionTableWidth )
|
|
{
|
|
ResizeInteractionTable();
|
|
}
|
|
}
|
|
|
|
UpdateEntityDef( entityHandle, re );
|
|
|
|
return entityHandle;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
UpdateEntityDef
|
|
|
|
Does not write to the demo file, which will only be updated for
|
|
visible entities
|
|
==============
|
|
*/
|
|
int c_callbackUpdate;
|
|
|
|
void idRenderWorldLocal::UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t* re )
|
|
{
|
|
if( r_skipUpdates.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr.pc.c_entityUpdates++;
|
|
|
|
if( !re->hModel && !re->callback )
|
|
{
|
|
common->Error( "idRenderWorld::UpdateEntityDef: NULL hModel" );
|
|
}
|
|
|
|
// create new slots if needed
|
|
if( entityHandle < 0 || entityHandle > LUDICROUS_INDEX )
|
|
{
|
|
common->Error( "idRenderWorld::UpdateEntityDef: index = %i", entityHandle );
|
|
}
|
|
while( entityHandle >= entityDefs.Num() )
|
|
{
|
|
entityDefs.Append( NULL );
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[entityHandle];
|
|
if( def != NULL )
|
|
{
|
|
|
|
if( !re->forceUpdate )
|
|
{
|
|
|
|
// check for exact match (OPTIMIZE: check through pointers more)
|
|
if( !re->joints && !re->callbackData && !def->dynamicModel && !memcmp( re, &def->parms, sizeof( *re ) ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if the only thing that changed was shaderparms, we can just leave things as they are
|
|
// after updating parms
|
|
|
|
// if we have a callback function and the bounds, origin, axis and model match,
|
|
// then we can leave the references as they are
|
|
if( re->callback )
|
|
{
|
|
|
|
bool axisMatch = ( re->axis == def->parms.axis );
|
|
bool originMatch = ( re->origin == def->parms.origin );
|
|
bool boundsMatch = ( re->bounds == def->localReferenceBounds );
|
|
bool modelMatch = ( re->hModel == def->parms.hModel );
|
|
|
|
if( boundsMatch && originMatch && axisMatch && modelMatch )
|
|
{
|
|
// only clear the dynamic model and interaction surfaces if they exist
|
|
c_callbackUpdate++;
|
|
R_ClearEntityDefDynamicModel( def );
|
|
def->parms = *re;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// save any decals if the model is the same, allowing marks to move with entities
|
|
if( def->parms.hModel == re->hModel )
|
|
{
|
|
R_FreeEntityDefDerivedData( def, true, true );
|
|
}
|
|
else
|
|
{
|
|
R_FreeEntityDefDerivedData( def, false, false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// creating a new one
|
|
def = new( TAG_RENDER_ENTITY ) idRenderEntityLocal;
|
|
entityDefs[entityHandle] = def;
|
|
|
|
def->world = this;
|
|
def->index = entityHandle;
|
|
}
|
|
|
|
def->parms = *re;
|
|
|
|
def->lastModifiedFrameNum = tr.frameCount;
|
|
if( common->WriteDemo() && def->archived )
|
|
{
|
|
WriteFreeEntity( entityHandle );
|
|
def->archived = false;
|
|
}
|
|
|
|
// optionally immediately issue any callbacks
|
|
if( !r_useEntityCallbacks.GetBool() && def->parms.callback != NULL )
|
|
{
|
|
R_IssueEntityDefCallback( def );
|
|
}
|
|
|
|
// trigger entities don't need to get linked in and processed,
|
|
// they only exist for editor use
|
|
if( def->parms.hModel != NULL && !def->parms.hModel->ModelHasDrawingSurfaces() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// based on the model bounds, add references in each area
|
|
// that may contain the updated surface
|
|
R_CreateEntityRefs( def );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
FreeEntityDef
|
|
|
|
Frees all references and lit surfaces from the model, and
|
|
NULL's out it's entry in the world list
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::FreeEntityDef( qhandle_t entityHandle )
|
|
{
|
|
idRenderEntityLocal* def;
|
|
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
common->Printf( "idRenderWorld::FreeEntityDef: handle %i > %i\n", entityHandle, entityDefs.Num() );
|
|
return;
|
|
}
|
|
|
|
def = entityDefs[entityHandle];
|
|
if( !def )
|
|
{
|
|
common->Printf( "idRenderWorld::FreeEntityDef: handle %i is NULL\n", entityHandle );
|
|
return;
|
|
}
|
|
|
|
R_FreeEntityDefDerivedData( def, false, false );
|
|
|
|
if( common->WriteDemo() && def->archived )
|
|
{
|
|
WriteFreeEntity( entityHandle );
|
|
}
|
|
|
|
// if we are playing a demo, these will have been freed
|
|
// in R_FreeEntityDefDerivedData(), otherwise the gui
|
|
// object still exists in the game
|
|
|
|
def->parms.gui[ 0 ] = NULL;
|
|
def->parms.gui[ 1 ] = NULL;
|
|
def->parms.gui[ 2 ] = NULL;
|
|
|
|
delete def;
|
|
entityDefs[ entityHandle ] = NULL;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
GetRenderEntity
|
|
==================
|
|
*/
|
|
const renderEntity_t* idRenderWorldLocal::GetRenderEntity( qhandle_t entityHandle ) const
|
|
{
|
|
idRenderEntityLocal* def;
|
|
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
common->Printf( "idRenderWorld::GetRenderEntity: invalid handle %i [0, %i]\n", entityHandle, entityDefs.Num() );
|
|
return NULL;
|
|
}
|
|
|
|
def = entityDefs[entityHandle];
|
|
if( !def )
|
|
{
|
|
common->Printf( "idRenderWorld::GetRenderEntity: handle %i is NULL\n", entityHandle );
|
|
return NULL;
|
|
}
|
|
|
|
return &def->parms;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
AddLightDef
|
|
==================
|
|
*/
|
|
qhandle_t idRenderWorldLocal::AddLightDef( const renderLight_t* rlight )
|
|
{
|
|
// try and reuse a free spot
|
|
int lightHandle = lightDefs.FindNull();
|
|
|
|
if( lightHandle == -1 )
|
|
{
|
|
lightHandle = lightDefs.Append( NULL );
|
|
if( interactionTable && lightDefs.Num() > interactionTableHeight )
|
|
{
|
|
ResizeInteractionTable();
|
|
}
|
|
}
|
|
UpdateLightDef( lightHandle, rlight );
|
|
|
|
return lightHandle;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
UpdateLightDef
|
|
|
|
The generation of all the derived interaction data will
|
|
usually be deferred until it is visible in a scene
|
|
|
|
Does not write to the demo file, which will only be done for visible lights
|
|
=================
|
|
*/
|
|
void idRenderWorldLocal::UpdateLightDef( qhandle_t lightHandle, const renderLight_t* rlight )
|
|
{
|
|
if( r_skipUpdates.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr.pc.c_lightUpdates++;
|
|
|
|
// create new slots if needed
|
|
if( lightHandle < 0 || lightHandle > LUDICROUS_INDEX )
|
|
{
|
|
common->Error( "idRenderWorld::UpdateLightDef: index = %i", lightHandle );
|
|
}
|
|
while( lightHandle >= lightDefs.Num() )
|
|
{
|
|
lightDefs.Append( NULL );
|
|
}
|
|
|
|
bool justUpdate = false;
|
|
idRenderLightLocal* light = lightDefs[lightHandle];
|
|
if( light )
|
|
{
|
|
// if the shape of the light stays the same, we don't need to dump
|
|
// any of our derived data, because shader parms are calculated every frame
|
|
if( rlight->axis == light->parms.axis && rlight->end == light->parms.end &&
|
|
rlight->lightCenter == light->parms.lightCenter && rlight->lightRadius == light->parms.lightRadius &&
|
|
rlight->noShadows == light->parms.noShadows && rlight->origin == light->parms.origin &&
|
|
rlight->parallel == light->parms.parallel && rlight->pointLight == light->parms.pointLight &&
|
|
rlight->right == light->parms.right && rlight->start == light->parms.start &&
|
|
rlight->target == light->parms.target && rlight->up == light->parms.up &&
|
|
rlight->shader == light->lightShader && rlight->prelightModel == light->parms.prelightModel )
|
|
{
|
|
justUpdate = true;
|
|
}
|
|
else
|
|
{
|
|
// if we are updating shadows, the prelight model is no longer valid
|
|
light->lightHasMoved = true;
|
|
R_FreeLightDefDerivedData( light );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// create a new one
|
|
light = new( TAG_RENDER_LIGHT ) idRenderLightLocal;
|
|
lightDefs[lightHandle] = light;
|
|
|
|
light->world = this;
|
|
light->index = lightHandle;
|
|
}
|
|
|
|
light->parms = *rlight;
|
|
light->lastModifiedFrameNum = tr.frameCount;
|
|
if( common->WriteDemo() && light->archived )
|
|
{
|
|
WriteFreeLight( lightHandle );
|
|
light->archived = false;
|
|
}
|
|
|
|
// new for BFG edition: force noShadows on spectrum lights so teleport spawns
|
|
// don't cause such a slowdown. Hell writing shouldn't be shadowed anyway...
|
|
if( light->parms.shader && light->parms.shader->Spectrum() )
|
|
{
|
|
light->parms.noShadows = true;
|
|
}
|
|
|
|
if( light->lightHasMoved )
|
|
{
|
|
light->parms.prelightModel = NULL;
|
|
}
|
|
|
|
if( !justUpdate )
|
|
{
|
|
R_CreateLightRefs( light );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
FreeLightDef
|
|
|
|
Frees all references and lit surfaces from the light, and
|
|
NULL's out it's entry in the world list
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::FreeLightDef( qhandle_t lightHandle )
|
|
{
|
|
idRenderLightLocal* light;
|
|
|
|
if( lightHandle < 0 || lightHandle >= lightDefs.Num() )
|
|
{
|
|
common->Printf( "idRenderWorld::FreeLightDef: invalid handle %i [0, %i]\n", lightHandle, lightDefs.Num() );
|
|
return;
|
|
}
|
|
|
|
light = lightDefs[lightHandle];
|
|
if( !light )
|
|
{
|
|
common->Printf( "idRenderWorld::FreeLightDef: handle %i is NULL\n", lightHandle );
|
|
return;
|
|
}
|
|
|
|
R_FreeLightDefDerivedData( light );
|
|
|
|
if( common->WriteDemo() && light->archived )
|
|
{
|
|
WriteFreeLight( lightHandle );
|
|
}
|
|
|
|
delete light;
|
|
lightDefs[lightHandle] = NULL;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
GetRenderLight
|
|
==================
|
|
*/
|
|
const renderLight_t* idRenderWorldLocal::GetRenderLight( qhandle_t lightHandle ) const
|
|
{
|
|
idRenderLightLocal* def;
|
|
|
|
if( lightHandle < 0 || lightHandle >= lightDefs.Num() )
|
|
{
|
|
common->Printf( "idRenderWorld::GetRenderLight: handle %i > %i\n", lightHandle, lightDefs.Num() );
|
|
return NULL;
|
|
}
|
|
|
|
def = lightDefs[lightHandle];
|
|
if( !def )
|
|
{
|
|
common->Printf( "idRenderWorld::GetRenderLight: handle %i is NULL\n", lightHandle );
|
|
return NULL;
|
|
}
|
|
|
|
return &def->parms;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ProjectDecalOntoWorld
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding& winding, const idVec3& projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial* material, const int startTime )
|
|
{
|
|
decalProjectionParms_t globalParms;
|
|
|
|
if( !idRenderModelDecal::CreateProjectionParms( globalParms, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// get the world areas touched by the projection volume
|
|
int areas[10];
|
|
int numAreas = BoundsInAreas( globalParms.projectionBounds, areas, 10 );
|
|
|
|
// check all areas for models
|
|
for( int i = 0; i < numAreas; i++ )
|
|
{
|
|
|
|
const portalArea_t* area = &portalAreas[ areas[i] ];
|
|
|
|
// check all models in this area
|
|
for( const areaReference_t* ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext )
|
|
{
|
|
idRenderEntityLocal* def = ref->entity;
|
|
|
|
if( def->parms.noOverlays )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( def->parms.customShader != NULL && !def->parms.customShader->AllowOverlays() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// completely ignore any dynamic or callback models
|
|
const idRenderModel* model = def->parms.hModel;
|
|
if( def->parms.callback != NULL || model == NULL || model->IsDynamicModel() != DM_STATIC )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idBounds bounds;
|
|
bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );
|
|
|
|
// if the model bounds do not overlap with the projection bounds
|
|
decalProjectionParms_t localParms;
|
|
if( !globalParms.projectionBounds.IntersectsBounds( bounds ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// transform the bounding planes, fade planes and texture axis into local space
|
|
idRenderModelDecal::GlobalProjectionParmsToLocal( localParms, globalParms, def->parms.origin, def->parms.axis );
|
|
localParms.force = ( def->parms.customShader != NULL );
|
|
|
|
if( def->decals == NULL )
|
|
{
|
|
def->decals = AllocDecal( def->index, startTime );
|
|
}
|
|
def->decals->AddDeferredDecal( localParms );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::ProjectDecal
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWinding& winding, const idVec3& projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial* material, const int startTime )
|
|
{
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
|
|
return;
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[ entityHandle ];
|
|
if( def == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const idRenderModel* model = def->parms.hModel;
|
|
|
|
if( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback != NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
decalProjectionParms_t globalParms;
|
|
if( !idRenderModelDecal::CreateProjectionParms( globalParms, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
idBounds bounds;
|
|
bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );
|
|
|
|
// if the model bounds do not overlap with the projection bounds
|
|
if( !globalParms.projectionBounds.IntersectsBounds( bounds ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// transform the bounding planes, fade planes and texture axis into local space
|
|
decalProjectionParms_t localParms;
|
|
idRenderModelDecal::GlobalProjectionParmsToLocal( localParms, globalParms, def->parms.origin, def->parms.axis );
|
|
localParms.force = ( def->parms.customShader != NULL );
|
|
|
|
if( def->decals == NULL )
|
|
{
|
|
def->decals = AllocDecal( def->index, startTime );
|
|
}
|
|
def->decals->AddDeferredDecal( localParms );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::ProjectOverlay
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial* material, const int startTime )
|
|
{
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
|
|
return;
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[ entityHandle ];
|
|
if( def == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const idRenderModel* model = def->parms.hModel;
|
|
if( model->IsDynamicModel() != DM_CACHED ) // FIXME: probably should be MD5 only
|
|
{
|
|
return;
|
|
}
|
|
|
|
overlayProjectionParms_t localParms;
|
|
localParms.localTextureAxis[0] = localTextureAxis[0];
|
|
localParms.localTextureAxis[1] = localTextureAxis[1];
|
|
localParms.material = material;
|
|
localParms.startTime = startTime;
|
|
|
|
if( def->overlays == NULL )
|
|
{
|
|
def->overlays = AllocOverlay( def->index, startTime );
|
|
}
|
|
def->overlays->AddDeferredOverlay( localParms );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::AllocDecal
|
|
====================
|
|
*/
|
|
idRenderModelDecal* idRenderWorldLocal::AllocDecal( qhandle_t newEntityHandle, int startTime )
|
|
{
|
|
int oldest = 0;
|
|
int oldestTime = MAX_TYPE( oldestTime );
|
|
for( int i = 0; i < decals.Num(); i++ )
|
|
{
|
|
if( decals[i].lastStartTime < oldestTime )
|
|
{
|
|
oldestTime = decals[i].lastStartTime;
|
|
oldest = i;
|
|
}
|
|
}
|
|
|
|
// remove any reference another model may still have to this decal
|
|
if( decals[oldest].entityHandle >= 0 && decals[oldest].entityHandle < entityDefs.Num() )
|
|
{
|
|
idRenderEntityLocal* def = entityDefs[decals[oldest].entityHandle];
|
|
if( def != NULL && def->decals == decals[oldest].decals )
|
|
{
|
|
def->decals = NULL;
|
|
}
|
|
}
|
|
|
|
decals[oldest].entityHandle = newEntityHandle;
|
|
decals[oldest].lastStartTime = startTime;
|
|
decals[oldest].decals->ReUse();
|
|
|
|
return decals[oldest].decals;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::AllocOverlay
|
|
====================
|
|
*/
|
|
idRenderModelOverlay* idRenderWorldLocal::AllocOverlay( qhandle_t newEntityHandle, int startTime )
|
|
{
|
|
int oldest = 0;
|
|
int oldestTime = MAX_TYPE( oldestTime );
|
|
for( int i = 0; i < overlays.Num(); i++ )
|
|
{
|
|
if( overlays[i].lastStartTime < oldestTime )
|
|
{
|
|
oldestTime = overlays[i].lastStartTime;
|
|
oldest = i;
|
|
}
|
|
}
|
|
|
|
// remove any reference another model may still have to this overlay
|
|
if( overlays[oldest].entityHandle >= 0 && overlays[oldest].entityHandle < entityDefs.Num() )
|
|
{
|
|
idRenderEntityLocal* def = entityDefs[overlays[oldest].entityHandle];
|
|
if( def != NULL && def->overlays == overlays[oldest].overlays )
|
|
{
|
|
def->overlays = NULL;
|
|
}
|
|
}
|
|
|
|
overlays[oldest].entityHandle = newEntityHandle;
|
|
overlays[oldest].lastStartTime = startTime;
|
|
overlays[oldest].overlays->ReUse();
|
|
|
|
return overlays[oldest].overlays;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::RemoveDecals
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::RemoveDecals( qhandle_t entityHandle )
|
|
{
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle );
|
|
return;
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[ entityHandle ];
|
|
if( !def )
|
|
{
|
|
return;
|
|
}
|
|
|
|
R_FreeEntityDefDecals( def );
|
|
R_FreeEntityDefOverlay( def );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::SetRenderView
|
|
|
|
Sets the current view so any calls to the render world will use the correct parms.
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::SetRenderView( const renderView_t* renderView )
|
|
{
|
|
tr.primaryRenderView = *renderView;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::RenderScene
|
|
|
|
Draw a 3D view into a part of the window, then return
|
|
to 2D drawing.
|
|
|
|
Rendering a scene may require multiple views to be rendered
|
|
to handle mirrors,
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::RenderScene( const renderView_t* renderView )
|
|
{
|
|
if( !R_IsInitialized() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
renderView_t copy = *renderView;
|
|
|
|
// skip front end rendering work, which will result
|
|
// in only gui drawing
|
|
if( r_skipFrontEnd.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPED_PROFILE_EVENT( "RenderWorld::RenderScene" );
|
|
|
|
if( renderView->fov_x <= 0 || renderView->fov_y <= 0 )
|
|
{
|
|
common->Error( "idRenderWorld::RenderScene: bad FOVs: %f, %f", renderView->fov_x, renderView->fov_y );
|
|
}
|
|
|
|
// close any gui drawing
|
|
tr.guiModel->EmitFullScreen();
|
|
tr.guiModel->Clear();
|
|
|
|
int startTime = Sys_Microseconds();
|
|
|
|
// setup view parms for the initial view
|
|
viewDef_t* parms = ( viewDef_t* )R_ClearedFrameAlloc( sizeof( *parms ), FRAME_ALLOC_VIEW_DEF );
|
|
parms->renderView = *renderView;
|
|
|
|
if( tr.takingScreenshot )
|
|
{
|
|
parms->renderView.forceUpdate = true;
|
|
}
|
|
|
|
int windowWidth = tr.GetWidth();
|
|
int windowHeight = tr.GetHeight();
|
|
tr.PerformResolutionScaling( windowWidth, windowHeight );
|
|
|
|
// screenFraction is just for quickly testing fill rate limitations
|
|
if( r_screenFraction.GetInteger() != 100 )
|
|
{
|
|
windowWidth = ( windowWidth * r_screenFraction.GetInteger() ) / 100;
|
|
windowHeight = ( windowHeight * r_screenFraction.GetInteger() ) / 100;
|
|
}
|
|
tr.CropRenderSize( windowWidth, windowHeight );
|
|
tr.GetCroppedViewport( &parms->viewport );
|
|
|
|
// the scissor bounds may be shrunk in subviews even if
|
|
// the viewport stays the same
|
|
// this scissor range is local inside the viewport
|
|
parms->scissor.x1 = 0;
|
|
parms->scissor.y1 = 0;
|
|
parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1;
|
|
parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1;
|
|
|
|
parms->isSubview = false;
|
|
parms->initialViewAreaOrigin = renderView->vieworg;
|
|
parms->renderWorld = this;
|
|
|
|
// see if the view needs to reverse the culling sense in mirrors
|
|
// or environment cube sides
|
|
idVec3 cross;
|
|
cross = parms->renderView.viewaxis[1].Cross( parms->renderView.viewaxis[2] );
|
|
if( cross * parms->renderView.viewaxis[0] > 0 )
|
|
{
|
|
parms->isMirror = false;
|
|
}
|
|
else
|
|
{
|
|
parms->isMirror = true;
|
|
}
|
|
|
|
// save this world for use by some console commands
|
|
tr.primaryWorld = this;
|
|
tr.primaryRenderView = *renderView;
|
|
tr.primaryView = parms;
|
|
|
|
// rendering this view may cause other views to be rendered
|
|
// for mirrors / portals / shadows / environment maps
|
|
// this will also cause any necessary entities and lights to be
|
|
// updated to the demo file
|
|
R_RenderView( parms );
|
|
|
|
// render any post processing after the view and all its subviews has been draw
|
|
R_RenderPostProcess( parms );
|
|
|
|
// now write delete commands for any modified-but-not-visible entities, and
|
|
// add the renderView command to the demo
|
|
if( common->WriteDemo() )
|
|
{
|
|
WriteRenderView( renderView );
|
|
}
|
|
|
|
#if 0
|
|
for( int i = 0; i < entityDefs.Num(); i++ )
|
|
{
|
|
idRenderEntityLocal* def = entityDefs[i];
|
|
if( !def )
|
|
{
|
|
continue;
|
|
}
|
|
if( def->parms.callback )
|
|
{
|
|
continue;
|
|
}
|
|
if( def->parms.hModel->IsDynamicModel() == DM_CONTINUOUS )
|
|
{
|
|
}
|
|
}
|
|
#endif
|
|
|
|
tr.UnCrop();
|
|
|
|
int endTime = Sys_Microseconds();
|
|
|
|
tr.pc.frontEndMicroSec += endTime - startTime;
|
|
|
|
// prepare for any 2D drawing after this
|
|
tr.guiModel->Clear();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::NumAreas
|
|
===================
|
|
*/
|
|
int idRenderWorldLocal::NumAreas() const
|
|
{
|
|
return numPortalAreas;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::NumPortalsInArea
|
|
===================
|
|
*/
|
|
int idRenderWorldLocal::NumPortalsInArea( int areaNum )
|
|
{
|
|
portalArea_t* area;
|
|
int count;
|
|
portal_t* portal;
|
|
|
|
if( areaNum >= numPortalAreas || areaNum < 0 )
|
|
{
|
|
common->Error( "idRenderWorld::NumPortalsInArea: bad areanum %i", areaNum );
|
|
}
|
|
area = &portalAreas[areaNum];
|
|
|
|
count = 0;
|
|
for( portal = area->portals; portal; portal = portal->next )
|
|
{
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::GetPortal
|
|
===================
|
|
*/
|
|
exitPortal_t idRenderWorldLocal::GetPortal( int areaNum, int portalNum )
|
|
{
|
|
portalArea_t* area;
|
|
int count;
|
|
portal_t* portal;
|
|
exitPortal_t ret;
|
|
|
|
if( areaNum > numPortalAreas )
|
|
{
|
|
common->Error( "idRenderWorld::GetPortal: areaNum > numAreas" );
|
|
}
|
|
area = &portalAreas[areaNum];
|
|
|
|
count = 0;
|
|
for( portal = area->portals; portal; portal = portal->next )
|
|
{
|
|
if( count == portalNum )
|
|
{
|
|
ret.areas[0] = areaNum;
|
|
ret.areas[1] = portal->intoArea;
|
|
ret.w = portal->w;
|
|
ret.blockingBits = portal->doublePortal->blockingBits;
|
|
ret.portalHandle = portal->doublePortal - doublePortals + 1;
|
|
return ret;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
common->Error( "idRenderWorld::GetPortal: portalNum > numPortals" );
|
|
|
|
memset( &ret, 0, sizeof( ret ) );
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idRenderWorldLocal::PointInAreaNum
|
|
|
|
Will return -1 if the point is not in an area, otherwise
|
|
it will return 0 <= value < tr.world->numPortalAreas
|
|
===============
|
|
*/
|
|
int idRenderWorldLocal::PointInArea( const idVec3& point ) const
|
|
{
|
|
areaNode_t* node;
|
|
int nodeNum;
|
|
float d;
|
|
|
|
node = areaNodes;
|
|
if( !node )
|
|
{
|
|
return -1;
|
|
}
|
|
while( 1 )
|
|
{
|
|
d = point * node->plane.Normal() + node->plane[3];
|
|
if( d > 0 )
|
|
{
|
|
nodeNum = node->children[0];
|
|
}
|
|
else
|
|
{
|
|
nodeNum = node->children[1];
|
|
}
|
|
if( nodeNum == 0 )
|
|
{
|
|
return -1; // in solid
|
|
}
|
|
if( nodeNum < 0 )
|
|
{
|
|
nodeNum = -1 - nodeNum;
|
|
if( nodeNum >= numPortalAreas )
|
|
{
|
|
common->Error( "idRenderWorld::PointInArea: area out of range" );
|
|
}
|
|
return nodeNum;
|
|
}
|
|
node = areaNodes + nodeNum;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::BoundsInAreas_r
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::BoundsInAreas_r( int nodeNum, const idBounds& bounds, int* areas, int* numAreas, int maxAreas ) const
|
|
{
|
|
int side, i;
|
|
areaNode_t* node;
|
|
|
|
do
|
|
{
|
|
if( nodeNum < 0 )
|
|
{
|
|
nodeNum = -1 - nodeNum;
|
|
|
|
for( i = 0; i < ( *numAreas ); i++ )
|
|
{
|
|
if( areas[i] == nodeNum )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if( i >= ( *numAreas ) && ( *numAreas ) < maxAreas )
|
|
{
|
|
areas[( *numAreas )++] = nodeNum;
|
|
}
|
|
return;
|
|
}
|
|
|
|
node = areaNodes + nodeNum;
|
|
|
|
side = bounds.PlaneSide( node->plane );
|
|
if( side == PLANESIDE_FRONT )
|
|
{
|
|
nodeNum = node->children[0];
|
|
}
|
|
else if( side == PLANESIDE_BACK )
|
|
{
|
|
nodeNum = node->children[1];
|
|
}
|
|
else
|
|
{
|
|
if( node->children[1] != 0 )
|
|
{
|
|
BoundsInAreas_r( node->children[1], bounds, areas, numAreas, maxAreas );
|
|
if( ( *numAreas ) >= maxAreas )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
nodeNum = node->children[0];
|
|
}
|
|
}
|
|
while( nodeNum != 0 );
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::BoundsInAreas
|
|
|
|
fills the *areas array with the number of the areas the bounds are in
|
|
returns the total number of areas the bounds are in
|
|
===================
|
|
*/
|
|
int idRenderWorldLocal::BoundsInAreas( const idBounds& bounds, int* areas, int maxAreas ) const
|
|
{
|
|
int numAreas = 0;
|
|
|
|
assert( areas );
|
|
assert( bounds[0][0] <= bounds[1][0] && bounds[0][1] <= bounds[1][1] && bounds[0][2] <= bounds[1][2] );
|
|
assert( bounds[1][0] - bounds[0][0] < 1e4f && bounds[1][1] - bounds[0][1] < 1e4f && bounds[1][2] - bounds[0][2] < 1e4f );
|
|
|
|
if( !areaNodes )
|
|
{
|
|
return numAreas;
|
|
}
|
|
BoundsInAreas_r( 0, bounds, areas, &numAreas, maxAreas );
|
|
return numAreas;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::GuiTrace
|
|
|
|
checks a ray trace against any gui surfaces in an entity, returning the
|
|
fraction location of the trace on the gui surface, or -1,-1 if no hit.
|
|
this doesn't do any occlusion testing, simply ignoring non-gui surfaces.
|
|
start / end are in global world coordinates.
|
|
================
|
|
*/
|
|
guiPoint_t idRenderWorldLocal::GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const
|
|
{
|
|
guiPoint_t pt;
|
|
pt.x = pt.y = -1;
|
|
pt.guiId = 0;
|
|
|
|
if( ( entityHandle < 0 ) || ( entityHandle >= entityDefs.Num() ) )
|
|
{
|
|
common->Printf( "idRenderWorld::GuiTrace: invalid handle %i\n", entityHandle );
|
|
return pt;
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[entityHandle];
|
|
if( def == NULL )
|
|
{
|
|
common->Printf( "idRenderWorld::GuiTrace: handle %i is NULL\n", entityHandle );
|
|
return pt;
|
|
}
|
|
|
|
idRenderModel* model = def->parms.hModel;
|
|
if( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback != NULL )
|
|
{
|
|
return pt;
|
|
}
|
|
|
|
// transform the points into local space
|
|
idVec3 localStart, localEnd;
|
|
R_GlobalPointToLocal( def->modelMatrix, start, localStart );
|
|
R_GlobalPointToLocal( def->modelMatrix, end, localEnd );
|
|
|
|
for( int i = 0; i < model->NumSurfaces(); i++ )
|
|
{
|
|
const modelSurface_t* surf = model->Surface( i );
|
|
|
|
const srfTriangles_t* tri = surf->geometry;
|
|
if( tri == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const idMaterial* shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
|
|
if( shader == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
// only trace against gui surfaces
|
|
if( !shader->HasGui() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
localTrace_t local = R_LocalTrace( localStart, localEnd, 0.0f, tri );
|
|
if( local.fraction < 1.0f )
|
|
{
|
|
idVec3 origin, axis[3];
|
|
|
|
R_SurfaceToTextureAxis( tri, origin, axis );
|
|
const idVec3 cursor = local.point - origin;
|
|
|
|
float axisLen[2];
|
|
axisLen[0] = axis[0].Length();
|
|
axisLen[1] = axis[1].Length();
|
|
|
|
pt.x = ( cursor * axis[0] ) / ( axisLen[0] * axisLen[0] );
|
|
pt.y = ( cursor * axis[1] ) / ( axisLen[1] * axisLen[1] );
|
|
pt.guiId = shader->GetEntityGui();
|
|
|
|
return pt;
|
|
}
|
|
}
|
|
|
|
return pt;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::ModelTrace
|
|
===================
|
|
*/
|
|
bool idRenderWorldLocal::ModelTrace( modelTrace_t& trace, qhandle_t entityHandle, const idVec3& start, const idVec3& end, const float radius ) const
|
|
{
|
|
|
|
memset( &trace, 0, sizeof( trace ) );
|
|
trace.fraction = 1.0f;
|
|
trace.point = end;
|
|
|
|
if( entityHandle < 0 || entityHandle >= entityDefs.Num() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
idRenderEntityLocal* def = entityDefs[entityHandle];
|
|
if( def == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
renderEntity_t* refEnt = &def->parms;
|
|
|
|
idRenderModel* model = R_EntityDefDynamicModel( def );
|
|
if( model == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// transform the points into local space
|
|
float modelMatrix[16];
|
|
idVec3 localStart;
|
|
idVec3 localEnd;
|
|
R_AxisToModelMatrix( refEnt->axis, refEnt->origin, modelMatrix );
|
|
R_GlobalPointToLocal( modelMatrix, start, localStart );
|
|
R_GlobalPointToLocal( modelMatrix, end, localEnd );
|
|
|
|
// if we have explicit collision surfaces, only collide against them
|
|
// (FIXME, should probably have a parm to control this)
|
|
bool collisionSurface = false;
|
|
for( int i = 0; i < model->NumBaseSurfaces(); i++ )
|
|
{
|
|
const modelSurface_t* surf = model->Surface( i );
|
|
|
|
const idMaterial* shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
|
|
|
|
if( shader->GetSurfaceFlags() & SURF_COLLISION )
|
|
{
|
|
collisionSurface = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// only use baseSurfaces, not any overlays
|
|
for( int i = 0; i < model->NumBaseSurfaces(); i++ )
|
|
{
|
|
const modelSurface_t* surf = model->Surface( i );
|
|
|
|
const idMaterial* shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
|
|
|
|
if( surf->geometry == NULL || shader == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( collisionSurface )
|
|
{
|
|
// only trace vs collision surfaces
|
|
if( ( shader->GetSurfaceFlags() & SURF_COLLISION ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// skip if not drawn or translucent
|
|
if( !shader->IsDrawn() || ( shader->Coverage() != MC_OPAQUE && shader->Coverage() != MC_PERFORATED ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
localTrace_t localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry );
|
|
|
|
if( localTrace.fraction < trace.fraction )
|
|
{
|
|
trace.fraction = localTrace.fraction;
|
|
R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point );
|
|
trace.normal = localTrace.normal * refEnt->axis;
|
|
trace.material = shader;
|
|
trace.entity = &def->parms;
|
|
trace.jointNumber = refEnt->hModel->NearestJoint( i, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] );
|
|
}
|
|
}
|
|
|
|
return ( trace.fraction < 1.0f );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::Trace
|
|
===================
|
|
*/
|
|
// FIXME: _D3XP added those.
|
|
const char* playerModelExcludeList[] =
|
|
{
|
|
"models/md5/characters/player/d3xp_spplayer.md5mesh",
|
|
"models/md5/characters/player/head/d3xp_head.md5mesh",
|
|
"models/md5/weapons/pistol_world/worldpistol.md5mesh",
|
|
NULL
|
|
};
|
|
|
|
const char* playerMaterialExcludeList[] =
|
|
{
|
|
"muzzlesmokepuff",
|
|
NULL
|
|
};
|
|
|
|
bool idRenderWorldLocal::Trace( modelTrace_t& trace, const idVec3& start, const idVec3& end, const float radius, bool skipDynamic, bool skipPlayer /*_D3XP*/ ) const
|
|
{
|
|
trace.fraction = 1.0f;
|
|
trace.point = end;
|
|
|
|
// bounds for the whole trace
|
|
idBounds traceBounds;
|
|
traceBounds.Clear();
|
|
traceBounds.AddPoint( start );
|
|
traceBounds.AddPoint( end );
|
|
|
|
// get the world areas the trace is in
|
|
int areas[128];
|
|
int numAreas = BoundsInAreas( traceBounds, areas, 128 );
|
|
|
|
int numSurfaces = 0;
|
|
|
|
// check all areas for models
|
|
for( int i = 0; i < numAreas; i++ )
|
|
{
|
|
|
|
portalArea_t* area = &portalAreas[ areas[i] ];
|
|
|
|
// check all models in this area
|
|
for( areaReference_t* ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext )
|
|
{
|
|
idRenderEntityLocal* def = ref->entity;
|
|
|
|
idRenderModel* model = def->parms.hModel;
|
|
if( model == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( model->IsDynamicModel() != DM_STATIC )
|
|
{
|
|
if( skipDynamic )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if 1 /* _D3XP addition. could use a cleaner approach */
|
|
if( skipPlayer )
|
|
{
|
|
bool exclude = false;
|
|
for( int k = 0; playerModelExcludeList[k] != NULL; k++ )
|
|
{
|
|
if( idStr::Cmp( model->Name(), playerModelExcludeList[k] ) == 0 )
|
|
{
|
|
exclude = true;
|
|
break;
|
|
}
|
|
}
|
|
if( exclude )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
model = R_EntityDefDynamicModel( def );
|
|
if( !model )
|
|
{
|
|
continue; // can happen with particle systems, which don't instantiate without a valid view
|
|
}
|
|
}
|
|
|
|
idBounds bounds;
|
|
bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis );
|
|
|
|
// if the model bounds do not overlap with the trace bounds
|
|
if( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check all model surfaces
|
|
for( int j = 0; j < model->NumSurfaces(); j++ )
|
|
{
|
|
const modelSurface_t* surf = model->Surface( j );
|
|
|
|
const idMaterial* shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader );
|
|
|
|
// if no geometry or no shader
|
|
if( surf->geometry == NULL || shader == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if 1 /* _D3XP addition. could use a cleaner approach */
|
|
if( skipPlayer )
|
|
{
|
|
bool exclude = false;
|
|
for( int k = 0; playerMaterialExcludeList[k] != NULL; k++ )
|
|
{
|
|
if( idStr::Cmp( shader->GetName(), playerMaterialExcludeList[k] ) == 0 )
|
|
{
|
|
exclude = true;
|
|
break;
|
|
}
|
|
}
|
|
if( exclude )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const srfTriangles_t* tri = surf->geometry;
|
|
|
|
bounds.FromTransformedBounds( tri->bounds, def->parms.origin, def->parms.axis );
|
|
|
|
// if triangle bounds do not overlap with the trace bounds
|
|
if( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
numSurfaces++;
|
|
|
|
// transform the points into local space
|
|
float modelMatrix[16];
|
|
idVec3 localStart, localEnd;
|
|
R_AxisToModelMatrix( def->parms.axis, def->parms.origin, modelMatrix );
|
|
R_GlobalPointToLocal( modelMatrix, start, localStart );
|
|
R_GlobalPointToLocal( modelMatrix, end, localEnd );
|
|
|
|
localTrace_t localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry );
|
|
|
|
if( localTrace.fraction < trace.fraction )
|
|
{
|
|
trace.fraction = localTrace.fraction;
|
|
R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point );
|
|
trace.normal = localTrace.normal * def->parms.axis;
|
|
trace.material = shader;
|
|
trace.entity = &def->parms;
|
|
trace.jointNumber = model->NearestJoint( j, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] );
|
|
|
|
traceBounds.Clear();
|
|
traceBounds.AddPoint( start );
|
|
traceBounds.AddPoint( start + trace.fraction * ( end - start ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ( trace.fraction < 1.0f );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idRenderWorldLocal::RecurseProcBSP
|
|
==================
|
|
*/
|
|
void idRenderWorldLocal::RecurseProcBSP_r( modelTrace_t* results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3& p1, const idVec3& p2 ) const
|
|
{
|
|
float t1, t2;
|
|
float frac;
|
|
idVec3 mid;
|
|
int side;
|
|
float midf;
|
|
areaNode_t* node;
|
|
|
|
if( results->fraction <= p1f )
|
|
{
|
|
return; // already hit something nearer
|
|
}
|
|
// empty leaf
|
|
if( nodeNum < 0 )
|
|
{
|
|
return;
|
|
}
|
|
// if solid leaf node
|
|
if( nodeNum == 0 )
|
|
{
|
|
if( parentNodeNum != -1 )
|
|
{
|
|
|
|
results->fraction = p1f;
|
|
results->point = p1;
|
|
node = &areaNodes[parentNodeNum];
|
|
results->normal = node->plane.Normal();
|
|
return;
|
|
}
|
|
}
|
|
node = &areaNodes[nodeNum];
|
|
|
|
// distance from plane for trace start and end
|
|
t1 = node->plane.Normal() * p1 + node->plane[3];
|
|
t2 = node->plane.Normal() * p2 + node->plane[3];
|
|
|
|
if( t1 >= 0.0f && t2 >= 0.0f )
|
|
{
|
|
RecurseProcBSP_r( results, nodeNum, node->children[0], p1f, p2f, p1, p2 );
|
|
return;
|
|
}
|
|
if( t1 < 0.0f && t2 < 0.0f )
|
|
{
|
|
RecurseProcBSP_r( results, nodeNum, node->children[1], p1f, p2f, p1, p2 );
|
|
return;
|
|
}
|
|
side = t1 < t2;
|
|
frac = t1 / ( t1 - t2 );
|
|
midf = p1f + frac * ( p2f - p1f );
|
|
mid[0] = p1[0] + frac * ( p2[0] - p1[0] );
|
|
mid[1] = p1[1] + frac * ( p2[1] - p1[1] );
|
|
mid[2] = p1[2] + frac * ( p2[2] - p1[2] );
|
|
RecurseProcBSP_r( results, nodeNum, node->children[side], p1f, midf, p1, mid );
|
|
RecurseProcBSP_r( results, nodeNum, node->children[side ^ 1], midf, p2f, mid, p2 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idRenderWorldLocal::FastWorldTrace
|
|
==================
|
|
*/
|
|
bool idRenderWorldLocal::FastWorldTrace( modelTrace_t& results, const idVec3& start, const idVec3& end ) const
|
|
{
|
|
memset( &results, 0, sizeof( modelTrace_t ) );
|
|
results.fraction = 1.0f;
|
|
if( areaNodes != NULL )
|
|
{
|
|
RecurseProcBSP_r( &results, -1, 0, 0.0f, 1.0f, start, end );
|
|
return ( results.fraction < 1.0f );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================================================================================
|
|
|
|
CREATE MODEL REFS
|
|
|
|
=================================================================================
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
idRenderWorldLocal::AddEntityRefToArea
|
|
|
|
This is called by R_PushVolumeIntoTree and also directly
|
|
for the world model references that are precalculated.
|
|
=================
|
|
*/
|
|
void idRenderWorldLocal::AddEntityRefToArea( idRenderEntityLocal* def, portalArea_t* area )
|
|
{
|
|
areaReference_t* ref;
|
|
|
|
if( def == NULL )
|
|
{
|
|
common->Error( "idRenderWorldLocal::AddEntityRefToArea: NULL def" );
|
|
return;
|
|
}
|
|
|
|
for( ref = def->entityRefs; ref != NULL; ref = ref->ownerNext )
|
|
{
|
|
if( ref->area == area )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
ref = areaReferenceAllocator.Alloc();
|
|
|
|
tr.pc.c_entityReferences++;
|
|
|
|
ref->entity = def;
|
|
|
|
// link to entityDef
|
|
ref->ownerNext = def->entityRefs;
|
|
def->entityRefs = ref;
|
|
|
|
// link to end of area list
|
|
ref->area = area;
|
|
ref->areaNext = &area->entityRefs;
|
|
ref->areaPrev = area->entityRefs.areaPrev;
|
|
ref->areaNext->areaPrev = ref;
|
|
ref->areaPrev->areaNext = ref;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::AddLightRefToArea
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::AddLightRefToArea( idRenderLightLocal* light, portalArea_t* area )
|
|
{
|
|
areaReference_t* lref;
|
|
|
|
for( lref = light->references; lref != NULL; lref = lref->ownerNext )
|
|
{
|
|
if( lref->area == area )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// add a lightref to this area
|
|
lref = areaReferenceAllocator.Alloc();
|
|
lref->light = light;
|
|
lref->area = area;
|
|
lref->ownerNext = light->references;
|
|
light->references = lref;
|
|
tr.pc.c_lightReferences++;
|
|
|
|
// doubly linked list so we can free them easily later
|
|
area->lightRefs.areaNext->areaPrev = lref;
|
|
lref->areaNext = area->lightRefs.areaNext;
|
|
lref->areaPrev = &area->lightRefs;
|
|
area->lightRefs.areaNext = lref;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::GenerateAllInteractions
|
|
|
|
Force the generation of all light / surface interactions at the start of a level
|
|
If this isn't called, they will all be dynamically generated
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::GenerateAllInteractions()
|
|
{
|
|
if( !R_IsInitialized() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int start = Sys_Milliseconds();
|
|
|
|
generateAllInteractionsCalled = false;
|
|
|
|
// let the interaction creation code know that it shouldn't
|
|
// try and do any view specific optimizations
|
|
tr.viewDef = NULL;
|
|
|
|
// build the interaction table
|
|
// this will be dynamically resized if the entity / light counts grow too much
|
|
interactionTableWidth = entityDefs.Num() + 100;
|
|
interactionTableHeight = lightDefs.Num() + 100;
|
|
int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable );
|
|
interactionTable = ( idInteraction** )R_ClearedStaticAlloc( size );
|
|
|
|
// itterate through all lights
|
|
int count = 0;
|
|
for( int i = 0; i < this->lightDefs.Num(); i++ )
|
|
{
|
|
idRenderLightLocal* ldef = this->lightDefs[i];
|
|
if( ldef == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check all areas the light touches
|
|
for( areaReference_t* lref = ldef->references; lref; lref = lref->ownerNext )
|
|
{
|
|
portalArea_t* area = lref->area;
|
|
|
|
// check all the models in this area
|
|
for( areaReference_t* eref = area->entityRefs.areaNext; eref != &area->entityRefs; eref = eref->areaNext )
|
|
{
|
|
idRenderEntityLocal* edef = eref->entity;
|
|
|
|
// scan the doubly linked lists, which may have several dozen entries
|
|
idInteraction* inter;
|
|
|
|
// we could check either model refs or light refs for matches, but it is
|
|
// assumed that there will be less lights in an area than models
|
|
// so the entity chains should be somewhat shorter (they tend to be fairly close).
|
|
for( inter = edef->firstInteraction; inter != NULL; inter = inter->entityNext )
|
|
{
|
|
if( inter->lightDef == ldef )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we already have an interaction, we don't need to do anything
|
|
if( inter != NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// make an interaction for this light / entity pair
|
|
// and add a pointer to it in the table
|
|
inter = idInteraction::AllocAndLink( edef, ldef );
|
|
count++;
|
|
|
|
// the interaction may create geometry
|
|
inter->CreateStaticInteraction();
|
|
}
|
|
}
|
|
|
|
session->Pump();
|
|
}
|
|
|
|
int end = Sys_Milliseconds();
|
|
int msec = end - start;
|
|
|
|
common->Printf( "idRenderWorld::GenerateAllInteractions, msec = %i\n", msec );
|
|
common->Printf( "interactionTable size: %i bytes\n", size );
|
|
common->Printf( "%i interactions take %i bytes\n", count, count * sizeof( idInteraction ) );
|
|
|
|
// entities flagged as noDynamicInteractions will no longer make any
|
|
generateAllInteractionsCalled = true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idRenderWorldLocal::FreeInteractions
|
|
===================
|
|
*/
|
|
void idRenderWorldLocal::FreeInteractions()
|
|
{
|
|
int i;
|
|
idRenderEntityLocal* def;
|
|
|
|
for( i = 0; i < entityDefs.Num(); i++ )
|
|
{
|
|
def = entityDefs[i];
|
|
if( !def )
|
|
{
|
|
continue;
|
|
}
|
|
// free all the interactions
|
|
while( def->firstInteraction != NULL )
|
|
{
|
|
def->firstInteraction->UnlinkAndFree();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idRenderWorldLocal::PushFrustumIntoTree_r
|
|
|
|
Used for both light volumes and model volumes.
|
|
|
|
This does not clip the points by the planes, so some slop
|
|
occurs.
|
|
|
|
tr.viewCount should be bumped before calling, allowing it
|
|
to prevent double checking areas.
|
|
|
|
We might alternatively choose to do this with an area flow.
|
|
==================
|
|
*/
|
|
void idRenderWorldLocal::PushFrustumIntoTree_r( idRenderEntityLocal* def, idRenderLightLocal* light,
|
|
const frustumCorners_t& corners, int nodeNum )
|
|
{
|
|
if( nodeNum < 0 )
|
|
{
|
|
int areaNum = -1 - nodeNum;
|
|
portalArea_t* area = &portalAreas[ areaNum ];
|
|
if( area->viewCount == tr.viewCount )
|
|
{
|
|
return; // already added a reference here
|
|
}
|
|
area->viewCount = tr.viewCount;
|
|
|
|
if( def != NULL )
|
|
{
|
|
AddEntityRefToArea( def, area );
|
|
}
|
|
if( light != NULL )
|
|
{
|
|
AddLightRefToArea( light, area );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
areaNode_t* node = areaNodes + nodeNum;
|
|
|
|
// if we know that all possible children nodes only touch an area
|
|
// we have already marked, we can early out
|
|
if( node->commonChildrenArea != CHILDREN_HAVE_MULTIPLE_AREAS && r_useNodeCommonChildren.GetBool() )
|
|
{
|
|
// note that we do NOT try to set a reference in this area
|
|
// yet, because the test volume may yet wind up being in the
|
|
// solid part, which would cause bounds slightly poked into
|
|
// a wall to show up in the next room
|
|
if( portalAreas[ node->commonChildrenArea ].viewCount == tr.viewCount )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// exact check all the corners against the node plane
|
|
frustumCull_t cull = idRenderMatrix::CullFrustumCornersToPlane( corners, node->plane );
|
|
|
|
if( cull != FRUSTUM_CULL_BACK )
|
|
{
|
|
nodeNum = node->children[0];
|
|
if( nodeNum != 0 ) // 0 = solid
|
|
{
|
|
PushFrustumIntoTree_r( def, light, corners, nodeNum );
|
|
}
|
|
}
|
|
|
|
if( cull != FRUSTUM_CULL_FRONT )
|
|
{
|
|
nodeNum = node->children[1];
|
|
if( nodeNum != 0 ) // 0 = solid
|
|
{
|
|
PushFrustumIntoTree_r( def, light, corners, nodeNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderWorldLocal::PushFrustumIntoTree
|
|
==============
|
|
*/
|
|
void idRenderWorldLocal::PushFrustumIntoTree( idRenderEntityLocal* def, idRenderLightLocal* light, const idRenderMatrix& frustumTransform, const idBounds& frustumBounds )
|
|
{
|
|
if( areaNodes == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// calculate the corners of the frustum in word space
|
|
ALIGNTYPE16 frustumCorners_t corners;
|
|
idRenderMatrix::GetFrustumCorners( corners, frustumTransform, frustumBounds );
|
|
|
|
PushFrustumIntoTree_r( def, light, corners, 0 );
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugClearLines
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugClearLines( int time )
|
|
{
|
|
RB_ClearDebugLines( time );
|
|
RB_ClearDebugText( time );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugLine
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugLine( const idVec4& color, const idVec3& start, const idVec3& end, const int lifetime, const bool depthTest )
|
|
{
|
|
RB_AddDebugLine( color, start, end, lifetime, depthTest );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::DebugArrow
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::DebugArrow( const idVec4& color, const idVec3& start, const idVec3& end, int size, const int lifetime )
|
|
{
|
|
idVec3 forward, right, up, v1, v2;
|
|
float a, s;
|
|
int i;
|
|
static float arrowCos[40];
|
|
static float arrowSin[40];
|
|
static int arrowStep;
|
|
|
|
DebugLine( color, start, end, lifetime );
|
|
|
|
if( r_debugArrowStep.GetInteger() <= 10 )
|
|
{
|
|
return;
|
|
}
|
|
// calculate sine and cosine when step size changes
|
|
if( arrowStep != r_debugArrowStep.GetInteger() )
|
|
{
|
|
arrowStep = r_debugArrowStep.GetInteger();
|
|
for( i = 0, a = 0; a < 360.0f; a += arrowStep, i++ )
|
|
{
|
|
arrowCos[i] = idMath::Cos16( DEG2RAD( a ) );
|
|
arrowSin[i] = idMath::Sin16( DEG2RAD( a ) );
|
|
}
|
|
arrowCos[i] = arrowCos[0];
|
|
arrowSin[i] = arrowSin[0];
|
|
}
|
|
// draw a nice arrow
|
|
forward = end - start;
|
|
forward.Normalize();
|
|
forward.NormalVectors( right, up );
|
|
for( i = 0, a = 0; a < 360.0f; a += arrowStep, i++ )
|
|
{
|
|
s = 0.5f * size * arrowCos[i];
|
|
v1 = end - size * forward;
|
|
v1 = v1 + s * right;
|
|
s = 0.5f * size * arrowSin[i];
|
|
v1 = v1 + s * up;
|
|
|
|
s = 0.5f * size * arrowCos[i + 1];
|
|
v2 = end - size * forward;
|
|
v2 = v2 + s * right;
|
|
s = 0.5f * size * arrowSin[i + 1];
|
|
v2 = v2 + s * up;
|
|
|
|
DebugLine( color, v1, end, lifetime );
|
|
DebugLine( color, v1, v2, lifetime );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugWinding
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugWinding( const idVec4& color, const idWinding& w, const idVec3& origin, const idMat3& axis, const int lifetime, const bool depthTest )
|
|
{
|
|
int i;
|
|
idVec3 point, lastPoint;
|
|
|
|
if( w.GetNumPoints() < 2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
lastPoint = origin + w[w.GetNumPoints() - 1].ToVec3() * axis;
|
|
for( i = 0; i < w.GetNumPoints(); i++ )
|
|
{
|
|
point = origin + w[i].ToVec3() * axis;
|
|
DebugLine( color, lastPoint, point, lifetime, depthTest );
|
|
lastPoint = point;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugCircle
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugCircle( const idVec4& color, const idVec3& origin, const idVec3& dir, const float radius, const int numSteps, const int lifetime, const bool depthTest )
|
|
{
|
|
int i;
|
|
float a;
|
|
idVec3 left, up, point, lastPoint;
|
|
|
|
dir.OrthogonalBasis( left, up );
|
|
left *= radius;
|
|
up *= radius;
|
|
lastPoint = origin + up;
|
|
for( i = 1; i <= numSteps; i++ )
|
|
{
|
|
a = idMath::TWO_PI * i / numSteps;
|
|
point = origin + idMath::Sin16( a ) * left + idMath::Cos16( a ) * up;
|
|
DebugLine( color, lastPoint, point, lifetime, depthTest );
|
|
lastPoint = point;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idRenderWorldLocal::DebugSphere
|
|
============
|
|
*/
|
|
void idRenderWorldLocal::DebugSphere( const idVec4& color, const idSphere& sphere, const int lifetime, const bool depthTest /*_D3XP*/ )
|
|
{
|
|
int i, j, n, num;
|
|
float s, c;
|
|
idVec3 p, lastp, *lastArray;
|
|
|
|
num = 360 / 15;
|
|
lastArray = ( idVec3* ) _alloca16( num * sizeof( idVec3 ) );
|
|
lastArray[0] = sphere.GetOrigin() + idVec3( 0, 0, sphere.GetRadius() );
|
|
for( n = 1; n < num; n++ )
|
|
{
|
|
lastArray[n] = lastArray[0];
|
|
}
|
|
|
|
for( i = 15; i <= 360; i += 15 )
|
|
{
|
|
s = idMath::Sin16( DEG2RAD( i ) );
|
|
c = idMath::Cos16( DEG2RAD( i ) );
|
|
lastp[0] = sphere.GetOrigin()[0];
|
|
lastp[1] = sphere.GetOrigin()[1] + sphere.GetRadius() * s;
|
|
lastp[2] = sphere.GetOrigin()[2] + sphere.GetRadius() * c;
|
|
for( n = 0, j = 15; j <= 360; j += 15, n++ )
|
|
{
|
|
p[0] = sphere.GetOrigin()[0] + idMath::Sin16( DEG2RAD( j ) ) * sphere.GetRadius() * s;
|
|
p[1] = sphere.GetOrigin()[1] + idMath::Cos16( DEG2RAD( j ) ) * sphere.GetRadius() * s;
|
|
p[2] = lastp[2];
|
|
|
|
DebugLine( color, lastp, p, lifetime, depthTest );
|
|
DebugLine( color, lastp, lastArray[n], lifetime, depthTest );
|
|
|
|
lastArray[n] = lastp;
|
|
lastp = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugBounds
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugBounds( const idVec4& color, const idBounds& bounds, const idVec3& org, const int lifetime )
|
|
{
|
|
int i;
|
|
idVec3 v[8];
|
|
|
|
if( bounds.IsCleared() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < 8; i++ )
|
|
{
|
|
v[i][0] = org[0] + bounds[( i ^ ( i >> 1 ) ) & 1][0];
|
|
v[i][1] = org[1] + bounds[( i >> 1 ) & 1][1];
|
|
v[i][2] = org[2] + bounds[( i >> 2 ) & 1][2];
|
|
}
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
DebugLine( color, v[i], v[( i + 1 ) & 3], lifetime );
|
|
DebugLine( color, v[4 + i], v[4 + ( ( i + 1 ) & 3 )], lifetime );
|
|
DebugLine( color, v[i], v[4 + i], lifetime );
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugBox
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugBox( const idVec4& color, const idBox& box, const int lifetime )
|
|
{
|
|
int i;
|
|
idVec3 v[8];
|
|
|
|
box.ToPoints( v );
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
DebugLine( color, v[i], v[( i + 1 ) & 3], lifetime );
|
|
DebugLine( color, v[4 + i], v[4 + ( ( i + 1 ) & 3 )], lifetime );
|
|
DebugLine( color, v[i], v[4 + i], lifetime );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idRenderWorldLocal::DebugCone
|
|
|
|
dir is the cone axis
|
|
radius1 is the radius at the apex
|
|
radius2 is the radius at apex+dir
|
|
============
|
|
*/
|
|
void idRenderWorldLocal::DebugCone( const idVec4& color, const idVec3& apex, const idVec3& dir, float radius1, float radius2, const int lifetime )
|
|
{
|
|
int i;
|
|
idMat3 axis;
|
|
idVec3 top, p1, p2, lastp1, lastp2, d;
|
|
|
|
axis[2] = dir;
|
|
axis[2].Normalize();
|
|
axis[2].NormalVectors( axis[0], axis[1] );
|
|
axis[1] = -axis[1];
|
|
|
|
top = apex + dir;
|
|
lastp2 = top + radius2 * axis[1];
|
|
|
|
if( radius1 == 0.0f )
|
|
{
|
|
for( i = 20; i <= 360; i += 20 )
|
|
{
|
|
d = idMath::Sin16( DEG2RAD( i ) ) * axis[0] + idMath::Cos16( DEG2RAD( i ) ) * axis[1];
|
|
p2 = top + d * radius2;
|
|
DebugLine( color, lastp2, p2, lifetime );
|
|
DebugLine( color, p2, apex, lifetime );
|
|
lastp2 = p2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastp1 = apex + radius1 * axis[1];
|
|
for( i = 20; i <= 360; i += 20 )
|
|
{
|
|
d = idMath::Sin16( DEG2RAD( i ) ) * axis[0] + idMath::Cos16( DEG2RAD( i ) ) * axis[1];
|
|
p1 = apex + d * radius1;
|
|
p2 = top + d * radius2;
|
|
DebugLine( color, lastp1, p1, lifetime );
|
|
DebugLine( color, lastp2, p2, lifetime );
|
|
DebugLine( color, p1, p2, lifetime );
|
|
lastp1 = p1;
|
|
lastp2 = p2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::DebugAxis
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::DebugAxis( const idVec3& origin, const idMat3& axis )
|
|
{
|
|
idVec3 start = origin;
|
|
idVec3 end = start + axis[0] * 20.0f;
|
|
DebugArrow( colorWhite, start, end, 2 );
|
|
end = start + axis[0] * -20.0f;
|
|
DebugArrow( colorWhite, start, end, 2 );
|
|
end = start + axis[1] * +20.0f;
|
|
DebugArrow( colorGreen, start, end, 2 );
|
|
end = start + axis[1] * -20.0f;
|
|
DebugArrow( colorGreen, start, end, 2 );
|
|
end = start + axis[2] * +20.0f;
|
|
DebugArrow( colorBlue, start, end, 2 );
|
|
end = start + axis[2] * -20.0f;
|
|
DebugArrow( colorBlue, start, end, 2 );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugClearPolygons
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugClearPolygons( int time )
|
|
{
|
|
RB_ClearDebugPolygons( time );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idRenderWorldLocal::DebugPolygon
|
|
====================
|
|
*/
|
|
void idRenderWorldLocal::DebugPolygon( const idVec4& color, const idWinding& winding, const int lifeTime, const bool depthTest )
|
|
{
|
|
RB_AddDebugPolygon( color, winding, lifeTime, depthTest );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::DebugScreenRect
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::DebugScreenRect( const idVec4& color, const idScreenRect& rect, const viewDef_t* viewDef, const int lifetime )
|
|
{
|
|
int i;
|
|
float centerx, centery, dScale, hScale, vScale;
|
|
idBounds bounds;
|
|
idVec3 p[4];
|
|
|
|
centerx = ( viewDef->viewport.x2 - viewDef->viewport.x1 ) * 0.5f;
|
|
centery = ( viewDef->viewport.y2 - viewDef->viewport.y1 ) * 0.5f;
|
|
|
|
dScale = r_znear.GetFloat() + 1.0f;
|
|
hScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_x * 0.5f ) );
|
|
vScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_y * 0.5f ) );
|
|
|
|
bounds[0][0] = bounds[1][0] = dScale;
|
|
bounds[0][1] = -( rect.x1 - centerx ) / centerx * hScale;
|
|
bounds[1][1] = -( rect.x2 - centerx ) / centerx * hScale;
|
|
bounds[0][2] = ( rect.y1 - centery ) / centery * vScale;
|
|
bounds[1][2] = ( rect.y2 - centery ) / centery * vScale;
|
|
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
p[i].x = bounds[0][0];
|
|
p[i].y = bounds[( i ^ ( i >> 1 ) ) & 1].y;
|
|
p[i].z = bounds[( i >> 1 ) & 1].z;
|
|
p[i] = viewDef->renderView.vieworg + p[i] * viewDef->renderView.viewaxis;
|
|
}
|
|
for( i = 0; i < 4; i++ )
|
|
{
|
|
DebugLine( color, p[i], p[( i + 1 ) & 3], false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::DrawTextLength
|
|
|
|
returns the length of the given text
|
|
================
|
|
*/
|
|
float idRenderWorldLocal::DrawTextLength( const char* text, float scale, int len )
|
|
{
|
|
return RB_DrawTextLength( text, scale, len );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::DrawText
|
|
|
|
oriented on the viewaxis
|
|
align can be 0-left, 1-center (default), 2-right
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::DrawText( const char* text, const idVec3& origin, float scale, const idVec4& color, const idMat3& viewAxis, const int align, const int lifetime, const bool depthTest )
|
|
{
|
|
RB_AddDebugText( text, origin, scale, color, viewAxis, align, lifetime, depthTest );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idRenderWorldLocal::RegenerateWorld
|
|
===============
|
|
*/
|
|
void idRenderWorldLocal::RegenerateWorld()
|
|
{
|
|
R_FreeDerivedData();
|
|
R_ReCreateWorldReferences();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_GlobalShaderOverride
|
|
===============
|
|
*/
|
|
bool R_GlobalShaderOverride( const idMaterial** shader )
|
|
{
|
|
|
|
if( !( *shader )->IsDrawn() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( tr.primaryRenderView.globalMaterial )
|
|
{
|
|
*shader = tr.primaryRenderView.globalMaterial;
|
|
return true;
|
|
}
|
|
|
|
if( r_materialOverride.GetString()[0] != '\0' )
|
|
{
|
|
*shader = declManager->FindMaterial( r_materialOverride.GetString() );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_RemapShaderBySkin
|
|
===============
|
|
*/
|
|
const idMaterial* R_RemapShaderBySkin( const idMaterial* shader, const idDeclSkin* skin, const idMaterial* customShader )
|
|
{
|
|
|
|
if( !shader )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// never remap surfaces that were originally nodraw, like collision hulls
|
|
if( !shader->IsDrawn() )
|
|
{
|
|
return shader;
|
|
}
|
|
|
|
if( customShader )
|
|
{
|
|
// this is sort of a hack, but cause deformed surfaces to map to empty surfaces,
|
|
// so the item highlight overlay doesn't highlight the autosprite surface
|
|
if( shader->Deform() )
|
|
{
|
|
return NULL;
|
|
}
|
|
return const_cast<idMaterial*>( customShader );
|
|
}
|
|
|
|
if( !skin )
|
|
{
|
|
return const_cast<idMaterial*>( shader );
|
|
}
|
|
|
|
return skin->RemapShaderBySkin( shader );
|
|
}
|