From 3c885a04573966b030bf66479945b658e6994ea3 Mon Sep 17 00:00:00 2001 From: Franco Cipollone <53065142+francocipollone@users.noreply.github.com> Date: Thu, 29 Apr 2021 18:14:15 -0300 Subject: [PATCH] MaliputViewerPlugin: Provides ray-cast support (#388) --- .../visualizer/layout2_maliput_viewer.config | 5 +- .../visualizer/maliput_viewer_plugin.cc | 77 ++++++++++++++++++- .../visualizer/maliput_viewer_plugin.hh | 47 ++++++++++- 3 files changed, 121 insertions(+), 8 deletions(-) diff --git a/delphyne_gui/visualizer/layout2_maliput_viewer.config b/delphyne_gui/visualizer/layout2_maliput_viewer.config index 51f47ddb..7cb7e895 100644 --- a/delphyne_gui/visualizer/layout2_maliput_viewer.config +++ b/delphyne_gui/visualizer/layout2_maliput_viewer.config @@ -79,6 +79,7 @@ + Main3DScene false false false @@ -104,4 +105,6 @@ scene - + + Main3DScene + diff --git a/delphyne_gui/visualizer/maliput_viewer_plugin.cc b/delphyne_gui/visualizer/maliput_viewer_plugin.cc index abd4cc7b..53b99e55 100644 --- a/delphyne_gui/visualizer/maliput_viewer_plugin.cc +++ b/delphyne_gui/visualizer/maliput_viewer_plugin.cc @@ -1,8 +1,10 @@ // Copyright 2021 Toyota Research Institute - #include "maliput_viewer_plugin.hh" +#include + #include +#include #include #include #include @@ -125,7 +127,7 @@ void MaliputViewerPlugin::timerEvent(QTimerEvent* _event) { return; } timer.stop(); - ConfigurateScene(); + Initialize(); RenderMeshes(); } @@ -301,6 +303,10 @@ void MaliputViewerPlugin::LoadConfig(const tinyxml2::XMLElement* _pluginElem) { ignerr << "Error reading plugin XML element " << std::endl; } + if (auto elem = _pluginElem->FirstChildElement("main_scene_plugin_title")) { + mainScene3dPluginTitle = elem->GetText(); + } + // Get the render engine. // Note: we don't support other engines than Ogre. auto engine = ignition::rendering::engine(kEngineName); @@ -316,16 +322,22 @@ void MaliputViewerPlugin::LoadConfig(const tinyxml2::XMLElement* _pluginElem) { timer.start(kTimerPeriodInMs, this); return; } - ConfigurateScene(); + Initialize(); RenderMeshes(); } -void MaliputViewerPlugin::ConfigurateScene() { +void MaliputViewerPlugin::Initialize() { + rayQuery = scene->CreateRayQuery(); rootVisual = scene->RootVisual(); if (!rootVisual) { ignerr << "Failed to find the root visual" << std::endl; return; } + camera = std::dynamic_pointer_cast(rootVisual->ChildByIndex(0)); + if (!camera) { + ignerr << "Failed to find the camera" << std::endl; + return; + } // Lights. const double lightRed = 0.88; const double lightGreen = 0.88; @@ -340,6 +352,63 @@ void MaliputViewerPlugin::ConfigurateScene() { directionalLight->SetDiffuseColor(lightRed, lightGreen, lightBlue); directionalLight->SetSpecularColor(lightRed, lightGreen, lightBlue); rootVisual->AddChild(directionalLight); + + // Install event filter to get mouse event from the main scene. + const ignition::gui::Plugin* scene3D = FilterPluginsByTitle(mainScene3dPluginTitle); + if (!scene3D) { + const std::string msg{"Scene3D plugin titled '" + mainScene3dPluginTitle + "' wasn't found"}; + ignerr << msg << std::endl; + MALIPUT_THROW_MESSAGE(msg); + } + auto renderWindowItem = scene3D->PluginItem()->findChild(); + if (!renderWindowItem) { + const std::string msg{"Scene3D's renderWindowItem child isn't found"}; + ignerr << msg << std::endl; + MALIPUT_THROW_MESSAGE(msg); + } + renderWindowItem->installEventFilter(this); +} + +ignition::gui::Plugin* MaliputViewerPlugin::FilterPluginsByTitle(const std::string& _pluginTitle) { + QList plugins = parent()->findChildren(); + auto plugin = std::find_if(std::begin(plugins), std::end(plugins), [&_pluginTitle](ignition::gui::Plugin* _plugin) { + return _plugin->Title() == _pluginTitle; + }); + return plugin == plugins.end() ? nullptr : *plugin; +} + +bool MaliputViewerPlugin::eventFilter(QObject* _obj, QEvent* _event) { + if (_event->type() == QEvent::Type::MouseButtonPress) { + const QMouseEvent* mouseEvent = static_cast(_event); + if (mouseEvent && mouseEvent->button() == Qt::LeftButton) { + MouseClickHandler(mouseEvent); + } + } + // Standard event processing + return QObject::eventFilter(_obj, _event); +} + +void MaliputViewerPlugin::MouseClickHandler(const QMouseEvent* _mouseEvent) { + const auto rayQueryResult = ScreenToScene(_mouseEvent->x(), _mouseEvent->y()); + if (rayQueryResult.distance >= 0) { + const maliput::api::Lane* lane = model->GetLaneFromWorldPosition(rayQueryResult.point); + if (lane) { + ignmsg << "Clicked lane ID: " << lane->id().string() << std::endl; + } + } +} + +ignition::rendering::RayQueryResult MaliputViewerPlugin::ScreenToScene(int _screenX, int _screenY) const { + // Normalize point on the image + const double width = camera->ImageWidth(); + const double height = camera->ImageHeight(); + + const double nx = 2.0 * _screenX / width - 1.0; + const double ny = 1.0 - 2.0 * _screenY / height; + + // Make a ray query + rayQuery->SetFromCamera(camera, {nx, ny}); + return rayQuery->ClosestPoint(); } } // namespace gui diff --git a/delphyne_gui/visualizer/maliput_viewer_plugin.hh b/delphyne_gui/visualizer/maliput_viewer_plugin.hh index 8a0b4fe3..cbd4f4f4 100644 --- a/delphyne_gui/visualizer/maliput_viewer_plugin.hh +++ b/delphyne_gui/visualizer/maliput_viewer_plugin.hh @@ -6,7 +6,9 @@ #include +#include #include +#include #include #include @@ -17,6 +19,11 @@ namespace gui { /// \brief Loads a road geometry out of a xodr file or a yaml file. /// Meshes are created and displayed in the scene. +/// +/// ## Configuration +/// +/// * \ : Title of the Scene3D plugin instance that manages the main scene. +/// Defaults to '3D Scene'. class MaliputViewerPlugin : public ignition::gui::Plugin { Q_OBJECT @@ -52,10 +59,15 @@ class MaliputViewerPlugin : public ignition::gui::Plugin { void LabelCheckboxesChanged(); protected: - /// @brief Timer event callback which handles the logic to load the meshes when + /// \brief Timer event callback which handles the logic to load the meshes when /// the scene is not ready yet. void timerEvent(QTimerEvent* _event) override; + /// \brief Filters QMouseEvents from a Scene3D plugin whose title matches with . + /// \details To make this method be called by Qt Event System, install the event filter in target object. + /// \see QObject::installEventFilter() method. + bool eventFilter(QObject* _obj, QEvent* _event) override; + protected slots: /// \brief Clears the visualizer, loads a RoadNetwork and update the GUI with meshes and labels. /// \param[in] _mapFile The path to the map file to load and visualize. @@ -82,6 +94,9 @@ class MaliputViewerPlugin : public ignition::gui::Plugin { /// @brief The scene name. static constexpr char const* kSceneName = "scene"; + /// @brief The Scene3D instance holding the main scene. + static constexpr char const* kMainScene3dPlugin = "Main3DScene"; + /// @brief The rendering engine name. static constexpr char const* kEngineName = "ogre"; @@ -116,8 +131,25 @@ class MaliputViewerPlugin : public ignition::gui::Plugin { /// \brief Clears all the references to text labels, meshes and the scene. void Clear(); - /// \brief Configurate scene. - void ConfigurateScene(); + /// \brief Configurate scene and install event filter for filtering QMouseEvents. + /// \details To install the event filter the Scene3D plugin hosting the scene + /// is expected to be titled as #kMainScene3dPlugin. + void Initialize(); + + /// \brief Handles the left click mouse event. + /// @param[in] _mouseEvent QMouseEvent pointer. + void MouseClickHandler(const QMouseEvent* _mouseEvent); + + /// \brief Performs a raycast on the screen. + /// \param[in] screenX X screen's coordinate. + /// \param[in] screenY Y screen's coordinate. + /// \return A ignition::rendering::RayQueryResult. + ignition::rendering::RayQueryResult ScreenToScene(int _screenX, int _screenY) const; + + /// \brief Filters by title all the children of the parent plugin. + /// \param _pluginTitle Title of the ignition::gui::plugin. + /// \return A pointer to the plugin if found, nullptr otherwise. + ignition::gui::Plugin* FilterPluginsByTitle(const std::string& _pluginTitle); /// \brief Holds the map file path. std::string mapFile{""}; @@ -131,6 +163,9 @@ class MaliputViewerPlugin : public ignition::gui::Plugin { /// \brief Holds the phase ring book file path. std::string phaseRingBookFile{""}; + /// \brief Holds the title of the main Scene3D plugin. + std::string mainScene3dPluginTitle{"3D Scene"}; + /// @brief Triggers an event every `kTimerPeriodInMs` to try to get the scene. QBasicTimer timer; @@ -146,6 +181,12 @@ class MaliputViewerPlugin : public ignition::gui::Plugin { /// \brief Holds a pointer to the scene. ignition::rendering::ScenePtr scene{nullptr}; + /// \brief Holds a pointer to a ray query. + ignition::rendering::RayQueryPtr rayQuery{nullptr}; + + /// \brief Holds a pointer to the camera. + ignition::rendering::CameraPtr camera{}; + /// \brief Model that holds the meshes and the visualization status. std::unique_ptr model{}; };