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"
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 );
41 newFace->flipWinding();
42 newFace->getPlane().offset( offset );
43 newFace->planeChanged();
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 );
55 newFace->flipWinding();
56 newFace->planeChanged();
61 #include "preferences.h"
62 #include "texwindow.h"
64 typedef std::vector<DoubleVector3> doublevector_vector_t;
75 const char* getCaulkShader(){
76 const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
77 if ( gotShader && *gotShader ){
80 return "textures/common/caulk";
85 DoubleVector3 ExclusionAxis;
88 doublevector_vector_t &exclude_vec;
90 CaulkFace( DoubleVector3 ExclusionAxis,
93 doublevector_vector_t &exclude_vec ):
94 ExclusionAxis( ExclusionAxis ),
97 exclude_vec( exclude_vec ){}
98 void operator()( Face& face ) const {
99 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
100 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
101 if( !exclude_vec.empty() ){
102 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
103 if( ( *i ) == face.getPlane().plane3().normal() ){
108 face.SetShader( getCaulkShader() );
118 eHollowType HollowType;
119 DoubleVector3 ExclusionAxis;
122 doublevector_vector_t &exclude_vec;
126 FaceMakeBrush( const Brush& brush,
129 eHollowType HollowType,
130 DoubleVector3 ExclusionAxis,
133 doublevector_vector_t &exclude_vec,
139 HollowType( HollowType ),
140 ExclusionAxis( ExclusionAxis ),
143 exclude_vec( exclude_vec ),
145 RemoveInner( RemoveInner ){
147 void operator()( Face& face ) const {
148 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
149 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
150 if( !exclude_vec.empty() ){
151 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
152 if( ( *i ) == face.getPlane().plane3().normal() ){
158 if( HollowType == pull ){
159 if ( face.contributes() ) {
160 face.getPlane().offset( offset );
162 out.push_back( new Brush( brush ) );
163 face.getPlane().offset( -offset );
167 Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot, exclude_vec ) );
169 std::shared_ptr<Face> newFace = out.back()->addFace( face );
170 if ( newFace != 0 ) {
171 newFace->flipWinding();
175 else if( HollowType == wrap ){
176 //Face_makeBrush( face, brush, out, offset );
177 if ( face.contributes() ) {
179 out.push_back( new Brush( brush ) );
180 if( !RemoveInner && caulk )
181 face.SetShader( getCaulkShader() );
182 std::shared_ptr<Face> newFace = out.back()->addFace( face );
183 face.getPlane().offset( -offset );
186 face.SetShader( getCaulkShader() );
187 if ( newFace != 0 ) {
188 newFace->flipWinding();
189 newFace->getPlane().offset( offset );
190 newFace->planeChanged();
194 else if( HollowType == extrude ){
195 if ( face.contributes() ) {
197 out.push_back( new Brush( brush ) );
200 std::shared_ptr<Face> newFace = out.back()->addFace( face );
201 if ( newFace != 0 ) {
202 newFace->getPlane().offset( offset );
203 newFace->planeChanged();
206 if( !RemoveInner && caulk )
207 face.SetShader( getCaulkShader() );
208 newFace = out.back()->addFace( face );
209 if ( newFace != 0 ) {
210 newFace->flipWinding();
212 Winding& winding = face.getWinding();
213 TextureProjection projection;
214 TexDef_Construct_Default( projection );
215 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
216 std::size_t index = std::distance( winding.begin(), j );
217 std::size_t next = Winding_next( winding, index );
219 out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
223 else if( HollowType == diag ){
224 if ( face.contributes() ) {
225 out.push_back( new Brush( brush ) );
228 std::shared_ptr<Face> newFace = out.back()->addFace( face );
229 if ( newFace != 0 ) {
231 newFace->planeChanged();
233 newFace = out.back()->addFace( face );
235 if ( newFace != 0 ) {
236 if( !RemoveInner && caulk )
237 newFace->SetShader( getCaulkShader() );
238 newFace->flipWinding();
239 newFace->getPlane().offset( offset );
240 newFace->planeChanged();
243 Winding& winding = face.getWinding();
244 TextureProjection projection;
245 TexDef_Construct_Default( projection );
246 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
247 std::size_t index = std::distance( winding.begin(), i );
248 std::size_t next = Winding_next( winding, index );
250 float bestdist = 999999;
252 for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
253 Winding& winding2 = ( *j )->getWinding();
254 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
255 std::size_t index2 = std::distance( winding2.begin(), k );
256 float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
257 if( testdist < bestdist ){
259 BestPoint = winding2[index2].vertex;
263 out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
273 DoubleVector3 ExclusionAxis;
277 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
278 : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
280 void operator()( Face& face ) const {
281 if( vector3_length_squared( ExclusionAxis ) != 0 ){
282 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
286 else if( dot > maxdot ){
296 DoubleVector3 ExclusionAxis;
299 doublevector_vector_t &exclude_vec;
301 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot, doublevector_vector_t &exclude_vec )
302 : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ), exclude_vec( exclude_vec ){
304 void operator()( Face& face ) const {
305 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
306 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
307 if( !exclude_vec.empty() ){
308 for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
309 if( ( *i ) == face.getPlane().plane3().normal() ){
315 face.getPlane().offset( offset );
321 class FaceExcludeSelected
323 doublevector_vector_t &outvec;
325 FaceExcludeSelected( doublevector_vector_t &outvec ): outvec( outvec ){
327 void operator()( FaceInstance& face ) const {
328 if( face.isSelected() ){
329 outvec.push_back( face.getFace().getPlane().plane3().normal() );
335 DoubleVector3 getExclusion();
337 bool getRemoveInner();
339 class BrushHollowSelectedWalker : public scene::Graph::Walker
342 eHollowType HollowType;
344 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
345 : offset( offset ), HollowType( HollowType ){
347 bool pre( const scene::Path& path, scene::Instance& instance ) const {
348 if ( path.top().get().visible() ) {
349 Brush* brush = Node_getBrush( path.top() );
351 && Instance_getSelectable( instance )->isSelected()
352 && path.size() > 1 ) {
354 doublevector_vector_t exclude_vec;
357 if( HollowType != room ){
358 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
359 if( mindot == 0 && maxdot == 0 ){
360 Brush_ForEachFaceInstance( *Instance_getBrush( instance ), FaceExcludeSelected( exclude_vec ) );
363 if( HollowType == room ){
364 Brush* tmpbrush = new Brush( *brush );
365 tmpbrush->removeEmptyFaces();
366 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, exclude_vec, true, true ) );
369 else if( HollowType == pull ){
370 if( !getRemoveInner() && getCaulk() ){
371 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
373 Brush* tmpbrush = new Brush( *brush );
374 tmpbrush->removeEmptyFaces();
375 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
378 else if( HollowType == diag ){
379 Brush* tmpbrush = new Brush( *brush );
380 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
381 tmpbrush->removeEmptyFaces();
382 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
384 if( !getRemoveInner() && getCaulk() ){
385 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
389 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
391 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
393 ( *i )->removeEmptyFaces();
394 if( ( *i )->hasContributingFaces() ){
395 NodeSmartReference node( ( new BrushNode() )->node() );
396 Node_getBrush( node )->copy( *( *i ) );
398 Node_getTraversable( path.parent() )->insert( node );
399 //path.push( makeReference( node.get() ) );
400 //selectPath( path, true );
401 //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
402 //Path_deleteTop( path );
411 typedef std::list<Brush*> brushlist_t;
413 class BrushGatherSelected : public scene::Graph::Walker
415 brush_vector_t& m_brushlist;
417 BrushGatherSelected( brush_vector_t& brushlist )
418 : m_brushlist( brushlist ){
421 bool pre( const scene::Path& path, scene::Instance& instance ) const {
422 if ( path.top().get().visible() ) {
423 Brush* brush = Node_getBrush( path.top() );
425 && Instance_getSelectable( instance )->isSelected() ) {
426 m_brushlist.push_back( brush );
433 class BrushDeleteSelected : public scene::Graph::Walker
436 bool pre( const scene::Path& path, scene::Instance& instance ) const {
439 void post( const scene::Path& path, scene::Instance& instance ) const {
440 if ( path.top().get().visible() ) {
441 Brush* brush = Node_getBrush( path.top() );
443 && Instance_getSelectable( instance )->isSelected()
444 && path.size() > 1 ) {
445 Path_deleteTop( path );
453 class BrushDeleteSelected : public scene::Graph::Walker
455 scene::Node* m_keepNode;
456 mutable bool m_eraseParent;
458 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
460 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
462 bool pre( const scene::Path& path, scene::Instance& instance ) const {
465 void post( const scene::Path& path, scene::Instance& instance ) const {
466 //globalOutputStream() << path.size() << "\n";
467 if ( path.top().get().visible() ) {
468 Brush* brush = Node_getBrush( path.top() );
470 && Instance_getSelectable( instance )->isSelected()
471 && path.size() > 1 ) {
472 scene::Node& parent = path.parent();
473 Path_deleteTop( path );
474 if( Node_getTraversable( parent )->empty() ){
475 m_eraseParent = true;
476 //globalOutputStream() << "Empty node?!.\n";
480 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
481 //globalOutputStream() << "about to Delete empty node!.\n";
482 m_eraseParent = false;
483 Entity* entity = Node_getEntity( path.top() );
484 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
485 && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
486 //globalOutputStream() << "now Deleting empty node!.\n";
487 Path_deleteTop( path );
498 void CSG_MakeRoom( void ){
499 UndoableCommand undo( "makeRoom" );
500 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
501 GlobalSceneGraph().traverse( BrushDeleteSelected() );
505 template<typename Type>
506 class RemoveReference
512 template<typename Type>
513 class RemoveReference<Type&>
519 template<typename Functor>
522 const Functor& functor;
524 Dereference( const Functor& functor ) : functor( functor ){
526 get_result_type<Functor> operator()( typename RemoveReference<get_argument<Functor, 0>>::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 template<typename Caller>
539 typedef get_argument<Caller, 1> FirstBound;
540 FirstBound firstBound;
542 BindArguments1( FirstBound firstBound )
543 : firstBound( firstBound ){
546 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
547 return Caller::call( firstArgument, firstBound );
551 template<typename Caller>
554 typedef get_argument<Caller, 1> FirstBound;
555 typedef get_argument<Caller, 2> SecondBound;
556 FirstBound firstBound;
557 SecondBound secondBound;
559 BindArguments2( FirstBound firstBound, SecondBound secondBound )
560 : firstBound( firstBound ), secondBound( secondBound ){
563 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
564 return Caller::call( firstArgument, firstBound, secondBound );
568 template<typename Caller, typename FirstBound, typename SecondBound>
569 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
570 return BindArguments2<Caller>( firstBound, secondBound );
573 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
574 return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
577 typedef Function<bool ( const Face &, const Plane3 &, bool ), Face_testPlane> FaceTestPlane;
580 /// \brief Returns true if
581 /// \li !flipped && brush is BACK or ON
582 /// \li flipped && brush is FRONT or ON
583 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
584 brush.evaluateBRep();
585 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
587 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
594 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
595 brush.evaluateBRep();
597 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
599 if ( ( *i )->contributes() ) {
600 split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
606 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
607 if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
608 brush_vector_t fragments;
609 fragments.reserve( other.size() );
612 for ( const std::shared_ptr<Face>& b : other )
614 if ( b->contributes() ) {
615 brushsplit_t split = Brush_classifyPlane( back, b->plane3() );
616 if ( split.counts[ePlaneFront] != 0
617 && split.counts[ePlaneBack] != 0 ) {
618 fragments.push_back( new Brush( back ) );
619 std::shared_ptr<Face> newFace = fragments.back()->addFace( *b );
620 if ( newFace != nullptr ) {
621 newFace->flipWinding();
625 else if ( split.counts[ePlaneBack] == 0 ) {
626 for ( Brush *i : fragments ) {
634 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
640 class SubtractBrushesFromUnselected : public scene::Graph::Walker
642 const brush_vector_t& m_brushlist;
643 std::size_t& m_before;
644 std::size_t& m_after;
645 mutable bool m_eraseParent;
647 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
648 : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
651 bool pre( const scene::Path& path, scene::Instance& instance ) const {
652 if ( path.top().get().visible() ) {
658 void post( const scene::Path& path, scene::Instance& instance ) const {
659 if ( path.top().get().visible() ) {
660 Brush* brush = Node_getBrush( path.top() );
662 && !Instance_getSelectable( instance )->isSelected() ) {
663 brush_vector_t buffer[2];
665 Brush* original = new Brush( *brush );
666 buffer[static_cast<std::size_t>( swap )].push_back( original );
669 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
671 for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
673 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
678 buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
681 buffer[static_cast<std::size_t>( swap )].clear();
686 brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
688 if ( out.size() == 1 && out.back() == original ) {
694 for ( Brush *b : out ) {
696 b->removeEmptyFaces();
698 NodeSmartReference node( ( new BrushNode() )->node() );
699 Node_getBrush( node )->copy( *b );
700 Node_getTraversable( path.parent() )->insert( node );
704 scene::Node& parent = path.parent();
705 Path_deleteTop( path );
706 if( Node_getTraversable( parent )->empty() ){
707 m_eraseParent = true;
712 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
713 m_eraseParent = false;
714 Entity* entity = Node_getEntity( path.top() );
715 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
716 && Node_getTraversable( path.top() )->empty() ) {
717 Path_deleteTop( path );
724 brush_vector_t selected_brushes;
725 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
727 if ( selected_brushes.empty() ) {
728 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
730 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
732 UndoableCommand undo( "brushSubtract" );
734 // subtract selected from unselected
735 std::size_t before = 0;
736 std::size_t after = 0;
737 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
738 globalOutputStream() << "CSG Subtract: Result: "
739 << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
740 << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
746 class BrushSplitByPlaneSelected : public scene::Graph::Walker
751 const char* m_shader;
752 const TextureProjection& m_projection;
755 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
756 : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
759 bool pre( const scene::Path& path, scene::Instance& instance ) const {
763 void post( const scene::Path& path, scene::Instance& instance ) const {
764 if ( !path.top().get().visible() ) {
768 Brush* brush = Node_getBrush( path.top() );
769 if ( brush == nullptr || !Instance_getSelectable( instance )->isSelected() ) {
773 Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
774 if ( !plane3_valid( plane ) ) {
778 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
779 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
780 // the plane intersects this brush
781 if ( m_split == eFrontAndBack ) {
782 NodeSmartReference node( ( new BrushNode() )->node() );
783 Brush* fragment = Node_getBrush( node );
784 fragment->copy( *brush );
785 std::shared_ptr<Face> newFace =
786 fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
787 if ( newFace != 0 && m_split != eFront ) {
788 newFace->flipWinding();
790 fragment->removeEmptyFaces();
791 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
793 Node_getTraversable( path.parent() )->insert( node );
795 scene::Path fragmentPath = path;
796 fragmentPath.top() = makeReference( node.get() );
797 selectPath( fragmentPath, true );
801 std::shared_ptr<Face> newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
802 if ( newFace != 0 && m_split == eFront ) {
803 newFace->flipWinding();
805 brush->removeEmptyFaces();
806 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
809 // the plane does not intersect this brush
810 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
811 // the brush is "behind" the plane
812 Path_deleteTop( path );
817 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
818 TextureProjection projection;
819 TexDef_Construct_Default( projection );
820 graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
825 class BrushInstanceSetClipPlane : public scene::Graph::Walker
829 BrushInstanceSetClipPlane( const Plane3& plane )
833 bool pre( const scene::Path& path, scene::Instance& instance ) const {
834 BrushInstance* brush = Instance_getBrush( instance );
836 && path.top().get().visible()
837 && brush->isSelected() ) {
838 BrushInstance& brushInstance = *brush;
839 brushInstance.setClipPlane( m_plane );
845 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
846 graph.traverse( BrushInstanceSetClipPlane( plane ) );
854 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
855 // gather potential outer faces
858 typedef std::vector<const Face*> Faces;
860 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
862 ( *i )->evaluateBRep();
863 for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
865 if ( !( *j )->contributes() ) {
869 const Face& face1 = *( *j );
872 // test faces of all input brushes
873 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
874 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
876 if ( k != i ) { // don't test a brush against itself
877 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
879 const Face& face2 = *( *l );
881 // face opposes another face
882 if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
883 // skip opposing planes
891 // check faces already stored
892 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
894 const Face& face2 = *( *m );
896 // face equals another face
897 if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
898 //if the texture/shader references should be the same but are not
899 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
902 // skip duplicate planes
907 // face1 plane intersects face2 winding or vice versa
908 if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
909 // result would not be convex
915 faces.push_back( &face1 );
919 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
921 if ( !brush.addFace( *( *i ) ) ) {
922 // result would have too many sides
928 brush.removeEmptyFaces();
933 void CSG_Merge( void ){
934 brush_vector_t selected_brushes;
937 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
939 if ( selected_brushes.empty() ) {
940 globalOutputStream() << "CSG Merge: No brushes selected.\n";
944 if ( selected_brushes.size() < 2 ) {
945 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
949 globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
951 UndoableCommand undo( "brushMerge" );
953 scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
955 NodeSmartReference node( ( new BrushNode() )->node() );
956 Brush* brush = Node_getBrush( node );
957 // if the new brush would not be convex
958 if ( !Brush_merge( *brush, selected_brushes, true ) ) {
959 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
963 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
965 // free the original brushes
966 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
969 Node_getTraversable( merged_path.top() )->insert( node );
970 merged_path.push( makeReference( node.get() ) );
972 selectPath( merged_path, true );
974 globalOutputStream() << "CSG Merge: Succeeded.\n";
989 #include "mainframe.h"
991 #include "gtkutil/dialog.h"
992 #include "gtkutil/button.h"
993 #include "gtkutil/accelerator.h"
994 #include "xywindow.h"
995 #include "camwindow.h"
1000 bool allocated{false};
1001 ui::Window window{ui::null};
1002 GtkToggleButton *radFaces, *radProj, *radCam, *caulk, *removeInner;
1005 CSGToolDialog g_csgtool_dialog;
1007 DoubleVector3 getExclusion(){
1008 if( gtk_toggle_button_get_active( g_csgtool_dialog.radProj ) ){
1009 if( GlobalXYWnd_getCurrentViewType() == YZ ){
1010 return DoubleVector3( 1, 0, 0 );
1012 else if( GlobalXYWnd_getCurrentViewType() == XZ ){
1013 return DoubleVector3( 0, 1, 0 );
1015 else if( GlobalXYWnd_getCurrentViewType() == XY ){
1016 return DoubleVector3( 0, 0, 1 );
1019 if( gtk_toggle_button_get_active( g_csgtool_dialog.radCam ) ){
1020 Vector3 angles( Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
1021 // globalOutputStream() << angles << " angles\n";
1022 DoubleVector3 radangles( degrees_to_radians( angles[0] ), degrees_to_radians( angles[1] ), degrees_to_radians( angles[2] ) );
1023 // globalOutputStream() << radangles << " radangles\n";
1024 // x = cos(yaw)*cos(pitch)
1025 // y = sin(yaw)*cos(pitch)
1027 DoubleVector3 viewvector;
1028 viewvector[0] = cos( radangles[1] ) * cos( radangles[0] );
1029 viewvector[1] = sin( radangles[1] ) * cos( radangles[0] );
1030 viewvector[2] = sin( radangles[0] );
1031 // globalOutputStream() << viewvector << " viewvector\n";
1034 return DoubleVector3( 0, 0, 0 );
1038 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
1044 bool getRemoveInner(){
1045 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
1051 class BrushFaceOffset
1055 BrushFaceOffset( float offset )
1058 void operator()( BrushInstance& brush ) const {
1061 doublevector_vector_t exclude_vec;
1062 Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
1063 if( mindot == 0 && maxdot == 0 ){
1064 Brush_ForEachFaceInstance( brush, FaceExcludeSelected( exclude_vec ) );
1066 Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
1070 //=================DLG
1072 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1073 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1074 UndoableCommand undo( "brushHollow::Diag" );
1075 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1076 if( getRemoveInner() )
1077 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1078 SceneChangeNotify();
1082 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1083 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1084 UndoableCommand undo( "brushHollow::Wrap" );
1085 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1086 if( getRemoveInner() )
1087 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1088 SceneChangeNotify();
1092 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1093 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1094 UndoableCommand undo( "brushHollow::Extrude" );
1095 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1096 if( getRemoveInner() )
1097 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1098 SceneChangeNotify();
1102 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1103 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1104 UndoableCommand undo( "brushHollow::Pull" );
1105 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1106 if( getRemoveInner() )
1107 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1108 SceneChangeNotify();
1112 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1113 gtk_spin_button_update ( dialog->spin );
1114 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1116 UndoableCommand undo( "Shrink brush" );
1117 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1118 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1119 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1120 SceneChangeNotify();
1124 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1125 gtk_spin_button_update ( dialog->spin );
1126 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1127 UndoableCommand undo( "Expand brush" );
1128 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1129 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1130 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1131 SceneChangeNotify();
1135 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1136 gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1140 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1141 gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1146 // FIXME: there is probably improvements to do less raw GTK stuff, more GTK wrapper
1147 if ( !g_csgtool_dialog.allocated ) {
1148 g_csgtool_dialog.allocated = true;
1149 g_csgtool_dialog.window = MainFrame_getWindow().create_dialog_window( "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1150 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1152 //GtkAccelGroup* accel = gtk_accel_group_new();
1153 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1154 global_accel_connect_window( g_csgtool_dialog.window );
1157 auto hbox = create_dialog_hbox( 4, 4 );
1158 gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1160 auto table = create_dialog_table( 3, 8, 4, 4 );
1161 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1163 //GtkWidget* label = gtk_label_new( "<->" );
1164 //gtk_widget_show( label );
1165 auto button = ui::Button( "Grid->" );
1166 table.attach( button, {0, 1, 0, 1}, {0, 0} );
1168 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1171 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1172 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1173 gtk_widget_show( GTK_WIDGET( spin ) );
1174 gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1175 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1176 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1177 (GtkAttachOptions) ( 0 ), 0, 0 );
1178 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1179 gtk_spin_button_set_numeric( spin, TRUE );
1181 g_csgtool_dialog.spin = spin;
1184 //radio button group for choosing the exclude axis
1185 GtkWidget* radFaces = gtk_radio_button_new_with_label( NULL, "-faces" );
1186 gtk_widget_set_tooltip_text( radFaces, "Exclude selected faces" );
1187 GtkWidget* radProj = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-proj" );
1188 gtk_widget_set_tooltip_text( radProj, "Exclude faces, most orthogonal to active projection" );
1189 GtkWidget* radCam = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-cam" );
1190 gtk_widget_set_tooltip_text( radCam, "Exclude faces, most orthogonal to camera view" );
1192 gtk_widget_show( radFaces );
1193 gtk_widget_show( radProj );
1194 gtk_widget_show( radCam );
1196 gtk_table_attach( table, radFaces, 2, 3, 0, 1,
1197 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1198 (GtkAttachOptions) ( 0 ), 0, 0 );
1199 gtk_table_attach( table, radProj, 3, 4, 0, 1,
1200 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1201 (GtkAttachOptions) ( 0 ), 0, 0 );
1202 gtk_table_attach( table, radCam, 4, 5, 0, 1,
1203 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1204 (GtkAttachOptions) ( 0 ), 0, 0 );
1206 g_csgtool_dialog.radFaces = GTK_TOGGLE_BUTTON( radFaces );
1207 g_csgtool_dialog.radProj = GTK_TOGGLE_BUTTON( radProj );
1208 g_csgtool_dialog.radCam = GTK_TOGGLE_BUTTON( radCam );
1211 GtkWidget* button = gtk_toggle_button_new();
1212 auto ubutton = ui::Button::from( button );
1213 button_set_icon( ubutton, "f-caulk.png" );
1214 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1215 table.attach( ubutton, { 6, 7, 0, 1 }, { GTK_EXPAND, 0 } );
1216 gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1217 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1219 g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1222 GtkWidget* button = gtk_toggle_button_new();
1223 auto ubutton = ui::Button::from( button );
1224 button_set_icon( ubutton, "csgtool_removeinner.png" );
1225 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1226 table.attach( ubutton, { 7, 8, 0, 1 }, { GTK_EXPAND, 0 } );
1227 gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1228 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1230 g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1233 GtkWidget* sep = gtk_hseparator_new();
1234 gtk_widget_show( sep );
1235 gtk_table_attach( table, sep, 0, 8, 1, 2,
1236 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1237 (GtkAttachOptions) ( 0 ), 0, 0 );
1240 GtkWidget* button = gtk_button_new();
1241 auto ubutton = ui::Button::from( button );
1242 button_set_icon( ubutton, "csgtool_shrink.png" );
1243 table.attach( ubutton, { 0, 1, 2, 3 }, { GTK_EXPAND, 0 } );
1244 gtk_widget_set_tooltip_text( button, "Shrink brush" );
1246 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1249 GtkWidget* button = gtk_button_new();
1250 auto ubutton = ui::Button::from( button );
1251 button_set_icon( ubutton, "csgtool_expand.png" );
1252 table.attach( ubutton, { 1, 2, 2, 3 }, { GTK_EXPAND, 0 } );
1253 gtk_widget_set_tooltip_text( button, "Expand brush" );
1255 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1258 GtkWidget* button = gtk_button_new();
1259 auto ubutton = ui::Button::from( button );
1260 button_set_icon( ubutton, "csgtool_diagonal.png" );
1261 table.attach( ubutton, { 3, 4, 2, 3 }, { GTK_EXPAND, 0 } );
1262 gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1264 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1267 GtkWidget* button = gtk_button_new();
1268 auto ubutton = ui::Button::from( button );
1269 button_set_icon( ubutton, "csgtool_wrap.png" );
1270 table.attach( ubutton, { 4, 5, 2, 3 }, { GTK_EXPAND, 0 } );
1271 gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1273 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1276 GtkWidget* button = gtk_button_new();
1277 auto ubutton = ui::Button::from( button );
1278 button_set_icon( ubutton, "csgtool_extrude.png" );
1279 table.attach( ubutton, { 5, 6, 2, 3 }, { GTK_EXPAND, 0 } );
1280 gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1282 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1285 GtkWidget* button = gtk_button_new();
1286 auto ubutton = ui::Button::from( button );
1287 button_set_icon( ubutton, "csgtool_pull.png" );
1288 table.attach( ubutton, { 6, 7, 2, 3 }, { GTK_EXPAND, 0 } );
1289 gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1291 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1298 gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1299 gtk_window_present( g_csgtool_dialog.window );