diff --git a/examples/standalone/scene_provider/README.md b/examples/standalone/scene_provider/README.md index 7b7691f80..3183a83e4 100644 --- a/examples/standalone/scene_provider/README.md +++ b/examples/standalone/scene_provider/README.md @@ -4,7 +4,7 @@ This example demonstrates how to populate and update a 3D scene using Gazebo Transport. This example is meant to be used with `examples/config/scened.config`, which -loads the `Scene3D` plugin to create the scene, and the `TransportSceneManager` +loads the `MinimalScene` plugin to create the scene, and the `TransportSceneManager` plugin to update the scene using Gazebo Transport. ## Build 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/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 28622a052..03daeaf9f 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -15,8 +15,8 @@ * */ -#ifndef GZ_GUI_PLUGINS_SCENE3D_HH_ -#define GZ_GUI_PLUGINS_SCENE3D_HH_ +#ifndef GZ_GUI_PLUGINS_MINIMALSCENE_HH_ +#define GZ_GUI_PLUGINS_MINIMALSCENE_HH_ #include #include 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 299b2a37c..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 gz-rendering and gz-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 Gazebo transport scene service name - /// \param[in] _poseTopic Gazebo transport pose topic name - /// \param[in] _deletionTopic Gazebo transport deletion topic name - /// \param[in] _sceneTopic Gazebo 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 Gazebo transport service name - /// \param[in] _poseTopic Gazebo transport pose topic name - /// \param[in] _deletionTopic Gazebo transport deletion topic name - /// \param[in] _sceneTopic Gazebo 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 gz-transport scene service name - private: std::string service; - - //// \brief gz-transport pose topic name - private: std::string poseTopic; - - //// \brief gz-transport deletion topic name - private: std::string deletionTopic; - - //// \brief gz-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 GzRenderer - class GzRendererPrivate - { - /// \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("gz-grey"); - if (!material) - { - material = this->scene->CreateMaterial("gz-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 gz-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); - } -} - -///////////////////////////////////////////////// -GzRenderer::GzRenderer() - : dataPtr(new GzRendererPrivate) -{ -} - - -///////////////////////////////////////////////// -GzRenderer::~GzRenderer() -{ -} - -///////////////////////////////////////////////// -void GzRenderer::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 GzRenderer::HandleMouseEvent() -{ - std::lock_guard lock(this->dataPtr->mutex); - this->BroadcastHoverPos(); - this->BroadcastLeftClick(); - this->BroadcastRightClick(); - this->BroadcastKeyPress(); - this->BroadcastKeyRelease(); - this->HandleMouseViewControl(); -} - -///////////////////////////////////////////////// -void GzRenderer::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 GzRenderer::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 GzRenderer::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 GzRenderer::BroadcastHoverPos() -{ - if (!this->dataPtr->hoverDirty) - return; - - auto pos = this->ScreenToScene(this->dataPtr->mouseHoverPos); - - events::HoverToScene hoverToSceneEvent(pos); - App()->sendEvent(App()->findChild(), &hoverToSceneEvent); -} - -///////////////////////////////////////////////// -void GzRenderer::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 GzRenderer::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 GzRenderer::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 GzRenderer::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 GzRenderer::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 GzRenderer::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 GzRenderer::NewHoverEvent(const math::Vector2i &_hoverPos) -{ - std::lock_guard lock(this->dataPtr->mutex); - this->dataPtr->mouseHoverPos = _hoverPos; - this->dataPtr->hoverDirty = true; -} - -///////////////////////////////////////////////// -void GzRenderer::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 GzRenderer::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->gzRenderer.initialized) - { - // Initialize renderer - auto loadingError = this->gzRenderer.Initialize(); - if (!loadingError.empty()) - { - this->errorCb(QString::fromStdString(loadingError)); - return; - } - } - - // check if engine has been successfully initialized - if (!this->gzRenderer.initialized) - { - gzerr << "Unable to initialize renderer" << std::endl; - return; - } - - this->gzRenderer.Render(); - - emit TextureReady(this->gzRenderer.textureId, this->gzRenderer.textureSize); -} - -///////////////////////////////////////////////// -void RenderThread::ShutDown() -{ - if (this->context && this->surface) - this->context->makeCurrent(this->surface); - - this->gzRenderer.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->gzRenderer.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->gzRenderer.textureSize = QSize(item->width(), item->height()); - this->gzRenderer.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->gzRenderer.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->gzRenderer.backgroundColor = _color; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetAmbientLight(const math::Color &_ambient) -{ - this->dataPtr->renderThread->gzRenderer.ambientLight = _ambient; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetEngineName(const std::string &_name) -{ - this->dataPtr->renderThread->gzRenderer.engineName = _name; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneName(const std::string &_name) -{ - this->dataPtr->renderThread->gzRenderer.sceneName = _name; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetCameraPose(const math::Pose3d &_pose) -{ - this->dataPtr->renderThread->gzRenderer.cameraPose = _pose; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneService(const std::string &_service) -{ - this->dataPtr->renderThread->gzRenderer.sceneService = _service; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetPoseTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->gzRenderer.poseTopic = _topic; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetDeletionTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->gzRenderer.deletionTopic = _topic; -} - -///////////////////////////////////////////////// -void RenderWindowItem::SetSceneTopic(const std::string &_topic) -{ - this->dataPtr->renderThread->gzRenderer.sceneTopic = _topic; -} - -///////////////////////////////////////////////// -Scene3D::Scene3D() - : Plugin(), dataPtr(new Scene3DPrivate) -{ - gzwarn << "This plugin is deprecated on gz-gui v6 and will be removed on " - << "gz-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->gzRenderer.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->gzRenderer.NewMouseEvent( - this->dataPtr->mouseEvent); -} - -//////////////////////////////////////////////// -void RenderWindowItem::mouseReleaseEvent(QMouseEvent *_e) -{ - this->dataPtr->mouseEvent = convert(*_e); - - this->dataPtr->renderThread->gzRenderer.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->gzRenderer.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->gzRenderer.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->gzRenderer.HandleKeyPress(_e); -} - -//////////////////////////////////////////////// -void RenderWindowItem::HandleKeyRelease(QKeyEvent *_e) -{ - this->dataPtr->renderThread->gzRenderer.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 7752ffa42..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 GzRendererPrivate; - 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 gz-rendering renderer. - /// All gz-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 GzRenderer - { - /// \brief Constructor - public: GzRenderer(); - - /// \brief Destructor - public: ~GzRenderer(); - - /// \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 gz-transport deletion topic name - public: std::string deletionTopic; - - /// \brief gz-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 gz-rendering renderer - public: GzRenderer gzRenderer; - }; - - - /// \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 gz-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 gz-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/src/plugins/screenshot/Screenshot_TEST.cc b/src/plugins/screenshot/Screenshot_TEST.cc index a6b30f17e..310b2e965 100644 --- a/src/plugins/screenshot/Screenshot_TEST.cc +++ b/src/plugins/screenshot/Screenshot_TEST.cc @@ -68,9 +68,10 @@ TEST(ScreenshotTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(Screenshot)) EXPECT_EQ(plugins.size(), 1); // TODO(anyone) Below is commented out because currently unable to load - // Scene3D from another plugin. Once resolved this test should be implemented + // MinimalScene from another plugin. Once resolved this test should be + // implemented - // EXPECT_TRUE(app.LoadPlugin("Scene3D")); + // EXPECT_TRUE(app.LoadPlugin("MinimalScene")); // EXPECT_TRUE(app.LoadPlugin("Screenshot")); // // // Get main window diff --git a/src/plugins/tape_measure/TapeMeasure.cc b/src/plugins/tape_measure/TapeMeasure.cc index 7528b991f..09f39e6e9 100644 --- a/src/plugins/tape_measure/TapeMeasure.cc +++ b/src/plugins/tape_measure/TapeMeasure.cc @@ -127,7 +127,7 @@ void TapeMeasure::Measure() this->dataPtr->measure = true; QGuiApplication::setOverrideCursor(Qt::CrossCursor); - // Notify Scene3D to disable the right click menu while we use it to + // Notify 3D scene to disable the right click menu while we use it to // cancel our current measuring action gz::gui::events::DropdownMenuEnabled dropdownMenuEnabledEvent(false); gz::gui::App()->sendEvent( @@ -156,7 +156,7 @@ void TapeMeasure::Reset() this->newDistance(); QGuiApplication::restoreOverrideCursor(); - // Notify Scene3D that we are done using the right click, so it can + // Notify 3D scene that we are done using the right click, so it can // re-enable the settings menu gz::gui::events::DropdownMenuEnabled dropdownMenuEnabledEvent(true); gz::gui::App()->sendEvent( @@ -236,7 +236,7 @@ bool TapeMeasure::eventFilter(QObject *_obj, QEvent *_event) auto hoverToSceneEvent = reinterpret_cast(_event); - // This event is called in Scene3d's RenderThread, so it's safe to make + // This event is called in the RenderThread, so it's safe to make // rendering calls here if (this->dataPtr->measure && hoverToSceneEvent) { @@ -260,7 +260,7 @@ bool TapeMeasure::eventFilter(QObject *_obj, QEvent *_event) auto leftClickToSceneEvent = reinterpret_cast(_event); - // This event is called in Scene3d's RenderThread, so it's safe to make + // This event is called in the RenderThread, so it's safe to make // rendering calls here if (this->dataPtr->measure && leftClickToSceneEvent) { @@ -285,7 +285,7 @@ bool TapeMeasure::eventFilter(QObject *_obj, QEvent *_event) this->newDistance(); QGuiApplication::restoreOverrideCursor(); - // Notify Scene3D that we are done using the right click, so it can + // Notify 3D scene that we are done using the right click, so it can // re-enable the settings menu gz::gui::events::DropdownMenuEnabled dropdownMenuEnabledEvent(true); diff --git a/test/integration/scene3d.cc b/test/integration/scene3d.cc deleted file mode 100644 index 205e49e6d..000000000 --- a/test/integration/scene3d.cc +++ /dev/null @@ -1,341 +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_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"); - - // 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(); -}