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