]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/csg.cpp
1b824b6abedc8f5e0e1199fc841803bb9cd55271
[xonotic/netradiant.git] / radiant / csg.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "csg.h"
23
24 #include "debugging/debugging.h"
25
26 #include <list>
27
28 #include "map.h"
29 #include "brushmanip.h"
30 #include "brushnode.h"
31 #include "grid.h"
32 /*
33 void Face_makeBrush( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
34         if ( face.contributes() ) {
35                 out.push_back( new Brush( brush ) );
36                 Face* newFace = out.back()->addFace( face );
37                 face.getPlane().offset( -offset );
38                 face.planeChanged();
39                 if ( newFace != 0 ) {
40                         newFace->flipWinding();
41                         newFace->getPlane().offset( offset );
42                         newFace->planeChanged();
43                 }
44         }
45 }
46
47 void Face_extrude( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
48         if ( face.contributes() ) {
49                 face.getPlane().offset( offset );
50                 out.push_back( new Brush( brush ) );
51                 face.getPlane().offset( -offset );
52                 Face* newFace = out.back()->addFace( face );
53                 if ( newFace != 0 ) {
54                         newFace->flipWinding();
55                         newFace->planeChanged();
56                 }
57         }
58 }
59 */
60 #include "preferences.h"
61 #include "texwindow.h"
62
63
64 enum eHollowType
65 {
66         diag = 0,
67         wrap = 1,
68         extrude = 2,
69         pull = 3,
70         room = 4,
71 };
72
73 const char* getCaulkShader(){
74         const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
75         if ( gotShader && *gotShader ){
76                 return gotShader;
77         }
78         return "textures/common/caulk";
79 }
80
81 class CaulkFace
82 {
83 DoubleVector3 ExclusionAxis;
84 double &mindot;
85 double &maxdot;
86 public:
87 CaulkFace( DoubleVector3 ExclusionAxis,
88                         double &mindot,
89                         double &maxdot ):
90                         ExclusionAxis( ExclusionAxis ),
91                         mindot( mindot ),
92                         maxdot( maxdot ){}
93 void operator()( Face& face ) const {
94         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
95         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) )
96                 face.SetShader( getCaulkShader() );
97 }
98 };
99
100 class FaceMakeBrush
101 {
102 const Brush& brush;
103 brush_vector_t& out;
104 float offset;
105 eHollowType HollowType;
106 DoubleVector3 ExclusionAxis;
107 double &mindot;
108 double &maxdot;
109 bool caulk;
110 bool RemoveInner;
111 public:
112 FaceMakeBrush( const Brush& brush,
113                         brush_vector_t& out,
114                         float offset,
115                         eHollowType HollowType,
116                         DoubleVector3 ExclusionAxis,
117                         double &mindot,
118                         double &maxdot,
119                         bool caulk,
120                         bool RemoveInner )
121         : brush( brush ),
122         out( out ),
123         offset( offset ),
124         HollowType( HollowType ),
125         ExclusionAxis( ExclusionAxis ),
126         mindot( mindot ),
127         maxdot( maxdot ),
128         caulk( caulk ),
129         RemoveInner( RemoveInner ){
130 }
131 void operator()( Face& face ) const {
132         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
133         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
134                 if( HollowType == pull ){
135                         if ( face.contributes() ) {
136                                 face.getPlane().offset( offset );
137                                 face.planeChanged();
138                                 out.push_back( new Brush( brush ) );
139                                 face.getPlane().offset( -offset );
140                                 face.planeChanged();
141
142                                 if( caulk ){
143                                         Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot ) );
144                                 }
145                                 Face* newFace = out.back()->addFace( face );
146                                 if ( newFace != 0 ) {
147                                         newFace->flipWinding();
148                                 }
149                         }
150                 }
151                 else if( HollowType == wrap ){
152                         //Face_makeBrush( face, brush, out, offset );
153                         if ( face.contributes() ) {
154                                 face.undoSave();
155                                 out.push_back( new Brush( brush ) );
156                                 if( !RemoveInner && caulk )
157                                         face.SetShader( getCaulkShader() );
158                                 Face* newFace = out.back()->addFace( face );
159                                 face.getPlane().offset( -offset );
160                                 face.planeChanged();
161                                 if( caulk )
162                                         face.SetShader( getCaulkShader() );
163                                 if ( newFace != 0 ) {
164                                         newFace->flipWinding();
165                                         newFace->getPlane().offset( offset );
166                                         newFace->planeChanged();
167                                 }
168                         }
169                 }
170                 else if( HollowType == extrude ){
171                         if ( face.contributes() ) {
172                                 //face.undoSave();
173                                 out.push_back( new Brush( brush ) );
174                                 out.back()->clear();
175
176                                 Face* newFace = out.back()->addFace( face );
177                                 if ( newFace != 0 ) {
178                                         newFace->getPlane().offset( offset );
179                                         newFace->planeChanged();
180                                 }
181
182                                 if( !RemoveInner && caulk )
183                                         face.SetShader( getCaulkShader() );
184                                 newFace = out.back()->addFace( face );
185                                 if ( newFace != 0 ) {
186                                         newFace->flipWinding();
187                                 }
188                                 Winding& winding = face.getWinding();
189                                 TextureProjection projection;
190                                 TexDef_Construct_Default( projection );
191                                 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
192                                         std::size_t index = std::distance( winding.begin(), j );
193                                         std::size_t next = Winding_next( winding, index );
194
195                                         out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
196                                 }
197                         }
198                 }
199                 else if( HollowType == diag ){
200                         if ( face.contributes() ) {
201                                 out.push_back( new Brush( brush ) );
202                                 out.back()->clear();
203
204                                 Face* newFace = out.back()->addFace( face );
205                                 if ( newFace != 0 ) {
206
207                                         newFace->planeChanged();
208                                 }
209                                 newFace = out.back()->addFace( face );
210
211                                 if ( newFace != 0 ) {
212                                         if( !RemoveInner && caulk )
213                                                 newFace->SetShader( getCaulkShader() );
214                                         newFace->flipWinding();
215                                         newFace->getPlane().offset( offset );
216                                         newFace->planeChanged();
217                                 }
218
219                                 Winding& winding = face.getWinding();
220                                 TextureProjection projection;
221                                 TexDef_Construct_Default( projection );
222                                 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
223                                         std::size_t index = std::distance( winding.begin(), i );
224                                         std::size_t next = Winding_next( winding, index );
225                                         Vector3 BestPoint;
226                                         float bestdist = 999999;
227
228                                         for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
229                                                 Winding& winding2 = ( *j )->getWinding();
230                                                 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
231                                                         std::size_t index2 = std::distance( winding2.begin(), k );
232                                                         float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
233                                                         if( testdist < bestdist ){
234                                                                 bestdist = testdist;
235                                                                 BestPoint = winding2[index2].vertex;
236                                                         }
237                                                 }
238                                         }
239                                         out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
240                                 }
241                         }
242                 }
243         }
244 }
245 };
246
247 class FaceExclude
248 {
249 DoubleVector3 ExclusionAxis;
250 double &mindot;
251 double &maxdot;
252 public:
253 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
254         : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
255 }
256 void operator()( Face& face ) const {
257         if( vector3_length_squared( ExclusionAxis ) != 0 ){
258                 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
259                 if( dot < mindot ){
260                         mindot = dot;
261                 }
262                 else if( dot > maxdot ){
263                         maxdot = dot;
264                 }
265         }
266 }
267 };
268
269 class FaceOffset
270 {
271 float offset;
272 DoubleVector3 ExclusionAxis;
273 double &mindot;
274 double &maxdot;
275 public:
276 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
277         : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
278 }
279 void operator()( Face& face ) const {
280         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
281         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
282                 face.undoSave();
283                 face.getPlane().offset( offset );
284                 face.planeChanged();
285         }
286 }
287 };
288
289
290 DoubleVector3 getExclusion();
291 bool getCaulk();
292 bool getRemoveInner();
293
294 class BrushHollowSelectedWalker : public scene::Graph::Walker
295 {
296 float offset;
297 eHollowType HollowType;
298 public:
299 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
300         : offset( offset ), HollowType( HollowType ){
301 }
302 bool pre( const scene::Path& path, scene::Instance& instance ) const {
303         if ( path.top().get().visible() ) {
304                 Brush* brush = Node_getBrush( path.top() );
305                 if ( brush != 0
306                          && Instance_getSelectable( instance )->isSelected()
307                          && path.size() > 1 ) {
308                         brush_vector_t out;
309                         double mindot = 0;
310                         double maxdot = 0;
311                         if( HollowType != room ){
312                                 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
313                         }
314                         if( HollowType == room ){
315                                 Brush* tmpbrush = new Brush( *brush );
316                                 tmpbrush->removeEmptyFaces();
317                                 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, true, true ) );
318                                 delete tmpbrush;
319                         }
320                         else if( HollowType == pull ){
321                                 if( !getRemoveInner() && getCaulk() ){
322                                         Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
323                                 }
324                                 Brush* tmpbrush = new Brush( *brush );
325                                 tmpbrush->removeEmptyFaces();
326                                 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
327                                 delete tmpbrush;
328                         }
329                         else if( HollowType == diag ){
330                                 Brush* tmpbrush = new Brush( *brush );
331                                 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
332                                 tmpbrush->removeEmptyFaces();
333                                 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
334                                 delete tmpbrush;
335                                 if( !getRemoveInner() && getCaulk() ){
336                                         Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
337                                 }
338                         }
339                         else{
340                                 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
341                         }
342                         for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
343                         {
344                                 ( *i )->removeEmptyFaces();
345                                 if( ( *i )->hasContributingFaces() ){
346                                         NodeSmartReference node( ( new BrushNode() )->node() );
347                                         Node_getBrush( node )->copy( *( *i ) );
348                                         delete ( *i );
349                                         Node_getTraversable( path.parent() )->insert( node );
350                                         //path.push( makeReference( node.get() ) );
351                                         //selectPath( path, true );
352                                         //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
353                                         //Path_deleteTop( path );
354                                 }
355                         }
356                 }
357         }
358         return true;
359 }
360 };
361
362 typedef std::list<Brush*> brushlist_t;
363
364 class BrushGatherSelected : public scene::Graph::Walker
365 {
366 brush_vector_t& m_brushlist;
367 public:
368 BrushGatherSelected( brush_vector_t& brushlist )
369         : m_brushlist( brushlist ){
370 }
371 bool pre( const scene::Path& path, scene::Instance& instance ) const {
372         if ( path.top().get().visible() ) {
373                 Brush* brush = Node_getBrush( path.top() );
374                 if ( brush != 0
375                          && Instance_getSelectable( instance )->isSelected() ) {
376                         m_brushlist.push_back( brush );
377                 }
378         }
379         return true;
380 }
381 };
382 /*
383 class BrushDeleteSelected : public scene::Graph::Walker
384 {
385 public:
386 bool pre( const scene::Path& path, scene::Instance& instance ) const {
387         return true;
388 }
389 void post( const scene::Path& path, scene::Instance& instance ) const {
390         if ( path.top().get().visible() ) {
391                 Brush* brush = Node_getBrush( path.top() );
392                 if ( brush != 0
393                          && Instance_getSelectable( instance )->isSelected()
394                          && path.size() > 1 ) {
395                         Path_deleteTop( path );
396                 }
397         }
398 }
399 };
400 */
401 #include "ientity.h"
402
403 class BrushDeleteSelected : public scene::Graph::Walker
404 {
405 scene::Node* m_keepNode;
406 mutable bool m_eraseParent;
407 public:
408 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
409 }
410 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
411 }
412 bool pre( const scene::Path& path, scene::Instance& instance ) const {
413         return true;
414 }
415 void post( const scene::Path& path, scene::Instance& instance ) const {
416         //globalOutputStream() << path.size() << "\n";
417         if ( path.top().get().visible() ) {
418                 Brush* brush = Node_getBrush( path.top() );
419                 if ( brush != 0
420                          && Instance_getSelectable( instance )->isSelected()
421                          && path.size() > 1 ) {
422                         Path_deleteTop( path );
423                         if( Node_getTraversable( path.parent() )->empty() ){
424                                 m_eraseParent = true;
425                                 //globalOutputStream() << "Empty node?!.\n";
426                         }
427                 }
428         }
429         if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
430                 //globalOutputStream() << "about to Delete empty node!.\n";
431                 m_eraseParent = false;
432                 Entity* entity = Node_getEntity( path.top() );
433                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
434                         && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
435                         //globalOutputStream() << "now Deleting empty node!.\n";
436                         Path_deleteTop( path );
437                 }
438         }
439 }
440 };
441
442 /*
443    =============
444    CSG_MakeRoom
445    =============
446  */
447 void CSG_MakeRoom( void ){
448         UndoableCommand undo( "makeRoom" );
449         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
450         GlobalSceneGraph().traverse( BrushDeleteSelected() );
451         SceneChangeNotify();
452 }
453
454 template<typename Type>
455 class RemoveReference
456 {
457 public:
458 typedef Type type;
459 };
460
461 template<typename Type>
462 class RemoveReference<Type&>
463 {
464 public:
465 typedef Type type;
466 };
467
468 template<typename Functor>
469 class Dereference
470 {
471 const Functor& functor;
472 public:
473 typedef typename RemoveReference<typename Functor::first_argument_type>::type* first_argument_type;
474 typedef typename Functor::result_type result_type;
475 Dereference( const Functor& functor ) : functor( functor ){
476 }
477 result_type operator()( first_argument_type firstArgument ) const {
478         return functor( *firstArgument );
479 }
480 };
481
482 template<typename Functor>
483 inline Dereference<Functor> makeDereference( const Functor& functor ){
484         return Dereference<Functor>( functor );
485 }
486
487 typedef Face* FacePointer;
488 const FacePointer c_nullFacePointer = 0;
489
490 template<typename Predicate>
491 Face* Brush_findIf( const Brush& brush, const Predicate& predicate ){
492         Brush::const_iterator i = std::find_if( brush.begin(), brush.end(), makeDereference( predicate ) );
493         return i == brush.end() ? c_nullFacePointer : *i; // uses c_nullFacePointer instead of 0 because otherwise gcc 4.1 attempts conversion to int
494 }
495
496 template<typename Caller>
497 class BindArguments1
498 {
499 typedef typename Caller::second_argument_type FirstBound;
500 FirstBound firstBound;
501 public:
502 typedef typename Caller::result_type result_type;
503 typedef typename Caller::first_argument_type first_argument_type;
504 BindArguments1( FirstBound firstBound )
505         : firstBound( firstBound ){
506 }
507 result_type operator()( first_argument_type firstArgument ) const {
508         return Caller::call( firstArgument, firstBound );
509 }
510 };
511
512 template<typename Caller>
513 class BindArguments2
514 {
515 typedef typename Caller::second_argument_type FirstBound;
516 typedef typename Caller::third_argument_type SecondBound;
517 FirstBound firstBound;
518 SecondBound secondBound;
519 public:
520 typedef typename Caller::result_type result_type;
521 typedef typename Caller::first_argument_type first_argument_type;
522 BindArguments2( FirstBound firstBound, SecondBound secondBound )
523         : firstBound( firstBound ), secondBound( secondBound ){
524 }
525 result_type operator()( first_argument_type firstArgument ) const {
526         return Caller::call( firstArgument, firstBound, secondBound );
527 }
528 };
529
530 template<typename Caller, typename FirstBound, typename SecondBound>
531 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
532         return BindArguments2<Caller>( firstBound, secondBound );
533 }
534
535 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
536         return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
537 }
538 typedef Function3<const Face&, const Plane3&, bool, bool, Face_testPlane> FaceTestPlane;
539
540
541
542 /// \brief Returns true if
543 /// \li !flipped && brush is BACK or ON
544 /// \li flipped && brush is FRONT or ON
545 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
546         brush.evaluateBRep();
547 #if 1
548         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
549         {
550                 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
551                         return false;
552                 }
553         }
554         return true;
555 #else
556         return Brush_findIf( brush, bindArguments( FaceTestPlane(), makeReference( plane ), flipped ) ) == 0;
557 #endif
558 }
559
560 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
561         brush.evaluateBRep();
562         brushsplit_t split;
563         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
564         {
565                 if ( ( *i )->contributes() ) {
566                         split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
567                 }
568         }
569         return split;
570 }
571
572 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
573         if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
574                 brush_vector_t fragments;
575                 fragments.reserve( other.size() );
576                 Brush back( brush );
577
578                 for ( Brush::const_iterator i( other.begin() ); i != other.end(); ++i )
579                 {
580                         if ( ( *i )->contributes() ) {
581                                 brushsplit_t split = Brush_classifyPlane( back, ( *i )->plane3() );
582                                 if ( split.counts[ePlaneFront] != 0
583                                          && split.counts[ePlaneBack] != 0 ) {
584                                         fragments.push_back( new Brush( back ) );
585                                         Face* newFace = fragments.back()->addFace( *( *i ) );
586                                         if ( newFace != 0 ) {
587                                                 newFace->flipWinding();
588                                         }
589                                         back.addFace( *( *i ) );
590                                 }
591                                 else if ( split.counts[ePlaneBack] == 0 ) {
592                                         for ( brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i )
593                                         {
594                                                 delete( *i );
595                                         }
596                                         return false;
597                                 }
598                         }
599                 }
600                 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
601                 return true;
602         }
603         return false;
604 }
605
606 class SubtractBrushesFromUnselected : public scene::Graph::Walker
607 {
608 const brush_vector_t& m_brushlist;
609 std::size_t& m_before;
610 std::size_t& m_after;
611 mutable bool m_eraseParent;
612 public:
613 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
614         : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
615 }
616 bool pre( const scene::Path& path, scene::Instance& instance ) const {
617         if ( path.top().get().visible() ) {
618                 return true;
619         }
620         return false;
621 }
622 void post( const scene::Path& path, scene::Instance& instance ) const {
623         if ( path.top().get().visible() ) {
624                 Brush* brush = Node_getBrush( path.top() );
625                 if ( brush != 0
626                          && !Instance_getSelectable( instance )->isSelected() ) {
627                         brush_vector_t buffer[2];
628                         bool swap = false;
629                         Brush* original = new Brush( *brush );
630                         buffer[static_cast<std::size_t>( swap )].push_back( original );
631
632                         {
633                                 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
634                                 {
635                                         for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
636                                         {
637                                                 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
638                                                         delete ( *j );
639                                                 }
640                                                 else
641                                                 {
642                                                         buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
643                                                 }
644                                         }
645                                         buffer[static_cast<std::size_t>( swap )].clear();
646                                         swap = !swap;
647                                 }
648                         }
649
650                         brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
651
652                         if ( out.size() == 1 && out.back() == original ) {
653                                 delete original;
654                         }
655                         else
656                         {
657                                 ++m_before;
658                                 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
659                                 {
660                                         ++m_after;
661                                         ( *i )->removeEmptyFaces();
662                                         if ( !( *i )->empty() ) {
663                                                 NodeSmartReference node( ( new BrushNode() )->node() );
664                                                 Node_getBrush( node )->copy( *( *i ) );
665                                                 delete ( *i );
666                                                 Node_getTraversable( path.parent() )->insert( node );
667                                         }
668                                         else{
669                                                 delete ( *i );
670                                         }
671                                 }
672                                 Path_deleteTop( path );
673                                 if( Node_getTraversable( path.parent() )->empty() ){
674                                         m_eraseParent = true;
675                                 }
676                         }
677                 }
678         }
679         if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
680                 m_eraseParent = false;
681                 Entity* entity = Node_getEntity( path.top() );
682                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
683                         && Node_getTraversable( path.top() )->empty() ) {
684                         Path_deleteTop( path );
685                 }
686         }
687 }
688 };
689
690 void CSG_Subtract(){
691         brush_vector_t selected_brushes;
692         GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
693
694         if ( selected_brushes.empty() ) {
695                 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
696         }
697         else
698         {
699                 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
700
701                 UndoableCommand undo( "brushSubtract" );
702
703                 // subtract selected from unselected
704                 std::size_t before = 0;
705                 std::size_t after = 0;
706                 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
707                 globalOutputStream() << "CSG Subtract: Result: "
708                                                          << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
709                                                          << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
710
711                 SceneChangeNotify();
712         }
713 }
714
715 class BrushSplitByPlaneSelected : public scene::Graph::Walker
716 {
717 const Vector3& m_p0;
718 const Vector3& m_p1;
719 const Vector3& m_p2;
720 const char* m_shader;
721 const TextureProjection& m_projection;
722 EBrushSplit m_split;
723 public:
724 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
725         : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
726 }
727 bool pre( const scene::Path& path, scene::Instance& instance ) const {
728         return true;
729 }
730 void post( const scene::Path& path, scene::Instance& instance ) const {
731         if ( path.top().get().visible() ) {
732                 Brush* brush = Node_getBrush( path.top() );
733                 if ( brush != 0
734                          && Instance_getSelectable( instance )->isSelected() ) {
735                         Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
736                         if ( plane3_valid( plane ) ) {
737                                 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
738                                 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
739                                         // the plane intersects this brush
740                                         if ( m_split == eFrontAndBack ) {
741                                                 NodeSmartReference node( ( new BrushNode() )->node() );
742                                                 Brush* fragment = Node_getBrush( node );
743                                                 fragment->copy( *brush );
744                                                 Face* newFace = fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
745                                                 if ( newFace != 0 && m_split != eFront ) {
746                                                         newFace->flipWinding();
747                                                 }
748                                                 fragment->removeEmptyFaces();
749                                                 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
750
751                                                 Node_getTraversable( path.parent() )->insert( node );
752                                                 {
753                                                         scene::Path fragmentPath = path;
754                                                         fragmentPath.top() = makeReference( node.get() );
755                                                         selectPath( fragmentPath, true );
756                                                 }
757                                         }
758
759                                         Face* newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
760                                         if ( newFace != 0 && m_split == eFront ) {
761                                                 newFace->flipWinding();
762                                         }
763                                         brush->removeEmptyFaces();
764                                         ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
765                                 }
766                                 else
767                                 // the plane does not intersect this brush
768                                 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
769                                         // the brush is "behind" the plane
770                                         Path_deleteTop( path );
771                                 }
772                         }
773                 }
774         }
775 }
776 };
777
778 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
779         TextureProjection projection;
780         TexDef_Construct_Default( projection );
781         graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
782         SceneChangeNotify();
783 }
784
785
786 class BrushInstanceSetClipPlane : public scene::Graph::Walker
787 {
788 Plane3 m_plane;
789 public:
790 BrushInstanceSetClipPlane( const Plane3& plane )
791         : m_plane( plane ){
792 }
793 bool pre( const scene::Path& path, scene::Instance& instance ) const {
794         BrushInstance* brush = Instance_getBrush( instance );
795         if ( brush != 0
796                  && path.top().get().visible()
797                  && brush->isSelected() ) {
798                 BrushInstance& brushInstance = *brush;
799                 brushInstance.setClipPlane( m_plane );
800         }
801         return true;
802 }
803 };
804
805 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
806         graph.traverse( BrushInstanceSetClipPlane( plane ) );
807 }
808
809 /*
810    =============
811    CSG_Merge
812    =============
813  */
814 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
815         // gather potential outer faces
816
817         {
818                 typedef std::vector<const Face*> Faces;
819                 Faces faces;
820                 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
821                 {
822                         ( *i )->evaluateBRep();
823                         for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
824                         {
825                                 if ( !( *j )->contributes() ) {
826                                         continue;
827                                 }
828
829                                 const Face& face1 = *( *j );
830
831                                 bool skip = false;
832                                 // test faces of all input brushes
833                                 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
834                                 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
835                                 {
836                                         if ( k != i ) { // don't test a brush against itself
837                                                 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
838                                                 {
839                                                         const Face& face2 = *( *l );
840
841                                                         // face opposes another face
842                                                         if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
843                                                                 // skip opposing planes
844                                                                 skip  = true;
845                                                                 break;
846                                                         }
847                                                 }
848                                         }
849                                 }
850
851                                 // check faces already stored
852                                 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
853                                 {
854                                         const Face& face2 = *( *m );
855
856                                         // face equals another face
857                                         if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
858                                                 //if the texture/shader references should be the same but are not
859                                                 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
860                                                         return false;
861                                                 }
862                                                 // skip duplicate planes
863                                                 skip = true;
864                                                 break;
865                                         }
866
867                                         // face1 plane intersects face2 winding or vice versa
868                                         if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
869                                                 // result would not be convex
870                                                 return false;
871                                         }
872                                 }
873
874                                 if ( !skip ) {
875                                         faces.push_back( &face1 );
876                                 }
877                         }
878                 }
879                 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
880                 {
881                         if ( !brush.addFace( *( *i ) ) ) {
882                                 // result would have too many sides
883                                 return false;
884                         }
885                 }
886         }
887
888         brush.removeEmptyFaces();
889
890         return true;
891 }
892
893 void CSG_Merge( void ){
894         brush_vector_t selected_brushes;
895
896         // remove selected
897         GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
898
899         if ( selected_brushes.empty() ) {
900                 globalOutputStream() << "CSG Merge: No brushes selected.\n";
901                 return;
902         }
903
904         if ( selected_brushes.size() < 2 ) {
905                 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
906                 return;
907         }
908
909         globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
910
911         UndoableCommand undo( "brushMerge" );
912
913         scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
914
915         NodeSmartReference node( ( new BrushNode() )->node() );
916         Brush* brush = Node_getBrush( node );
917         // if the new brush would not be convex
918         if ( !Brush_merge( *brush, selected_brushes, true ) ) {
919                 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
920         }
921         else
922         {
923                 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
924
925                 // free the original brushes
926                 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
927
928                 merged_path.pop();
929                 Node_getTraversable( merged_path.top() )->insert( node );
930                 merged_path.push( makeReference( node.get() ) );
931
932                 selectPath( merged_path, true );
933
934                 globalOutputStream() << "CSG Merge: Succeeded.\n";
935                 SceneChangeNotify();
936         }
937 }
938
939
940
941
942
943
944 /*
945    =============
946    CSG_Tool
947    =============
948  */
949 #include "mainframe.h"
950 #include <gtk/gtk.h>
951 #include "gtkutil/dialog.h"
952 #include "gtkutil/button.h"
953 #include "gtkutil/accelerator.h"
954
955 struct CSGToolDialog
956 {
957         GtkSpinButton* spin;
958         GtkWindow *window;
959         GtkToggleButton *radXYZ, *radX, *radY, *radZ, *caulk, *removeInner;
960 };
961
962 CSGToolDialog g_csgtool_dialog;
963
964 DoubleVector3 getExclusion(){
965         if( gtk_toggle_button_get_active( g_csgtool_dialog.radX ) ){
966                 return DoubleVector3( 1, 0, 0 );
967         }
968         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radY ) ){
969                 return DoubleVector3( 0, 1, 0 );
970         }
971         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radZ ) ){
972                 return DoubleVector3( 0, 0, 1 );
973         }
974         return DoubleVector3( 0, 0, 0 );
975 }
976
977 bool getCaulk(){
978                 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
979                 return true;
980         }
981         return false;
982 }
983
984 bool getRemoveInner(){
985                 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
986                 return true;
987         }
988         return false;
989 }
990
991 class BrushFaceOffset
992 {
993 float offset;
994 public:
995 BrushFaceOffset( float offset )
996         : offset( offset ){
997 }
998 void operator()( BrushInstance& brush ) const {
999         double mindot = 0;
1000         double maxdot = 0;
1001         Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1002         Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
1003 }
1004 };
1005
1006 //=================DLG
1007
1008 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1009         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1010         UndoableCommand undo( "brushHollow::Diag" );
1011         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1012         if( getRemoveInner() )
1013                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1014         SceneChangeNotify();
1015         return TRUE;
1016 }
1017
1018 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1019         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1020         UndoableCommand undo( "brushHollow::Wrap" );
1021         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1022         if( getRemoveInner() )
1023                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1024         SceneChangeNotify();
1025         return TRUE;
1026 }
1027
1028 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1029         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1030         UndoableCommand undo( "brushHollow::Extrude" );
1031         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1032         if( getRemoveInner() )
1033                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1034         SceneChangeNotify();
1035         return TRUE;
1036 }
1037
1038 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1039         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1040         UndoableCommand undo( "brushHollow::Pull" );
1041         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1042         if( getRemoveInner() )
1043                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1044         SceneChangeNotify();
1045         return TRUE;
1046 }
1047
1048 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1049         gtk_spin_button_update ( dialog->spin );
1050         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1051         offset *= -1;
1052         UndoableCommand undo( "Shrink brush" );
1053 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1054         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1055         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1056         SceneChangeNotify();
1057         return TRUE;
1058 }
1059
1060 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1061         gtk_spin_button_update ( dialog->spin );
1062         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1063         UndoableCommand undo( "Expand brush" );
1064 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1065         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1066         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1067         SceneChangeNotify();
1068         return TRUE;
1069 }
1070
1071 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1072         gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1073         return TRUE;
1074 }
1075
1076 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1077         gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1078         return TRUE;
1079 }
1080
1081 void CSG_Tool(){
1082         if ( g_csgtool_dialog.window == NULL ) {
1083                 g_csgtool_dialog.window = create_dialog_window( MainFrame_getWindow(), "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1084                 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1085
1086                 //GtkAccelGroup* accel = gtk_accel_group_new();
1087                 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1088                 global_accel_connect_window( g_csgtool_dialog.window );
1089
1090                 {
1091                         GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1092                         gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1093                         {
1094                                 GtkTable* table = create_dialog_table( 3, 8, 4, 4 );
1095                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1096                                 {
1097                                         //GtkWidget* label = gtk_label_new( "<->" );
1098                                         //gtk_widget_show( label );
1099                                         GtkWidget* button = gtk_button_new_with_label( "Grid->" );
1100                                         gtk_table_attach( table, button, 0, 1, 0, 1,
1101                                                                           (GtkAttachOptions) ( 0 ),
1102                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1103                                         gtk_widget_show( button );
1104                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1105                                 }
1106                                 {
1107                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1108                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1109                                         gtk_widget_show( GTK_WIDGET( spin ) );
1110                                         gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1111                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1112                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1113                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1114                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1115                                         gtk_spin_button_set_numeric( spin, TRUE );
1116
1117                                         g_csgtool_dialog.spin = spin;
1118                                 }
1119                                 {
1120                                         //radio button group for choosing the exclude axis
1121                                         GtkWidget* radXYZ = gtk_radio_button_new_with_label( NULL, "XYZ" );
1122                                         GtkWidget* radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-X" );
1123                                         GtkWidget* radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Y" );
1124                                         GtkWidget* radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Z" );
1125                                         gtk_widget_show( radXYZ );
1126                                         gtk_widget_show( radX );
1127                                         gtk_widget_show( radY );
1128                                         gtk_widget_show( radZ );
1129
1130                                         gtk_table_attach( table, radXYZ, 2, 3, 0, 1,
1131                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1132                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1133                                         gtk_table_attach( table, radX, 3, 4, 0, 1,
1134                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1135                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1136                                         gtk_table_attach( table, radY, 4, 5, 0, 1,
1137                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1138                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1139                                         gtk_table_attach( table, radZ, 5, 6, 0, 1,
1140                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1141                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1142
1143                                         g_csgtool_dialog.radXYZ = GTK_TOGGLE_BUTTON( radXYZ );
1144                                         g_csgtool_dialog.radX = GTK_TOGGLE_BUTTON( radX );
1145                                         g_csgtool_dialog.radY = GTK_TOGGLE_BUTTON( radY );
1146                                         g_csgtool_dialog.radZ = GTK_TOGGLE_BUTTON( radZ );
1147                                 }
1148                                 {
1149                                         GtkWidget* button = gtk_toggle_button_new();
1150                                         button_set_icon( GTK_BUTTON( button ), "f-caulk.png" );
1151                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1152                                         gtk_table_attach( table, button, 6, 7, 0, 1,
1153                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1154                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1155                                         gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1156                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1157                                         gtk_widget_show( button );
1158                                         g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1159                                 }
1160                                 {
1161                                         GtkWidget* button = gtk_toggle_button_new();
1162                                         button_set_icon( GTK_BUTTON( button ), "csgtool_removeinner.png" );
1163                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1164                                         gtk_table_attach( table, button, 7, 8, 0, 1,
1165                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1166                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1167                                         gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1168                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1169                                         gtk_widget_show( button );
1170                                         g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1171                                 }
1172                                 {
1173                                         GtkWidget* sep = gtk_hseparator_new();
1174                                         gtk_widget_show( sep );
1175                                         gtk_table_attach( table, sep, 0, 8, 1, 2,
1176                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1177                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1178                                 }
1179                                 {
1180                                         GtkWidget* button = gtk_button_new();
1181                                         button_set_icon( GTK_BUTTON( button ), "csgtool_shrink.png" );
1182                                         gtk_table_attach( table, button, 0, 1, 2, 3,
1183                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1184                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1185                                         gtk_widget_set_tooltip_text( button, "Shrink brush" );
1186                                         gtk_widget_show( button );
1187                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1188                                 }
1189                                 {
1190                                         GtkWidget* button = gtk_button_new();
1191                                         button_set_icon( GTK_BUTTON( button ), "csgtool_expand.png" );
1192                                         gtk_table_attach( table, button, 1, 2, 2, 3,
1193                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1194                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1195                                         gtk_widget_set_tooltip_text( button, "Expand brush" );
1196                                         gtk_widget_show( button );
1197                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1198                                 }
1199                                 {
1200                                         GtkWidget* button = gtk_button_new();
1201                                         button_set_icon( GTK_BUTTON( button ), "csgtool_diagonal.png" );
1202                                         gtk_table_attach( table, button, 3, 4, 2, 3,
1203                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1204                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1205                                         gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1206                                         gtk_widget_show( button );
1207                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1208                                 }
1209                                 {
1210                                         GtkWidget* button = gtk_button_new();
1211                                         button_set_icon( GTK_BUTTON( button ), "csgtool_wrap.png" );
1212                                         gtk_table_attach( table, button, 4, 5, 2, 3,
1213                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1214                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1215                                         gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1216                                         gtk_widget_show( button );
1217                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1218                                 }
1219                                 {
1220                                         GtkWidget* button = gtk_button_new();
1221                                         button_set_icon( GTK_BUTTON( button ), "csgtool_extrude.png" );
1222                                         gtk_table_attach( table, button, 5, 6, 2, 3,
1223                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1224                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1225                                         gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1226                                         gtk_widget_show( button );
1227                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1228                                 }
1229                                 {
1230                                         GtkWidget* button = gtk_button_new();
1231                                         button_set_icon( GTK_BUTTON( button ), "csgtool_pull.png" );
1232                                         gtk_table_attach( table, button, 6, 7, 2, 3,
1233                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1234                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1235                                         gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1236                                         gtk_widget_show( button );
1237                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1238                                 }
1239
1240                         }
1241                 }
1242         }
1243
1244         gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1245         gtk_window_present( g_csgtool_dialog.window );
1246 }
1247