Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MaliputViewerPlugin: Provides ray-cast support #388

Merged
merged 4 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions delphyne_gui/visualizer/layout2_maliput_viewer.config
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@

<plugin filename="Scene3D">
<ignition-gui>
<title>Main3DScene</title>
<property type="bool" key="showTitleBar">false</property>
<property type="bool" key="showCollapseButton">false</property>
<property type="bool" key="showDockButton">false</property>
Expand Down
65 changes: 62 additions & 3 deletions delphyne_gui/visualizer/maliput_viewer_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "maliput_viewer_plugin.hh"

#include <ignition/common/Console.hh>
#include <ignition/gui/Conversions.hh>
#include <ignition/plugin/Register.hh>
#include <ignition/rendering/RenderEngine.hh>
#include <ignition/rendering/RenderingIface.hh>
Expand Down Expand Up @@ -125,7 +126,7 @@ void MaliputViewerPlugin::timerEvent(QTimerEvent* _event) {
return;
}
timer.stop();
ConfigurateScene();
Initialize();
RenderMeshes();
}

Expand Down Expand Up @@ -316,16 +317,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<ignition::rendering::Camera>(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;
Expand All @@ -340,6 +347,58 @@ 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.
QList<Plugin*> plugins = parent()->findChildren<Plugin*>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we convert this block in a function that filters plugins by title?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Plugin* scene3D{nullptr};
for (auto& plugin : plugins) {
if (plugin->Title() == kMainScene3dPlugin) {
scene3D = plugin;
break;
}
}
if (!scene3D) {
ignerr << "Scene3D plugin titled '" << kMainScene3dPlugin << "' wasn't found. MouseEvents won't be filtered."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be a definitive error? I recommend returning and flagging and error or raising an exception.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

<< std::endl;
} else {
auto renderWindowItem = scene3D->PluginItem()->findChild<QQuickItem*>();
renderWindowItem->installEventFilter(this);
}
}

bool MaliputViewerPlugin::eventFilter(QObject* _obj, QEvent* _event) {
if (_event->type() == QEvent::Type::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(_event);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be const?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (mouseEvent && mouseEvent->button() == Qt::LeftButton) {
MouseClickHandler(mouseEvent);
}
}
// Standard event processing
return QObject::eventFilter(_obj, _event);
}

void MaliputViewerPlugin::MouseClickHandler(QMouseEvent* _mouseEvent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can _mouseEvent be const?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const auto rayQueryResult = ScreenToScene(_mouseEvent->x(), _mouseEvent->y());
if (rayQueryResult.distance > -1) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that distance being less than zero is an error (i.e. no collision)? Can we test for non-negative values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! My bad. Done

const maliput::api::Lane* lane = model->GetLaneFromWorldPosition(rayQueryResult.point);
if (lane) {
const std::string& lane_id = lane->id().string();
ignmsg << "Clicked lane ID: " << lane_id << std::endl;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you do: ignmsg << "Clicked lane ID: " << lane->id().string() << std::endl;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Done.

}
}
}

ignition::rendering::RayQueryResult MaliputViewerPlugin::ScreenToScene(int screenX, int screenY) const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you missed _ in variable names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// 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
Expand Down
35 changes: 32 additions & 3 deletions delphyne_gui/visualizer/maliput_viewer_plugin.hh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

#include <maliput/api/road_geometry.h>

#include <ignition/common/MouseEvent.hh>
#include <ignition/gui/Plugin.hh>
#include <ignition/rendering/RayQuery.hh>
#include <ignition/rendering/RenderTypes.hh>
#include <ignition/rendering/Scene.hh>

Expand Down Expand Up @@ -52,10 +54,16 @@ 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 Intercepts an @p _event delivered to other object @p _obj.
/// \details We are particularly interested in QMouseEvents happening within the main scene.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rephrase this to say:

Filters QMouseEvents from a Scene3D plugin whose title is set via <scene_plugin_title>.
To make this method be called by Qt Event System, install the event filter in target object
( \see QObject::installEventFilter() method).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/// This method is called automatically by the QT Event System iff the event filter was installed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: QT -> Qt

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/// in the 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.
Expand All @@ -82,6 +90,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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a plugin argument.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And change occurrences of kMainScene3dPlugin to refer the xml argument.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also update the plugin documentation to state what it is expected as configuration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Done.


/// @brief The rendering engine name.
static constexpr char const* kEngineName = "ogre";

Expand All @@ -104,6 +115,16 @@ class MaliputViewerPlugin : public ignition::gui::Plugin {
/// \brief Renders meshes for the road and the labels.
void RenderMeshes();

/// \brief Handles the left click mouse event.
/// @param[in] _mouseEvent QMouseEvent pointer.
void MouseClickHandler(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 Builds visuals for each mesh inside @p _maliputMeshes that is
/// enabled.
/// \param[in] _maliputMeshes A map of meshes to render.
Expand All @@ -116,8 +137,10 @@ 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 Holds the map file path.
std::string mapFile{""};
Expand Down Expand Up @@ -146,6 +169,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<MaliputViewerModel> model{};
};
Expand Down