diff --git a/CHANGES b/CHANGES index f54a80e..be96d41 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,12 @@ This is the changelog for developers, != changelog for the end user that we distribute with the binaries. (see changelog) +07/10/2006 +- Added "select inside" and "select touching" + Both functions now work with multiple selectionbrushes, allowing complex + selection operations. +- Added entries for the selectionfunctions in "Edit" and the main toolbar. + 06/10/2006 namespace - Changed ETB not to show any texture if a tag search doesn't match anything (Shaderman) diff --git a/TODO b/TODO index d91876a..dcb862f 100644 --- a/TODO +++ b/TODO @@ -19,7 +19,6 @@ FEATURES - paint-select or equivalent (e.g. area-selection with occlusion) - select-complete-tall or equivalent (e.g. subtract-from-selection modifier key) -- need some equivalent to select-inside. - texture pane names are often illegible, becuase 1. they are long and overlap each other and 2. they overlap the outline rectangles around the images themselves. - texture sizes sometimes vary wildly. It would be nice to find a way to normalize their display size so that very big textures are shrunk a little, and very small textures are blown-up a little. @@ -134,7 +133,6 @@ Selection: Add shear manipulator? Textures Window: Improve texture-manipulation and texture-browsing tools. Undo: make selections undoable? Win32 Installer: Automatically upgrade existing installation. -Selection: grow-brush-selection feature - select all neighbouring brushes General: refactor game-specific hacks to be parameterised by .game file Patch: Overlays, Bend Mode, Thicken. Brush: Add brush-specific plugin API. diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 7c8a118..47cfca1 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -1980,6 +1980,8 @@ GtkMenuItem* create_edit_menu() menu_separator(menu); create_menu_item_with_mnemonic(menu, "C_lear Selection", "UnSelectSelection"); create_menu_item_with_mnemonic(menu, "_Invert Selection", "InvertSelection"); + create_menu_item_with_mnemonic(menu, "Select i_nside", "SelectInside"); + create_menu_item_with_mnemonic(menu, "Select _touching", "SelectTouching"); GtkMenu* convert_menu = create_sub_menu_with_mnemonic(menu, "E_xpand Selection"); create_menu_item_with_mnemonic(convert_menu, "To Whole _Entities", "ExpandSelectionToEntities"); @@ -2398,6 +2400,12 @@ void RotateFlip_constructToolbar(GtkToolbar* toolbar) toolbar_append_button(toolbar, "z-axis Rotate", "brush_rotatez.bmp", "RotateSelectionZ"); } +void Select_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Select touching", "selection_selecttouching.bmp", "SelectTouching"); + toolbar_append_button(toolbar, "Select inside", "selection_selectinside.bmp", "SelectInside"); +} + void CSG_constructToolbar(GtkToolbar* toolbar) { toolbar_append_button(toolbar, "CSG Subtract", "selection_csgsubtract.bmp", "CSGSubtract"); @@ -2449,6 +2457,10 @@ GtkToolbar* create_main_toolbar(MainFrame::EViewStyle style) gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + Select_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + CSG_constructToolbar(toolbar); gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); @@ -3344,6 +3356,8 @@ void MainFrame_Construct() GlobalCommands_insert("ParentSelection", FreeCaller()); GlobalCommands_insert("UnSelectSelection", FreeCaller(), Accelerator(GDK_Escape)); GlobalCommands_insert("InvertSelection", FreeCaller(), Accelerator('I')); + GlobalCommands_insert("SelectInside", FreeCaller()); + GlobalCommands_insert("SelectTouching", FreeCaller()); GlobalCommands_insert("ExpandSelectionToEntities", FreeCaller(), Accelerator('E', (GdkModifierType)(GDK_MOD1_MASK|GDK_CONTROL_MASK))); GlobalCommands_insert("Preferences", FreeCaller(), Accelerator('P')); diff --git a/radiant/select.cpp b/radiant/select.cpp index 496d96a..70e815d 100644 --- a/radiant/select.cpp +++ b/radiant/select.cpp @@ -38,6 +38,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "gtkutil/dialog.h" #include "gtkutil/widget.h" #include "brushmanip.h" +#include "brush.h" #include "patchmanip.h" #include "patchdialog.h" #include "selection.h" @@ -52,6 +53,172 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA select_workzone_t g_select_workzone; +/** + Loops over all selected brushes and stores their + world AABBs in the specified array. +*/ +class CollectSelectedBrushesBounds : public SelectionSystem::Visitor +{ + AABB* m_bounds; // array of AABBs + Unsigned m_max; // max AABB-elements in array + Unsigned& m_count;// count of valid AABBs stored in array + +public: + CollectSelectedBrushesBounds(AABB* bounds, Unsigned max, Unsigned& count) + : m_bounds(bounds), + m_max(max), + m_count(count) + { + m_count = 0; + } + + void visit(scene::Instance& instance) const + { + ASSERT_MESSAGE(m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds"); + + // stop if the array is already full + if(m_count == m_max) + return; + + Selectable* selectable = Instance_getSelectable(instance); + if((selectable != 0) + && instance.isSelected()) + { + // brushes only + if(Instance_getBrush(instance) != 0) + { + m_bounds[m_count] = instance.worldAABB(); + ++m_count; + } + } + } +}; + +/** + Selects all objects that intersect one of the bounding AABBs. + The exact intersection-method is specified through TSelectionPolicy +*/ +template +class SelectByBounds : public scene::Graph::Walker +{ + AABB* m_aabbs; // selection aabbs + Unsigned m_count; // number of aabbs in m_aabbs + TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb + +public: + SelectByBounds(AABB* aabbs, Unsigned count) + : m_aabbs(aabbs), + m_count(count) + { + } + + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + + // ignore worldspawn + Entity* entity = Node_getEntity(path.top()); + if(entity) + { + if(string_equal(entity->getKeyValue("classname"), "worldspawn")) + return true; + } + + if( (path.size() > 1) && + (!path.top().get().isRoot()) && + (selectable != 0) + ) + { + for(Unsigned i = 0; i < m_count; ++i) + { + if(policy.Evaluate(m_aabbs[i], instance)) + { + selectable->setSelected(true); + } + } + } + + return true; + } + + /** + Performs selection operation on the global scenegraph. + If delete_bounds_src is true, then the objects which were + used as source for the selection aabbs will be deleted. +*/ + static void DoSelection(bool delete_bounds_src = true) + { + if(GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) + { + // we may not need all AABBs since not all selected objects have to be brushes + const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected(); + AABB* aabbs = new AABB[max]; + + Unsigned count; + CollectSelectedBrushesBounds collector(aabbs, max, count); + GlobalSelectionSystem().foreachSelected(collector); + + // nothing usable in selection + if(!count) + { + delete[] aabbs; + return; + } + + // delete selected objects + if(delete_bounds_src)// see deleteSelection + { + UndoableCommand undo("deleteSelected"); + Select_Delete(); + } + + // select objects with bounds + GlobalSceneGraph().traverse(SelectByBounds(aabbs, count)); + + SceneChangeNotify(); + delete[] aabbs; + } + } +}; + +/** + SelectionPolicy for SelectByBounds + Returns true if box and the AABB of instance intersect +*/ +class SelectionPolicy_Touching +{ +public: + bool Evaluate(const AABB& box, scene::Instance& instance) const + { + const AABB& other(instance.worldAABB()); + for(Unsigned i = 0; i < 3; ++i) + { + if(fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] + other.extents[i])) + return false; + } + return true; + } +}; + +/** + SelectionPolicy for SelectByBounds + Returns true if the AABB of instance is inside box +*/ +class SelectionPolicy_Inside +{ +public: + bool Evaluate(const AABB& box, scene::Instance& instance) const + { + const AABB& other(instance.worldAABB()); + for(Unsigned i = 0; i < 3; ++i) + { + if(fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] - other.extents[i])) + return false; + } + return true; + } +}; + class DeleteSelected : public scene::Graph::Walker { mutable bool m_remove; @@ -73,12 +240,13 @@ public: { m_remove = true; - return false; + return false;// dont traverse into child elements } return true; } void post(const scene::Path& path, scene::Instance& instance) const { + if(m_removedChild) { m_removedChild = false; @@ -93,6 +261,7 @@ public: } } + // node should be removed if(m_remove) { if(Node_isEntity(path.parent()) != 0) @@ -575,7 +744,15 @@ void Select_AllOfType() } } +void Select_Inside(void) +{ + SelectByBounds::DoSelection(); +} +void Select_Touching(void) +{ + SelectByBounds::DoSelection(false); +} void Select_FitTexture(float horizontal, float vertical) { diff --git a/radiant/select.h b/radiant/select.h index db71181..5a019f7 100644 --- a/radiant/select.h +++ b/radiant/select.h @@ -29,6 +29,8 @@ void Select_GetMid(Vector3& mid); void Select_Delete(); void Select_Invert(); +void Select_Inside(); +void Select_Touching(); void Scene_ExpandSelectionToEntities(); void Selection_Flipx();