1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 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.
23
24 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.
25
26 ===========================================================================
27 */
28
29 #include "sys/platform.h"
30 #include "framework/FileSystem.h"
31 #include "framework/Game.h"
32 #include "renderer/RenderWorld.h"
33
34 #include "tools/compilers/aas/AASBuild_local.h"
35
36 #define BFL_PATCH 0x1000
37
38 //===============================================================
39 //
40 // idAASBuild
41 //
42 //===============================================================
43
44 /*
45 ============
46 idAASBuild::idAASBuild
47 ============
48 */
idAASBuild(void)49 idAASBuild::idAASBuild( void ) {
50 file = NULL;
51 procNodes = NULL;
52 numProcNodes = 0;
53 numGravitationalSubdivisions = 0;
54 numMergedLeafNodes = 0;
55 numLedgeSubdivisions = 0;
56 ledgeMap = NULL;
57 }
58
59 /*
60 ============
61 idAASBuild::~idAASBuild
62 ============
63 */
~idAASBuild(void)64 idAASBuild::~idAASBuild( void ) {
65 Shutdown();
66 }
67
68 /*
69 ================
70 idAASBuild::Shutdown
71 ================
72 */
Shutdown(void)73 void idAASBuild::Shutdown( void ) {
74 aasSettings = NULL;
75 if ( file ) {
76 delete file;
77 file = NULL;
78 }
79 DeleteProcBSP();
80 numGravitationalSubdivisions = 0;
81 numMergedLeafNodes = 0;
82 numLedgeSubdivisions = 0;
83 ledgeList.Clear();
84 if ( ledgeMap ) {
85 delete ledgeMap;
86 ledgeMap = NULL;
87 }
88 }
89
90 /*
91 ================
92 idAASBuild::ParseProcNodes
93 ================
94 */
ParseProcNodes(idLexer * src)95 void idAASBuild::ParseProcNodes( idLexer *src ) {
96 int i;
97
98 src->ExpectTokenString( "{" );
99
100 idAASBuild::numProcNodes = src->ParseInt();
101 if ( idAASBuild::numProcNodes < 0 ) {
102 src->Error( "idAASBuild::ParseProcNodes: bad numProcNodes" );
103 }
104 idAASBuild::procNodes = (aasProcNode_t *)Mem_ClearedAlloc( idAASBuild::numProcNodes * sizeof( aasProcNode_t ) );
105
106 for ( i = 0; i < idAASBuild::numProcNodes; i++ ) {
107 aasProcNode_t *node;
108
109 node = &(idAASBuild::procNodes[i]);
110
111 src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
112 node->children[0] = src->ParseInt();
113 node->children[1] = src->ParseInt();
114 }
115
116 src->ExpectTokenString( "}" );
117 }
118
119 /*
120 ================
121 idAASBuild::LoadProcBSP
122 ================
123 */
LoadProcBSP(const char * name,ID_TIME_T minFileTime)124 bool idAASBuild::LoadProcBSP( const char *name, ID_TIME_T minFileTime ) {
125 idStr fileName;
126 idToken token;
127 idLexer *src;
128
129 // load it
130 fileName = name;
131 fileName.SetFileExtension( PROC_FILE_EXT );
132 src = new idLexer( fileName, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
133 if ( !src->IsLoaded() ) {
134 common->Warning("idAASBuild::LoadProcBSP: couldn't load %s", fileName.c_str() );
135 delete src;
136 return false;
137 }
138
139 // if the file is too old
140 if ( src->GetFileTime() < minFileTime ) {
141 delete src;
142 return false;
143 }
144
145 if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) {
146 common->Warning( "idAASBuild::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID );
147 delete src;
148 return false;
149 }
150
151 // parse the file
152 while ( 1 ) {
153 if ( !src->ReadToken( &token ) ) {
154 break;
155 }
156
157 if ( token == "model" ) {
158 src->SkipBracedSection();
159 continue;
160 }
161
162 if ( token == "shadowModel" ) {
163 src->SkipBracedSection();
164 continue;
165 }
166
167 if ( token == "interAreaPortals" ) {
168 src->SkipBracedSection();
169 continue;
170 }
171
172 if ( token == "nodes" ) {
173 idAASBuild::ParseProcNodes( src );
174 break;
175 }
176
177 src->Error( "idAASBuild::LoadProcBSP: bad token \"%s\"", token.c_str() );
178 }
179
180 delete src;
181
182 return true;
183 }
184
185 /*
186 ============
187 idAASBuild::DeleteProcBSP
188 ============
189 */
DeleteProcBSP(void)190 void idAASBuild::DeleteProcBSP( void ) {
191 if ( procNodes ) {
192 Mem_Free( procNodes );
193 procNodes = NULL;
194 }
195 numProcNodes = 0;
196 }
197
198 /*
199 ============
200 idAASBuild::ChoppedAwayByProcBSP
201 ============
202 */
ChoppedAwayByProcBSP(int nodeNum,idFixedWinding * w,const idVec3 & normal,const idVec3 & origin,const float radius)203 bool idAASBuild::ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) {
204 int res;
205 idFixedWinding back;
206 aasProcNode_t *node;
207 float dist;
208
209 do {
210 node = idAASBuild::procNodes + nodeNum;
211 dist = node->plane.Normal() * origin + node->plane[3];
212 if ( dist > radius ) {
213 res = SIDE_FRONT;
214 }
215 else if ( dist < -radius ) {
216 res = SIDE_BACK;
217 }
218 else {
219 res = w->Split( &back, node->plane, ON_EPSILON );
220 }
221 if ( res == SIDE_FRONT ) {
222 nodeNum = node->children[0];
223 }
224 else if ( res == SIDE_BACK ) {
225 nodeNum = node->children[1];
226 }
227 else if ( res == SIDE_ON ) {
228 // continue with the side the winding faces
229 if ( node->plane.Normal() * normal > 0.0f ) {
230 nodeNum = node->children[0];
231 }
232 else {
233 nodeNum = node->children[1];
234 }
235 }
236 else {
237 // if either node is not solid
238 if ( node->children[0] < 0 || node->children[1] < 0 ) {
239 return false;
240 }
241 // only recurse if the node is not solid
242 if ( node->children[1] > 0 ) {
243 if ( !idAASBuild::ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) {
244 return false;
245 }
246 }
247 nodeNum = node->children[0];
248 }
249 } while ( nodeNum > 0 );
250 if ( nodeNum < 0 ) {
251 return false;
252 }
253 return true;
254 }
255
256 /*
257 ============
258 idAASBuild::ClipBrushSidesWithProcBSP
259 ============
260 */
ClipBrushSidesWithProcBSP(idBrushList & brushList)261 void idAASBuild::ClipBrushSidesWithProcBSP( idBrushList &brushList ) {
262 int i, clippedSides;
263 idBrush *brush;
264 idFixedWinding neww;
265 idBounds bounds;
266 float radius;
267 idVec3 origin;
268
269 // if the .proc file has no BSP tree
270 if ( idAASBuild::procNodes == NULL ) {
271 return;
272 }
273
274 clippedSides = 0;
275 for ( brush = brushList.Head(); brush; brush = brush->Next() ) {
276 for ( i = 0; i < brush->GetNumSides(); i++ ) {
277
278 if ( !brush->GetSide(i)->GetWinding() ) {
279 continue;
280 }
281
282 // make a local copy of the winding
283 neww = *brush->GetSide(i)->GetWinding();
284 neww.GetBounds( bounds );
285 origin = (bounds[1] - bounds[0]) * 0.5f;
286 radius = origin.Length() + ON_EPSILON;
287 origin = bounds[0] + origin;
288
289 if ( ChoppedAwayByProcBSP( 0, &neww, brush->GetSide(i)->GetPlane().Normal(), origin, radius ) ) {
290 brush->GetSide(i)->SetFlag( SFL_USED_SPLITTER );
291 clippedSides++;
292 }
293 }
294 }
295
296 common->Printf( "%6d brush sides clipped\n", clippedSides );
297 }
298
299 /*
300 ============
301 idAASBuild::ContentsForAAS
302 ============
303 */
ContentsForAAS(int contents)304 int idAASBuild::ContentsForAAS( int contents ) {
305 int c;
306
307 if ( contents & ( CONTENTS_SOLID|CONTENTS_AAS_SOLID|CONTENTS_MONSTERCLIP ) ) {
308 return AREACONTENTS_SOLID;
309 }
310 c = 0;
311 if ( contents & CONTENTS_WATER ) {
312 c |= AREACONTENTS_WATER;
313 }
314 if ( contents & CONTENTS_AREAPORTAL ) {
315 c |= AREACONTENTS_CLUSTERPORTAL;
316 }
317 if ( contents & CONTENTS_AAS_OBSTACLE ) {
318 c |= AREACONTENTS_OBSTACLE;
319 }
320 return c;
321 }
322
323 /*
324 ============
325 idAASBuild::AddBrushForMapBrush
326 ============
327 */
AddBrushesForMapBrush(const idMapBrush * mapBrush,const idVec3 & origin,const idMat3 & axis,int entityNum,int primitiveNum,idBrushList brushList)328 idBrushList idAASBuild::AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
329 int contents, i;
330 idMapBrushSide *mapSide;
331 const idMaterial *mat;
332 idList<idBrushSide *> sideList;
333 idBrush *brush;
334 idPlane plane;
335
336 contents = 0;
337 for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
338 mapSide = mapBrush->GetSide(i);
339 mat = declManager->FindMaterial( mapSide->GetMaterial() );
340 contents |= mat->GetContentFlags();
341 plane = mapSide->GetPlane();
342 plane.FixDegeneracies( DEGENERATE_DIST_EPSILON );
343 sideList.Append( new idBrushSide( plane, -1 ) );
344 }
345
346 contents = ContentsForAAS( contents );
347 if ( !contents ) {
348 for ( i = 0; i < sideList.Num(); i++ ) {
349 delete sideList[i];
350 }
351 return brushList;
352 }
353
354 brush = new idBrush();
355 brush->SetContents( contents );
356
357 if ( !brush->FromSides( sideList ) ) {
358 common->Warning( "brush primitive %d on entity %d is degenerate", primitiveNum, entityNum );
359 delete brush;
360 return brushList;
361 }
362
363 brush->SetEntityNum( entityNum );
364 brush->SetPrimitiveNum( primitiveNum );
365 brush->Transform( origin, axis );
366 brushList.AddToTail( brush );
367
368 return brushList;
369 }
370
371 /*
372 ============
373 idAASBuild::AddBrushesForPatch
374 ============
375 */
AddBrushesForMapPatch(const idMapPatch * mapPatch,const idVec3 & origin,const idMat3 & axis,int entityNum,int primitiveNum,idBrushList brushList)376 idBrushList idAASBuild::AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ) {
377 int i, j, contents, validBrushes;
378 float dot;
379 int v1, v2, v3, v4;
380 idFixedWinding w;
381 idPlane plane;
382 idVec3 d1, d2;
383 idBrush *brush;
384 idSurface_Patch mesh;
385 const idMaterial *mat;
386
387 mat = declManager->FindMaterial( mapPatch->GetMaterial() );
388 contents = ContentsForAAS( mat->GetContentFlags() );
389
390 if ( !contents ) {
391 return brushList;
392 }
393
394 mesh = idSurface_Patch( *mapPatch );
395
396 // if the patch has an explicit number of subdivisions use it to avoid cracks
397 if ( mapPatch->GetExplicitlySubdivided() ) {
398 mesh.SubdivideExplicit( mapPatch->GetHorzSubdivisions(), mapPatch->GetVertSubdivisions(), false, true );
399 } else {
400 mesh.Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false );
401 }
402
403 validBrushes = 0;
404
405 for ( i = 0; i < mesh.GetWidth() - 1; i++ ) {
406 for ( j = 0; j < mesh.GetHeight() - 1; j++ ) {
407
408 v1 = j * mesh.GetWidth() + i;
409 v2 = v1 + 1;
410 v3 = v1 + mesh.GetWidth() + 1;
411 v4 = v1 + mesh.GetWidth();
412
413 d1 = mesh[v2].xyz - mesh[v1].xyz;
414 d2 = mesh[v3].xyz - mesh[v1].xyz;
415 plane.SetNormal( d1.Cross(d2) );
416 if ( plane.Normalize() != 0.0f ) {
417 plane.FitThroughPoint( mesh[v1].xyz );
418 dot = plane.Distance( mesh[v4].xyz );
419 // if we can turn it into a quad
420 if ( idMath::Fabs(dot) < 0.1f ) {
421 w.Clear();
422 w += mesh[v1].xyz;
423 w += mesh[v2].xyz;
424 w += mesh[v3].xyz;
425 w += mesh[v4].xyz;
426
427 brush = new idBrush();
428 brush->SetContents( contents );
429 if ( brush->FromWinding( w, plane ) ) {
430 brush->SetEntityNum( entityNum );
431 brush->SetPrimitiveNum( primitiveNum );
432 brush->SetFlag( BFL_PATCH );
433 brush->Transform( origin, axis );
434 brushList.AddToTail( brush );
435 validBrushes++;
436 }
437 else {
438 delete brush;
439 }
440 continue;
441 }
442 else {
443 // create one of the triangles
444 w.Clear();
445 w += mesh[v1].xyz;
446 w += mesh[v2].xyz;
447 w += mesh[v3].xyz;
448
449 brush = new idBrush();
450 brush->SetContents( contents );
451 if ( brush->FromWinding( w, plane ) ) {
452 brush->SetEntityNum( entityNum );
453 brush->SetPrimitiveNum( primitiveNum );
454 brush->SetFlag( BFL_PATCH );
455 brush->Transform( origin, axis );
456 brushList.AddToTail( brush );
457 validBrushes++;
458 }
459 else {
460 delete brush;
461 }
462 }
463 }
464 // create the other triangle
465 d1 = mesh[v3].xyz - mesh[v1].xyz;
466 d2 = mesh[v4].xyz - mesh[v1].xyz;
467 plane.SetNormal( d1.Cross(d2) );
468 if ( plane.Normalize() != 0.0f ) {
469 plane.FitThroughPoint( mesh[v1].xyz );
470
471 w.Clear();
472 w += mesh[v1].xyz;
473 w += mesh[v3].xyz;
474 w += mesh[v4].xyz;
475
476 brush = new idBrush();
477 brush->SetContents( contents );
478 if ( brush->FromWinding( w, plane ) ) {
479 brush->SetEntityNum( entityNum );
480 brush->SetPrimitiveNum( primitiveNum );
481 brush->SetFlag( BFL_PATCH );
482 brush->Transform( origin, axis );
483 brushList.AddToTail( brush );
484 validBrushes++;
485 }
486 else {
487 delete brush;
488 }
489 }
490 }
491 }
492
493 if ( !validBrushes ) {
494 common->Warning( "patch primitive %d on entity %d is completely degenerate", primitiveNum, entityNum );
495 }
496
497 return brushList;
498 }
499
500 /*
501 ============
502 idAASBuild::AddBrushesForMapEntity
503 ============
504 */
AddBrushesForMapEntity(const idMapEntity * mapEnt,int entityNum,idBrushList brushList)505 idBrushList idAASBuild::AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList ) {
506 int i;
507 idVec3 origin;
508 idMat3 axis;
509
510 if ( mapEnt->GetNumPrimitives() < 1 ) {
511 return brushList;
512 }
513
514 mapEnt->epairs.GetVector( "origin", "0 0 0", origin );
515 if ( !mapEnt->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) {
516 float angle = mapEnt->epairs.GetFloat( "angle" );
517 if ( angle != 0.0f ) {
518 axis = idAngles( 0.0f, angle, 0.0f ).ToMat3();
519 } else {
520 axis.Identity();
521 }
522 }
523
524 for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
525 idMapPrimitive *mapPrim;
526
527 mapPrim = mapEnt->GetPrimitive(i);
528 if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
529 brushList = AddBrushesForMapBrush( static_cast<idMapBrush*>(mapPrim), origin, axis, entityNum, i, brushList );
530 continue;
531 }
532 if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
533 if ( aasSettings->usePatches ) {
534 brushList = AddBrushesForMapPatch( static_cast<idMapPatch*>(mapPrim), origin, axis, entityNum, i, brushList );
535 }
536 continue;
537 }
538 }
539
540 return brushList;
541 }
542
543 /*
544 ============
545 idAASBuild::AddBrushesForMapFile
546 ============
547 */
AddBrushesForMapFile(const idMapFile * mapFile,idBrushList brushList)548 idBrushList idAASBuild::AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList ) {
549 int i;
550
551 common->Printf( "[Brush Load]\n" );
552
553 brushList = AddBrushesForMapEntity( mapFile->GetEntity( 0 ), 0, brushList );
554
555 for ( i = 1; i < mapFile->GetNumEntities(); i++ ) {
556 const char *classname = mapFile->GetEntity( i )->epairs.GetString( "classname" );
557
558 if ( idStr::Icmp( classname, "func_aas_obstacle" ) == 0 ) {
559 brushList = AddBrushesForMapEntity( mapFile->GetEntity( i ), i, brushList );
560 }
561 }
562
563 common->Printf( "%6d brushes\n", brushList.Num() );
564
565 return brushList;
566 }
567
568 /*
569 ============
570 idAASBuild::CheckForEntities
571 ============
572 */
CheckForEntities(const idMapFile * mapFile,idStrList & entityClassNames) const573 bool idAASBuild::CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const {
574 int i;
575 idStr classname;
576
577 com_editors |= EDITOR_AAS;
578
579 for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
580 if ( !mapFile->GetEntity(i)->epairs.GetString( "classname", "", classname ) ) {
581 continue;
582 }
583
584 if ( aasSettings->ValidEntity( classname ) ) {
585 entityClassNames.AddUnique( classname );
586 }
587 }
588
589 com_editors &= ~EDITOR_AAS;
590
591 return ( entityClassNames.Num() != 0 );
592 }
593
594 /*
595 ============
596 MergeAllowed
597 ============
598 */
MergeAllowed(idBrush * b1,idBrush * b2)599 bool MergeAllowed( idBrush *b1, idBrush *b2 ) {
600 return ( b1->GetContents() == b2->GetContents() && !( ( b1->GetFlags() | b2->GetFlags() ) & BFL_PATCH ) );
601 }
602
603 /*
604 ============
605 ExpandedChopAllowed
606 ============
607 */
ExpandedChopAllowed(idBrush * b1,idBrush * b2)608 bool ExpandedChopAllowed( idBrush *b1, idBrush *b2 ) {
609 return ( b1->GetContents() == b2->GetContents() );
610 }
611
612 /*
613 ============
614 ExpandedMergeAllowed
615 ============
616 */
ExpandedMergeAllowed(idBrush * b1,idBrush * b2)617 bool ExpandedMergeAllowed( idBrush *b1, idBrush *b2 ) {
618 return ( b1->GetContents() == b2->GetContents() );
619 }
620
621 /*
622 ============
623 idAASBuild::ChangeMultipleBoundingBoxContents
624 ============
625 */
ChangeMultipleBoundingBoxContents_r(idBrushBSPNode * node,int mask)626 void idAASBuild::ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask ) {
627 while( node ) {
628 if ( !( node->GetContents() & mask ) ) {
629 node->SetContents( node->GetContents() & ~AREACONTENTS_SOLID );
630 }
631 ChangeMultipleBoundingBoxContents_r( node->GetChild( 0 ), mask );
632 node = node->GetChild( 1 );
633 }
634 }
635
636 /*
637 ============
638 idAASBuild::Build
639 ============
640 */
Build(const idStr & fileName,const idAASSettings * settings)641 bool idAASBuild::Build( const idStr &fileName, const idAASSettings *settings ) {
642 int i, bit, mask, startTime;
643 idMapFile * mapFile;
644 idBrushList brushList;
645 idList<idBrushList*> expandedBrushes;
646 idBrush *b;
647 idBrushBSP bsp;
648 idStr name;
649 idAASReach reach;
650 idAASCluster cluster;
651 idStrList entityClassNames;
652
653 startTime = Sys_Milliseconds();
654
655 Shutdown();
656
657 aasSettings = settings;
658
659 name = fileName;
660 name.SetFileExtension( "map" );
661
662 mapFile = new idMapFile;
663 if ( !mapFile->Parse( name ) ) {
664 delete mapFile;
665 common->Error( "Couldn't load map file: '%s'", name.c_str() );
666 return false;
667 }
668
669 // check if this map has any entities that use this AAS file
670 if ( !CheckForEntities( mapFile, entityClassNames ) ) {
671 delete mapFile;
672 common->Printf( "no entities in map that use %s\n", settings->fileExtension.c_str() );
673 return true;
674 }
675
676 // load map file brushes
677 brushList = AddBrushesForMapFile( mapFile, brushList );
678
679 // if empty map
680 if ( brushList.Num() == 0 ) {
681 delete mapFile;
682 common->Error( "%s is empty", name.c_str() );
683 return false;
684 }
685
686 // merge as many brushes as possible before expansion
687 brushList.Merge( MergeAllowed );
688
689 // if there is a .proc file newer than the .map file
690 if ( LoadProcBSP( fileName, mapFile->GetFileTime() ) ) {
691 ClipBrushSidesWithProcBSP( brushList );
692 DeleteProcBSP();
693 }
694
695 // make copies of the brush list
696 expandedBrushes.Append( &brushList );
697 for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
698 expandedBrushes.Append( brushList.Copy() );
699 }
700
701 // expand brushes for the axial bounding boxes
702 mask = AREACONTENTS_SOLID;
703 for ( i = 0; i < expandedBrushes.Num(); i++ ) {
704 for ( b = expandedBrushes[i]->Head(); b; b = b->Next() ) {
705 b->ExpandForAxialBox( aasSettings->boundingBoxes[i] );
706 bit = 1 << ( i + AREACONTENTS_BBOX_BIT );
707 mask |= bit;
708 b->SetContents( b->GetContents() | bit );
709 }
710 }
711
712 // move all brushes back into the original list
713 for ( i = 1; i < aasSettings->numBoundingBoxes; i++ ) {
714 brushList.AddToTail( *expandedBrushes[i] );
715 delete expandedBrushes[i];
716 }
717
718 if ( aasSettings->writeBrushMap ) {
719 bsp.WriteBrushMap( fileName, "_" + aasSettings->fileExtension, AREACONTENTS_SOLID );
720 }
721
722 // build BSP tree from brushes
723 bsp.Build( brushList, AREACONTENTS_SOLID, ExpandedChopAllowed, ExpandedMergeAllowed );
724
725 // only solid nodes with all bits set for all bounding boxes need to stay solid
726 ChangeMultipleBoundingBoxContents_r( bsp.GetRootNode(), mask );
727
728 // portalize the bsp tree
729 bsp.Portalize();
730
731 // remove subspaces not reachable by entities
732 if ( !bsp.RemoveOutside( mapFile, AREACONTENTS_SOLID, entityClassNames ) ) {
733 bsp.LeakFile( name );
734 delete mapFile;
735 common->Printf( "%s has no outside", name.c_str() );
736 return false;
737 }
738
739 // gravitational subdivision
740 GravitationalSubdivision( bsp );
741
742 // merge portals where possible
743 bsp.MergePortals( AREACONTENTS_SOLID );
744
745 // melt portal windings
746 bsp.MeltPortals( AREACONTENTS_SOLID );
747
748 if ( aasSettings->writeBrushMap ) {
749 WriteLedgeMap( fileName, "_" + aasSettings->fileExtension + "_ledge" );
750 }
751
752 // ledge subdivisions
753 LedgeSubdivision( bsp );
754
755 // merge leaf nodes
756 MergeLeafNodes( bsp );
757
758 // merge portals where possible
759 bsp.MergePortals( AREACONTENTS_SOLID );
760
761 // melt portal windings
762 bsp.MeltPortals( AREACONTENTS_SOLID );
763
764 // store the file from the bsp tree
765 StoreFile( bsp );
766 file->settings = *aasSettings;
767
768 // calculate reachability
769 reach.Build( mapFile, file );
770
771 // build clusters
772 cluster.Build( file );
773
774 // optimize the file
775 if ( !aasSettings->noOptimize ) {
776 file->Optimize();
777 }
778
779 // write the file
780 name.SetFileExtension( aasSettings->fileExtension );
781 file->Write( name, mapFile->GetGeometryCRC() );
782
783 // delete the map file
784 delete mapFile;
785
786 common->Printf( "%6d seconds to create AAS\n", (Sys_Milliseconds() - startTime) / 1000 );
787
788 return true;
789 }
790
791 /*
792 ============
793 idAASBuild::BuildReachability
794 ============
795 */
BuildReachability(const idStr & fileName,const idAASSettings * settings)796 bool idAASBuild::BuildReachability( const idStr &fileName, const idAASSettings *settings ) {
797 int startTime;
798 idMapFile * mapFile;
799 idStr name;
800 idAASReach reach;
801 idAASCluster cluster;
802
803 startTime = Sys_Milliseconds();
804
805 aasSettings = settings;
806
807 name = fileName;
808 name.SetFileExtension( "map" );
809
810 mapFile = new idMapFile;
811 if ( !mapFile->Parse( name ) ) {
812 delete mapFile;
813 common->Error( "Couldn't load map file: '%s'", name.c_str() );
814 return false;
815 }
816
817 file = new idAASFileLocal();
818
819 name.SetFileExtension( aasSettings->fileExtension );
820 if ( !file->Load( name, 0 ) ) {
821 delete mapFile;
822 common->Error( "Couldn't load AAS file: '%s'", name.c_str() );
823 return false;
824 }
825
826 file->settings = *aasSettings;
827
828 // calculate reachability
829 reach.Build( mapFile, file );
830
831 // build clusters
832 cluster.Build( file );
833
834 // write the file
835 file->Write( name, mapFile->GetGeometryCRC() );
836
837 // delete the map file
838 delete mapFile;
839
840 common->Printf( "%6d seconds to calculate reachability\n", (Sys_Milliseconds() - startTime) / 1000 );
841
842 return true;
843 }
844
845 /*
846 ============
847 ParseOptions
848 ============
849 */
ParseOptions(const idCmdArgs & args,idAASSettings & settings)850 int ParseOptions( const idCmdArgs &args, idAASSettings &settings ) {
851 int i;
852 idStr str;
853
854 for ( i = 1; i < args.Argc(); i++ ) {
855
856 str = args.Argv( i );
857 str.StripLeading( '-' );
858
859 if ( str.Icmp( "usePatches" ) == 0 ) {
860 settings.usePatches = true;
861 common->Printf( "usePatches = true\n" );
862 } else if ( str.Icmp( "writeBrushMap" ) == 0 ) {
863 settings.writeBrushMap = true;
864 common->Printf( "writeBrushMap = true\n" );
865 } else if ( str.Icmp( "playerFlood" ) == 0 ) {
866 settings.playerFlood = true;
867 common->Printf( "playerFlood = true\n" );
868 } else if ( str.Icmp( "noOptimize" ) == 0 ) {
869 settings.noOptimize = true;
870 common->Printf( "noOptimize = true\n" );
871 }
872 }
873 return args.Argc() - 1;
874 }
875
876 /*
877 ============
878 RunAAS_f
879 ============
880 */
RunAAS_f(const idCmdArgs & args)881 void RunAAS_f( const idCmdArgs &args ) {
882 int i;
883 idAASBuild aas;
884 idAASSettings settings;
885 idStr mapName;
886
887 if ( args.Argc() <= 1 ) {
888 common->Printf( "runAAS [options] <mapfile>\n"
889 "options:\n"
890 " -usePatches = use bezier patches for collision detection.\n"
891 " -writeBrushMap = write a brush map with the AAS geometry.\n"
892 " -playerFlood = use player spawn points as valid AAS positions.\n" );
893 return;
894 }
895
896 common->ClearWarnings( "compiling AAS" );
897
898 common->SetRefreshOnPrint( true );
899
900 // get the aas settings definitions
901 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
902 if ( !dict ) {
903 common->Error( "Unable to find entityDef for 'aas_types'" );
904 }
905
906 const idKeyValue *kv = dict->MatchPrefix( "type" );
907 while( kv != NULL ) {
908 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
909 if ( !settingsDict ) {
910 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
911 } else {
912 settings.FromDict( kv->GetValue(), settingsDict );
913 i = ParseOptions( args, settings );
914 mapName = args.Argv(i);
915 mapName.BackSlashesToSlashes();
916 if ( mapName.Icmpn( "maps/", 4 ) != 0 ) {
917 mapName = "maps/" + mapName;
918 }
919 aas.Build( mapName, &settings );
920 }
921
922 kv = dict->MatchPrefix( "type", kv );
923 if ( kv ) {
924 common->Printf( "=======================================================\n" );
925 }
926 }
927 common->SetRefreshOnPrint( false );
928 common->PrintWarnings();
929 }
930
931 /*
932 ============
933 RunAASDir_f
934 ============
935 */
RunAASDir_f(const idCmdArgs & args)936 void RunAASDir_f( const idCmdArgs &args ) {
937 int i;
938 idAASBuild aas;
939 idAASSettings settings;
940 idFileList *mapFiles;
941
942 if ( args.Argc() <= 1 ) {
943 common->Printf( "runAASDir <folder>\n" );
944 return;
945 }
946
947 common->ClearWarnings( "compiling AAS" );
948
949 common->SetRefreshOnPrint( true );
950
951 // get the aas settings definitions
952 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
953 if ( !dict ) {
954 common->Error( "Unable to find entityDef for 'aas_types'" );
955 }
956
957 // scan for .map files
958 mapFiles = fileSystem->ListFiles( idStr("maps/") + args.Argv(1), ".map" );
959
960 // create AAS files for all the .map files
961 for ( i = 0; i < mapFiles->GetNumFiles(); i++ ) {
962 if ( i ) {
963 common->Printf( "=======================================================\n" );
964 }
965
966 const idKeyValue *kv = dict->MatchPrefix( "type" );
967 while( kv != NULL ) {
968 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
969 if ( !settingsDict ) {
970 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
971 } else {
972 settings.FromDict( kv->GetValue(), settingsDict );
973 aas.Build( idStr( "maps/" ) + args.Argv( 1 ) + "/" + mapFiles->GetFile( i ), &settings );
974 }
975
976 kv = dict->MatchPrefix( "type", kv );
977 if ( kv ) {
978 common->Printf( "=======================================================\n" );
979 }
980 }
981 }
982
983 fileSystem->FreeFileList( mapFiles );
984
985 common->SetRefreshOnPrint( false );
986 common->PrintWarnings();
987 }
988
989 /*
990 ============
991 RunReach_f
992 ============
993 */
RunReach_f(const idCmdArgs & args)994 void RunReach_f( const idCmdArgs &args ) {
995 int i;
996 idAASBuild aas;
997 idAASSettings settings;
998
999 if ( args.Argc() <= 1 ) {
1000 common->Printf( "runReach [options] <mapfile>\n" );
1001 return;
1002 }
1003
1004 common->ClearWarnings( "calculating AAS reachability" );
1005
1006 common->SetRefreshOnPrint( true );
1007
1008 // get the aas settings definitions
1009 const idDict *dict = gameEdit->FindEntityDefDict( "aas_types", false );
1010 if ( !dict ) {
1011 common->Error( "Unable to find entityDef for 'aas_types'" );
1012 }
1013
1014 const idKeyValue *kv = dict->MatchPrefix( "type" );
1015 while( kv != NULL ) {
1016 const idDict *settingsDict = gameEdit->FindEntityDefDict( kv->GetValue(), false );
1017 if ( !settingsDict ) {
1018 common->Warning( "Unable to find '%s' in def/aas.def", kv->GetValue().c_str() );
1019 } else {
1020 settings.FromDict( kv->GetValue(), settingsDict );
1021 i = ParseOptions( args, settings );
1022 aas.BuildReachability( idStr("maps/") + args.Argv(i), &settings );
1023 }
1024
1025 kv = dict->MatchPrefix( "type", kv );
1026 if ( kv ) {
1027 common->Printf( "=======================================================\n" );
1028 }
1029 }
1030
1031 common->SetRefreshOnPrint( false );
1032 common->PrintWarnings();
1033 }
1034