mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 21:21:27 +00:00
1160 lines
No EOL
28 KiB
C++
1160 lines
No EOL
28 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"
|
|
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::FreeWorld
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::FreeWorld()
|
|
{
|
|
// this will free all the lightDefs and entityDefs
|
|
FreeDefs();
|
|
|
|
// free all the portals and check light/model references
|
|
for( int i = 0; i < numPortalAreas; i++ )
|
|
{
|
|
portalArea_t* area;
|
|
portal_t* portal, *nextPortal;
|
|
|
|
area = &portalAreas[i];
|
|
for( portal = area->portals; portal; portal = nextPortal )
|
|
{
|
|
nextPortal = portal->next;
|
|
delete portal->w;
|
|
R_StaticFree( portal );
|
|
}
|
|
|
|
// there shouldn't be any remaining lightRefs or entityRefs
|
|
if( area->lightRefs.areaNext != &area->lightRefs )
|
|
{
|
|
common->Error( "FreeWorld: unexpected remaining lightRefs" );
|
|
}
|
|
if( area->entityRefs.areaNext != &area->entityRefs )
|
|
{
|
|
common->Error( "FreeWorld: unexpected remaining entityRefs" );
|
|
}
|
|
}
|
|
|
|
if( portalAreas )
|
|
{
|
|
R_StaticFree( portalAreas );
|
|
portalAreas = NULL;
|
|
numPortalAreas = 0;
|
|
R_StaticFree( areaScreenRect );
|
|
areaScreenRect = NULL;
|
|
}
|
|
|
|
if( doublePortals )
|
|
{
|
|
R_StaticFree( doublePortals );
|
|
doublePortals = NULL;
|
|
numInterAreaPortals = 0;
|
|
}
|
|
|
|
if( areaNodes )
|
|
{
|
|
R_StaticFree( areaNodes );
|
|
areaNodes = NULL;
|
|
}
|
|
|
|
// free all the inline idRenderModels
|
|
for( int i = 0; i < localModels.Num(); i++ )
|
|
{
|
|
renderModelManager->RemoveModel( localModels[i] );
|
|
delete localModels[i];
|
|
}
|
|
localModels.Clear();
|
|
|
|
areaReferenceAllocator.Shutdown();
|
|
interactionAllocator.Shutdown();
|
|
|
|
mapName = "<FREED>";
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::TouchWorldModels
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::TouchWorldModels()
|
|
{
|
|
for( int i = 0; i < localModels.Num(); i++ )
|
|
{
|
|
renderModelManager->CheckModel( localModels[i]->Name() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ReadBinaryShadowModel
|
|
================
|
|
*/
|
|
idRenderModel* idRenderWorldLocal::ReadBinaryModel( idFile* fileIn )
|
|
{
|
|
idStrStatic< MAX_OSPATH > name;
|
|
fileIn->ReadString( name );
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty( name );
|
|
if( model->LoadBinaryModel( fileIn, mapTimeStamp ) )
|
|
{
|
|
return model;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
extern idCVar r_binaryLoadRenderModels;
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ParseModel
|
|
================
|
|
*/
|
|
idRenderModel* idRenderWorldLocal::ParseModel( idLexer* src, const char* mapName, ID_TIME_T mapTimeStamp, idFile* fileOut )
|
|
{
|
|
idToken token;
|
|
|
|
src->ExpectTokenString( "{" );
|
|
|
|
// parse the name
|
|
src->ExpectAnyToken( &token );
|
|
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty( token );
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
// write out the type so the binary reader knows what to instantiate
|
|
fileOut->WriteString( "shadowmodel" );
|
|
fileOut->WriteString( token );
|
|
}
|
|
|
|
int numSurfaces = src->ParseInt();
|
|
if( numSurfaces < 0 )
|
|
{
|
|
src->Error( "R_ParseModel: bad numSurfaces" );
|
|
}
|
|
|
|
for( int i = 0; i < numSurfaces; i++ )
|
|
{
|
|
src->ExpectTokenString( "{" );
|
|
|
|
src->ExpectAnyToken( &token );
|
|
|
|
modelSurface_t surf;
|
|
surf.shader = declManager->FindMaterial( token );
|
|
|
|
( ( idMaterial* )surf.shader )->AddReference();
|
|
|
|
srfTriangles_t* tri = R_AllocStaticTriSurf();
|
|
surf.geometry = tri;
|
|
|
|
tri->numVerts = src->ParseInt();
|
|
tri->numIndexes = src->ParseInt();
|
|
|
|
// parse the vertices
|
|
idTempArray<float> verts( tri->numVerts * 8 );
|
|
for( int j = 0; j < tri->numVerts; j++ )
|
|
{
|
|
src->Parse1DMatrix( 8, &verts[j * 8] );
|
|
}
|
|
|
|
// parse the indices
|
|
idTempArray<triIndex_t> indexes( tri->numIndexes );
|
|
for( int j = 0; j < tri->numIndexes; j++ )
|
|
{
|
|
indexes[j] = src->ParseInt();
|
|
}
|
|
|
|
#if 1
|
|
// find the island that each vertex belongs to
|
|
idTempArray<int> vertIslands( tri->numVerts );
|
|
idTempArray<bool> trisVisited( tri->numIndexes );
|
|
vertIslands.Zero();
|
|
trisVisited.Zero();
|
|
int numIslands = 0;
|
|
for( int j = 0; j < tri->numIndexes; j += 3 )
|
|
{
|
|
if( trisVisited[j] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int islandNum = ++numIslands;
|
|
vertIslands[indexes[j + 0]] = islandNum;
|
|
vertIslands[indexes[j + 1]] = islandNum;
|
|
vertIslands[indexes[j + 2]] = islandNum;
|
|
trisVisited[j] = true;
|
|
|
|
idList<int> queue;
|
|
queue.Append( j );
|
|
for( int n = 0; n < queue.Num(); n++ )
|
|
{
|
|
int t = queue[n];
|
|
for( int k = 0; k < tri->numIndexes; k += 3 )
|
|
{
|
|
if( trisVisited[k] )
|
|
{
|
|
continue;
|
|
}
|
|
bool connected = indexes[t + 0] == indexes[k + 0] || indexes[t + 0] == indexes[k + 1] || indexes[t + 0] == indexes[k + 2] ||
|
|
indexes[t + 1] == indexes[k + 0] || indexes[t + 1] == indexes[k + 1] || indexes[t + 1] == indexes[k + 2] ||
|
|
indexes[t + 2] == indexes[k + 0] || indexes[t + 2] == indexes[k + 1] || indexes[t + 2] == indexes[k + 2];
|
|
if( connected )
|
|
{
|
|
vertIslands[indexes[k + 0]] = islandNum;
|
|
vertIslands[indexes[k + 1]] = islandNum;
|
|
vertIslands[indexes[k + 2]] = islandNum;
|
|
trisVisited[k] = true;
|
|
queue.Append( k );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// center the texture coordinates for each island for maximum 16-bit precision
|
|
for( int j = 1; j <= numIslands; j++ )
|
|
{
|
|
float minS = idMath::INFINITY;
|
|
float minT = idMath::INFINITY;
|
|
float maxS = -idMath::INFINITY;
|
|
float maxT = -idMath::INFINITY;
|
|
for( int k = 0; k < tri->numVerts; k++ )
|
|
{
|
|
if( vertIslands[k] == j )
|
|
{
|
|
minS = Min( minS, verts[k * 8 + 3] );
|
|
maxS = Max( maxS, verts[k * 8 + 3] );
|
|
minT = Min( minT, verts[k * 8 + 4] );
|
|
maxT = Max( maxT, verts[k * 8 + 4] );
|
|
}
|
|
}
|
|
const float averageS = idMath::Ftoi( ( minS + maxS ) * 0.5f );
|
|
const float averageT = idMath::Ftoi( ( minT + maxT ) * 0.5f );
|
|
for( int k = 0; k < tri->numVerts; k++ )
|
|
{
|
|
if( vertIslands[k] == j )
|
|
{
|
|
verts[k * 8 + 3] -= averageS;
|
|
verts[k * 8 + 4] -= averageT;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
for( int j = 0; j < tri->numVerts; j++ )
|
|
{
|
|
tri->verts[j].xyz[0] = verts[j * 8 + 0];
|
|
tri->verts[j].xyz[1] = verts[j * 8 + 1];
|
|
tri->verts[j].xyz[2] = verts[j * 8 + 2];
|
|
tri->verts[j].SetTexCoord( verts[j * 8 + 3], verts[j * 8 + 4] );
|
|
tri->verts[j].SetNormal( verts[j * 8 + 5], verts[j * 8 + 6], verts[j * 8 + 7] );
|
|
}
|
|
|
|
R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );
|
|
for( int j = 0; j < tri->numIndexes; j++ )
|
|
{
|
|
tri->indexes[j] = indexes[j];
|
|
}
|
|
src->ExpectTokenString( "}" );
|
|
|
|
// add the completed surface to the model
|
|
model->AddSurface( surf );
|
|
}
|
|
|
|
src->ExpectTokenString( "}" );
|
|
|
|
model->FinishSurfaces();
|
|
|
|
if( fileOut != NULL && model->SupportsBinaryModel() && r_binaryLoadRenderModels.GetBool() )
|
|
{
|
|
model->WriteBinaryModel( fileOut, &mapTimeStamp );
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ReadBinaryShadowModel
|
|
================
|
|
*/
|
|
idRenderModel* idRenderWorldLocal::ReadBinaryShadowModel( idFile* fileIn )
|
|
{
|
|
idStrStatic< MAX_OSPATH > name;
|
|
fileIn->ReadString( name );
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty( name );
|
|
if( model->LoadBinaryModel( fileIn, mapTimeStamp ) )
|
|
{
|
|
return model;
|
|
}
|
|
return NULL;
|
|
}
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ParseShadowModel
|
|
================
|
|
*/
|
|
idRenderModel* idRenderWorldLocal::ParseShadowModel( idLexer* src, idFile* fileOut )
|
|
{
|
|
idToken token;
|
|
|
|
src->ExpectTokenString( "{" );
|
|
|
|
// parse the name
|
|
src->ExpectAnyToken( &token );
|
|
|
|
idRenderModel* model = renderModelManager->AllocModel();
|
|
model->InitEmpty( token );
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
// write out the type so the binary reader knows what to instantiate
|
|
fileOut->WriteString( "shadowmodel" );
|
|
fileOut->WriteString( token );
|
|
}
|
|
|
|
srfTriangles_t* tri = R_AllocStaticTriSurf();
|
|
|
|
tri->numVerts = src->ParseInt();
|
|
tri->numShadowIndexesNoCaps = src->ParseInt();
|
|
tri->numShadowIndexesNoFrontCaps = src->ParseInt();
|
|
tri->numIndexes = src->ParseInt();
|
|
tri->shadowCapPlaneBits = src->ParseInt();
|
|
|
|
assert( ( tri->numVerts & 1 ) == 0 );
|
|
|
|
R_AllocStaticTriSurfPreLightShadowVerts( tri, ALIGN( tri->numVerts, 2 ) );
|
|
tri->bounds.Clear();
|
|
for( int j = 0; j < tri->numVerts; j++ )
|
|
{
|
|
float vec[8];
|
|
|
|
src->Parse1DMatrix( 3, vec );
|
|
tri->preLightShadowVertexes[j].xyzw[0] = vec[0];
|
|
tri->preLightShadowVertexes[j].xyzw[1] = vec[1];
|
|
tri->preLightShadowVertexes[j].xyzw[2] = vec[2];
|
|
tri->preLightShadowVertexes[j].xyzw[3] = 1.0f; // no homogenous value
|
|
|
|
tri->bounds.AddPoint( tri->preLightShadowVertexes[j].xyzw.ToVec3() );
|
|
}
|
|
// clear the last vertex if it wasn't stored
|
|
if( ( tri->numVerts & 1 ) != 0 )
|
|
{
|
|
tri->preLightShadowVertexes[ALIGN( tri->numVerts, 2 ) - 1].xyzw.Zero();
|
|
}
|
|
|
|
// to be consistent set the number of vertices to half the number of shadow vertices
|
|
tri->numVerts = ALIGN( tri->numVerts, 2 ) / 2;
|
|
|
|
R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );
|
|
for( int j = 0; j < tri->numIndexes; j++ )
|
|
{
|
|
tri->indexes[j] = src->ParseInt();
|
|
}
|
|
|
|
// add the completed surface to the model
|
|
modelSurface_t surf;
|
|
surf.id = 0;
|
|
surf.shader = tr.defaultMaterial;
|
|
surf.geometry = tri;
|
|
|
|
model->AddSurface( surf );
|
|
|
|
src->ExpectTokenString( "}" );
|
|
|
|
// NOTE: we do NOT do a model->FinishSurfaceces, because we don't need sil edges, planes, tangents, etc.
|
|
|
|
if( fileOut != NULL && model->SupportsBinaryModel() && r_binaryLoadRenderModels.GetBool() )
|
|
{
|
|
model->WriteBinaryModel( fileOut, &mapTimeStamp );
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::SetupAreaRefs
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::SetupAreaRefs()
|
|
{
|
|
connectedAreaNum = 0;
|
|
for( int i = 0; i < numPortalAreas; i++ )
|
|
{
|
|
portalAreas[i].areaNum = i;
|
|
portalAreas[i].lightRefs.areaNext =
|
|
portalAreas[i].lightRefs.areaPrev = &portalAreas[i].lightRefs;
|
|
portalAreas[i].entityRefs.areaNext =
|
|
portalAreas[i].entityRefs.areaPrev = &portalAreas[i].entityRefs;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ParseInterAreaPortals
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::ParseInterAreaPortals( idLexer* src, idFile* fileOut )
|
|
{
|
|
src->ExpectTokenString( "{" );
|
|
|
|
numPortalAreas = src->ParseInt();
|
|
if( numPortalAreas < 0 )
|
|
{
|
|
src->Error( "R_ParseInterAreaPortals: bad numPortalAreas" );
|
|
return;
|
|
}
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
// write out the type so the binary reader knows what to instantiate
|
|
fileOut->WriteString( "interAreaPortals" );
|
|
}
|
|
|
|
|
|
portalAreas = ( portalArea_t* )R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) );
|
|
areaScreenRect = ( idScreenRect* ) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) );
|
|
|
|
// set the doubly linked lists
|
|
SetupAreaRefs();
|
|
|
|
numInterAreaPortals = src->ParseInt();
|
|
if( numInterAreaPortals < 0 )
|
|
{
|
|
src->Error( "R_ParseInterAreaPortals: bad numInterAreaPortals" );
|
|
return;
|
|
}
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
fileOut->WriteBig( numPortalAreas );
|
|
fileOut->WriteBig( numInterAreaPortals );
|
|
}
|
|
|
|
doublePortals = ( doublePortal_t* )R_ClearedStaticAlloc( numInterAreaPortals *
|
|
sizeof( doublePortals [0] ) );
|
|
|
|
for( int i = 0; i < numInterAreaPortals; i++ )
|
|
{
|
|
int numPoints, a1, a2;
|
|
idWinding* w;
|
|
portal_t* p;
|
|
|
|
numPoints = src->ParseInt();
|
|
a1 = src->ParseInt();
|
|
a2 = src->ParseInt();
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
fileOut->WriteBig( numPoints );
|
|
fileOut->WriteBig( a1 );
|
|
fileOut->WriteBig( a2 );
|
|
}
|
|
|
|
w = new( TAG_RENDER_WINDING ) idWinding( numPoints );
|
|
w->SetNumPoints( numPoints );
|
|
for( int j = 0; j < numPoints; j++ )
|
|
{
|
|
src->Parse1DMatrix( 3, ( *w )[j].ToFloatPtr() );
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
fileOut->WriteBig( ( *w )[j].x );
|
|
fileOut->WriteBig( ( *w )[j].y );
|
|
fileOut->WriteBig( ( *w )[j].z );
|
|
}
|
|
// no texture coordinates
|
|
( *w )[j][3] = 0;
|
|
( *w )[j][4] = 0;
|
|
}
|
|
|
|
// add the portal to a1
|
|
p = ( portal_t* )R_ClearedStaticAlloc( sizeof( *p ) );
|
|
p->intoArea = a2;
|
|
p->doublePortal = &doublePortals[i];
|
|
p->w = w;
|
|
p->w->GetPlane( p->plane );
|
|
|
|
p->next = portalAreas[a1].portals;
|
|
portalAreas[a1].portals = p;
|
|
|
|
doublePortals[i].portals[0] = p;
|
|
|
|
// reverse it for a2
|
|
p = ( portal_t* )R_ClearedStaticAlloc( sizeof( *p ) );
|
|
p->intoArea = a1;
|
|
p->doublePortal = &doublePortals[i];
|
|
p->w = w->Reverse();
|
|
p->w->GetPlane( p->plane );
|
|
|
|
p->next = portalAreas[a2].portals;
|
|
portalAreas[a2].portals = p;
|
|
|
|
doublePortals[i].portals[1] = p;
|
|
}
|
|
|
|
src->ExpectTokenString( "}" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ParseInterAreaPortals
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::ReadBinaryAreaPortals( idFile* file )
|
|
{
|
|
|
|
file->ReadBig( numPortalAreas );
|
|
file->ReadBig( numInterAreaPortals );
|
|
|
|
portalAreas = ( portalArea_t* )R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) );
|
|
areaScreenRect = ( idScreenRect* ) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) );
|
|
|
|
// set the doubly linked lists
|
|
SetupAreaRefs();
|
|
|
|
doublePortals = ( doublePortal_t* )R_ClearedStaticAlloc( numInterAreaPortals * sizeof( doublePortals [0] ) );
|
|
|
|
for( int i = 0; i < numInterAreaPortals; i++ )
|
|
{
|
|
int numPoints, a1, a2;
|
|
idWinding* w;
|
|
portal_t* p;
|
|
|
|
file->ReadBig( numPoints );
|
|
file->ReadBig( a1 );
|
|
file->ReadBig( a2 );
|
|
w = new( TAG_RENDER_WINDING ) idWinding( numPoints );
|
|
w->SetNumPoints( numPoints );
|
|
for( int j = 0; j < numPoints; j++ )
|
|
{
|
|
file->ReadBig( ( *w )[ j ][ 0 ] );
|
|
file->ReadBig( ( *w )[ j ][ 1 ] );
|
|
file->ReadBig( ( *w )[ j ][ 2 ] );
|
|
// no texture coordinates
|
|
( *w )[ j ][ 3 ] = 0;
|
|
( *w )[ j ][ 4 ] = 0;
|
|
}
|
|
|
|
// add the portal to a1
|
|
p = ( portal_t* )R_ClearedStaticAlloc( sizeof( *p ) );
|
|
p->intoArea = a2;
|
|
p->doublePortal = &doublePortals[i];
|
|
p->w = w;
|
|
p->w->GetPlane( p->plane );
|
|
|
|
p->next = portalAreas[a1].portals;
|
|
portalAreas[a1].portals = p;
|
|
|
|
doublePortals[i].portals[0] = p;
|
|
|
|
// reverse it for a2
|
|
p = ( portal_t* )R_ClearedStaticAlloc( sizeof( *p ) );
|
|
p->intoArea = a1;
|
|
p->doublePortal = &doublePortals[i];
|
|
p->w = w->Reverse();
|
|
p->w->GetPlane( p->plane );
|
|
|
|
p->next = portalAreas[a2].portals;
|
|
portalAreas[a2].portals = p;
|
|
|
|
doublePortals[i].portals[1] = p;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ParseNodes
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::ParseNodes( idLexer* src, idFile* fileOut )
|
|
{
|
|
src->ExpectTokenString( "{" );
|
|
|
|
numAreaNodes = src->ParseInt();
|
|
if( numAreaNodes < 0 )
|
|
{
|
|
src->Error( "R_ParseNodes: bad numAreaNodes" );
|
|
}
|
|
areaNodes = ( areaNode_t* )R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) );
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
// write out the type so the binary reader knows what to instantiate
|
|
fileOut->WriteString( "nodes" );
|
|
}
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
fileOut->WriteBig( numAreaNodes );
|
|
}
|
|
|
|
for( int i = 0; i < numAreaNodes; i++ )
|
|
{
|
|
areaNode_t* node;
|
|
|
|
node = &areaNodes[i];
|
|
|
|
src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
|
|
|
|
node->children[0] = src->ParseInt();
|
|
node->children[1] = src->ParseInt();
|
|
|
|
if( fileOut != NULL )
|
|
{
|
|
fileOut->WriteBig( node->plane[ 0 ] );
|
|
fileOut->WriteBig( node->plane[ 1 ] );
|
|
fileOut->WriteBig( node->plane[ 2 ] );
|
|
fileOut->WriteBig( node->plane[ 3 ] );
|
|
fileOut->WriteBig( node->children[ 0 ] );
|
|
fileOut->WriteBig( node->children[ 1 ] );
|
|
}
|
|
|
|
}
|
|
|
|
src->ExpectTokenString( "}" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::ReadBinaryNodes
|
|
================
|
|
*/
|
|
void idRenderWorldLocal::ReadBinaryNodes( idFile* file )
|
|
{
|
|
file->ReadBig( numAreaNodes );
|
|
areaNodes = ( areaNode_t* )R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) );
|
|
for( int i = 0; i < numAreaNodes; i++ )
|
|
{
|
|
areaNode_t* node = &areaNodes[ i ];
|
|
file->ReadBig( node->plane[ 0 ] );
|
|
file->ReadBig( node->plane[ 1 ] );
|
|
file->ReadBig( node->plane[ 2 ] );
|
|
file->ReadBig( node->plane[ 3 ] );
|
|
file->ReadBig( node->children[ 0 ] );
|
|
file->ReadBig( node->children[ 1 ] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderWorldLocal::CommonChildrenArea_r
|
|
================
|
|
*/
|
|
int idRenderWorldLocal::CommonChildrenArea_r( areaNode_t* node )
|
|
{
|
|
int nums[2];
|
|
|
|
for( int i = 0; i < 2; i++ )
|
|
{
|
|
if( node->children[i] <= 0 )
|
|
{
|
|
nums[i] = -1 - node->children[i];
|
|
}
|
|
else
|
|
{
|
|
nums[i] = CommonChildrenArea_r( &areaNodes[ node->children[i] ] );
|
|
}
|
|
}
|
|
|
|
// solid nodes will match any area
|
|
if( nums[0] == AREANUM_SOLID )
|
|
{
|
|
nums[0] = nums[1];
|
|
}
|
|
if( nums[1] == AREANUM_SOLID )
|
|
{
|
|
nums[1] = nums[0];
|
|
}
|
|
|
|
int common;
|
|
if( nums[0] == nums[1] )
|
|
{
|
|
common = nums[0];
|
|
}
|
|
else
|
|
{
|
|
common = CHILDREN_HAVE_MULTIPLE_AREAS;
|
|
}
|
|
|
|
node->commonChildrenArea = common;
|
|
|
|
return common;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderWorldLocal::ClearWorld
|
|
|
|
Sets up for a single area world
|
|
=================
|
|
*/
|
|
void idRenderWorldLocal::ClearWorld()
|
|
{
|
|
numPortalAreas = 1;
|
|
portalAreas = ( portalArea_t* )R_ClearedStaticAlloc( sizeof( portalAreas[0] ) );
|
|
areaScreenRect = ( idScreenRect* ) R_ClearedStaticAlloc( sizeof( idScreenRect ) );
|
|
|
|
SetupAreaRefs();
|
|
|
|
// even though we only have a single area, create a node
|
|
// that has both children pointing at it so we don't need to
|
|
//
|
|
areaNodes = ( areaNode_t* )R_ClearedStaticAlloc( sizeof( areaNodes[0] ) );
|
|
areaNodes[0].plane[3] = 1;
|
|
areaNodes[0].children[0] = -1;
|
|
areaNodes[0].children[1] = -1;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderWorldLocal::FreeDefs
|
|
|
|
dump all the interactions
|
|
=================
|
|
*/
|
|
void idRenderWorldLocal::FreeDefs()
|
|
{
|
|
generateAllInteractionsCalled = false;
|
|
|
|
if( interactionTable )
|
|
{
|
|
R_StaticFree( interactionTable );
|
|
interactionTable = NULL;
|
|
}
|
|
|
|
// free all lightDefs
|
|
for( int i = 0; i < lightDefs.Num(); i++ )
|
|
{
|
|
idRenderLightLocal* light = lightDefs[i];
|
|
if( light != NULL && light->world == this )
|
|
{
|
|
FreeLightDef( i );
|
|
lightDefs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// free all entityDefs
|
|
for( int i = 0; i < entityDefs.Num(); i++ )
|
|
{
|
|
idRenderEntityLocal* mod = entityDefs[i];
|
|
if( mod != NULL && mod->world == this )
|
|
{
|
|
FreeEntityDef( i );
|
|
entityDefs[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// Reset decals and overlays
|
|
for( int i = 0; i < decals.Num(); i++ )
|
|
{
|
|
decals[i].entityHandle = -1;
|
|
decals[i].lastStartTime = 0;
|
|
}
|
|
for( int i = 0; i < overlays.Num(); i++ )
|
|
{
|
|
overlays[i].entityHandle = -1;
|
|
overlays[i].lastStartTime = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderWorldLocal::InitFromMap
|
|
|
|
A NULL or empty name will make a world without a map model, which
|
|
is still useful for displaying a bare model
|
|
=================
|
|
*/
|
|
bool idRenderWorldLocal::InitFromMap( const char* name )
|
|
{
|
|
idLexer* src;
|
|
idToken token;
|
|
idRenderModel* lastModel;
|
|
|
|
// if this is an empty world, initialize manually
|
|
if( !name || !name[0] )
|
|
{
|
|
FreeWorld();
|
|
mapName.Clear();
|
|
ClearWorld();
|
|
return true;
|
|
}
|
|
|
|
// load it
|
|
idStrStatic< MAX_OSPATH > filename = name;
|
|
filename.SetFileExtension( PROC_FILE_EXT );
|
|
|
|
// check for generated file
|
|
idStrStatic< MAX_OSPATH > generatedFileName = filename;
|
|
generatedFileName.Insert( "generated/", 0 );
|
|
generatedFileName.SetFileExtension( "bproc" );
|
|
|
|
// if we are reloading the same map, check the timestamp
|
|
// and try to skip all the work
|
|
ID_TIME_T currentTimeStamp = fileSystem->GetTimestamp( filename );
|
|
|
|
if( name == mapName )
|
|
{
|
|
if( fileSystem->InProductionMode() || ( currentTimeStamp != FILE_NOT_FOUND_TIMESTAMP && currentTimeStamp == mapTimeStamp ) )
|
|
{
|
|
common->Printf( "idRenderWorldLocal::InitFromMap: retaining existing map\n" );
|
|
FreeDefs();
|
|
TouchWorldModels();
|
|
AddWorldModelEntities();
|
|
ClearPortalStates();
|
|
return true;
|
|
}
|
|
common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" );
|
|
}
|
|
|
|
FreeWorld();
|
|
|
|
// see if we have a generated version of this
|
|
static const byte BPROC_VERSION = 1;
|
|
static const unsigned int BPROC_MAGIC = ( 'P' << 24 ) | ( 'R' << 16 ) | ( 'O' << 8 ) | BPROC_VERSION;
|
|
bool loaded = false;
|
|
idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) );
|
|
if( file != NULL )
|
|
{
|
|
int numEntries = 0;
|
|
int magic = 0;
|
|
file->ReadBig( magic );
|
|
if( magic == BPROC_MAGIC )
|
|
{
|
|
file->ReadBig( numEntries );
|
|
file->ReadString( mapName );
|
|
file->ReadBig( mapTimeStamp );
|
|
loaded = true;
|
|
for( int i = 0; i < numEntries; i++ )
|
|
{
|
|
idStrStatic< MAX_OSPATH > type;
|
|
file->ReadString( type );
|
|
type.ToLower();
|
|
if( type == "model" )
|
|
{
|
|
idRenderModel* lastModel = ReadBinaryModel( file );
|
|
if( lastModel == NULL )
|
|
{
|
|
loaded = false;
|
|
break;
|
|
}
|
|
renderModelManager->AddModel( lastModel );
|
|
localModels.Append( lastModel );
|
|
}
|
|
else if( type == "shadowmodel" )
|
|
{
|
|
idRenderModel* lastModel = ReadBinaryModel( file );
|
|
if( lastModel == NULL )
|
|
{
|
|
loaded = false;
|
|
break;
|
|
}
|
|
renderModelManager->AddModel( lastModel );
|
|
localModels.Append( lastModel );
|
|
}
|
|
else if( type == "interareaportals" )
|
|
{
|
|
ReadBinaryAreaPortals( file );
|
|
}
|
|
else if( type == "nodes" )
|
|
{
|
|
ReadBinaryNodes( file );
|
|
}
|
|
else
|
|
{
|
|
idLib::Error( "Binary proc file failed, unexpected type %s\n", type.c_str() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !loaded )
|
|
{
|
|
|
|
src = new( TAG_RENDER ) idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
|
|
if( !src->IsLoaded() )
|
|
{
|
|
common->Printf( "idRenderWorldLocal::InitFromMap: %s not found\n", filename.c_str() );
|
|
ClearWorld();
|
|
return false;
|
|
}
|
|
|
|
|
|
mapName = name;
|
|
mapTimeStamp = currentTimeStamp;
|
|
|
|
// if we are writing a demo, archive the load command
|
|
if( common->WriteDemo() )
|
|
{
|
|
WriteLoadMap();
|
|
}
|
|
|
|
if( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) )
|
|
{
|
|
common->Printf( "idRenderWorldLocal::InitFromMap: bad id '%s' instead of '%s'\n", token.c_str(), PROC_FILE_ID );
|
|
delete src;
|
|
return false;
|
|
}
|
|
|
|
int numEntries = 0;
|
|
idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) );
|
|
if( outputFile != NULL )
|
|
{
|
|
int magic = BPROC_MAGIC;
|
|
outputFile->WriteBig( magic );
|
|
outputFile->WriteBig( numEntries );
|
|
outputFile->WriteString( mapName );
|
|
outputFile->WriteBig( mapTimeStamp );
|
|
}
|
|
|
|
// parse the file
|
|
while( 1 )
|
|
{
|
|
if( !src->ReadToken( &token ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
common->UpdateLevelLoadPacifier();
|
|
|
|
|
|
if( token == "model" )
|
|
{
|
|
lastModel = ParseModel( src, name, currentTimeStamp, outputFile );
|
|
|
|
// add it to the model manager list
|
|
renderModelManager->AddModel( lastModel );
|
|
|
|
// save it in the list to free when clearing this map
|
|
localModels.Append( lastModel );
|
|
|
|
numEntries++;
|
|
|
|
continue;
|
|
}
|
|
|
|
if( token == "shadowModel" )
|
|
{
|
|
lastModel = ParseShadowModel( src, outputFile );
|
|
|
|
// add it to the model manager list
|
|
renderModelManager->AddModel( lastModel );
|
|
|
|
// save it in the list to free when clearing this map
|
|
localModels.Append( lastModel );
|
|
|
|
numEntries++;
|
|
continue;
|
|
}
|
|
|
|
if( token == "interAreaPortals" )
|
|
{
|
|
ParseInterAreaPortals( src, outputFile );
|
|
|
|
numEntries++;
|
|
continue;
|
|
}
|
|
|
|
if( token == "nodes" )
|
|
{
|
|
ParseNodes( src, outputFile );
|
|
|
|
numEntries++;
|
|
continue;
|
|
}
|
|
|
|
src->Error( "idRenderWorldLocal::InitFromMap: bad token \"%s\"", token.c_str() );
|
|
}
|
|
|
|
delete src;
|
|
|
|
if( outputFile != NULL )
|
|
{
|
|
outputFile->Seek( 0, FS_SEEK_SET );
|
|
int magic = BPROC_MAGIC;
|
|
outputFile->WriteBig( magic );
|
|
outputFile->WriteBig( numEntries );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it was a trivial map without any areas, create a single area
|
|
if( !numPortalAreas )
|
|
{
|
|
ClearWorld();
|
|
}
|
|
|
|
// find the points where we can early-our of reference pushing into the BSP tree
|
|
CommonChildrenArea_r( &areaNodes[0] );
|
|
|
|
AddWorldModelEntities();
|
|
ClearPortalStates();
|
|
|
|
// done!
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idRenderWorldLocal::ClearPortalStates
|
|
=====================
|
|
*/
|
|
void idRenderWorldLocal::ClearPortalStates()
|
|
{
|
|
// all portals start off open
|
|
for( int i = 0; i < numInterAreaPortals; i++ )
|
|
{
|
|
doublePortals[i].blockingBits = PS_BLOCK_NONE;
|
|
}
|
|
|
|
// flood fill all area connections
|
|
for( int i = 0; i < numPortalAreas; i++ )
|
|
{
|
|
for( int j = 0; j < NUM_PORTAL_ATTRIBUTES; j++ )
|
|
{
|
|
connectedAreaNum++;
|
|
FloodConnectedAreas( &portalAreas[i], j );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idRenderWorldLocal::AddWorldModelEntities
|
|
=====================
|
|
*/
|
|
void idRenderWorldLocal::AddWorldModelEntities()
|
|
{
|
|
// add the world model for each portal area
|
|
// we can't just call AddEntityDef, because that would place the references
|
|
// based on the bounding box, rather than explicitly into the correct area
|
|
for( int i = 0; i < numPortalAreas; i++ )
|
|
{
|
|
common->UpdateLevelLoadPacifier();
|
|
|
|
|
|
idRenderEntityLocal* def = new( TAG_RENDER_ENTITY ) idRenderEntityLocal;
|
|
|
|
// try and reuse a free spot
|
|
int index = entityDefs.FindNull();
|
|
if( index == -1 )
|
|
{
|
|
index = entityDefs.Append( def );
|
|
}
|
|
else
|
|
{
|
|
entityDefs[index] = def;
|
|
}
|
|
|
|
def->index = index;
|
|
def->world = this;
|
|
|
|
def->parms.hModel = renderModelManager->FindModel( va( "_area%i", i ) );
|
|
if( def->parms.hModel->IsDefaultModel() || !def->parms.hModel->IsStaticWorldModel() )
|
|
{
|
|
common->Error( "idRenderWorldLocal::InitFromMap: bad area model lookup" );
|
|
}
|
|
|
|
idRenderModel* hModel = def->parms.hModel;
|
|
|
|
for( int j = 0; j < hModel->NumSurfaces(); j++ )
|
|
{
|
|
const modelSurface_t* surf = hModel->Surface( j );
|
|
|
|
if( surf->shader->GetName() == idStr( "textures/smf/portal_sky" ) )
|
|
{
|
|
def->needsPortalSky = true;
|
|
}
|
|
}
|
|
|
|
// the local and global reference bounds are the same for area models
|
|
def->localReferenceBounds = def->parms.hModel->Bounds();
|
|
def->globalReferenceBounds = def->parms.hModel->Bounds();
|
|
|
|
def->parms.axis[0][0] = 1.0f;
|
|
def->parms.axis[1][1] = 1.0f;
|
|
def->parms.axis[2][2] = 1.0f;
|
|
|
|
// in case an explicit shader is used on the world, we don't
|
|
// want it to have a 0 alpha or color
|
|
def->parms.shaderParms[0] = 1.0f;
|
|
def->parms.shaderParms[1] = 1.0f;
|
|
def->parms.shaderParms[2] = 1.0f;
|
|
def->parms.shaderParms[3] = 1.0f;
|
|
|
|
R_DeriveEntityData( def );
|
|
|
|
AddEntityRefToArea( def, &portalAreas[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CheckAreaForPortalSky
|
|
=====================
|
|
*/
|
|
bool idRenderWorldLocal::CheckAreaForPortalSky( int areaNum )
|
|
{
|
|
assert( areaNum >= 0 && areaNum < numPortalAreas );
|
|
|
|
for( areaReference_t* ref = portalAreas[areaNum].entityRefs.areaNext; ref->entity; ref = ref->areaNext )
|
|
{
|
|
assert( ref->area == &portalAreas[areaNum] );
|
|
|
|
if( ref->entity && ref->entity->needsPortalSky )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
ResetLocalRenderModels
|
|
=====================
|
|
*/
|
|
void idRenderWorldLocal::ResetLocalRenderModels()
|
|
{
|
|
localModels.Clear(); // Clear out the list when switching between expansion packs, so InitFromMap doesn't try to delete the list whose content has already been deleted by the model manager being re-started
|
|
} |