From a0f24ce5d4b2c162066dd703f48c453fe28ae078 Mon Sep 17 00:00:00 2001 From: Fabian Paus <fabian.paus@kit.edu> Date: Mon, 10 Jan 2022 14:51:38 +0100 Subject: [PATCH] ArViz: Allow translation only along specified axes (first working version) --- .../RobotAPI/components/ArViz/Client/Client.h | 34 +--- .../components/ArViz/Coin/Visualizer.cpp | 177 ++++++++++++------ .../components/ArViz/Coin/Visualizer.h | 3 +- .../components/ArViz/Example/ArVizExample.cpp | 30 ++- source/RobotAPI/interface/ArViz/Elements.ice | 21 +-- 5 files changed, 153 insertions(+), 112 deletions(-) diff --git a/source/RobotAPI/components/ArViz/Client/Client.h b/source/RobotAPI/components/ArViz/Client/Client.h index 5500e7747..12fc06825 100644 --- a/source/RobotAPI/components/ArViz/Client/Client.h +++ b/source/RobotAPI/components/ArViz/Client/Client.h @@ -146,31 +146,6 @@ namespace viz } } - bool isTranslation() const - { - return data_.type & data::InteractionFeedbackType::TRANSLATION_FLAG; - } - - bool isRotation() const - { - return data_.type & data::InteractionFeedbackType::ROTATION_FLAG; - } - - bool isAxisX() const - { - return data_.type & data::InteractionFeedbackType::AXIS_X_FLAG; - } - - bool isAxisY() const - { - return data_.type & data::InteractionFeedbackType::AXIS_Y_FLAG; - } - - bool isAxisZ() const - { - return data_.type & data::InteractionFeedbackType::AXIS_Z_FLAG; - } - bool isTransformBegin() const { return data_.type & data::InteractionFeedbackType::TRANSFORM_BEGIN_FLAG; @@ -206,14 +181,9 @@ namespace viz return data_.chosenContextMenuEntry; } - Eigen::Matrix4f originalPose() const - { - return toEigen(data_.originalPose); - } - - Eigen::Matrix4f chosenPose() const + Eigen::Matrix4f transformation() const { - return toEigen(data_.chosenPose); + return toEigen(data_.transformation); } data::InteractionFeedback data_; diff --git a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp index a2fdeadfa..3af96a367 100644 --- a/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp +++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.cpp @@ -4,11 +4,12 @@ #include <ArmarXCore/core/logging/Logging.h> #include <ArmarXCore/util/CPPUtility/GetTypeString.h> -#include <Inventor/nodes/SoMaterial.h> -#include <Inventor/nodes/SoUnits.h> #include <Inventor/SoPath.h> +#include <Eigen/Core> +#include <Eigen/Geometry> + namespace armarx::viz { namespace coin @@ -53,19 +54,19 @@ namespace coin static void startManipulationCallback(void* data, SoDragger* dragger) { CoinVisualizer* this_ = static_cast<CoinVisualizer*>(data); - this_->onManipulation(data::InteractionFeedbackType::TRANSFORM_BEGIN_FLAG); + this_->onManipulation(dragger, data::InteractionFeedbackType::TRANSFORM_BEGIN_FLAG); } static void duringManipulationCallback(void* data, SoDragger* dragger) { CoinVisualizer* this_ = static_cast<CoinVisualizer*>(data); - this_->onManipulation(data::InteractionFeedbackType::TRANSFORM_DURING_FLAG); + this_->onManipulation(dragger, data::InteractionFeedbackType::TRANSFORM_DURING_FLAG); } static void finishManipulationCallback(void* data, SoDragger* dragger) { CoinVisualizer* this_ = static_cast<CoinVisualizer*>(data); - this_->onManipulation(data::InteractionFeedbackType::TRANSFORM_END_FLAG); + this_->onManipulation(dragger, data::InteractionFeedbackType::TRANSFORM_END_FLAG); } static const char* toString(CoinVisualizerState state) @@ -336,6 +337,7 @@ namespace coin foundInteraction->element = elementID; foundInteraction->interaction = interactionDesc; + foundInteraction->visu = visu; // Add user data to Coin node visu->separator->setUserData(foundInteraction); @@ -575,27 +577,10 @@ namespace coin return; } - ElementInteractionData const* id = elementInteractions[index].get(); - - 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; - } + ElementInteractionData* id = elementInteractions[index].get(); selection->deselectAll(); - selection->select(element->visu->separator); + selection->select(id->visu->separator); } static ElementInteractionData* findInteractionDataOnPath(SoPath* path) @@ -654,24 +639,6 @@ namespace coin // Does the element support transform interactions? if (enableFlags & ANY_TRANSFORM) { - // TODO: Look up the element and add it to the manipulator group - 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; - } - manipulatorGroup->removeAllChildren(); // We need to create the manipulator everytime to achieve the desired effect // If we reuse an instance, the manipulator gets stuck to old objects... @@ -684,7 +651,7 @@ namespace coin manipulatorGroup->addChild(manipulator); // We add the same visualization node again - manipulatorGroup->addChild(element->visu->separator); + manipulatorGroup->addChild(id->visu->separator); manipulator->unsquishKnobs(); } @@ -703,7 +670,29 @@ namespace coin feedback.revision = pulledUpdates.revision; } - void CoinVisualizer::onManipulation(int eventType) + Eigen::Matrix4f toEigen(SbMat const& mat) + { + Eigen::Matrix4f result; + for (int y = 0; y < 4; ++y) + { + for (int x = 0; x < 4; ++x) + { + result(x, y) = mat[y][x]; + } + } + + return result; + } + + Eigen::Affine3f toEigen(SbRotation const& rot, SbVec3f const& transl) + { + Eigen::Affine3f result; + result.linear() = Eigen::Quaternion(rot[3], rot[0], rot[1], rot[2]).toRotationMatrix(); + result.translation() = Eigen::Vector3f(transl[0], transl[1], transl[2]); + return result; + } + + void CoinVisualizer::onManipulation(SoDragger* dragger, int eventType) { if (state != CoinVisualizerState::RUNNING) { @@ -715,30 +704,94 @@ namespace coin return; } - std::string elementID = selectedElement->layer.first + "/" - + selectedElement->layer.second + "/" - + selectedElement->element; - - SbVec3f translation = manipulator->translation.getValue(); - SbRotation rotation = manipulator->rotation.getValue(); - if (eventType == data::InteractionFeedbackType::TRANSFORM_BEGIN_FLAG) + // If there is an entry in the feedback buffer already, update it + // This way, we prevent multiple events being sent to the client + viz::data::InteractionFeedback* newFeedback = nullptr; + for (viz::data::InteractionFeedback& feedback : interactionFeedbackBuffer) { - ARMARX_INFO << elementID << ": Begin manipulation \n" - << "Translation: " << translation.toString().getString() - << "\nRotation: " << rotation.toString().getString(); + if ((feedback.type & 0x7) == data::InteractionFeedbackType::TRANSFORM + && feedback.component == selectedElement->layer.first + && feedback.layer == selectedElement->layer.second + && feedback.element == selectedElement->element) + { + // This is a transform interaction concerning the same element + newFeedback = &feedback; + } } - else if (eventType == data::InteractionFeedbackType::TRANSFORM_DURING_FLAG) + if (newFeedback == nullptr) { - ARMARX_INFO << elementID << ": During manipulation \n" - << "Translation: " << translation.toString().getString() - << "\nRotation: " << rotation.toString().getString(); + // Create a new interaction + newFeedback = &interactionFeedbackBuffer.emplace_back(); + newFeedback->component = selectedElement->layer.first; + newFeedback->layer = selectedElement->layer.second; + newFeedback->element = selectedElement->element; } - else if (eventType == data::InteractionFeedbackType::TRANSFORM_END_FLAG) + + // TODO: Add more flags about translation/rotation, axes + // Can we get this information from the dragger? + newFeedback->type = data::InteractionFeedbackType::TRANSFORM | eventType; + newFeedback->revision = pulledUpdates.revision; + + + SbVec3f t_m = manipulator->translation.getValue(); + SbRotation r_m = manipulator->rotation.getValue(); + SbVec3f s_m = manipulator->scaleFactor.getValue(); + + // TODO: Compute the same math but using Coin math types, so we can get rid of the Eigen/ include! + + Eigen::Affine3f T_G_X = toEigen(r_m, t_m); + Eigen::Affine3f T_G_O; + + // Lookup the element + // Now, we can get the transformation of the element + SbVec3f t_O = selectedElement->visu->transform->translation.getValue(); + SbRotation R_O = selectedElement->visu->transform->rotation.getValue(); + + T_G_O = toEigen(R_O, t_O); + + // Let us calculate perceived translation and see whether it meets our expectation + Eigen::Matrix3f R_G_X = T_G_X.linear(); + Eigen::Vector3f t_G_O = 0.001f * T_G_O.translation(); + Eigen::Vector3f t_G_X = T_G_X.translation(); + + Eigen::DiagonalMatrix<float, 3> scaleMatrix(s_m[0], s_m[1], s_m[2]); + + // This value stays constant during a pure rotation! + // It is zero when the manipulation first starts + // So we can reduce the perceived motion here (hopefully) + Eigen::Vector3f deltaT = (R_G_X * scaleMatrix - Eigen::Matrix3f::Identity()) * t_G_O + t_G_X; + ARMARX_INFO << "deltaT: " << deltaT.transpose(); + + int enableFlags = selectedElement->interaction.enableFlags; { - ARMARX_INFO << elementID << ": End manipulation \n" - << "Translation: " << translation.toString().getString() - << "\nRotation: " << rotation.toString().getString(); + // Prevent translation along disabled axes + if ((enableFlags & data::InteractionEnableFlags::TRANSLATION_X) == 0) + { + deltaT.x() = 0.0f; + } + if ((enableFlags & data::InteractionEnableFlags::TRANSLATION_Y) == 0) + { + deltaT.y() = 0.0f; + } + if ((enableFlags & data::InteractionEnableFlags::TRANSLATION_Z) == 0) + { + deltaT.z() = 0.0f; + } + + Eigen::Vector3f t_G_X_projected = deltaT - (R_G_X * scaleMatrix - Eigen::Matrix3f::Identity()) * t_G_O; + manipulator->translation.setValue(t_G_X_projected.x(), t_G_X_projected.y(), t_G_X_projected.z()); + //manipulator->rotation.setValue(newRotation.x(), newRotation.y(), newRotation.z(), newRotation.w()); } + // TODO: Prevent rotation along disabled axes + + data::GlobalPose& transformation = newFeedback->transformation; + transformation.x = t_m[0]; + transformation.y = t_m[1]; + transformation.z = t_m[2]; + transformation.qw = r_m[3]; + transformation.qx = r_m[0]; + transformation.qy = r_m[1]; + transformation.qz = r_m[2]; } 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 37404e6ca..573e86708 100644 --- a/source/RobotAPI/components/ArViz/Coin/Visualizer.h +++ b/source/RobotAPI/components/ArViz/Coin/Visualizer.h @@ -192,6 +192,7 @@ namespace armarx::viz CoinLayerID layer; std::string element; viz::data::InteractionDescription interaction; + coin::ElementVisualization* visu = nullptr; }; class CoinVisualizer @@ -242,7 +243,7 @@ namespace armarx::viz void selectElement(int index); void onSelectEvent(SoPath* path, int eventType); - void onManipulation(int eventType); + void onManipulation(SoDragger* dragger, int eventType); // 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. diff --git a/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp b/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp index 7ac979f03..fd84d32cb 100644 --- a/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp +++ b/source/RobotAPI/components/ArViz/Example/ArVizExample.cpp @@ -512,7 +512,8 @@ namespace armarx box.enable(viz::interaction() .selection() .contextMenu({"First Option", "Second Option", "Third Option"}) - .fullTransform()); + .rotation() + .translation(true, false, false)); layer.add(box); @@ -526,7 +527,8 @@ namespace armarx cyl.enable(viz::interaction() .selection() .contextMenu({"Cyl Option 1", "Cyl Option 2"}) - .fullTransform()); + .rotation() + .translation(false, true, false)); layer.add(cyl); } @@ -634,6 +636,30 @@ namespace armarx << "] Chosen context menu: " << interaction.chosenContextMenuEntry(); } + else if (interaction.type() == viz::InteractionFeedbackType::Transform) + { + std::string transformState; + if (interaction.isTransformBegin()) + { + transformState = "Begin"; + } + else if (interaction.isTransformDuring()) + { + transformState = "During"; + } + else if (interaction.isTransformEnd()) + { + transformState = "End"; + } + else + { + transformState = "<Unknwon>"; + } + ARMARX_INFO << "[" << interaction.layer() + << "/" << interaction.element() + << "] Transformation " << transformState + << ": \n" << interaction.transformation(); + } else { ARMARX_INFO << "[" << interaction.layer() diff --git a/source/RobotAPI/interface/ArViz/Elements.ice b/source/RobotAPI/interface/ArViz/Elements.ice index ca27a0e10..7558d379c 100644 --- a/source/RobotAPI/interface/ArViz/Elements.ice +++ b/source/RobotAPI/interface/ArViz/Elements.ice @@ -69,19 +69,10 @@ module data const int TRANSFORM = 4; - // Flag to indicate the kind of transformation - const int TRANSLATION_FLAG = 16; - const int ROTATION_FLAG = 32; - - // Flag to indicate the axis used for transformation - const int AXIS_X_FLAG = 64; - const int AXIS_Y_FLAG = 128; - const int AXIS_Z_FLAG = 256; - // Flag to indicate state of the transformation - const int TRANSFORM_BEGIN_FLAG = 512; - const int TRANSFORM_DURING_FLAG = 1024; - const int TRANSFORM_END_FLAG = 2048; + const int TRANSFORM_BEGIN_FLAG = 16; + const int TRANSFORM_DURING_FLAG = 32; + const int TRANSFORM_END_FLAG = 64; }; struct InteractionFeedback @@ -100,9 +91,9 @@ module data // Chosen context menu entry is only relevant for type == CONTEXT_MENU_CHOSEN int chosenContextMenuEntry = 0; - // Original and chosen poase are only relevant for type == TRANSFORM - GlobalPose originalPose; - GlobalPose chosenPose; + // Applied transformation is only relevant for type == TRANSFORM + GlobalPose transformation; + Vector3f scale; }; sequence<InteractionFeedback> InteractionFeedbackSeq; -- GitLab