From c783e481fa62c448306d77ee03bdc27181ba3238 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 28 Jun 2022 15:44:25 -0700 Subject: [PATCH 01/11] Break scene3d test apart into component tests Signed-off-by: Michael Carroll --- test/integration/minimal_scene.cc | 1 - test/integration/scene3d_config.cc | 115 ++++++++++++++++++ .../{scene3d.cc => scene3d_events.cc} | 103 +--------------- test/integration/scene3d_load.cc | 69 +++++++++++ test/integration/transport_scene_manager.cc | 2 +- 5 files changed, 187 insertions(+), 103 deletions(-) create mode 100644 test/integration/scene3d_config.cc rename test/integration/{scene3d.cc => scene3d_events.cc} (74%) create mode 100644 test/integration/scene3d_load.cc diff --git a/test/integration/minimal_scene.cc b/test/integration/minimal_scene.cc index d6dc05d75..eff5850b4 100644 --- a/test/integration/minimal_scene.cc +++ b/test/integration/minimal_scene.cc @@ -164,5 +164,4 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) win->QuickWindow()->close(); engine->DestroyScene(scene); - EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } diff --git a/test/integration/scene3d_config.cc b/test/integration/scene3d_config.cc new file mode 100644 index 000000000..e6769f92a --- /dev/null +++ b/test/integration/scene3d_config.cc @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_config.h" // NOLINT(build/include) +#include "../helpers/TestHelper.hh" +#include "gz/gui/Application.hh" +#include "gz/gui/GuiEvents.hh" +#include "gz/gui/Plugin.hh" +#include "gz/gui/MainWindow.hh" + +int g_argc = 1; +char* g_argv[] = +{ + reinterpret_cast(const_cast("./Scene3d_config_TEST")), +}; + +using namespace gz; +using namespace gui; + +///////////////////////////////////////////////// +TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) +{ + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); + + // Load plugin + const char *pluginStr = + "" + "ogre" + "banana" + "1.0 0 0" + "0 1 0" + "1 2 3 0 0 1.57" + ""; + + tinyxml2::XMLDocument pluginDoc; + pluginDoc.Parse(pluginStr); + EXPECT_TRUE(app.LoadPlugin("Scene3D", + pluginDoc.FirstChildElement("plugin"))); + + // Get main window + auto win = app.findChild(); + ASSERT_NE(nullptr, win); + + // Show, but don't exec, so we don't block + win->QuickWindow()->show(); + + // Check scene + auto engine = rendering::engine("ogre"); + ASSERT_NE(nullptr, engine); + + int sleep = 0; + int maxSleep = 30; + while (0 == engine->SceneCount() && sleep < maxSleep) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + QCoreApplication::processEvents(); + sleep++; + } + + EXPECT_EQ(1u, engine->SceneCount()); + auto scene = engine->SceneByName("banana"); + ASSERT_NE(nullptr, scene); + + EXPECT_EQ(math::Color(0, 1, 0), scene->BackgroundColor()); + EXPECT_EQ(math::Color(1, 0, 0), scene->AmbientLight()); + + auto root = scene->RootVisual(); + ASSERT_NE(nullptr, root); + EXPECT_EQ(1u, root->ChildCount()); + + // Check camera + auto camera = std::dynamic_pointer_cast( + root->ChildByIndex(0)); + ASSERT_NE(nullptr, camera); + + EXPECT_EQ(math::Pose3d(1, 2, 3, 0, 0, 1.57), camera->WorldPose()); + + // Cleanup + auto plugins = win->findChildren(); + for (auto & p : plugins) + { + auto pluginName = p->CardItem()->objectName(); + app.RemovePlugin(pluginName.toStdString()); + } + win->QuickWindow()->close(); + engine->DestroyScene(scene); +} diff --git a/test/integration/scene3d.cc b/test/integration/scene3d_events.cc similarity index 74% rename from test/integration/scene3d.cc rename to test/integration/scene3d_events.cc index 205e49e6d..fa3cd1cae 100644 --- a/test/integration/scene3d.cc +++ b/test/integration/scene3d_events.cc @@ -36,118 +36,19 @@ int g_argc = 1; char* g_argv[] = { - reinterpret_cast(const_cast("./Scene3d_TEST")), + reinterpret_cast(const_cast("./Scene3d_events_TEST")), }; using namespace gz; using namespace gui; -///////////////////////////////////////////////// -TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Load)) -{ - common::Console::SetVerbosity(4); - - Application app(g_argc, g_argv); - app.AddPluginPath(std::string(PROJECT_BINARY_PATH) + "/lib"); - - EXPECT_TRUE(app.LoadPlugin("Scene3D")); - - // Get main window - auto win = app.findChild(); - ASSERT_NE(nullptr, win); - - // Get plugin - auto plugins = win->findChildren(); - EXPECT_EQ(plugins.size(), 1); - - auto plugin = plugins[0]; - EXPECT_EQ(plugin->Title(), "3D Scene"); - - // Cleanup - plugins.clear(); - win->QuickWindow()->close(); -} - -///////////////////////////////////////////////// -TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) -{ - common::Console::SetVerbosity(4); - - Application app(g_argc, g_argv); - app.AddPluginPath(std::string(PROJECT_BINARY_PATH) + "/lib"); - - // Load plugin - const char *pluginStr = - "" - "ogre" - "banana" - "1.0 0 0" - "0 1 0" - "1 2 3 0 0 1.57" - ""; - - tinyxml2::XMLDocument pluginDoc; - pluginDoc.Parse(pluginStr); - EXPECT_TRUE(app.LoadPlugin("Scene3D", - pluginDoc.FirstChildElement("plugin"))); - - // Get main window - auto win = app.findChild(); - ASSERT_NE(nullptr, win); - - // Show, but don't exec, so we don't block - win->QuickWindow()->show(); - - // Check scene - auto engine = rendering::engine("ogre"); - ASSERT_NE(nullptr, engine); - - int sleep = 0; - int maxSleep = 30; - while (0 == engine->SceneCount() && sleep < maxSleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - - EXPECT_EQ(1u, engine->SceneCount()); - auto scene = engine->SceneByName("banana"); - ASSERT_NE(nullptr, scene); - - EXPECT_EQ(math::Color(0, 1, 0), scene->BackgroundColor()); - EXPECT_EQ(math::Color(1, 0, 0), scene->AmbientLight()); - - auto root = scene->RootVisual(); - ASSERT_NE(nullptr, root); - EXPECT_EQ(1u, root->ChildCount()); - - // Check camera - auto camera = std::dynamic_pointer_cast( - root->ChildByIndex(0)); - ASSERT_NE(nullptr, camera); - - EXPECT_EQ(math::Pose3d(1, 2, 3, 0, 0, 1.57), camera->WorldPose()); - - // Cleanup - auto plugins = win->findChildren(); - for (auto & p : plugins) - { - auto pluginName = p->CardItem()->objectName(); - app.RemovePlugin(pluginName.toStdString()); - } - win->QuickWindow()->close(); - engine->DestroyScene(scene); - rendering::unloadEngine(engine->Name()); -} - ///////////////////////////////////////////////// TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Events)) { common::Console::SetVerbosity(4); Application app(g_argc, g_argv); - app.AddPluginPath(std::string(PROJECT_BINARY_PATH) + "/lib"); + app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); // Load plugin const char *pluginStr = diff --git a/test/integration/scene3d_load.cc b/test/integration/scene3d_load.cc new file mode 100644 index 000000000..411c47f67 --- /dev/null +++ b/test/integration/scene3d_load.cc @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_config.h" // NOLINT(build/include) +#include "../helpers/TestHelper.hh" +#include "gz/gui/Application.hh" +#include "gz/gui/GuiEvents.hh" +#include "gz/gui/Plugin.hh" +#include "gz/gui/MainWindow.hh" + +int g_argc = 1; +char* g_argv[] = +{ + reinterpret_cast(const_cast("./Scene3d_load_TEST")), +}; + +using namespace gz; +using namespace gui; + +///////////////////////////////////////////////// +TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Load)) +{ + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); + + EXPECT_TRUE(app.LoadPlugin("Scene3D")); + + // Get main window + auto win = app.findChild(); + ASSERT_NE(nullptr, win); + + // Get plugin + auto plugins = win->findChildren(); + EXPECT_EQ(plugins.size(), 1); + + auto plugin = plugins[0]; + EXPECT_EQ(plugin->Title(), "3D Scene"); + + // Cleanup + plugins.clear(); + win->QuickWindow()->close(); +} diff --git a/test/integration/transport_scene_manager.cc b/test/integration/transport_scene_manager.cc index 189816105..0dbd77af0 100644 --- a/test/integration/transport_scene_manager.cc +++ b/test/integration/transport_scene_manager.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -260,6 +261,5 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) win->QuickWindow()->close(); engine->DestroyScene(scene); - EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } From bce5f0aa10c6edffd2179f3c1c3b48cbe5bca76e Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 28 Jun 2022 16:32:01 -0700 Subject: [PATCH 02/11] Fix include paths Signed-off-by: Michael Carroll --- test/integration/scene3d_config.cc | 2 +- test/integration/scene3d_load.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/scene3d_config.cc b/test/integration/scene3d_config.cc index e6769f92a..f6af73eaf 100644 --- a/test/integration/scene3d_config.cc +++ b/test/integration/scene3d_config.cc @@ -26,7 +26,7 @@ #include #include -#include "test_config.h" // NOLINT(build/include) +#include "test_config.hh" #include "../helpers/TestHelper.hh" #include "gz/gui/Application.hh" #include "gz/gui/GuiEvents.hh" diff --git a/test/integration/scene3d_load.cc b/test/integration/scene3d_load.cc index 411c47f67..4e5ca20db 100644 --- a/test/integration/scene3d_load.cc +++ b/test/integration/scene3d_load.cc @@ -26,7 +26,7 @@ #include #include -#include "test_config.h" // NOLINT(build/include) +#include "test_config.hh" #include "../helpers/TestHelper.hh" #include "gz/gui/Application.hh" #include "gz/gui/GuiEvents.hh" From 872a45c0227981df2400e08a6409a8040233f523 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 28 Jun 2022 17:08:56 -0700 Subject: [PATCH 03/11] Remove scene3d as it is deprecated Signed-off-by: Michael Carroll --- src/plugins/CMakeLists.txt | 1 - src/plugins/scene3d/CMakeLists.txt | 9 - src/plugins/scene3d/Scene3D.cc | 1827 ---------------------------- src/plugins/scene3d/Scene3D.hh | 436 ------- src/plugins/scene3d/Scene3D.qml | 83 -- src/plugins/scene3d/Scene3D.qrc | 5 - test/integration/scene3d_config.cc | 115 -- test/integration/scene3d_events.cc | 242 ---- test/integration/scene3d_load.cc | 69 -- 9 files changed, 2787 deletions(-) delete mode 100644 src/plugins/scene3d/CMakeLists.txt delete mode 100644 src/plugins/scene3d/Scene3D.cc delete mode 100644 src/plugins/scene3d/Scene3D.hh delete mode 100644 src/plugins/scene3d/Scene3D.qml delete mode 100644 src/plugins/scene3d/Scene3D.qrc delete mode 100644 test/integration/scene3d_config.cc delete mode 100644 test/integration/scene3d_events.cc delete mode 100644 test/integration/scene3d_load.cc diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index a5eb9bd77..ff282d6b9 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -123,7 +123,6 @@ add_subdirectory(publisher) add_subdirectory(marker_manager) add_subdirectory(minimal_scene) add_subdirectory(navsat_map) -add_subdirectory(scene3d) add_subdirectory(screenshot) add_subdirectory(shutdown_button) add_subdirectory(tape_measure) diff --git a/src/plugins/scene3d/CMakeLists.txt b/src/plugins/scene3d/CMakeLists.txt deleted file mode 100644 index 03e36bae3..000000000 --- a/src/plugins/scene3d/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -gz_gui_add_plugin(Scene3D - SOURCES - Scene3D.cc - QT_HEADERS - Scene3D.hh - PUBLIC_LINK_LIBS - gz-rendering${GZ_RENDERING_VER}::gz-rendering${GZ_RENDERING_VER} -) - diff --git a/src/plugins/scene3d/Scene3D.cc b/src/plugins/scene3d/Scene3D.cc deleted file mode 100644 index eead88cf5..000000000 --- a/src/plugins/scene3d/Scene3D.cc +++ /dev/null @@ -1,1827 +0,0 @@ -/* - * Copyright (C) 2017 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 "Scene3D.hh" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include - -// TODO(louise) Remove these pragmas once ign-rendering and ign-msgs -// are disabling the warnings -#ifdef _MSC_VER -#pragma warning(push, 0) -#endif -#include - -#include -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#include - -#include "gz/gui/Application.hh" -#include "gz/gui/Conversions.hh" -#include "gz/gui/GuiEvents.hh" -#include "gz/gui/MainWindow.hh" - -namespace gz -{ -namespace gui -{ -namespace plugins -{ - /// \brief Scene manager class for loading and managing objects in the scene - class SceneManager - { - /// \brief Constructor - public: SceneManager(); - - /// \brief Constructor - /// \param[in] _service Ign transport scene service name - /// \param[in] _poseTopic Ign transport pose topic name - /// \param[in] _deletionTopic Ign transport deletion topic name - /// \param[in] _sceneTopic Ign transport scene topic name - /// \param[in] _scene Pointer to the rendering scene - public: SceneManager(const std::string &_service, - const std::string &_poseTopic, - const std::string &_deletionTopic, - const std::string &_sceneTopic, - rendering::ScenePtr _scene); - - /// \brief Load the scene manager - /// \param[in] _service Ign transport service name - /// \param[in] _poseTopic Ign transport pose topic name - /// \param[in] _deletionTopic Ign transport deletion topic name - /// \param[in] _sceneTopic Ign transport scene topic name - /// \param[in] _scene Pointer to the rendering scene - public: void Load(const std::string &_service, - const std::string &_poseTopic, - const std::string &_deletionTopic, - const std::string &_sceneTopic, - rendering::ScenePtr _scene); - - /// \brief Make the scene service request and populate the scene - public: void Request(); - - /// \brief Update the scene based on pose msgs received - public: void Update(); - - /// \brief Callback function for the pose topic - /// \param[in] _msg Pose vector msg - private: void OnPoseVMsg(const msgs::Pose_V &_msg); - - /// \brief Load the scene from a scene msg - /// \param[in] _msg Scene msg - private: void LoadScene(const msgs::Scene &_msg); - - /// \brief Callback function for the request topic - /// \param[in] _msg Deletion message - private: void OnDeletionMsg(const msgs::UInt32_V &_msg); - - /// \brief Load the scene from a scene msg - /// \param[in] _msg Scene msg - private: void OnSceneSrvMsg(const msgs::Scene &_msg, const bool result); - - /// \brief Called when there's an entity is added to the scene - /// \param[in] _msg Scene msg - private: void OnSceneMsg(const msgs::Scene &_msg); - - /// \brief Load the model from a model msg - /// \param[in] _msg Model msg - /// \return Model visual created from the msg - private: rendering::VisualPtr LoadModel(const msgs::Model &_msg); - - /// \brief Load a link from a link msg - /// \param[in] _msg Link msg - /// \return Link visual created from the msg - private: rendering::VisualPtr LoadLink(const msgs::Link &_msg); - - /// \brief Load a visual from a visual msg - /// \param[in] _msg Visual msg - /// \return Visual visual created from the msg - private: rendering::VisualPtr LoadVisual(const msgs::Visual &_msg); - - /// \brief Load a geometry from a geometry msg - /// \param[in] _msg Geometry msg - /// \param[out] _scale Geometry scale that will be set based on msg param - /// \param[out] _localPose Additional local pose to be applied after the - /// visual's pose - /// \return Geometry object created from the msg - private: rendering::GeometryPtr LoadGeometry(const msgs::Geometry &_msg, - math::Vector3d &_scale, math::Pose3d &_localPose); - - /// \brief Load a material from a material msg - /// \param[in] _msg Material msg - /// \return Material object created from the msg - private: rendering::MaterialPtr LoadMaterial(const msgs::Material &_msg); - - /// \brief Load a light from a light msg - /// \param[in] _msg Light msg - /// \return Light object created from the msg - private: rendering::LightPtr LoadLight(const msgs::Light &_msg); - - /// \brief Delete an entity - /// \param[in] _entity Entity to delete - private: void DeleteEntity(const unsigned int _entity); - - //// \brief Ign-transport scene service name - private: std::string service; - - //// \brief Ign-transport pose topic name - private: std::string poseTopic; - - //// \brief Ign-transport deletion topic name - private: std::string deletionTopic; - - //// \brief Ign-transport scene topic name - private: std::string sceneTopic; - - //// \brief Pointer to the rendering scene - private: rendering::ScenePtr scene; - - //// \brief Mutex to protect the pose msgs - private: std::mutex mutex; - - /// \brief Map of entity id to pose - private: std::map poses; - - /// \brief Map of entity id to initial local poses - /// This is currently used to handle the normal vector in plane visuals. In - /// general, this can be used to store any local transforms between the - /// parent Visual and geometry. - private: std::map localPoses; - - /// \brief Map of visual id to visual pointers. - private: std::map visuals; - - /// \brief Map of light id to light pointers. - private: std::map lights; - - /// Entities to be deleted - private: std::vector toDeleteEntities; - - /// \brief Keeps the a list of unprocessed scene messages - private: std::vector sceneMsgs; - - /// \brief Transport node for making service request and subscribing to - /// pose topic - private: gz::transport::Node node; - }; - - /// \brief Private data class for IgnRenderer - class IgnRendererPrivate - { - /// \brief Flag to indicate if mouse event is dirty - public: bool mouseDirty = false; - - /// \brief Flag to indicate if hover event is dirty - public: bool hoverDirty = false; - - /// \brief Mouse event - public: common::MouseEvent mouseEvent; - - /// \brief Key event - public: common::KeyEvent keyEvent; - - /// \brief Mouse move distance since last event. - public: math::Vector2d drag; - - /// \brief Mutex to protect mouse events - public: std::mutex mutex; - - /// \brief User camera - public: rendering::CameraPtr camera; - - /// \brief Camera orbit controller - public: rendering::OrbitViewController viewControl; - - /// \brief The currently hovered mouse position in screen coordinates - public: math::Vector2i mouseHoverPos{math::Vector2i::Zero}; - - /// \brief Ray query for mouse clicks - public: rendering::RayQueryPtr rayQuery; - - /// \brief Scene requester to get scene info - public: SceneManager sceneManager; - - /// \brief View control focus target - public: math::Vector3d target; - }; - - /// \brief Private data class for RenderWindowItem - class RenderWindowItemPrivate - { - /// \brief Keep latest mouse event - public: common::MouseEvent mouseEvent; - - /// \brief Render thread - public : RenderThread *renderThread = nullptr; - - //// \brief List of threads - public: static QList threads; - }; - - /// \brief Private data class for Scene3D - class Scene3DPrivate - { - }; -} -} -} - -using namespace gz; -using namespace gui; -using namespace plugins; - -QList RenderWindowItemPrivate::threads; - -///////////////////////////////////////////////// -SceneManager::SceneManager() -{ -} - -///////////////////////////////////////////////// -SceneManager::SceneManager(const std::string &_service, - const std::string &_poseTopic, - const std::string &_deletionTopic, - const std::string &_sceneTopic, - rendering::ScenePtr _scene) -{ - this->Load(_service, _poseTopic, _deletionTopic, _sceneTopic, _scene); -} - -///////////////////////////////////////////////// -void SceneManager::Load(const std::string &_service, - const std::string &_poseTopic, - const std::string &_deletionTopic, - const std::string &_sceneTopic, - rendering::ScenePtr _scene) -{ - this->service = _service; - this->poseTopic = _poseTopic; - this->deletionTopic = _deletionTopic; - this->sceneTopic = _sceneTopic; - this->scene = _scene; -} - -///////////////////////////////////////////////// -void SceneManager::Request() -{ - // wait for the service to be advertized - std::vector publishers; - const std::chrono::duration sleepDuration{1.0}; - const std::size_t tries = 30; - for (std::size_t i = 0; i < tries; ++i) - { - this->node.ServiceInfo(this->service, publishers); - if (publishers.size() > 0) - break; - std::this_thread::sleep_for(sleepDuration); - gzdbg << "Waiting for service " << this->service << "\n"; - } - - if (publishers.empty() || - !this->node.Request(this->service, &SceneManager::OnSceneSrvMsg, this)) - { - gzerr << "Error making service request to " << this->service << std::endl; - } -} - -///////////////////////////////////////////////// -void SceneManager::OnPoseVMsg(const msgs::Pose_V &_msg) -{ - std::lock_guard lock(this->mutex); - for (int i = 0; i < _msg.pose_size(); ++i) - { - math::Pose3d pose = msgs::Convert(_msg.pose(i)); - - // apply additional local poses if available - const auto it = this->localPoses.find(_msg.pose(i).id()); - if (it != this->localPoses.end()) - { - pose = pose * it->second; - } - - this->poses[_msg.pose(i).id()] = pose; - } -} - -///////////////////////////////////////////////// -void SceneManager::OnDeletionMsg(const msgs::UInt32_V &_msg) -{ - std::lock_guard lock(this->mutex); - std::copy(_msg.data().begin(), _msg.data().end(), - std::back_inserter(this->toDeleteEntities)); -} - -///////////////////////////////////////////////// -void SceneManager::Update() -{ - // process msgs - std::lock_guard lock(this->mutex); - - for (const auto &msg : this->sceneMsgs) - { - this->LoadScene(msg); - } - this->sceneMsgs.clear(); - - for (const auto &entity : this->toDeleteEntities) - { - this->DeleteEntity(entity); - } - this->toDeleteEntities.clear(); - - - for (auto pIt = this->poses.begin(); pIt != this->poses.end();) - { - auto vIt = this->visuals.find(pIt->first); - if (vIt != this->visuals.end()) - { - auto visual = vIt->second.lock(); - if (visual) - { - visual->SetLocalPose(pIt->second); - } - else - { - this->visuals.erase(vIt); - } - this->poses.erase(pIt++); - } - else - { - auto lIt = this->lights.find(pIt->first); - if (lIt != this->lights.end()) - { - auto light = lIt->second.lock(); - if (light) - { - light->SetLocalPose(pIt->second); - } - else - { - this->lights.erase(lIt); - } - this->poses.erase(pIt++); - } - else - { - ++pIt; - } - } - } - - // Note we are clearing the pose msgs here but later on we may need to - // consider the case where pose msgs arrive before scene/visual msgs - this->poses.clear(); -} - - -///////////////////////////////////////////////// -void SceneManager::OnSceneMsg(const msgs::Scene &_msg) -{ - std::lock_guard lock(this->mutex); - this->sceneMsgs.push_back(_msg); -} - -///////////////////////////////////////////////// -void SceneManager::OnSceneSrvMsg(const msgs::Scene &_msg, const bool result) -{ - if (!result) - { - gzerr << "Error making service request to " << this->service - << std::endl; - return; - } - - { - std::lock_guard lock(this->mutex); - this->sceneMsgs.push_back(_msg); - } - - if (!this->poseTopic.empty()) - { - if (!this->node.Subscribe(this->poseTopic, &SceneManager::OnPoseVMsg, this)) - { - gzerr << "Error subscribing to pose topic: " << this->poseTopic - << std::endl; - } - } - else - { - gzwarn << "The pose topic, set via , for the Scene3D plugin " - << "is missing or empty. Please set this topic so that the Scene3D " - << "can receive and process pose information.\n"; - } - - if (!this->deletionTopic.empty()) - { - if (!this->node.Subscribe(this->deletionTopic, &SceneManager::OnDeletionMsg, - this)) - { - gzerr << "Error subscribing to deletion topic: " << this->deletionTopic - << std::endl; - } - } - else - { - gzwarn << "The deletion topic, set via , for the " - << "Scene3D plugin is missing or empty. Please set this topic so that " - << "the Scene3D can receive and process deletion information.\n"; - } - - if (!this->sceneTopic.empty()) - { - if (!this->node.Subscribe( - this->sceneTopic, &SceneManager::OnSceneMsg, this)) - { - gzerr << "Error subscribing to scene topic: " << this->sceneTopic - << std::endl; - } - } - else - { - gzwarn << "The scene topic, set via , for the " - << "Scene3D plugin is missing or empty. Please set this topic so that " - << "the Scene3D can receive and process scene information.\n"; - } -} - -void SceneManager::LoadScene(const msgs::Scene &_msg) -{ - rendering::VisualPtr rootVis = this->scene->RootVisual(); - - // load models - for (int i = 0; i < _msg.model_size(); ++i) - { - // Only add if it's not already loaded - if (this->visuals.find(_msg.model(i).id()) == this->visuals.end()) - { - rendering::VisualPtr modelVis = this->LoadModel(_msg.model(i)); - if (modelVis) - rootVis->AddChild(modelVis); - else - gzerr << "Failed to load model: " << _msg.model(i).name() << std::endl; - } - } - - // load lights - for (int i = 0; i < _msg.light_size(); ++i) - { - if (this->lights.find(_msg.light(i).id()) == this->lights.end()) - { - rendering::LightPtr light = this->LoadLight(_msg.light(i)); - if (light) - rootVis->AddChild(light); - else - gzerr << "Failed to load light: " << _msg.light(i).name() << std::endl; - } - } -} - -///////////////////////////////////////////////// -rendering::VisualPtr SceneManager::LoadModel(const msgs::Model &_msg) -{ - rendering::VisualPtr modelVis = this->scene->CreateVisual(); - if (_msg.has_pose()) - modelVis->SetLocalPose(msgs::Convert(_msg.pose())); - this->visuals[_msg.id()] = modelVis; - - // load links - for (int i = 0; i < _msg.link_size(); ++i) - { - rendering::VisualPtr linkVis = this->LoadLink(_msg.link(i)); - if (linkVis) - modelVis->AddChild(linkVis); - else - gzerr << "Failed to load link: " << _msg.link(i).name() << std::endl; - } - - // load nested models - for (int i = 0; i < _msg.model_size(); ++i) - { - rendering::VisualPtr nestedModelVis = this->LoadModel(_msg.model(i)); - if (nestedModelVis) - modelVis->AddChild(nestedModelVis); - else - gzerr << "Failed to load nested model: " << _msg.model(i).name() - << std::endl; - } - - return modelVis; -} - -///////////////////////////////////////////////// -rendering::VisualPtr SceneManager::LoadLink(const msgs::Link &_msg) -{ - rendering::VisualPtr linkVis = this->scene->CreateVisual(); - if (_msg.has_pose()) - linkVis->SetLocalPose(msgs::Convert(_msg.pose())); - this->visuals[_msg.id()] = linkVis; - - // load visuals - for (int i = 0; i < _msg.visual_size(); ++i) - { - rendering::VisualPtr visualVis = this->LoadVisual(_msg.visual(i)); - if (visualVis) - linkVis->AddChild(visualVis); - else - gzerr << "Failed to load visual: " << _msg.visual(i).name() << std::endl; - } - - // load lights - for (int i = 0; i < _msg.light_size(); ++i) - { - rendering::LightPtr light = this->LoadLight(_msg.light(i)); - if (light) - linkVis->AddChild(light); - else - gzerr << "Failed to load light: " << _msg.light(i).name() << std::endl; - } - - return linkVis; -} - -///////////////////////////////////////////////// -rendering::VisualPtr SceneManager::LoadVisual(const msgs::Visual &_msg) -{ - if (!_msg.has_geometry()) - return rendering::VisualPtr(); - - rendering::VisualPtr visualVis = this->scene->CreateVisual(); - this->visuals[_msg.id()] = visualVis; - - math::Vector3d scale = math::Vector3d::One; - math::Pose3d localPose; - rendering::GeometryPtr geom = - this->LoadGeometry(_msg.geometry(), scale, localPose); - - if (_msg.has_pose()) - visualVis->SetLocalPose(msgs::Convert(_msg.pose()) * localPose); - else - visualVis->SetLocalPose(localPose); - - if (geom) - { - // store the local pose - this->localPoses[_msg.id()] = localPose; - - visualVis->AddGeometry(geom); - visualVis->SetLocalScale(scale); - - // set material - rendering::MaterialPtr material{nullptr}; - if (_msg.has_material()) - { - material = this->LoadMaterial(_msg.material()); - } - // Don't set a default material for meshes because they - // may have their own - // TODO(anyone) support overriding mesh material - else if (!_msg.geometry().has_mesh()) - { - // create default material - material = this->scene->Material("ign-grey"); - if (!material) - { - material = this->scene->CreateMaterial("ign-grey"); - material->SetAmbient(0.3, 0.3, 0.3); - material->SetDiffuse(0.7, 0.7, 0.7); - material->SetSpecular(1.0, 1.0, 1.0); - material->SetRoughness(0.2f); - material->SetMetalness(1.0f); - } - } - else - { - // meshes created by mesh loader may have their own materials - // update/override their properties based on input sdf element values - auto mesh = std::dynamic_pointer_cast(geom); - for (unsigned int i = 0; i < mesh->SubMeshCount(); ++i) - { - auto submesh = mesh->SubMeshByIndex(i); - auto submeshMat = submesh->Material(); - if (submeshMat) - { - double productAlpha = (1.0-_msg.transparency()) * - (1.0 - submeshMat->Transparency()); - submeshMat->SetTransparency(1 - productAlpha); - submeshMat->SetCastShadows(_msg.cast_shadows()); - } - } - } - - if (material) - { - // set transparency - material->SetTransparency(_msg.transparency()); - - // cast shadows - material->SetCastShadows(_msg.cast_shadows()); - - geom->SetMaterial(material); - // todo(anyone) SetMaterial function clones the input material. - // but does not take ownership of it so we need to destroy it here. - // This is not ideal. We should let ign-rendering handle the lifetime - // of this material - this->scene->DestroyMaterial(material); - } - } - else - { - gzerr << "Failed to load geometry for visual: " << _msg.name() - << std::endl; - } - - return visualVis; -} - -///////////////////////////////////////////////// -rendering::GeometryPtr SceneManager::LoadGeometry(const msgs::Geometry &_msg, - math::Vector3d &_scale, math::Pose3d &_localPose) -{ - math::Vector3d scale = math::Vector3d::One; - math::Pose3d localPose = math::Pose3d::Zero; - rendering::GeometryPtr geom{nullptr}; - if (_msg.has_box()) - { - geom = this->scene->CreateBox(); - if (_msg.box().has_size()) - scale = msgs::Convert(_msg.box().size()); - } - else if (_msg.has_cylinder()) - { - geom = this->scene->CreateCylinder(); - scale.X() = _msg.cylinder().radius() * 2; - scale.Y() = scale.X(); - scale.Z() = _msg.cylinder().length(); - } - else if (_msg.has_capsule()) - { - auto capsule = this->scene->CreateCapsule(); - capsule->SetRadius(_msg.capsule().radius()); - capsule->SetLength(_msg.capsule().length()); - geom = capsule; - - scale.X() = _msg.capsule().radius() * 2; - scale.Y() = scale.X(); - scale.Z() = _msg.capsule().length() + scale.X(); - } - else if (_msg.has_ellipsoid()) - { - geom = this->scene->CreateSphere(); - scale.X() = _msg.ellipsoid().radii().x() * 2; - scale.Y() = _msg.ellipsoid().radii().y() * 2; - scale.Z() = _msg.ellipsoid().radii().z() * 2; - } - else if (_msg.has_plane()) - { - geom = this->scene->CreatePlane(); - - if (_msg.plane().has_size()) - { - scale.X() = _msg.plane().size().x(); - scale.Y() = _msg.plane().size().y(); - } - - if (_msg.plane().has_normal()) - { - // Create a rotation for the plane mesh to account for the normal vector. - // The rotation is the angle between the +z(0,0,1) vector and the - // normal, which are both expressed in the local (Visual) frame. - math::Vector3d normal = msgs::Convert(_msg.plane().normal()); - localPose.Rot().SetFrom2Axes(math::Vector3d::UnitZ, normal.Normalized()); - } - } - else if (_msg.has_sphere()) - { - geom = this->scene->CreateSphere(); - scale.X() = _msg.sphere().radius() * 2; - scale.Y() = scale.X(); - scale.Z() = scale.X(); - } - else if (_msg.has_mesh()) - { - if (_msg.mesh().filename().empty()) - { - gzerr << "Mesh geometry missing filename" << std::endl; - return geom; - } - rendering::MeshDescriptor descriptor; - - // Assume absolute path to mesh file - descriptor.meshName = _msg.mesh().filename(); - - gz::common::MeshManager* meshManager = - gz::common::MeshManager::Instance(); - descriptor.mesh = meshManager->Load(descriptor.meshName); - geom = this->scene->CreateMesh(descriptor); - - scale = msgs::Convert(_msg.mesh().scale()); - } - else - { - gzerr << "Unsupported geometry type" << std::endl; - } - _scale = scale; - _localPose = localPose; - return geom; -} - -///////////////////////////////////////////////// -rendering::MaterialPtr SceneManager::LoadMaterial(const msgs::Material &_msg) -{ - rendering::MaterialPtr material = this->scene->CreateMaterial(); - if (_msg.has_ambient()) - { - material->SetAmbient(msgs::Convert(_msg.ambient())); - } - if (_msg.has_diffuse()) - { - material->SetDiffuse(msgs::Convert(_msg.diffuse())); - } - if (_msg.has_specular()) - { - material->SetSpecular(msgs::Convert(_msg.specular())); - } - if (_msg.has_emissive()) - { - material->SetEmissive(msgs::Convert(_msg.emissive())); - } - - return material; -} - -///////////////////////////////////////////////// -rendering::LightPtr SceneManager::LoadLight(const msgs::Light &_msg) -{ - rendering::LightPtr light; - - switch (_msg.type()) - { - case msgs::Light_LightType_POINT: - light = this->scene->CreatePointLight(); - break; - case msgs::Light_LightType_SPOT: - { - light = this->scene->CreateSpotLight(); - rendering::SpotLightPtr spotLight = - std::dynamic_pointer_cast(light); - spotLight->SetInnerAngle(_msg.spot_inner_angle()); - spotLight->SetOuterAngle(_msg.spot_outer_angle()); - spotLight->SetFalloff(_msg.spot_falloff()); - break; - } - case msgs::Light_LightType_DIRECTIONAL: - { - light = this->scene->CreateDirectionalLight(); - rendering::DirectionalLightPtr dirLight = - std::dynamic_pointer_cast(light); - - if (_msg.has_direction()) - dirLight->SetDirection(msgs::Convert(_msg.direction())); - break; - } - default: - gzerr << "Light type not supported" << std::endl; - return light; - } - - if (_msg.has_pose()) - light->SetLocalPose(msgs::Convert(_msg.pose())); - - if (_msg.has_diffuse()) - light->SetDiffuseColor(msgs::Convert(_msg.diffuse())); - - if (_msg.has_specular()) - light->SetSpecularColor(msgs::Convert(_msg.specular())); - - light->SetAttenuationConstant(_msg.attenuation_constant()); - light->SetAttenuationLinear(_msg.attenuation_linear()); - light->SetAttenuationQuadratic(_msg.attenuation_quadratic()); - light->SetAttenuationRange(_msg.range()); - - light->SetCastShadows(_msg.cast_shadows()); - - this->lights[_msg.id()] = light; - return light; -} - -///////////////////////////////////////////////// -void SceneManager::DeleteEntity(const unsigned int _entity) -{ - if (this->visuals.find(_entity) != this->visuals.end()) - { - auto visual = this->visuals[_entity].lock(); - if (visual) - { - this->scene->DestroyVisual(visual, true); - } - this->visuals.erase(_entity); - } - else if (this->lights.find(_entity) != this->lights.end()) - { - auto light = this->lights[_entity].lock(); - if (light) - { - this->scene->DestroyLight(light, true); - } - this->lights.erase(_entity); - } -} - -///////////////////////////////////////////////// -IgnRenderer::IgnRenderer() - : dataPtr(new IgnRendererPrivate) -{ -} - - -///////////////////////////////////////////////// -IgnRenderer::~IgnRenderer() -{ -} - -///////////////////////////////////////////////// -void IgnRenderer::Render() -{ - if (this->textureDirty) - { - this->dataPtr->camera->SetImageWidth(this->textureSize.width()); - this->dataPtr->camera->SetImageHeight(this->textureSize.height()); - this->dataPtr->camera->SetAspectRatio(this->textureSize.width() / - this->textureSize.height()); - // setting the size should cause the render texture to be rebuilt - this->dataPtr->camera->PreRender(); - this->textureId = this->dataPtr->camera->RenderTextureGLId(); - this->textureDirty = false; - } - - // update the scene - this->dataPtr->sceneManager.Update(); - - // view control - this->HandleMouseEvent(); - - // update and render to texture - this->dataPtr->camera->Update(); - - if (gz::gui::App()) - { - gz::gui::App()->sendEvent( - gz::gui::App()->findChild(), - new gui::events::Render()); - } -} - -///////////////////////////////////////////////// -void IgnRenderer::HandleMouseEvent() -{ - std::lock_guard lock(this->dataPtr->mutex); - this->BroadcastHoverPos(); - this->BroadcastLeftClick(); - this->BroadcastRightClick(); - this->BroadcastKeyPress(); - this->BroadcastKeyRelease(); - this->HandleMouseViewControl(); -} - -///////////////////////////////////////////////// -void IgnRenderer::HandleMouseViewControl() -{ - if (!this->dataPtr->mouseDirty) - return; - - this->dataPtr->viewControl.SetCamera(this->dataPtr->camera); - - if (this->dataPtr->mouseEvent.Type() == common::MouseEvent::SCROLL) - { - this->dataPtr->target = - this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); - this->dataPtr->viewControl.SetTarget(this->dataPtr->target); - double distance = this->dataPtr->camera->WorldPosition().Distance( - this->dataPtr->target); - double amount = -this->dataPtr->drag.Y() * distance / 5.0; - this->dataPtr->viewControl.Zoom(amount); - } - else - { - if (this->dataPtr->drag == math::Vector2d::Zero) - { - this->dataPtr->target = this->ScreenToScene( - this->dataPtr->mouseEvent.PressPos()); - this->dataPtr->viewControl.SetTarget(this->dataPtr->target); - } - - // Pan with left button - if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::LEFT) - { - if (Qt::ShiftModifier == QGuiApplication::queryKeyboardModifiers()) - this->dataPtr->viewControl.Orbit(this->dataPtr->drag); - else - this->dataPtr->viewControl.Pan(this->dataPtr->drag); - } - // Orbit with middle button - else if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::MIDDLE) - { - this->dataPtr->viewControl.Orbit(this->dataPtr->drag); - } - else if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::RIGHT) - { - double hfov = this->dataPtr->camera->HFOV().Radian(); - double vfov = 2.0f * atan(tan(hfov / 2.0f) / - this->dataPtr->camera->AspectRatio()); - double distance = this->dataPtr->camera->WorldPosition().Distance( - this->dataPtr->target); - double amount = ((-this->dataPtr->drag.Y() / - static_cast(this->dataPtr->camera->ImageHeight())) - * distance * tan(vfov/2.0) * 6.0); - this->dataPtr->viewControl.Zoom(amount); - } - } - this->dataPtr->drag = 0; - this->dataPtr->mouseDirty = false; -} - -//////////////////////////////////////////////// -void IgnRenderer::HandleKeyPress(QKeyEvent *_e) -{ - if (_e->isAutoRepeat()) - return; - - std::lock_guard lock(this->dataPtr->mutex); - - this->dataPtr->keyEvent.SetKey(_e->key()); - this->dataPtr->keyEvent.SetText(_e->text().toStdString()); - - this->dataPtr->keyEvent.SetControl( - (_e->modifiers() & Qt::ControlModifier)); - this->dataPtr->keyEvent.SetShift( - (_e->modifiers() & Qt::ShiftModifier)); - this->dataPtr->keyEvent.SetAlt( - (_e->modifiers() & Qt::AltModifier)); - - this->dataPtr->mouseEvent.SetControl(this->dataPtr->keyEvent.Control()); - this->dataPtr->mouseEvent.SetShift(this->dataPtr->keyEvent.Shift()); - this->dataPtr->mouseEvent.SetAlt(this->dataPtr->keyEvent.Alt()); - this->dataPtr->keyEvent.SetType(common::KeyEvent::PRESS); -} - -//////////////////////////////////////////////// -void IgnRenderer::HandleKeyRelease(QKeyEvent *_e) -{ - if (_e->isAutoRepeat()) - return; - - std::lock_guard lock(this->dataPtr->mutex); - - this->dataPtr->keyEvent.SetKey(_e->key()); - - this->dataPtr->keyEvent.SetControl( - (_e->modifiers() & Qt::ControlModifier) - && (_e->key() != Qt::Key_Control)); - this->dataPtr->keyEvent.SetShift( - (_e->modifiers() & Qt::ShiftModifier) - && (_e->key() != Qt::Key_Shift)); - this->dataPtr->keyEvent.SetAlt( - (_e->modifiers() & Qt::AltModifier) - && (_e->key() != Qt::Key_Alt)); - - this->dataPtr->mouseEvent.SetControl(this->dataPtr->keyEvent.Control()); - this->dataPtr->mouseEvent.SetShift(this->dataPtr->keyEvent.Shift()); - this->dataPtr->mouseEvent.SetAlt(this->dataPtr->keyEvent.Alt()); - this->dataPtr->keyEvent.SetType(common::KeyEvent::RELEASE); -} - -///////////////////////////////////////////////// -void IgnRenderer::BroadcastHoverPos() -{ - if (!this->dataPtr->hoverDirty) - return; - - auto pos = this->ScreenToScene(this->dataPtr->mouseHoverPos); - - events::HoverToScene hoverToSceneEvent(pos); - App()->sendEvent(App()->findChild(), &hoverToSceneEvent); -} - -///////////////////////////////////////////////// -void IgnRenderer::BroadcastLeftClick() -{ - if (!this->dataPtr->mouseDirty) - return; - - if (this->dataPtr->mouseEvent.Dragging()) - return; - - if (this->dataPtr->mouseEvent.Button() != common::MouseEvent::LEFT || - this->dataPtr->mouseEvent.Type() != common::MouseEvent::RELEASE) - return; - - auto pos = this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); - - events::LeftClickToScene leftClickToSceneEvent(pos); - events::LeftClickOnScene leftClickOnSceneEvent(this->dataPtr->mouseEvent); - - App()->sendEvent(App()->findChild(), &leftClickToSceneEvent); - App()->sendEvent(App()->findChild(), &leftClickOnSceneEvent); -} - -///////////////////////////////////////////////// -void IgnRenderer::BroadcastRightClick() -{ - if (!this->dataPtr->mouseDirty) - return; - - if (this->dataPtr->mouseEvent.Dragging()) - return; - - if (this->dataPtr->mouseEvent.Button() != common::MouseEvent::RIGHT || - this->dataPtr->mouseEvent.Type() != common::MouseEvent::RELEASE) - return; - - auto pos = this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); - - events::RightClickToScene rightClickToSceneEvent(pos); - events::RightClickOnScene rightClickOnSceneEvent(this->dataPtr->mouseEvent); - - App()->sendEvent(App()->findChild(), &rightClickToSceneEvent); - App()->sendEvent(App()->findChild(), &rightClickOnSceneEvent); -} - -///////////////////////////////////////////////// -void IgnRenderer::BroadcastKeyRelease() -{ - if (this->dataPtr->keyEvent.Type() == common::KeyEvent::RELEASE) - { - events::KeyReleaseOnScene keyRelease(this->dataPtr->keyEvent); - App()->sendEvent(App()->findChild(), &keyRelease); - this->dataPtr->keyEvent.SetType(common::KeyEvent::NO_EVENT); - } -} - -///////////////////////////////////////////////// -void IgnRenderer::BroadcastKeyPress() -{ - if (this->dataPtr->keyEvent.Type() == common::KeyEvent::PRESS) - { - events::KeyPressOnScene keyPress(this->dataPtr->keyEvent); - App()->sendEvent(App()->findChild(), &keyPress); - this->dataPtr->keyEvent.SetType(common::KeyEvent::NO_EVENT); - } -} - -///////////////////////////////////////////////// -std::string IgnRenderer::Initialize() -{ - if (this->initialized) - return std::string(); - - // Currently only support one engine at a time - rendering::RenderEngine *engine{nullptr}; - auto loadedEngines = rendering::loadedEngines(); - - // Load engine if there's no engine yet - if (loadedEngines.empty()) - { - std::map params; - params["useCurrentGLContext"] = "1"; - params["winID"] = std::to_string( - gz::gui::App()->findChild()-> - QuickWindow()->winId()); - engine = rendering::engine(this->engineName, params); - } - else - { - if (loadedEngines.front() != this->engineName) - { - gzwarn << "Failed to load engine [" << this->engineName - << "]. Using engine [" << loadedEngines.front() - << "], which is already loaded. Currently only one engine is " - << "supported at a time." << std::endl; - } - engine = rendering::engine(loadedEngines.front()); - } - - if (!engine) - { - return "Engine [" + this->engineName + "] is not supported"; - } - - // Scene - auto scene = engine->SceneByName(this->sceneName); - if (!scene) - { - gzdbg << "Create scene [" << this->sceneName << "]" << std::endl; - scene = engine->CreateScene(this->sceneName); - scene->SetAmbientLight(this->ambientLight); - scene->SetBackgroundColor(this->backgroundColor); - } - else - { - return "Currently only one plugin providing a 3D scene is supported at a " - "time."; - } - - auto root = scene->RootVisual(); - - // Camera - this->dataPtr->camera = scene->CreateCamera(); - root->AddChild(this->dataPtr->camera); - this->dataPtr->camera->SetLocalPose(this->cameraPose); - this->dataPtr->camera->SetImageWidth(this->textureSize.width()); - this->dataPtr->camera->SetImageHeight(this->textureSize.height()); - this->dataPtr->camera->SetAntiAliasing(8); - this->dataPtr->camera->SetHFOV(M_PI * 0.5); - // setting the size and calling PreRender should cause the render texture to - // be rebuilt - this->dataPtr->camera->PreRender(); - this->textureId = this->dataPtr->camera->RenderTextureGLId(); - - // Make service call to populate scene - if (!this->sceneService.empty()) - { - this->dataPtr->sceneManager.Load(this->sceneService, this->poseTopic, - this->deletionTopic, this->sceneTopic, - scene); - this->dataPtr->sceneManager.Request(); - } - - // Ray Query - this->dataPtr->rayQuery = this->dataPtr->camera->Scene()->CreateRayQuery(); - - this->initialized = true; - return std::string(); -} - -///////////////////////////////////////////////// -void IgnRenderer::Destroy() -{ - auto engine = rendering::engine(this->engineName); - if (!engine) - return; - auto scene = engine->SceneByName(this->sceneName); - if (!scene) - return; - scene->DestroySensor(this->dataPtr->camera); - - // If that was the last sensor, destroy scene - if (scene->SensorCount() == 0) - { - gzdbg << "Destroy scene [" << scene->Name() << "]" << std::endl; - engine->DestroyScene(scene); - - // TODO(anyone) If that was the last scene, terminate engine? - } -} - -///////////////////////////////////////////////// -void IgnRenderer::NewHoverEvent(const math::Vector2i &_hoverPos) -{ - std::lock_guard lock(this->dataPtr->mutex); - this->dataPtr->mouseHoverPos = _hoverPos; - this->dataPtr->hoverDirty = true; -} - -///////////////////////////////////////////////// -void IgnRenderer::NewMouseEvent(const common::MouseEvent &_e, - const math::Vector2d &_drag) -{ - std::lock_guard lock(this->dataPtr->mutex); - this->dataPtr->mouseEvent = _e; - this->dataPtr->drag += _drag; - this->dataPtr->mouseDirty = true; -} - -///////////////////////////////////////////////// -math::Vector3d IgnRenderer::ScreenToScene( - const math::Vector2i &_screenPos) const -{ - // Normalize point on the image - double width = this->dataPtr->camera->ImageWidth(); - double height = this->dataPtr->camera->ImageHeight(); - - double nx = 2.0 * _screenPos.X() / width - 1.0; - double ny = 1.0 - 2.0 * _screenPos.Y() / height; - - // Make a ray query - this->dataPtr->rayQuery->SetFromCamera( - this->dataPtr->camera, math::Vector2d(nx, ny)); - - auto result = this->dataPtr->rayQuery->ClosestPoint(); - if (result) - return result.point; - - // Set point to be 10m away if no intersection found - return this->dataPtr->rayQuery->Origin() + - this->dataPtr->rayQuery->Direction() * 10; -} - -///////////////////////////////////////////////// -RenderThread::RenderThread() -{ - RenderWindowItemPrivate::threads << this; -} - -///////////////////////////////////////////////// -void RenderThread::SetErrorCb(std::function _cb) -{ - this->errorCb = _cb; -} - -///////////////////////////////////////////////// -void RenderThread::RenderNext() -{ - this->context->makeCurrent(this->surface); - - if (!this->ignRenderer.initialized) - { - // Initialize renderer - auto loadingError = this->ignRenderer.Initialize(); - if (!loadingError.empty()) - { - this->errorCb(QString::fromStdString(loadingError)); - return; - } - } - - // check if engine has been successfully initialized - if (!this->ignRenderer.initialized) - { - gzerr << "Unable to initialize renderer" << std::endl; - return; - } - - this->ignRenderer.Render(); - - emit TextureReady(this->ignRenderer.textureId, this->ignRenderer.textureSize); -} - -///////////////////////////////////////////////// -void RenderThread::ShutDown() -{ - if (this->context && this->surface) - this->context->makeCurrent(this->surface); - - this->ignRenderer.Destroy(); - - if (this->context) - { - this->context->doneCurrent(); - delete this->context; - } - - // schedule this to be deleted only after we're done cleaning up - if (this->surface) - this->surface->deleteLater(); - - // Stop event processing, move the thread to GUI and make sure it is deleted. - if (this->ignRenderer.initialized) - this->moveToThread(QGuiApplication::instance()->thread()); -} - - -///////////////////////////////////////////////// -void RenderThread::SizeChanged() -{ - auto item = qobject_cast(this->sender()); - if (!item) - { - gzerr << "Internal error, sender is not QQuickItem." << std::endl; - return; - } - - if (item->width() <= 0 || item->height() <= 0) - return; - - this->ignRenderer.textureSize = QSize(item->width(), item->height()); - this->ignRenderer.textureDirty = true; -} - -///////////////////////////////////////////////// -TextureNode::TextureNode(QQuickWindow *_window) - : window(_window) -{ - // Our texture node must have a texture, so use the default 0 texture. -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - this->texture = this->window->createTextureFromId(0, QSize(1, 1)); -#else - void * nativeLayout; - this->texture = this->window->createTextureFromNativeObject( - QQuickWindow::NativeObjectTexture, &nativeLayout, 0, QSize(1, 1), - QQuickWindow::TextureIsOpaque); -#endif - this->setTexture(this->texture); -} - -///////////////////////////////////////////////// -TextureNode::~TextureNode() -{ - delete this->texture; -} - -///////////////////////////////////////////////// -void TextureNode::NewTexture(int _id, const QSize &_size) -{ - this->mutex.lock(); - this->id = _id; - this->size = _size; - this->mutex.unlock(); - - // We cannot call QQuickWindow::update directly here, as this is only allowed - // from the rendering thread or GUI thread. - emit PendingNewTexture(); -} - -///////////////////////////////////////////////// -void TextureNode::PrepareNode() -{ - this->mutex.lock(); - int newId = this->id; - QSize sz = this->size; - this->id = 0; - this->mutex.unlock(); - if (newId) - { - delete this->texture; - // note: include QQuickWindow::TextureHasAlphaChannel if the rendered - // content has alpha. -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - this->texture = this->window->createTextureFromId( - newId, sz, QQuickWindow::TextureIsOpaque); -#else - // TODO(anyone) Use createTextureFromNativeObject - // https://github.com/gazebosim/gz-gui/issues/113 -#ifndef _WIN32 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - this->texture = this->window->createTextureFromId( - newId, sz, QQuickWindow::TextureIsOpaque); -#ifndef _WIN32 -# pragma GCC diagnostic pop -#endif - -#endif - this->setTexture(this->texture); - - this->markDirty(DirtyMaterial); - - // This will notify the rendering thread that the texture is now being - // rendered and it can start rendering to the other one. - emit TextureInUse(); - } -} - -///////////////////////////////////////////////// -RenderWindowItem::RenderWindowItem(QQuickItem *_parent) - : QQuickItem(_parent), dataPtr(new RenderWindowItemPrivate) -{ - this->setAcceptedMouseButtons(Qt::AllButtons); - this->setFlag(ItemHasContents); - this->dataPtr->renderThread = new RenderThread(); -} - -///////////////////////////////////////////////// -RenderWindowItem::~RenderWindowItem() -{ -} - -///////////////////////////////////////////////// -void RenderWindowItem::Ready() -{ - this->dataPtr->renderThread->surface = new QOffscreenSurface(); - this->dataPtr->renderThread->surface->setFormat( - this->dataPtr->renderThread->context->format()); - this->dataPtr->renderThread->surface->create(); - - this->dataPtr->renderThread->ignRenderer.textureSize = - QSize(std::max({this->width(), 1.0}), std::max({this->height(), 1.0})); - - this->dataPtr->renderThread->moveToThread(this->dataPtr->renderThread); - - this->connect(this, &QObject::destroyed, - this->dataPtr->renderThread, &RenderThread::ShutDown, - Qt::QueuedConnection); - - this->connect(this, &QQuickItem::widthChanged, - this->dataPtr->renderThread, &RenderThread::SizeChanged); - this->connect(this, &QQuickItem::heightChanged, - this->dataPtr->renderThread, &RenderThread::SizeChanged); - - this->dataPtr->renderThread->start(); - this->update(); -} - -///////////////////////////////////////////////// -QSGNode *RenderWindowItem::updatePaintNode(QSGNode *_node, - QQuickItem::UpdatePaintNodeData * /*_data*/) -{ - TextureNode *node = static_cast(_node); - - if (!this->dataPtr->renderThread->context) - { - QOpenGLContext *current = this->window()->openglContext(); - // Some GL implementations require that the currently bound context is - // made non-current before we set up sharing, so we doneCurrent here - // and makeCurrent down below while setting up our own context. - current->doneCurrent(); - - this->dataPtr->renderThread->context = new QOpenGLContext(); - this->dataPtr->renderThread->context->setFormat(current->format()); - this->dataPtr->renderThread->context->setShareContext(current); - this->dataPtr->renderThread->context->create(); - this->dataPtr->renderThread->context->moveToThread( - this->dataPtr->renderThread); - - current->makeCurrent(this->window()); - - QMetaObject::invokeMethod(this, "Ready"); - return nullptr; - } - - if (!node) - { - node = new TextureNode(this->window()); - - // Set up connections to get the production of render texture in sync with - // vsync on the rendering thread. - // - // When a new texture is ready on the rendering thread, we use a direct - // connection to the texture node to let it know a new texture can be used. - // The node will then emit PendingNewTexture which we bind to - // QQuickWindow::update to schedule a redraw. - // - // When the scene graph starts rendering the next frame, the PrepareNode() - // function is used to update the node with the new texture. Once it - // completes, it emits TextureInUse() which we connect to the rendering - // thread's RenderNext() to have it start producing content into its render - // texture. - // - // This rendering pipeline is throttled by vsync on the scene graph - // rendering thread. - - this->connect(this->dataPtr->renderThread, &RenderThread::TextureReady, - node, &TextureNode::NewTexture, Qt::DirectConnection); - this->connect(node, &TextureNode::PendingNewTexture, this->window(), - &QQuickWindow::update, Qt::QueuedConnection); - this->connect(this->window(), &QQuickWindow::beforeRendering, node, - &TextureNode::PrepareNode, Qt::DirectConnection); - this->connect(node, &TextureNode::TextureInUse, this->dataPtr->renderThread, - &RenderThread::RenderNext, Qt::QueuedConnection); - - // Get the production of FBO textures started.. - QMetaObject::invokeMethod(this->dataPtr->renderThread, "RenderNext", - Qt::QueuedConnection); - } - - node->setRect(this->boundingRect()); - - return node; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetBackgroundColor(const math::Color &_color) -{ - this->dataPtr->renderThread->ignRenderer.backgroundColor = _color; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetAmbientLight(const math::Color &_ambient) -{ - this->dataPtr->renderThread->ignRenderer.ambientLight = _ambient; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetEngineName(const std::string &_name) -{ - this->dataPtr->renderThread->ignRenderer.engineName = _name; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneName(const std::string &_name) -{ - this->dataPtr->renderThread->ignRenderer.sceneName = _name; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetCameraPose(const math::Pose3d &_pose) -{ - this->dataPtr->renderThread->ignRenderer.cameraPose = _pose; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneService(const std::string &_service) -{ - this->dataPtr->renderThread->ignRenderer.sceneService = _service; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetPoseTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->ignRenderer.poseTopic = _topic; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetDeletionTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->ignRenderer.deletionTopic = _topic; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->ignRenderer.sceneTopic = _topic; -} - -///////////////////////////////////////////////// -Scene3D::Scene3D() - : Plugin(), dataPtr(new Scene3DPrivate) -{ - gzwarn << "This plugin is deprecated on ign-gui v6 and will be removed on " - << "ign-gui v7. Use MinimalScene + TransportSceneManager instead." - << std::endl; - - qmlRegisterType("RenderWindow", 1, 0, "RenderWindow"); -} - - -///////////////////////////////////////////////// -Scene3D::~Scene3D() -{ -} - -///////////////////////////////////////////////// -void Scene3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) -{ - RenderWindowItem *renderWindow = - this->PluginItem()->findChild(); - if (!renderWindow) - { - gzerr << "Unable to find Render Window item. " - << "Render window will not be created" << std::endl; - return; - } - renderWindow->SetErrorCb(std::bind(&Scene3D::SetLoadingError, this, - std::placeholders::_1)); - - if (this->title.empty()) - this->title = "3D Scene"; - - // Custom parameters - if (_pluginElem) - { - auto elem = _pluginElem->FirstChildElement("engine"); - if (nullptr != elem && nullptr != elem->GetText()) - { - renderWindow->SetEngineName(elem->GetText()); - // there is a problem with displaying ogre2 render textures that are in - // sRGB format. Workaround for now is to apply gamma correction manually. - // There maybe a better way to solve the problem by making OpenGL calls.. - if (elem->GetText() == std::string("ogre2")) - this->PluginItem()->setProperty("gammaCorrect", true); - } - - elem = _pluginElem->FirstChildElement("scene"); - if (nullptr != elem && nullptr != elem->GetText()) - renderWindow->SetSceneName(elem->GetText()); - - elem = _pluginElem->FirstChildElement("ambient_light"); - if (nullptr != elem && nullptr != elem->GetText()) - { - math::Color ambient; - std::stringstream colorStr; - colorStr << std::string(elem->GetText()); - colorStr >> ambient; - renderWindow->SetAmbientLight(ambient); - } - - elem = _pluginElem->FirstChildElement("background_color"); - if (nullptr != elem && nullptr != elem->GetText()) - { - math::Color bgColor; - std::stringstream colorStr; - colorStr << std::string(elem->GetText()); - colorStr >> bgColor; - renderWindow->SetBackgroundColor(bgColor); - } - - elem = _pluginElem->FirstChildElement("camera_pose"); - if (nullptr != elem && nullptr != elem->GetText()) - { - math::Pose3d pose; - std::stringstream poseStr; - poseStr << std::string(elem->GetText()); - poseStr >> pose; - renderWindow->SetCameraPose(pose); - } - - elem = _pluginElem->FirstChildElement("service"); - if (nullptr != elem && nullptr != elem->GetText()) - { - std::string service = elem->GetText(); - renderWindow->SetSceneService(service); - } - - elem = _pluginElem->FirstChildElement("pose_topic"); - if (nullptr != elem && nullptr != elem->GetText()) - { - std::string topic = elem->GetText(); - renderWindow->SetPoseTopic(topic); - } - - elem = _pluginElem->FirstChildElement("deletion_topic"); - if (nullptr != elem && nullptr != elem->GetText()) - { - std::string topic = elem->GetText(); - renderWindow->SetDeletionTopic(topic); - } - - elem = _pluginElem->FirstChildElement("scene_topic"); - if (nullptr != elem && nullptr != elem->GetText()) - { - std::string topic = elem->GetText(); - renderWindow->SetSceneTopic(topic); - } - } -} - -///////////////////////////////////////////////// -void RenderWindowItem::OnHovered(const gz::math::Vector2i &_hoverPos) -{ - this->dataPtr->renderThread->ignRenderer.NewHoverEvent(_hoverPos); -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetErrorCb(std::function _cb) -{ - this->dataPtr->renderThread->SetErrorCb(_cb); -} - -///////////////////////////////////////////////// -void RenderWindowItem::mousePressEvent(QMouseEvent *_e) -{ - auto event = convert(*_e); - event.SetPressPos(event.Pos()); - this->dataPtr->mouseEvent = event; - - this->dataPtr->renderThread->ignRenderer.NewMouseEvent( - this->dataPtr->mouseEvent); -} - -//////////////////////////////////////////////// -void RenderWindowItem::mouseReleaseEvent(QMouseEvent *_e) -{ - this->dataPtr->mouseEvent = convert(*_e); - - this->dataPtr->renderThread->ignRenderer.NewMouseEvent( - this->dataPtr->mouseEvent); -} - -//////////////////////////////////////////////// -void RenderWindowItem::mouseMoveEvent(QMouseEvent *_e) -{ - auto event = convert(*_e); - event.SetPressPos(this->dataPtr->mouseEvent.PressPos()); - - if (!event.Dragging()) - return; - - auto dragInt = event.Pos() - this->dataPtr->mouseEvent.Pos(); - auto dragDistance = math::Vector2d(dragInt.X(), dragInt.Y()); - - this->dataPtr->renderThread->ignRenderer.NewMouseEvent(event, dragDistance); - this->dataPtr->mouseEvent = event; -} - -//////////////////////////////////////////////// -void RenderWindowItem::wheelEvent(QWheelEvent *_e) -{ - this->dataPtr->mouseEvent.SetType(common::MouseEvent::SCROLL); -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - this->dataPtr->mouseEvent.SetPos(_e->x(), _e->y()); -#else - this->dataPtr->mouseEvent.SetPos(_e->position().x(), _e->position().y()); -#endif - double scroll = (_e->angleDelta().y() > 0) ? -1.0 : 1.0; - this->dataPtr->renderThread->ignRenderer.NewMouseEvent( - this->dataPtr->mouseEvent, math::Vector2d(scroll, scroll)); -} - -//////////////////////////////////////////////// -void RenderWindowItem::keyPressEvent(QKeyEvent *_event) -{ - this->HandleKeyPress(_event); -} - -//////////////////////////////////////////////// -void RenderWindowItem::keyReleaseEvent(QKeyEvent *_event) -{ - this->HandleKeyRelease(_event); -} - -//////////////////////////////////////////////// -void RenderWindowItem::HandleKeyPress(QKeyEvent *_e) -{ - this->dataPtr->renderThread->ignRenderer.HandleKeyPress(_e); -} - -//////////////////////////////////////////////// -void RenderWindowItem::HandleKeyRelease(QKeyEvent *_e) -{ - this->dataPtr->renderThread->ignRenderer.HandleKeyRelease(_e); -} - -///////////////////////////////////////////////// -bool Scene3D::eventFilter(QObject *_obj, QEvent *_event) -{ - if (_event->type() == QEvent::KeyPress) - { - QKeyEvent *keyEvent = static_cast(_event); - if (keyEvent) - { - auto renderWindow = this->PluginItem()->findChild(); - renderWindow->HandleKeyPress(keyEvent); - } - } - else if (_event->type() == QEvent::KeyRelease) - { - QKeyEvent *keyEvent = static_cast(_event); - if (keyEvent) - { - auto renderWindow = this->PluginItem()->findChild(); - renderWindow->HandleKeyRelease(keyEvent); - } - } - - // Standard event processing - return QObject::eventFilter(_obj, _event); -} - -///////////////////////////////////////////////// -void Scene3D::OnHovered(int _mouseX, int _mouseY) -{ - auto renderWindow = this->PluginItem()->findChild(); - renderWindow->OnHovered({_mouseX, _mouseY}); -} - -///////////////////////////////////////////////// -void Scene3D::OnFocusWindow() -{ - auto renderWindow = this->PluginItem()->findChild(); - renderWindow->forceActiveFocus(); -} - -///////////////////////////////////////////////// -QString Scene3D::LoadingError() const -{ - return this->loadingError; -} - -///////////////////////////////////////////////// -void Scene3D::SetLoadingError(const QString &_loadingError) -{ - this->loadingError = _loadingError; - this->LoadingErrorChanged(); -} - -// Register this plugin -GZ_ADD_PLUGIN(gz::gui::plugins::Scene3D, - gz::gui::Plugin) diff --git a/src/plugins/scene3d/Scene3D.hh b/src/plugins/scene3d/Scene3D.hh deleted file mode 100644 index 0068a00f7..000000000 --- a/src/plugins/scene3d/Scene3D.hh +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright (C) 2017 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 GZ_GUI_PLUGINS_SCENE3D_HH_ -#define GZ_GUI_PLUGINS_SCENE3D_HH_ - -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "gz/gui/qt.h" -#include "gz/gui/Plugin.hh" - -namespace gz -{ -namespace gui -{ -namespace plugins -{ - class IgnRendererPrivate; - class RenderWindowItemPrivate; - class Scene3DPrivate; - - /// \brief Creates a Gazebo rendering scene and user camera. - /// It is possible to orbit the camera around the scene with - /// the mouse. Use other plugins to manage objects in the scene. - /// - /// Only one plugin displaying a Gazebo Rendering scene can be used at a - /// time. - /// - /// ## Configuration - /// - /// * \ : Optional render engine name, defaults to 'ogre'. If another - /// engine is already loaded, that will be used, because only - /// one engine is supported at a time currently. - /// * \ : Optional scene name, defaults to 'scene'. The plugin will - /// create a scene with this name if there isn't one yet. If - /// there is already one, a new camera is added to it. - /// * \ : Optional color for ambient light, defaults to - /// (0.3, 0.3, 0.3, 1.0) - /// * \ : Optional background color, defaults to - /// (0.3, 0.3, 0.3, 1.0) - /// * \ : Optional starting pose for the camera, defaults to - /// (0, 0, 5, 0, 0, 0) - class Scene3D : public Plugin - { - Q_OBJECT - - /// \brief Loading error message - Q_PROPERTY( - QString loadingError - READ LoadingError - WRITE SetLoadingError - NOTIFY LoadingErrorChanged - ) - - /// \brief Constructor - public: Scene3D(); - - /// \brief Destructor - public: virtual ~Scene3D(); - - /// \brief Callback when the mouse hovers to a new position. - /// \param[in] _mouseX x coordinate of the hovered mouse position. - /// \param[in] _mouseY y coordinate of the hovered mouse position. - public slots: void OnHovered(int _mouseX, int _mouseY); - - /// \brief Callback when the mouse enters the render window to - /// focus the window for mouse/key events - public slots: void OnFocusWindow(); - - // Documentation inherited - protected: bool eventFilter(QObject *_obj, QEvent *_event) override; - - // Documentation inherited - public: virtual void LoadConfig(const tinyxml2::XMLElement *_pluginElem) - override; - - /// \brief Get the loading error string. - /// \return String explaining the loading error. If empty, there's no error. - public: Q_INVOKABLE QString LoadingError() const; - - /// \brief Set the loading error message. - /// \param[in] _loadingError Error message. - public: Q_INVOKABLE void SetLoadingError(const QString &_loadingError); - - /// \brief Notify that loading error has changed - signals: void LoadingErrorChanged(); - - /// \brief Loading error message - public: QString loadingError; - - /// \internal - /// \brief Pointer to private data. - private: std::unique_ptr dataPtr; - }; - - /// \brief Ign-rendering renderer. - /// All ign-rendering calls should be performed inside this class as it makes - /// sure that opengl calls in the underlying render engine do not interfere - /// with QtQuick's opengl render operations. The main Render function will - /// render to an offscreen texture and notify via signal and slots when it's - /// ready to be displayed. - class IgnRenderer - { - /// \brief Constructor - public: IgnRenderer(); - - /// \brief Destructor - public: ~IgnRenderer(); - - /// \brief Main render function - public: void Render(); - - /// \brief Initialize the render engine - /// \return Error message if initialization failed. If empty, no errors - /// occurred. - public: std::string Initialize(); - - /// \brief Destroy camera associated with this renderer - public: void Destroy(); - - /// \brief New mouse event triggered - /// \param[in] _e New mouse event - /// \param[in] _drag Mouse move distance - public: void NewMouseEvent(const common::MouseEvent &_e, - const math::Vector2d &_drag = math::Vector2d::Zero); - - /// \brief New hover event triggered. - /// \param[in] _hoverPos Mouse hover screen position - public: void NewHoverEvent(const math::Vector2i &_hoverPos); - - /// \brief Handle key press event for snapping - /// \param[in] _e The key event to process. - public: void HandleKeyPress(QKeyEvent *_e); - - /// \brief Handle key release event for snapping - /// \param[in] _e The key event to process. - public: void HandleKeyRelease(QKeyEvent *_e); - - /// \brief Handle mouse event for view control - private: void HandleMouseEvent(); - - /// \brief Handle mouse event for view control - private: void HandleMouseViewControl(); - - /// \brief Broadcasts the currently hovered 3d scene location. - private: void BroadcastHoverPos(); - - /// \brief Broadcasts a left click within the scene - private: void BroadcastLeftClick(); - - /// \brief Broadcasts a right click within the scene - private: void BroadcastRightClick(); - - /// \brief Broadcasts the current key release - private: void BroadcastKeyRelease(); - - /// \brief Broadcasts the current key press - private: void BroadcastKeyPress(); - - /// \brief Retrieve the first point on a surface in the 3D scene hit by a - /// ray cast from the given 2D screen coordinates. - /// \param[in] _screenPos 2D coordinates on the screen, in pixels. - /// \return 3D coordinates of a point in the 3D scene. - private: math::Vector3d ScreenToScene(const math::Vector2i &_screenPos) - const; - - /// \brief Render texture id - public: GLuint textureId = 0u; - - /// \brief Render engine to use - public: std::string engineName = "ogre"; - - /// \brief Unique scene name - public: std::string sceneName = "scene"; - - /// \brief Initial Camera pose - public: math::Pose3d cameraPose = math::Pose3d(0, 0, 2, 0, 0.4, 0); - - /// \brief Scene background color - public: math::Color backgroundColor = math::Color::Black; - - /// \brief Ambient color - public: math::Color ambientLight = math::Color(0.3f, 0.3f, 0.3f, 1.0f); - - /// \brief True if engine has been initialized; - public: bool initialized = false; - - /// \brief Render texture size - public: QSize textureSize = QSize(1024, 1024); - - /// \brief Flag to indicate texture size has changed. - public: bool textureDirty = false; - - /// \brief Scene service. If not empty, a request will be made to get the - /// scene information using this service and the renderer will populate the - /// scene based on the response data - public: std::string sceneService; - - /// \brief Scene pose topic. If not empty, a node will subcribe to this - /// topic to get pose updates of objects in the scene - public: std::string poseTopic; - - /// \brief Ign-transport deletion topic name - public: std::string deletionTopic; - - /// \brief Ign-transport scene topic name - /// New scene messages will be published to this topic when an entities are - /// added - public: std::string sceneTopic; - - /// \internal - /// \brief Pointer to private data. - private: std::unique_ptr dataPtr; - }; - - /// \brief Rendering thread - class RenderThread : public QThread - { - Q_OBJECT - - /// \brief Constructor - public: RenderThread(); - - /// \brief Render the next frame - public slots: void RenderNext(); - - /// \brief Shutdown the thread and the render engine - public slots: void ShutDown(); - - /// \brief Slot called to update render texture size - public slots: void SizeChanged(); - - /// \brief Signal to indicate that a frame has been rendered and ready - /// to be displayed - /// \param[in] _id GLuid of the opengl texture - /// \param[in] _size Size of the texture - signals: void TextureReady(int _id, const QSize &_size); - - /// \brief Set a callback to be called in case there are errors. - /// \param[in] _cb Error callback - public: void SetErrorCb(std::function _cb); - - /// \brief Function to be called if there are errors. - public: std::function errorCb; - - /// \brief Offscreen surface to render to - public: QOffscreenSurface *surface = nullptr; - - /// \brief OpenGL context to be passed to the render engine - public: QOpenGLContext *context = nullptr; - - /// \brief Ign-rendering renderer - public: IgnRenderer ignRenderer; - }; - - - /// \brief A QQUickItem that manages the render window - class RenderWindowItem : public QQuickItem - { - Q_OBJECT - - /// \brief Constructor - /// \param[in] _parent Parent item - public: explicit RenderWindowItem(QQuickItem *_parent = nullptr); - - /// \brief Destructor - public: virtual ~RenderWindowItem(); - - /// \brief Set background color of render window - /// \param[in] _color Color of render window background - public: void SetBackgroundColor(const math::Color &_color); - - /// \brief Set ambient light of render window - /// \param[in] _ambient Color of ambient light - public: void SetAmbientLight(const math::Color &_ambient); - - /// \brief Set engine name used to create the render window - /// \param[in] _name Name of render engine - public: void SetEngineName(const std::string &_name); - - /// \brief Set name of scene created inside the render window - /// \param[in] _name Name of scene - public: void SetSceneName(const std::string &_name); - - /// \brief Set the initial pose the render window camera - /// \param[in] _pose Initical camera pose - public: void SetCameraPose(const math::Pose3d &_pose); - - /// \brief Set scene service to use in this render window - /// A service call will be made using ign-transport to get scene - /// data using this service - /// \param[in] _service Scene service name - public: void SetSceneService(const std::string &_service); - - /// \brief Set pose topic to use for updating objects in the scene - /// The renderer will subscribe to this topic to get pose messages of - /// visuals in the scene - /// \param[in] _topic Pose topic - public: void SetPoseTopic(const std::string &_topic); - - /// \brief Set deletion topic to use for deleting objects from the scene - /// The renderer will subscribe to this topic to get notified when entities - /// in the scene get deleted - /// \param[in] _topic Deletion topic - public: void SetDeletionTopic(const std::string &_topic); - - /// \brief Set the scene topic to use for updating objects in the scene - /// The renderer will subscribe to this topic to get updates scene messages - /// \param[in] _topic Scene topic - public: void SetSceneTopic(const std::string &_topic); - - /// \brief Called when the mouse hovers to a new position. - /// \param[in] _hoverPos 2D coordinates of the hovered mouse position on - /// the render window. - public: void OnHovered(const gz::math::Vector2i &_hoverPos); - - /// \brief Slot called when thread is ready to be started - public Q_SLOTS: void Ready(); - - /// \brief Handle key press event for snapping - /// \param[in] _e The key event to process. - public: void HandleKeyPress(QKeyEvent *_e); - - /// \brief Handle key release event for snapping - /// \param[in] _e The key event to process. - public: void HandleKeyRelease(QKeyEvent *_e); - - // Documentation inherited - protected: virtual void mousePressEvent(QMouseEvent *_e) override; - - // Documentation inherited - protected: virtual void mouseReleaseEvent(QMouseEvent *_e) override; - - // Documentation inherited - protected: virtual void mouseMoveEvent(QMouseEvent *_e) override; - - // Documentation inherited - protected: virtual void wheelEvent(QWheelEvent *_e) override; - - // Documentation inherited - protected: virtual void keyPressEvent(QKeyEvent *_event) override; - - // Documentation inherited - protected: virtual void keyReleaseEvent(QKeyEvent *_event) override; - - /// \brief Overrides the paint event to render the render engine - /// camera view - /// \param[in] _oldNode The node passed in previous updatePaintNode - /// function. It represents the visual representation of the item. - /// \param[in] _data The node transformation data. - /// \return Updated node. - private: QSGNode *updatePaintNode(QSGNode *_oldNode, - QQuickItem::UpdatePaintNodeData *_data) override; - - /// \brief Set a callback to be called in case there are errors. - /// \param[in] _cb Error callback - public: void SetErrorCb(std::function _cb); - - /// \internal - /// \brief Pointer to private data. - private: std::unique_ptr dataPtr; - }; - - /// \brief Texture node for displaying the render texture from ign-renderer - class TextureNode : public QObject, public QSGSimpleTextureNode - { - Q_OBJECT - - /// \brief Constructor - /// \param[in] _window Parent window - public: explicit TextureNode(QQuickWindow *_window); - - /// \brief Destructor - public: ~TextureNode() override; - - /// \brief This function gets called on the FBO rendering thread and will - /// store the texture id and size and schedule an update on the window. - /// \param[in] _id OpenGL render texture Id - /// \param[in] _size Texture size - public slots: void NewTexture(int _id, const QSize &_size); - - /// \brief Before the scene graph starts to render, we update to the - /// pending texture - public slots: void PrepareNode(); - - /// \brief Signal emitted when the texture is being rendered and renderer - /// can start rendering next frame - signals: void TextureInUse(); - - /// \brief Signal emitted when a new texture is ready to trigger window - /// update - signals: void PendingNewTexture(); - - /// \brief OpenGL texture id - public: int id = 0; - - /// \brief Texture size - public: QSize size = QSize(0, 0); - - /// \brief Mutex to protect the texture variables - public: QMutex mutex; - - /// \brief Qt's scene graph texture - public: QSGTexture *texture = nullptr; - - /// \brief Qt quick window - public: QQuickWindow *window = nullptr; - }; -} -} -} - -#endif diff --git a/src/plugins/scene3d/Scene3D.qml b/src/plugins/scene3d/Scene3D.qml deleted file mode 100644 index 817dc9413..000000000 --- a/src/plugins/scene3d/Scene3D.qml +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2018 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 QtGraphicalEffects 1.0 -import QtQuick 2.9 -import QtQuick.Controls 2.0 -import QtQuick.Layouts 1.3 -import RenderWindow 1.0 - -Rectangle { - Layout.minimumWidth: 200 - Layout.minimumHeight: 200 - anchors.fill: parent - - /** - * True to enable gamma correction - */ - property bool gammaCorrect: false - - /** - * Get mouse position on 3D widget - */ - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - visible: Scene3D.loadingError.length == 0 - onEntered: { - Scene3D.OnFocusWindow() - } - onPositionChanged: { - Scene3D.OnHovered(mouseArea.mouseX, mouseArea.mouseY); - } - } - - RenderWindow { - id: renderWindow - objectName: "rw" - anchors.fill: parent - visible: Scene3D.loadingError.length == 0 - } - - /* - * Gamma correction for sRGB output. Enabled when engine is set to ogre2 - */ - GammaAdjust { - anchors.fill: renderWindow - source: renderWindow - gamma: 2.4 - enabled: gammaCorrect - visible: gammaCorrect - } - - onParentChanged: { - if (undefined === parent) - return; - - width = Qt.binding(function() {return parent.parent.width}) - height = Qt.binding(function() {return parent.parent.height}) - } - - Label { - anchors.fill: parent - anchors.margins: 10 - text: Scene3D.loadingError - visible: (Scene3D.loadingError.length > 0); - wrapMode: Text.WordWrap - } -} diff --git a/src/plugins/scene3d/Scene3D.qrc b/src/plugins/scene3d/Scene3D.qrc deleted file mode 100644 index 0c4c25de7..000000000 --- a/src/plugins/scene3d/Scene3D.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - Scene3D.qml - - diff --git a/test/integration/scene3d_config.cc b/test/integration/scene3d_config.cc deleted file mode 100644 index f6af73eaf..000000000 --- a/test/integration/scene3d_config.cc +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2017 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_config.hh" -#include "../helpers/TestHelper.hh" -#include "gz/gui/Application.hh" -#include "gz/gui/GuiEvents.hh" -#include "gz/gui/Plugin.hh" -#include "gz/gui/MainWindow.hh" - -int g_argc = 1; -char* g_argv[] = -{ - reinterpret_cast(const_cast("./Scene3d_config_TEST")), -}; - -using namespace gz; -using namespace gui; - -///////////////////////////////////////////////// -TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) -{ - common::Console::SetVerbosity(4); - - Application app(g_argc, g_argv); - app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); - - // Load plugin - const char *pluginStr = - "" - "ogre" - "banana" - "1.0 0 0" - "0 1 0" - "1 2 3 0 0 1.57" - ""; - - tinyxml2::XMLDocument pluginDoc; - pluginDoc.Parse(pluginStr); - EXPECT_TRUE(app.LoadPlugin("Scene3D", - pluginDoc.FirstChildElement("plugin"))); - - // Get main window - auto win = app.findChild(); - ASSERT_NE(nullptr, win); - - // Show, but don't exec, so we don't block - win->QuickWindow()->show(); - - // Check scene - auto engine = rendering::engine("ogre"); - ASSERT_NE(nullptr, engine); - - int sleep = 0; - int maxSleep = 30; - while (0 == engine->SceneCount() && sleep < maxSleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - - EXPECT_EQ(1u, engine->SceneCount()); - auto scene = engine->SceneByName("banana"); - ASSERT_NE(nullptr, scene); - - EXPECT_EQ(math::Color(0, 1, 0), scene->BackgroundColor()); - EXPECT_EQ(math::Color(1, 0, 0), scene->AmbientLight()); - - auto root = scene->RootVisual(); - ASSERT_NE(nullptr, root); - EXPECT_EQ(1u, root->ChildCount()); - - // Check camera - auto camera = std::dynamic_pointer_cast( - root->ChildByIndex(0)); - ASSERT_NE(nullptr, camera); - - EXPECT_EQ(math::Pose3d(1, 2, 3, 0, 0, 1.57), camera->WorldPose()); - - // Cleanup - auto plugins = win->findChildren(); - for (auto & p : plugins) - { - auto pluginName = p->CardItem()->objectName(); - app.RemovePlugin(pluginName.toStdString()); - } - win->QuickWindow()->close(); - engine->DestroyScene(scene); -} diff --git a/test/integration/scene3d_events.cc b/test/integration/scene3d_events.cc deleted file mode 100644 index fa3cd1cae..000000000 --- a/test/integration/scene3d_events.cc +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2017 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_config.hh" // NOLINT(build/include) -#include "../helpers/TestHelper.hh" -#include "gz/gui/Application.hh" -#include "gz/gui/GuiEvents.hh" -#include "gz/gui/Plugin.hh" -#include "gz/gui/MainWindow.hh" - -int g_argc = 1; -char* g_argv[] = -{ - reinterpret_cast(const_cast("./Scene3d_events_TEST")), -}; - -using namespace gz; -using namespace gui; - -///////////////////////////////////////////////// -TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Events)) -{ - common::Console::SetVerbosity(4); - - Application app(g_argc, g_argv); - app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); - - // Load plugin - const char *pluginStr = - "" - "ogre" - "banana" - "1.0 0 0" - "0 1 0" - "1 2 3 0 0 1.57" - ""; - - tinyxml2::XMLDocument pluginDoc; - pluginDoc.Parse(pluginStr); - EXPECT_TRUE(app.LoadPlugin("Scene3D", - pluginDoc.FirstChildElement("plugin"))); - - // Get main window - auto win = app.findChild(); - ASSERT_NE(nullptr, win); - - // Show, but don't exec, so we don't block - win->QuickWindow()->show(); - - // Flags to check if events were received - bool receivedRenderEvent{false}; - bool receivedRightEvent{false}; - bool receivedLeftEvent{false}; - bool receivedRightAltEvent{false}; - bool receivedRightControlEvent{false}; - bool receivedRightShiftEvent{false}; - bool receivedLeftAltEvent{false}; - bool receivedLeftControlEvent{false}; - bool receivedLeftShiftEvent{false}; - bool receivedHoverEvent{false}; - bool receivedKeyPressEvent{false}; - bool receivedKeyPressEventAlt{false}; - bool receivedKeyPressEventControl{false}; - bool receivedKeyPressEventShift{false}; - bool receivedKeyReleaseEvent{false}; - bool receivedKeyReleaseEventAlt{false}; - bool receivedKeyReleaseEventControl{false}; - bool receivedKeyReleaseEventShift{false}; - - // Position vectors reported by click events - math::Vector3d leftClickPoint, rightClickPoint; - // key pressed or released - int keyPressedValue, keyReleasedValue; - - // Helper to filter events - auto testHelper = std::make_unique(); - testHelper->forwardEvent = [&](QEvent *_event) - { - if (_event->type() == events::Render::kType) - { - receivedRenderEvent = true; - } - else if (_event->type() == events::RightClickToScene::kType) - { - receivedRightEvent = true; - auto rightClickToScene = static_cast(_event); - rightClickPoint = rightClickToScene->Point(); - } - else if (_event->type() == events::RightClickOnScene::kType) - { - auto rightClickOnScene = static_cast(_event); - receivedRightAltEvent = rightClickOnScene->Mouse().Alt(); - receivedRightControlEvent = rightClickOnScene->Mouse().Control(); - receivedRightShiftEvent = rightClickOnScene->Mouse().Shift(); - } - else if (_event->type() == events::LeftClickToScene::kType) - { - receivedLeftEvent = true; - auto leftClickToScene = static_cast(_event); - leftClickPoint = leftClickToScene->Point(); - } - else if (_event->type() == events::LeftClickOnScene::kType) - { - auto leftClickOnScene = static_cast(_event); - receivedLeftAltEvent = leftClickOnScene->Mouse().Alt(); - receivedLeftControlEvent = leftClickOnScene->Mouse().Control(); - receivedLeftShiftEvent = leftClickOnScene->Mouse().Shift(); - } - else if (_event->type() == events::HoverToScene::kType) - { - receivedHoverEvent = true; - } - else if (_event->type() == events::KeyReleaseOnScene::kType) - { - receivedKeyReleaseEvent = true; - auto keyReleased = static_cast(_event); - keyReleasedValue = keyReleased->Key().Key(); - receivedKeyReleaseEventAlt = keyReleased->Key().Alt(); - receivedKeyReleaseEventControl = keyReleased->Key().Control(); - receivedKeyReleaseEventShift = keyReleased->Key().Shift(); - } - else if (_event->type() == events::KeyPressOnScene::kType) - { - receivedKeyPressEvent = true; - auto keyPress = static_cast(_event); - keyPressedValue = keyPress->Key().Key(); - receivedKeyPressEventAlt = keyPress->Key().Alt(); - receivedKeyPressEventControl = keyPress->Key().Control(); - receivedKeyPressEventShift = keyPress->Key().Shift(); - } - }; - - int sleep = 0; - int maxSleep = 30; - while (!receivedRenderEvent && sleep < maxSleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - sleep = 0; - while (!receivedHoverEvent && sleep < maxSleep) - { - QTest::mouseMove(win->QuickWindow(), QPoint(70, 100), -1); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - sleep = 0; - while (!receivedRightEvent && sleep < maxSleep) - { - QTest::mouseClick(win->QuickWindow(), Qt::RightButton, Qt::ShiftModifier); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - sleep = 0; - while (!receivedLeftEvent && sleep < maxSleep) - { - QTest::mouseClick(win->QuickWindow(), Qt::LeftButton, Qt::AltModifier); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - sleep = 0; - while (!receivedKeyPressEvent && sleep < maxSleep) - { - QTest::keyPress(win->QuickWindow(), Qt::Key_A, Qt::AltModifier); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - sleep = 0; - while (!receivedKeyReleaseEvent && sleep < maxSleep) - { - QTest::keyRelease(win->QuickWindow(), Qt::Key_Escape, Qt::NoModifier); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - - EXPECT_TRUE(receivedRenderEvent); - EXPECT_TRUE(receivedLeftEvent); - EXPECT_TRUE(receivedRightEvent); - EXPECT_TRUE(receivedHoverEvent); - EXPECT_TRUE(receivedLeftAltEvent); - EXPECT_FALSE(receivedLeftControlEvent); - EXPECT_FALSE(receivedLeftShiftEvent); - EXPECT_FALSE(receivedRightAltEvent); - EXPECT_FALSE(receivedRightControlEvent); - EXPECT_TRUE(receivedRightShiftEvent); - - EXPECT_EQ(leftClickPoint, rightClickPoint); - EXPECT_NEAR(1.0, leftClickPoint.X(), 1e-3); - EXPECT_NEAR(11.942695, leftClickPoint.Y(), 1e-1); - EXPECT_NEAR(4.159424, leftClickPoint.Z(), 0.5); - - EXPECT_TRUE(receivedKeyReleaseEvent); - EXPECT_FALSE(receivedKeyReleaseEventAlt); - EXPECT_FALSE(receivedKeyReleaseEventControl); - EXPECT_FALSE(receivedKeyReleaseEventShift); - EXPECT_EQ(Qt::Key_Escape, keyReleasedValue); - EXPECT_TRUE(receivedKeyPressEvent); - EXPECT_TRUE(receivedKeyPressEventAlt); - EXPECT_FALSE(receivedKeyPressEventControl); - EXPECT_FALSE(receivedKeyPressEventShift); - EXPECT_EQ(Qt::Key_A, keyPressedValue); - - // Cleanups - auto plugins = win->findChildren(); - for (auto & p : plugins) - { - auto pluginName = p->CardItem()->objectName(); - app.RemovePlugin(pluginName.toStdString()); - } - win->QuickWindow()->close(); -} diff --git a/test/integration/scene3d_load.cc b/test/integration/scene3d_load.cc deleted file mode 100644 index 4e5ca20db..000000000 --- a/test/integration/scene3d_load.cc +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2017 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_config.hh" -#include "../helpers/TestHelper.hh" -#include "gz/gui/Application.hh" -#include "gz/gui/GuiEvents.hh" -#include "gz/gui/Plugin.hh" -#include "gz/gui/MainWindow.hh" - -int g_argc = 1; -char* g_argv[] = -{ - reinterpret_cast(const_cast("./Scene3d_load_TEST")), -}; - -using namespace gz; -using namespace gui; - -///////////////////////////////////////////////// -TEST(Scene3DTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Load)) -{ - common::Console::SetVerbosity(4); - - Application app(g_argc, g_argv); - app.AddPluginPath(common::joinPaths(PROJECT_BINARY_PATH, "lib")); - - EXPECT_TRUE(app.LoadPlugin("Scene3D")); - - // Get main window - auto win = app.findChild(); - ASSERT_NE(nullptr, win); - - // Get plugin - auto plugins = win->findChildren(); - EXPECT_EQ(plugins.size(), 1); - - auto plugin = plugins[0]; - EXPECT_EQ(plugin->Title(), "3D Scene"); - - // Cleanup - plugins.clear(); - win->QuickWindow()->close(); -} From 961f98b0d9a69091e8de4601dc093b6673428a68 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 28 Jun 2022 17:26:25 -0700 Subject: [PATCH 04/11] Remove message header Signed-off-by: Michael Carroll --- test/integration/transport_scene_manager.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/transport_scene_manager.cc b/test/integration/transport_scene_manager.cc index 0dbd77af0..7f97bd348 100644 --- a/test/integration/transport_scene_manager.cc +++ b/test/integration/transport_scene_manager.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include From 7327d26669d1d638cfd98991e261f34ae3806d0b Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Wed, 29 Jun 2022 14:58:46 -0700 Subject: [PATCH 05/11] Bump everything to ogre2 by default Signed-off-by: Michael Carroll --- src/Helpers_TEST.cc | 4 ++-- src/plugins/minimal_scene/MinimalScene.hh | 2 +- test/integration/camera_tracking.cc | 4 ++-- test/integration/marker_manager.cc | 4 ++-- test/integration/minimal_scene.cc | 4 ++-- test/integration/transport_scene_manager.cc | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Helpers_TEST.cc b/src/Helpers_TEST.cc index 980306fd8..4556cf7fa 100644 --- a/src/Helpers_TEST.cc +++ b/src/Helpers_TEST.cc @@ -194,11 +194,11 @@ TEST(HelpersTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(renderEngine)) EXPECT_TRUE(renderEngineName().empty()); // Set the render engine GUI name - mainWindow->SetRenderEngine("ogre"); + mainWindow->SetRenderEngine("ogre2"); // Has render engine EXPECT_FALSE(renderEngineName().empty()); - EXPECT_EQ("ogre", renderEngineName()); + EXPECT_EQ("ogre2", renderEngineName()); // Set no render engine mainWindow->SetRenderEngine({}); diff --git a/src/plugins/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 4487cb32b..4dd6590c8 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -208,8 +208,8 @@ namespace plugins /// \param[out] _texturePtr Pointer to a texture Id public: void TextureId(void* _texturePtr); + public: std::string engineName = "ogre2"; /// \brief Render engine to use - public: std::string engineName = "ogre"; /// \brief Unique scene name public: std::string sceneName = "scene"; diff --git a/test/integration/camera_tracking.cc b/test/integration/camera_tracking.cc index 51489a091..714e1df74 100644 --- a/test/integration/camera_tracking.cc +++ b/test/integration/camera_tracking.cc @@ -62,7 +62,7 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Load plugins const char *pluginStr = "" - "ogre" + "ogre2" "banana" "1.0 0 0" "0 1 0" @@ -121,7 +121,7 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) EXPECT_TRUE(poseMsg.has_position()); EXPECT_TRUE(poseMsg.has_orientation()); - auto engine = rendering::engine("ogre"); + auto engine = rendering::engine("ogre2"); ASSERT_NE(nullptr, engine); auto scene = engine->SceneByName("banana"); diff --git a/test/integration/marker_manager.cc b/test/integration/marker_manager.cc index d7437ccf1..13ac87589 100644 --- a/test/integration/marker_manager.cc +++ b/test/integration/marker_manager.cc @@ -116,7 +116,7 @@ TEST_F(MarkerManagerTestFixture, const char *pluginMinimalSceneStr = "" - "ogre" + "ogre2" "scene" ""; @@ -144,7 +144,7 @@ TEST_F(MarkerManagerTestFixture, window->QuickWindow()->show(); // Check scene - auto engine = rendering::engine("ogre"); + auto engine = rendering::engine("ogre2"); ASSERT_NE(nullptr, engine); int sleep = 0; diff --git a/test/integration/minimal_scene.cc b/test/integration/minimal_scene.cc index eff5850b4..659b7a243 100644 --- a/test/integration/minimal_scene.cc +++ b/test/integration/minimal_scene.cc @@ -80,7 +80,7 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Load plugin const char *pluginStr = "" - "ogre" + "ogre2" "banana" "1.0 0 0" "0 1 0" @@ -120,7 +120,7 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) }; // Check scene - auto engine = rendering::engine("ogre"); + auto engine = rendering::engine("ogre2"); ASSERT_NE(nullptr, engine); int sleep = 0; diff --git a/test/integration/transport_scene_manager.cc b/test/integration/transport_scene_manager.cc index 7f97bd348..e621026f9 100644 --- a/test/integration/transport_scene_manager.cc +++ b/test/integration/transport_scene_manager.cc @@ -122,7 +122,7 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Load plugins const char *pluginStr = "" - "ogre" + "ogre2" "banana" "1.0 0 0" "0 1 0" @@ -154,7 +154,7 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) win->QuickWindow()->show(); // Get scene - auto engine = rendering::engine("ogre"); + auto engine = rendering::engine("ogre2"); ASSERT_NE(nullptr, engine); int sleep = 0; From cb008b6336d68f219196e01ac8919f5239ebb0b2 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Fri, 22 Jul 2022 21:14:53 -0700 Subject: [PATCH 06/11] line order Signed-off-by: Louise Poubel --- src/plugins/minimal_scene/MinimalScene.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 1bc13388b..9639c9666 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -208,8 +208,8 @@ namespace plugins /// \param[out] _texturePtr Pointer to a texture Id public: void TextureId(void* _texturePtr); - public: std::string engineName = "ogre2"; /// \brief Render engine to use + public: std::string engineName = "ogre2"; /// \brief Unique scene name public: std::string sceneName = "scene"; From fbfd8a0cd6f65ebb275d94ae8e5d0034feb6d770 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Tue, 6 Jun 2023 11:59:26 -0700 Subject: [PATCH 07/11] Fix tests by delaying get render engine calls (#535) --------- Signed-off-by: Ian Chen --- src/plugins/minimal_scene/MinimalScene.cc | 4 + test/helpers/RenderEngineHelper.hh | 81 +++++++++++++++++++++ test/integration/marker_manager.cc | 15 +--- test/integration/minimal_scene.cc | 33 +-------- test/integration/transport_scene_manager.cc | 10 ++- 5 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 test/helpers/RenderEngineHelper.hh diff --git a/src/plugins/minimal_scene/MinimalScene.cc b/src/plugins/minimal_scene/MinimalScene.cc index 903ac6fb2..462c98890 100644 --- a/src/plugins/minimal_scene/MinimalScene.cc +++ b/src/plugins/minimal_scene/MinimalScene.cc @@ -687,6 +687,10 @@ void GzRenderer::Destroy() // TODO(anyone) If that was the last scene, terminate engine? } + + // clean up in the rendering thread + this->dataPtr->camera.reset(); + this->dataPtr->rayQuery.reset(); } ///////////////////////////////////////////////// diff --git a/test/helpers/RenderEngineHelper.hh b/test/helpers/RenderEngineHelper.hh new file mode 100644 index 000000000..06d0ad6dd --- /dev/null +++ b/test/helpers/RenderEngineHelper.hh @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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 GZ_GUI_RENDERENGINEHELPER_HH_ +#define GZ_GUI_RENDERENGINEHELPER_HH_ + +#include + +#include +#include + +#include +#include + +#include "gz/gui/GuiEvents.hh" + +#include "TestHelper.hh" + +namespace gz +{ +namespace gui +{ +namespace testing +{ + /// \brief Get the render engine + /// This function should be called after the main window is + /// shown (mainWindow.show()). It blocks until render events + /// are received. + /// \return A pointer to the render engine or nullptr if it's not available. + static rendering::RenderEngine* getRenderEngine( + const std::string &_engine) + { + // Filter events + bool receivedPreRenderEvent{false}; + bool receivedRenderEvent{false}; + auto testHelper = std::make_unique(); + testHelper->forwardEvent = [&](QEvent *_event) + { + if (_event->type() == gui::events::PreRender::kType) + { + receivedPreRenderEvent = true; + } + if (_event->type() == gui::events::Render::kType) + { + receivedRenderEvent = true; + } + }; + + int sleep = 0; + int maxSleep = 30; + while (!receivedRenderEvent && sleep < maxSleep) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + QCoreApplication::processEvents(); + ++sleep; + } + EXPECT_TRUE(receivedPreRenderEvent); + EXPECT_TRUE(receivedRenderEvent); + + // Check scene + auto engine = rendering::engine(_engine); + return engine; + } +} +} +} + +#endif diff --git a/test/integration/marker_manager.cc b/test/integration/marker_manager.cc index 4da20517b..aae9fcc4f 100644 --- a/test/integration/marker_manager.cc +++ b/test/integration/marker_manager.cc @@ -32,6 +32,8 @@ #include #include "test_config.hh" // NOLINT(build/include) +#include "../helpers/TestHelper.hh" +#include "../helpers/RenderEngineHelper.hh" #include "gz/gui/Application.hh" #include "gz/gui/GuiEvents.hh" #include "gz/gui/MainWindow.hh" @@ -142,19 +144,10 @@ TEST_F(MarkerManagerTestFixture, // Show, but don't exec, so we don't block window->QuickWindow()->show(); - // Check scene - auto engine = rendering::engine("ogre2"); + // get render engine after window is shown + auto engine = gz::gui::testing::getRenderEngine("ogre2"); ASSERT_NE(nullptr, engine); - int sleep = 0; - int maxSleep = 30; - while (0 == engine->SceneCount() && sleep < maxSleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - sleep++; - } - EXPECT_EQ(1u, engine->SceneCount()); scene = engine->SceneByName("scene"); ASSERT_NE(nullptr, scene); diff --git a/test/integration/minimal_scene.cc b/test/integration/minimal_scene.cc index 5a55dca21..5e7396d56 100644 --- a/test/integration/minimal_scene.cc +++ b/test/integration/minimal_scene.cc @@ -28,6 +28,7 @@ #include "test_config.hh" // NOLINT(build/include) #include "../helpers/TestHelper.hh" +#include "../helpers/RenderEngineHelper.hh" #include "gz/gui/Application.hh" #include "gz/gui/GuiEvents.hh" #include "gz/gui/Plugin.hh" @@ -106,37 +107,10 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Show, but don't exec, so we don't block win->QuickWindow()->show(); - // Filter events - bool receivedPreRenderEvent{false}; - bool receivedRenderEvent{false}; - auto testHelper = std::make_unique(); - testHelper->forwardEvent = [&](QEvent *_event) - { - if (_event->type() == events::PreRender::kType) - { - receivedPreRenderEvent = true; - } - if (_event->type() == events::Render::kType) - { - receivedRenderEvent = true; - } - }; - - // Check scene - auto engine = rendering::engine("ogre2"); + // get render engine after window is shown + auto engine = gz::gui::testing::getRenderEngine("ogre2"); ASSERT_NE(nullptr, engine); - int sleep = 0; - int maxSleep = 30; - while (!receivedRenderEvent && sleep < maxSleep) - { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - QCoreApplication::processEvents(); - ++sleep; - } - EXPECT_TRUE(receivedPreRenderEvent); - EXPECT_TRUE(receivedRenderEvent); - EXPECT_EQ(1u, engine->SceneCount()); auto scene = engine->SceneByName("banana"); ASSERT_NE(nullptr, scene); @@ -172,4 +146,5 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) win->QuickWindow()->close(); engine->DestroyScene(scene); + EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } diff --git a/test/integration/transport_scene_manager.cc b/test/integration/transport_scene_manager.cc index 57355f34a..514bdba4e 100644 --- a/test/integration/transport_scene_manager.cc +++ b/test/integration/transport_scene_manager.cc @@ -32,6 +32,7 @@ #include "test_config.hh" // NOLINT(build/include) #include "../helpers/TestHelper.hh" +#include "../helpers/RenderEngineHelper.hh" #include "gz/gui/Application.hh" #include "gz/gui/GuiEvents.hh" #include "gz/gui/Plugin.hh" @@ -154,10 +155,6 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Show, but don't exec, so we don't block win->QuickWindow()->show(); - // Get scene - auto engine = rendering::engine("ogre2"); - ASSERT_NE(nullptr, engine); - int sleep = 0; int maxSleep = 30; while (!sceneRequested && sleep < maxSleep) @@ -169,6 +166,10 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) EXPECT_TRUE(sceneRequested); EXPECT_LT(sleep, maxSleep); + // get render engine after window is shown + auto engine = gz::gui::testing::getRenderEngine("ogre2"); + ASSERT_NE(nullptr, engine); + auto scene = engine->SceneByName("banana"); ASSERT_NE(nullptr, scene); @@ -261,5 +262,6 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) win->QuickWindow()->close(); engine->DestroyScene(scene); + EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } From 44fe63ce887347ffba718cc711e79676e788bd6b Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Tue, 6 Jun 2023 19:04:04 +0000 Subject: [PATCH 08/11] revert default engine Signed-off-by: Ian Chen --- src/plugins/minimal_scene/MinimalScene.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 289c208e7..67a88592d 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -213,7 +213,7 @@ namespace plugins public: void TextureId(void* _texturePtr); /// \brief Render engine to use - public: std::string engineName = "ogre2"; + public: std::string engineName = "ogre"; /// \brief Unique scene name public: std::string sceneName = "scene"; From bb82bb868a6c9a96ffd9b60e33d92be4f9ca99ca Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Mon, 21 Aug 2023 14:26:54 -0700 Subject: [PATCH 09/11] Fix rendering tests (#561) * debugging Signed-off-by: Ian Chen * reset cam Signed-off-by: Ian Chen * more testing Signed-off-by: Ian Chen * no unload Signed-off-by: Ian Chen * more testing with no unload Signed-off-by: Ian Chen * unload engine in gzrenderer Signed-off-by: Ian Chen * unload engine in gzrenderer debugging Signed-off-by: Ian Chen * unload engine in gzrenderer reset scene Signed-off-by: Ian Chen * comment out unload Signed-off-by: Ian Chen * increase timeout Signed-off-by: Ian Chen * test timing Signed-off-by: Ian Chen * cleanup Signed-off-by: Ian Chen * cleanup Signed-off-by: Ian Chen * more cleanup Signed-off-by: Ian Chen --------- Signed-off-by: Ian Chen --- test/integration/marker_manager.cc | 3 +++ test/integration/minimal_scene.cc | 4 ++-- test/integration/transport_scene_manager.cc | 11 +++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/integration/marker_manager.cc b/test/integration/marker_manager.cc index aae9fcc4f..0463d406d 100644 --- a/test/integration/marker_manager.cc +++ b/test/integration/marker_manager.cc @@ -211,4 +211,7 @@ TEST_F(MarkerManagerTestFixture, // Cleanup plugins.clear(); + scene.reset(); + + window->QuickWindow()->close(); } diff --git a/test/integration/minimal_scene.cc b/test/integration/minimal_scene.cc index 5e7396d56..a789cc396 100644 --- a/test/integration/minimal_scene.cc +++ b/test/integration/minimal_scene.cc @@ -144,7 +144,7 @@ TEST(MinimalSceneTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) EXPECT_TRUE(app.RemovePlugin(pluginName)); plugins.clear(); + + scene.reset(); win->QuickWindow()->close(); - engine->DestroyScene(scene); - EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } diff --git a/test/integration/transport_scene_manager.cc b/test/integration/transport_scene_manager.cc index 514bdba4e..6cfd41ada 100644 --- a/test/integration/transport_scene_manager.cc +++ b/test/integration/transport_scene_manager.cc @@ -155,6 +155,10 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Show, but don't exec, so we don't block win->QuickWindow()->show(); + // get render engine after window is shown + auto engine = gz::gui::testing::getRenderEngine("ogre2"); + ASSERT_NE(nullptr, engine); + int sleep = 0; int maxSleep = 30; while (!sceneRequested && sleep < maxSleep) @@ -166,10 +170,6 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) EXPECT_TRUE(sceneRequested); EXPECT_LT(sleep, maxSleep); - // get render engine after window is shown - auto engine = gz::gui::testing::getRenderEngine("ogre2"); - ASSERT_NE(nullptr, engine); - auto scene = engine->SceneByName("banana"); ASSERT_NE(nullptr, scene); @@ -260,8 +260,7 @@ TEST(TransportSceneManagerTest, GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) } plugins.clear(); + scene.reset(); win->QuickWindow()->close(); - engine->DestroyScene(scene); - EXPECT_TRUE(rendering::unloadEngine(engine->Name())); } From 09ac57f22e2215c1f6b42e09ec67c8cd2a0b0213 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 22 Aug 2023 00:35:44 +0000 Subject: [PATCH 10/11] Suppress warnings in metal Signed-off-by: Michael Carroll --- src/plugins/minimal_scene/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/minimal_scene/CMakeLists.txt b/src/plugins/minimal_scene/CMakeLists.txt index 8e2a9526c..ce23b527e 100644 --- a/src/plugins/minimal_scene/CMakeLists.txt +++ b/src/plugins/minimal_scene/CMakeLists.txt @@ -37,5 +37,6 @@ if (APPLE) PROPERTIES COMPILE_FLAGS "-fobjc-arc" + "-Wno-nullability-completeness" ) endif() From c2351010cef37d6e23686f14841b59a6e0bb4404 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 22 Aug 2023 13:18:47 +0000 Subject: [PATCH 11/11] Incorrect args Signed-off-by: Michael Carroll --- src/plugins/minimal_scene/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/minimal_scene/CMakeLists.txt b/src/plugins/minimal_scene/CMakeLists.txt index ce23b527e..ebd6fd108 100644 --- a/src/plugins/minimal_scene/CMakeLists.txt +++ b/src/plugins/minimal_scene/CMakeLists.txt @@ -36,7 +36,6 @@ if (APPLE) MinimalSceneRhiMetal.mm PROPERTIES COMPILE_FLAGS - "-fobjc-arc" - "-Wno-nullability-completeness" + "-fobjc-arc -Wno-nullability-completeness" ) endif()