diff --git a/examples/projector/CMakeLists.txt b/examples/projector/CMakeLists.txt new file mode 100644 index 000000000..7a7317faa --- /dev/null +++ b/examples/projector/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) +project(gz-rendering-projector) +find_package(gz-rendering7 REQUIRED) + +include_directories(SYSTEM + ${PROJECT_BINARY_DIR} +) + +find_package(GLUT REQUIRED) +include_directories(SYSTEM ${GLUT_INCLUDE_DIRS}) +link_directories(${GLUT_LIBRARY_DIRS}) + +find_package(OpenGL REQUIRED) +include_directories(SYSTEM ${OpenGL_INCLUDE_DIRS}) +link_directories(${OpenGL_LIBRARY_DIRS}) + +if (NOT APPLE) + find_package(GLEW REQUIRED) + include_directories(SYSTEM ${GLEW_INCLUDE_DIRS}) + link_directories(${GLEW_LIBRARY_DIRS}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + +configure_file (example_config.hh.in ${PROJECT_BINARY_DIR}/example_config.hh) + +add_executable(projector Main.cc GlutWindow.cc) + +target_link_libraries(projector + ${GLUT_LIBRARIES} + ${OPENGL_LIBRARIES} + ${GLEW_LIBRARIES} + gz-rendering7::gz-rendering7 + gz-rendering7::core +) + +add_custom_command(TARGET projector POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/media + $/media) diff --git a/examples/projector/GlutWindow.cc b/examples/projector/GlutWindow.cc new file mode 100644 index 000000000..5c965bccb --- /dev/null +++ b/examples/projector/GlutWindow.cc @@ -0,0 +1,379 @@ +/* + * 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. + * + */ + +#if __APPLE__ + #include + #include + #include +#else + #include + #include + #include +#endif + +#if !defined(__APPLE__) && !defined(_WIN32) + #include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GlutWindow.hh" + +#define KEY_ESC 27 +#define KEY_TAB 9 + +////////////////////////////////////////////////// +unsigned int imgw = 0; +unsigned int imgh = 0; + +std::vector g_cameras; +ir::CameraPtr g_camera; +ir::CameraPtr g_currCamera; +unsigned int g_cameraIndex = 0; +ir::ImagePtr g_image; + +bool g_initContext = false; + +double g_pitch = 0.001; +bool g_moveProjector = true; + +#if __APPLE__ + CGLContextObj g_context; + CGLContextObj g_glutContext; +#elif _WIN32 +#else + GLXContext g_context; + Display *g_display; + GLXDrawable g_drawable; + GLXContext g_glutContext; + Display *g_glutDisplay; + GLXDrawable g_glutDrawable; +#endif + +// view control variables +ir::RayQueryPtr g_rayQuery; +ir::OrbitViewController g_viewControl; +ir::RayQueryResult g_target; +struct mouseButton +{ + int button = 0; + int state = GLUT_UP; + int x = 0; + int y = 0; + int motionX = 0; + int motionY = 0; + int dragX = 0; + int dragY = 0; + int scroll = 0; + bool buttonDirty = false; + bool motionDirty = false; +}; +struct mouseButton g_mouse; +std::mutex g_mouseMutex; + +////////////////////////////////////////////////// +void mouseCB(int _button, int _state, int _x, int _y) +{ + // ignore unknown mouse button numbers + if (_button >= 5) + return; + + std::lock_guard lock(g_mouseMutex); + g_mouse.button = _button; + g_mouse.state = _state; + g_mouse.x = _x; + g_mouse.y = _y; + g_mouse.motionX = _x; + g_mouse.motionY = _y; + g_mouse.buttonDirty = true; +} + +////////////////////////////////////////////////// +void motionCB(int _x, int _y) +{ + std::lock_guard lock(g_mouseMutex); + int deltaX = _x - g_mouse.motionX; + int deltaY = _y - g_mouse.motionY; + g_mouse.motionX = _x; + g_mouse.motionY = _y; + + if (g_mouse.motionDirty) + { + g_mouse.dragX += deltaX; + g_mouse.dragY += deltaY; + } + else + { + g_mouse.dragX = deltaX; + g_mouse.dragY = deltaY; + } + g_mouse.motionDirty = true; +} + +////////////////////////////////////////////////// +void handleMouse() +{ + std::lock_guard lock(g_mouseMutex); + // only ogre supports ray query for now so use + // ogre camera located at camera index = 0. + ir::CameraPtr rayCamera = g_cameras[0]; + if (!g_rayQuery) + { + g_rayQuery = rayCamera->Scene()->CreateRayQuery(); + if (!g_rayQuery) + { + gzerr << "Failed to create Ray Query" << std::endl; + return; + } + } + if (g_mouse.buttonDirty) + { + g_mouse.buttonDirty = false; + double nx = + 2.0 * g_mouse.x / static_cast(rayCamera->ImageWidth()) - 1.0; + double ny = 1.0 - + 2.0 * g_mouse.y / static_cast(rayCamera->ImageHeight()); + g_rayQuery->SetFromCamera(rayCamera, gz::math::Vector2d(nx, ny)); + g_target = g_rayQuery->ClosestPoint(); + if (!g_target) + { + // set point to be 10m away if no intersection found + g_target.point = g_rayQuery->Origin() + g_rayQuery->Direction() * 10; + return; + } + + // mouse wheel scroll zoom + if ((g_mouse.button == 3 || g_mouse.button == 4) && + g_mouse.state == GLUT_UP) + { + double scroll = (g_mouse.button == 3) ? -1.0 : 1.0; + double distance = rayCamera->WorldPosition().Distance( + g_target.point); + int factor = 1; + double amount = -(scroll * factor) * (distance / 5.0); + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Zoom(amount); + } + } + } + + if (g_mouse.motionDirty) + { + g_mouse.motionDirty = false; + auto drag = gz::math::Vector2d(g_mouse.dragX, g_mouse.dragY); + + // left mouse button pan + if (g_mouse.button == GLUT_LEFT_BUTTON && g_mouse.state == GLUT_DOWN) + { + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Pan(drag); + } + } + else if (g_mouse.button == GLUT_MIDDLE_BUTTON && g_mouse.state == GLUT_DOWN) + { + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Orbit(drag); + } + } + // right mouse button zoom + else if (g_mouse.button == GLUT_RIGHT_BUTTON && g_mouse.state == GLUT_DOWN) + { + double hfov = rayCamera->HFOV().Radian(); + double vfov = 2.0f * atan(tan(hfov / 2.0f) / + rayCamera->AspectRatio()); + double distance = rayCamera->WorldPosition().Distance( + g_target.point); + double amount = ((-g_mouse.dragY / + static_cast(rayCamera->ImageHeight())) + * distance * tan(vfov/2.0) * 6.0); + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Zoom(amount); + } + } + } +} + +////////////////////////////////////////////////// +void moveProjector() +{ + ir::CameraPtr camera = g_cameras[g_cameraIndex]; + ir::ProjectorPtr projector = + std::dynamic_pointer_cast( + camera->Scene()->VisualByName("projector")); + + gz::math::Quaterniond rot = projector->LocalRotation(); + gz::math::Vector3d euler = rot.Euler(); + if (euler.Y() <= 0u || euler.Y() > (GZ_PI / 3.0)) + g_pitch = -g_pitch; + gz::math::Quaterniond pitchRot(0, g_pitch, 0); + projector->SetLocalRotation(pitchRot * rot); +} + +////////////////////////////////////////////////// +void displayCB() +{ +#if __APPLE__ + CGLSetCurrentContext(g_context); +#elif _WIN32 +#else + if (g_display) + { + glXMakeCurrent(g_display, g_drawable, g_context); + } +#endif + + if (g_moveProjector) + moveProjector(); + + g_cameras[g_cameraIndex]->Capture(*g_image); + handleMouse(); + +#if __APPLE__ + CGLSetCurrentContext(g_glutContext); +#elif _WIN32 +#else + glXMakeCurrent(g_glutDisplay, g_glutDrawable, g_glutContext); +#endif + + unsigned char *data = g_image->Data(); + + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glPixelZoom(1, -1); + glRasterPos2f(-1, 1); + glDrawPixels(imgw, imgh, GL_RGB, GL_UNSIGNED_BYTE, data); + + glutSwapBuffers(); +} + +////////////////////////////////////////////////// +void idleCB() +{ + glutPostRedisplay(); +} + +////////////////////////////////////////////////// +void keyboardCB(unsigned char _key, int, int) +{ + if (_key == KEY_ESC || _key == 'q' || _key == 'Q') + { + exit(0); + } + else if (_key == KEY_TAB) + { + g_cameraIndex = (g_cameraIndex + 1) % g_cameras.size(); + } + else if (_key == 'p') + { + g_moveProjector = !g_moveProjector; + } +} + +////////////////////////////////////////////////// +void initCamera(ir::CameraPtr _camera) +{ + g_camera = _camera; + imgw = g_camera->ImageWidth(); + imgh = g_camera->ImageHeight(); + ir::Image image = g_camera->CreateImage(); + g_image = std::make_shared(image); + g_camera->Capture(*g_image); +} + +////////////////////////////////////////////////// +void initContext() +{ + glutInitDisplayMode(GLUT_DOUBLE); + glutInitWindowPosition(0, 0); + glutInitWindowSize(imgw, imgh); + glutCreateWindow("Projector"); + glutDisplayFunc(displayCB); + glutIdleFunc(idleCB); + glutKeyboardFunc(keyboardCB); + + glutMouseFunc(mouseCB); + glutMotionFunc(motionCB); +} + +////////////////////////////////////////////////// +void printUsage() +{ + std::cout << "=================================" << std::endl; + std::cout << " TAB - Switch render engines " << std::endl; + std::cout << " ESC - Exit " << std::endl; + std::cout << " P - Toggle projector motion " << std::endl; + std::cout << "=================================" << std::endl; +} + +////////////////////////////////////////////////// +void run(std::vector _cameras) +{ + if (_cameras.empty()) + { + gzerr << "No cameras found. Scene will not be rendered" << std::endl; + return; + } + +#if __APPLE__ + g_context = CGLGetCurrentContext(); +#elif _WIN32 +#else + g_context = glXGetCurrentContext(); + g_display = glXGetCurrentDisplay(); + g_drawable = glXGetCurrentDrawable(); +#endif + + g_cameras = _cameras; + initCamera(_cameras[0]); + initContext(); + printUsage(); + +#if __APPLE__ + g_glutContext = CGLGetCurrentContext(); +#elif _WIN32 +#else + g_glutDisplay = glXGetCurrentDisplay(); + g_glutDrawable = glXGetCurrentDrawable(); + g_glutContext = glXGetCurrentContext(); +#endif + + glutMainLoop(); +} + + diff --git a/examples/projector/GlutWindow.hh b/examples/projector/GlutWindow.hh new file mode 100644 index 000000000..1100af6c4 --- /dev/null +++ b/examples/projector/GlutWindow.hh @@ -0,0 +1,29 @@ +/* + * 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_RENDERING_EXAMPLES_PROJECTOR_GLUTWINDOW_HH_ +#define GZ_RENDERING_EXAMPLES_PROJECTOR_GLUTWINDOW_HH_ + +#include +#include "gz/rendering/RenderTypes.hh" + +namespace ir = gz::rendering; + +/// \brief Run the demo and render the scene from the cameras +/// \param[in] _cameras Cameras in the scene +void run(std::vector _cameras); + +#endif diff --git a/examples/projector/Main.cc b/examples/projector/Main.cc new file mode 100644 index 000000000..a991c98dd --- /dev/null +++ b/examples/projector/Main.cc @@ -0,0 +1,218 @@ +/* + * 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. + * + */ + +#if defined(__APPLE__) + #include + #include +#elif not defined(_WIN32) + #include + #include + #include +#endif + +#include +#include + +#include +#include + +#include "example_config.hh" +#include "GlutWindow.hh" + +using namespace gz; +using namespace rendering; + +const std::string RESOURCE_PATH = + common::joinPaths(std::string(PROJECT_BINARY_PATH), "media"); + +////////////////////////////////////////////////// +void buildScene(ScenePtr _scene) +{ + // initialize _scene + _scene->SetAmbientLight(0.3, 0.3, 0.3); + VisualPtr root = _scene->RootVisual(); + + // create directional light + DirectionalLightPtr light0 = _scene->CreateDirectionalLight(); + light0->SetDirection(-0.5, 0.5, -1); + light0->SetDiffuseColor(0.5, 0.5, 0.5); + light0->SetSpecularColor(0.5, 0.5, 0.5); + root->AddChild(light0); + + // create projector + std::string texture = common::joinPaths(RESOURCE_PATH, + "stereo_projection_pattern_high_res_red.png"); + + ProjectorPtr projector = std::dynamic_pointer_cast( + _scene->Extension()->CreateExt("projector", "projector")); + // \todo(iche033) uncomment and use official API in gz-rendering8 + // ProjectorPtr projector = _scene->CreateProjector(); + projector->SetLocalPosition(-4, 0, 4); + projector->SetNearClipPlane(1.0); + projector->SetFarClipPlane(10.0); + projector->SetLocalRotation(0, 0.6, 0); + projector->SetTexture(texture); + projector->SetVisibilityFlags(0x01); + root->AddChild(projector); + + // create blue material + MaterialPtr blue = _scene->CreateMaterial(); + blue->SetAmbient(0.0, 0.0, 0.5); + blue->SetDiffuse(0.0, 0.0, 0.7); + blue->SetSpecular(0.5, 0.5, 0.5); + + // create visual representing the projector + VisualPtr box = _scene->CreateVisual(); + box->AddGeometry(_scene->CreateBox()); + box->SetLocalScale(0.1, 0.1, 0.1); + box->SetMaterial(blue); + projector->AddChild(box); + + // create white material + MaterialPtr white = _scene->CreateMaterial(); + white->SetAmbient(0.5, 0.5, 0.5); + white->SetDiffuse(0.8, 0.8, 0.8); + + // create plane visual + VisualPtr plane = _scene->CreateVisual(); + plane->AddGeometry(_scene->CreatePlane()); + plane->SetLocalScale(10, 10, 1); + plane->SetMaterial(white); + root->AddChild(plane); + + // create plane2 visual + VisualPtr plane2 = _scene->CreateVisual(); + plane2->AddGeometry(_scene->CreatePlane()); + plane2->SetLocalScale(10, 10, 1); + plane2->SetLocalPosition(5, 0, 5); + plane2->SetLocalRotation(math::Quaternion(math::Vector3d(0, -GZ_PI/2, 0))); + plane2->SetMaterial(white); + root->AddChild(plane2); + + // create plane3 visual + VisualPtr plane3 = _scene->CreateVisual(); + plane3->AddGeometry(_scene->CreatePlane()); + plane3->SetLocalScale(10, 10, 1); + plane3->SetLocalPosition(0, -5, 5); + plane3->SetLocalRotation(math::Quaternion(math::Vector3d(-GZ_PI/2, 0, 0))); + plane3->SetMaterial(white); + root->AddChild(plane3); + + // create plane4 visual + VisualPtr plane4 = _scene->CreateVisual(); + plane4->AddGeometry(_scene->CreatePlane()); + plane4->SetLocalScale(10, 10, 1); + plane4->SetLocalPosition(0, 5, 5); + plane4->SetLocalRotation(math::Quaternion(math::Vector3d(GZ_PI/2, 0, 0))); + plane4->SetMaterial(white); + root->AddChild(plane4); + + // create green material + MaterialPtr green = _scene->CreateMaterial(); + green->SetAmbient(0.0, 0.5, 0.0); + green->SetDiffuse(0.0, 0.7, 0.0); + green->SetSpecular(0.5, 0.5, 0.5); + + // create sphere visual + VisualPtr sphere = _scene->CreateVisual(); + sphere->AddGeometry(_scene->CreateSphere()); + sphere->SetLocalPosition(3, 0, 1); + sphere->SetMaterial(green); + root->AddChild(sphere); + + // create camera + CameraPtr camera = _scene->CreateCamera("camera"); + camera->SetLocalPosition(-5, 0.0, 5.0); + camera->SetLocalRotation(0.0, GZ_PI / 5.0, 0.0); + camera->SetImageWidth(800); + camera->SetImageHeight(600); + camera->SetAspectRatio(1.333); + camera->SetHFOV(GZ_PI / 2); + camera->SetVisibilityMask(0x01); + + root->AddChild(camera); +} + +////////////////////////////////////////////////// +CameraPtr createCamera(const std::string &_engineName, + const std::map& _params) +{ + // create and populate scene + RenderEngine *engine = rendering::engine(_engineName, _params); + if (!engine) + { + std::cout << "Engine '" << _engineName + << "' is not supported" << std::endl; + return CameraPtr(); + } + ScenePtr scene = engine->CreateScene("scene"); + buildScene(scene); + + // return camera sensor + SensorPtr sensor = scene->SensorByName("camera"); + return std::dynamic_pointer_cast(sensor); +} + +////////////////////////////////////////////////// +int main(int _argc, char** _argv) +{ + glutInit(&_argc, _argv); + + // Expose engine name to command line because we can't instantiate both + // ogre and ogre2 at the same time + std::string ogreEngineName("ogre"); + if (_argc > 1) + { + ogreEngineName = _argv[1]; + } + + GraphicsAPI graphicsApi = GraphicsAPI::OPENGL; + if (_argc > 2) + { + graphicsApi = GraphicsAPIUtils::Set(std::string(_argv[2])); + } + + common::Console::SetVerbosity(4); + std::vector engineNames; + std::vector cameras; + + engineNames.push_back(ogreEngineName); + engineNames.push_back("optix"); + for (auto engineName : engineNames) + { + try + { + std::map params; + if (engineName.compare("ogre2") == 0 + && graphicsApi == GraphicsAPI::METAL) + { + params["metal"] = "1"; + } + + CameraPtr camera = createCamera(engineName, params); + if (camera) + cameras.push_back(camera); + } + catch (...) + { + std::cerr << "Error starting up: " << engineName << std::endl; + } + } + run(cameras); + + return 0; +} diff --git a/examples/projector/example_config.hh.in b/examples/projector/example_config.hh.in new file mode 100644 index 000000000..6e44e6df7 --- /dev/null +++ b/examples/projector/example_config.hh.in @@ -0,0 +1 @@ +#define PROJECT_BINARY_PATH "${PROJECT_BINARY_DIR}" diff --git a/examples/projector/media/stereo_projection_pattern_high_res_red.png b/examples/projector/media/stereo_projection_pattern_high_res_red.png new file mode 100644 index 000000000..c5ae76394 Binary files /dev/null and b/examples/projector/media/stereo_projection_pattern_high_res_red.png differ diff --git a/include/gz/rendering/Projector.hh b/include/gz/rendering/Projector.hh new file mode 100644 index 000000000..b041d966a --- /dev/null +++ b/include/gz/rendering/Projector.hh @@ -0,0 +1,88 @@ +/* + * 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_RENDERING_PROJECTOR_HH_ +#define GZ_RENDERING_PROJECTOR_HH_ + +#include + +#include + +#include "gz/rendering/config.hh" +#include "gz/rendering/Export.hh" +#include "gz/rendering/RenderTypes.hh" +#include "gz/rendering/Visual.hh" + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + // + /// \class Projector Projector.hh + /// gz/rendering/Projector.hh + // + /// \brief A projector that projects a texture onto a surface + class GZ_RENDERING_VISIBLE Projector : + public virtual Visual + { + /// \brief Destructor + public: virtual ~Projector(); + + /// \brief Get the projector's far clipping plane distance + /// \return Far clipping plane distance + public: virtual double FarClipPlane() const = 0; + + /// \brief Set the projector's far clipping plane distance + /// \param[in] _far Far clipping plane distance + public: virtual void SetFarClipPlane(double _far) = 0; + + /// \brief Get the projector's near clipping plane distance + /// \return Near clipping plane distance + public: virtual double NearClipPlane() const = 0; + + /// \brief Set the projector's near clipping plane distance + /// \param[in] _near Near clipping plane distance + public: virtual void SetNearClipPlane(double _near) = 0; + + /// \brief Get the projector's horizontal field-of-view + /// \return Angle containing the projector's horizontal field-of-view + public: virtual math::Angle HFOV() const = 0; + + /// \brief Set the projector's horizontal field-of-view + /// \param[in] _hfov Desired horizontal field-of-view + public: virtual void SetHFOV(const math::Angle &_hfov) = 0; + + /// \brief Get the URI of the texture file used by the projector + /// \return URI of the texture file + public: virtual std::string Texture() const = 0; + + /// \brief Load a texture into the projector. + /// \param[in] _texture Name of the texture to project. + public: virtual void SetTexture(const std::string &_texture) = 0; + + /// \brief Get whether the projector is enabled or disabled. + /// \return true if enabled, false otherwise + public: virtual bool IsEnabled() const = 0; + + /// \brief Set whether the projector is enabled or disabled. + /// \param[in] _enabled True to enable the projector. + public: virtual void SetEnabled(bool _enabled) = 0; + }; + } + } +} +#endif diff --git a/include/gz/rendering/RenderTypes.hh b/include/gz/rendering/RenderTypes.hh index e029ced0e..e5168eb1e 100644 --- a/include/gz/rendering/RenderTypes.hh +++ b/include/gz/rendering/RenderTypes.hh @@ -76,6 +76,7 @@ namespace gz class ObjectFactory; class ParticleEmitter; class PointLight; + class Projector; class RayQuery; class RenderEngine; class RenderPass; @@ -219,6 +220,10 @@ namespace gz /// \brief Shared pointer to ParticleEmitter typedef shared_ptr ParticleEmitterPtr; + /// \typedef ProjectorPtr + /// \brief Shared pointer to Projector + typedef shared_ptr ProjectorPtr; + /// \typedef PointLightPtr /// \brief Shared pointer to PointLight typedef shared_ptr PointLightPtr; @@ -373,11 +378,15 @@ namespace gz /// \brief Shared pointer to const ParticleEmitter typedef shared_ptr ConstParticleEmitterPtr; + /// \typedef const ProjectorPtr + /// \brief Shared pointer to const Projector + typedef shared_ptr ConstProjectorPtr; + /// \typedef const PointLightPtr /// \brief Shared pointer to const PointLight typedef shared_ptr ConstPointLightPtr; - /// \typedef RayQueryPtr + /// \typedef const RayQueryPtr /// \brief Shared pointer to RayQuery typedef shared_ptr ConstRayQueryPtr; diff --git a/include/gz/rendering/Scene.hh b/include/gz/rendering/Scene.hh index 29a01ed57..de516bcff 100644 --- a/include/gz/rendering/Scene.hh +++ b/include/gz/rendering/Scene.hh @@ -26,6 +26,8 @@ #include +#include "gz/rendering/base/SceneExt.hh" + #include "gz/rendering/config.hh" #include "gz/rendering/HeightmapDescriptor.hh" #include "gz/rendering/MeshDescriptor.hh" @@ -40,6 +42,7 @@ namespace gz inline namespace GZ_RENDERING_VERSION_NAMESPACE { // class RenderEngine; + class SceneExt; /// \class Scene Scene.hh gz/rendering/Scene.hh /// \brief Manages a single scene-graph. This class updates scene-wide @@ -1171,6 +1174,41 @@ namespace gz public: virtual ParticleEmitterPtr CreateParticleEmitter( unsigned int _id, const std::string &_name) = 0; + /// \cond PRIVATE + /// \brief Create new projector. A unique ID and name will + /// automatically be assigned to the visual. + /// \return The created projector + /// \todo(iche033) uncomment in gz-rendering8 + /// public: virtual ProjectorPtr CreateProjector() = 0; + + /// \brief Create new projector with the given ID. A unique name + /// will automatically be assigned to the visual. If the given ID is + /// already in use, NULL will be returned. + /// \param[in] _id ID of the new projector + /// \return The created projector + /// \todo(iche033) uncomment in gz-rendering8 + /// public: virtual ProjectorPtr CreateProjector( + /// unsigned int _id) = 0; + + /// \brief Create new projector with the given name. A unique ID + /// will automatically be assigned to the visual. If the given name is + /// already in use, NULL will be returned. + /// \param[in] _name Name of the new projector + /// \return The created projector + /// \todo(iche033) uncomment in gz-rendering8 + /// public: virtual ProjectorPtr CreateProjector( + /// const std::string &_name) = 0; + + /// \brief Create new projector with the given name. If either the + /// given ID or name is already in use, NULL will be returned. + /// \param[in] _id ID of the new projector + /// \param[in] _name Name of the new projector + /// \return The created projector + /// \todo(iche033) uncomment in gz-rendering8 + /// public: virtual ProjectorPtr CreateProjector( + /// unsigned int _id, const std::string &_name) = 0; + /// \endcond + /// \brief Enable sky in the scene. /// \param[in] _enabled True to enable sky public: virtual void SetSkyEnabled(bool _enabled) = 0; @@ -1306,6 +1344,14 @@ namespace gz /// use of this scene after its destruction will result in undefined /// behavior. public: virtual void Destroy() = 0; + + /// \brief Get scene extention APIs + /// This provides new Scene APIs that are experimental + public: SceneExt *Extension() const; + + /// \brief Set the scene extention API + /// This is called by underlying render engines + protected: void SetExtension(SceneExt *_ext); }; } } diff --git a/include/gz/rendering/base/BaseProjector.hh b/include/gz/rendering/base/BaseProjector.hh new file mode 100644 index 000000000..3d990a5fd --- /dev/null +++ b/include/gz/rendering/base/BaseProjector.hh @@ -0,0 +1,180 @@ +/* + * 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_RENDERING_BASE_BASEPROJECTOR_HH_ +#define GZ_RENDERING_BASE_BASEPROJECTOR_HH_ + +#include +#include "gz/rendering/base/BaseScene.hh" +#include "gz/rendering/base/BaseNode.hh" +#include "gz/rendering/Projector.hh" + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + // + /* \class BaseProjector BaseProjector.hh \ + * gz/rendering/base/BaseProjector.hh + */ + /// \brief A base implementation of the Projector class + template + class BaseProjector : + public virtual Projector, + public virtual T + { + /// \brief Constructor + protected: BaseProjector(); + + /// \brief Destructor + public: virtual ~BaseProjector(); + + // Documentation inherited + public: virtual double FarClipPlane() const override; + + // Documentation inherited + public: virtual void SetFarClipPlane(double _far) override; + + // Documentation inherited + public: virtual double NearClipPlane() const override; + + // Documentation inherited + public: virtual void SetNearClipPlane(double _near) override; + + // Documentation inherited + public: virtual math::Angle HFOV() const override; + + // Documentation inherited + public: virtual void SetHFOV(const math::Angle &_hfov) override; + + // Documentation inherited + public: virtual std::string Texture() const override; + + // Documentation inherited + public: void SetTexture(const std::string &_texture) override; + + // Documentation inherited + public: bool IsEnabled() const override; + + // Documentation inherited + public: void SetEnabled(bool _enabled) override; + + /// \brief Projector's near clip plane + protected: double nearClip = 0.1; + + /// \brief Projector's far clip plane + protected: double farClip = 10.0; + + /// \brief Projector's horizontal field of view + protected: math::Angle hfov = math::Angle(0.785398); + + /// \brief Texture used by the projector + protected: std::string textureName; + + /// \brief Indicates whether or not the projector is enabled + protected: bool enabled = false; + + /// \brief Only the scene can create a particle emitter + private: friend class BaseScene; + }; + + ////////////////////////////////////////////////// + template + BaseProjector::BaseProjector() + { + } + + ////////////////////////////////////////////////// + template + BaseProjector::~BaseProjector() + { + } + + ///////////////////////////////////////////////// + template + double BaseProjector::FarClipPlane() const + { + return this->farClip; + } + + ///////////////////////////////////////////////// + template + void BaseProjector::SetFarClipPlane(double _far) + { + this->farClip = _far; + } + + ///////////////////////////////////////////////// + template + double BaseProjector::NearClipPlane() const + { + return this->nearClip; + } + + ///////////////////////////////////////////////// + template + void BaseProjector::SetNearClipPlane(double _near) + { + this->nearClip = _near; + } + + ///////////////////////////////////////////////// + template + math::Angle BaseProjector::HFOV() const + { + return this->hfov; + } + + ///////////////////////////////////////////////// + template + void BaseProjector::SetHFOV(const math::Angle &_hfov) + { + this->hfov = _hfov; + } + + ///////////////////////////////////////////////// + template + std::string BaseProjector::Texture() const + { + return this->textureName; + } + + ///////////////////////////////////////////////// + template + void BaseProjector::SetTexture(const std::string &_texture) + { + this->textureName = _texture; + } + + ///////////////////////////////////////////////// + template + bool BaseProjector::IsEnabled() const + { + return this->enabled; + } + + ///////////////////////////////////////////////// + template + void BaseProjector::SetEnabled(bool _enabled) + { + this->enabled = _enabled; + } + } + } +} +#endif diff --git a/include/gz/rendering/base/BaseScene.hh b/include/gz/rendering/base/BaseScene.hh index ea7922efc..9a89837bd 100644 --- a/include/gz/rendering/base/BaseScene.hh +++ b/include/gz/rendering/base/BaseScene.hh @@ -575,6 +575,25 @@ namespace gz public: virtual ParticleEmitterPtr CreateParticleEmitter( unsigned int _id, const std::string &_name) override; + // Documentation inherited. + // \todo(iche033) uncomment in gz-rendering8 + // public: virtual ProjectorPtr CreateProjector() override; + + // Documentation inherited. + // \todo(iche033) uncomment in gz-rendering8 + // public: virtual ProjectorPtr CreateProjector(unsigned int _id) + // override; + + // Documentation inherited. + // \todo(iche033) uncomment in gz-rendering8 + // public: virtual ProjectorPtr CreateProjector( + // const std::string &_name) override; + + // Documentation inherited. + // \todo(iche033) uncomment in gz-rendering8 + // public: virtual ProjectorPtr CreateProjector( + // unsigned int _id, const std::string &_name) override; + // Documentation inherited. public: virtual void SetSkyEnabled(bool _enabled) override; @@ -823,6 +842,21 @@ namespace gz return ParticleEmitterPtr(); } + /// \brief Implementation for creating a Projector. + /// \param[in] _id Unique id. + /// \param[in] _name Name of Projector. + /// \return Pointer to the created projector + // \todo(iche033) uncomment in gz-rendering8 + // protected: virtual ProjectorPtr CreateProjectorImpl( + // unsigned int _id, const std::string &_name) + // { + // (void)_id; + // (void)_name; + // gzerr << "Projector not supported by: " + // << this->Engine()->Name() << std::endl; + // return ProjectorPtr(); + // } + protected: virtual LightStorePtr Lights() const = 0; protected: virtual SensorStorePtr Sensors() const = 0; diff --git a/include/gz/rendering/base/SceneExt.hh b/include/gz/rendering/base/SceneExt.hh new file mode 100644 index 000000000..7450bf46a --- /dev/null +++ b/include/gz/rendering/base/SceneExt.hh @@ -0,0 +1,64 @@ +/* + * 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_RENDERING_SCENEEXT_HH_ +#define GZ_RENDERING_SCENEEXT_HH_ + +#include + +#include "gz/rendering/config.hh" +#include "gz/rendering/RenderTypes.hh" +#include "gz/rendering/Scene.hh" +#include "gz/rendering/Export.hh" + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + // + /// \brief Scene Extension class. Provides API extension to the Scene + /// class without breaking ABI. + class GZ_RENDERING_VISIBLE SceneExt + { + /// \brief Constructor + /// \param[in] _scene Pointer to scene + public: SceneExt(Scene *_scene) + { + this->scene = _scene; + } + + /// \brief Destructor + public: ~SceneExt() = default; + + /// \brief Generic create function + /// \param[in] _type Type of object to create + /// \param[in] _name Name of object + public: virtual ObjectPtr CreateExt(const std::string & _type, + const std::string & _name = "") + { + (void)_type; + (void)_name; + return ObjectPtr(); + } + + /// \brief Pointer to scene + protected: Scene *scene{nullptr}; + }; + } + } +} +#endif diff --git a/ogre/include/gz/rendering/ogre/OgreProjector.hh b/ogre/include/gz/rendering/ogre/OgreProjector.hh new file mode 100644 index 000000000..b91938ce1 --- /dev/null +++ b/ogre/include/gz/rendering/ogre/OgreProjector.hh @@ -0,0 +1,67 @@ +/* + * 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_RENDERING_OGRE_OGREPROJECTOR_HH_ +#define GZ_RENDERING_OGRE_OGREPROJECTOR_HH_ + + +#include + +#include "gz/rendering/config.hh" + +#include "gz/rendering/base/BaseProjector.hh" +#include "gz/rendering/ogre/Export.hh" +#include "gz/rendering/ogre/OgreVisual.hh" + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + + /// \brief Ogre 1.x implementation of a Projector class + class GZ_RENDERING_OGRE_VISIBLE OgreProjector : + public BaseProjector + { + /// \brief Constructor. + protected: OgreProjector(); + + /// \brief Destructor. + public: virtual ~OgreProjector(); + + // Documentation inherited. + public: virtual void PreRender() override; + + // Documentation inherited. + public: virtual void SetEnabled(bool _enabled) override; + + /// \brief Update the list of cameras that the listener is added to. + /// It loops through all cameras in each iteration to make sure + /// we handle the decal's visibility in each of these cameras' view + private: void UpdateCameraListener(); + + /// \brief Only the ogre scene can instanstiate this class + private: friend class OgreScene; + + /// \cond warning + /// \brief Private data pointer + GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) + /// \endcond + }; + } + } +} +#endif diff --git a/ogre/include/gz/rendering/ogre/OgreRenderTypes.hh b/ogre/include/gz/rendering/ogre/OgreRenderTypes.hh index 9f9005b6c..06fabd6f8 100644 --- a/ogre/include/gz/rendering/ogre/OgreRenderTypes.hh +++ b/ogre/include/gz/rendering/ogre/OgreRenderTypes.hh @@ -53,6 +53,7 @@ namespace gz class OgreObjectInterface; class OgreParticleEmitter; class OgrePointLight; + class OgreProjector; class OgreRayQuery; class OgreRenderEngine; class OgreRenderTarget; @@ -109,6 +110,7 @@ namespace gz typedef shared_ptr OgreObjectInterfacePtr; typedef shared_ptr OgreParticleEmitterPtr; typedef shared_ptr OgrePointLightPtr; + typedef shared_ptr OgreProjectorPtr; typedef shared_ptr OgreRayQueryPtr; typedef shared_ptr OgreRenderEnginePtr; typedef shared_ptr OgreRenderTargetPtr; diff --git a/ogre/include/gz/rendering/ogre/OgreScene.hh b/ogre/include/gz/rendering/ogre/OgreScene.hh index cdd9e9ed5..a88b3e0dd 100644 --- a/ogre/include/gz/rendering/ogre/OgreScene.hh +++ b/ogre/include/gz/rendering/ogre/OgreScene.hh @@ -205,6 +205,11 @@ namespace gz protected: virtual ParticleEmitterPtr CreateParticleEmitterImpl( unsigned int _id, const std::string &_name) override; + // Documentation inherited + // \todo(iche033) make this virtual in gz-rendering8 + protected: ProjectorPtr CreateProjectorImpl( + unsigned int _id, const std::string &_name); + protected: virtual bool InitObject(OgreObjectPtr _object, unsigned int _id, const std::string &_name); @@ -216,6 +221,9 @@ namespace gz protected: virtual MaterialMapPtr Materials() const override; + // Documentation inherited + protected: unsigned int CreateObjectId() override; + /// \brief Remove internal material cache for a specific material /// \param[in] _name Name of the template material to remove. public: void ClearMaterialsCache(const std::string &_name); @@ -247,6 +255,19 @@ namespace gz protected: Ogre::SceneManager *ogreSceneManager; private: friend class OgreRenderEngine; + private: friend class OgreSceneExt; + }; + + /// \brief Ogre implementation of the scene extension API + class OgreSceneExt : public SceneExt + { + /// \brief Constructor + /// \param[in] _scene Pointer to scene + public: OgreSceneExt(Scene *_scene); + + // Documentation inherited + public: virtual ObjectPtr CreateExt(const std::string &_type, + const std::string &_name = "") override; }; } } diff --git a/ogre/src/OgreProjector.cc b/ogre/src/OgreProjector.cc new file mode 100644 index 000000000..6744428dc --- /dev/null +++ b/ogre/src/OgreProjector.cc @@ -0,0 +1,743 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gz/rendering/ogre/OgreCamera.hh" +#include "gz/rendering/ogre/OgreConversions.hh" +#include "gz/rendering/ogre/OgreDepthCamera.hh" +#include "gz/rendering/ogre/OgreProjector.hh" +#include "gz/rendering/ogre/OgreRTShaderSystem.hh" +#include "gz/rendering/ogre/OgreScene.hh" +#include "gz/rendering/Utils.hh" + +using namespace gz; +using namespace rendering; + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + // + /// \brief Projector listener, used to add a new decal material pass + /// onto other entities' materials + class OgreProjectorListener + : public Ogre::RenderTargetListener, Ogre::MaterialManager::Listener + { + /// \brief Constructor. + public: OgreProjectorListener(); + + /// \brief Destructor. + public: virtual ~OgreProjectorListener(); + + //// \brief Initialize the projector listener + /// \param[in] _parent Parent node to attach the frustum to + /// \param[in] _textureName Name of projection texture + /// \param[in] _near Near clip plane + /// \param[in] _far Far clip plane + /// \param[in] _hfov Horizontal FOV + public: void Init(Ogre::SceneNode *_parent, + const std::string &_textureName, + double _near = 0.5, + double _far = 10, + const math::Angle &_hfov = math::Angle(0.785398163)); + + /// \brief Destroy the projector listener by cleaning up resources + public: void Destroy(); + + /// \brief Set whether to enable the projector + /// \param[in] _enabled True to enable projector, false to disable + public: void SetEnabled(bool _enabled); + + /// \brief Add decal to materials of entity visible in the frustum + public: void AddDecalToVisibleMaterials(); + + /// \brief Remove decal from materials of entities + public: void RemoveDecalFromMaterials(); + + /// \brief Find a list of materials visible in the frustum + /// and update the visibleMaterials variable. This function is used + /// when decal has visibility flags + public: void UpdateVisibleMaterials(); + + /// \brief Find a list of materials visible in the frustum + private: std::unordered_set FindVisibleMaterials(); + + /// \brief Remove decal from materials of entities that are no longer + /// visible + /// \param[in] _matSet A set of materials + private: void RemoveDecalFromInvisibleMaterials( + std::unordered_set& _matSet); + + //// \brief Set the visibility flags for this projector + /// \param[in] _flags Visibility flags to set + public: void SetVisibilityFlags(uint32_t _flags); + + /// \brief Set texture to use for projection + /// \param[in] _textureName Name of texture + private: void SetTexture(const std::string &_textureName); + + /// \brief Create the frustum scene nodes + private: void CreateSceneNode(); + + /// \brief Set the frustum near and far clip planes + /// \param[in] _near Near clip plane + /// \param[in] _far Far clip plane + private: void SetFrustumClipDistance(double _near, double _far); + + /// \brief Add decal to a set of entity materials. + /// \param[in] _matSet A set of material names + private: void AddDecalToMaterials( + std::unordered_set &_matSet); + + /// \brief Add decal to an entity's material. + /// \param[in] _matSet Name of material + private: void AddDecalToMaterial(const std::string &_matName); + + /// \brief Remove decal from an entity material + /// \param[in] _matSet Name of material + private: void RemoveDecalFromMaterial(const std::string &_matName); + + /// \brief Ogre's pre render update callback + /// \param[in] _evt Ogre render target event containing information about + /// the source render target. + private: virtual void preRenderTargetUpdate( + const Ogre::RenderTargetEvent &_evt); + + /// \brief Ogre's post render update callback + /// \param[in] _evt Ogre render target event containing information about + /// the source render target. + private: virtual void postRenderTargetUpdate( + const Ogre::RenderTargetEvent &_evt); + + // Documentation inherited + public: virtual Ogre::Technique *handleSchemeNotFound( + uint16_t _schemeIndex, const Ogre::String &_schemeName, + Ogre::Material *_originalMaterial, uint16_t _lodIndex, + const Ogre::Renderable *_rend); + + /// \brief Enabled state of projector listener + public: bool enabled{false}; + + /// \brief Indicates whether the projector listener is + /// initialized or not + public: bool initialized{false}; + + /// \brief Name of node that frustum is attached to + public: std::string nodeName; + + /// \brief Name of node that frustum filter is attached to + public: std::string filterNodeName; + + /// \brief Texture being projected + public: std::string textureName; + + /// \brief Projection frustum + public: std::unique_ptr frustum; + + /// \brief Projection frustum for removing backface projection + public: std::unique_ptr filterFrustum; + + /// \brief Query used to find objects visible in the projector frustum + public: Ogre::PlaneBoundedVolumeListSceneQuery *projectorQuery{nullptr}; + + /// \brief Parent of frustum node + public: Ogre::SceneNode *parentOgreNode{nullptr}; + + /// \brief Frustum node + public: Ogre::SceneNode *node{nullptr}; + + /// \brief Frustum filter node + public: Ogre::SceneNode *filterNode{nullptr}; + + /// \brief Ogre scene manager + public: Ogre::SceneManager *sceneMgr{nullptr}; + + /// \brief A map of targets that has decal texture projected + /// onto. Key value pairs are: + public: std::unordered_map projectorTargets; + + /// \brief Projector's visibility flags + private: uint32_t visibilityFlags = 0u; + + /// \brief Name of the default material scheme. We restore the camera + /// viewports to this default once the camera is done rendering. + /// Used when decal has custom visibility flags + private: std::string defaultScheme; + + /// \brief A clone of materials for projecting texture onto. Used when + /// decal has custom visibility flags + private: std::unordered_map matClones; + + /// \brief A set of visible materials in the frustum. Used when decal + /// has custom visiblility flags + private: std::unordered_set visibleMaterials; + }; + } + } +} + +/// \brief Private data for the OgreProjector class +class gz::rendering::OgreProjector::Implementation +{ + /// \brief The projection frame listener. + public: OgreProjectorListener projector; + + /// \brief Indicate whether the projector is intialized or not + public: bool initialized{false}; + + /// \brief A map of cameras () that the listener has been + /// added to + public: std::unordered_map + camerasWithListener; +}; + +///////////////////////////////////////////////// +OgreProjector::OgreProjector() + : dataPtr(utils::MakeUniqueImpl()) +{ +} + +///////////////////////////////////////////////// +OgreProjector::~OgreProjector() +{ + if (!this->Scene()->IsInitialized()) + return; + this->SetEnabled(false); + this->dataPtr->projector.Destroy(); +} + +///////////////////////////////////////////////// +void OgreProjector::PreRender() +{ + if (this->dataPtr->initialized) + { + this->UpdateCameraListener(); + return; + } + + // Initialize the projector + this->dataPtr->projector.Init(this->ogreNode, this->textureName, + this->nearClip, this->farClip, this->hfov); + + if (!this->dataPtr->projector.initialized) + { + gzwarn << "Starting projector failed." << std::endl;; + return; + } + + this->dataPtr->projector.SetEnabled(true); + + this->dataPtr->initialized = true; +} + +///////////////////////////////////////////////// +void OgreProjector::SetEnabled(bool _enabled) +{ + BaseProjector::SetEnabled(_enabled); + this->dataPtr->projector.SetEnabled(_enabled); +} + +///////////////////////////////////////////////// +OgreProjectorListener::OgreProjectorListener() +{ +} + +///////////////////////////////////////////////// +OgreProjectorListener::~OgreProjectorListener() +{ + this->Destroy(); +} + +///////////////////////////////////////////////// +void OgreProjectorListener::Init(Ogre::SceneNode *_parent, + const std::string &_textureName, double _near, double _far, + const math::Angle &_fov) +{ + if (this->initialized) + return; + + if (_textureName.empty()) + { + gzerr << "Projector is missing a texture\n"; + return; + } + + this->parentOgreNode = _parent; + + this->nodeName = this->parentOgreNode->getName() + "_Projector"; + this->filterNodeName = this->parentOgreNode->getName() + "_ProjectorFilter"; + + this->frustum = std::make_unique(); + this->filterFrustum = std::make_unique(); + this->filterFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC); + + this->sceneMgr = this->parentOgreNode->getCreator(); + this->projectorQuery = this->sceneMgr->createPlaneBoundedVolumeQuery( + Ogre::PlaneBoundedVolumeList()); + + this->CreateSceneNode(); + this->SetTexture(_textureName); + this->SetFrustumClipDistance(_near, _far); + + common::Image image(_textureName); + double aspectRatio = image.Width() / image.Height(); + const double vfov = 2.0 * atan(tan(_fov.Radian() / 2.0) + / aspectRatio); + + this->frustum->setFOVy(Ogre::Radian(vfov)); + this->filterFrustum->setFOVy(Ogre::Radian(vfov)); + + this->initialized = true; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::Destroy() +{ + this->RemoveDecalFromMaterials(); + + if (this->filterNode) + { + this->filterNode->detachObject(this->filterFrustum.get()); + this->node->removeAndDestroyChild(this->filterNodeName); + this->filterNode = nullptr; + } + + if (this->node) + { + this->node->detachObject(this->frustum.get()); + this->sceneMgr->destroySceneNode(this->node); + this->node = nullptr; + } + + this->frustum.reset(); + this->filterFrustum.reset(); + + if (this->projectorQuery) + { + this->sceneMgr->destroyQuery(this->projectorQuery); + this->projectorQuery = nullptr; + } + + this->visibleMaterials.clear(); + this->matClones.clear(); + this->initialized = false; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::SetEnabled(bool _enabled) +{ + this->enabled = _enabled; + if (!this->enabled) + this->RemoveDecalFromMaterials(); + OgreRTShaderSystem::Instance()->UpdateShaders(); +} + +///////////////////////////////////////////////// +void OgreProjectorListener::CreateSceneNode() +{ + if (this->filterNode) + { + this->filterNode->detachObject(this->filterFrustum.get()); + this->node->removeAndDestroyChild(this->filterNodeName); + this->filterNode = nullptr; + } + + if (this->node) + { + this->node->detachObject(this->frustum.get()); + this->parentOgreNode->removeAndDestroyChild(this->nodeName); + this->node = nullptr; + } + + this->node = this->parentOgreNode->createChildSceneNode( + this->nodeName); + this->node->yaw(Ogre::Degree(-90)); + this->node->roll(Ogre::Degree(-90)); + + this->filterNode = this->node->createChildSceneNode( + this->filterNodeName); + + if (this->node) + this->node->attachObject(this->frustum.get()); + + if (this->filterNode) + { + this->filterNode->attachObject(this->filterFrustum.get()); + this->filterNode->setOrientation( + Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Y)); + } +} + +///////////////////////////////////////////////// +void OgreProjectorListener::SetTexture( + const std::string &_textureName) +{ + this->textureName = _textureName; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::SetFrustumClipDistance(double _near, + double _far) +{ + this->frustum->setNearClipDistance(_near); + this->filterFrustum->setNearClipDistance(_near); + this->frustum->setFarClipDistance(_far); + this->filterFrustum->setFarClipDistance(_far); +} + +///////////////////////////////////////////////// +std::unordered_set OgreProjectorListener::FindVisibleMaterials() +{ + std::unordered_set newVisibleMaterials; + Ogre::PlaneBoundedVolumeList volumeList; + + volumeList.push_back(this->frustum->getPlaneBoundedVolume()); + + this->projectorQuery->setVolumes(volumeList); + Ogre::SceneQueryResult result = this->projectorQuery->execute(); + + // Find all visible materials + Ogre::SceneQueryResultMovableList::iterator it; + for (it = result.movables.begin(); it != result.movables.end(); ++it) + { + Ogre::Entity *entity = dynamic_cast(*it); + if (entity && !entity->getUserObjectBindings().getUserAny().isEmpty() && + entity->getUserObjectBindings().getUserAny().getType() == + typeid(unsigned int)) + { + for (unsigned int i = 0; i < entity->getNumSubEntities(); i++) + { + newVisibleMaterials.insert( + entity->getSubEntity(i)->getMaterialName()); + } + } + } + + return newVisibleMaterials; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::AddDecalToVisibleMaterials() +{ + auto newVisibleMaterials = std::move(this->FindVisibleMaterials()); + + this->AddDecalToMaterials(newVisibleMaterials); +} + +///////////////////////////////////////////////// +void OgreProjectorListener::RemoveDecalFromInvisibleMaterials( + std::unordered_set &_matSet) +{ + std::string invisibleMaterial; + std::unordered_set::iterator visibleMaterial; + + // Loop through all existing passes, removing those for materials + // not in the newlist and skipping pass creation for those in the + // newlist that have already been created + auto used = this->projectorTargets.begin(); + while (used != this->projectorTargets.end()) + { + visibleMaterial = std::find(_matSet.begin(), _matSet.end(), used->first); + + // Remove the pass if it applies to a material not in the new list + if (visibleMaterial == _matSet.end()) + { + invisibleMaterial = used->first; + ++used; + this->RemoveDecalFromMaterial(invisibleMaterial); + } + // Otherwise remove it from the list of passes to be added + else + { + _matSet.erase(used->first); + ++used; + } + } +} + +///////////////////////////////////////////////// +void OgreProjectorListener::AddDecalToMaterials( + std::unordered_set &_matSet) +{ + this->RemoveDecalFromInvisibleMaterials(_matSet); + + if (!_matSet.empty()) + { + // Add pass for new materials + while (!_matSet.empty()) + { + this->AddDecalToMaterial(*_matSet.begin()); + _matSet.erase(_matSet.begin()); + } + + OgreRTShaderSystem::Instance()->UpdateShaders(); + } +} + +///////////////////////////////////////////////// +void OgreProjectorListener::AddDecalToMaterial( + const std::string &_matName) +{ + if (this->projectorTargets.find(_matName) != this->projectorTargets.end()) + { + return; + } + + Ogre::MaterialPtr mat = static_cast( + Ogre::MaterialManager::getSingleton().getByName(_matName)); + Ogre::Pass *pass = mat->getTechnique(0)->createPass(); + + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + pass->setDepthBias(1); + pass->setLightingEnabled(false); + + if (!Ogre::ResourceGroupManager::getSingleton().resourceExists( + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + this->textureName )) + { + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + this->textureName, "FileSystem", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + + Ogre::TextureUnitState *texState = + pass->createTextureUnitState(this->textureName); + texState->setProjectiveTexturing(true, this->frustum.get()); + texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_BORDER); + texState->setTextureFiltering(Ogre::TFO_ANISOTROPIC); + texState->setTextureBorderColour(Ogre::ColourValue(0.0, 0.0, 0.0, 0.0)); + texState->setColourOperation(Ogre::LBO_ALPHA_BLEND); + + texState = pass->createTextureUnitState("projection_filter.png"); + texState->setProjectiveTexturing(true, this->filterFrustum.get()); + texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + texState->setTextureFiltering(Ogre::TFO_NONE); + + this->projectorTargets[_matName] = pass; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::RemoveDecalFromMaterials() +{ + for (auto it = this->projectorTargets.begin(); + it != this->projectorTargets.end(); ++it) + { + it->second->getParent()->removePass(it->second->getIndex()); + } + this->projectorTargets.clear(); + + OgreRTShaderSystem::Instance()->UpdateShaders(); +} + +///////////////////////////////////////////////// +void OgreProjectorListener::RemoveDecalFromMaterial( + const std::string &_matName) +{ + auto projectorTargetIt = this->projectorTargets.find(_matName); + if (projectorTargetIt != this->projectorTargets.end()) + { + projectorTargetIt->second->getParent()->removePass( + projectorTargetIt->second->getIndex()); + this->projectorTargets.erase(projectorTargetIt); + } +} + +///////////////////////////////////////////////// +void OgreProjector::UpdateCameraListener() +{ + // if projector does not have custom visibility flags + // project the texture onto entity's original material. It'll be visible + // to all cameras + if (this->VisibilityFlags() == GZ_VISIBILITY_ALL) + { + for (auto &ogreCamIt : this->dataPtr->camerasWithListener) + { + Ogre::String camName = ogreCamIt.second; + // instead of getting the camera pointer through ogreCamIt.first, + // find camera pointer again to make sure the camera still exists + // because there is a chance that we are holding onto a dangling pointer + // if that camera was deleted already + auto ogreCam = this->scene->OgreSceneManager()->getCamera(camName); + ogreCam->getViewport()->getTarget()->removeListener( + &this->dataPtr->projector); + } + this->dataPtr->camerasWithListener.clear(); + + this->dataPtr->projector.AddDecalToVisibleMaterials(); + return; + } + + // if a custom visibility flag is set, we will need to use a listener + // for toggling the visibility of the decal + // Modifying the original material directly on each projector update + // didn't seem to work. So the strategy here is to clone the + // object's material and add the projected texture onto the cloned material + // \todo(anyone) figure out if it is possible to do achieve the same + // result without cloning materials + + // Collect all materials that are visible in the frustum in current frame + this->dataPtr->projector.UpdateVisibleMaterials(); + + this->dataPtr->projector.SetVisibilityFlags(this->VisibilityFlags()); + + // loop through color cameras and add listener to toggle visibility of + // decals in these cameras + for (unsigned int i = 0; i < this->scene->SensorCount(); ++i) + { + auto sensor = this->scene->SensorByIndex(i); + OgreCameraPtr camera = std::dynamic_pointer_cast(sensor); + if (camera) + { + auto ogreCam = camera->Camera(); + if (this->dataPtr->camerasWithListener.find(ogreCam) + == this->dataPtr->camerasWithListener.end()) + { + ogreCam->getViewport()->getTarget()->addListener( + &this->dataPtr->projector); + this->dataPtr->camerasWithListener[ogreCam] = ogreCam->getName(); + } + } + else + { + // depth camera can also generate rgb output (when simulating + // RGBD cameras) + OgreDepthCameraPtr depthCamera = + std::dynamic_pointer_cast(sensor); + if (depthCamera) + { + auto ogreCam = depthCamera->Camera(); + if (this->dataPtr->camerasWithListener.find(ogreCam) + == this->dataPtr->camerasWithListener.end()) + { + ogreCam->getViewport()->getTarget()->addListener( + &this->dataPtr->projector); + this->dataPtr->camerasWithListener[ogreCam] = ogreCam->getName(); + } + } + } + } +} + +////////////////////////////////////////////////// +void OgreProjectorListener::SetVisibilityFlags(uint32_t _flags) +{ + this->visibilityFlags = _flags; +} + +///////////////////////////////////////////////// +void OgreProjectorListener::UpdateVisibleMaterials() +{ + this->visibleMaterials = + std::move(this->FindVisibleMaterials()); +} + +///////////////////////////////////////////////// +void OgreProjectorListener::preRenderTargetUpdate( + const Ogre::RenderTargetEvent &_evt) +{ + if (this->defaultScheme.empty()) + { + this->defaultScheme = + _evt.source->getViewport(0)->getMaterialScheme(); + } + + // set material scheme so that we can switch an entity's + // material to a cloned copy that has the projected texture + // for cameras that can see the projector + uint32_t mask = _evt.source->getViewport(0)->getVisibilityMask(); + if (this->visibilityFlags & mask) + { + Ogre::MaterialManager::getSingleton().addListener(this); + _evt.source->getViewport(0)->setMaterialScheme("projector"); + } +} + +///////////////////////////////////////////////// +void OgreProjectorListener::postRenderTargetUpdate( + const Ogre::RenderTargetEvent &_evt) +{ + // remove the material scheme for the camera so it does not interfere + // with other rendering operations + _evt.source->getViewport(0)->setMaterialScheme(this->defaultScheme); + Ogre::MaterialManager::getSingleton().removeListener(this); +} + +///////////////////////////////////////////////// +Ogre::Technique *OgreProjectorListener::handleSchemeNotFound( + uint16_t /*_schemeIndex*/, const Ogre::String &_schemeName, + Ogre::Material *_originalMaterial, uint16_t /*_lodIndex*/, + const Ogre::Renderable *_rend) +{ + if (_schemeName != "projector") + return nullptr; + + if (!_rend || typeid(*_rend) != typeid(Ogre::SubEntity)) + return nullptr; + + std::string projectedMaterialName = + _originalMaterial->getName() + "_" + this->nodeName; + + // check if the material for the current entity is visble in the frustum + if (this->visibleMaterials.find(_originalMaterial->getName()) + == this->visibleMaterials.end()) + { + // if the material is not visible, check to see if it was visible before + auto it = this->projectorTargets.find(projectedMaterialName); + if (it != this->projectorTargets.end()) + { + this->RemoveDecalFromMaterial(projectedMaterialName); + } + + return nullptr; + } + + // if visible check to see if we have a clone of the material already + Ogre::Material *clone = nullptr; + auto it = this->matClones.find(_originalMaterial->getName()); + if (it != this->matClones.end()) + { + clone = it->second; + // if the clnoe material is in the view, that means it has the projected + // texture already + if (this->projectorTargets.find(projectedMaterialName) != + this->projectorTargets.end()) + { + return clone->getTechnique(0u); + } + } + // if clone is not available, clone it and add the projected texture to the + // material + else + { + clone = _originalMaterial->clone(projectedMaterialName).get(); + this->matClones[_originalMaterial->getName()] = clone; + } + + this->AddDecalToMaterial(clone->getName()); + return clone->getTechnique(0u); +} diff --git a/ogre/src/OgreRenderEngine.cc b/ogre/src/OgreRenderEngine.cc index 96351ec45..9ff5c3f60 100644 --- a/ogre/src/OgreRenderEngine.cc +++ b/ogre/src/OgreRenderEngine.cc @@ -603,8 +603,8 @@ void OgreRenderEngine::CreateResources() std::make_pair(p + "/materials/programs", "General")); archNames.push_back( std::make_pair(p + "/materials/scripts", "General")); - // archNames.push_back( - // std::make_pair(prefix + "/materials/textures", "General")); + archNames.push_back( + std::make_pair(p + "/materials/textures", "General")); // archNames.push_back( // std::make_pair(prefix + "/media/models", "General")); archNames.push_back( diff --git a/ogre/src/OgreScene.cc b/ogre/src/OgreScene.cc index c44f0d49d..36531eb21 100644 --- a/ogre/src/OgreScene.cc +++ b/ogre/src/OgreScene.cc @@ -17,6 +17,8 @@ #include +#include "gz/rendering/base/SceneExt.hh" + #include "gz/rendering/ogre/OgreArrowVisual.hh" #include "gz/rendering/ogre/OgreAxisVisual.hh" #include "gz/rendering/ogre/OgreCamera.hh" @@ -38,6 +40,7 @@ #include "gz/rendering/ogre/OgreMaterial.hh" #include "gz/rendering/ogre/OgreMeshFactory.hh" #include "gz/rendering/ogre/OgreParticleEmitter.hh" +#include "gz/rendering/ogre/OgreProjector.hh" #include "gz/rendering/ogre/OgreRTShaderSystem.hh" #include "gz/rendering/ogre/OgreRayQuery.hh" #include "gz/rendering/ogre/OgreRenderEngine.hh" @@ -143,6 +146,10 @@ OgreScene::OgreScene(unsigned int _id, const std::string &_name) : this->backgroundColor = math::Color::Black; this->gradientBackgroundColor = {math::Color::Black, math::Color::Black, math::Color::Black, math::Color::Black}; + + // there should only be one scene + static OgreSceneExt ext(this); + this->SetExtension(&ext); } ////////////////////////////////////////////////// @@ -668,6 +675,15 @@ ParticleEmitterPtr OgreScene::CreateParticleEmitterImpl(unsigned int _id, return (result) ? visual : nullptr; } +////////////////////////////////////////////////// +ProjectorPtr OgreScene::CreateProjectorImpl(unsigned int _id, + const std::string &_name) +{ + OgreProjectorPtr projector(new OgreProjector); + bool result = this->InitObject(projector, _id, _name); + return (result) ? projector : nullptr; +} + ////////////////////////////////////////////////// bool OgreScene::InitObject(OgreObjectPtr _object, unsigned int _id, const std::string &_name) @@ -738,3 +754,40 @@ OgreScenePtr OgreScene::SharedThis() ScenePtr sharedBase = this->shared_from_this(); return std::dynamic_pointer_cast(sharedBase); } + +////////////////////////////////////////////////// +unsigned int OgreScene::CreateObjectId() +{ + return BaseScene::CreateObjectId(); +} + +////////////////////////////////////////////////// +OgreSceneExt::OgreSceneExt(Scene *_scene) + : SceneExt(_scene) +{ +} + +////////////////////////////////////////////////// +ObjectPtr OgreSceneExt::CreateExt(const std::string &_type, + const std::string &_name) +{ + if (_type == "projector") + { + OgreScene *ogreScene = dynamic_cast(this->scene); + unsigned int objId = ogreScene->CreateObjectId(); + std::string objName = _name; + if (objName.empty()) + { + std::stringstream ss; + ss << ogreScene->Name() << "::" << "Projector"; + ss << "(" << std::to_string(objId) << ")"; + objName = ss.str(); + } + ProjectorPtr projector = ogreScene->CreateProjectorImpl( + objId, objName); + bool result = ogreScene->Visuals()->Add(projector); + return (result) ? projector : nullptr; + } + + return ObjectPtr(); +} diff --git a/ogre/src/media/materials/CMakeLists.txt b/ogre/src/media/materials/CMakeLists.txt index 0c8754d07..fdb4fb8cd 100644 --- a/ogre/src/media/materials/CMakeLists.txt +++ b/ogre/src/media/materials/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(programs) add_subdirectory(scripts) +add_subdirectory(textures) diff --git a/ogre/src/media/materials/textures/CMakeLists.txt b/ogre/src/media/materials/textures/CMakeLists.txt new file mode 100644 index 000000000..90f0fc4de --- /dev/null +++ b/ogre/src/media/materials/textures/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB files "*.png") + +install(FILES ${files} DESTINATION ${GZ_RENDERING_RESOURCE_PATH}/ogre/media/materials/textures) diff --git a/ogre/src/media/materials/textures/projection_filter.png b/ogre/src/media/materials/textures/projection_filter.png new file mode 100644 index 000000000..8cd531c13 Binary files /dev/null and b/ogre/src/media/materials/textures/projection_filter.png differ diff --git a/ogre2/include/gz/rendering/ogre2/Ogre2Projector.hh b/ogre2/include/gz/rendering/ogre2/Ogre2Projector.hh new file mode 100644 index 000000000..46e240f57 --- /dev/null +++ b/ogre2/include/gz/rendering/ogre2/Ogre2Projector.hh @@ -0,0 +1,70 @@ +/* + * 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_RENDERING_OGRE2_OGRE2PROJECTOR_HH_ +#define GZ_RENDERING_OGRE2_OGRE2PROJECTOR_HH_ + + +#include + +#include "gz/rendering/config.hh" + +#include "gz/rendering/base/BaseProjector.hh" +#include "gz/rendering/ogre/Export.hh" +#include "gz/rendering/ogre2/Ogre2Visual.hh" + +namespace gz +{ + namespace rendering + { + inline namespace GZ_RENDERING_VERSION_NAMESPACE { + + /// \brief Ogre 2.x implementation of a Projector class. + class GZ_RENDERING_OGRE_VISIBLE Ogre2Projector : + public BaseProjector + { + /// \brief Constructor. + protected: Ogre2Projector(); + + /// \brief Destructor. + public: virtual ~Ogre2Projector(); + + // Documentation inherited. + public: virtual void PreRender() override; + + // Documentation inherited. + public: virtual void SetEnabled(bool _enabled) override; + + /// \brief Create projector resources + private: void CreateProjector(); + + /// \brief Update the list of cameras that the listener is added to. + /// It loops through all cameras in each iteration to make sure + /// we handle the decal's visibility in each of these cameras' view + private: void UpdateCameraListener(); + + /// \brief Only the ogre scene can instanstiate this class + private: friend class Ogre2Scene; + + /// \cond warning + /// \brief Private data pointer + GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) + /// \endcond + }; + } + } +} +#endif diff --git a/ogre2/include/gz/rendering/ogre2/Ogre2RenderTypes.hh b/ogre2/include/gz/rendering/ogre2/Ogre2RenderTypes.hh index 4db245ce2..19738f77c 100644 --- a/ogre2/include/gz/rendering/ogre2/Ogre2RenderTypes.hh +++ b/ogre2/include/gz/rendering/ogre2/Ogre2RenderTypes.hh @@ -54,6 +54,7 @@ namespace gz class Ogre2Object; class Ogre2ObjectInterface; class Ogre2ParticleEmitter; + class Ogre2Projector; class Ogre2PointLight; class Ogre2RayQuery; class Ogre2RenderEngine; @@ -106,6 +107,7 @@ namespace gz typedef shared_ptr Ogre2ObjectPtr; typedef shared_ptr Ogre2ObjectInterfacePtr; typedef shared_ptr Ogre2ParticleEmitterPtr; + typedef shared_ptr Ogre2ProjectorPtr; typedef shared_ptr Ogre2PointLightPtr; typedef shared_ptr Ogre2RayQueryPtr; typedef shared_ptr Ogre2RenderEnginePtr; diff --git a/ogre2/include/gz/rendering/ogre2/Ogre2Scene.hh b/ogre2/include/gz/rendering/ogre2/Ogre2Scene.hh index 36c168e83..95e914391 100644 --- a/ogre2/include/gz/rendering/ogre2/Ogre2Scene.hh +++ b/ogre2/include/gz/rendering/ogre2/Ogre2Scene.hh @@ -345,6 +345,11 @@ namespace gz protected: virtual ParticleEmitterPtr CreateParticleEmitterImpl( unsigned int _id, const std::string &_name) override; + // Documentation inherited + // \todo(iche033) make this virtual in gz-rendering8 + protected: ProjectorPtr CreateProjectorImpl( + unsigned int _id, const std::string &_name); + /// \brief Helper function to initialize an ogre2 object /// \param[in] _object Ogre2 object that will be initialized /// \param[in] _id Unique Id to assign to the object @@ -397,6 +402,9 @@ namespace gz // Documentation inherited protected: virtual MaterialMapPtr Materials() const override; + // Documentation inherited + protected: unsigned int CreateObjectId() override; + /// \brief Create the GL context private: void CreateContext(); @@ -445,7 +453,22 @@ namespace gz /// \brief Make the render engine our friend private: friend class Ogre2RenderEngine; + + private: friend class Ogre2SceneExt; }; + + /// \brief Ogre2 implementation of the scene extension API + class Ogre2SceneExt : public SceneExt + { + /// \brief Constructor + /// \param[in] _scene Pointer to scene + public: Ogre2SceneExt(Scene *_scene); + + // Documentation inherited + public: virtual ObjectPtr CreateExt(const std::string &_type, + const std::string &_name = "") override; + }; + } } } diff --git a/ogre2/src/Ogre2Material.cc b/ogre2/src/Ogre2Material.cc index 240e9164a..464158028 100644 --- a/ogre2/src/Ogre2Material.cc +++ b/ogre2/src/Ogre2Material.cc @@ -1535,6 +1535,9 @@ void Ogre2Material::SetFragmentShader(const std::string &_path) auto mat = this->Material(); auto pass = mat->getTechnique(0u)->getPass(0); + Ogre::HlmsBlendblock block; + block.setBlendType(Ogre::SBT_TRANSPARENT_ALPHA); + pass->setBlendblock(block); pass->setFragmentProgram(fragmentShader->getName()); mat->compile(); mat->load(); diff --git a/ogre2/src/Ogre2Projector.cc b/ogre2/src/Ogre2Projector.cc new file mode 100644 index 000000000..1e25281e8 --- /dev/null +++ b/ogre2/src/Ogre2Projector.cc @@ -0,0 +1,339 @@ +/* + * 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. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "gz/rendering/ogre2/Ogre2Camera.hh" +#include "gz/rendering/ogre2/Ogre2DepthCamera.hh" +#include "gz/rendering/ogre2/Ogre2Projector.hh" +#include "gz/rendering/ogre2/Ogre2RenderEngine.hh" +#include "gz/rendering/ogre2/Ogre2Scene.hh" +#include "gz/rendering/Utils.hh" + +namespace gz +{ +namespace rendering +{ +inline namespace GZ_RENDERING_VERSION_NAMESPACE { +// +/// \brief Helper class for checking visibility of projecter to a camera +class Ogre2ProjectorCameraListener: public Ogre::Camera::Listener +{ + /// \brief Constructor + /// \param[in] _decal Pointer to the ogre decal (projected texture) + public: Ogre2ProjectorCameraListener(Ogre::Decal *_decal); + + //// \brief Set the visibility flags for this projector + /// \param[in] _flags Visibility flags to set + public: void SetVisibilityFlags(uint32_t _flags); + + /// \brief Callback when a camara is about to be rendered + /// \param[in] _cam Ogre camera pointer which is about to render + private: virtual void cameraPreRenderScene( + Ogre::Camera * _cam) override; + + /// \brief Callback when a camera is finisned being rendered + /// \param[in] _cam Ogre camera pointer which has already render + private: virtual void cameraPostRenderScene( + Ogre::Camera * _cam) override; + + /// \brief Projector's visibility flags + private: uint32_t visibilityFlags = 0u; + + /// \brief Pointer to the decal ogre scene node + public: Ogre::SceneNode *decalNode{nullptr}; + + /// \brief Decal - Texture projected onto a surface + public: Ogre::Decal *decal{nullptr}; +}; +} +} +} + +using namespace gz; +using namespace rendering; + +/// \brief Private data for the Ogre2Projector class +class gz::rendering::Ogre2Projector::Implementation +{ + /// \brief The decal ogre scene node + public: Ogre::SceneNode *decalNode{nullptr}; + + /// \brief Decal diffuse texture + public: Ogre::TextureGpu *textureDiff{nullptr}; + + /// \brief Decal - Texture projected onto a surface + public: Ogre::Decal *decal{nullptr}; + + /// \brief Indicate whether the projector is intialized or not + public: bool initialized{false}; + + /// \brief A map of cameras () that the listener has been + /// added to + public: std::unordered_map + camerasWithListener; + + /// \brief Listener for togging projector visibility + /// We are using a custom listener because Ogre::Decal's setVisibilityFlags + /// does not seem to work + public: std::unique_ptr listener; +}; + +///////////////////////////////////////////////// +Ogre2Projector::Ogre2Projector() + : dataPtr(utils::MakeUniqueImpl()) +{ +} + +///////////////////////////////////////////////// +Ogre2Projector::~Ogre2Projector() +{ + this->SetEnabled(false); + + if (!this->scene->IsInitialized()) + return; + + for (const auto &ogreCamIt : this->dataPtr->camerasWithListener) + { + Ogre::IdString camName = ogreCamIt.second; + auto ogreCam = this->scene->OgreSceneManager()->findCameraNoThrow(camName); + ogreCam->removeListener(this->dataPtr->listener.get()); + } + this->dataPtr->camerasWithListener.clear(); + + if (this->dataPtr->textureDiff) + { + auto engine = Ogre2RenderEngine::Instance(); + auto ogreRoot = engine->OgreRoot(); + auto textureGpuManager = + ogreRoot->getRenderSystem()->getTextureGpuManager(); + textureGpuManager->destroyTexture(this->dataPtr->textureDiff); + this->dataPtr->textureDiff = nullptr; + } + if (this->dataPtr->decal) + { + this->scene->OgreSceneManager()->destroyDecal(this->dataPtr->decal); + this->dataPtr->decal = nullptr; + } + if (this->dataPtr->decalNode) + { + this->scene->OgreSceneManager()->destroySceneNode(this->dataPtr->decalNode); + this->dataPtr->decalNode = nullptr; + } +} + +///////////////////////////////////////////////// +void Ogre2Projector::PreRender() +{ + if (!this->dataPtr->initialized) + { + this->CreateProjector(); + this->dataPtr->initialized = true; + this->SetEnabled(true); + } + + this->UpdateCameraListener(); +} + +///////////////////////////////////////////////// +void Ogre2Projector::UpdateCameraListener() +{ + // if a custom visibility flag is set, we will need to use a listener + // for toggling the visibility of the decal + if (this->VisibilityFlags() == GZ_VISIBILITY_ALL) + { + this->dataPtr->decalNode->setVisible(true); + + for (auto &ogreCamIt : this->dataPtr->camerasWithListener) + { + Ogre::IdString camName = ogreCamIt.second; + // instead of getting the camera pointer through ogreCamIt.first, + // find camera pointer again to make sure the camera still exists + // because there is a chance that we are holding onto a dangling pointer + // if that camera was deleted already + auto ogreCam = + this->scene->OgreSceneManager()->findCameraNoThrow(camName); + ogreCam->removeListener(this->dataPtr->listener.get()); + } + this->dataPtr->camerasWithListener.clear(); + return; + } + + if (!this->dataPtr->listener) + { + this->dataPtr->listener = std::make_unique( + this->dataPtr->decal); + } + this->dataPtr->listener->SetVisibilityFlags(this->VisibilityFlags()); + this->dataPtr->decalNode->setVisible(false); + + // loop through color cameras and add listener to toggle visibility of + // decals in these cameras + for (unsigned int i = 0; i < this->scene->SensorCount(); ++i) + { + auto sensor = this->scene->SensorByIndex(i); + Ogre2CameraPtr camera = std::dynamic_pointer_cast(sensor); + if (camera) + { + auto ogreCam = camera->OgreCamera(); + if (this->dataPtr->camerasWithListener.find(ogreCam) + == this->dataPtr->camerasWithListener.end()) + { + ogreCam->addListener(this->dataPtr->listener.get()); + this->dataPtr->camerasWithListener[ogreCam] = ogreCam->getName(); + } + } + else + { + // depth camera can also generate rgb output (when simulating + // RGBD cameras) + Ogre2DepthCameraPtr depthCamera = + std::dynamic_pointer_cast(sensor); + if (depthCamera) + { + auto ogreCam = depthCamera->OgreCamera(); + if (this->dataPtr->camerasWithListener.find(ogreCam) + == this->dataPtr->camerasWithListener.end()) + { + ogreCam->addListener(this->dataPtr->listener.get()); + this->dataPtr->camerasWithListener[ogreCam] = ogreCam->getName(); + } + } + } + } +} + +///////////////////////////////////////////////// +void Ogre2Projector::CreateProjector() +{ + this->dataPtr->decalNode = this->ogreNode->createChildSceneNode(); + this->dataPtr->decalNode->roll(Ogre::Degree(90)); + + this->dataPtr->decal = this->scene->OgreSceneManager()->createDecal(); + this->dataPtr->decalNode->attachObject(this->dataPtr->decal); + + if (common::isFile(this->textureName)) + { + std::string baseName = common::basename(this->textureName); + size_t idx = this->textureName.rfind(baseName); + if (idx != std::string::npos) + { + std::string dirPath = this->textureName.substr(0, idx); + if (!dirPath.empty() && + !Ogre::ResourceGroupManager::getSingleton().resourceLocationExists( + dirPath)) + { + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + dirPath, "FileSystem", "General"); + } + } + } + else + { + gzerr << "Unable to create projector. Projector texture not found: " + << this->textureName << std::endl; + return; + } + + auto engine = Ogre2RenderEngine::Instance(); + auto ogreRoot = engine->OgreRoot(); + Ogre::TextureGpuManager *textureManager = + ogreRoot->getRenderSystem()->getTextureGpuManager(); + int decalDiffuseId = 1; + this->dataPtr->textureDiff = textureManager->createOrRetrieveTexture( + this->textureName, this->textureName + "_alias", + Ogre::GpuPageOutStrategy::Discard, + Ogre::CommonTextureTypes::Diffuse, + Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, + decalDiffuseId); + this->dataPtr->textureDiff->scheduleTransitionTo( + Ogre::GpuResidency::Resident); + + this->dataPtr->decal->setDiffuseTexture(this->dataPtr->textureDiff); + + // approximate frustum size + common::Image image(this->textureName); + const double aspectRatio = image.Width() / image.Height(); + const double vfov = 2.0 * atan(tan(this->hfov.Radian() / 2.0) + / aspectRatio); + + // ogre2 uses screen space decal. Unfortunately this is different from + // ogre 1.x. In ogre 1.x the decal projects out like a camera frustum + // but in ogre 2.x the decal is a rectangular volume. See related question in + // https://forums.ogre3d.org/viewtopic.php?t=95298 + // Here we are just computing the rectangular volume of the ogre 2.x decal + // based on projector properties. It is essentially a bounding box of the + // frustum. \todo(anyone) We should change the implementation to match + // ogre 1.x + const double depth = this->farClip - this->nearClip; + const double width = 2 * (tan(this->hfov.Radian() / 2) * this->farClip); + const double height = 2 * (tan(vfov / 2) * this->farClip); + + this->dataPtr->decalNode->setPosition( + Ogre::Vector3(this->nearClip + depth * 0.5, 0, 0)); + this->dataPtr->decalNode->setScale(width, depth, height); +} + +///////////////////////////////////////////////// +void Ogre2Projector::SetEnabled(bool _enabled) +{ + BaseProjector::SetEnabled(_enabled); + this->SetVisible(_enabled); +} + +////////////////////////////////////////////////// +void Ogre2ProjectorCameraListener::SetVisibilityFlags(uint32_t _flags) +{ + this->visibilityFlags = _flags; +} + +////////////////////////////////////////////////// +Ogre2ProjectorCameraListener::Ogre2ProjectorCameraListener( + Ogre::Decal *_decal) +{ + this->decal = _decal; + this->decalNode = _decal->getParentSceneNode();; +} + +////////////////////////////////////////////////// +void Ogre2ProjectorCameraListener::cameraPreRenderScene( + Ogre::Camera *_cam) +{ + uint32_t mask = _cam->getLastViewport()->getVisibilityMask(); + if (this->visibilityFlags & mask && this->decalNode && this->decal) + { + this->decalNode->setVisible(true); + this->decalNode->getCreator()->setDecalsDiffuse( + this->decal->getDiffuseTexture()); + } +} + +////////////////////////////////////////////////// +void Ogre2ProjectorCameraListener::cameraPostRenderScene( + Ogre::Camera * /*_cam*/) +{ + if (this->decalNode) + this->decalNode->setVisible(false); +} diff --git a/ogre2/src/Ogre2Scene.cc b/ogre2/src/Ogre2Scene.cc index ad01dc8bf..6e855896e 100644 --- a/ogre2/src/Ogre2Scene.cc +++ b/ogre2/src/Ogre2Scene.cc @@ -17,6 +17,8 @@ #include +#include "gz/rendering/base/SceneExt.hh" + #include "gz/rendering/RenderTypes.hh" #include "gz/rendering/ogre2/Ogre2ArrowVisual.hh" #include "gz/rendering/ogre2/Ogre2AxisVisual.hh" @@ -40,6 +42,7 @@ #include "gz/rendering/ogre2/Ogre2MeshFactory.hh" #include "gz/rendering/ogre2/Ogre2Node.hh" #include "gz/rendering/ogre2/Ogre2ParticleEmitter.hh" +#include "gz/rendering/ogre2/Ogre2Projector.hh" #include "gz/rendering/ogre2/Ogre2RayQuery.hh" #include "gz/rendering/ogre2/Ogre2RenderEngine.hh" #include "gz/rendering/ogre2/Ogre2RenderTarget.hh" @@ -109,6 +112,9 @@ using namespace rendering; Ogre2Scene::Ogre2Scene(unsigned int _id, const std::string &_name) : BaseScene(_id, _name), dataPtr(std::make_unique()) { + // there should only be one scene / scene ext API + static Ogre2SceneExt ext(this); + this->SetExtension(&ext); } ////////////////////////////////////////////////// @@ -1307,6 +1313,15 @@ ParticleEmitterPtr Ogre2Scene::CreateParticleEmitterImpl(unsigned int _id, return (result) ? visual : nullptr; } +////////////////////////////////////////////////// +ProjectorPtr Ogre2Scene::CreateProjectorImpl(unsigned int _id, + const std::string &_name) +{ + Ogre2ProjectorPtr projector(new Ogre2Projector); + bool result = this->InitObject(projector, _id, _name); + return (result) ? projector : nullptr; +} + ////////////////////////////////////////////////// bool Ogre2Scene::InitObject(Ogre2ObjectPtr _object, unsigned int _id, const std::string &_name) @@ -1355,7 +1370,7 @@ void Ogre2Scene::CreateContext() // this is required for non-shadow-casting point lights and // spot lights to work this->ogreSceneManager->setForwardClustered( - true, 16, 8, 24, 96, 0, 0, 1, 500); + true, 16, 8, 24, 96, 4, 0, 1, 500); } ////////////////////////////////////////////////// @@ -1453,3 +1468,40 @@ bool Ogre2Scene::SkyEnabled() const { return this->dataPtr->skyEnabled; } + +////////////////////////////////////////////////// +unsigned int Ogre2Scene::CreateObjectId() +{ + return BaseScene::CreateObjectId(); +} + +////////////////////////////////////////////////// +Ogre2SceneExt::Ogre2SceneExt(Scene *_scene) + : SceneExt(_scene) +{ +} + +////////////////////////////////////////////////// +ObjectPtr Ogre2SceneExt::CreateExt(const std::string &_type, + const std::string &_name) +{ + if (_type == "projector") + { + Ogre2Scene *ogreScene = dynamic_cast(this->scene); + unsigned int objId = ogreScene->CreateObjectId(); + std::string objName = _name; + if (objName.empty()) + { + std::stringstream ss; + ss << ogreScene->Name() << "::" << "Projector"; + ss << "(" << std::to_string(objId) << ")"; + objName = ss.str(); + } + ProjectorPtr projector = ogreScene->CreateProjectorImpl( + objId, objName); + bool result = ogreScene->Visuals()->Add(projector); + return (result) ? projector : nullptr; + } + + return ObjectPtr(); +} diff --git a/src/Projector.cc b/src/Projector.cc new file mode 100644 index 000000000..fd69c5e05 --- /dev/null +++ b/src/Projector.cc @@ -0,0 +1,25 @@ +/* + * 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. + * + */ + +#include "gz/rendering/Projector.hh" + +namespace gz::rendering +{ + +Projector::~Projector() = default; + +} // namespace gz::rendering diff --git a/src/Scene.cc b/src/Scene.cc index c45dd6d89..a5a966c16 100644 --- a/src/Scene.cc +++ b/src/Scene.cc @@ -17,9 +17,27 @@ #include "gz/rendering/Scene.hh" -namespace gz::rendering -{ +using namespace gz; +using namespace rendering; + +/// \brief Keep track of scene extensions +// added as static var here for ABI compatibility +static std::unordered_map g_sceneExtMap; +////////////////////////////////////////////////// Scene::~Scene() = default; -} // namespace gz::rendering +////////////////////////////////////////////////// +SceneExt *Scene::Extension() const +{ + auto it = g_sceneExtMap.find(this); + if (it != g_sceneExtMap.end()) + return it->second; + return nullptr; +} + +////////////////////////////////////////////////// +void Scene::SetExtension(SceneExt *_ext) +{ + g_sceneExtMap[this] = _ext; +} diff --git a/src/base/BaseScene.cc b/src/base/BaseScene.cc index 4d66a36ef..3803d127f 100644 --- a/src/base/BaseScene.cc +++ b/src/base/BaseScene.cc @@ -38,6 +38,7 @@ #include "gz/rendering/GpuRays.hh" #include "gz/rendering/Grid.hh" #include "gz/rendering/ParticleEmitter.hh" +#include "gz/rendering/Projector.hh" #include "gz/rendering/RayQuery.hh" #include "gz/rendering/RenderTarget.hh" #include "gz/rendering/Text.hh" @@ -1367,6 +1368,37 @@ ParticleEmitterPtr BaseScene::CreateParticleEmitter(unsigned int _id, return (result) ? visual : nullptr; } +// \todo(iche033) uncomment in gz-rendering8 +// ////////////////////////////////////////////////// +// ProjectorPtr BaseScene::CreateProjector() +// { +// unsigned int objId = this->CreateObjectId(); +// return this->CreateProjector(objId); +// } +// +// ////////////////////////////////////////////////// +// ProjectorPtr BaseScene::CreateProjector(unsigned int _id) +// { +// std::string objName = this->CreateObjectName(_id, "Projector"); +// return this->CreateProjector(_id, objName); +// } +// +// ////////////////////////////////////////////////// +// ProjectorPtr BaseScene::CreateProjector(const std::string &_name) +// { +// unsigned int objId = this->CreateObjectId(); +// return this->CreateProjector(objId, _name); +// } +// +// ////////////////////////////////////////////////// +// ProjectorPtr BaseScene::CreateProjector(unsigned int _id, +// const std::string &_name) +// { +// ProjectorPtr projector = this->CreateProjectorImpl(_id, _name); +// bool result = this->RegisterVisual(projector); +// return (result) ? projector : nullptr; +// } + ////////////////////////////////////////////////// void BaseScene::SetSkyEnabled(bool _enabled) // NOLINT(readability/casting) { diff --git a/test/common_test/CMakeLists.txt b/test/common_test/CMakeLists.txt index 5e0f7ff4f..42f282726 100644 --- a/test/common_test/CMakeLists.txt +++ b/test/common_test/CMakeLists.txt @@ -11,7 +11,7 @@ set(tests GaussianNoisePass_TEST GizmoVisual_TEST Grid_TEST - Heightmap_TEST + Heightmap_TEST InertiaVisual_TEST LidarVisual_TEST Light_TEST @@ -20,11 +20,12 @@ set(tests Material_TEST Mesh_TEST MeshDescriptor_TEST - MoveToHelper_TEST - Node_TEST - OrbitViewController_TEST - OrthoViewController_TEST + MoveToHelper_TEST + Node_TEST + OrbitViewController_TEST + OrthoViewController_TEST ParticleEmitter_TEST + Projector_TEST RayQuery_TEST RenderEngine_TEST RenderEngineManager_TEST diff --git a/test/common_test/Projector_TEST.cc b/test/common_test/Projector_TEST.cc new file mode 100644 index 000000000..596a7d2a5 --- /dev/null +++ b/test/common_test/Projector_TEST.cc @@ -0,0 +1,92 @@ +/* + * 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. + * +*/ + +#include +#include + +#include "CommonRenderingTest.hh" + +#include + +#include "gz/rendering/Projector.hh" +#include "gz/rendering/Scene.hh" + +using namespace gz; +using namespace rendering; + +/// \brief The test fixture. +class ProjectorTest : public CommonRenderingTest +{ + /// \brief A directory under test/ with some textures. + protected: const std::string TEST_MEDIA_PATH = + common::joinPaths(std::string(PROJECT_SOURCE_PATH), + "test", "media", "materials", "textures"); +}; + +///////////////////////////////////////////////// +TEST_F(ProjectorTest, Projector) +{ + ScenePtr scene = engine->CreateScene("scene"); + EXPECT_NE(nullptr, scene); + + // Projector and can only be accessed by the scene extension API + // in gz-rendering7 + if (!scene->Extension()) + return; + + // Create a projector + // \todo(iche033) uncomment and use official API in gz-rendering8 + // ProjectorPtr projector = scene->CreateProjector(); + ProjectorPtr projector = std::dynamic_pointer_cast( + scene->Extension()->CreateExt("projector")); + + // check default properties + EXPECT_LT(0U, projector->NearClipPlane()); + EXPECT_LT(0U, projector->FarClipPlane()); + EXPECT_LT(0U, projector->HFOV().Radian()); + EXPECT_EQ(GZ_VISIBILITY_ALL, projector->VisibilityFlags()); + EXPECT_TRUE(projector->Texture().empty()); + EXPECT_FALSE(projector->IsEnabled()); + + // test APIs + double nearClip = 1.1; + double farClip = 15.5; + math::Angle hfov(2.3); + uint32_t visibilityFlags = 0x03; + std::string texture = common::joinPaths(TEST_MEDIA_PATH, + "blue_texture.png"); + + projector->SetNearClipPlane(nearClip); + EXPECT_DOUBLE_EQ(nearClip, projector->NearClipPlane()); + + projector->SetFarClipPlane(farClip); + EXPECT_DOUBLE_EQ(farClip, projector->FarClipPlane()); + + projector->SetHFOV(hfov); + EXPECT_EQ(hfov, projector->HFOV()); + + projector->SetVisibilityFlags(visibilityFlags); + EXPECT_EQ(visibilityFlags, projector->VisibilityFlags()); + + projector->SetTexture(texture); + EXPECT_EQ(texture, projector->Texture()); + + projector->SetEnabled(true); + EXPECT_TRUE(projector->IsEnabled()); + + engine->DestroyScene(scene); +} diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 6aecf651f..5503b4198 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -7,6 +7,7 @@ set(tests gpu_rays heightmap lidar_visual + projector render_pass scene segmentation_camera diff --git a/test/integration/load_unload.cc b/test/integration/load_unload.cc index 7ed14bb8d..d3b6d4c83 100644 --- a/test/integration/load_unload.cc +++ b/test/integration/load_unload.cc @@ -54,6 +54,7 @@ TEST_F(LoadUnloadTest, GZ_UTILS_TEST_DISABLED_ON_MAC(Thread)) std::thread renderThread = std::thread(&LoadUnloadTest::RenderThread, this); EXPECT_TRUE(renderThread.joinable()); EXPECT_NO_THROW(renderThread.join()); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } diff --git a/test/integration/projector.cc b/test/integration/projector.cc new file mode 100644 index 000000000..d03a198d8 --- /dev/null +++ b/test/integration/projector.cc @@ -0,0 +1,192 @@ +/* + * 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. + * +*/ + +#include + +#include "CommonRenderingTest.hh" + +#include + +#include "gz/rendering/Camera.hh" +#include "gz/rendering/Material.hh" +#include "gz/rendering/Projector.hh" +#include "gz/rendering/Scene.hh" +#include "gz/rendering/Visual.hh" + +#include + +using namespace gz; +using namespace rendering; + +class ProjectorTest: public CommonRenderingTest +{ + // Path to test media directory + public: const std::string TEST_MEDIA_PATH = + gz::common::joinPaths(std::string(PROJECT_SOURCE_PATH), + "test", "media"); +}; + + +///////////////////////////////////////////////// +TEST_F(ProjectorTest, GZ_UTILS_TEST_DISABLED_ON_WIN32(Visibility)) +{ + ScenePtr scene = engine->CreateScene("scene"); + ASSERT_NE(nullptr, scene); + + // Projector and can only be accessed by the scene extension API + // in gz-rendering7 + if (!scene->Extension()) + return; + + scene->SetBackgroundColor(0, 0, 0); + scene->SetAmbientLight(1, 1, 1); + + VisualPtr root = scene->RootVisual(); + ASSERT_NE(nullptr, root); + + DirectionalLightPtr light0 = scene->CreateDirectionalLight(); + light0->SetDirection(0.0, 0.0, -1); + light0->SetDiffuseColor(1.0, 1.0, 1.0); + light0->SetSpecularColor(1.0, 1.0, 1.0); + root->AddChild(light0); + + CameraPtr cameraA = scene->CreateCamera(); + ASSERT_NE(nullptr, cameraA); + cameraA->SetWorldPosition(0, 0, -2); + cameraA->SetWorldRotation(0, GZ_PI / 2.0, 0); + cameraA->SetVisibilityMask(0x01); + cameraA->SetImageWidth(256); + cameraA->SetImageHeight(256); + root->AddChild(cameraA); + + CameraPtr cameraB = scene->CreateCamera(); + ASSERT_NE(nullptr, cameraB); + cameraB->SetWorldPosition(0, 0, -2); + cameraB->SetWorldRotation(0, GZ_PI / 2.0, 0); + cameraB->SetVisibilityMask(0x02); + cameraB->SetImageWidth(256); + cameraB->SetImageHeight(256); + root->AddChild(cameraB); + + // create projectors with different visibility flags + std::string textureRed = common::joinPaths( + TEST_MEDIA_PATH, "materials", "textures", + "red_texture.png"); + // \todo(iche033) uncomment and use official API in gz-rendering8 + // ProjectorPtr projectorA = scene->CreateProjector(); + ProjectorPtr projectorA = std::dynamic_pointer_cast( + scene->Extension()->CreateExt("projector")); + ASSERT_NE(nullptr, projectorA); + projectorA->SetNearClipPlane(1.0); + projectorA->SetFarClipPlane(6.0); + projectorA->SetTexture(textureRed); + projectorA->SetVisibilityFlags(0x01); + projectorA->SetWorldRotation(0, GZ_PI / 2.0, 0); + root->AddChild(projectorA); + + std::string textureBlue = common::joinPaths( + TEST_MEDIA_PATH, "materials", "textures", + "blue_texture.png"); + // \todo(iche033) uncomment and use official API in gz-rendering8 + // ProjectorPtr projectorB = scene->CreateProjector(); + ProjectorPtr projectorB = std::dynamic_pointer_cast( + scene->Extension()->CreateExt("projector")); + ASSERT_NE(nullptr, projectorB); + + projectorB->SetNearClipPlane(1.0); + projectorB->SetFarClipPlane(6.0); + projectorB->SetTexture(textureBlue); + projectorB->SetVisibilityFlags(0x02); + projectorB->SetWorldRotation(0, GZ_PI / 2.0, 0); + root->AddChild(projectorB); + + // create background wall visual for projection + VisualPtr visual = scene->CreateVisual(); + visual->AddGeometry(scene->CreateBox()); + visual->SetWorldPosition(0.0, 0.0, -5); + visual->SetLocalScale(10.0, 10.0, 1.0); + root->AddChild(visual); + + // create green material and assign to wall + MaterialPtr green = scene->CreateMaterial(); + green->SetAmbient(0.0, 1.0, 0.0); + green->SetDiffuse(0.0, 1.0, 0.0); + green->SetSpecular(0.0, 1.0, 0.0); + visual->SetMaterial(green); + + // create images to store camera data + Image imageA = cameraA->CreateImage(); + Image imageB = cameraB->CreateImage(); + + unsigned int height = cameraA->ImageHeight(); + unsigned int width = cameraA->ImageWidth(); + unsigned int bpp = PixelUtil::BytesPerPixel(cameraA->ImageFormat()); + unsigned int step = width * bpp; + + ASSERT_GT(height, 0u); + ASSERT_GT(width, 0u); + ASSERT_GT(bpp, 0u); + + // verify that cameraA only sees red texture from projector A and + // cameraB only sees texture from projector B + // ogre requires rendering a couple of frames to get correct output + unsigned int iterations = 1u; + if (engine->Name() == "ogre") + iterations = 2u; + for (unsigned int i = 0; i < iterations; ++i) + { + cameraA->Capture(imageA); + cameraB->Capture(imageB); + } + + unsigned char *dataA = imageA.Data(); + unsigned char *dataB = imageB.Data(); + + common::Image imgA; + imgA.SetFromData(dataA, width, height, common::Image::RGB_INT8); + imgA.SavePNG("imageA.png"); + + common::Image imgB; + imgB.SetFromData(dataB, width, height, common::Image::RGB_INT8); + imgB.SavePNG("imageB.png"); + + for (unsigned int i = 0; i < height; ++i) + { + for (unsigned int j = 0; j < step; j+=bpp) + { + unsigned int idx = i * step + j; + unsigned int rA = dataA[idx]; + unsigned int gA = dataA[idx+1]; + unsigned int bA = dataA[idx+2]; + + // color should be predominantly red + EXPECT_GT(rA, gA); + EXPECT_GT(rA, bA); + + unsigned int rB = dataB[idx]; + unsigned int gB = dataB[idx+1]; + unsigned int bB = dataB[idx+2]; + + // color should be predominantly blue + EXPECT_GT(bB, gB); + EXPECT_GT(bB, rB); + } + } + + // Clean up + engine->DestroyScene(scene); +} diff --git a/test/media/materials/textures/red_texture.png b/test/media/materials/textures/red_texture.png new file mode 100644 index 000000000..de4f07841 Binary files /dev/null and b/test/media/materials/textures/red_texture.png differ