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