From 7b57cad11a87f7445cee2ec62ee1741dca771e69 Mon Sep 17 00:00:00 2001 From: codereader Date: Sun, 29 Mar 2020 13:19:41 +0200 Subject: [PATCH] #5122: Add SelectByFilterWalker to evaluate whether a single XMLFilter applies to various nodes in which case they will be highlighted --- radiant/filters/BasicFilterSystem.cpp | 60 +++++++++++++--- radiant/filters/BasicFilterSystem.h | 65 ++++++++--------- radiant/filters/InstanceUpdateWalker.h | 83 ++++++++++++--------- radiant/filters/SelectByFilterWalker.h | 99 ++++++++++++++++++++++++++ tools/msvc/DarkRadiant.vcxproj | 1 + tools/msvc/DarkRadiant.vcxproj.filters | 3 + 6 files changed, 233 insertions(+), 78 deletions(-) create mode 100644 radiant/filters/SelectByFilterWalker.h diff --git a/radiant/filters/BasicFilterSystem.cpp b/radiant/filters/BasicFilterSystem.cpp index 3da4bb8acb..e0f7bc6376 100644 --- a/radiant/filters/BasicFilterSystem.cpp +++ b/radiant/filters/BasicFilterSystem.cpp @@ -1,6 +1,6 @@ #include "BasicFilterSystem.h" -#include "InstanceUpdateWalker.h" +#include #include "iradiant.h" #include "itextstream.h" @@ -11,7 +11,8 @@ #include "ishaders.h" #include "modulesystem/StaticModule.h" -#include +#include "InstanceUpdateWalker.h" +#include "SelectByFilterWalker.h" namespace filters { @@ -68,6 +69,32 @@ void BasicFilterSystem::setAllFilterStatesCmd(const cmd::ArgumentList& args) setAllFilterStates(args.front().getInt() != 0); } +void BasicFilterSystem::selectObjectsByFilterCmd(const cmd::ArgumentList& args) +{ + if (args.size() != 1) + { + rMessage() << "Usage: SelectObjectsByFilter FilterName" << std::endl; + return; + } + + if (!GlobalSceneGraph().root()) + { + rError() << "No map loaded." << std::endl; + return; + } + + auto f = _availableFilters.find(args[0].getString()); + + if (f == _availableFilters.end()) + { + rError() << "Cannot find the filter named " << args[0].getString() << std::endl; + return; + } + + SelectByFilterWalker walker(*this, f->second, true); + GlobalSceneGraph().root()->traverse(walker); +} + // Initialise the filter system void BasicFilterSystem::initialiseModule(const ApplicationContext& ctx) { @@ -97,6 +124,9 @@ void BasicFilterSystem::initialiseModule(const ApplicationContext& ctx) GlobalEventManager().addCommand("ActivateAllFilters", "ActivateAllFilters"); GlobalEventManager().addCommand("DeactivateAllFilters", "DeactivateAllFilters"); + + GlobalCommandSystem().addCommand("SelectObjectsByFilter", + std::bind(&BasicFilterSystem::selectObjectsByFilterCmd, this, std::placeholders::_1), { cmd::ARGTYPE_STRING }); } void BasicFilterSystem::addFiltersFromXML(const xml::NodeList& nodes, bool readOnly) @@ -153,10 +183,7 @@ void BasicFilterSystem::addFiltersFromXML(const xml::NodeList& nodes, bool readO ).first->second; // Add the according toggle command to the eventmanager - IEventPtr fEvent = GlobalEventManager().addToggle( - filter.getEventName(), - std::bind(&XMLFilter::toggle, &inserted, std::placeholders::_1) - ); + auto fEvent = createEventToggle(inserted); // If this filter is in our active set, enable it if (activeFilterNames.find(filterName) != activeFilterNames.end()) @@ -164,6 +191,9 @@ void BasicFilterSystem::addFiltersFromXML(const xml::NodeList& nodes, bool readO fEvent->setToggled(true); _activeFilters.insert(std::make_pair(filterName, inserted)); } + + // Add the statement to the command system + // TODO } } @@ -253,6 +283,11 @@ std::string BasicFilterSystem::getFilterEventName(const std::string& filter) return f != _availableFilters.end() ? f->second.getEventName() : std::string(); } +bool BasicFilterSystem::getFilterState(const std::string& filter) +{ + return _activeFilters.find(filter) != _activeFilters.end(); +} + // Change the state of a named filter void BasicFilterSystem::setFilterState(const std::string& filter, bool state) { @@ -320,10 +355,7 @@ bool BasicFilterSystem::addFilter(const std::string& filterName, const FilterRul result.first->second.setRules(ruleSet); // Add the according toggle command to the eventmanager - auto fEvent = GlobalEventManager().addToggle( - result.first->second.getEventName(), - std::bind(&XMLFilter::toggle, &result.first->second, std::placeholders::_1) - ); + createEventToggle(result.first->second); // Clear the cache, the rules have changed _visibilityCache.clear(); @@ -333,6 +365,14 @@ bool BasicFilterSystem::addFilter(const std::string& filterName, const FilterRul return true; } +IEventPtr BasicFilterSystem::createEventToggle(XMLFilter& filter) +{ + return GlobalEventManager().addToggle( + filter.getEventName(), + std::bind(&XMLFilter::toggle, &filter, std::placeholders::_1) + ); +} + bool BasicFilterSystem::removeFilter(const std::string& filter) { auto f = _availableFilters.find(filter); diff --git a/radiant/filters/BasicFilterSystem.h b/radiant/filters/BasicFilterSystem.h index 661d8ed11a..9f66c9ae05 100644 --- a/radiant/filters/BasicFilterSystem.h +++ b/radiant/filters/BasicFilterSystem.h @@ -4,6 +4,7 @@ #include "imodule.h" #include "ifilter.h" #include "icommandsystem.h" +#include "ieventmanager.h" #include "xmlutil/Node.h" #include @@ -16,10 +17,10 @@ namespace filters /** FilterSystem implementation class. */ - -class BasicFilterSystem -: public FilterSystem +class BasicFilterSystem : + public FilterSystem { +private: // Hashtable of available filters, indexed by name typedef std::map FilterTable; FilterTable _availableFilters; @@ -46,68 +47,64 @@ class BasicFilterSystem void addFiltersFromXML(const xml::NodeList& nodes, bool readOnly); -public: - virtual ~BasicFilterSystem() {} + IEventPtr createEventToggle(XMLFilter& filter); + + void setAllFilterStatesCmd(const cmd::ArgumentList& args); + void selectObjectsByFilterCmd(const cmd::ArgumentList& args); + +public: // FilterSystem implementation - sigc::signal filtersChangedSignal() const; + sigc::signal filtersChangedSignal() const override; // Invoke the InstanceUpateWalker to update the filtered status. - void update(); + void update() override; // Updates the given subgraph - void updateSubgraph(const scene::INodePtr& root); + void updateSubgraph(const scene::INodePtr& root) override; // Filter system visit function - void forEachFilter(IFilterVisitor& visitor); + void forEachFilter(IFilterVisitor& visitor) override; - std::string getFilterEventName(const std::string& filter); + std::string getFilterEventName(const std::string& filter) override; - bool getFilterState(const std::string& filter) { - return (_activeFilters.find(filter) != _activeFilters.end()); - } + bool getFilterState(const std::string& filter) override; // Set the state of a filter - void setFilterState(const std::string& filter, bool state); + void setFilterState(const std::string& filter, bool state) override; // Query whether an item is visible or filtered out - bool isVisible(const FilterRule::Type type, const std::string& name); + bool isVisible(const FilterRule::Type type, const std::string& name) override; // Query whether an entity is visible or filtered out - bool isEntityVisible(const FilterRule::Type type, const Entity& entity); + bool isEntityVisible(const FilterRule::Type type, const Entity& entity) override; // Whether this filter is read-only and can't be changed - bool filterIsReadOnly(const std::string& filter); + bool filterIsReadOnly(const std::string& filter) override; // Adds a new filter to the system - bool addFilter(const std::string& filterName, const FilterRules& ruleSet); + bool addFilter(const std::string& filterName, const FilterRules& ruleSet) override; // Removes the filter and returns true on success - bool removeFilter(const std::string& filter); + bool removeFilter(const std::string& filter) override; // Renames the filter and updates eventmanager - bool renameFilter(const std::string& oldFilterName, const std::string& newFilterName); + bool renameFilter(const std::string& oldFilterName, const std::string& newFilterName) override; // Returns the ruleset of the named filter - FilterRules getRuleSet(const std::string& filter); + FilterRules getRuleSet(const std::string& filter) override; // Applies the ruleset and replaces the previous one for a given filter. - bool setFilterRules(const std::string& filter, const FilterRules& ruleSet); + bool setFilterRules(const std::string& filter, const FilterRules& ruleSet) override; - /** - * Activates or deactivates all known filters. - */ - void setAllFilterStates(bool state); - - // Command target, inspects arguments and passes on to the - void setAllFilterStatesCmd(const cmd::ArgumentList& args); + // Activates or deactivates all known filters. + void setAllFilterStates(bool state) override; // RegisterableModule implementation - virtual const std::string& getName() const; - virtual const StringSet& getDependencies() const; - virtual void initialiseModule(const ApplicationContext& ctx); - virtual void shutdownModule(); + const std::string& getName() const override; + const StringSet& getDependencies() const override; + void initialiseModule(const ApplicationContext& ctx) override; + void shutdownModule() override; }; -typedef std::shared_ptr BasicFilterSystemPtr; } // namespace filters diff --git a/radiant/filters/InstanceUpdateWalker.h b/radiant/filters/InstanceUpdateWalker.h index b377abd532..2079961837 100644 --- a/radiant/filters/InstanceUpdateWalker.h +++ b/radiant/filters/InstanceUpdateWalker.h @@ -1,9 +1,8 @@ #pragma once -#include "iscenegraph.h" +#include "inode.h" #include "ientity.h" #include "iselectable.h" -#include "ieclass.h" #include "ipatch.h" #include "ibrush.h" @@ -42,8 +41,8 @@ class NodeVisibilityUpdater : }; /** - * Scenegraph walker to update filtered status of Instances based on the - * status of their parent entity class. + * Scenegraph walker to update filtered status of nodes based on the + * currently active set of filters. */ class InstanceUpdateWalker : public scene::NodeVisitor @@ -69,63 +68,79 @@ class InstanceUpdateWalker : _brushesAreVisible(_filterSystem.isVisible(FilterRule::TYPE_OBJECT, "brush")) {} - // Pre-descent walker function bool pre(const scene::INodePtr& node) override { // Check entity eclass and spawnargs if (Node_isEntity(node)) { - Entity* entity = Node_getEntity(node); + bool isVisible = evaluateEntity(node); - // Check the eclass first - bool entityIsVisible = _filterSystem.isEntityVisible(FilterRule::TYPE_ENTITYCLASS, *entity) && - _filterSystem.isEntityVisible(FilterRule::TYPE_ENTITYKEYVALUE, *entity); + setSubgraphFilterStatus(node, isVisible); - node->traverse(entityIsVisible ? _showWalker : _hideWalker); - - if (!entityIsVisible) - { - // de-select this node and all children - node->traverse(_deselector); - } - - // If the entity is hidden, don't traverse the child nodes - return entityIsVisible; + // If the entity is hidden, don't traverse its child nodes + return isVisible; } - // greebo: Update visibility of Patches + // greebo: Check visibility of Patches if (Node_isPatch(node)) { - auto patchNode = std::dynamic_pointer_cast(node); + bool isVisible = evaluatePatch(node); - bool isVisible = _patchesAreVisible && patchNode->getPatch().hasVisibleMaterial(); - - node->traverse(isVisible ? _showWalker : _hideWalker); + setSubgraphFilterStatus(node, isVisible); } - // greebo: Update visibility of Brushes + // greebo: Check visibility of Brushes else if (Node_isBrush(node)) { - auto brush = Node_getIBrush(node); - - bool isVisible = _brushesAreVisible && brush->hasVisibleMaterial(); + bool isVisible = evaluateBrush(node); - node->traverse(isVisible ? _showWalker : _hideWalker); + setSubgraphFilterStatus(node, isVisible); // In case the brush has at least one visible material trigger a fine-grained update if (isVisible) { - brush->updateFaceVisibility(); + Node_getIBrush(node)->updateFaceVisibility(); } } - if (!node->visible()) + // Continue the traversal + return true; + } + +private: + bool evaluateEntity(const scene::INodePtr& node) + { + assert(Node_isEntity(node)); + + Entity* entity = Node_getEntity(node); + + // Check the eclass first + return _filterSystem.isEntityVisible(FilterRule::TYPE_ENTITYCLASS, *entity) && + _filterSystem.isEntityVisible(FilterRule::TYPE_ENTITYKEYVALUE, *entity); + } + + bool evaluatePatch(const scene::INodePtr& node) + { + assert(Node_isPatch(node)); + + return _patchesAreVisible && Node_getIPatch(node)->hasVisibleMaterial(); + } + + bool evaluateBrush(const scene::INodePtr& node) + { + assert(Node_isBrush(node)); + + return _brushesAreVisible && Node_getIBrush(node)->hasVisibleMaterial(); + } + + void setSubgraphFilterStatus(const scene::INodePtr& node, bool isVisible) + { + node->traverse(isVisible ? _showWalker : _hideWalker); + + if (!isVisible) { // de-select this node and all children node->traverse(_deselector); } - - // Continue the traversal - return true; } }; diff --git a/radiant/filters/SelectByFilterWalker.h b/radiant/filters/SelectByFilterWalker.h new file mode 100644 index 0000000000..fe8630ddeb --- /dev/null +++ b/radiant/filters/SelectByFilterWalker.h @@ -0,0 +1,99 @@ +#pragma once + +#include "inode.h" +#include "ifilter.h" +#include "ipatch.h" +#include "ibrush.h" +#include "ientity.h" +#include "iselectable.h" + +#include "XMLFilter.h" + +namespace filters +{ + +class SelectByFilterWalker : + public scene::NodeVisitor +{ +private: + XMLFilter& _filter; + bool _selectIfFiltered; + +public: + SelectByFilterWalker(FilterSystem& filterSystem, XMLFilter& filter, bool selectIfFiltered) : + _filter(filter), + _selectIfFiltered(selectIfFiltered) + {} + + bool pre(const scene::INodePtr& node) override + { + // Check entity eclass and spawnargs + if (Node_isEntity(node)) + { + Entity* entity = Node_getEntity(node); + + bool isVisible = _filter.isEntityVisible(FilterRule::TYPE_ENTITYCLASS, *entity) && + _filter.isEntityVisible(FilterRule::TYPE_ENTITYKEYVALUE, *entity); + + if (!isVisible) + { + // The filter would hide this item, apply the action + Node_setSelected(node, _selectIfFiltered); + } + + // If the entity is affected, don't traverse its child nodes + return isVisible; + } + + // greebo: Check visibility of Patches + if (Node_isPatch(node)) + { + // Check by object type "patch" and by the patch's material + bool isVisible = _filter.isVisible(FilterRule::TYPE_OBJECT, "patch") && + materialIsVisible(Node_getIPatch(node)->getShader()); + + if (!isVisible) + { + // The filter would hide this item, apply the action + Node_setSelected(node, _selectIfFiltered); + } + } + // greebo: Check visibility of Brushes + else if (Node_isBrush(node)) + { + // Check by object type "brush" and by the brush materials + bool isVisible = _filter.isVisible(FilterRule::TYPE_OBJECT, "brush") && + allBrushMaterialsVisible(Node_getIBrush(node)); + + if (!isVisible) + { + // The filter would hide this item (at least partially), apply the action + Node_setSelected(node, _selectIfFiltered); + } + } + + // Continue the traversal + return true; + } + +private: + bool materialIsVisible(const std::string& materialName) + { + return _filter.isVisible(FilterRule::TYPE_TEXTURE, materialName); + } + + bool allBrushMaterialsVisible(IBrush* brush) + { + for (std::size_t i = 0; i < brush->getNumFaces(); ++i) + { + if (!materialIsVisible(brush->getFace(i).getShader())) + { + return false; + } + } + + return brush->hasContributingFaces(); + } +}; + +} diff --git a/tools/msvc/DarkRadiant.vcxproj b/tools/msvc/DarkRadiant.vcxproj index 3481d2504f..42e3c10f25 100644 --- a/tools/msvc/DarkRadiant.vcxproj +++ b/tools/msvc/DarkRadiant.vcxproj @@ -1097,6 +1097,7 @@ + diff --git a/tools/msvc/DarkRadiant.vcxproj.filters b/tools/msvc/DarkRadiant.vcxproj.filters index 8a8a7be3be..5063417cc1 100644 --- a/tools/msvc/DarkRadiant.vcxproj.filters +++ b/tools/msvc/DarkRadiant.vcxproj.filters @@ -3222,6 +3222,9 @@ src\layers + + src\filters +