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