diff --git a/examples/plugin/custom_component/README.md b/examples/plugin/custom_component/README.md index 5d53652d02..c752fb36d4 100644 --- a/examples/plugin/custom_component/README.md +++ b/examples/plugin/custom_component/README.md @@ -9,7 +9,7 @@ See `CustomComponentPlugin.hh` for more information. From the root of the `ign-gazebo` repository, do the following to build the example: ~~~ -cd ign-gazebo/examples/plugins/custom_component +cd examples/plugin/custom_component mkdir build cd build cmake .. @@ -23,7 +23,7 @@ This will generate the `CustomComponent` library under `build`. Add the library to the path: ~~~ -cd ign-gazebo/examples/plugins/custom_component +cd examples/plugin/custom_component export IGN_GAZEBO_SYSTEM_PLUGIN_PATH=`pwd`/build ~~~ diff --git a/examples/plugin/gui_system_plugin/README.md b/examples/plugin/gui_system_plugin/README.md index da2bec4278..83c9f1a26b 100644 --- a/examples/plugin/gui_system_plugin/README.md +++ b/examples/plugin/gui_system_plugin/README.md @@ -14,7 +14,7 @@ See `GuiSystemPluginPlugin.hh` for more information. From the root of the `ign-gazebo` repository, do the following to build the example: ~~~ -cd ign-gazebo/examples/plugins/gui_system_plugin +cd examples/plugin/gui_system_plugin mkdir build cd build cmake .. @@ -28,7 +28,7 @@ This will generate the `GuiSystemPlugin` library under `build`. Add the library to the path: ~~~ -cd ign-gazebo/examples/plugins/gui_system_plugin +cd examples/plugin/gui_system_plugin export IGN_GUI_PLUGIN_PATH=`pwd`/build ~~~ diff --git a/examples/plugin/rendering_plugins/CMakeLists.txt b/examples/plugin/rendering_plugins/CMakeLists.txt new file mode 100644 index 0000000000..3978b17d4d --- /dev/null +++ b/examples/plugin/rendering_plugins/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) + +if(POLICY CMP0100) + cmake_policy(SET CMP0100 NEW) +endif() + +project(RenderingPlugins) + +# Common to both plugins +find_package(ignition-rendering3 REQUIRED) + +# GUI plugin +set(GUI_PLUGIN RenderingGuiPlugin) + +set(CMAKE_AUTOMOC ON) + +find_package(ignition-gui3 REQUIRED) + +QT5_ADD_RESOURCES(resources_RCC ${GUI_PLUGIN}.qrc) + +add_library(${GUI_PLUGIN} SHARED + ${GUI_PLUGIN}.cc + ${resources_RCC} +) +target_link_libraries(${GUI_PLUGIN} + PRIVATE + ignition-gui3::ignition-gui3 + ignition-rendering3::ignition-rendering3 +) + +# Server plugin +set(SERVER_PLUGIN RenderingServerPlugin) + +find_package(ignition-plugin1 REQUIRED COMPONENTS register) +find_package(ignition-gazebo3 REQUIRED) + +add_library(${SERVER_PLUGIN} SHARED ${SERVER_PLUGIN}.cc) +set_property(TARGET ${SERVER_PLUGIN} PROPERTY CXX_STANDARD 17) +target_link_libraries(${SERVER_PLUGIN} + PRIVATE + ignition-plugin1::ignition-plugin1 + ignition-gazebo3::ignition-gazebo3 + ignition-rendering3::ignition-rendering3 +) diff --git a/examples/plugin/rendering_plugins/README.md b/examples/plugin/rendering_plugins/README.md new file mode 100644 index 0000000000..9ad374ca14 --- /dev/null +++ b/examples/plugin/rendering_plugins/README.md @@ -0,0 +1,38 @@ +# Rendering plugins + +Demo of 2 plugins that use Ignition Rendering, one for the server and one for the client. + +## Build + +From the root of the `ign-gazebo` repository, do the following to build the example: + +~~~ +cd examples/plugin/rendering_plugins +mkdir build +cd build +cmake .. +make +~~~ + +This will generate the `RenderingGuiPlugin` and `RenderingServerPlugin` libraries under `build`. + +## Run + +Add the libraries to the correct paths: + +~~~ +cd examples/plugin/rendering_plugins +export IGN_GUI_PLUGIN_PATH=`pwd`/build +export IGN_GAZEBO_SYSTEM_PLUGIN_PATH=`pwd`/build +~~~ + +Run the example world + +~~~ +cd examples/plugin/rendering_plugins +ign gazebo -v 4 -r rendering_plugins.sdf +~~~ + +The ambient light on the server scene, visible from the camera sensor, will change every 2 seconds. + +The ambient light on the client scene, visible from the GUI, will change every time the button is pressed. diff --git a/examples/plugin/rendering_plugins/RenderingGuiPlugin.cc b/examples/plugin/rendering_plugins/RenderingGuiPlugin.cc new file mode 100644 index 0000000000..e0c629f09b --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingGuiPlugin.cc @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include +//! [includeGuiEvents] +#include +//! [includeGuiEvents] +#include +#include +#include +#include +#include +#include + +#include "RenderingGuiPlugin.hh" + +///////////////////////////////////////////////// +void RenderingGuiPlugin::LoadConfig(const tinyxml2::XMLElement * /*_pluginElem*/) +{ + // This is necessary to receive the Render event on eventFilter +//! [connectToGuiEvent] + ignition::gui::App()->findChild< + ignition::gui::MainWindow *>()->installEventFilter(this); +//! [connectToGuiEvent] +} + +///////////////////////////////////////////////// +void RenderingGuiPlugin::RandomColor() +{ + this->dirty = true; +} + +///////////////////////////////////////////////// +//! [eventFilter] +bool RenderingGuiPlugin::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == ignition::gui::events::Render::kType) + { + // This event is called in the render thread, so it's safe to make + // rendering calls here + this->PerformRenderingOperations(); + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); +} +//! [eventFilter] + +///////////////////////////////////////////////// +//! [performRenderingOperations] +void RenderingGuiPlugin::PerformRenderingOperations() +{ + if (!this->dirty) + { + return; + } + + if (nullptr == this->scene) + { + this->FindScene(); + } + + if (nullptr == this->scene) + return; + + this->scene->SetAmbientLight({ + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + 1.0}); + + this->dirty = false; +} +//! [performRenderingOperations] + +///////////////////////////////////////////////// +void RenderingGuiPlugin::FindScene() +{ + auto loadedEngNames = ignition::rendering::loadedEngines(); + if (loadedEngNames.empty()) + { + igndbg << "No rendering engine is loaded yet" << std::endl; + return; + } + + // assume there is only one engine loaded + auto engineName = loadedEngNames[0]; + if (loadedEngNames.size() > 1) + { + igndbg << "More than one engine is available. " + << "Using engine [" << engineName << "]" << std::endl; + } + auto engine = ignition::rendering::engine(engineName); + if (!engine) + { + ignerr << "Internal error: failed to load engine [" << engineName + << "]. Grid plugin won't work." << std::endl; + return; + } + + if (engine->SceneCount() == 0) + { + igndbg << "No scene has been created yet" << std::endl; + return; + } + + // Get first scene + auto scenePtr = engine->SceneByIndex(0); + if (nullptr == scenePtr) + { + ignerr << "Internal error: scene is null." << std::endl; + return; + } + + if (engine->SceneCount() > 1) + { + igndbg << "More than one scene is available. " + << "Using scene [" << scene->Name() << "]" << std::endl; + } + + if (!scenePtr->IsInitialized() || nullptr == scenePtr->RootVisual()) + { + return; + } + + this->scene = scenePtr; +} + +// Register this plugin +IGNITION_ADD_PLUGIN(RenderingGuiPlugin, + ignition::gui::Plugin) diff --git a/examples/plugin/rendering_plugins/RenderingGuiPlugin.hh b/examples/plugin/rendering_plugins/RenderingGuiPlugin.hh new file mode 100644 index 0000000000..7958c38b07 --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingGuiPlugin.hh @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef RENDERING_GUI_PLUGIN_HH_ +#define RENDERING_GUI_PLUGIN_HH_ + +#include +#include +#include + +/// \brief Example of a GUI plugin that uses Ignition Rendering. +/// This plugin works with either Ignition GUI's Scene3D or Ignition Gazebo's +/// Scene3D. +class RenderingGuiPlugin : public ignition::gui::Plugin +{ + Q_OBJECT + + ///\brief Called once at startup. + public: void LoadConfig(const tinyxml2::XMLElement *) override; + + /// \brief Callback when user clicks button. + public slots: void RandomColor(); + + /// \brief Callback for all installed event filters. + /// \param[in] _obj Object that received the event + /// \param[in] _event Event + private: bool eventFilter(QObject *_obj, QEvent *_event) override; + + /// \brief All rendering operations must happen within this call + private: void PerformRenderingOperations(); + + /// \brief Encapsulates the logic to find the rendering scene through the + /// render engine singleton. + private: void FindScene(); + + /// \brief Marks when a new change has been requested. + private: bool dirty{false}; + + /// \brief Pointer to the rendering scene. + private: ignition::rendering::ScenePtr scene{nullptr}; +}; + +#endif diff --git a/examples/plugin/rendering_plugins/RenderingGuiPlugin.qml b/examples/plugin/rendering_plugins/RenderingGuiPlugin.qml new file mode 100644 index 0000000000..af4e0f4ca3 --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingGuiPlugin.qml @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +Rectangle { + color: "transparent" + anchors.fill: parent + Layout.minimumWidth: 250 + Layout.minimumHeight: 100 + Button { + text: qsTr("Random GUI color!") + onClicked: { + RenderingGuiPlugin.RandomColor(); + } + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } +} diff --git a/examples/plugin/rendering_plugins/RenderingGuiPlugin.qrc b/examples/plugin/rendering_plugins/RenderingGuiPlugin.qrc new file mode 100644 index 0000000000..ff99b777d8 --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingGuiPlugin.qrc @@ -0,0 +1,5 @@ + + + RenderingGuiPlugin.qml + + diff --git a/examples/plugin/rendering_plugins/RenderingServerPlugin.cc b/examples/plugin/rendering_plugins/RenderingServerPlugin.cc new file mode 100644 index 0000000000..2054975787 --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingServerPlugin.cc @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#include "RenderingServerPlugin.hh" + +//! [includeRenderingEvents] +#include +//! [includeRenderingEvents] +#include +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +////////////////////////////////////////////////// +void RenderingServerPlugin::Configure( + const ignition::gazebo::Entity &/*_entity*/, + const std::shared_ptr &/*_sdf*/, + ignition::gazebo::EntityComponentManager &/*_ecm*/, + ignition::gazebo::EventManager &_eventMgr) +{ +//! [connectToServerEvent] + this->connection = _eventMgr.Connect( + std::bind(&RenderingServerPlugin::PerformRenderingOperations, this)); +//! [connectToServerEvent] +} + +////////////////////////////////////////////////// +//! [performRenderingOperations] +void RenderingServerPlugin::PerformRenderingOperations() +{ + if (nullptr == this->scene) + { + this->FindScene(); + } + + if (nullptr == this->scene) + return; + + if (this->simTime - this->lastUpdate < 2s) + return; + + this->scene->SetAmbientLight({ + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + static_cast(ignition::math::Rand::DblUniform(0.0, 1.0)), + 1.0}); + + this->lastUpdate = this->simTime; +} +//! [performRenderingOperations] + +///////////////////////////////////////////////// +//! [findScene] +void RenderingServerPlugin::FindScene() +{ + auto loadedEngNames = ignition::rendering::loadedEngines(); + if (loadedEngNames.empty()) + { + igndbg << "No rendering engine is loaded yet" << std::endl; + return; + } + + // assume there is only one engine loaded + auto engineName = loadedEngNames[0]; + if (loadedEngNames.size() > 1) + { + igndbg << "More than one engine is available. " + << "Using engine [" << engineName << "]" << std::endl; + } + auto engine = ignition::rendering::engine(engineName); + if (!engine) + { + ignerr << "Internal error: failed to load engine [" << engineName + << "]. Grid plugin won't work." << std::endl; + return; + } + + if (engine->SceneCount() == 0) + { + igndbg << "No scene has been created yet" << std::endl; + return; + } + + // Get first scene + auto scenePtr = engine->SceneByIndex(0); + if (nullptr == scenePtr) + { + ignerr << "Internal error: scene is null." << std::endl; + return; + } + + if (engine->SceneCount() > 1) + { + igndbg << "More than one scene is available. " + << "Using scene [" << scene->Name() << "]" << std::endl; + } + + if (!scenePtr->IsInitialized() || nullptr == scenePtr->RootVisual()) + { + return; + } + + this->scene = scenePtr; +} +//! [findScene] + +////////////////////////////////////////////////// +void RenderingServerPlugin::PreUpdate(const ignition::gazebo::UpdateInfo &_info, + ignition::gazebo::EntityComponentManager &_ecm) +{ + this->simTime = _info.simTime; +} + +IGNITION_ADD_PLUGIN( + RenderingServerPlugin, + ignition::gazebo::System, + RenderingServerPlugin::ISystemConfigure, + RenderingServerPlugin::ISystemPreUpdate +) diff --git a/examples/plugin/rendering_plugins/RenderingServerPlugin.hh b/examples/plugin/rendering_plugins/RenderingServerPlugin.hh new file mode 100644 index 0000000000..9f9218f4c6 --- /dev/null +++ b/examples/plugin/rendering_plugins/RenderingServerPlugin.hh @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef RENDERING_SERVER_PLUGIN_HH_ +#define RENDERING_SERVER_PLUGIN_HH_ + +#include +#include + +/// \brief Server-side system that uses Ignition Rendering APIs. +/// It changes the ambient color every 2 simulation seconds. +class RenderingServerPlugin: + public ignition::gazebo::System, + public ignition::gazebo::ISystemConfigure, + public ignition::gazebo::ISystemPreUpdate +{ + /// \brief Called once at startup + /// \param[in] _entity Entity that the plugin is attached to, not used. + /// \param[in] _sdf Element with custom configuration, not used. + /// \param[in] _ecm Entity component manager + /// \param[in] _eventMgr Event manager + public: void Configure(const ignition::gazebo::Entity &_entity, + const std::shared_ptr &_sdf, + ignition::gazebo::EntityComponentManager &_ecm, + ignition::gazebo::EventManager &_eventMgr) override; + + /// \brief Called just before each simulation update. + /// \param[in] _info Contains information like sim time. + /// \param[in] _ecm Entity component manager + public: void PreUpdate( + const ignition::gazebo::UpdateInfo &_info, + ignition::gazebo::EntityComponentManager &_ecm) override; + + /// \brief All rendering operations must happen within this call + private: void PerformRenderingOperations(); + + /// \brief Encapsulates the logic to find the rendering scene through the + /// render engine singleton. + private: void FindScene(); + + /// \brief Connection to pre-render event callback + private: ignition::common::ConnectionPtr connection{nullptr}; + + /// \brief Pointer to rendering scene + private: ignition::rendering::ScenePtr scene{nullptr}; + + /// \brief Current simulation time. + private: std::chrono::steady_clock::duration simTime{0}; + + /// \brief Time when light was last updated + private: std::chrono::steady_clock::duration lastUpdate{0}; +}; + +#endif diff --git a/examples/plugin/rendering_plugins/rendering_plugins.sdf b/examples/plugin/rendering_plugins/rendering_plugins.sdf new file mode 100644 index 0000000000..8fbb572bda --- /dev/null +++ b/examples/plugin/rendering_plugins/rendering_plugins.sdf @@ -0,0 +1,191 @@ + + + + + ogre2 + + + + + + + + + + + + 3D View + false + docked + + + ogre2 + scene + 0.4 0.4 0.4 + 0.8 0.8 0.8 + -6 0 6 0 0.5 0 + + + + + + World control + false + false + 72 + 121 + 1 + + floating + + + + + + + true + true + true + + + + + + + World stats + false + false + 110 + 290 + 1 + + floating + + + + + + + true + true + true + true + + + + + Camera + floating + 350 + 315 + + camera + false + + + + + Rendering GUI Plugin + floating + 350 + 100 + 500 + + + + + + true + 0 0 10 0 0 0 + 0.8 0.8 0.8 1 + 0.2 0.2 0.2 1 + + 1000 + 0.9 + 0.01 + 0.001 + + -0.5 0.1 -0.9 + + + + true + + + + + + 20 20 0.1 + + + + + + + 20 20 0.1 + + + + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + + + + + + + 2.5 0 1.5 0 0.0 3.14 + + 0.05 0.05 0.05 0 0 0 + + 0.1 + + 0.000166667 + 0.000166667 + 0.000166667 + + + + + + 0.1 0.1 0.1 + + + + + + + 0.1 0.1 0.1 + + + + + + 1.047 + + 320 + 240 + + + 0.1 + 100 + + + 1 + 30 + true + camera + + + true + + + diff --git a/examples/standalone/joy_to_twist/README.md b/examples/standalone/joy_to_twist/README.md index fe7b7d8e85..893bdeed1d 100644 --- a/examples/standalone/joy_to_twist/README.md +++ b/examples/standalone/joy_to_twist/README.md @@ -11,7 +11,7 @@ messages according to user-defined configuration. From the root of the `ign-gazebo` repository, do the following to build the example: ~~~ -cd ign-gazebo/examples/standalone/joy_to_twist +cd examples/standalone/joy_to_twist mkdir build cd build cmake .. @@ -41,19 +41,19 @@ that can be controlled using a joystick. You can run it as follows: messages. See that standalone program's instructions to details on how to build it. Once it's built, you can run it as follows: - cd ign-gazebo/examples/standalone/joystick + cd examples/standalone/joystick ./joystick ../joystick.sdf 1. On another terminal, run the `joy_to_twist` executable as described above, which will convert joy messages to twist messages: - cd ign-gazebo/examples/standalone/joy_to_twist + cd examples/standalone/joy_to_twist ./joy_to_twist ../joy_to_twist.sdf 1. Finally, on a 3rd terminal, run `ign gazebo` with the vehicle that will consume the twist messages: - cd ign-gazebo/examples/worlds + cd examples/worlds ign gazebo -v 4 diff_drive.sdf 1. Now hold your joystick's A button (or equivalent) and move the directional diff --git a/examples/standalone/joystick/README.md b/examples/standalone/joystick/README.md index 1b965b40e8..9e05e2e172 100644 --- a/examples/standalone/joystick/README.md +++ b/examples/standalone/joystick/README.md @@ -11,7 +11,7 @@ The mapping of joystick buttons to fields in the message is the same as [this](h From the root of the `ign-gazebo` repository, do the following to build the example: ~~~ -cd ign-gazebo/examples/standalone/joystick +cd examples/standalone/joystick mkdir build cd build cmake .. @@ -49,20 +49,20 @@ that can be controlled using a joystick. You can run it as follows: 1. In a terminal, run the joystick executable as described above to publish joystick messages: - cd ign-gazebo/examples/standalone/joystick + cd examples/standalone/joystick ./joystick ../joystick.sdf 1. On another terminal, run the `joy_to_twist` executable to convert joy messages to twist messages. See that standalone program's instructions for details on how to build it. Once it's built, you can run it as follows: - cd ign-gazebo/examples/standalone/joy_to_twist + cd examples/standalone/joy_to_twist ./joy_to_twist ../joy_to_twist.sdf 1. Finally, on a 3rd terminal, run `ign gazebo` with the vehicle that will consume the twist messages: - cd ign-gazebo/examples/worlds + cd examples/worlds ign gazebo -v 4 diff_drive.sdf 1. Now hold your joystick's A button (or equivalent) and move the directional diff --git a/examples/standalone/keyboard/README.md b/examples/standalone/keyboard/README.md index c5e3a342ae..8912a81839 100644 --- a/examples/standalone/keyboard/README.md +++ b/examples/standalone/keyboard/README.md @@ -5,7 +5,7 @@ From the root of the `ign-gazebo` repository, do the following to build the example: ~~~ -cd ign-gazebo/examples/standalone/keyboard +cd examples/standalone/keyboard mkdir build cd build cmake .. @@ -30,13 +30,13 @@ that can be controlled using a keyboard. You can run it as follows: 1. In a terminal, run the keyboard executable as described above: - cd ign-gazebo/examples/standalone/keyboard/build + cd examples/standalone/keyboard/build ./keyboard ../keyboard.sdf 1. On another terminal, run `ign gazebo` with the vehicle that will consume the twist messages: - cd ign-gazebo/examples/worlds + cd examples/worlds ign gazebo -v 4 diff_drive.sdf 1. Switch back to the first terminal. Use the arrow keys to control one vehicle, diff --git a/tutorials.md.in b/tutorials.md.in index 43c4a3244f..0a96bde98f 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -9,6 +9,7 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively. * \subpage terminology "Terminology": List of terms used across the documentation. * \subpage createsystemplugins "Create System Plugins": Programmatically access simulation using C++ plugins. +* \subpage rendering_plugins "Rendering plugins": Write plugins that use Ignition Rendering on the server and client. * \subpage levels "Levels": Load entities on demand in large environments. * \subpage distributedsimulation "Distributed Simulation": Spread simulation across several processes. * \subpage resources "Finding resources": The different ways in which Ignition looks for files. diff --git a/tutorials/files/rendering_plugins.gif b/tutorials/files/rendering_plugins.gif new file mode 100644 index 0000000000..19ff4f8596 Binary files /dev/null and b/tutorials/files/rendering_plugins.gif differ diff --git a/tutorials/rendering_plugins.md b/tutorials/rendering_plugins.md new file mode 100644 index 0000000000..1a9de801a7 --- /dev/null +++ b/tutorials/rendering_plugins.md @@ -0,0 +1,138 @@ +\page rendering_plugins Rendering plugins + +This tutorial will go over how to write Ignition Gazebo plugins that alter the +3D scene's visual appearance using Ignition Rendering APIs. + +This is not to be confused with integrating a new rendering engine. See +[How to write your own rendering engine plugin](https://ignitionrobotics.org/api/rendering/4.2/renderingplugin.html) +for that. + +This tutorial will go over a couple of example plugins that are located at +https://github.com/ignitionrobotics/ign-gazebo/tree/main/examples/plugin/rendering_plugins. + +## Scenes + +During simulation, there are up to two 3D scenes being rendered by +Ignition Gazebo, one on the server process and one on the client process. + +The server-side scene will only be created when using the +`ignition::gazebo::systems::Sensors` system plugin on the server. This is the +scene that shows what the sensors see. + +The client-side scene will only be created when using the +`ignition::gazebo::Scene3D` GUI system plugin on the client. This is the +scene that shows what the user sees. + +For the user to see what the sensors see, they need to use other GUI plugins +that display sensor data, such as `ignition::gui::plugins::ImageDisplay` for +camera images or `ignition::gazebo::VisualizeLidar` for lidar point clouds. + +Ignition Gazebo keeps these scenes in sync by sending periodic state messages +from the server to the client that contain entity and component data with +the `ignition::gazebo::systems::SceneBroadcaster` plugin. Any +changes done to these scenes using Ignition Rendering APIs directly, as +described in this tutorial, will only affect one of the scenes and will not be +synchronized. The examples below will show how to change the ambient light for +each scene separately. + +## Plugin types + +Depending on the scene that you want to affect, you'll need to write a +different plugin. + +To interact with the server-side scene, you'll need to write an +`ignition::gazebo::System`. +See [Create System Plugins](createsystemplugins.html). + +To interact with the client-side scene, you'll need to write an +[ignition::gui::Plugin](https://ignitionrobotics.org/api/gui/4.1/classignition_1_1gui_1_1Plugin.html), +or a more specialized `ignition::gazebo::GuiSystem` +if you need to access entities and components. +See the [GUI system plugin example](https://github.com/ignitionrobotics/ign-gazebo/tree/main/examples/plugin/gui_system_plugin). + +## Getting the scene + +When writing either plugin type, the `ignition::rendering::Scene` pointer can +be conveniently found using the rendering engine's singleton. Both example +plugins use the exact same logic to get the scene: + +\snippet examples/plugin/rendering_plugins/RenderingServerPlugin.cc findScene + +The function above works for most cases, but you're welcome to customize it +for your use case. + +## Render thread + +Rendering operations aren't thread-safe. To make sure there are no race +conditions, all rendering operations should happen in the same thread, the +"render thread". In order to access that thread from a custom plugin, it's +necessary to listen to events that the 3D scene is emitting. These are +different for each plugin type. + +### Render events on the GUI + +The GUI plugin will need to listen to +[ignition::gui::events::Render](https://ignitionrobotics.org/api/gui/4.1/classignition_1_1gui_1_1events_1_1Render.html) +events. Here's how to do it: + +1. Include the GUI events header: + + \snippet examples/plugin/rendering_plugins/RenderingGuiPlugin.cc includeGuiEvents + +2. The 3D scene sends render events periodically to the `ignition::gui::MainWindow`, + not directly to every plugin. Therefore, your plugin will need to install a filter + so that it receives all events coming from the `MainWindow`. In your plugin's + `LoadConfig` call, install the filter as follows: + + \snippet examples/plugin/rendering_plugins/RenderingGuiPlugin.cc connectToGuiEvent + +3. The filter will direct all of `MainWindow`'s events to the `eventFilter` callback. Add + that function to your plugin as follows. Be sure to check for the event that you want, + and end the `eventFilter` function by forwarding the event to the base class. + + \snippet examples/plugin/rendering_plugins/RenderingGuiPlugin.cc eventFilter + +4. All your rendering operations should happen right there where + `PerformRenderingOperations` is located. In this example plugin, it checks if the + `dirty` flag is set, and if it is, it changes the scene's ambient light color randomly. + + \snippet examples/plugin/rendering_plugins/RenderingGuiPlugin.cc performRenderingOperations + +### Render events on the server + +The server plugin will need to listen to `ignition::gazebo::events::PreRender` or +`ignition::gazebo::events::PostRender` events. + +Here's how to do it: + +1. Include the rendering events header: + + \snippet examples/plugin/rendering_plugins/RenderingServerPlugin.cc includeRenderingEvents + +2. To receive `PreRender` events, connect to it as follows, and the + `PerformRenderingOperations` function will be called periodically: + + \snippet examples/plugin/rendering_plugins/RenderingServerPlugin.cc connectToServerEvent + +3. All your rendering operations should happen at `PerformRenderingOperations` is located. + In this example plugin, it checks if enough time has elapsed since the last color update, + and updates the color if it's time: + + \snippet examples/plugin/rendering_plugins/RenderingServerPlugin.cc performRenderingOperations + +## Running examples + +Follow the build instructions on the rendering plugins +[README](https://github.com/ignitionrobotics/ign-gazebo/blob/main/examples/plugin/rendering_plugins) +and you'll generate both plugins: + +* `RenderingGuiPlugin`: GUI plugin that updates the GUI scene's ambient light with a random color at each click. +* `RenderingServerPlugin`: Server plugin that updates the server scene's ambient light every 2 simulation seconds. + +Run the example world that uses both plugins and observe how the scene seen by +the camera sensor, displayed on the top-left camera image, is different from +the one on the GUI. Try pausing simulation and pressing the +`RANDOM GUI COLOR` button to see which scene gets updated. + +@image html files/rendering_plugins.gif +