From 51bf2254c38d3367a66d21d70a8c96355d0de8eb Mon Sep 17 00:00:00 2001
From: Fabian Paus <fabian.paus@kit.edu>
Date: Thu, 30 Dec 2021 10:04:20 +0100
Subject: [PATCH] ArViz: Add selection callbacks

---
 .../ArViz/Client/elements/ElementOps.h        |   1 +
 .../components/ArViz/Coin/ElementVisualizer.h |   1 +
 .../components/ArViz/Coin/Visualizer.cpp      | 165 +++++++++++++-----
 .../components/ArViz/Coin/Visualizer.h        |  19 ++
 .../ArViz/ArVizWidgetController.cpp           |  10 +-
 5 files changed, 147 insertions(+), 49 deletions(-)

diff --git a/source/RobotAPI/components/ArViz/Client/elements/ElementOps.h b/source/RobotAPI/components/ArViz/Client/elements/ElementOps.h
index b4dc3c192..fb3f4b81a 100644
--- a/source/RobotAPI/components/ArViz/Client/elements/ElementOps.h
+++ b/source/RobotAPI/components/ArViz/Client/elements/ElementOps.h
@@ -227,6 +227,7 @@ namespace armarx::viz
         DerivedT& enable(InteractionDescription const& interactionDescription)
         {
             data_->interaction = interactionDescription.data_;
+            return *static_cast<DerivedT*>(this);
         }
 
 
diff --git a/source/RobotAPI/components/ArViz/Coin/ElementVisualizer.h b/source/RobotAPI/components/ArViz/Coin/ElementVisualizer.h
index af58bde80..595db2893 100644
--- a/source/RobotAPI/components/ArViz/Coin/ElementVisualizer.h
+++ b/source/RobotAPI/components/ArViz/Coin/ElementVisualizer.h
@@ -29,6 +29,7 @@ namespace armarx::viz::coin
         SoUnits* units;
         SoTransform* transform;
         SoMaterial* material;
+        // TODO: Transform to flag system
         bool wasUpdated = true;
         bool visible = true;
     };
diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
index c76af807a..b20d6f504 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
@@ -34,6 +34,18 @@ namespace armarx::viz
         }
     };
 
+    static void selectionCallback(void* data, SoPath* path)
+    {
+        CoinVisualizer* this_ = static_cast<CoinVisualizer*>(data);
+        this_->onSelection(path);
+    }
+
+    static void deselectionCallback(void* data, SoPath* path)
+    {
+        CoinVisualizer* this_ = static_cast<CoinVisualizer*>(data);
+        this_->onDeselection(path);
+    }
+
     static const char* toString(CoinVisualizerState state)
     {
         switch (state)
@@ -86,7 +98,15 @@ namespace armarx::viz
         callback = newCallback_StorageInterface_pullUpdatesSince(callbackData,
                    &CoinVisualizerWrapper::onUpdateSuccess,
                    &CoinVisualizerWrapper::onUpdateFailure);
+
         root = new SoSeparator;
+
+        // The SoSelection node enable selection of nodes via mouse click / ray casting
+        selection = new SoSelection;
+        selection->addSelectionCallback(&selectionCallback, this);
+        selection->addDeselectionCallback(&deselectionCallback, this);
+
+        selection->addChild(root);
     }
 
     CoinVisualizer::~CoinVisualizer()
@@ -124,6 +144,24 @@ namespace armarx::viz
         state = CoinVisualizerState::STOPPED;
     }
 
+    static void removeElementsIfNotUpdated(CoinLayer* layer)
+    {
+        for (auto iter = layer->elements.begin(); iter != layer->elements.end();)
+        {
+            coin::ElementVisualization& elementVisu = *iter->visu;
+            if (elementVisu.wasUpdated)
+            {
+                elementVisu.wasUpdated = false;
+                ++iter;
+            }
+            else
+            {
+                layer->node->removeChild(elementVisu.separator);
+                iter = layer->elements.erase(iter);
+            }
+        }
+    }
+
     CoinVisualizer_ApplyTiming CoinVisualizer::apply(data::LayerUpdate const& update)
     {
         IceUtil::Time time_start = IceUtil::Time::now();
@@ -132,6 +170,31 @@ namespace armarx::viz
         timing.layerName = update.name;
 
         CoinLayerID layerID(update.component, update.name);
+        CoinLayer& layer = findOrAddLayer(layerID);
+
+        IceUtil::Time time_addLayer = IceUtil::Time::now();
+        timing.addLayer = time_addLayer - time_start;
+
+        addOrUpdateElements(&layer, update);
+
+        IceUtil::Time time_updates = IceUtil::Time::now();
+        timing.updateElements = time_updates - time_addLayer;
+
+        removeElementsIfNotUpdated(&layer);
+
+        IceUtil::Time time_remove = IceUtil::Time::now();
+        timing.removeElements = time_remove - time_updates;
+
+        emitLayerUpdated(layerID, layer);
+
+        IceUtil::Time time_end = IceUtil::Time::now();
+        timing.total = time_end - time_start;
+
+        return timing;
+    }
+
+    CoinLayer& CoinVisualizer::findOrAddLayer(CoinLayerID const& layerID)
+    {
         auto layerIt = layers.lowerBound(layerID);
 
         if (layerIt == layers.data.end() || layerIt->id != layerID)
@@ -144,13 +207,13 @@ namespace armarx::viz
             layerIt = layers.data.insert(layerIt, CoinLayer(layerID, coinNode));
         }
 
-        IceUtil::Time time_addLayer = IceUtil::Time::now();
-        timing.addLayer = time_addLayer - time_start;
+        return *layerIt;
+    }
 
-        // Add or update the elements in the update
-        CoinLayer& layer = *layerIt;
-        layer.elements.reserve(update.elements.size());
-        for (auto& updatedElementPtr : update.elements)
+    void CoinVisualizer::addOrUpdateElements(CoinLayer* layer, data::LayerUpdate const& update)
+    {
+        layer->elements.reserve(update.elements.size());
+        for (viz::data::ElementPtr const& updatedElementPtr : update.elements)
         {
             if (!updatedElementPtr)
             {
@@ -179,9 +242,9 @@ namespace armarx::viz
             }
             coin::ElementVisualizer* visualizer = elementVisualizers[visuIndex].get();
 
-            auto oldElementIter = layer.lowerBound(updatedElement.id);
+            auto oldElementIter = layer->lowerBound(updatedElement.id);
             CoinLayerElement* oldElement = nullptr;
-            if (oldElementIter != layer.elements.end() && oldElementIter->data->id == updatedElement.id)
+            if (oldElementIter != layer->elements.end() && oldElementIter->data->id == updatedElement.id)
             {
                 // Element already exists
                 CoinLayerElement* oldElement = &*oldElementIter;
@@ -199,14 +262,14 @@ namespace armarx::viz
                 else
                 {
                     // Types are different, so we delete the old element and create a new one
-                    layer.node->removeChild(oldElementVisu.separator);
+                    layer->node->removeChild(oldElementVisu.separator);
                 }
             }
 
             auto elementVisu = visualizer->create(updatedElement);
             if (elementVisu->separator)
             {
-                layer.node->addChild(elementVisu->separator);
+                layer->node->addChild(elementVisu->separator);
                 if (oldElement)
                 {
                     oldElement->data = updatedElementPtr;
@@ -215,7 +278,7 @@ namespace armarx::viz
                 else
                 {
                     // Need to add a new element
-                    layer.elements.insert(oldElementIter, CoinLayerElement{updatedElementPtr, std::move(elementVisu)});
+                    layer->elements.insert(oldElementIter, CoinLayerElement{updatedElementPtr, std::move(elementVisu)});
                 }
             }
             else
@@ -226,35 +289,6 @@ namespace armarx::viz
                                << "You need to register a visualizer for each type in ArViz/Coin/Visualizer.cpp";
             }
         }
-
-        IceUtil::Time time_updates = IceUtil::Time::now();
-        timing.updateElements = time_updates - time_addLayer;
-
-        // Remove the elements which were not contained in the update
-        for (auto iter = layer.elements.begin(); iter != layer.elements.end();)
-        {
-            coin::ElementVisualization& elementVisu = *iter->visu;
-            if (elementVisu.wasUpdated)
-            {
-                elementVisu.wasUpdated = false;
-                ++iter;
-            }
-            else
-            {
-                layer.node->removeChild(elementVisu.separator);
-                iter = layer.elements.erase(iter);
-            }
-        }
-
-        IceUtil::Time time_remove = IceUtil::Time::now();
-        timing.removeElements = time_remove - time_updates;
-
-        emitLayerUpdated(layerID, layer);
-
-        IceUtil::Time time_end = IceUtil::Time::now();
-        timing.total = time_end - time_start;
-
-        return timing;
     }
 
     void CoinVisualizer::update()
@@ -437,6 +471,57 @@ namespace armarx::viz
         }
     }
 
+    void CoinVisualizer::onSelection(SoPath* path)
+    {
+        SoNode* tailNode = path->getTail();
+
+        // TODO: Store CoinElementID into user data if interaction is enabled!
+        // Search for user data
+        void* userData = tailNode->getUserData();
+        if (userData == nullptr)
+        {
+            // In this case, we want to deselect all objects in the scene,
+            // because the selected object was not marked for selection interactions
+            selection->deselectAll();
+
+            // TODO: Remove logging, this is not an error, but expected
+            ARMARX_INFO << "Selected: " << tailNode->getClassTypeId().getName().getString()
+                        << "\nBut no user data attached!";
+        }
+        CoinElementID* id = static_cast<CoinElementID*>(userData);
+
+        CoinLayer* layer = layers.findLayer(id->layer);
+        if (layer == nullptr)
+        {
+            ARMARX_WARNING << "Selected an element whose layer does not exist: \n"
+                           << "Layer: " << id->layer.first << "/" << id->layer.second
+                           << ", element: " << id->element;
+            return;
+        }
+        CoinLayerElement* element = layer->findElement(id->element);
+        if (element == nullptr)
+        {
+            ARMARX_WARNING << "Selected an element which does not exist: \n"
+                           << "Layer: " << id->layer.first << "/" << id->layer.second
+                           << ", element: " << id->element;
+            return;
+        }
+
+        ARMARX_INFO << "Selected element: \n"
+                    << "Layer: " << id->layer.first << "/" << id->layer.second
+                    << ", element: " << id->element;
+        // TODO: Mark object as selected,
+        //       Store interaction
+        //       Batch send interaction at the end of the main loop?
+    }
+
+    void CoinVisualizer::onDeselection(SoPath* path)
+    {
+        // TODO
+        SoNode* tailNode = path->getTail();
+        ARMARX_INFO << "Deselected: " << tailNode->getClassTypeId().getName().getString();
+    }
+
     void CoinVisualizer::exportToVRML(const std::string& exportFilePath)
     {
 
diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.h b/source/RobotAPI/components/ArViz/Coin/Visualizer.h
index 195172e9c..6ae911753 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.h
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.h
@@ -4,7 +4,9 @@
 
 #include <RobotAPI/interface/ArViz/Component.h>
 
+#include <Inventor/nodes/SoSelection.h>
 #include <Inventor/nodes/SoSeparator.h>
+
 #include <IceUtil/Shared.h>
 
 #include <functional>
@@ -184,6 +186,12 @@ namespace armarx::viz
 
     struct CoinVisualizerWrapper;
 
+    struct CoinElementID
+    {
+        CoinLayerID layer;
+        std::string element;
+    };
+
     class CoinVisualizer
     {
     public:
@@ -219,10 +227,20 @@ namespace armarx::viz
 
         CoinVisualizer_UpdateTiming getTiming();
 
+        CoinLayer& findOrAddLayer(CoinLayerID const& layerID);
+        void addOrUpdateElements(CoinLayer* layer, data::LayerUpdate const& update);
+
         std::vector<CoinLayerID> getLayerIDs();
         void emitLayersChanged(std::vector<CoinLayerID> const& layerIDs);
         void emitLayerUpdated(CoinLayerID const& layerID, CoinLayer const& layer);
 
+        void onSelection(SoPath* path);
+        void onDeselection(SoPath* path);
+        // These are selectable element IDs and need to be persistent in memory.
+        // We store a raw pointer to these into the SoSeperator objects of Coin.
+        // Later, we can retrieve the element ID from this pointer, if it has been selected.
+        std::vector<std::unique_ptr<CoinElementID>> selectableElementIDs;
+
         void onUpdateSuccess(data::LayerUpdates const& updates);
         void onUpdateFailure(Ice::Exception const& ex);
         IceUtil::Handle<CoinVisualizerWrapper> callbackData;
@@ -236,6 +254,7 @@ namespace armarx::viz
         std::vector<std::type_index> elementVisualizersTypes;
         std::vector<std::unique_ptr<coin::ElementVisualizer>> elementVisualizers;
 
+        SoSelection* selection = nullptr;
         SoSeparator* root = nullptr;
 
         std::atomic<CoinVisualizerUpdateResult> updateResult{CoinVisualizerUpdateResult::SUCCESS};
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
index c3aad744b..5df52a995 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
@@ -31,8 +31,6 @@
 #include <QTimer>
 #include <QFileDialog>
 
-#define ENABLE_INTROSPECTION 1
-
 
 namespace armarx
 {
@@ -98,7 +96,6 @@ namespace armarx
 
         connect(widget.layerTree, &QTreeWidget::currentItemChanged, this, &This::updateSelectedLayer);
 
-#if ENABLE_INTROSPECTION
         connect(widget.layerInfoTreeGroupBox, &QGroupBox::toggled, &layerInfoTree, &LayerInfoTree::setEnabled);
         connect(widget.defaultShowLimitSpinBox, qOverload<int>(&QSpinBox::valueChanged),
                 &layerInfoTree, &LayerInfoTree::setMaxElementCountDefault);
@@ -108,8 +105,6 @@ namespace armarx
         layerInfoTree.setEnabled(widget.layerInfoTreeGroupBox->isChecked());
         layerInfoTree.registerVisualizerCallbacks(visualizer);
 
-#endif
-
 
         // We need a callback from the visualizer, when the layers have changed
         // So we can update the tree accordingly
@@ -321,7 +316,6 @@ namespace armarx
 
     void ArVizWidgetController::updateSelectedLayer(QTreeWidgetItem* current, QTreeWidgetItem* previous)
     {
-#if ENABLE_INTROSPECTION
         (void) previous;
 
         if (!current->parent())
@@ -342,8 +336,6 @@ namespace armarx
         {
             layerInfoTree.setSelectedLayer(id, &visualizer.layers);
         }
-
-#endif
     }
 
     void ArVizWidgetController::onCollapseAll(bool)
@@ -1004,7 +996,7 @@ namespace armarx
 
     SoNode* ArVizWidgetController::getScene()
     {
-        return visualizer.root;
+        return visualizer.selection;
     }
 
     static const std::string CONFIG_KEY_STORAGE = "Storage";
-- 
GitLab