]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/csg.cpp
Q3map2:
[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                         scene::Node& parent = path.parent();
423                         Path_deleteTop( path );
424                         if( Node_getTraversable( parent )->empty() ){
425                                 m_eraseParent = true;
426                                 //globalOutputStream() << "Empty node?!.\n";
427                         }
428                 }
429         }
430         if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
431                 //globalOutputStream() << "about to Delete empty node!.\n";
432                 m_eraseParent = false;
433                 Entity* entity = Node_getEntity( path.top() );
434                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
435                         && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
436                         //globalOutputStream() << "now Deleting empty node!.\n";
437                         Path_deleteTop( path );
438                 }
439         }
440 }
441 };
442
443 /*
444    =============
445    CSG_MakeRoom
446    =============
447  */
448 void CSG_MakeRoom( void ){
449         UndoableCommand undo( "makeRoom" );
450         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
451         GlobalSceneGraph().traverse( BrushDeleteSelected() );
452         SceneChangeNotify();
453 }
454
455 template<typename Type>
456 class RemoveReference
457 {
458 public:
459 typedef Type type;
460 };
461
462 template<typename Type>
463 class RemoveReference<Type&>
464 {
465 public:
466 typedef Type type;
467 };
468
469 template<typename Functor>
470 class Dereference
471 {
472 const Functor& functor;
473 public:
474 typedef typename RemoveReference<typename Functor::first_argument_type>::type* first_argument_type;
475 typedef typename Functor::result_type result_type;
476 Dereference( const Functor& functor ) : functor( functor ){
477 }
478 result_type operator()( first_argument_type firstArgument ) const {
479         return functor( *firstArgument );
480 }
481 };
482
483 template<typename Functor>
484 inline Dereference<Functor> makeDereference( const Functor& functor ){
485         return Dereference<Functor>( functor );
486 }
487
488 typedef Face* FacePointer;
489 const FacePointer c_nullFacePointer = 0;
490
491 template<typename Predicate>
492 Face* Brush_findIf( const Brush& brush, const Predicate& predicate ){
493         Brush::const_iterator i = std::find_if( brush.begin(), brush.end(), makeDereference( predicate ) );
494         return i == brush.end() ? c_nullFacePointer : *i; // uses c_nullFacePointer instead of 0 because otherwise gcc 4.1 attempts conversion to int
495 }
496
497 template<typename Caller>
498 class BindArguments1
499 {
500 typedef typename Caller::second_argument_type FirstBound;
501 FirstBound firstBound;
502 public:
503 typedef typename Caller::result_type result_type;
504 typedef typename Caller::first_argument_type first_argument_type;
505 BindArguments1( FirstBound firstBound )
506         : firstBound( firstBound ){
507 }
508 result_type operator()( first_argument_type firstArgument ) const {
509         return Caller::call( firstArgument, firstBound );
510 }
511 };
512
513 template<typename Caller>
514 class BindArguments2
515 {
516 typedef typename Caller::second_argument_type FirstBound;
517 typedef typename Caller::third_argument_type SecondBound;
518 FirstBound firstBound;
519 SecondBound secondBound;
520 public:
521 typedef typename Caller::result_type result_type;
522 typedef typename Caller::first_argument_type first_argument_type;
523 BindArguments2( FirstBound firstBound, SecondBound secondBound )
524         : firstBound( firstBound ), secondBound( secondBound ){
525 }
526 result_type operator()( first_argument_type firstArgument ) const {
527         return Caller::call( firstArgument, firstBound, secondBound );
528 }
529 };
530
531 template<typename Caller, typename FirstBound, typename SecondBound>
532 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
533         return BindArguments2<Caller>( firstBound, secondBound );
534 }
535
536 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
537         return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
538 }
539 typedef Function3<const Face&, const Plane3&, bool, bool, Face_testPlane> FaceTestPlane;
540
541
542
543 /// \brief Returns true if
544 /// \li !flipped && brush is BACK or ON
545 /// \li flipped && brush is FRONT or ON
546 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
547         brush.evaluateBRep();
548 #if 1
549         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
550         {
551                 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
552                         return false;
553                 }
554         }
555         return true;
556 #else
557         return Brush_findIf( brush, bindArguments( FaceTestPlane(), makeReference( plane ), flipped ) ) == 0;
558 #endif
559 }
560
561 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
562         brush.evaluateBRep();
563         brushsplit_t split;
564         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
565         {
566                 if ( ( *i )->contributes() ) {
567                         split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
568                 }
569         }
570         return split;
571 }
572
573 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
574         if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
575                 brush_vector_t fragments;
576                 fragments.reserve( other.size() );
577                 Brush back( brush );
578
579                 for ( Brush::const_iterator i( other.begin() ); i != other.end(); ++i )
580                 {
581                         if ( ( *i )->contributes() ) {
582                                 brushsplit_t split = Brush_classifyPlane( back, ( *i )->plane3() );
583                                 if ( split.counts[ePlaneFront] != 0
584                                          && split.counts[ePlaneBack] != 0 ) {
585                                         fragments.push_back( new Brush( back ) );
586                                         Face* newFace = fragments.back()->addFace( *( *i ) );
587                                         if ( newFace != 0 ) {
588                                                 newFace->flipWinding();
589                                         }
590                                         back.addFace( *( *i ) );
591                                 }
592                                 else if ( split.counts[ePlaneBack] == 0 ) {
593                                         for ( brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i )
594                                         {
595                                                 delete( *i );
596                                         }
597                                         return false;
598                                 }
599                         }
600                 }
601                 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
602                 return true;
603         }
604         return false;
605 }
606
607 class SubtractBrushesFromUnselected : public scene::Graph::Walker
608 {
609 const brush_vector_t& m_brushlist;
610 std::size_t& m_before;
611 std::size_t& m_after;
612 mutable bool m_eraseParent;
613 public:
614 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
615         : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
616 }
617 bool pre( const scene::Path& path, scene::Instance& instance ) const {
618         if ( path.top().get().visible() ) {
619                 return true;
620         }
621         return false;
622 }
623 void post( const scene::Path& path, scene::Instance& instance ) const {
624         if ( path.top().get().visible() ) {
625                 Brush* brush = Node_getBrush( path.top() );
626                 if ( brush != 0
627                          && !Instance_getSelectable( instance )->isSelected() ) {
628                         brush_vector_t buffer[2];
629                         bool swap = false;
630                         Brush* original = new Brush( *brush );
631                         buffer[static_cast<std::size_t>( swap )].push_back( original );
632
633                         {
634                                 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
635                                 {
636                                         for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
637                                         {
638                                                 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
639                                                         delete ( *j );
640                                                 }
641                                                 else
642                                                 {
643                                                         buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
644                                                 }
645                                         }
646                                         buffer[static_cast<std::size_t>( swap )].clear();
647                                         swap = !swap;
648                                 }
649                         }
650
651                         brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
652
653                         if ( out.size() == 1 && out.back() == original ) {
654                                 delete original;
655                         }
656                         else
657                         {
658                                 ++m_before;
659                                 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
660                                 {
661                                         ++m_after;
662                                         ( *i )->removeEmptyFaces();
663                                         if ( !( *i )->empty() ) {
664                                                 NodeSmartReference node( ( new BrushNode() )->node() );
665                                                 Node_getBrush( node )->copy( *( *i ) );
666                                                 delete ( *i );
667                                                 Node_getTraversable( path.parent() )->insert( node );
668                                         }
669                                         else{
670                                                 delete ( *i );
671                                         }
672                                 }
673                                 scene::Node& parent = path.parent();
674                                 Path_deleteTop( path );
675                                 if( Node_getTraversable( parent )->empty() ){
676                                         m_eraseParent = true;
677                                 }
678                         }
679                 }
680         }
681         if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
682                 m_eraseParent = false;
683                 Entity* entity = Node_getEntity( path.top() );
684                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
685                         && Node_getTraversable( path.top() )->empty() ) {
686                         Path_deleteTop( path );
687                 }
688         }
689 }
690 };
691
692 void CSG_Subtract(){
693         brush_vector_t selected_brushes;
694         GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
695
696         if ( selected_brushes.empty() ) {
697                 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
698         }
699         else
700         {
701                 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
702
703                 UndoableCommand undo( "brushSubtract" );
704
705                 // subtract selected from unselected
706                 std::size_t before = 0;
707                 std::size_t after = 0;
708                 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
709                 globalOutputStream() << "CSG Subtract: Result: "
710                                                          << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
711                                                          << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
712
713                 SceneChangeNotify();
714         }
715 }
716
717 class BrushSplitByPlaneSelected : public scene::Graph::Walker
718 {
719 const Vector3& m_p0;
720 const Vector3& m_p1;
721 const Vector3& m_p2;
722 const char* m_shader;
723 const TextureProjection& m_projection;
724 EBrushSplit m_split;
725 public:
726 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
727         : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
728 }
729 bool pre( const scene::Path& path, scene::Instance& instance ) const {
730         return true;
731 }
732 void post( const scene::Path& path, scene::Instance& instance ) const {
733         if ( path.top().get().visible() ) {
734                 Brush* brush = Node_getBrush( path.top() );
735                 if ( brush != 0
736                          && Instance_getSelectable( instance )->isSelected() ) {
737                         Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
738                         if ( plane3_valid( plane ) ) {
739                                 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
740                                 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
741                                         // the plane intersects this brush
742                                         if ( m_split == eFrontAndBack ) {
743                                                 NodeSmartReference node( ( new BrushNode() )->node() );
744                                                 Brush* fragment = Node_getBrush( node );
745                                                 fragment->copy( *brush );
746                                                 Face* newFace = fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
747                                                 if ( newFace != 0 && m_split != eFront ) {
748                                                         newFace->flipWinding();
749                                                 }
750                                                 fragment->removeEmptyFaces();
751                                                 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
752
753                                                 Node_getTraversable( path.parent() )->insert( node );
754                                                 {
755                                                         scene::Path fragmentPath = path;
756                                                         fragmentPath.top() = makeReference( node.get() );
757                                                         selectPath( fragmentPath, true );
758                                                 }
759                                         }
760
761                                         Face* newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
762                                         if ( newFace != 0 && m_split == eFront ) {
763                                                 newFace->flipWinding();
764                                         }
765                                         brush->removeEmptyFaces();
766                                         ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
767                                 }
768                                 else
769                                 // the plane does not intersect this brush
770                                 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
771                                         // the brush is "behind" the plane
772                                         Path_deleteTop( path );
773                                 }
774                         }
775                 }
776         }
777 }
778 };
779
780 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
781         TextureProjection projection;
782         TexDef_Construct_Default( projection );
783         graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
784         SceneChangeNotify();
785 }
786
787
788 class BrushInstanceSetClipPlane : public scene::Graph::Walker
789 {
790 Plane3 m_plane;
791 public:
792 BrushInstanceSetClipPlane( const Plane3& plane )
793         : m_plane( plane ){
794 }
795 bool pre( const scene::Path& path, scene::Instance& instance ) const {
796         BrushInstance* brush = Instance_getBrush( instance );
797         if ( brush != 0
798                  && path.top().get().visible()
799                  && brush->isSelected() ) {
800                 BrushInstance& brushInstance = *brush;
801                 brushInstance.setClipPlane( m_plane );
802         }
803         return true;
804 }
805 };
806
807 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
808         graph.traverse( BrushInstanceSetClipPlane( plane ) );
809 }
810
811 /*
812    =============
813    CSG_Merge
814    =============
815  */
816 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
817         // gather potential outer faces
818
819         {
820                 typedef std::vector<const Face*> Faces;
821                 Faces faces;
822                 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
823                 {
824                         ( *i )->evaluateBRep();
825                         for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
826                         {
827                                 if ( !( *j )->contributes() ) {
828                                         continue;
829                                 }
830
831                                 const Face& face1 = *( *j );
832
833                                 bool skip = false;
834                                 // test faces of all input brushes
835                                 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
836                                 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
837                                 {
838                                         if ( k != i ) { // don't test a brush against itself
839                                                 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
840                                                 {
841                                                         const Face& face2 = *( *l );
842
843                                                         // face opposes another face
844                                                         if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
845                                                                 // skip opposing planes
846                                                                 skip  = true;
847                                                                 break;
848                                                         }
849                                                 }
850                                         }
851                                 }
852
853                                 // check faces already stored
854                                 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
855                                 {
856                                         const Face& face2 = *( *m );
857
858                                         // face equals another face
859                                         if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
860                                                 //if the texture/shader references should be the same but are not
861                                                 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
862                                                         return false;
863                                                 }
864                                                 // skip duplicate planes
865                                                 skip = true;
866                                                 break;
867                                         }
868
869                                         // face1 plane intersects face2 winding or vice versa
870                                         if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
871                                                 // result would not be convex
872                                                 return false;
873                                         }
874                                 }
875
876                                 if ( !skip ) {
877                                         faces.push_back( &face1 );
878                                 }
879                         }
880                 }
881                 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
882                 {
883                         if ( !brush.addFace( *( *i ) ) ) {
884                                 // result would have too many sides
885                                 return false;
886                         }
887                 }
888         }
889
890         brush.removeEmptyFaces();
891
892         return true;
893 }
894
895 void CSG_Merge( void ){
896         brush_vector_t selected_brushes;
897
898         // remove selected
899         GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
900
901         if ( selected_brushes.empty() ) {
902                 globalOutputStream() << "CSG Merge: No brushes selected.\n";
903                 return;
904         }
905
906         if ( selected_brushes.size() < 2 ) {
907                 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
908                 return;
909         }
910
911         globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
912
913         UndoableCommand undo( "brushMerge" );
914
915         scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
916
917         NodeSmartReference node( ( new BrushNode() )->node() );
918         Brush* brush = Node_getBrush( node );
919         // if the new brush would not be convex
920         if ( !Brush_merge( *brush, selected_brushes, true ) ) {
921                 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
922         }
923         else
924         {
925                 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
926
927                 // free the original brushes
928                 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
929
930                 merged_path.pop();
931                 Node_getTraversable( merged_path.top() )->insert( node );
932                 merged_path.push( makeReference( node.get() ) );
933
934                 selectPath( merged_path, true );
935
936                 globalOutputStream() << "CSG Merge: Succeeded.\n";
937                 SceneChangeNotify();
938         }
939 }
940
941
942
943
944
945
946 /*
947    =============
948    CSG_Tool
949    =============
950  */
951 #include "mainframe.h"
952 #include <gtk/gtk.h>
953 #include "gtkutil/dialog.h"
954 #include "gtkutil/button.h"
955 #include "gtkutil/accelerator.h"
956
957 struct CSGToolDialog
958 {
959         GtkSpinButton* spin;
960         GtkWindow *window;
961         GtkToggleButton *radXYZ, *radX, *radY, *radZ, *caulk, *removeInner;
962 };
963
964 CSGToolDialog g_csgtool_dialog;
965
966 DoubleVector3 getExclusion(){
967         if( gtk_toggle_button_get_active( g_csgtool_dialog.radX ) ){
968                 return DoubleVector3( 1, 0, 0 );
969         }
970         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radY ) ){
971                 return DoubleVector3( 0, 1, 0 );
972         }
973         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radZ ) ){
974                 return DoubleVector3( 0, 0, 1 );
975         }
976         return DoubleVector3( 0, 0, 0 );
977 }
978
979 bool getCaulk(){
980                 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
981                 return true;
982         }
983         return false;
984 }
985
986 bool getRemoveInner(){
987                 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
988                 return true;
989         }
990         return false;
991 }
992
993 class BrushFaceOffset
994 {
995 float offset;
996 public:
997 BrushFaceOffset( float offset )
998         : offset( offset ){
999 }
1000 void operator()( BrushInstance& brush ) const {
1001         double mindot = 0;
1002         double maxdot = 0;
1003         Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1004         Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
1005 }
1006 };
1007
1008 //=================DLG
1009
1010 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1011         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1012         UndoableCommand undo( "brushHollow::Diag" );
1013         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1014         if( getRemoveInner() )
1015                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1016         SceneChangeNotify();
1017         return TRUE;
1018 }
1019
1020 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1021         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1022         UndoableCommand undo( "brushHollow::Wrap" );
1023         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1024         if( getRemoveInner() )
1025                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1026         SceneChangeNotify();
1027         return TRUE;
1028 }
1029
1030 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1031         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1032         UndoableCommand undo( "brushHollow::Extrude" );
1033         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1034         if( getRemoveInner() )
1035                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1036         SceneChangeNotify();
1037         return TRUE;
1038 }
1039
1040 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1041         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1042         UndoableCommand undo( "brushHollow::Pull" );
1043         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1044         if( getRemoveInner() )
1045                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1046         SceneChangeNotify();
1047         return TRUE;
1048 }
1049
1050 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1051         gtk_spin_button_update ( dialog->spin );
1052         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1053         offset *= -1;
1054         UndoableCommand undo( "Shrink brush" );
1055 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1056         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1057         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1058         SceneChangeNotify();
1059         return TRUE;
1060 }
1061
1062 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1063         gtk_spin_button_update ( dialog->spin );
1064         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1065         UndoableCommand undo( "Expand brush" );
1066 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1067         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1068         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1069         SceneChangeNotify();
1070         return TRUE;
1071 }
1072
1073 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1074         gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1075         return TRUE;
1076 }
1077
1078 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1079         gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1080         return TRUE;
1081 }
1082
1083 void CSG_Tool(){
1084         if ( g_csgtool_dialog.window == NULL ) {
1085                 g_csgtool_dialog.window = create_dialog_window( MainFrame_getWindow(), "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1086                 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1087
1088                 //GtkAccelGroup* accel = gtk_accel_group_new();
1089                 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1090                 global_accel_connect_window( g_csgtool_dialog.window );
1091
1092                 {
1093                         GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1094                         gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1095                         {
1096                                 GtkTable* table = create_dialog_table( 3, 8, 4, 4 );
1097                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1098                                 {
1099                                         //GtkWidget* label = gtk_label_new( "<->" );
1100                                         //gtk_widget_show( label );
1101                                         GtkWidget* button = gtk_button_new_with_label( "Grid->" );
1102                                         gtk_table_attach( table, button, 0, 1, 0, 1,
1103                                                                           (GtkAttachOptions) ( 0 ),
1104                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1105                                         gtk_widget_show( button );
1106                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1107                                 }
1108                                 {
1109                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1110                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1111                                         gtk_widget_show( GTK_WIDGET( spin ) );
1112                                         gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1113                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1114                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1115                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1116                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1117                                         gtk_spin_button_set_numeric( spin, TRUE );
1118
1119                                         g_csgtool_dialog.spin = spin;
1120                                 }
1121                                 {
1122                                         //radio button group for choosing the exclude axis
1123                                         GtkWidget* radXYZ = gtk_radio_button_new_with_label( NULL, "XYZ" );
1124                                         GtkWidget* radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-X" );
1125                                         GtkWidget* radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Y" );
1126                                         GtkWidget* radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Z" );
1127                                         gtk_widget_show( radXYZ );
1128                                         gtk_widget_show( radX );
1129                                         gtk_widget_show( radY );
1130                                         gtk_widget_show( radZ );
1131
1132                                         gtk_table_attach( table, radXYZ, 2, 3, 0, 1,
1133                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1134                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1135                                         gtk_table_attach( table, radX, 3, 4, 0, 1,
1136                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1137                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1138                                         gtk_table_attach( table, radY, 4, 5, 0, 1,
1139                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1140                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1141                                         gtk_table_attach( table, radZ, 5, 6, 0, 1,
1142                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1143                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1144
1145                                         g_csgtool_dialog.radXYZ = GTK_TOGGLE_BUTTON( radXYZ );
1146                                         g_csgtool_dialog.radX = GTK_TOGGLE_BUTTON( radX );
1147                                         g_csgtool_dialog.radY = GTK_TOGGLE_BUTTON( radY );
1148                                         g_csgtool_dialog.radZ = GTK_TOGGLE_BUTTON( radZ );
1149                                 }
1150                                 {
1151                                         GtkWidget* button = gtk_toggle_button_new();
1152                                         button_set_icon( GTK_BUTTON( button ), "f-caulk.png" );
1153                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1154                                         gtk_table_attach( table, button, 6, 7, 0, 1,
1155                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1156                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1157                                         gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1158                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1159                                         gtk_widget_show( button );
1160                                         g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1161                                 }
1162                                 {
1163                                         GtkWidget* button = gtk_toggle_button_new();
1164                                         button_set_icon( GTK_BUTTON( button ), "csgtool_removeinner.png" );
1165                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1166                                         gtk_table_attach( table, button, 7, 8, 0, 1,
1167                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1168                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1169                                         gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1170                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1171                                         gtk_widget_show( button );
1172                                         g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1173                                 }
1174                                 {
1175                                         GtkWidget* sep = gtk_hseparator_new();
1176                                         gtk_widget_show( sep );
1177                                         gtk_table_attach( table, sep, 0, 8, 1, 2,
1178                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1179                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1180                                 }
1181                                 {
1182                                         GtkWidget* button = gtk_button_new();
1183                                         button_set_icon( GTK_BUTTON( button ), "csgtool_shrink.png" );
1184                                         gtk_table_attach( table, button, 0, 1, 2, 3,
1185                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1186                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1187                                         gtk_widget_set_tooltip_text( button, "Shrink brush" );
1188                                         gtk_widget_show( button );
1189                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1190                                 }
1191                                 {
1192                                         GtkWidget* button = gtk_button_new();
1193                                         button_set_icon( GTK_BUTTON( button ), "csgtool_expand.png" );
1194                                         gtk_table_attach( table, button, 1, 2, 2, 3,
1195                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1196                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1197                                         gtk_widget_set_tooltip_text( button, "Expand brush" );
1198                                         gtk_widget_show( button );
1199                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1200                                 }
1201                                 {
1202                                         GtkWidget* button = gtk_button_new();
1203                                         button_set_icon( GTK_BUTTON( button ), "csgtool_diagonal.png" );
1204                                         gtk_table_attach( table, button, 3, 4, 2, 3,
1205                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1206                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1207                                         gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1208                                         gtk_widget_show( button );
1209                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1210                                 }
1211                                 {
1212                                         GtkWidget* button = gtk_button_new();
1213                                         button_set_icon( GTK_BUTTON( button ), "csgtool_wrap.png" );
1214                                         gtk_table_attach( table, button, 4, 5, 2, 3,
1215                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1216                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1217                                         gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1218                                         gtk_widget_show( button );
1219                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1220                                 }
1221                                 {
1222                                         GtkWidget* button = gtk_button_new();
1223                                         button_set_icon( GTK_BUTTON( button ), "csgtool_extrude.png" );
1224                                         gtk_table_attach( table, button, 5, 6, 2, 3,
1225                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1226                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1227                                         gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1228                                         gtk_widget_show( button );
1229                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1230                                 }
1231                                 {
1232                                         GtkWidget* button = gtk_button_new();
1233                                         button_set_icon( GTK_BUTTON( button ), "csgtool_pull.png" );
1234                                         gtk_table_attach( table, button, 6, 7, 2, 3,
1235                                                                           (GtkAttachOptions) ( GTK_EXPAND ),
1236                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1237                                         gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1238                                         gtk_widget_show( button );
1239                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1240                                 }
1241
1242                         }
1243                 }
1244         }
1245
1246         gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1247         gtk_window_present( g_csgtool_dialog.window );
1248 }
1249