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