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