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