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"
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;
88 CaulkFace( DoubleVector3 ExclusionAxis,
91 ExclusionAxis( ExclusionAxis ),
94 void operator()( Face& face ) const {
95 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
96 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) )
97 face.SetShader( getCaulkShader() );
106 eHollowType HollowType;
107 DoubleVector3 ExclusionAxis;
113 FaceMakeBrush( const Brush& brush,
116 eHollowType HollowType,
117 DoubleVector3 ExclusionAxis,
125 HollowType( HollowType ),
126 ExclusionAxis( ExclusionAxis ),
130 RemoveInner( RemoveInner ){
132 void operator()( Face& face ) const {
133 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
134 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
135 if( HollowType == pull ){
136 if ( face.contributes() ) {
137 face.getPlane().offset( offset );
139 out.push_back( new Brush( brush ) );
140 face.getPlane().offset( -offset );
144 Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot ) );
146 std::shared_ptr<Face> newFace = out.back()->addFace( face );
147 if ( newFace != 0 ) {
148 newFace->flipWinding();
152 else if( HollowType == wrap ){
153 //Face_makeBrush( face, brush, out, offset );
154 if ( face.contributes() ) {
156 out.push_back( new Brush( brush ) );
157 if( !RemoveInner && caulk )
158 face.SetShader( getCaulkShader() );
159 std::shared_ptr<Face> newFace = out.back()->addFace( face );
160 face.getPlane().offset( -offset );
163 face.SetShader( getCaulkShader() );
164 if ( newFace != 0 ) {
165 newFace->flipWinding();
166 newFace->getPlane().offset( offset );
167 newFace->planeChanged();
171 else if( HollowType == extrude ){
172 if ( face.contributes() ) {
174 out.push_back( new Brush( brush ) );
177 std::shared_ptr<Face> newFace = out.back()->addFace( face );
178 if ( newFace != 0 ) {
179 newFace->getPlane().offset( offset );
180 newFace->planeChanged();
183 if( !RemoveInner && caulk )
184 face.SetShader( getCaulkShader() );
185 newFace = out.back()->addFace( face );
186 if ( newFace != 0 ) {
187 newFace->flipWinding();
189 Winding& winding = face.getWinding();
190 TextureProjection projection;
191 TexDef_Construct_Default( projection );
192 for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
193 std::size_t index = std::distance( winding.begin(), j );
194 std::size_t next = Winding_next( winding, index );
196 out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
200 else if( HollowType == diag ){
201 if ( face.contributes() ) {
202 out.push_back( new Brush( brush ) );
205 std::shared_ptr<Face> newFace = out.back()->addFace( face );
206 if ( newFace != 0 ) {
208 newFace->planeChanged();
210 newFace = out.back()->addFace( face );
212 if ( newFace != 0 ) {
213 if( !RemoveInner && caulk )
214 newFace->SetShader( getCaulkShader() );
215 newFace->flipWinding();
216 newFace->getPlane().offset( offset );
217 newFace->planeChanged();
220 Winding& winding = face.getWinding();
221 TextureProjection projection;
222 TexDef_Construct_Default( projection );
223 for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
224 std::size_t index = std::distance( winding.begin(), i );
225 std::size_t next = Winding_next( winding, index );
227 float bestdist = 999999;
229 for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
230 Winding& winding2 = ( *j )->getWinding();
231 for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
232 std::size_t index2 = std::distance( winding2.begin(), k );
233 float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
234 if( testdist < bestdist ){
236 BestPoint = winding2[index2].vertex;
240 out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
250 DoubleVector3 ExclusionAxis;
254 FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
255 : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
257 void operator()( Face& face ) const {
258 if( vector3_length_squared( ExclusionAxis ) != 0 ){
259 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
263 else if( dot > maxdot ){
273 DoubleVector3 ExclusionAxis;
277 FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
278 : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
280 void operator()( Face& face ) const {
281 double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
282 if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
284 face.getPlane().offset( offset );
291 DoubleVector3 getExclusion();
293 bool getRemoveInner();
295 class BrushHollowSelectedWalker : public scene::Graph::Walker
298 eHollowType HollowType;
300 BrushHollowSelectedWalker( float offset, eHollowType HollowType )
301 : offset( offset ), HollowType( HollowType ){
303 bool pre( const scene::Path& path, scene::Instance& instance ) const {
304 if ( path.top().get().visible() ) {
305 Brush* brush = Node_getBrush( path.top() );
307 && Instance_getSelectable( instance )->isSelected()
308 && path.size() > 1 ) {
312 if( HollowType != room ){
313 Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
315 if( HollowType == room ){
316 Brush* tmpbrush = new Brush( *brush );
317 tmpbrush->removeEmptyFaces();
318 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, true, true ) );
321 else if( HollowType == pull ){
322 if( !getRemoveInner() && getCaulk() ){
323 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
325 Brush* tmpbrush = new Brush( *brush );
326 tmpbrush->removeEmptyFaces();
327 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
330 else if( HollowType == diag ){
331 Brush* tmpbrush = new Brush( *brush );
332 Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
333 tmpbrush->removeEmptyFaces();
334 Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
336 if( !getRemoveInner() && getCaulk() ){
337 Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot ) );
341 Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, getCaulk(), getRemoveInner() ) );
343 for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
345 ( *i )->removeEmptyFaces();
346 if( ( *i )->hasContributingFaces() ){
347 NodeSmartReference node( ( new BrushNode() )->node() );
348 Node_getBrush( node )->copy( *( *i ) );
350 Node_getTraversable( path.parent() )->insert( node );
351 //path.push( makeReference( node.get() ) );
352 //selectPath( path, true );
353 //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
354 //Path_deleteTop( path );
363 typedef std::list<Brush*> brushlist_t;
365 class BrushGatherSelected : public scene::Graph::Walker
367 brush_vector_t& m_brushlist;
369 BrushGatherSelected( brush_vector_t& brushlist )
370 : m_brushlist( brushlist ){
373 bool pre( const scene::Path& path, scene::Instance& instance ) const {
374 if ( path.top().get().visible() ) {
375 Brush* brush = Node_getBrush( path.top() );
377 && Instance_getSelectable( instance )->isSelected() ) {
378 m_brushlist.push_back( brush );
385 class BrushDeleteSelected : public scene::Graph::Walker
388 bool pre( const scene::Path& path, scene::Instance& instance ) const {
391 void post( const scene::Path& path, scene::Instance& instance ) const {
392 if ( path.top().get().visible() ) {
393 Brush* brush = Node_getBrush( path.top() );
395 && Instance_getSelectable( instance )->isSelected()
396 && path.size() > 1 ) {
397 Path_deleteTop( path );
405 class BrushDeleteSelected : public scene::Graph::Walker
407 scene::Node* m_keepNode;
408 mutable bool m_eraseParent;
410 BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
412 BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
414 bool pre( const scene::Path& path, scene::Instance& instance ) const {
417 void post( const scene::Path& path, scene::Instance& instance ) const {
418 //globalOutputStream() << path.size() << "\n";
419 if ( path.top().get().visible() ) {
420 Brush* brush = Node_getBrush( path.top() );
422 && Instance_getSelectable( instance )->isSelected()
423 && path.size() > 1 ) {
424 scene::Node& parent = path.parent();
425 Path_deleteTop( path );
426 if( Node_getTraversable( parent )->empty() ){
427 m_eraseParent = true;
428 //globalOutputStream() << "Empty node?!.\n";
432 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
433 //globalOutputStream() << "about to Delete empty node!.\n";
434 m_eraseParent = false;
435 Entity* entity = Node_getEntity( path.top() );
436 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
437 && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
438 //globalOutputStream() << "now Deleting empty node!.\n";
439 Path_deleteTop( path );
450 void CSG_MakeRoom( void ){
451 UndoableCommand undo( "makeRoom" );
452 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
453 GlobalSceneGraph().traverse( BrushDeleteSelected() );
457 template<typename Type>
458 class RemoveReference
464 template<typename Type>
465 class RemoveReference<Type&>
471 template<typename Functor>
474 const Functor& functor;
476 Dereference( const Functor& functor ) : functor( functor ){
478 get_result_type<Functor> operator()( typename RemoveReference<get_argument<Functor, 0>>::type *firstArgument ) const {
479 return functor( *firstArgument );
483 template<typename Functor>
484 inline Dereference<Functor> makeDereference( const Functor& functor ){
485 return Dereference<Functor>( functor );
488 template<typename Caller>
491 typedef get_argument<Caller, 1> FirstBound;
492 FirstBound firstBound;
494 BindArguments1( FirstBound firstBound )
495 : firstBound( firstBound ){
498 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
499 return Caller::call( firstArgument, firstBound );
503 template<typename Caller>
506 typedef get_argument<Caller, 1> FirstBound;
507 typedef get_argument<Caller, 2> SecondBound;
508 FirstBound firstBound;
509 SecondBound secondBound;
511 BindArguments2( FirstBound firstBound, SecondBound secondBound )
512 : firstBound( firstBound ), secondBound( secondBound ){
515 get_result_type<Caller> operator()( get_argument<Caller, 0> firstArgument ) const {
516 return Caller::call( firstArgument, firstBound, secondBound );
520 template<typename Caller, typename FirstBound, typename SecondBound>
521 BindArguments2<Caller> bindArguments( const Caller& caller, FirstBound firstBound, SecondBound secondBound ){
522 return BindArguments2<Caller>( firstBound, secondBound );
525 inline bool Face_testPlane( const Face& face, const Plane3& plane, bool flipped ){
526 return face.contributes() && !Winding_TestPlane( face.getWinding(), plane, flipped );
529 typedef Function<bool ( const Face &, const Plane3 &, bool ), Face_testPlane> FaceTestPlane;
532 /// \brief Returns true if
533 /// \li !flipped && brush is BACK or ON
534 /// \li flipped && brush is FRONT or ON
535 bool Brush_testPlane( const Brush& brush, const Plane3& plane, bool flipped ){
536 brush.evaluateBRep();
537 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
539 if ( Face_testPlane( *( *i ), plane, flipped ) ) {
546 brushsplit_t Brush_classifyPlane( const Brush& brush, const Plane3& plane ){
547 brush.evaluateBRep();
549 for ( Brush::const_iterator i( brush.begin() ); i != brush.end(); ++i )
551 if ( ( *i )->contributes() ) {
552 split += Winding_ClassifyPlane( ( *i )->getWinding(), plane );
558 bool Brush_subtract( const Brush& brush, const Brush& other, brush_vector_t& ret_fragments ){
559 if ( aabb_intersects_aabb( brush.localAABB(), other.localAABB() ) ) {
560 brush_vector_t fragments;
561 fragments.reserve( other.size() );
564 for ( const std::shared_ptr<Face>& b : other )
566 if ( b->contributes() ) {
567 brushsplit_t split = Brush_classifyPlane( back, b->plane3() );
568 if ( split.counts[ePlaneFront] != 0
569 && split.counts[ePlaneBack] != 0 ) {
570 fragments.push_back( new Brush( back ) );
571 std::shared_ptr<Face> newFace = fragments.back()->addFace( *b );
572 if ( newFace != nullptr ) {
573 newFace->flipWinding();
577 else if ( split.counts[ePlaneBack] == 0 ) {
578 for ( Brush *i : fragments ) {
586 ret_fragments.insert( ret_fragments.end(), fragments.begin(), fragments.end() );
592 class SubtractBrushesFromUnselected : public scene::Graph::Walker
594 const brush_vector_t& m_brushlist;
595 std::size_t& m_before;
596 std::size_t& m_after;
597 mutable bool m_eraseParent;
599 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
600 : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
603 bool pre( const scene::Path& path, scene::Instance& instance ) const {
604 if ( path.top().get().visible() ) {
610 void post( const scene::Path& path, scene::Instance& instance ) const {
611 if ( path.top().get().visible() ) {
612 Brush* brush = Node_getBrush( path.top() );
614 && !Instance_getSelectable( instance )->isSelected() ) {
615 brush_vector_t buffer[2];
617 Brush* original = new Brush( *brush );
618 buffer[static_cast<std::size_t>( swap )].push_back( original );
621 for ( brush_vector_t::const_iterator i( m_brushlist.begin() ); i != m_brushlist.end(); ++i )
623 for ( brush_vector_t::iterator j( buffer[static_cast<std::size_t>( swap )].begin() ); j != buffer[static_cast<std::size_t>( swap )].end(); ++j )
625 if ( Brush_subtract( *( *j ), *( *i ), buffer[static_cast<std::size_t>( !swap )] ) ) {
630 buffer[static_cast<std::size_t>( !swap )].push_back( ( *j ) );
633 buffer[static_cast<std::size_t>( swap )].clear();
638 brush_vector_t& out = buffer[static_cast<std::size_t>( swap )];
640 if ( out.size() == 1 && out.back() == original ) {
646 for ( Brush *b : out ) {
648 b->removeEmptyFaces();
650 NodeSmartReference node( ( new BrushNode() )->node() );
651 Node_getBrush( node )->copy( *b );
652 Node_getTraversable( path.parent() )->insert( node );
656 scene::Node& parent = path.parent();
657 Path_deleteTop( path );
658 if( Node_getTraversable( parent )->empty() ){
659 m_eraseParent = true;
664 if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
665 m_eraseParent = false;
666 Entity* entity = Node_getEntity( path.top() );
667 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
668 && Node_getTraversable( path.top() )->empty() ) {
669 Path_deleteTop( path );
676 brush_vector_t selected_brushes;
677 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
679 if ( selected_brushes.empty() ) {
680 globalOutputStream() << "CSG Subtract: No brushes selected.\n";
682 globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned( selected_brushes.size() ) << " brushes.\n";
684 UndoableCommand undo( "brushSubtract" );
686 // subtract selected from unselected
687 std::size_t before = 0;
688 std::size_t after = 0;
689 GlobalSceneGraph().traverse( SubtractBrushesFromUnselected( selected_brushes, before, after ) );
690 globalOutputStream() << "CSG Subtract: Result: "
691 << Unsigned( after ) << " fragment" << ( after == 1 ? "" : "s" )
692 << " from " << Unsigned( before ) << " brush" << ( before == 1 ? "" : "es" ) << ".\n";
698 class BrushSplitByPlaneSelected : public scene::Graph::Walker
703 const char* m_shader;
704 const TextureProjection& m_projection;
707 BrushSplitByPlaneSelected( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split )
708 : m_p0( p0 ), m_p1( p1 ), m_p2( p2 ), m_shader( shader ), m_projection( projection ), m_split( split ){
711 bool pre( const scene::Path& path, scene::Instance& instance ) const {
715 void post( const scene::Path& path, scene::Instance& instance ) const {
716 if ( !path.top().get().visible() ) {
720 Brush* brush = Node_getBrush( path.top() );
721 if ( brush == nullptr || !Instance_getSelectable( instance )->isSelected() ) {
725 Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
726 if ( !plane3_valid( plane ) ) {
730 brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
731 if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
732 // the plane intersects this brush
733 if ( m_split == eFrontAndBack ) {
734 NodeSmartReference node( ( new BrushNode() )->node() );
735 Brush* fragment = Node_getBrush( node );
736 fragment->copy( *brush );
737 std::shared_ptr<Face> newFace =
738 fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
739 if ( newFace != 0 && m_split != eFront ) {
740 newFace->flipWinding();
742 fragment->removeEmptyFaces();
743 ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
745 Node_getTraversable( path.parent() )->insert( node );
747 scene::Path fragmentPath = path;
748 fragmentPath.top() = makeReference( node.get() );
749 selectPath( fragmentPath, true );
753 std::shared_ptr<Face> newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
754 if ( newFace != 0 && m_split == eFront ) {
755 newFace->flipWinding();
757 brush->removeEmptyFaces();
758 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
761 // the plane does not intersect this brush
762 if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
763 // the brush is "behind" the plane
764 Path_deleteTop( path );
769 void Scene_BrushSplitByPlane( scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split ){
770 TextureProjection projection;
771 TexDef_Construct_Default( projection );
772 graph.traverse( BrushSplitByPlaneSelected( p0, p1, p2, shader, projection, split ) );
777 class BrushInstanceSetClipPlane : public scene::Graph::Walker
781 BrushInstanceSetClipPlane( const Plane3& plane )
785 bool pre( const scene::Path& path, scene::Instance& instance ) const {
786 BrushInstance* brush = Instance_getBrush( instance );
788 && path.top().get().visible()
789 && brush->isSelected() ) {
790 BrushInstance& brushInstance = *brush;
791 brushInstance.setClipPlane( m_plane );
797 void Scene_BrushSetClipPlane( scene::Graph& graph, const Plane3& plane ){
798 graph.traverse( BrushInstanceSetClipPlane( plane ) );
806 bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){
807 // gather potential outer faces
810 typedef std::vector<const Face*> Faces;
812 for ( brush_vector_t::const_iterator i( in.begin() ); i != in.end(); ++i )
814 ( *i )->evaluateBRep();
815 for ( Brush::const_iterator j( ( *i )->begin() ); j != ( *i )->end(); ++j )
817 if ( !( *j )->contributes() ) {
821 const Face& face1 = *( *j );
824 // test faces of all input brushes
825 //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards.
826 for ( brush_vector_t::const_iterator k( in.begin() ); !skip && k != in.end(); ++k )
828 if ( k != i ) { // don't test a brush against itself
829 for ( Brush::const_iterator l( ( *k )->begin() ); !skip && l != ( *k )->end(); ++l )
831 const Face& face2 = *( *l );
833 // face opposes another face
834 if ( plane3_opposing( face1.plane3(), face2.plane3() ) ) {
835 // skip opposing planes
843 // check faces already stored
844 for ( Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m )
846 const Face& face2 = *( *m );
848 // face equals another face
849 if ( plane3_equal( face1.plane3(), face2.plane3() ) ) {
850 //if the texture/shader references should be the same but are not
851 if ( !onlyshape && !shader_equal( face1.getShader().getShader(), face2.getShader().getShader() ) ) {
854 // skip duplicate planes
859 // face1 plane intersects face2 winding or vice versa
860 if ( Winding_PlanesConcave( face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3() ) ) {
861 // result would not be convex
867 faces.push_back( &face1 );
871 for ( Faces::const_iterator i = faces.begin(); i != faces.end(); ++i )
873 if ( !brush.addFace( *( *i ) ) ) {
874 // result would have too many sides
880 brush.removeEmptyFaces();
885 void CSG_Merge( void ){
886 brush_vector_t selected_brushes;
889 GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) );
891 if ( selected_brushes.empty() ) {
892 globalOutputStream() << "CSG Merge: No brushes selected.\n";
896 if ( selected_brushes.size() < 2 ) {
897 globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n";
901 globalOutputStream() << "CSG Merge: Merging " << Unsigned( selected_brushes.size() ) << " brushes.\n";
903 UndoableCommand undo( "brushMerge" );
905 scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path();
907 NodeSmartReference node( ( new BrushNode() )->node() );
908 Brush* brush = Node_getBrush( node );
909 // if the new brush would not be convex
910 if ( !Brush_merge( *brush, selected_brushes, true ) ) {
911 globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n";
915 ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
917 // free the original brushes
918 GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
921 Node_getTraversable( merged_path.top() )->insert( node );
922 merged_path.push( makeReference( node.get() ) );
924 selectPath( merged_path, true );
926 globalOutputStream() << "CSG Merge: Succeeded.\n";
941 #include "mainframe.h"
943 #include "gtkutil/dialog.h"
944 #include "gtkutil/button.h"
945 #include "gtkutil/accelerator.h"
950 bool allocated{false};
951 ui::Window window{ui::null};
952 GtkToggleButton *radXYZ, *radX, *radY, *radZ, *caulk, *removeInner;
955 CSGToolDialog g_csgtool_dialog;
957 DoubleVector3 getExclusion(){
958 if( gtk_toggle_button_get_active( g_csgtool_dialog.radX ) ){
959 return DoubleVector3( 1, 0, 0 );
961 else if( gtk_toggle_button_get_active( g_csgtool_dialog.radY ) ){
962 return DoubleVector3( 0, 1, 0 );
964 else if( gtk_toggle_button_get_active( g_csgtool_dialog.radZ ) ){
965 return DoubleVector3( 0, 0, 1 );
967 return DoubleVector3( 0, 0, 0 );
971 if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
977 bool getRemoveInner(){
978 if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
984 class BrushFaceOffset
988 BrushFaceOffset( float offset )
991 void operator()( BrushInstance& brush ) const {
994 Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
995 Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot ) );
999 //=================DLG
1001 static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
1002 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1003 UndoableCommand undo( "brushHollow::Diag" );
1004 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
1005 if( getRemoveInner() )
1006 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1007 SceneChangeNotify();
1011 static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
1012 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1013 UndoableCommand undo( "brushHollow::Wrap" );
1014 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
1015 if( getRemoveInner() )
1016 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1017 SceneChangeNotify();
1021 static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
1022 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1023 UndoableCommand undo( "brushHollow::Extrude" );
1024 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
1025 if( getRemoveInner() )
1026 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1027 SceneChangeNotify();
1031 static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
1032 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1033 UndoableCommand undo( "brushHollow::Pull" );
1034 GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
1035 if( getRemoveInner() )
1036 GlobalSceneGraph().traverse( BrushDeleteSelected() );
1037 SceneChangeNotify();
1041 static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
1042 gtk_spin_button_update ( dialog->spin );
1043 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1045 UndoableCommand undo( "Shrink brush" );
1046 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1047 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1048 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1049 SceneChangeNotify();
1053 static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
1054 gtk_spin_button_update ( dialog->spin );
1055 float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
1056 UndoableCommand undo( "Expand brush" );
1057 // GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
1058 //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
1059 Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
1060 SceneChangeNotify();
1064 static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
1065 gtk_spin_button_set_value( dialog->spin, GetGridSize() );
1069 static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
1070 gtk_widget_hide( GTK_WIDGET( dialog->window ) );
1075 // FIXME: there is probably improvements to do less raw GTK stuff, more GTK wrapper
1076 if ( !g_csgtool_dialog.allocated ) {
1077 g_csgtool_dialog.allocated = true;
1078 g_csgtool_dialog.window = MainFrame_getWindow().create_dialog_window( "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
1079 gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
1081 //GtkAccelGroup* accel = gtk_accel_group_new();
1082 //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
1083 global_accel_connect_window( g_csgtool_dialog.window );
1086 auto hbox = create_dialog_hbox( 4, 4 );
1087 gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
1089 auto table = create_dialog_table( 3, 8, 4, 4 );
1090 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
1092 //GtkWidget* label = gtk_label_new( "<->" );
1093 //gtk_widget_show( label );
1094 auto button = ui::Button( "Grid->" );
1095 table.attach( button, {0, 1, 0, 1}, {0, 0} );
1097 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
1100 GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
1101 GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
1102 gtk_widget_show( GTK_WIDGET( spin ) );
1103 gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
1104 gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
1105 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1106 (GtkAttachOptions) ( 0 ), 0, 0 );
1107 gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
1108 gtk_spin_button_set_numeric( spin, TRUE );
1110 g_csgtool_dialog.spin = spin;
1113 //radio button group for choosing the exclude axis
1114 GtkWidget* radXYZ = gtk_radio_button_new_with_label( NULL, "XYZ" );
1115 GtkWidget* radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-X" );
1116 GtkWidget* radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Y" );
1117 GtkWidget* radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radXYZ), "-Z" );
1118 gtk_widget_show( radXYZ );
1119 gtk_widget_show( radX );
1120 gtk_widget_show( radY );
1121 gtk_widget_show( radZ );
1123 gtk_table_attach( table, radXYZ, 2, 3, 0, 1,
1124 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1125 (GtkAttachOptions) ( 0 ), 0, 0 );
1126 gtk_table_attach( table, radX, 3, 4, 0, 1,
1127 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1128 (GtkAttachOptions) ( 0 ), 0, 0 );
1129 gtk_table_attach( table, radY, 4, 5, 0, 1,
1130 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1131 (GtkAttachOptions) ( 0 ), 0, 0 );
1132 gtk_table_attach( table, radZ, 5, 6, 0, 1,
1133 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1134 (GtkAttachOptions) ( 0 ), 0, 0 );
1136 g_csgtool_dialog.radXYZ = GTK_TOGGLE_BUTTON( radXYZ );
1137 g_csgtool_dialog.radX = GTK_TOGGLE_BUTTON( radX );
1138 g_csgtool_dialog.radY = GTK_TOGGLE_BUTTON( radY );
1139 g_csgtool_dialog.radZ = GTK_TOGGLE_BUTTON( radZ );
1142 GtkWidget* button = gtk_toggle_button_new();
1143 auto ubutton = ui::Button::from( button );
1144 button_set_icon( ubutton, "f-caulk.png" );
1145 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1146 table.attach( ubutton, { 6, 7, 0, 1 }, { GTK_EXPAND, 0 } );
1147 gtk_widget_set_tooltip_text( button, "Caulk some faces" );
1148 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1150 g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
1153 GtkWidget* button = gtk_toggle_button_new();
1154 auto ubutton = ui::Button::from( button );
1155 button_set_icon( ubutton, "csgtool_removeinner.png" );
1156 gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
1157 table.attach( ubutton, { 7, 8, 0, 1 }, { GTK_EXPAND, 0 } );
1158 gtk_widget_set_tooltip_text( button, "Remove inner brush" );
1159 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
1161 g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
1164 GtkWidget* sep = gtk_hseparator_new();
1165 gtk_widget_show( sep );
1166 gtk_table_attach( table, sep, 0, 8, 1, 2,
1167 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
1168 (GtkAttachOptions) ( 0 ), 0, 0 );
1171 GtkWidget* button = gtk_button_new();
1172 auto ubutton = ui::Button::from( button );
1173 button_set_icon( ubutton, "csgtool_shrink.png" );
1174 table.attach( ubutton, { 0, 1, 2, 3 }, { GTK_EXPAND, 0 } );
1175 gtk_widget_set_tooltip_text( button, "Shrink brush" );
1177 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
1180 GtkWidget* button = gtk_button_new();
1181 auto ubutton = ui::Button::from( button );
1182 button_set_icon( ubutton, "csgtool_expand.png" );
1183 table.attach( ubutton, { 1, 2, 2, 3 }, { GTK_EXPAND, 0 } );
1184 gtk_widget_set_tooltip_text( button, "Expand brush" );
1186 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
1189 GtkWidget* button = gtk_button_new();
1190 auto ubutton = ui::Button::from( button );
1191 button_set_icon( ubutton, "csgtool_diagonal.png" );
1192 table.attach( ubutton, { 3, 4, 2, 3 }, { GTK_EXPAND, 0 } );
1193 gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
1195 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
1198 GtkWidget* button = gtk_button_new();
1199 auto ubutton = ui::Button::from( button );
1200 button_set_icon( ubutton, "csgtool_wrap.png" );
1201 table.attach( ubutton, { 4, 5, 2, 3 }, { GTK_EXPAND, 0 } );
1202 gtk_widget_set_tooltip_text( button, "Hollow::warp" );
1204 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
1207 GtkWidget* button = gtk_button_new();
1208 auto ubutton = ui::Button::from( button );
1209 button_set_icon( ubutton, "csgtool_extrude.png" );
1210 table.attach( ubutton, { 5, 6, 2, 3 }, { GTK_EXPAND, 0 } );
1211 gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
1213 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
1216 GtkWidget* button = gtk_button_new();
1217 auto ubutton = ui::Button::from( button );
1218 button_set_icon( ubutton, "csgtool_pull.png" );
1219 table.attach( ubutton, { 6, 7, 2, 3 }, { GTK_EXPAND, 0 } );
1220 gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
1222 g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
1229 gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
1230 gtk_window_present( g_csgtool_dialog.window );