From 59b494a739ceb117f37eee31b5f99ef4e9fd8113 Mon Sep 17 00:00:00 2001
From: Fabian Paus <fabian.paus@kit.edu>
Date: Thu, 30 Dec 2021 15:47:21 +0100
Subject: [PATCH] ArViz: Collect interaction feedback in a buffer

Signed-off-by: Fabian Paus <fabian.paus@kit.edu>
---
 .../components/ArViz/Coin/Visualizer.cpp      | 79 +++++++++++++++----
 .../components/ArViz/Coin/Visualizer.h        |  1 +
 .../components/ArViz/Example/ArVizExample.cpp | 14 ++++
 source/RobotAPI/interface/ArViz/Elements.ice  |  3 +-
 4 files changed, 82 insertions(+), 15 deletions(-)

diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
index 76f82431e..2c5ab83a7 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp
@@ -246,6 +246,7 @@ namespace armarx::viz
                     // Has an interaction been added?
                     viz::data::InteractionDescription& oldInteraction = oldElement->data->interaction;
                     viz::data::InteractionDescription& newInteraction = updatedElementPtr->interaction;
+                    // TODO: Also handle the case, when an interaction is removed!
                     if (newInteraction.enableFlags != oldInteraction.enableFlags
                             || oldInteraction.contextMenuOptions != newInteraction.contextMenuOptions)
                     {
@@ -360,6 +361,9 @@ namespace armarx::viz
                         return entry.get() == userData;
                     });
                     elementInteractions.erase(removedInteraction);
+
+                    elementVisu.separator->setUserData(nullptr);
+                    elementVisu.separator->setName("");
                 }
                 layer->node->removeChild(elementVisu.separator);
                 iter = layer->elements.erase(iter);
@@ -381,8 +385,8 @@ namespace armarx::viz
                 storage = stateStorage;
                 root->removeAllChildren();
                 layers.data.clear();
-                pulledUpdates.revision = 0;
-                pulledUpdates.updates.clear();
+                edUpdates.revision = 0;
+                edUpdates.updates.clear();
                 updateResult = CoinVisualizerUpdateResult::SUCCESS;
                 state = CoinVisualizerState::RUNNING;
             }
@@ -410,12 +414,17 @@ namespace armarx::viz
                 IceUtil::Time time_start = IceUtil::Time::now();
                 CoinVisualizer_UpdateTiming timing;
 
-                // We should restart the pull for updates so it can run in parallel
                 data::LayerUpdates currentUpdates = pulledUpdates;
                 updateResult = CoinVisualizerUpdateResult::WAITING;
                 timing.waitStart = time_start;
                 storage->begin_pullUpdatesSince(currentUpdates.revision, callback);
 
+                // TODO: Also send the interaction feedback to the storage
+                //       Would be best to do it in a single network call
+
+                // Clear interaction feedback buffer after it has been sent
+                interactionFeedbackBuffer.clear();
+
                 auto layerIDsBefore = getLayerIDs();
 
                 IceUtil::Time time_pull = IceUtil::Time::now();
@@ -547,7 +556,7 @@ namespace armarx::viz
         }
     }
 
-    void CoinVisualizer::onSelection(SoPath* path)
+    static ElementInteractionData* findInteractionDataOnPath(SoPath* path)
     {
         // Search for user data in the path
         // We stored ElementInteractionData into the user data of the parent SoSeparator
@@ -567,17 +576,23 @@ namespace armarx::viz
             }
         }
 
-        if (userData == nullptr)
+        return static_cast<ElementInteractionData*>(userData);
+    }
+
+    void CoinVisualizer::onSelection(SoPath* path)
+    {
+        ElementInteractionData* id = findInteractionDataOnPath(path);
+        if (id == 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 something. But no user data attached!";
+            // Removed logging, since this is not an error, but expected
+            // This happens, when the user selects an object that was not marked as selectable
+            // ARMARX_INFO << "Selected something. But no user data attached.";
             return;
         }
-        ElementInteractionData* id = static_cast<ElementInteractionData*>(userData);
 
         CoinLayer* layer = layers.findLayer(id->layer);
         if (layer == nullptr)
@@ -599,16 +614,52 @@ namespace armarx::viz
         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?
+
+        viz::data::InteractionFeedback& feedback = interactionFeedbackBuffer.emplace_back();
+        feedback.type = viz::data::InteractionFeedbackType::SELECT;
+        feedback.component = id->layer.first;
+        feedback.layer = id->layer.second;
+        feedback.element = id->element;
+        feedback.revision = pulledUpdates.revision;
     }
 
     void CoinVisualizer::onDeselection(SoPath* path)
     {
-        // TODO
-        SoNode* tailNode = path->getTail();
-        ARMARX_INFO << "Deselected: " << tailNode->getClassTypeId().getName().getString();
+        ElementInteractionData* id = findInteractionDataOnPath(path);
+        if (id == nullptr)
+        {
+            // An object was deselected that does not have any interactions enabled
+            return;
+        }
+
+        CoinLayer* layer = layers.findLayer(id->layer);
+        if (layer == nullptr)
+        {
+            ARMARX_WARNING << "Deselected 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 << "Deselected an element which does not exist: \n"
+                           << "Layer: " << id->layer.first << "/" << id->layer.second
+                           << ", element: " << id->element;
+            return;
+        }
+
+        ARMARX_INFO << "Deselected element: \n"
+                    << "Layer: " << id->layer.first << "/" << id->layer.second
+                    << ", element: " << id->element;
+
+
+        viz::data::InteractionFeedback& feedback = interactionFeedbackBuffer.emplace_back();
+        feedback.type = viz::data::InteractionFeedbackType::DESELECT;
+        feedback.component = id->layer.first;
+        feedback.layer = id->layer.second;
+        feedback.element = id->element;
+        feedback.revision = pulledUpdates.revision;
     }
 
     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 cbb5dca4e..0581a3869 100644
--- a/source/RobotAPI/components/ArViz/Coin/Visualizer.h
+++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.h
@@ -242,6 +242,7 @@ namespace armarx::viz
         // 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<ElementInteractionData>> elementInteractions;
+        std::vector<viz::data::InteractionFeedback> interactionFeedbackBuffer;
 
         void onUpdateSuccess(data::LayerUpdates const& updates);
         void onUpdateFailure(Ice::Exception const& ex);
diff --git a/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp b/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp
index c61c26054..2cef2e309 100644
--- a/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp
+++ b/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp
@@ -337,6 +337,7 @@ namespace armarx
         viz::PointCloud pc = viz::PointCloud("points")
                              .position(Eigen::Vector3f(2000.0f, 0.0f, 400.0f))
                              .transparency(0.0f);
+        pc.enable(viz::interaction().selection());
 
         viz::ColoredPoint p;
         p.color = viz::Color::fromRGBA(255, 255, 0, 255);
@@ -514,6 +515,19 @@ namespace armarx
                    .contextMenu({"First Option", "Second Option", "Third Option"}));
 
         layer.add(box);
+
+        viz::Cylinder cyl = viz::Cylinder("cylinder")
+                       .position(Eigen::Vector3f(1000.0f, 0.0f, 2000.0f))
+                       .direction(Eigen::Vector3f::UnitZ())
+                       .height(200.0f)
+                       .radius(50.0f)
+                       .color(viz::Color::fromRGBA(255, 165, 0));
+        // Enable some interaction possibilities
+        cyl.enable(viz::interaction()
+                   .selection()
+                   .contextMenu({"Cyl Option 1", "Cyl Option 2"}));
+
+        layer.add(cyl);
     }
 
 
diff --git a/source/RobotAPI/interface/ArViz/Elements.ice b/source/RobotAPI/interface/ArViz/Elements.ice
index 72bca2162..8b7aa4db0 100644
--- a/source/RobotAPI/interface/ArViz/Elements.ice
+++ b/source/RobotAPI/interface/ArViz/Elements.ice
@@ -55,7 +55,7 @@ module data
     {
         const int NONE = 0;
 
-        const int SELCECT  = 1;
+        const int SELECT  = 1;
         const int DESELECT = 2;
 
         const int CONTEXT_MENU_OPEN   = 3;
@@ -85,6 +85,7 @@ module data
         int type = 0;
 
         // The element with which the interaction took place
+        string component;
         string layer;
         string element;
         // The revision in which the interaction took place
-- 
GitLab