2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
24 #include "debugging/debugging.h"
29 #include "brushmanip.h"
30 #include "brushnode.h"
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 );
40 newFace->flipWinding();
41 newFace->getPlane().offset( offset );
42 newFace->planeChanged();
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 );
54 newFace->flipWinding();
55 newFace->planeChanged();
60 #include "preferences.h"
61 #include "texwindow.h"
73 const char* getCaulkShader(){
74 const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
75 if ( gotShader && *gotShader ){
78 return "textures/common/caulk";
83 DoubleVector3 ExclusionAxis;
87 CaulkFace( DoubleVector3 ExclusionAxis,
90 ExclusionAxis( ExclusionAxis ),
93 void operator()( Face& face ) const {
94 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
95 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) )
96 face.SetShader( getCaulkShader() );
105 eHollowType HollowType;
106 DoubleVector3 ExclusionAxis;
112 FaceMakeBrush( const Brush& brush,
115 eHollowType HollowType,
116 DoubleVector3 ExclusionAxis,
124 HollowType( HollowType ),
125 ExclusionAxis( ExclusionAxis ),
129 RemoveInner( RemoveInner ){
131 void operator()( Face& face ) const {
132 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
133 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
134 if( HollowType == pull ){
135 if ( face.contributes() ) {
136 face.getPlane().offset( offset );
138 out.push_back( new Brush( brush ) );
139 face.getPlane().offset( -offset );
143 Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot ) );
145 Face* newFace = out.back()->addFace( face );
146 if ( newFace != 0 ) {
147 newFace->flipWinding();
151 else if( HollowType == wrap ){
152 //Face_makeBrush( face, brush, out, offset );
153 if ( face.contributes() ) {
155 out.push_back( new Brush( brush ) );
156 if( !RemoveInner && caulk )
157 face.SetShader( getCaulkShader() );
158 Face* newFace = out.back()->addFace( face );
159 face.getPlane().offset( -offset );
162 face.SetShader( getCaulkShader() );
163 if ( newFace != 0 ) {
164 newFace->flipWinding();
165 newFace->getPlane().offset( offset );
166 newFace->planeChanged();
170 else if( HollowType == extrude ){
171 if ( face.contributes() ) {
173 out.push_back( new Brush( brush ) );
176 Face* newFace = out.back()->addFace( face );
177 if ( newFace != 0 ) {
178 newFace->getPlane().offset( offset );
179 newFace->planeChanged();
182 if( !RemoveInner && caulk )
183 face.SetShader( getCaulkShader() );
184 newFace = out.back()->addFace( face );
185 if ( newFace != 0 ) {
186 newFace->flipWinding();
188 Winding& winding = face.getWinding();
189 TextureProjection projection;
190 TexDef_Construct_Default( projection );
191 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
192 std::size_t index = std::distance( winding.begin(), j );
193 std::size_t next = Winding_next( winding, index );
195 out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
199 else if( HollowType == diag ){
200 if ( face.contributes() ) {
201 out.push_back( new Brush( brush ) );
204 Face* newFace = out.back()->addFace( face );
205 if ( newFace != 0 ) {
207 newFace->planeChanged();
209 newFace = out.back()->addFace( face );
211 if ( newFace != 0 ) {
212 if( !RemoveInner && caulk )
213 newFace->SetShader( getCaulkShader() );
214 newFace->flipWinding();
215 newFace->getPlane().offset( offset );
216 newFace->planeChanged();
219 Winding& winding = face.getWinding();
220 TextureProjection projection;
221 TexDef_Construct_Default( projection );
222 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
223 std::size_t index = std::distance( winding.begin(), i );
224 std::size_t next = Winding_next( winding, index );
226 float bestdist = 999999;
228 for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
229 Winding& winding2 = ( *j )->getWinding();
230 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
231 std::size_t index2 = std::distance( winding2.begin(), k );
232 float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
233 if( testdist < bestdist ){
235 BestPoint = winding2[index2].vertex;
239 out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
249 DoubleVector3 ExclusionAxis;
253 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
254 : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
256 void operator()( Face& face ) const {
257 if( vector3_length_squared( ExclusionAxis ) != 0 ){
258 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
262 else if( dot > maxdot ){
272 DoubleVector3 ExclusionAxis;
276 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
277 : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
279 void operator()( Face& face ) const {
280 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
281 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
283 face.getPlane().offset( offset );
290 DoubleVector3 getExclusion();
292 bool getRemoveInner();
294 class BrushHollowSelectedWalker : public scene::Graph::Walker
297 eHollowType HollowType;
299 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
300 : offset( offset ), HollowType( HollowType ){
302 bool pre( const scene::Path& path, scene::Instance& instance ) const {
303 if ( path.top().get().visible() ) {
304 Brush* brush = Node_getBrush( path.top() );
306 && Instance_getSelectable( instance )->isSelected()
307 && path.size() > 1 ) {
311 if( HollowType != room ){
312 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
314 if( HollowType == room ){
315 Brush* tmpbrush = new Brush( *brush );
316 tmpbrush->removeEmptyFaces();
317 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, true, true ) );
320 else if( HollowType == pull ){
321 if( !getRemoveInner() && getCaulk() ){
322 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
324 Brush* tmpbrush = new Brush( *brush );
325 tmpbrush->removeEmptyFaces();
326 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
329 else if( HollowType == diag ){
330 Brush* tmpbrush = new Brush( *brush );
331 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
332 tmpbrush->removeEmptyFaces();
333 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
335 if( !getRemoveInner() && getCaulk() ){
336 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
340 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
342 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
344 ( *i )->removeEmptyFaces();
345 if( ( *i )->hasContributingFaces() ){
346 NodeSmartReference node( ( new BrushNode() )->node() );
347 Node_getBrush( node )->copy( *( *i ) );
349 Node_getTraversable( path.parent() )->insert( node );
350 //path.push( makeReference( node.get() ) );
351 //selectPath( path, true );
352 //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
353 //Path_deleteTop( path );
362 typedef std::list<Brush*> brushlist_t;
364 class BrushGatherSelected : public scene::Graph::Walker
366 brush_vector_t& m_brushlist;
368 BrushGatherSelected( brush_vector_t& brushlist )
369 : m_brushlist( brushlist ){
371 bool pre( const scene::Path& path, scene::Instance& instance ) const {
372 if ( path.top().get().visible() ) {
373 Brush* brush = Node_getBrush( path.top() );
375 && Instance_getSelectable( instance )->isSelected() ) {
376 m_brushlist.push_back( brush );
383 class BrushDeleteSelected : public scene::Graph::Walker
386 bool pre( const scene::Path& path, scene::Instance& instance ) const {
389 void post( const scene::Path& path, scene::Instance& instance ) const {
390 if ( path.top().get().visible() ) {
391 Brush* brush = Node_getBrush( path.top() );
393 && Instance_getSelectable( instance )->isSelected()
394 && path.size() > 1 ) {
395 Path_deleteTop( path );
403 class BrushDeleteSelected : public scene::Graph::Walker
405 scene::Node* m_keepNode;
406 mutable bool m_eraseParent;
408 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
410 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
412 bool pre( const scene::Path& path, scene::Instance& instance ) const {
415 void post( const scene::Path& path, scene::Instance& instance ) const {
416 //globalOutputStream() << path.size() << "\n";
417 if ( path.top().get().visible() ) {
418 Brush* brush = Node_getBrush( path.top() );
420 && Instance_getSelectable( instance )->isSelected()
421 && path.size() > 1 ) {
422 Path_deleteTop( path );
423 if( Node_getTraversable( path.parent() )->empty() ){
424 m_eraseParent = true;
425 //globalOutputStream() << "Empty node?!.\n";
429 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
430 //globalOutputStream() << "about to Delete empty node!.\n";
431 m_eraseParent = false;
432 Entity* entity = Node_getEntity( path.top() );
433 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
434 && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
435 //globalOutputStream() << "now Deleting empty node!.\n";
436 Path_deleteTop( path );
447 void CSG_MakeRoom( void ){
448 UndoableCommand undo( "makeRoom" );
449 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
450 GlobalSceneGraph().traverse( BrushDeleteSelected() );
454 template<typename Type>
455 class RemoveReference
461 template<typename Type>
462 class RemoveReference<Type&>
468 template<typename Functor>
471 const Functor& functor;
473 typedef typename RemoveReference<typename Functor::first_argument_type>::type* first_argument_type;
474 typedef typename Functor::result_type result_type;
475 Dereference( const Functor& functor ) : functor( functor ){
477 result_type operator()( first_argument_type firstArgument ) const {
478 return functor( *firstArgument );
482 template<typename Functor>
483 inline Dereference<Functor> makeDereference( const Functor& functor ){
484 return Dereference<Functor>( functor );
487 typedef Face* FacePointer;
488 const FacePointer c_nullFacePointer = 0;
490 template<typename Predicate>
491 Face* Brush_findIf( const Brush& brush, const Predicate& predicate ){
492 Brush::const_iterator i = std::find_if( brush.begin(), brush.end(), makeDereference( predicate ) );
493 return i == brush.end() ? c_nullFacePointer : *i; // uses c_nullFacePointer instead of 0 because otherwise gcc 4.1 attempts conversion to int
496 template<typename Caller>
499 typedef typename Caller::second_argument_type FirstBound;
500 FirstBound firstBound;
502 typedef typename Caller::result_type result_type;
503 typedef typename Caller::first_argument_type first_argument_type;
504 BindArguments1( FirstBound firstBound )
505 : firstBound( firstBound ){
507 result_type operator()( first_argument_type firstArgument ) const {
508 return Caller::call( firstArgument, firstBound );
512 template<typename Caller>
515 typedef typename Caller::second_argument_type FirstBound;
516 typedef typename Caller::third_argument_type SecondBound;
517 FirstBound firstBound;
518 SecondBound secondBound;
520 typedef typename Caller::result_type result_type;
521 typedef typename Caller::first_argument_type first_argument_type;
522 BindArguments2( FirstBound firstBound, SecondBound secondBound )
523 : firstBound( firstBound ), secondBound( secondBound ){
525 result_type operator()( first_argument_type firstArgument ) const {
526 return Caller::call( firstArgument, firstBound, secondBound );
530 template<typename Caller, typename FirstBound, typename SecondBound>
531 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
532 return BindArguments2<Caller>( firstBound, secondBound );
535 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
536 return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
538 typedef Function3<const Face&, const Plane3&, bool, bool, Face_testPlane> FaceTestPlane;
542 /// \brief Returns true if
543 /// \li !flipped && brush is BACK or ON
544 /// \li flipped && brush is FRONT or ON
545 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
546 brush.evaluateBRep();
548 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
550 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
556 return Brush_findIf( brush, bindArguments( FaceTestPlane(), makeReference( plane ), flipped ) ) == 0;
560 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
561 brush.evaluateBRep();
563 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
565 if ( ( *i )->contributes() ) {
566 split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
572 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
573 if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
574 brush_vector_t fragments;
575 fragments.reserve( other.size() );
578 for ( Brush::const_iterator i( other.begin() ); i != other.end(); ++i )
580 if ( ( *i )->contributes() ) {
581 brushsplit_t split = Brush_classifyPlane( back, ( *i )->plane3() );
582 if ( split.counts[ePlaneFront] != 0
583 && split.counts[ePlaneBack] != 0 ) {
584 fragments.push_back( new Brush( back ) );
585 Face* newFace = fragments.back()->addFace( *( *i ) );
586 if ( newFace != 0 ) {
587 newFace->flipWinding();
589 back.addFace( *( *i ) );
591 else if ( split.counts[ePlaneBack] == 0 ) {
592 for ( brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i )
600 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
606 class SubtractBrushesFromUnselected : public scene::Graph::Walker
608 const brush_vector_t& m_brushlist;
609 std::size_t& m_before;
610 std::size_t& m_after;
611 mutable bool m_eraseParent;
613 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
614 : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
616 bool pre( const scene::Path& path, scene::Instance& instance ) const {
617 if ( path.top().get().visible() ) {
622 void post( const scene::Path& path, scene::Instance& instance ) const {
623 if ( path.top().get().visible() ) {
624 Brush* brush = Node_getBrush( path.top() );
626 && !Instance_getSelectable( instance )->isSelected() ) {
627 brush_vector_t buffer[2];
629 Brush* original = new Brush( *brush );
630 buffer[static_cast<std::size_t>( swap )].push_back( original );
633 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
635 for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
637 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
642 buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
645 buffer[static_cast<std::size_t>( swap )].clear();
650 brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
652 if ( out.size() == 1 && out.back() == original ) {
658 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
661 ( *i )->removeEmptyFaces();
662 if ( !( *i )->empty() ) {
663 NodeSmartReference node( ( new BrushNode() )->node() );
664 Node_getBrush( node )->copy( *( *i ) );
666 Node_getTraversable( path.parent() )->insert( node );
672 Path_deleteTop( path );
673 if( Node_getTraversable( path.parent() )->empty() ){
674 m_eraseParent = true;
679 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
680 m_eraseParent = false;
681 Entity* entity = Node_getEntity( path.top() );
682 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
683 && Node_getTraversable( path.top() )->empty() ) {
684 Path_deleteTop( path );
691 brush_vector_t selected_brushes;
692 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
694 if ( selected_brushes.empty() ) {
695 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
699 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
701 UndoableCommand undo( "brushSubtract" );
703 // subtract selected from unselected
704 std::size_t before = 0;
705 std::size_t after = 0;
706 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
707 globalOutputStream() << "CSG Subtract: Result: "
708 << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
709 << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
715 class BrushSplitByPlaneSelected : public scene::Graph::Walker
720 const char* m_shader;
721 const TextureProjection& m_projection;
724 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
725 : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
727 bool pre( const scene::Path& path, scene::Instance& instance ) const {
730 void post( const scene::Path& path, scene::Instance& instance ) const {
731 if ( path.top().get().visible() ) {
732 Brush* brush = Node_getBrush( path.top() );
734 && Instance_getSelectable( instance )->isSelected() ) {
735 Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
736 if ( plane3_valid( plane ) ) {
737 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
738 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
739 // the plane intersects this brush
740 if ( m_split == eFrontAndBack ) {
741 NodeSmartReference node( ( new BrushNode() )->node() );
742 Brush* fragment = Node_getBrush( node );
743 fragment->copy( *brush );
744 Face* newFace = fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
745 if ( newFace != 0 && m_split != eFront ) {
746 newFace->flipWinding();
748 fragment->removeEmptyFaces();
749 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
751 Node_getTraversable( path.parent() )->insert( node );
753 scene::Path fragmentPath = path;
754 fragmentPath.top() = makeReference( node.get() );
755 selectPath( fragmentPath, true );
759 Face* newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
760 if ( newFace != 0 && m_split == eFront ) {
761 newFace->flipWinding();
763 brush->removeEmptyFaces();
764 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
767 // the plane does not intersect this brush
768 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
769 // the brush is "behind" the plane
770 Path_deleteTop( path );
778 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
779 TextureProjection projection;
780 TexDef_Construct_Default( projection );
781 graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
786 class BrushInstanceSetClipPlane : public scene::Graph::Walker
790 BrushInstanceSetClipPlane( const Plane3& plane )
793 bool pre( const scene::Path& path, scene::Instance& instance ) const {
794 BrushInstance* brush = Instance_getBrush( instance );
796 && path.top().get().visible()
797 && brush->isSelected() ) {
798 BrushInstance& brushInstance = *brush;
799 brushInstance.setClipPlane( m_plane );
805 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
806 graph.traverse( BrushInstanceSetClipPlane( plane ) );
814 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
815 // gather potential outer faces
818 typedef std::vector<const Face*> Faces;
820 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
822 ( *i )->evaluateBRep();
823 for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
825 if ( !( *j )->contributes() ) {
829 const Face& face1 = *( *j );
832 // test faces of all input brushes
833 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
834 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
836 if ( k != i ) { // don't test a brush against itself
837 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
839 const Face& face2 = *( *l );
841 // face opposes another face
842 if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
843 // skip opposing planes
851 // check faces already stored
852 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
854 const Face& face2 = *( *m );
856 // face equals another face
857 if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
858 //if the texture/shader references should be the same but are not
859 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
862 // skip duplicate planes
867 // face1 plane intersects face2 winding or vice versa
868 if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
869 // result would not be convex
875 faces.push_back( &face1 );
879 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
881 if ( !brush.addFace( *( *i ) ) ) {
882 // result would have too many sides
888 brush.removeEmptyFaces();
893 void CSG_Merge( void ){
894 brush_vector_t selected_brushes;
897 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
899 if ( selected_brushes.empty() ) {
900 globalOutputStream() << "CSG Merge: No brushes selected.\n";
904 if ( selected_brushes.size() < 2 ) {
905 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
909 globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
911 UndoableCommand undo( "brushMerge" );
913 scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
915 NodeSmartReference node( ( new BrushNode() )->node() );
916 Brush* brush = Node_getBrush( node );
917 // if the new brush would not be convex
918 if ( !Brush_merge( *brush, selected_brushes, true ) ) {
919 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
923 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
925 // free the original brushes
926 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
929 Node_getTraversable( merged_path.top() )->insert( node );
930 merged_path.push( makeReference( node.get() ) );
932 selectPath( merged_path, true );
934 globalOutputStream() << "CSG Merge: Succeeded.\n";
949 #include "mainframe.h"
951 #include "gtkutil/dialog.h"
952 #include "gtkutil/button.h"
953 #include "gtkutil/accelerator.h"
959 GtkToggleButton *radXYZ, *radX, *radY, *radZ, *caulk, *removeInner;
962 CSGToolDialog g_csgtool_dialog;
964 DoubleVector3 getExclusion(){
965 if( gtk_toggle_button_get_active( g_csgtool_dialog.radX ) ){
966 return DoubleVector3( 1, 0, 0 );
968 else if( gtk_toggle_button_get_active( g_csgtool_dialog.radY ) ){
969 return DoubleVector3( 0, 1, 0 );
971 else if( gtk_toggle_button_get_active( g_csgtool_dialog.radZ ) ){
972 return DoubleVector3( 0, 0, 1 );
974 return DoubleVector3( 0, 0, 0 );
978 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
984 bool getRemoveInner(){
985 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
991 class BrushFaceOffset
995 BrushFaceOffset( float offset )
998 void operator()( BrushInstance& brush ) const {
1001 Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1002 Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
1006 //=================DLG
1008 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1009 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1010 UndoableCommand undo( "brushHollow::Diag" );
1011 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1012 if( getRemoveInner() )
1013 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1014 SceneChangeNotify();
1018 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1019 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1020 UndoableCommand undo( "brushHollow::Wrap" );
1021 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1022 if( getRemoveInner() )
1023 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1024 SceneChangeNotify();
1028 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1029 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1030 UndoableCommand undo( "brushHollow::Extrude" );
1031 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1032 if( getRemoveInner() )
1033 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1034 SceneChangeNotify();
1038 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1039 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1040 UndoableCommand undo( "brushHollow::Pull" );
1041 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1042 if( getRemoveInner() )
1043 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1044 SceneChangeNotify();
1048 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1049 gtk_spin_button_update ( dialog->spin );
1050 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1052 UndoableCommand undo( "Shrink brush" );
1053 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1054 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1055 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1056 SceneChangeNotify();
1060 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1061 gtk_spin_button_update ( dialog->spin );
1062 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1063 UndoableCommand undo( "Expand brush" );
1064 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1065 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1066 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1067 SceneChangeNotify();
1071 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1072 gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1076 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1077 gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1082 if ( g_csgtool_dialog.window == NULL ) {
1083 g_csgtool_dialog.window = create_dialog_window( MainFrame_getWindow(), "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1084 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1086 //GtkAccelGroup* accel = gtk_accel_group_new();
1087 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1088 global_accel_connect_window( g_csgtool_dialog.window );
1091 GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1092 gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1094 GtkTable* table = create_dialog_table( 3, 8, 4, 4 );
1095 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1097 //GtkWidget* label = gtk_label_new( "<->" );
1098 //gtk_widget_show( label );
1099 GtkWidget* button = gtk_button_new_with_label( "Grid->" );
1100 gtk_table_attach( table, button, 0, 1, 0, 1,
1101 (GtkAttachOptions) ( 0 ),
1102 (GtkAttachOptions) ( 0 ), 0, 0 );
1103 gtk_widget_show( button );
1104 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1107 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1108 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1109 gtk_widget_show( GTK_WIDGET( spin ) );
1110 gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1111 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1112 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1113 (GtkAttachOptions) ( 0 ), 0, 0 );
1114 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1115 gtk_spin_button_set_numeric( spin, TRUE );
1117 g_csgtool_dialog.spin = spin;
1120 //radio button group for choosing the exclude axis
1121 GtkWidget* radXYZ = gtk_radio_button_new_with_label( NULL, "XYZ" );
1122 GtkWidget* radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-X" );
1123 GtkWidget* radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Y" );
1124 GtkWidget* radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Z" );
1125 gtk_widget_show( radXYZ );
1126 gtk_widget_show( radX );
1127 gtk_widget_show( radY );
1128 gtk_widget_show( radZ );
1130 gtk_table_attach( table, radXYZ, 2, 3, 0, 1,
1131 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1132 (GtkAttachOptions) ( 0 ), 0, 0 );
1133 gtk_table_attach( table, radX, 3, 4, 0, 1,
1134 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1135 (GtkAttachOptions) ( 0 ), 0, 0 );
1136 gtk_table_attach( table, radY, 4, 5, 0, 1,
1137 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1138 (GtkAttachOptions) ( 0 ), 0, 0 );
1139 gtk_table_attach( table, radZ, 5, 6, 0, 1,
1140 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1141 (GtkAttachOptions) ( 0 ), 0, 0 );
1143 g_csgtool_dialog.radXYZ = GTK_TOGGLE_BUTTON( radXYZ );
1144 g_csgtool_dialog.radX = GTK_TOGGLE_BUTTON( radX );
1145 g_csgtool_dialog.radY = GTK_TOGGLE_BUTTON( radY );
1146 g_csgtool_dialog.radZ = GTK_TOGGLE_BUTTON( radZ );
1149 GtkWidget* button = gtk_toggle_button_new();
1150 button_set_icon( GTK_BUTTON( button ), "f-caulk.png" );
1151 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1152 gtk_table_attach( table, button, 6, 7, 0, 1,
1153 (GtkAttachOptions) ( GTK_EXPAND ),
1154 (GtkAttachOptions) ( 0 ), 0, 0 );
1155 gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1156 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1157 gtk_widget_show( button );
1158 g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1161 GtkWidget* button = gtk_toggle_button_new();
1162 button_set_icon( GTK_BUTTON( button ), "csgtool_removeinner.png" );
1163 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1164 gtk_table_attach( table, button, 7, 8, 0, 1,
1165 (GtkAttachOptions) ( GTK_EXPAND ),
1166 (GtkAttachOptions) ( 0 ), 0, 0 );
1167 gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1168 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1169 gtk_widget_show( button );
1170 g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1173 GtkWidget* sep = gtk_hseparator_new();
1174 gtk_widget_show( sep );
1175 gtk_table_attach( table, sep, 0, 8, 1, 2,
1176 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1177 (GtkAttachOptions) ( 0 ), 0, 0 );
1180 GtkWidget* button = gtk_button_new();
1181 button_set_icon( GTK_BUTTON( button ), "csgtool_shrink.png" );
1182 gtk_table_attach( table, button, 0, 1, 2, 3,
1183 (GtkAttachOptions) ( GTK_EXPAND ),
1184 (GtkAttachOptions) ( 0 ), 0, 0 );
1185 gtk_widget_set_tooltip_text( button, "Shrink brush" );
1186 gtk_widget_show( button );
1187 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1190 GtkWidget* button = gtk_button_new();
1191 button_set_icon( GTK_BUTTON( button ), "csgtool_expand.png" );
1192 gtk_table_attach( table, button, 1, 2, 2, 3,
1193 (GtkAttachOptions) ( GTK_EXPAND ),
1194 (GtkAttachOptions) ( 0 ), 0, 0 );
1195 gtk_widget_set_tooltip_text( button, "Expand brush" );
1196 gtk_widget_show( button );
1197 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1200 GtkWidget* button = gtk_button_new();
1201 button_set_icon( GTK_BUTTON( button ), "csgtool_diagonal.png" );
1202 gtk_table_attach( table, button, 3, 4, 2, 3,
1203 (GtkAttachOptions) ( GTK_EXPAND ),
1204 (GtkAttachOptions) ( 0 ), 0, 0 );
1205 gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1206 gtk_widget_show( button );
1207 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1210 GtkWidget* button = gtk_button_new();
1211 button_set_icon( GTK_BUTTON( button ), "csgtool_wrap.png" );
1212 gtk_table_attach( table, button, 4, 5, 2, 3,
1213 (GtkAttachOptions) ( GTK_EXPAND ),
1214 (GtkAttachOptions) ( 0 ), 0, 0 );
1215 gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1216 gtk_widget_show( button );
1217 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1220 GtkWidget* button = gtk_button_new();
1221 button_set_icon( GTK_BUTTON( button ), "csgtool_extrude.png" );
1222 gtk_table_attach( table, button, 5, 6, 2, 3,
1223 (GtkAttachOptions) ( GTK_EXPAND ),
1224 (GtkAttachOptions) ( 0 ), 0, 0 );
1225 gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1226 gtk_widget_show( button );
1227 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1230 GtkWidget* button = gtk_button_new();
1231 button_set_icon( GTK_BUTTON( button ), "csgtool_pull.png" );
1232 gtk_table_attach( table, button, 6, 7, 2, 3,
1233 (GtkAttachOptions) ( GTK_EXPAND ),
1234 (GtkAttachOptions) ( 0 ), 0, 0 );
1235 gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1236 gtk_widget_show( button );
1237 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1244 gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1245 gtk_window_present( g_csgtool_dialog.window );