mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-24 05:11:21 +00:00
79ad905e05
Excluding 3rd party files.
682 lines
16 KiB
C++
682 lines
16 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "dmap.h"
|
|
|
|
//=================================================================================
|
|
|
|
|
|
#if 0
|
|
|
|
should we try and snap values very close to 0.5, 0.25, 0.125, etc?
|
|
|
|
do we write out normals, or just a "smooth shade" flag?
|
|
resolved: normals. otherwise adjacent facet shaded surfaces get their
|
|
vertexes merged, and they would have to be split apart before drawing
|
|
|
|
do we save out "wings" for shadow silhouette info?
|
|
|
|
|
|
#endif
|
|
|
|
static idFile *procFile;
|
|
|
|
#define AREANUM_DIFFERENT -2
|
|
/*
|
|
=============
|
|
PruneNodes_r
|
|
|
|
Any nodes that have all children with the same
|
|
area can be combined into a single leaf node
|
|
|
|
Returns the area number of all children, or
|
|
AREANUM_DIFFERENT if not the same.
|
|
=============
|
|
*/
|
|
int PruneNodes_r( node_t *node ) {
|
|
int a1, a2;
|
|
|
|
if ( node->planenum == PLANENUM_LEAF ) {
|
|
return node->area;
|
|
}
|
|
|
|
a1 = PruneNodes_r( node->children[0] );
|
|
a2 = PruneNodes_r( node->children[1] );
|
|
|
|
if ( a1 != a2 || a1 == AREANUM_DIFFERENT ) {
|
|
return AREANUM_DIFFERENT;
|
|
}
|
|
|
|
// free all the nodes below this point
|
|
FreeTreePortals_r( node->children[0] );
|
|
FreeTreePortals_r( node->children[1] );
|
|
FreeTree_r( node->children[0] );
|
|
FreeTree_r( node->children[1] );
|
|
|
|
// change this node to a leaf
|
|
node->planenum = PLANENUM_LEAF;
|
|
node->area = a1;
|
|
|
|
return a1;
|
|
}
|
|
|
|
static void WriteFloat( idFile *f, float v )
|
|
{
|
|
if ( idMath::Fabs(v - idMath::Rint(v)) < 0.001 ) {
|
|
f->WriteFloatString( "%i ", (int)idMath::Rint(v) );
|
|
}
|
|
else {
|
|
f->WriteFloatString( "%f ", v );
|
|
}
|
|
}
|
|
|
|
void Write1DMatrix( idFile *f, int x, float *m ) {
|
|
int i;
|
|
|
|
f->WriteFloatString( "( " );
|
|
|
|
for ( i = 0; i < x; i++ ) {
|
|
WriteFloat( f, m[i] );
|
|
}
|
|
|
|
f->WriteFloatString( ") " );
|
|
}
|
|
|
|
static int CountUniqueShaders( optimizeGroup_t *groups ) {
|
|
optimizeGroup_t *a, *b;
|
|
int count;
|
|
|
|
count = 0;
|
|
|
|
for ( a = groups ; a ; a = a->nextGroup ) {
|
|
if ( !a->triList ) { // ignore groups with no tris
|
|
continue;
|
|
}
|
|
for ( b = groups ; b != a ; b = b->nextGroup ) {
|
|
if ( !b->triList ) {
|
|
continue;
|
|
}
|
|
if ( a->material != b->material ) {
|
|
continue;
|
|
}
|
|
if ( a->mergeGroup != b->mergeGroup ) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if ( a == b ) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
MatchVert
|
|
==============
|
|
*/
|
|
#define XYZ_EPSILON 0.01
|
|
#define ST_EPSILON 0.001
|
|
#define COSINE_EPSILON 0.999
|
|
|
|
static bool MatchVert( const idDrawVert *a, const idDrawVert *b ) {
|
|
if ( idMath::Fabs( a->xyz[0] - b->xyz[0] ) > XYZ_EPSILON ) {
|
|
return false;
|
|
}
|
|
if ( idMath::Fabs( a->xyz[1] - b->xyz[1] ) > XYZ_EPSILON ) {
|
|
return false;
|
|
}
|
|
if ( idMath::Fabs( a->xyz[2] - b->xyz[2] ) > XYZ_EPSILON ) {
|
|
return false;
|
|
}
|
|
if ( idMath::Fabs( a->st[0] - b->st[0] ) > ST_EPSILON ) {
|
|
return false;
|
|
}
|
|
if ( idMath::Fabs( a->st[1] - b->st[1] ) > ST_EPSILON ) {
|
|
return false;
|
|
}
|
|
|
|
// if the normal is 0 (smoothed normals), consider it a match
|
|
if ( a->normal[0] == 0 && a->normal[1] == 0 && a->normal[2] == 0
|
|
&& b->normal[0] == 0 && b->normal[1] == 0 && b->normal[2] == 0 ) {
|
|
return true;
|
|
}
|
|
|
|
// otherwise do a dot-product cosine check
|
|
if ( DotProduct( a->normal, b->normal ) < COSINE_EPSILON ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ShareMapTriVerts
|
|
|
|
Converts independent triangles to shared vertex triangles
|
|
====================
|
|
*/
|
|
srfTriangles_t *ShareMapTriVerts( const mapTri_t *tris ) {
|
|
const mapTri_t *step;
|
|
int count;
|
|
int i, j;
|
|
int numVerts;
|
|
int numIndexes;
|
|
srfTriangles_t *uTri;
|
|
|
|
// unique the vertexes
|
|
count = CountTriList( tris );
|
|
|
|
uTri = R_AllocStaticTriSurf();
|
|
R_AllocStaticTriSurfVerts( uTri, count * 3 );
|
|
R_AllocStaticTriSurfIndexes( uTri, count * 3 );
|
|
|
|
numVerts = 0;
|
|
numIndexes = 0;
|
|
|
|
for ( step = tris ; step ; step = step->next ) {
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
const idDrawVert *dv;
|
|
|
|
dv = &step->v[i];
|
|
|
|
// search for a match
|
|
for ( j = 0 ; j < numVerts ; j++ ) {
|
|
if ( MatchVert( &uTri->verts[j], dv ) ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j == numVerts ) {
|
|
numVerts++;
|
|
uTri->verts[j].xyz = dv->xyz;
|
|
uTri->verts[j].normal = dv->normal;
|
|
uTri->verts[j].st[0] = dv->st[0];
|
|
uTri->verts[j].st[1] = dv->st[1];
|
|
}
|
|
|
|
uTri->indexes[numIndexes++] = j;
|
|
}
|
|
}
|
|
|
|
uTri->numVerts = numVerts;
|
|
uTri->numIndexes = numIndexes;
|
|
|
|
return uTri;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CleanupUTriangles
|
|
==================
|
|
*/
|
|
static void CleanupUTriangles( srfTriangles_t *tri ) {
|
|
// perform cleanup operations
|
|
|
|
R_RangeCheckIndexes( tri );
|
|
R_CreateSilIndexes( tri );
|
|
// R_RemoveDuplicatedTriangles( tri ); // this may remove valid overlapped transparent triangles
|
|
R_RemoveDegenerateTriangles( tri );
|
|
// R_RemoveUnusedVerts( tri );
|
|
|
|
R_FreeStaticTriSurfSilIndexes( tri );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
WriteUTriangles
|
|
|
|
Writes text verts and indexes to procfile
|
|
====================
|
|
*/
|
|
static void WriteUTriangles( const srfTriangles_t *uTris ) {
|
|
int col;
|
|
int i;
|
|
|
|
// emit this chain
|
|
procFile->WriteFloatString( "/* numVerts = */ %i /* numIndexes = */ %i\n",
|
|
uTris->numVerts, uTris->numIndexes );
|
|
|
|
// verts
|
|
col = 0;
|
|
for ( i = 0 ; i < uTris->numVerts ; i++ ) {
|
|
float vec[8];
|
|
const idDrawVert *dv;
|
|
|
|
dv = &uTris->verts[i];
|
|
|
|
vec[0] = dv->xyz[0];
|
|
vec[1] = dv->xyz[1];
|
|
vec[2] = dv->xyz[2];
|
|
vec[3] = dv->st[0];
|
|
vec[4] = dv->st[1];
|
|
vec[5] = dv->normal[0];
|
|
vec[6] = dv->normal[1];
|
|
vec[7] = dv->normal[2];
|
|
Write1DMatrix( procFile, 8, vec );
|
|
|
|
if ( ++col == 3 ) {
|
|
col = 0;
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
if ( col != 0 ) {
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
|
|
// indexes
|
|
col = 0;
|
|
for ( i = 0 ; i < uTris->numIndexes ; i++ ) {
|
|
procFile->WriteFloatString( "%i ", uTris->indexes[i] );
|
|
|
|
if ( ++col == 18 ) {
|
|
col = 0;
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
if ( col != 0 ) {
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
WriteShadowTriangles
|
|
|
|
Writes text verts and indexes to procfile
|
|
====================
|
|
*/
|
|
static void WriteShadowTriangles( const srfTriangles_t *tri ) {
|
|
int col;
|
|
int i;
|
|
|
|
// emit this chain
|
|
procFile->WriteFloatString( "/* numVerts = */ %i /* noCaps = */ %i /* noFrontCaps = */ %i /* numIndexes = */ %i /* planeBits = */ %i\n",
|
|
tri->numVerts, tri->numShadowIndexesNoCaps, tri->numShadowIndexesNoFrontCaps, tri->numIndexes, tri->shadowCapPlaneBits );
|
|
|
|
// verts
|
|
col = 0;
|
|
for ( i = 0 ; i < tri->numVerts ; i++ ) {
|
|
Write1DMatrix( procFile, 3, &tri->shadowVertexes[i].xyz[0] );
|
|
|
|
if ( ++col == 5 ) {
|
|
col = 0;
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
if ( col != 0 ) {
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
|
|
// indexes
|
|
col = 0;
|
|
for ( i = 0 ; i < tri->numIndexes ; i++ ) {
|
|
procFile->WriteFloatString( "%i ", tri->indexes[i] );
|
|
|
|
if ( ++col == 18 ) {
|
|
col = 0;
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
if ( col != 0 ) {
|
|
procFile->WriteFloatString( "\n" );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
GroupsAreSurfaceCompatible
|
|
|
|
Planes, texcoords, and groupLights can differ,
|
|
but the material and mergegroup must match
|
|
=======================
|
|
*/
|
|
static bool GroupsAreSurfaceCompatible( const optimizeGroup_t *a, const optimizeGroup_t *b ) {
|
|
if ( a->material != b->material ) {
|
|
return false;
|
|
}
|
|
if ( a->mergeGroup != b->mergeGroup ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
WriteOutputSurfaces
|
|
====================
|
|
*/
|
|
static void WriteOutputSurfaces( int entityNum, int areaNum ) {
|
|
mapTri_t *ambient, *copy;
|
|
int surfaceNum;
|
|
int numSurfaces;
|
|
idMapEntity *entity;
|
|
uArea_t *area;
|
|
optimizeGroup_t *group, *groupStep;
|
|
int i; // , j;
|
|
// int col;
|
|
srfTriangles_t *uTri;
|
|
// mapTri_t *tri;
|
|
typedef struct interactionTris_s {
|
|
struct interactionTris_s *next;
|
|
mapTri_t *triList;
|
|
mapLight_t *light;
|
|
} interactionTris_t;
|
|
|
|
interactionTris_t *interactions, *checkInter; //, *nextInter;
|
|
|
|
|
|
area = &dmapGlobals.uEntities[entityNum].areas[areaNum];
|
|
entity = dmapGlobals.uEntities[entityNum].mapEntity;
|
|
|
|
numSurfaces = CountUniqueShaders( area->groups );
|
|
|
|
|
|
if ( entityNum == 0 ) {
|
|
procFile->WriteFloatString( "model { /* name = */ \"_area%i\" /* numSurfaces = */ %i\n\n",
|
|
areaNum, numSurfaces );
|
|
} else {
|
|
const char *name;
|
|
|
|
entity->epairs.GetString( "name", "", &name );
|
|
if ( !name[0] ) {
|
|
common->Error( "Entity %i has surfaces, but no name key", entityNum );
|
|
}
|
|
procFile->WriteFloatString( "model { /* name = */ \"%s\" /* numSurfaces = */ %i\n\n",
|
|
name, numSurfaces );
|
|
}
|
|
|
|
surfaceNum = 0;
|
|
for ( group = area->groups ; group ; group = group->nextGroup ) {
|
|
if ( group->surfaceEmited ) {
|
|
continue;
|
|
}
|
|
|
|
// combine all groups compatible with this one
|
|
// usually several optimizeGroup_t can be combined into a single
|
|
// surface, even though they couldn't be merged together to save
|
|
// vertexes because they had different planes, texture coordinates, or lights.
|
|
// Different mergeGroups will stay in separate surfaces.
|
|
ambient = NULL;
|
|
|
|
// each light that illuminates any of the groups in the surface will
|
|
// get its own list of indexes out of the original surface
|
|
interactions = NULL;
|
|
|
|
for ( groupStep = group ; groupStep ; groupStep = groupStep->nextGroup ) {
|
|
if ( groupStep->surfaceEmited ) {
|
|
continue;
|
|
}
|
|
if ( !GroupsAreSurfaceCompatible( group, groupStep ) ) {
|
|
continue;
|
|
}
|
|
|
|
// copy it out to the ambient list
|
|
copy = CopyTriList( groupStep->triList );
|
|
ambient = MergeTriLists( ambient, copy );
|
|
groupStep->surfaceEmited = true;
|
|
|
|
// duplicate it into an interaction for each groupLight
|
|
for ( i = 0 ; i < groupStep->numGroupLights ; i++ ) {
|
|
for ( checkInter = interactions ; checkInter ; checkInter = checkInter->next ) {
|
|
if ( checkInter->light == groupStep->groupLights[i] ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( !checkInter ) {
|
|
// create a new interaction
|
|
checkInter = (interactionTris_t *)Mem_ClearedAlloc( sizeof( *checkInter ) );
|
|
checkInter->light = groupStep->groupLights[i];
|
|
checkInter->next = interactions;
|
|
interactions = checkInter;
|
|
}
|
|
copy = CopyTriList( groupStep->triList );
|
|
checkInter->triList = MergeTriLists( checkInter->triList, copy );
|
|
}
|
|
}
|
|
|
|
if ( !ambient ) {
|
|
continue;
|
|
}
|
|
|
|
if ( surfaceNum >= numSurfaces ) {
|
|
common->Error( "WriteOutputSurfaces: surfaceNum >= numSurfaces" );
|
|
}
|
|
|
|
procFile->WriteFloatString( "/* surface %i */ { ", surfaceNum );
|
|
surfaceNum++;
|
|
procFile->WriteFloatString( "\"%s\" ", ambient->material->GetName() );
|
|
|
|
uTri = ShareMapTriVerts( ambient );
|
|
FreeTriList( ambient );
|
|
|
|
CleanupUTriangles( uTri );
|
|
WriteUTriangles( uTri );
|
|
R_FreeStaticTriSurf( uTri );
|
|
|
|
procFile->WriteFloatString( "}\n\n" );
|
|
}
|
|
|
|
procFile->WriteFloatString( "}\n\n" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
WriteNode_r
|
|
|
|
===============
|
|
*/
|
|
static void WriteNode_r( node_t *node ) {
|
|
int child[2];
|
|
int i;
|
|
idPlane *plane;
|
|
|
|
if ( node->planenum == PLANENUM_LEAF ) {
|
|
// we shouldn't get here unless the entire world
|
|
// was a single leaf
|
|
procFile->WriteFloatString( "/* node 0 */ ( 0 0 0 0 ) -1 -1\n" );
|
|
return;
|
|
}
|
|
|
|
for ( i = 0 ; i < 2 ; i++ ) {
|
|
if ( node->children[i]->planenum == PLANENUM_LEAF ) {
|
|
child[i] = -1 - node->children[i]->area;
|
|
} else {
|
|
child[i] = node->children[i]->nodeNumber;
|
|
}
|
|
}
|
|
|
|
plane = &dmapGlobals.mapPlanes[node->planenum];
|
|
|
|
procFile->WriteFloatString( "/* node %i */ ", node->nodeNumber );
|
|
Write1DMatrix( procFile, 4, plane->ToFloatPtr() );
|
|
procFile->WriteFloatString( "%i %i\n", child[0], child[1] );
|
|
|
|
if ( child[0] > 0 ) {
|
|
WriteNode_r( node->children[0] );
|
|
}
|
|
if ( child[1] > 0 ) {
|
|
WriteNode_r( node->children[1] );
|
|
}
|
|
}
|
|
|
|
static int NumberNodes_r( node_t *node, int nextNumber ) {
|
|
if ( node->planenum == PLANENUM_LEAF ) {
|
|
return nextNumber;
|
|
}
|
|
node->nodeNumber = nextNumber;
|
|
nextNumber++;
|
|
nextNumber = NumberNodes_r( node->children[0], nextNumber );
|
|
nextNumber = NumberNodes_r( node->children[1], nextNumber );
|
|
|
|
return nextNumber;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
WriteOutputNodes
|
|
====================
|
|
*/
|
|
static void WriteOutputNodes( node_t *node ) {
|
|
int numNodes;
|
|
|
|
// prune unneeded nodes and count
|
|
PruneNodes_r( node );
|
|
numNodes = NumberNodes_r( node, 0 );
|
|
|
|
// output
|
|
procFile->WriteFloatString( "nodes { /* numNodes = */ %i\n\n", numNodes );
|
|
procFile->WriteFloatString( "/* node format is: ( planeVector ) positiveChild negativeChild */\n" );
|
|
procFile->WriteFloatString( "/* a child number of 0 is an opaque, solid area */\n" );
|
|
procFile->WriteFloatString( "/* negative child numbers are areas: (-1-child) */\n" );
|
|
|
|
WriteNode_r( node );
|
|
|
|
procFile->WriteFloatString( "}\n\n" );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
WriteOutputPortals
|
|
====================
|
|
*/
|
|
static void WriteOutputPortals( uEntity_t *e ) {
|
|
int i, j;
|
|
interAreaPortal_t *iap;
|
|
idWinding *w;
|
|
|
|
procFile->WriteFloatString( "interAreaPortals { /* numAreas = */ %i /* numIAP = */ %i\n\n",
|
|
e->numAreas, numInterAreaPortals );
|
|
procFile->WriteFloatString( "/* interAreaPortal format is: numPoints positiveSideArea negativeSideArea ( point) ... */\n" );
|
|
for ( i = 0 ; i < numInterAreaPortals ; i++ ) {
|
|
iap = &interAreaPortals[i];
|
|
w = iap->side->winding;
|
|
procFile->WriteFloatString("/* iap %i */ %i %i %i ", i, w->GetNumPoints(), iap->area0, iap->area1 );
|
|
for ( j = 0 ; j < w->GetNumPoints() ; j++ ) {
|
|
Write1DMatrix( procFile, 3, (*w)[j].ToFloatPtr() );
|
|
}
|
|
procFile->WriteFloatString("\n" );
|
|
}
|
|
|
|
procFile->WriteFloatString( "}\n\n" );
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
WriteOutputEntity
|
|
====================
|
|
*/
|
|
static void WriteOutputEntity( int entityNum ) {
|
|
int i;
|
|
uEntity_t *e;
|
|
|
|
e = &dmapGlobals.uEntities[entityNum];
|
|
|
|
if ( entityNum != 0 ) {
|
|
// entities may have enclosed, empty areas that we don't need to write out
|
|
if ( e->numAreas > 1 ) {
|
|
e->numAreas = 1;
|
|
}
|
|
}
|
|
|
|
for ( i = 0 ; i < e->numAreas ; i++ ) {
|
|
WriteOutputSurfaces( entityNum, i );
|
|
}
|
|
|
|
// we will completely skip the portals and nodes if it is a single area
|
|
if ( entityNum == 0 && e->numAreas > 1 ) {
|
|
// output the area portals
|
|
WriteOutputPortals( e );
|
|
|
|
// output the nodes
|
|
WriteOutputNodes( e->tree->headnode );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
WriteOutputFile
|
|
====================
|
|
*/
|
|
void WriteOutputFile( void ) {
|
|
int i;
|
|
uEntity_t *entity;
|
|
idStr qpath;
|
|
|
|
// write the file
|
|
common->Printf( "----- WriteOutputFile -----\n" );
|
|
|
|
sprintf( qpath, "%s." PROC_FILE_EXT, dmapGlobals.mapFileBase );
|
|
|
|
common->Printf( "writing %s\n", qpath.c_str() );
|
|
// _D3XP used fs_cdpath
|
|
procFile = fileSystem->OpenFileWrite( qpath, "fs_devpath" );
|
|
if ( !procFile ) {
|
|
common->Error( "Error opening %s", qpath.c_str() );
|
|
}
|
|
|
|
procFile->WriteFloatString( "%s\n\n", PROC_FILE_ID );
|
|
|
|
// write the entity models and information, writing entities first
|
|
for ( i=dmapGlobals.num_entities - 1 ; i >= 0 ; i-- ) {
|
|
entity = &dmapGlobals.uEntities[i];
|
|
|
|
if ( !entity->primitives ) {
|
|
continue;
|
|
}
|
|
|
|
WriteOutputEntity( i );
|
|
}
|
|
|
|
// write the shadow volumes
|
|
for ( i = 0 ; i < dmapGlobals.mapLights.Num() ; i++ ) {
|
|
mapLight_t *light = dmapGlobals.mapLights[i];
|
|
if ( !light->shadowTris ) {
|
|
continue;
|
|
}
|
|
|
|
procFile->WriteFloatString( "shadowModel { /* name = */ \"_prelight_%s\"\n\n", light->name );
|
|
WriteShadowTriangles( light->shadowTris );
|
|
procFile->WriteFloatString( "}\n\n" );
|
|
|
|
R_FreeStaticTriSurf( light->shadowTris );
|
|
light->shadowTris = NULL;
|
|
}
|
|
|
|
fileSystem->CloseFile( procFile );
|
|
}
|