]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/csg.cpp
Merge commit '4616fea08c641887b841bffa3a974dae63b4c7e7' into garux-merge
[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 /*
34 void Face_makeBrush( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
35         if ( face.contributes() ) {
36                 out.push_back( new Brush( brush ) );
37                 Face* newFace = out.back()->addFace( face );
38                 face.getPlane().offset( -offset );
39                 face.planeChanged();
40                 if ( newFace != 0 ) {
41                         newFace->flipWinding();
42                         newFace->getPlane().offset( offset );
43                         newFace->planeChanged();
44                 }
45         }
46 }
47
48 void Face_extrude( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
49         if ( face.contributes() ) {
50                 face.getPlane().offset( offset );
51                 out.push_back( new Brush( brush ) );
52                 face.getPlane().offset( -offset );
53                 Face* newFace = out.back()->addFace( face );
54                 if ( newFace != 0 ) {
55                         newFace->flipWinding();
56                         newFace->planeChanged();
57                 }
58         }
59 }
60 */
61 #include "preferences.h"
62 #include "texwindow.h"
63
64
65 enum eHollowType
66 {
67         diag = 0,
68         wrap = 1,
69         extrude = 2,
70         pull = 3,
71         room = 4,
72 };
73
74 const char* getCaulkShader(){
75         const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
76         if ( gotShader && *gotShader ){
77                 return gotShader;
78         }
79         return "textures/common/caulk";
80 }
81
82 class CaulkFace
83 {
84 DoubleVector3 ExclusionAxis;
85 double &mindot;
86 double &maxdot;
87 public:
88 CaulkFace( DoubleVector3 ExclusionAxis,
89                         double &mindot,
90                         double &maxdot ):
91                         ExclusionAxis( ExclusionAxis ),
92                         mindot( mindot ),
93                         maxdot( maxdot ){}
94 void operator()( Face& face ) const {
95         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
96         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) )
97                 face.SetShader( getCaulkShader() );
98 }
99 };
100
101 class FaceMakeBrush
102 {
103 const Brush& brush;
104 brush_vector_t& out;
105 float offset;
106 eHollowType HollowType;
107 DoubleVector3 ExclusionAxis;
108 double &mindot;
109 double &maxdot;
110 bool caulk;
111 bool RemoveInner;
112 public:
113 FaceMakeBrush( const Brush& brush,
114                         brush_vector_t& out,
115                         float offset,
116                         eHollowType HollowType,
117                         DoubleVector3 ExclusionAxis,
118                         double &mindot,
119                         double &maxdot,
120                         bool caulk,
121                         bool RemoveInner )
122         : brush( brush ),
123         out( out ),
124         offset( offset ),
125         HollowType( HollowType ),
126         ExclusionAxis( ExclusionAxis ),
127         mindot( mindot ),
128         maxdot( maxdot ),
129         caulk( caulk ),
130         RemoveInner( RemoveInner ){
131 }
132 void operator()( Face& face ) const {
133         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
134         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
135                 if( HollowType == pull ){
136                         if ( face.contributes() ) {
137                                 face.getPlane().offset( offset );
138                                 face.planeChanged();
139                                 out.push_back( new Brush( brush ) );
140                                 face.getPlane().offset( -offset );
141                                 face.planeChanged();
142
143                                 if( caulk ){
144                                         Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot ) );
145                                 }
146                                 Face* newFace = out.back()->addFace( face );
147                                 if ( newFace != 0 ) {
148                                         newFace->flipWinding();
149                                 }
150                         }
151                 }
152                 else if( HollowType == wrap ){
153                         //Face_makeBrush( face, brush, out, offset );
154                         if ( face.contributes() ) {
155                                 face.undoSave();
156                                 out.push_back( new Brush( brush ) );
157                                 if( !RemoveInner && caulk )
158                                         face.SetShader( getCaulkShader() );
159                                 Face* newFace = out.back()->addFace( face );
160                                 face.getPlane().offset( -offset );
161                                 face.planeChanged();
162                                 if( caulk )
163                                         face.SetShader( getCaulkShader() );
164                                 if ( newFace != 0 ) {
165                                         newFace->flipWinding();
166                                         newFace->getPlane().offset( offset );
167                                         newFace->planeChanged();
168                                 }
169                         }
170                 }
171                 else if( HollowType == extrude ){
172                         if ( face.contributes() ) {
173                                 //face.undoSave();
174                                 out.push_back( new Brush( brush ) );
175                                 out.back()->clear();
176
177                                 Face* newFace = out.back()->addFace( face );
178                                 if ( newFace != 0 ) {
179                                         newFace->getPlane().offset( offset );
180                                         newFace->planeChanged();
181                                 }
182
183                                 if( !RemoveInner && caulk )
184                                         face.SetShader( getCaulkShader() );
185                                 newFace = out.back()->addFace( face );
186                                 if ( newFace != 0 ) {
187                                         newFace->flipWinding();
188                                 }
189                                 Winding& winding = face.getWinding();
190                                 TextureProjection projection;
191                                 TexDef_Construct_Default( projection );
192                                 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
193                                         std::size_t index = std::distance( winding.begin(), j );
194                                         std::size_t next = Winding_next( winding, index );
195
196                                         out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
197                                 }
198                         }
199                 }
200                 else if( HollowType == diag ){
201                         if ( face.contributes() ) {
202                                 out.push_back( new Brush( brush ) );
203                                 out.back()->clear();
204
205                                 Face* newFace = out.back()->addFace( face );
206                                 if ( newFace != 0 ) {
207
208                                         newFace->planeChanged();
209                                 }
210                                 newFace = out.back()->addFace( face );
211
212                                 if ( newFace != 0 ) {
213                                         if( !RemoveInner && caulk )
214                                                 newFace->SetShader( getCaulkShader() );
215                                         newFace->flipWinding();
216                                         newFace->getPlane().offset( offset );
217                                         newFace->planeChanged();
218                                 }
219
220                                 Winding& winding = face.getWinding();
221                                 TextureProjection projection;
222                                 TexDef_Construct_Default( projection );
223                                 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
224                                         std::size_t index = std::distance( winding.begin(), i );
225                                         std::size_t next = Winding_next( winding, index );
226                                         Vector3 BestPoint;
227                                         float bestdist = 999999;
228
229                                         for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
230                                                 Winding& winding2 = ( *j )->getWinding();
231                                                 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
232                                                         std::size_t index2 = std::distance( winding2.begin(), k );
233                                                         float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
234                                                         if( testdist < bestdist ){
235                                                                 bestdist = testdist;
236                                                                 BestPoint = winding2[index2].vertex;
237                                                         }
238                                                 }
239                                         }
240                                         out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
241                                 }
242                         }
243                 }
244         }
245 }
246 };
247
248 class FaceExclude
249 {
250 DoubleVector3 ExclusionAxis;
251 double &mindot;
252 double &maxdot;
253 public:
254 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
255         : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
256 }
257 void operator()( Face& face ) const {
258         if( vector3_length_squared( ExclusionAxis ) != 0 ){
259                 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
260                 if( dot < mindot ){
261                         mindot = dot;
262                 }
263                 else if( dot > maxdot ){
264                         maxdot = dot;
265                 }
266         }
267 }
268 };
269
270 class FaceOffset
271 {
272 float offset;
273 DoubleVector3 ExclusionAxis;
274 double &mindot;
275 double &maxdot;
276 public:
277 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
278         : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
279 }
280 void operator()( Face& face ) const {
281         double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
282         if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
283                 face.undoSave();
284                 face.getPlane().offset( offset );
285                 face.planeChanged();
286         }
287 }
288 };
289
290
291 DoubleVector3 getExclusion();
292 bool getCaulk();
293 bool getRemoveInner();
294
295 class BrushHollowSelectedWalker : public scene::Graph::Walker
296 {
297 float offset;
298 eHollowType HollowType;
299 public:
300 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
301         : offset( offset ), HollowType( HollowType ){
302 }
303 bool pre( const scene::Path& path, scene::Instance& instance ) const {
304         if ( path.top().get().visible() ) {
305                 Brush* brush = Node_getBrush( path.top() );
306                 if ( brush != 0
307                          && Instance_getSelectable( instance )->isSelected()
308                          && path.size() > 1 ) {
309                         brush_vector_t out;
310                         double mindot = 0;
311                         double maxdot = 0;
312                         if( HollowType != room ){
313                                 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
314                         }
315                         if( HollowType == room ){
316                                 Brush* tmpbrush = new Brush( *brush );
317                                 tmpbrush->removeEmptyFaces();
318                                 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, true, true ) );
319                                 delete tmpbrush;
320                         }
321                         else if( HollowType == pull ){
322                                 if( !getRemoveInner() && getCaulk() ){
323                                         Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
324                                 }
325                                 Brush* tmpbrush = new Brush( *brush );
326                                 tmpbrush->removeEmptyFaces();
327                                 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
328                                 delete tmpbrush;
329                         }
330                         else if( HollowType == diag ){
331                                 Brush* tmpbrush = new Brush( *brush );
332                                 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
333                                 tmpbrush->removeEmptyFaces();
334                                 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
335                                 delete tmpbrush;
336                                 if( !getRemoveInner() && getCaulk() ){
337                                         Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
338                                 }
339                         }
340                         else{
341                                 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
342                         }
343                         for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
344                         {
345                                 ( *i )->removeEmptyFaces();
346                                 if( ( *i )->hasContributingFaces() ){
347                                         NodeSmartReference node( ( new BrushNode() )->node() );
348                                         Node_getBrush( node )->copy( *( *i ) );
349                                         delete ( *i );
350                                         Node_getTraversable( path.parent() )->insert( node );
351                                         //path.push( makeReference( node.get() ) );
352                                         //selectPath( path, true );
353                                         //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
354                                         //Path_deleteTop( path );
355                                 }
356                         }
357                 }
358         }
359         return true;
360 }
361 };
362
363 typedef std::list<Brush*> brushlist_t;
364
365 class BrushGatherSelected : public scene::Graph::Walker
366 {
367 brush_vector_t& m_brushlist;
368 public:
369 BrushGatherSelected( brush_vector_t& brushlist )
370         : m_brushlist( brushlist ){
371 }
372
373 bool pre( const scene::Path& path, scene::Instance& instance ) const {
374         if ( path.top().get().visible() ) {
375                 Brush* brush = Node_getBrush( path.top() );
376                 if ( brush != 0
377                          && Instance_getSelectable( instance )->isSelected() ) {
378                         m_brushlist.push_back( brush );
379                 }
380         }
381         return true;
382 }
383 };
384 /*
385 class BrushDeleteSelected : public scene::Graph::Walker
386 {
387 public:
388 bool pre( const scene::Path& path, scene::Instance& instance ) const {
389         return true;
390 }
391 void post( const scene::Path& path, scene::Instance& instance ) const {
392         if ( path.top().get().visible() ) {
393                 Brush* brush = Node_getBrush( path.top() );
394                 if ( brush != 0
395                          && Instance_getSelectable( instance )->isSelected()
396                          && path.size() > 1 ) {
397                         Path_deleteTop( path );
398                 }
399         }
400 }
401 };
402 */
403 #include "ientity.h"
404
405 class BrushDeleteSelected : public scene::Graph::Walker
406 {
407 scene::Node* m_keepNode;
408 mutable bool m_eraseParent;
409 public:
410 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
411 }
412 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
413 }
414 bool pre( const scene::Path& path, scene::Instance& instance ) const {
415         return true;
416 }
417 void post( const scene::Path& path, scene::Instance& instance ) const {
418         //globalOutputStream() << path.size() << "\n";
419         if ( path.top().get().visible() ) {
420                 Brush* brush = Node_getBrush( path.top() );
421                 if ( brush != 0
422                          && Instance_getSelectable( instance )->isSelected()
423                          && path.size() > 1 ) {
424                         Path_deleteTop( path );
425                         if( Node_getTraversable( path.parent() )->empty() ){
426                                 m_eraseParent = true;
427                                 //globalOutputStream() << "Empty node?!.\n";
428                         }
429                 }
430         }
431         if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
432                 //globalOutputStream() << "about to Delete empty node!.\n";
433                 m_eraseParent = false;
434                 Entity* entity = Node_getEntity( path.top() );
435                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
436                         && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
437                         //globalOutputStream() << "now Deleting empty node!.\n";
438                         Path_deleteTop( path );
439                 }
440         }
441 }
442 };
443
444 /*
445    =============
446    CSG_MakeRoom
447    =============
448  */
449 void CSG_MakeRoom( void ){
450         UndoableCommand undo( "makeRoom" );
451         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
452         GlobalSceneGraph().traverse( BrushDeleteSelected() );
453         SceneChangeNotify();
454 }
455
456 template<typename Type>
457 class RemoveReference
458 {
459 public:
460 typedef Type type;
461 };
462
463 template<typename Type>
464 class RemoveReference<Type&>
465 {
466 public:
467 typedef Type type;
468 };
469
470 template<typename Functor>
471 class Dereference
472 {
473 const Functor& functor;
474 public:
475 Dereference( const Functor& functor ) : functor( functor ){
476 }
477 get_result_type<Functor> operator()( typename RemoveReference<get_argument<Functor, 0>>::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 get_argument<Caller, 1> FirstBound;
500 FirstBound firstBound;
501 public:
502 BindArguments1( FirstBound firstBound )
503         : firstBound( firstBound ){
504 }
505
506 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
507         return Caller::call( firstArgument, firstBound );
508 }
509 };
510
511 template<typename Caller>
512 class BindArguments2
513 {
514 typedef get_argument<Caller, 1> FirstBound;
515 typedef get_argument<Caller, 2> SecondBound;
516 FirstBound firstBound;
517 SecondBound secondBound;
518 public:
519 BindArguments2( FirstBound firstBound, SecondBound secondBound )
520         : firstBound( firstBound ), secondBound( secondBound ){
521 }
522
523 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
524         return Caller::call( firstArgument, firstBound, secondBound );
525 }
526 };
527
528 template<typename Caller, typename FirstBound, typename SecondBound>
529 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
530         return BindArguments2<Caller>( firstBound, secondBound );
531 }
532
533 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
534         return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
535 }
536
537 typedef Function<bool ( const Face &, const Plane3 &, bool ), Face_testPlane> FaceTestPlane;
538
539
540 /// \brief Returns true if
541 /// \li !flipped && brush is BACK or ON
542 /// \li flipped && brush is FRONT or ON
543 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
544         brush.evaluateBRep();
545 #if 1
546         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
547         {
548                 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
549                         return false;
550                 }
551         }
552         return true;
553 #else
554         return Brush_findIf( brush, bindArguments( FaceTestPlane(), makeReference( plane ), flipped ) ) == 0;
555 #endif
556 }
557
558 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
559         brush.evaluateBRep();
560         brushsplit_t split;
561         for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
562         {
563                 if ( ( *i )->contributes() ) {
564                         split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
565                 }
566         }
567         return split;
568 }
569
570 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
571         if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
572                 brush_vector_t fragments;
573                 fragments.reserve( other.size() );
574                 Brush back( brush );
575
576                 for ( Brush::const_iterator i( other.begin() ); i != other.end(); ++i )
577                 {
578                         if ( ( *i )->contributes() ) {
579                                 brushsplit_t split = Brush_classifyPlane( back, ( *i )->plane3() );
580                                 if ( split.counts[ePlaneFront] != 0
581                                          && split.counts[ePlaneBack] != 0 ) {
582                                         fragments.push_back( new Brush( back ) );
583                                         Face* newFace = fragments.back()->addFace( *( *i ) );
584                                         if ( newFace != 0 ) {
585                                                 newFace->flipWinding();
586                                         }
587                                         back.addFace( *( *i ) );
588                                 }
589                                 else if ( split.counts[ePlaneBack] == 0 ) {
590                                         for ( brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i )
591                                         {
592                                                 delete( *i );
593                                         }
594                                         return false;
595                                 }
596                         }
597                 }
598                 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
599                 return true;
600         }
601         return false;
602 }
603
604 class SubtractBrushesFromUnselected : public scene::Graph::Walker
605 {
606 const brush_vector_t& m_brushlist;
607 std::size_t& m_before;
608 std::size_t& m_after;
609 mutable bool m_eraseParent;
610 public:
611 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
612         : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
613 }
614
615 bool pre( const scene::Path& path, scene::Instance& instance ) const {
616         if ( path.top().get().visible() ) {
617                 return true;
618         }
619         return false;
620 }
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
728 bool pre( const scene::Path& path, scene::Instance& instance ) const {
729         return true;
730 }
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
796 bool pre( const scene::Path& path, scene::Instance& instance ) const {
797         BrushInstance* brush = Instance_getBrush( instance );
798         if ( brush != 0
799                  && path.top().get().visible()
800                  && brush->isSelected() ) {
801                 BrushInstance& brushInstance = *brush;
802                 brushInstance.setClipPlane( m_plane );
803         }
804         return true;
805 }
806 };
807
808 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
809         graph.traverse( BrushInstanceSetClipPlane( plane ) );
810 }
811
812 /*
813    =============
814    CSG_Merge
815    =============
816  */
817 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
818         // gather potential outer faces
819
820         {
821                 typedef std::vector<const Face*> Faces;
822                 Faces faces;
823                 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
824                 {
825                         ( *i )->evaluateBRep();
826                         for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
827                         {
828                                 if ( !( *j )->contributes() ) {
829                                         continue;
830                                 }
831
832                                 const Face& face1 = *( *j );
833
834                                 bool skip = false;
835                                 // test faces of all input brushes
836                                 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
837                                 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
838                                 {
839                                         if ( k != i ) { // don't test a brush against itself
840                                                 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
841                                                 {
842                                                         const Face& face2 = *( *l );
843
844                                                         // face opposes another face
845                                                         if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
846                                                                 // skip opposing planes
847                                                                 skip  = true;
848                                                                 break;
849                                                         }
850                                                 }
851                                         }
852                                 }
853
854                                 // check faces already stored
855                                 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
856                                 {
857                                         const Face& face2 = *( *m );
858
859                                         // face equals another face
860                                         if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
861                                                 //if the texture/shader references should be the same but are not
862                                                 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
863                                                         return false;
864                                                 }
865                                                 // skip duplicate planes
866                                                 skip = true;
867                                                 break;
868                                         }
869
870                                         // face1 plane intersects face2 winding or vice versa
871                                         if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
872                                                 // result would not be convex
873                                                 return false;
874                                         }
875                                 }
876
877                                 if ( !skip ) {
878                                         faces.push_back( &face1 );
879                                 }
880                         }
881                 }
882                 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
883                 {
884                         if ( !brush.addFace( *( *i ) ) ) {
885                                 // result would have too many sides
886                                 return false;
887                         }
888                 }
889         }
890
891         brush.removeEmptyFaces();
892
893         return true;
894 }
895
896 void CSG_Merge( void ){
897         brush_vector_t selected_brushes;
898
899         // remove selected
900         GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
901
902         if ( selected_brushes.empty() ) {
903                 globalOutputStream() << "CSG Merge: No brushes selected.\n";
904                 return;
905         }
906
907         if ( selected_brushes.size() < 2 ) {
908                 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
909                 return;
910         }
911
912         globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
913
914         UndoableCommand undo( "brushMerge" );
915
916         scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
917
918         NodeSmartReference node( ( new BrushNode() )->node() );
919         Brush* brush = Node_getBrush( node );
920         // if the new brush would not be convex
921         if ( !Brush_merge( *brush, selected_brushes, true ) ) {
922                 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
923         }
924         else
925         {
926                 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
927
928                 // free the original brushes
929                 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
930
931                 merged_path.pop();
932                 Node_getTraversable( merged_path.top() )->insert( node );
933                 merged_path.push( makeReference( node.get() ) );
934
935                 selectPath( merged_path, true );
936
937                 globalOutputStream() << "CSG Merge: Succeeded.\n";
938                 SceneChangeNotify();
939         }
940 }
941
942
943
944
945
946
947 /*
948    =============
949    CSG_Tool
950    =============
951  */
952 #include "mainframe.h"
953 #include <gtk/gtk.h>
954 #include "gtkutil/dialog.h"
955 #include "gtkutil/button.h"
956 #include "gtkutil/accelerator.h"
957
958 struct CSGToolDialog
959 {
960         GtkSpinButton* spin;
961         bool allocated{false};
962         ui::Window window{ui::null};
963         GtkToggleButton *radXYZ, *radX, *radY, *radZ, *caulk, *removeInner;
964 };
965
966 CSGToolDialog g_csgtool_dialog;
967
968 DoubleVector3 getExclusion(){
969         if( gtk_toggle_button_get_active( g_csgtool_dialog.radX ) ){
970                 return DoubleVector3( 1, 0, 0 );
971         }
972         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radY ) ){
973                 return DoubleVector3( 0, 1, 0 );
974         }
975         else if( gtk_toggle_button_get_active( g_csgtool_dialog.radZ ) ){
976                 return DoubleVector3( 0, 0, 1 );
977         }
978         return DoubleVector3( 0, 0, 0 );
979 }
980
981 bool getCaulk(){
982                 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
983                 return true;
984         }
985         return false;
986 }
987
988 bool getRemoveInner(){
989                 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
990                 return true;
991         }
992         return false;
993 }
994
995 class BrushFaceOffset
996 {
997 float offset;
998 public:
999 BrushFaceOffset( float offset )
1000         : offset( offset ){
1001 }
1002 void operator()( BrushInstance& brush ) const {
1003         double mindot = 0;
1004         double maxdot = 0;
1005         Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1006         Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
1007 }
1008 };
1009
1010 //=================DLG
1011
1012 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1013         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1014         UndoableCommand undo( "brushHollow::Diag" );
1015         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1016         if( getRemoveInner() )
1017                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1018         SceneChangeNotify();
1019         return TRUE;
1020 }
1021
1022 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1023         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1024         UndoableCommand undo( "brushHollow::Wrap" );
1025         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1026         if( getRemoveInner() )
1027                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1028         SceneChangeNotify();
1029         return TRUE;
1030 }
1031
1032 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1033         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1034         UndoableCommand undo( "brushHollow::Extrude" );
1035         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1036         if( getRemoveInner() )
1037                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1038         SceneChangeNotify();
1039         return TRUE;
1040 }
1041
1042 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1043         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1044         UndoableCommand undo( "brushHollow::Pull" );
1045         GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1046         if( getRemoveInner() )
1047                 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1048         SceneChangeNotify();
1049         return TRUE;
1050 }
1051
1052 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1053         gtk_spin_button_update ( dialog->spin );
1054         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1055         offset *= -1;
1056         UndoableCommand undo( "Shrink brush" );
1057 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1058         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1059         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1060         SceneChangeNotify();
1061         return TRUE;
1062 }
1063
1064 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1065         gtk_spin_button_update ( dialog->spin );
1066         float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1067         UndoableCommand undo( "Expand brush" );
1068 //      GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1069         //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1070         Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1071         SceneChangeNotify();
1072         return TRUE;
1073 }
1074
1075 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1076         gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1077         return TRUE;
1078 }
1079
1080 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1081         gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1082         return TRUE;
1083 }
1084
1085 void CSG_Tool(){
1086         // FIXME: there is probably improvements to do less raw GTK stuff, more GTK wrapper
1087         if ( !g_csgtool_dialog.allocated ) {
1088                 g_csgtool_dialog.allocated = true;
1089                 g_csgtool_dialog.window = MainFrame_getWindow().create_dialog_window( "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1090                 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1091
1092                 //GtkAccelGroup* accel = gtk_accel_group_new();
1093                 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1094                 global_accel_connect_window( g_csgtool_dialog.window );
1095
1096                 {
1097                         auto hbox = create_dialog_hbox( 4, 4 );
1098                         gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1099                         {
1100                                 auto table = create_dialog_table( 3, 8, 4, 4 );
1101                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1102                                 {
1103                                         //GtkWidget* label = gtk_label_new( "<->" );
1104                                         //gtk_widget_show( label );
1105                                         auto button = ui::Button( "Grid->" );
1106                                         table.attach( button, {0, 1, 0, 1}, {0, 0} );
1107                                         button.show();
1108                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1109                                 }
1110                                 {
1111                                         GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1112                                         GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1113                                         gtk_widget_show( GTK_WIDGET( spin ) );
1114                                         gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1115                                         gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1116                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1117                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
1118                                         gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1119                                         gtk_spin_button_set_numeric( spin, TRUE );
1120
1121                                         g_csgtool_dialog.spin = spin;
1122                                 }
1123                                 {
1124                                         //radio button group for choosing the exclude axis
1125                                         GtkWidget* radXYZ = gtk_radio_button_new_with_label( NULL, "XYZ" );
1126                                         GtkWidget* radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-X" );
1127                                         GtkWidget* radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Y" );
1128                                         GtkWidget* radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Z" );
1129                                         gtk_widget_show( radXYZ );
1130                                         gtk_widget_show( radX );
1131                                         gtk_widget_show( radY );
1132                                         gtk_widget_show( radZ );
1133
1134                                         gtk_table_attach( table, radXYZ, 2, 3, 0, 1,
1135                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1136                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1137                                         gtk_table_attach( table, radX, 3, 4, 0, 1,
1138                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1139                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1140                                         gtk_table_attach( table, radY, 4, 5, 0, 1,
1141                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1142                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1143                                         gtk_table_attach( table, radZ, 5, 6, 0, 1,
1144                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1145                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
1146
1147                                         g_csgtool_dialog.radXYZ = GTK_TOGGLE_BUTTON( radXYZ );
1148                                         g_csgtool_dialog.radX = GTK_TOGGLE_BUTTON( radX );
1149                                         g_csgtool_dialog.radY = GTK_TOGGLE_BUTTON( radY );
1150                                         g_csgtool_dialog.radZ = GTK_TOGGLE_BUTTON( radZ );
1151                                 }
1152                                 {
1153                                         GtkWidget* button = gtk_toggle_button_new();
1154                                         auto ubutton = ui::Button::from( button );
1155                                         button_set_icon( ubutton, "f-caulk.png" );
1156                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1157                                         table.attach( ubutton, { 6, 7, 0, 1 }, { GTK_EXPAND, 0 } );
1158                                         gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1159                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1160                                         ubutton.show();
1161                                         g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1162                                 }
1163                                 {
1164                                         GtkWidget* button = gtk_toggle_button_new();
1165                                         auto ubutton = ui::Button::from( button );
1166                                         button_set_icon( ubutton, "csgtool_removeinner.png" );
1167                                         gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1168                                         table.attach( ubutton, { 7, 8, 0, 1 }, { GTK_EXPAND, 0 } );
1169                                         gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1170                                         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1171                                         ubutton.show();
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                                         auto ubutton = ui::Button::from( button );
1184                                         button_set_icon( ubutton, "csgtool_shrink.png" );
1185                                         table.attach( ubutton, { 0, 1, 2, 3 }, { GTK_EXPAND, 0 } );
1186                                         gtk_widget_set_tooltip_text( button, "Shrink brush" );
1187                                         ubutton.show();
1188                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1189                                 }
1190                                 {
1191                                         GtkWidget* button = gtk_button_new();
1192                                         auto ubutton = ui::Button::from( button );
1193                                         button_set_icon( ubutton, "csgtool_expand.png" );
1194                                         table.attach( ubutton, { 1, 2, 2, 3 }, { GTK_EXPAND, 0 } );
1195                                         gtk_widget_set_tooltip_text( button, "Expand brush" );
1196                                         ubutton.show();
1197                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1198                                 }
1199                                 {
1200                                         GtkWidget* button = gtk_button_new();
1201                                         auto ubutton = ui::Button::from( button );
1202                                         button_set_icon( ubutton, "csgtool_diagonal.png" );
1203                                         table.attach( ubutton, { 3, 4, 2, 3 }, { GTK_EXPAND, 0 } );
1204                                         gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1205                                         ubutton.show();
1206                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1207                                 }
1208                                 {
1209                                         GtkWidget* button = gtk_button_new();
1210                                         auto ubutton = ui::Button::from( button );
1211                                         button_set_icon( ubutton, "csgtool_wrap.png" );
1212                                         table.attach( ubutton, { 4, 5, 2, 3 }, { GTK_EXPAND, 0 } );
1213                                         gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1214                                         ubutton.show();
1215                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1216                                 }
1217                                 {
1218                                         GtkWidget* button = gtk_button_new();
1219                                         auto ubutton = ui::Button::from( button );
1220                                         button_set_icon( ubutton, "csgtool_extrude.png" );
1221                                         table.attach( ubutton, { 5, 6, 2, 3 }, { GTK_EXPAND, 0 } );
1222                                         gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1223                                         ubutton.show();
1224                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1225                                 }
1226                                 {
1227                                         GtkWidget* button = gtk_button_new();
1228                                         auto ubutton = ui::Button::from( button );
1229                                         button_set_icon( ubutton, "csgtool_pull.png" );
1230                                         table.attach( ubutton, { 6, 7, 2, 3 }, { GTK_EXPAND, 0 } );
1231                                         gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1232                                         ubutton.show();
1233                                         g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1234                                 }
1235
1236                         }
1237                 }
1238         }
1239
1240         gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1241         gtk_window_present( g_csgtool_dialog.window );
1242 }
1243