4a2b9c097969c4fa9766ab0ffd3cda41d5b211b5
[xonotic/netradiant.git] / contrib / bobtoolz / DBrush.cpp
1 /*
2    BobToolz plugin for GtkRadiant
3    Copyright (C) 2001 Gordon Biggans
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 // DBrush.cpp: implementation of the DBrush class.
21 //
22 //////////////////////////////////////////////////////////////////////
23
24 #include "DBrush.h"
25
26 #ifdef WIN32
27 #pragma warning(disable : 4786)
28 #endif
29
30 #include <list>
31 #include "str.h"
32
33 #include "DPoint.h"
34 #include "DPlane.h"
35 #include "DEPair.h"
36 #include "DPatch.h"
37 #include "DEntity.h"
38 #include "DWinding.h"
39
40 #include "dialogs/dialogs-gtk.h"
41
42 #include "misc.h"
43
44 #include "iundo.h"
45
46 #include "generic/referencecounted.h"
47
48 #include "scenelib.h"
49
50 //////////////////////////////////////////////////////////////////////
51 // Construction/Destruction
52 //////////////////////////////////////////////////////////////////////
53
54 DBrush::DBrush( int ID ){
55         m_nBrushID = ID;
56         bBoundsBuilt = false;
57         QER_entity = NULL;
58         QER_brush = NULL;
59 }
60
61 DBrush::~DBrush(){
62         ClearFaces();
63         ClearPoints();
64 }
65
66 //////////////////////////////////////////////////////////////////////
67 // Implementation
68 //////////////////////////////////////////////////////////////////////
69
70 DPlane* DBrush::AddFace( const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData ){
71 #ifdef _DEBUG
72 //      Sys_Printf("(%f %f %f) (%f %f %f) (%f %f %f)\n", va[0], va[1], va[2], vb[0], vb[1], vb[2], vc[0], vc[1], vc[2]);
73 #endif
74         bBoundsBuilt = false;
75         DPlane* newFace = new DPlane( va, vb, vc, texData );
76         faceList.push_back( newFace );
77
78         return newFace;
79 }
80
81 int DBrush::BuildPoints(){
82         ClearPoints();
83
84         if ( faceList.size() <= 3 ) {  // if less than 3 faces, there can be no points
85                 return 0;                   // with only 3 faces u can't have a bounded soild
86
87         }
88         for ( std::list<DPlane *>::const_iterator p1 = faceList.begin(); p1 != faceList.end(); p1++ )
89         {
90                 std::list<DPlane *>::const_iterator p2 = p1;
91                 for ( p2++; p2 != faceList.end(); p2++ )
92                 {
93                         std::list<DPlane *>::const_iterator p3 = p2;
94                         for ( p3++; p3 != faceList.end(); p3++ )
95                         {
96                                 vec3_t pnt;
97                                 if ( ( *p1 )->PlaneIntersection( *p2, *p3, pnt ) ) {
98                                         int pos = PointPosition( pnt );
99
100                                         if ( pos == POINT_IN_BRUSH ) { // ???? shouldn't happen here
101                                                 globalErrorStream() << "ERROR:: Build Brush Points: Point IN brush!!!\n";
102                                         }
103                                         else if ( pos == POINT_ON_BRUSH ) { // normal point
104                                                 if ( !HasPoint( pnt ) ) {
105                                                         AddPoint( pnt );
106                                                 }
107 /*                                              else
108                             Sys_Printf("Duplicate Point Found, pyramids ahoy!!!!!\n");*/
109                                                 // point lies on more that 3 planes
110                                         }
111
112                                         // otherwise point is removed due to another plane..
113
114                                         // Sys_Printf("(%f, %f, %f)\n", pnt[0], pnt[1], pnt[2]);
115                                 }
116                         }
117                 }
118         }
119
120 #ifdef _DEBUG
121 //      Sys_Printf("%i points on brush\n", pointList.size());
122 #endif
123
124         return static_cast<int>( pointList.size() );
125 }
126
127 void DBrush_addFace( DBrush& brush, const _QERFaceData& faceData ){
128         brush.AddFace( vector3_to_array( faceData.m_p0 ), vector3_to_array( faceData.m_p1 ), vector3_to_array( faceData.m_p2 ), 0 );
129 }
130 typedef ReferenceCaller1<DBrush, const _QERFaceData&, DBrush_addFace> DBrushAddFaceCaller;
131
132 void DBrush_addFaceTextured( DBrush& brush, const _QERFaceData& faceData ){
133         brush.AddFace( vector3_to_array( faceData.m_p0 ), vector3_to_array( faceData.m_p1 ), vector3_to_array( faceData.m_p2 ), &faceData );
134 }
135 typedef ReferenceCaller1<DBrush, const _QERFaceData&, DBrush_addFaceTextured> DBrushAddFaceTexturedCaller;
136
137 void DBrush::LoadFromBrush( scene::Instance& brush, bool textured ){
138         ClearFaces();
139         ClearPoints();
140
141         GlobalBrushCreator().Brush_forEachFace( brush.path().top(), textured ? BrushFaceDataCallback( DBrushAddFaceTexturedCaller( *this ) ) : BrushFaceDataCallback( DBrushAddFaceCaller( *this ) ) );
142
143         QER_entity = brush.path().parent().get_pointer();
144         QER_brush = brush.path().top().get_pointer();
145 }
146
147 int DBrush::PointPosition( vec3_t pnt ){
148         int state = POINT_IN_BRUSH; // if nothing happens point is inside brush
149
150         for ( std::list<DPlane *>::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
151         {
152                 float dist = ( *chkPlane )->DistanceToPoint( pnt );
153
154                 if ( dist > MAX_ROUND_ERROR ) {
155                         return POINT_OUT_BRUSH;     // if point is in front of plane, it CANT be in the brush
156                 }
157                 else if ( fabs( dist ) < MAX_ROUND_ERROR ) {
158                         state = POINT_ON_BRUSH;     // if point is ON plane point is either ON the brush
159                 }
160                 // or outside it, it can no longer be in it
161         }
162
163         return state;
164 }
165
166 void DBrush::ClearPoints(){
167         for ( std::list<DPoint *>::const_iterator deadPoint = pointList.begin(); deadPoint != pointList.end(); deadPoint++ ) {
168                 delete *deadPoint;
169         }
170         pointList.clear();
171 }
172
173 void DBrush::ClearFaces(){
174         bBoundsBuilt = false;
175         for ( std::list<DPlane *>::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ )
176         {
177                 delete *deadPlane;
178         }
179         faceList.clear();
180 }
181
182 void DBrush::AddPoint( vec3_t pnt ){
183         DPoint* newPoint = new DPoint;
184         VectorCopy( pnt, newPoint->_pnt );
185         pointList.push_back( newPoint );
186 }
187
188 bool DBrush::HasPoint( vec3_t pnt ){
189         for ( std::list<DPoint *>::const_iterator chkPoint = pointList.begin(); chkPoint != pointList.end(); chkPoint++ )
190         {
191                 if ( **chkPoint == pnt ) {
192                         return true;
193                 }
194         }
195
196         return false;
197 }
198
199 int DBrush::RemoveRedundantPlanes(){
200         int cnt = 0;
201         std::list<DPlane *>::iterator chkPlane;
202
203         // find duplicate planes
204         std::list<DPlane *>::iterator p1 = faceList.begin();
205
206         while ( p1 != faceList.end() )
207         {
208                 std::list<DPlane *>::iterator p2 = p1;
209
210                 for ( p2++; p2 != faceList.end(); p2++ )
211                 {
212                         if ( **p1 == **p2 ) {
213                                 if ( !strcmp( ( *p1 )->m_shader.c_str(), "textures/common/caulk" ) ) {
214                                         delete *p1;
215                                         p1 = faceList.erase( p1 );    // duplicate plane
216                                 }
217                                 else
218                                 {
219                                         delete *p2;
220                                         p2 = faceList.erase( p2 );    // duplicate plane
221                                 }
222
223                                 cnt++;
224                                 break;
225                         }
226                 }
227
228                 if ( p2 == faceList.end() ) {
229                         p1++;
230                 }
231         }
232
233         //+djbob kill planes with bad normal, they are more of a nuisance than losing a brush
234         chkPlane = faceList.begin();
235         while ( chkPlane != faceList.end() )
236         {
237                 if ( VectorLength( ( *chkPlane )->normal ) == 0 ) { // plane has bad normal
238                         delete *chkPlane;
239                         chkPlane = faceList.erase( chkPlane );
240                         cnt++;
241                 }
242                 else {
243                         chkPlane++;
244                 }
245         }
246         //-djbob
247
248         if ( pointList.size() == 0 ) { // if points may not have been built, build them
249 /*              if(BuildPoints() == 0)  // just let the planes die if they are all bad
250             return cnt;*/
251                 BuildPoints();
252         }
253
254         chkPlane = faceList.begin();
255         while ( chkPlane != faceList.end() )
256         {
257                 if ( ( *chkPlane )->IsRedundant( pointList ) ) { // checks that plane "0wnz" :), 3 or more points
258                         delete *chkPlane;
259                         chkPlane = faceList.erase( chkPlane );
260                         cnt++;
261                 }
262                 else{
263                         chkPlane++;
264                 }
265         }
266
267         return cnt;
268 }
269
270 bool DBrush::GetBounds( vec3_t min, vec3_t max ){
271         BuildBounds();
272
273         if ( !bBoundsBuilt ) {
274                 return false;
275         }
276
277         VectorCopy( bbox_min, min );
278         VectorCopy( bbox_max, max );
279
280         return true;
281 }
282
283 bool DBrush::BBoxCollision( DBrush* chkBrush ){
284         vec3_t min1, min2;
285         vec3_t max1, max2;
286
287         GetBounds( min1, max1 );
288         chkBrush->GetBounds( min2, max2 );
289
290         if ( min1[0] >= max2[0] ) {
291                 return false;
292         }
293         if ( min1[1] >= max2[1] ) {
294                 return false;
295         }
296         if ( min1[2] >= max2[2] ) {
297                 return false;
298         }
299
300         if ( max1[0] <= min2[0] ) {
301                 return false;
302         }
303         if ( max1[1] <= min2[1] ) {
304                 return false;
305         }
306         if ( max1[2] <= min2[2] ) {
307                 return false;
308         }
309
310         return true;
311 }
312
313 DPlane* DBrush::HasPlane( DPlane* chkPlane ){
314         for ( std::list<DPlane *>::const_iterator brushPlane = faceList.begin(); brushPlane != faceList.end(); brushPlane++ )
315         {
316                 if ( **brushPlane == *chkPlane ) {
317                         return *brushPlane;
318                 }
319         }
320         return NULL;
321 }
322
323 bool DBrush::IsCutByPlane( DPlane *cuttingPlane ){
324         bool isInFront;
325
326         if ( pointList.size() == 0 ) {
327                 if ( BuildPoints() == 0 ) {
328                         return false;
329                 }
330         }
331
332         std::list<DPoint *>::const_iterator chkPnt = pointList.begin();
333
334         if ( chkPnt == pointList.end() ) {
335                 return false;
336         }
337
338         float dist = cuttingPlane->DistanceToPoint( ( *chkPnt )->_pnt );
339
340         if ( dist > MAX_ROUND_ERROR ) {
341                 isInFront = false;
342         }
343         else if ( dist < MAX_ROUND_ERROR ) {
344                 isInFront = true;
345         }
346         else{
347                 return true;
348         }
349
350         for ( chkPnt++ = pointList.begin(); chkPnt != pointList.end(); chkPnt++ )
351         {
352                 dist = cuttingPlane->DistanceToPoint( ( *chkPnt )->_pnt );
353
354                 if ( dist > MAX_ROUND_ERROR ) {
355                         if ( isInFront ) {
356                                 return true;
357                         }
358                 }
359                 else if ( dist < MAX_ROUND_ERROR ) {
360                         if ( !isInFront ) {
361                                 return true;
362                         }
363                 }
364                 else{
365                         return true;
366                 }
367         }
368
369         return false;
370 }
371
372
373 scene::Node* DBrush::BuildInRadiant( bool allowDestruction, int* changeCnt, scene::Node* entity ){
374         if ( allowDestruction ) {
375                 bool kill = true;
376
377                 for ( std::list<DPlane *>::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
378                 {
379                         if ( ( *chkPlane )->m_bChkOk ) {
380                                 kill = false;
381                                 break;
382                         }
383                 }
384                 if ( kill ) {
385                         return NULL;
386                 }
387         }
388
389         //+djbob: fixed bug when brush had no faces "phantom brush" in radiant.
390         if ( faceList.size() < 4 ) {
391                 globalErrorStream() << "Possible Phantom Brush Found, will not rebuild\n";
392                 return NULL;
393         }
394         //-djbob
395
396         NodeSmartReference node( GlobalBrushCreator().createBrush() );
397
398         for ( std::list<DPlane *>::const_iterator buildPlane = faceList.begin(); buildPlane != faceList.end(); buildPlane++ ) {
399                 if ( ( *buildPlane )->AddToBrush( node ) && changeCnt ) {
400                         ( *changeCnt )++;
401                 }
402         }
403
404         if ( entity ) {
405                 Node_getTraversable( *entity )->insert( node );
406         }
407         else {
408                 Node_getTraversable( GlobalRadiant().getMapWorldEntity() )->insert( node );
409         }
410
411         QER_entity = entity;
412         QER_brush = node.get_pointer();
413
414         return node.get_pointer();
415 }
416
417 void DBrush::CutByPlane( DPlane *cutPlane, DBrush **newBrush1, DBrush **newBrush2 ){
418         if ( !IsCutByPlane( cutPlane ) ) {
419                 *newBrush1 = NULL;
420                 *newBrush2 = NULL;
421                 return;
422         }
423
424         DBrush* b1 = new DBrush;
425         DBrush* b2 = new DBrush;
426
427         for ( std::list<DPlane *>::const_iterator parsePlane = faceList.begin(); parsePlane != faceList.end(); parsePlane++ )
428         {
429                 b1->AddFace( ( *parsePlane )->points[0], ( *parsePlane )->points[1], ( *parsePlane )->points[2], NULL );
430                 b2->AddFace( ( *parsePlane )->points[0], ( *parsePlane )->points[1], ( *parsePlane )->points[2], NULL );
431         }
432
433         b1->AddFace( cutPlane->points[0], cutPlane->points[1], cutPlane->points[2], NULL );
434         b2->AddFace( cutPlane->points[2], cutPlane->points[1], cutPlane->points[0], NULL );
435
436         b1->RemoveRedundantPlanes();
437         b2->RemoveRedundantPlanes();
438
439         *newBrush1 = b1;
440         *newBrush2 = b2;
441 }
442
443 bool DBrush::IntersectsWith( DBrush *chkBrush ){
444         if ( pointList.size() == 0 ) {
445                 if ( BuildPoints() == 0 ) {
446                         return false;   // invalid brush!!!!
447
448                 }
449         }
450         if ( chkBrush->pointList.size() == 0 ) {
451                 if ( chkBrush->BuildPoints() == 0 ) {
452                         return false;   // invalid brush!!!!
453
454                 }
455         }
456         if ( !BBoxCollision( chkBrush ) ) {
457                 return false;
458         }
459
460         std::list<DPlane *>::const_iterator iplPlane;
461
462         for ( iplPlane = faceList.begin(); iplPlane != faceList.end(); iplPlane++ )
463         {
464
465                 bool allInFront = true;
466                 for ( std::list<DPoint *>::const_iterator iPoint = chkBrush->pointList.begin(); iPoint != chkBrush->pointList.end(); iPoint++ )
467                 {
468                         if ( ( *iplPlane )->DistanceToPoint( ( *iPoint )->_pnt ) < -MAX_ROUND_ERROR ) {
469                                 allInFront = false;
470                                 break;
471                         }
472                 }
473                 if ( allInFront ) {
474                         return false;
475                 }
476         }
477
478         for ( iplPlane = chkBrush->faceList.begin(); iplPlane != chkBrush->faceList.end(); iplPlane++ )
479         {
480                 bool allInFront = true;
481                 for ( std::list<DPoint *>::const_iterator iPoint = pointList.begin(); iPoint != pointList.end(); iPoint++ )
482                 {
483                         if ( ( *iplPlane )->DistanceToPoint( ( *iPoint )->_pnt ) < -MAX_ROUND_ERROR ) {
484                                 allInFront = false;
485                                 break;
486                         }
487                 }
488                 if ( allInFront ) {
489                         return false;
490                 }
491         }
492
493         return true;
494 }
495
496 bool DBrush::IntersectsWith( DPlane* p1, DPlane* p2, vec3_t v ) {
497         vec3_t vDown = { 0, 0, -1 };
498
499         std::list<DPlane *>::const_iterator iplPlane;
500         for ( iplPlane = faceList.begin(); iplPlane != faceList.end(); iplPlane++ ) {
501                 DPlane* p = ( *iplPlane );
502
503                 vec_t d = DotProduct( p->normal, vDown );
504                 if ( d >= 0 ) {
505                         continue;
506                 }
507                 if ( p->PlaneIntersection( p1, p2, v ) ) {
508                         if ( PointPosition( v ) != POINT_OUT_BRUSH ) {
509                                 return true;
510                         }
511                 }
512         }
513
514         return false;
515 }
516
517 void DBrush::BuildBounds(){
518         if ( !bBoundsBuilt ) {
519                 if ( pointList.size() == 0 ) { // if points may not have been built, build them
520                         if ( BuildPoints() == 0 ) {
521                                 return;
522                         }
523                 }
524
525                 std::list<DPoint *>::const_iterator first = pointList.begin();
526                 VectorCopy( ( *first )->_pnt, bbox_min );
527                 VectorCopy( ( *first )->_pnt, bbox_max );
528
529                 std::list<DPoint *>::const_iterator point = pointList.begin();
530                 for ( point++; point != pointList.end(); point++ )
531                 {
532                         if ( ( *point )->_pnt[0] > bbox_max[0] ) {
533                                 bbox_max[0] = ( *point )->_pnt[0];
534                         }
535                         if ( ( *point )->_pnt[1] > bbox_max[1] ) {
536                                 bbox_max[1] = ( *point )->_pnt[1];
537                         }
538                         if ( ( *point )->_pnt[2] > bbox_max[2] ) {
539                                 bbox_max[2] = ( *point )->_pnt[2];
540                         }
541
542                         if ( ( *point )->_pnt[0] < bbox_min[0] ) {
543                                 bbox_min[0] = ( *point )->_pnt[0];
544                         }
545                         if ( ( *point )->_pnt[1] < bbox_min[1] ) {
546                                 bbox_min[1] = ( *point )->_pnt[1];
547                         }
548                         if ( ( *point )->_pnt[2] < bbox_min[2] ) {
549                                 bbox_min[2] = ( *point )->_pnt[2];
550                         }
551                 }
552
553                 bBoundsBuilt = true;
554         }
555 }
556
557 bool DBrush::BBoxTouch( DBrush *chkBrush ){
558         vec3_t min1, min2;
559         vec3_t max1, max2;
560
561         GetBounds( min1, max1 );
562         chkBrush->GetBounds( min2, max2 );
563
564         if ( ( min1[0] - max2[0] ) > MAX_ROUND_ERROR ) {
565                 return false;
566         }
567         if ( ( min1[1] - max2[1] ) > MAX_ROUND_ERROR ) {
568                 return false;
569         }
570         if ( ( min1[2] - max2[2] ) > MAX_ROUND_ERROR ) {
571                 return false;
572         }
573
574         if ( ( min2[0] - max1[0] ) > MAX_ROUND_ERROR ) {
575                 return false;
576         }
577         if ( ( min2[1] - max1[1] ) > MAX_ROUND_ERROR ) {
578                 return false;
579         }
580         if ( ( min2[2] - max1[2] ) > MAX_ROUND_ERROR ) {
581                 return false;
582         }
583
584         int cnt = 0;
585
586         if ( ( min2[0] - max1[0] ) == 0 ) {
587                 cnt++;
588         }
589
590         if ( ( min2[1] - max1[1] ) == 0 ) {
591                 cnt++;
592         }
593
594         if ( ( min2[2] - max1[2] ) == 0 ) {
595                 cnt++;
596         }
597
598         if ( ( min1[0] - max2[0] ) == 0 ) {
599                 cnt++;
600         }
601
602         if ( ( min1[1] - max2[1] ) == 0 ) {
603                 cnt++;
604         }
605
606         if ( ( min1[2] - max2[2] ) == 0 ) {
607                 cnt++;
608         }
609
610         if ( cnt > 1 ) {
611                 return false;
612         }
613
614         return true;
615 }
616
617 void DBrush::ResetChecks( std::list<Str>* exclusionList ){
618         for ( std::list<DPlane *>::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ )
619         {
620                 bool set = false;
621
622                 if ( exclusionList ) {
623                         for ( std::list<Str>::iterator eTexture = exclusionList->begin(); eTexture != exclusionList->end(); eTexture++ )
624                         {
625                                 if ( strstr( ( *resetPlane )->m_shader.c_str(), eTexture->GetBuffer() ) ) {
626                                         set = true;
627                                         break;
628                                 }
629                         }
630                 }
631
632                 ( *resetPlane )->m_bChkOk = set;
633         }
634 }
635
636 DPlane* DBrush::HasPlaneInverted( DPlane *chkPlane ){
637         for ( std::list<DPlane *>::const_iterator brushPlane = faceList.begin(); brushPlane != faceList.end(); brushPlane++ )
638         {
639                 if ( **brushPlane != *chkPlane ) {
640                         if ( fabs( ( *brushPlane )->_d + chkPlane->_d ) < 0.1 ) {
641                                 return ( *brushPlane );
642                         }
643                 }
644         }
645         return NULL;
646 }
647
648 bool DBrush::HasTexture( const char *textureName ){
649         for ( std::list<DPlane *>::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
650         {
651                 if ( strstr( ( *chkPlane )->m_shader.c_str(), textureName ) ) {
652                         return true;
653                 }
654
655         }
656         return false;
657 }
658
659 bool DBrush::IsDetail(){
660         for ( std::list<DPlane *>::const_iterator chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
661         {
662                 if ( ( *chkPlane )->texInfo.contents & FACE_DETAIL ) {
663                         return true;
664                 }
665
666         }
667         return false;
668 }
669
670 void DBrush::BuildFromWinding( DWinding *w ){
671         if ( w->numpoints < 3 ) {
672                 globalErrorStream() << "Winding has invalid number of points";
673                 return;
674         }
675
676         DPlane* wPlane = w->WindingPlane();
677
678         DWinding* w2;
679         w2 = w->CopyWinding();
680         int i;
681         for ( i = 0; i < w2->numpoints; i++ )
682                 VectorAdd( w2->p[i], wPlane->normal, w2->p[i] );
683
684         AddFace( w2->p[0], w2->p[1], w2->p[2], NULL );
685         AddFace( w->p[2], w->p[1], w->p[0], NULL );
686
687         for ( i = 0; i < w->numpoints - 1; i++ )
688                 AddFace( w2->p[i], w->p[i], w->p[i + 1], NULL );
689         AddFace( w2->p[w->numpoints - 1], w->p[w->numpoints - 1], w->p[0], NULL );
690
691         delete wPlane;
692         delete w2;
693 }
694
695 void DBrush::SaveToFile( FILE *pFile ){
696         fprintf( pFile, "{\n" );
697
698         for ( std::list<DPlane *>::const_iterator pp = faceList.begin(); pp != faceList.end(); pp++ )
699         {
700                 char buffer[512];
701
702                 sprintf( buffer, "( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) %s %.0f %.0f %f %f %.0f 0 0 0\n",
703                                  ( *pp )->points[0][0], ( *pp )->points[0][1], ( *pp )->points[0][2],
704                                  ( *pp )->points[1][0], ( *pp )->points[1][1], ( *pp )->points[1][2],
705                                  ( *pp )->points[2][0], ( *pp )->points[2][1], ( *pp )->points[2][2],
706                                  ( *pp )->m_shader.c_str(),
707                                  ( *pp )->texInfo.m_texdef.shift[0], ( *pp )->texInfo.m_texdef.shift[1],
708                                  ( *pp )->texInfo.m_texdef.scale[0], ( *pp )->texInfo.m_texdef.scale[0],
709                                  ( *pp )->texInfo.m_texdef.rotate );
710
711                 fprintf( pFile, "%s", buffer );
712         }
713
714         fprintf( pFile, "}\n" );
715 }
716
717 void DBrush::Rotate( vec3_t vOrigin, vec3_t vRotation ){
718         for ( std::list<DPlane *>::const_iterator rotPlane = faceList.begin(); rotPlane != faceList.end(); rotPlane++ )
719         {
720                 for ( int i = 0; i < 3; i++ )
721                         VectorRotate( ( *rotPlane )->points[i], vRotation, vOrigin );
722
723                 ( *rotPlane )->Rebuild();
724         }
725 }
726
727 void DBrush::RotateAboutCentre( vec3_t vRotation ){
728         vec3_t min, max, centre;
729         GetBounds( min, max );
730         VectorAdd( min, max, centre );
731         VectorScale( centre, 0.5f, centre );
732
733         Rotate( centre, vRotation );
734 }
735
736 bool DBrush::ResetTextures( const char* textureName, float fScale[2],    float fShift[2],    int rotation, const char* newTextureName,
737                                                         int bResetTextureName,   int bResetScale[2], int bResetShift[2], int bResetRotation ){
738         if ( textureName ) {
739                 bool changed = false;
740                 for ( std::list<DPlane *>::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ )
741                 {
742                         if ( !strcmp( ( *resetPlane )->m_shader.c_str(), textureName ) ) {
743                                 if ( bResetTextureName ) {
744                                         ( *resetPlane )->m_shader = newTextureName;
745                                 }
746
747                                 if ( bResetScale[0] ) {
748                                         ( *resetPlane )->texInfo.m_texdef.scale[0] = fScale[0];
749                                 }
750                                 if ( bResetScale[1] ) {
751                                         ( *resetPlane )->texInfo.m_texdef.scale[1] = fScale[1];
752                                 }
753
754                                 if ( bResetShift[0] ) {
755                                         ( *resetPlane )->texInfo.m_texdef.shift[0] = fShift[0];
756                                 }
757                                 if ( bResetShift[1] ) {
758                                         ( *resetPlane )->texInfo.m_texdef.shift[1] = fShift[1];
759                                 }
760
761                                 if ( bResetRotation ) {
762                                         ( *resetPlane )->texInfo.m_texdef.rotate = (float)rotation;
763                                 }
764
765                                 changed = true;
766                         }
767                 }
768                 return changed; // no point rebuilding unless we need to, only slows things down
769         }
770         else
771         {
772                 for ( std::list<DPlane *>::const_iterator resetPlane = faceList.begin(); resetPlane != faceList.end(); resetPlane++ )
773                 {
774                         if ( bResetTextureName ) {
775                                 ( *resetPlane )->m_shader = newTextureName;
776                         }
777
778                         if ( bResetScale[0] ) {
779                                 ( *resetPlane )->texInfo.m_texdef.scale[0] = fScale[0];
780                         }
781                         if ( bResetScale[1] ) {
782                                 ( *resetPlane )->texInfo.m_texdef.scale[1] = fScale[1];
783                         }
784
785                         if ( bResetShift[0] ) {
786                                 ( *resetPlane )->texInfo.m_texdef.shift[0] = fShift[0];
787                         }
788                         if ( bResetShift[1] ) {
789                                 ( *resetPlane )->texInfo.m_texdef.shift[1] = fShift[1];
790                         }
791
792                         if ( bResetRotation ) {
793                                 ( *resetPlane )->texInfo.m_texdef.rotate = (float)rotation;
794                         }
795                 }
796                 return true;
797         }
798 }
799
800 bool DBrush::operator ==( DBrush* other ){
801         std::list<DPlane *>::const_iterator chkPlane;
802
803         for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
804         {
805                 if ( !other->HasPlane( ( *chkPlane ) ) ) {
806                         return false;
807                 }
808         }
809
810         for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ )
811         {
812                 if ( !HasPlane( ( *chkPlane ) ) ) {
813                         return false;
814                 }
815         }
816
817         return true;
818 }
819
820 DPlane* DBrush::AddFace( const vec3_t va, const vec3_t vb, const vec3_t vc, const char *textureName, bool bDetail ){
821         bBoundsBuilt = false;
822         DPlane* newFace = new DPlane( va, vb, vc, textureName, bDetail );
823         faceList.push_back( newFace );
824
825         return newFace;
826 }
827
828 DPlane* DBrush::FindPlaneWithClosestNormal( vec_t* normal ) {
829         vec_t bestDot = -2;
830         DPlane* bestDotPlane = NULL;
831         std::list<DPlane *>::const_iterator chkPlane;
832         for ( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) {
833                 DPlane* pPlane = ( *chkPlane );
834
835                 vec_t dot = DotProduct( pPlane->normal, normal );
836                 if ( dot > bestDot ) {
837                         bestDot = dot;
838                         bestDotPlane = pPlane;
839                 }
840         }
841
842         return bestDotPlane;
843 }
844
845 int DBrush::FindPointsForPlane( DPlane* plane, DPoint** pnts, int maxpnts ) {
846         int numpnts = 0;
847
848         if ( !maxpnts ) {
849                 return 0;
850         }
851
852         BuildPoints();
853
854         for ( std::list<DPoint *>::const_iterator points = pointList.begin(); points != pointList.end(); points++ ) {
855                 DPoint* point = ( *points );
856
857                 if ( fabs( plane->DistanceToPoint( point->_pnt ) ) < MAX_ROUND_ERROR ) {
858                         pnts[numpnts] = point;
859                         numpnts++;
860
861                         if ( numpnts >= maxpnts ) {
862                                 return numpnts;
863                         }
864
865                 }
866         }
867
868         return numpnts;
869 }
870
871 void DBrush::RemovePlane( DPlane* plane ) {
872         bBoundsBuilt = false;
873         for ( std::list<DPlane *>::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ ) {
874                 if ( *deadPlane == plane ) {
875                         delete *deadPlane;
876                         faceList.remove( plane );
877                 }
878         }
879 }