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"
63 typedef std::vector<DoubleVector3> doublevector_vector_t;
74 const char* getCaulkShader(){
75 const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
76 if ( gotShader && *gotShader ){
79 return "textures/common/caulk";
84 DoubleVector3 ExclusionAxis;
87 doublevector_vector_t &exclude_vec;
89 CaulkFace( DoubleVector3 ExclusionAxis,
92 doublevector_vector_t &exclude_vec ):
93 ExclusionAxis( ExclusionAxis ),
96 exclude_vec( exclude_vec ){}
97 void operator()( Face& face ) const {
98 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
99 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
100 if( !exclude_vec.empty() ){
101 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
102 if( ( *i ) == face.getPlane().plane3().normal() ){
107 face.SetShader( getCaulkShader() );
117 eHollowType HollowType;
118 DoubleVector3 ExclusionAxis;
121 doublevector_vector_t &exclude_vec;
125 FaceMakeBrush( const Brush& brush,
128 eHollowType HollowType,
129 DoubleVector3 ExclusionAxis,
132 doublevector_vector_t &exclude_vec,
138 HollowType( HollowType ),
139 ExclusionAxis( ExclusionAxis ),
142 exclude_vec( exclude_vec ),
144 RemoveInner( RemoveInner ){
146 void operator()( Face& face ) const {
147 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
148 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
149 if( !exclude_vec.empty() ){
150 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
151 if( ( *i ) == face.getPlane().plane3().normal() ){
157 if( HollowType == pull ){
158 if ( face.contributes() ) {
159 face.getPlane().offset( offset );
161 out.push_back( new Brush( brush ) );
162 face.getPlane().offset( -offset );
166 Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot, exclude_vec ) );
168 Face* newFace = out.back()->addFace( face );
169 if ( newFace != 0 ) {
170 newFace->flipWinding();
174 else if( HollowType == wrap ){
175 //Face_makeBrush( face, brush, out, offset );
176 if ( face.contributes() ) {
178 out.push_back( new Brush( brush ) );
179 if( !RemoveInner && caulk )
180 face.SetShader( getCaulkShader() );
181 Face* newFace = out.back()->addFace( face );
182 face.getPlane().offset( -offset );
185 face.SetShader( getCaulkShader() );
186 if ( newFace != 0 ) {
187 newFace->flipWinding();
188 newFace->getPlane().offset( offset );
189 newFace->planeChanged();
193 else if( HollowType == extrude ){
194 if ( face.contributes() ) {
196 out.push_back( new Brush( brush ) );
199 Face* newFace = out.back()->addFace( face );
200 if ( newFace != 0 ) {
201 newFace->getPlane().offset( offset );
202 newFace->planeChanged();
205 if( !RemoveInner && caulk )
206 face.SetShader( getCaulkShader() );
207 newFace = out.back()->addFace( face );
208 if ( newFace != 0 ) {
209 newFace->flipWinding();
211 Winding& winding = face.getWinding();
212 TextureProjection projection;
213 TexDef_Construct_Default( projection );
214 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
215 std::size_t index = std::distance( winding.begin(), j );
216 std::size_t next = Winding_next( winding, index );
218 out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
222 else if( HollowType == diag ){
223 if ( face.contributes() ) {
224 out.push_back( new Brush( brush ) );
227 Face* newFace = out.back()->addFace( face );
228 if ( newFace != 0 ) {
230 newFace->planeChanged();
232 newFace = out.back()->addFace( face );
234 if ( newFace != 0 ) {
235 if( !RemoveInner && caulk )
236 newFace->SetShader( getCaulkShader() );
237 newFace->flipWinding();
238 newFace->getPlane().offset( offset );
239 newFace->planeChanged();
242 Winding& winding = face.getWinding();
243 TextureProjection projection;
244 TexDef_Construct_Default( projection );
245 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
246 std::size_t index = std::distance( winding.begin(), i );
247 std::size_t next = Winding_next( winding, index );
249 float bestdist = 999999;
251 for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
252 Winding& winding2 = ( *j )->getWinding();
253 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
254 std::size_t index2 = std::distance( winding2.begin(), k );
255 float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
256 if( testdist < bestdist ){
258 BestPoint = winding2[index2].vertex;
262 out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
272 DoubleVector3 ExclusionAxis;
276 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
277 : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
279 void operator()( Face& face ) const {
280 if( vector3_length_squared( ExclusionAxis ) != 0 ){
281 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
285 else if( dot > maxdot ){
295 DoubleVector3 ExclusionAxis;
298 doublevector_vector_t &exclude_vec;
300 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot, doublevector_vector_t &exclude_vec )
301 : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ), exclude_vec( exclude_vec ){
303 void operator()( Face& face ) const {
304 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
305 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
306 if( !exclude_vec.empty() ){
307 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
308 if( ( *i ) == face.getPlane().plane3().normal() ){
314 face.getPlane().offset( offset );
320 class FaceExcludeSelected
322 doublevector_vector_t &outvec;
324 FaceExcludeSelected( doublevector_vector_t &outvec ): outvec( outvec ){
326 void operator()( FaceInstance& face ) const {
327 if( face.isSelected() ){
328 outvec.push_back( face.getFace().getPlane().plane3().normal() );
334 DoubleVector3 getExclusion();
336 bool getRemoveInner();
338 class BrushHollowSelectedWalker : public scene::Graph::Walker
341 eHollowType HollowType;
343 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
344 : offset( offset ), HollowType( HollowType ){
346 bool pre( const scene::Path& path, scene::Instance& instance ) const {
347 if ( path.top().get().visible() ) {
348 Brush* brush = Node_getBrush( path.top() );
350 && Instance_getSelectable( instance )->isSelected()
351 && path.size() > 1 ) {
353 doublevector_vector_t exclude_vec;
356 if( HollowType != room ){
357 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
358 if( mindot == 0 && maxdot == 0 ){
359 Brush_ForEachFaceInstance( *Instance_getBrush( instance ), FaceExcludeSelected( exclude_vec ) );
362 if( HollowType == room ){
363 Brush* tmpbrush = new Brush( *brush );
364 tmpbrush->removeEmptyFaces();
365 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, exclude_vec, true, true ) );
368 else if( HollowType == pull ){
369 if( !getRemoveInner() && getCaulk() ){
370 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
372 Brush* tmpbrush = new Brush( *brush );
373 tmpbrush->removeEmptyFaces();
374 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
377 else if( HollowType == diag ){
378 Brush* tmpbrush = new Brush( *brush );
379 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
380 tmpbrush->removeEmptyFaces();
381 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
383 if( !getRemoveInner() && getCaulk() ){
384 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
388 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
390 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
392 ( *i )->removeEmptyFaces();
393 if( ( *i )->hasContributingFaces() ){
394 NodeSmartReference node( ( new BrushNode() )->node() );
395 Node_getBrush( node )->copy( *( *i ) );
397 Node_getTraversable( path.parent() )->insert( node );
398 //path.push( makeReference( node.get() ) );
399 //selectPath( path, true );
400 //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
401 //Path_deleteTop( path );
410 typedef std::list<Brush*> brushlist_t;
412 class BrushGatherSelected : public scene::Graph::Walker
414 brush_vector_t& m_brushlist;
416 BrushGatherSelected( brush_vector_t& brushlist )
417 : m_brushlist( brushlist ){
419 bool pre( const scene::Path& path, scene::Instance& instance ) const {
420 if ( path.top().get().visible() ) {
421 Brush* brush = Node_getBrush( path.top() );
423 && Instance_getSelectable( instance )->isSelected() ) {
424 m_brushlist.push_back( brush );
431 class BrushDeleteSelected : public scene::Graph::Walker
434 bool pre( const scene::Path& path, scene::Instance& instance ) const {
437 void post( const scene::Path& path, scene::Instance& instance ) const {
438 if ( path.top().get().visible() ) {
439 Brush* brush = Node_getBrush( path.top() );
441 && Instance_getSelectable( instance )->isSelected()
442 && path.size() > 1 ) {
443 Path_deleteTop( path );
451 class BrushDeleteSelected : public scene::Graph::Walker
453 scene::Node* m_keepNode;
454 mutable bool m_eraseParent;
456 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
458 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
460 bool pre( const scene::Path& path, scene::Instance& instance ) const {
463 void post( const scene::Path& path, scene::Instance& instance ) const {
464 //globalOutputStream() << path.size() << "\n";
465 if ( path.top().get().visible() ) {
466 Brush* brush = Node_getBrush( path.top() );
468 && Instance_getSelectable( instance )->isSelected()
469 && path.size() > 1 ) {
470 scene::Node& parent = path.parent();
471 Path_deleteTop( path );
472 if( Node_getTraversable( parent )->empty() ){
473 m_eraseParent = true;
474 //globalOutputStream() << "Empty node?!.\n";
478 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
479 //globalOutputStream() << "about to Delete empty node!.\n";
480 m_eraseParent = false;
481 Entity* entity = Node_getEntity( path.top() );
482 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
483 && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
484 //globalOutputStream() << "now Deleting empty node!.\n";
485 Path_deleteTop( path );
496 void CSG_MakeRoom( void ){
497 UndoableCommand undo( "makeRoom" );
498 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
499 GlobalSceneGraph().traverse( BrushDeleteSelected() );
503 template<typename Type>
504 class RemoveReference
510 template<typename Type>
511 class RemoveReference<Type&>
517 template<typename Functor>
520 const Functor& functor;
522 typedef typename RemoveReference<typename Functor::first_argument_type>::type* first_argument_type;
523 typedef typename Functor::result_type result_type;
524 Dereference( const Functor& functor ) : functor( functor ){
526 result_type operator()( first_argument_type firstArgument ) const {
527 return functor( *firstArgument );
531 template<typename Functor>
532 inline Dereference<Functor> makeDereference( const Functor& functor ){
533 return Dereference<Functor>( functor );
536 typedef Face* FacePointer;
537 const FacePointer c_nullFacePointer = 0;
539 template<typename Predicate>
540 Face* Brush_findIf( const Brush& brush, const Predicate& predicate ){
541 Brush::const_iterator i = std::find_if( brush.begin(), brush.end(), makeDereference( predicate ) );
542 return i == brush.end() ? c_nullFacePointer : *i; // uses c_nullFacePointer instead of 0 because otherwise gcc 4.1 attempts conversion to int
545 template<typename Caller>
548 typedef typename Caller::second_argument_type FirstBound;
549 FirstBound firstBound;
551 typedef typename Caller::result_type result_type;
552 typedef typename Caller::first_argument_type first_argument_type;
553 BindArguments1( FirstBound firstBound )
554 : firstBound( firstBound ){
556 result_type operator()( first_argument_type firstArgument ) const {
557 return Caller::call( firstArgument, firstBound );
561 template<typename Caller>
564 typedef typename Caller::second_argument_type FirstBound;
565 typedef typename Caller::third_argument_type SecondBound;
566 FirstBound firstBound;
567 SecondBound secondBound;
569 typedef typename Caller::result_type result_type;
570 typedef typename Caller::first_argument_type first_argument_type;
571 BindArguments2( FirstBound firstBound, SecondBound secondBound )
572 : firstBound( firstBound ), secondBound( secondBound ){
574 result_type operator()( first_argument_type firstArgument ) const {
575 return Caller::call( firstArgument, firstBound, secondBound );
579 template<typename Caller, typename FirstBound, typename SecondBound>
580 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
581 return BindArguments2<Caller>( firstBound, secondBound );
584 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
585 return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
587 typedef Function3<const Face&, const Plane3&, bool, bool, Face_testPlane> FaceTestPlane;
591 /// \brief Returns true if
592 /// \li !flipped && brush is BACK or ON
593 /// \li flipped && brush is FRONT or ON
594 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
595 brush.evaluateBRep();
597 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
599 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
605 return Brush_findIf( brush, bindArguments( FaceTestPlane(), makeReference( plane ), flipped ) ) == 0;
609 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
610 brush.evaluateBRep();
612 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
614 if ( ( *i )->contributes() ) {
615 split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
621 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
622 if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
623 brush_vector_t fragments;
624 fragments.reserve( other.size() );
627 for ( Brush::const_iterator i( other.begin() ); i != other.end(); ++i )
629 if ( ( *i )->contributes() ) {
630 brushsplit_t split = Brush_classifyPlane( back, ( *i )->plane3() );
631 if ( split.counts[ePlaneFront] != 0
632 && split.counts[ePlaneBack] != 0 ) {
633 fragments.push_back( new Brush( back ) );
634 Face* newFace = fragments.back()->addFace( *( *i ) );
635 if ( newFace != 0 ) {
636 newFace->flipWinding();
638 back.addFace( *( *i ) );
640 else if ( split.counts[ePlaneBack] == 0 ) {
641 for ( brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i )
649 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
655 class SubtractBrushesFromUnselected : public scene::Graph::Walker
657 const brush_vector_t& m_brushlist;
658 std::size_t& m_before;
659 std::size_t& m_after;
660 mutable bool m_eraseParent;
662 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
663 : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
665 bool pre( const scene::Path& path, scene::Instance& instance ) const {
666 if ( path.top().get().visible() ) {
671 void post( const scene::Path& path, scene::Instance& instance ) const {
672 if ( path.top().get().visible() ) {
673 Brush* brush = Node_getBrush( path.top() );
675 && !Instance_getSelectable( instance )->isSelected() ) {
676 brush_vector_t buffer[2];
678 Brush* original = new Brush( *brush );
679 buffer[static_cast<std::size_t>( swap )].push_back( original );
682 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
684 for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
686 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
691 buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
694 buffer[static_cast<std::size_t>( swap )].clear();
699 brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
701 if ( out.size() == 1 && out.back() == original ) {
707 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
710 ( *i )->removeEmptyFaces();
711 if ( !( *i )->empty() ) {
712 NodeSmartReference node( ( new BrushNode() )->node() );
713 Node_getBrush( node )->copy( *( *i ) );
715 Node_getTraversable( path.parent() )->insert( node );
721 scene::Node& parent = path.parent();
722 Path_deleteTop( path );
723 if( Node_getTraversable( parent )->empty() ){
724 m_eraseParent = true;
729 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
730 m_eraseParent = false;
731 Entity* entity = Node_getEntity( path.top() );
732 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
733 && Node_getTraversable( path.top() )->empty() ) {
734 Path_deleteTop( path );
741 brush_vector_t selected_brushes;
742 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
744 if ( selected_brushes.empty() ) {
745 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
749 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
751 UndoableCommand undo( "brushSubtract" );
753 // subtract selected from unselected
754 std::size_t before = 0;
755 std::size_t after = 0;
756 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
757 globalOutputStream() << "CSG Subtract: Result: "
758 << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
759 << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
765 class BrushSplitByPlaneSelected : public scene::Graph::Walker
770 const char* m_shader;
771 const TextureProjection& m_projection;
774 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
775 : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
777 bool pre( const scene::Path& path, scene::Instance& instance ) const {
780 void post( const scene::Path& path, scene::Instance& instance ) const {
781 if ( path.top().get().visible() ) {
782 Brush* brush = Node_getBrush( path.top() );
784 && Instance_getSelectable( instance )->isSelected() ) {
785 Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
786 if ( plane3_valid( plane ) ) {
787 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
788 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
789 // the plane intersects this brush
790 if ( m_split == eFrontAndBack ) {
791 NodeSmartReference node( ( new BrushNode() )->node() );
792 Brush* fragment = Node_getBrush( node );
793 fragment->copy( *brush );
794 Face* newFace = fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
795 if ( newFace != 0 && m_split != eFront ) {
796 newFace->flipWinding();
798 fragment->removeEmptyFaces();
799 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
801 Node_getTraversable( path.parent() )->insert( node );
803 scene::Path fragmentPath = path;
804 fragmentPath.top() = makeReference( node.get() );
805 selectPath( fragmentPath, true );
809 Face* newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
810 if ( newFace != 0 && m_split == eFront ) {
811 newFace->flipWinding();
813 brush->removeEmptyFaces();
814 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
817 // the plane does not intersect this brush
818 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
819 // the brush is "behind" the plane
820 Path_deleteTop( path );
828 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
829 TextureProjection projection;
830 TexDef_Construct_Default( projection );
831 graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
836 class BrushInstanceSetClipPlane : public scene::Graph::Walker
840 BrushInstanceSetClipPlane( const Plane3& plane )
843 bool pre( const scene::Path& path, scene::Instance& instance ) const {
844 BrushInstance* brush = Instance_getBrush( instance );
846 && path.top().get().visible()
847 && brush->isSelected() ) {
848 BrushInstance& brushInstance = *brush;
849 brushInstance.setClipPlane( m_plane );
855 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
856 graph.traverse( BrushInstanceSetClipPlane( plane ) );
864 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
865 // gather potential outer faces
868 typedef std::vector<const Face*> Faces;
870 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
872 ( *i )->evaluateBRep();
873 for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
875 if ( !( *j )->contributes() ) {
879 const Face& face1 = *( *j );
882 // test faces of all input brushes
883 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
884 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
886 if ( k != i ) { // don't test a brush against itself
887 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
889 const Face& face2 = *( *l );
891 // face opposes another face
892 if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
893 // skip opposing planes
901 // check faces already stored
902 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
904 const Face& face2 = *( *m );
906 // face equals another face
907 if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
908 //if the texture/shader references should be the same but are not
909 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
912 // skip duplicate planes
917 // face1 plane intersects face2 winding or vice versa
918 if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
919 // result would not be convex
925 faces.push_back( &face1 );
929 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
931 if ( !brush.addFace( *( *i ) ) ) {
932 // result would have too many sides
938 brush.removeEmptyFaces();
943 void CSG_Merge( void ){
944 brush_vector_t selected_brushes;
947 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
949 if ( selected_brushes.empty() ) {
950 globalOutputStream() << "CSG Merge: No brushes selected.\n";
954 if ( selected_brushes.size() < 2 ) {
955 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
959 globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
961 UndoableCommand undo( "brushMerge" );
963 scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
965 NodeSmartReference node( ( new BrushNode() )->node() );
966 Brush* brush = Node_getBrush( node );
967 // if the new brush would not be convex
968 if ( !Brush_merge( *brush, selected_brushes, true ) ) {
969 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
973 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
975 // free the original brushes
976 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
979 Node_getTraversable( merged_path.top() )->insert( node );
980 merged_path.push( makeReference( node.get() ) );
982 selectPath( merged_path, true );
984 globalOutputStream() << "CSG Merge: Succeeded.\n";
999 #include "mainframe.h"
1000 #include <gtk/gtk.h>
1001 #include "gtkutil/dialog.h"
1002 #include "gtkutil/button.h"
1003 #include "gtkutil/accelerator.h"
1004 #include "xywindow.h"
1005 #include "camwindow.h"
1007 struct CSGToolDialog
1009 GtkSpinButton* spin;
1011 GtkToggleButton *radFaces, *radProj, *radCam, *caulk, *removeInner;
1014 CSGToolDialog g_csgtool_dialog;
1016 DoubleVector3 getExclusion(){
1017 if( gtk_toggle_button_get_active( g_csgtool_dialog.radProj ) ){
1018 if( GlobalXYWnd_getCurrentViewType() == YZ ){
1019 return DoubleVector3( 1, 0, 0 );
1021 else if( GlobalXYWnd_getCurrentViewType() == XZ ){
1022 return DoubleVector3( 0, 1, 0 );
1024 else if( GlobalXYWnd_getCurrentViewType() == XY ){
1025 return DoubleVector3( 0, 0, 1 );
1028 if( gtk_toggle_button_get_active( g_csgtool_dialog.radCam ) ){
1029 Vector3 angles( Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
1030 // globalOutputStream() << angles << " angles\n";
1031 DoubleVector3 radangles( degrees_to_radians( angles[0] ), degrees_to_radians( angles[1] ), degrees_to_radians( angles[2] ) );
1032 // globalOutputStream() << radangles << " radangles\n";
1033 // x = cos(yaw)*cos(pitch)
1034 // y = sin(yaw)*cos(pitch)
1036 DoubleVector3 viewvector;
1037 viewvector[0] = cos( radangles[1] ) * cos( radangles[0] );
1038 viewvector[1] = sin( radangles[1] ) * cos( radangles[0] );
1039 viewvector[2] = sin( radangles[0] );
1040 // globalOutputStream() << viewvector << " viewvector\n";
1043 return DoubleVector3( 0, 0, 0 );
1047 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
1053 bool getRemoveInner(){
1054 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
1060 class BrushFaceOffset
1064 BrushFaceOffset( float offset )
1067 void operator()( BrushInstance& brush ) const {
1070 doublevector_vector_t exclude_vec;
1071 Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1072 if( mindot == 0 && maxdot == 0 ){
1073 Brush_ForEachFaceInstance( brush, FaceExcludeSelected( exclude_vec ) );
1075 Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
1079 //=================DLG
1081 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1082 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1083 UndoableCommand undo( "brushHollow::Diag" );
1084 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1085 if( getRemoveInner() )
1086 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1087 SceneChangeNotify();
1091 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1092 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1093 UndoableCommand undo( "brushHollow::Wrap" );
1094 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1095 if( getRemoveInner() )
1096 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1097 SceneChangeNotify();
1101 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1102 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1103 UndoableCommand undo( "brushHollow::Extrude" );
1104 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1105 if( getRemoveInner() )
1106 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1107 SceneChangeNotify();
1111 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1112 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1113 UndoableCommand undo( "brushHollow::Pull" );
1114 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1115 if( getRemoveInner() )
1116 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1117 SceneChangeNotify();
1121 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1122 gtk_spin_button_update ( dialog->spin );
1123 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1125 UndoableCommand undo( "Shrink brush" );
1126 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1127 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1128 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1129 SceneChangeNotify();
1133 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1134 gtk_spin_button_update ( dialog->spin );
1135 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1136 UndoableCommand undo( "Expand brush" );
1137 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1138 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1139 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1140 SceneChangeNotify();
1144 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1145 gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1149 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1150 gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1155 if ( g_csgtool_dialog.window == NULL ) {
1156 g_csgtool_dialog.window = create_dialog_window( MainFrame_getWindow(), "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1157 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1159 //GtkAccelGroup* accel = gtk_accel_group_new();
1160 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1161 global_accel_connect_window( g_csgtool_dialog.window );
1164 GtkHBox* hbox = create_dialog_hbox( 4, 4 );
1165 gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1167 GtkTable* table = create_dialog_table( 3, 8, 4, 4 );
1168 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1170 //GtkWidget* label = gtk_label_new( "<->" );
1171 //gtk_widget_show( label );
1172 GtkWidget* button = gtk_button_new_with_label( "Grid->" );
1173 gtk_table_attach( table, button, 0, 1, 0, 1,
1174 (GtkAttachOptions) ( 0 ),
1175 (GtkAttachOptions) ( 0 ), 0, 0 );
1176 gtk_widget_show( button );
1177 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1180 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1181 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1182 gtk_widget_show( GTK_WIDGET( spin ) );
1183 gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1184 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1185 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1186 (GtkAttachOptions) ( 0 ), 0, 0 );
1187 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1188 gtk_spin_button_set_numeric( spin, TRUE );
1190 g_csgtool_dialog.spin = spin;
1193 //radio button group for choosing the exclude axis
1194 GtkWidget* radFaces = gtk_radio_button_new_with_label( NULL, "-faces" );
1195 gtk_widget_set_tooltip_text( radFaces, "Exclude selected faces" );
1196 GtkWidget* radProj = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-proj" );
1197 gtk_widget_set_tooltip_text( radProj, "Exclude faces, most orthogonal to active projection" );
1198 GtkWidget* radCam = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-cam" );
1199 gtk_widget_set_tooltip_text( radCam, "Exclude faces, most orthogonal to camera view" );
1201 gtk_widget_show( radFaces );
1202 gtk_widget_show( radProj );
1203 gtk_widget_show( radCam );
1205 gtk_table_attach( table, radFaces, 2, 3, 0, 1,
1206 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1207 (GtkAttachOptions) ( 0 ), 0, 0 );
1208 gtk_table_attach( table, radProj, 3, 4, 0, 1,
1209 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1210 (GtkAttachOptions) ( 0 ), 0, 0 );
1211 gtk_table_attach( table, radCam, 4, 5, 0, 1,
1212 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1213 (GtkAttachOptions) ( 0 ), 0, 0 );
1215 g_csgtool_dialog.radFaces = GTK_TOGGLE_BUTTON( radFaces );
1216 g_csgtool_dialog.radProj = GTK_TOGGLE_BUTTON( radProj );
1217 g_csgtool_dialog.radCam = GTK_TOGGLE_BUTTON( radCam );
1220 GtkWidget* button = gtk_toggle_button_new();
1221 button_set_icon( GTK_BUTTON( button ), "f-caulk.png" );
1222 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1223 gtk_table_attach( table, button, 6, 7, 0, 1,
1224 (GtkAttachOptions) ( GTK_EXPAND ),
1225 (GtkAttachOptions) ( 0 ), 0, 0 );
1226 gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1227 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1228 gtk_widget_show( button );
1229 g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1232 GtkWidget* button = gtk_toggle_button_new();
1233 button_set_icon( GTK_BUTTON( button ), "csgtool_removeinner.png" );
1234 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1235 gtk_table_attach( table, button, 7, 8, 0, 1,
1236 (GtkAttachOptions) ( GTK_EXPAND ),
1237 (GtkAttachOptions) ( 0 ), 0, 0 );
1238 gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1239 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1240 gtk_widget_show( button );
1241 g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1244 GtkWidget* sep = gtk_hseparator_new();
1245 gtk_widget_show( sep );
1246 gtk_table_attach( table, sep, 0, 8, 1, 2,
1247 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1248 (GtkAttachOptions) ( 0 ), 0, 0 );
1251 GtkWidget* button = gtk_button_new();
1252 button_set_icon( GTK_BUTTON( button ), "csgtool_shrink.png" );
1253 gtk_table_attach( table, button, 0, 1, 2, 3,
1254 (GtkAttachOptions) ( GTK_EXPAND ),
1255 (GtkAttachOptions) ( 0 ), 0, 0 );
1256 gtk_widget_set_tooltip_text( button, "Shrink brush" );
1257 gtk_widget_show( button );
1258 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1261 GtkWidget* button = gtk_button_new();
1262 button_set_icon( GTK_BUTTON( button ), "csgtool_expand.png" );
1263 gtk_table_attach( table, button, 1, 2, 2, 3,
1264 (GtkAttachOptions) ( GTK_EXPAND ),
1265 (GtkAttachOptions) ( 0 ), 0, 0 );
1266 gtk_widget_set_tooltip_text( button, "Expand brush" );
1267 gtk_widget_show( button );
1268 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1271 GtkWidget* button = gtk_button_new();
1272 button_set_icon( GTK_BUTTON( button ), "csgtool_diagonal.png" );
1273 gtk_table_attach( table, button, 3, 4, 2, 3,
1274 (GtkAttachOptions) ( GTK_EXPAND ),
1275 (GtkAttachOptions) ( 0 ), 0, 0 );
1276 gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1277 gtk_widget_show( button );
1278 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1281 GtkWidget* button = gtk_button_new();
1282 button_set_icon( GTK_BUTTON( button ), "csgtool_wrap.png" );
1283 gtk_table_attach( table, button, 4, 5, 2, 3,
1284 (GtkAttachOptions) ( GTK_EXPAND ),
1285 (GtkAttachOptions) ( 0 ), 0, 0 );
1286 gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1287 gtk_widget_show( button );
1288 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1291 GtkWidget* button = gtk_button_new();
1292 button_set_icon( GTK_BUTTON( button ), "csgtool_extrude.png" );
1293 gtk_table_attach( table, button, 5, 6, 2, 3,
1294 (GtkAttachOptions) ( GTK_EXPAND ),
1295 (GtkAttachOptions) ( 0 ), 0, 0 );
1296 gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1297 gtk_widget_show( button );
1298 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1301 GtkWidget* button = gtk_button_new();
1302 button_set_icon( GTK_BUTTON( button ), "csgtool_pull.png" );
1303 gtk_table_attach( table, button, 6, 7, 2, 3,
1304 (GtkAttachOptions) ( GTK_EXPAND ),
1305 (GtkAttachOptions) ( 0 ), 0, 0 );
1306 gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1307 gtk_widget_show( button );
1308 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1315 gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1316 gtk_window_present( g_csgtool_dialog.window );