diff --git a/libs/scenelib.h b/libs/scenelib.h index 91dab80a..d03031f7 100644 --- a/libs/scenelib.h +++ b/libs/scenelib.h @@ -49,12 +49,15 @@ virtual void setSelectedComponents( bool select, SelectionSystem::EComponentMode virtual void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ) = 0; }; +typedef Callback1 Vector3Callback; + class ComponentEditable { public: STRING_CONSTANT( Name, "ComponentEditable" ); virtual const AABB& getSelectedComponentsBounds() const = 0; +virtual void gatherSelectedComponents( const Vector3Callback& callback ) const = 0; }; class ComponentSnappable @@ -932,8 +935,6 @@ virtual void increment() = 0; virtual void decrement() = 0; }; -#include "generic/callback.h" - class SimpleCounter : public Counter { Callback m_countChanged; diff --git a/plugins/entity/doom3group.cpp b/plugins/entity/doom3group.cpp index b8dc9701..b7d47198 100644 --- a/plugins/entity/doom3group.cpp +++ b/plugins/entity/doom3group.cpp @@ -560,6 +560,8 @@ const AABB& getSelectedComponentsBounds() const { m_curveCatmullRom.forEachSelected( ControlPointAddBounds( m_aabb_component ) ); return m_aabb_component; } +void gatherSelectedComponents( const Vector3Callback& callback ) const { +} void snapComponents( float snap ){ if ( m_curveNURBS.isSelected() ) { diff --git a/radiant/brush.h b/radiant/brush.h index 59553762..3a1c1b81 100644 --- a/radiant/brush.h +++ b/radiant/brush.h @@ -2670,6 +2670,27 @@ void iterate_selected( AABB& aabb ) const { SelectedComponents_foreach( AABBExtendByPoint( aabb ) ); } +void gatherSelectedComponents( const Vector3Callback& callback ) const { + const Winding& winding = getFace().getWinding(); + if( isSelected() ) + for ( std::size_t i = 0; i != winding.numpoints; ++i ) + callback( winding[i].vertex ); + for ( VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i ){ + std::size_t index = Winding_FindAdjacent( winding, *i ); + if ( index != c_brush_maxFaces ) { + callback( winding[index].vertex ); + } + } + for ( VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i ){ + std::size_t index = Winding_FindAdjacent( winding, *i ); + if ( index != c_brush_maxFaces ) { + std::size_t adjacent = Winding_next( winding, index ); + callback( winding[index].vertex ); + callback( winding[adjacent].vertex ); + } + } +} + class RenderablePointVectorPushBack { RenderablePointVector& m_points; @@ -3694,6 +3715,12 @@ const AABB& getSelectedComponentsBounds() const { return m_aabb_component; } +void gatherSelectedComponents( const Vector3Callback& callback ) const { + for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i ) + { + ( *i ).gatherSelectedComponents( callback ); + } +} void snapComponents( float snap ){ for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i ) diff --git a/radiant/brushmanip.cpp b/radiant/brushmanip.cpp index 3f0b5f3f..06382797 100644 --- a/radiant/brushmanip.cpp +++ b/radiant/brushmanip.cpp @@ -1566,6 +1566,7 @@ void Brush_constructMenu( GtkMenu* menu ){ } create_menu_item_with_mnemonic( menu_in_menu, "CSG _Subtract", "CSGSubtract" ); create_menu_item_with_mnemonic( menu_in_menu, "CSG _Merge", "CSGMerge" ); + create_menu_item_with_mnemonic( menu_in_menu, "CSG _Wrap Merge", "CSGWrapMerge" ); create_menu_item_with_mnemonic( menu_in_menu, "Make _Room", "CSGroom" ); create_menu_item_with_mnemonic( menu_in_menu, "CSG _Tool", "CSGTool" ); } diff --git a/radiant/csg.cpp b/radiant/csg.cpp index 08b7b357..cf4e5a53 100644 --- a/radiant/csg.cpp +++ b/radiant/csg.cpp @@ -911,6 +911,24 @@ bool Brush_merge( Brush& brush, const brush_vector_t& in, bool onlyshape ){ return true; } +scene::Path ultimate_group_path(){ + if ( GlobalSelectionSystem().countSelected() != 0 ) { + scene::Path path = GlobalSelectionSystem().ultimateSelected().path(); + scene::Node& node = path.top(); + if( Node_isPrimitive( node ) ){ + path.pop(); + return path; + } + if ( Node_isEntity( node ) && node_is_group( node ) ){ + return path; + } + } + scene::Path path; + path.push( makeReference( GlobalSceneGraph().root() ) ); + path.push( makeReference( Map_FindOrInsertWorldspawn( g_map ) ) ); + return path; +} + void CSG_Merge( void ){ brush_vector_t selected_brushes; @@ -931,8 +949,6 @@ void CSG_Merge( void ){ UndoableCommand undo( "brushMerge" ); - scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path(); - NodeSmartReference node( ( new BrushNode() )->node() ); Brush* brush = Node_getBrush( node ); // if the new brush would not be convex @@ -943,14 +959,15 @@ void CSG_Merge( void ){ { ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" ); + scene::Path path = ultimate_group_path(); + // free the original brushes - GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) ); + GlobalSceneGraph().traverse( BrushDeleteSelected( path.top().get_pointer() ) ); - merged_path.pop(); - Node_getTraversable( merged_path.top() )->insert( node ); - merged_path.push( makeReference( node.get() ) ); + Node_getTraversable( path.top() )->insert( node ); + path.push( makeReference( node.get() ) ); - selectPath( merged_path, true ); + selectPath( path, true ); globalOutputStream() << "CSG Merge: Succeeded.\n"; SceneChangeNotify(); @@ -959,6 +976,203 @@ void CSG_Merge( void ){ +class MergeVertices +{ + typedef std::vector Vertices; + Vertices m_vertices; +public: + typedef Vertices::const_iterator const_iterator; + void insert( const Vector3& vertex ){ + for( const_iterator i = begin(); i != end(); ++i ) + if( Edge_isDegenerate( vertex, *i ) ) + return; + m_vertices.push_back( vertex ); + } + const_iterator begin() const { + return m_vertices.begin(); + } + const_iterator end() const { + return m_vertices.end(); + } + std::size_t size() const { + return m_vertices.size(); + } + brushsplit_t classify_plane( const Plane3& plane ) const { + brushsplit_t split; + for( const_iterator i = begin(); i != end(); ++i ){ + WindingVertex_ClassifyPlane( ( *i ), plane, split ); + if( ( split.counts[ePlaneFront] != 0 ) && ( split.counts[ePlaneBack] != 0 ) ) + break; + } + return split; + } +}; + +class Scene_gatherSelectedComponents : public scene::Graph::Walker +{ +MergeVertices& m_mergeVertices; +void call( const Vector3& value ) const { + m_mergeVertices.insert( value ); +} +typedef ConstMemberCaller1 Caller; +const Vector3Callback m_callback; +public: +Scene_gatherSelectedComponents( MergeVertices& mergeVertices ) + : m_mergeVertices( mergeVertices ), m_callback( Vector3Callback( Scene_gatherSelectedComponents::Caller( *this ) ) ){ +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + if ( path.top().get().visible() ) { + ComponentEditable* componentEditable = Instance_getComponentEditable( instance ); + if ( componentEditable ) { + componentEditable->gatherSelectedComponents( m_callback ); + } + return true; + } + return false; +} +}; + +class MergePlane +{ +public: + Plane3 m_plane; + const Face* m_face; + Vector3 m_v1; + Vector3 m_v2; + Vector3 m_v3; + MergePlane( const Plane3& plane, const Face* face ) : m_plane( plane ), m_face( face ){ + } + MergePlane( const Plane3& plane, const Vector3& v1, const Vector3& v2, const Vector3& v3 ) : m_plane( plane ), m_face( 0 ), m_v1( v1 ), m_v2( v2 ), m_v3( v3 ){ + } +}; + +class MergePlanes +{ + typedef std::vector Planes; + Planes m_planes; +public: + typedef Planes::const_iterator const_iterator; + void insert( const MergePlane& plane ){ + for( const_iterator i = begin(); i != end(); ++i ) + if( plane3_equal( plane.m_plane, i->m_plane ) ) + return; + m_planes.push_back( plane ); + } + const_iterator begin() const { + return m_planes.begin(); + } + const_iterator end() const { + return m_planes.end(); + } + std::size_t size() const { + return m_planes.size(); + } +}; + +void CSG_WrapMerge(){ + const bool primit = ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ); + brush_vector_t selected_brushes; + if( primit ) + GlobalSceneGraph().traverse( BrushGatherSelected( selected_brushes ) ); + + if ( !GlobalSelectionSystem().countSelected() && !GlobalSelectionSystem().countSelectedComponents() ) { + globalWarningStream() << "CSG Wrap Merge: No brushes or components selected.\n"; + return; + } + + MergeVertices mergeVertices; + /* gather unique vertices */ + for ( brush_vector_t::const_iterator b = selected_brushes.begin(); b != selected_brushes.end(); ++b ) + for ( Brush::const_iterator f = ( *b )->begin(); f != ( *b )->end(); ++f ) + if ( ( *f )->contributes() ){ + const Winding& winding = ( *f )->getWinding(); + for ( std::size_t w = 0; w != winding.numpoints; ++w ) + mergeVertices.insert( winding[w].vertex ); + } + + GlobalSceneGraph().traverse( Scene_gatherSelectedComponents( mergeVertices ) ); + +//globalOutputStream() << mergeVertices.size() << " mergeVertices.size()\n"; + if( mergeVertices.size() < 4 ){ + globalWarningStream() << "CSG Wrap Merge: Too few vertices: " << mergeVertices.size() << ".\n"; + return; + } + + MergePlanes mergePlanes; + /* gather unique && worthy planes */ + for ( brush_vector_t::const_iterator b = selected_brushes.begin(); b != selected_brushes.end(); ++b ) + for ( Brush::const_iterator f = ( *b )->begin(); f != ( *b )->end(); ++f ){ + const Face& face = *( *f ); + if ( face.contributes() ){ + const brushsplit_t split = mergeVertices.classify_plane( face.getPlane().plane3() ); + if( ( split.counts[ePlaneFront] == 0 ) != ( split.counts[ePlaneBack] == 0 ) ) + mergePlanes.insert( MergePlane( face.getPlane().plane3(), &face ) ); + } + } + /* bruteforce new planes */ + for( MergeVertices::const_iterator i = mergeVertices.begin() + 0; i != mergeVertices.end() - 2; ++i ) + for( MergeVertices::const_iterator j = i + 1; j != mergeVertices.end() - 1; ++j ) + for( MergeVertices::const_iterator k = j + 1; k != mergeVertices.end() - 0; ++k ){ + const Plane3 plane = plane3_for_points( *i, *j, *k ); + if( plane3_valid( plane ) ){ + const brushsplit_t split = mergeVertices.classify_plane( plane ); + if( ( split.counts[ePlaneFront] == 0 ) != ( split.counts[ePlaneBack] == 0 ) ){ + if( split.counts[ePlaneFront] != 0 ) + mergePlanes.insert( MergePlane( plane3_flipped( plane ), *i, *j, *k ) ); + else + mergePlanes.insert( MergePlane( plane, *i, *k, *j ) ); + } + } + } + +//globalOutputStream() << mergePlanes.size() << " mergePlanes.size()\n"; + if( mergePlanes.size() < 4 ){ + globalWarningStream() << "CSG Wrap Merge: Too few planes: " << mergePlanes.size() << ".\n"; + return; + } + + UndoableCommand undo( "brushWrapMerge" ); + + NodeSmartReference node( ( new BrushNode() )->node() ); + Brush* brush = Node_getBrush( node ); + + { + const char* shader = TextureBrowser_GetSelectedShader(); + TextureProjection projection; + TexDef_Construct_Default( projection ); + for( MergePlanes::const_iterator i = mergePlanes.begin(); i != mergePlanes.end(); ++i ){ + if( i->m_face ) + brush->addFace( *( i->m_face ) ); + else + brush->addPlane( i->m_v1, i->m_v2, i->m_v3, shader, projection ); +// globalOutputStream() << i->m_plane.normal() << " " << i->m_plane.dist() << " i->m_plane\n"; + } + brush->removeEmptyFaces(); + } + + // if the new brush would not be convex + if ( !brush->hasContributingFaces() ) { + globalWarningStream() << "CSG Wrap Merge: Failed - result would not be convex.\n"; + } + else + { + ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" ); + + scene::Path path = ultimate_group_path(); + + // free the original brushes + if( primit ) + GlobalSceneGraph().traverse( BrushDeleteSelected( path.top().get_pointer() ) ); + + Node_getTraversable( path.top() )->insert( node ); + path.push( makeReference( node.get() ) ); + + selectPath( path, true ); + } +} + + + diff --git a/radiant/csg.h b/radiant/csg.h index 3e9bc7cc..ee959642 100644 --- a/radiant/csg.h +++ b/radiant/csg.h @@ -25,6 +25,7 @@ void CSG_MakeRoom(); void CSG_Subtract(); void CSG_Merge(); +void CSG_WrapMerge(); void CSG_Tool(); namespace scene diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 9b2e22ec..18b80a7f 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -2459,7 +2459,7 @@ void Select_constructToolbar( GtkToolbar* toolbar ){ void CSG_constructToolbar( GtkToolbar* toolbar ){ toolbar_append_button( toolbar, "CSG Subtract (SHIFT + U)", "selection_csgsubtract.png", "CSGSubtract" ); - toolbar_append_button( toolbar, "CSG Merge (CTRL + U)", "selection_csgmerge.png", "CSGMerge" ); + toolbar_append_button( toolbar, "CSG Wrap Merge (CTRL + U)", "selection_csgmerge.png", "CSGWrapMerge" ); toolbar_append_button( toolbar, "Room", "selection_makeroom.png", "CSGroom" ); toolbar_append_button( toolbar, "CSG Tool", "ellipsis.png", "CSGTool" ); } @@ -3564,7 +3564,8 @@ void MainFrame_Construct(){ GlobalCommands_insert( "CSGSubtract", FreeCaller(), Accelerator( 'U', (GdkModifierType)GDK_SHIFT_MASK ) ); - GlobalCommands_insert( "CSGMerge", FreeCaller(), Accelerator( 'U', (GdkModifierType)GDK_CONTROL_MASK ) ); + GlobalCommands_insert( "CSGMerge", FreeCaller() ); + GlobalCommands_insert( "CSGWrapMerge", FreeCaller(), Accelerator( 'U', (GdkModifierType)GDK_CONTROL_MASK ) ); GlobalCommands_insert( "CSGroom", FreeCaller() ); GlobalCommands_insert( "CSGTool", FreeCaller() ); diff --git a/radiant/patch.h b/radiant/patch.h index b32bf026..be1c5c99 100644 --- a/radiant/patch.h +++ b/radiant/patch.h @@ -1554,6 +1554,14 @@ const AABB& getSelectedComponentsBounds() const { return m_aabb_component; } +void gatherSelectedComponents( const Vector3Callback& callback ) const { + for ( PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i ) + { + if ( ( *i ).m_selectable.isSelected() ) { + callback( ( *i ).m_ctrl->m_vertex ); + } + } +} void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){ test.BeginMesh( localToWorld() ); diff --git a/radiant/winding.cpp b/radiant/winding.cpp index 3ada9647..c17a1a35 100644 --- a/radiant/winding.cpp +++ b/radiant/winding.cpp @@ -183,6 +183,10 @@ brushsplit_t Winding_ClassifyPlane( const Winding& winding, const Plane3& plane return split; } +void WindingVertex_ClassifyPlane( const Vector3& vertex, const Plane3& plane, brushsplit_t& split ){ + ++split.counts[Winding_ClassifyDistance( plane3_distance_to_point( plane, vertex ), ON_EPSILON )]; +} + #define DEBUG_EPSILON ON_EPSILON const double DEBUG_EPSILON_SQUARED = DEBUG_EPSILON * DEBUG_EPSILON; diff --git a/radiant/winding.h b/radiant/winding.h index 924e84bf..b68fdb0f 100644 --- a/radiant/winding.h +++ b/radiant/winding.h @@ -264,6 +264,7 @@ struct brushsplit_t }; brushsplit_t Winding_ClassifyPlane( const Winding& w, const Plane3& plane ); +void WindingVertex_ClassifyPlane( const Vector3& vertex, const Plane3& plane, brushsplit_t& split ); bool Winding_PlanesConcave( const Winding& w1, const Winding& w2, const Plane3& plane1, const Plane3& plane2 ); bool Winding_TestPlane( const Winding& w, const Plane3& plane, bool flipped );